Skip to content

Commit 6f0348d

Browse files
MarvinSchenkelgithub-actions[bot]
authored andcommitted
Fix track duration shrinking when seeking near the end with smart crossfade (#4176)
1 parent 8e6758f commit 6f0348d

1 file changed

Lines changed: 11 additions & 4 deletions

File tree

  • music_assistant/controllers/streams

music_assistant/controllers/streams/audio.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1750,6 +1750,7 @@ async def get_queue_item_stream_with_smartfade(
17501750
# Round down to nearest frame boundary
17511751
crossfade_buffer_size = (crossfade_buffer_size // frame_size) * frame_size
17521752
fade_out_data: bytes | None = None
1753+
uncredited_tail_bytes = 0
17531754

17541755
# pin the body to DYNAMIC when the intro was baked DYNAMIC,
17551756
# else a late measurement flips it and causes a volume jump
@@ -1939,6 +1940,8 @@ async def _limited_fade_in() -> AsyncGenerator[bytes]:
19391940
bytes_written += len(mix_chunk)
19401941
else:
19411942
second_part_buf.extend(mix_chunk)
1943+
# tail consumed by the mix but not credited to bytes_written
1944+
uncredited_tail_bytes = len(fade_out_data) - first_part_written
19421945
self._crossfade_data[queue_item.queue_id] = CrossfadeData(
19431946
data=bytes(second_part_buf),
19441947
fade_in_size=fade_in_bytes_consumed,
@@ -1983,10 +1986,12 @@ async def _limited_fade_in() -> AsyncGenerator[bytes]:
19831986
# this also accounts for crossfade and silence stripping
19841987
seconds_streamed = bytes_written / pcm_format.pcm_sample_size
19851988
streamdetails.seconds_streamed = seconds_streamed
1989+
uncredited_tail_seconds = uncredited_tail_bytes / pcm_format.pcm_sample_size
19861990
# streamdetails.duration is in media-time; seconds_streamed is stream-time
19871991
# (post-atempo), so we scale by playback_speed to recover media-time.
19881992
streamdetails.duration = int(
1989-
streamdetails.seek_position + seconds_streamed * playback_speed
1993+
streamdetails.seek_position
1994+
+ (seconds_streamed + uncredited_tail_seconds) * playback_speed
19901995
)
19911996
# propagate accurate duration to queue_item so UI displays it
19921997
queue_item.duration = streamdetails.duration
@@ -2355,10 +2360,13 @@ def _superseded() -> bool:
23552360
# this also accounts for crossfade and silence stripping
23562361
seconds_streamed = bytes_written / pcm_sample_size
23572362
queue_track.streamdetails.seconds_streamed = seconds_streamed
2363+
# the held-back crossfade tail still counts as this track's media-time
2364+
tail_seconds = len(last_fadeout_part) / pcm_sample_size
23582365
# streamdetails.duration is in media-time; seconds_streamed is stream-time
23592366
# (post-atempo), so we scale by the track's playback_speed to recover media-time.
23602367
queue_track.streamdetails.duration = int(
2361-
queue_track.streamdetails.seek_position + seconds_streamed * track_playback_speed
2368+
queue_track.streamdetails.seek_position
2369+
+ (seconds_streamed + tail_seconds) * track_playback_speed
23622370
)
23632371
# propagate accurate duration to queue_item so UI displays it
23642372
queue_track.duration = queue_track.streamdetails.duration
@@ -2389,14 +2397,13 @@ def _superseded() -> bool:
23892397
for pcm_slice in iter_pcm_slices(last_fadeout_part, pcm_format, 1000):
23902398
yield pcm_slice
23912399
await asyncio.sleep(0)
2392-
# correct seconds streamed/duration
2400+
# correct seconds streamed - the duration already includes the tail
23932401
last_part_seconds = len(last_fadeout_part) / pcm_sample_size
23942402
streamdetails = queue_track.streamdetails
23952403
assert streamdetails is not None
23962404
streamdetails.seconds_streamed = (
23972405
streamdetails.seconds_streamed or 0
23982406
) + last_part_seconds
2399-
streamdetails.duration = int((streamdetails.duration or 0) + last_part_seconds)
24002407
# also update the play log entry so elapsed time tracking stays in sync
24012408
if last_play_log_entry:
24022409
assert last_play_log_entry.seconds_streamed is not None

0 commit comments

Comments
 (0)