Stop reading live-stream stats while the SDK tears the stream down
Stopping a stream to an RTMP receiver crashed the app with SIGSEGV inside LiveStreamManager.stopStream (Mavic Pro, 2026-10-22). stopStream returns at once and frees the native streamer on an SDK thread, while the bridge kept reading bitrate/fps/time through native getters from its poll and right after the stop. The poll now ends first, nothing reads those getters while stopping, and the bridge waits for the SDK to report the stream stopped. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
727850bb0e
commit
128220dcb7
1 file changed
+27
-2
@@ -30,6 +30,13 @@ class LiveStreamBridge(private val ctx: BridgeCtx) : SubBridge {
|
||||
private var lastError: String? = null
|
||||
private var polling = false
|
||||
|
||||
/**
|
||||
* `stopStream` returns at once and frees the native streamer on an SDK
|
||||
* thread; reading the bitrate/fps/time getters meanwhile crashed the app
|
||||
* with SIGSEGV (Mavic Pro, 2026-10-22). While this is set, nothing reads them.
|
||||
*/
|
||||
@Volatile private var stopping = false
|
||||
|
||||
private val statusListener = LiveStreamManager.OnLiveChangeListener { status ->
|
||||
lastStatus = status
|
||||
ctx.mainHandler.post {
|
||||
@@ -91,7 +98,12 @@ class LiveStreamBridge(private val ctx: BridgeCtx) : SubBridge {
|
||||
lastError = null
|
||||
worker.execute {
|
||||
try {
|
||||
if (m.isStreaming) m.stopStream()
|
||||
if (m.isStreaming) {
|
||||
stopping = true
|
||||
m.stopStream()
|
||||
Thread.sleep(STOP_GRACE_MS)
|
||||
stopping = false
|
||||
}
|
||||
m.setLiveUrl(url)
|
||||
m.setAudioStreamingEnabled(audio)
|
||||
val code = m.startStream()
|
||||
@@ -113,14 +125,24 @@ class LiveStreamBridge(private val ctx: BridgeCtx) : SubBridge {
|
||||
|
||||
private fun stop(result: MethodChannel.Result) {
|
||||
val m = manager() ?: run { ctx.mainHandler.post { result.success(null) }; return }
|
||||
// Stop reading the native getters before the teardown starts.
|
||||
polling = false
|
||||
ctx.mainHandler.removeCallbacks(poll)
|
||||
stopping = true
|
||||
worker.execute {
|
||||
try {
|
||||
m.stopStream()
|
||||
} catch (_: Throwable) {
|
||||
// Already stopped.
|
||||
}
|
||||
// Give the SDK's own teardown time to finish, or until it says it stopped.
|
||||
var waited = 0L
|
||||
while (waited < STOP_SETTLE_MS && (try { m.isStreaming } catch (_: Throwable) { false })) {
|
||||
Thread.sleep(100); waited += 100
|
||||
}
|
||||
Thread.sleep(STOP_GRACE_MS)
|
||||
ctx.mainHandler.post {
|
||||
polling = false
|
||||
stopping = false
|
||||
publish()
|
||||
result.success(null)
|
||||
}
|
||||
@@ -139,6 +161,7 @@ class LiveStreamBridge(private val ctx: BridgeCtx) : SubBridge {
|
||||
}
|
||||
|
||||
private fun state(m: LiveStreamManager): Map<String, Any?> {
|
||||
if (stopping) return mapOf("streaming" to false, "status" to "STOPPED", "videoKbps" to 0, "audioKbps" to 0, "fps" to 0.0, "seconds" to 0L, "cacheSize" to 0, "error" to lastError)
|
||||
val streaming = try { m.isStreaming } catch (_: Throwable) { false }
|
||||
return mapOf(
|
||||
"streaming" to streaming,
|
||||
@@ -160,5 +183,7 @@ class LiveStreamBridge(private val ctx: BridgeCtx) : SubBridge {
|
||||
|
||||
private companion object {
|
||||
const val POLL_MS = 1000L
|
||||
const val STOP_SETTLE_MS = 3000L
|
||||
const val STOP_GRACE_MS = 500L
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user