diff --git a/Yattee/Services/Player/MPVBackend.swift b/Yattee/Services/Player/MPVBackend.swift index 6e08f32d..c46209f5 100644 --- a/Yattee/Services/Player/MPVBackend.swift +++ b/Yattee/Services/Player/MPVBackend.swift @@ -68,6 +68,9 @@ final class MPVBackend: PlayerBackend { // MARK: - Private Properties private var mpvClient: MPVClient? + #if os(iOS) + private var videoFillsFrame = false + #endif #if os(macOS) private var renderView: MPVOGLView? #elseif targetEnvironment(simulator) && (os(iOS) || os(tvOS)) @@ -173,6 +176,14 @@ final class MPVBackend: PlayerBackend { _playerView?.pauseRendering() } + /// Stretch the video over the whole view (ignoring its aspect ratio) + /// instead of letterboxing it. + func setVideoFillsFrame(_ fills: Bool) { + guard fills != videoFillsFrame else { return } + videoFillsFrame = fills + mpvClient?.setProperty("keepaspect", !fills) + } + /// Resume video rendering func resumeRendering() { MPVLogging.log("MPVBackend.resumeRendering called") diff --git a/Yattee/Views/Player/MPVRenderViewRepresentable.swift b/Yattee/Views/Player/MPVRenderViewRepresentable.swift index 0f84221d..e680f0a4 100644 --- a/Yattee/Views/Player/MPVRenderViewRepresentable.swift +++ b/Yattee/Views/Player/MPVRenderViewRepresentable.swift @@ -18,10 +18,16 @@ struct MPVRenderViewRepresentable: UIViewRepresentable { /// Optional player state to update PiP availability var playerState: PlayerState? + /// Stretch the video to fill the view instead of letterboxing it (the mini + /// player's tiny preview, where bars look like a glitch). + var fillsFrame = false + func makeUIView(context: Context) -> UIView { // Create a container that will hold the actual player view let container = MPVContainerView() container.backgroundColor = .black + container.backend = backend + container.fillsFrame = fillsFrame MPVLogging.log("MPVRenderViewRepresentable.makeUIView: creating container", details: "hasPlayerView:\(backend.playerView != nil)") @@ -64,6 +70,10 @@ private class MPVContainerView: UIView { private weak var currentPlayerView: UIView? private let containerID = UUID() + /// Applied to the backend whenever this container takes the player view + weak var backend: MPVBackend? + var fillsFrame = false + // Track all living containers to enable view transfer on deinit private static var livingContainers = NSHashTable.weakObjects() @@ -159,6 +169,9 @@ private class MPVContainerView: UIView { playerView.trailingAnchor.constraint(equalTo: trailingAnchor) ]) currentPlayerView = playerView + #if os(iOS) + backend?.setVideoFillsFrame(fillsFrame) + #endif // Size it (and re-render its framebuffer) before anyone reacts to the move if !bounds.isEmpty { layoutIfNeeded() diff --git a/Yattee/Views/Player/MiniPlayerView.swift b/Yattee/Views/Player/MiniPlayerView.swift index 2a7f7892..64a502f8 100644 --- a/Yattee/Views/Player/MiniPlayerView.swift +++ b/Yattee/Views/Player/MiniPlayerView.swift @@ -344,8 +344,13 @@ struct MiniPlayerView: View { if let backend = playerService?.currentBackend as? MPVBackend, let playerState, shouldMountVideoView { + #if os(iOS) + MPVRenderViewRepresentable(backend: backend, playerState: playerState, fillsFrame: true) + .allowsHitTesting(false) + #else MPVRenderViewRepresentable(backend: backend, playerState: playerState) .allowsHitTesting(false) + #endif } // Thumbnail layer - on top so it can cover the black MPV view during PiP