Qt实现进度条拖动视频回放

参考:http://blog.csdn.net/liutingxi0709/article/details/51983137

新建一个Qt Widgets Application的项目,类名为MediaPlayer,基类为QMainWindow,自动生成头文件和源文件

项目的进行过程: 

  1. 创建基本布局 
  2. QMediaPlayer的基本使用 
  3. 结合QSlider实现播放进度控制 
  4. 重载QSlider鼠标响应事件,实现单击跳转至任意位置

1、创建基本布局

首先双击mediaplayer.ui文件,打开Qt Designer,拖几个需要的控件,包括一个Label用于播放视频,objectName属性为label_player,一个Horizontal Slider用于控制进度,objectName属性为slider_progress,三个PushButton,名字改为音量、播放、打开,objectName属性分别为pushButton_volume,pushButton_play_and_pause,pushButton_open_file,再根据需求放几个Spacer。将除了Label以外的多个控件同时选中,右键,用一个Horizontal Layout(水平布局)进行对齐,如图:

Qt实现进度条拖动视频回放_第1张图片

同理,将Label控件和上述水平布局同时选中,添加一个栅格布局或者垂直布局: 

Qt实现进度条拖动视频回放_第2张图片

在没有控件的空白处,右击-布局-垂直布局,添加一个顶级布局: 

Qt实现进度条拖动视频回放_第3张图片

设置各个控件的sizePolicy(尺寸策略),常用策略包括:

  • Fixed:固定为控件的默认尺寸sizeHint,不可改变
  • Minimum :以sizeHint为最小尺寸,只能放大
  • Maximum :以sizeHint为最大尺寸,只能缩小
  • Preferred:允许放大或者缩小
  • Expandint:控件可以自行增大或者缩小

例如在本项目中,可以把Label的垂直策略和Slider的水平策略设置为Expandint。

Qt实现进度条拖动视频回放_第4张图片

其余的一些常规属性修改和设置。最后基本界面如下: 

Qt实现进度条拖动视频回放_第5张图片

完成简单的布局设计之后,就进入下一步

2、QMediaPlayer的基本使用

首先打开Qt的工程文件*.pro,向其中添加引用项:

QT       += core gui multimedia multimediawidgets

点击 构建-执行qmake,或者在项目窗口中右击执行qmake,使.pro文件配置生效,在mediaplayer.cpp中添加头文件,并定义全局变量

#include 
#include 
#include 
#include 
#include 
#include 
#include 
//播放视频的全局变量
QVBoxLayout* layout_video;//布局
QMediaPlayer* player;   //播放器
QVideoWidget* widget;   //视频播放控件

//播放状态,true为播放,false为暂停
bool play_state;
//是否重新载入视频
bool if_reload=false;

在载入ui时,先禁用播放/暂停按钮和音量按钮:

MediaPlayer::MediaPlayer(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MediaPlayer)
{
    ui->setupUi(this);
    ui->pushButton_play_and_pause->setEnabled(false);
    ui->pushButton_volume->setEnabled(false);
}

在Designer中右击“打开”pushButton控件,“转到槽”->”clicked()”->”ok”,槽函数如下:

void MediaPlayer::on_pushButton_open_file_clicked()
{
    //选择视频文件
    QString filename = QFileDialog::getOpenFileName(this,tr("选择视频文件"),".",tr("视频格式(*.avi *.mp4 *.flv *.mkv)"));
    QFile file(filename);
    if(!file.open(QIODevice::ReadOnly))
    {
        QMessageBox::information(NULL, "Title", "Content", QMessageBox::Ok, QMessageBox::Ok);
        return;
    }
    if(if_reload)
    {//重新加载视频时,重置以下变量
        delete layout_video;
        delete player;
        delete widget;
        delete timer;
    }
    if_reload = true;

    //实例化三个全局变量
    layout_video = new QVBoxLayout;
    player = new QMediaPlayer;
    widget = new QVideoWidget;
    //设置视频播放区域与Label的边距
    layout_video->setMargin(1);
    //根据label_player尺寸设置播放区域
    widget->resize(ui->label_player->size());
    layout_video->addWidget(widget);
    ui->label_player->setLayout(layout_video);
    player->setVideoOutput(widget);

    //设置播放器
    player->setMedia(QUrl::fromLocalFile(filename));
    //play_state为true表示播放,false表示暂停
    play_state = true;
    //启用播放/暂停按钮,并将其文本设置为“暂停”
    ui->pushButton_play_and_pause->setEnabled(true);
    ui->pushButton_play_and_pause->setText("暂停");
    //播放器开启
    player->play();
}

到这一步,就已经可以播放视频了。接下来我们添加“播放/暂停”按钮的槽函数:

void MediaPlayer::on_pushButton_play_and_pause_clicked()
{
    //反转播放状态
    if(play_state)
    {
        player->pause();
        ui->pushButton_play_and_pause->setText("播放");
    }
    else
    {
        player->play();
        ui->pushButton_play_and_pause->setText("暂停");
    }

    play_state = !play_state;
}

