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?,
positionMs: Long = C.TIME_UNSET,
userInitiated: Boolean,
enableDirectPlay: Boolean = true,
enableDirectStream: Boolean = true,
) = withContext(Dispatchers.IO) {
val itemId = item.id
@ -380,7 +382,10 @@ class PlaybackViewModel
// Timber.i("No change in playback for changeStreams")
// 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
val maxBitrate =
@ -393,17 +398,18 @@ class PlaybackViewModel
PlaybackInfoDto(
startTimeTicks = null,
deviceProfile = deviceProfile,
enableDirectPlay = true,
enableDirectStream = true,
maxAudioChannels = null,
audioStreamIndex = audioIndex,
subtitleStreamIndex = subtitleIndex,
allowVideoStreamCopy = true,
allowAudioStreamCopy = true,
autoOpenLiveStream = true,
mediaSourceId = currentItemPlayback.sourceId?.toServerString(),
alwaysBurnInSubtitleWhenTranscoding = null,
maxStreamingBitrate = maxBitrate.toInt(),
enableDirectPlay = enableDirectPlay,
enableDirectStream = enableDirectStream,
allowVideoStreamCopy = enableDirectStream,
allowAudioStreamCopy = enableDirectStream,
enableTranscoding = true,
autoOpenLiveStream = true,
),
)
if (response.errorCode != null) {
@ -422,9 +428,15 @@ class PlaybackViewModel
playSessionId = response.playSessionId,
)
} else if (source.supportsDirectStream) {
api.createUrl(source.transcodingUrl!!)
source.transcodingUrl?.let(api::createUrl)
} 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 =
when {
@ -745,7 +757,35 @@ class PlaybackViewModel
override fun onPlayerError(error: PlaybackException) {
Timber.e(error, "Playback error")
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()
}
}
}
}
}
}