From c589e46871fe3b644e8ba54b7fe33e6809d44a29 Mon Sep 17 00:00:00 2001 From: Pauljd Date: Tue, 3 Nov 2026 10:20:54 +0000 Subject: [PATCH] 0.83.2: the on-air preloaded show survives loading the next one, fixing the second changeover in a row Co-Authored-By: Claude Opus 5.5 (1M context) --- VERIFICATION-0.83.1.md => VERIFICATION-0.83.2.md | 5 +++++ android/app/build.gradle.kts | 4 ++-- android/app/src/main/java/tv/orbit/classic/EmbyClient.kt | 2 +- .../src/main/java/tv/orbit/classic/SampleChannelScreens.kt | 6 ++++-- .../src/main/java/tv/orbit/classic/SecureMediaDataSource.kt | 2 +- 5 files changed, 13 insertions(+), 6 deletions(-) rename VERIFICATION-0.83.1.md => VERIFICATION-0.83.2.md (74%) diff --git a/VERIFICATION-0.83.1.md b/VERIFICATION-0.83.2.md similarity index 74% rename from VERIFICATION-0.83.1.md rename to VERIFICATION-0.83.2.md index b78d51e..2b69d1f 100644 --- a/VERIFICATION-0.83.1.md +++ b/VERIFICATION-0.83.2.md @@ -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. diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 85c638e..37742c0 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -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 } diff --git a/android/app/src/main/java/tv/orbit/classic/EmbyClient.kt b/android/app/src/main/java/tv/orbit/classic/EmbyClient.kt index 081dae0..b70ca7c 100644 --- a/android/app/src/main/java/tv/orbit/classic/EmbyClient.kt +++ b/android/app/src/main/java/tv/orbit/classic/EmbyClient.kt @@ -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) diff --git a/android/app/src/main/java/tv/orbit/classic/SampleChannelScreens.kt b/android/app/src/main/java/tv/orbit/classic/SampleChannelScreens.kt index a88f942..423b7e8 100644 --- a/android/app/src/main/java/tv/orbit/classic/SampleChannelScreens.kt +++ b/android/app/src/main/java/tv/orbit/classic/SampleChannelScreens.kt @@ -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(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(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) } diff --git a/android/app/src/main/java/tv/orbit/classic/SecureMediaDataSource.kt b/android/app/src/main/java/tv/orbit/classic/SecureMediaDataSource.kt index ea61452..c976d48 100644 --- a/android/app/src/main/java/tv/orbit/classic/SecureMediaDataSource.kt +++ b/android/app/src/main/java/tv/orbit/classic/SecureMediaDataSource.kt @@ -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()) {