mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-11-06 18:54:49 +01:00
Experimental option to transcode surround sound audio to AC3 (#947)
## Description Adds an experimental setting to prefer transcoding audio with more than 2 channels to AC3. Stereo audio does not transcode. This is basically a workaround for HDMI ARC (not eARC) or optical audio devices because instead of 2 uncompressed PCM channels, 6 AC3 channels (ie 5.1) is possible. An downside of enabling this is that embedded subtitles be burned in causing a video transcode as well. ### Related issues Fixes #255 Incorporates some code from #1410 ### Testing Just on the emulator so far to observe if the settings trigger the transcode or not ## Screenshots N/A ## AI or LLM usage None --------- Co-authored-by: darkflame91 <darkflame91@gmail.com>
This commit is contained in:
parent
f2287a85e8
commit
d481aea85a
11 changed files with 205 additions and 54 deletions
|
|
@ -53,7 +53,10 @@ sealed interface AppPreference<Pref, T> {
|
||||||
value: T?,
|
value: T?,
|
||||||
): String? = null
|
): String? = null
|
||||||
|
|
||||||
fun validate(value: T): PreferenceValidation = PreferenceValidation.Valid
|
fun validate(
|
||||||
|
prefs: Pref,
|
||||||
|
value: T,
|
||||||
|
): PreferenceValidation = PreferenceValidation.Valid
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val SkipForward =
|
val SkipForward =
|
||||||
|
|
@ -391,18 +394,25 @@ sealed interface AppPreference<Pref, T> {
|
||||||
defaultValue = true,
|
defaultValue = true,
|
||||||
getter = { it.playbackPreferences.overrides.ac3Supported },
|
getter = { it.playbackPreferences.overrides.ac3Supported },
|
||||||
setter = { prefs, value ->
|
setter = { prefs, value ->
|
||||||
prefs.updatePlaybackOverrides { ac3Supported = value }
|
if (!value) prefs.updateExperimentalPreferences { preferAc3Surround = false }
|
||||||
|
prefs.updatePlaybackOverrides {
|
||||||
|
ac3Supported = value
|
||||||
|
}
|
||||||
},
|
},
|
||||||
summaryOn = R.string.enabled,
|
summaryOn = R.string.enabled,
|
||||||
summaryOff = R.string.disabled,
|
summaryOff = R.string.disabled,
|
||||||
)
|
)
|
||||||
|
|
||||||
val DownMixStereo =
|
val DownMixStereo =
|
||||||
AppSwitchPreference<AppPreferences>(
|
AppSwitchPreference<AppPreferences>(
|
||||||
title = R.string.downmix_stereo,
|
title = R.string.downmix_stereo,
|
||||||
defaultValue = false,
|
defaultValue = false,
|
||||||
getter = { it.playbackPreferences.overrides.downmixStereo },
|
getter = { it.playbackPreferences.overrides.downmixStereo },
|
||||||
setter = { prefs, value ->
|
setter = { prefs, value ->
|
||||||
prefs.updatePlaybackOverrides { downmixStereo = value }
|
if (value) prefs.updateExperimentalPreferences { preferAc3Surround = false }
|
||||||
|
prefs.updatePlaybackOverrides {
|
||||||
|
downmixStereo = value
|
||||||
|
}
|
||||||
},
|
},
|
||||||
summaryOn = R.string.enabled,
|
summaryOn = R.string.enabled,
|
||||||
summaryOff = R.string.disabled,
|
summaryOff = R.string.disabled,
|
||||||
|
|
@ -1311,11 +1321,16 @@ data class AppSwitchPreference<Pref>(
|
||||||
override val defaultValue: Boolean,
|
override val defaultValue: Boolean,
|
||||||
override val getter: (prefs: Pref) -> Boolean,
|
override val getter: (prefs: Pref) -> Boolean,
|
||||||
override val setter: (prefs: Pref, value: Boolean) -> Pref,
|
override val setter: (prefs: Pref, value: Boolean) -> Pref,
|
||||||
val validator: (value: Boolean) -> PreferenceValidation = { PreferenceValidation.Valid },
|
val validator: (prefs: Pref, value: Boolean) -> PreferenceValidation = { _, _ -> PreferenceValidation.Valid },
|
||||||
@param:StringRes val summary: Int? = null,
|
@param:StringRes val summary: Int? = null,
|
||||||
@param:StringRes val summaryOn: Int? = null,
|
@param:StringRes val summaryOn: Int? = null,
|
||||||
@param:StringRes val summaryOff: Int? = null,
|
@param:StringRes val summaryOff: Int? = null,
|
||||||
) : AppPreference<Pref, Boolean> {
|
) : AppPreference<Pref, Boolean> {
|
||||||
|
override fun validate(
|
||||||
|
prefs: Pref,
|
||||||
|
value: Boolean,
|
||||||
|
): PreferenceValidation = validator.invoke(prefs, value)
|
||||||
|
|
||||||
override fun summary(
|
override fun summary(
|
||||||
context: Context,
|
context: Context,
|
||||||
value: Boolean?,
|
value: Boolean?,
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import com.github.damontecres.wholphin.R
|
||||||
import com.github.damontecres.wholphin.ui.nav.Destination
|
import com.github.damontecres.wholphin.ui.nav.Destination
|
||||||
import com.github.damontecres.wholphin.ui.preferences.PreferenceGroup
|
import com.github.damontecres.wholphin.ui.preferences.PreferenceGroup
|
||||||
import com.github.damontecres.wholphin.ui.preferences.PreferenceScreenOption
|
import com.github.damontecres.wholphin.ui.preferences.PreferenceScreenOption
|
||||||
|
import com.github.damontecres.wholphin.ui.preferences.PreferenceValidation
|
||||||
|
|
||||||
object ExperimentalPreference {
|
object ExperimentalPreference {
|
||||||
val Enable =
|
val Enable =
|
||||||
|
|
@ -35,6 +36,30 @@ object ExperimentalPreference {
|
||||||
summaryOn = R.string.enabled,
|
summaryOn = R.string.enabled,
|
||||||
summaryOff = R.string.disabled,
|
summaryOff = R.string.disabled,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val PreferAc3ForSurround =
|
||||||
|
AppSwitchPreference<AppPreferences>(
|
||||||
|
title = R.string.prefer_ac3_for_surround,
|
||||||
|
defaultValue = true,
|
||||||
|
getter = { it.experimentalPreferences.preferAc3Surround },
|
||||||
|
setter = { prefs, value ->
|
||||||
|
prefs.updateExperimentalPreferences {
|
||||||
|
preferAc3Surround = value
|
||||||
|
}
|
||||||
|
},
|
||||||
|
summary = R.string.prefer_ac3_for_surround_summary,
|
||||||
|
validator = { prefs, value ->
|
||||||
|
prefs.playbackPreferences.overrides.let {
|
||||||
|
if (value && !it.ac3Supported) {
|
||||||
|
PreferenceValidation.Invalid("AC3 support is not enabled")
|
||||||
|
} else if (value && it.downmixStereo) {
|
||||||
|
PreferenceValidation.Invalid("Always downmixing to stereo")
|
||||||
|
} else {
|
||||||
|
PreferenceValidation.Valid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val experimentalPreferences =
|
val experimentalPreferences =
|
||||||
|
|
@ -45,6 +70,7 @@ val experimentalPreferences =
|
||||||
preferences =
|
preferences =
|
||||||
listOf(
|
listOf(
|
||||||
ExperimentalPreference.VideoTunneling,
|
ExperimentalPreference.VideoTunneling,
|
||||||
|
ExperimentalPreference.PreferAc3ForSurround,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
package com.github.damontecres.wholphin.services
|
package com.github.damontecres.wholphin.services
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import com.github.damontecres.wholphin.preferences.AppPreferences
|
||||||
import com.github.damontecres.wholphin.preferences.AssPlaybackMode
|
import com.github.damontecres.wholphin.preferences.AssPlaybackMode
|
||||||
import com.github.damontecres.wholphin.preferences.PlaybackPreferences
|
import com.github.damontecres.wholphin.preferences.ExperimentalPreferences
|
||||||
|
import com.github.damontecres.wholphin.preferences.PlaybackOverrides
|
||||||
import com.github.damontecres.wholphin.util.WholphinDispatchers
|
import com.github.damontecres.wholphin.util.WholphinDispatchers
|
||||||
import com.github.damontecres.wholphin.util.profile.MediaCodecCapabilitiesTest
|
import com.github.damontecres.wholphin.util.profile.MediaCodecCapabilitiesTest
|
||||||
import com.github.damontecres.wholphin.util.profile.createDeviceProfile
|
import com.github.damontecres.wholphin.util.profile.createDeviceProfile
|
||||||
|
|
@ -34,20 +36,17 @@ class DeviceProfileService
|
||||||
private var deviceProfile: DeviceProfile? = null
|
private var deviceProfile: DeviceProfile? = null
|
||||||
|
|
||||||
suspend fun getOrCreateDeviceProfile(
|
suspend fun getOrCreateDeviceProfile(
|
||||||
prefs: PlaybackPreferences,
|
appPrefs: AppPreferences,
|
||||||
serverVersion: ServerVersion?,
|
serverVersion: ServerVersion?,
|
||||||
): DeviceProfile =
|
): DeviceProfile =
|
||||||
withContext(WholphinDispatchers.Default) {
|
withContext(WholphinDispatchers.Default) {
|
||||||
|
val prefs = appPrefs.playbackPreferences
|
||||||
mutex.withLock {
|
mutex.withLock {
|
||||||
val newConfig =
|
val newConfig =
|
||||||
DeviceProfileConfiguration(
|
DeviceProfileConfiguration(
|
||||||
maxBitrate = prefs.maxBitrate.toInt(),
|
maxBitrate = prefs.maxBitrate.toInt(),
|
||||||
isAC3Enabled = prefs.overrides.ac3Supported,
|
overrides = prefs.overrides,
|
||||||
downMixAudio = prefs.overrides.downmixStereo,
|
experimental = appPrefs.experimentalPreferences,
|
||||||
assPlaybackMode = prefs.overrides.assPlaybackMode,
|
|
||||||
pgsDirectPlay = prefs.overrides.directPlayPgs,
|
|
||||||
dolbyVisionELDirectPlay = prefs.overrides.directPlayDolbyVisionEL,
|
|
||||||
decodeAv1 = prefs.overrides.decodeAv1,
|
|
||||||
jellyfinTenEleven =
|
jellyfinTenEleven =
|
||||||
serverVersion != null && serverVersion >= ServerVersion(10, 11, 0),
|
serverVersion != null && serverVersion >= ServerVersion(10, 11, 0),
|
||||||
)
|
)
|
||||||
|
|
@ -57,12 +56,13 @@ class DeviceProfileService
|
||||||
createDeviceProfile(
|
createDeviceProfile(
|
||||||
mediaTest = mediaCodecCapabilitiesTest,
|
mediaTest = mediaCodecCapabilitiesTest,
|
||||||
maxBitrate = newConfig.maxBitrate,
|
maxBitrate = newConfig.maxBitrate,
|
||||||
isAC3Enabled = newConfig.isAC3Enabled,
|
isAC3Enabled = newConfig.overrides.ac3Supported,
|
||||||
downMixAudio = newConfig.downMixAudio,
|
downMixAudio = newConfig.overrides.downmixStereo,
|
||||||
assDirectPlay = newConfig.assPlaybackMode != AssPlaybackMode.ASS_TRANSCODE,
|
assDirectPlay = newConfig.overrides.assPlaybackMode != AssPlaybackMode.ASS_TRANSCODE,
|
||||||
pgsDirectPlay = newConfig.pgsDirectPlay,
|
pgsDirectPlay = newConfig.overrides.directPlayPgs,
|
||||||
dolbyVisionELDirectPlay = newConfig.dolbyVisionELDirectPlay,
|
dolbyVisionELDirectPlay = newConfig.overrides.directPlayDolbyVisionEL,
|
||||||
decodeAv1 = prefs.overrides.decodeAv1,
|
decodeAv1 = prefs.overrides.decodeAv1,
|
||||||
|
preferAc3ForSurround = appPrefs.experimentalPreferences.preferAc3Surround,
|
||||||
jellyfinTenEleven = newConfig.jellyfinTenEleven,
|
jellyfinTenEleven = newConfig.jellyfinTenEleven,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -76,11 +76,7 @@ class DeviceProfileService
|
||||||
*/
|
*/
|
||||||
data class DeviceProfileConfiguration(
|
data class DeviceProfileConfiguration(
|
||||||
val maxBitrate: Int,
|
val maxBitrate: Int,
|
||||||
val isAC3Enabled: Boolean,
|
val overrides: PlaybackOverrides,
|
||||||
val downMixAudio: Boolean,
|
val experimental: ExperimentalPreferences,
|
||||||
val assPlaybackMode: AssPlaybackMode,
|
|
||||||
val pgsDirectPlay: Boolean,
|
|
||||||
val dolbyVisionELDirectPlay: Boolean,
|
|
||||||
val decodeAv1: Boolean,
|
|
||||||
val jellyfinTenEleven: Boolean,
|
val jellyfinTenEleven: Boolean,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -61,10 +61,10 @@ class MediaReportService
|
||||||
.getItem(itemId = item.id)
|
.getItem(itemId = item.id)
|
||||||
.content.mediaSources
|
.content.mediaSources
|
||||||
val sourcesJson = json.encodeToString(sources)
|
val sourcesJson = json.encodeToString(sources)
|
||||||
val playbackPrefs = userPreferencesService.getCurrent().appPreferences.playbackPreferences
|
val appPreferences = userPreferencesService.getCurrent().appPreferences
|
||||||
val serverVersion = serverRepository.currentServer?.serverVersion
|
val serverVersion = serverRepository.currentServer?.serverVersion
|
||||||
val deviceProfile =
|
val deviceProfile =
|
||||||
deviceProfileService.getOrCreateDeviceProfile(playbackPrefs, serverVersion)
|
deviceProfileService.getOrCreateDeviceProfile(appPreferences, serverVersion)
|
||||||
val deviceProfileJson = json.encodeToString(deviceProfile)
|
val deviceProfileJson = json.encodeToString(deviceProfile)
|
||||||
val body =
|
val body =
|
||||||
"""
|
"""
|
||||||
|
|
@ -76,7 +76,14 @@ class MediaReportService
|
||||||
model=${Build.MODEL}
|
model=${Build.MODEL}
|
||||||
apiLevel=${Build.VERSION.SDK_INT}
|
apiLevel=${Build.VERSION.SDK_INT}
|
||||||
|
|
||||||
playbackPrefs=${playbackPrefs.toString().replace("\n", ", ").replace("\t", " ")}
|
playbackPrefs=${
|
||||||
|
appPreferences.playbackPreferences.toString().replace("\n", ", ")
|
||||||
|
.replace("\t", " ")
|
||||||
|
}
|
||||||
|
experimental=${
|
||||||
|
appPreferences.experimentalPreferences.toString().replace("\n", ", ")
|
||||||
|
.replace("\t", " ")
|
||||||
|
}
|
||||||
|
|
||||||
deviceProfile=$deviceProfileJson
|
deviceProfile=$deviceProfileJson
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import com.github.damontecres.wholphin.data.model.AudioItem
|
||||||
import com.github.damontecres.wholphin.data.model.BaseItem
|
import com.github.damontecres.wholphin.data.model.BaseItem
|
||||||
import com.github.damontecres.wholphin.services.hilt.DefaultCoroutineScope
|
import com.github.damontecres.wholphin.services.hilt.DefaultCoroutineScope
|
||||||
import com.github.damontecres.wholphin.ui.DefaultItemFields
|
import com.github.damontecres.wholphin.ui.DefaultItemFields
|
||||||
|
import com.github.damontecres.wholphin.ui.gt
|
||||||
import com.github.damontecres.wholphin.ui.main.settings.MoveDirection
|
import com.github.damontecres.wholphin.ui.main.settings.MoveDirection
|
||||||
import com.github.damontecres.wholphin.ui.onMain
|
import com.github.damontecres.wholphin.ui.onMain
|
||||||
import com.github.damontecres.wholphin.ui.seekBack
|
import com.github.damontecres.wholphin.ui.seekBack
|
||||||
|
|
@ -25,6 +26,7 @@ import com.github.damontecres.wholphin.util.LoadingState
|
||||||
import com.github.damontecres.wholphin.util.PlaybackItemState
|
import com.github.damontecres.wholphin.util.PlaybackItemState
|
||||||
import com.github.damontecres.wholphin.util.TrackActivityPlaybackListener
|
import com.github.damontecres.wholphin.util.TrackActivityPlaybackListener
|
||||||
import com.github.damontecres.wholphin.util.WholphinDispatchers
|
import com.github.damontecres.wholphin.util.WholphinDispatchers
|
||||||
|
import com.github.damontecres.wholphin.util.profile.Codec
|
||||||
import com.github.damontecres.wholphin.util.profile.supportedAudioCodecs
|
import com.github.damontecres.wholphin.util.profile.supportedAudioCodecs
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
|
@ -44,6 +46,7 @@ import org.jellyfin.sdk.api.client.extensions.universalAudioApi
|
||||||
import org.jellyfin.sdk.api.sockets.subscribe
|
import org.jellyfin.sdk.api.sockets.subscribe
|
||||||
import org.jellyfin.sdk.model.api.BaseItemKind
|
import org.jellyfin.sdk.model.api.BaseItemKind
|
||||||
import org.jellyfin.sdk.model.api.ImageType
|
import org.jellyfin.sdk.model.api.ImageType
|
||||||
|
import org.jellyfin.sdk.model.api.MediaStreamType
|
||||||
import org.jellyfin.sdk.model.api.PlayMethod
|
import org.jellyfin.sdk.model.api.PlayMethod
|
||||||
import org.jellyfin.sdk.model.api.PlaystateCommand
|
import org.jellyfin.sdk.model.api.PlaystateCommand
|
||||||
import org.jellyfin.sdk.model.api.PlaystateMessage
|
import org.jellyfin.sdk.model.api.PlaystateMessage
|
||||||
|
|
@ -72,6 +75,7 @@ class MusicService
|
||||||
private val playerFactory: PlayerFactory,
|
private val playerFactory: PlayerFactory,
|
||||||
private val serverRepository: ServerRepository,
|
private val serverRepository: ServerRepository,
|
||||||
private val imageUrlService: ImageUrlService,
|
private val imageUrlService: ImageUrlService,
|
||||||
|
private val userPreferencesService: UserPreferencesService,
|
||||||
) {
|
) {
|
||||||
private val _state = MutableStateFlow(MusicServiceState.EMPTY)
|
private val _state = MutableStateFlow(MusicServiceState.EMPTY)
|
||||||
val state: StateFlow<MusicServiceState> = _state
|
val state: StateFlow<MusicServiceState> = _state
|
||||||
|
|
@ -92,6 +96,11 @@ class MusicService
|
||||||
private var activityTracker: TrackActivityPlaybackListener? = null
|
private var activityTracker: TrackActivityPlaybackListener? = null
|
||||||
private var websocketJob: Job? = null
|
private var websocketJob: Job? = null
|
||||||
|
|
||||||
|
private suspend fun preferAc3Surround() =
|
||||||
|
userPreferencesService
|
||||||
|
.getCurrent()
|
||||||
|
.appPreferences.experimentalPreferences.preferAc3Surround
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start music playback
|
* Start music playback
|
||||||
*
|
*
|
||||||
|
|
@ -195,10 +204,11 @@ class MusicService
|
||||||
shuffled: Boolean,
|
shuffled: Boolean,
|
||||||
) {
|
) {
|
||||||
Timber.d("setQueue: %s items, shuffled=%s", items.size, shuffled)
|
Timber.d("setQueue: %s items, shuffled=%s", items.size, shuffled)
|
||||||
|
val preferAc3Surround = preferAc3Surround()
|
||||||
val mediaItems =
|
val mediaItems =
|
||||||
items
|
items
|
||||||
.filter { it.type == BaseItemKind.AUDIO }
|
.filter { it.type == BaseItemKind.AUDIO }
|
||||||
.map(::convert)
|
.map { convert(it, preferAc3Surround) }
|
||||||
withContext(WholphinDispatchers.Main) {
|
withContext(WholphinDispatchers.Main) {
|
||||||
player.setMediaItems(mediaItems)
|
player.setMediaItems(mediaItems)
|
||||||
player.shuffleModeEnabled = shuffled
|
player.shuffleModeEnabled = shuffled
|
||||||
|
|
@ -215,7 +225,8 @@ class MusicService
|
||||||
index: Int? = null,
|
index: Int? = null,
|
||||||
) {
|
) {
|
||||||
if (item.type == BaseItemKind.AUDIO) {
|
if (item.type == BaseItemKind.AUDIO) {
|
||||||
val mediaItem = convert(item)
|
val preferAc3Surround = preferAc3Surround()
|
||||||
|
val mediaItem = convert(item, preferAc3Surround)
|
||||||
withContext(WholphinDispatchers.Main) {
|
withContext(WholphinDispatchers.Main) {
|
||||||
if (index != null) {
|
if (index != null) {
|
||||||
player.addMediaItem(index, mediaItem)
|
player.addMediaItem(index, mediaItem)
|
||||||
|
|
@ -242,6 +253,7 @@ class MusicService
|
||||||
list: BlockingList<BaseItem?>,
|
list: BlockingList<BaseItem?>,
|
||||||
startIndex: Int,
|
startIndex: Int,
|
||||||
) = loading {
|
) = loading {
|
||||||
|
val preferAc3Surround = preferAc3Surround()
|
||||||
var remaining = startIndex
|
var remaining = startIndex
|
||||||
list.indices
|
list.indices
|
||||||
.chunked(25)
|
.chunked(25)
|
||||||
|
|
@ -252,7 +264,7 @@ class MusicService
|
||||||
list
|
list
|
||||||
.getBlocking(it)
|
.getBlocking(it)
|
||||||
?.takeIf { it.type == BaseItemKind.AUDIO }
|
?.takeIf { it.type == BaseItemKind.AUDIO }
|
||||||
?.let(::convert)
|
?.let { convert(it, preferAc3Surround) }
|
||||||
} else {
|
} else {
|
||||||
Timber.v("Skipping $remaining")
|
Timber.v("Skipping $remaining")
|
||||||
remaining--
|
remaining--
|
||||||
|
|
@ -268,12 +280,36 @@ class MusicService
|
||||||
/**
|
/**
|
||||||
* Converts a [BaseItem] into a [MediaItem] setting an [AudioItem] as its tag
|
* Converts a [BaseItem] into a [MediaItem] setting an [AudioItem] as its tag
|
||||||
*/
|
*/
|
||||||
private fun convert(audio: BaseItem): MediaItem {
|
private fun convert(
|
||||||
|
audio: BaseItem,
|
||||||
|
preferAc3Surround: Boolean,
|
||||||
|
): MediaItem {
|
||||||
|
val needsAc3Transcode =
|
||||||
|
preferAc3Surround &&
|
||||||
|
audio.data.mediaSources
|
||||||
|
?.firstOrNull()
|
||||||
|
?.mediaStreams
|
||||||
|
?.any { stream ->
|
||||||
|
stream.type == MediaStreamType.AUDIO &&
|
||||||
|
stream.channels.gt(2)
|
||||||
|
stream.codec != Codec.Audio.AC3
|
||||||
|
} == true
|
||||||
val url =
|
val url =
|
||||||
api.universalAudioApi.getUniversalAudioStreamUrl(
|
if (needsAc3Transcode) {
|
||||||
itemId = audio.id,
|
api.universalAudioApi.getUniversalAudioStreamUrl(
|
||||||
container = audioFormats,
|
itemId = audio.id,
|
||||||
)
|
container = listOf("mka"),
|
||||||
|
transcodingContainer = "mka",
|
||||||
|
maxAudioChannels = 6,
|
||||||
|
transcodingAudioChannels = 6,
|
||||||
|
audioCodec = Codec.Audio.AC3,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
api.universalAudioApi.getUniversalAudioStreamUrl(
|
||||||
|
itemId = audio.id,
|
||||||
|
container = audioFormats,
|
||||||
|
)
|
||||||
|
}
|
||||||
Timber.i("url=%s", url)
|
Timber.i("url=%s", url)
|
||||||
val imageUrl =
|
val imageUrl =
|
||||||
audio.data.albumId?.let { albumId ->
|
audio.data.albumId?.let { albumId ->
|
||||||
|
|
@ -354,7 +390,7 @@ class MusicService
|
||||||
* Play this item next after the current, ie add the item as the next index in the queue
|
* Play this item next after the current, ie add the item as the next index in the queue
|
||||||
*/
|
*/
|
||||||
suspend fun playNext(song: BaseItem) {
|
suspend fun playNext(song: BaseItem) {
|
||||||
val mediaItem = convert(song)
|
val mediaItem = convert(song, preferAc3Surround())
|
||||||
onMain {
|
onMain {
|
||||||
player.addMediaItem(state.value.currentIndex + 1, mediaItem)
|
player.addMediaItem(state.value.currentIndex + 1, mediaItem)
|
||||||
if (player.mediaItemCount == 1) {
|
if (player.mediaItemCount == 1) {
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,7 @@ import com.github.damontecres.wholphin.services.ScreensaverService
|
||||||
import com.github.damontecres.wholphin.services.StreamChoiceService
|
import com.github.damontecres.wholphin.services.StreamChoiceService
|
||||||
import com.github.damontecres.wholphin.services.UserPreferencesService
|
import com.github.damontecres.wholphin.services.UserPreferencesService
|
||||||
import com.github.damontecres.wholphin.ui.formatBitrate
|
import com.github.damontecres.wholphin.ui.formatBitrate
|
||||||
|
import com.github.damontecres.wholphin.ui.gt
|
||||||
import com.github.damontecres.wholphin.ui.isNotNullOrBlank
|
import com.github.damontecres.wholphin.ui.isNotNullOrBlank
|
||||||
import com.github.damontecres.wholphin.ui.launchDefault
|
import com.github.damontecres.wholphin.ui.launchDefault
|
||||||
import com.github.damontecres.wholphin.ui.launchIO
|
import com.github.damontecres.wholphin.ui.launchIO
|
||||||
|
|
@ -646,7 +647,7 @@ class PlaybackViewModel
|
||||||
deviceProfile =
|
deviceProfile =
|
||||||
if (currentPlayer.value!!.backend == PlayerBackend.EXO_PLAYER) {
|
if (currentPlayer.value!!.backend == PlayerBackend.EXO_PLAYER) {
|
||||||
deviceProfileService.getOrCreateDeviceProfile(
|
deviceProfileService.getOrCreateDeviceProfile(
|
||||||
preferences.appPreferences.playbackPreferences,
|
preferences.appPreferences,
|
||||||
serverRepository.currentServer?.serverVersion,
|
serverRepository.currentServer?.serverVersion,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -855,9 +856,24 @@ class PlaybackViewModel
|
||||||
// TODO there's probably no reason why we can't add external subtitles?
|
// TODO there's probably no reason why we can't add external subtitles?
|
||||||
Timber.v("changeStreams direct play")
|
Timber.v("changeStreams direct play")
|
||||||
|
|
||||||
|
// TODO Better way to handle unsupported types in general is needed
|
||||||
|
// This is a workaround for switching to a non AC3 track when the user wants audio transcoded to AC3
|
||||||
|
if (preferences.appPreferences.experimentalPreferences.preferAc3Surround && audioIndex != null) {
|
||||||
|
currentPlayback.mediaSourceInfo.mediaStreams
|
||||||
|
.orEmpty()
|
||||||
|
.firstOrNull { it.index == audioIndex }
|
||||||
|
?.let {
|
||||||
|
if (it.channels.gt(2) && it.codec != Codec.Audio.AC3) {
|
||||||
|
// User wants to transcode audio into AC3
|
||||||
|
return@withContext false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val source = currentPlayback.mediaSourceInfo
|
val source = currentPlayback.mediaSourceInfo
|
||||||
val externalSubtitle = source.findExternalSubtitle(subtitleIndex)
|
val externalSubtitle = source.findExternalSubtitle(subtitleIndex)
|
||||||
|
|
||||||
|
// TODO there's probably no reason why we can't add external subtitles?
|
||||||
if (externalSubtitle == null) {
|
if (externalSubtitle == null) {
|
||||||
val result =
|
val result =
|
||||||
withContext(WholphinDispatchers.Main) {
|
withContext(WholphinDispatchers.Main) {
|
||||||
|
|
|
||||||
|
|
@ -621,7 +621,7 @@ fun PreferencesContent(
|
||||||
value = value,
|
value = value,
|
||||||
onNavigate = viewModel.navigationManager::navigateTo,
|
onNavigate = viewModel.navigationManager::navigateTo,
|
||||||
onValueChange = { newValue ->
|
onValueChange = { newValue ->
|
||||||
val validation = pref.validate(newValue)
|
val validation = pref.validate(preferences, newValue)
|
||||||
when (validation) {
|
when (validation) {
|
||||||
is PreferenceValidation.Invalid -> {
|
is PreferenceValidation.Invalid -> {
|
||||||
// TODO?
|
// TODO?
|
||||||
|
|
|
||||||
|
|
@ -162,7 +162,7 @@ fun SubtitlePreferencesContent(
|
||||||
value = value,
|
value = value,
|
||||||
onNavigate = viewModel.navigationManager::navigateTo,
|
onNavigate = viewModel.navigationManager::navigateTo,
|
||||||
onValueChange = { newValue ->
|
onValueChange = { newValue ->
|
||||||
val validation = pref.validate(newValue)
|
val validation = pref.validate(preferences, newValue)
|
||||||
when (validation) {
|
when (validation) {
|
||||||
is PreferenceValidation.Invalid -> {
|
is PreferenceValidation.Invalid -> {
|
||||||
// TODO?
|
// TODO?
|
||||||
|
|
|
||||||
|
|
@ -71,6 +71,7 @@ fun createDeviceProfile(
|
||||||
dolbyVisionELDirectPlay: Boolean,
|
dolbyVisionELDirectPlay: Boolean,
|
||||||
decodeAv1: Boolean,
|
decodeAv1: Boolean,
|
||||||
jellyfinTenEleven: Boolean,
|
jellyfinTenEleven: Boolean,
|
||||||
|
preferAc3ForSurround: Boolean,
|
||||||
) = buildDeviceProfile {
|
) = buildDeviceProfile {
|
||||||
val allowedAudioCodecs =
|
val allowedAudioCodecs =
|
||||||
when {
|
when {
|
||||||
|
|
@ -132,31 +133,61 @@ fun createDeviceProfile(
|
||||||
|
|
||||||
// / Transcoding profiles
|
// / Transcoding profiles
|
||||||
// Video
|
// Video
|
||||||
transcodingProfile {
|
if (preferAc3ForSurround) {
|
||||||
type = DlnaProfileType.VIDEO
|
transcodingProfile {
|
||||||
context = EncodingContext.STREAMING
|
type = DlnaProfileType.VIDEO
|
||||||
|
context = EncodingContext.STREAMING
|
||||||
|
|
||||||
container = Codec.Container.TS
|
container = Codec.Container.TS
|
||||||
protocol = MediaStreamProtocol.HLS
|
protocol = MediaStreamProtocol.HLS
|
||||||
|
|
||||||
if (supportsHevc) videoCodec(Codec.Video.HEVC)
|
if (supportsHevc) videoCodec(Codec.Video.HEVC)
|
||||||
videoCodec(Codec.Video.H264)
|
videoCodec(Codec.Video.H264)
|
||||||
|
|
||||||
audioCodec(*allowedAudioCodecs)
|
audioCodec(Codec.Audio.AC3)
|
||||||
|
|
||||||
copyTimestamps = false
|
copyTimestamps = false
|
||||||
enableSubtitlesInManifest = true
|
enableSubtitlesInManifest = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
transcodingProfile {
|
||||||
|
type = DlnaProfileType.VIDEO
|
||||||
|
context = EncodingContext.STREAMING
|
||||||
|
|
||||||
|
container = Codec.Container.TS
|
||||||
|
protocol = MediaStreamProtocol.HLS
|
||||||
|
|
||||||
|
if (supportsHevc) videoCodec(Codec.Video.HEVC)
|
||||||
|
videoCodec(Codec.Video.H264)
|
||||||
|
|
||||||
|
audioCodec(*allowedAudioCodecs)
|
||||||
|
|
||||||
|
copyTimestamps = false
|
||||||
|
enableSubtitlesInManifest = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Audio
|
// Audio
|
||||||
transcodingProfile {
|
if (preferAc3ForSurround) {
|
||||||
type = DlnaProfileType.AUDIO
|
transcodingProfile {
|
||||||
context = EncodingContext.STREAMING
|
type = DlnaProfileType.AUDIO
|
||||||
|
context = EncodingContext.STREAMING
|
||||||
|
|
||||||
container = Codec.Container.TS
|
container = Codec.Container.TS
|
||||||
protocol = MediaStreamProtocol.HLS
|
protocol = MediaStreamProtocol.HLS
|
||||||
|
|
||||||
audioCodec(Codec.Audio.AAC)
|
audioCodec(Codec.Audio.AC3)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
transcodingProfile {
|
||||||
|
type = DlnaProfileType.AUDIO
|
||||||
|
context = EncodingContext.STREAMING
|
||||||
|
|
||||||
|
container = Codec.Container.TS
|
||||||
|
protocol = MediaStreamProtocol.HLS
|
||||||
|
|
||||||
|
audioCodec(Codec.Audio.AAC)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// / Direct play profiles
|
// / Direct play profiles
|
||||||
|
|
@ -530,6 +561,27 @@ fun createDeviceProfile(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (preferAc3ForSurround) {
|
||||||
|
supportedAudioCodecs
|
||||||
|
.filterNot { it == Codec.Audio.AC3 }
|
||||||
|
.forEach { audioCodec ->
|
||||||
|
codecProfile {
|
||||||
|
type = CodecType.VIDEO_AUDIO
|
||||||
|
codec = audioCodec
|
||||||
|
conditions {
|
||||||
|
ProfileConditionValue.AUDIO_CHANNELS lowerThanOrEquals 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
codecProfile {
|
||||||
|
type = CodecType.AUDIO
|
||||||
|
codec = audioCodec
|
||||||
|
conditions {
|
||||||
|
ProfileConditionValue.AUDIO_CHANNELS lowerThanOrEquals 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Audio channel profile
|
// Audio channel profile
|
||||||
codecProfile {
|
codecProfile {
|
||||||
type = CodecType.VIDEO_AUDIO
|
type = CodecType.VIDEO_AUDIO
|
||||||
|
|
|
||||||
|
|
@ -215,6 +215,7 @@ message MusicPreferences {
|
||||||
message ExperimentalPreferences {
|
message ExperimentalPreferences {
|
||||||
bool enabled = 1;
|
bool enabled = 1;
|
||||||
bool video_tunneling_enabled = 2;
|
bool video_tunneling_enabled = 2;
|
||||||
|
bool prefer_ac3_surround = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
message AppPreferences {
|
message AppPreferences {
|
||||||
|
|
|
||||||
|
|
@ -551,6 +551,8 @@
|
||||||
<string name="display_presets">Display presets</string>
|
<string name="display_presets">Display presets</string>
|
||||||
<string name="display_presets_description">Built-in presets to quickly style all rows</string>
|
<string name="display_presets_description">Built-in presets to quickly style all rows</string>
|
||||||
<string name="customize_home_summary">Choose rows and images on the home page</string>
|
<string name="customize_home_summary">Choose rows and images on the home page</string>
|
||||||
|
<string name="prefer_ac3_for_surround_summary"><![CDATA[Transcode non-stereo audio to AC3/Dolby Digital]]></string>
|
||||||
|
<string name="prefer_ac3_for_surround">Use AC3 for surround sound audio</string>
|
||||||
<string name="start_after">Start after</string>
|
<string name="start_after">Start after</string>
|
||||||
<string name="animate">Animate</string>
|
<string name="animate">Animate</string>
|
||||||
<string name="max_age_rating">Max age rating</string>
|
<string name="max_age_rating">Max age rating</string>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue