From e0418770f4c6f869c27ad5cde2b95845d4dc1fd0 Mon Sep 17 00:00:00 2001 From: Pauljd Date: Mon, 2 Nov 2026 12:26:39 +0000 Subject: [PATCH] Number channels in Sky-style blocks by category (0.69.0) Co-Authored-By: Claude Opus 5.5 (1M context) --- VERIFICATION-0.69.0.md | 13 ++++ android/app/build.gradle.kts | 4 +- .../java/tv/orbit/classic/ChannelBranding.kt | 50 ++++++------ .../java/tv/orbit/classic/ChannelPresets.kt | 54 ++++++------- .../java/tv/orbit/classic/ChannelSchedule.kt | 34 ++++---- .../src/main/java/tv/orbit/classic/DriveIn.kt | 8 +- .../main/java/tv/orbit/classic/EmbyClient.kt | 2 +- .../java/tv/orbit/classic/ScheduleStore.kt | 6 +- .../tv/orbit/classic/SecureMediaDataSource.kt | 2 +- .../tv/orbit/classic/AfterDarkMoviesTest.kt | 24 +++--- .../tv/orbit/classic/ChannelPresetsTest.kt | 60 ++++++++------ .../tv/orbit/classic/ChannelScheduleTest.kt | 78 +++++++++---------- .../java/tv/orbit/classic/GuideGridTest.kt | 14 ++-- .../tv/orbit/classic/RememberedStateTest.kt | 8 +- .../tv/orbit/classic/ToonamiChannelTest.kt | 4 +- docs/roadmap.md | 2 +- 16 files changed, 194 insertions(+), 169 deletions(-) create mode 100644 VERIFICATION-0.69.0.md diff --git a/VERIFICATION-0.69.0.md b/VERIFICATION-0.69.0.md new file mode 100644 index 0000000..2ddd28a --- /dev/null +++ b/VERIFICATION-0.69.0.md @@ -0,0 +1,13 @@ +# Orbit TV 0.69.0: channels in blocks + +Numbers now run in blocks like satellite TV, biggest channels first. Includes everything in 0.68, which was never installed. + +- 100s entertainment and comedy: 101 Orbit 1, 102 Orbit 2, 103 Orbit Three, 104 Gold, 105 Riot, 106 Paradox, 107 Evidence +- 200s factual: 201 Terra, 202 Odyssey, 203 Wild, 204 Dark Files +- 300s films: 300 Drive-In, 301 Premiere, 302 Action, 303 Sci-Fi, 304 Horror, 305 After Dark, 306 VHS, 307 Romance, 308 Detour, 309 BritX +- 400s sport: 401 Ring X +- 600s cartoons and kids: 601 Vortex, 602 Heroes, 603 Orbit X, 604 Rewind, 605 ToonZ, 611 Junior, 612 Wonder, 613 PixelZ + +Vortex moves from the Entertainment category to Cartoons. Files move once at first launch, keeping every schedule, place and choice. The catalogue isn't touched. + +Checked: build, lint and all unit tests pass, including the block rule and the move. Not checked on the box until installed: the move on real files, and the board text on the TV. diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 5da1dc5..2673616 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 = 69 - versionName = "0.68.0-orbit-numbers" + versionCode = 70 + versionName = "0.69.0-channel-blocks" } buildFeatures { compose = true } compileOptions { sourceCompatibility = JavaVersion.VERSION_17; targetCompatibility = JavaVersion.VERSION_17 } diff --git a/android/app/src/main/java/tv/orbit/classic/ChannelBranding.kt b/android/app/src/main/java/tv/orbit/classic/ChannelBranding.kt index f693709..b0fe97f 100644 --- a/android/app/src/main/java/tv/orbit/classic/ChannelBranding.kt +++ b/android/app/src/main/java/tv/orbit/classic/ChannelBranding.kt @@ -6,34 +6,34 @@ internal object ChannelBrands { private val brands = mapOf( 101 to ChannelBranding("orbit-1", "Orbit 1", R.drawable.orbit_one_epg, R.drawable.orbit_one_background, R.drawable.orbit_one_bug, ChannelCategory.Entertainment), AFTER_DARK_NUMBER 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), - VORTEX_NUMBER 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), + VORTEX_NUMBER to ChannelBranding("vortex", "Vortex", R.drawable.vortex_epg, R.drawable.vortex_background, R.drawable.vortex_bug, ChannelCategory.Cartoons), + 104 to ChannelBranding("orbit-gold", "Orbit Gold", R.drawable.orbit_gold_epg, R.drawable.orbit_gold_background, R.drawable.orbit_gold_bug, ChannelCategory.Comedy), 103 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), - 114 to ChannelBranding("orbit-detour", "Orbit Detour", R.drawable.orbit_detour_epg, R.drawable.orbit_one_background, R.drawable.orbit_detour_bug, ChannelCategory.Movies), - 115 to ChannelBranding("orbit-britx", "Orbit BritX", R.drawable.orbit_britx_epg, R.drawable.orbit_one_background, R.drawable.orbit_britx_bug, ChannelCategory.Movies), - 125 to ChannelBranding("orbit-junior", "Orbit Junior", R.drawable.orbit_junior_epg, R.drawable.orbit_one_background, R.drawable.orbit_junior_bug, ChannelCategory.Kids), - 143 to ChannelBranding("orbit-wild", "Orbit Wild", R.drawable.orbit_wild_epg, R.drawable.orbit_one_background, R.drawable.orbit_wild_bug, ChannelCategory.Documentaries), + 105 to ChannelBranding("orbit-riot", "Orbit Riot", R.drawable.orbit_riot_epg, R.drawable.orbit_riot_background, R.drawable.orbit_riot_bug, ChannelCategory.Comedy), + 204 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), + 308 to ChannelBranding("orbit-detour", "Orbit Detour", R.drawable.orbit_detour_epg, R.drawable.orbit_one_background, R.drawable.orbit_detour_bug, ChannelCategory.Movies), + 309 to ChannelBranding("orbit-britx", "Orbit BritX", R.drawable.orbit_britx_epg, R.drawable.orbit_one_background, R.drawable.orbit_britx_bug, ChannelCategory.Movies), + 611 to ChannelBranding("orbit-junior", "Orbit Junior", R.drawable.orbit_junior_epg, R.drawable.orbit_one_background, R.drawable.orbit_junior_bug, ChannelCategory.Kids), + 203 to ChannelBranding("orbit-wild", "Orbit Wild", R.drawable.orbit_wild_epg, R.drawable.orbit_one_background, R.drawable.orbit_wild_bug, ChannelCategory.Documentaries), 102 to ChannelBranding("orbit-2", "Orbit 2", R.drawable.orbit_2_epg, R.drawable.orbit_one_background, R.drawable.orbit_2_bug, ChannelCategory.Entertainment), - 126 to ChannelBranding("wonder", "Orbit Wonder", R.drawable.wonder_epg, R.drawable.orbit_one_background, R.drawable.wonder_bug, ChannelCategory.Kids), - 127 to ChannelBranding("orbit-pixelz", "Orbit PixelZ", R.drawable.orbit_pixelz_epg, R.drawable.orbit_one_background, R.drawable.orbit_pixelz_bug, ChannelCategory.Kids), - 131 to ChannelBranding("orbit-vhs", "Orbit VHS", R.drawable.orbit_vhs_epg, R.drawable.orbit_one_background, R.drawable.orbit_vhs_bug, ChannelCategory.Movies), - 132 to ChannelBranding("orbit-sci-fi", "Orbit Sci-Fi", R.drawable.orbit_sci_fi_epg, R.drawable.orbit_one_background, R.drawable.orbit_sci_fi_bug, ChannelCategory.Movies), - 133 to ChannelBranding("orbit-horror", "Orbit Horror", R.drawable.orbit_horror_epg, R.drawable.orbit_one_background, R.drawable.orbit_horror_bug, ChannelCategory.Movies), - 134 to ChannelBranding("orbit-action", "Orbit Action", R.drawable.orbit_action_epg, R.drawable.orbit_one_background, R.drawable.orbit_action_bug, ChannelCategory.Movies), - 135 to ChannelBranding("orbit-romance", "Orbit Romance", R.drawable.orbit_romance_epg, R.drawable.orbit_one_background, R.drawable.orbit_romance_bug, ChannelCategory.Movies), - 136 to ChannelBranding("orbit-premiere", "Orbit Premiere", R.drawable.orbit_premiere_epg, R.drawable.orbit_one_background, R.drawable.orbit_premiere_bug, ChannelCategory.Movies), - 144 to ChannelBranding("orbit-odyssey", "Orbit Odyssey", R.drawable.orbit_odyssey_epg, R.drawable.orbit_one_background, R.drawable.orbit_odyssey_bug, ChannelCategory.Documentaries), - 150 to ChannelBranding("orbit-ring-x", "Orbit Ring X", R.drawable.orbit_ring_x_epg, R.drawable.orbit_one_background, R.drawable.orbit_ring_x_bug, ChannelCategory.Sport), + 612 to ChannelBranding("wonder", "Orbit Wonder", R.drawable.wonder_epg, R.drawable.orbit_one_background, R.drawable.wonder_bug, ChannelCategory.Kids), + 613 to ChannelBranding("orbit-pixelz", "Orbit PixelZ", R.drawable.orbit_pixelz_epg, R.drawable.orbit_one_background, R.drawable.orbit_pixelz_bug, ChannelCategory.Kids), + 306 to ChannelBranding("orbit-vhs", "Orbit VHS", R.drawable.orbit_vhs_epg, R.drawable.orbit_one_background, R.drawable.orbit_vhs_bug, ChannelCategory.Movies), + 303 to ChannelBranding("orbit-sci-fi", "Orbit Sci-Fi", R.drawable.orbit_sci_fi_epg, R.drawable.orbit_one_background, R.drawable.orbit_sci_fi_bug, ChannelCategory.Movies), + 304 to ChannelBranding("orbit-horror", "Orbit Horror", R.drawable.orbit_horror_epg, R.drawable.orbit_one_background, R.drawable.orbit_horror_bug, ChannelCategory.Movies), + 302 to ChannelBranding("orbit-action", "Orbit Action", R.drawable.orbit_action_epg, R.drawable.orbit_one_background, R.drawable.orbit_action_bug, ChannelCategory.Movies), + 307 to ChannelBranding("orbit-romance", "Orbit Romance", R.drawable.orbit_romance_epg, R.drawable.orbit_one_background, R.drawable.orbit_romance_bug, ChannelCategory.Movies), + 301 to ChannelBranding("orbit-premiere", "Orbit Premiere", R.drawable.orbit_premiere_epg, R.drawable.orbit_one_background, R.drawable.orbit_premiere_bug, ChannelCategory.Movies), + 202 to ChannelBranding("orbit-odyssey", "Orbit Odyssey", R.drawable.orbit_odyssey_epg, R.drawable.orbit_one_background, R.drawable.orbit_odyssey_bug, ChannelCategory.Documentaries), + 401 to ChannelBranding("orbit-ring-x", "Orbit Ring X", R.drawable.orbit_ring_x_epg, R.drawable.orbit_one_background, R.drawable.orbit_ring_x_bug, ChannelCategory.Sport), DRIVE_IN_NUMBER to ChannelBranding("orbit-drive-in", "Orbit Drive-In", R.drawable.orbit_drive_in_epg, R.drawable.orbit_one_background, R.drawable.orbit_drive_in_bug, ChannelCategory.Movies), - 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), - 124 to ChannelBranding("orbit-x", "Orbit X", R.drawable.orbit_x_epg, R.drawable.orbit_x_background, R.drawable.orbit_x_bug, ChannelCategory.Cartoons), - 141 to ChannelBranding("evidence", "Orbit Evidence", R.drawable.evidence_epg, R.drawable.evidence_background, R.drawable.evidence_bug, ChannelCategory.Entertainment), - 140 to ChannelBranding("orbit-terra", "Orbit Terra", R.drawable.orbit_terra_epg, R.drawable.orbit_terra_background, R.drawable.orbit_terra_bug, ChannelCategory.Documentaries), - 152 to ChannelBranding("paradox", "Orbit Paradox", R.drawable.paradox_epg, R.drawable.paradox_background, R.drawable.paradox_bug, ChannelCategory.Entertainment)) + 602 to ChannelBranding("heroes", "Orbit Heroes", R.drawable.heroes_epg, R.drawable.heroes_background, R.drawable.heroes_bug, ChannelCategory.Cartoons), + 605 to ChannelBranding("toonz", "Orbit ToonZ", R.drawable.toonz_epg, R.drawable.toonz_background, R.drawable.toonz_bug, ChannelCategory.Cartoons), + 604 to ChannelBranding("rewind", "Orbit Rewind", R.drawable.rewind_epg, R.drawable.rewind_background, R.drawable.rewind_bug, ChannelCategory.Cartoons), + 603 to ChannelBranding("orbit-x", "Orbit X", R.drawable.orbit_x_epg, R.drawable.orbit_x_background, R.drawable.orbit_x_bug, ChannelCategory.Cartoons), + 107 to ChannelBranding("evidence", "Orbit Evidence", R.drawable.evidence_epg, R.drawable.evidence_background, R.drawable.evidence_bug, ChannelCategory.Entertainment), + 201 to ChannelBranding("orbit-terra", "Orbit Terra", R.drawable.orbit_terra_epg, R.drawable.orbit_terra_background, R.drawable.orbit_terra_bug, ChannelCategory.Documentaries), + 106 to ChannelBranding("paradox", "Orbit Paradox", R.drawable.paradox_epg, R.drawable.paradox_background, R.drawable.paradox_bug, ChannelCategory.Entertainment)) fun forNumber(number: Int): ChannelBranding? = brands[number] /** Saved channels keep their original names; what the viewer sees comes from the brand. */ fun displayName(channel: SampleChannel) = brands[channel.number]?.displayName ?: channel.name diff --git a/android/app/src/main/java/tv/orbit/classic/ChannelPresets.kt b/android/app/src/main/java/tv/orbit/classic/ChannelPresets.kt index 6ae59aa..d22abca 100644 --- a/android/app/src/main/java/tv/orbit/classic/ChannelPresets.kt +++ b/android/app/src/main/java/tv/orbit/classic/ChannelPresets.kt @@ -20,65 +20,65 @@ internal val RingXImpact = listOf("TNA iMPACT!", "TNA Impact!", "TNA Impact Wres internal val RingXAew = listOf("AEW Dynamite", "All Elite Wrestling: Dynamite", "AEW Rampage", "All Elite Wrestling: Rampage", "AEW Collision", "AEW Dark") internal val RingXVault = listOf("WWE SmackDown", "WWF SmackDown", "WWE Friday Night SmackDown", "ECW Hardcore TV", "ECW on TNN", "WWE ECW", "WCW Thunder", "WCW Saturday Night", "WWE NXT", "WWF Superstars", "WWF Attitude", "WrestleMania", "WWE WrestleMania", "WWE Pay-Per-View", "WWF Pay-Per-View", "WCW Pay-Per-View", "ECW Pay-Per-View", "The Monday Night War", "Dark Side of the Ring", "WWE Legends' House") -/** Orbit 1, 2 and Three hold 101 to 103, as Paul asked. Vortex and After Dark moved to the head of their blocks. */ -internal const val VORTEX_NUMBER = 120 -internal const val AFTER_DARK_NUMBER = 130 +/** Numbered in blocks, like satellite TV, with the biggest channels first: 100s entertainment, 200s factual, 300s films, 400s sport, 600s cartoons and kids. */ +internal const val VORTEX_NUMBER = 601 +internal const val AFTER_DARK_NUMBER = 305 internal val ChannelPresets = listOf( ChannelPreset(101, "Orbit 1", listOf("Frasier", "Cheers", "The Fresh Prince of Bel-Air", "Home Improvement", "Malcolm in the Middle", "Everybody Loves Raymond", "The Big Bang Theory", "Modern Family", "My Wife and Kids", "Sister, Sister", "Sabrina the Teenage Witch", "Saved by the Bell", "Boy Meets World", "The Wonder Years", "Full House", "Family Matters", "The Golden Girls", "Happy Days", "Last of the Summer Wine", "My Family", "Outnumbered", "Not Going Out", "Miranda", "Mrs. Brown's Boys", "Goodnight Sweetheart", "Bread", "Red Dwarf", "Young Sheldon", "The King of Queens", "Two and a Half Men"), smartOnly = true), - 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", + ChannelPreset(201, "Terra", listOf("Blue Planet", "Planet Earth", "Frozen Planet", "Life", "Our Planet")), + ChannelPreset(106, "Paradox", listOf("X-Files", "Buffy the Vampire Slayer", "Supernatural", "Fringe", "Doctor Who")), + ChannelPreset(104, "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(103, "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", + ChannelPreset(105, "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", + ChannelPreset(602, "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", "Spider-Man and His Amazing Friends", "Ben 10"), smartOnly = true), - ChannelPreset(122, "ToonZ", listOf("Looney Tunes", "The Looney Tunes Show", "Merrie Melodies", "The Bugs Bunny Show", "Tom and Jerry", "The Tom and Jerry Show", "The Flintstones", "The Jetsons", + ChannelPreset(605, "ToonZ", listOf("Looney Tunes", "The Looney Tunes Show", "Merrie Melodies", "The Bugs Bunny Show", "Tom and Jerry", "The Tom and Jerry Show", "The Flintstones", "The Jetsons", "Popeye the Sailor", "Popeye", "Top Cat", "The Yogi Bear Show", "Wacky Races", "Scooby-Doo, Where Are You!", "The Pink Panther Show", "Woody Woodpecker", "Dastardly and Muttley in Their Flying Machines", "The Huckleberry Hound Show", "Hong Kong Phooey", "Danger Mouse", "Count Duckula", "Bananaman"), smartOnly = true), - ChannelPreset(123, "Rewind", listOf("Dexter's Laboratory", "Johnny Bravo", "What's New, Scooby-Doo?", "Courage the Cowardly Dog", "Ed, Edd n Eddy", "Ed Edd n Eddy", "Recess", "The Powerpuff Girls", "Cow and Chicken", + ChannelPreset(604, "Rewind", listOf("Dexter's Laboratory", "Johnny Bravo", "What's New, Scooby-Doo?", "Courage the Cowardly Dog", "Ed, Edd n Eddy", "Ed Edd n Eddy", "Recess", "The Powerpuff Girls", "Cow and Chicken", "I Am Weasel", "The Grim Adventures of Billy & Mandy", "Codename: Kids Next Door", "Foster's Home for Imaginary Friends", "Hey Arnold!", "Rugrats", "Doug", "CatDog", "Rocko's Modern Life", "Animaniacs", "Pinky and the Brain", "The Wild Thornberrys", "SpongeBob SquarePants"), smartOnly = true), - ChannelPreset(124, "Orbit X", listOf("Digimon: Digital Monsters", "Digimon Adventure", "Gargoyles", "The Transformers", "He-Man and the Masters of the Universe", "ThunderCats", "Biker Mice from Mars", + ChannelPreset(603, "Orbit X", listOf("Digimon: Digital Monsters", "Digimon Adventure", "Gargoyles", "The Transformers", "He-Man and the Masters of the Universe", "ThunderCats", "Biker Mice from Mars", "Mighty Morphin Power Rangers", "G.I. Joe: A Real American Hero", "Teenage Mutant Ninja Turtles", "Street Sharks", "SWAT Kats: The Radical Squadron", "Captain Planet and the Planeteers", "M.A.S.K.", "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", + ChannelPreset(107, "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), - ChannelPreset(142, "Dark Files", DarkFilesCrime + DarkFilesMysteries + DarkFilesParanormal, smartOnly = true), - ChannelPreset(114, "Detour", listOf("American Pie", "American Pie 2", "American Wedding", "American Reunion", "Road Trip", "EuroTrip", "Harold & Kumar Go to White Castle", "Harold & Kumar Escape from Guantanamo Bay", + ChannelPreset(204, "Dark Files", DarkFilesCrime + DarkFilesMysteries + DarkFilesParanormal, smartOnly = true), + ChannelPreset(308, "Detour", listOf("American Pie", "American Pie 2", "American Wedding", "American Reunion", "Road Trip", "EuroTrip", "Harold & Kumar Go to White Castle", "Harold & Kumar Escape from Guantanamo Bay", "A Very Harold & Kumar Christmas", "The Hangover", "The Hangover Part II", "The Hangover Part III", "Van Wilder", "National Lampoon's Van Wilder", "Van Wilder: The Rise of Taj", "Van Wilder 2: The Rise of Taj", "Old School", "Sex Drive", "Accepted", "Superbad", "Pineapple Express", "Dude, Where's My Car?", "Without a Paddle", "The Girl Next Door", "Not Another Teen Movie", "Project X", "21 & Over", "Beerfest", "Grandma's Boy", "Waiting..."), smartOnly = true, films = true), - ChannelPreset(115, "BritX", listOf("Shaun of the Dead", "Hot Fuzz", "The World's End", "Lock, Stock and Two Smoking Barrels", "Snatch", "RocknRolla", "Layer Cake", "Trainspotting", "T2 Trainspotting", "Green Street", "Green Street Hooligans", + ChannelPreset(309, "BritX", listOf("Shaun of the Dead", "Hot Fuzz", "The World's End", "Lock, Stock and Two Smoking Barrels", "Snatch", "RocknRolla", "Layer Cake", "Trainspotting", "T2 Trainspotting", "Green Street", "Green Street Hooligans", "The Football Factory", "Dog Soldiers", "Dead Man's Shoes", "28 Days Later", "28 Weeks Later", "This Is England", "The Gentlemen", "The Business", "The Firm", "Rise of the Footsoldier", "The 51st State", "Sexy Beast", "The Bank Job", "The Long Good Friday", "Human Traffic", "Twin Town", "Shallow Grave", "Four Lions", "Severance", "Attack the Block", "The Descent", "In Bruges", "Filth", "Calibre", "Sightseers", "Sweet Sixteen"), smartOnly = true, films = true), - ChannelPreset(125, "Orbit Junior", listOf("Thomas the Tank Engine & Friends", "Thomas & Friends", "Fireman Sam", "Postman Pat", "Brum", "Rosie and Jim", "Bob the Builder", "Pingu", "Noddy", "Teletubbies", "Tweenies", "Tots TV", + ChannelPreset(611, "Orbit Junior", listOf("Thomas the Tank Engine & Friends", "Thomas & Friends", "Fireman Sam", "Postman Pat", "Brum", "Rosie and Jim", "Bob the Builder", "Pingu", "Noddy", "Teletubbies", "Tweenies", "Tots TV", "Pingu in the City", "Peppa Pig", "Bluey", "Bagpuss", "The Clangers", "Camberwick Green", "Trumpton", "Button Moon", "Rainbow", "Balamory", "Fimbles"), smartOnly = true), - ChannelPreset(143, "Orbit Wild", listOf("The Crocodile Hunter", "Crocodile Hunter Diaries", "Deadliest Catch", "River Monsters", "Man vs. Wild", "Running Wild with Bear Grylls", "Survivorman", "Dual Survival", "Naked and Afraid", "Ice Road Truckers", + ChannelPreset(203, "Orbit Wild", listOf("The Crocodile Hunter", "Crocodile Hunter Diaries", "Deadliest Catch", "River Monsters", "Man vs. Wild", "Running Wild with Bear Grylls", "Survivorman", "Dual Survival", "Naked and Afraid", "Ice Road Truckers", "Swamp People", "Wild Things with Dominic Monaghan", "Lone Star Law", "Alone", "Ax Men", "Monster Fish", "Dirty Jobs", "Shark Week", "Whale Wars", "Dangerous Encounters with Brady Barr"), smartOnly = true), ChannelPreset(102, "Orbit 2", listOf("Twin Peaks", "Quantum Leap", "The Twilight Zone", "The Outer Limits", "Tales from the Crypt", "Eerie, Indiana", "Are You Afraid of the Dark?", "Goosebumps", "Sliders", "Knight Rider", "The A-Team", "Airwolf", "MacGyver", "Red Dwarf", "The Crystal Maze", "Robot Wars", "Gladiators", "Takeshi's Castle", "Gadget Show", "Top Gear", "Time Team", "Scrapheap Challenge", "Countdown", "Have I Got News for You", "QI", "Would I Lie to You?", "Pointless", "Mastermind", "University Challenge", "Mythbusters", "How It's Made"), smartOnly = true), - ChannelPreset(126, "Wonder", listOf("Snow White and the Seven Dwarfs", "Pinocchio", "Dumbo", "Bambi", "Cinderella", "Alice in Wonderland", "Peter Pan", "Lady and the Tramp", "Sleeping Beauty", "One Hundred and One Dalmatians", "The Jungle Book", "The Aristocats", "Robin Hood", + ChannelPreset(612, "Wonder", listOf("Snow White and the Seven Dwarfs", "Pinocchio", "Dumbo", "Bambi", "Cinderella", "Alice in Wonderland", "Peter Pan", "Lady and the Tramp", "Sleeping Beauty", "One Hundred and One Dalmatians", "The Jungle Book", "The Aristocats", "Robin Hood", "The Fox and the Hound", "The Little Mermaid", "Beauty and the Beast", "Aladdin", "The Lion King", "Pocahontas", "The Hunchback of Notre Dame", "Hercules", "Mulan", "Tarzan", "The Emperor's New Groove", "Atlantis: The Lost Empire", "Lilo & Stitch", "Treasure Planet", "Brother Bear", "The Princess and the Frog", "An American Tail", "The Land Before Time", "All Dogs Go to Heaven", "Anastasia", "The Secret of NIMH", "Thumbelina", "The Iron Giant", "The Prince of Egypt", "Spirited Away", "My Neighbor Totoro", "Kiki's Delivery Service", "Howl's Moving Castle", "Ponyo", "Princess Mononoke", "Castle in the Sky"), smartOnly = true, films = true), - ChannelPreset(127, "PixelZ", listOf("Toy Story", "Toy Story 2", "Toy Story 3", "Toy Story 4", "A Bug's Life", "Monsters, Inc.", "Monsters University", "Finding Nemo", "Finding Dory", "The Incredibles", "Incredibles 2", "Cars", "Cars 2", "Cars 3", "Ratatouille", "WALL·E", "Up", "Brave", "Inside Out", "Coco", "Luca", "Soul", + ChannelPreset(613, "PixelZ", listOf("Toy Story", "Toy Story 2", "Toy Story 3", "Toy Story 4", "A Bug's Life", "Monsters, Inc.", "Monsters University", "Finding Nemo", "Finding Dory", "The Incredibles", "Incredibles 2", "Cars", "Cars 2", "Cars 3", "Ratatouille", "WALL·E", "Up", "Brave", "Inside Out", "Coco", "Luca", "Soul", "Shrek", "Shrek 2", "Shrek the Third", "Shrek Forever After", "Puss in Boots", "Madagascar", "Madagascar: Escape 2 Africa", "Kung Fu Panda", "Kung Fu Panda 2", "How to Train Your Dragon", "How to Train Your Dragon 2", "Megamind", "Antz", "Ice Age", "Ice Age: The Meltdown", "Despicable Me", "Despicable Me 2", "Minions", "The Lego Movie", "Tangled", "Frozen", "Moana", "Zootopia", "Big Hero 6", "Wreck-It Ralph", "Rio", "Robots"), smartOnly = true, films = true), - ChannelPreset(131, "Orbit VHS", listOf("Back to the Future", "Back to the Future Part II", "Back to the Future Part III", "The Goonies", "Gremlins", "Gremlins 2: The New Batch", "Ghostbusters", "Ghostbusters II", "E.T. the Extra-Terrestrial", "Honey, I Shrunk the Kids", "Flight of the Navigator", "The NeverEnding Story", "Labyrinth", "Willow", "The Princess Bride", "Short Circuit", "Batteries Not Included", "Explorers", "Stand by Me", "The Karate Kid", "The Karate Kid Part II", + ChannelPreset(306, "Orbit VHS", listOf("Back to the Future", "Back to the Future Part II", "Back to the Future Part III", "The Goonies", "Gremlins", "Gremlins 2: The New Batch", "Ghostbusters", "Ghostbusters II", "E.T. the Extra-Terrestrial", "Honey, I Shrunk the Kids", "Flight of the Navigator", "The NeverEnding Story", "Labyrinth", "Willow", "The Princess Bride", "Short Circuit", "Batteries Not Included", "Explorers", "Stand by Me", "The Karate Kid", "The Karate Kid Part II", "Ferris Bueller's Day Off", "The Breakfast Club", "Weird Science", "Teen Wolf", "Bill & Ted's Excellent Adventure", "Bill & Ted's Bogus Journey", "Wayne's World", "Big", "Home Alone", "Home Alone 2: Lost in New York", "Beetlejuice", "Hook", "Jumanji", "Mrs. Doubtfire", "Mac and Me", "Police Academy", "Top Gun", "Crocodile Dundee", "Who Framed Roger Rabbit", "Howard the Duck", "Masters of the Universe", "Innerspace", "Big Trouble in Little China", "Three Amigos", "Spaceballs"), smartOnly = true, films = true), - ChannelPreset(132, "Orbit Sci-Fi", listOf("2001: A Space Odyssey", "Star Wars", "The Empire Strikes Back", "Return of the Jedi", "Alien", "Aliens", "Blade Runner", "The Terminator", "Terminator 2: Judgment Day", "RoboCop", "Total Recall", "Predator", "Stargate", "Starship Troopers", "The Fifth Element", "Contact", "Gattaca", "Dark City", "The Matrix", "Pitch Black", "Event Horizon", "Sunshine", "Moon", "District 9", "Inception", "Interstellar", "Gravity", "The Martian", "Arrival", "Ex Machina", "Dune", "Edge of Tomorrow", "Minority Report", "Looper", "Children of Men", "Star Trek II: The Wrath of Khan", "Star Trek: First Contact", "Star Trek", "Tron", "Close Encounters of the Third Kind", "The Abyss", "Independence Day", "Men in Black", "Serenity", "Planet of the Apes", "Soylent Green", "Logan's Run", "Primer"), smartOnly = true, films = true), - ChannelPreset(133, "Orbit Horror", listOf("A Nightmare on Elm Street", "Friday the 13th", "The Texas Chain Saw Massacre", "Night of the Living Dead", "Dawn of the Dead", "The Exorcist", "The Omen", "The Shining", "Carrie", "Pet Sematary", "It", "Misery", "Child's Play", "Candyman", "Jaws", "The Blair Witch Project", "The Ring", "Saw", "Hostel", "The Descent", "Insidious", "The Conjuring", "Sinister", "Hereditary", "Midsommar", "The Witch", "It Follows", "Get Out", "A Quiet Place", "Rosemary's Baby", "Psycho", "The Birds", "Black Christmas", "Sleepaway Camp", "Maniac", "The Hills Have Eyes", "Critters", "Leprechaun", "Pumpkinhead", "Army of Darkness", "Evil Dead II", "Braindead", "The Mist", "Drag Me to Hell", "The Babadook", "Train to Busan", "Ringu", "Barbarian"), smartOnly = true, films = true), - ChannelPreset(134, "Orbit Action", listOf("Die Hard", "Die Hard 2", "Die Hard with a Vengeance", "Lethal Weapon", "Lethal Weapon 2", "Commando", "Rambo: First Blood", "First Blood", "Rocky", "Cobra", "Tango & Cash", "Demolition Man", "Speed", "The Rock", "Con Air", "Face/Off", "Heat", "Point Break", "Bad Boys", "Enter the Dragon", "Rush Hour", "Police Story", "Bloodsport", "Kickboxer", "Under Siege", "Hard Target", "Mortal Kombat", "Blade", "John Wick", "John Wick: Chapter 2", "Taken", "The Raid", "Mad Max", "Mad Max 2", "Mad Max: Fury Road", "The Bourne Identity", "The Bourne Supremacy", "The Bourne Ultimatum", "Casino Royale", "GoldenEye", "Mission: Impossible", "Gladiator", "The Fast and the Furious", "The Transporter", "Crank", "Kill Bill: Vol. 1", "Kill Bill: Vol. 2", "The Expendables", "Dredd", "Sicario"), smartOnly = true, films = true), - ChannelPreset(135, "Orbit Romance", listOf("Pretty Woman", "When Harry Met Sally...", "Sleepless in Seattle", "You've Got Mail", "Notting Hill", "Four Weddings and a Funeral", "Love Actually", "Bridget Jones's Diary", "About Time", "Titanic", "Ghost", "Dirty Dancing", "The Notebook", "Crazy, Stupid, Love.", "10 Things I Hate About You", "She's All That", "Clueless", "The Holiday", "The Proposal", "Hitch", "Maid in Manhattan", "Runaway Bride", "My Best Friend's Wedding", "While You Were Sleeping", "Serendipity", "How to Lose a Guy in 10 Days", "13 Going on 30", "The Wedding Singer", "50 First Dates", "There's Something About Mary", "La La Land", "The Princess Diaries", "Mamma Mia!", "Grease", "Casablanca", "Roman Holiday", "Breakfast at Tiffany's", "Amélie", "Pride & Prejudice", "Sense and Sensibility", "Crazy Rich Asians", "Me Before You", "Forrest Gump", "Jerry Maguire", "Shakespeare in Love"), smartOnly = true, films = true), - ChannelPreset(136, "Orbit Premiere", listOf("Jurassic Park", "The Lost World: Jurassic Park", "Raiders of the Lost Ark", "Indiana Jones and the Temple of Doom", "Indiana Jones and the Last Crusade", "Superman", "Batman", "Batman Returns", "Batman Begins", "The Dark Knight", "The Dark Knight Rises", "Spider-Man", "Spider-Man 2", "X-Men", "X2", "Iron Man", "The Avengers", "Avengers: Infinity War", "Avengers: Endgame", "Black Panther", "Guardians of the Galaxy", "The Lord of the Rings: The Fellowship of the Ring", "The Lord of the Rings: The Two Towers", "The Lord of the Rings: The Return of the King", "Harry Potter and the Philosopher's Stone", "Pirates of the Caribbean: The Curse of the Black Pearl", "Avatar", "Transformers", "Armageddon", "Twister", "Top Gun: Maverick", "The Mummy", "The Mask of Zorro", "Braveheart", "Skyfall", "Mission: Impossible - Fallout", "King Kong", "Godzilla", "Pacific Rim", "Wonder Woman", "Oppenheimer"), smartOnly = true, films = true), - ChannelPreset(144, "Odyssey", listOf("Cosmos", "Cosmos: A Spacetime Odyssey", "Wonders of the Universe", "Wonders of the Solar System", "The Universe", "How the Universe Works", "Civilisation", "Civilizations", "Connections", "The Ascent of Man", "The World at War", "Band of Brothers", "The Vietnam War", "The Civil War", "Apollo 11", "Horizon", "Seconds from Disaster", "Air Crash Investigation", "Mayday", "Engineering the Impossible", "Impossible Engineering", "Extreme Engineering", "Big Bigger Biggest", "Ancient Apocalypse", "Rome's Greatest Battles", "The Story of India", "Egypt's Lost Cities", "Long Way Round", "Long Way Down", "Parts Unknown", "An Idiot Abroad", "Michael Palin's Himalaya", "Pole to Pole", "Around the World in 80 Days", "James May's Man Lab", "The Story of Maths", "Walking with Dinosaurs"), smartOnly = true), - ChannelPreset(150, "Ring X", RingXMondayNight + RingXImpact + RingXAew + RingXVault, smartOnly = true)) + ChannelPreset(303, "Orbit Sci-Fi", listOf("2001: A Space Odyssey", "Star Wars", "The Empire Strikes Back", "Return of the Jedi", "Alien", "Aliens", "Blade Runner", "The Terminator", "Terminator 2: Judgment Day", "RoboCop", "Total Recall", "Predator", "Stargate", "Starship Troopers", "The Fifth Element", "Contact", "Gattaca", "Dark City", "The Matrix", "Pitch Black", "Event Horizon", "Sunshine", "Moon", "District 9", "Inception", "Interstellar", "Gravity", "The Martian", "Arrival", "Ex Machina", "Dune", "Edge of Tomorrow", "Minority Report", "Looper", "Children of Men", "Star Trek II: The Wrath of Khan", "Star Trek: First Contact", "Star Trek", "Tron", "Close Encounters of the Third Kind", "The Abyss", "Independence Day", "Men in Black", "Serenity", "Planet of the Apes", "Soylent Green", "Logan's Run", "Primer"), smartOnly = true, films = true), + ChannelPreset(304, "Orbit Horror", listOf("A Nightmare on Elm Street", "Friday the 13th", "The Texas Chain Saw Massacre", "Night of the Living Dead", "Dawn of the Dead", "The Exorcist", "The Omen", "The Shining", "Carrie", "Pet Sematary", "It", "Misery", "Child's Play", "Candyman", "Jaws", "The Blair Witch Project", "The Ring", "Saw", "Hostel", "The Descent", "Insidious", "The Conjuring", "Sinister", "Hereditary", "Midsommar", "The Witch", "It Follows", "Get Out", "A Quiet Place", "Rosemary's Baby", "Psycho", "The Birds", "Black Christmas", "Sleepaway Camp", "Maniac", "The Hills Have Eyes", "Critters", "Leprechaun", "Pumpkinhead", "Army of Darkness", "Evil Dead II", "Braindead", "The Mist", "Drag Me to Hell", "The Babadook", "Train to Busan", "Ringu", "Barbarian"), smartOnly = true, films = true), + ChannelPreset(302, "Orbit Action", listOf("Die Hard", "Die Hard 2", "Die Hard with a Vengeance", "Lethal Weapon", "Lethal Weapon 2", "Commando", "Rambo: First Blood", "First Blood", "Rocky", "Cobra", "Tango & Cash", "Demolition Man", "Speed", "The Rock", "Con Air", "Face/Off", "Heat", "Point Break", "Bad Boys", "Enter the Dragon", "Rush Hour", "Police Story", "Bloodsport", "Kickboxer", "Under Siege", "Hard Target", "Mortal Kombat", "Blade", "John Wick", "John Wick: Chapter 2", "Taken", "The Raid", "Mad Max", "Mad Max 2", "Mad Max: Fury Road", "The Bourne Identity", "The Bourne Supremacy", "The Bourne Ultimatum", "Casino Royale", "GoldenEye", "Mission: Impossible", "Gladiator", "The Fast and the Furious", "The Transporter", "Crank", "Kill Bill: Vol. 1", "Kill Bill: Vol. 2", "The Expendables", "Dredd", "Sicario"), smartOnly = true, films = true), + ChannelPreset(307, "Orbit Romance", listOf("Pretty Woman", "When Harry Met Sally...", "Sleepless in Seattle", "You've Got Mail", "Notting Hill", "Four Weddings and a Funeral", "Love Actually", "Bridget Jones's Diary", "About Time", "Titanic", "Ghost", "Dirty Dancing", "The Notebook", "Crazy, Stupid, Love.", "10 Things I Hate About You", "She's All That", "Clueless", "The Holiday", "The Proposal", "Hitch", "Maid in Manhattan", "Runaway Bride", "My Best Friend's Wedding", "While You Were Sleeping", "Serendipity", "How to Lose a Guy in 10 Days", "13 Going on 30", "The Wedding Singer", "50 First Dates", "There's Something About Mary", "La La Land", "The Princess Diaries", "Mamma Mia!", "Grease", "Casablanca", "Roman Holiday", "Breakfast at Tiffany's", "Amélie", "Pride & Prejudice", "Sense and Sensibility", "Crazy Rich Asians", "Me Before You", "Forrest Gump", "Jerry Maguire", "Shakespeare in Love"), smartOnly = true, films = true), + ChannelPreset(301, "Orbit Premiere", listOf("Jurassic Park", "The Lost World: Jurassic Park", "Raiders of the Lost Ark", "Indiana Jones and the Temple of Doom", "Indiana Jones and the Last Crusade", "Superman", "Batman", "Batman Returns", "Batman Begins", "The Dark Knight", "The Dark Knight Rises", "Spider-Man", "Spider-Man 2", "X-Men", "X2", "Iron Man", "The Avengers", "Avengers: Infinity War", "Avengers: Endgame", "Black Panther", "Guardians of the Galaxy", "The Lord of the Rings: The Fellowship of the Ring", "The Lord of the Rings: The Two Towers", "The Lord of the Rings: The Return of the King", "Harry Potter and the Philosopher's Stone", "Pirates of the Caribbean: The Curse of the Black Pearl", "Avatar", "Transformers", "Armageddon", "Twister", "Top Gun: Maverick", "The Mummy", "The Mask of Zorro", "Braveheart", "Skyfall", "Mission: Impossible - Fallout", "King Kong", "Godzilla", "Pacific Rim", "Wonder Woman", "Oppenheimer"), smartOnly = true, films = true), + ChannelPreset(202, "Odyssey", listOf("Cosmos", "Cosmos: A Spacetime Odyssey", "Wonders of the Universe", "Wonders of the Solar System", "The Universe", "How the Universe Works", "Civilisation", "Civilizations", "Connections", "The Ascent of Man", "The World at War", "Band of Brothers", "The Vietnam War", "The Civil War", "Apollo 11", "Horizon", "Seconds from Disaster", "Air Crash Investigation", "Mayday", "Engineering the Impossible", "Impossible Engineering", "Extreme Engineering", "Big Bigger Biggest", "Ancient Apocalypse", "Rome's Greatest Battles", "The Story of India", "Egypt's Lost Cities", "Long Way Round", "Long Way Down", "Parts Unknown", "An Idiot Abroad", "Michael Palin's Himalaya", "Pole to Pole", "Around the World in 80 Days", "James May's Man Lab", "The Story of Maths", "Walking with Dinosaurs"), smartOnly = true), + ChannelPreset(401, "Ring X", RingXMondayNight + RingXImpact + RingXAew + RingXVault, 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, programming: ChannelProgramming): SampleChannel? { 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 760f409..922e5fb 100644 --- a/android/app/src/main/java/tv/orbit/classic/ChannelSchedule.kt +++ b/android/app/src/main/java/tv/orbit/classic/ChannelSchedule.kt @@ -58,36 +58,36 @@ internal val SmartChannels = listOf( "Life on Earth", "The Living Planet", "The Trials of Life", "Our Planet", "Our Planet II", "Dynasties", "Dynasties II", "Seven Worlds, One Planet", "A Perfect Planet", "Hostile Planet", "Africa", "Wild China", "South Pacific", "Great Barrier Reef", "The Hunt", "The Life of Mammals", "The Life of Birds", "Nature's Great Events", "Green Planet", "Prehistoric Planet"), seriesCount = 20, finishTogether = true, exactTitles = true), - ChannelProgramming(152, "Paradox", ChannelPresets.first { it.number == 152 }.searches)) + + ChannelProgramming(152, "Paradox", ChannelPresets.first { it.number == 106 }.searches)) + ChannelPresets.filter { it.smartOnly }.map { ChannelProgramming(it.number, it.name, it.searches, seriesCount = 60, finishTogether = !it.films, exactTitles = true, filmRestMs = if (it.films) 7 * 24 * 60 * 60_000L else null, suggestionRules = SmartChannelRules[it.number].orEmpty(), dayParts = SmartChannelParts[it.number].orEmpty()) } /** Dark Files, from the programming bible: crime by day, mysteries early evening, the paranormal late. */ -private val SmartChannelParts: Map> get() = mapOf(142 to listOf( +private val SmartChannelParts: Map> get() = mapOf(204 to listOf( DayPart(6, 18, DarkFilesCrime, group = "day"), DayPart(18, 21, DarkFilesMysteries, group = "evening"), DayPart(21, 6, DarkFilesParanormal, group = "late")), // Ring X, from the bible: each weeknight's own show, and the whole vault the rest of the week. - 150 to listOf(DayPart(0, 24, RingXMondayNight, days = setOf(java.time.DayOfWeek.MONDAY)), DayPart(0, 24, RingXImpact, days = setOf(java.time.DayOfWeek.TUESDAY)), + 401 to listOf(DayPart(0, 24, RingXMondayNight, days = setOf(java.time.DayOfWeek.MONDAY)), DayPart(0, 24, RingXImpact, days = setOf(java.time.DayOfWeek.TUESDAY)), DayPart(0, 24, RingXAew, days = setOf(java.time.DayOfWeek.WEDNESDAY)))) /** 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> 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(), 40, setOf("Animation")), + 602 to listOf(SuggestionRule("all", "Superhero animation", "Series", listOf("Animation", "Action"), 1990..2020, 7f, setOf("TV-MA"), 30, setOf("Anime"), titleContains = null)), + 605 to listOf(SuggestionRule("all", "Classic cartoons", "Series", listOf("Animation", "Comedy"), 1940..1990, 6.5f, setOf("TV-MA"), 40)), + 604 to listOf(SuggestionRule("all", "After-school cartoons", "Series", listOf("Animation", "Comedy"), 1990..2010, 7f, setOf("TV-MA"), 40, setOf("Anime"))), + 603 to listOf(SuggestionRule("all", "Action cartoons", "Series", listOf("Animation", "Action"), 1980..2000, 6.5f, setOf("TV-MA"), 40, setOf("Anime"))), + 204 to listOf(SuggestionRule("day", "Crime documentary", "Series", listOf("Documentary", "Crime"), 1990..2024, 7f, emptySet(), 40, setOf("Animation")), SuggestionRule("evening", "Mystery documentary", "Series", listOf("Documentary", "Mystery"), 1985..2024, 6.5f, 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"))), - 131 to listOf(SuggestionRule("all", "80s and 90s adventure", "Movie", listOf("Adventure"), 1980..1999, 6.5f, setOf("NC-17", "X"), 40, setOf("Documentary"), 70), + 105 to listOf(SuggestionRule("all", "Adult animation", "Series", listOf("Animation", "Comedy"), 1990..2024, 7.5f, emptySet(), 30, setOf("Anime", "Family", "Children"))), + 306 to listOf(SuggestionRule("all", "80s and 90s adventure", "Movie", listOf("Adventure"), 1980..1999, 6.5f, setOf("NC-17", "X"), 40, setOf("Documentary"), 70), SuggestionRule("all", "80s and 90s comedy", "Movie", listOf("Comedy"), 1980..1999, 7f, setOf("NC-17", "X"), 40, setOf("Documentary", "Horror"), 70)), - 132 to listOf(SuggestionRule("all", "Science fiction", "Movie", listOf("Science Fiction"), 1968..2025, 6.5f, emptySet(), 60, setOf("Animation", "Documentary", "Family"), 70)), - 133 to listOf(SuggestionRule("all", "Horror", "Movie", listOf("Horror"), 1970..2025, 6f, emptySet(), 60, setOf("Animation", "Documentary"), 70)), - 134 to listOf(SuggestionRule("all", "Action", "Movie", listOf("Action"), 1980..2025, 6.5f, emptySet(), 60, setOf("Animation", "Documentary", "Family", "Horror"), 70)), - 135 to listOf(SuggestionRule("all", "Romantic comedy", "Movie", listOf("Romance", "Comedy"), 1985..2025, 6.5f, emptySet(), 60, setOf("Animation", "Documentary", "Horror"), 70)), - 136 to listOf(SuggestionRule("all", "Big event films", "Movie", listOf("Adventure", "Action"), 1975..2025, 7.5f, emptySet(), 40, setOf("Animation", "Documentary"), 80)), - 144 to listOf(SuggestionRule("all", "History and science", "Series", listOf("Documentary", "History"), 1970..2025, 7f, emptySet(), 30, setOf("Crime", "Animation"))), - 141 to listOf(SuggestionRule("all", "Crime drama", "Series", listOf("Crime", "Drama"), 1985..2022, 7f, emptySet(), 40, setOf("Documentary", "Animation")))) + 303 to listOf(SuggestionRule("all", "Science fiction", "Movie", listOf("Science Fiction"), 1968..2025, 6.5f, emptySet(), 60, setOf("Animation", "Documentary", "Family"), 70)), + 304 to listOf(SuggestionRule("all", "Horror", "Movie", listOf("Horror"), 1970..2025, 6f, emptySet(), 60, setOf("Animation", "Documentary"), 70)), + 302 to listOf(SuggestionRule("all", "Action", "Movie", listOf("Action"), 1980..2025, 6.5f, emptySet(), 60, setOf("Animation", "Documentary", "Family", "Horror"), 70)), + 307 to listOf(SuggestionRule("all", "Romantic comedy", "Movie", listOf("Romance", "Comedy"), 1985..2025, 6.5f, emptySet(), 60, setOf("Animation", "Documentary", "Horror"), 70)), + 301 to listOf(SuggestionRule("all", "Big event films", "Movie", listOf("Adventure", "Action"), 1975..2025, 7.5f, emptySet(), 40, setOf("Animation", "Documentary"), 80)), + 202 to listOf(SuggestionRule("all", "History and science", "Series", listOf("Documentary", "History"), 1970..2025, 7f, emptySet(), 30, setOf("Crime", "Animation"))), + 107 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 { override fun compareTo(other: EpisodeMark) = compareValuesBy(this, other, { it.season }, { it.episode }) diff --git a/android/app/src/main/java/tv/orbit/classic/DriveIn.kt b/android/app/src/main/java/tv/orbit/classic/DriveIn.kt index 7d5dc44..a4e3c7b 100644 --- a/android/app/src/main/java/tv/orbit/classic/DriveIn.kt +++ b/android/app/src/main/java/tv/orbit/classic/DriveIn.kt @@ -49,13 +49,13 @@ import java.time.ZoneId import java.time.format.TextStyle as DateTextStyle /** Not a channel in the guide: one linear evening's bill, with nothing on between shows. */ -internal const val DRIVE_IN_NUMBER = 160 +internal const val DRIVE_IN_NUMBER = 300 internal const val DRIVE_IN_SCENES = 3 private const val DRIVE_IN_REST_MS = 14 * 24 * 60 * 60_000L -internal val DriveInMarqueeChannels = listOf(136, 132, 134, 131) -internal val DriveInLateChannels = listOf(133, AFTER_DARK_NUMBER) -internal val DriveInBillChannels = listOf(136, 131, 132, 133, 134, 135, 126, 127, 114, 115, AFTER_DARK_NUMBER) +internal val DriveInMarqueeChannels = listOf(301, 303, 302, 306) +internal val DriveInLateChannels = listOf(304, AFTER_DARK_NUMBER) +internal val DriveInBillChannels = listOf(301, 306, 303, 304, 302, 307, 612, 613, 308, 309, AFTER_DARK_NUMBER) /** Hand-picked, so a bill always belongs together. One whose films aren't all in the library is skipped. */ private class NamedBill(val name: String, vararg films: String) { val films = films.toList() } 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 0390d6d..bc40707 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.68.0\"") + connection.setRequestProperty("X-Emby-Authorization", "Emby Client=\"Orbit TV\", Device=\"Orbit TV\", DeviceId=\"$deviceId\", Version=\"0.69.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/ScheduleStore.kt b/android/app/src/main/java/tv/orbit/classic/ScheduleStore.kt index da8a853..468edca 100644 --- a/android/app/src/main/java/tv/orbit/classic/ScheduleStore.kt +++ b/android/app/src/main/java/tv/orbit/classic/ScheduleStore.kt @@ -5,8 +5,10 @@ import org.json.JSONObject import java.io.File import java.io.FileOutputStream -/** Old number to new, from when Orbit 2 and Orbit Three took 102 and 103. */ -internal val OldChannelNumbers = mapOf(102 to AFTER_DARK_NUMBER, 103 to VORTEX_NUMBER, 104 to 102, 112 to 103) +/** Old number to new, from when the channels moved into blocks. */ +internal val OldChannelNumbers = mapOf(104 to 102, 112 to 103, 111 to 104, 113 to 105, 152 to 106, 141 to 107, 140 to 201, 144 to 202, 143 to 203, 142 to 204, 160 to DRIVE_IN_NUMBER, + 136 to 301, 134 to 302, 132 to 303, 133 to 304, 102 to AFTER_DARK_NUMBER, 131 to 306, 135 to 307, 114 to 308, 115 to 309, 150 to 401, + 103 to VORTEX_NUMBER, 121 to 602, 124 to 603, 123 to 604, 122 to 605, 125 to 611, 126 to 612, 127 to 613) /** * Moves each channel's files to its new number, once, so schedules and places carry on untouched. New copies are finished 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 d077b36..083276e 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.68.0") + next.setRequestProperty("User-Agent", "OrbitTV/0.69.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/AfterDarkMoviesTest.kt b/android/app/src/test/java/tv/orbit/classic/AfterDarkMoviesTest.kt index a47e568..f1d2bc2 100644 --- a/android/app/src/test/java/tv/orbit/classic/AfterDarkMoviesTest.kt +++ b/android/app/src/test/java/tv/orbit/classic/AfterDarkMoviesTest.kt @@ -55,7 +55,7 @@ class AfterDarkMoviesTest { val fake = Fake(); val active = EmbySampleSession(fake, "fixture-token", "fixture-user", EmbySample(emptyList(), 0)) active.checkAfterDarkMovies("Horror") val channel = active.buildAfterDarkChannel(setOf("one", "two")) - assertEquals(130, channel.number); assertEquals("Orbit After Dark", channel.name) + assertEquals(305, channel.number); assertEquals("Orbit After Dark", channel.name) assertEquals(5_400_123L, channel.lineup.first().durationMs) assertEquals(3, fake.requests.size) assertTrue(fake.requests.drop(1).all { !JSONObject(it.body!!).getBoolean("IsPlayback") }) @@ -69,33 +69,33 @@ class AfterDarkMoviesTest { assertEquals(1, count) } @Test fun oldSavedSetupStillLoadsAnd102RoundTripKeepsTheExistingClock() { - val old = RememberedState(RememberedIdentity("fixture-user", "fixture-token", "fixture-device"), EmbySample(emptyList(), 0), emptyList(), emptySet(), null, null, null, 120) + val old = RememberedState(RememberedIdentity("fixture-user", "fixture-token", "fixture-device"), EmbySample(emptyList(), 0), emptyList(), emptySet(), null, null, null, 601) val legacy = JSONObject(String(RememberedStateCodec.encode(old))) for (key in listOf("afterDark", "darkMovies", "approvedDark", "selectedChannel")) legacy.remove(key) val loaded = RememberedStateCodec.decode(legacy.toString().toByteArray()) - assertEquals(120, loaded.selectedNumber); assertNull(loaded.afterDark); assertTrue(loaded.darkMovies.isEmpty()) + assertEquals(601, loaded.selectedNumber); assertNull(loaded.afterDark); assertTrue(loaded.darkMovies.isEmpty()) val movies = listOf(candidate("one"), candidate("two")) - val channel = SampleChannel(movies.map { ScheduledMedia(it.item, "${it.item.id}-source", 5_400_123) }, "Orbit After Dark", 130) - val state = RememberedState(old.identity, old.sample, emptyList(), emptySet(), null, null, null, 130, channel, movies, setOf("one", "two")) + val channel = SampleChannel(movies.map { ScheduledMedia(it.item, "${it.item.id}-source", 5_400_123) }, "Orbit After Dark", 305) + val state = RememberedState(old.identity, old.sample, emptyList(), emptySet(), null, null, null, 305, channel, movies, setOf("one", "two")) val restored = RememberedStateCodec.decode(RememberedStateCodec.encode(state)) assertEquals(channel.guide(1_793_491_234_567L), restored.afterDark!!.guide(1_793_491_234_567L)) - assertEquals(130, restored.selectedNumber); assertEquals(state.approvedDark, restored.approvedDark) - assertEquals(130, state.withoutToken().afterDark!!.number) - assertEquals(130, JSONObject(String(RememberedStateCodec.encode(state))).getInt("selected")) + assertEquals(305, restored.selectedNumber); assertEquals(state.approvedDark, restored.approvedDark) + assertEquals(305, state.withoutToken().afterDark!!.number) + assertEquals(305, JSONObject(String(RememberedStateCodec.encode(state))).getInt("selected")) } @Test fun adding102Preserves101And103AndThreeWayGuideOrder() { val movies = listOf(candidate("one"), candidate("two")) val one = SampleChannel(movies.map { ScheduledMedia(it.item, "${it.item.id}-source", 5_400_000) }) - val toonami = SampleChannel(List(2) { i -> ScheduledMedia(SampleItem("Fixture episode", "Episode", 24, "mkv", "ep-$i", 1_440_000), "ep-$i-source", 1_440_000) }, "Toonami", 120) - val dark = SampleChannel(one.lineup, "Orbit After Dark", 130) + val toonami = SampleChannel(List(2) { i -> ScheduledMedia(SampleItem("Fixture episode", "Episode", 24, "mkv", "ep-$i", 1_440_000), "ep-$i-source", 1_440_000) }, "Toonami", 601) + val dark = SampleChannel(one.lineup, "Orbit After Dark", 305) val saved = RememberedState(RememberedIdentity("fixture-user", "fixture-token", "fixture-device"), EmbySample(movies.map { it.item }, 2), - emptyList(), emptySet(), null, one, toonami, 130, dark, movies, setOf("one", "two")) + emptyList(), emptySet(), null, one, toonami, 305, dark, movies, setOf("one", "two")) val restored = RememberedStateCodec.decode(RememberedStateCodec.encode(saved)) val time = 1_793_491_234_567L assertEquals(one.guide(time), restored.orbitOne!!.guide(time)) assertEquals(toonami.guide(time), restored.toonami!!.guide(time)) assertEquals(dark.guide(time), restored.afterDark!!.guide(time)) - assertEquals(listOf(101, 120, 130), availableSampleChannels(restored.toonami, restored.afterDark, restored.orbitOne, null, restored.orbitOne).map { it.number }) + assertEquals(listOf(101, 305, 601), availableSampleChannels(restored.toonami, restored.afterDark, restored.orbitOne, null, restored.orbitOne).map { it.number }) } } diff --git a/android/app/src/test/java/tv/orbit/classic/ChannelPresetsTest.kt b/android/app/src/test/java/tv/orbit/classic/ChannelPresetsTest.kt index ab231b9..4d4e7b2 100644 --- a/android/app/src/test/java/tv/orbit/classic/ChannelPresetsTest.kt +++ b/android/app/src/test/java/tv/orbit/classic/ChannelPresetsTest.kt @@ -50,10 +50,10 @@ class ChannelPresetsTest { @Test fun presetChannelsSurviveASaveAndRestore() { - val terra = SampleChannel(listOf(media("a"), media("b")), "Terra", 140) - val restored = RememberedStateCodec.decode(RememberedStateCodec.encode(state(listOf(terra), 140))) - assertEquals(listOf(140), restored.extra.map { it.number }) - assertEquals(140, restored.selectedNumber) + val terra = SampleChannel(listOf(media("a"), media("b")), "Terra", 201) + val restored = RememberedStateCodec.decode(RememberedStateCodec.encode(state(listOf(terra), 201))) + assertEquals(listOf(201), restored.extra.map { it.number }) + assertEquals(201, restored.selectedNumber) assertEquals(listOf("a", "b"), restored.extra.single().lineup.map { it.item.id }) } @@ -66,7 +66,7 @@ class ChannelPresetsTest { @Test fun aChannelNumberThatIsNotAPresetIsRejected() { - val saved = JSONObject(String(RememberedStateCodec.encode(state(listOf(SampleChannel(listOf(media("a"), media("b")), "Terra", 140)), 101)))) + val saved = JSONObject(String(RememberedStateCodec.encode(state(listOf(SampleChannel(listOf(media("a"), media("b")), "Terra", 201)), 101)))) saved.getJSONArray("extra").getJSONObject(0).put("number", 999) assertThrows(Exception::class.java) { RememberedStateCodec.decode(saved.toString().toByteArray()) } } @@ -84,12 +84,12 @@ class ChannelPresetsTest { } private fun poolShow(id: String, n: Int) = PoolSeries(id, "Show $id", (1..n).map { PoolEpisode("$id-$it", "s$id-$it", EpisodeMark(1, it), "E$it", 60_000) }) - private val programming = ChannelProgramming(121, "Heroes", listOf("x")) + private val programming = ChannelProgramming(602, "Heroes", listOf("x")) @Test fun aChannelWithNoLoopGetsAFallbackFromItsPool() { val loop = loopFromPool(listOf(poolShow("a", 20), poolShow("b", 2)), programming)!! assertEquals(15, loop.lineup.size) - assertEquals(121, loop.number) + assertEquals(602, loop.number) assertEquals(listOf("a-1", "b-1", "a-2", "b-2", "a-3"), loop.lineup.take(5).map { it.item.id }) } @@ -100,18 +100,18 @@ class ChannelPresetsTest { @Test fun theNewChannelsSurviveASaveAndRestore() { val heroes = loopFromPool(listOf(poolShow("a", 3)), programming)!! - val restored = RememberedStateCodec.decode(RememberedStateCodec.encode(state(listOf(heroes), 121))) - assertEquals(121, restored.selectedNumber) - assertEquals(listOf(121), restored.extra.map { it.number }) + val restored = RememberedStateCodec.decode(RememberedStateCodec.encode(state(listOf(heroes), 602))) + assertEquals(602, restored.selectedNumber) + assertEquals(listOf(602), restored.extra.map { it.number }) } @Test fun theNewChannelsAreConfiguredAndBranded() { - listOf(111, 103, 113, 121, 122, 123, 124, 141, 142).forEach { number -> + listOf(104, 103, 105, 602, 605, 604, 603, 107, 204).forEach { number -> assertTrue(SmartChannels.any { it.number == number && it.exactTitles && it.finishTogether }) assertNotNull(ChannelBrands.forNumber(number)) assertTrue(ChannelPresets.first { it.number == number }.smartOnly) } - assertEquals(ChannelCategory.Comedy, ChannelBrands.forNumber(111)!!.category) + assertEquals(ChannelCategory.Comedy, ChannelBrands.forNumber(104)!!.category) } @Test fun smartChannelNumbersAreUnique() { @@ -119,7 +119,7 @@ class ChannelPresetsTest { } @Test fun darkFilesFollowsTheBiblesDay() { - val parts = SmartChannels.first { it.number == 142 }.dayParts + val parts = SmartChannels.first { it.number == 204 }.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")) @@ -133,41 +133,41 @@ class ChannelPresetsTest { assertTrue(DarkFilesCrime.size >= 20) assertTrue(DarkFilesMysteries.size >= 10) assertTrue(DarkFilesParanormal.size >= 15) - val all = ChannelPresets.first { it.number == 142 }.searches + val all = ChannelPresets.first { it.number == 204 }.searches assertEquals(all.size, all.distinct().size) - assertTrue(SmartChannels.first { it.number == 142 }.seriesCount >= all.size) + assertTrue(SmartChannels.first { it.number == 204 }.seriesCount >= all.size) } @Test fun detourAndBritXAreFilmChannelsWithTheirOwnRest() { - listOf(114, 115).forEach { number -> + listOf(308, 309).forEach { number -> val channel = SmartChannels.first { it.number == number } assertNotNull(channel.filmRestMs) assertFalse(channel.finishTogether) assertEquals(ChannelCategory.Movies, ChannelBrands.forNumber(number)!!.category) } - assertEquals(ChannelCategory.Kids, ChannelBrands.forNumber(125)!!.category) - assertEquals(ChannelCategory.Documentaries, ChannelBrands.forNumber(143)!!.category) + assertEquals(ChannelCategory.Kids, ChannelBrands.forNumber(611)!!.category) + assertEquals(ChannelCategory.Documentaries, ChannelBrands.forNumber(203)!!.category) } @Test fun aFilmChannelsFallbackShowsFilmsNotEpisodes() { val film = { id: String -> PoolSeries(id, "Film $id", listOf(PoolEpisode(id, "s$id", EpisodeMark(1, 1), "Film $id", 60_000))) } - val loop = loopFromPool(listOf(film("a"), film("b")), ChannelProgramming(114, "Detour", listOf("x")))!! + val loop = loopFromPool(listOf(film("a"), film("b")), ChannelProgramming(308, "Detour", listOf("x")))!! assertEquals(listOf("Movie", "Movie"), loop.lineup.map { it.item.type }) assertEquals("Film a", loop.lineup.first().item.title) } @Test fun theLastChannelsAreConfiguredAndBranded() { - listOf(102, 126, 127, 131, 132, 133, 134, 135, 136, 144, 150).forEach { number -> + listOf(102, 612, 613, 306, 303, 304, 302, 307, 301, 202, 401).forEach { number -> assertNotNull(ChannelBrands.forNumber(number)) assertTrue(SmartChannels.any { it.number == number && it.exactTitles }) } - listOf(126, 127, 131, 132, 133, 134, 135, 136).forEach { assertNotNull(SmartChannels.first { channel -> channel.number == it }.filmRestMs) } - assertEquals(ChannelCategory.Sport, ChannelBrands.forNumber(150)!!.category) + listOf(612, 613, 306, 303, 304, 302, 307, 301).forEach { assertNotNull(SmartChannels.first { channel -> channel.number == it }.filmRestMs) } + assertEquals(ChannelCategory.Sport, ChannelBrands.forNumber(401)!!.category) assertEquals(29, SmartChannels.size) } @Test fun ringXKeepsItsWeeknightsAndFallsBackToTheVault() { - val ringX = SmartChannels.first { it.number == 150 } + val ringX = SmartChannels.first { it.number == 401 } val london = java.time.ZoneId.of("Europe/London") fun show(title: String) = PoolSeries(title, title, listOf(PoolEpisode("$title-1", "s", EpisodeMark(1, 1), "E1", 60_000))) val pool = listOf(show("WCW Monday Nitro"), show("AEW Dynamite"), show("WrestleMania")) @@ -192,10 +192,20 @@ class ChannelPresetsTest { java.io.File(folder, "$kind-$number$suffix").writeText("""{"schema":1,"user":"u","channel":$number,"marker":"$marker"}""") private fun read(folder: java.io.File, name: String) = org.json.JSONObject(java.io.File(folder, name).readText()) + @Test fun channelsSitInTheirBlocks() { + fun block(category: ChannelCategory) = SmartChannels.map { it.number }.filter { ChannelBrands.forNumber(it)?.category == category } + assertTrue(block(ChannelCategory.Movies).all { it in 300..399 }) + assertTrue(block(ChannelCategory.Documentaries).all { it in 200..299 }) + assertTrue(block(ChannelCategory.Sport).all { it in 400..499 }) + assertTrue((block(ChannelCategory.Cartoons) + block(ChannelCategory.Kids)).all { it in 600..699 }) + assertTrue((block(ChannelCategory.Entertainment) + block(ChannelCategory.Comedy)).all { it in 100..199 }) + assertEquals(OldChannelNumbers.size, OldChannelNumbers.values.distinct().size) + } + @Test fun channelFilesMoveToTheirNewNumbersOnce() { val folder = java.nio.file.Files.createTempDirectory("numbers").toFile() schedule(folder, "schedule", 102, "after dark"); schedule(folder, "schedule", 104, "orbit 2"); schedule(folder, "schedule", 103, "vortex", ".json.prev") - schedule(folder, "progress", 112, "three"); schedule(folder, "schedule", 140, "terra") + schedule(folder, "progress", 112, "three"); schedule(folder, "schedule", 999, "other") migrateChannelNumbers(folder) assertEquals("after dark", read(folder, "schedule-$AFTER_DARK_NUMBER.json").getString("marker")) assertEquals(AFTER_DARK_NUMBER, read(folder, "schedule-$AFTER_DARK_NUMBER.json").getInt("channel")) @@ -203,7 +213,7 @@ class ChannelPresetsTest { assertEquals(102, read(folder, "schedule-102.json").getInt("channel")) assertEquals("vortex", read(folder, "schedule-$VORTEX_NUMBER.json.prev").getString("marker")) assertEquals("three", read(folder, "progress-103.json").getString("marker")) - assertEquals("terra", read(folder, "schedule-140.json").getString("marker")) + assertEquals("other", read(folder, "schedule-999.json").getString("marker")) assertFalse(java.io.File(folder, "schedule-104.json").exists()) // A second run changes nothing, even with a channel now on an old number. migrateChannelNumbers(folder) diff --git a/android/app/src/test/java/tv/orbit/classic/ChannelScheduleTest.kt b/android/app/src/test/java/tv/orbit/classic/ChannelScheduleTest.kt index 9214cba..88d5d13 100644 --- a/android/app/src/test/java/tv/orbit/classic/ChannelScheduleTest.kt +++ b/android/app/src/test/java/tv/orbit/classic/ChannelScheduleTest.kt @@ -17,7 +17,7 @@ class ChannelScheduleTest { (1..perSeason).map { number -> PoolEpisode("$id-$season-$number", "src-$id-$season-$number", EpisodeMark(season, number), "E$number", minutes * minute) } }) private val pool = listOf(show("a", 2, 3), show("b", 1, 2), show("c", 3, 10, 22)) - private val loop = SampleChannel(listOf(media("x"), media("y")), "Paradox", 152) + private val loop = SampleChannel(listOf(media("x"), media("y")), "Paradox", 106) private fun media(id: String) = ScheduledMedia(SampleItem(id, "Episode", 1, "", id, minute), "s-$id", minute) private fun store() = ScheduleStore(folder.newFolder()) @@ -110,16 +110,16 @@ class ChannelScheduleTest { @Test fun aRestartReadsBackTheSameSchedule() { val store = store() val plan = topUpPlan(null, ScheduleCursor(), pool, now)!! - store.savePlan(user, 152, plan) - assertEquals(plan, store.loadPlan(user, 152)) + store.savePlan(user, 106, plan) + assertEquals(plan, store.loadPlan(user, 106)) } @Test fun losingTheScheduleCarriesOnFromTheAiredPlace() { val store = store() val plan = topUpPlan(null, ScheduleCursor(), pool, now)!! val aired = airedCursor(ScheduleCursor(), plan.entries, plan.entries[6].startMs) - store.saveProgress(user, 152, ScheduleStore.SavedProgress(true, aired)) - val rebuilt = topUpPlan(store.loadPlan(user, 152), store.loadProgress(user, 152)!!.aired, pool, plan.entries[6].startMs + minute)!! + store.saveProgress(user, 106, ScheduleStore.SavedProgress(true, aired)) + val rebuilt = topUpPlan(store.loadPlan(user, 106), store.loadProgress(user, 106)!!.aired, pool, plan.entries[6].startMs + minute)!! assertEquals(extendSchedule(pool, aired, 0, 1).entries.single().media, rebuilt.entries.first().media) } @@ -127,61 +127,61 @@ class ChannelScheduleTest { val dir = folder.newFolder() val store = ScheduleStore(dir) val plan = topUpPlan(null, ScheduleCursor(), pool, now)!! - store.savePlan(user, 152, plan) - store.savePlan(user, 152, plan) - java.io.File(dir, "schedule-152.json").writeText("{ not json") - assertEquals(plan, store.loadPlan(user, 152)) + store.savePlan(user, 106, plan) + store.savePlan(user, 106, plan) + java.io.File(dir, "schedule-106.json").writeText("{ not json") + assertEquals(plan, store.loadPlan(user, 106)) } @Test fun aScheduleDamagedEverywhereIsAbsentAndProgressSurvives() { val dir = folder.newFolder() val store = ScheduleStore(dir) - store.savePlan(user, 152, topUpPlan(null, ScheduleCursor(), pool, now)!!) - store.saveProgress(user, 152, ScheduleStore.SavedProgress(true, ScheduleCursor("a", mapOf("a" to EpisodeMark(2, 1))))) + store.savePlan(user, 106, topUpPlan(null, ScheduleCursor(), pool, now)!!) + store.saveProgress(user, 106, ScheduleStore.SavedProgress(true, ScheduleCursor("a", mapOf("a" to EpisodeMark(2, 1))))) dir.listFiles()!!.filter { it.name.startsWith("schedule") }.forEach { it.writeText("") } - assertNull(store.loadPlan(user, 152)) - assertEquals(EpisodeMark(2, 1), store.loadProgress(user, 152)!!.aired.marks["a"]) + assertNull(store.loadPlan(user, 106)) + assertEquals(EpisodeMark(2, 1), store.loadProgress(user, 106)!!.aired.marks["a"]) } @Test fun anotherAccountOrChannelOrFormatIsIgnored() { val dir = folder.newFolder() val store = ScheduleStore(dir) - store.savePlan(user, 152, topUpPlan(null, ScheduleCursor(), pool, now)!!) - assertNull(store.loadPlan("other-user", 152)) - assertNull(store.loadPlan(user, 140)) - val file = java.io.File(dir, "schedule-152.json") + store.savePlan(user, 106, topUpPlan(null, ScheduleCursor(), pool, now)!!) + assertNull(store.loadPlan("other-user", 106)) + assertNull(store.loadPlan(user, 201)) + val file = java.io.File(dir, "schedule-106.json") file.writeText(JSONObject(file.readText()).put("schema", 999).toString()) - assertNull(store.loadPlan(user, 152)) + assertNull(store.loadPlan(user, 106)) } @Test fun overlappingEntriesAreRejected() { val dir = folder.newFolder() val store = ScheduleStore(dir) - store.savePlan(user, 152, topUpPlan(null, ScheduleCursor(), pool, now)!!) - val file = java.io.File(dir, "schedule-152.json") + store.savePlan(user, 106, topUpPlan(null, ScheduleCursor(), pool, now)!!) + val file = java.io.File(dir, "schedule-106.json") val root = JSONObject(file.readText()) root.getJSONArray("entries").getJSONObject(1).put("a", now) file.writeText(root.toString()) - assertNull(store.loadPlan(user, 152)) + assertNull(store.loadPlan(user, 106)) } @Test fun theEpisodeCacheRoundTrips() { val store = store() - store.savePool(user, 152, ScheduleStore.SavedPool(pool, now)) - val read = store.loadPool(user, 152)!! + store.savePool(user, 106, ScheduleStore.SavedPool(pool, now)) + val read = store.loadPool(user, 106)!! assertEquals(pool, read.series); assertEquals(now, read.fetchedAtMs) } @Test fun loopChoiceKeepsTheSavedPlace() { val store = store() val scheduler = ChannelScheduler(store) { now } - store.saveProgress(user, 152, ScheduleStore.SavedProgress(true, ScheduleCursor("a", mapOf("a" to EpisodeMark(1, 2))))) - scheduler.setEnabled(user, 152, false) - assertFalse(scheduler.enabled(user, 152)) - assertEquals(EpisodeMark(1, 2), store.loadProgress(user, 152)!!.aired.marks["a"]) - store.savePlan(user, 152, topUpPlan(null, ScheduleCursor(), pool, now)!!) + store.saveProgress(user, 106, ScheduleStore.SavedProgress(true, ScheduleCursor("a", mapOf("a" to EpisodeMark(1, 2))))) + scheduler.setEnabled(user, 106, false) + assertFalse(scheduler.enabled(user, 106)) + assertEquals(EpisodeMark(1, 2), store.loadProgress(user, 106)!!.aired.marks["a"]) + store.savePlan(user, 106, topUpPlan(null, ScheduleCursor(), pool, now)!!) assertNull(scheduler.load(user, loop)) - scheduler.setEnabled(user, 152, true) + scheduler.setEnabled(user, 106, true) assertNotNull(scheduler.load(user, loop)) } @@ -263,12 +263,12 @@ class ChannelScheduleTest { @Test fun aSavedPlanFromBeforeFilmsStillOpens() { val dir = folder.newFolder() val store = ScheduleStore(dir) - store.savePlan(user, 152, topUpPlan(null, ScheduleCursor(), pool, now)!!) - val file = java.io.File(dir, "schedule-152.json") + store.savePlan(user, 106, topUpPlan(null, ScheduleCursor(), pool, now)!!) + val file = java.io.File(dir, "schedule-106.json") val root = JSONObject(file.readText()) root.getJSONObject("tail").remove("shown") file.writeText(root.toString()) - assertNotNull(store.loadPlan(user, 152)) + assertNotNull(store.loadPlan(user, 106)) } @Test fun airedFilmsAreRemembered() { @@ -334,13 +334,13 @@ class ChannelScheduleTest { @Test fun theEpisodeCacheKeepsItsSignature() { val store = store() - store.savePool(user, 140, ScheduleStore.SavedPool(pool, now, "sig")) - assertEquals("sig", store.loadPool(user, 140)!!.signature) + store.savePool(user, 201, ScheduleStore.SavedPool(pool, now, "sig")) + assertEquals("sig", store.loadPool(user, 201)!!.signature) val dir = folder.newFolder(); val old = ScheduleStore(dir) - old.savePool(user, 140, ScheduleStore.SavedPool(pool, now, "sig")) - val file = java.io.File(dir, "episodes-140.json") + old.savePool(user, 201, ScheduleStore.SavedPool(pool, now, "sig")) + val file = java.io.File(dir, "episodes-201.json") file.writeText(JSONObject(file.readText()).apply { remove("signature") }.toString()) - assertEquals("", old.loadPool(user, 140)!!.signature) + assertEquals("", old.loadPool(user, 201)!!.signature) } private val london = java.time.ZoneId.of("Europe/London") @@ -453,7 +453,7 @@ class ChannelScheduleTest { @Test fun theLoopNeverRunsOverThePlansFirstProgramme() { val longLoop = SampleChannel(listOf(ScheduledMedia(SampleItem("x", "Episode", 60, "", "x", 60 * minute), "s-x", 60 * minute), - ScheduledMedia(SampleItem("y", "Episode", 60, "", "y", 60 * minute), "s-y", 60 * minute)), "Paradox", 152) + ScheduledMedia(SampleItem("y", "Episode", 60, "", "y", 60 * minute), "s-y", 60 * minute)), "Paradox", 106) val plan = topUpPlan(null, ScheduleCursor(), pool, now)!! val channel = ScheduledChannel(longLoop, plan) val before = channel.at(now - minute) @@ -536,7 +536,7 @@ class ChannelScheduleTest { @Test fun vortexKeepsSubtitledShowsAndCartoonChannelsWantEnglish() { assertFalse(SmartChannels.first { it.number == VORTEX_NUMBER }.englishAudio) - assertTrue(SmartChannels.first { it.number == 123 }.englishAudio) + assertTrue(SmartChannels.first { it.number == 604 }.englishAudio) } @Test fun aSmallFilmPoolStillAvoidsClashesWhileItsRestIsBroken() { diff --git a/android/app/src/test/java/tv/orbit/classic/GuideGridTest.kt b/android/app/src/test/java/tv/orbit/classic/GuideGridTest.kt index 8766258..44b9fb9 100644 --- a/android/app/src/test/java/tv/orbit/classic/GuideGridTest.kt +++ b/android/app/src/test/java/tv/orbit/classic/GuideGridTest.kt @@ -23,7 +23,7 @@ class GuideGridTest { } } @Test fun aLongFilmIsClippedToTheWindow() { - val longOne = SampleChannel(listOf(media("x", 600), media("y", 600)), "Fixture", 130) + val longOne = SampleChannel(listOf(media("x", 600), media("y", 600)), "Fixture", 305) val cells = guideCells(longOne, start, start + GUIDE_SLOT_MS) assertEquals(1, cells.size); assertEquals(1f, cells[0].widthFraction, .001f) } @@ -53,17 +53,17 @@ class GuideGridTest { @Test fun homeOnlyListsCategoriesWithChannels() { - assertEquals(listOf(ChannelCategory.All, ChannelCategory.Entertainment, ChannelCategory.Movies), homeCategories(listOf(101, 130, 120))) - assertEquals(listOf(ChannelCategory.All, ChannelCategory.Movies), homeCategories(listOf(130))) + assertEquals(listOf(ChannelCategory.All, ChannelCategory.Entertainment, ChannelCategory.Movies, ChannelCategory.Cartoons), homeCategories(listOf(101, 305, 601))) + assertEquals(listOf(ChannelCategory.All, ChannelCategory.Movies), homeCategories(listOf(305))) assertEquals(listOf(ChannelCategory.All), homeCategories(listOf(999))) } @Test fun aCategoryOpensOnlyItsOwnChannels() { - val dark = SampleChannel(listOf(media("a", 20), media("b", 20)), "Fixture", 130) - val vortex = SampleChannel(listOf(media("c", 20), media("d", 20)), "Fixture", 120) - assertEquals(listOf(130), channelsIn(ChannelCategory.Movies, listOf(dark, vortex)).map { it.number }) - assertEquals(listOf(130, 120), channelsIn(ChannelCategory.All, listOf(dark, vortex)).map { it.number }) + val dark = SampleChannel(listOf(media("a", 20), media("b", 20)), "Fixture", 305) + val vortex = SampleChannel(listOf(media("c", 20), media("d", 20)), "Fixture", 601) + assertEquals(listOf(305), channelsIn(ChannelCategory.Movies, listOf(dark, vortex)).map { it.number }) + assertEquals(listOf(305, 601), channelsIn(ChannelCategory.All, listOf(dark, vortex)).map { it.number }) } @Test diff --git a/android/app/src/test/java/tv/orbit/classic/RememberedStateTest.kt b/android/app/src/test/java/tv/orbit/classic/RememberedStateTest.kt index ef955ca..ee75956 100644 --- a/android/app/src/test/java/tv/orbit/classic/RememberedStateTest.kt +++ b/android/app/src/test/java/tv/orbit/classic/RememberedStateTest.kt @@ -10,9 +10,9 @@ class RememberedStateTest { private fun state(): RememberedState { val series = SeriesCandidate("fixture-series", "Fixture series", 1996, "TV-PG") val episodes = SeriesEpisodes(series, List(2) { EpisodeCandidate("episode-$it", "Fixture $it", 1, it + 1, 60_000) }, false) - val channel = SampleChannel(episodes.episodes.map { ScheduledMedia(episodeSampleItem(series, it), "source-${it.id}", 60_000) }, "Toonami", 120) + val channel = SampleChannel(episodes.episodes.map { ScheduledMedia(episodeSampleItem(series, it), "source-${it.id}", 60_000) }, "Toonami", 601) return RememberedState(RememberedIdentity("fixture-user", "fixture-token", "fixture-device"), EmbySample(emptyList(), 0), - listOf(SeriesMatches(ToonamiSearches.first(), listOf(series), false)), setOf(series.id), listOf(episodes), null, channel, 120) + listOf(SeriesMatches(ToonamiSearches.first(), listOf(series), false)), setOf(series.id), listOf(episodes), null, channel, 601) } private fun key() = KeyGenerator.getInstance("AES").apply { init(256) }.generateKey() @Test fun encryptedRoundTripPreservesChoicesAndAbsoluteTimeline() { @@ -23,7 +23,7 @@ class RememberedStateTest { val restored = RememberedStateCodec.decode(ConnectionCipher.open(key, sealed)) assertEquals(original.approved, restored.approved); assertEquals("fixture-token", restored.identity.token) assertEquals(original.toonami!!.guide(1_793_491_234_567L), restored.toonami!!.guide(1_793_491_234_567L)) - assertEquals(120, restored.selectedNumber); assertEquals(2, restored.episodes!!.single().episodes.size) + assertEquals(601, restored.selectedNumber); assertEquals(2, restored.episodes!!.single().episodes.size) assertFalse(restored.toString().contains("fixture-token")); assertFalse(restored.identity.toString().contains("fixture-token")) } @Test fun freshNoncesProduceDifferentCiphertextAndTamperingIsRejected() { @@ -46,7 +46,7 @@ class RememberedStateTest { } @Test fun expiredTokenCanBeRemovedWithoutLosingChannelChoices() { val noToken = RememberedStateCodec.decode(RememberedStateCodec.encode(state().withoutToken())) - assertNull(noToken.identity.token); assertEquals(120, noToken.toonami!!.number) + assertNull(noToken.identity.token); assertEquals(601, noToken.toonami!!.number) assertEquals(setOf("fixture-series"), noToken.approved) } @Test fun resumesByValidatingSavedUserWithoutAuthenticatingOrRefreshingSchedule() { diff --git a/android/app/src/test/java/tv/orbit/classic/ToonamiChannelTest.kt b/android/app/src/test/java/tv/orbit/classic/ToonamiChannelTest.kt index b97ae58..cb14448 100644 --- a/android/app/src/test/java/tv/orbit/classic/ToonamiChannelTest.kt +++ b/android/app/src/test/java/tv/orbit/classic/ToonamiChannelTest.kt @@ -22,7 +22,7 @@ class ToonamiChannelTest { } @Test fun fullFifteenEpisodeSampleUsesClockBoundariesAndRepeats() { val ordered = toonamiEpisodeOrder(listOf(group("a", 5), group("b", 5), group("c", 5))) - val channel = SampleChannel(ordered.map { ScheduledMedia(it, "${it.id}-source", 60_000) }, "Toonami", 120) + val channel = SampleChannel(ordered.map { ScheduledMedia(it, "${it.id}-source", 60_000) }, "Toonami", 601) assertEquals(15, channel.lineup.size); assertEquals("b-1", channel.at(60_000).media.item.id) assertEquals("a-1", channel.at(channel.cycleMs).media.item.id) assertEquals(12_345L, channel.at(72_345).offsetAt(72_345)) @@ -55,7 +55,7 @@ class ToonamiChannelTest { assertTrue(fake.requests.isEmpty()) session.checkSeriesEpisodes(listOf(group("a").series, group("b").series)) val channel = session.buildToonamiChannel() - assertEquals(120, channel.number); assertEquals("Toonami", channel.name) + assertEquals(601, channel.number); assertEquals("Toonami", channel.name) assertEquals(listOf("a-1", "b-1", "a-2", "b-2"), channel.lineup.map { it.item.id }) assertEquals(60_123L, channel.lineup.first().durationMs) val probes = fake.requests.filter { it.path.endsWith("/PlaybackInfo") } diff --git a/docs/roadmap.md b/docs/roadmap.md index dfed121..cfc9e69 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -100,7 +100,7 @@ Paul's ideas, 1 November 2026. Not fixes and not scheduled. ## Channel numbers -Done in 0.68: 101, 102 and 103 are Orbit 1, Orbit 2 and Orbit Three, as Paul asked. Vortex moved to 120 and After Dark to 130, at the head of their blocks. Other numbers are still placeholders. +Done in 0.69, as Paul asked: blocks like Sky, biggest channels first. 100s entertainment and comedy (101 Orbit 1, 102 Orbit 2, 103 Orbit Three), 200s factual, 300s films, 400s sport, 600s cartoons and kids. The full list is in VERIFICATION-0.69.0.md. ## Interface direction