From c7e276c6f8e97f58bd93e5ee6946cfb94c66afba Mon Sep 17 00:00:00 2001 From: "Elie G." Date: Sun, 24 Aug 2025 11:20:09 +0300 Subject: [PATCH 1/4] Refactor MediaFoundationLib to use JNA direct mapping Replaces interface-based library loading with object-based JNA direct mapping for improved performance and simplicity. Adjusts related method calls and removes redundant singleton implementation. --- .../windows/MediaFoundationLib.kt | 115 +++++++++--------- .../windows/WindowsVideoPlayerState.kt | 4 +- winlib | 2 +- 3 files changed, 61 insertions(+), 60 deletions(-) diff --git a/mediaplayer/src/jvmMain/kotlin/io/github/kdroidfilter/composemediaplayer/windows/MediaFoundationLib.kt b/mediaplayer/src/jvmMain/kotlin/io/github/kdroidfilter/composemediaplayer/windows/MediaFoundationLib.kt index e81d68ee..5b3db145 100644 --- a/mediaplayer/src/jvmMain/kotlin/io/github/kdroidfilter/composemediaplayer/windows/MediaFoundationLib.kt +++ b/mediaplayer/src/jvmMain/kotlin/io/github/kdroidfilter/composemediaplayer/windows/MediaFoundationLib.kt @@ -8,17 +8,23 @@ import com.sun.jna.ptr.FloatByReference import com.sun.jna.ptr.IntByReference import com.sun.jna.ptr.LongByReference import com.sun.jna.ptr.PointerByReference -import com.sun.jna.win32.StdCallLibrary import io.github.kdroidfilter.composemediaplayer.VideoMetadata -internal interface MediaFoundationLib : StdCallLibrary { +internal object MediaFoundationLib { + /** + * Register the native library for JNA direct mapping + */ + init { + Native.register("NativeVideoPlayer") + } + /** * JNA structure that maps to the C++ VideoMetadata structure */ @Structure.FieldOrder( - "title", "duration", "width", "height", "bitrate", "frameRate", "mimeType", - "audioChannels", "audioSampleRate", "hasTitle", "hasDuration", "hasWidth", - "hasHeight", "hasBitrate", "hasFrameRate", "hasMimeType", "hasAudioChannels", + "title", "duration", "width", "height", "bitrate", "frameRate", "mimeType", + "audioChannels", "audioSampleRate", "hasTitle", "hasDuration", "hasWidth", + "hasHeight", "hasBitrate", "hasFrameRate", "hasMimeType", "hasAudioChannels", "hasAudioSampleRate" ) class NativeVideoMetadata : Structure() { @@ -59,61 +65,56 @@ internal interface MediaFoundationLib : StdCallLibrary { } } - companion object { - val INSTANCE: MediaFoundationLib by lazy { - Native.load("NativeVideoPlayer", MediaFoundationLib::class.java) - } - - /** - * Creates a new instance of the native video player - * @return A pointer to the native instance or null if creation failed - */ - fun createInstance(): Pointer? { - val ptrRef = PointerByReference() - val hr = INSTANCE.CreateVideoPlayerInstance(ptrRef) - return if (hr >= 0 && ptrRef.value != null) ptrRef.value else null - } + /** + * Helper: Creates a new instance of the native video player + * @return A pointer to the native instance or null if creation failed + */ + fun createInstance(): Pointer? { + val ptrRef = PointerByReference() + val hr = CreateVideoPlayerInstance(ptrRef) + return if (hr >= 0 && ptrRef.value != null) ptrRef.value else null + } - /** - * Destroys a native video player instance - * @param instance The pointer to the native instance to destroy - */ - fun destroyInstance(instance: Pointer) { - INSTANCE.DestroyVideoPlayerInstance(instance) - } + /** + * Helper: Destroys a native video player instance + * @param instance The pointer to the native instance to destroy + */ + fun destroyInstance(instance: Pointer) { + DestroyVideoPlayerInstance(instance) + } - /** - * Retrieves metadata for the current media - * @param instance Pointer to the native instance - * @return VideoMetadata object containing all available metadata, or null if retrieval failed - */ - fun getVideoMetadata(instance: Pointer): VideoMetadata? { - val metadata = NativeVideoMetadata() - val hr = INSTANCE.GetVideoMetadata(instance, metadata) - return if (hr >= 0) metadata.toVideoMetadata() else null - } + /** + * Helper: Retrieves metadata for the current media + * @param instance Pointer to the native instance + * @return VideoMetadata object containing all available metadata, or null if retrieval failed + */ + fun getVideoMetadata(instance: Pointer): VideoMetadata? { + val metadata = NativeVideoMetadata() + val hr = GetVideoMetadata(instance, metadata) + return if (hr >= 0) metadata.toVideoMetadata() else null } - fun InitMediaFoundation(): Int - fun CreateVideoPlayerInstance(ppInstance: PointerByReference): Int - fun DestroyVideoPlayerInstance(pInstance: Pointer) - fun OpenMedia(pInstance: Pointer, url: WString, startPlayback: Boolean = true): Int - fun ReadVideoFrame(pInstance: Pointer, pData: PointerByReference, pDataSize: IntByReference): Int - fun UnlockVideoFrame(pInstance: Pointer): Int - fun CloseMedia(pInstance: Pointer) - fun IsEOF(pInstance: Pointer): Boolean - fun GetVideoSize(pInstance: Pointer, pWidth: IntByReference, pHeight: IntByReference) - fun GetVideoFrameRate(pInstance: Pointer, pNum: IntByReference, pDenom: IntByReference): Int - fun SeekMedia(pInstance: Pointer, lPosition: Long): Int - fun GetMediaDuration(pInstance: Pointer, pDuration: LongByReference): Int - fun GetMediaPosition(pInstance: Pointer, pPosition: LongByReference): Int - fun SetPlaybackState(pInstance: Pointer, isPlaying: Boolean, bStop: Boolean = false): Int - fun ShutdownMediaFoundation(): Int - fun SetAudioVolume(pInstance: Pointer, volume: Float): Int - fun GetAudioVolume(pInstance: Pointer, volume: FloatByReference): Int - fun GetAudioLevels(pInstance: Pointer, pLeftLevel: FloatByReference, pRightLevel: FloatByReference): Int - fun SetPlaybackSpeed(pInstance: Pointer, speed: Float): Int - fun GetPlaybackSpeed(pInstance: Pointer, pSpeed: FloatByReference): Int + // === Direct mapped native methods === + @JvmStatic external fun InitMediaFoundation(): Int + @JvmStatic external fun CreateVideoPlayerInstance(ppInstance: PointerByReference): Int + @JvmStatic external fun DestroyVideoPlayerInstance(pInstance: Pointer) + @JvmStatic external fun OpenMedia(pInstance: Pointer, url: WString, startPlayback: Boolean): Int + @JvmStatic external fun ReadVideoFrame(pInstance: Pointer, pData: PointerByReference, pDataSize: IntByReference): Int + @JvmStatic external fun UnlockVideoFrame(pInstance: Pointer): Int + @JvmStatic external fun CloseMedia(pInstance: Pointer) + @JvmStatic external fun IsEOF(pInstance: Pointer): Boolean + @JvmStatic external fun GetVideoSize(pInstance: Pointer, pWidth: IntByReference, pHeight: IntByReference) + @JvmStatic external fun GetVideoFrameRate(pInstance: Pointer, pNum: IntByReference, pDenom: IntByReference): Int + @JvmStatic external fun SeekMedia(pInstance: Pointer, lPosition: Long): Int + @JvmStatic external fun GetMediaDuration(pInstance: Pointer, pDuration: LongByReference): Int + @JvmStatic external fun GetMediaPosition(pInstance: Pointer, pPosition: LongByReference): Int + @JvmStatic external fun SetPlaybackState(pInstance: Pointer, isPlaying: Boolean, bStop: Boolean): Int + @JvmStatic external fun ShutdownMediaFoundation(): Int + @JvmStatic external fun SetAudioVolume(pInstance: Pointer, volume: Float): Int + @JvmStatic external fun GetAudioVolume(pInstance: Pointer, volume: FloatByReference): Int + @JvmStatic external fun GetAudioLevels(pInstance: Pointer, pLeftLevel: FloatByReference, pRightLevel: FloatByReference): Int + @JvmStatic external fun SetPlaybackSpeed(pInstance: Pointer, speed: Float): Int + @JvmStatic external fun GetPlaybackSpeed(pInstance: Pointer, pSpeed: FloatByReference): Int /** * Retrieves all available metadata for the current media @@ -121,5 +122,5 @@ internal interface MediaFoundationLib : StdCallLibrary { * @param pMetadata Pointer to receive the metadata structure * @return S_OK on success, or an error code */ - fun GetVideoMetadata(pInstance: Pointer, pMetadata: NativeVideoMetadata): Int + @JvmStatic external fun GetVideoMetadata(pInstance: Pointer, pMetadata: NativeVideoMetadata): Int } diff --git a/mediaplayer/src/jvmMain/kotlin/io/github/kdroidfilter/composemediaplayer/windows/WindowsVideoPlayerState.kt b/mediaplayer/src/jvmMain/kotlin/io/github/kdroidfilter/composemediaplayer/windows/WindowsVideoPlayerState.kt index 0f5d80d8..b30d7e69 100644 --- a/mediaplayer/src/jvmMain/kotlin/io/github/kdroidfilter/composemediaplayer/windows/WindowsVideoPlayerState.kt +++ b/mediaplayer/src/jvmMain/kotlin/io/github/kdroidfilter/composemediaplayer/windows/WindowsVideoPlayerState.kt @@ -67,7 +67,7 @@ class WindowsVideoPlayerState : PlatformVideoPlayerState { */ private fun ensureMfInitialized() { if (!isMfBootstrapped.getAndSet(true)) { - val hr = MediaFoundationLib.INSTANCE.InitMediaFoundation() + val hr = MediaFoundationLib.InitMediaFoundation() if (hr < 0) { windowsLogger.e { "Media Foundation initialization failed (hr=0x${hr.toString(16)})" } } @@ -81,7 +81,7 @@ class WindowsVideoPlayerState : PlatformVideoPlayerState { } /** Instance of the native Media Foundation player */ - private val player = MediaFoundationLib.INSTANCE + private val player = MediaFoundationLib /** Coroutine scope for all async operations */ private val scope = CoroutineScope(Dispatchers.Default + SupervisorJob()) diff --git a/winlib b/winlib index a505e660..bea90194 160000 --- a/winlib +++ b/winlib @@ -1 +1 @@ -Subproject commit a505e660530bd8cbc721769343014cf79fb661d9 +Subproject commit bea901944d31481f1e060ba2ea306ea3a7d08a4d From af0d638370c4755a58eb81d0a1e27ae9e228fd21 Mon Sep 17 00:00:00 2001 From: "Elie G." Date: Sun, 24 Aug 2025 11:32:38 +0300 Subject: [PATCH 2/4] Optimize resize handling in `WindowsVideoPlayerState` Simplified resize logic by removing redundant memory cleanup and heavy operations. Introduced a shorter debounce delay for smoother resizing and adjusted pipeline responsiveness to reduce latency during resize events. --- .../windows/WindowsVideoPlayerState.kt | 32 ++++++------------- 1 file changed, 10 insertions(+), 22 deletions(-) diff --git a/mediaplayer/src/jvmMain/kotlin/io/github/kdroidfilter/composemediaplayer/windows/WindowsVideoPlayerState.kt b/mediaplayer/src/jvmMain/kotlin/io/github/kdroidfilter/composemediaplayer/windows/WindowsVideoPlayerState.kt index b30d7e69..8db0eb85 100644 --- a/mediaplayer/src/jvmMain/kotlin/io/github/kdroidfilter/composemediaplayer/windows/WindowsVideoPlayerState.kt +++ b/mediaplayer/src/jvmMain/kotlin/io/github/kdroidfilter/composemediaplayer/windows/WindowsVideoPlayerState.kt @@ -1017,26 +1017,14 @@ class WindowsVideoPlayerState : PlatformVideoPlayerState { fun onResized() { if (isDisposing.get()) return + // Mark resizing in progress and debounce rapid events without heavy operations isResizing.set(true) - scope.launch { - try { - // Clear frame channel to stop processing frames during resize - clearFrameChannel() - - // Release shared frame buffer to reduce memory pressure during resize - // This is especially important for 4K videos - sharedFrameBuffer = null - - // Force garbage collection to free up memory - System.gc() - } finally { - resizeJob?.cancel() - resizeJob = scope.launch { - // Increased delay for 4K videos (was 200ms) - delay(500) - isResizing.set(false) - } - } + // Cancel any pending end-of-resize job and schedule a shorter debounce + resizeJob?.cancel() + resizeJob = scope.launch { + // Short debounce to smooth out successive resize events + delay(120) + isResizing.set(false) } } @@ -1166,9 +1154,9 @@ class WindowsVideoPlayerState : PlatformVideoPlayerState { private suspend fun waitIfResizing(): Boolean { if (isResizing.get()) { try { - // Increased delay for 4K videos (was 100ms) - // This helps reduce memory pressure during resizing - delay(200) + // Keep the pipeline responsive during resize while avoiding busy-wait + yield() + delay(8) } catch (e: CancellationException) { throw e } From 379cdf075a1d0e9db5bed0eeed8784cd6557b080 Mon Sep 17 00:00:00 2001 From: "Elie G." Date: Sun, 24 Aug 2025 11:35:36 +0300 Subject: [PATCH 3/4] Hint GC after resource cleanup in `WindowsVideoPlayerState` to optimize memory management. --- .../composemediaplayer/windows/WindowsVideoPlayerState.kt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/mediaplayer/src/jvmMain/kotlin/io/github/kdroidfilter/composemediaplayer/windows/WindowsVideoPlayerState.kt b/mediaplayer/src/jvmMain/kotlin/io/github/kdroidfilter/composemediaplayer/windows/WindowsVideoPlayerState.kt index 8db0eb85..778b5bf2 100644 --- a/mediaplayer/src/jvmMain/kotlin/io/github/kdroidfilter/composemediaplayer/windows/WindowsVideoPlayerState.kt +++ b/mediaplayer/src/jvmMain/kotlin/io/github/kdroidfilter/composemediaplayer/windows/WindowsVideoPlayerState.kt @@ -369,6 +369,9 @@ class WindowsVideoPlayerState : PlatformVideoPlayerState { // Reset initialFrameRead flag to ensure we read an initial frame when reinitialized initialFrameRead.set(false) + + // Hint the GC after freeing big objects synchronously + System.gc() } private fun releaseAllResources() { @@ -396,6 +399,9 @@ class WindowsVideoPlayerState : PlatformVideoPlayerState { // Reset initialFrameRead flag to ensure we read an initial frame when reinitialized initialFrameRead.set(false) + + // Hint GC after releasing frame buffers and bitmaps + System.gc() } private fun clearFrameChannel() { From e2a611dd50155deab42b9ca384e0b2f3346a87c7 Mon Sep 17 00:00:00 2001 From: Elie Gambache Date: Sun, 24 Aug 2025 12:16:43 +0300 Subject: [PATCH 4/4] Refactor `SharedVideoPlayer` from interface to object with direct JNA mapping. Simplify native method calls across `MacVideoPlayerState`. --- .../composemediaplayer/mac/AvPlayerLib.kt | 67 +++++++++---------- .../mac/MacVideoPlayerState.kt | 64 +++++++++--------- 2 files changed, 65 insertions(+), 66 deletions(-) diff --git a/mediaplayer/src/jvmMain/kotlin/io/github/kdroidfilter/composemediaplayer/mac/AvPlayerLib.kt b/mediaplayer/src/jvmMain/kotlin/io/github/kdroidfilter/composemediaplayer/mac/AvPlayerLib.kt index 832dce1a..c9f7bbdf 100644 --- a/mediaplayer/src/jvmMain/kotlin/io/github/kdroidfilter/composemediaplayer/mac/AvPlayerLib.kt +++ b/mediaplayer/src/jvmMain/kotlin/io/github/kdroidfilter/composemediaplayer/mac/AvPlayerLib.kt @@ -1,44 +1,43 @@ package io.github.kdroidfilter.composemediaplayer.mac -import com.sun.jna.Library import com.sun.jna.Native import com.sun.jna.Pointer /** - * JNA interface to the native library. - * Includes new methods to retrieve frame rate information. + * JNA direct mapping to the native library. + * Includes methods to retrieve frame rate and metadata information. */ -internal interface SharedVideoPlayer : Library { - fun createVideoPlayer(): Pointer? - fun openUri(context: Pointer?, uri: String?) - fun playVideo(context: Pointer?) - fun pauseVideo(context: Pointer?) - fun setVolume(context: Pointer?, volume: Float) - fun getVolume(context: Pointer?): Float - fun getLatestFrame(context: Pointer?): Pointer? - fun getFrameWidth(context: Pointer?): Int - fun getFrameHeight(context: Pointer?): Int - fun getVideoFrameRate(context: Pointer?): Float - fun getScreenRefreshRate(context: Pointer?): Float - fun getCaptureFrameRate(context: Pointer?): Float - fun getVideoDuration(context: Pointer?): Double - fun getCurrentTime(context: Pointer?): Double - fun seekTo(context: Pointer?, time: Double) - fun disposeVideoPlayer(context: Pointer?) - fun getLeftAudioLevel(context: Pointer?): Float - fun getRightAudioLevel(context: Pointer?): Float - fun setPlaybackSpeed(context: Pointer?, speed: Float) - fun getPlaybackSpeed(context: Pointer?): Float +internal object SharedVideoPlayer { + init { + // Register the native library for direct mapping + Native.register("NativeVideoPlayer") + } - // Metadata retrieval functions - fun getVideoTitle(context: Pointer?): String? - fun getVideoBitrate(context: Pointer?): Long - fun getVideoMimeType(context: Pointer?): String? - fun getAudioChannels(context: Pointer?): Int - fun getAudioSampleRate(context: Pointer?): Int + @JvmStatic external fun createVideoPlayer(): Pointer? + @JvmStatic external fun openUri(context: Pointer?, uri: String?) + @JvmStatic external fun playVideo(context: Pointer?) + @JvmStatic external fun pauseVideo(context: Pointer?) + @JvmStatic external fun setVolume(context: Pointer?, volume: Float) + @JvmStatic external fun getVolume(context: Pointer?): Float + @JvmStatic external fun getLatestFrame(context: Pointer?): Pointer? + @JvmStatic external fun getFrameWidth(context: Pointer?): Int + @JvmStatic external fun getFrameHeight(context: Pointer?): Int + @JvmStatic external fun getVideoFrameRate(context: Pointer?): Float + @JvmStatic external fun getScreenRefreshRate(context: Pointer?): Float + @JvmStatic external fun getCaptureFrameRate(context: Pointer?): Float + @JvmStatic external fun getVideoDuration(context: Pointer?): Double + @JvmStatic external fun getCurrentTime(context: Pointer?): Double + @JvmStatic external fun seekTo(context: Pointer?, time: Double) + @JvmStatic external fun disposeVideoPlayer(context: Pointer?) + @JvmStatic external fun getLeftAudioLevel(context: Pointer?): Float + @JvmStatic external fun getRightAudioLevel(context: Pointer?): Float + @JvmStatic external fun setPlaybackSpeed(context: Pointer?, speed: Float) + @JvmStatic external fun getPlaybackSpeed(context: Pointer?): Float - companion object { - val INSTANCE: SharedVideoPlayer = - Native.load("NativeVideoPlayer", SharedVideoPlayer::class.java) - } + // Metadata retrieval functions + @JvmStatic external fun getVideoTitle(context: Pointer?): String? + @JvmStatic external fun getVideoBitrate(context: Pointer?): Long + @JvmStatic external fun getVideoMimeType(context: Pointer?): String? + @JvmStatic external fun getAudioChannels(context: Pointer?): Int + @JvmStatic external fun getAudioSampleRate(context: Pointer?): Int } diff --git a/mediaplayer/src/jvmMain/kotlin/io/github/kdroidfilter/composemediaplayer/mac/MacVideoPlayerState.kt b/mediaplayer/src/jvmMain/kotlin/io/github/kdroidfilter/composemediaplayer/mac/MacVideoPlayerState.kt index 045e9c9d..b606ae24 100644 --- a/mediaplayer/src/jvmMain/kotlin/io/github/kdroidfilter/composemediaplayer/mac/MacVideoPlayerState.kt +++ b/mediaplayer/src/jvmMain/kotlin/io/github/kdroidfilter/composemediaplayer/mac/MacVideoPlayerState.kt @@ -181,7 +181,7 @@ class MacVideoPlayerState : PlatformVideoPlayerState { private suspend fun initPlayer() = ioScope.launch { macLogger.d { "initPlayer() - Creating native player" } try { - val ptr = SharedVideoPlayer.INSTANCE.createVideoPlayer() + val ptr = SharedVideoPlayer.createVideoPlayer() if (ptr != null) { mainMutex.withLock { playerPtr = ptr } macLogger.d { "Native player created successfully" } @@ -208,9 +208,9 @@ class MacVideoPlayerState : PlatformVideoPlayerState { val ptr = mainMutex.withLock { playerPtr } ?: return try { - videoFrameRate = SharedVideoPlayer.INSTANCE.getVideoFrameRate(ptr) - screenRefreshRate = SharedVideoPlayer.INSTANCE.getScreenRefreshRate(ptr) - captureFrameRate = SharedVideoPlayer.INSTANCE.getCaptureFrameRate(ptr) + videoFrameRate = SharedVideoPlayer.getVideoFrameRate(ptr) + screenRefreshRate = SharedVideoPlayer.getScreenRefreshRate(ptr) + captureFrameRate = SharedVideoPlayer.getCaptureFrameRate(ptr) macLogger.d { "Frame Rates - Video: $videoFrameRate, Screen: $screenRefreshRate, Capture: $captureFrameRate" } } catch (e: Exception) { if (e is CancellationException) throw e @@ -316,7 +316,7 @@ class MacVideoPlayerState : PlatformVideoPlayerState { // Release resources outside of the mutex lock ptrToDispose?.let { try { - SharedVideoPlayer.INSTANCE.disposeVideoPlayer(it) + SharedVideoPlayer.disposeVideoPlayer(it) } catch (e: Exception) { if (e is CancellationException) throw e macLogger.e { "Error disposing player: ${e.message}" } @@ -334,7 +334,7 @@ class MacVideoPlayerState : PlatformVideoPlayerState { val isPlayerNull = mainMutex.withLock { playerPtr == null } if (isPlayerNull) { - val ptr = SharedVideoPlayer.INSTANCE.createVideoPlayer() + val ptr = SharedVideoPlayer.createVideoPlayer() if (ptr != null) { mainMutex.withLock { playerPtr = ptr } applyVolume() @@ -365,7 +365,7 @@ class MacVideoPlayerState : PlatformVideoPlayerState { return try { // Open video asynchronously - SharedVideoPlayer.INSTANCE.openUri(ptr, uri) + SharedVideoPlayer.openUri(ptr, uri) // Instead of directly calling `updateMetadata()`, // we poll until valid dimensions are available @@ -390,8 +390,8 @@ class MacVideoPlayerState : PlatformVideoPlayerState { */ private suspend fun pollDimensionsUntilReady(ptr: Pointer, maxAttempts: Int = 20) { for (attempt in 1..maxAttempts) { - val width = SharedVideoPlayer.INSTANCE.getFrameWidth(ptr) - val height = SharedVideoPlayer.INSTANCE.getFrameHeight(ptr) + val width = SharedVideoPlayer.getFrameWidth(ptr) + val height = SharedVideoPlayer.getFrameHeight(ptr) if (width > 0 && height > 0) { macLogger.d { "Dimensions validated (w=$width, h=$height) after $attempt attempts" } @@ -409,10 +409,10 @@ class MacVideoPlayerState : PlatformVideoPlayerState { val ptr = mainMutex.withLock { playerPtr } ?: return try { - val width = SharedVideoPlayer.INSTANCE.getFrameWidth(ptr) - val height = SharedVideoPlayer.INSTANCE.getFrameHeight(ptr) - val duration = SharedVideoPlayer.INSTANCE.getVideoDuration(ptr).toLong() - val frameRate = SharedVideoPlayer.INSTANCE.getVideoFrameRate(ptr) + val width = SharedVideoPlayer.getFrameWidth(ptr) + val height = SharedVideoPlayer.getFrameHeight(ptr) + val duration = SharedVideoPlayer.getVideoDuration(ptr).toLong() + val frameRate = SharedVideoPlayer.getVideoFrameRate(ptr) // Calculate aspect ratio val newAspectRatio = if (width > 0 && height > 0) { @@ -424,11 +424,11 @@ class MacVideoPlayerState : PlatformVideoPlayerState { } // Get additional metadata - val title = SharedVideoPlayer.INSTANCE.getVideoTitle(ptr) - val bitrate = SharedVideoPlayer.INSTANCE.getVideoBitrate(ptr) - val mimeType = SharedVideoPlayer.INSTANCE.getVideoMimeType(ptr) - val audioChannels = SharedVideoPlayer.INSTANCE.getAudioChannels(ptr) - val audioSampleRate = SharedVideoPlayer.INSTANCE.getAudioSampleRate(ptr) + val title = SharedVideoPlayer.getVideoTitle(ptr) + val bitrate = SharedVideoPlayer.getVideoBitrate(ptr) + val mimeType = SharedVideoPlayer.getVideoMimeType(ptr) + val audioChannels = SharedVideoPlayer.getAudioChannels(ptr) + val audioSampleRate = SharedVideoPlayer.getAudioSampleRate(ptr) withContext(Dispatchers.Main) { // Update metadata @@ -537,15 +537,15 @@ class MacVideoPlayerState : PlatformVideoPlayerState { val ptr = mainMutex.withLock { playerPtr } ?: return // Get frame dimensions - val width = SharedVideoPlayer.INSTANCE.getFrameWidth(ptr) - val height = SharedVideoPlayer.INSTANCE.getFrameHeight(ptr) + val width = SharedVideoPlayer.getFrameWidth(ptr) + val height = SharedVideoPlayer.getFrameHeight(ptr) if (width <= 0 || height <= 0) { return } // Get the latest frame to minimize mutex lock time - val framePtr = SharedVideoPlayer.INSTANCE.getLatestFrame(ptr) ?: return + val framePtr = SharedVideoPlayer.getLatestFrame(ptr) ?: return // Create or reuse a buffered image on a compute thread val bufferedImage = withContext(Dispatchers.Default) { @@ -599,8 +599,8 @@ class MacVideoPlayerState : PlatformVideoPlayerState { try { val ptr = mainMutex.withLock { playerPtr } if (ptr != null) { - val newLeft = SharedVideoPlayer.INSTANCE.getLeftAudioLevel(ptr) - val newRight = SharedVideoPlayer.INSTANCE.getRightAudioLevel(ptr) + val newLeft = SharedVideoPlayer.getLeftAudioLevel(ptr) + val newRight = SharedVideoPlayer.getRightAudioLevel(ptr) // macLogger.d { "Audio levels fetched: L=$newLeft, R=$newRight" } // Converts the linear level to a percentage on a logarithmic scale. @@ -710,7 +710,7 @@ class MacVideoPlayerState : PlatformVideoPlayerState { val ptr = mainMutex.withLock { playerPtr } ?: return try { - SharedVideoPlayer.INSTANCE.playVideo(ptr) + SharedVideoPlayer.playVideo(ptr) withContext(Dispatchers.Main) { isPlaying = true @@ -737,7 +737,7 @@ class MacVideoPlayerState : PlatformVideoPlayerState { val ptr = mainMutex.withLock { playerPtr } ?: return try { - SharedVideoPlayer.INSTANCE.pauseVideo(ptr) + SharedVideoPlayer.pauseVideo(ptr) withContext(Dispatchers.Main) { isPlaying = false @@ -803,10 +803,10 @@ class MacVideoPlayerState : PlatformVideoPlayerState { lastFrameUpdateTime = System.currentTimeMillis() val ptr = mainMutex.withLock { playerPtr } ?: return - SharedVideoPlayer.INSTANCE.seekTo(ptr, seekTime.toDouble()) + SharedVideoPlayer.seekTo(ptr, seekTime.toDouble()) if (isPlaying) { - SharedVideoPlayer.INSTANCE.playVideo(ptr) + SharedVideoPlayer.playVideo(ptr) // Reduce delay to update frame faster for local videos delay(10) updateFrameAsync() @@ -854,7 +854,7 @@ class MacVideoPlayerState : PlatformVideoPlayerState { ptrToDispose?.let { macLogger.d { "dispose() - Disposing native player" } try { - SharedVideoPlayer.INSTANCE.disposeVideoPlayer(it) + SharedVideoPlayer.disposeVideoPlayer(it) } catch (e: Exception) { if (e is CancellationException) throw e macLogger.e { "Error disposing player: ${e.message}" } @@ -919,7 +919,7 @@ class MacVideoPlayerState : PlatformVideoPlayerState { private suspend fun getPositionSafely(): Double { val ptr = mainMutex.withLock { playerPtr } ?: return 0.0 return try { - SharedVideoPlayer.INSTANCE.getCurrentTime(ptr) + SharedVideoPlayer.getCurrentTime(ptr) } catch (e: Exception) { if (e is CancellationException) throw e macLogger.e { "Error getting position: ${e.message}" } @@ -931,7 +931,7 @@ class MacVideoPlayerState : PlatformVideoPlayerState { private suspend fun getDurationSafely(): Double { val ptr = mainMutex.withLock { playerPtr } ?: return 0.0 return try { - SharedVideoPlayer.INSTANCE.getVideoDuration(ptr) + SharedVideoPlayer.getVideoDuration(ptr) } catch (e: Exception) { if (e is CancellationException) throw e macLogger.e { "Error getting duration: ${e.message}" } @@ -948,7 +948,7 @@ class MacVideoPlayerState : PlatformVideoPlayerState { mainMutex.withLock { playerPtr?.let { ptr -> try { - SharedVideoPlayer.INSTANCE.setVolume(ptr, _volumeState.value) + SharedVideoPlayer.setVolume(ptr, _volumeState.value) } catch (e: Exception) { if (e is CancellationException) throw e macLogger.e { "Error applying volume: ${e.message}" } @@ -966,7 +966,7 @@ class MacVideoPlayerState : PlatformVideoPlayerState { mainMutex.withLock { playerPtr?.let { ptr -> try { - SharedVideoPlayer.INSTANCE.setPlaybackSpeed(ptr, _playbackSpeedState.value) + SharedVideoPlayer.setPlaybackSpeed(ptr, _playbackSpeedState.value) } catch (e: Exception) { if (e is CancellationException) throw e macLogger.e { "Error applying playback speed: ${e.message}" }