优化视频资源释放逻辑,避免主线程阻塞,添加追帧状态更新日志
This commit is contained in:
Binary file not shown.
@@ -151,16 +151,27 @@ class InteractiveLiveActivity : AppCompatActivity() {
|
|||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
rtcEngine?.setCaptureVideoFrameInterceptor(null)
|
rtcEngine?.setCaptureVideoFrameInterceptor(null)
|
||||||
leaveChannel()
|
|
||||||
InteractiveRtcEngine.destroy(rtcEngine)
|
|
||||||
rtcEngine = null
|
|
||||||
localRenderer?.let { releaseRenderer(it) }
|
|
||||||
remoteRendererMap.values.forEach { releaseRenderer(it) }
|
|
||||||
remoteRendererMap.clear()
|
|
||||||
fuFrameInterceptor = null
|
fuFrameInterceptor = null
|
||||||
try { beautyRenderer?.release() } catch (_: Exception) {}
|
|
||||||
beautyRenderer = null
|
|
||||||
remoteMediaState.clear()
|
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 {
|
override fun onSupportNavigateUp(): Boolean {
|
||||||
@@ -685,19 +696,29 @@ class InteractiveLiveActivity : AppCompatActivity() {
|
|||||||
slot.layout.detachRenderer()
|
slot.layout.detachRenderer()
|
||||||
updateSlotOverlay(slot)
|
updateSlotOverlay(slot)
|
||||||
}
|
}
|
||||||
rtcEngine?.clearRemoteVideo(userId)
|
val engine = rtcEngine
|
||||||
remoteRendererMap.remove(userId)?.let { releaseRenderer(it) }
|
val renderer = remoteRendererMap.remove(userId)
|
||||||
remoteStats.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) {
|
private fun resetVideoSlots(releaseRemotes: Boolean = true) {
|
||||||
if (releaseRemotes) {
|
if (releaseRemotes) {
|
||||||
|
val engine = rtcEngine
|
||||||
val remoteIds = remoteRendererMap.keys.toList()
|
val remoteIds = remoteRendererMap.keys.toList()
|
||||||
remoteIds.forEach { userId ->
|
val renderersToRelease = remoteIds.mapNotNull { remoteRendererMap.remove(it) }
|
||||||
rtcEngine?.clearRemoteVideo(userId)
|
|
||||||
remoteRendererMap.remove(userId)?.let { releaseRenderer(it) }
|
|
||||||
}
|
|
||||||
remoteStats.clear()
|
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 ->
|
remoteSlots.forEach { slot ->
|
||||||
slot.userId = null
|
slot.userId = null
|
||||||
@@ -722,7 +743,9 @@ class InteractiveLiveActivity : AppCompatActivity() {
|
|||||||
private fun displayId(userId: String): String = userId
|
private fun displayId(userId: String): String = userId
|
||||||
|
|
||||||
private fun leaveChannel() {
|
private fun leaveChannel() {
|
||||||
rtcEngine?.leaveChannel()
|
// SDK 的 leaveChannel() 会同步停止 Whip/Whep 客户端,阻塞主线程
|
||||||
|
val engine = rtcEngine
|
||||||
|
Thread { try { engine?.leaveChannel() } catch (_: Exception) {} }.start()
|
||||||
resetUiAfterLeave()
|
resetUiAfterLeave()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ class LivePlayActivity : AppCompatActivity() {
|
|||||||
private var firstAudioFrameCostMs: Long? = null
|
private var firstAudioFrameCostMs: Long? = null
|
||||||
private var isLatencyChasingActive: Boolean = false
|
private var isLatencyChasingActive: Boolean = false
|
||||||
private var lastLatencyChasingSpeed: Float? = null
|
private var lastLatencyChasingSpeed: Float? = null
|
||||||
|
private var lastLatencyChasingUpdate: SellyLatencyChasingUpdate? = null
|
||||||
private var hasReleasedPlayer: Boolean = false
|
private var hasReleasedPlayer: Boolean = false
|
||||||
|
|
||||||
private val logLines: ArrayDeque<String> = ArrayDeque()
|
private val logLines: ArrayDeque<String> = ArrayDeque()
|
||||||
@@ -157,29 +158,35 @@ class LivePlayActivity : AppCompatActivity() {
|
|||||||
override fun onLatencyChasingUpdate(update: SellyLatencyChasingUpdate) {
|
override fun onLatencyChasingUpdate(update: SellyLatencyChasingUpdate) {
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
val speedRounded = kotlin.math.round(update.speed * 10f) / 10f
|
val speedRounded = kotlin.math.round(update.speed * 10f) / 10f
|
||||||
|
val speedText = formatLatencyChasingSpeed(speedRounded)
|
||||||
|
val chasingDetail = buildLatencyChasingDetail(update)
|
||||||
|
lastLatencyChasingUpdate = update
|
||||||
val isChasing = speedRounded > 1.0f
|
val isChasing = speedRounded > 1.0f
|
||||||
if (isChasing && !isLatencyChasingActive) {
|
if (isChasing && !isLatencyChasingActive) {
|
||||||
isLatencyChasingActive = true
|
isLatencyChasingActive = true
|
||||||
val speedText = String.format(Locale.US, "%.1f", speedRounded)
|
logEvent("追帧开始: 速度=${speedText}x, $chasingDetail")
|
||||||
logEvent("追帧开始: 速度=${speedText}x")
|
|
||||||
lastLatencyChasingSpeed = speedRounded
|
lastLatencyChasingSpeed = speedRounded
|
||||||
} else if (isChasing && isLatencyChasingActive) {
|
} else if (isChasing && isLatencyChasingActive) {
|
||||||
if (lastLatencyChasingSpeed == null || lastLatencyChasingSpeed != speedRounded) {
|
if (lastLatencyChasingSpeed == null || lastLatencyChasingSpeed != speedRounded) {
|
||||||
val speedText = String.format(Locale.US, "%.1f", speedRounded)
|
logEvent("追帧速率变化: 速度=${speedText}x, $chasingDetail")
|
||||||
logEvent("追帧速率变化: 速度=${speedText}x")
|
|
||||||
lastLatencyChasingSpeed = speedRounded
|
lastLatencyChasingSpeed = speedRounded
|
||||||
}
|
}
|
||||||
} else if (!isChasing && isLatencyChasingActive) {
|
} else if (!isChasing && isLatencyChasingActive) {
|
||||||
isLatencyChasingActive = false
|
isLatencyChasingActive = false
|
||||||
logEvent("追帧结束: 速度=1.0x")
|
logEvent("追帧结束: 速度=1.0x, $chasingDetail")
|
||||||
lastLatencyChasingSpeed = null
|
lastLatencyChasingSpeed = null
|
||||||
|
} else {
|
||||||
|
logEvent("追帧状态更新: 速度=${speedText}x, $chasingDetail")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onLatencyChasingReloadRequired(latencyMs: Long) {
|
override fun onLatencyChasingReloadRequired(latencyMs: Long) {
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
logEvent("追帧触发重载: 延迟=${latencyMs}ms")
|
val lastUpdateDetail = lastLatencyChasingUpdate
|
||||||
|
?.let { ", 最近追帧: ${buildLatencyChasingDetail(it)}" }
|
||||||
|
.orEmpty()
|
||||||
|
logEvent("追帧触发重载: 延迟=${latencyMs}ms$lastUpdateDetail")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -709,9 +716,27 @@ class LivePlayActivity : AppCompatActivity() {
|
|||||||
firstAudioFrameCostMs = null
|
firstAudioFrameCostMs = null
|
||||||
isLatencyChasingActive = false
|
isLatencyChasingActive = false
|
||||||
lastLatencyChasingSpeed = null
|
lastLatencyChasingSpeed = null
|
||||||
|
lastLatencyChasingUpdate = null
|
||||||
logEvent("播放尝试开始")
|
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 {
|
private fun formatState(state: SellyPlayerState): String {
|
||||||
return "${stateLabel(state)}(${state.name})"
|
return "${stateLabel(state)}(${state.name})"
|
||||||
}
|
}
|
||||||
@@ -744,6 +769,7 @@ class LivePlayActivity : AppCompatActivity() {
|
|||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "LivePlayActivity"
|
private const val TAG = "LivePlayActivity"
|
||||||
private const val MAX_LOG_LINES = 200
|
private const val MAX_LOG_LINES = 200
|
||||||
|
private const val CACHE_SKEW_WARNING_MS = 300L
|
||||||
@Volatile
|
@Volatile
|
||||||
private var pipActivityRef: WeakReference<LivePlayActivity>? = null
|
private var pipActivityRef: WeakReference<LivePlayActivity>? = null
|
||||||
const val EXTRA_PLAY_PROTOCOL = "play_protocol"
|
const val EXTRA_PLAY_PROTOCOL = "play_protocol"
|
||||||
|
|||||||
Reference in New Issue
Block a user