Add Orbit Three, Riot and Dark Files (0.58.0 in testing)
pauljd/davidson-ci: Davidson CI / quality (push) Failing after 1m37s
pauljd/davidson-ci: Davidson CI / ai-review (push) Failing after 1m26s

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-11-02 09:59:13 +00:00
co-authored by Claude Opus 5.5
parent 988a3476e0
commit e630737b69
17 changed files with 38 additions and 9 deletions
+2 -2
View File
@@ -9,8 +9,8 @@ android {
applicationId = "tv.orbit.classic"
minSdk = 26
targetSdk = 36
versionCode = 58
versionName = "0.57.0-real-artwork"
versionCode = 59
versionName = "0.58.0-comedy-dark-files"
}
buildFeatures { compose = true }
compileOptions { sourceCompatibility = JavaVersion.VERSION_17; targetCompatibility = JavaVersion.VERSION_17 }
@@ -8,6 +8,9 @@ internal object ChannelBrands {
102 to ChannelBranding("orbit-after-dark", "Orbit After Dark", R.drawable.orbit_after_dark_epg, R.drawable.orbit_after_dark_background, R.drawable.orbit_after_dark_bug, ChannelCategory.Movies),
103 to ChannelBranding("vortex", "Vortex", R.drawable.vortex_epg, R.drawable.vortex_background, R.drawable.vortex_bug, ChannelCategory.Entertainment),
111 to ChannelBranding("orbit-gold", "Orbit Gold", R.drawable.orbit_gold_epg, R.drawable.orbit_gold_background, R.drawable.orbit_gold_bug, ChannelCategory.Comedy),
112 to ChannelBranding("orbit-three", "Orbit Three", R.drawable.orbit_three_epg, R.drawable.orbit_three_background, R.drawable.orbit_three_bug, ChannelCategory.Comedy),
113 to ChannelBranding("orbit-riot", "Orbit Riot", R.drawable.orbit_riot_epg, R.drawable.orbit_riot_background, R.drawable.orbit_riot_bug, ChannelCategory.Comedy),
142 to ChannelBranding("orbit-dark-files", "Orbit Dark Files", R.drawable.orbit_dark_files_epg, R.drawable.orbit_dark_files_background, R.drawable.orbit_dark_files_bug, ChannelCategory.Documentaries),
121 to ChannelBranding("heroes", "Orbit Heroes", R.drawable.heroes_epg, R.drawable.heroes_background, R.drawable.heroes_bug, ChannelCategory.Cartoons),
122 to ChannelBranding("toonz", "Orbit ToonZ", R.drawable.toonz_epg, R.drawable.toonz_background, R.drawable.toonz_bug, ChannelCategory.Cartoons),
123 to ChannelBranding("rewind", "Orbit Rewind", R.drawable.rewind_epg, R.drawable.rewind_background, R.drawable.rewind_bug, ChannelCategory.Cartoons),
@@ -5,12 +5,20 @@ internal data class ChannelPreset(val number: Int, val name: String, val searche
/** Started from its smart schedule alone, with no separate loop to build first. */
val smartOnly: Boolean = false)
internal val DarkFilesCrime = listOf("Forensic Files", "Cold Case Files", "The First 48", "Making a Murderer", "The Staircase", "The Jinx", "I Am a Killer", "Evil Lives Here", "Conversations with a Killer: The Ted Bundy Tapes", "Crime Scene: The Vanishing at the Cecil Hotel")
internal val DarkFilesMysteries = listOf("Unsolved Mysteries", "Wild Wild Country", "Dark Tourist", "Ancient Aliens")
internal val DarkFilesParanormal = listOf("Paranormal Witness", "Most Haunted", "Ghost Adventures", "Ghost Hunters", "Hellier")
internal val ChannelPresets = listOf(
ChannelPreset(140, "Terra", listOf("Blue Planet", "Planet Earth", "Frozen Planet", "Life", "Our Planet")),
ChannelPreset(152, "Paradox", listOf("X-Files", "Buffy the Vampire Slayer", "Supernatural", "Fringe", "Doctor Who")),
ChannelPreset(111, "Orbit Gold", listOf("Only Fools and Horses", "Father Ted", "Blackadder", "The Black Adder", "Fawlty Towers", "Ghosts", "The Good Life", "The Green Green Grass",
"The Vicar of Dibley", "Porridge", "Taskmaster", "8 Out of 10 Cats", "Dad's Army", "Yes Minister", "Keeping Up Appearances", "Open All Hours", "Rising Damp", "The Likely Lads", "Steptoe and Son", "Hi-de-Hi!", "'Allo 'Allo!",
"Are You Being Served?", "Birds of a Feather", "Men Behaving Badly", "The IT Crowd", "Peep Show"), smartOnly = true),
ChannelPreset(112, "Orbit Three", listOf("Still Game", "Limmy's Show", "The Mighty Boosh", "Two Pints of Lager and a Packet of Crisps", "Gary: Tank Commander", "Family Guy", "Gavin & Stacey", "Little Britain",
"The Inbetweeners", "Bad Education", "Burnistoun", "Rab C. Nesbitt", "Chewin' the Fat", "Phoenix Nights", "Him & Her", "Spaced", "Black Books", "Fresh Meat", "Uncle", "Scot Squad"), smartOnly = true),
ChannelPreset(113, "Riot", listOf("South Park", "The Simpsons", "It's Always Sunny in Philadelphia", "Trailer Park Boys", "Friends", "How I Met Your Mother", "Futurama", "American Dad!", "Bob's Burgers",
"King of the Hill", "Rick and Morty", "Beavis and Butt-Head", "Archer", "BoJack Horseman", "Seinfeld", "Scrubs", "The Office", "Arrested Development", "Parks and Recreation", "Curb Your Enthusiasm"), smartOnly = true),
ChannelPreset(121, "Heroes", listOf("Batman: The Animated Series", "Superman: The Animated Series", "Spider-Man: The Animated Series", "X-Men: The Animated Series", "Batman Beyond",
"Spider-Man Unlimited", "Justice League", "Justice League Unlimited", "Teen Titans", "Static Shock", "The Spectacular Spider-Man", "Young Justice",
"Batman: The Brave and the Bold", "Iron Man: Armored Adventures", "The Avengers: Earth's Mightiest Heroes", "The Batman", "Fantastic Four", "Silver Surfer",
@@ -26,7 +34,8 @@ internal val ChannelPresets = listOf(
"Exosquad", "The Real Ghostbusters", "Inspector Gadget", "She-Ra: Princess of Power", "Bucky O'Hare and the Toad Wars", "Visionaries: Knights of the Magical Light"), smartOnly = true),
ChannelPreset(141, "Evidence", listOf("CSI: Crime Scene Investigation", "NCIS", "CSI: Miami", "CSI: NY", "Law & Order", "Law & Order: Special Victims Unit", "Criminal Minds", "Bones",
"Castle", "The Mentalist", "Monk", "Columbo", "Midsomer Murders", "Vera", "Luther", "Line of Duty", "Taggart", "Silent Witness", "Waking the Dead", "A Touch of Frost", "Dexter", "Sherlock", "Broadchurch", "Death in Paradise", "Lewis", "Inspector Morse",
"Dalziel and Pascoe", "Happy Valley", "The Wire", "Hawaii Five-0", "Blue Bloods"), smartOnly = true))
"Dalziel and Pascoe", "Happy Valley", "The Wire", "Hawaii Five-0", "Blue Bloods"), smartOnly = true),
ChannelPreset(142, "Dark Files", DarkFilesCrime + DarkFilesMysteries + DarkFilesParanormal, smartOnly = true))
/** A fallback for a channel with no old loop: its first episodes, in turn. Used only outside its plan. */
internal fun loopFromPool(pool: List<PoolSeries>, programming: ChannelProgramming): SampleChannel? {
@@ -58,14 +58,20 @@ internal val SmartChannels = listOf(
seriesCount = 20, finishTogether = true, exactTitles = true),
ChannelProgramming(152, "Paradox", ChannelPresets.first { it.number == 152 }.searches)) +
ChannelPresets.filter { it.smartOnly }.map { ChannelProgramming(it.number, it.name, it.searches, seriesCount = 40, finishTogether = true, exactTitles = true,
suggestionRules = SmartChannelRules[it.number].orEmpty()) }
suggestionRules = SmartChannelRules[it.number].orEmpty(), dayParts = SmartChannelParts[it.number].orEmpty()) }
/** Optional bigger pools, only used once reviewed. No rule for Orbit Gold: nothing in the library's details can tell a British sitcom apart. */
/** Dark Files, from the programming bible: crime by day, mysteries early evening, the paranormal late. */
private val SmartChannelParts: Map<Int, List<DayPart>> get() = mapOf(142 to listOf(
DayPart(6, 18, DarkFilesCrime, group = "day"), DayPart(18, 21, DarkFilesMysteries, group = "evening"), DayPart(21, 6, DarkFilesParanormal, group = "late")))
/** Optional bigger pools, only used once reviewed. No rule for Orbit Gold or Orbit Three: nothing in the library's details can tell a British sitcom apart. */
private val SmartChannelRules: Map<Int, List<SuggestionRule>> get() = mapOf(
121 to listOf(SuggestionRule("all", "Superhero animation", "Series", listOf("Animation", "Action"), 1990..2020, 7f, setOf("TV-MA"), 30, setOf("Anime"), titleContains = null)),
122 to listOf(SuggestionRule("all", "Classic cartoons", "Series", listOf("Animation", "Comedy"), 1940..1990, 6.5f, setOf("TV-MA"), 40)),
123 to listOf(SuggestionRule("all", "After-school cartoons", "Series", listOf("Animation", "Comedy"), 1990..2010, 7f, setOf("TV-MA"), 40, setOf("Anime"))),
124 to listOf(SuggestionRule("all", "Action cartoons", "Series", listOf("Animation", "Action"), 1980..2000, 6.5f, setOf("TV-MA"), 40, setOf("Anime"))),
142 to listOf(SuggestionRule("day", "Crime documentary", "Series", listOf("Documentary", "Crime"), 1990..2024, 7f, emptySet(), 30, setOf("Animation"))),
113 to listOf(SuggestionRule("all", "Adult animation", "Series", listOf("Animation", "Comedy"), 1990..2024, 7.5f, emptySet(), 30, setOf("Anime", "Family", "Children"))),
141 to listOf(SuggestionRule("all", "Crime drama", "Series", listOf("Crime", "Drama"), 1985..2022, 7f, emptySet(), 40, setOf("Documentary", "Animation"))))
internal data class EpisodeMark(val season: Int, val episode: Int) : Comparable<EpisodeMark> {
@@ -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.57.0\"")
connection.setRequestProperty("X-Emby-Authorization", "Emby Client=\"Orbit TV\", Device=\"Orbit TV\", DeviceId=\"$deviceId\", Version=\"0.58.0\"")
request.token?.let { connection.setRequestProperty("X-Emby-Token", it) }
if (request.method == "POST") {
val bytes = request.body.orEmpty().toByteArray(Charsets.UTF_8)
@@ -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.57.0")
next.setRequestProperty("User-Agent", "OrbitTV/0.58.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()) {
Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

@@ -106,7 +106,7 @@ class ChannelPresetsTest {
}
@Test fun theNewChannelsAreConfiguredAndBranded() {
listOf(111, 121, 122, 123, 124, 141).forEach { number ->
listOf(111, 112, 113, 121, 122, 123, 124, 141, 142).forEach { number ->
assertTrue(SmartChannels.any { it.number == number && it.exactTitles && it.finishTogether })
assertNotNull(ChannelBrands.forNumber(number))
assertTrue(ChannelPresets.first { it.number == number }.smartOnly)
@@ -117,4 +117,15 @@ class ChannelPresetsTest {
@Test fun smartChannelNumbersAreUnique() {
assertEquals(SmartChannels.size, SmartChannels.map { it.number }.distinct().size)
}
@Test fun darkFilesFollowsTheBiblesDay() {
val parts = SmartChannels.first { it.number == 142 }.dayParts
val london = java.time.ZoneId.of("Europe/London")
fun show(title: String) = PoolSeries(title, title, listOf(PoolEpisode("$title-1", "s", EpisodeMark(1, 1), "E", 60_000)))
val pool = listOf(show("Forensic Files"), show("Unsolved Mysteries"), show("Most Haunted"))
fun at(h: Int) = java.time.ZonedDateTime.of(2026, 11, 2, h, 0, 0, 0, london).toInstant().toEpochMilli()
assertEquals(listOf("Forensic Files"), dayPartPool(pool, parts, at(12), london).map { it.title })
assertEquals(listOf("Unsolved Mysteries"), dayPartPool(pool, parts, at(19), london).map { it.title })
assertEquals(listOf("Most Haunted"), dayPartPool(pool, parts, at(23), london).map { it.title })
}
}
+1 -1
View File
@@ -71,7 +71,7 @@ Audit and phased plan are in review, from `docs/briefs/indexing-programming-engi
## Smart channels status
Phase A of [the Smart Channels Foundation brief](briefs/smart-channels-foundation-2026-11-01.md) passed Paul's review on 2 November 2026, with Paradox on the box (VERIFICATION-0.39.0.md). Phase B (Terra and After Dark, VERIFICATION-0.40.0.md) and Phase C (Vortex, plus fewer repeats on Terra, VERIFICATION-0.41.0.md) are on the box as 0.41, tested and awaiting Paul's review. Day parts are on the box on Vortex and After Dark (VERIFICATION-0.42.0.md). Phase D (the remaining channels) needs separate approval. Reviewed suggestions are built and in use on After Dark and Vortex (VERIFICATION-0.47.0.md), the first piece of the programming director. Vortex Movie Night, the first weekday event, is on the box. Phase D has started with Orbit Gold (111), Heroes (121) and Evidence (141), on the box as 0.49, then ToonZ (122), Rewind (123) and Orbit X (124), all on the box as 0.51 with ten smart channels running (VERIFICATION-0.51.0.md). Numbers are placeholders until Paul maps the lineup. A show may run on several channels but never at the same moment. Animated idents are design files only and aren't in the app yet.
Phase A of [the Smart Channels Foundation brief](briefs/smart-channels-foundation-2026-11-01.md) passed Paul's review on 2 November 2026, with Paradox on the box (VERIFICATION-0.39.0.md). Phase B (Terra and After Dark, VERIFICATION-0.40.0.md) and Phase C (Vortex, plus fewer repeats on Terra, VERIFICATION-0.41.0.md) are on the box as 0.41, tested and awaiting Paul's review. Day parts are on the box on Vortex and After Dark (VERIFICATION-0.42.0.md). Phase D (the remaining channels) needs separate approval. Reviewed suggestions are built and in use on After Dark and Vortex (VERIFICATION-0.47.0.md), the first piece of the programming director. Vortex Movie Night, the first weekday event, is on the box. Phase D has started with Orbit Gold (111), Heroes (121) and Evidence (141), on the box as 0.49, then ToonZ (122), Rewind (123) and Orbit X (124), all on the box as 0.51 with ten smart channels running (VERIFICATION-0.51.0.md). Orbit Three (112), Riot (113) and Dark Files (142, with day parts from the bible) are built as 0.58. Numbers are placeholders until Paul maps the lineup. A show may run on several channels but never at the same moment. Animated idents are design files only and aren't in the app yet.
## Programming director