mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-11-05 18:30:59 +01:00
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 <!-- Please include screenshots if the PR alters any UI elements --> <img width="4284" height="5712" alt="IMG_9486" src="https://github.com/user-attachments/assets/ddf95e68-6ebd-42f9-9565-568de4fbfc42" /> ## AI or LLM usage Developed in pair with Gemini and human-reviewed, tested. --------- Co-authored-by: Damontecres <damontecres@gmail.com>
This commit is contained in:
parent
de30c2ccac
commit
1c0e9a5853
6 changed files with 61 additions and 18 deletions
|
|
@ -182,6 +182,8 @@ class AppPreferencesSerializer
|
|||
.apply {
|
||||
enabled = false
|
||||
videoTunnelingEnabled = false
|
||||
preferAc3Surround = ExperimentalPreference.PreferAc3ForSurround.defaultValue
|
||||
disableAudioOffload = ExperimentalPreference.DisableAudioOffload.defaultValue
|
||||
}.build()
|
||||
}.build()
|
||||
|
||||
|
|
|
|||
|
|
@ -60,6 +60,17 @@ object ExperimentalPreference {
|
|||
}
|
||||
},
|
||||
)
|
||||
|
||||
val DisableAudioOffload =
|
||||
AppSwitchPreference<AppPreferences>(
|
||||
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,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -404,6 +404,8 @@
|
|||
<string name="will_remove_pin">Will remove PIN</string>
|
||||
<string name="pin_digits_hint">[0–9]</string>
|
||||
<string name="image_cache_size">Image disk cache size (MB)</string>
|
||||
<string name="disable_audio_offload">Disable audio hardware offload</string>
|
||||
<string name="disable_audio_offload_summary">Disabling offloading audio decoding may fix playback freezes on some devices</string>
|
||||
<string name="view_options">View options</string>
|
||||
<string name="columns">Columns</string>
|
||||
<string name="spacing">Spacing</string>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue