基于QMediaPlayerQt+数据库实现的视频播放器+实现了抓取帧的操作
预览
功能
基本功能
- 实现本地视频的播放
- 实现文件列表,显示添加到播放器的视频文件
- 实现收藏列表,可以点击收藏按钮可以收藏指定视频
- 实现收藏列表,可以显示收藏的视频
- 播放,暂停,继续播放,倍速,重新加载,删除该视频
- 设计抓取按钮,播放过程中可以抓取当前视频的帧图像,并自动保存该图像相关信息
- 设计进度条,实时显示以播放时长与视频总时长
- 数据库管理数据
具体设计
1. 存储视频信息的数据库设计
- SQLite 是一个轻量级的本地数据库,它非常适合小型应用的开发
视频名称
视频地址
视频是否被收藏
2. 选择合适的视频解码器
- Qt 中的多媒体播放,底层是使用DirectShowPlayerService,所以安装一个DirectShow解码器,例如LAV Filters,或者k-lite解码器,就可以解决运行出错问题
-
第一次选择LAV (卡顿, 花屏)
-
第二次选择K-lite(较好)
-
两款软件只能二选一,推荐选择:K-Lite。
原因:两款软件均采用Lav 算法,但LAV Filters 软件效果较差,总容易出现花屏现象。卸载LAV Filters,再安装K-Lite后,问题解决,视频流利播放。就实际使用来说k-lite更流畅,不容易出现花屏与卡死
3. ui设计
-
播放视频列表窗口
- 所有视频列表
- 收藏视频列表
-
播放窗口
-
按钮组窗口
-
进度条窗口
4. 添加文件
5. 添加文件到数据库
- 数据库表格式:文件名称 + 文件地址 + 是否收藏
5. 视频列表窗口
- 将数据库中的文件显示到视频列表中
6. 从文件列表中播放视频
- 双击视频列表中的视频文件,将会自动播放视频
7. 播放与暂停
- 相互切换
8. 删除视频
- 删除所有视频列表中的视频,同时在数据库中删除
- 取消收藏收藏列表中的某些视频
9. 倍速
- 调整视频播放速度(点击按钮视频加速)
10. 抓取当前播放视频的帧图像
-
点击该功能按钮的时候,获取当前播放视频的当前帧的图片
-
实现方法:
- 通过继承虚基类
QAbstractVideoSurface
,子类中实现present
和supportedPixelFormats
方法 - 当点击抓帧按钮的时候,切换当前视频输出流到
VedioSurface
对象中,获取present
发出信号表示获取到当前帧 - 主函数中保存当前帧的图像及相关信息
11. 进度条
-
声音
-
进度
同时还要显示视频总时间与剩余时间:
- 进度条右边设置两个文本框,一个用来显示当前时间,一个用来显示总视频时长
12. 收藏按钮
- 点击按钮将当前视频对应数据项修改为收藏,表示收藏该视频,在收藏列表中显示
gitee地址
部分代码
#include "widget.h"
#include "ui_widget.h"
#include <QDebug>
#include <QFileDialog>
#include <QTime>
#include <QVideoFrame>
#include <QAbstractVideoSurface>
#include <qabstractvideobuffer.h>
#include <QPainter>
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
// 实例化
my_video_player = new QMediaPlayer;
my_video_list = new QMediaPlaylist;
my_video_widget = new QVideoWidget(ui->play_label);
my_videosurface = new VideoSurface;
// 设置播放者要播放的播放列表以及播放到的窗口
my_video_player->setPlaylist(my_video_list);
my_video_player->setVideoOutput(my_video_widget);
// 设置播放窗口的大小
my_video_widget->resize(ui->play_label->size());
// 设置初始音量
my_video_player->setVolume(50);
ui->music_horizontalSlider_2->setValue(50);
// 设置视频列表与收藏列表
ui->viedo_tabWidget->setTabText(0, "所有视频");
ui->viedo_tabWidget->setTabText(1, "我的收藏");
ui->viedo_tabWidget->setCurrentIndex(0);
setWindowTitle("视频播放器");
resize(1024, 768);
// 美化视频进度条
ui->progress_horizontalSlider->setStyleSheet("QSlider::groove:horizontal { \
height: 8px; \
background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, \
stop: 0 rgb(124, 124, 124), \
stop: 1.0 rgb(72, 71, 71));} \
QSlider::handle:horizontal { \
border-image: url(:/image/飞船.png); \
width: 40px; \
height:40px; \
margin: -8px px -8px -3px;} \
QSlider::sub-page:horizontal{ \
background:rgba(255, 85, 127,1);} \
");
// 添加数据库
// SQLite 是一个轻量级的本地数据库,它非常适合小型应用的开发
db = QSqlDatabase::addDatabase("QSQLITE"); //添加sqlite驱动
db.setDatabaseName("myVediodatabase.db"); // 设置表名
// 打开数据库连接,执行数据库操作,添加属性是否收藏了该视频
db.open();
QString mlist = "create table mlist(filename text,path text,loved bool);";
db.exec(mlist); // 创建数据库表
QString sql = "select * from mlist;";
QSqlQuery query;
if(query.exec(sql)){
qDebug()<<"显示列表";
}
else{
qDebug()<<"显示失败";
}
// 默认添加数据库中的数据信息到视频列表中,同时将数据库数据添加到播放列表中
while(query.next()){
QString file_name = QString(query.value("filename").toString() ); //转换类型
QString path = QString( query.value("path").toString() ); //转换类型
bool loved = bool(query.value("loved").toBool());
// 如果该视频被收藏同时 添加到收藏列表
if (loved){
ui->love_listWidget_2->addItem(file_name);
}
ui->videoslistWidget->addItem(file_name);
my_video_list->addMedia(QUrl(path));
}
// 播放收藏列表中的视频
connect(ui->love_listWidget_2, &QListWidget::doubleClicked, this, [&](){
is_start = true;
QString filename;
//选择文件播放
QListWidgetItem* item = ui->love_listWidget_2->currentItem(); //获取当前item
filename = item->text();
now_play_vedio = filename;
// 查询该文件是否在数据库中
QString sql = QString("select * from mlist where filename='%1';").arg(filename);
QSqlQuery query;
// 查询该视频数据是否在数据库中
if(query.exec(sql)){
qDebug()<<"查询成功";
}
else {
qDebug()<<"查询失败";
}
if(query.next()){
qDebug()<<"登录成功";
filename = query.value("path").toString(); // 找到的文件的路径
}
else{
qDebug()<<"登录失败";
}
// 播放者播放的文件设置为当前查询到的文件
my_video_player->setMedia(QUrl(filename));
qDebug()<<"name:"<<filename;
// 播放视频
my_video_player->play();
});
// 如果双击视频列表中的某一个视频,直接播放该视频
connect(ui->videoslistWidget, &QListWidget::doubleClicked, this, [&](){
QString filename;
//选择文件播放
QListWidgetItem* item = ui->videoslistWidget->currentItem(); //获取当前item
filename = item->text();
now_play_vedio = filename;
// 查询该文件是否在数据库中
QString sql = QString("select * from mlist where filename='%1';").arg(filename);
QSqlQuery query;
// 查询该视频数据是否在数据库中
if(query.exec(sql)){
qDebug()<<"查询成功";
}
else {
qDebug()<<"查询失败";
}
if(query.next()){
qDebug()<<"登录成功";
filename = query.value("path").toString(); // 找到的文件的路径
}
else{
qDebug()<<"登录失败";
}
// 播放者播放的文件设置为当前查询到的文件
my_video_player->setMedia(QUrl(filename));
qDebug()<<"name:"<<filename;
// 播放视频
is_start = true;
ui->play_pause_pushButton->setStyleSheet("QPushButton#play_pause_pushButton{border-image:url"
"(:/image/暂停.png)}");
my_video_player->play();
});
// 视频进度改变,进度条改变
connect(my_video_player, &QMediaPlayer::positionChanged, this, &Widget::updatePosition);
// 切换到其他视频时,进度条长度改变,同时视频总时长也改变
connect(my_video_player, &QMediaPlayer::durationChanged, this, &Widget::updateDuration);
}
Widget::~Widget()
{
delete ui;
}
void Widget::paintEvent(QPaintEvent *event){
my_video_widget->resize(ui->play_label->size());
// 加载背景
QPainter painter(this);
QPixmap pix;
pix.load(":/image/background1.jpeg");
painter.setOpacity(0.7);//透明度设置
painter.drawPixmap(0,0,this->width(),this->height(),pix);
}
void Widget::on_openFile_pushButton_clicked()
{
/* 打开文件功能,获取要打开的视频列表 */
QString curPash = "D:/桌面";
QString dlgTitle="选择视频文件";
QString filter="mp4文件(*.mp4);;mp3文件(*.mp3);;flv文件(*.flv);;avi文件(*.avi)";
QStringList fileList = QFileDialog::getOpenFileNames(this,dlgTitle,curPash,filter);
qDebug() << fileList;
// 把导入的文件加入到显示视频列表的框中
db.open();
for(int i=0; i<fileList.count(); i++)
{
//将选择的文件显示在文本框上
QString path = fileList.at(i);
QString name = path.mid(path.lastIndexOf("/")+1,path.lastIndexOf(".")-1-path.lastIndexOf("/"));
// 防止同一个视频在数据库中添加多次
QSqlQuery query;
QString sql_find_vedio = QString("select * from mlist where filename='%1';").arg(name);
query.exec(sql_find_vedio);
if(query.next()){ // 判断视频数据库中是否存在该视频
qDebug()<<"数据库已经存在该数据";
continue;
}
// 向数据库插入数据
QString sql = QString("insert into mlist values('%1','%2','%3');").arg(name).arg(path).arg(false);
qDebug()<<sql;
if (query.exec(sql)){
qDebug() << "插入数据库成功";
}
else{
qDebug()<<"插入数据库失败";
continue;
}
ui->videoslistWidget->addItem(name);
}
}
void Widget::on_play_pause_pushButton_clicked()
{
/* 视频播放与暂停 */
// 如果当前视频正在播放,点击后会暂停
if (is_start){
my_video_player->pause();
is_start = false;
// 变换图标
ui->play_pause_pushButton->setStyleSheet("QPushButton#play_pause_pushButton{border-image:url"
"(:/image/播放.png)}");
}
else{
my_video_player->play();
is_start = true;
ui->play_pause_pushButton->setStyleSheet("QPushButton#play_pause_pushButton{border-image:url"
"(:/image/暂停.png)}");
}
}
void Widget::on_stop_pushButton_clicked()
{
/* 重写加载视频 */
my_video_player->stop();
my_video_player->play();
}
void Widget::onShowImage(QVideoFrame frame)
{
/* 获取当前帧的槽函数 */
// 保存图片
my_video_player->pause();
// 将视频帧数据映射到内存空间
frame.map(QAbstractVideoBuffer::ReadOnly);
QImage image(
frame.bits(),
frame.width(),
frame.height(),
frame.bytesPerLine(),
QVideoFrame::imageFormatFromPixelFormat(frame.pixelFormat())
);
QTime nowtime;
nowtime = QTime::currentTime();
QString savepixname = "D:\\桌面\\get_images\\" + nowtime.toString("hh-mm-ss-zzz") + ".jpg";
image.save(savepixname);
frame.unmap();
// 获取当前播放位置
my_video_player->setPosition(my_video_player->position());
my_video_player->setVideoOutput(my_video_widget);
// 断开本次链接
disconnect(my_videosurface, &VideoSurface::showImage, this, &Widget::onShowImage);
my_video_player->play();
}
void Widget::on_getimage_pushButton_clicked()
{
/* 抓取当前视频的帧图像 */
// 点击截屏按钮的时候,将my_Mediaplayer流输出到vediosurface中,用于截屏保存
//qint64 m_nPausePos = my_video_player->position();
connect(my_videosurface, &VideoSurface::showImage, this, &Widget::onShowImage);
// 暂停当前播放视频保存视频位置
my_video_player->pause();
qint64 now_position = my_video_player->position();
// 修改输出设备
my_video_player->setVideoOutput(my_videosurface); // 将输出设备设置为vediosurface
my_video_player->setPosition(now_position);
my_video_player->play();
}
void Widget::on_del_pushButton_3_clicked()
{
my_video_player->stop();
ui->play_pause_pushButton->setStyleSheet("QPushButton#play_pause_pushButton{border-image:url"
"(:/image/播放.png)}");
QString filename;
QString sql;
QSqlQuery query;
/* 判断当前在视频列表页还是收藏列表列,删除对应的视频 */
if (ui->viedo_tabWidget->currentIndex() == 0){
QListWidgetItem* item = ui->videoslistWidget->currentItem(); //获取当前item
filename = item->text();
sql = QString("delete from mlist where filename='%1';").arg(filename);
if(query.exec(sql)){
qDebug()<<"删除成功";
}
else {
qDebug()<<"删除失败";
}
delete item;
}
// 删除收藏的视频,但是该视频还在数据库中(取消收藏)
else if (ui->viedo_tabWidget->currentIndex() == 1){
QListWidgetItem* item = ui->love_listWidget_2->currentItem(); //获取当前item
QSqlQuery query;
QString sql_find_vedio = QString("UPDATE mlist SET loved='%1' WHERE filename='%2';").arg(false).arg(item->text());
query.exec(sql_find_vedio);
delete item;
}
}
void Widget::on_music_horizontalSlider_2_valueChanged(int value)
{
/* 改变声音大小 */
my_video_player->setVolume(value);
}
void Widget::on_progress_horizontalSlider_valueChanged(int value)
{
/* 拖动进度条,视频进度改变 */
my_video_player->setPosition(qint64(value));
}
void Widget::updateDuration(qint64 duration)
{
/* 更新当前视频进度 */
ui->totaltime_label->setText(QString("%1:%2").arg(duration/1000/60,2,10,QChar('0')).arg(duration/1000%60));
ui->progress_horizontalSlider->setRange(0, duration);
ui->progress_horizontalSlider->setEnabled(duration > 0);
ui->progress_horizontalSlider->setPageStep(duration / 10);
}
void Widget::updatePosition(qint64 position)
{
ui->progress_horizontalSlider->setValue(position);
// 设置当前时间
QTime duration(0, position / 60000, qRound((position % 60000) / 1000.0));
ui->curtime_label->setText(duration.toString(tr("mm:ss")) + "/ ");
}
void Widget::on_love_pushButton_clicked()
{
/* 点击按钮收藏喜欢的视频 */
// 点击收藏按钮可以收藏当前播放的视频,并且在收藏列表中添加
// 在数据库中查找该视频对应项,修改为喜欢该视频,然后添加到收藏列表中
QSqlQuery query;
QString sql_find_vedio = QString("UPDATE mlist SET loved='%1' WHERE filename='%2';").arg(true).arg(now_play_vedio);
query.exec(sql_find_vedio);
ui->love_listWidget_2->addItem(now_play_vedio);
}
void Widget::on_speed_pushButton_clicked()
{
/* 点击按钮视频二倍速播放,再次点击视频原速播放 */
if (is_speed){
ui->speed_pushButton->setStyleSheet("QPushButton#speed_pushButton{border-image:url"
"(:/image/加速.png)}");
my_video_player->setPlaybackRate(1);
is_speed = false;
}
else{
ui->speed_pushButton->setStyleSheet("QPushButton#speed_pushButton{border-image:url"
"(:/image/减速.png)}");
my_video_player->setPlaybackRate(2);
is_speed = true;
}
}
改进
-
美化ui
进度条美化
-
抓取帧后将数据信息保存完整,保存到一个指定文件夹中
-
bug
- 删除视频列表中视频后收藏列表中还有该视频