0.81.4: Drive-In days turn over at midnight, lineup soonest first, changeovers in playback notes
pauljd/davidson-ci: Davidson CI / quality (push) Failing after 3m33s
pauljd/davidson-ci: Davidson CI / ai-review (push) Failing after 1m44s

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-11-03 08:35:07 +00:00
co-authored by Claude Opus 5.5
parent 83f83590e9
commit 99ffebfe47
7 changed files with 33 additions and 8 deletions
+8
View File
@@ -0,0 +1,8 @@
# Orbit TV 0.81.4: Drive-In days and order
- Drive-In day names turn over at midnight. After 00:00, today's 20:00 bill says Tonight, not Tomorrow. A late show still belongs to the evening it starts on.
- The Drive-In lineup lists events soonest first, so both of tonight's come before Friday and Saturday.
- Playback notes now record each changeover, and whether an ident was shown, so a missed ident can be explained.
- Unit tests: 309 passed, 0 failed. Lint passed.
Not yet checked on the box. It refused adb this morning, so wireless debugging is probably off after a restart.
+2 -2
View File
@@ -9,8 +9,8 @@ android {
applicationId = "tv.orbit.classic"
minSdk = 26
targetSdk = 36
versionCode = 94
versionName = "0.81.3-audio-choice"
versionCode = 95
versionName = "0.81.4-drive-in-days"
}
buildFeatures { compose = true }
compileOptions { sourceCompatibility = JavaVersion.VERSION_17; targetCompatibility = JavaVersion.VERSION_17 }
@@ -165,16 +165,17 @@ internal fun driveInBills(plan: ChannelPlan, zone: ZoneId = ZoneId.systemDefault
}
}
/** The next of each kind, for the list on screen. */
/** The next of each kind, for the list on screen, soonest first. */
internal fun driveInLineup(bills: List<DriveInBill>, nowMs: Long): List<DriveInBill> =
DriveInKind.values().mapNotNull { kind -> bills.firstOrNull { it.kind == kind && it.endMs > nowMs } }
DriveInKind.values().mapNotNull { kind -> bills.firstOrNull { it.kind == kind && it.endMs > nowMs } }.sortedBy { it.startMs }
internal fun driveInAiring(plan: ChannelPlan, nowMs: Long) = plan.entries.any { it.startMs <= nowMs && nowMs < it.endMs }
internal fun driveInDay(bill: DriveInBill, nowMs: Long, zone: ZoneId = ZoneId.systemDefault()): String {
if (bill.startMs <= nowMs) return "Now showing"
// A late show belongs to the evening it starts on. Today turns over at midnight, as it does on a clock.
val date = eveningOf(bill.startMs, zone)
val today = eveningOf(nowMs, zone)
val today = Instant.ofEpochMilli(nowMs).atZone(zone).toLocalDate()
return when (date) {
today -> "Tonight"
today.plusDays(1) -> "Tomorrow"
@@ -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.81.3\"")
connection.setRequestProperty("X-Emby-Authorization", "Emby Client=\"Orbit TV\", Device=\"Orbit TV\", DeviceId=\"$deviceId\", Version=\"0.81.4\"")
request.token?.let { connection.setRequestProperty("X-Emby-Token", it) }
if (request.method == "POST") {
val bytes = request.body.orEmpty().toByteArray(Charsets.UTF_8)
@@ -57,9 +57,17 @@ internal fun ScheduledChannelPlayer(session: EmbySampleSession, channel: SampleC
val identsOn = remember { OrbitSettings(settingsContext).idents }
val identsHere = channel is ScheduledChannel && clockOffsetMs == 0L && channel.number != DRIVE_IN_NUMBER && next.startMs == current.endMs && (identsOn || channel.number == TOTS_NUMBER)
val phase = if (identsHere) identPhase(current.startMs, current.endMs, now, identMs) else IdentPhase.None
// Each changeover in the playback notes, so a missing ident can be explained afterwards.
val notes = remember { java.io.File(settingsContext.noBackupFilesDir, "schedule") }
LaunchedEffect(channel, current.startMs) {
PlaybackNotes.add(notes, "channel=${channel.number} programme start=${current.startMs} lengthMs=${current.endMs - current.startMs} nextFollows=${next.startMs == current.endMs} scheduled=${channel is ScheduledChannel} offset=$clockOffsetMs identsOn=$identsOn")
}
val playerContext = androidx.compose.ui.platform.LocalContext.current
var loaded by remember(channel) { mutableStateOf<LoadedNext?>(null) }
DisposableEffect(channel) { onDispose { loaded?.discard(session) } }
LaunchedEffect(channel, current.startMs, phase == IdentPhase.Showing) {
if (phase == IdentPhase.Showing) PlaybackNotes.add(notes, "channel=${channel.number} ident shown next=${next.startMs} loaded=${loaded?.startMs == next.startMs}")
}
// A problem here only means the next show tunes in the ordinary way.
LaunchedEffect(channel, current.startMs, phase == IdentPhase.None) {
if (phase == IdentPhase.None || loaded?.startMs == next.startMs) return@LaunchedEffect
@@ -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.81.3")
next.setRequestProperty("User-Agent", "OrbitTV/0.81.4")
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()) {
@@ -78,7 +78,15 @@ class DriveInTest {
@Test fun theLineupIsTheNextOfEachKind() {
val plan = planDriveIn(null, big, late, bills, at(2, 10), london)!!
val lineup = driveInLineup(driveInBills(plan, london), at(2, 10))
assertEquals(DriveInKind.values().toList(), lineup.map { it.kind })
assertEquals(DriveInKind.values().toSet(), lineup.map { it.kind }.toSet())
assertEquals(lineup.sortedBy { it.startMs }, lineup)
}
@Test fun afterMidnightTheNewDayIsTonight() {
val plan = planDriveIn(null, big, late, bills, at(2, 10), london)!!
val tuesday = driveInBills(plan, london).first { it.startMs >= at(3, 6) }
assertEquals("Tonight", driveInDay(tuesday, at(3, 0, 30), london))
assertEquals("Tomorrow", driveInDay(tuesday, at(2, 23, 30), london))
}
@Test fun dayNamesCountTheSmallHoursAsTheNightBefore() {