From a591a28c74cfde143e8e7fe9b448e679e071646c Mon Sep 17 00:00:00 2001 From: Pauljd Date: Sun, 1 Nov 2026 23:15:45 +0000 Subject: [PATCH] Keep the programme bar hidden when one programme rolls into the next (0.32.0) Co-Authored-By: Claude Opus 5.5 (1M context) --- VERIFICATION-0.32.0.md | 16 ++++++++++++++++ android/app/build.gradle.kts | 4 ++-- .../src/main/java/tv/orbit/classic/EmbyClient.kt | 2 +- .../java/tv/orbit/classic/EmbyPlaybackDialog.kt | 6 +++--- .../tv/orbit/classic/SampleChannelScreens.kt | 6 +++++- .../tv/orbit/classic/SecureMediaDataSource.kt | 2 +- 6 files changed, 28 insertions(+), 8 deletions(-) create mode 100644 VERIFICATION-0.32.0.md diff --git a/VERIFICATION-0.32.0.md b/VERIFICATION-0.32.0.md new file mode 100644 index 0000000..82f8668 --- /dev/null +++ b/VERIFICATION-0.32.0.md @@ -0,0 +1,16 @@ +# Orbit TV 0.32.0 verification + +The programme bar and green channel number no longer appear when one programme rolls into the next. Paul reported the bar popping up unprompted. + +## Changed +- At a programme boundary on the same channel, the bar and number stay hidden; the channel's logo shows instead. +- They still appear when tuning in or changing channel, and OK still brings up the bar at any time. +- Includes 0.31's stream handover, which wasn't installed on its own. + +## Checked +- assembleDebug, testDebugUnitTest and lintDebug pass; 138 tests, 0 failures. None of these exercise the rollover on screen. +- APK signature verified with apksigner. + +## Not checked +- Not yet run on the box. Both this and the 0.31 handover need Paul's on-TV check. +- The tuning screen still shows briefly between programmes while the next one opens. diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index e185845..8c8d162 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 = 32 - versionName = "0.31.0-seamless-picture" + versionCode = 33 + versionName = "0.32.0-quiet-rollover" } 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 9e4f0f0..eb10813 100644 --- a/android/app/src/main/java/tv/orbit/classic/EmbyClient.kt +++ b/android/app/src/main/java/tv/orbit/classic/EmbyClient.kt @@ -41,7 +41,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.31.0\"") + connection.setRequestProperty("X-Emby-Authorization", "Emby Client=\"Orbit TV\", Device=\"Orbit TV\", DeviceId=\"$deviceId\", Version=\"0.32.0\"") 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/EmbyPlaybackDialog.kt b/android/app/src/main/java/tv/orbit/classic/EmbyPlaybackDialog.kt index bb92d56..c3f4bc5 100644 --- a/android/app/src/main/java/tv/orbit/classic/EmbyPlaybackDialog.kt +++ b/android/app/src/main/java/tv/orbit/classic/EmbyPlaybackDialog.kt @@ -38,13 +38,13 @@ import kotlinx.coroutines.withContext @androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class) @Composable internal fun EmbyPlaybackDialog(session: EmbySampleSession, plan: EmbyPlaybackPlan, onClose: () -> Unit, scheduled: ScheduledPlayback? = null, - onStep: (Int) -> Unit = {}, onLast: () -> Unit = {}, onNumber: (Int) -> Boolean = { false }, subtitlePreference: MutableState? = null, runningPlayer: ExoPlayer? = null) { + onStep: (Int) -> Unit = {}, onLast: () -> Unit = {}, onNumber: (Int) -> Boolean = { false }, subtitlePreference: MutableState? = null, runningPlayer: ExoPlayer? = null, announce: Boolean = true) { val context = LocalContext.current var failure by remember { mutableStateOf(null) } - var tuningNumberVisible by remember { mutableStateOf(true) } + var tuningNumberVisible by remember { mutableStateOf(announce) } var ready by remember { mutableStateOf(false) } var playing by remember { mutableStateOf(false) } - var infoVisible by remember { mutableStateOf(true) } + var infoVisible by remember { mutableStateOf(announce) } var lastInteraction by remember { mutableLongStateOf(SystemClock.elapsedRealtime()) } fun showInfo() { infoVisible = true; lastInteraction = SystemClock.elapsedRealtime() } var numberEntry by remember { mutableStateOf(null) } 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 d2fb6fd..993f61e 100644 --- a/android/app/src/main/java/tv/orbit/classic/SampleChannelScreens.kt +++ b/android/app/src/main/java/tv/orbit/classic/SampleChannelScreens.kt @@ -97,6 +97,8 @@ internal fun ScheduledChannelPlayer(session: EmbySampleSession, channel: SampleC LaunchedEffect(channel, clockOffsetMs) { while (true) { now = System.currentTimeMillis() + clockOffsetMs; delay(250) } } val shows = channel.guide(now, 2) val current = shows[0] + // The bar and number greet a tune or channel change. One programme rolling into the next stays clean. + val announcedChannel = remember { intArrayOf(-1) } val handed = remember { val stream = handoff?.take() if (stream != null && stream.channelNumber == channel.number && stream.startMs == current.startMs && clockOffsetMs == 0L) stream @@ -129,8 +131,10 @@ internal fun ScheduledChannelPlayer(session: EmbySampleSession, channel: SampleC val plan = prepared?.takeIf { it.item.id == current.media.item.id && preparedSlot == current.startMs && problem == null } if (plan != null) { key(current.startMs) { + val announce = remember { announcedChannel[0] != channel.number } + SideEffect { announcedChannel[0] = channel.number } EmbyPlaybackDialog(session, plan, onClose, - scheduled = ScheduledPlayback(channel, current, shows[1], clockOffsetMs), onStep = onStep, onLast = onLast, onNumber = onNumber, subtitlePreference = subtitlePreference, runningPlayer = handed?.takeIf { it.plan === plan }?.claim()) + scheduled = ScheduledPlayback(channel, current, shows[1], clockOffsetMs), onStep = onStep, onLast = onLast, onNumber = onNumber, subtitlePreference = subtitlePreference, runningPlayer = handed?.takeIf { it.plan === plan }?.claim(), announce = announce) } } else Dialog(onDismissRequest = onClose, properties = DialogProperties(usePlatformDefaultWidth = false, decorFitsSystemWindows = false, securePolicy = SecureFlagPolicy.SecureOn)) { 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 017df2c..486768c 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.31.0") + next.setRequestProperty("User-Agent", "OrbitTV/0.32.0") 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()) {