mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 15:50:16 +02:00
Fixes the skip segment button appearance behavior (#884)
## Description Fixes the segment (intro, credits, etc) skip button appearance behavior For the button shown on the video: if the button for a given segment is clicked, dismissed by the back button, or times out after 10 seconds, it will never appear again even if you rewind the video. However, the current non-ignored segment will always show a button on the playback controls though so it's possible to still use it. ### Related issues Fixes #875 ### Testing Tested a bunch of scenarios on the emulator ## Screenshots N/A ## AI or LLM usage None
This commit is contained in:
parent
028553dc91
commit
4161ea418c
3 changed files with 73 additions and 26 deletions
|
|
@ -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).
|
||||
*
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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>(CurrentMediaInfo.EMPTY)
|
||||
val currentPlayback = MutableStateFlow<CurrentPlayback?>(null)
|
||||
val currentItemPlayback = MutableLiveData<ItemPlayback>()
|
||||
val currentSegment = EqualityMutableLiveData<MediaSegmentDto?>(null)
|
||||
val currentSegment = MutableStateFlow<MediaSegmentState?>(null)
|
||||
private val autoSkippedSegments = mutableSetOf<UUID>()
|
||||
|
||||
val subtitleCues = MutableLiveData<List<Cue>>(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,
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue