Support playing multiple file versions (#30)

Adds support for playing different versions of a media item.
Additionally, you can pick the audio or subtitle tracks for the chosen
version.

The file with the highest resolution is the default, but you can switch
versions or tracks in the "More" menu.

These choices are persisted across playbacks, just as in #26.
This commit is contained in:
damontecres 2025-10-18 14:11:06 -04:00 committed by GitHub
parent e2db5ab4f3
commit f57fd25f8e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 771 additions and 196 deletions

View file

@ -5,6 +5,10 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import com.github.damontecres.wholphin.R
import com.github.damontecres.wholphin.data.ChosenStreams
import com.github.damontecres.wholphin.data.model.ItemPlayback
import com.github.damontecres.wholphin.data.model.chooseSource
import com.github.damontecres.wholphin.data.model.chooseStream
import com.github.damontecres.wholphin.preferences.UserPreferences
import com.github.damontecres.wholphin.ui.isNotNullOrBlank
import org.jellyfin.sdk.model.api.BaseItemDto
import org.jellyfin.sdk.model.api.MediaStream
@ -65,16 +69,32 @@ fun formatSubtitleLang(mediaStreams: List<MediaStream>?): String? =
?.distinct()
?.joinToString(", ") { languageName(it) }
/**
* Gets the selected audio display title for the given item & chosen streams
*/
fun getAudioDisplay(
item: BaseItemDto,
chosenStreams: ChosenStreams?,
) = (
chosenStreams?.audioStream
?: item.mediaStreams?.firstOrNull { it.type == MediaStreamType.AUDIO }
)?.displayTitle
preferences: UserPreferences,
) = getAudioDisplay(item, chosenStreams?.itemPlayback, preferences)
/**
* Gets the selected audio display title for the given item & chosen streams
*/
fun getAudioDisplay(
item: BaseItemDto,
itemPlayback: ItemPlayback?,
preferences: UserPreferences,
) = chooseStream(item, itemPlayback, MediaStreamType.AUDIO, preferences)
?.displayTitle
?.replace(" - Default", "")
?.ifBlank { null }
/**
* Gets the selected subtitle language for the given item & chosen streams
*
* If none are chosen, returns a concatenated list of languages available
*/
@Composable
fun getSubtitleDisplay(
item: BaseItemDto,
@ -84,5 +104,7 @@ fun getSubtitleDisplay(
} else if (chosenStreams?.subtitleStream != null) {
languageName(chosenStreams.subtitleStream.language)
} else {
formatSubtitleLang(item.mediaStreams)
chooseSource(item, chosenStreams?.itemPlayback)?.let {
formatSubtitleLang(it.mediaStreams)
}
}

View file

@ -7,6 +7,7 @@ import androidx.media3.common.Format
import androidx.media3.common.MimeTypes
import androidx.media3.common.Tracks
import androidx.media3.common.util.UnstableApi
import com.github.damontecres.wholphin.ui.playback.idAsInt
import timber.log.Timber
import java.util.Locale
@ -118,11 +119,20 @@ fun checkForSupport(tracks: Tracks): List<TrackSupport> =
} else {
it.value
}
} +
if (type == TrackType.VIDEO) {
listOf("res=${format.width}x${format.height}")
} else if (type == TrackType.AUDIO) {
listOf("channels=${format.channelCount}", "lang=${format.language}")
} else if (type == TrackType.TEXT) {
listOf("lang=${format.language}")
} else {
listOf()
}
val reason = TrackSupportReason.fromInt(it.getTrackSupport(i))
add(
TrackSupport(
format.id,
format.id + " (${format.idAsInt})",
type,
reason,
it.isSelected,