v0.5.2: custom start times survive a track change
Playing a track with an iTunes start time (e.g. "Pretty Girls is a Motherfucker", 0:40) began at 0:00 whenever another track was already loaded. QMediaPlayer.setSource() synchronously emits two status changes before returning: a LoadedMedia still reporting the *outgoing* source, then LoadingMedia for the new one. The stale first event consumed the one-shot _pending_start_ms armed in Round 22 and seeked the dying pipeline, so the new track's real LoadedMedia ~5 ms later found nothing armed. Only the first track after launch worked. The armed seek now carries the URL it belongs to and is consumed only when source() matches. tests/test_round32.py models the real Qt event sequence, which Round 22's plain MagicMock never emitted; the test_round8/test_round22 stubs now report the new source by the time its LoadedMedia arrives, as Qt does.
This commit is contained in:
@@ -1,3 +1,3 @@
|
||||
"""LinTunes — iTunes-style music library manager and player for Linux."""
|
||||
|
||||
__version__ = "0.5.1"
|
||||
__version__ = "0.5.2"
|
||||
|
||||
+22
-6
@@ -106,8 +106,11 @@ class LocalSink(PlaybackSink):
|
||||
super().__init__(parent)
|
||||
self._volume = 1.0 # logical 0..1; what the volume slider shows
|
||||
# Custom start time (ms) for the loaded track: applied once the media
|
||||
# reports itself loaded, which is when it is reliably seekable.
|
||||
# reports itself loaded, which is when it is reliably seekable. Tagged
|
||||
# with the URL it was armed for, because setSource() fires a LoadedMedia
|
||||
# for the *outgoing* media that would otherwise consume it (see load()).
|
||||
self._pending_start_ms = 0
|
||||
self._pending_start_url = QUrl()
|
||||
self._paused_at: float | None = None # monotonic time of last pause
|
||||
self._shutdown_done = False
|
||||
|
||||
@@ -132,7 +135,7 @@ class LocalSink(PlaybackSink):
|
||||
self._media.errorOccurred.connect(self._on_error)
|
||||
|
||||
def load(self, track, autoplay: bool, start_ms: int):
|
||||
self._pending_start_ms = max(0, start_ms)
|
||||
start_ms = max(0, start_ms)
|
||||
url = QUrl.fromLocalFile(track.location)
|
||||
if self._media.source() == url:
|
||||
# setSource() no-ops on an unchanged URL, so LoadedMedia never
|
||||
@@ -140,10 +143,19 @@ class LocalSink(PlaybackSink):
|
||||
# a clear+reload races the FFmpeg backend, which snaps the seek
|
||||
# back to 0). The media is already loaded: rewind and seek now.
|
||||
self._media.stop()
|
||||
if self._pending_start_ms:
|
||||
self._media.setPosition(self._pending_start_ms)
|
||||
self._pending_start_ms = 0
|
||||
self._pending_start_ms = 0
|
||||
self._pending_start_url = QUrl()
|
||||
if start_ms:
|
||||
self._media.setPosition(start_ms)
|
||||
else:
|
||||
# setSource() synchronously emits a *stale* LoadedMedia that still
|
||||
# reports the outgoing media (the previous track dropping back from
|
||||
# BufferedMedia) before it starts loading the new one. Tagging the
|
||||
# armed seek with its URL keeps that event from eating it — the
|
||||
# real LoadedMedia lands a few ms later, and until Round 32 the
|
||||
# start time was silently lost on every track change.
|
||||
self._pending_start_ms = start_ms
|
||||
self._pending_start_url = url
|
||||
self._media.setSource(url)
|
||||
if autoplay:
|
||||
self._media.play()
|
||||
@@ -236,11 +248,15 @@ class LocalSink(PlaybackSink):
|
||||
|
||||
def _on_media_status(self, status):
|
||||
if (status == QMediaPlayer.MediaStatus.LoadedMedia
|
||||
and self._pending_start_ms):
|
||||
and self._pending_start_ms
|
||||
and self._media.source() == self._pending_start_url):
|
||||
# The media is only reliably seekable once loaded; jump to the
|
||||
# custom start time now (works whether or not we're autoplaying).
|
||||
# The source check rejects the stale LoadedMedia that setSource()
|
||||
# fires for the media being replaced.
|
||||
start = self._pending_start_ms
|
||||
self._pending_start_ms = 0
|
||||
self._pending_start_url = QUrl()
|
||||
self._media.setPosition(start)
|
||||
return
|
||||
if status == QMediaPlayer.MediaStatus.BufferedMedia:
|
||||
|
||||
Reference in New Issue
Block a user