diff --git a/android/app/src/main/java/com/fabledsword/minstrel/player/output/OutputPickerController.kt b/android/app/src/main/java/com/fabledsword/minstrel/player/output/OutputPickerController.kt index 382bd1c4..69119c51 100644 --- a/android/app/src/main/java/com/fabledsword/minstrel/player/output/OutputPickerController.kt +++ b/android/app/src/main/java/com/fabledsword/minstrel/player/output/OutputPickerController.kt @@ -14,6 +14,7 @@ import com.fabledsword.minstrel.player.StreamTokenProvider import com.fabledsword.minstrel.player.output.upnp.AVTransportClient import com.fabledsword.minstrel.player.output.upnp.RenderingControlClient import com.fabledsword.minstrel.player.output.upnp.SoapClient +import com.fabledsword.minstrel.player.output.upnp.SoapFaultException import com.fabledsword.minstrel.player.output.upnp.UpnpDiscoveryController import com.fabledsword.minstrel.player.output.upnp.bareUdn import dagger.hilt.android.qualifiers.ApplicationContext @@ -350,12 +351,14 @@ class OutputPickerController @Inject constructor( "UPnP extend: appending %d tracks starting at position %d", tracks.size, startPosition + 1, ) - tracks.forEachIndexed { i, ref -> + var consecutiveFailures = 0 + var succeeded = 0 + for ((i, ref) in tracks.withIndex()) { if (activeUpnpHolder.active.value?.routeId != route.id) { Timber.w("UPnP extend: cancelled at offset %d (route changed)", i) - return + break } - runCatching { + val outcome = runCatching { val token = streamTokens.mint(ref.id) transport.addURIToQueue( uri = token.url, @@ -363,9 +366,27 @@ class OutputPickerController @Inject constructor( title = token.title, enqueuedURIPosition = startPosition + i + 1, ) - }.onFailure { Timber.w(it, "UPnP extend: append failed at offset %d", i) } + } + if (outcome.isSuccess) { + consecutiveFailures = 0 + succeeded += 1 + } else { + consecutiveFailures += 1 + val e = outcome.exceptionOrNull() + val detail = (e as? SoapFaultException)?.let { + "code=${it.code} desc=${it.description}" + } ?: e?.message + Timber.w(e, "UPnP extend: append failed at offset %d -- %s", i, detail) + if (consecutiveFailures >= EXTEND_ABORT_AFTER_FAILURES) { + Timber.w( + "UPnP extend: aborting after %d consecutive failures", + consecutiveFailures, + ) + break + } + } } - Timber.w("UPnP extend: done (%d tracks appended)", tracks.size) + Timber.w("UPnP extend: done (%d / %d appended)", succeeded, tracks.size) } private fun renderingClientFor(routeId: String): RenderingControlClient? { @@ -396,4 +417,8 @@ class OutputPickerController @Inject constructor( val (builtIn, rest) = all.partition { it.kind == OutputRoute.Kind.BuiltIn } return builtIn + rest.sortedBy { it.name.lowercase() } } + + private companion object { + const val EXTEND_ABORT_AFTER_FAILURES = 3 + } }