运行此代码:
from PySide6 import QtWidgets as qtw
from PySide6 import QtMultimedia as qtm
from PySide6 import QtMultimediaWidgets as qtmw
from PySide6 import QtCore as qtc
app = qtw.QApplication()
path = "video.mp4"
video_widget = qtmw.QVideoWidget()
video_widget.show()
media_player = qtm.QMediaPlayer()
media_player.setVideoOutput(video_widget)
media_player.mediaStatusChanged.connect(media_player.play)
media_player.setSource(path)
qtc.QTimer.singleShot(2000, lambda: media_player.setSource(''))
app.exec()
,您会注意到,当
media_player.setSource('')
毫秒后调用 2000
时,GUI 在大约一秒左右的时间里变得没有响应。似乎不可能仅将 setSource()
调用移至单独的线程,我收到一条警告,提示 QObject::killTimer: Timers cannot be stopped from another thread
(您可以通过用 qtc.QTimer...
替换 qtc.QThreadPool.globalInstance().start(lambda: media_player.setSource(''))
调用来复制此内容)。因此,经过几个小时的思考,作为解决方法,我将整个 QMediaPlayer
实例以及与其相关的所有对象移动到它自己的线程中,并使用 QVideoSink
的 videoFrameChanged
信号来更新视频上的视频小部件:
from PySide6 import QtWidgets as qtw
from PySide6 import QtMultimedia as qtm
from PySide6 import QtMultimediaWidgets as qtmw
from PySide6 import QtCore as qtc
path = "video.mp4"
class VideoPlayer(qtmw.QVideoWidget):
_stop_worker_signal = qtc.Signal()
_initialize_media_player_signal = qtc.Signal()
_set_media_source_signal = qtc.Signal(str)
_play_media_signal = qtc.Signal()
_stop_media_signal = qtc.Signal()
_pause_media_signal = qtc.Signal()
def __init__(self) -> None:
super().__init__()
self._worker = Worker()
self._worker_thread = qtc.QThread()
self._worker.moveToThread(self._worker_thread)
self._stop_worker_signal.connect(self._worker.stop)
self._initialize_media_player_signal.connect(self._worker.create_media_player)
self._worker.video_sink_frame_changed_signal.connect(lambda frame: self.videoSink().setVideoFrame(frame))
self._set_media_source_signal.connect(self._worker.set_media_source)
self._stop_media_signal.connect(self._worker.stop_media)
self._worker_thread.start()
self._initialize_media_player_signal.emit()
self._set_media_source_signal.emit(path)
self.show()
def keyPressEvent(self, event):
if event.key() == qtc.Qt.Key.Key_Q:
self._set_media_source_signal.emit('')
def closeEvent(self, event: qtc.QEvent):
loop = qtc.QEventLoop()
self._worker.stopped_signal.connect(loop.exit)
self._stop_worker_signal.emit()
loop.exec()
self._worker_thread.exit()
class Worker(qtc.QObject):
video_sink_frame_changed_signal = qtc.Signal(qtm.QVideoFrame)
stopped_signal = qtc.Signal()
def create_media_player(self):
self._audio_output = qtm.QAudioOutput(qtm.QMediaDevices.defaultAudioOutput())
self._video_sink = qtm.QVideoSink()
self._video_sink.videoFrameChanged.connect(self.video_sink_frame_changed_signal)
self._media_player = qtm.QMediaPlayer()
self._media_player.setAudioOutput(self._audio_output)
self._media_player.setVideoSink(self._video_sink)
self._media_player.mediaStatusChanged.connect(self._media_player.play)
def set_media_source(self, source: str) -> None:
self._media_player.setSource(source)
def stop_media(self) -> None:
if not self._media_player.playbackState() == qtm.QMediaPlayer.PlaybackState.StoppedState:
self._media_player.stop()
def stop(self):
self._media_player.stop()
self._audio_output.deleteLater()
self._video_sink.deleteLater()
self._media_player.deleteLater()
self.stopped_signal.emit()
app = qtw.QApplication()
vp = VideoPlayer()
app.exec()
现在,当调用
setSource()
时,GUI 中不会出现无响应的情况,但是,正如人们所看到的,这是一种非常复杂的方法。有更好的办法吗?
我遇到了这个确切的问题,我做了研究,但找不到任何有吸引力的答案。
我研究了代码,似乎 .setSource() 不喜欢在 QMediaPlayer 完全停止之前被调用。
经过一番思考,发现解决办法很简单:
这实际上解决了我的问题:
import time
#... the code here
def changeSource(source):
# Stop playing first
myplayer.stop()
# Here pause a little bit before setSource()
time.sleep(.01)
# Now set source
myplayer.setSource(source)
# Now you can play
myplayer.play()
编码快乐!