Search code examples
pythonpyqtpyqt5qmediaplayer

How QMediaPlayer.setPosition works for mp3?


I got a problem when I try to play a song from a certain position: it does'nt work (the song plays from the beginning).

This problem only occurs when the song is a 'mp3' song, not a 'm4a' one (they're the only format I tested).

The problem seems to come from qt (or PyQt ?) but I'm not sure, here's a minimal example, do I miss something ?

from PyQt5.QtWidgets import QApplication
from PyQt5.QtMultimedia import QMediaContent, QMediaPlayer
from PyQt5.QtCore import QUrl

if __name__ == "__main__":
    app = QApplication([])

    player = QMediaPlayer()
    media_content = QMediaContent(QUrl.fromLocalFile("path_to_my_music_file.mp3"))
    player.setMedia(media_content)
    player.setPosition(10000)
    player.play()

    app.exec_()

Solution

  • setMedia() is asynchronous:

    Note: This function returns immediately after recording the specified source of the media. It does not wait for the media to finish loading and does not check for errors. Listen for the mediaStatusChanged() and error() signals to be notified when the media is loaded and when an error occurs during loading.

    It's possible that, due to the nature of MP3 files, Qt takes some time before being able to correctly seek. Unfortunately, as far as I can tell, this can only be done after some amount of time has passed after playing the file.

    A possible solution is to connect to a custom function that delays the setPosition until the media becomes seekable.

    This is a subclass that should take care of it (I only tested with mp3 files, so you should try it with other file types to ensure that it works properly).

    class Player(QMediaPlayer):
        _delayedPos = 0
        def setPosition(self, pos):
            super().setPosition(pos)
            if pos and not self.isSeekable():
                self._delayedPos = pos
                try:
                    # ensure that the connection is done only once
                    self.seekableChanged.connect(self.delaySetPosition, Qt.UniqueConnection)
                except:
                    pass
            else:
                self._delayedPos = 0
    
        def delaySetPosition(self, seekable):
            if seekable:
                self.setPosition(self._delayedPos)
            try:
                # just to be safe, in case the media changes before the previous one
                # becomes seekable
                self.seekableChanged.disconnect(self.delaySetPosition)
            except:
                pass