QT项目——音乐播放器

前言

完整代码:音乐播放器完整代码

一、项目介绍

本项目主要涉及 Qt 界面设计、文件与目录操作、图像处理与绘制、多媒体播放功能、Qt 信号与槽等的应用,通过扫描指定目录下的 MP3 歌曲文件以及对应的 LRC 歌词文件,将歌曲添加到播放列表显示,进行歌曲的操作,包括曲目切换、循环播放、播放/暂停等操作,同时通过图像处理技术,实现专辑封面的加载、缩放以及添加圆角效果等展示功能,增强界面交互性与视觉体验。

界面展示:

音乐播放器演示.gif

二、项目基础知识

1. QPixmap 类

QPixmap类是一种离屏图像表示形式,可用作绘图设备。

与部件配合显示QPixmap 是为在屏幕上高效显示图像而设计的。它可以很方便地与 QLabelQAbstractButton 的子类(如 QPushButtonQToolButton)一起使用来展示图像。例如,对于 QLabel,你可以通过设置其 pixmap 属性来显示图像;对于 QAbstractButton,可利用其 icon 属性(本质上也是基于 QPixmap)来显示图标。

绘图设备角色:作为一种绘图设备,QPixmap 能够接收 QPainter 的绘制操作。这意味着你可以使用 QPainterQPixmap 对象上绘制各种图形、线条、文字等。

隐式数据共享QPixmap 类使用隐式数据共享机制。这一特性使得它可以按值传递,在传递过程中如果数据没有被修改,多个 QPixmap 对象可以共享同一份图像数据。这不仅节省了内存空间,还提高了数据传递的效率。例如,在一个函数中传递 QPixmap 参数时,只要没有对其进行修改,就不会产生数据的复制开销。

数据存储与系统交互:其像素数据由底层窗口系统管理,这使得它能够很好地与操作系统的图形处理功能相结合。例如,在不同的操作系统(如 WindowsLinuxmacOS)下,QPixmap 能够利用系统的图形库来高效地存储和显示图像。

获取和变换信息:提供了一系列函数来获取有关 pixmap 的各种信息,如尺寸、深度等。同时,还有一些函数可以对 pixmap 进行变换,比如缩放、旋转、裁剪等操作。

QImage 转换:可以和 QImage 相互转换。通常可以先用 QImage 加载图像文件,对图像数据进行操作(如修改像素值、调整颜色等),然后再转换为 QPixmap 进行屏幕显示。

2. QDir 类

QDir类用于提供对目录结构及其内容的访问。

QDir可用于操作路径名称、获取有关路径和文件的信息,以及操作底层文件系统。它还能用于访问 Qt 的资源系统。

Qt 使用 “/” 作为通用的目录分隔符,就如同在统一资源定位符(URL)中 “/” 被用作路径分隔符那样。如果始终使用 “/” 作为目录分隔符,Qt 会将你的路径转换为符合底层操作系统要求的格式。

QDir可以使用相对路径或绝对路径指向一个文件。绝对路径以目录分隔符开头(在 Windows 系统下,可选择地在前面加上驱动器标识)。相对文件名以目录名或文件名开头,它指定的路径是相对于当前目录而言的。

绝对路径示例如下:

QDir("/home/user/Documents")
QDir("C:/Documents and Settings")

在 Windows 系统中,当使用上面第二个示例去访问文件时,它将会被转换为 “C:\Documents and Settings”。

相对路径示例如下:

QDir("images/landscape.png")

可以使用isRelative()isAbsolute()函数来检查一个QDir使用的是相对路径还是绝对路径。调用makeAbsolute()函数可以将一个相对的QDir转换为绝对路径形式。

3. QMediaPlayer 类

QMediaPlayer类用于播放媒体资源。

多种媒体格式支持QMediaPlayer 是一个高级别的媒体播放类,能够播放多种类型的媒体内容,包括但不限于歌曲(如 MP3、WAV 等音频格式)、电影(如 MP4、AVI 等视频格式)以及网络电台等,它提供了一个统一的接口来处理不同类型的媒体。

