You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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())

关键修改点

  1. 添加_target_position属性保存切换媒体前的播放位置,避免直接在setSource后立即调用setPosition
  2. 监听mediaStatusChanged信号,在媒体进入LoadedMedia状态且isSeekable()为True时,再设置位置并播放
  3. 设置位置后重置_target_position,防止多次切换时位置被错误复用
  4. 修复了原代码中_previous_action和_next_action重复赋值的问题(原代码中Backward和Previous、Forward和Next共用了同一个变量)

内容的提问来源于stack exchange,提问作者DSP novice

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 01:36:21