优化视频资源释放逻辑,避免主线程阻塞,添加追帧状态更新日志

This commit is contained in:
2026-02-21 20:43:18 +08:00
parent 6c8d5cce2f
commit 1d3fdac957
3 changed files with 70 additions and 21 deletions

Binary file not shown.

View File

@@ -151,16 +151,27 @@ class InteractiveLiveActivity : AppCompatActivity() {
override fun onDestroy() {
super.onDestroy()
rtcEngine?.setCaptureVideoFrameInterceptor(null)
leaveChannel()
InteractiveRtcEngine.destroy(rtcEngine)
rtcEngine = null
localRenderer?.let { releaseRenderer(it) }
remoteRendererMap.values.forEach { releaseRenderer(it) }
remoteRendererMap.clear()
fuFrameInterceptor = null
try { beautyRenderer?.release() } catch (_: Exception) {}
beautyRenderer = null
remoteMediaState.clear()
// 捕获需要释放的引用,避免主线程阻塞导致 ANR
val engine = rtcEngine
val local = localRenderer
val remotes = remoteRendererMap.values.toList()
val beauty = beautyRenderer
rtcEngine = null
localRenderer = null
remoteRendererMap.clear()
beautyRenderer = null
// 重量级资源释放移到后台线程
Thread {
try { engine?.leaveChannel() } catch (_: Exception) {}
try { InteractiveRtcEngine.destroy(engine) } catch (_: Exception) {}
try { local?.release() } catch (_: Exception) {}
remotes.forEach { try { it.release() } catch (_: Exception) {} }
try { beauty?.release() } catch (_: Exception) {}
}.start()
}
override fun onSupportNavigateUp(): Boolean {
@@ -685,19 +696,29 @@ class InteractiveLiveActivity : AppCompatActivity() {
slot.layout.detachRenderer()
updateSlotOverlay(slot)
}
rtcEngine?.clearRemoteVideo(userId)
remoteRendererMap.remove(userId)?.let { releaseRenderer(it) }
val engine = rtcEngine
val renderer = remoteRendererMap.remove(userId)
remoteStats.remove(userId)
// SurfaceViewRenderer.release() 会死锁主线程,移到后台
Thread {
try { engine?.clearRemoteVideo(userId) } catch (_: Exception) {}
try { renderer?.release() } catch (_: Exception) {}
}.start()
}
private fun resetVideoSlots(releaseRemotes: Boolean = true) {
if (releaseRemotes) {
val engine = rtcEngine
val remoteIds = remoteRendererMap.keys.toList()
remoteIds.forEach { userId ->
rtcEngine?.clearRemoteVideo(userId)
remoteRendererMap.remove(userId)?.let { releaseRenderer(it) }
}
val renderersToRelease = remoteIds.mapNotNull { remoteRendererMap.remove(it) }
remoteStats.clear()
// SurfaceViewRenderer.release() 会死锁主线程,移到后台
Thread {
remoteIds.forEach { userId ->
try { engine?.clearRemoteVideo(userId) } catch (_: Exception) {}
}
renderersToRelease.forEach { try { it.release() } catch (_: Exception) {} }
}.start()
}
remoteSlots.forEach { slot ->
slot.userId = null
@@ -722,7 +743,9 @@ class InteractiveLiveActivity : AppCompatActivity() {
private fun displayId(userId: String): String = userId
private fun leaveChannel() {
rtcEngine?.leaveChannel()
// SDK 的 leaveChannel() 会同步停止 Whip/Whep 客户端,阻塞主线程
val engine = rtcEngine
Thread { try { engine?.leaveChannel() } catch (_: Exception) {} }.start()
resetUiAfterLeave()
}

View File

@@ -81,6 +81,7 @@ class LivePlayActivity : AppCompatActivity() {
private var firstAudioFrameCostMs: Long? = null
private var isLatencyChasingActive: Boolean = false
private var lastLatencyChasingSpeed: Float? = null
private var lastLatencyChasingUpdate: SellyLatencyChasingUpdate? = null
private var hasReleasedPlayer: Boolean = false
private val logLines: ArrayDeque<String> = ArrayDeque()
@@ -157,29 +158,35 @@ class LivePlayActivity : AppCompatActivity() {
override fun onLatencyChasingUpdate(update: SellyLatencyChasingUpdate) {
runOnUiThread {
val speedRounded = kotlin.math.round(update.speed * 10f) / 10f
val speedText = formatLatencyChasingSpeed(speedRounded)
val chasingDetail = buildLatencyChasingDetail(update)
lastLatencyChasingUpdate = update
val isChasing = speedRounded > 1.0f
if (isChasing && !isLatencyChasingActive) {
isLatencyChasingActive = true
val speedText = String.format(Locale.US, "%.1f", speedRounded)
logEvent("追帧开始: 速度=${speedText}x")
logEvent("追帧开始: 速度=${speedText}x, $chasingDetail")
lastLatencyChasingSpeed = speedRounded
} else if (isChasing && isLatencyChasingActive) {
if (lastLatencyChasingSpeed == null || lastLatencyChasingSpeed != speedRounded) {
val speedText = String.format(Locale.US, "%.1f", speedRounded)
logEvent("追帧速率变化: 速度=${speedText}x")
logEvent("追帧速率变化: 速度=${speedText}x, $chasingDetail")
lastLatencyChasingSpeed = speedRounded
}
} else if (!isChasing && isLatencyChasingActive) {
isLatencyChasingActive = false
logEvent("追帧结束: 速度=1.0x")
logEvent("追帧结束: 速度=1.0x, $chasingDetail")
lastLatencyChasingSpeed = null
} else {
logEvent("追帧状态更新: 速度=${speedText}x, $chasingDetail")
}
}
}
override fun onLatencyChasingReloadRequired(latencyMs: Long) {
runOnUiThread {
logEvent("追帧触发重载: 延迟=${latencyMs}ms")
val lastUpdateDetail = lastLatencyChasingUpdate
?.let { ", 最近追帧: ${buildLatencyChasingDetail(it)}" }
.orEmpty()
logEvent("追帧触发重载: 延迟=${latencyMs}ms$lastUpdateDetail")
}
}
@@ -709,9 +716,27 @@ class LivePlayActivity : AppCompatActivity() {
firstAudioFrameCostMs = null
isLatencyChasingActive = false
lastLatencyChasingSpeed = null
lastLatencyChasingUpdate = null
logEvent("播放尝试开始")
}
private fun formatLatencyChasingSpeed(speed: Float): String {
return String.format(Locale.US, "%.1f", speed)
}
private fun buildLatencyChasingDetail(update: SellyLatencyChasingUpdate): String {
val cacheDeltaMs = update.audioCachedMs - update.videoCachedMs
val deltaText = if (cacheDeltaMs > 0L) "+$cacheDeltaMs" else cacheDeltaMs.toString()
val cacheSkewTag = when {
cacheDeltaMs >= CACHE_SKEW_WARNING_MS -> "音频缓存领先"
cacheDeltaMs <= -CACHE_SKEW_WARNING_MS -> "视频缓存领先"
else -> "音视频缓存接近"
}
return "延迟=${update.latencyMs}ms, 档位=${update.tier}, 缓冲中=${if (update.buffering) "是" else "否"}, " +
"首帧已出=${if (update.firstFrameRendered) "是" else "否"}, 音频缓存=${update.audioCachedMs}ms, " +
"视频缓存=${update.videoCachedMs}ms, 缓存差(音-视)=${deltaText}ms($cacheSkewTag)"
}
private fun formatState(state: SellyPlayerState): String {
return "${stateLabel(state)}(${state.name})"
}
@@ -744,6 +769,7 @@ class LivePlayActivity : AppCompatActivity() {
companion object {
private const val TAG = "LivePlayActivity"
private const val MAX_LOG_LINES = 200
private const val CACHE_SKEW_WARNING_MS = 300L
@Volatile
private var pipActivityRef: WeakReference<LivePlayActivity>? = null
const val EXTRA_PLAY_PROTOCOL = "play_protocol"