Fix video fixture ownership and identify playback resource diagnostics
This commit is contained in:
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) {
|
||||
|
||||
+2
-2
@@ -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")
|
||||
|
||||
Reference in new issue
Block a user