Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ Pods/
/mediaplayer/src/jvmMain/resources/composemediaplayer/native/
/mediaplayer/src/jvmMain/resources/win32-x86-64/
/mediaplayer/src/jvmMain/resources/win32-arm64/
/mediaplayer/src/jvmMain/resources/darwin-aarch64/
/mediaplayer/src/jvmMain/resources/darwin-x86-64/

# Native build artifacts
/mediaplayer/src/jvmMain/native/windows/build-x64/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -681,22 +681,23 @@ class MacVideoPlayerState : VideoPlayerState {
}

// Check for looping
checkLoopingAsync(current, duration)
checkLoopingAsync()
} catch (e: Exception) {
if (e is CancellationException) throw e
macLogger.e { "Error in updatePositionAsync: ${e.message}" }
}
}

/** Checks if looping is enabled and restarts the video if needed. */
private suspend fun checkLoopingAsync(
current: Double,
duration: Double,
) {
/** Checks if playback has ended and triggers loop or stop accordingly. */
private suspend fun checkLoopingAsync() {
val ptr = playerPtr
val ended = ptr != 0L && MacNativeBridge.nConsumeDidPlayToEnd(ptr)
// Also check position as fallback for content where the notification may not fire
if (!ended && (duration <= 0 || current < duration - 0.5)) return
if (ptr == 0L) return

// Trust AVPlayerItemDidPlayToEndTime: it fires reliably on macOS for both
// file and HLS playback. A position-based fallback (current >= duration - x)
// is dangerous because it stops playback x seconds early — the slider
// freezes at (duration - x) / duration instead of reaching 100%.
if (!MacNativeBridge.nConsumeDidPlayToEnd(ptr)) return

if (loop) {
macLogger.d { "checkLoopingAsync() - Loop enabled, restarting video" }
Expand Down
27 changes: 23 additions & 4 deletions mediaplayer/src/jvmMain/native/macos/NativeVideoPlayer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -747,15 +747,22 @@ class MacVideoPlayer {
self.nativeVideoWidth = self.frameWidth
self.nativeVideoHeight = self.frameHeight

// Build a video composition that applies the preferred transform so
// that AVPlayerItemVideoOutput delivers pixel buffers already rotated
// to display orientation (fixes portrait videos rendering sideways).
let videoComposition: AVVideoComposition? = self.isHLSStream
? nil
: (try? await AVVideoComposition.videoComposition(withPropertiesOf: asset))

// Continue with player setup
self.setupVideoOutputAndPlayer(with: asset)
self.setupVideoOutputAndPlayer(with: asset, videoComposition: videoComposition)
} catch {
print("Error loading video track properties: \(error.localizedDescription)")
// Use default dimensions for HLS if loading fails
if self.isHLSStream {
self.frameWidth = 1920
self.frameHeight = 1080
self.setupVideoOutputAndPlayer(with: asset)
self.setupVideoOutputAndPlayer(with: asset, videoComposition: nil)
}
}
}
Expand All @@ -770,8 +777,14 @@ class MacVideoPlayer {
nativeVideoWidth = frameWidth
nativeVideoHeight = frameHeight

// Build a video composition that applies the preferred transform (see modern
// path above for rationale). Skip for HLS streams.
let videoComposition: AVVideoComposition? = isHLSStream
? nil
: AVMutableVideoComposition(propertiesOf: asset)

// Continue with player setup
setupVideoOutputAndPlayer(with: asset)
setupVideoOutputAndPlayer(with: asset, videoComposition: videoComposition)
}
}
}
Expand Down Expand Up @@ -825,7 +838,7 @@ class MacVideoPlayer {
}

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

let item = AVPlayerItem(asset: asset)

// Apply the video composition (if any) so that pixel buffers delivered to
// AVPlayerItemVideoOutput are pre-rotated to the display orientation.
if let videoComposition = videoComposition {
item.videoComposition = videoComposition
}

// Configure for HLS if needed
if isHLSStream {
// Set buffer duration for HLS
Expand Down