mirror of
https://github.com/yattee/yattee.git
synced 2024-12-22 21:43:41 +00:00
Improve fullscreen animation
This commit is contained in:
parent
8318b4c5fb
commit
5f5bd37bd4
@ -811,7 +811,7 @@ final class PlayerModel: ObservableObject {
|
||||
}
|
||||
|
||||
func toggleFullscreen(_ isFullScreen: Bool) {
|
||||
controls.resetTimer()
|
||||
controls.presentingControls = false
|
||||
|
||||
#if os(macOS)
|
||||
if isFullScreen {
|
||||
|
98
Shared/Player/PlayerBackendView.swift
Normal file
98
Shared/Player/PlayerBackendView.swift
Normal 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()
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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)
|
||||
#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 []
|
||||
}
|
||||
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,17 +203,16 @@ struct VideoPlayerView: View {
|
||||
if player.playingInPictureInPicture {
|
||||
pictureInPicturePlaceholder
|
||||
} else {
|
||||
playerView
|
||||
|
||||
PlayerBackendView()
|
||||
#if !os(tvOS)
|
||||
.modifier(
|
||||
VideoPlayerSizeModifier(
|
||||
geometry: geometry,
|
||||
aspectRatio: player.aspectRatio,
|
||||
fullScreen: fullScreenLayout
|
||||
.modifier(
|
||||
VideoPlayerSizeModifier(
|
||||
geometry: geometry,
|
||||
aspectRatio: player.aspectRatio,
|
||||
fullScreen: fullScreenLayout
|
||||
)
|
||||
)
|
||||
)
|
||||
.overlay(playerPlaceholder)
|
||||
.overlay(playerPlaceholder)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@ -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,14 +445,12 @@ 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)
|
||||
}
|
||||
player.exitFullScreen()
|
||||
Orientation.lockOrientation(.allButUpsideDown, andRotateTo: .portrait)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 */,
|
||||
|
Loading…
Reference in New Issue
Block a user