diff --git a/pillarbox-demo-tv/src/main/java/ch/srgssr/pillarbox/demo/tv/ui/player/compose/settings/PlaybackSettingsDrawer.kt b/pillarbox-demo-tv/src/main/java/ch/srgssr/pillarbox/demo/tv/ui/player/compose/settings/PlaybackSettingsDrawer.kt index fb7298c72..5fe41e7eb 100644 --- a/pillarbox-demo-tv/src/main/java/ch/srgssr/pillarbox/demo/tv/ui/player/compose/settings/PlaybackSettingsDrawer.kt +++ b/pillarbox-demo-tv/src/main/java/ch/srgssr/pillarbox/demo/tv/ui/player/compose/settings/PlaybackSettingsDrawer.kt @@ -58,6 +58,7 @@ import ch.srgssr.pillarbox.demo.shared.ui.player.settings.TracksSettingItem import ch.srgssr.pillarbox.demo.tv.ui.theme.paddings import ch.srgssr.pillarbox.player.extension.displayName import ch.srgssr.pillarbox.player.extension.hasAccessibilityRoles +import ch.srgssr.pillarbox.player.extension.isForced /** * Drawer used to display a player's settings. @@ -318,8 +319,10 @@ private fun NavigationDrawerScope.TracksSetting( tracksSetting.tracks.forEach { group -> items(group.length) { trackIndex -> + val format = group.getTrackFormat(trackIndex) NavigationDrawerItem( selected = group.isTrackSelected(trackIndex), + enabled = group.isTrackSupported(trackIndex) && !format.isForced(), onClick = { onTrackClick(group, trackIndex) }, leadingContent = { AnimatedVisibility(visible = group.isTrackSelected(trackIndex)) { @@ -330,7 +333,6 @@ private fun NavigationDrawerScope.TracksSetting( } }, content = { - val format = group.getTrackFormat(trackIndex) val label = buildString { append(format.displayName) diff --git a/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/player/settings/TrackSelectionSettings.kt b/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/player/settings/TrackSelectionSettings.kt index eaabc7414..8a4166bc5 100644 --- a/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/player/settings/TrackSelectionSettings.kt +++ b/pillarbox-demo/src/main/java/ch/srgssr/pillarbox/demo/ui/player/settings/TrackSelectionSettings.kt @@ -32,6 +32,7 @@ import ch.srgssr.pillarbox.demo.shared.ui.player.settings.TracksSettingItem import ch.srgssr.pillarbox.demo.ui.theme.PillarboxTheme import ch.srgssr.pillarbox.player.extension.displayName import ch.srgssr.pillarbox.player.extension.hasAccessibilityRoles +import ch.srgssr.pillarbox.player.extension.isForced /** * Track selection settings @@ -78,14 +79,15 @@ fun TrackSelectionSettings( } tracksSetting.tracks.forEach { group -> items(group.length) { trackIndex -> + val format = group.getTrackFormat(trackIndex) SettingsOption( modifier = itemModifier, selected = group.isTrackSelected(trackIndex), + enabled = group.isTrackSupported(trackIndex) && !format.isForced(), onClick = { onTrackClick(group, trackIndex) }, content = { - val format = group.getTrackFormat(trackIndex) when (group.type) { C.TRACK_TYPE_AUDIO -> { val str = StringBuilder() diff --git a/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/extension/Tracks.kt b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/extension/Tracks.kt index 5846eec55..cd265d5cb 100644 --- a/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/extension/Tracks.kt +++ b/pillarbox-player/src/main/java/ch/srgssr/pillarbox/player/extension/Tracks.kt @@ -4,72 +4,28 @@ */ package ch.srgssr.pillarbox.player.extension -import android.annotation.SuppressLint import androidx.media3.common.C import androidx.media3.common.C.TrackType -import androidx.media3.common.Format -import androidx.media3.common.TrackGroup import androidx.media3.common.Tracks /** - * Text tracks + * Text tracks. */ val Tracks.text: List - get() = filterByTrackType(C.TRACK_TYPE_TEXT).mapNotNull { it.filterForcedAndUnsupported() } + get() = filterByTrackType(C.TRACK_TYPE_TEXT) /** * Audio tracks. */ val Tracks.audio: List - get() = filterByTrackType(C.TRACK_TYPE_AUDIO).mapNotNull { it.filterUnsupported() } + get() = filterByTrackType(C.TRACK_TYPE_AUDIO) /** * Video tracks. */ val Tracks.video: List - get() = filterByTrackType(C.TRACK_TYPE_VIDEO).mapNotNull { it.filterUnsupported() } + get() = filterByTrackType(C.TRACK_TYPE_VIDEO) private fun Tracks.filterByTrackType(trackType: @TrackType Int): List { return groups.filter { it.type == trackType } } - -private fun Tracks.Group.filterForcedAndUnsupported(): Tracks.Group? { - return filterBy { group, i -> group.isTrackSupported(i) && !group.getTrackFormat(i).isForced() } -} - -internal fun Tracks.Group.filterUnsupported(): Tracks.Group? { - return filterBy { group, i -> group.isTrackSupported(i) } -} - -/** - * Filter [Format] that matching [predicate]. - * - * @param predicate function that takes the index of an element and the element itself and returns the result of predicate evaluation on the element. - * @receiver - * @return element matching [predicate] or null if filtered items is empty because [TrackGroup] can not be empty. - */ -@SuppressLint("WrongConstant") -@Suppress("SpreadOperator", "ReturnCount") -internal fun Tracks.Group.filterBy(predicate: (Tracks.Group, Int) -> Boolean): Tracks.Group? { - val listIndexMatchingPredicate = ArrayList(length) - for (i in 0 until length) { - if (predicate(this, i)) { - listIndexMatchingPredicate.add(i) - } - } - // All format doesn't match predicate. - if (listIndexMatchingPredicate.isEmpty()) return null - // All format matching the predicate, nothing to change. - if (listIndexMatchingPredicate.size == length) return this - val count = listIndexMatchingPredicate.size - val formats = ArrayList(count) - val trackSupport = IntArray(count) - val trackSelect = BooleanArray(count) - for (i in 0 until count) { - val trackIndex = listIndexMatchingPredicate[i] - formats.add(getTrackFormat(trackIndex)) - trackSupport[i] = getTrackSupport(trackIndex) - trackSelect[i] = isTrackSelected(trackIndex) - } - return Tracks.Group(TrackGroup(mediaTrackGroup.id, *formats.toTypedArray()), isAdaptiveSupported, trackSupport, trackSelect) -} diff --git a/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/extension/TracksTest.kt b/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/extension/TracksTest.kt index ca85dff8a..19fd1792f 100644 --- a/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/extension/TracksTest.kt +++ b/pillarbox-player/src/test/java/ch/srgssr/pillarbox/player/extension/TracksTest.kt @@ -15,7 +15,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import org.junit.runner.RunWith import kotlin.test.Test import kotlin.test.assertEquals -import kotlin.test.assertTrue @RunWith(AndroidJUnit4::class) class TracksTest { @@ -25,7 +24,13 @@ class TracksTest { createFormatTrackFormat("t2", mimeType = TEXT_MIME_TYPE, selectionFlags = C.SELECTION_FLAG_AUTOSELECT or C.SELECTION_FLAG_DEFAULT), ), createTrackGroup( - createFormatTrackFormat("t1-sdh", mimeType = TEXT_MIME_TYPE, roleFlags = C.ROLE_FLAG_DESCRIBES_MUSIC_AND_SOUND), + formats = listOf( + createFormatTrackFormat("t1-sdh", mimeType = TEXT_MIME_TYPE, roleFlags = C.ROLE_FLAG_DESCRIBES_MUSIC_AND_SOUND), + createFormatTrackFormat("t3", mimeType = TEXT_MIME_TYPE), + ), + trackSupport = intArrayOf( + C.FORMAT_HANDLED, C.FORMAT_EXCEEDS_CAPABILITIES, + ), ), ) private val forcedSubtitleTracks = listOf( @@ -38,14 +43,26 @@ class TracksTest { createFormatTrackFormat("a1", mimeType = AUDIO_MIME_TYPE), ), createTrackGroup( - createFormatTrackFormat("a1-ad", mimeType = AUDIO_MIME_TYPE, roleFlags = C.ROLE_FLAG_DESCRIBES_VIDEO), + formats = listOf( + createFormatTrackFormat("a1-ad", mimeType = AUDIO_MIME_TYPE, roleFlags = C.ROLE_FLAG_DESCRIBES_VIDEO), + createFormatTrackFormat("a2", mimeType = AUDIO_MIME_TYPE), + ), + trackSupport = intArrayOf( + C.FORMAT_HANDLED, C.FORMAT_EXCEEDS_CAPABILITIES + ), ), ) private val videoTracks = listOf( createTrackGroup( - createFormatTrackFormat("v1", mimeType = VIDEO_MIME_TYPE), - createFormatTrackFormat("v2", mimeType = VIDEO_MIME_TYPE), - createFormatTrackFormat("v3", mimeType = VIDEO_MIME_TYPE), + formats = listOf( + createFormatTrackFormat("v1", mimeType = VIDEO_MIME_TYPE), + createFormatTrackFormat("v2", mimeType = VIDEO_MIME_TYPE), + createFormatTrackFormat("v3", mimeType = VIDEO_MIME_TYPE), + createFormatTrackFormat("v4", mimeType = VIDEO_MIME_TYPE), + ), + trackSupport = intArrayOf( + C.FORMAT_HANDLED, C.FORMAT_HANDLED, C.FORMAT_EXCEEDS_CAPABILITIES, C.FORMAT_HANDLED, + ), ), ) private val tracks = Tracks(audioTracks + textTracks + videoTracks + forcedSubtitleTracks) @@ -53,7 +70,7 @@ class TracksTest { @Test fun `text with text tracks`() { val textTracks = tracks.text - assertEquals(this.textTracks, textTracks) + assertEquals(this.textTracks + forcedSubtitleTracks, textTracks) } @Test @@ -86,85 +103,6 @@ class TracksTest { assertEquals(emptyList(), videoTracks) } - @Test - fun `filterUnsupported one supported track`() { - val format = createFormatTrackFormat("Unsupported", mimeType = AUDIO_MIME_TYPE) - val trackGroup = TrackGroup(format) - val selected = booleanArrayOf(false) - val trackSupport = intArrayOf(C.FORMAT_HANDLED) - val tracks = Tracks(listOf(Tracks.Group(trackGroup, false, trackSupport, selected))) - val expectedTracksGroups = listOf( - Tracks.Group( - trackGroup, - false, - trackSupport, - selected, - ) - ) - assertEquals(expectedTracksGroups, tracks.groups.mapNotNull { it.filterUnsupported() }) - } - - @Test - fun `filterUnsupported only supported track`() { - val format1 = createFormatTrackFormat("F1", mimeType = AUDIO_MIME_TYPE) - val format2 = createFormatTrackFormat("F2", mimeType = AUDIO_MIME_TYPE) - val trackGroup = TrackGroup(format1, format2) - val selected = booleanArrayOf(false, false) - val trackSupport = intArrayOf(C.FORMAT_HANDLED, C.FORMAT_HANDLED) - val tracks = Tracks(listOf(Tracks.Group(trackGroup, false, trackSupport, selected))) - val expectedTracksGroups = listOf( - Tracks.Group( - trackGroup, - false, - trackSupport, - selected, - ) - ) - assertEquals(expectedTracksGroups, tracks.groups.mapNotNull { it.filterUnsupported() }) - } - - @Test - fun `filterUnsupported multiple tracks, one unsupported`() { - val format1 = createFormatTrackFormat("F1", mimeType = AUDIO_MIME_TYPE) - val format2 = createFormatTrackFormat("F2", mimeType = AUDIO_MIME_TYPE) - val formatUnsupportedTrack = createFormatTrackFormat("Unsupported", mimeType = AUDIO_MIME_TYPE) - val trackGroup = TrackGroup(format1, format2, formatUnsupportedTrack) - val selected = booleanArrayOf(false, false, false) - val trackSupport = intArrayOf(C.FORMAT_HANDLED, C.FORMAT_HANDLED, C.FORMAT_UNSUPPORTED_SUBTYPE) - val tracks = Tracks(listOf(Tracks.Group(trackGroup, false, trackSupport, selected))) - val expectedTracksGroups = listOf( - Tracks.Group( - TrackGroup(format1, format2), - false, - intArrayOf(C.FORMAT_HANDLED, C.FORMAT_HANDLED), - booleanArrayOf(false, false), - ) - ) - assertEquals(expectedTracksGroups, tracks.groups.mapNotNull { it.filterUnsupported() }) - } - - @Test - fun `filterUnsupported multiple unsupported tracks`() { - val format1 = createFormatTrackFormat("F1", mimeType = AUDIO_MIME_TYPE) - val format2 = createFormatTrackFormat("F2", mimeType = AUDIO_MIME_TYPE) - val formatUnsupportedTrack = createFormatTrackFormat("Unsupported", mimeType = AUDIO_MIME_TYPE) - val trackGroup = TrackGroup(format1, format2, formatUnsupportedTrack) - val selected = booleanArrayOf(false, false, false) - val trackSupport = intArrayOf(C.FORMAT_UNSUPPORTED_SUBTYPE, C.FORMAT_UNSUPPORTED_SUBTYPE, C.FORMAT_UNSUPPORTED_SUBTYPE) - val tracks = Tracks(listOf(Tracks.Group(trackGroup, false, trackSupport, selected))) - assertTrue(tracks.groups.mapNotNull { it.filterUnsupported() }.isEmpty()) - } - - @Test - fun `filterUnsupported one unsupported track`() { - val formatUnsupportedTrack = createFormatTrackFormat("Unsupported", mimeType = AUDIO_MIME_TYPE) - val trackGroup = TrackGroup(formatUnsupportedTrack) - val selected = booleanArrayOf(false) - val trackSupport = intArrayOf(C.FORMAT_UNSUPPORTED_SUBTYPE) - val tracks = Tracks(listOf(Tracks.Group(trackGroup, false, trackSupport, selected))) - assertTrue(tracks.groups.mapNotNull { it.filterUnsupported() }.isEmpty()) - } - private companion object { private const val TEXT_MIME_TYPE = MimeTypes.APPLICATION_TTML private const val VIDEO_MIME_TYPE = MimeTypes.VIDEO_H265 @@ -187,10 +125,17 @@ class TracksTest { } private fun createTrackGroup(vararg formats: Format): Tracks.Group { - val trackGroup = TrackGroup(*formats) val trackSupport = IntArray(formats.size) { C.FORMAT_HANDLED } + return createTrackGroup(formats.toList(), trackSupport) + } + + private fun createTrackGroup( + formats: List, + trackSupport: IntArray, + ): Tracks.Group { + val trackGroup = TrackGroup(*formats.toTypedArray()) val selected = BooleanArray(formats.size) return Tracks.Group(trackGroup, false, trackSupport, selected) }