PyQt6/Pyside6官方例程学习笔记——视频播放器(Player Example)
Posted on 2023-08-11 23:53 Karlf 阅读(596) 评论(0) 编辑 收藏 举报 来源官方例程连接
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
"""PySide6 Multimedia player example"""
import sys
from PySide6.QtCore import QStandardPaths, Qt, Slot
from PySide6.QtGui import QAction, QIcon, QKeySequence
from PySide6.QtWidgets import (QApplication, QDialog, QFileDialog,
QMainWindow, QSlider, QStyle, QToolBar)
from PySide6.QtMultimedia import (QAudioOutput, QMediaFormat,
QMediaPlayer)
from PySide6.QtMultimediaWidgets import QVideoWidget
引用的库
AVI = "video/x-msvideo" # AVI
MP4 = 'video/mp4'
全局变量
def get_supported_mime_types():
result = []
for f in QMediaFormat().supportedFileFormats(QMediaFormat.Decode):
mime_type = QMediaFormat(f).mimeType()
result.append(mime_type.name())
return result
全局函数
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
一般Qt项目都会设计这样一个MainWindow的类,方便调用。然后首先是init函数的声明,第一句有点让人摸不着头脑。super()是Python的一个内置函数,简单来讲作用就是允许调用父类的属性。
更多关于super()的介绍可以看这两篇文章
Python super(self.__class__, self).__init__() 怎样理解? - 知乎
self._playlist = [] # FIXME 6.3: Replace by QMediaPlaylist?
self._playlist_index = -1
self._audio_output = QAudioOutput()
self._player = QMediaPlayer()
self._player.setAudioOutput(self._audio_output)
self._player.errorOccurred.connect(self._player_error)
首先是_playlist方法
然后是关于音频输出的方法_audio_output和setAudioOutput,由于使用Qt进行视频播放音频和画面是分开输出的,没有这个方法就会导致打开视频时没有声音。
然后是_play方法,用于视频图像的输出。
tool_bar = QToolBar()
self.addToolBar(tool_bar)
file_menu = self.menuBar().addMenu("&File")
icon = QIcon.fromTheme("document-open")
open_action = QAction(icon, "&Open...", self,
shortcut=QKeySequence.Open, triggered=self.open)
file_menu.addAction(open_action)
tool_bar.addAction(open_action)
icon = QIcon.fromTheme("application-exit")
exit_action = QAction(icon, "E&xit", self,
shortcut="Ctrl+Q", triggered=self.close)
file_menu.addAction(exit_action)
接下来一段程序的作用是绘制图形界面的菜单栏。本段绘制的是上方菜单栏第一个选项File的下拉菜单,并配置了事件。
前两行的作用是实例化QToolBar方法,并使用addToolBar方法添加到窗口。需要注意的是此处的toolBar和menuBar是不一样的。menuBar实现的效果在窗口标题下方一行,而toolBar实现的在menuBar下方,并且它需要手动创建。
接下来第三行,显然是在菜单栏添加File选项。第四行作为一个局部变量并且在之后被多次赋值,它的目的是作为QAction()的一个实参。这样做的目的我不太清楚,因为在这里并没有自动回收之类的问题,我试着将第四行直接填入QAction()作为实参,依然可以完成打开文件的功能。
第五行设置了按键的事件,shortcut配置快捷键,triggered配置点击后调用的函数。
第六和第七行使用addAction方法将open_action这个事件连接到file_menu和tool_bar两个按键的,如果配置多个事件,在按键的下拉菜单上将从上到下按照添加的顺序显示。
之后三行则是设置下拉菜单的Exit选项,方法同上。
play_menu = self.menuBar().addMenu("&Play")
style = self.style()
icon = QIcon.fromTheme("media-playback-start.png",
style.standardIcon(QStyle.SP_MediaPlay))
self._play_action = tool_bar.addAction(icon, "Play")
self._play_action.triggered.connect(self._player.play)
play_menu.addAction(self._play_action)
icon = QIcon.fromTheme("media-skip-backward-symbolic.svg",
style.standardIcon(QStyle.SP_MediaSkipBackward))
self._previous_action = tool_bar.addAction(icon, "Previous")
self._previous_action.triggered.connect(self.previous_clicked)
play_menu.addAction(self._previous_action)
icon = QIcon.fromTheme("media-playback-pause.png",
style.standardIcon(QStyle.SP_MediaPause))
self._pause_action = tool_bar.addAction(icon, "Pause")
self._pause_action.triggered.connect(self._player.pause)
play_menu.addAction(self._pause_action)
icon = QIcon.fromTheme("media-skip-forward-symbolic.svg",
style.standardIcon(QStyle.SP_MediaSkipForward))
self._next_action = tool_bar.addAction(icon, "Next")
self._next_action.triggered.connect(self.next_clicked)
play_menu.addAction(self._next_action)
icon = QIcon.fromTheme("media-playback-stop.png",
style.standardIcon(QStyle.SP_MediaStop))
self._stop_action = tool_bar.addAction(icon, "Stop")
self._stop_action.triggered.connect(self._ensure_stopped)
play_menu.addAction(self._stop_action)
这段代码绘制了Play选项及其下拉菜单,用到了Qt的图标类即QIcon
第一行在菜单栏添加Play选项。第二行实例化style类,作用是后面绘制下拉菜单的时候通过
style.standardIcon()
引入Qt内置图标,参数可选择,具体内容可以参考这篇博客。
从第四行,可以注意到addAction()被赋值到self._play_action。这个self._play_action是什么呢?通过查询文档能够得知这是属于MainWindow类的方法,与视频播放功能有关。另外几个播放选项也有类似的两条语句,一句将按键动作和图标addAction赋值到self._play_action,一句使用self._play_action.triggered.connect()方法将按键关联到视频播放的动作,实现需要的功能。
第六行添加到下拉菜单,与File类似。
self._volume_slider = QSlider()
self._volume_slider.setOrientation(Qt.Horizontal)
self._volume_slider.setMinimum(0)
self._volume_slider.setMaximum(100)
available_width = self.screen().availableGeometry().width()
self._volume_slider.setFixedWidth(available_width / 10)
self._volume_slider.setValue(self._audio_output.volume())
self._volume_slider.setTickInterval(10)
self._volume_slider.setTickPosition(QSlider.TicksBelow)
self._volume_slider.setToolTip("Volume")
self._volume_slider.valueChanged.connect(self._audio_output.setVolume)
tool_bar.addWidget(self._volume_slider)
这段代码用于绘制调节音量的滑动条,主要使用的QSlider控件在这篇博文当中有更详细的介绍。
第二行,setOrientation()函数用于设置滑动条垂直或是水平绘制,此处设为水平。
第三、四行,设置滑动条对应的最大和最小值。
第五、六行,参考这篇博文,availableGeometry可以返回屏幕的可用几何图形大小,是screenGeometry()即窗口相对于父窗口的几何形状的大小的子矩形,在顶层窗口的情况下返回不包括边框的屏幕大小。这两行将滑动条的长度设置为屏幕宽度的十分之一
第七行将滑动条的位置关联到self._audio_output.volume(),也就是音频的音量大小。
第八、九行,setTickInterval()用于设置刻度间隔,参数10为步长。setTickPosition()用于设置指定刻度线相对于滑块和用户操作的位置。
第十行,setToolTip用于设定提示,效果如下图
第十一行,连接信号槽设置音量大小
第十二行,将控件添加到窗口。注意到之前的按键并没有这行语句,可能因为这是特殊控件才需要这句?
about_menu = self.menuBar().addMenu("&About")
about_qt_act = QAction("About &Qt", self, triggered=qApp.aboutQt)
about_menu.addAction(about_qt_act)
这部分代码绘制菜单栏的About按键,信号槽连接到qApp.aboutQt,点击出现一个文档
self._video_widget = QVideoWidget()
self.setCentralWidget(self._video_widget)
self._player.playbackStateChanged.connect(self.update_buttons)
self._player.setVideoOutput(self._video_widget)
self.update_buttons(self._player.playbackState())
self._mime_types = []
这部分代码绘制最重要的功能即视频窗口。
第一行,实例化QVideoWidget
第二行,将视频窗口添加到主窗口的中央
第三行,playbackStateChanged视频播放状态发生改变,将update_buttons关联
第四行,在视频窗口进行视频的画面输出
第五行,_mime_types初始化
def closeEvent(self, event):
self._ensure_stopped()
event.accept()
closeEvent是Qt从窗口接受到关闭请求时触发,此处重写了QWidget中的方法。语句上看应该是播放器停止后才能关闭,防止窗口关闭后视频继续播放
@Slot()
def open(self):
self._ensure_stopped()
file_dialog = QFileDialog(self)
is_windows = sys.platform == 'win32'
if not self._mime_types:
self._mime_types = get_supported_mime_types()
if (is_windows and AVI not in self._mime_types):
self._mime_types.append(AVI)
elif MP4 not in self._mime_types:
self._mime_types.append(MP4)
第一行,插槽,将函数注册为槽函数,具体的资料比较复杂,这里不多说
从第二行开始声明open方法,功能与打开视频文件相关。
第三行,调用了自己类当中的一个函数_ensure_stopped(),确保播放器已停止
第四行,调用QFileDialog,可以遍历文件系统以选择文件或目录,在图形界面当中的作用是打开一个文件对话框
第五行,sys.platform获取当前系统平台,判断是否为windows系统
第六行,判断当前mime类型列表为空,则执行后续判断
第七行,获取支持的mime类型列表
第八行以后,将AVI和MP4文件类型添加到mime列表
file_dialog.setMimeTypeFilters(self._mime_types)
default_mimetype = AVI if is_windows else MP4
if default_mimetype in self._mime_types:
file_dialog.selectMimeTypeFilter(default_mimetype)
movies_location = QStandardPaths.writableLocation(QStandardPaths.MoviesLocation)
file_dialog.setDirectory(movies_location)
if file_dialog.exec() == QDialog.Accepted:
url = file_dialog.selectedUrls()[0]
self._playlist.append(url)
self._playlist_index = len(self._playlist) - 1
self._player.setSource(url)
self._player.play()
第一行,从mime类型列表中设置文件对话框中使用的过滤器
第二到第四行,根据操作系统是否是windows设置默认mime类型
第五、六行,从系统获取默认的Movies地址,并设置为“Open”文件对话框的起始位置,就是下图这个效果
第七行以后,定义的是用户选择文件之后的操作
第七行,QDialog.Accepted表示槽函数接收到确认信号
第八行,获取用户选择的文件的url
第九行,将url添加到播放列表
第十行,重写播放列表索引_playlist_index
第十一、十二行,设置视频文件地址并开始播放,_player是视频画面的播放窗口
@Slot()
def _ensure_stopped(self):
if self._player.playbackState() != QMediaPlayer.StoppedState:
self._player.stop()
这段代码定义的函数作用是确保播放器已经停止,如果当前播放器状态不是停止状态,则停止播放。
@Slot()
def previous_clicked(self):
# Go to previous track if we are within the first 5 seconds of playback
# Otherwise, seek to the beginning.
if self._player.position() <= 5000 and self._playlist_index > 0:
self._playlist_index -= 1
self._playlist.previous()
self._player.setSource(self._playlist[self._playlist_index])
else:
self._player.setPosition(0)
这段代码定义了Previous键关联的事件,如果当前播放位置小于等于5秒,并且当前播放列表中有上一首歌曲,则播放列表索引-1,开始播放上一部视频,否则将播放位置设置为0。
@Slot()
def next_clicked(self):
if self._playlist_index < len(self._playlist) - 1:
self._playlist_index += 1
self._player.setSource(self._playlist[self._playlist_index])
这段代码定义了Next键关联的事件,同样和_playlist_index的操作有关。
@Slot("QMediaPlayer::PlaybackState")
def update_buttons(self, state):
media_count = len(self._playlist)
self._play_action.setEnabled(media_count > 0
and state != QMediaPlayer.PlayingState)
self._pause_action.setEnabled(state == QMediaPlayer.PlayingState)
self._stop_action.setEnabled(state != QMediaPlayer.StoppedState)
self._previous_action.setEnabled(self._player.position() > 0)
self._next_action.setEnabled(media_count > 1)
例程GUI没有这样一个“更新按钮”,这段函数的作用是更新播放相关按键的可用状态
第四、五行,如果播放列表中有媒体文件且播放器不在播放状态,则启用播放按钮,否则禁用播放按钮
第六行,如果播放器在播放状态,则启用暂停按钮,否则禁用暂停按钮
第七行,播放器不在停止状态,则启用停止按钮,否则禁用停止按钮
第八、九行同理
def show_status_message(self, message):
self.statusBar().showMessage(message, 5000)
在状态栏中显示一条消息,5秒后消失。
@Slot("QMediaPlayer::Error", str)
def _player_error(self, error, error_string):
print(error_string, file=sys.stderr)
self.show_status_message(error_string)
处理播放器发生错误的情况。将错误信息输出到终端,并在状态栏中显示错误消息。
if __name__ == '__main__':
app = QApplication(sys.argv)
main_win = MainWindow()
available_geometry = main_win.screen().availableGeometry()
main_win.resize(available_geometry.width() / 3,
available_geometry.height() / 2)
main_win.show()
sys.exit(app.exec())
这部分是主函数。
第五、六行,调整主窗口的高度和宽度