好了,打开测试一下: 

Qt实现进度条拖动视频回放_第6张图片

如果播放时出现只有声音而没有画面的情况,那就是解码器的问题。在写这个程序时找了久的原因,最后在这篇博文里找到了解决方案: [ CN小黑 极客人生] 推荐安装K-Lite解码器。至此,基于QMediaPlayer的核心播放功能已经编写完成,接下来我们通过QSlider对播放进度和音量进行控制。

3、结合QSlider实现播放进度控制和音量控制

3.1 进度控制

QSlider类继承自QAbstractSlider类,可以参考 [ 官方文档 ],其自带的信号如下:

  • valueChanged():当进度值改变时触发
  • sliderPressed():当用户按下滑块时触发
  • sliderMoved():当用户拖动滑块时触发
  • sliderMoved():当用户释放滑块时触发

对于播放器来说,其进度条应该有两种控制方式,一是拖动,二是点击。 其中我们需要用到sliderMoved()和sliderReleased()两种信号来实现拖动功能,对于点击,QSlider的mousePressEvent()默认的方式是,点击之后跳跃一定的固定距离,无法实现“指哪打哪”,因此我们需要对mousePressEvent()进行重写。具体方法将在下一节中介绍。

首先在ui载入时将Slider禁用,等到文件载入时才启用。然后连接三组信号槽,第一组是由重载的mousePressEvent事件发送的,为避免混淆换了个costomSliderClicked的名字(下一节中有类的构造方法)。其余两组是QSlider自带的sliderMoved和sliderReleased信号。

MediaPlayer::MediaPlayer(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MediaPlayer)
{
    ui->setupUi(this);
    ui->pushButton_play_and_pause->setEnabled(false);
    ui->pushButton_volume->setEnabled(false);

    ui->slider_progress->setEnabled(false);
    connect(ui->slider_progress,&CustomSlider::costomSliderClicked,this,&MediaPlayer::slider_progress_clicked);
    connect(ui->slider_progress,&CustomSlider::sliderMoved,this,&MediaPlayer::slider_progress_moved);
    connect(ui->slider_progress,&CustomSlider::sliderReleased,this,&MediaPlayer::slider_progress_released);
}

首先我们需要配合定时器QTimer实现Slider随播放进度而移动。 之所以选择QTimer,而不是player的positionChanged信号来驱动,是因为positionChange和后面要用的QSlider两个信号槽互相修改,容易出现冲突。在mediaplayer.cpp中定义全局变量:

//与Slider有关的播放控制变量
QTimer * timer;
int maxValue = 1000;//设置进度条的最大值

在on_pushButton_open_file_clicked槽函数中添加Slider和Timer的相关代码,并将timer连接至onTimerOut槽函数:

void MediaPlayer::on_pushButton_open_file_clicked()
{
    //前面部分代码与第2篇中相同//
    //启用slider并设置范围
    ui->slider_progress->setEnabled(true);
    ui->slider_progress->setRange(0,maxValue);

    timer = new QTimer();
    timer->setInterval(1000);//如果想看起来流畅些,可以把时间间隔调小,如100ms
    timer->start();
    //将timer连接至onTimerOut槽函数
    connect(timer, SIGNAL(timeout()), this, SLOT(onTimerOut()));
}

添加定时器的槽函数onTimerOut(),其原理就是根据一定的间隔(本例中为1000ms)刷新Slider的值,这个值是根据播放器player的position(当前位置)和duration(总时长)计算出来的:

void MediaPlayer::onTimerOut()
{
    ui->slider_progress->setValue(player->position()*maxValue/player->duration());
}

到这里就实现了Slider随进度移动的功能,接下来添加控制代码。 三个槽函数分别对应单击、拖动和释放。在拖动过程中,可以先暂停计时器,等用户拖动完成释放之后,再重启定时器。这样防止用户在拖动过程中滑块依然按照定时器触发进行移动,瞎跳,闹心。

void MediaPlayer::slider_progress_clicked()
{
    player->setPosition(ui->slider_progress->value()*player->duration()/maxValue);
}

void MediaPlayer::slider_progress_moved()
{
    //暂时停止计时器,在用户拖动过程中不修改slider的值
    timer->stop();
    player->setPosition(ui->slider_progress->value()*player->duration()/maxValue);
}

void MediaPlayer::slider_progress_released()
{
    //用户释放滑块后,重启定时器
    timer->start();
}

在mediaplayer.h中添加槽函数的声明

private slots:

    void on_pushButton_open_file_clicked();
    void on_pushButton_play_and_pause_clicked();
    void onTimerOut();
    void slider_progress_clicked();
    void slider_progress_moved();
    void slider_progress_released();

3.2 音量控制

音量控制的方法与进度控制非常相似,可以直接使用重载后的CustomSlider类。这里我们不在Designer中拖入控件,而是通过手动实现的方式来添加音量控制的Slider。首先在mediaplayer.cpp的构造函数中继续添加初始化内容

