Improve orientation and safe area handling

Fix #369
Fix #382
This commit is contained in:
Arkadiusz Fal
2023-05-20 16:04:58 +02:00
parent b53b5eac56
commit f67b1d4feb
14 changed files with 176 additions and 118 deletions

View File

@@ -5,7 +5,7 @@ import SwiftUI
#if os(iOS)
struct AppleAVPlayerView: UIViewRepresentable {
func makeUIView(context _: Context) -> some UIView {
PlayerLayerView(frame: .zero)
PlayerLayerView()
}
func updateUIView(_: UIViewType, context _: Context) {}

View File

@@ -13,6 +13,7 @@ struct PlayerControls: View {
#if os(iOS)
@Environment(\.verticalSizeClass) private var verticalSizeClass
@ObservedObject private var safeAreaModel = SafeAreaModel.shared
#elseif os(tvOS)
enum Field: Hashable {
case seekOSD
@@ -229,7 +230,7 @@ struct PlayerControls: View {
guard player.playerSize.height.isFinite else { return 200 }
var inset = 0.0
#if os(iOS)
inset = SafeArea.insets.bottom
inset = safeAreaModel.safeArea.bottom
#endif
return [player.playerSize.height - inset, 500].min()!
}

View File

@@ -5,6 +5,7 @@ struct PlayerBackendView: View {
@Environment(\.verticalSizeClass) private var verticalSizeClass
#endif
@ObservedObject private var player = PlayerModel.shared
@ObservedObject private var safeAreaModel = SafeAreaModel.shared
var body: some View {
ZStack(alignment: .top) {
@@ -55,19 +56,17 @@ struct PlayerBackendView: View {
guard player.playingFullScreen else { return 0 }
if UIDevice.current.userInterfaceIdiom != .pad {
return verticalSizeClass == .compact ? SafeArea.insets.top : 0
return verticalSizeClass == .compact ? safeAreaModel.safeArea.top : 0
} else {
return SafeArea.insets.top.isZero ? SafeArea.insets.bottom : SafeArea.insets.top
return safeAreaModel.safeArea.top.isZero ? safeAreaModel.safeArea.bottom : safeAreaModel.safeArea.top
}
}
var controlsBottomPadding: Double {
guard player.playingFullScreen else { return 0 }
if UIDevice.current.userInterfaceIdiom != .pad {
return player.playingFullScreen && verticalSizeClass == .compact ? SafeArea.insets.bottom : 0
return player.playingFullScreen || verticalSizeClass == .compact ? safeAreaModel.safeArea.bottom : 0
} else {
return player.playingFullScreen ? SafeArea.insets.bottom : 0
return player.playingFullScreen ? safeAreaModel.safeArea.bottom : 0
}
}
#endif

View File

@@ -1,77 +0,0 @@
import Defaults
import Foundation
import SwiftUI
extension VideoPlayerView {
func configureOrientationUpdatesBasedOnAccelerometer() {
let currentOrientation = OrientationTracker.shared.currentInterfaceOrientation
if currentOrientation.isLandscape,
Defaults[.enterFullscreenInLandscape],
!Defaults[.honorSystemOrientationLock],
!player.playingFullScreen,
!player.currentItem.isNil,
player.lockedOrientation.isNil || player.lockedOrientation!.contains(.landscape),
!player.playingInPictureInPicture,
player.presentingPlayer
{
DispatchQueue.main.async {
player.controls.presentingControls = false
player.enterFullScreen(showControls: false)
}
player.onPresentPlayer.append {
Orientation.lockOrientation(.allButUpsideDown, andRotateTo: currentOrientation)
}
}
orientationObserver = NotificationCenter.default.addObserver(
forName: OrientationTracker.deviceOrientationChangedNotification,
object: nil,
queue: .main
) { _ in
guard !Defaults[.honorSystemOrientationLock],
player.presentingPlayer,
!player.playingInPictureInPicture,
player.lockedOrientation.isNil
else {
return
}
let orientation = OrientationTracker.shared.currentInterfaceOrientation
guard lastOrientation != orientation else {
return
}
lastOrientation = orientation
DispatchQueue.main.async {
guard Defaults[.enterFullscreenInLandscape],
player.presentingPlayer
else {
return
}
orientationDebouncer.callback = {
DispatchQueue.main.async {
if orientation.isLandscape {
player.controls.presentingControls = false
player.enterFullScreen(showControls: false)
Orientation.lockOrientation(OrientationTracker.shared.currentInterfaceOrientationMask, andRotateTo: orientation)
} else {
player.exitFullScreen(showControls: false)
Orientation.lockOrientation(.allButUpsideDown, andRotateTo: .portrait)
}
}
}
orientationDebouncer.call()
}
}
}
func stopOrientationUpdates() {
guard let observer = orientationObserver else { return }
NotificationCenter.default.removeObserver(observer)
}
}

View File

@@ -37,10 +37,8 @@ struct VideoPlayerView: View {
#if os(iOS)
@Environment(\.verticalSizeClass) private var verticalSizeClass
@State internal var orientation = UIInterfaceOrientation.portrait
@State internal var lastOrientation: UIInterfaceOrientation?
@State internal var orientationDebouncer = Debouncer(.milliseconds(300))
@ObservedObject private var safeAreaModel = SafeAreaModel.shared
private var orientationModel = OrientationModel.shared
#elseif os(macOS)
var hoverThrottle = Throttle(interval: 0.5)
var mouseLocation: CGPoint { NSEvent.mouseLocation }
@@ -52,7 +50,6 @@ struct VideoPlayerView: View {
@State internal var isHorizontalDrag = false
@State internal var isVerticalDrag = false
@State internal var viewDragOffset = Self.hiddenOffset
@State internal var orientationObserver: Any?
#endif
@ObservedObject internal var player = PlayerModel.shared
@@ -101,13 +98,12 @@ struct VideoPlayerView: View {
return GeometryReader { geometry in
HStack(spacing: 0) {
content
.ignoresSafeArea(.all, edges: .bottom)
.frame(height: playerHeight.isNil ? nil : Double(playerHeight!))
.onAppear {
playerSize = geometry.size
}
}
#if os(iOS)
.padding(.bottom, fullScreenPlayer ? 0.0001 : geometry.safeAreaInsets.bottom)
#endif
.onChange(of: geometry.size) { _ in
self.playerSize = geometry.size
}
@@ -115,8 +111,6 @@ struct VideoPlayerView: View {
player.backend.setNeedsDrawing(!value)
}
#if os(iOS)
.frame(width: playerWidth.isNil ? nil : Double(playerWidth!), height: playerHeight.isNil ? nil : Double(playerHeight!))
.ignoresSafeArea(.all, edges: .bottom)
.onChange(of: player.presentingPlayer) { newValue in
if newValue {
viewDragOffset = 0
@@ -131,7 +125,7 @@ struct VideoPlayerView: View {
viewDragOffset = 0
Delay.by(0.2) {
configureOrientationUpdatesBasedOnAccelerometer()
orientationModel.configureOrientationUpdatesBasedOnAccelerometer()
if let orientationMask = player.lockedOrientation {
Orientation.lockOrientation(
@@ -149,7 +143,7 @@ struct VideoPlayerView: View {
} else {
Orientation.lockOrientation(.allButUpsideDown)
}
stopOrientationUpdates()
orientationModel.stopOrientationUpdates()
player.controls.hideOverlays()
}
.onReceive(NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification)) { _ in
@@ -258,13 +252,10 @@ struct VideoPlayerView: View {
dragGestureState && !isHorizontalDrag ? dragGestureOffset.height : viewDragOffset
}
var playerWidth: Double? {
fullScreenPlayer ? (UIScreen.main.bounds.size.width - SafeArea.insets.left - SafeArea.insets.right) : nil
}
var playerHeight: Double? {
let lockedPortrait = player.lockedOrientation?.contains(.portrait) ?? false
return fullScreenPlayer ? UIScreen.main.bounds.size.height - (OrientationTracker.shared.currentInterfaceOrientation.isPortrait || lockedPortrait ? (SafeArea.insets.top + SafeArea.insets.bottom) : 0) : nil
let isPortrait = OrientationTracker.shared.currentInterfaceOrientation.isPortrait || lockedPortrait
return fullScreenPlayer ? UIScreen.main.bounds.size.height - (isPortrait ? safeAreaModel.safeArea.top + safeAreaModel.safeArea.bottom : 0) : nil
}
#endif
@@ -282,22 +273,20 @@ struct VideoPlayerView: View {
.ignoresSafeArea()
#else
GeometryReader { geometry in
ZStack {
player.playerBackendView
}
.modifier(
VideoPlayerSizeModifier(
geometry: geometry,
aspectRatio: player.aspectRatio,
fullScreen: fullScreenPlayer
player.playerBackendView
.modifier(
VideoPlayerSizeModifier(
geometry: geometry,
aspectRatio: player.aspectRatio,
fullScreen: fullScreenPlayer
)
)
)
.frame(maxWidth: fullScreenPlayer ? .infinity : nil, maxHeight: fullScreenPlayer ? .infinity : nil)
.onHover { hovering in
hoveringPlayer = hovering
hovering ? player.controls.show() : player.controls.hide()
}
.gesture(player.controls.presentingOverlays ? nil : playerDragGesture)
.onHover { hovering in
hoveringPlayer = hovering
hovering ? player.controls.show() : player.controls.hide()
}
.gesture(player.controls.presentingOverlays ? nil : playerDragGesture)
#if os(macOS)
.onAppear(perform: {
NSEvent.addLocalMonitorForEvents(matching: [.mouseMoved]) {
@@ -338,6 +327,7 @@ struct VideoPlayerView: View {
}
#endif
}
.background(BackgroundBlackView().edgesIgnoringSafeArea(.all))
.background(((colorScheme == .dark || fullScreenPlayer) ? Color.black : Color.white).edgesIgnoringSafeArea(.all))
#if os(macOS)
.frame(minWidth: 650)
@@ -489,3 +479,16 @@ struct VideoPlayerView_Previews: PreviewProvider {
}
}
}
struct BackgroundBlackView: UIViewRepresentable {
func makeUIView(context _: Context) -> UIView {
let view = UIView()
DispatchQueue.main.async {
view.superview?.superview?.backgroundColor = .black
view.superview?.superview?.layer.removeAllAnimations()
}
return view
}
func updateUIView(_: UIView, context _: Context) {}
}