PySide6中QMediaPlayer.setSource后setPosition失效问题求助
PySide6 QMediaPlayer切换媒体后无法设置播放位置的解决方法
问题现象
原本在PyQt5中实现的切换音乐时保持播放位置的功能,迁移到PySide6后失效。调用setSource()切换媒体后,立即调用setPosition()无法生效,打印显示位置重置为0,且此时isSeekable()返回False。
切换逻辑的核心代码片段:
@Slot() def next_clicked(self): if self._playlist_index < len(self._playlist) - 1: self._playlist_index += 1 position = self._player.position() print('position before change source', position) self._player.setSource(self._playlist[self._playlist_index]) self._player.play() self._player.setPosition(position) print('position after change source', self._player.position())
打印输出结果:
position before change source 1910 position after change source 0
尝试过基于seekableChanged信号延迟设置位置、监听LoadedMedia状态设置位置的两种方案,均在PySide6中失效。
原因分析
PySide6的QMediaPlayer在媒体源切换后,媒体元数据(包括可定位状态)的加载是异步的。直接在setSource()后调用setPosition()时,媒体还未完成加载,此时播放器处于不可定位状态,设置位置的操作会被忽略。另外,PySide6对媒体状态信号的触发逻辑与PyQt5存在细微差异,原有方案的信号监听时机或连接方式不再适用。
解决方案
修改切换逻辑,保存需要设置的目标位置,监听播放器的mediaStatusChanged信号,在媒体进入LoadedMedia状态且isSeekable()为True时,再设置播放位置并启动播放。同时处理多次切换媒体时的位置覆盖问题。
修改后的完整代码:
import sys from PySide6.QtCore import QUrl, Qt, Slot from PySide6.QtGui import QAction, QIcon, QStyle from PySide6.QtWidgets import (QApplication, QMainWindow, QToolBar) from PySide6.QtMultimedia import (QAudioOutput, QMediaPlayer) class MainWindow(QMainWindow): def __init__(self): super().__init__() url1 = QUrl.fromLocalFile('path/to/music1.mp3') url2 = QUrl.fromLocalFile('path/to/music2.mp3') self._playlist = [url1, url2] self._playlist_index = 0 self._target_position = 0 # 保存切换后的目标位置 self._audio_output = QAudioOutput() self._player = QMediaPlayer() self._player.setAudioOutput(self._audio_output) self._player.setSource(self._playlist[self._playlist_index]) # 监听媒体状态变化 self._player.mediaStatusChanged.connect(self.on_media_status_changed) tool_bar = QToolBar() self.addToolBar(tool_bar) 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) 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) icon = QIcon.fromTheme("media-seek-backward-symbolic.svg", style.standardIcon(QStyle.SP_MediaSeekBackward)) self._backward_action = tool_bar.addAction(icon, "Backward") self._backward_action.triggered.connect(self.backward_clicked) 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) icon = QIcon.fromTheme("media-seek-forward-symbolic.svg", style.standardIcon(QStyle.SP_MediaSeekForward)) self._forward_action = tool_bar.addAction(icon, "Forward") self._forward_action.triggered.connect(self.forward_clicked) 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) 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) @Slot() def _ensure_stopped(self): if self._player.playbackState() != QMediaPlayer.StoppedState: self._player.stop() @Slot() def previous_clicked(self): if self._playlist_index > 0: self._playlist_index -= 1 self._target_position = self._player.position() self._player.setSource(self._playlist[self._playlist_index]) @Slot() def next_clicked(self): if self._playlist_index < len(self._playlist) - 1: self._playlist_index += 1 self._target_position = self._player.position() self._player.setSource(self._playlist[self._playlist_index]) @Slot() def backward_clicked(self): self._player.setPosition(self._player.position() - 5000) @Slot() def forward_clicked(self): self._player.setPosition(self._player.position() + 5000) @Slot(QMediaPlayer.MediaStatus) def on_media_status_changed(self, status): # 媒体加载完成且可定位时,设置目标位置并播放 if status == QMediaPlayer.MediaStatus.LoadedMedia and self._player.isSeekable(): if self._target_position > 0: self._player.setPosition(self._target_position) self._target_position = 0 # 重置目标位置,避免重复设置 self._player.play() @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._playlist_index > 0) self._next_action.setEnabled(self._playlist_index < len(self._playlist) - 1) if __name__ == '__main__': app = QApplication(sys.argv) main_win = MainWindow() main_win.show() sys.exit(app.exec())
关键修改点
- 添加
_target_position属性保存切换媒体前的播放位置,避免直接在setSource后立即调用setPosition - 监听
mediaStatusChanged信号,在媒体进入LoadedMedia状态且isSeekable()为True时,再设置位置并播放 - 设置位置后重置
_target_position,防止多次切换时位置被错误复用 - 修复了原代码中
_previous_action和_next_action重复赋值的问题(原代码中Backward和Previous、Forward和Next共用了同一个变量)
内容的提问来源于stack exchange,提问作者DSP novice
相关产品推荐
相关产品推荐

