feat(android): SetNextAVTransportURI pre-queue for gap-free UPnP advance
android / Build + lint + test (push) Failing after 1m35s
android / Build + lint + test (push) Failing after 1m35s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -52,6 +52,15 @@ class MinstrelForwardingPlayer(
|
||||
scope.launch {
|
||||
holder.active.collect { active -> onActiveChanged(active) }
|
||||
}
|
||||
delegate.addListener(object : Player.Listener {
|
||||
override fun onMediaItemTransition(
|
||||
mediaItem: androidx.media3.common.MediaItem?,
|
||||
reason: Int,
|
||||
) {
|
||||
val active = holder.active.value ?: return
|
||||
scope.launch { preQueueNext(active) }
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun isRemote(): Boolean = holder.active.value != null
|
||||
@@ -159,6 +168,34 @@ class MinstrelForwardingPlayer(
|
||||
}.onFailure { handleSoapFailure(active, it) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Speculative pre-queue of the next track so Sonos can advance gap-free.
|
||||
* Failures (token mint OR SetNextAVTransportURI rejection) silently fall
|
||||
* back to the poll-driven advance path -- the pollLoop detects STOPPED +
|
||||
* 0:00 RelTime and calls syncCurrentItemToRemote on whatever the new
|
||||
* currentMediaItem is. The only consequence of failure here is a sub-second
|
||||
* audible gap at track boundary, not a broken route.
|
||||
*/
|
||||
private suspend fun preQueueNext(active: ActiveUpnp) {
|
||||
val nextMediaId = handlerEvaluate { nextItemMediaId() } ?: return
|
||||
val token = runCatching { tokens.mint(nextMediaId) }.getOrNull() ?: return
|
||||
runCatching {
|
||||
active.avTransport.setNextAVTransportURI(
|
||||
uri = token.url,
|
||||
mime = token.mime,
|
||||
title = token.title,
|
||||
)
|
||||
}.onFailure {
|
||||
Timber.w(it, "SetNextAVTransportURI failed (poll-driven advance will fall back)")
|
||||
}
|
||||
}
|
||||
|
||||
private fun nextItemMediaId(): String? {
|
||||
val nextIdx = delegate.nextMediaItemIndex
|
||||
if (nextIdx == androidx.media3.common.C.INDEX_UNSET) return null
|
||||
return delegate.getMediaItemAt(nextIdx).mediaId
|
||||
}
|
||||
|
||||
private fun handleSoapFailure(active: ActiveUpnp, t: Throwable) {
|
||||
pollJob?.cancel()
|
||||
Timber.w(t, "UPnP transport call failed on %s", active.routeName)
|
||||
|
||||
Reference in New Issue
Block a user