Improve fullscreen animation

This commit is contained in:
Arkadiusz Fal 2022-08-06 15:27:34 +02:00
parent 8318b4c5fb
commit 5f5bd37bd4
5 changed files with 184 additions and 123 deletions

View File

@ -811,7 +811,7 @@ final class PlayerModel: ObservableObject {
}
func toggleFullscreen(_ isFullScreen: Bool) {
controls.resetTimer()
controls.presentingControls = false
#if os(macOS)
if isFullScreen {

View File

@ -0,0 +1,98 @@
import SwiftUI
struct PlayerBackendView: View {
#if os(iOS)
@Environment(\.verticalSizeClass) private var verticalSizeClass
#endif
@EnvironmentObject<PlayerModel> private var player
@EnvironmentObject<ThumbnailsModel> private var thumbnails
var body: some View {
ZStack(alignment: .top) {
Group {
switch player.activeBackend {
case .mpv:
player.mpvPlayerView
case .appleAVPlayer:
player.avPlayerView
#if os(iOS)
.onAppear {
player.pipController = .init(playerLayer: player.playerLayerView.playerLayer)
let pipDelegate = PiPDelegate()
pipDelegate.player = player
player.pipDelegate = pipDelegate
player.pipController?.delegate = pipDelegate
player.playerLayerView.playerLayer.player = player.avPlayerBackend.avPlayer
}
#endif
}
}
.overlay(GeometryReader { proxy in
Color.clear
.onAppear {
player.playerSize = proxy.size
}
.onChange(of: proxy.size) { _ in player.playerSize = proxy.size }
.onChange(of: player.controls.presentingOverlays) { _ in player.playerSize = proxy.size }
.onChange(of: player.aspectRatio) { _ in player.playerSize = proxy.size }
})
#if os(iOS)
.padding(.top, player.playingFullScreen && verticalSizeClass == .regular ? 20 : 0)
#endif
#if !os(tvOS)
PlayerGestures()
PlayerControls(player: player, thumbnails: thumbnails)
#if os(iOS)
.padding(.top, controlsTopPadding)
.padding(.bottom, controlsBottomPadding)
#endif
#endif
}
#if os(iOS)
.statusBarHidden(fullScreenLayout)
#endif
}
var fullScreenLayout: Bool {
#if os(iOS)
player.playingFullScreen || verticalSizeClass == .compact
#else
player.playingFullScreen
#endif
}
#if os(iOS)
var controlsTopPadding: Double {
guard fullScreenLayout else { return 0 }
if UIDevice.current.userInterfaceIdiom != .pad {
return verticalSizeClass == .compact ? safeAreaInsets.top : 0
} else {
return safeAreaInsets.top.isZero ? safeAreaInsets.bottom : safeAreaInsets.top
}
}
var controlsBottomPadding: Double {
guard fullScreenLayout else { return 0 }
if UIDevice.current.userInterfaceIdiom != .pad {
return fullScreenLayout && verticalSizeClass == .compact ? safeAreaInsets.bottom : 0
} else {
return fullScreenLayout ? safeAreaInsets.bottom : 0
}
}
var safeAreaInsets: UIEdgeInsets {
UIApplication.shared.windows.first?.safeAreaInsets ?? .init()
}
#endif
}
struct PlayerBackendView_Previews: PreviewProvider {
static var previews: some View {
PlayerBackendView()
.injectFixtureEnvironmentObjects()
}
}

View File

@ -37,12 +37,12 @@ struct VideoPlayerSizeModifier: ViewModifier {
return VideoPlayerView.defaultAspectRatio
}
return [aspectRatio, VideoPlayerView.defaultAspectRatio].min()!
return aspectRatio
}
var usedAspectRatioContentMode: ContentMode {
#if os(iOS)
!fullScreen ? .fit : .fill
fullScreen ? .fill : .fit
#else
.fit
#endif

View File

@ -95,6 +95,9 @@ struct VideoPlayerView: View {
playerSize = geometry.size
}
}
#if os(iOS)
.frame(width: playerWidth.isNil ? nil : Double(playerWidth!), height: playerHeight.isNil ? nil : Double(playerHeight!))
#endif
.ignoresSafeArea(.all, edges: playerEdgesIgnoringSafeArea)
.onChange(of: geometry.size) { size in
self.playerSize = size
@ -141,25 +144,32 @@ struct VideoPlayerView: View {
#endif
}
var playerEdgesIgnoringSafeArea: Edge.Set {
#if os(iOS)
var playerWidth: Double? {
fullScreenLayout ? (UIScreen.main.bounds.size.width - safeAreaInsets.left - safeAreaInsets.right) : nil
}
var playerHeight: Double? {
fullScreenLayout ? UIScreen.main.bounds.size.height - (OrientationTracker.shared.currentInterfaceOrientation.isPortrait ? (safeAreaInsets.top + safeAreaInsets.bottom) : 0) : nil
}
var playerEdgesIgnoringSafeArea: Edge.Set {
if fullScreenLayout, UIDevice.current.orientation.isLandscape {
return [.vertical]
}
#endif
return []
}
#endif
var content: some View {
Group {
ZStack(alignment: .bottomLeading) {
#if os(tvOS)
ZStack {
playerView
PlayerBackendView()
tvControls
}
.ignoresSafeArea(.all, edges: .all)
.onMoveCommand { direction in
if direction == .up || direction == .down {
playerControls.show()
@ -193,8 +203,7 @@ struct VideoPlayerView: View {
if player.playingInPictureInPicture {
pictureInPicturePlaceholder
} else {
playerView
PlayerBackendView()
#if !os(tvOS)
.modifier(
VideoPlayerSizeModifier(
@ -239,8 +248,8 @@ struct VideoPlayerView: View {
VideoDetails(sidebarQueue: sidebarQueue, fullScreen: $fullScreenDetails)
#endif
}
#if !os(macOS)
.transition(.move(edge: .bottom))
#if os(iOS)
.transition(.asymmetric(insertion: .opacity, removal: .identity))
#endif
.background(colorScheme == .dark ? Color.black : Color.white)
.modifier(VideoDetailsPaddingModifier(
@ -279,102 +288,6 @@ struct VideoPlayerView: View {
#endif
}
var playerView: some View {
ZStack(alignment: .top) {
Group {
switch player.activeBackend {
case .mpv:
player.mpvPlayerView
case .appleAVPlayer:
player.avPlayerView
#if os(iOS)
.onAppear {
player.pipController = .init(playerLayer: player.playerLayerView.playerLayer)
let pipDelegate = PiPDelegate()
pipDelegate.player = player
player.pipDelegate = pipDelegate
player.pipController?.delegate = pipDelegate
player.playerLayerView.playerLayer.player = player.avPlayerBackend.avPlayer
}
#endif
}
}
.overlay(GeometryReader { proxy in
Color.clear
.onAppear { player.playerSize = proxy.size }
.onChange(of: proxy.size) { _ in player.playerSize = proxy.size }
.onChange(of: player.controls.presentingOverlays) { _ in player.playerSize = proxy.size }
.onChange(of: player.aspectRatio) { _ in player.playerSize = proxy.size }
})
#if os(iOS)
.padding(.top, player.playingFullScreen && verticalSizeClass == .regular ? 20 : 0)
#endif
#if !os(tvOS)
PlayerGestures()
PlayerControls(player: player, thumbnails: thumbnails)
#if os(iOS)
.padding(.top, controlsTopPadding)
.padding(.bottom, fullScreenLayout ? safeAreaInsets.bottom : 0)
#endif
#endif
}
#if os(iOS)
.statusBarHidden(fullScreenLayout)
#endif
}
#if os(iOS)
var playerDragGesture: some Gesture {
DragGesture(minimumDistance: 0, coordinateSpace: .global)
.onChanged { value in
guard player.presentingPlayer,
!playerControls.presentingControlsOverlay else { return }
let drag = value.translation.height
guard drag > 0 else { return }
if drag > 60,
player.playingFullScreen,
!OrientationTracker.shared.currentInterfaceOrientation.isLandscape
{
player.exitFullScreen()
player.lockedOrientation = nil
}
viewVerticalOffset = drag
}
.onEnded { _ in
guard player.presentingPlayer,
!playerControls.presentingControlsOverlay else { return }
if viewVerticalOffset > 100 {
player.backend.setNeedsDrawing(false)
player.hide()
player.exitFullScreen()
} else {
viewVerticalOffset = 0
player.backend.setNeedsDrawing(true)
player.show()
}
}
}
var controlsTopPadding: Double {
guard fullScreenLayout else { return 0 }
let idiom = UIDevice.current.userInterfaceIdiom
guard idiom == .pad else { return 0 }
return safeAreaInsets.top.isZero ? safeAreaInsets.bottom : safeAreaInsets.top
}
var safeAreaInsets: UIEdgeInsets {
UIApplication.shared.windows.first?.safeAreaInsets ?? .init()
}
#endif
var fullScreenLayout: Bool {
#if os(iOS)
player.playingFullScreen || verticalSizeClass == .compact
@ -450,6 +363,49 @@ struct VideoPlayerView: View {
}
#if os(iOS)
var playerDragGesture: some Gesture {
DragGesture(minimumDistance: 0, coordinateSpace: .global)
.onChanged { value in
guard player.presentingPlayer,
!playerControls.presentingControlsOverlay else { return }
if player.controls.presentingControls {
player.controls.presentingControls = false
}
let drag = value.translation.height
guard drag > 0 else { return }
if drag > 60,
player.playingFullScreen,
!OrientationTracker.shared.currentInterfaceOrientation.isLandscape
{
player.exitFullScreen()
player.lockedOrientation = nil
}
viewVerticalOffset = drag
}
.onEnded { _ in
guard player.presentingPlayer,
!playerControls.presentingControlsOverlay else { return }
if viewVerticalOffset > 100 {
player.backend.setNeedsDrawing(false)
player.hide()
player.exitFullScreen()
} else {
viewVerticalOffset = 0
player.backend.setNeedsDrawing(true)
player.show()
}
}
}
var safeAreaInsets: UIEdgeInsets {
UIApplication.shared.windows.first?.safeAreaInsets ?? .init()
}
private func configureOrientationUpdatesBasedOnAccelerometer() {
if OrientationTracker.shared.currentInterfaceOrientation.isLandscape,
Defaults[.enterFullscreenInLandscape],
@ -457,6 +413,7 @@ struct VideoPlayerView: View {
!player.playingInPictureInPicture
{
DispatchQueue.main.async {
player.controls.presentingControls = false
player.enterFullScreen()
}
}
@ -488,18 +445,16 @@ struct VideoPlayerView: View {
}
if orientation.isLandscape {
player.controls.presentingControls = false
player.enterFullScreen()
Orientation.lockOrientation(OrientationTracker.shared.currentInterfaceOrientationMask, andRotateTo: orientation)
} else {
if !player.playingFullScreen {
player.exitFullScreen()
} else {
Orientation.lockOrientation(.allButUpsideDown, andRotateTo: .portrait)
}
}
}
}
}
private func stopOrientationUpdates() {
guard let observer = orientationObserver else { return }

View File

@ -340,6 +340,9 @@
375E45F627B1976B00BA7902 /* MPVOGLView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 375E45F427B1976B00BA7902 /* MPVOGLView.swift */; };
375E45F827B1AC4700BA7902 /* PlayerControlsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 375E45F727B1AC4700BA7902 /* PlayerControlsModel.swift */; };
375E45F927B1AC4700BA7902 /* PlayerControlsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 375E45F727B1AC4700BA7902 /* PlayerControlsModel.swift */; };
375F7410289DC35A00747050 /* PlayerBackendView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 375F740F289DC35A00747050 /* PlayerBackendView.swift */; };
375F7411289DC35A00747050 /* PlayerBackendView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 375F740F289DC35A00747050 /* PlayerBackendView.swift */; };
375F7412289DC35A00747050 /* PlayerBackendView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 375F740F289DC35A00747050 /* PlayerBackendView.swift */; };
3761ABFD26F0F8DE00AA496F /* EnvironmentValues.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3761ABFC26F0F8DE00AA496F /* EnvironmentValues.swift */; };
3761ABFE26F0F8DE00AA496F /* EnvironmentValues.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3761ABFC26F0F8DE00AA496F /* EnvironmentValues.swift */; };
3761ABFF26F0F8DE00AA496F /* EnvironmentValues.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3761ABFC26F0F8DE00AA496F /* EnvironmentValues.swift */; };
@ -1033,6 +1036,7 @@
375DFB5726F9DA010013F468 /* InstancesModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstancesModel.swift; sourceTree = "<group>"; };
375E45F427B1976B00BA7902 /* MPVOGLView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPVOGLView.swift; sourceTree = "<group>"; };
375E45F727B1AC4700BA7902 /* PlayerControlsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerControlsModel.swift; sourceTree = "<group>"; };
375F740F289DC35A00747050 /* PlayerBackendView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerBackendView.swift; sourceTree = "<group>"; };
3761ABFC26F0F8DE00AA496F /* EnvironmentValues.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EnvironmentValues.swift; sourceTree = "<group>"; };
3763495026DFF59D00B9A393 /* AppSidebarRecents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSidebarRecents.swift; sourceTree = "<group>"; };
37648B68286CF5F1003D330B /* TVControls.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TVControls.swift; sourceTree = "<group>"; };
@ -1491,7 +1495,9 @@
371B7E602759706A00D21217 /* CommentsView.swift */,
37EF9A75275BEB8E0043B585 /* CommentView.swift */,
37DD9DA22785BBC900539416 /* NoCommentsView.swift */,
375F740F289DC35A00747050 /* PlayerBackendView.swift */,
3703100127B0713600ECDDAA /* PlayerGestures.swift */,
373031F22838388A000CFD59 /* PlayerLayerView.swift */,
3743CA4D270EFE3400E4D32B /* PlayerQueueRow.swift */,
37CC3F4B270CFE1700608308 /* PlayerQueueView.swift */,
373197D82732015300EF734F /* RelatedView.swift */,
@ -1501,7 +1507,6 @@
37B81AF826D2C9A700675966 /* VideoPlayerSizeModifier.swift */,
37BE0BCE26A0E2D50092E2DB /* VideoPlayerView.swift */,
37F9619A27BD89E000058149 /* TapRecognizerViewModifier.swift */,
373031F22838388A000CFD59 /* PlayerLayerView.swift */,
);
path = Player;
sourceTree = "<group>";
@ -2789,6 +2794,7 @@
37C3A24527235DA70087A57A /* ChannelPlaylist.swift in Sources */,
37030FFF27B04DCC00ECDDAA /* PlayerControls.swift in Sources */,
374C053F272472C0009BDDBE /* PlayerSponsorBlock.swift in Sources */,
375F7410289DC35A00747050 /* PlayerBackendView.swift in Sources */,
37FB28412721B22200A57617 /* ContentItem.swift in Sources */,
37C3A24D272360470087A57A /* ChannelPlaylist+Fixtures.swift in Sources */,
37484C2D26FC844700287258 /* InstanceSettings.swift in Sources */,
@ -3008,6 +3014,7 @@
3748186726A7627F0084E870 /* Video+Fixtures.swift in Sources */,
3784B23E2728B85300B09468 /* ShareButton.swift in Sources */,
37BE0BDA26A214630092E2DB /* AppleAVPlayerViewController.swift in Sources */,
375F7411289DC35A00747050 /* PlayerBackendView.swift in Sources */,
37FEF11427EFD8580033912F /* PlaceholderCell.swift in Sources */,
37E64DD226D597EB00C71877 /* SubscriptionsModel.swift in Sources */,
374108D1272B11B2006C5CC8 /* PictureInPictureDelegate.swift in Sources */,
@ -3226,6 +3233,7 @@
37E70929271CDDAE00D34DDE /* OpenSettingsButton.swift in Sources */,
370F4FAA27CC163B001B35DC /* PlayerBackend.swift in Sources */,
376A33E62720CB35000C1D6B /* Account.swift in Sources */,
375F7412289DC35A00747050 /* PlayerBackendView.swift in Sources */,
37599F3A272B4D740087F250 /* FavoriteButton.swift in Sources */,
37EF5C242739D37B00B03725 /* MenuModel.swift in Sources */,
37C7A1D7267BFD9D0010EAD6 /* SponsorBlockSegment.swift in Sources */,