Fallback to transcoding if direct play fails (#79)

If direct playing (or direct streaming) fails on device, attempt to fall
back to transcoding.

Also display more error information if available
This commit is contained in:
damontecres 2025-10-27 16:11:30 -04:00 committed by GitHub
parent 07a0db8e98
commit c4df751918
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 62 additions and 9 deletions

View file

@ -42,6 +42,19 @@ fun ErrorMessage(
) )
} }
} }
var cause = exception?.cause
while (cause != null) {
cause.localizedMessage?.let {
item {
Text(
text = "Caused by: $it",
color = MaterialTheme.colorScheme.error,
style = MaterialTheme.typography.titleSmall,
)
}
}
cause = cause.cause
}
} }
} }

View file

@ -367,6 +367,8 @@ class PlaybackViewModel
subtitleIndex: Int?, subtitleIndex: Int?,
positionMs: Long = C.TIME_UNSET, positionMs: Long = C.TIME_UNSET,
userInitiated: Boolean, userInitiated: Boolean,
enableDirectPlay: Boolean = true,
enableDirectStream: Boolean = true,
) = withContext(Dispatchers.IO) { ) = withContext(Dispatchers.IO) {
val itemId = item.id val itemId = item.id
@ -380,7 +382,10 @@ class PlaybackViewModel
// Timber.i("No change in playback for changeStreams") // Timber.i("No change in playback for changeStreams")
// return@withContext // return@withContext
// } // }
Timber.d("changeStreams: userInitiated=$userInitiated, audioIndex=$audioIndex, subtitleIndex=$subtitleIndex") Timber.d(
"changeStreams: userInitiated=$userInitiated, audioIndex=$audioIndex, subtitleIndex=$subtitleIndex, " +
"enableDirectPlay=$enableDirectPlay, enableDirectStream=$enableDirectStream",
)
// TODO if the new audio or subtitle index is already in the streams (eg direct play), should toggle in the player instead // TODO if the new audio or subtitle index is already in the streams (eg direct play), should toggle in the player instead
val maxBitrate = val maxBitrate =
@ -393,17 +398,18 @@ class PlaybackViewModel
PlaybackInfoDto( PlaybackInfoDto(
startTimeTicks = null, startTimeTicks = null,
deviceProfile = deviceProfile, deviceProfile = deviceProfile,
enableDirectPlay = true,
enableDirectStream = true,
maxAudioChannels = null, maxAudioChannels = null,
audioStreamIndex = audioIndex, audioStreamIndex = audioIndex,
subtitleStreamIndex = subtitleIndex, subtitleStreamIndex = subtitleIndex,
allowVideoStreamCopy = true,
allowAudioStreamCopy = true,
autoOpenLiveStream = true,
mediaSourceId = currentItemPlayback.sourceId?.toServerString(), mediaSourceId = currentItemPlayback.sourceId?.toServerString(),
alwaysBurnInSubtitleWhenTranscoding = null, alwaysBurnInSubtitleWhenTranscoding = null,
maxStreamingBitrate = maxBitrate.toInt(), maxStreamingBitrate = maxBitrate.toInt(),
enableDirectPlay = enableDirectPlay,
enableDirectStream = enableDirectStream,
allowVideoStreamCopy = enableDirectStream,
allowAudioStreamCopy = enableDirectStream,
enableTranscoding = true,
autoOpenLiveStream = true,
), ),
) )
if (response.errorCode != null) { if (response.errorCode != null) {
@ -422,10 +428,16 @@ class PlaybackViewModel
playSessionId = response.playSessionId, playSessionId = response.playSessionId,
) )
} else if (source.supportsDirectStream) { } else if (source.supportsDirectStream) {
api.createUrl(source.transcodingUrl!!) source.transcodingUrl?.let(api::createUrl)
} else { } else {
api.createUrl(source.transcodingUrl!!) source.transcodingUrl?.let(api::createUrl)
} }
if (mediaUrl.isNullOrBlank()) {
loading.setValueOnMain(
LoadingState.Error("Unable to get media URL from the server. Do you have permission to view and/or transcode?"),
)
return@withContext
}
val transcodeType = val transcodeType =
when { when {
source.supportsDirectPlay -> PlayMethod.DIRECT_PLAY source.supportsDirectPlay -> PlayMethod.DIRECT_PLAY
@ -745,7 +757,35 @@ class PlaybackViewModel
override fun onPlayerError(error: PlaybackException) { override fun onPlayerError(error: PlaybackException) {
Timber.e(error, "Playback error") Timber.e(error, "Playback error")
viewModelScope.launch(Dispatchers.Main + ExceptionHandler()) { viewModelScope.launch(Dispatchers.Main + ExceptionHandler()) {
loading.value = LoadingState.Error("Error during playback", error) currentPlayback.value?.let {
when (it.playMethod) {
PlayMethod.TRANSCODE ->
loading.setValueOnMain(
LoadingState.Error(
"Error during playback",
error,
),
)
PlayMethod.DIRECT_STREAM, PlayMethod.DIRECT_PLAY -> {
Timber.w("Playback error during ${it.playMethod}, falling back to transcoding")
changeStreams(
dto,
currentItemPlayback.value!!,
currentItemPlayback.value?.audioIndex,
currentItemPlayback.value?.subtitleIndex,
player.currentPosition,
false,
enableDirectPlay = false,
enableDirectStream = false,
)
withContext(Dispatchers.Main) {
player.prepare()
player.play()
}
}
}
}
} }
} }