Picture hands over to full screen; shorter squeeze-back for shows
Port of release 0.108.64. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -29,6 +29,12 @@ import kotlinx.coroutines.withContext
|
||||
internal class LiveStream(val channelNumber: Int, val startMs: Long, val plan: EmbyPlaybackPlan, private val player: ExoPlayer) {
|
||||
private var claimed = false
|
||||
val available get() = !claimed
|
||||
/**
|
||||
* Handed to full screen. The picture then leaves it running even though full screen, which opens in its own window,
|
||||
* only claims it a moment later (Paul, 6 November 2026: choosing full screen still re-tuned, as the picture let go of
|
||||
* the stream before full screen had taken it).
|
||||
*/
|
||||
var handedOff = false
|
||||
fun claim(): ExoPlayer? = if (claimed) null else player.also { claimed = true }
|
||||
fun discard(session: EmbySampleSession) { claim()?.let { closeLiveStream(session, plan, it) } }
|
||||
}
|
||||
@@ -39,7 +45,7 @@ internal class LiveHandoff {
|
||||
/** Call before going full screen. The picture then leaves its stream running for the player to take. */
|
||||
fun handOver(channelNumber: Int) {
|
||||
android.util.Log.i("OrbitSchedule", "picture handover channel=$channelNumber pictureOpen=${open != null} sameChannel=${open?.channelNumber == channelNumber}")
|
||||
handed = open?.takeIf { it.channelNumber == channelNumber }; open = null
|
||||
handed = open?.takeIf { it.channelNumber == channelNumber }?.also { it.handedOff = true }; open = null
|
||||
}
|
||||
fun take(): LiveStream? = handed.also { handed = null }
|
||||
/** Left by the full-screen player on the way out, for the picture to carry on with. */
|
||||
@@ -164,7 +170,7 @@ private fun GuideLivePlayer(session: EmbySampleSession, plan: EmbyPlaybackPlan,
|
||||
player.removeListener(listener)
|
||||
if (handoff.open === stream) handoff.open = null
|
||||
// Handed to the full-screen player, which owns it from here.
|
||||
if (handoff.handed !== stream && stream.available) stream.discard(session)
|
||||
if (!stream.handedOff && stream.available) stream.discard(session)
|
||||
}
|
||||
}
|
||||
LaunchedEffect(player, ready) {
|
||||
|
||||
@@ -56,6 +56,8 @@ internal fun ScheduledChannelPlayer(session: EmbySampleSession, channel: SampleC
|
||||
if (stream != null && stream.channelNumber == channel.number && stream.startMs == current.startMs && clockOffsetMs == 0L) stream
|
||||
else { stream?.discard(session); null }
|
||||
}
|
||||
// A handed stream full screen didn't end up using is closed with it, so it never plays on unseen.
|
||||
DisposableEffect(Unit) { onDispose { handed?.discard(session) } }
|
||||
val next = shows[1]
|
||||
val identsOn = remember { OrbitSettings(settingsContext).idents }
|
||||
val specialOn = remember { OrbitSettings(settingsContext).specialIdents }
|
||||
@@ -66,7 +68,7 @@ internal fun ScheduledChannelPlayer(session: EmbySampleSession, channel: SampleC
|
||||
// The squeeze-back (Paul, 6 November 2026): over the last minute, with the next programme loading from its start.
|
||||
val squeezeOn = remember { OrbitSettings(settingsContext).squeezeBack }
|
||||
val calmMotion = remember { OrbitSettings(settingsContext).reduceMotion }
|
||||
val squeezing = squeezeOn && channel is ScheduledChannel && squeezeBackDue(channel.number, current.startMs, current.endMs, next.startMs, now, if (identsHere) identMs else 0L, clockOffsetMs)
|
||||
val squeezing = squeezeOn && channel is ScheduledChannel && squeezeBackDue(channel.number, current.startMs, current.endMs, next.startMs, now, if (identsHere) identMs else 0L, clockOffsetMs, squeezeLength(current.media.item.type))
|
||||
val squeeze by androidx.compose.animation.core.animateFloatAsState(if (squeezing) 1f else 0f,
|
||||
if (calmMotion) androidx.compose.animation.core.snap() else androidx.compose.animation.core.tween(1_000), label = "squeeze")
|
||||
val phase = if (identsHere) identPhase(current.startMs, current.endMs, now, identMs, if (squeezeOn) SQUEEZE_MS + IDENT_LOAD_AHEAD_MS else IDENT_LOAD_AHEAD_MS) else IdentPhase.None
|
||||
|
||||
@@ -31,12 +31,19 @@ internal const val SQUEEZE_MS = 60_000L
|
||||
internal const val SQUEEZE_SHRINK = .46f
|
||||
private const val SQUEEZE_SHORTEST_MS = 10 * 60_000L
|
||||
|
||||
/** Whether the squeeze-back is showing now: the minute before the ident (or before the end, with no ident). */
|
||||
internal fun squeezeBackDue(number: Int, startMs: Long, endMs: Long, nextStartMs: Long, nowMs: Long, identMs: Long, offsetMs: Long): Boolean {
|
||||
/**
|
||||
* How long the squeeze-back lasts (Paul, 6 November 2026): a TV show's end goes small for its last 22 seconds, a film's for
|
||||
* its last minute, as films have longer credits.
|
||||
*/
|
||||
internal const val SQUEEZE_SHOW_MS = 22_000L
|
||||
internal fun squeezeLength(type: String) = if (type == "Movie") SQUEEZE_MS else SQUEEZE_SHOW_MS
|
||||
|
||||
/** Whether the squeeze-back is showing now: the [lengthMs] before the ident (or before the end, with no ident). */
|
||||
internal fun squeezeBackDue(number: Int, startMs: Long, endMs: Long, nextStartMs: Long, nowMs: Long, identMs: Long, offsetMs: Long, lengthMs: Long = SQUEEZE_MS): Boolean {
|
||||
if (offsetMs != 0L || number == DRIVE_IN_NUMBER || endMs - startMs < SQUEEZE_SHORTEST_MS || nextStartMs != endMs) return false
|
||||
if (number == TOTS_NUMBER && totsMood(nowMs).evening) return false
|
||||
val until = endMs - identMs
|
||||
return nowMs >= until - SQUEEZE_MS && nowMs < until
|
||||
return nowMs >= until - lengthMs && nowMs < until
|
||||
}
|
||||
|
||||
/** Each channel's cast member in the squeeze-back: Ollie on Tots, the others shared round the rest. */
|
||||
|
||||
@@ -45,4 +45,12 @@ class SqueezeBackTest {
|
||||
assertNull(squeezeCast(TOTS_NUMBER)); assertNotNull(squeezeCast(106))
|
||||
assertEquals(squeezeCast(106), squeezeCast(106))
|
||||
}
|
||||
|
||||
@Test fun aShowGoesSmallForItsLast22SecondsAFilmForItsLastMinute() {
|
||||
assertEquals(SQUEEZE_SHOW_MS, squeezeLength("Episode")); assertEquals(SQUEEZE_MS, squeezeLength("Movie"))
|
||||
val start = 0L; val end = 30 * 60_000L
|
||||
assertFalse(squeezeBackDue(106, start, end, end, end - 30_000, 0, 0, squeezeLength("Episode")))
|
||||
assertTrue(squeezeBackDue(106, start, end, end, end - 20_000, 0, 0, squeezeLength("Episode")))
|
||||
assertTrue(squeezeBackDue(106, start, end, end, end - 50_000, 0, 0, squeezeLength("Movie")))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user