diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/Extensions.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/Extensions.kt index 12854cd7..c997efd1 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/Extensions.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/Extensions.kt @@ -391,6 +391,15 @@ fun CoroutineScope.launchIO( block: suspend CoroutineScope.() -> Unit, ): Job = launch(context = Dispatchers.IO + context, start = start, block = block) +/** + * Launches a coroutine with [Dispatchers.Default] plus the provided [CoroutineContext] defaulting to using [ExceptionHandler] + */ +fun CoroutineScope.launchDefault( + context: CoroutineContext = ExceptionHandler(), + start: CoroutineStart = CoroutineStart.DEFAULT, + block: suspend CoroutineScope.() -> Unit, +): Job = launch(context = Dispatchers.Default + context, start = start, block = block) + /** * Converts a UUID to the format used server-side (ie without hyphens). * diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/playback/PlaybackPage.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/playback/PlaybackPage.kt index f8b80020..0313a8c5 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/playback/PlaybackPage.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/playback/PlaybackPage.kt @@ -86,7 +86,6 @@ import com.github.damontecres.wholphin.util.mpv.MpvPlayer import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.launch -import org.jellyfin.sdk.model.extensions.ticks import timber.log.Timber import java.util.UUID import kotlin.time.Duration @@ -163,8 +162,7 @@ fun PlaybackPageContent( itemId = UUID.randomUUID(), ), ) - val currentSegment by viewModel.currentSegment.observeAsState(null) - var segmentCancelled by remember(currentSegment?.id) { mutableStateOf(false) } + val currentSegment by viewModel.currentSegment.collectAsState() val cues by viewModel.subtitleCues.observeAsState(listOf()) var showDebugInfo by remember { mutableStateOf(prefs.showDebugInfo) } @@ -302,10 +300,10 @@ fun PlaybackPageContent( } val showSegment = - !segmentCancelled && currentSegment != null && + currentSegment?.interacted == false && nextUp == null && !controllerViewState.controlsVisible && skipIndicatorDuration == 0L BackHandler(showSegment) { - segmentCancelled = true + viewModel.updateSegment(currentSegment?.segment?.id, true) } Box( @@ -400,7 +398,7 @@ fun PlaybackPageContent( onClickPlaylist = { viewModel.playItemInPlaylist(it) }, - currentSegment = currentSegment, + currentSegment = currentSegment?.segment, showClock = preferences.appPreferences.interfacePreferences.showClock, ) } @@ -462,13 +460,12 @@ fun PlaybackPageContent( LaunchedEffect(Unit) { focusRequester.tryRequestFocus() delay(10.seconds) - segmentCancelled = true + viewModel.updateSegment(segment.segment.id, true) } TextButton( - stringRes = segment.type.skipStringRes, + stringRes = segment.segment.type.skipStringRes, onClick = { - segmentCancelled = true - player.seekTo(segment.endTicks.ticks.inWholeMilliseconds) + viewModel.updateSegment(segment.segment.id, false) }, modifier = Modifier.focusRequester(focusRequester), ) diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/playback/PlaybackViewModel.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/playback/PlaybackViewModel.kt index 931ac551..7ed25311 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/playback/PlaybackViewModel.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/playback/PlaybackViewModel.kt @@ -49,6 +49,7 @@ import com.github.damontecres.wholphin.services.RefreshRateService import com.github.damontecres.wholphin.services.StreamChoiceService import com.github.damontecres.wholphin.services.UserPreferencesService import com.github.damontecres.wholphin.ui.isNotNullOrBlank +import com.github.damontecres.wholphin.ui.launchDefault import com.github.damontecres.wholphin.ui.launchIO import com.github.damontecres.wholphin.ui.nav.Destination import com.github.damontecres.wholphin.ui.onMain @@ -57,7 +58,6 @@ import com.github.damontecres.wholphin.ui.seekForward import com.github.damontecres.wholphin.ui.setValueOnMain import com.github.damontecres.wholphin.ui.showToast import com.github.damontecres.wholphin.ui.toServerString -import com.github.damontecres.wholphin.util.EqualityMutableLiveData import com.github.damontecres.wholphin.util.ExceptionHandler import com.github.damontecres.wholphin.util.LoadingState import com.github.damontecres.wholphin.util.TrackActivityPlaybackListener @@ -164,7 +164,7 @@ class PlaybackViewModel val currentMediaInfo = MutableLiveData(CurrentMediaInfo.EMPTY) val currentPlayback = MutableStateFlow(null) val currentItemPlayback = MutableLiveData() - val currentSegment = EqualityMutableLiveData(null) + val currentSegment = MutableStateFlow(null) private val autoSkippedSegments = mutableSetOf() val subtitleCues = MutableLiveData>(listOf()) @@ -962,10 +962,10 @@ class PlaybackViewModel /** * Cancels listening for segments and clears current segment state */ - private suspend fun resetSegmentState() { + private fun resetSegmentState() { segmentJob?.cancel() autoSkippedSegments.clear() - currentSegment.setValueOnMain(null) + currentSegment.value = null } /** @@ -988,15 +988,21 @@ class PlaybackViewModel it.type != MediaSegmentType.UNKNOWN && currentTicks >= it.startTicks && currentTicks < it.endTicks } if (currentSegment != null && - currentSegment.itemId == this@PlaybackViewModel.itemId && - autoSkippedSegments.add(currentSegment.id) + currentSegment.itemId == this@PlaybackViewModel.itemId ) { - Timber.d( - "Found media segment for %s: %s, %s", - currentSegment.itemId, - currentSegment.id, - currentSegment.type, - ) + if (currentSegment.id != + this@PlaybackViewModel + .currentSegment.value + ?.segment + ?.id + ) { + Timber.d( + "Found media segment for %s: %s, %s", + currentSegment.itemId, + currentSegment.id, + currentSegment.type, + ) + } val playlist = this@PlaybackViewModel.playlist.value if (currentSegment.type == MediaSegmentType.OUTRO && @@ -1021,13 +1027,21 @@ class PlaybackViewModel withContext(Dispatchers.Main) { when (behavior) { SkipSegmentBehavior.AUTO_SKIP -> { - this@PlaybackViewModel.currentSegment.value = null - player.seekTo(currentSegment.endTicks.ticks.inWholeMilliseconds + 1) + if (autoSkippedSegments.add(currentSegment.id)) { + onMain { player.seekTo(currentSegment.endTicks.ticks.inWholeMilliseconds + 1) } + } + this@PlaybackViewModel.currentSegment.update { + MediaSegmentState(currentSegment, true) + } } SkipSegmentBehavior.ASK_TO_SKIP -> { - this@PlaybackViewModel.currentSegment.value = - currentSegment + this@PlaybackViewModel.currentSegment.update { + MediaSegmentState( + currentSegment, + autoSkippedSegments.contains(currentSegment.id), + ) + } } else -> { @@ -1046,6 +1060,28 @@ class PlaybackViewModel } } + fun updateSegment( + segmentId: UUID?, + dismissed: Boolean, + ) { + viewModelScope.launchDefault { + val segment = currentSegment.value?.segment + if (segment != null && segment.id == segmentId) { + autoSkippedSegments.add(segment.id) + if (dismissed) { + currentSegment.update { + it?.copy(interacted = true) + } + } else { + currentSegment.update { + null + } + onMain { player.seekTo(segment.endTicks.ticks.inWholeMilliseconds + 1) } + } + } + } + } + private fun listenForTranscodeReason(): Job = viewModelScope.launchIO { currentPlayback.collectLatest { @@ -1379,3 +1415,8 @@ data class PlayerState( val player: Player, val backend: PlayerBackend, ) + +data class MediaSegmentState( + val segment: MediaSegmentDto, + val interacted: Boolean, +)