本地和网络媒体播放:可以播放本地文件系统中的媒体文件,通过QUrl::fromLocalFile将本地文件路径转换为QUrl对象后传递给setMedia函数来指定播放内容。同时,也能够播放网络媒体资源,直接将网络媒体的QUrl(如网络视频或网络电台的链接)传递给setMedia函数即可。

与播放列表结合(QMediaPlaylist:可以与 QMediaPlaylist 类协同工作,实现播放列表功能。QMediaPlaylist 用于管理多个媒体资源,通过addMedia函数向播放列表中添加媒体文件,然后将播放列表关联到 QMediaPlayer(使用setPlaylist函数),这样 QMediaPlayer 就可以按照播放列表的顺序来播放媒体内容。

视频渲染(QVideoWidget:在播放视频内容时,与 QVideoWidget 配合使用来实现视频的渲染和显示。QVideoWidget 作为视频输出部件,通过setVideoOutput函数将其与 QMediaPlayer 关联起来,使得视频内容能够在指定的 QVideoWidget 部件上显示。

播放控制操作:提供了基本的播放控制方法,如playpausestop等,用于启动、暂停或者停止媒体播放。

播放状态信号通知:利用信号与槽机制,QMediaPlayer 会发出各种信号来通知应用程序播放状态的变化。例如,positionChanged信号在播放位置改变时发出,stateChanged信号在播放状态(如播放、暂停、停止)改变时发出。

元数据访问信号通知:作为QMediaObject的子类,还可以访问当前播放媒体的元数据。通过metaData函数以及预定义的元数据键来获取诸如媒体标题、艺术家名字、专辑封面等信息,并且在元数据可用改变时也会发出相应信号,方便开发者根据这些信息来丰富用户界面的展示内容,如在音乐播放器中显示歌曲的标题和艺术家信息。

4. QMediaPlaylist 类

QMediaPlaylist类用于提供一个待播放的媒体内容列表。

QMediaPlaylist类旨在与其他媒体对象(例如QMediaPlayer)配合使用。它能够在服务本身具备播放列表功能时访问该功能,如果没有,则提供基于本地内存的播放列表实现方式。

以下是一段示例代码及相关解释:

// 创建一个QMediaPlaylist对象实例
playlist = new QMediaPlaylist;

// 向播放列表中添加媒体内容,这里添加的是三个网络视频文件的URL地址
playlist->addMedia(QUrl("http://example.com/movie1.mp4"));
playlist->addMedia(QUrl("http://example.com/movie2.mp4"));
playlist->addMedia(QUrl("http://example.com/movie3.mp4"));

// 设置当前播放项的索引为1,意味着将从列表中的第二个媒体内容(索引从0开始计数)开始播放
playlist->setCurrentIndex(1);

// 创建一个QMediaPlayer对象实例
player = new QMediaPlayer;
// 将前面创建的播放列表关联到QMediaPlayer上,这样播放器就可以按照播放列表中的顺序来播放媒体内容了
player->setPlaylist(playlist);

// 创建一个QVideoWidget对象实例,用于视频渲染显示
videoWidget = new QVideoWidget;
// 将QVideoWidget设置为QMediaPlayer的视频输出部件,以便视频能在该部件上显示出来
player->setVideoOutput(videoWidget);
// 显示QVideoWidget部件,使其在界面上可见
videoWidget->show();

// 启动播放操作,此时QMediaPlayer会按照QMediaPlaylist所设定的顺序播放媒体内容,并通过QVideoWidget进行视频渲染展示
player->play();

三、代码框架

首先在 QT 项目 .pro 文件中添加多媒体模块:

QT += core gui multimedia

image.png
image.png

1. 媒体播放器初始化

初始化媒体播放器,遍历文件夹中的歌曲,显示在界面上。

代码示例:

/* 媒体信息结构体 */
struct MediaObjectInfo {
    // 用于保存歌曲文件名
    QString fileName;
    // 用于保存歌曲文件路径
    QString filePath;
    // 歌词文件路径
    QString lyricsPath;
};

private:
    /* 媒体播放器,用于播放音乐 */
    QMediaPlayer *musicPlayer;
    /* 媒体列表 */
    QMediaPlaylist *mediaPlaylist;
    /* 媒体信息存储 */
    QVector<MediaObjectInfo> mediaObjectInfo;
    
    /* 媒体播放器类初始化 */
    void mediaPlayerInit();
    /* 扫描歌曲 */
    void scanSongs();

Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    /* 媒体播放器初始化 */
    mediaPlayerInit();
    /* 扫描歌曲 */
    scanSongs();
}

// 初始化媒体播放器
void Widget::mediaPlayerInit()
{
    musicPlayer = new QMediaPlayer(this);
    mediaPlaylist = new QMediaPlaylist(this);

    /* 确保列表是空的 */
    mediaPlaylist->clear();

    /* 设置音乐播放器的列表为mediaPlaylist */
    musicPlayer->setPlaylist(mediaPlaylist);

    /* 设置播放模式,Loop是列循环 */
    mediaPlaylist->setPlaybackMode(QMediaPlaylist::Loop);
}

// 扫描歌曲目录,将歌曲添加到媒体列表中,并为有歌词的歌曲保存歌词路径
void Widget::scanSongs()
{
    // 歌曲目录
    QDir dir(QCoreApplication::applicationDirPath()
             + "/myMusic");
    // 获取歌曲目录绝对路径
    QDir dirbsolutePath(dir.absolutePath());
    qDebug() << dirbsolutePath.path() <<endl;

    /* 如果目录存在 */
    if (dirbsolutePath.exists())
    {
        /* 定义过滤器 */
        QStringList filter;
        /* 包含所有.mp3后缀的文件 */
        filter << "*.mp3";
        /* 获取该目录下的所有文件 */
        QFileInfoList files =
                dirbsolutePath.entryInfoList(filter, QDir::Files);

        // 歌词映射:歌曲文件名 -> 歌词文件完整路径
        QHash<QString, QString> songToLyricsMap;

        // 添加歌词文件的搜索
        filter.clear(); // 清空过滤器
        filter << "*.lrc";
        QFileInfoList lrcFiles = dirbsolutePath.entryInfoList(filter, QDir::Files);

        for (const QFileInfo &lrcFileInfo : lrcFiles) {
            QString baseName = lrcFileInfo.completeBaseName(); // 完整的基础文件名,不含扩展名
            songToLyricsMap.insert(baseName, lrcFileInfo.absoluteFilePath());
        }

        /* 遍历 */
        for (int i = 0; i < files.count(); i++) {
            MediaObjectInfo info;
            QString baseName = files.at(i).completeBaseName();
            QString fileName = baseName;

            info.fileName = fileName + "\n"
                    + fileName.split("-").at(1);
            info.filePath = QString::fromUtf8(files.at(i)
                                              .filePath()
                                              .toUtf8()
                                              .data());
            // 获取歌词路径
            QString lyricsPath = songToLyricsMap.value(baseName);
            /* 媒体列表添加歌曲 */
            if (mediaPlaylist->addMedia(
                        QUrl::fromLocalFile(info.filePath))) {
                // 如果歌词文件存在,则保存其路径
                if (!lyricsPath.isEmpty()) {
                    info.lyricsPath = lyricsPath;
                }
                /* 添加到容器数组里储存 */
                mediaObjectInfo.append(info);
                /* 添加歌曲名字至列表 */
                ui->listWidget->addItem(info.fileName);
            } else {
                qDebug()<<
                           mediaPlaylist->errorString()
                           .toUtf8().data()
                        << endl;
                qDebug()<< "  Error number:"
                         << mediaPlaylist->error()
                         << endl;
            }
        }
    }
    else
        qDebug() << "dir is no exists" <<endl;
}

编译运行结果:

image.png

2. 点击列表歌曲播放

代码示例:

Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    /* 媒体信号槽连接 */
    /* 1. 列表项点击信号,播放和切换歌曲 */
    connect(ui->listWidget, SIGNAL(itemClicked(QListWidgetItem*)), this, SLOT(listWidgetCliked(QListWidgetItem*)));
}

