From 1c0e9a585353cea54f6627c9f9865fade06bb2a7 Mon Sep 17 00:00:00 2001 From: Nehereus <55364637+Nehereus@users.noreply.github.com> Date: Wed, 12 Aug 2026 01:36:03 +0800 Subject: [PATCH] Add a preference option to disable hardware audio offloading (#1564) ## Description By default, Wholphin plays music with hardware audio offloading enabled. This delegates audio decoding to the device's hardware DSP, making playback stability highly dependent on the manufacturer's system audio implementation. On certain hardware (such as my TCL TVs, e.g., the Chinese Model Q9L Pro), this implementation causes music playback to hang indefinitely at 0 seconds. ### Related issues #1372 ### Testing Tested on a TCL TV (Q9L Pro): - Some musics playback hangs at 0s with offloading enabled. - Disabling audio offloading fixes the issues for all medias. ## Screenshots IMG_9486 ## AI or LLM usage Developed in pair with Gemini and human-reviewed, tested. --------- Co-authored-by: Damontecres --- .../preferences/AppPreferencesSerializer.kt | 2 + .../preferences/ExperimentalPreference.kt | 12 +++++ .../wholphin/services/MusicService.kt | 11 +++- .../wholphin/services/PlayerFactory.kt | 51 ++++++++++++------- app/src/main/proto/WholphinDataStore.proto | 1 + app/src/main/res/values/strings.xml | 2 + 6 files changed, 61 insertions(+), 18 deletions(-) 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 abcee865..c6dcd8ec 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 @@ -182,6 +182,8 @@ class AppPreferencesSerializer .apply { enabled = false videoTunnelingEnabled = false + preferAc3Surround = ExperimentalPreference.PreferAc3ForSurround.defaultValue + disableAudioOffload = ExperimentalPreference.DisableAudioOffload.defaultValue }.build() }.build() diff --git a/app/src/main/java/com/github/damontecres/wholphin/preferences/ExperimentalPreference.kt b/app/src/main/java/com/github/damontecres/wholphin/preferences/ExperimentalPreference.kt index 3950d626..e5923659 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/preferences/ExperimentalPreference.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/preferences/ExperimentalPreference.kt @@ -60,6 +60,17 @@ object ExperimentalPreference { } }, ) + + val DisableAudioOffload = + AppSwitchPreference( + title = R.string.disable_audio_offload, + defaultValue = false, + getter = { it.experimentalPreferences.disableAudioOffload }, + setter = { prefs, value -> + prefs.updateExperimentalPreferences { disableAudioOffload = value } + }, + summary = R.string.disable_audio_offload_summary, + ) } val experimentalPreferences = @@ -71,6 +82,7 @@ val experimentalPreferences = listOf( ExperimentalPreference.VideoTunneling, ExperimentalPreference.PreferAc3ForSurround, + ExperimentalPreference.DisableAudioOffload, ), ), ) diff --git a/app/src/main/java/com/github/damontecres/wholphin/services/MusicService.kt b/app/src/main/java/com/github/damontecres/wholphin/services/MusicService.kt index eea66307..38888485 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/services/MusicService.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/services/MusicService.kt @@ -13,6 +13,7 @@ import androidx.media3.session.MediaSession import com.github.damontecres.wholphin.data.ServerRepository import com.github.damontecres.wholphin.data.model.AudioItem import com.github.damontecres.wholphin.data.model.BaseItem +import com.github.damontecres.wholphin.preferences.get import com.github.damontecres.wholphin.services.hilt.DefaultCoroutineScope import com.github.damontecres.wholphin.ui.DefaultItemFields import com.github.damontecres.wholphin.ui.gt @@ -37,6 +38,7 @@ import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.update +import kotlinx.coroutines.runBlocking import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withContext @@ -83,8 +85,15 @@ class MusicService private val audioFormats by lazy { listOf(*supportedAudioCodecs) } val player: Player by lazy { + val disableAudioOffload = + runBlocking { + userPreferencesService + .getCurrent() + .appPreferences.experimentalPreferences + .get { disableAudioOffload } ?: false + } playerFactory - .createAudioPlayer() + .createAudioPlayer(disableAudioOffload = disableAudioOffload) .also { it.addListener(MusicPlayerListener(it, _state)) } diff --git a/app/src/main/java/com/github/damontecres/wholphin/services/PlayerFactory.kt b/app/src/main/java/com/github/damontecres/wholphin/services/PlayerFactory.kt index 24736015..cec0a205 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/services/PlayerFactory.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/services/PlayerFactory.kt @@ -132,9 +132,15 @@ class PlayerFactory extractorsFactory, ) } + val disableAudioOffload = + appPreferences.experimentalPreferences.get { disableAudioOffload } ?: false val tunneling = appPreferences.experimentalPreferences.get { videoTunnelingEnabled } - val trackSelector = createTrackSelector(tunneling) + val trackSelector = + createTrackSelector( + tunneling = tunneling, + disableAudioOffload = disableAudioOffload, + ) ExoPlayer .Builder(context) @@ -164,7 +170,10 @@ class PlayerFactory return PlayerCreation(newPlayer, assHandler) } - fun createAudioPlayer(extensions: MediaExtensionStatus = MediaExtensionStatus.MES_FALLBACK): ExoPlayer { + fun createAudioPlayer( + disableAudioOffload: Boolean, + extensions: MediaExtensionStatus = MediaExtensionStatus.MES_FALLBACK, + ): ExoPlayer { val rendererMode = when (extensions) { MediaExtensionStatus.MES_FALLBACK -> DefaultRenderersFactory.EXTENSION_RENDERER_MODE_ON @@ -182,7 +191,7 @@ class PlayerFactory OkHttpDataSource.Factory(authOkHttpClient), extractorsFactory, ) - val trackSelector = createTrackSelector() + val trackSelector = createTrackSelector(disableAudioOffload = disableAudioOffload) return ExoPlayer .Builder(context) .setMediaSourceFactory(mediaSourceFactory) @@ -205,20 +214,28 @@ class PlayerFactory .setConstantBitrateSeekingEnabled(true) .setConstantBitrateSeekingAlwaysEnabled(true) - private fun createTrackSelector(tunneling: Boolean? = null) = - DefaultTrackSelector(context).apply { - setParameters( - buildUponParameters() - .apply { - tunneling?.let { setTunnelingEnabled(tunneling) } - }.setAudioOffloadPreferences( - AudioOffloadPreferences - .Builder() - .setAudioOffloadMode(AudioOffloadPreferences.AUDIO_OFFLOAD_MODE_ENABLED) - .build(), - ), - ) - } + private fun createTrackSelector( + tunneling: Boolean? = null, + disableAudioOffload: Boolean = false, + ) = DefaultTrackSelector(context).apply { + val offloadMode = + if (disableAudioOffload) { + AudioOffloadPreferences.AUDIO_OFFLOAD_MODE_DISABLED + } else { + AudioOffloadPreferences.AUDIO_OFFLOAD_MODE_ENABLED + } + setParameters( + buildUponParameters() + .apply { + tunneling?.let { setTunnelingEnabled(tunneling) } + }.setAudioOffloadPreferences( + AudioOffloadPreferences + .Builder() + .setAudioOffloadMode(offloadMode) + .build(), + ), + ) + } fun createMediaSession(player: Player) = MediaSession diff --git a/app/src/main/proto/WholphinDataStore.proto b/app/src/main/proto/WholphinDataStore.proto index 2db6201b..f190b43b 100644 --- a/app/src/main/proto/WholphinDataStore.proto +++ b/app/src/main/proto/WholphinDataStore.proto @@ -225,6 +225,7 @@ message ExperimentalPreferences { bool enabled = 1; bool video_tunneling_enabled = 2; bool prefer_ac3_surround = 3; + bool disable_audio_offload = 4; } message AppPreferences { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index bf37ea15..3b1bd402 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -404,6 +404,8 @@ Will remove PIN [0–9] Image disk cache size (MB) + Disable audio hardware offload + Disabling offloading audio decoding may fix playback freezes on some devices View options Columns Spacing