'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 1:[1]

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

Solution 2:[2]

Following @musicamante answer ; a more robust version would be to use one of the following signals :

The best option being mediaStatusChanged which allows you to check the QMediaPlayer::MediaStatus :

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

def update_position(status):
    if(status == QMediaPlayer.LoadedMedia):
        player.setPosition(10000)
        player.play()

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

    player = QMediaPlayer()
    media_content = QMediaContent(QUrl.fromLocalFile("path_to_my_music_file.mp3"))
    player.mediaStatusChanged.connect(update_position)
    player.setMedia(media_content)

    app.exec_()

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 musicamante
Solution 2 Geneviève M.