C++搭配QT开发一套音乐播放器(QML+QT)

写在前面,本文转载自CSDN博客:https://blog.csdn.net/xyygudu/article/details/130134269 ,作者:xyygudu

代码地址

xyygudu/Player: Qt和QML实现了视频播放器和音乐播放器 (github.com)

部分效果展示

在这里插入图片描述

实现的功能

视频相关:播放暂停、播放进度调节、音量调节、列表显示指定目录下的视频文件

音频相关:播放暂停、上一曲下一曲、随机播放和顺序播放、播放进度调节、歌词滚动以及高亮、列表显示指定目录下的音频文件

其他功能:支持皮肤切换、窗口最大化最小化、窗口任意形状调节、窗口移动、边框阴影

主要文件描述

在这里插入图片描述

视频播放

实现思路

视频列表通过TableView实现,其代理(delegate)要显示的数据来自videoplayer.h的m_videoTableModel,当双击列表某一行时,触发VideoTable.qml中自定义的信号doubleClicked(int row),该信号将页面切换到VideoSecondPage.qml(播放视频的页面),同时把双击的行号传给VideoSecondPage.qml,这样VideoSecondPage.qml就可以根据行号调用C++函数获取当前双击行对应的视频路径,因此就可以实现视频的播放

// 切换页面并传递行号 VideoPanel.qml
VideoFirstPage {
    
    
  onTableRowDoubleClicked: {
    
      // 其实就是VideoTable.qml的doubleClicked信号
    root.selectedRow = row  // 将选中的行号赋值给root.selectedRow便于传入到secondPage中
    stackView.push(secondPage)   // 跳转到第二个页面(播放视频的页面)
  }
}// 视频播放器 VideoSecondPage.qml
VideoPlayer {
    
    
  id: videoPlayer
  anchors.left: parent.left
  anchors.right: parent.right
  height: width * 9 / 16
  source: myVideoPlayer.getVideoPathByRow(root.selectedRow)
}

音频播放

实现思路

播放音频实现:音频列表通过TableView实现,其代理(delegate)要显示的数据来自musicplayer.h的m_musicTableModel,当双击列表某一行时,触发MusicTable.qml中自定义的信号doubleClicked(int row),该信号把双击的行号传给MusicControlBar.qml(控制音频播放等页面),这样MusicControlBar.qml就可以根据行号调用C++函数获取当前双击行对应的视频路径,因此就可以实现视频的播放

上一曲/下一曲实现:MusicControlBar.qml有上一曲下一曲按钮,实现切歌只需要改变MusicControlBar.qml的seclectedRow属性即可,seclectedRow记录当前播放的音乐在列表的哪一行。当点击下一曲后,将seclectedRow增加1即可,不需要其他操作,因为MediaPlayer(位于MusicControlBar.qml中)的source属性是和seclectedRow是相互绑定的,只要seclectedRow改变,MediaPlayer的source也会相应改变,而source的改变又会触发onSourceChanged信号,onSourceChanged实现了音频的播放,主要代码如下:

// 下一曲按钮
CusWidgets.ImageButton {
    
    
    ...省略代码一万行
    onClicked: {
    
    
        if (root.playMode === root.order) {
    
    // 如果是顺序播放
            if (root.seclectedRow !== -1) {
    
    
                if (root.seclectedRow === myMusicPlayer.musicTableModel.rowCount()-1) {
    
    
                    root.seclectedRow = 0
                } else {
    
    
                    root.seclectedRow += 1
                }
            } else {
    
    
                root.seclectedRow = 0
            }
        } else {
    
     // 如果是随机播放
            root.seclectedRow = getRandomNum(0, myMusicPlayer.musicTableModel.rowCount())
        }
    }
}
​
MediaPlayer {
    
    
    id: musicPlayer
    source: root.seclectedRow === -1 ? "" : myMusicPlayer.getMusicPathByRow(root.seclectedRow)
    onSourceChanged: {
    
    
        musicPlayer.play()
    }
}

歌词显示实现:

(1)歌词模型的更新:前面说到,切歌只需要改变MusicControlBar.qml自定义的seclectedRow属性即可,当seclectedRow改变后,其触发的信号就可以实现对歌词模型的更新

扫描二维码关注公众号,回复: 16088304 查看本文章
MusicControlBar {
    
     // 位于MusicPanel.qml
    id: musicControlBar
    onSeclectedRowChanged: {
    
    
        myMusicPlayer.upDateLyricModelBy(seclectedRow) //调用C++函数来更新歌词信息
    }
}