// 列表项点击事件处理
void Widget::on_listWidget_itemClicked(QListWidgetItem *item)
{
    musicPlayer->stop();
    mediaPlaylist->setCurrentIndex(ui->listWidget->row(item));
    emit ui->btn_play->clicked(true);
}

// 播放/暂停按钮点击事件处理
void Widget::on_btn_play_clicked(bool checked)
{
    int state = musicPlayer->state();

    switch (state) {
    case QMediaPlayer::StoppedState:
        /* 媒体播放 */
        musicPlayer->play();
        break;
    case QMediaPlayer::PlayingState:
        /* 媒体暂停 */
        musicPlayer->pause();
        break;
    case QMediaPlayer::PausedState:
        musicPlayer->play();
        break;
    }
}

编译运行结果:

image.png

3. 通过播放状态修改图标

代码示例:

Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    /* 媒体信号槽连接 */
    /* 2. 状态改变信号,修改图标 */
    connect(musicPlayer, SIGNAL(stateChanged(QMediaPlayer::State)), this, SLOT(mediaPlayerStateChanged(QMediaPlayer::State)));
}

// 媒体播放器状态改变时的槽函数
void Widget::mediaPlayerStateChanged(QMediaPlayer::State state)
{
    switch (state) {
    case QMediaPlayer::StoppedState: // 停止
        ui->btn_play->setChecked(false);
        break;

    case QMediaPlayer::PlayingState: // 播放
        ui->btn_play->setChecked(true);
        break;

    case QMediaPlayer::PausedState: // 暂停
        ui->btn_play->setChecked(false);
        break;
    }
}

编译运行结果:点击播放歌曲时,图标从暂停变为播放

image.png

4. 上下首歌曲切换

代码示例:

Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    /* 媒体信号槽连接 */
    /* 3. 媒体播放列表当前索引改变信号(上一首下一首图标显示) */
    connect(mediaPlaylist, &QMediaPlaylist::currentIndexChanged, this, &Widget::mediaPlaylistCurrentIndexChanged);
}

// 下一曲按钮点击事件处理
void Widget::on_btn_next_clicked()
{
    musicPlayer->stop();
    int count = mediaPlaylist->mediaCount();
    if (0 == count) {
        qDebug() << "mediaPlaylist->mediaCount = 0" << endl;
        return;
    }

    /* 列表下一个 */
    mediaPlaylist->next();
    emit ui->btn_play->clicked(true);
}

// 媒体播放列表当前索引改变时的槽函数
void Widget::mediaPlaylistCurrentIndexChanged(int index)
{
    if (-1 == index)
        return;

    /* 设置列表正在播放的项 */
    ui->listWidget->setCurrentRow(index);
}

编译运行结果:

动画.gif

5. 显示播放进度

通过水平滑动条设置时间和播放进度。

代码示例:

Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    /* 媒体信号槽连接 */
    /* 4. 媒体播放时长改变信号(显示歌曲时长) */
    connect(musicPlayer, SIGNAL(durationChanged(qint64)), this, SLOT(musicPlayerDurationChanged(qint64)));

    /* 5. 媒体播放位置改变信号 */
    connect(musicPlayer, SIGNAL(positionChanged(qint64)), this, SLOT(mediaPlayerPositionChanged(qint64)));
}

