Qt入门项目:基于Linux的简易音乐播放器

目录

 1、mplayer简介

 2、简易的音乐播放器

(1)显示歌曲列表

(2)导入本地文件按钮

(3)播放与暂停按钮 

(4)上一曲与下一曲的实现

(5)曲毕,自动播放下一曲

(6)双击歌单切换歌曲

(7)音量调节

(8)进度条调节

(9)歌词显示与歌词滚动 

 3、最终演示视频


对于qt的初学者来说,音乐播放器可以作为第一个练手的项目,语言基础:已经学完了面向对象部分的c++,软件基础:Linux操作系统,mplayer播放器,面向Linux的qt软件。

    首先,我们要对于mplayer播放器有一定的了解。

 1、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 //打印出当前文件的'年份'的元数据
	

 2、简易的音乐播放器

了解mplayer后,我们就可以利用这些指令,制作属于我们的简易音乐播放器 

(1)显示歌曲列表

         此函数用来筛选文件夹中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)展示效果

(2)导入本地文件按钮

(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)效果展示

(3)播放与暂停按钮 

(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");
        }

}

(4)上一曲与下一曲的实现

上一曲与下一曲的实现方法有异曲同工之妙,这里我详细介绍下一曲的实现方法和步骤,仅展示上一曲的源码

(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("暂停");
      }
    
}

(5)曲毕,自动播放下一曲

(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();
    }
}

(6)双击歌单切换歌曲

在熟悉了上一曲,下一曲的播放原理后,双击切换歌曲的原理与之差不多,可以很好的进行类比。

(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("暂停");
      }
    
}

(7)音量调节

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);//开始音量
}

(8)进度条调节

经过对于音量调节的学习,相必大家先想到的就是实时监测进度条值的变化,那么仅仅如此吗。大家可以试一下,这里不做演示。

因为进度条要根据歌曲的播放进度是实时变化的,所有我们需要一个定时器,每过一段时间就向终端进行询问,“现在到哪了”。来监测音乐的播放进度条。

//插入头文件
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();
}

(9)歌词显示与歌词滚动 

 歌词的显示我们可以仿照歌单一样建立一个列表,当歌词显示大于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();
    }
}

 3、最终演示视频

完整演示视频

好了至此已经完整的完成了基于linux的音乐播放器。可能会有一些小bug

  • 32
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值