@@ -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