雖然之前介紹了 libVLC 的工作流程,但只能實現簡單的播放。與真正的媒體播放器相比,還相差甚遠,因為它連一些基本的控制都沒有,像播放/暫停、停止、跳播、快進/快退、音量調節、靜音等。
為了讓我們的播放器更加專業一些,現在是時候加上這些功能了!
包裝器
為了和 UI 分離,需要單獨定義一個 Player 類,作為 libVLC 的一個包裝器,它的主要作用是提供基本的媒體播放控制功能!
將上述所描述的接口添加進來,同時,再定義一些對應的信號,當有事件發生時,進行通知:
class Player : public QObject
{
Q_OBJECT
public:
// 播放狀態
typedef enum State {
Idle,
Opening,
Buffering,
Playing,
Paused,
Stopped,
Ended,
Error
} State;
explicit Player(QObject *parent = nullptr);
~Player();
// 設置視頻輸出窗口
void setVideoWindow(QWidget *window);
// 獲取當前狀態
Player::State state();
Q_SIGNALS:
// 總時長發生變化
void durationChanged(qint64 dur);
// 當前時間發生變化
void timeChanged(qint64 time);
// 播放位置發生變化
void positionChanged(float pos);
// 狀態發生變化
void stateChanged(Player::State state);
public Q_SLOTS:
// 打開文件
void openFile(const QString &file);
// 設置音量
void setVolume(int vol);
// 跳播
void seek(int pos);
// 播放
void play();
// 暫停
void pause();
// 停止
void stop();
private:
// 訂閱事件
void attachEvents();
private:
libvlc_instance_t *m_instance {nullptr};
libvlc_media_player_t *m_player {nullptr};
libvlc_media_t *m_media {nullptr};
libvlc_event_manager_t *m_eventManager {nullptr};
QWidget *m_videoWindow {nullptr};
};
接口較多,挑一些核心的介紹一下。
訂閱事件
這一步很關鍵,因為要實時獲取媒體信息的話,必須訂閱相關事件。比如,播放時間發生變化時,需要更新 UI 上的當前時間,這時就需要監聽 libvlc_MediaPlayerTimeChanged 事件。
有關播放狀態、音量、是否靜音、媒體時長等相關的事件為以下幾個:
void Player::attachEvents()
{
// 事件列表
QList<libvlc_event_e> events;
events << libvlc_MediaPlayerOpening
<< libvlc_MediaPlayerBuffering
<< libvlc_MediaPlayerPlaying
<< libvlc_MediaPlayerPaused
<< libvlc_MediaPlayerStopped
<< libvlc_MediaPlayerEncounteredError
<< libvlc_MediaPlayerMuted
<< libvlc_MediaPlayerUnmuted
<< libvlc_MediaPlayerAudioVolume
<< libvlc_MediaPlayerLengthChanged
<< libvlc_MediaPlayerTimeChanged
<< libvlc_MediaPlayerPositionChanged;
// 訂閱事件
foreach (const libvlc_event_e &e, events) {
libvlc_event_attach(m_eventManager, e, handleEvents, this);
}
}
緊接著,需要在回調函數中對這些事件進行處理,可以將這些信息保存起來,也可以通過信號的形式發射出去:
// 回調函數,用於事件處理
static void handleEvents(const libvlc_event_t *event, void *userData)
{
Player *player = static_cast<Player *>(userData);
switch (event->type) {
// 播放狀態改變
case libvlc_MediaPlayerOpening:
case libvlc_MediaPlayerBuffering:
break;
case libvlc_MediaPlayerPlaying: {
emit player->stateChanged(Player::Playing);
break;
}
case libvlc_MediaPlayerPaused: {
emit player->stateChanged(Player::Paused);
break;
}
case libvlc_MediaPlayerStopped: {
emit player->stateChanged(Player::Stopped);
break;
}
case libvlc_MediaPlayerEncounteredError: {
emit player->stateChanged(Player::Error);
break;
}
// 時長改變
case libvlc_MediaPlayerLengthChanged: {
qint64 dur = event->u.media_player_length_changed.new_length;
emit player->durationChanged(dur);
break;
}
// 播放時間改變
case libvlc_MediaPlayerTimeChanged: {
qint64 time = event->u.media_player_time_changed.new_time;
emit player->timeChanged(time);
break;
}
// 播放位置改變
case libvlc_MediaPlayerPositionChanged: {
float pos = event->u.media_player_position_changed.new_position;
emit player->positionChanged(pos);
break;
}
default:
break;
}
}
播放控制
當然了,最主要的是控制入口,這是外部調用的主要方式。
對於播放來說,需要判斷一下當前的狀態;如果當前處於暫停狀態,則恢復播放;否則,直接進行播放:
void Player::play()
{
WId curWId = 0;
if (nullptr != m_videoWindow)
curWId = m_videoWindow->winId();
// 指定輸出窗口
#if defined (Q_OS_WIN)
libvlc_media_player_set_hwnd(m_player, (void*)curWId);
#elif defined(Q_OS_MAC)
libvlc_media_player_set_nsobject(m_player, (void *)curWId);
#else
libvlc_media_player_set_xwindow(m_player, curWId);
#endif
// 恢復或者播放
if (state() == Player::Paused) {
libvlc_media_player_set_pause(m_player, false);
} else {
libvlc_media_player_play(m_player);
}
}
和上面的恢復播放一樣,暫停也使用的是 libvlc_media_player_set_pause() 接口,只不過第二個參數為 true:
void Player::pause()
{
if (libvlc_media_player_can_pause(m_player))
libvlc_media_player_set_pause(m_player, true);
}
如果想停止播放,直接使用 libvlc_media_player_stop() 即可,比較簡單:
void Player::stop()
{
libvlc_media_player_stop(m_player);
}
在設置音量時,需要注意一下, libvlc_audio_set_volume() 的第二個參數是指音量的百分比。如果為 0,則表示靜音;如果是 100,則是 0dB:
void Player::setVolume(int vol)
{
libvlc_audio_set_volume(m_player, vol);
}
跳播相對來說複雜一些,因為需要將位置和時長對應起來。跳播的實現方式有兩種,任選一種都可以:
以方式一為例,假設我們的播放滑塊取值範圍是 0 - 100,那麼 float(pos)/100 就是 pos 所在位置佔總範圍的百分比。將這個結果乘以總時長 float(duration),算出的就是 pos 位置對應的時間。有了這個時間,就可以使用 libvlc_media_player_set_time() 進行跳播了:
void Player::seek(int pos)
{
libvlc_media_t *curMedia = libvlc_media_player_get_media(m_player);
if (nullptr == curMedia)
return;
libvlc_time_t duration = libvlc_media_get_duration(curMedia);
float ms = float(pos)/100 * float(duration);
libvlc_media_player_set_time(m_player, libvlc_time_t(ms));
}
大概的功能先介紹到這裡,後面的更精彩,敬請期待吧!
點個在看,麼麼噠!