fix(nests): T12 carry audio group sequence across hot-swaps
Every JWT-refresh hot-swap reset the audio publisher's group sequence
counter to 0, because `PublisherStateImpl` always initialised
`nextSequence = 0L`. kixelated/hang's `Container.Consumer.#run`
discards any consumer with `sequence < this.#active`, so a watcher
whose `#active` had advanced to e.g. 50 from the previous session's
publisher would silently drop every group on the new session until
either `#active` rolls over or the watcher re-subscribes — audible as
"speaker goes silent for a minute every 9 minutes" (the proactive
JWT refresh window).
Carry the sequence forward end-to-end:
- MoqLiteSession.publish gains a `startSequence: Long = 0L`
parameter; PublisherStateImpl seeds `nextSequenceField` from it
instead of hard-coding 0.
- MoqLitePublisherHandle exposes a `nextSequence: Long` snapshot.
`@Volatile`-backed so the hot-swap caller can read it without
contending the publisher's gate; race-window between read and
a concurrent send is sub-millisecond and would only produce a
single duplicate sequence in the very unlikely overlap, which
listeners tolerate.
- HotSwappablePublisherSource.openPublisherForHotSwap takes
`startSequence: Long = 0L`. MoqLiteNestsSpeaker passes it
through to session.publish.
- NestMoqLiteBroadcaster exposes its current publisher via a
read-only `currentPublisher` so the hot-swap pump in
ReconnectingNestsSpeaker.runHotSwapIteration can read its
`nextSequence` before opening the replacement publisher and
pass it as the seed.
- Catalog publisher keeps `startSequence = 0L` (the default) —
catalog isn't subject to the same `#active` accumulation
because each new SUBSCRIBE triggers a fresh emit-on-subscribe
write that resets the watcher's catalog state.
Listener-side change is none — the watcher already reads whatever
sequence we send. A new MoqLiteSessionTest pins the contract:
publishing with startSequence=42 makes the first uni stream's
GroupHeader.sequence == 42 and advances to 43 after the first send.
https://claude.ai/code/session_014JfZJHSTvyYYWJbC9VbB47
This commit is contained in:
+22
-2
@@ -190,7 +190,15 @@ class MoqLiteNestsSpeaker internal constructor(
|
||||
* reconnect wrapper's hot-swap path; not called from the
|
||||
* non-reconnecting path which goes through [startBroadcasting].
|
||||
*/
|
||||
override suspend fun openPublisherForHotSwap(track: String): MoqLitePublisherHandle = session.publish(broadcastSuffix = speakerPubkeyHex, track = track)
|
||||
override suspend fun openPublisherForHotSwap(
|
||||
track: String,
|
||||
startSequence: Long,
|
||||
): MoqLitePublisherHandle =
|
||||
session.publish(
|
||||
broadcastSuffix = speakerPubkeyHex,
|
||||
track = track,
|
||||
startSequence = startSequence,
|
||||
)
|
||||
|
||||
/**
|
||||
* Compare-and-clear that runs from inside [close] (already holds
|
||||
@@ -348,8 +356,20 @@ internal interface HotSwappablePublisherSource {
|
||||
* session. Caller owns the returned handle's lifetime (typically
|
||||
* via [com.vitorpamplona.nestsclient.audio.NestMoqLiteBroadcaster.swapPublisher]'s
|
||||
* close-the-old contract).
|
||||
*
|
||||
* @param startSequence first group sequence the new publisher will
|
||||
* assign. Used by the hot-swap path to seed the new session's
|
||||
* audio track with the previous session's
|
||||
* [MoqLitePublisherHandle.nextSequence] so kixelated/hang's
|
||||
* `Container.Consumer.#run` doesn't drop every post-recycle
|
||||
* group as `sequence < #active`. Pass `0L` for fresh (non-
|
||||
* continuation) publishers — the catalog track is one such
|
||||
* case, since its `#active` semantics are different from audio.
|
||||
*/
|
||||
suspend fun openPublisherForHotSwap(track: String): MoqLitePublisherHandle
|
||||
suspend fun openPublisherForHotSwap(
|
||||
track: String,
|
||||
startSequence: Long = 0L,
|
||||
): MoqLitePublisherHandle
|
||||
|
||||
/**
|
||||
* Surface a broadcast-pipeline terminal failure (e.g. sustained
|
||||
|
||||
Reference in New Issue
Block a user