feat(android): RemotePlayerState container for UPnP-synthesized player state
android / Build + lint + test (push) Failing after 1m28s

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-03 17:08:04 -04:00
parent 9a7d3b2d30
commit 3aee2276bc
2 changed files with 131 additions and 0 deletions
@@ -0,0 +1,65 @@
package com.fabledsword.minstrel.player
/**
* Synthesized state for the UPnP route -- what the ForwardingPlayer
* exposes via Player.getCurrentPosition / isPlaying / etc. when the
* remote leg is active. Not a Player; a container.
*
* Updates flow in from:
* - 1Hz GetPositionInfo poll -> applyPositionInfo()
* - Transport SOAP calls landing 200 OK -> applyTransport{Playing,Paused,Stopped}()
* - Error paths -> applyError() (drop fallback) or recordPollFailure()
*
* The poll-failure counter implements the rolling-3 drop heuristic: 3
* consecutive poll failures = remote considered dropped (returns true
* from recordPollFailure for the caller to surface). Success resets it.
*/
class RemotePlayerState {
@Volatile var positionMs: Long = 0L; private set
@Volatile var durationMs: Long = 0L; private set
@Volatile var isPlaying: Boolean = false; private set
@Volatile var currentTrackUri: String = ""; private set
@Volatile var lastError: Throwable? = null; private set
private var consecutivePollFailures: Int = 0
fun applyPositionInfo(positionMs: Long, durationMs: Long, trackUri: String) {
this.positionMs = positionMs
this.durationMs = durationMs
this.currentTrackUri = trackUri
}
fun applyTransportPlaying() { isPlaying = true }
fun applyTransportPaused() { isPlaying = false }
fun applyTransportStopped() {
isPlaying = false
positionMs = 0L
}
fun applyError(t: Throwable) {
isPlaying = false
lastError = t
}
/** Returns true when the rolling threshold trips this call. */
fun recordPollFailure(): Boolean {
consecutivePollFailures += 1
return consecutivePollFailures >= DROP_THRESHOLD
}
fun recordPollSuccess() { consecutivePollFailures = 0 }
fun reset() {
positionMs = 0L
durationMs = 0L
isPlaying = false
currentTrackUri = ""
lastError = null
consecutivePollFailures = 0
}
private companion object {
const val DROP_THRESHOLD = 3
}
}