mirror of
https://github.com/yattee/yattee.git
synced 2024-12-23 05:53:41 +00:00
parent
026a65bfd7
commit
a71a7760be
@ -1,5 +1,6 @@
|
|||||||
import AVFAudio
|
import AVFAudio
|
||||||
import CoreMedia
|
import CoreMedia
|
||||||
|
import Defaults
|
||||||
import Foundation
|
import Foundation
|
||||||
import Logging
|
import Logging
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
@ -247,7 +248,13 @@ final class MPVBackend: PlayerBackend {
|
|||||||
client?.stop()
|
client?.stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
func enterFullScreen() {}
|
func enterFullScreen() {
|
||||||
|
model.toggleFullscreen(controls?.playingFullscreen ?? false)
|
||||||
|
|
||||||
|
if Defaults[.lockLandscapeWhenEnteringFullscreen] {
|
||||||
|
Orientation.lockOrientation(.landscape, andRotateTo: UIDevice.current.orientation.isLandscape ? nil : .landscapeRight)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func exitFullScreen() {}
|
func exitFullScreen() {}
|
||||||
|
|
||||||
|
@ -134,11 +134,11 @@ final class MPVClient: ObservableObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var currentTime: CMTime {
|
var currentTime: CMTime {
|
||||||
CMTime.secondsInDefaultTimescale(getDouble("time-pos"))
|
CMTime.secondsInDefaultTimescale(mpv.isNil ? -1 : getDouble("time-pos"))
|
||||||
}
|
}
|
||||||
|
|
||||||
var duration: CMTime {
|
var duration: CMTime {
|
||||||
CMTime.secondsInDefaultTimescale(getDouble("duration"))
|
CMTime.secondsInDefaultTimescale(mpv.isNil ? -1 : getDouble("duration"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func seek(relative time: CMTime, completionHandler: ((Bool) -> Void)? = nil) {
|
func seek(relative time: CMTime, completionHandler: ((Bool) -> Void)? = nil) {
|
||||||
|
@ -57,8 +57,6 @@ final class PlayerModel: ObservableObject {
|
|||||||
|
|
||||||
@Published var preservedTime: CMTime?
|
@Published var preservedTime: CMTime?
|
||||||
|
|
||||||
@Published var playerNavigationLinkActive = false { didSet { handleNavigationViewPlayerPresentationChange() } }
|
|
||||||
|
|
||||||
@Published var sponsorBlock = SponsorBlockAPI()
|
@Published var sponsorBlock = SponsorBlockAPI()
|
||||||
@Published var segmentRestorationTime: CMTime?
|
@Published var segmentRestorationTime: CMTime?
|
||||||
@Published var lastSkipped: Segment? { didSet { rebuildTVMenu() } }
|
@Published var lastSkipped: Segment? { didSet { rebuildTVMenu() } }
|
||||||
@ -120,23 +118,24 @@ final class PlayerModel: ObservableObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func show() {
|
func show() {
|
||||||
guard !presentingPlayer else {
|
#if os(macOS)
|
||||||
#if os(macOS)
|
if presentingPlayer {
|
||||||
Windows.player.focus()
|
Windows.player.focus()
|
||||||
#endif
|
return
|
||||||
return
|
}
|
||||||
}
|
#endif
|
||||||
|
|
||||||
|
presentingPlayer = true
|
||||||
|
|
||||||
#if os(macOS)
|
#if os(macOS)
|
||||||
Windows.player.open()
|
Windows.player.open()
|
||||||
Windows.player.focus()
|
Windows.player.focus()
|
||||||
#endif
|
#endif
|
||||||
presentingPlayer = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func hide() {
|
func hide() {
|
||||||
controls.playingFullscreen = false
|
controls.playingFullscreen = false
|
||||||
presentingPlayer = false
|
presentingPlayer = false
|
||||||
playerNavigationLinkActive = false
|
|
||||||
|
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
if Defaults[.lockPortraitWhenBrowsing] {
|
if Defaults[.lockPortraitWhenBrowsing] {
|
||||||
@ -206,18 +205,25 @@ final class PlayerModel: ObservableObject {
|
|||||||
backend.pause()
|
backend.pause()
|
||||||
}
|
}
|
||||||
|
|
||||||
func play(_ video: Video, at time: CMTime? = nil, inNavigationView: Bool = false) {
|
func play(_ video: Video, at time: CMTime? = nil) {
|
||||||
playNow(video, at: time)
|
var delay = 0.0
|
||||||
|
#if !os(macOS)
|
||||||
|
delay = 0.3
|
||||||
|
#endif
|
||||||
|
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [weak self] in
|
||||||
|
guard let self = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
self.playNow(video, at: time)
|
||||||
|
}
|
||||||
|
|
||||||
guard !playingInPictureInPicture else {
|
guard !playingInPictureInPicture else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if inNavigationView {
|
show()
|
||||||
playerNavigationLinkActive = true
|
|
||||||
} else {
|
|
||||||
show()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func playStream(
|
func playStream(
|
||||||
@ -297,7 +303,18 @@ final class PlayerModel: ObservableObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func handlePresentationChange() {
|
private func handlePresentationChange() {
|
||||||
backend.setNeedsDrawing(presentingPlayer)
|
var delay = 0.0
|
||||||
|
|
||||||
|
#if os(iOS)
|
||||||
|
if presentingPlayer {
|
||||||
|
delay = 0.2
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [weak self] in
|
||||||
|
self?.backend.setNeedsDrawing(self?.presentingPlayer ?? false)
|
||||||
|
}
|
||||||
|
|
||||||
controls.hide()
|
controls.hide()
|
||||||
|
|
||||||
#if !os(macOS)
|
#if !os(macOS)
|
||||||
@ -323,17 +340,6 @@ final class PlayerModel: ObservableObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func handleNavigationViewPlayerPresentationChange() {
|
|
||||||
backend.setNeedsDrawing(playerNavigationLinkActive)
|
|
||||||
controls.hide()
|
|
||||||
|
|
||||||
if pauseOnHidingPlayer, !playingInPictureInPicture, !playerNavigationLinkActive {
|
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
|
||||||
self.pause()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func changeActiveBackend(from: PlayerBackendType, to: PlayerBackendType) {
|
func changeActiveBackend(from: PlayerBackendType, to: PlayerBackendType) {
|
||||||
Defaults[.activeBackend] = to
|
Defaults[.activeBackend] = to
|
||||||
self.activeBackend = to
|
self.activeBackend = to
|
||||||
|
@ -8,7 +8,7 @@ extension PlayerModel {
|
|||||||
currentItem?.video
|
currentItem?.video
|
||||||
}
|
}
|
||||||
|
|
||||||
func play(_ videos: [Video], shuffling: Bool = false, inNavigationView: Bool = false) {
|
func play(_ videos: [Video], shuffling: Bool = false) {
|
||||||
let videosToPlay = shuffling ? videos.shuffled() : videos
|
let videosToPlay = shuffling ? videos.shuffled() : videos
|
||||||
|
|
||||||
guard let first = videosToPlay.first else {
|
guard let first = videosToPlay.first else {
|
||||||
@ -27,11 +27,7 @@ extension PlayerModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if inNavigationView {
|
show()
|
||||||
playerNavigationLinkActive = true
|
|
||||||
} else {
|
|
||||||
show()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func playNext(_ video: Video) {
|
func playNext(_ video: Video) {
|
||||||
|
@ -1,10 +1,6 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
private struct InNavigationViewKey: EnvironmentKey {
|
|
||||||
static let defaultValue = false
|
|
||||||
}
|
|
||||||
|
|
||||||
private struct InChannelViewKey: EnvironmentKey {
|
private struct InChannelViewKey: EnvironmentKey {
|
||||||
static let defaultValue = false
|
static let defaultValue = false
|
||||||
}
|
}
|
||||||
@ -40,11 +36,6 @@ private struct ScrollViewBottomPaddingKey: EnvironmentKey {
|
|||||||
}
|
}
|
||||||
|
|
||||||
extension EnvironmentValues {
|
extension EnvironmentValues {
|
||||||
var inNavigationView: Bool {
|
|
||||||
get { self[InNavigationViewKey.self] }
|
|
||||||
set { self[InNavigationViewKey.self] = newValue }
|
|
||||||
}
|
|
||||||
|
|
||||||
var inChannelView: Bool {
|
var inChannelView: Bool {
|
||||||
get { self[InChannelViewKey.self] }
|
get { self[InChannelViewKey.self] }
|
||||||
set { self[InChannelViewKey.self] = newValue }
|
set { self[InChannelViewKey.self] = newValue }
|
||||||
|
@ -63,24 +63,6 @@ struct AppSidebarNavigation: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.environment(\.navigationStyle, .sidebar)
|
.environment(\.navigationStyle, .sidebar)
|
||||||
#if os(iOS)
|
|
||||||
.background(
|
|
||||||
EmptyView().fullScreenCover(isPresented: $player.presentingPlayer) {
|
|
||||||
VideoPlayerView()
|
|
||||||
.environmentObject(accounts)
|
|
||||||
.environmentObject(comments)
|
|
||||||
.environmentObject(instances)
|
|
||||||
.environmentObject(navigation)
|
|
||||||
.environmentObject(player)
|
|
||||||
.environmentObject(playerControls)
|
|
||||||
.environmentObject(playlists)
|
|
||||||
.environmentObject(recents)
|
|
||||||
.environmentObject(subscriptions)
|
|
||||||
.environmentObject(thumbnailsModel)
|
|
||||||
.environment(\.navigationStyle, .sidebar)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var toolbarContent: some ToolbarContent {
|
var toolbarContent: some ToolbarContent {
|
||||||
|
@ -51,14 +51,11 @@ struct AppTabNavigation: View {
|
|||||||
ChannelVideosView(channel: channel)
|
ChannelVideosView(channel: channel)
|
||||||
.environment(\.managedObjectContext, persistenceController.container.viewContext)
|
.environment(\.managedObjectContext, persistenceController.container.viewContext)
|
||||||
.environment(\.inChannelView, true)
|
.environment(\.inChannelView, true)
|
||||||
.environment(\.inNavigationView, true)
|
|
||||||
.environmentObject(accounts)
|
.environmentObject(accounts)
|
||||||
.environmentObject(navigation)
|
.environmentObject(navigation)
|
||||||
.environmentObject(player)
|
.environmentObject(player)
|
||||||
.environmentObject(subscriptions)
|
.environmentObject(subscriptions)
|
||||||
.environmentObject(thumbnailsModel)
|
.environmentObject(thumbnailsModel)
|
||||||
|
|
||||||
.background(playerNavigationLink)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -69,25 +66,15 @@ struct AppTabNavigation: View {
|
|||||||
NavigationView {
|
NavigationView {
|
||||||
ChannelPlaylistView(playlist: playlist)
|
ChannelPlaylistView(playlist: playlist)
|
||||||
.environment(\.managedObjectContext, persistenceController.container.viewContext)
|
.environment(\.managedObjectContext, persistenceController.container.viewContext)
|
||||||
.environment(\.inNavigationView, true)
|
|
||||||
.environmentObject(accounts)
|
.environmentObject(accounts)
|
||||||
.environmentObject(navigation)
|
.environmentObject(navigation)
|
||||||
.environmentObject(player)
|
.environmentObject(player)
|
||||||
.environmentObject(subscriptions)
|
.environmentObject(subscriptions)
|
||||||
.environmentObject(thumbnailsModel)
|
.environmentObject(thumbnailsModel)
|
||||||
|
|
||||||
.background(playerNavigationLink)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.background(
|
|
||||||
EmptyView().fullScreenCover(isPresented: $player.presentingPlayer) {
|
|
||||||
videoPlayer
|
|
||||||
.environment(\.managedObjectContext, persistenceController.container.viewContext)
|
|
||||||
.environment(\.navigationStyle, .tab)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private var favoritesNavigationView: some View {
|
private var favoritesNavigationView: some View {
|
||||||
@ -172,15 +159,6 @@ struct AppTabNavigation: View {
|
|||||||
.tag(TabSelection.search)
|
.tag(TabSelection.search)
|
||||||
}
|
}
|
||||||
|
|
||||||
private var playerNavigationLink: some View {
|
|
||||||
NavigationLink(isActive: $player.playerNavigationLinkActive, destination: {
|
|
||||||
videoPlayer
|
|
||||||
.environment(\.inNavigationView, true)
|
|
||||||
}) {
|
|
||||||
EmptyView()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private var videoPlayer: some View {
|
private var videoPlayer: some View {
|
||||||
VideoPlayerView()
|
VideoPlayerView()
|
||||||
.environmentObject(accounts)
|
.environmentObject(accounts)
|
||||||
|
@ -57,50 +57,54 @@ struct ContentView: View {
|
|||||||
.environmentObject(subscriptions)
|
.environmentObject(subscriptions)
|
||||||
.environmentObject(thumbnailsModel)
|
.environmentObject(thumbnailsModel)
|
||||||
|
|
||||||
// iOS 14 has problem with multiple sheets in one view
|
#if !os(macOS)
|
||||||
// but it's ok when it's in background
|
.overlay(videoPlayer)
|
||||||
.background(
|
|
||||||
EmptyView().sheet(isPresented: $navigation.presentingWelcomeScreen) {
|
|
||||||
WelcomeScreen()
|
|
||||||
.environmentObject(accounts)
|
|
||||||
.environmentObject(navigation)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
#if !os(tvOS)
|
|
||||||
.onOpenURL { OpenURLHandler(accounts: accounts, player: player).handle($0) }
|
|
||||||
.background(
|
|
||||||
EmptyView().sheet(isPresented: $navigation.presentingAddToPlaylist) {
|
|
||||||
AddToPlaylistView(video: navigation.videoToAddToPlaylist)
|
|
||||||
.environmentObject(playlists)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.background(
|
|
||||||
EmptyView().sheet(isPresented: $navigation.presentingPlaylistForm) {
|
|
||||||
PlaylistFormView(playlist: $navigation.editedPlaylist)
|
|
||||||
.environmentObject(accounts)
|
|
||||||
.environmentObject(playlists)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.background(
|
|
||||||
EmptyView().sheet(isPresented: $navigation.presentingSettings, onDismiss: openWelcomeScreenIfAccountEmpty) {
|
|
||||||
SettingsView()
|
|
||||||
.environmentObject(accounts)
|
|
||||||
.environmentObject(instances)
|
|
||||||
.environmentObject(player)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
#endif
|
#endif
|
||||||
.alert(isPresented: $navigation.presentingUnsubscribeAlert) {
|
|
||||||
Alert(
|
// iOS 14 has problem with multiple sheets in one view
|
||||||
title: Text(
|
// but it's ok when it's in background
|
||||||
"Are you sure you want to unsubscribe from \(navigation.channelToUnsubscribe.name)?"
|
.background(
|
||||||
),
|
EmptyView().sheet(isPresented: $navigation.presentingWelcomeScreen) {
|
||||||
primaryButton: .destructive(Text("Unsubscribe")) {
|
WelcomeScreen()
|
||||||
subscriptions.unsubscribe(navigation.channelToUnsubscribe.id)
|
.environmentObject(accounts)
|
||||||
},
|
.environmentObject(navigation)
|
||||||
secondaryButton: .cancel()
|
}
|
||||||
)
|
)
|
||||||
}
|
#if !os(tvOS)
|
||||||
|
.onOpenURL { OpenURLHandler(accounts: accounts, player: player).handle($0) }
|
||||||
|
.background(
|
||||||
|
EmptyView().sheet(isPresented: $navigation.presentingAddToPlaylist) {
|
||||||
|
AddToPlaylistView(video: navigation.videoToAddToPlaylist)
|
||||||
|
.environmentObject(playlists)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.background(
|
||||||
|
EmptyView().sheet(isPresented: $navigation.presentingPlaylistForm) {
|
||||||
|
PlaylistFormView(playlist: $navigation.editedPlaylist)
|
||||||
|
.environmentObject(accounts)
|
||||||
|
.environmentObject(playlists)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.background(
|
||||||
|
EmptyView().sheet(isPresented: $navigation.presentingSettings, onDismiss: openWelcomeScreenIfAccountEmpty) {
|
||||||
|
SettingsView()
|
||||||
|
.environmentObject(accounts)
|
||||||
|
.environmentObject(instances)
|
||||||
|
.environmentObject(player)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
#endif
|
||||||
|
.alert(isPresented: $navigation.presentingUnsubscribeAlert) {
|
||||||
|
Alert(
|
||||||
|
title: Text(
|
||||||
|
"Are you sure you want to unsubscribe from \(navigation.channelToUnsubscribe.name)?"
|
||||||
|
),
|
||||||
|
primaryButton: .destructive(Text("Unsubscribe")) {
|
||||||
|
subscriptions.unsubscribe(navigation.channelToUnsubscribe.id)
|
||||||
|
},
|
||||||
|
secondaryButton: .cancel()
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func configure() {
|
func configure() {
|
||||||
@ -222,6 +226,21 @@ struct ContentView: View {
|
|||||||
|
|
||||||
navigation.presentingWelcomeScreen = true
|
navigation.presentingWelcomeScreen = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var videoPlayer: some View {
|
||||||
|
VideoPlayerView()
|
||||||
|
.environmentObject(accounts)
|
||||||
|
.environmentObject(comments)
|
||||||
|
.environmentObject(instances)
|
||||||
|
.environmentObject(navigation)
|
||||||
|
.environmentObject(player)
|
||||||
|
.environmentObject(playerControls)
|
||||||
|
.environmentObject(playlists)
|
||||||
|
.environmentObject(recents)
|
||||||
|
.environmentObject(subscriptions)
|
||||||
|
.environmentObject(thumbnailsModel)
|
||||||
|
.environment(\.navigationStyle, .sidebar)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ContentView_Previews: PreviewProvider {
|
struct ContentView_Previews: PreviewProvider {
|
||||||
|
@ -178,11 +178,7 @@ extension AppleAVPlayerViewController: AVPlayerViewControllerDelegate {
|
|||||||
restoreUserInterfaceForPictureInPictureStopWithCompletionHandler completionHandler: @escaping (Bool) -> Void
|
restoreUserInterfaceForPictureInPictureStopWithCompletionHandler completionHandler: @escaping (Bool) -> Void
|
||||||
) {
|
) {
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||||
if self.navigationModel.presentingChannel {
|
self.playerModel.show()
|
||||||
self.playerModel.playerNavigationLinkActive = true
|
|
||||||
} else {
|
|
||||||
self.playerModel.show()
|
|
||||||
}
|
|
||||||
|
|
||||||
#if os(tvOS)
|
#if os(tvOS)
|
||||||
if self.playerModel.playingInPictureInPicture {
|
if self.playerModel.playingInPictureInPicture {
|
||||||
@ -198,7 +194,6 @@ extension AppleAVPlayerViewController: AVPlayerViewControllerDelegate {
|
|||||||
|
|
||||||
func playerViewControllerWillStartPictureInPicture(_: AVPlayerViewController) {
|
func playerViewControllerWillStartPictureInPicture(_: AVPlayerViewController) {
|
||||||
playerModel.playingInPictureInPicture = true
|
playerModel.playingInPictureInPicture = true
|
||||||
playerModel.playerNavigationLinkActive = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func playerViewControllerWillStopPictureInPicture(_: AVPlayerViewController) {
|
func playerViewControllerWillStopPictureInPicture(_: AVPlayerViewController) {
|
||||||
|
@ -240,7 +240,7 @@ struct PlayerControls: View {
|
|||||||
player.hide()
|
player.hide()
|
||||||
player.closePiP()
|
player.closePiP()
|
||||||
|
|
||||||
var delay = 0.3
|
var delay = 0.2
|
||||||
#if os(macOS)
|
#if os(macOS)
|
||||||
delay = 0.0
|
delay = 0.0
|
||||||
#endif
|
#endif
|
||||||
|
@ -21,7 +21,6 @@ struct VideoDetails: View {
|
|||||||
@State private var currentPage = Page.info
|
@State private var currentPage = Page.info
|
||||||
|
|
||||||
@Environment(\.presentationMode) private var presentationMode
|
@Environment(\.presentationMode) private var presentationMode
|
||||||
@Environment(\.inNavigationView) private var inNavigationView
|
|
||||||
@Environment(\.navigationStyle) private var navigationStyle
|
@Environment(\.navigationStyle) private var navigationStyle
|
||||||
|
|
||||||
@EnvironmentObject<AccountsModel> private var accounts
|
@EnvironmentObject<AccountsModel> private var accounts
|
||||||
@ -112,7 +111,6 @@ struct VideoDetails: View {
|
|||||||
.edgesIgnoringSafeArea(.horizontal)
|
.edgesIgnoringSafeArea(.horizontal)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(.top, inNavigationView && fullScreen ? 10 : 0)
|
|
||||||
.onAppear {
|
.onAppear {
|
||||||
if video.isNil && !sidebarQueue {
|
if video.isNil && !sidebarQueue {
|
||||||
currentPage = .queue
|
currentPage = .queue
|
||||||
|
@ -37,6 +37,10 @@ struct VideoPlayerView: View {
|
|||||||
var mouseLocation: CGPoint { NSEvent.mouseLocation }
|
var mouseLocation: CGPoint { NSEvent.mouseLocation }
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if !os(macOS)
|
||||||
|
@State private var playerOffset = UIScreen.main.bounds.height
|
||||||
|
#endif
|
||||||
|
|
||||||
@EnvironmentObject<AccountsModel> private var accounts
|
@EnvironmentObject<AccountsModel> private var accounts
|
||||||
@EnvironmentObject<PlayerControlsModel> private var playerControls
|
@EnvironmentObject<PlayerControlsModel> private var playerControls
|
||||||
@EnvironmentObject<PlayerModel> private var player
|
@EnvironmentObject<PlayerModel> private var player
|
||||||
@ -54,10 +58,6 @@ struct VideoPlayerView: View {
|
|||||||
content
|
content
|
||||||
.onAppear {
|
.onAppear {
|
||||||
playerSize = geometry.size
|
playerSize = geometry.size
|
||||||
|
|
||||||
#if os(iOS)
|
|
||||||
configureOrientationUpdatesBasedOnAccelerometer()
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onChange(of: geometry.size) { size in
|
.onChange(of: geometry.size) { size in
|
||||||
@ -70,22 +70,10 @@ struct VideoPlayerView: View {
|
|||||||
.onReceive(NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification)) { _ in
|
.onReceive(NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification)) { _ in
|
||||||
handleOrientationDidChangeNotification()
|
handleOrientationDidChangeNotification()
|
||||||
}
|
}
|
||||||
.onDisappear {
|
|
||||||
guard !playerControls.playingFullscreen else {
|
|
||||||
return // swiftlint:disable:this implicit_return
|
|
||||||
}
|
|
||||||
|
|
||||||
if Defaults[.lockPortraitWhenBrowsing] {
|
|
||||||
Orientation.lockOrientation(.portrait, andRotateTo: .portrait)
|
|
||||||
} else {
|
|
||||||
Orientation.lockOrientation(.allButUpsideDown)
|
|
||||||
}
|
|
||||||
|
|
||||||
motionManager?.stopAccelerometerUpdates()
|
|
||||||
motionManager = nil
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
.offset(y: playerOffset)
|
||||||
|
.animation(.easeIn(duration: 0.2), value: playerOffset)
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -138,6 +126,59 @@ struct VideoPlayerView: View {
|
|||||||
hoveringPlayer = hovering
|
hoveringPlayer = hovering
|
||||||
hovering ? playerControls.show() : playerControls.hide()
|
hovering ? playerControls.show() : playerControls.hide()
|
||||||
}
|
}
|
||||||
|
#if !os(tvOS)
|
||||||
|
.onChange(of: player.presentingPlayer) { newValue in
|
||||||
|
if newValue {
|
||||||
|
playerOffset = 0
|
||||||
|
#if os(iOS)
|
||||||
|
configureOrientationUpdatesBasedOnAccelerometer()
|
||||||
|
#endif
|
||||||
|
} else {
|
||||||
|
#if !os(macOS)
|
||||||
|
playerOffset = UIScreen.main.bounds.height
|
||||||
|
|
||||||
|
#if os(iOS)
|
||||||
|
if Defaults[.lockPortraitWhenBrowsing] {
|
||||||
|
Orientation.lockOrientation(.portrait, andRotateTo: .portrait)
|
||||||
|
} else {
|
||||||
|
Orientation.lockOrientation(.allButUpsideDown)
|
||||||
|
}
|
||||||
|
|
||||||
|
motionManager?.stopAccelerometerUpdates()
|
||||||
|
motionManager = nil
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.gesture(
|
||||||
|
DragGesture(minimumDistance: 0)
|
||||||
|
.onChanged { value in
|
||||||
|
guard !fullScreenLayout else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
player.backend.setNeedsDrawing(false)
|
||||||
|
let drag = value.translation.height
|
||||||
|
|
||||||
|
guard drag > 0 else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
withAnimation(.easeIn(duration: 0.2)) {
|
||||||
|
playerOffset = drag
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onEnded { _ in
|
||||||
|
if playerOffset > 100 {
|
||||||
|
player.backend.setNeedsDrawing(true)
|
||||||
|
player.hide()
|
||||||
|
} else {
|
||||||
|
player.show()
|
||||||
|
playerOffset = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
#endif
|
||||||
#if os(macOS)
|
#if os(macOS)
|
||||||
.onAppear(perform: {
|
.onAppear(perform: {
|
||||||
NSEvent.addLocalMonitorForEvents(matching: [.mouseMoved]) {
|
NSEvent.addLocalMonitorForEvents(matching: [.mouseMoved]) {
|
||||||
@ -406,7 +447,8 @@ struct VideoPlayerView: View {
|
|||||||
} else {
|
} else {
|
||||||
guard abs(acceleration.z) <= 0.74,
|
guard abs(acceleration.z) <= 0.74,
|
||||||
player.lockedOrientation.isNil,
|
player.lockedOrientation.isNil,
|
||||||
enterFullscreenInLandscape
|
enterFullscreenInLandscape,
|
||||||
|
!lockLandscapeOnRotation
|
||||||
else {
|
else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -421,6 +463,7 @@ struct VideoPlayerView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func handleOrientationDidChangeNotification() {
|
private func handleOrientationDidChangeNotification() {
|
||||||
|
playerOffset = playerOffset == 0 ? 0 : UIScreen.main.bounds.height
|
||||||
let newOrientation = UIApplication.shared.windows.first?.windowScene?.interfaceOrientation
|
let newOrientation = UIApplication.shared.windows.first?.windowScene?.interfaceOrientation
|
||||||
if newOrientation?.isLandscape ?? false,
|
if newOrientation?.isLandscape ?? false,
|
||||||
player.presentingPlayer,
|
player.presentingPlayer,
|
||||||
|
@ -6,7 +6,6 @@ import SwiftUI
|
|||||||
struct VideoCell: View {
|
struct VideoCell: View {
|
||||||
private var video: Video
|
private var video: Video
|
||||||
|
|
||||||
@Environment(\.inNavigationView) private var inNavigationView
|
|
||||||
@Environment(\.navigationStyle) private var navigationStyle
|
@Environment(\.navigationStyle) private var navigationStyle
|
||||||
|
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
@ -46,11 +45,8 @@ struct VideoCell: View {
|
|||||||
.buttonStyle(.plain)
|
.buttonStyle(.plain)
|
||||||
.contentShape(RoundedRectangle(cornerRadius: thumbnailRoundingCornerRadius))
|
.contentShape(RoundedRectangle(cornerRadius: thumbnailRoundingCornerRadius))
|
||||||
.contextMenu {
|
.contextMenu {
|
||||||
VideoContextMenuView(
|
VideoContextMenuView(video: video)
|
||||||
video: video,
|
.environmentObject(accounts)
|
||||||
playerNavigationLinkActive: $player.playerNavigationLinkActive
|
|
||||||
)
|
|
||||||
.environmentObject(accounts)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,7 +89,7 @@ struct VideoCell: View {
|
|||||||
|
|
||||||
player.avPlayerBackend.startPictureInPictureOnPlay = player.playingInPictureInPicture
|
player.avPlayerBackend.startPictureInPictureOnPlay = player.playingInPictureInPicture
|
||||||
|
|
||||||
player.play(video, at: playAt, inNavigationView: inNavigationView)
|
player.play(video, at: playAt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,7 +10,6 @@ struct ChannelPlaylistView: View {
|
|||||||
@StateObject private var store = Store<ChannelPlaylist>()
|
@StateObject private var store = Store<ChannelPlaylist>()
|
||||||
|
|
||||||
@Environment(\.colorScheme) private var colorScheme
|
@Environment(\.colorScheme) private var colorScheme
|
||||||
@Environment(\.inNavigationView) private var inNavigationView
|
|
||||||
|
|
||||||
@EnvironmentObject<AccountsModel> private var accounts
|
@EnvironmentObject<AccountsModel> private var accounts
|
||||||
@EnvironmentObject<PlayerModel> private var player
|
@EnvironmentObject<PlayerModel> private var player
|
||||||
@ -24,19 +23,9 @@ struct ChannelPlaylistView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
#if os(iOS)
|
BrowserPlayerControls {
|
||||||
if inNavigationView {
|
content
|
||||||
content
|
}
|
||||||
} else {
|
|
||||||
BrowserPlayerControls {
|
|
||||||
content
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
BrowserPlayerControls {
|
|
||||||
content
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var content: some View {
|
var content: some View {
|
||||||
@ -94,9 +83,6 @@ struct ChannelPlaylistView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.navigationTitle(playlist.title)
|
.navigationTitle(playlist.title)
|
||||||
#if os(iOS)
|
|
||||||
.navigationBarHidden(player.playerNavigationLinkActive)
|
|
||||||
#endif
|
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,7 +96,7 @@ struct ChannelPlaylistView: View {
|
|||||||
|
|
||||||
private var playButton: some View {
|
private var playButton: some View {
|
||||||
Button {
|
Button {
|
||||||
player.play(videos, inNavigationView: inNavigationView)
|
player.play(videos)
|
||||||
} label: {
|
} label: {
|
||||||
Label("Play All", systemImage: "play")
|
Label("Play All", systemImage: "play")
|
||||||
}
|
}
|
||||||
@ -118,7 +104,7 @@ struct ChannelPlaylistView: View {
|
|||||||
|
|
||||||
private var shuffleButton: some View {
|
private var shuffleButton: some View {
|
||||||
Button {
|
Button {
|
||||||
player.play(videos, shuffling: true, inNavigationView: inNavigationView)
|
player.play(videos, shuffling: true)
|
||||||
} label: {
|
} label: {
|
||||||
Label("Shuffle", systemImage: "shuffle")
|
Label("Shuffle", systemImage: "shuffle")
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,6 @@ struct ChannelVideosView: View {
|
|||||||
@Environment(\.colorScheme) private var colorScheme
|
@Environment(\.colorScheme) private var colorScheme
|
||||||
|
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
@Environment(\.inNavigationView) private var inNavigationView
|
|
||||||
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
|
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
|
||||||
@EnvironmentObject<PlayerModel> private var player
|
@EnvironmentObject<PlayerModel> private var player
|
||||||
#endif
|
#endif
|
||||||
@ -29,19 +28,9 @@ struct ChannelVideosView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
#if os(iOS)
|
BrowserPlayerControls {
|
||||||
if inNavigationView {
|
content
|
||||||
content
|
}
|
||||||
} else {
|
|
||||||
BrowserPlayerControls {
|
|
||||||
content
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
BrowserPlayerControls {
|
|
||||||
content
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var content: some View {
|
var content: some View {
|
||||||
@ -115,9 +104,6 @@ struct ChannelVideosView: View {
|
|||||||
resource.load()
|
resource.load()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#if os(iOS)
|
|
||||||
.navigationBarHidden(player.playerNavigationLinkActive)
|
|
||||||
#endif
|
|
||||||
.navigationTitle(navigationTitle)
|
.navigationTitle(navigationTitle)
|
||||||
|
|
||||||
return Group {
|
return Group {
|
||||||
|
@ -4,7 +4,6 @@ import SwiftUI
|
|||||||
struct PlaylistVideosView: View {
|
struct PlaylistVideosView: View {
|
||||||
let playlist: Playlist
|
let playlist: Playlist
|
||||||
|
|
||||||
@Environment(\.inNavigationView) private var inNavigationView
|
|
||||||
@EnvironmentObject<PlayerModel> private var player
|
@EnvironmentObject<PlayerModel> private var player
|
||||||
@EnvironmentObject<PlaylistsModel> private var model
|
@EnvironmentObject<PlaylistsModel> private var model
|
||||||
|
|
||||||
@ -66,13 +65,13 @@ struct PlaylistVideosView: View {
|
|||||||
FavoriteButton(item: FavoriteItem(section: .channelPlaylist(playlist.id, playlist.title)))
|
FavoriteButton(item: FavoriteItem(section: .channelPlaylist(playlist.id, playlist.title)))
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
player.play(videos, inNavigationView: inNavigationView)
|
player.play(videos)
|
||||||
} label: {
|
} label: {
|
||||||
Label("Play All", systemImage: "play")
|
Label("Play All", systemImage: "play")
|
||||||
}
|
}
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
player.play(videos, shuffling: true, inNavigationView: inNavigationView)
|
player.play(videos, shuffling: true)
|
||||||
} label: {
|
} label: {
|
||||||
Label("Shuffle", systemImage: "shuffle")
|
Label("Shuffle", systemImage: "shuffle")
|
||||||
}
|
}
|
||||||
|
@ -5,9 +5,6 @@ import SwiftUI
|
|||||||
struct VideoContextMenuView: View {
|
struct VideoContextMenuView: View {
|
||||||
let video: Video
|
let video: Video
|
||||||
|
|
||||||
@Binding var playerNavigationLinkActive: Bool
|
|
||||||
|
|
||||||
@Environment(\.inNavigationView) private var inNavigationView
|
|
||||||
@Environment(\.inChannelView) private var inChannelView
|
@Environment(\.inChannelView) private var inChannelView
|
||||||
@Environment(\.inChannelPlaylistView) private var inChannelPlaylistView
|
@Environment(\.inChannelPlaylistView) private var inChannelPlaylistView
|
||||||
@Environment(\.navigationStyle) private var navigationStyle
|
@Environment(\.navigationStyle) private var navigationStyle
|
||||||
@ -26,9 +23,8 @@ struct VideoContextMenuView: View {
|
|||||||
|
|
||||||
private var viewContext: NSManagedObjectContext = PersistenceController.shared.container.viewContext
|
private var viewContext: NSManagedObjectContext = PersistenceController.shared.container.viewContext
|
||||||
|
|
||||||
init(video: Video, playerNavigationLinkActive: Binding<Bool>) {
|
init(video: Video) {
|
||||||
self.video = video
|
self.video = video
|
||||||
_playerNavigationLinkActive = playerNavigationLinkActive
|
|
||||||
_watchRequest = video.watchFetchRequest
|
_watchRequest = video.watchFetchRequest
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -111,7 +107,7 @@ struct VideoContextMenuView: View {
|
|||||||
|
|
||||||
private var continueButton: some View {
|
private var continueButton: some View {
|
||||||
Button {
|
Button {
|
||||||
player.play(video, at: .secondsInDefaultTimescale(watch!.stoppedAt), inNavigationView: inNavigationView)
|
player.play(video, at: .secondsInDefaultTimescale(watch!.stoppedAt))
|
||||||
} label: {
|
} label: {
|
||||||
Label("Continue from \(watch!.stoppedAt.formattedAsPlaybackTime() ?? "where I left off")", systemImage: "playpause")
|
Label("Continue from \(watch!.stoppedAt.formattedAsPlaybackTime() ?? "where I left off")", systemImage: "playpause")
|
||||||
}
|
}
|
||||||
@ -131,7 +127,7 @@ struct VideoContextMenuView: View {
|
|||||||
|
|
||||||
private var playNowButton: some View {
|
private var playNowButton: some View {
|
||||||
Button {
|
Button {
|
||||||
player.play(video, inNavigationView: inNavigationView)
|
player.play(video)
|
||||||
} label: {
|
} label: {
|
||||||
Label("Play Now", systemImage: "play")
|
Label("Play Now", systemImage: "play")
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user