MediaPlayer::MediaPlayer(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MediaPlayer)
{
    /*与上述相同*/
    /*
        ……
    */
    //手动设置slider_volume 包括初始化 方向 禁用,以及槽函数
    slider_volume = new CustomSlider(this);
    slider_volume->setOrientation(Qt::Vertical);
    slider_volume->setEnabled(false);
    slider_volume->hide();
    //由于不涉及到slider值的刷新,因此只需对move和自定义click两个信号进行处理,并且可以共用一个槽函数
    connect(slider_volume,&CustomSlider::costomSliderClicked,this,&MediaPlayer::slider_volume_changed);
    connect(slider_volume,&CustomSlider::sliderMoved,this,&MediaPlayer::slider_volume_changed);

}

槽函数

//音量控制Slider的槽函数
void MediaPlayer::slider_volume_changed()
{
    player->setVolume(slider_volume->value());
}

音量控制按钮的槽函数,通过hide()和show()方法,实现音量控制Slider的唤出和收起

bool state_slider_volume = false;
void MediaPlayer::on_pushButton_volume_clicked()
{
    if(state_slider_volume)
    {
        slider_volume->hide();
    }
    else
    {
        slider_volume->setValue(player->volume());
        //计算位置,使其位于音量控制按钮的上方
        slider_volume->setGeometry(QRect(ui->pushButton_volume->pos().rx()+0.5*ui->pushButton_volume->width()-15, ui->pushButton_volume->y()-100 , 30, 102));
        slider_volume->show();
    }
    state_slider_volume = !state_slider_volume;
}

在mediaplayer.h中继续添加槽函数的声明

private slots:

    void slider_volume_changed();
    void on_pushButton_volume_clicked();

private:

    CustomSlider *slider_volume;

到这里,QSlider的移动与控制功能就已经完成了,但是实际上我们需要重载QSlider的mousePressEvent以实现指哪打哪的效果,因此需要继承一个新类CustomSlider,详情见下一节。

 4、重载QSlider鼠标响应事件,实现单击跳转至任意位置

前面提到,对于点击,QSlider的mousePressEvent()默认的方式是,点击之后跳跃一定的固定距离,无法实现“指哪打哪”。 
想要实现单击跳转至任意位置,有两种方案: 

  1. 重载mouseMovedEvent事件; 
  2. 使用事件过滤器eventFilter。 

对于后者掌握还不太熟练,而且项目中后续需要用到多个Slider,封装成子类来用会比较方便。所以还是根据 [ 冬南风 的博客 ]通过重载来完成这一功能。与原博稍有区别,原博采用的方式是重载mouseMovedEvent之后,向父窗口发送自定义事件event type,这样就可以在父窗口中捕获这个事件进行处理。 

我的选择是自定义了一个costomSliderClicked信号,在mouseMovedEvent执行时将该信号发出,然后统一用槽函数处理。槽函数的具体写法在上一篇中已经做了介绍。

第一步是给QSlider添加一个子类。在项目窗口中,右击工程的根目录,添加新文件,选择C++ Class,填写基类为QSlider。我这里将其命名为CustomSlider。

customslider.h

#ifndef CUSTOMSLIDER_H
#define CUSTOMSLIDER_H
#include 
#include 
#include 

class CustomSlider : public QSlider
{
    Q_OBJECT
public:
    CustomSlider(QWidget *parent = 0) : QSlider(parent)
    {
    }
protected:
    void mousePressEvent(QMouseEvent *ev);//重写QSlider的mousePressEvent事件
signals:
    void costomSliderClicked();//自定义的鼠标单击信号,用于捕获并处理
};

#endif // CUSTOMSLIDER_H

customslider.cpp

#include "customslider.h"

void CustomSlider::mousePressEvent(QMouseEvent *ev)
{
    //注意应先调用父类的鼠标点击处理事件,这样可以不影响拖动的情况
    QSlider::mousePressEvent(ev);
    //获取鼠标的位置,这里并不能直接从ev中取值(因为如果是拖动的话,鼠标开始点击的位置没有意义了)
    double pos = ev->pos().x() / (double)width();
    setValue(pos * (maximum() - minimum()) + minimum());
    //发送自定义的鼠标单击信号
    emit costomSliderClicked();
}

然后,打开Designer,右击slider,选择“提升为”

Qt实现进度条拖动视频回放_第7张图片

填写继承的子类名,勾选 

Qt实现进度条拖动视频回放_第8张图片

然后在mediaplayer中加入子类的头文件customslider.h,在上一节中已经介绍了信号槽的连接与槽函数的写法。至此,一个基于QMediaPlayer的简易视频播放器就完成了,包括了视频播放、进度控制和音量控制等基本功能,并且能很好地支持绝大多数的常见格式。(音量的效果最终没有出来,还需查找一下原因)

你可能感兴趣的:(Qt)