Add Orbit Three, Riot and Dark Files (0.58.0 in testing)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@@ -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()) {
|
||||
|
||||
|
After Width: | Height: | Size: 47 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 5.9 KiB |
|
After Width: | Height: | Size: 100 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 5.4 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
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 })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||