赞
踩
目录
对于qt的初学者来说,音乐播放器可以作为第一个练手的项目,语言基础:已经学完了面向对象部分的c++,软件基础:Linux操作系统,mplayer播放器,面向Linux的qt软件。
首先,我们要对于mplayer播放器有一定的了解。
mplayer是一款开源多媒体播放器,以GNU通用公共许可证发布。此款软件可在各主流操作系统使用,例如Linux和其他类Unix系统、Windows及Mac OS X系统。支持播放多种主流的音视频格式。 mplayer有两种工作模式,一种普通模式,一种 slave模式。
(1)普通模式: mplayer -quiet 音视频文件名即为 mplayer -quiet x.mp3
mplayer -quiet x.mp3
显示如下情况则为成功
在普通模式运行下的其他指令,这些指令要在mplayer运行的前提下才能使用
- left or right 向后/向前搜索10秒
- up or down 向后/向前搜索1分钟
- p or SPACE 暂停播放(按任意键继续)
- q or ESC 停止播放并退出
- 注意: up down left right 是键盘的功能区的按钮且只能在普通模式下使用
(2)slave 模式
在普通模式上加上参数 -slave mplayer -quiet -slave 路径名 音视频文件名
在这种模式下可以从标准输入设备获取命令来控制播放,也可以在程序中发送指定来
控制播放。
- mplayer -quite -slave /mnt/hgfs/share 1.mp3
- //把路径和歌曲名字改为自己的
在该模式下也有一些指令来控制mplayer,与普通模式不一样的是在控制台输入指令进行控制的,常见指令如下:
- 注意:mplayer不能识别带空格的文件名,所以使用时请将本地文件改为不带空格的名字
- loadfile newfilename //会结束正在播放的文件,播放新文件。
- // lodafine lovestory.mp3
-
- volume x 1 //设置音量 中间的x为音量的大小。
- //volume 20 1
- // volume 80 1
-
- mute 1/0 //静音开关 , 1:静音;0:取消静音。
-
- pause //暂停/取消暂停
- //注意:在暂停的状态下,发送任何指令都会继续播放
-
- quit 退出MPlayer。
-
- get_time_length //返回值是播放文件的长度,以秒为单位。
- //回复格式: ANS_LENGTH=273.00
- //注意对于其他格式转码成MP3格式的时间会出现错误
-
- get_percent_pos //返回播放进度的百分比(0--99)
- //回复格式: ANS_PERCENT_POSITION=30
-
- get_time_pos //返回当前播放位置的时间,单位是秒,采用浮点数
- //回复格式: ANS_TIME_POSITION=118.1
-
- seek value type 跳转到指定位置进行播放
- type 为0时, value表示相对目前所在位置的秒数,可正可负
- type 为1时, value表示百分比,定位到 value% 处播放
- type 为2时, value表示绝对时间,如: value为50,表示定位到第50秒处播放
-
- //其他指令
- get_file_name //打印出当前文件名
- get_meta_album //打印出当前文件的'专辑'的元数据
- get_meta_artist //打印出当前文件的'艺术家'的元数据
- get_meta_comment //打印出当前文件的'评论'的元数据
- get_meta_genre //打印出当前文件的'流派'的元数据
- get_meta_title //打印出当前文件的'标题'的元数据
- get_meta_year //打印出当前文件的'年份'的元数据
-
了解mplayer后,我们就可以利用这些指令,制作属于我们的简易音乐播放器
此函数用来筛选文件夹中MP3或者其他格式音频的文件,这里用MP3文件展示
(i)加入头文件定义自己的歌曲文件路径(根据自己的情况来确定推荐使用相对路径这里我用绝对路径来展示),并且定义showMusciName()函数
- #include<QString>
- #include<QDir>
- #include<QStringList>
- public:
- void showMusicName();
-
- private:
- #define DEFAULT_PATH "/mnt/hgfs/share/mus" //根据实际情况来定
(ii)在.c文件中实现showMusipaly()函数,这里有2种实现方法,一种时一个一个的加入到歌曲列表中,另外一个全部统一加入到歌曲列表中
- //单个加入
- musciplay::showMusicName()
- {
- ui->listWidget_name->clear();//清除原来的歌单
- QDir dir(music_path);//实例化一个目录对象
- QStringList x;
- x <<"*.mp3";//筛选MP3格式文件
- QStringList list=dir.entryList(x);
- for(int i=0;i<list.size();i++){
- ui->listWidget_name->addItem(list.at(i));
- }
- songSize = music_name_list.size();
-
- }
-
- //全部整体加入
- musciplay::showMusicName()
- {
- ui->listWidget_name->clear();
- QDir dir(music_path);
- QStringList x;
- x <<"*.mp3";
- music_name_list=dir.entryList(x);
- ui->listWidget_name->addItems(music_name_list);
- }
-
(iii)在构造函数中调用showMusicName()函数,并且对路径music_path进行初始化
- //musicplay.h文件
- private:
- QString music_path;//用来保存当前音乐文件所在路径
- QStringList music_name_list;//把读取到的文件先保存在这个列表里面然后加入到ui界面上去
-
- //musicplay.c文件
- musciplay::musciplay(QWidget *parent) :
- QWidget(parent),
- ui(new Ui::musciplay)
- {
- ui->setupUi(this);
- music_path=DEFAULT_PATH;
- showMusicName();
- }
(iiii)展示效果
(i)在ui界面中加入button按钮,并且命名为导入,单机导入按钮转到槽,选择clicked()
注意:因为是转到槽函数,这里不需要对其进行额外的函数声明
(ii)加入头文件,并且完善该槽函数
- #include<QDebug>//用来调试输出到终端上显示
- #include<QFileDialog>
- void musicplay::on_pushButton_clicked()
- {
- QString path=QFileDialog::getExistingDirectory();//弹出文件对话框,选择一个目录并返回目录的绝对路径,中途取消返回空字符串
- if(!path.isEmpty())
- {
- music_path=path;
- }
- showMusicName();
- }
(iii)效果展示
(i)与导入一样在ui界面选择button并且命名为播放,右击转到槽,依旧选择clicked,这里就不做展示
(ii)定义一个play函数,在每次点击播放按钮时,来调用play函数进行播放,对于play函数,我们的想法是ui界面接受播放信息时,就会启动进程,向终端发送-slave格式的播放信息。
- #include<QFileDialog>
- #include<QProcess>
-
- public:
- void play();
-
- private:
- int index;//当前正在播放的音乐下标记
- QProcess *process;
- //我们在析构函数这统一结束进程
- musciplay::~musciplay()
- {
- delete ui;
- process->close();
- }
-
-
- void musciplay::play()
- {
- //file保存当前要播放的音乐的完整路径名
- QString file=QString("%1/%2").arg(music_path).arg(music_name_list[index]);//%1代表路径,%2代表名字
- QStringList arg;
- arg<<"-quiet"<<"-slave"<<file;//利用file保存播放音乐的指令代码,然后发送到终端
- process->setArguments(arg);
- process->start();//启动进程
- }
(iii) 对于槽函数on_pushButton_play_clicked()有2种情况进行分析,先判断进程是否在进行,如果在进行就暂停,如果没有在进行就继续播放,同时在播放时点击,播放就会变成暂停利用setText实现。反之,依然。
- void musciplay::on_pushButton_play_clicked()
- {
-
- if(ui->pushButton_play->text()=="播放")
- {
- if(process->state()!=QProcess::Running)
- {
- play();
- }//正在运行的情况
- else
- {
- process->write("pause\n");
- }
- ui->pushButton_play->setText("暂停");
- }
- else
- {
- ui->pushButton_play->setText("播放");
- process->write("pause\n");
- }
-
- }
上一曲与下一曲的实现方法有异曲同工之妙,这里我详细介绍下一曲的实现方法和步骤,仅展示上一曲的源码
(i)同前面一样,我们在点击下一曲按钮时,系统会向终端发出指令切换歌曲。所有我们槽函数也是选择clicked。
(ii)与上文一样,也是向终端发送切换歌曲的指令,然后改变播放按钮的文本
- //下一曲
- void musciplay::on_pushButton_next_clicked()
- {
- index++;
- if(index==music_name_list.size())
- {
- index=0;
- }//当歌曲到达最后一首时回到第一首
- if(process->state()!=QProcess::Running)
- {
- play();
- }
- else
- {
- //正在播放中,点了下一曲
- QString cmd =QString("loadfile %1/%2\n").arg(music_path).arg(music_name_list[index]);
- process->write(cmd.toUtf8());
- }
- if(ui->pushButton_play->text()=="播放")
- {
- ui->pushButton_play->setText("暂停");
- }
-
- }
- //下一曲,只改变了index的计算方法,与下一曲几乎一样
- void musciplay::on_pushButton_previous_clicked()
- {
- if (index == 0)
- {
- index = music_name_list.size() - 1;
- }
- else
- {
- index--;
- }
- if (process->state()!= QProcess::Running)
- {
- play();
- }
- else// 正在播放中,加载上一曲
- {
- QString cmd = QString("loadfile %1/%2\n").arg(music_path).arg(music_name_list[index]);
- process->write(cmd.toUtf8());
- }
- if(ui->pushButton_play->text()=="播放")
- {
- ui->pushButton_play->setText("暂停");
- }
-
- }
(i) 先判断进程是否结束,来判断音乐是否播放完,播放完就调用play函数
- musicplay.h文件
- private slots:
- void onFinished();
-
-
- muscicpaly.cpp文件
- musicplay::musicplay(QWidget *parent) :
- QWidget(parent),
- ui(new Ui::musicplay)
- {
-
- connect(process,SIGNAL(finished(int)),this,SLOT(onFinished()));
- }
-
- void musicplay::onFinished()
- {
- qDebug()<<"mplayer播放结束了";
- index++;
- if(index==music_name_list.size())
- {
- index=0;
- }
- else
- {
- play();
- }
- }
-
在熟悉了上一曲,下一曲的播放原理后,双击切换歌曲的原理与之差不多,可以很好的进行类比。
(i)找到ui界面,点击歌单列表右击点到槽函数,选择doubleCliced
(ii)因为c++的语法,自定义的类里面会自带一个this指针,用this->index=index.row()//第一个this是我们自己定义的index来记录当前播放在第几曲了,第二个为槽函数的index,用index.row()来获取当前点击的第几行。
- void musciplay::on_listWidget_name_doubleClicked(const QModelIndex &index)
- {
- this->index=index.row();//返回单击的行数
- if(process->state()!= QProcess::Running) {
- play();
- ui->pushButton_play->setText("暂停");
- } else
- {
- QString cmd = QString("loadfile %1/%2\n").arg(music_path).arg(music_name_list[this->index]);
- process->write(cmd.toUtf8());
- }
- if(ui->pushButton_play->text()=="播放")
- {
- ui->pushButton_play->setText("暂停");
- }
-
- }
mplayer播放器自带一个volume指令可以用来调剂音量,我们连接slider就可以实现对于音量的就行调节。但是在选择槽函数时我们该做如何选择呢?
(i)先用label标签,来标识音量,如何选择slier转到槽,选择valueChange(int),因为音量是一个动态变化的,所有要实时监控音量的变化值
(ii)音量变化的核心思想也是把命令用字符串储存起来,如何利用write函数写到终端进行命令控制。
- musicplay.cpp 文件
-
- void musciplay::on_horizontalSlide_volume_valueChanged(int value)
- {
- QString cmd = QString("volume %1 1\n").arg(value);
- process->write(cmd.toUtf8());
- }
(iii)到此基本功能已经实现了,但是有一个新问题,我们的初始音量不是0,但是打开进程,初始音量显示0,那么我们该如何解决呢? 我们选择在ui界面显示上做功夫,对于音量的初始进行设定
- #include<QTextStream>
- //该文件可以对ui界面进行文字流的输入
- #include<QScrollBar>
- //该文件主要是对于滚动条进行调节
-
- musicplay::musicplay(QWidget *parent) :
- QWidget(parent),
- ui(new Ui::musicplay)
- {
- ui->horizontalSlider_volunme->setValue(80);//开始音量
- }
经过对于音量调节的学习,相必大家先想到的就是实时监测进度条值的变化,那么仅仅如此吗。大家可以试一下,这里不做演示。
因为进度条要根据歌曲的播放进度是实时变化的,所有我们需要一个定时器,每过一段时间就向终端进行询问,“现在到哪了”。来监测音乐的播放进度条。
- //插入头文件
- musciplay.h 文件
- #include<QTimer>
-
- musciplay.h 文件
- private:
- QTimer *timer;
-
- musicplay.cpp文件
- musicplay::musicplay(QWidget *parent) :
- QWidget(parent),
- ui(new Ui::musicplay)
- {
- timer=new QTimer(this);
- timer->setInterval(1);//设置间隔时间
- timer->start(); //开启定时器
- }
-
(i)首先我们需要获取歌曲的文件长度,查阅前文发现,用 get_time_length 指令来获取文件长度,回复格式: ANS_LENGTH=273.00。get_time_pos 指令来获取播放位置的时间,单位是秒,采用浮点数,回复格式: ANS_TIME_POSITION=118.1。因为slider是百分比的,所以我们采用get_percent_pos 指令获取播放进度的百分比(0--99)回复格式:ANS_PERCENT_POSITION=30 ,我们定义一个sumtime标签,每个歌词文件获取后利用ui文本写入,即可显示出来。并且在ui界面上添加slider控件
此外我们将定义一个ontimeput函数,每次计时器开始时都会发送指令给终端来获取我们需要的数据。利用timeout()信号选择器,可以方便的得到。
- musciplay::musciplay(QWidget *parent) :
- QWidget(parent),
- ui(new Ui::musciplay)
- {
- connect(timer,SIGNAL(timeout()),this,SLOT(onTimeput()));
- }
-
- musciplay::onTimeput()
- {
- process->write("get_time_length\n");
- process->write("get_percent_pos\n");
- process->write("get_time_pos\n");
- }
(ii) 定义一个onReadyRead()函数,来多次询问终端获取总时间和实时时间,每次指令获取我们需要去处不需要的文字部分,只保留自己需要的部分,并且在构造函数中进行连接,因为我们需要在接收完终端数据后开始调用函数,所以采用信号接受时采用readyread(),单我们在切歌曲时没有进行调用,所以我们直接在play()里面进行调用,因为在暂停状态下恢复播放,我们没有调用play()函数所以我们也要在上一曲,下一曲,双击切歌的第二个选项里面,同时因为在暂停时我们的计时器是关闭的,所以我们也需要打开计时器。
- private slots:
- void onReadyRead();
-
- musciplay::musciplay(QWidget *parent) :
- QWidget(parent),
- ui(new Ui::musciplay)
- {
- connect(process,SIGNAL(readyRead()),this,SLOT(onReadyRead()));
- }
-
- void musciplay::onReadyRead()
- {
- while(1)
- {
- //只要mplayer回复数据给process就会执行该槽函数
- QString s=process->readLine();
- //qDebug()<<s;
- if(s.isEmpty()) break;//读完跳出循环
- if(s.contains("ANS_LENGTH="))//回复总时长
- {
- qDebug()<<s;
- s.remove(0,11);//去除从0开始的连续11个字符
- s.chop(4);//去除最后4个
- s = s.trimmed(); //去除头尾空格
- ui->label_sumtime->setText(s);
- }
- else if(s.contains("ANS_PERCENT_POSITION="))
- {
- s.remove(0,21);
- ui->horizontalSlider->setValue(s.toInt());
- }
- else if(s.contains("ANS_TIME_POSITION="))
- {
- s.remove(0,18);
- s.chop(1);
- ui->label_nowtime->setText(s);
- }
- }
- }
-
- void musciplay::on_pushButton_next_clicked()
- {
-
- if(process->state()!=QProcess::Running)
- {
- play();
- }
- else
- {
- timer->start();
- onTimeput();
- }
- }
-
- void musciplay::on_pushButton_previous_clicked()
- {
-
- if (process->state()!= QProcess::Running)
- {
- play();
- }
- else// 正在播放中,加载上一曲
- {
- timer->start();
- onTimeput();
- }
- }
- void musciplay::on_listWidget_name_doubleClicked(const QModelIndex &index)
- {
- if(process->state()!= QProcess::Running)
- {
- play();
- ui->pushButton_play->setText("暂停");
- }
- else
- {
- timer->start();
- onTimeput();
- }
- }
(iii) 上文中我们基本实现了现在歌曲进度与歌曲总时间的显示,但是进度条的拖动还是没有实现,同时我们遇到一个问题那就是播放键失灵了,无法暂停与播放了,我们该如何做呢。因为定时器每隔一秒就会发送指令,我们的暂停指令刚开始就被覆盖了,那我们想,那就在点击暂停的时候停止计时器,这样是不是就可以解决了呢。停止后我们也需要及时的回复,这样我们的实时进度才会继续。
- void musciplay::on_pushButton_play_clicked()
- {
-
- if(ui->pushButton_play->text()=="播放")
- {
- if(process->state()!=QProcess::Running)
- {
- play();
- }
- else
- {
- timer->start();
- process->write("pause\n");
- }
- ui->pushButton_play->setText("暂停");
- }
- else
- {
- timer->stop();
- ui->pushButton_play->setText("播放");
- process->write("pause\n");
- }
-
- }
我们点击slider转到槽函数,选择Released()和pressed (),为什么要选择2个呢,因为我们需要在调节进度条时,把计时器关闭,松开后我们将恢复计时器。
- void musciplay::on_horizontalSlider_sliderReleased()
- {
- timer->start();
- int x=ui->horizontalSlider->value();
- if(ui->pushButton_play->text()=="播放")
- {
- ui->pushButton_play->setText("暂停");
- }
- //seek命令
- QString cmd=QString("seek %1 1\n").arg(x);
- process->write(cmd.toUtf8());
- onTimeput();
- }
-
-
- void musciplay::on_horizontalSlider_sliderPressed()
- {
- timer->stop();
- }
歌词的显示我们可以仿照歌单一样建立一个列表,当歌词显示大于7的时候我们将进行实时滚动,另外一种方法时采用七条标签,不断的进行歌词的提取,显示,高亮,滚动。这里我将采用列表的形式进行演示。我们要实现找到与歌曲匹配的lrc文件,这里可以在百度上搜索,歌词文件名与歌曲名一致。
(i)歌词显示
新建一个ListWidget,我们命名为lrc
我们首先要提取歌词到列表上,所以我们要去除歌词前面的时间戳,去处前我们需要提取时间戳,用来在后面滚动歌词时用到。我们建立一个time_list的列表,用来保存。因为我们实时获取的歌曲进度时以秒为单位,所以我们将时间戳转换为秒。我在网上找到时间戳格式为[00:01.330],数组的1、2为小时位,4、5为分钟位,7、8、9为秒位。
- #include<QFile>
-
- private:
- QList<double> time_list;
-
- //在歌词显示之前我们需要先判断,是否能正常打开歌词文件,歌词路径采用歌曲一样的路径
- void musciplay::onShowLyric()
- {
- //清空控件中的歌词内容
- ui->listWidget_lyric->clear();
- //清空时间列表
- time_list.clear();
- //每次显示歌词的时候都回到第一行进行显示
- ui->listWidget_lyric->verticalScrollBar()->setSliderPosition(0);
- //获取当前播放的歌曲的歌词路径名
- QString lrc_path = music_path+ "/" + music_name_list[index];
- // lrc_path.replace(".mp3",".lrc");
- lrc_path.remove(lrc_path.length()-3,3);
- lrc_path += "lrc";
- //实例化一个文件对象
- QFile file(lrc_path);
- if(!file.exists())
- {
- //如果歌词文件不存在,直接返回,不显示歌词
- qDebug() << lrc_path << " is not exists!";
- return;
- }
- //打开文件
- if(!file.open(QFile::ReadOnly))
- {
- //歌词文件打开失败
- qDebug() << "open " << lrc_path << " error:" << file.errorString();
- return;
- }
- //实例化一个文本流对象
- QTextStream in(&file);
- //设置编码格式
- //in.setCodec("GBK");
- //读取歌词
- while (1)
- {
- if(in.atEnd()) break;
- //读一行歌词
- QString line = in.readLine();
- //提取歌词前面的时间
- float time = (line.mid(1,2).toFloat()*60)+ line.mid(4,4).toFloat();
- qDebug()<<time;
- //将提取出来的时间保存到容器中去
- time_list.append(time);
- //去除歌词前面的时间
- if(line.at(0) == '[' and line.at(10) == ']')
- {
- //这行歌词前面有时间,删除时间
- line.remove(0,11);
- }
- //歌词居中显示
- QListWidgetItem *item = new QListWidgetItem(line);
- item->setTextAlignment(Qt::AlignCenter);
- //将歌词显示到控件上去
- ui->listWidget_lyric->addItem(item);
- }
- //关闭歌词文件
- file.close();
- }
(ii)歌词滚动
歌词滚动只需要简单的大于7,setSliderPosition(lrc_index-7)如果条件为真,这行代码会设置滚动条的位置。它将滚动条移动到 lrc_index - 7
的位置。这样做的效果是将当前的歌词项调整为可见,确保在 lrc_index
为8时,滚动条位置被设置为1,这样第8项就能在视图中可见,因为前7项已被滚动出视野。
- if(lrc_index > 7)
- {
- ui->listWidget_lyric->verticalScrollBar()->setSliderPosition(lrc_index-7);
- }
观察常用音乐软件可以发现,我们还需要将当前歌词红色显示,我们需要匹配时间戳与当前时间,当二者匹配时,我们将当前行数标红,上一个红色的我们标为黑色,就可以实现了。
- void musciplay::inStepLrc(float time)
- {
- //去歌词的时间列表中匹配某个时间点的行号
- int lrc_index = time_list.indexOf(time);
- //如果找到这个时间点了,就返回这个时间点的行号
- //如果没找到,就返回-1
- if(lrc_index == -1)
- {
- //没有匹配到这个时间点的歌词
- return;
- }
- //将前一行的歌词颜色恢复为黑色
- if(lrc_index >0)
- {
- ui->listWidget_lyric->item(lrc_index-1)->setTextColor(QColor(0,0,0));
- }
- //将匹配到的这行歌词高亮显示(显示红色)
-
- ui->listWidget_lyric->item(lrc_index)->setTextColor(QColor(0xFF,0,0));
-
- QListWidgetItem *currentItem = ui->listWidget_lyric->item(lrc_index);
- currentItem->setTextColor(QColor(0xFF,0,0));
- //居中显示当前歌词
- ui->listWidget_lyric->scrollToItem(currentItem, QAbstractItemView::PositionAtCenter);
-
- //歌词自动滚屏
- if(lrc_index > 7)
- {
- ui->listWidget_lyric->verticalScrollBar()->setSliderPosition(lrc_index-7);
- }
-
- }
(iii)合理的放置函数
onShowLyric()与上文的放置发送指令的函数一样,在play,上一曲,下一曲,双击播放中。而歌词滚动函数只要放在获取时间的函数之中即可时间。
- void musciplay::onReadyRead()
- {
- while(1)
- {
-
- else if(s.contains("ANS_TIME_POSITION="))
- {
- inStepLrc(s.toFloat());
- }
- }
- }
-
- void musciplay::play()
- {
- onShowLyric();
- }
- void musciplay::on_pushButton_next_clicked()
- {
- else
- {
- onShowLyric();
- }
- }
-
- void musciplay::on_pushButton_previous_clicked()
- {
-
- i
- else// 正在播放中,加载上一曲
- {
- onShowLyric();
- }
- }
- void musciplay::on_listWidget_name_doubleClicked(const QModelIndex &index)
- {
-
- else
- {
- onShowLyric();
- }
- }
完整演示视频
好了至此已经完整的完成了基于linux的音乐播放器。可能会有一些小bug
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。