Merge pull request #2461 from davotoula/claude/hls-resolution-selection-jzBTG
HLS video: Lowest resolution in feed/PiP, auto resolution in full screen
This commit is contained in:
+7
@@ -26,6 +26,8 @@ import androidx.compose.runtime.mutableStateOf
|
|||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
|
import com.vitorpamplona.amethyst.service.playback.composable.controls.ApplyInitialVideoQuality
|
||||||
|
import com.vitorpamplona.amethyst.service.playback.composable.controls.VideoQualityPolicy
|
||||||
import com.vitorpamplona.amethyst.service.playback.composable.mainVideo.VideoPlayerActiveMutex
|
import com.vitorpamplona.amethyst.service.playback.composable.mainVideo.VideoPlayerActiveMutex
|
||||||
import com.vitorpamplona.amethyst.service.playback.composable.mediaitem.GetMediaItem
|
import com.vitorpamplona.amethyst.service.playback.composable.mediaitem.GetMediaItem
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
@@ -50,6 +52,7 @@ fun VideoViewInner(
|
|||||||
controllerVisible: MutableState<Boolean> = mutableStateOf(false),
|
controllerVisible: MutableState<Boolean> = mutableStateOf(false),
|
||||||
onZoom: (() -> Unit)? = null,
|
onZoom: (() -> Unit)? = null,
|
||||||
hasBlurhash: Boolean = false,
|
hasBlurhash: Boolean = false,
|
||||||
|
isFullscreen: Boolean = false,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
) {
|
) {
|
||||||
// keeps a copy of the value to avoid recompositions here when the DEFAULT value changes
|
// keeps a copy of the value to avoid recompositions here when the DEFAULT value changes
|
||||||
@@ -71,6 +74,10 @@ fun VideoViewInner(
|
|||||||
mediaItem = mediaItem,
|
mediaItem = mediaItem,
|
||||||
muted = muted,
|
muted = muted,
|
||||||
) { controller ->
|
) { controller ->
|
||||||
|
ApplyInitialVideoQuality(
|
||||||
|
player = controller.controller,
|
||||||
|
policy = if (isFullscreen) VideoQualityPolicy.AUTO else VideoQualityPolicy.LOWEST,
|
||||||
|
)
|
||||||
VideoPlayerActiveMutex(controller) { videoModifier, isClosestToTheCenterOfTheScreen ->
|
VideoPlayerActiveMutex(controller) { videoModifier, isClosestToTheCenterOfTheScreen ->
|
||||||
ControlWhenPlayerIsActive(controller, automaticallyStartPlayback, isClosestToTheCenterOfTheScreen)
|
ControlWhenPlayerIsActive(controller, automaticallyStartPlayback, isClosestToTheCenterOfTheScreen)
|
||||||
RenderVideoPlayer(
|
RenderVideoPlayer(
|
||||||
|
|||||||
+117
@@ -0,0 +1,117 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||||
|
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.vitorpamplona.amethyst.service.playback.composable.controls
|
||||||
|
|
||||||
|
import androidx.annotation.MainThread
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.DisposableEffect
|
||||||
|
import androidx.compose.runtime.MutableState
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.media3.common.Player
|
||||||
|
import androidx.media3.common.Tracks
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Which HLS rendition to pick automatically the first time tracks become available for a media
|
||||||
|
* item. Expressed as an explicit policy instead of a `Boolean` so call sites must commit to one
|
||||||
|
* value at construction and any future dynamic-state caller is forced to key the effect on it.
|
||||||
|
*/
|
||||||
|
enum class VideoQualityPolicy {
|
||||||
|
/** Lock to the lowest-resolution rendition (feed and PiP: save bandwidth). */
|
||||||
|
LOWEST,
|
||||||
|
|
||||||
|
/** Clear any video override so the player uses adaptive bitrate (fullscreen). */
|
||||||
|
AUTO,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies a default video quality when tracks become available on the given player.
|
||||||
|
*
|
||||||
|
* The initial selection is applied once per media item. If the user later changes the quality
|
||||||
|
* manually, or swaps to a different media item, the new choice wins — we don't reapply for the
|
||||||
|
* same media id. Selections intentionally don't persist across composable lifecycles, so opening
|
||||||
|
* a feed video in fullscreen starts with [VideoQualityPolicy.AUTO] and returning to the feed
|
||||||
|
* starts with [VideoQualityPolicy.LOWEST] again.
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun ApplyInitialVideoQuality(
|
||||||
|
player: Player,
|
||||||
|
policy: VideoQualityPolicy,
|
||||||
|
) {
|
||||||
|
// Tracks the media id we've already initialized so we don't fight user overrides after the
|
||||||
|
// first application.
|
||||||
|
val appliedForMediaId = remember(player) { mutableStateOf<String?>(null) }
|
||||||
|
|
||||||
|
DisposableEffect(player, policy) {
|
||||||
|
// Re-arm the guard whenever the player or the policy changes so a new policy gets a
|
||||||
|
// chance to apply even if the same media id has already been handled under the old one.
|
||||||
|
appliedForMediaId.value = null
|
||||||
|
|
||||||
|
val listener =
|
||||||
|
object : Player.Listener {
|
||||||
|
override fun onTracksChanged(tracks: Tracks) {
|
||||||
|
applyInitialQuality(player, tracks, policy, appliedForMediaId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tracks might already be available by the time we attach the listener.
|
||||||
|
applyInitialQuality(player, player.currentTracks, policy, appliedForMediaId)
|
||||||
|
player.addListener(listener)
|
||||||
|
onDispose { player.removeListener(listener) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invoked from Player.Listener callbacks and DisposableEffect bodies, both of which run on
|
||||||
|
// the player's application looper (main thread for ExoPlayer). The body mutates Compose state
|
||||||
|
// and trackSelectionParameters; both are main-thread-only.
|
||||||
|
@MainThread
|
||||||
|
private fun applyInitialQuality(
|
||||||
|
player: Player,
|
||||||
|
tracks: Tracks,
|
||||||
|
policy: VideoQualityPolicy,
|
||||||
|
appliedForMediaId: MutableState<String?>,
|
||||||
|
) {
|
||||||
|
val mediaId = player.currentMediaItem?.mediaId ?: return
|
||||||
|
if (appliedForMediaId.value == mediaId) return
|
||||||
|
|
||||||
|
val videoGroup = getVideoTrackGroup(tracks) ?: return
|
||||||
|
// No point forcing a choice when there's only one rendition, and no future update will
|
||||||
|
// change that for this media id, so mark it as settled.
|
||||||
|
if (videoGroup.length <= 1) {
|
||||||
|
appliedForMediaId.value = mediaId
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
when (policy) {
|
||||||
|
VideoQualityPolicy.AUTO -> {
|
||||||
|
if (hasVideoOverride(player)) clearVideoOverride(player)
|
||||||
|
appliedForMediaId.value = mediaId
|
||||||
|
}
|
||||||
|
|
||||||
|
VideoQualityPolicy.LOWEST -> {
|
||||||
|
// If no supported track has a positive short side yet, leave the guard unset so we
|
||||||
|
// retry on the next onTracksChanged when real video dimensions arrive.
|
||||||
|
val lowestIndex = findLowestResolutionTrackIndex(videoGroup) ?: return
|
||||||
|
selectVideoTrack(player, videoGroup, lowestIndex)
|
||||||
|
appliedForMediaId.value = mediaId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
-41
@@ -1,41 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2025 Vitor Pamplona
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
||||||
* this software and associated documentation files (the "Software"), to deal in
|
|
||||||
* the Software without restriction, including without limitation the rights to use,
|
|
||||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
|
||||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
|
||||||
* subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in all
|
|
||||||
* copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
|
||||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
|
||||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
|
||||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
||||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
*/
|
|
||||||
package com.vitorpamplona.amethyst.service.playback.composable.controls
|
|
||||||
|
|
||||||
import androidx.media3.common.C
|
|
||||||
import androidx.media3.common.Tracks
|
|
||||||
|
|
||||||
fun getVideoTrackGroup(tracks: Tracks): Tracks.Group? = tracks.groups.firstOrNull { it.type == C.TRACK_TYPE_VIDEO && it.length > 0 }
|
|
||||||
|
|
||||||
// Returns the "Xp" value for the currently selected video track. Uses min(width, height) so
|
|
||||||
// that a portrait video's renditions get the same "360p / 540p / 720p" labels as a landscape
|
|
||||||
// source — the streaming convention is to label by the short side, not format.height which is
|
|
||||||
// the long side for portrait content.
|
|
||||||
fun getCurrentPlayingShortSide(tracks: Tracks): Int? {
|
|
||||||
val group = getVideoTrackGroup(tracks) ?: return null
|
|
||||||
for (i in 0 until group.length) {
|
|
||||||
if (group.isTrackSelected(i)) {
|
|
||||||
val format = group.getTrackFormat(i)
|
|
||||||
return minOf(format.width, format.height).takeIf { it > 0 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
+19
-27
@@ -51,10 +51,9 @@ import androidx.compose.ui.draw.clip
|
|||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.window.Popup
|
import androidx.compose.ui.window.Popup
|
||||||
import androidx.compose.ui.window.PopupProperties
|
import androidx.compose.ui.window.PopupProperties
|
||||||
import androidx.media3.common.C
|
|
||||||
import androidx.media3.common.Player
|
import androidx.media3.common.Player
|
||||||
import androidx.media3.common.TrackSelectionOverride
|
|
||||||
import androidx.media3.common.Tracks
|
import androidx.media3.common.Tracks
|
||||||
|
import androidx.media3.common.VideoSize
|
||||||
import androidx.media3.common.util.UnstableApi
|
import androidx.media3.common.util.UnstableApi
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.ui.stringRes
|
import com.vitorpamplona.amethyst.ui.stringRes
|
||||||
@@ -119,6 +118,21 @@ fun VideoQualityButton(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (openDialog) {
|
if (openDialog) {
|
||||||
|
// Scope videoSize tracking to the open popup: HLS ABR fires onVideoSizeChanged on every
|
||||||
|
// rung switch, and we don't want to pay recomposition cost on cards whose menu isn't open.
|
||||||
|
var videoSize by remember(player) { mutableStateOf(player.videoSize) }
|
||||||
|
DisposableEffect(player) {
|
||||||
|
val listener =
|
||||||
|
object : Player.Listener {
|
||||||
|
override fun onVideoSizeChanged(newSize: VideoSize) {
|
||||||
|
videoSize = newSize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
player.addListener(listener)
|
||||||
|
onDispose { player.removeListener(listener) }
|
||||||
|
}
|
||||||
|
val currentShortSide = minOf(videoSize.width, videoSize.height).takeIf { it > 0 }
|
||||||
|
|
||||||
Popup(
|
Popup(
|
||||||
alignment = Alignment.BottomCenter,
|
alignment = Alignment.BottomCenter,
|
||||||
onDismissRequest = { openDialog = false },
|
onDismissRequest = { openDialog = false },
|
||||||
@@ -126,10 +140,10 @@ fun VideoQualityButton(
|
|||||||
) {
|
) {
|
||||||
VideoQualityChoices(
|
VideoQualityChoices(
|
||||||
videoGroup = videoGroup,
|
videoGroup = videoGroup,
|
||||||
currentShortSide = getCurrentPlayingShortSide(tracks),
|
currentShortSide = currentShortSide,
|
||||||
isAuto = !hasVideoOverride(player),
|
isAuto = !hasVideoOverride(player),
|
||||||
onSelectAuto = {
|
onSelectAuto = {
|
||||||
clearVideoOverride(player)
|
if (hasVideoOverride(player)) clearVideoOverride(player)
|
||||||
openDialog = false
|
openDialog = false
|
||||||
},
|
},
|
||||||
onSelectTrack = { trackIndex ->
|
onSelectTrack = { trackIndex ->
|
||||||
@@ -195,6 +209,7 @@ private data class QualityChoice(
|
|||||||
private fun buildQualityChoices(group: Tracks.Group): ImmutableList<QualityChoice> {
|
private fun buildQualityChoices(group: Tracks.Group): ImmutableList<QualityChoice> {
|
||||||
val choices = mutableListOf<QualityChoice>()
|
val choices = mutableListOf<QualityChoice>()
|
||||||
for (i in 0 until group.length) {
|
for (i in 0 until group.length) {
|
||||||
|
if (!group.isTrackSupported(i)) continue
|
||||||
val format = group.getTrackFormat(i)
|
val format = group.getTrackFormat(i)
|
||||||
val shortSide = minOf(format.width, format.height)
|
val shortSide = minOf(format.width, format.height)
|
||||||
if (shortSide > 0) {
|
if (shortSide > 0) {
|
||||||
@@ -210,26 +225,3 @@ private fun formatBitrate(bitrate: Int): String =
|
|||||||
bitrate >= 1_000_000 -> String.format(Locale.US, "%.1f Mbps", bitrate / 1_000_000.0)
|
bitrate >= 1_000_000 -> String.format(Locale.US, "%.1f Mbps", bitrate / 1_000_000.0)
|
||||||
else -> String.format(Locale.US, "%.0f kbps", bitrate / 1_000.0)
|
else -> String.format(Locale.US, "%.0f kbps", bitrate / 1_000.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(UnstableApi::class)
|
|
||||||
private fun hasVideoOverride(player: Player): Boolean = player.trackSelectionParameters.overrides.any { (key, _) -> key.type == C.TRACK_TYPE_VIDEO }
|
|
||||||
|
|
||||||
private fun clearVideoOverride(player: Player) {
|
|
||||||
player.trackSelectionParameters =
|
|
||||||
player.trackSelectionParameters
|
|
||||||
.buildUpon()
|
|
||||||
.clearOverridesOfType(C.TRACK_TYPE_VIDEO)
|
|
||||||
.build()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun selectVideoTrack(
|
|
||||||
player: Player,
|
|
||||||
group: Tracks.Group,
|
|
||||||
trackIndex: Int,
|
|
||||||
) {
|
|
||||||
player.trackSelectionParameters =
|
|
||||||
player.trackSelectionParameters
|
|
||||||
.buildUpon()
|
|
||||||
.setOverrideForType(TrackSelectionOverride(group.mediaTrackGroup, trackIndex))
|
|
||||||
.build()
|
|
||||||
}
|
|
||||||
|
|||||||
+74
@@ -0,0 +1,74 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||||
|
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.vitorpamplona.amethyst.service.playback.composable.controls
|
||||||
|
|
||||||
|
import androidx.annotation.OptIn
|
||||||
|
import androidx.media3.common.C
|
||||||
|
import androidx.media3.common.Player
|
||||||
|
import androidx.media3.common.TrackSelectionOverride
|
||||||
|
import androidx.media3.common.Tracks
|
||||||
|
import androidx.media3.common.util.UnstableApi
|
||||||
|
|
||||||
|
internal fun getVideoTrackGroup(tracks: Tracks): Tracks.Group? = tracks.groups.firstOrNull { it.type == C.TRACK_TYPE_VIDEO && it.length > 0 }
|
||||||
|
|
||||||
|
// Finds the track with the smallest short side (min(width, height)) in the given video group.
|
||||||
|
// Returns null if no track has a positive short side. Used to force lowest-resolution playback
|
||||||
|
// in feeds to save bandwidth. Skips tracks the device can't decode — ExoPlayer would silently
|
||||||
|
// reject an override pointing at an unsupported track and fall back to adaptive selection.
|
||||||
|
@OptIn(UnstableApi::class)
|
||||||
|
internal fun findLowestResolutionTrackIndex(group: Tracks.Group): Int? {
|
||||||
|
var bestIndex: Int? = null
|
||||||
|
var bestShortSide = Int.MAX_VALUE
|
||||||
|
for (i in 0 until group.length) {
|
||||||
|
if (!group.isTrackSupported(i)) continue
|
||||||
|
val format = group.getTrackFormat(i)
|
||||||
|
val shortSide = minOf(format.width, format.height)
|
||||||
|
if (shortSide > 0 && shortSide < bestShortSide) {
|
||||||
|
bestShortSide = shortSide
|
||||||
|
bestIndex = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bestIndex
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(UnstableApi::class)
|
||||||
|
internal fun hasVideoOverride(player: Player): Boolean = player.trackSelectionParameters.overrides.any { (key, _) -> key.type == C.TRACK_TYPE_VIDEO }
|
||||||
|
|
||||||
|
internal fun clearVideoOverride(player: Player) {
|
||||||
|
player.trackSelectionParameters =
|
||||||
|
player.trackSelectionParameters
|
||||||
|
.buildUpon()
|
||||||
|
.clearOverridesOfType(C.TRACK_TYPE_VIDEO)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(UnstableApi::class)
|
||||||
|
internal fun selectVideoTrack(
|
||||||
|
player: Player,
|
||||||
|
group: Tracks.Group,
|
||||||
|
trackIndex: Int,
|
||||||
|
) {
|
||||||
|
player.trackSelectionParameters =
|
||||||
|
player.trackSelectionParameters
|
||||||
|
.buildUpon()
|
||||||
|
.setOverrideForType(TrackSelectionOverride(group.mediaTrackGroup, trackIndex))
|
||||||
|
.build()
|
||||||
|
}
|
||||||
+8
@@ -34,6 +34,8 @@ import androidx.compose.runtime.remember
|
|||||||
import androidx.media3.common.util.UnstableApi
|
import androidx.media3.common.util.UnstableApi
|
||||||
import com.vitorpamplona.amethyst.service.playback.composable.DEFAULT_MUTED_SETTING
|
import com.vitorpamplona.amethyst.service.playback.composable.DEFAULT_MUTED_SETTING
|
||||||
import com.vitorpamplona.amethyst.service.playback.composable.GetVideoController
|
import com.vitorpamplona.amethyst.service.playback.composable.GetVideoController
|
||||||
|
import com.vitorpamplona.amethyst.service.playback.composable.controls.ApplyInitialVideoQuality
|
||||||
|
import com.vitorpamplona.amethyst.service.playback.composable.controls.VideoQualityPolicy
|
||||||
import com.vitorpamplona.amethyst.service.playback.composable.mediaitem.GetMediaItem
|
import com.vitorpamplona.amethyst.service.playback.composable.mediaitem.GetMediaItem
|
||||||
import com.vitorpamplona.amethyst.service.playback.composable.mediaitem.MediaItemData
|
import com.vitorpamplona.amethyst.service.playback.composable.mediaitem.MediaItemData
|
||||||
|
|
||||||
@@ -50,6 +52,12 @@ class PipVideoActivity : ComponentActivity() {
|
|||||||
|
|
||||||
GetMediaItem(mediaItemData) { mediaItem ->
|
GetMediaItem(mediaItemData) { mediaItem ->
|
||||||
GetVideoController(mediaItem, muted, true) { controllerState ->
|
GetVideoController(mediaItem, muted, true) { controllerState ->
|
||||||
|
// PiP window is small, keep bandwidth low by forcing the lowest
|
||||||
|
// rendition. User can still manually change quality via controls.
|
||||||
|
ApplyInitialVideoQuality(
|
||||||
|
player = controllerState.controller,
|
||||||
|
policy = VideoQualityPolicy.LOWEST,
|
||||||
|
)
|
||||||
RegisterBackgroundMedia(controllerState)
|
RegisterBackgroundMedia(controllerState)
|
||||||
RegisterControllerReceiver(controllerState)
|
RegisterControllerReceiver(controllerState)
|
||||||
WatchControllerForActions(mediaItemData, controllerState)
|
WatchControllerForActions(mediaItemData, controllerState)
|
||||||
|
|||||||
+2
@@ -570,6 +570,7 @@ private fun RenderImageOrVideo(
|
|||||||
automaticallyStartPlayback = true,
|
automaticallyStartPlayback = true,
|
||||||
controllerVisible = controllerVisible,
|
controllerVisible = controllerVisible,
|
||||||
hasBlurhash = content.blurhash != null,
|
hasBlurhash = content.blurhash != null,
|
||||||
|
isFullscreen = true,
|
||||||
accountViewModel = accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -630,6 +631,7 @@ private fun RenderImageOrVideo(
|
|||||||
automaticallyStartPlayback = true,
|
automaticallyStartPlayback = true,
|
||||||
controllerVisible = controllerVisible,
|
controllerVisible = controllerVisible,
|
||||||
hasBlurhash = content.blurhash != null,
|
hasBlurhash = content.blurhash != null,
|
||||||
|
isFullscreen = true,
|
||||||
accountViewModel = accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user