package link.roadio.riff.userstate /** One publication deadline; repeated delivery and no-ops never call changed(). */ class DirtyCadence( private val softMs: Long = 5_000, private val hardMs: Long = 30_000, ) { private var first: Long? = null private var last: Long? = null init { require(softMs > 0 && hardMs >= softMs) } fun changed(now: Long) { require(now >= 0) if (first == null) first = now last = now } fun deadline(): Long? = first?.let { minOf(Math.addExact(it, hardMs), Math.addExact(checkNotNull(last), softMs)) } fun due(now: Long): Boolean = deadline()?.let { now >= it } ?: false fun clear() { first = null last = null } fun copy(): DirtyCadence = DirtyCadence(softMs, hardMs).also { it.first = first it.last = last } } /** Accumulates actual playing time; seeking, pauses and buffering contribute nothing. */ class StartQualification( private val thresholdMs: Long, ) { private var last: Long? = null private var elapsed = 0L private var wasPlaying = false var qualified: Boolean = false private set init { require(thresholdMs > 0) } fun observe( now: Long, playing: Boolean, ): Boolean { val previous = last if (previous != null) { require(now >= previous) if (wasPlaying) elapsed = Math.addExact(elapsed, now - previous) } last = now wasPlaying = playing if (!qualified && elapsed > thresholdMs) { qualified = true return true } return false } }