Include ffmpeg extensions for some audio formats (#109)

Related to #103

Adds ffmpeg decoder module to support a few extra audio formats such as
AC3, EAC3, DTS, & TrueHD.

This increases the apk size by about 5.4mb (release 17.4mb->22.8mb), but
definitely worth it for the added support.
This commit is contained in:
damontecres 2025-10-29 20:34:41 -04:00 committed by GitHub
parent f76556f90e
commit b1275ae210
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 666 additions and 5 deletions

View file

@ -535,6 +535,19 @@ sealed interface AppPreference<T> {
valueToIndex = { it.number },
)
val FfmpegPreference =
AppChoicePreference<MediaExtensionStatus>(
title = R.string.ffmpeg_extension_pref,
defaultValue = MediaExtensionStatus.MES_FALLBACK,
getter = { it.playbackPreferences.overrides.mediaExtensionsEnabled },
setter = { prefs, value ->
prefs.updatePlaybackOverrides { mediaExtensionsEnabled = value }
},
displayValues = R.array.ffmpeg_extension_options,
indexToValue = { MediaExtensionStatus.forNumber(it) },
valueToIndex = { it.number },
)
val ClearImageCache =
AppClickablePreference(
title = R.string.clear_image_cache,
@ -657,6 +670,7 @@ val advancedPreferences =
AppPreference.Ac3Supported,
AppPreference.DirectPlayAss,
AppPreference.DirectPlayPgs,
AppPreference.FfmpegPreference,
),
),
PreferenceGroup(

View file

@ -52,6 +52,8 @@ class AppPreferencesSerializer
downmixStereo = AppPreference.DownMixStereo.defaultValue
directPlayAss = AppPreference.DirectPlayAss.defaultValue
directPlayPgs = AppPreference.DirectPlayPgs.defaultValue
mediaExtensionsEnabled =
AppPreference.FfmpegPreference.defaultValue
}.build()
}.build()
homePagePreferences =

View file

@ -5,6 +5,7 @@ import android.widget.Toast
import androidx.annotation.OptIn
import androidx.compose.ui.text.intl.Locale
import androidx.core.net.toUri
import androidx.datastore.core.DataStore
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
@ -16,6 +17,7 @@ import androidx.media3.common.Player
import androidx.media3.common.TrackSelectionOverride
import androidx.media3.common.Tracks
import androidx.media3.common.util.UnstableApi
import androidx.media3.exoplayer.DefaultRenderersFactory
import androidx.media3.exoplayer.ExoPlayer
import com.github.damontecres.wholphin.data.ItemPlaybackDao
import com.github.damontecres.wholphin.data.ItemPlaybackRepository
@ -29,6 +31,8 @@ import com.github.damontecres.wholphin.data.model.TrackIndex
import com.github.damontecres.wholphin.data.model.chooseSource
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.SkipSegmentBehavior
import com.github.damontecres.wholphin.preferences.UserPreferences
import com.github.damontecres.wholphin.ui.launchIO
@ -55,10 +59,12 @@ import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import org.jellyfin.sdk.api.client.ApiClient
import org.jellyfin.sdk.api.client.extensions.mediaInfoApi
@ -104,6 +110,7 @@ data class StreamDecision(
)
@HiltViewModel
@OptIn(markerClass = [UnstableApi::class])
class PlaybackViewModel
@Inject
constructor(
@ -114,15 +121,31 @@ class PlaybackViewModel
val itemPlaybackDao: ItemPlaybackDao,
val serverRepository: ServerRepository,
val itemPlaybackRepository: ItemPlaybackRepository,
val appPreferences: DataStore<AppPreferences>,
) : ViewModel(),
Player.Listener {
val player =
val player by lazy {
val extensions =
runBlocking { appPreferences.data.firstOrNull() }?.playbackPreferences?.overrides?.mediaExtensionsEnabled
Timber.v("extensions=$extensions")
val rendererMode =
when (extensions) {
MediaExtensionStatus.MES_FALLBACK -> DefaultRenderersFactory.EXTENSION_RENDERER_MODE_ON
MediaExtensionStatus.MES_PREFERRED -> DefaultRenderersFactory.EXTENSION_RENDERER_MODE_PREFER
MediaExtensionStatus.MES_DISABLED -> DefaultRenderersFactory.EXTENSION_RENDERER_MODE_OFF
else -> DefaultRenderersFactory.EXTENSION_RENDERER_MODE_ON
}
ExoPlayer
.Builder(context)
.build()
.setRenderersFactory(
DefaultRenderersFactory(context)
.setEnableDecoderFallback(true)
.setExtensionRendererMode(rendererMode),
).build()
.apply {
playWhenReady = true
}
}
val loading = MutableLiveData<LoadingState>(LoadingState.Loading)

View file

@ -18,11 +18,18 @@ enum PrefContentScale{
FILL_HEIGHT = 5;
}
enum MediaExtensionStatus{
MES_FALLBACK = 0;
MES_PREFERRED = 1;
MES_DISABLED = 2;
}
message PlaybackOverrides{
bool ac3_supported = 1;
bool downmix_stereo = 2;
bool direct_play_ass = 3;
bool direct_play_pgs = 4;
MediaExtensionStatus media_extensions_enabled = 5;
}
message PlaybackPreferences {

View file

@ -31,4 +31,10 @@
<item>Fill Height</item>
</string-array>
<string-array name="ffmpeg_extension_options">
<item>Only use FFmpeg if no built-in decoder exists</item>
<item>Prefer to use FFmpeg over built-in decoders</item>
<item>Never use FFmpeg decoders</item>
</string-array>
</resources>

View file

@ -97,5 +97,6 @@
<string name="send_app_logs">Send app logs to current server</string>
<string name="send_app_logs_summary">Useful for debugging</string>
<string name="global_content_scale">Default content scale</string>
<string name="ffmpeg_extension_pref">Use FFmpeg decoder module</string>
</resources>