// 媒体播放时长改变时的槽函数
void Widget::musicPlayerDurationChanged(qint64 duration)
{
    // 设置歌曲播放时间范围 duration / 1000 :毫秒转换为秒
    ui->horizontalSlider->setRange(0, duration / 1000);
    int second  = duration / 1000; // 获取秒
    int minute = second / 60; // 获取分
    second %= 60;

    QString mediaDuration;
    mediaDuration.clear();
    // 分、秒小于10则前面补0,否则正常显示
    if (minute >= 10)
        mediaDuration = QString::number(minute, 10);
    else
        mediaDuration = "0" + QString::number(minute, 10);

    if (second >= 10)
        mediaDuration = mediaDuration
                + ":" + QString::number(second, 10);
    else
        mediaDuration = mediaDuration
                + ":0" + QString::number(second, 10);

    /* 显示媒体总长度时间 */
    ui->label_3->setText(mediaDuration);
}

// 水平滑块释放事件处理
void Widget::on_horizontalSlider_sliderReleased()
{
    /* 设置媒体播放的位置 */
    musicPlayer->setPosition(ui->horizontalSlider->value() * 1000);
}

// 媒体播放位置改变时的槽函数 拖动进度条
void Widget::mediaPlayerPositionChanged(qint64 position)
{
    // 判断滑动条是否被按下
    if (!ui->horizontalSlider->isSliderDown())
        ui->horizontalSlider->setValue(position/1000);

    int second  = position / 1000;
    int minute = second / 60;
    second %= 60;

    QString mediaPosition;
    mediaPosition.clear();

    if (minute >= 10)
        mediaPosition = QString::number(minute, 10);
    else
        mediaPosition = "0" + QString::number(minute, 10);

    if (second >= 10)
        mediaPosition = mediaPosition
                + ":" + QString::number(second, 10);
    else
        mediaPosition = mediaPosition
                + ":0" + QString::number(second, 10);

    /* 显示现在播放的时间 */
    ui->label_2->setText(mediaPosition);
}

编译运行结果:

image.png

6. 显示歌词

设置歌词显示居中,字体大小为13。

image.png
image.png

根据播放位置显示歌词

代码示例:

Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    /* 媒体信号槽连接 */
    /* 6. 根据播放位置显示歌词 */
    connect(musicPlayer, &QMediaPlayer::positionChanged, this, &Widget::updateLyrics);
}


// 媒体播放列表当前索引改变时的槽函数
void Widget::mediaPlaylistCurrentIndexChanged(int index)
{
    if (-1 == index)
        return;

    /* 设置列表正在播放的项 */
    ui->listWidget->setCurrentRow(index);

    // 检查索引有效性
    if (index >= 0 && index < mediaObjectInfo.size()) {
        // 更新当前歌词路径
        currentLyricsPath = mediaObjectInfo.at(index).lyricsPath;
        qDebug() << currentLyricsPath << endl;
        // 如果需要,你可以在这里调用更新歌词显示的函数
        // updateLyrics();
    } else {
        // 如果索引无效,清除当前歌词路径r
        currentLyricsPath.clear();
        qDebug() << "currentLyricsPath.clear" << endl;
    }
}

// 根据播放进度更新歌词显示
void Widget::updateLyrics(qint64 currentTime)
{
    if (!currentLyricsPath.isEmpty()) {
        QFile file(currentLyricsPath);
        // 以只读方式打开文本
        if (file.open(QIODevice::ReadOnly | QIODevice::Text))
        {
            QTextStream stream(&file);
            stream.setCodec("UTF-8");

            //qint64 offset = 4000;

            while (!stream.atEnd())
            {
                QString line = stream.readLine();
                // 解析每一句歌词的时间
                // 歌词格式通常是如下形式:[00:12.00]歌词内容
                QRegExp timeRegex("\\[(\\d+):(\\d+\\.\\d+)\\]");
                if (timeRegex.indexIn(line) != -1)
                {
                    // 将分钟数和秒数相加得到歌词播放时间,精确到小数点后两位
                    qint64 startTime = timeRegex.cap(1).toInt() * 60000 + // 提取分钟数
                                       timeRegex.cap(2).toFloat() * 1000; // 提取秒数

                    //startTime += offset;

                    QString currentLyric = line.remove(timeRegex);
                    // 开始时间小于等于现在播放时间
                    if (startTime <= currentTime)
                    {
                        ui->label_4->setText(currentLyric); // 显示
                        //ui->label_4->setText("测试"); // 显示
                    }
                    else
                    {
                        break;
                    }
                }
            }
        }
    }
    else
        qDebug() << "currentLyricsPath isEmpty " << endl;
}

