From f220aeee44b70fce16122b0b7cb2a9dd1996e23e Mon Sep 17 00:00:00 2001 From: damontecres <154766448+damontecres@users.noreply.github.com> Date: Fri, 17 Oct 2025 12:46:55 -0400 Subject: [PATCH] Add toggles to override direct playback (#24) Fixes #10 Adds toggles to disable direct play of AC3 audio, ASS subtitles, and PGS subtitles. Also a toggle to force down mixing audio to stereo. This copies the behavior from the official client. I'm not sure if there is a way to determine if direct playing AC3 is possible because it can depend on devices other than the Android TV device such as a sound bar. So it's up to the user to toggle off support if necessary. Attribution: This PR uses on [code from the official client](https://github.com/jellyfin/jellyfin-androidtv/blob/c775603df457862c495b010550ae0aee1a66c0bc/app/src/main/java/org/jellyfin/androidtv/util/profile/deviceProfile.kt). --- .../damontecres/wholphin/MainActivity.kt | 11 +++ .../wholphin/preferences/AppPreference.kt | 55 ++++++++++++ .../preferences/AppPreferencesSerializer.kt | 15 ++++ .../wholphin/util/AppUpgradeHandler.kt | 84 +++++++++++++++++++ .../util/profile/DeviceProfileUtils.kt | 21 ++--- app/src/main/proto/WholphinDataStore.proto | 9 ++ app/src/main/res/values/strings.xml | 5 ++ 7 files changed, 190 insertions(+), 10 deletions(-) create mode 100644 app/src/main/java/com/github/damontecres/wholphin/util/AppUpgradeHandler.kt diff --git a/app/src/main/java/com/github/damontecres/wholphin/MainActivity.kt b/app/src/main/java/com/github/damontecres/wholphin/MainActivity.kt index 79d700c7..6d390e6a 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/MainActivity.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/MainActivity.kt @@ -20,6 +20,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.unit.dp import androidx.datastore.core.DataStore +import androidx.lifecycle.lifecycleScope import androidx.navigation3.runtime.rememberNavBackStack import androidx.tv.material3.ExperimentalTvMaterial3Api import androidx.tv.material3.MaterialTheme @@ -34,9 +35,13 @@ import com.github.damontecres.wholphin.ui.nav.ApplicationContent import com.github.damontecres.wholphin.ui.nav.Destination import com.github.damontecres.wholphin.ui.nav.NavigationManager import com.github.damontecres.wholphin.ui.theme.WholphinTheme +import com.github.damontecres.wholphin.util.AppUpgradeHandler +import com.github.damontecres.wholphin.util.ExceptionHandler import com.github.damontecres.wholphin.util.UpdateChecker import com.github.damontecres.wholphin.util.profile.createDeviceProfile import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch import okhttp3.OkHttpClient import timber.log.Timber import javax.inject.Inject @@ -59,10 +64,16 @@ class MainActivity : AppCompatActivity() { @Inject lateinit var updateChecker: UpdateChecker + @Inject + lateinit var appUpgradeHandler: AppUpgradeHandler + @OptIn(ExperimentalTvMaterial3Api::class) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) Timber.i("MainActivity.onCreate") + lifecycleScope.launch(Dispatchers.IO + ExceptionHandler()) { + appUpgradeHandler.run() + } setContent { CoilConfig(okHttpClient, false) val appPreferences by userPreferencesDataStore.data.collectAsState(null) diff --git a/app/src/main/java/com/github/damontecres/wholphin/preferences/AppPreference.kt b/app/src/main/java/com/github/damontecres/wholphin/preferences/AppPreference.kt index 47049a38..271e2a38 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/preferences/AppPreference.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/preferences/AppPreference.kt @@ -313,6 +313,51 @@ sealed interface AppPreference { }, ) + val Ac3Supported = + AppSwitchPreference( + title = R.string.ac3_supported, + defaultValue = true, + getter = { it.playbackPreferences.overrides.ac3Supported }, + setter = { prefs, value -> + prefs.updatePlaybackOverrides { ac3Supported = value } + }, + summaryOn = R.string.enabled, + summaryOff = R.string.disabled, + ) + val DownMixStereo = + AppSwitchPreference( + title = R.string.downmix_stereo, + defaultValue = false, + getter = { it.playbackPreferences.overrides.downmixStereo }, + setter = { prefs, value -> + prefs.updatePlaybackOverrides { downmixStereo = value } + }, + summaryOn = R.string.enabled, + summaryOff = R.string.disabled, + ) + val DirectPlayAss = + AppSwitchPreference( + title = R.string.direct_play_ass, + defaultValue = true, + getter = { it.playbackPreferences.overrides.directPlayAss }, + setter = { prefs, value -> + prefs.updatePlaybackOverrides { directPlayAss = value } + }, + summaryOn = R.string.enabled, + summaryOff = R.string.disabled, + ) + val DirectPlayPgs = + AppSwitchPreference( + title = R.string.direct_play_pgs, + defaultValue = true, + getter = { it.playbackPreferences.overrides.directPlayPgs }, + setter = { prefs, value -> + prefs.updatePlaybackOverrides { directPlayPgs = value } + }, + summaryOn = R.string.enabled, + summaryOff = R.string.disabled, + ) + val RememberSelectedTab = AppSwitchPreference( title = R.string.remember_selected_tab, @@ -521,6 +566,16 @@ val advancedPreferences = AppPreference.PlaybackDebugInfo, ), ), + PreferenceGroup( + title = R.string.playback_overrides, + preferences = + listOf( + AppPreference.DownMixStereo, + AppPreference.Ac3Supported, + AppPreference.DirectPlayAss, + AppPreference.DirectPlayPgs, + ), + ), PreferenceGroup( title = R.string.updates, preferences = diff --git a/app/src/main/java/com/github/damontecres/wholphin/preferences/AppPreferencesSerializer.kt b/app/src/main/java/com/github/damontecres/wholphin/preferences/AppPreferencesSerializer.kt index bcc6ed6f..c0289afe 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/preferences/AppPreferencesSerializer.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/preferences/AppPreferencesSerializer.kt @@ -42,6 +42,16 @@ class AppPreferencesSerializer skipCommercials = AppPreference.SkipCommercials.defaultValue skipPreviews = AppPreference.SkipPreviews.defaultValue skipRecaps = AppPreference.SkipRecaps.defaultValue + + overrides = + PlaybackOverrides + .newBuilder() + .apply { + ac3Supported = AppPreference.Ac3Supported.defaultValue + downmixStereo = AppPreference.DownMixStereo.defaultValue + directPlayAss = AppPreference.DirectPlayAss.defaultValue + directPlayPgs = AppPreference.DirectPlayPgs.defaultValue + }.build() }.build() homePagePreferences = HomePagePreferences @@ -81,6 +91,11 @@ inline fun AppPreferences.updatePlaybackPreferences(block: PlaybackPreferences.B playbackPreferences = playbackPreferences.toBuilder().apply(block).build() } +inline fun AppPreferences.updatePlaybackOverrides(block: PlaybackOverrides.Builder.() -> Unit): AppPreferences = + updatePlaybackPreferences { + overrides = overrides.toBuilder().apply(block).build() + } + inline fun AppPreferences.updateHomePagePreferences(block: HomePagePreferences.Builder.() -> Unit): AppPreferences = update { homePagePreferences = homePagePreferences.toBuilder().apply(block).build() diff --git a/app/src/main/java/com/github/damontecres/wholphin/util/AppUpgradeHandler.kt b/app/src/main/java/com/github/damontecres/wholphin/util/AppUpgradeHandler.kt new file mode 100644 index 00000000..d7296c2d --- /dev/null +++ b/app/src/main/java/com/github/damontecres/wholphin/util/AppUpgradeHandler.kt @@ -0,0 +1,84 @@ +package com.github.damontecres.wholphin.util + +import android.content.Context +import android.os.Build +import androidx.core.content.edit +import androidx.datastore.core.DataStore +import androidx.preference.PreferenceManager +import com.github.damontecres.wholphin.WholphinApplication +import com.github.damontecres.wholphin.preferences.AppPreference +import com.github.damontecres.wholphin.preferences.AppPreferences +import com.github.damontecres.wholphin.preferences.updatePlaybackOverrides +import dagger.hilt.android.qualifiers.ApplicationContext +import timber.log.Timber +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class AppUpgradeHandler + @Inject + constructor( + @param:ApplicationContext private val context: Context, + private val appPreferences: DataStore, + ) { + suspend fun run() { + val pkgInfo = WholphinApplication.instance.packageManager.getPackageInfo(WholphinApplication.instance.packageName, 0) + val prefs = PreferenceManager.getDefaultSharedPreferences(context) + val previousVersion = prefs.getString(VERSION_NAME_CURRENT_KEY, null) + val previousVersionCode = prefs.getLong(VERSION_CODE_CURRENT_KEY, -1) + + val newVersion = pkgInfo.versionName!! + val newVersionCode = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + pkgInfo.longVersionCode + } else { + pkgInfo.versionCode.toLong() + } + if (newVersion != previousVersion || newVersionCode != previousVersionCode) { + Timber.i( + "App updated: $previousVersion=>$newVersion, $previousVersionCode=>$newVersionCode", + ) + prefs.edit(true) { + putString(VERSION_NAME_PREVIOUS_KEY, previousVersion) + putLong(VERSION_CODE_PREVIOUS_KEY, previousVersionCode) + putString(VERSION_NAME_CURRENT_KEY, newVersion) + putLong(VERSION_CODE_CURRENT_KEY, newVersionCode) + } + try { + upgradeApp( + context, + Version.fromString(previousVersion ?: "0.0.0"), + Version.fromString(newVersion), + appPreferences, + ) + } catch (ex: Exception) { + Timber.e(ex, "Exception during app upgrade") + } + } + } + + companion object { + const val VERSION_NAME_PREVIOUS_KEY = "version.previous.name" + const val VERSION_CODE_PREVIOUS_KEY = "version.previous.code" + const val VERSION_NAME_CURRENT_KEY = "version.current.name" + const val VERSION_CODE_CURRENT_KEY = "version.current.code" + } + } + +suspend fun upgradeApp( + context: Context, + previous: Version, + current: Version, + appPreferences: DataStore, +) { + if (previous.isEqualOrBefore(Version.fromString("0.1.0-2-g0"))) { + appPreferences.updateData { + it.updatePlaybackOverrides { + ac3Supported = AppPreference.Ac3Supported.defaultValue + downmixStereo = AppPreference.DownMixStereo.defaultValue + directPlayAss = AppPreference.DirectPlayAss.defaultValue + directPlayPgs = AppPreference.DirectPlayPgs.defaultValue + } + } + } +} diff --git a/app/src/main/java/com/github/damontecres/wholphin/util/profile/DeviceProfileUtils.kt b/app/src/main/java/com/github/damontecres/wholphin/util/profile/DeviceProfileUtils.kt index 9dce4edd..fc7fcd11 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/util/profile/DeviceProfileUtils.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/util/profile/DeviceProfileUtils.kt @@ -45,20 +45,21 @@ private val supportedAudioCodecs = Codec.Audio.VORBIS, ) -// TODO use preferences fun createDeviceProfile( context: Context, userPreferences: UserPreferences, disableDirectPlay: Boolean = false, -) = createDeviceProfile( - mediaTest = MediaCodecCapabilitiesTest(context), - maxBitrate = 100_000_000, - disableDirectPlay = disableDirectPlay, - isAC3Enabled = true, - downMixAudio = false, - assDirectPlay = true, - pgsDirectPlay = true, -) +) = userPreferences.appPreferences.playbackPreferences.let { prefs -> + createDeviceProfile( + mediaTest = MediaCodecCapabilitiesTest(context), + maxBitrate = 100_000_000, + disableDirectPlay = disableDirectPlay, + isAC3Enabled = prefs.overrides.ac3Supported, + downMixAudio = prefs.overrides.downmixStereo, + assDirectPlay = prefs.overrides.directPlayAss, + pgsDirectPlay = prefs.overrides.directPlayPgs, + ) +} fun createDeviceProfile( mediaTest: MediaCodecCapabilitiesTest, diff --git a/app/src/main/proto/WholphinDataStore.proto b/app/src/main/proto/WholphinDataStore.proto index 4a30cbdf..bc93e765 100644 --- a/app/src/main/proto/WholphinDataStore.proto +++ b/app/src/main/proto/WholphinDataStore.proto @@ -9,6 +9,13 @@ enum SkipSegmentBehavior{ ASK_TO_SKIP = 2; } +message PlaybackOverrides{ + bool ac3_supported = 1; + bool downmix_stereo = 2; + bool direct_play_ass = 3; + bool direct_play_pgs = 4; +} + message PlaybackPreferences { int64 skip_forward_ms = 1; int64 skip_back_ms = 2; @@ -25,6 +32,8 @@ message PlaybackPreferences { SkipSegmentBehavior skip_commercials = 12; SkipSegmentBehavior skip_recaps = 13; SkipSegmentBehavior skip_previews = 14; + + PlaybackOverrides overrides = 15; } message HomePagePreferences{ diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 12a11406..88d9276b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -77,5 +77,10 @@ Clear image cache Shuffle + Device supports AC3/Dolby Digital + Always downmix to stereo + Playback overrides + Direct play ASS subtitles + Direct play PGS subtitles