Qt:QMediaPlayer.setSource() 阻塞了 GUI,因此我将其移至单独的线程;我这样做对吗?

问题描述 投票:0回答:1

运行此代码:

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 中不会出现无响应的情况,但是,正如人们所看到的,这是一种非常复杂的方法。有更好的办法吗?

qt pyside pyside6 qt6
1个回答
0
投票

我遇到了这个确切的问题,我做了研究,但找不到任何有吸引力的答案。

我研究了代码,似乎 .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()

编码快乐!

© www.soinside.com 2019 - 2024. All rights reserved.