Files
yattee/Yattee/Services/Player/MPV/MPVReflectionLayer.swift
Arkadiusz Fal 83895a1fd9 Reflect the video below the hinge in the tabletop layout
The iPhone Duo tabletop layout mirrors every presented frame into the
lower half, blurred and fading out, like a screen standing on a glossy
table. The render view blits a quarter-resolution copy of the display
framebuffer into a ring of IOSurface-backed buffers (no Y flip, so the
surface is already mirrored) and hands it to a CALayer hosted by
MPVReflectionView; the simulator's software renderer feeds the same
layer a flipped CGImage. Off during PiP, for audio-only streams, and
via the new Playback → Foldable → "Reflection Below Video" setting.
2026-10-01 20:59:48 +02:00

53 lines
1.3 KiB
Swift

//
// MPVReflectionLayer.swift
// Yattee
//
// Layer that shows a vertically mirrored copy of the rendered video frame,
// used for the "reflection" below the video in the iPhone Duo tabletop
// layout. The render view feeds it every presented frame.
//
import Foundation
import QuartzCore
import IOSurface
#if canImport(UIKit)
import UIKit
final class MPVReflectionLayer: CALayer {
override init() {
super.init()
contentsGravity = .resizeAspect
isOpaque = false
// Frame updates must not trigger implicit fades.
actions = ["contents": NSNull(), "bounds": NSNull(), "position": NSNull(), "transform": NSNull()]
}
override init(layer: Any) {
super.init(layer: layer)
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
/// Show a surface whose rows are already mirrored (GL blit without Y flip).
func display(_ surface: IOSurface) {
contents = surface
}
/// Show an upright image; the layer mirrors it itself.
func displayMirrored(_ image: CGImage) {
if CATransform3DIsIdentity(transform) {
transform = CATransform3DMakeScale(1, -1, 1)
}
contents = image
}
func clear() {
contents = nil
}
}
#endif