0.83.2: the on-air preloaded show survives loading the next one, fixing the second changeover in a row
pauljd/davidson-ci: Davidson CI / quality (push) Failing after 3m11s
pauljd/davidson-ci: Davidson CI / ai-review (push) Failing after 1m45s

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-11-03 10:20:54 +00:00
co-authored by Claude Opus 5.5
parent 34ea521d62
commit c589e46871
5 changed files with 13 additions and 6 deletions
@@ -15,3 +15,8 @@
- Committed plans are untouched, so Monday 9 November's committed Tots run still airs unless Tots' plan is rebuilt.
Unit tests: 321 passed, 0 failed. Lint passed.
## 0.83.2, second changeover in a row
- Cause, from the box's log at 10:15:24: when the show on air had itself been loaded ahead, loading the next one replaced it. The screen lost the on-air show's plan, closed its player (released at 10:15:24.433), went back to tuning, and re-opened it. Then the changeover had no ident. That's what Paul saw on Heroes at 10:15, and it explains the missed Vortex ident this morning: both were the second changeover in a row.
- Fix: the on-air preloaded show is kept apart from the next one, so loading the next can't take it off the screen.
- Unit tests: 321 passed. Lint passed. The fix is in the composable, so it's proven on the box by two changeovers in a row.
+2 -2
View File
@@ -9,8 +9,8 @@ android {
applicationId = "tv.orbit.classic"
minSdk = 26
targetSdk = 36
versionCode = 98
versionName = "0.83.1-no-third-in-a-row"
versionCode = 99
versionName = "0.83.2-second-changeover"
}
buildFeatures { compose = true }
compileOptions { sourceCompatibility = JavaVersion.VERSION_17; targetCompatibility = JavaVersion.VERSION_17 }
@@ -66,7 +66,7 @@ internal class HttpsEmbyTransport(internal val deviceId: String = UUID.randomUUI
connection.connectTimeout = 10_000
connection.readTimeout = request.readTimeoutMs ?: if (request.path == "/Sessions/Logout") 5_000 else 15_000
connection.setRequestProperty("Accept", "application/json")
connection.setRequestProperty("X-Emby-Authorization", "Emby Client=\"Orbit TV\", Device=\"Orbit TV\", DeviceId=\"$deviceId\", Version=\"0.83.1\"")
connection.setRequestProperty("X-Emby-Authorization", "Emby Client=\"Orbit TV\", Device=\"Orbit TV\", DeviceId=\"$deviceId\", Version=\"0.83.2\"")
request.token?.let { connection.setRequestProperty("X-Emby-Token", it) }
if (request.method == "POST") {
val bytes = request.body.orEmpty().toByteArray(Charsets.UTF_8)
@@ -67,6 +67,8 @@ internal fun ScheduledChannelPlayer(session: EmbySampleSession, channel: SampleC
}
val playerContext = androidx.compose.ui.platform.LocalContext.current
var loaded by remember(channel) { mutableStateOf<LoadedNext?>(null) }
// The show on air when it was itself loaded ahead. Kept apart, so loading the one after can't take it off the screen.
var playing by remember(channel) { mutableStateOf<LoadedNext?>(null) }
DisposableEffect(channel) { onDispose { loaded?.discard(session) } }
LaunchedEffect(channel, current.startMs, phase == IdentPhase.Showing) {
if (phase == IdentPhase.Showing) PlaybackNotes.add(notes, "channel=${channel.number} ident shown next=${next.startMs} loaded=${loaded?.startMs == next.startMs}")
@@ -77,13 +79,13 @@ internal fun ScheduledChannelPlayer(session: EmbySampleSession, channel: SampleC
try {
val opened = withContext(Dispatchers.IO) { session.prepareForTuning(next.media.item, next.media.sourceId) { Thread.sleep(2_000) } }
if (scheduledRuntimeMatches(opened.durationMs, next.media.durationMs)) {
loaded?.discard(session)
if (loaded?.startMs == current.startMs) playing = loaded else loaded?.discard(session)
loaded = LoadedNext(next.startMs, opened, loadNextPlayer(playerContext, session, opened, audioPreference.value))
android.util.Log.i("OrbitSchedule", "channel=${channel.number} nextLoadedAheadMs=${next.startMs - System.currentTimeMillis()}")
}
} catch (cancelled: CancellationException) { throw cancelled } catch (_: Exception) { }
}
val loadedNow = loaded?.takeIf { it.startMs == current.startMs }
val loadedNow = listOfNotNull(loaded, playing).firstOrNull { it.startMs == current.startMs }
val prepareMutex = remember(channel) { Mutex() }
val handedNow = handed?.takeIf { it.available && it.channelNumber == channel.number }
var prepared by remember(channel) { mutableStateOf(handedNow?.plan) }
@@ -33,7 +33,7 @@ internal class SecureMediaDataSource(private val session: EmbySampleSession) : B
next.instanceFollowRedirects = false
next.connectTimeout = 10_000
next.readTimeout = 20_000
next.setRequestProperty("User-Agent", "OrbitTV/0.83.1")
next.setRequestProperty("User-Agent", "OrbitTV/0.83.2")
next.setRequestProperty("Accept-Encoding", "identity")
session.mediaHeaders(address).forEach { (key, value) -> next.setRequestProperty(key, value) }
if (dataSpec.position != 0L || dataSpec.length != C.LENGTH_UNSET.toLong()) {