Skip to content

Commit 79369a8

Browse files
committed
fix(mac): apply rotation metadata and reach 100% on progress bar
- Set AVVideoComposition built from the asset's preferred transform on the AVPlayerItem so AVPlayerItemVideoOutput delivers pixel buffers already rotated to display orientation. Fixes #202 (portrait phone videos rendering sideways on JVM/macOS). - Drop the 0.5s position-based fallback in checkLoopingAsync that was triggering pauseInBackground half a second before the actual end, freezing sliderPos at (duration - 0.5)/duration. Rely solely on AVPlayerItemDidPlayToEndTime, which is reliable on macOS. - gitignore the prebuilt darwin-aarch64/x86-64 dylib resource dirs.
1 parent 7a47043 commit 79369a8

3 files changed

Lines changed: 35 additions & 13 deletions

File tree

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ Pods/
1919
/mediaplayer/src/jvmMain/resources/composemediaplayer/native/
2020
/mediaplayer/src/jvmMain/resources/win32-x86-64/
2121
/mediaplayer/src/jvmMain/resources/win32-arm64/
22+
/mediaplayer/src/jvmMain/resources/darwin-aarch64/
23+
/mediaplayer/src/jvmMain/resources/darwin-x86-64/
2224

2325
# Native build artifacts
2426
/mediaplayer/src/jvmMain/native/windows/build-x64/

mediaplayer/src/jvmMain/kotlin/io/github/kdroidfilter/composemediaplayer/mac/MacVideoPlayerState.kt

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -681,22 +681,23 @@ class MacVideoPlayerState : VideoPlayerState {
681681
}
682682

683683
// Check for looping
684-
checkLoopingAsync(current, duration)
684+
checkLoopingAsync()
685685
} catch (e: Exception) {
686686
if (e is CancellationException) throw e
687687
macLogger.e { "Error in updatePositionAsync: ${e.message}" }
688688
}
689689
}
690690

691-
/** Checks if looping is enabled and restarts the video if needed. */
692-
private suspend fun checkLoopingAsync(
693-
current: Double,
694-
duration: Double,
695-
) {
691+
/** Checks if playback has ended and triggers loop or stop accordingly. */
692+
private suspend fun checkLoopingAsync() {
696693
val ptr = playerPtr
697-
val ended = ptr != 0L && MacNativeBridge.nConsumeDidPlayToEnd(ptr)
698-
// Also check position as fallback for content where the notification may not fire
699-
if (!ended && (duration <= 0 || current < duration - 0.5)) return
694+
if (ptr == 0L) return
695+
696+
// Trust AVPlayerItemDidPlayToEndTime: it fires reliably on macOS for both
697+
// file and HLS playback. A position-based fallback (current >= duration - x)
698+
// is dangerous because it stops playback x seconds early — the slider
699+
// freezes at (duration - x) / duration instead of reaching 100%.
700+
if (!MacNativeBridge.nConsumeDidPlayToEnd(ptr)) return
700701

701702
if (loop) {
702703
macLogger.d { "checkLoopingAsync() - Loop enabled, restarting video" }

mediaplayer/src/jvmMain/native/macos/NativeVideoPlayer.swift

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -747,15 +747,22 @@ class MacVideoPlayer {
747747
self.nativeVideoWidth = self.frameWidth
748748
self.nativeVideoHeight = self.frameHeight
749749

750+
// Build a video composition that applies the preferred transform so
751+
// that AVPlayerItemVideoOutput delivers pixel buffers already rotated
752+
// to display orientation (fixes portrait videos rendering sideways).
753+
let videoComposition: AVVideoComposition? = self.isHLSStream
754+
? nil
755+
: (try? await AVVideoComposition.videoComposition(withPropertiesOf: asset))
756+
750757
// Continue with player setup
751-
self.setupVideoOutputAndPlayer(with: asset)
758+
self.setupVideoOutputAndPlayer(with: asset, videoComposition: videoComposition)
752759
} catch {
753760
print("Error loading video track properties: \(error.localizedDescription)")
754761
// Use default dimensions for HLS if loading fails
755762
if self.isHLSStream {
756763
self.frameWidth = 1920
757764
self.frameHeight = 1080
758-
self.setupVideoOutputAndPlayer(with: asset)
765+
self.setupVideoOutputAndPlayer(with: asset, videoComposition: nil)
759766
}
760767
}
761768
}
@@ -770,8 +777,14 @@ class MacVideoPlayer {
770777
nativeVideoWidth = frameWidth
771778
nativeVideoHeight = frameHeight
772779

780+
// Build a video composition that applies the preferred transform (see modern
781+
// path above for rationale). Skip for HLS streams.
782+
let videoComposition: AVVideoComposition? = isHLSStream
783+
? nil
784+
: AVMutableVideoComposition(propertiesOf: asset)
785+
773786
// Continue with player setup
774-
setupVideoOutputAndPlayer(with: asset)
787+
setupVideoOutputAndPlayer(with: asset, videoComposition: videoComposition)
775788
}
776789
}
777790
}
@@ -825,7 +838,7 @@ class MacVideoPlayer {
825838
}
826839

827840
// Helper method to setup video output and player
828-
private func setupVideoOutputAndPlayer(with asset: AVAsset) {
841+
private func setupVideoOutputAndPlayer(with asset: AVAsset, videoComposition: AVVideoComposition? = nil) {
829842
// Create attributes for the CVPixelBuffer (BGRA format) with IOSurface for better performance
830843
let pixelBufferAttributes: [String: Any] = [
831844
kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_32BGRA,
@@ -837,6 +850,12 @@ class MacVideoPlayer {
837850

838851
let item = AVPlayerItem(asset: asset)
839852

853+
// Apply the video composition (if any) so that pixel buffers delivered to
854+
// AVPlayerItemVideoOutput are pre-rotated to the display orientation.
855+
if let videoComposition = videoComposition {
856+
item.videoComposition = videoComposition
857+
}
858+
840859
// Configure for HLS if needed
841860
if isHLSStream {
842861
// Set buffer duration for HLS

0 commit comments

Comments
 (0)