Show video thumbnail in mini player during PiP

When PiP is active, the MPV render view shows a black frame since
rendering goes to the PiP sample buffer layer. Overlay the video
thumbnail (preferring DeArrow) on top to cover the black area,
fading it in/out smoothly when PiP starts/stops.
This commit is contained in:
Arkadiusz Fal
2026-02-12 07:19:30 +01:00
parent 284aec679f
commit 0cfe365d4f

View File

@@ -28,6 +28,9 @@ struct MiniPlayerView: View {
private var currentVideo: Video? { playerState?.currentVideo } private var currentVideo: Video? { playerState?.currentVideo }
/// Whether PiP is currently active
private var isPiPActive: Bool { playerState?.pipState == .active }
/// Whether video preview should be shown (video ready and player not expanded/expanding) /// Whether video preview should be shown (video ready and player not expanded/expanding)
private var shouldShowVideoPreview: Bool { private var shouldShowVideoPreview: Bool {
guard let state = playerState else { return false } guard let state = playerState else { return false }
@@ -257,11 +260,6 @@ struct MiniPlayerView: View {
@ViewBuilder @ViewBuilder
private var videoPreviewView: some View { private var videoPreviewView: some View {
ZStack { ZStack {
// Thumbnail layer - shown when video not ready or during expand animation
thumbnailView
.opacity(shouldShowVideoPreview ? 0 : 1)
.animation(.easeInOut(duration: 0.15), value: shouldShowVideoPreview)
// Video layer - mounted during collapse or when ready to show // Video layer - mounted during collapse or when ready to show
// Keep it in hierarchy during collapse so the container can receive the player view // Keep it in hierarchy during collapse so the container can receive the player view
if let backend = playerService?.currentBackend as? MPVBackend, if let backend = playerService?.currentBackend as? MPVBackend,
@@ -269,8 +267,15 @@ struct MiniPlayerView: View {
shouldMountVideoView { shouldMountVideoView {
MPVRenderViewRepresentable(backend: backend, playerState: playerState) MPVRenderViewRepresentable(backend: backend, playerState: playerState)
.allowsHitTesting(false) .allowsHitTesting(false)
// No animation on video opacity - show immediately when ready
} }
// Thumbnail layer - on top so it can cover the black MPV view during PiP
// Shown when video not ready, during expand animation, or during PiP
thumbnailView
.opacity(shouldShowVideoPreview && !isPiPActive ? 0 : 1)
.animation(.easeInOut(duration: 0.15), value: shouldShowVideoPreview)
.animation(.easeInOut(duration: 0.25), value: isPiPActive)
.allowsHitTesting(false)
} }
.onChange(of: navigationCoordinator?.isPlayerCollapsing) { _, isCollapsing in .onChange(of: navigationCoordinator?.isPlayerCollapsing) { _, isCollapsing in
// When collapse animation finishes, resume rendering if video preview should be shown // When collapse animation finishes, resume rendering if video preview should be shown