编译运行结果:

image.png

7. 调整声音大小和播放模式

通过垂直滑动条调节声音大小,通过点击按钮改变播放模式为循环播放或者单独循环。

代码示例:

// 垂直滑块释放事件处理
void Widget::on_verticalSlider_sliderReleased()
{
    musicPlayer->setVolume(ui->verticalSlider->value());
}


// 循环模式按钮点击事件处理
void Widget::on_btn_loop_clicked(bool checked)
{
    if(checked) 
    {
        // 当前项循环
        mediaPlaylist->setPlaybackMode(QMediaPlaylist::CurrentItemInLoop);
    }
    else 
    {
        // 列表循环
        mediaPlaylist->setPlaybackMode(QMediaPlaylist::Loop);
    }
}

8. 美化 UI 界面

作者认为 QT 的 UI 界面美化远比功能实现复杂,QT 的灵魂在于如何做出一个漂亮的界面。

为什么非要布局,直接自己摆不是更好吗?

刚开始学的时候,我也有这样的想法,因为刚开始接触界面,布局确实不是那么重要,我们做的小项目,组件可能很少,只需要几个按钮,几个文本框就可以实现,那么随着我们的软件越来越庞大,你就得考虑软件大小变化,后期添加组件等问题,所以让组件自动分配空间显得尤为重要。

由于作者能力有限,对界面布局和美化也只知道些皮毛,后续有时间还需继续深入学习, UI 界面美化你们得自己去深入研究。

篇幅有限,最终 UI 界面展示,可自行参考网上资料实现:

image.png

1 关于 Easy Player: Easy Player 是由于个人兴趣而制作的一款基于Qt的在线音乐播放器 目前是第一个版本 并未进行足量优化 因此 在使用过程中可能存在某些Bug 请谅解 2 功能介绍: 目前功能支持歌曲在线搜索 单曲循环(其他循环方式后期添加) 添加搜索结果到试听列表 下载音乐到本地 歌词同步显示 还不能同步滚动 3 使用方法: (1)首先 从按钮说起: 左边第一排:播放(暂停) 下一首 单曲循环 下载当前歌曲 歌词显示; 左边第二排:音量键 右边第一排:歌曲时间轴 (2)其次 搜索: “歌曲特征”输入关键词搜索 会呈现搜索结果在搜索列表 搜索列表右边的按钮表示添加歌曲到播放列表 (3)最后 播放列表: 在歌曲列表中双击歌曲播放 右边的按钮表示下载歌曲 目前是下载完成之后才会提示 之后会做一个下载列表界面 4 其他 本来打算在下载的时候加入多线程 另外加一个数据库保存播放信息 但由于时间关系 并没有在这个版本加入 之后的版本会不断完善 欢迎大家下载测试和提意见 声明:代码仅供参考 请尊重原创 作者:Reyn 博客地址:http: blog csdn net jan5 reyn">1 关于 Easy Player: Easy Player 是由于个人兴趣而制作的一款基于Qt的在线音乐播放器 目前是第一个版本 并未进行足量优化 因此 在使用过程中可能存在某些Bug 请谅解 2 功能介绍: 目前功能支持歌曲在线搜索 单曲 [更多]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

热爱嵌入式的小佳同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值