Add option to play item forcing transcoding (#153)

Closes #97 

Adds an option to force an item to play by transcoding. This can be
useful in the few cases where the device reports that it supports direct
play for a file, but it really doesn't.

Also rearranges the "More" dialog list to move picking audio & subtitle
track near the top.
This commit is contained in:
damontecres 2025-11-03 20:34:46 -05:00 committed by GitHub
parent 2840fa3f2a
commit a0b9ac7673
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 85 additions and 47 deletions

View file

@ -65,53 +65,7 @@ fun buildMoreDialogItems(
) )
}, },
) )
add(
DialogItem(
text = if (watched) R.string.mark_unwatched else R.string.mark_watched,
iconStringRes = if (watched) R.string.fa_eye else R.string.fa_eye_slash,
) {
onClickWatch.invoke(!watched)
},
)
add(
DialogItem(
text = if (favorite) R.string.remove_favorite else R.string.add_favorite,
iconStringRes = R.string.fa_heart,
iconColor = if (favorite) Color.Red else Color.Unspecified,
) {
onClickFavorite.invoke(!favorite)
},
)
series?.let {
add(
DialogItem(
context.getString(R.string.go_to_series),
Icons.AutoMirrored.Filled.ArrowForward,
) {
navigateTo(
Destination.MediaItem(
series.id,
BaseItemKind.SERIES,
series,
),
)
},
)
}
item.data.mediaSources?.letNotEmpty { sources -> item.data.mediaSources?.letNotEmpty { sources ->
if (sources.size > 1) {
add(
DialogItem(
context.getString(
R.string.choose_stream,
context.getString(R.string.version),
),
R.string.fa_file_video,
) {
onChooseVersion.invoke()
},
)
}
val source = val source =
sourceId?.let { sources.firstOrNull { it.id?.toUUIDOrNull() == sourceId } } sourceId?.let { sources.firstOrNull { it.id?.toUUIDOrNull() == sourceId } }
?: sources.firstOrNull() ?: sources.firstOrNull()
@ -145,8 +99,69 @@ fun buildMoreDialogItems(
) )
} }
} }
if (sources.size > 1) {
add(
DialogItem(
context.getString(
R.string.choose_stream,
context.getString(R.string.version),
),
R.string.fa_file_video,
) {
onChooseVersion.invoke()
},
)
} }
} }
add(
DialogItem(
text = if (watched) R.string.mark_unwatched else R.string.mark_watched,
iconStringRes = if (watched) R.string.fa_eye else R.string.fa_eye_slash,
) {
onClickWatch.invoke(!watched)
},
)
add(
DialogItem(
text = if (favorite) R.string.remove_favorite else R.string.add_favorite,
iconStringRes = R.string.fa_heart,
iconColor = if (favorite) Color.Red else Color.Unspecified,
) {
onClickFavorite.invoke(!favorite)
},
)
series?.let {
add(
DialogItem(
context.getString(R.string.go_to_series),
Icons.AutoMirrored.Filled.ArrowForward,
) {
navigateTo(
Destination.MediaItem(
series.id,
BaseItemKind.SERIES,
series,
),
)
},
)
}
add(
DialogItem(
context.getString(R.string.play_with_transcoding),
Icons.Default.PlayArrow,
) {
navigateTo(
Destination.Playback(
item.id,
item.resumeMs ?: 0L,
item,
forceTranscoding = true,
),
)
},
)
}
fun buildMoreDialogItemsForHome( fun buildMoreDialogItemsForHome(
context: Context, context: Context,

View file

@ -73,6 +73,7 @@ sealed class Destination(
val startIndex: Int? = null, val startIndex: Int? = null,
val shuffle: Boolean = false, val shuffle: Boolean = false,
val itemPlayback: ItemPlayback? = null, val itemPlayback: ItemPlayback? = null,
val forceTranscoding: Boolean = false,
) : Destination(true) { ) : Destination(true) {
override fun toString(): String = "Playback(itemId=$itemId, positionMs=$positionMs)" override fun toString(): String = "Playback(itemId=$itemId, positionMs=$positionMs)"

View file

@ -209,6 +209,11 @@ class PlaybackViewModel
destination.startIndex, destination.startIndex,
destination.shuffle, destination.shuffle,
) )
if (playlist.items.isEmpty()) {
showToast(context, "Playlist is empty", Toast.LENGTH_SHORT)
navigationManager.goBack()
return@launch
}
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
this@PlaybackViewModel.playlist.value = playlist this@PlaybackViewModel.playlist.value = playlist
} }
@ -218,7 +223,13 @@ class PlaybackViewModel
queriedItem queriedItem
} }
val played = play(base, destination.positionMs, destination.itemPlayback) val played =
play(
base,
destination.positionMs,
destination.itemPlayback,
destination.forceTranscoding,
)
if (!played) { if (!played) {
playUpNextUp() playUpNextUp()
} }
@ -238,6 +249,7 @@ class PlaybackViewModel
base: BaseItemDto, base: BaseItemDto,
positionMs: Long, positionMs: Long,
itemPlayback: ItemPlayback? = null, itemPlayback: ItemPlayback? = null,
forceTranscoding: Boolean = false,
): Boolean = ): Boolean =
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
Timber.i("Playing ${base.id}") Timber.i("Playing ${base.id}")
@ -369,6 +381,8 @@ class PlaybackViewModel
subtitleIndex, subtitleIndex,
if (positionMs > 0) positionMs else C.TIME_UNSET, if (positionMs > 0) positionMs else C.TIME_UNSET,
itemPlayback != null, // If it was passed in, then it was not queried from the database itemPlayback != null, // If it was passed in, then it was not queried from the database
enableDirectPlay = !forceTranscoding,
enableDirectStream = !forceTranscoding,
) )
player.prepare() player.prepare()

View file

@ -75,6 +75,13 @@ class SwitchUserViewModel
fun init() { fun init() {
viewModelScope.launchIO { viewModelScope.launchIO {
quickConnectJob?.cancel()
withContext(Dispatchers.Main) {
users.value = listOf()
serverStatus.value = mapOf()
serverQuickConnect.value = mapOf()
}
val allServers = val allServers =
serverDao serverDao
.getServers() .getServers()

View file

@ -137,6 +137,7 @@
<string name="video">Video</string> <string name="video">Video</string>
<string name="watch_live">Watch live</string> <string name="watch_live">Watch live</string>
<string name="years_old">%1$d years old</string> <string name="years_old">%1$d years old</string>
<string name="play_with_transcoding">Play with transcoding</string>
<plurals name="downloads"> <plurals name="downloads">
<item quantity="zero">%d downloads</item> <item quantity="zero">%d downloads</item>