Add settings to switch player backends

This commit is contained in:
Damontecres 2025-11-05 16:04:10 -05:00
parent 4bd6672a8c
commit 5563935a51
No known key found for this signature in database
8 changed files with 106 additions and 22 deletions

View file

@ -654,6 +654,30 @@ sealed interface AppPreference<T> {
summaryOn = R.string.enabled, summaryOn = R.string.enabled,
summaryOff = R.string.disabled, summaryOff = R.string.disabled,
) )
val PlayerBackendPref =
AppChoicePreference<PlayerBackend>(
title = R.string.player_backend,
defaultValue = PlayerBackend.EXO_PLAYER,
getter = { it.playbackPreferences.playerBackend },
setter = { prefs, value ->
prefs.updatePlaybackPreferences { playerBackend = value }
},
displayValues = R.array.player_backend_options,
indexToValue = { PlayerBackend.forNumber(it) },
valueToIndex = { it.number },
)
val MpvHardwareDecoding =
AppSwitchPreference(
title = R.string.mpv_hardware_decoding,
defaultValue = true,
getter = { it.playbackPreferences.mpvOptions.enableHardwareDecoding },
setter = { prefs, value ->
prefs.updateMpvOptions { enableHardwareDecoding = value }
},
summary = R.string.disable_if_crash,
)
} }
} }
@ -739,6 +763,8 @@ val advancedPreferences =
AppPreference.SkipRecaps, AppPreference.SkipRecaps,
AppPreference.MaxBitrate, AppPreference.MaxBitrate,
AppPreference.PlaybackDebugInfo, AppPreference.PlaybackDebugInfo,
AppPreference.PlayerBackendPref,
AppPreference.MpvHardwareDecoding,
), ),
), ),
PreferenceGroup( PreferenceGroup(

View file

@ -44,6 +44,7 @@ class AppPreferencesSerializer
passOutProtectionMs = passOutProtectionMs =
AppPreference.PassOutProtection.defaultValue.hours.inWholeMilliseconds AppPreference.PassOutProtection.defaultValue.hours.inWholeMilliseconds
showNextUpWhen = AppPreference.ShowNextUpTiming.defaultValue showNextUpWhen = AppPreference.ShowNextUpTiming.defaultValue
playerBackend = AppPreference.PlayerBackendPref.defaultValue
overrides = overrides =
PlaybackOverrides PlaybackOverrides
@ -56,6 +57,14 @@ class AppPreferencesSerializer
mediaExtensionsEnabled = mediaExtensionsEnabled =
AppPreference.FfmpegPreference.defaultValue AppPreference.FfmpegPreference.defaultValue
}.build() }.build()
mpvOptions =
MpvOptions
.newBuilder()
.apply {
enableHardwareDecoding =
AppPreference.MpvHardwareDecoding.defaultValue
}.build()
}.build() }.build()
homePagePreferences = homePagePreferences =
HomePagePreferences HomePagePreferences
@ -103,6 +112,11 @@ inline fun AppPreferences.updatePlaybackOverrides(block: PlaybackOverrides.Build
overrides = overrides.toBuilder().apply(block).build() overrides = overrides.toBuilder().apply(block).build()
} }
inline fun AppPreferences.updateMpvOptions(block: MpvOptions.Builder.() -> Unit): AppPreferences =
updatePlaybackPreferences {
mpvOptions = mpvOptions.toBuilder().apply(block).build()
}
inline fun AppPreferences.updateHomePagePreferences(block: HomePagePreferences.Builder.() -> Unit): AppPreferences = inline fun AppPreferences.updateHomePagePreferences(block: HomePagePreferences.Builder.() -> Unit): AppPreferences =
update { update {
homePagePreferences = homePagePreferences.toBuilder().apply(block).build() homePagePreferences = homePagePreferences.toBuilder().apply(block).build()

View file

@ -390,6 +390,12 @@ fun PlaybackOverlay(
.padding(16.dp) .padding(16.dp)
.background(AppColors.TransparentBlack50), .background(AppColors.TransparentBlack50),
) { ) {
Text(
text = "Backend: ${currentPlayback?.backend}",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurface,
modifier = Modifier.padding(start = 8.dp),
)
Text( Text(
text = "Play method: ${currentPlayback?.playMethod?.serialName}", text = "Play method: ${currentPlayback?.playMethod?.serialName}",
style = MaterialTheme.typography.bodySmall, style = MaterialTheme.typography.bodySmall,

View file

@ -18,6 +18,7 @@ import androidx.media3.common.TrackSelectionOverride
import androidx.media3.common.Tracks import androidx.media3.common.Tracks
import androidx.media3.common.util.UnstableApi import androidx.media3.common.util.UnstableApi
import androidx.media3.exoplayer.DefaultRenderersFactory import androidx.media3.exoplayer.DefaultRenderersFactory
import androidx.media3.exoplayer.ExoPlayer
import com.github.damontecres.wholphin.R import com.github.damontecres.wholphin.R
import com.github.damontecres.wholphin.data.ItemPlaybackDao import com.github.damontecres.wholphin.data.ItemPlaybackDao
import com.github.damontecres.wholphin.data.ItemPlaybackRepository import com.github.damontecres.wholphin.data.ItemPlaybackRepository
@ -33,6 +34,7 @@ import com.github.damontecres.wholphin.data.model.chooseStream
import com.github.damontecres.wholphin.preferences.AppPreference import com.github.damontecres.wholphin.preferences.AppPreference
import com.github.damontecres.wholphin.preferences.AppPreferences import com.github.damontecres.wholphin.preferences.AppPreferences
import com.github.damontecres.wholphin.preferences.MediaExtensionStatus import com.github.damontecres.wholphin.preferences.MediaExtensionStatus
import com.github.damontecres.wholphin.preferences.PlayerBackend
import com.github.damontecres.wholphin.preferences.ShowNextUpWhen import com.github.damontecres.wholphin.preferences.ShowNextUpWhen
import com.github.damontecres.wholphin.preferences.SkipSegmentBehavior import com.github.damontecres.wholphin.preferences.SkipSegmentBehavior
import com.github.damontecres.wholphin.preferences.UserPreferences import com.github.damontecres.wholphin.preferences.UserPreferences
@ -121,28 +123,41 @@ class PlaybackViewModel
) : ViewModel(), ) : ViewModel(),
Player.Listener { Player.Listener {
val player by lazy { val player by lazy {
val extensions = val prefs = runBlocking { appPreferences.data.firstOrNull()?.playbackPreferences }
runBlocking { appPreferences.data.firstOrNull() }?.playbackPreferences?.overrides?.mediaExtensionsEnabled
Timber.v("extensions=$extensions") val backend = prefs?.playerBackend ?: AppPreference.PlayerBackendPref.defaultValue
val rendererMode = when (backend) {
when (extensions) { PlayerBackend.MPV -> {
MediaExtensionStatus.MES_FALLBACK -> DefaultRenderersFactory.EXTENSION_RENDERER_MODE_ON val enableHardwareDecoding =
MediaExtensionStatus.MES_PREFERRED -> DefaultRenderersFactory.EXTENSION_RENDERER_MODE_PREFER prefs?.mpvOptions?.enableHardwareDecoding
MediaExtensionStatus.MES_DISABLED -> DefaultRenderersFactory.EXTENSION_RENDERER_MODE_OFF ?: AppPreference.MpvHardwareDecoding.defaultValue
else -> DefaultRenderersFactory.EXTENSION_RENDERER_MODE_ON MpvPlayer(context, enableHardwareDecoding)
.apply {
playWhenReady = true
}
} }
// ExoPlayer
// .Builder(context) PlayerBackend.EXO_PLAYER,
// .setRenderersFactory( PlayerBackend.UNRECOGNIZED,
// DefaultRenderersFactory(context) -> {
// .setEnableDecoderFallback(true) val extensions = prefs?.overrides?.mediaExtensionsEnabled
// .setExtensionRendererMode(rendererMode), Timber.v("extensions=$extensions")
// ).build() val rendererMode =
// TODO add settings for enabling HW decoding when (extensions) {
MpvPlayer(context, false) MediaExtensionStatus.MES_FALLBACK -> DefaultRenderersFactory.EXTENSION_RENDERER_MODE_ON
.apply { MediaExtensionStatus.MES_PREFERRED -> DefaultRenderersFactory.EXTENSION_RENDERER_MODE_PREFER
playWhenReady = true MediaExtensionStatus.MES_DISABLED -> DefaultRenderersFactory.EXTENSION_RENDERER_MODE_OFF
else -> DefaultRenderersFactory.EXTENSION_RENDERER_MODE_ON
}
ExoPlayer
.Builder(context)
.setRenderersFactory(
DefaultRenderersFactory(context)
.setEnableDecoderFallback(true)
.setExtensionRendererMode(rendererMode),
).build()
} }
}
} }
val loading = MutableLiveData<LoadingState>(LoadingState.Loading) val loading = MutableLiveData<LoadingState>(LoadingState.Loading)
@ -518,7 +533,8 @@ class PlaybackViewModel
val playback = val playback =
CurrentPlayback( CurrentPlayback(
listOf(), backend = preferences.appPreferences.playbackPreferences.playerBackend,
tracks = listOf(),
playMethod = transcodeType, playMethod = transcodeType,
playSessionId = response.playSessionId, playSessionId = response.playSessionId,
liveStreamId = source.liveStreamId, liveStreamId = source.liveStreamId,
@ -1029,6 +1045,7 @@ class PlaybackViewModel
} }
data class CurrentPlayback( data class CurrentPlayback(
val backend: PlayerBackend,
val tracks: List<TrackSupport>, val tracks: List<TrackSupport>,
val playMethod: PlayMethod, val playMethod: PlayMethod,
val playSessionId: String?, val playSessionId: String?,

View file

@ -293,8 +293,10 @@ class MpvPlayer(
} }
override fun getDuration(): Long { override fun getDuration(): Long {
throwIfReleased()
if (DEBUG) Timber.v("getDuration") if (DEBUG) Timber.v("getDuration")
if (isReleased) {
return durationMs
}
val duration = val duration =
MPVLib.getPropertyDouble("duration/full")?.seconds?.inWholeMilliseconds MPVLib.getPropertyDouble("duration/full")?.seconds?.inWholeMilliseconds
?: durationMs ?: durationMs

View file

@ -29,6 +29,15 @@ enum MediaExtensionStatus{
MES_DISABLED = 2; MES_DISABLED = 2;
} }
enum PlayerBackend{
EXO_PLAYER = 0;
MPV = 1;
}
message MpvOptions{
bool enable_hardware_decoding = 1;
}
message PlaybackOverrides{ message PlaybackOverrides{
bool ac3_supported = 1; bool ac3_supported = 1;
bool downmix_stereo = 2; bool downmix_stereo = 2;
@ -58,6 +67,8 @@ message PlaybackPreferences {
int64 pass_out_protection_ms = 16; int64 pass_out_protection_ms = 16;
PrefContentScale global_content_scale = 17; PrefContentScale global_content_scale = 17;
ShowNextUpWhen show_next_up_when = 18; ShowNextUpWhen show_next_up_when = 18;
PlayerBackend player_backend = 19;
MpvOptions mpv_options = 20;
} }
message HomePagePreferences{ message HomePagePreferences{

View file

@ -90,4 +90,9 @@
<item>During end credits/outro</item> <item>During end credits/outro</item>
</string-array> </string-array>
<string-array name="player_backend_options">
<item>ExoPlayer (default)</item>
<item>MPV (Experimental)</item>
</string-array>
</resources> </resources>

View file

@ -142,6 +142,9 @@
<string name="aired_episode_order">Aired Episode Order</string> <string name="aired_episode_order">Aired Episode Order</string>
<string name="create_playlist">Create new playlist</string> <string name="create_playlist">Create new playlist</string>
<string name="add_to_playlist">Add to playlist</string> <string name="add_to_playlist">Add to playlist</string>
<string name="player_backend">Playback Backend</string>
<string name="mpv_hardware_decoding">MPV: Use hardware decoding</string>
<string name="disable_if_crash">Disable if you experience crashes</string>
<plurals name="downloads"> <plurals name="downloads">
<item quantity="zero">%d downloads</item> <item quantity="zero">%d downloads</item>