diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 0181f2e..d024a20 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 = 153 - versionName = "0.104.0-orbit-x-stories" + versionCode = 154 + versionName = "0.105.0-vortex-stories" } buildFeatures { compose = true } compileOptions { sourceCompatibility = JavaVersion.VERSION_17; targetCompatibility = JavaVersion.VERSION_17 } diff --git a/android/app/src/main/java/tv/orbit/classic/ChannelSchedule.kt b/android/app/src/main/java/tv/orbit/classic/ChannelSchedule.kt index 493932d..78d1609 100644 --- a/android/app/src/main/java/tv/orbit/classic/ChannelSchedule.kt +++ b/android/app/src/main/java/tv/orbit/classic/ChannelSchedule.kt @@ -182,7 +182,7 @@ internal val SmartChannels = listOf( SuggestionRule("day", "Daytime", "Movie", listOf("Thriller", "Mystery"), 1975..2005, 6.5f, setOf("NC-17", "X"), 30, NotCultCinema + "Science Fiction", 70, 7.9f), SuggestionRule("late", "Late night", "Movie", listOf("Horror"), 1970..2010, 6f, emptySet(), 90, NotCultCinema, 70))), ChannelProgramming(VORTEX_NUMBER, "Vortex", VortexShows, seriesCount = 40, finishTogether = true, exactTitles = true, englishAudio = false, tierWeights = VortexTiers, catalogueScaleHours = CATALOGUE_SCALE_HOURS, - dayParts = VortexDayParts, withdrawn = setOf(BEERUS_OTHER_COPY), + dayParts = VortexDayParts, withdrawn = setOf(BEERUS_OTHER_COPY), storyGrouping = true, suggestionRules = listOf( SuggestionRule("movie", "Movie Night", "Movie", listOf("Animation"), 1985..2025, 5f, setOf("NC-17", "X"), 30, setOf("Documentary"), 30, titleContains = "Dragon Ball"), SuggestionRule("movie", "Movie Night", "Movie", listOf("Animation", "Action"), 1985..2015, 7f, setOf("NC-17", "X"), 30, setOf("Documentary", "Family"), 60), @@ -201,7 +201,7 @@ internal val SmartChannels = listOf( catalogueScaleHours = if (it.number in CatalogueScaledChannels) CATALOGUE_SCALE_HOURS else null, catalogueScaleFloor = CatalogueScaleFloors[it.number] ?: MIN_CATALOGUE_SCALE, storyGrouping = it.number in StoryGroupingChannels) } -/** Living Network Stage E: story grouping beyond Paradox, one channel at a time. Heroes first (4 November 2026): 119 story parts a week, none booked together; then Orbit X. */ +/** Living Network Stage E: story grouping beyond Paradox, one channel at a time. Heroes first (4 November 2026): 119 story parts a week, none booked together; then Orbit X. Vortex sets its own, above. */ private val StoryGroupingChannels get() = setOf(602, 603) /** 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 ff19e01..0321d3d 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.104.0\"") + connection.setRequestProperty("X-Emby-Authorization", "Emby Client=\"Orbit TV\", Device=\"Orbit TV\", DeviceId=\"$deviceId\", Version=\"0.105.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 9836608..8d4cbc3 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.104.0") + next.setRequestProperty("User-Agent", "OrbitTV/0.105.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/test/java/tv/orbit/classic/StoryGroupingTest.kt b/android/app/src/test/java/tv/orbit/classic/StoryGroupingTest.kt index 2ecfd46..347b039 100644 --- a/android/app/src/test/java/tv/orbit/classic/StoryGroupingTest.kt +++ b/android/app/src/test/java/tv/orbit/classic/StoryGroupingTest.kt @@ -129,8 +129,8 @@ class StoryGroupingTest { } @Test fun onlyApprovedChannelsGroupStories() { - // Paradox (Stage A), then Heroes and Orbit X (Stage E, one channel at a time). Grouping works through weighted rotation. - assertEquals(listOf(106, 602, 603), SmartChannels.filter { it.storyGrouping }.map { it.number }.sorted()) + // Paradox (Stage A), then Heroes, Orbit X and Vortex (Stage E, one channel at a time). Grouping works through weighted rotation. + assertEquals(listOf(106, 601, 602, 603), SmartChannels.filter { it.storyGrouping }.map { it.number }.sorted()) assertTrue(SmartChannels.filter { it.storyGrouping }.all { it.tierWeights != null }) } } diff --git a/docs/VERIFICATION-0.105.0.md b/docs/VERIFICATION-0.105.0.md new file mode 100644 index 0000000..f964fa2 --- /dev/null +++ b/docs/VERIFICATION-0.105.0.md @@ -0,0 +1,32 @@ +# 0.105.0: story grouping on Vortex (Stage E, third channel) + +Vortex's `ChannelProgramming` now has `storyGrouping = true`. Heroes and Orbit X stay on `StoryGroupingChannels`. + +## Checks + +- 467 unit tests run, 0 failures; lint clean. +- **Four-week replay** from the box's plans and pools at 18:27 today. Every fingerprint is identical to 0.104.0 except Vortex, and Vortex's committed bookings are unchanged. +- **Vortex over the four weeks:** + +| | 0.104.0 | 0.105.0 | +|---|---|---| +| Programmes | 1,707 | 1,705 | +| Story openers followed by part 2 | 0 | 57 | +| Openers left hanging | 58 | 2 | +| Later parts aired alone | 76 | 11 | +| Same episode again within 7 days | 1 | 0 | + +- **Afterburn premiere:** still on time, episodes 1 and 2 on 21 November and 3 and 4 on 28 November. + +| Night | 0.104.0 | 0.105.0 | +|---|---|---| +| 21 November | 21:08 | 21:02 | +| 28 November | 21:00 | 21:07 | + + The 28 November start is within the premiere's 10-minute grace. Dragon Ball Movie Night bookings: 20 before, 21 after. + +**What's left apart:** +- **Attack on Titan:** most of the 11 later parts are its Struggle for Trost, which runs 9 parts. That's past the 6-part limit, so it isn't grouped. It plays in order across days, as a serial, which is the case Paul's "serial slots" idea covers. +- **Two others:** a Yu-Gi-Oh! Zexal opener and a Gravity Falls part 2 whose part 1 isn't numbered as one. + +APK: `outputs/revision-0.105.0`, signed. Not installed.