Fix timing issues with segments (#648)

## Description
Processing segments happens in a separate coroutine which wasn't
cancelled until _after_ playback of the next item begins. So in some
cases the outro from a previous item might be triggered during the very
beginning of the newly starting item.

So this PR instead cancels segment processing immediately when starting
the process for playback of an item. Additionally, the processing also
now checks that the current segment is for the current playback item as
an additional safeguard.

### Related issues
I believe this should fix both of these issues:

Fixes #369
Fixes #647
This commit is contained in:
Ray 2026-01-07 13:23:55 -05:00 committed by GitHub
parent 49740c47cf
commit 4df17e41cd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -325,11 +325,14 @@ class PlaybackViewModel
withContext(Dispatchers.IO) {
Timber.i("Playing ${item.id}")
// New item, so we can clear the media segment tracker & subtitle cues
resetSegmentState()
this@PlaybackViewModel.subtitleCues.setValueOnMain(listOf())
viewModelScope.launchIO {
// Starting playback, so want to invalidate the last played timestamp for this item
datePlayedService.invalidate(item)
// New item, so we can clear the media segment tracker & subtitle cues
autoSkippedSegments.clear()
this@PlaybackViewModel.subtitleCues.setValueOnMain(listOf())
}
if (item.type !in supportItemKinds) {
showToast(
@ -457,7 +460,7 @@ class PlaybackViewModel
player.prepare()
player.play()
}
listenForSegments()
listenForSegments(item.id)
return@withContext true
}
@ -809,10 +812,19 @@ class PlaybackViewModel
private var segmentJob: Job? = null
/**
* Cancels listening for segments and clears current segment state
*/
private suspend fun resetSegmentState() {
segmentJob?.cancel()
autoSkippedSegments.clear()
currentSegment.setValueOnMain(null)
}
/**
* This sets up a coroutine to periodically check whether the current playback progress is within a media segment (intro, outro, etc)
*/
private fun listenForSegments() {
private fun listenForSegments(itemId: UUID) {
segmentJob?.cancel()
segmentJob =
viewModelScope.launchIO {
@ -828,7 +840,10 @@ class PlaybackViewModel
.firstOrNull {
it.type != MediaSegmentType.UNKNOWN && currentTicks >= it.startTicks && currentTicks < it.endTicks
}
if (currentSegment != null && autoSkippedSegments.add(currentSegment.id)) {
if (currentSegment != null &&
currentSegment.itemId == this@PlaybackViewModel.itemId &&
autoSkippedSegments.add(currentSegment.id)
) {
Timber.d(
"Found media segment for %s: %s, %s",
currentSegment.itemId,