Skip to content

Commit ec57ac2

Browse files
committed
Fix Linux video player: pause release handling and aspect ratio scaling
- Fix seek in paused state: add else branch to update frame and clear loading state - Fix aspect ratio: compute output dimensions that preserve video ratio instead of stretching to surface dimensions - Fix dispose race condition: call nDisposePlayer synchronously to stop GStreamer before cancelling ioScope (fixes audio continuing after release)
1 parent 90d925f commit ec57ac2

1 file changed

Lines changed: 40 additions & 25 deletions

File tree

mediaplayer/src/jvmMain/kotlin/io/github/kdroidfilter/composemediaplayer/linux/LinuxVideoPlayerState.kt

Lines changed: 40 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -684,6 +684,12 @@ class LinuxVideoPlayerState : VideoPlayerState {
684684
withContext(Dispatchers.Main) { isLoading = false }
685685
}
686686
}
687+
} else {
688+
delay(50)
689+
updateFrameAsync()
690+
seekInProgress = false
691+
targetSeekTime = null
692+
withContext(Dispatchers.Main) { isLoading = false }
687693
}
688694
} catch (e: Exception) {
689695
if (e is CancellationException) throw e
@@ -712,32 +718,25 @@ class LinuxVideoPlayerState : VideoPlayerState {
712718
uiUpdateJob?.cancel()
713719
playerScope.cancel()
714720

715-
ioScope.launch {
716-
val ptrToDispose =
717-
withContext(frameDispatcher) {
718-
val ptr = playerPtrAtomic.getAndSet(0L)
719-
720-
skiaBitmapA?.close()
721-
skiaBitmapB?.close()
722-
skiaBitmapA = null
723-
skiaBitmapB = null
724-
skiaBitmapWidth = 0
725-
skiaBitmapHeight = 0
726-
nextSkiaBitmapA = true
727-
728-
ptr
729-
}
721+
// Dispose the native player synchronously to guarantee cleanup before
722+
// ioScope is cancelled — otherwise GStreamer keeps running (audio leak).
723+
val ptrToDispose = playerPtrAtomic.getAndSet(0L)
730724

731-
if (ptrToDispose != 0L) {
732-
try {
733-
LinuxNativeBridge.nDisposePlayer(ptrToDispose)
734-
} catch (e: Exception) {
735-
if (e is CancellationException) throw e
736-
linuxLogger.e { "Error disposing player: ${e.message}" }
737-
}
738-
}
725+
skiaBitmapA?.close()
726+
skiaBitmapB?.close()
727+
skiaBitmapA = null
728+
skiaBitmapB = null
729+
skiaBitmapWidth = 0
730+
skiaBitmapHeight = 0
731+
nextSkiaBitmapA = true
739732

740-
resetState()
733+
if (ptrToDispose != 0L) {
734+
try {
735+
LinuxNativeBridge.nDisposePlayer(ptrToDispose)
736+
} catch (e: Exception) {
737+
if (e is CancellationException) throw e
738+
linuxLogger.e { "Error disposing player: ${e.message}" }
739+
}
741740
}
742741

743742
ioScope.cancel()
@@ -794,7 +793,23 @@ class LinuxVideoPlayerState : VideoPlayerState {
794793
if (sw <= 0 || sh <= 0) return
795794
val ptr = playerPtr
796795
if (ptr == 0L) return
797-
LinuxNativeBridge.nSetOutputSize(ptr, sw, sh)
796+
797+
// Compute output dimensions that fit within the surface while preserving
798+
// the video's native aspect ratio. Passing the raw surface size would let
799+
// GStreamer stretch the frame to an arbitrary ratio.
800+
val videoRatio = _aspectRatio.value
801+
val surfaceRatio = sw.toFloat() / sh.toFloat()
802+
803+
val (outW, outH) =
804+
if (videoRatio > surfaceRatio) {
805+
// Video is wider than surface → fit to width
806+
sw to (sw / videoRatio).toInt().coerceAtLeast(1)
807+
} else {
808+
// Video is taller than surface → fit to height
809+
(sh * videoRatio).toInt().coerceAtLeast(1) to sh
810+
}
811+
812+
LinuxNativeBridge.nSetOutputSize(ptr, outW, outH)
798813
}
799814

800815
// --- Internal helpers ---

0 commit comments

Comments
 (0)