diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index a8e3b88..3fc4d76 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 = 150 - versionName = "0.101.0-new-arrivals" + versionCode = 151 + versionName = "0.102.0-vortex-idents" } buildFeatures { compose = true } compileOptions { sourceCompatibility = JavaVersion.VERSION_17; targetCompatibility = JavaVersion.VERSION_17 } diff --git a/android/app/src/main/java/tv/orbit/classic/BlockIdents.kt b/android/app/src/main/java/tv/orbit/classic/BlockIdents.kt index 44bf8ba..ee676ac 100644 --- a/android/app/src/main/java/tv/orbit/classic/BlockIdents.kt +++ b/android/app/src/main/java/tv/orbit/classic/BlockIdents.kt @@ -44,6 +44,10 @@ internal val BlockIdents: Map = mapOf( "gold.sunday" to BlockIdent(R.raw.block_gold_sunday, R.raw.block_gold_sunday_sting), "three.scottish" to BlockIdent(R.raw.block_three_scottish, R.raw.block_three_scottish_sting), "vortex.movie" to BlockIdent(R.raw.block_vortex_movie, R.raw.block_vortex_movie_sting), + // Batch 6, approved by Paul on 4 November 2026: Vortex's afternoon and late blocks, and the Afterburn premiere. + "vortex.prime" to BlockIdent(R.raw.block_vortex_prime, R.raw.block_vortex_prime_sting), + "vortex.afterburn" to BlockIdent(R.raw.block_vortex_afterburn, R.raw.block_vortex_afterburn_sting), + "vortex.afterburn.premiere" to BlockIdent(R.raw.block_vortex_premiere, R.raw.block_vortex_premiere_sting), "orbit2.retro" to BlockIdent(R.raw.block_orbit2_retro, R.raw.block_orbit2_retro_sting), "orbit2.spooky" to BlockIdent(R.raw.block_orbit2_spooky, R.raw.block_orbit2_spooky_sting), "ringx.ppv" to BlockIdent(R.raw.block_ringx_ppv, R.raw.block_ringx_ppv_sting, BlockOverlay.TonightsEvent), diff --git a/android/app/src/main/java/tv/orbit/classic/ChannelIdentities.kt b/android/app/src/main/java/tv/orbit/classic/ChannelIdentities.kt index 3836bcd..cb850b4 100644 --- a/android/app/src/main/java/tv/orbit/classic/ChannelIdentities.kt +++ b/android/app/src/main/java/tv/orbit/classic/ChannelIdentities.kt @@ -155,7 +155,8 @@ internal val VortexDayParts get() = listOf( // Mornings are the staples. Reviewed modern anime joins in the afternoon, so it complements the channel rather than leading it. DayPart(6, 12, VortexCartoons + VortexFavourites + listOf("Cardcaptor Sakura", "Cardcaptors", "Sailor Moon", "Dragon Ball", "Zoids: Chaotic Century", "Zoids")), DayPart(12, 16, VortexCartoons + VortexFavourites + VortexFamilyAnime + listOf("Dragon Ball Z", "Mobile Suit Gundam Wing", "Tenchi Muyo! Ryo-Ohki", "Yu Yu Hakusho", "Outlaw Star", "The Big O")), - DayPart(16, 18, VortexToonami, group = "toonami"), DayPart(18, 21, VortexToonami + VortexEvening, group = "night"), DayPart(21, 23, VortexToonami, group = "toonami"), + DayPart(16, 18, VortexToonami, group = "toonami", key = "vortex.prime"), DayPart(18, 21, VortexToonami + VortexEvening, group = "night"), + DayPart(21, 23, VortexToonami, group = "toonami", key = "vortex.afterburn"), DayPart(23, 6, VortexLate, group = "day")) /** Heroes: the recognisable superhero cartoons every day, Marvel on Saturday mornings as Fox Kids had it. */ 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 408716f..338ae9c 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.101.0\"") + connection.setRequestProperty("X-Emby-Authorization", "Emby Client=\"Orbit TV\", Device=\"Orbit TV\", DeviceId=\"$deviceId\", Version=\"0.102.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/SecureMediaDataSource.kt b/android/app/src/main/java/tv/orbit/classic/SecureMediaDataSource.kt index a662064..f666d5c 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.101.0") + next.setRequestProperty("User-Agent", "OrbitTV/0.102.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()) { diff --git a/android/app/src/main/res/raw/block_vortex_afterburn.mp4 b/android/app/src/main/res/raw/block_vortex_afterburn.mp4 new file mode 100644 index 0000000..0bf202e Binary files /dev/null and b/android/app/src/main/res/raw/block_vortex_afterburn.mp4 differ diff --git a/android/app/src/main/res/raw/block_vortex_afterburn_sting.m4a b/android/app/src/main/res/raw/block_vortex_afterburn_sting.m4a new file mode 100644 index 0000000..67cc3e1 Binary files /dev/null and b/android/app/src/main/res/raw/block_vortex_afterburn_sting.m4a differ diff --git a/android/app/src/main/res/raw/block_vortex_premiere.mp4 b/android/app/src/main/res/raw/block_vortex_premiere.mp4 new file mode 100644 index 0000000..ccbec6b Binary files /dev/null and b/android/app/src/main/res/raw/block_vortex_premiere.mp4 differ diff --git a/android/app/src/main/res/raw/block_vortex_premiere_sting.m4a b/android/app/src/main/res/raw/block_vortex_premiere_sting.m4a new file mode 100644 index 0000000..52feecd Binary files /dev/null and b/android/app/src/main/res/raw/block_vortex_premiere_sting.m4a differ diff --git a/android/app/src/main/res/raw/block_vortex_prime.mp4 b/android/app/src/main/res/raw/block_vortex_prime.mp4 new file mode 100644 index 0000000..057d72a Binary files /dev/null and b/android/app/src/main/res/raw/block_vortex_prime.mp4 differ diff --git a/android/app/src/main/res/raw/block_vortex_prime_sting.m4a b/android/app/src/main/res/raw/block_vortex_prime_sting.m4a new file mode 100644 index 0000000..6839056 Binary files /dev/null and b/android/app/src/main/res/raw/block_vortex_prime_sting.m4a differ diff --git a/android/app/src/test/java/tv/orbit/classic/PresentationTest.kt b/android/app/src/test/java/tv/orbit/classic/PresentationTest.kt index 771cd3a..6498e31 100644 --- a/android/app/src/test/java/tv/orbit/classic/PresentationTest.kt +++ b/android/app/src/test/java/tv/orbit/classic/PresentationTest.kt @@ -18,10 +18,9 @@ class PresentationTest { @Test fun everyApprovedBlockKeyHasItsIdent() { val keys = SmartChannels.flatMap { it.dayParts }.map { it.key }.filter { it.isNotEmpty() }.toSet() - // Riot 3, After Dark 3, Horror, Gold, Three, Vortex, Orbit 2 2, Ring X 7 (six nights and PPV), Terra, and the Vortex Afterburn premiere. - assertEquals(21, keys.size) - // The premiere's own ident isn't made yet: until it is, the standard Vortex ident plays (Stage D1). - assertEquals(setOf("vortex.afterburn.premiere"), keys - BlockIdents.keys) + // Riot 3, After Dark 3, Horror, Gold, Three, Vortex 4 (Movie Night, Prime, Afterburn and its premiere), Orbit 2 2, Ring X 7 (six nights and PPV), Terra. + assertEquals(23, keys.size) + assertEquals(emptySet(), keys - BlockIdents.keys) assertTrue(keys.all { PRESENTATION_KEY.matches(it) }) } diff --git a/android/app/src/test/java/tv/orbit/classic/ReplayFingerprintTest.kt b/android/app/src/test/java/tv/orbit/classic/ReplayFingerprintTest.kt index 926f93c..46188f2 100644 --- a/android/app/src/test/java/tv/orbit/classic/ReplayFingerprintTest.kt +++ b/android/app/src/test/java/tv/orbit/classic/ReplayFingerprintTest.kt @@ -9,7 +9,8 @@ import java.io.File * Four weeks of top-ups on every channel from copies of the box's committed plans and pools, run by hand: * ORBIT_REPLAY_DIR= ./gradlew :app:testDebugUnitTest --tests '*ReplayFingerprintTest*' * Writes replay-fingerprints.txt there, one line per channel: a fingerprint of every programme booked and why. Run it - * on two versions of the app to show which channels a change touches. Needs 0.97.0 or later (weighted rotation). + * on two versions of the app to show which channels a change touches. ORBIT_FINGERPRINT_DUMP= also writes each + * channel's full list, to see exactly what changed. Needs 0.97.0 or later (weighted rotation). */ class ReplayFingerprintTest { private val dir = System.getenv("ORBIT_REPLAY_DIR")?.let(::File) @@ -56,6 +57,7 @@ class ReplayFingerprintTest { now += 6 * 60 * 60_000L } val text = all.joinToString("\n") { "${it.startMs} ${it.seriesId} ${it.mark} ${it.media.item.id} ${it.why} ${it.presentation}" } + System.getenv("ORBIT_FINGERPRINT_DUMP")?.let { File(dir, "$it-${programming.number}.txt").writeText(text) } val digest = java.security.MessageDigest.getInstance("SHA-256").digest(text.toByteArray()).joinToString("") { "%02x".format(it) }.take(16) val committedDigest = java.security.MessageDigest.getInstance("SHA-256").digest(all.take(committed.entries.size).joinToString("\n") { "${it.startMs} ${it.seriesId} ${it.mark} ${it.media.item.id} ${it.why} ${it.presentation}" }.toByteArray()).joinToString("") { "%02x".format(it) }.take(16) out.appendLine("${programming.number} committed=$committedDigest kept=${all.take(committed.entries.size) == committed.entries} fourWeeks=$digest programmes=${all.size}") diff --git a/docs/VERIFICATION-0.102.0.md b/docs/VERIFICATION-0.102.0.md new file mode 100644 index 0000000..fd11fb3 --- /dev/null +++ b/docs/VERIFICATION-0.102.0.md @@ -0,0 +1,16 @@ +# 0.102.0: Vortex Prime and Afterburn idents + +Paul approved the Batch 6 previews on 4 November 2026 ("they all look amazing"). + +- **New presentation keys** on Vortex's day parts: `vortex.prime` (16:00 to 18:00) and `vortex.afterburn` (21:00 to 23:00). +- **Premiere:** `vortex.afterburn.premiere` now has its own clip; until now it fell back to the standard Vortex ident. +- **Assets:** `res/raw/block_vortex_prime|afterburn|premiere.mp4` and `_sting.m4a` (AAC 32 kbps). Stings play only with Special ident sound on. + +## Checks + +- 467 unit tests run, 0 failures; lint clean. PresentationTest now expects 23 keys, every one with its ident. +- **Four-week replay** from the box's plans and pools at 18:27 today (`ReplayFingerprintTest`, with the new `ORBIT_FINGERPRINT_DUMP` to write each channel's full list): + - Every channel's fingerprint is identical to 0.101.0 except Vortex. + - Vortex's 2,190 programmes are identical apart from the key: 145 now carry `vortex.prime` and 117 `vortex.afterburn`. + - Committed bookings are unchanged (`kept=true`). The first keyed booking is Wednesday 11 November at 16:10, because bookings already made keep no key. +- APK: `outputs/revision-0.102.0`, signed. Not installed.