From d7b011e52fd3b32e01f3e3ce2a8e833361dc4219 Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Sat, 6 Jun 2026 17:27:52 -0400 Subject: [PATCH] feat(android): pure idle-revert decision for UPnP output --- .../player/output/IdleRevertDecision.kt | 21 +++++++ .../player/output/IdleRevertDecisionTest.kt | 55 +++++++++++++++++++ 2 files changed, 76 insertions(+) create mode 100644 android/app/src/main/java/com/fabledsword/minstrel/player/output/IdleRevertDecision.kt create mode 100644 android/app/src/test/java/com/fabledsword/minstrel/player/output/IdleRevertDecisionTest.kt diff --git a/android/app/src/main/java/com/fabledsword/minstrel/player/output/IdleRevertDecision.kt b/android/app/src/main/java/com/fabledsword/minstrel/player/output/IdleRevertDecision.kt new file mode 100644 index 00000000..07d5f341 --- /dev/null +++ b/android/app/src/main/java/com/fabledsword/minstrel/player/output/IdleRevertDecision.kt @@ -0,0 +1,21 @@ +package com.fabledsword.minstrel.player.output + +/** Action the idle-revert collector should take on a player-state change. */ +internal enum class IdleRevertAction { ARM, CANCEL, IGNORE } + +/** + * Pure decision for the UPnP idle-revert timer. [armed] is whether the + * idle timer Job is currently running. We ARM only on the transition INTO + * "engaged + not playing" (so repeated paused emissions don't reset the + * countdown), CANCEL whenever playback resumes or UPnP disengages, and + * otherwise IGNORE. + */ +internal fun idleRevertAction( + upnpEngaged: Boolean, + isPlaying: Boolean, + armed: Boolean, +): IdleRevertAction = when { + upnpEngaged && !isPlaying -> if (armed) IdleRevertAction.IGNORE else IdleRevertAction.ARM + armed -> IdleRevertAction.CANCEL + else -> IdleRevertAction.IGNORE +} diff --git a/android/app/src/test/java/com/fabledsword/minstrel/player/output/IdleRevertDecisionTest.kt b/android/app/src/test/java/com/fabledsword/minstrel/player/output/IdleRevertDecisionTest.kt new file mode 100644 index 00000000..8aa91a05 --- /dev/null +++ b/android/app/src/test/java/com/fabledsword/minstrel/player/output/IdleRevertDecisionTest.kt @@ -0,0 +1,55 @@ +package com.fabledsword.minstrel.player.output + +import org.junit.jupiter.api.Test +import kotlin.test.assertEquals + +class IdleRevertDecisionTest { + + @Test + fun `engaged and paused and not armed arms the timer`() { + assertEquals( + IdleRevertAction.ARM, + idleRevertAction(upnpEngaged = true, isPlaying = false, armed = false), + ) + } + + @Test + fun `engaged and paused and already armed is ignored`() { + assertEquals( + IdleRevertAction.IGNORE, + idleRevertAction(upnpEngaged = true, isPlaying = false, armed = true), + ) + } + + @Test + fun `resuming playback cancels an armed timer`() { + assertEquals( + IdleRevertAction.CANCEL, + idleRevertAction(upnpEngaged = true, isPlaying = true, armed = true), + ) + } + + @Test + fun `playing with no timer armed is ignored`() { + assertEquals( + IdleRevertAction.IGNORE, + idleRevertAction(upnpEngaged = true, isPlaying = true, armed = false), + ) + } + + @Test + fun `disengaging UPnP cancels an armed timer`() { + assertEquals( + IdleRevertAction.CANCEL, + idleRevertAction(upnpEngaged = false, isPlaying = false, armed = true), + ) + } + + @Test + fun `disengaged with no timer armed is ignored`() { + assertEquals( + IdleRevertAction.IGNORE, + idleRevertAction(upnpEngaged = false, isPlaying = true, armed = false), + ) + } +}