(2)歌词滚动实现: LRC歌词文件如下:

// 第一个数字表示分钟,第二个数字表示秒,第三个数字可能是毫秒
// 不过我们只用到前两个数字和歌词内容
[0:0.250.00]沉默是金 - 张国荣
[0:1.500.00]词:许冠杰
[0:2.190.00]曲:张国荣
[0:27.450.00]夜风凛凛 独回望旧事前尘
[0:33.120.00]是以往的我充满怒愤
​
// 另一种lrc歌词文件格式是下面这样的
// 第一个数字是分钟,第二个数字是秒,第三个数字是多少个10毫秒,也就是说,
// 第三个数最大为100
[ver:v1.0]     // 我的程序为了方便没有解析这些信息
[ti:]          
[00:00.36]- 许巍 
[00:01.04]词:许巍
[00:01.69]曲:许巍
[00:37.95]拥抱着亲人的时候

了解歌词格式后,可以通过正则表达式解析歌词(见lyrics.h的parseLyrics函数),该函数把歌词的时间戳(比如[0:27.450.00])和歌词对应的索引(索引标记着这是第几句歌词,比如[0:27.450.00]对应的索引为3,索引从0开始)存储到QMap,即下面代码的m_lyric2IdxMap中,时间戳是键,索引是值,然后把歌词内容(比如:[0:27.450.00]对应歌词:夜风凛凛 独回望旧事前尘)存储到歌词模型LyricModel中,LyricModel其实就是对QStringList进行管理。歌词解析完成后,接下来就是实现歌词滚动,如下面代码所示,我们可以在MediaPlayer的onPositionChanged信号处理中,获取当前应该显示歌词的索引号,获取索引的思路是根据当前音乐播放进度(即形参qint64 pos的值),遍历上述m_lyric2IdxMap,找到pos在那个歌词的时间段内,然后返回该歌词在LyricModel的索引。

MediaPlayer {
    
          
    onPositionChanged: {
    
    
        // 实时显示歌词 调用c++函数,返回此时应该显示的歌词的索引
        var idx = myMusicPlayer.getLyricIdxByPosition(position)
        if (idx === -1) {
    
    
            root.lyricIdx = -1   // -1表示当前position没有合适的歌词
        } else {
    
    
            root.lyricIdx = idx
        }
    }
}
// c++函数,见musicplayer.cpp
int MusicPlayer::getLyricIdxByPosition(qint64 pos)
{
    
    
     QMap<qint64, int>::iterator iter = m_lyric2IdxMap.begin();
     while (iter != m_lyric2IdxMap.end())
     {
    
    
         if ((iter.key()-500<=pos)  && (iter+1).key()-500 > pos)
         {
    
    
             return iter.value();
         }
         iter++;
     }
     return -1;
}

根据播放进度获得当前歌词索引后,只需要吧该歌词索引值赋值给显示歌词的ListView的currentIndex属性即可

ListView {
    
    
  id: lyricsList
  currentIndex: lyricIndex===-1?currentIndex:lyricIndex
  // 省略代码一万行
}

BUG

点击左侧导航栏的音频播放,然后最大化窗口,点击左侧视频播放,发现视频表格并没有随着窗口宽度变化而发生变化,不知道为什么,明明已经再TableView的onWidthChangede中调用了forceLayout()。如果解决了请告知一声

TableView {
    
    
    id: tableView
    // 省略代码
    columnWidthProvider: function(colum) {
    
     return root.columnWidths[colum] }
    onWidthChanged: {
    
    
        // If you change the values that a rowHeightProvider or a columnWidthProvider
        // return for rows and columns inside the viewport, you must call forceLayout.
        // This informs TableView that it needs to use the provider functions again to
        // recalculate and update the layout.
        forceLayout()
    }
}

参考资料

皮肤切换实现:玩转Qml(3)-换皮肤 | 涛哥的博客

解析歌词:基于Qt的网络音乐播放器(五)实现歌词滚动显示qt歌词滚动花狗Fdog的博客-CSDN博客;QML中ListView向上滚动效果_hp_cpp的博客-CSDN博客

歌词显示:QML中ListView向上滚动效果_hp_cpp的博客-CSDN博客

窗口宽高调整:qml 去除标题栏后 拖动窗口和改变窗口大小_qml 去标题无法移动_林兴南的博客-CSDN博客

猜你喜欢

转载自blog.csdn.net/hallobike/article/details/130263803