Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,10 @@ sealed interface AppPreference<Pref, T> {
value: T?,
): String? = null

fun validate(value: T): PreferenceValidation = PreferenceValidation.Valid
fun validate(
prefs: Pref,
value: T,
): PreferenceValidation = PreferenceValidation.Valid

companion object {
val SkipForward =
Expand Down Expand Up @@ -391,18 +394,49 @@ sealed interface AppPreference<Pref, T> {
defaultValue = true,
getter = { it.playbackPreferences.overrides.ac3Supported },
setter = { prefs, value ->
prefs.updatePlaybackOverrides { ac3Supported = value }
prefs.updatePlaybackOverrides {
ac3Supported = value
if (!value) preferAc3Surround = false
}
},
summaryOn = R.string.enabled,
summaryOff = R.string.disabled,
)
val PreferAc3ForSurround =
AppSwitchPreference<AppPreferences>(
title = R.string.prefer_ac3_for_surround,
defaultValue = true,
getter = { it.playbackPreferences.overrides.preferAc3Surround },
setter = { prefs, value ->
prefs.updatePlaybackOverrides {
preferAc3Surround = value
}
},
summaryOn = R.string.prefer_ac3_for_surround_summary,
// summaryOn = R.string.enabled,
summaryOff = R.string.disabled,
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 DownMixStereo =
AppSwitchPreference<AppPreferences>(
title = R.string.downmix_stereo,
defaultValue = false,
getter = { it.playbackPreferences.overrides.downmixStereo },
setter = { prefs, value ->
prefs.updatePlaybackOverrides { downmixStereo = value }
prefs.updatePlaybackOverrides {
downmixStereo = value
if (value) preferAc3Surround = false
}
},
summaryOn = R.string.enabled,
summaryOff = R.string.disabled,
Expand Down Expand Up @@ -1108,6 +1142,7 @@ private val ExoPlayerSettings =
AppPreference.FfmpegPreference,
AppPreference.DownMixStereo,
AppPreference.Ac3Supported,
AppPreference.PreferAc3ForSurround,
AppPreference.AssSubtitleMode,
AppPreference.DirectPlayPgs,
AppPreference.DirectPlayDoviProfile7,
Expand Down Expand Up @@ -1276,11 +1311,16 @@ data class AppSwitchPreference<Pref>(
override val defaultValue: Boolean,
override val getter: (prefs: Pref) -> Boolean,
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 summaryOn: Int? = null,
@param:StringRes val summaryOff: Int? = null,
) : AppPreference<Pref, Boolean> {
override fun validate(
prefs: Pref,
value: Boolean,
): PreferenceValidation = validator.invoke(prefs, value)

override fun summary(
context: Context,
value: Boolean?,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ class DeviceProfileService
pgsDirectPlay = prefs.overrides.directPlayPgs,
dolbyVisionELDirectPlay = prefs.overrides.directPlayDolbyVisionEL,
decodeAv1 = prefs.overrides.decodeAv1,
preferAc3ForSurround = prefs.overrides.preferAc3Surround,
jellyfinTenEleven =
serverVersion != null && serverVersion >= ServerVersion(10, 11, 0),
)
Expand All @@ -63,6 +64,7 @@ class DeviceProfileService
pgsDirectPlay = newConfig.pgsDirectPlay,
dolbyVisionELDirectPlay = newConfig.dolbyVisionELDirectPlay,
decodeAv1 = prefs.overrides.decodeAv1,
preferAc3ForSurround = prefs.overrides.preferAc3Surround,
jellyfinTenEleven = newConfig.jellyfinTenEleven,
)
}
Expand All @@ -82,5 +84,6 @@ data class DeviceProfileConfiguration(
val pgsDirectPlay: Boolean,
val dolbyVisionELDirectPlay: Boolean,
val decodeAv1: Boolean,
val preferAc3ForSurround: Boolean,
val jellyfinTenEleven: Boolean,
)
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ package com.github.damontecres.wholphin.ui.detail

import android.content.Context
import android.hardware.display.DisplayManager
import android.media.AudioDeviceCallback
import android.media.AudioDeviceInfo
import android.media.AudioFormat
import android.media.AudioManager
import android.os.Build
import android.util.Log
import android.view.Display
Expand All @@ -15,8 +19,10 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
Expand All @@ -39,11 +45,15 @@ import com.github.damontecres.wholphin.data.ItemPlaybackDao
import com.github.damontecres.wholphin.data.ServerRepository
import com.github.damontecres.wholphin.data.model.ItemPlayback
import com.github.damontecres.wholphin.preferences.UserPreferences
import com.github.damontecres.wholphin.ui.launchDefault
import com.github.damontecres.wholphin.ui.launchIO
import com.github.damontecres.wholphin.ui.showToast
import com.github.damontecres.wholphin.util.ExceptionHandler
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.acra.util.versionCodeLong
Expand All @@ -61,13 +71,15 @@ import javax.inject.Inject
class DebugViewModel
@Inject
constructor(
@param:ApplicationContext private val context: Context,
val serverRepository: ServerRepository,
val itemPlaybackDao: ItemPlaybackDao,
val clientInfo: ClientInfo,
val deviceInfo: DeviceInfo,
) : ViewModel() {
val itemPlaybacks = MutableLiveData<List<ItemPlayback>>(listOf())
val logcat = MutableLiveData<List<LogcatLine>>(listOf())
val audioInfo = MutableStateFlow<AudioInfo>(AudioInfo())

val supportedModes by lazy {
val displayManager =
Expand Down Expand Up @@ -117,6 +129,35 @@ class DebugViewModel
this@DebugViewModel.logcat.value = logcat
}
}
viewModelScope.launchDefault {
val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
val callback =
object : AudioDeviceCallback() {
override fun onAudioDevicesAdded(addedDevices: Array<AudioDeviceInfo>) {
audioInfo.update {
it.copy(
devices =
it.devices
.toMutableList()
.apply { addAll(addedDevices.filter { it.isSink }) },
)
}
}

override fun onAudioDevicesRemoved(removedDevices: Array<out AudioDeviceInfo>) {
audioInfo.update {
it.copy(
devices =
it.devices
.toMutableList()
.apply { removeAll(removedDevices) },
)
}
}
}
audioManager.registerAudioDeviceCallback(callback, null)
addCloseable { audioManager.unregisterAudioDeviceCallback(callback) }
}
}

companion object {
Expand Down Expand Up @@ -193,6 +234,127 @@ data class LogcatLine(
val text: String,
)

data class AudioInfo(
val devices: List<AudioDeviceInfo> = emptyList(),
)

val AudioDeviceInfo.details: String
get() {
val typeName =
when (type) {
AudioDeviceInfo.TYPE_HDMI -> "HDMI"
AudioDeviceInfo.TYPE_HDMI_ARC -> "ARC"
AudioDeviceInfo.TYPE_HDMI_EARC -> "eARC"
AudioDeviceInfo.TYPE_BUILTIN_SPEAKER -> "Speaker"
AudioDeviceInfo.TYPE_BUILTIN_SPEAKER_SAFE -> "Speaker Safe"
else -> "N/A"
}
val addressStr =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
address
} else {
null
}
val encodings =
encodings.map { enc ->
when (enc) {
AudioFormat.ENCODING_INVALID -> "INVALID"

AudioFormat.ENCODING_PCM_16BIT -> "PCM_16BIT"

AudioFormat.ENCODING_PCM_8BIT -> "PCM_8BIT"

AudioFormat.ENCODING_PCM_FLOAT -> "PCM_FLOAT"

AudioFormat.ENCODING_AC3 -> "AC3"

AudioFormat.ENCODING_E_AC3 -> "E_AC3"

AudioFormat.ENCODING_DTS -> "DTS"

AudioFormat.ENCODING_DTS_HD -> "DTS_HD"

AudioFormat.ENCODING_MP3 -> "MP3"

AudioFormat.ENCODING_AAC_LC -> "AAC_LC"

AudioFormat.ENCODING_AAC_HE_V1 -> "AAC_HE_V1"

AudioFormat.ENCODING_AAC_HE_V2 -> "AAC_HE_V2"

AudioFormat.ENCODING_IEC61937 -> "IEC61937"

AudioFormat.ENCODING_DOLBY_TRUEHD -> "DOLBY_TRUEHD"

AudioFormat.ENCODING_AAC_ELD -> "AAC_ELD"

AudioFormat.ENCODING_AAC_XHE -> "AAC_XHE"

AudioFormat.ENCODING_AC4 -> "AC4"

// AudioFormat.ENCODING_AC4_L4->"AC4_L4"

AudioFormat.ENCODING_E_AC3_JOC -> "E_AC3_JOC"

AudioFormat.ENCODING_DOLBY_MAT -> "DOLBY_MAT"

AudioFormat.ENCODING_OPUS -> "OPUS"

AudioFormat.ENCODING_PCM_24BIT_PACKED -> "PCM_24BIT_PACKED"

AudioFormat.ENCODING_PCM_32BIT -> "PCM_32BIT"

AudioFormat.ENCODING_MPEGH_BL_L3 -> "MPEGH_BL_L3"

AudioFormat.ENCODING_MPEGH_BL_L4 -> "MPEGH_BL_L4"

AudioFormat.ENCODING_MPEGH_LC_L3 -> "MPEGH_LC_L3"

AudioFormat.ENCODING_MPEGH_LC_L4 -> "MPEGH_LC_L4"

AudioFormat.ENCODING_DTS_UHD_P1 -> "DTS_UHD_P1"

AudioFormat.ENCODING_DRA -> "DRA"

AudioFormat.ENCODING_DTS_HD_MA -> "DTS_HD_MA"

AudioFormat.ENCODING_DTS_UHD_P2 -> "DTS_UHD_P2"

AudioFormat.ENCODING_DSD -> "DSD"

AudioFormat.ENCODING_IAMF_BASE_ENHANCED_PROFILE_AAC -> "IAMF_BASE_ENHANCED_PROFILE_AAC"

AudioFormat.ENCODING_IAMF_BASE_ENHANCED_PROFILE_FLAC -> "IAMF_BASE_ENHANCED_PROFILE_FLAC"

AudioFormat.ENCODING_IAMF_BASE_ENHANCED_PROFILE_OPUS -> "IAMF_BASE_ENHANCED_PROFILE_OPUS"

AudioFormat.ENCODING_IAMF_BASE_ENHANCED_PROFILE_PCM -> "IAMF_BASE_ENHANCED_PROFILE_PCM"

AudioFormat.ENCODING_IAMF_BASE_PROFILE_AAC -> "IAMF_BASE_PROFILE_AAC"

AudioFormat.ENCODING_IAMF_BASE_PROFILE_FLAC -> "IAMF_BASE_PROFILE_FLAC"

AudioFormat.ENCODING_IAMF_BASE_PROFILE_OPUS -> "IAMF_BASE_PROFILE_OPUS"

AudioFormat.ENCODING_IAMF_BASE_PROFILE_PCM -> "IAMF_BASE_PROFILE_PCM"

AudioFormat.ENCODING_IAMF_SIMPLE_PROFILE_AAC -> "IAMF_SIMPLE_PROFILE_AAC"

AudioFormat.ENCODING_IAMF_SIMPLE_PROFILE_FLAC -> "IAMF_SIMPLE_PROFILE_FLAC"

AudioFormat.ENCODING_IAMF_SIMPLE_PROFILE_OPUS -> "IAMF_SIMPLE_PROFILE_OPUS"

AudioFormat.ENCODING_IAMF_SIMPLE_PROFILE_PCM -> "IAMF_SIMPLE_PROFILE_PCM"

else -> "invalid encoding $enc"
}
}
return "AudioDeviceInfo(id=$id, type=$type ($typeName), " +
"channelCounts=${channelCounts.contentToString()}, " +
"encodings=$encodings, " +
"productName=$productName, address=$addressStr)"
}

@Composable
fun DebugPage(
preferences: UserPreferences,
Expand Down Expand Up @@ -296,15 +458,21 @@ fun DebugPage(
style = MaterialTheme.typography.displaySmall,
color = MaterialTheme.colorScheme.onSurface,
)

listOf(
"DeviceInfo: ${viewModel.deviceInfo}",
"Manufacturer: ${Build.MANUFACTURER}",
"Model: ${Build.MODEL}",
"API Level: ${Build.VERSION.SDK_INT}",
"Display Modes:",
*viewModel.supportedModes,
).forEach {
val details =
listOf(
"DeviceInfo: ${viewModel.deviceInfo}",
"Manufacturer: ${Build.MANUFACTURER}",
"Model: ${Build.MODEL}",
"API Level: ${Build.VERSION.SDK_INT}",
"",
"Display Modes:",
*viewModel.supportedModes,
"",
"Audio Devices:",
)
val audioInfo by viewModel.audioInfo.collectAsState()
val audio = remember(audioInfo) { audioInfo.devices.map { it.details } }
(details + audio).forEach {
Text(
text = it.toString(),
style = MaterialTheme.typography.bodySmall,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ import com.github.damontecres.wholphin.services.ScreensaverService
import com.github.damontecres.wholphin.services.StreamChoiceService
import com.github.damontecres.wholphin.services.UserPreferencesService
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.launchDefault
import com.github.damontecres.wholphin.ui.launchIO
Expand Down Expand Up @@ -844,12 +845,26 @@ class PlaybackViewModel
userInitiated: Boolean,
): Boolean =
withContext(Dispatchers.IO) {
// TODO there's probably no reason why we can't add external subtitles?
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.playbackPreferences.overrides.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 externalSubtitle = source.findExternalSubtitle(subtitleIndex)

// TODO there's probably no reason why we can't add external subtitles?
if (externalSubtitle == null) {
val result =
withContext(Dispatchers.Main) {
Expand Down
Loading
Loading