前言
我已经有四五天没有发布文章了,趁着这个周末有空,就又开始构思我们自己的QT组件库中的新组件,思考还有哪些有用、有趣、值得研究学习并构建实现的组件,于是又有了两个新的目标,即多媒体播放组件和地图组件。之所以准备实现这两个小组件,是因为现在流行的很多软件应用中几乎都能用得到音频视频播放和地图导航或定位功能。所以我们在自己的软件开发中如果也遇到类似的需求,能有一个使用方便、快捷灵活的多媒体或地图组件,肯定能够节省我们很多的工作量和精力,QT虽然有相关的类但是没有相关的组件,如是我就开始了自己的实现。
功能展示
今天先跟大家分享一下我自己实现的多媒体播放组件,使用该组件可以很方便的用来播放音乐和视频。而且所有功能完全是基于QT自带的QMediaPlayer类和QVideoWidget类进行实现,没有使用任何第三方库,所以上手也比较容易。目前已实现的基本功能有:
- 音乐/视频播放、暂停功能
- 时长和进度显示
- 音量调节和静音功能
- 播放进度调整,支持时间轴点选、拖拉和键盘控制
- 全屏
因为录制的gif没有声音,所以我只展示视频播放功能,具体实现效果如下方动图所示:
播放、暂停功能,点击按钮或视频窗口都能实现该功能:
进度调节和音量调节:
静音:
全屏:
实现方法
1、怎么使用QMediaPlayer类播放音乐和视频?
播放音乐只需要设置好源即可,源既可以是本地路径也可以是网址,如果播放使用本地文件的话,需要使用
QUrl::fromLocalFile函数,方法如下:
m_pPlayer = new QMediaPlayer;m_pPlayer->setMedia(QMediaContent(QUrl::fromLocalFile(strPath)));m_pPlayer->play();
播放视频的话还需要设置视频输出位置,所以QMediaPlayer通常需要连接一个QVideoWidget来进行视频的播放,方法如下:
m_pPlayer = new QMediaPlayer;m_pVideoWidget = new QVideoWidget;m_pPlayer->setMedia(QMediaContent(QUrl::fromLocalFile(strPath)));m_pPlayer->setVideoOutput(m_pVideoWidget);m_pVideoWidget->show();
2、怎么显示视频总时长和当前播放进度?
总时长可以通过m_pPlayer->duration()获取,当前播放进度可以通过m_pPlayer->position()获取,这两个函数返回的都是毫秒数,我们只需要将该毫秒数转换为时间并显示到标签上即可,我为了这两个值能够与时间轴对应起来,所以我都是先转换成秒,然后再对秒进行处理:
//将秒数转换为HH:mm::ss格式QString MediaPlayer::changeSecondsToStr(int nMesc){ m_nHour = nSeconds / 3600; nSeconds %= 3600; m_nMinute = nSeconds / 60; nSeconds %= 60; m_nSecond = nSeconds; return QString("%1:%2:%3").arg(m_nHour, 2, 10, QLatin1Char('0')).arg(m_nMinute, 2, 10, QLatin1Char('0')).arg(m_nSecond, 2, 10, QLatin1Char('0'));}
为了能够实时更新当前的播放进度,我设置了一个定时器,每50ms检查一次当前秒数是否变化并对界面进行刷新:
//定时器超时处理void MediaPlayer::onTimerOut(){ if (m_pSlider->sliderPosition() != m_nLastPosition) { //之所以会出现这种情况,是因为触发了键盘事件,比如用户按下了方向键、PgUp、PgDn、Home、End m_nLastPosition = m_pSlider->sliderPosition(); m_pSlider->setValue(m_nLastPosition); m_pPlayer->setPosition(m_nLastPosition * 1000); setPlayTime(m_nLastPosition); return; } int nCurPosition = m_pPlayer->position() / 1000; if (m_nLastPosition != nCurPosition) { m_nLastPosition = nCurPosition; //检查是否播放完毕 if (nCurPosition >= m_pPlayer->duration() / 1000) play(); m_pSlider->setValue(nCurPosition); setPlayTime(nCurPosition); }}
3、如何使用QSlider对视频进行进行展示和控制?
展示功能是很好实现的,只需要设置好QSlider的范围并在定时器中setValue即可,QSlider本身也支持拖动控制,但是当我自定义了QSlider上滑块的样式后,发现拖动滑块不是很灵敏且经常无法选中滑块,且QSlider本身并不支持点击改变当前值,因此我对QSlider添加了事件过滤器,并自己处理了它的鼠标点击事件和鼠标移动事件。
m_pSlider = new QSlider(Qt::Horizontal, this); m_pSlider->setObjectName("m_pSlider"); m_pSlider->setCursor(Qt::PointingHandCursor); m_pSlider->setSingleStep(1); m_pSlider->setPageStep(10); m_pSlider->installEventFilter(this); m_pSlider->setEnabled(false);
bool MediaPlayer::eventFilter(QObject *watched, QEvent *event){ //进度滑动条 if(watched == m_pSlider) { if (event->type() == QEvent::MouseButtonPress) { QMouseEvent *mouseEvent = static_cast(event); if (mouseEvent->button() == Qt::LeftButton)//判断左键 { QSlider* pSlider = (QSlider*)watched; int dur = pSlider->maximum() - pSlider->minimum(); int pos = pSlider->minimum() + dur * ((double)mouseEvent->x() / pSlider->width()); if (pos != pSlider->sliderPosition()) { //if (pSlider == m_pSlider) { if (m_pSlider->isEnabled()) { m_pSlider->setValue(pos); onSliderPressed(); onSliderMoved(pos); onSliderReleased(); return true; } } } } } else if (event->type() == QEvent::MouseMove) { QMouseEvent *mouseEvent = static_cast(event); //if (mouseEvent->button() == Qt::LeftButton)//判断左键 { QSlider* pSlider = (QSlider*)watched; int dur = pSlider->maximum() - pSlider->minimum(); int pos = pSlider->minimum() + dur * ((double)mouseEvent->x() / pSlider->width()); //防止超出范围 if (pos > pSlider->maximum()) pos = pSlider->maximum(); if (pos < pSlider->minimum()) pos = pSlider->minimum(); if (pos != pSlider->sliderPosition()) { //if (pSlider == m_pSlider) { if (m_pSlider->isEnabled()) { m_pSlider->setValue(pos); onSliderPressed(); onSliderMoved(pos); onSliderReleased(); return true; } } } } } } return QObject::eventFilter(watched, event);}
如果在视频播放过程中改变进度的话,定时器还在运行,可能也会同时修改视频的进度,所以在用m_pSlider改变播放进度时,需要先停止视频播放和定时器,这个操作我们通过管理QSlider的鼠标按下响应、鼠标移动响应和鼠标释放响应进行处理。当鼠标按下时停止视频播放和定时器,鼠标移动时修改视频进度,鼠标释放时恢复视频播放和重启定时器。
connect(m_pSlider, SIGNAL(sliderPressed()), this, SLOT(onSliderPressed()));connect(m_pSlider, SIGNAL(sliderMoved(int)), this, SLOT(onSliderMoved(int)));connect(m_pSlider, SIGNAL(sliderReleased()), this, SLOT(onSliderReleased()));
//滑动条鼠标点击响应void MediaPlayer::onSliderPressed(){ m_bIsLastPlay = m_bIsPlay;}//滑动条滑动响应void MediaPlayer::onSliderMoved(int nPosition){ Q_UNUSED(nPosition); //在拖动过程中暂停播放 if (m_bIsPlay) play(); if (m_nLastPosition != nPosition) { m_nLastPosition = nPosition; m_pSlider->setValue(m_nLastPosition); m_pPlayer->setPosition(m_nLastPosition * 1000); setPlayTime(m_nLastPosition); }}//滑动条鼠标释放响应void MediaPlayer::onSliderReleased(){ if (m_bIsLastPlay) play();}
4、如何实现音量控制和音量控制窗口的自动显示和隐藏?
音量修改也是使用滑动条进行控制,实现方法和改变播放进度类似,就不再细说。这里只说一下怎么控制音量窗口的自动出现和隐藏。当鼠标移动到音量按钮上方时,音量窗口自动显示,当鼠标移动到音量窗口上方时,音量窗口一直保持显示状态,当鼠标从音量窗口移出1s以后,音量窗口自动隐藏。了解这个过程以后,其实不难发现,还是需要对鼠标事件进行过滤和自定义处理。至于怎么使窗口延迟1s后隐藏,使用QTimer::singleShot进行单次触发最为方便合适。
bool MediaPlayer::eventFilter(QObject *watched, QEvent *event){ if (watched == m_pVolumeButton) //音量键 { if (event->type() == QEvent::Enter) { changeVolume(m_pPlayer->volume()); m_pVolumeSlider->setValue(m_pPlayer->volume()); //计算位置,使其位于音量控制按钮的上方 m_pVolumeWidget->setGeometry(QRect(m_pVolumeButton->pos().rx()+0.5*m_pVolumeButton->width()-m_pVolumeWidget->width()/2, m_pControlWidget->y()-110 , m_pVolumeWidget->width(), 100)); m_pVolumeWidget->show(); m_bVolume = true; } if (event->type() == QEvent::Leave) { m_bVolume = false; QTimer::singleShot(1000, this, SLOT(hideSlider())); } } else if (watched == m_pVolumeWidget) //音量窗口 { if (event->type() == QEvent::Enter) m_bVolume = true; else if(event->type() == QEvent::Leave) { m_bVolume = false; QTimer::singleShot(1000, this, SLOT(hideSlider())); } } return QObject::eventFilter(watched, event);}
5、如何实现实现静音和全屏显示?
当我们点击音量按钮的时候进行打开静音或关闭静音操作,当然静音时手动的设置音量为0也无可厚非,但是QMediaPlayer中也提高了相关接口,使用方法如下:
//打开或关闭静音void MediaPlayer::openOrCloseMute(){ if (m_pPlayer->isMuted()) m_pVolumeButton->setIcon(QIcon(":/Images/MediaPlayer_Icon/sound.png")); else m_pVolumeButton->setIcon(QIcon(":/Images/MediaPlayer_Icon/mute.png")); m_pPlayer->setMuted(!m_pPlayer->isMuted());
全屏方法也是调用接口,直接上代码:
//打开或关闭全屏显示void MediaPlayer::openOrCloseFullScreen(){ if (this->isFullScreen()) { m_pFullScreenButton->setIcon(QIcon(":/Images/MediaPlayer_Icon/showMax.png")); m_pFullScreenButton->setToolTip("进入全屏"); this->showNormal(); } else { m_pFullScreenButton->setIcon(QIcon(":/Images/MediaPlayer_Icon/showNormal.png")); m_pFullScreenButton->setToolTip("退出全屏"); this->showFullScreen(); }}
总结
其实整个实现过程也很简单,主要是QSlider的使用和对鼠标移动事件的处理,该组件的下方控制窗口也可以实现自动隐藏,实现方法和音量窗口类似。还可以集成之前我们自己做的弹幕组件,从而支持发送弹幕功能,由于时间关系,我就没有实现。该组件的使用方法也非常方便:
MediaPlayer *pMediaPlayer = new MediaPlayer;pMediaPlayer->show();//pMediaPlayer->playMusic("D:/CloudMusic/1.mp3"); //播放音乐pMediaPlayer->playVideo("C:/Users/金/Desktop/2.mp4"); //播放视频
源码大概有500行左右,这里就不再贴出来了,否则篇幅太长,主要功能实现,上面也都放了源码,如果有需要也可以评论留言,后期也会统一上传github。