From c4df751918b0b8f6b4099f2dd02e124a30a94d4e Mon Sep 17 00:00:00 2001 From: damontecres <154766448+damontecres@users.noreply.github.com> Date: Mon, 27 Oct 2025 16:11:30 -0400 Subject: [PATCH] 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 --- .../wholphin/ui/components/ErrorMessage.kt | 13 +++++ .../wholphin/ui/playback/PlaybackViewModel.kt | 58 ++++++++++++++++--- 2 files changed, 62 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/components/ErrorMessage.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/components/ErrorMessage.kt index 05f55f83..83f00f9c 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/components/ErrorMessage.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/components/ErrorMessage.kt @@ -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 + } } } diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/playback/PlaybackViewModel.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/playback/PlaybackViewModel.kt index 4b03acfa..f915cf0c 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/playback/PlaybackViewModel.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/playback/PlaybackViewModel.kt @@ -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,10 +428,16 @@ 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 { source.supportsDirectPlay -> PlayMethod.DIRECT_PLAY @@ -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() + } + } + } + } } }