Fix video fixture ownership and identify playback resource diagnostics

This commit is contained in:
evgeny committed 2026-10-29 13:34:25 +01:00
1 parent 8678ace89b
commit e18ecda41e
3 files changed
+9 -6

No files matched your search

@@ -41,6 +41,7 @@ internal class VideoFileMetadata : AutoCloseable {
try {
current.release(); retriever = null
main.removeCallbacks(retry); scheduled = false; retained.remove(this)
LogManager.addLog("DEBUG", "VideoPlayer", "Metadata reader released owner=${System.identityHashCode(this)}")
} catch (e: Exception) {
retained.add(this)
LogManager.addLog("ERROR", "VideoPlayer", "Metadata release failed; owner retained: $e")
@@ -20,6 +20,7 @@ internal data class VideoFilePlayback(val prepared: Boolean = false, val playing
internal class VideoFilePlayer(onCompleted: () -> Unit) : AutoCloseable {
companion object { private val retained = ConcurrentHashMap.newKeySet<VideoFilePlayer>() }
private val main = Handler(Looper.getMainLooper())
private val id = System.identityHashCode(this)
private val mutable = MutableStateFlow(VideoFilePlayback())
val state: StateFlow<VideoFilePlayback> = mutable
private var completion: (() -> Unit)? = onCompleted
@@ -45,7 +46,7 @@ internal class VideoFilePlayer(onCompleted: () -> Unit) : AutoCloseable {
if (!retiring && player === current) try {
current.setVideoScalingMode(MediaPlayer.VIDEO_SCALING_MODE_SCALE_TO_FIT)
mutable.value = mutable.value.copy(prepared = true, durationMs = current.duration.coerceAtLeast(0))
LogManager.addLog("INFO", "VideoPlayer", "Prepared ${current.videoWidth}x${current.videoHeight} durationMs=${current.duration}")
LogManager.addLog("INFO", "VideoPlayer", "Prepared owner=$id ${current.videoWidth}x${current.videoHeight} durationMs=${current.duration}")
if (wantsPlay && bound != null) play()
} catch (e: Exception) { fail("prepare callback", e) }
}
@@ -70,6 +71,7 @@ internal class VideoFilePlayer(onCompleted: () -> Unit) : AutoCloseable {
if (uiTargets.containsKey(texture)) return
val target = try { StreamPreviewTarget(texture) } catch (e: Exception) { fail("create Surface", e); return }
uiTargets[texture] = target
LogManager.addLog("DEBUG", "VideoPlayer", "Texture adopted owner=$id texture=${System.identityHashCode(texture)} disposed=$disposed")
if (disposed) { target.release(); return }
if (retiring) return // UI сохраняет собственную texture до destroy/close даже после ошибки player.
bound = texture
@@ -135,7 +137,7 @@ internal class VideoFilePlayer(onCompleted: () -> Unit) : AutoCloseable {
}
private fun fail(stage: String, error: Exception, message: Int = R.string.video_playback_failed) {
LogManager.addLog("ERROR", "VideoPlayer", "Operation failed stage=$stage; resources retained through release: $error")
LogManager.addLog("ERROR", "VideoPlayer", "Operation failed stage=$stage owner=$id; resources retained through release: $error")
retiring = true; wantsPlay = false; completion = null
mutable.value = mutable.value.copy(playing = false, prepared = false, error = message)
cleanup()
@@ -152,12 +154,12 @@ internal class VideoFilePlayer(onCompleted: () -> Unit) : AutoCloseable {
private fun releasePlayerTargets() { playerTargets.forEach { it.release() }; playerTargets.clear() }
private fun cleanup() {
if (player != null) try { player!!.release(); player = null } catch (e: Exception) {
LogManager.addLog("ERROR", "VideoPlayer", "MediaPlayer release failed; owner retained: $e")
LogManager.addLog("ERROR", "VideoPlayer", "MediaPlayer release failed owner=$id; retained: $e")
}
if (player == null) {
releasePlayerTargets()
main.removeCallbacks(retry); scheduled = false; retained.remove(this)
LogManager.addLog("INFO", "VideoPlayer", "Player released disposed=$disposed")
LogManager.addLog("INFO", "VideoPlayer", "Player released owner=$id disposed=$disposed")
} else {
retained.add(this)
if (!scheduled) {
@@ -33,7 +33,7 @@ class VideoFilePlayerTest {
assertTrue(MediaPlayer.created.isEmpty())
}
@Test fun surfaceBeforeStartIsAdoptedAndPlaybackWaitsForPrepared() {
val player = owner(); val texture = texture(); player.attach(texture()); assertTrue(MediaPlayer.created.isEmpty())
val player = owner(); val texture = texture(); player.attach(texture); assertTrue(MediaPlayer.created.isEmpty())
player.start("file.mp4"); assertNotNull(sdk.surface); assertFalse(player.state.value.playing)
sdk.preparedCallback!!(sdk)
assertTrue(player.state.value.playing); assertEquals(10_000, player.state.value.durationMs); assertEquals(1, sdk.starts)
@@ -89,7 +89,7 @@ class VideoFilePlayerTest {
}
@Test fun partialSurfaceBindFailureKeepsUiOutputAndUsesPlayerReleaseBarrier() {
val player = owner(); player.start("file.mp4"); sdk.failures = setOf("surface after", "release")
val texture = texture(); player.attach(texture()); player.close()
val texture = texture(); player.attach(texture); player.close()
assertFalse(texture.released)
sdk.failures = emptySet(); main.advanceBy(250)
assertTrue(texture.released); diagnostic("stage=attach Surface")