mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 23:51:21 +02:00
Option to show next up during credits/outros (#130)
Adds an option to the show the next up during the end credits (outro) instead of at the very end of the video. The next up pushes up from the bottom and the video scales down to fit above. Still a WIP though ### Sample screenshot <img width="1280" height="771" alt="next_up_credits Large" src="https://github.com/user-attachments/assets/9a03d0ec-325d-4efe-a28e-51270ddaaa89" />
This commit is contained in:
parent
715dc1abda
commit
d76f829804
7 changed files with 87 additions and 37 deletions
|
|
@ -602,6 +602,19 @@ sealed interface AppPreference<T> {
|
|||
summaryOn = R.string.enabled,
|
||||
summaryOff = R.string.nav_drawer_switch_on_focus_summary_off,
|
||||
)
|
||||
|
||||
val ShowNextUpTiming =
|
||||
AppChoicePreference<ShowNextUpWhen>(
|
||||
title = R.string.show_next_up_when,
|
||||
defaultValue = ShowNextUpWhen.END_OF_PLAYBACK,
|
||||
getter = { it.playbackPreferences.showNextUpWhen },
|
||||
setter = { prefs, value ->
|
||||
prefs.updatePlaybackPreferences { showNextUpWhen = value }
|
||||
},
|
||||
displayValues = R.array.show_next_up_when_options,
|
||||
indexToValue = { ShowNextUpWhen.forNumber(it) },
|
||||
valueToIndex = { it.number },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -613,7 +626,6 @@ val basicPreferences =
|
|||
listOf(
|
||||
AppPreference.HomePageItems,
|
||||
AppPreference.RewatchNextUp,
|
||||
AppPreference.CombineContinueNext,
|
||||
AppPreference.PlayThemeMusic,
|
||||
AppPreference.RememberSelectedTab,
|
||||
AppPreference.ThemeColors,
|
||||
|
|
@ -625,11 +637,17 @@ val basicPreferences =
|
|||
listOf(
|
||||
AppPreference.SkipForward,
|
||||
AppPreference.SkipBack,
|
||||
AppPreference.ControllerTimeout,
|
||||
AppPreference.SkipBackOnResume,
|
||||
),
|
||||
),
|
||||
PreferenceGroup(
|
||||
title = R.string.next_up,
|
||||
preferences =
|
||||
listOf(
|
||||
AppPreference.ShowNextUpTiming,
|
||||
AppPreference.AutoPlayNextUp,
|
||||
AppPreference.AutoPlayNextDelay,
|
||||
AppPreference.PassOutProtection,
|
||||
AppPreference.SkipBackOnResume,
|
||||
),
|
||||
),
|
||||
PreferenceGroup(
|
||||
|
|
@ -664,7 +682,9 @@ val advancedPreferences =
|
|||
title = R.string.ui_interface,
|
||||
preferences =
|
||||
listOf(
|
||||
AppPreference.CombineContinueNext,
|
||||
AppPreference.NavDrawerSwitchOnFocus,
|
||||
AppPreference.ControllerTimeout,
|
||||
),
|
||||
),
|
||||
PreferenceGroup(
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ class AppPreferencesSerializer
|
|||
skipRecaps = AppPreference.SkipRecaps.defaultValue
|
||||
passOutProtectionMs =
|
||||
AppPreference.PassOutProtection.defaultValue.hours.inWholeMilliseconds
|
||||
showNextUpWhen = AppPreference.ShowNextUpTiming.defaultValue
|
||||
|
||||
overrides =
|
||||
PlaybackOverrides
|
||||
|
|
|
|||
|
|
@ -69,9 +69,11 @@ import com.github.damontecres.wholphin.ui.components.ErrorMessage
|
|||
import com.github.damontecres.wholphin.ui.components.LoadingPage
|
||||
import com.github.damontecres.wholphin.ui.nav.Destination
|
||||
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
||||
import com.github.damontecres.wholphin.util.ExceptionHandler
|
||||
import com.github.damontecres.wholphin.util.LoadingState
|
||||
import com.github.damontecres.wholphin.util.seasonEpisode
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import org.jellyfin.sdk.model.api.DeviceProfile
|
||||
import org.jellyfin.sdk.model.extensions.ticks
|
||||
import java.util.UUID
|
||||
|
|
@ -162,7 +164,6 @@ fun PlaybackPage(
|
|||
val scaledModifier =
|
||||
Modifier.resizeWithContentScale(contentScale, presentationState.videoSizeDp)
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
val controllerFocusRequester = remember { FocusRequester() }
|
||||
val playPauseState = rememberPlayPauseButtonState(player)
|
||||
val seekBarState = rememberSeekBarState(player, scope)
|
||||
|
||||
|
|
@ -215,9 +216,9 @@ fun PlaybackPage(
|
|||
|
||||
Box(
|
||||
modifier
|
||||
.background(Color.Black),
|
||||
.background(if (nextUp == null) Color.Black else MaterialTheme.colorScheme.background),
|
||||
) {
|
||||
val playerSize by animateFloatAsState(if (nextUp == null) 1f else .66f)
|
||||
val playerSize by animateFloatAsState(if (nextUp == null) 1f else .6f)
|
||||
Box(
|
||||
modifier =
|
||||
Modifier
|
||||
|
|
@ -418,7 +419,13 @@ fun PlaybackPage(
|
|||
|
||||
// Next up episode
|
||||
BackHandler(nextUp != null) {
|
||||
viewModel.navigationManager.goBack()
|
||||
if (player.isPlaying) {
|
||||
scope.launch(ExceptionHandler()) {
|
||||
viewModel.cancelUpNextEpisode()
|
||||
}
|
||||
} else {
|
||||
viewModel.navigationManager.goBack()
|
||||
}
|
||||
}
|
||||
AnimatedVisibility(
|
||||
nextUp != null,
|
||||
|
|
@ -470,7 +477,7 @@ fun PlaybackPage(
|
|||
Modifier
|
||||
.padding(8.dp)
|
||||
// .height(128.dp)
|
||||
.fillMaxHeight(.5f)
|
||||
.fillMaxHeight(1 - playerSize)
|
||||
.fillMaxWidth(.66f)
|
||||
.align(Alignment.BottomCenter)
|
||||
.background(
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ import com.github.damontecres.wholphin.data.model.chooseStream
|
|||
import com.github.damontecres.wholphin.preferences.AppPreference
|
||||
import com.github.damontecres.wholphin.preferences.AppPreferences
|
||||
import com.github.damontecres.wholphin.preferences.MediaExtensionStatus
|
||||
import com.github.damontecres.wholphin.preferences.ShowNextUpWhen
|
||||
import com.github.damontecres.wholphin.preferences.SkipSegmentBehavior
|
||||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||
import com.github.damontecres.wholphin.ui.launchIO
|
||||
|
|
@ -97,12 +98,6 @@ import javax.inject.Inject
|
|||
import kotlin.time.Duration
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
|
||||
enum class TranscodeType {
|
||||
DIRECT_PLAY,
|
||||
DIRECT_STREAM,
|
||||
TRANSCODE,
|
||||
}
|
||||
|
||||
data class StreamDecision(
|
||||
val itemId: UUID,
|
||||
val type: PlayMethod,
|
||||
|
|
@ -159,6 +154,7 @@ class PlaybackViewModel
|
|||
val trickplay = MutableLiveData<TrickplayInfo?>(null)
|
||||
val chapters = MutableLiveData<List<Chapter>>(listOf())
|
||||
val currentSegment = EqualityMutableLiveData<MediaSegmentDto?>(null)
|
||||
private val autoSkippedSegments = mutableSetOf<UUID>()
|
||||
|
||||
private lateinit var preferences: UserPreferences
|
||||
private lateinit var deviceProfile: DeviceProfile
|
||||
|
|
@ -244,6 +240,7 @@ class PlaybackViewModel
|
|||
): Boolean =
|
||||
withContext(Dispatchers.IO) {
|
||||
Timber.i("Playing ${base.id}")
|
||||
autoSkippedSegments.clear()
|
||||
if (base.type !in supportItemKinds) {
|
||||
showToast(
|
||||
context,
|
||||
|
|
@ -358,11 +355,9 @@ class PlaybackViewModel
|
|||
audioIndex = audioIndex ?: TrackIndex.UNSPECIFIED,
|
||||
subtitleIndex = subtitleIndex ?: TrackIndex.UNSPECIFIED,
|
||||
)
|
||||
withContext(Dispatchers.Main) {
|
||||
this@PlaybackViewModel.currentItemPlayback.value = itemPlaybackToUse
|
||||
}
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
this@PlaybackViewModel.currentItemPlayback.value = itemPlaybackToUse
|
||||
this@PlaybackViewModel.audioStreams.value = audioStreams
|
||||
this@PlaybackViewModel.subtitleStreams.value = subtitleStreams
|
||||
|
||||
|
|
@ -664,39 +659,53 @@ class PlaybackViewModel
|
|||
.firstOrNull {
|
||||
it.type != MediaSegmentType.UNKNOWN && currentTicks >= it.startTicks && currentTicks < it.endTicks
|
||||
}
|
||||
if (currentSegment != null) {
|
||||
if (currentSegment != null && autoSkippedSegments.add(currentSegment.id)) {
|
||||
Timber.d(
|
||||
"Found media segment for %s: %s, %s",
|
||||
currentSegment.itemId,
|
||||
currentSegment.id,
|
||||
currentSegment.type,
|
||||
)
|
||||
val behavior =
|
||||
when (currentSegment.type) {
|
||||
MediaSegmentType.COMMERCIAL -> prefs.skipCommercials
|
||||
MediaSegmentType.PREVIEW -> prefs.skipPreviews
|
||||
MediaSegmentType.RECAP -> prefs.skipRecaps
|
||||
MediaSegmentType.OUTRO -> prefs.skipOutros
|
||||
MediaSegmentType.INTRO -> prefs.skipIntros
|
||||
MediaSegmentType.UNKNOWN -> SkipSegmentBehavior.IGNORE
|
||||
val playlist = this@PlaybackViewModel.playlist.value
|
||||
|
||||
if (currentSegment.type == MediaSegmentType.OUTRO &&
|
||||
prefs.showNextUpWhen == ShowNextUpWhen.DURING_CREDITS &&
|
||||
playlist != null && playlist.hasNext()
|
||||
) {
|
||||
val nextItem = playlist.peek()
|
||||
Timber.v("Setting next up during outro to ${nextItem?.id}")
|
||||
withContext(Dispatchers.Main) {
|
||||
nextUp.value = nextItem
|
||||
}
|
||||
withContext(Dispatchers.Main) {
|
||||
when (behavior) {
|
||||
SkipSegmentBehavior.AUTO_SKIP -> {
|
||||
this@PlaybackViewModel.currentSegment.value = null
|
||||
player.seekTo(currentSegment.endTicks.ticks.inWholeMilliseconds + 1)
|
||||
} else {
|
||||
val behavior =
|
||||
when (currentSegment.type) {
|
||||
MediaSegmentType.COMMERCIAL -> prefs.skipCommercials
|
||||
MediaSegmentType.PREVIEW -> prefs.skipPreviews
|
||||
MediaSegmentType.RECAP -> prefs.skipRecaps
|
||||
MediaSegmentType.OUTRO -> prefs.skipOutros
|
||||
MediaSegmentType.INTRO -> prefs.skipIntros
|
||||
MediaSegmentType.UNKNOWN -> SkipSegmentBehavior.IGNORE
|
||||
}
|
||||
withContext(Dispatchers.Main) {
|
||||
when (behavior) {
|
||||
SkipSegmentBehavior.AUTO_SKIP -> {
|
||||
this@PlaybackViewModel.currentSegment.value = null
|
||||
player.seekTo(currentSegment.endTicks.ticks.inWholeMilliseconds + 1)
|
||||
}
|
||||
|
||||
SkipSegmentBehavior.ASK_TO_SKIP -> {
|
||||
this@PlaybackViewModel.currentSegment.value = currentSegment
|
||||
}
|
||||
SkipSegmentBehavior.ASK_TO_SKIP -> {
|
||||
this@PlaybackViewModel.currentSegment.value =
|
||||
currentSegment
|
||||
}
|
||||
|
||||
else -> {
|
||||
this@PlaybackViewModel.currentSegment.value = null
|
||||
else -> {
|
||||
this@PlaybackViewModel.currentSegment.value = null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
} else if (currentSegment == null) {
|
||||
withContext(Dispatchers.Main) {
|
||||
this@PlaybackViewModel.currentSegment.value = null
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,11 @@ syntax = "proto3";
|
|||
option java_package = "com.github.damontecres.wholphin.preferences";
|
||||
option java_multiple_files = true;
|
||||
|
||||
enum ShowNextUpWhen{
|
||||
END_OF_PLAYBACK = 0;
|
||||
DURING_CREDITS = 1;
|
||||
}
|
||||
|
||||
enum SkipSegmentBehavior{
|
||||
IGNORE = 0;
|
||||
AUTO_SKIP = 1;
|
||||
|
|
@ -52,6 +57,7 @@ message PlaybackPreferences {
|
|||
PlaybackOverrides overrides = 15;
|
||||
int64 pass_out_protection_ms = 16;
|
||||
PrefContentScale global_content_scale = 17;
|
||||
ShowNextUpWhen show_next_up_when = 18;
|
||||
}
|
||||
|
||||
message HomePagePreferences{
|
||||
|
|
|
|||
|
|
@ -37,4 +37,9 @@
|
|||
<item>Never use FFmpeg decoders</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="show_next_up_when_options">
|
||||
<item>At the end of playback</item>
|
||||
<item>During end credits/outro</item>
|
||||
</string-array>
|
||||
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -100,5 +100,7 @@
|
|||
<string name="ffmpeg_extension_pref">Use FFmpeg decoder module</string>
|
||||
<string name="nav_drawer_switch_on_focus">Switch nav drawer pages on focus</string>
|
||||
<string name="nav_drawer_switch_on_focus_summary_off">Click to switch pages</string>
|
||||
<string name="show_next_up_when">Show next up</string>
|
||||
<string name="next_up">Next Up</string>
|
||||
|
||||
</resources>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue