diff --git a/Model/Player/Backends/MPVBackend.swift b/Model/Player/Backends/MPVBackend.swift index 5837b4e2..2cc01fe6 100644 --- a/Model/Player/Backends/MPVBackend.swift +++ b/Model/Player/Backends/MPVBackend.swift @@ -276,6 +276,13 @@ final class MPVBackend: PlayerBackend { startClientUpdates() onFileLoaded = nil + case MPV_EVENT_PLAYBACK_RESTART: + isLoadingVideo = false + + onFileLoaded?() + startClientUpdates() + onFileLoaded = nil + case MPV_EVENT_UNPAUSE: isLoadingVideo = false diff --git a/Shared/Player/Controls/PlayerControls.swift b/Shared/Player/Controls/PlayerControls.swift index 9d4da795..50da7624 100644 --- a/Shared/Player/Controls/PlayerControls.swift +++ b/Shared/Player/Controls/PlayerControls.swift @@ -10,6 +10,14 @@ struct PlayerControls: View { #if os(iOS) @Environment(\.verticalSizeClass) private var verticalSizeClass + #elseif os(tvOS) + enum Field: Hashable { + case play + case backward + case forward + } + + @FocusState private var focusedField: Field? #endif init(player: PlayerModel) { @@ -57,14 +65,27 @@ struct PlayerControls: View { } .opacity(model.presentingControls ? 1 : 0) } - .background(controlsBackground) - .environment(\.colorScheme, .dark) + #if os(tvOS) + .onChange(of: model.presentingControls) { _ in + if model.presentingControls { + focusedField = .play + } + } + .onChange(of: focusedField) { _ in + model.resetTimer() + } + #else + .background(controlsBackground) + #endif + .environment(\.colorScheme, .dark) } - var controlsBackground: some View { - PlayerGestures() - .background(Color.black.opacity(model.presentingControls ? 0.5 : 0)) - } + #if !os(tvOS) + var controlsBackground: some View { + PlayerGestures() + .background(Color.black.opacity(model.presentingControls ? 0.5 : 0)) + } + #endif var timeline: some View { TimelineView(duration: durationBinding, current: currentTimeBinding, cornerRadius: 0) @@ -93,11 +114,16 @@ struct PlayerControls: View { Spacer() - ToggleBackendButton() - Text("•") - StreamControl() - #if os(macOS) - .frame(maxWidth: 160) + #if !os(tvOS) + ToggleBackendButton() + Text("•") + + StreamControl() + #if os(macOS) + .frame(maxWidth: 160) + #endif + #else + Text(player.stream?.description ?? "") #endif } .foregroundColor(.primary) @@ -111,7 +137,9 @@ struct PlayerControls: View { } label: { Image(systemName: "chevron.down.circle.fill") } + #if !os(tvOS) .keyboardShortcut(.cancelAction) + #endif } private var playbackStatus: String { @@ -146,7 +174,9 @@ struct PlayerControls: View { var buttonsBar: some View { HStack { - fullscreenButton + #if !os(tvOS) + fullscreenButton + #endif Spacer() // button("Music Mode", systemImage: "music.note") } @@ -159,15 +189,26 @@ struct PlayerControls: View { ) { model.toggleFullscreen(fullScreenLayout) } + #if !os(tvOS) .keyboardShortcut(fullScreenLayout ? .cancelAction : .defaultAction) + #endif } var mediumButtonsBar: some View { HStack { - button("Seek Backward", systemImage: "gobackward.10", size: 50, cornerRadius: 10) { - player.backend.seek(relative: .secondsInDefaultTimescale(-10)) - } - .keyboardShortcut("k") + #if !os(tvOS) + button("Seek Backward", systemImage: "gobackward.10", size: 50, cornerRadius: 10) { + player.backend.seek(relative: .secondsInDefaultTimescale(-10)) + } + + #if os(tvOS) + .focused($focusedField, equals: .backward) + #else + .keyboardShortcut("k") + .keyboardShortcut(.leftArrow) + #endif + + #endif Spacer() @@ -179,15 +220,27 @@ struct PlayerControls: View { ) { player.backend.togglePlay() } + #if os(tvOS) + .focused($focusedField, equals: .play) + #else .keyboardShortcut("p") + .keyboardShortcut(.space) + #endif .disabled(model.isLoadingVideo) Spacer() - button("Seek Forward", systemImage: "goforward.10", size: 50, cornerRadius: 10) { - player.backend.seek(relative: .secondsInDefaultTimescale(10)) - } - .keyboardShortcut("l") + #if !os(tvOS) + button("Seek Forward", systemImage: "goforward.10", size: 50, cornerRadius: 10) { + player.backend.seek(relative: .secondsInDefaultTimescale(10)) + } + #if os(tvOS) + .focused($focusedField, equals: .forward) + #else + .keyboardShortcut("l") + .keyboardShortcut(.rightArrow) + #endif + #endif } .font(.system(size: 30)) .padding(.horizontal, 4) @@ -234,7 +287,7 @@ struct PlayerControls: View { } var fullScreenLayout: Bool { - #if !os(macOS) + #if os(iOS) model.playingFullscreen || verticalSizeClass == .compact #else model.playingFullscreen diff --git a/Shared/Player/TimelineView.swift b/Shared/Player/TimelineView.swift index 762bc902..0e2b816a 100644 --- a/Shared/Player/TimelineView.swift +++ b/Shared/Player/TimelineView.swift @@ -47,6 +47,7 @@ struct TimelineView: View { .frame(maxHeight: height * 2) + #if !os(tvOS) .gesture( DragGesture(minimumDistance: 0) .onChanged { value in @@ -79,6 +80,7 @@ struct TimelineView: View { controls.resetTimer() } ) + #endif ZStack { RoundedRectangle(cornerRadius: cornerRadius) @@ -100,11 +102,13 @@ struct TimelineView: View { self.size = size } }) + #if !os(tvOS) .gesture(DragGesture(minimumDistance: 0).onEnded { value in let target = (value.location.x / size.width) * units current = target player.backend.seek(to: target) }) + #endif } var projectedValue: Double { diff --git a/Shared/Player/VideoDetails.swift b/Shared/Player/VideoDetails.swift index 2655d65e..bacf42e7 100644 --- a/Shared/Player/VideoDetails.swift +++ b/Shared/Player/VideoDetails.swift @@ -89,7 +89,7 @@ struct VideoDetails: View { if fullScreen { fullScreen = false } else { - self.presentationMode.wrappedValue.dismiss() + self.player.hide() } } } diff --git a/Shared/Player/VideoPlayerView.swift b/Shared/Player/VideoPlayerView.swift index c331cdf1..5eec75ee 100644 --- a/Shared/Player/VideoPlayerView.swift +++ b/Shared/Player/VideoPlayerView.swift @@ -93,8 +93,26 @@ struct VideoPlayerView: View { Group { Group { #if os(tvOS) - player.playerView + playerView .ignoresSafeArea(.all, edges: .all) + .onMoveCommand { direction in + if direction == .left { + playerControls.resetTimer() + player.backend.seek(relative: .secondsInDefaultTimescale(-10)) + } + if direction == .right { + playerControls.resetTimer() + player.backend.seek(relative: .secondsInDefaultTimescale(10)) + } + if direction == .up { + playerControls.show() + playerControls.resetTimer() + } + if direction == .down { + playerControls.show() + playerControls.resetTimer() + } + } #else GeometryReader { geometry in VStack(spacing: 0) { @@ -103,30 +121,8 @@ struct VideoPlayerView: View { } else if player.playingInPictureInPicture { pictureInPicturePlaceholder(geometry: geometry) } else { - ZStack(alignment: .top) { - switch player.activeBackend { - case .mpv: - player.mpvPlayerView - .overlay(GeometryReader { proxy in - Color.clear - .onAppear { - player.playerSize = proxy.size - // TODO: move to backend method - player.mpvBackend.client?.setSize(proxy.size.width, proxy.size.height) - } - .onChange(of: proxy.size) { _ in - player.playerSize = proxy.size - player.mpvBackend.client?.setSize(proxy.size.width, proxy.size.height) - } - }) - case .appleAVPlayer: - player.avPlayerView - } - - PlayerGestures() - - PlayerControls(player: player) - } + playerView + #if !os(tvOS) .modifier( VideoPlayerSizeModifier( geometry: geometry, @@ -134,6 +130,7 @@ struct VideoPlayerView: View { fullScreen: playerControls.playingFullscreen ) ) + #endif } } .frame(maxWidth: fullScreenLayout ? .infinity : nil, maxHeight: fullScreenLayout ? .infinity : nil) @@ -165,24 +162,26 @@ struct VideoPlayerView: View { .background(Color.black) - if !playerControls.playingFullscreen { - Group { - #if os(iOS) - if verticalSizeClass == .regular { - VideoDetails(sidebarQueue: sidebarQueueBinding, fullScreen: $fullScreenDetails) - } + #if !os(tvOS) + if !playerControls.playingFullscreen { + Group { + #if os(iOS) + if verticalSizeClass == .regular { + VideoDetails(sidebarQueue: sidebarQueueBinding, fullScreen: $fullScreenDetails) + } - #else - VideoDetails(sidebarQueue: sidebarQueueBinding, fullScreen: $fullScreenDetails) - #endif + #else + VideoDetails(sidebarQueue: sidebarQueueBinding, fullScreen: $fullScreenDetails) + #endif + } + .background(colorScheme == .dark ? Color.black : Color.white) + .modifier(VideoDetailsPaddingModifier( + geometry: geometry, + aspectRatio: player.avPlayerBackend.controller?.aspectRatio, + fullScreen: fullScreenDetails + )) } - .background(colorScheme == .dark ? Color.black : Color.white) - .modifier(VideoDetailsPaddingModifier( - geometry: geometry, - aspectRatio: player.avPlayerBackend.controller?.aspectRatio, - fullScreen: fullScreenDetails - )) - } + #endif } #endif } @@ -205,14 +204,40 @@ struct VideoPlayerView: View { } } .ignoresSafeArea(.all, edges: fullScreenLayout ? .vertical : Edge.Set()) - #if !os(macOS) + #if os(iOS) .statusBar(hidden: playerControls.playingFullscreen) .navigationBarHidden(true) #endif } + var playerView: some View { + ZStack(alignment: .top) { + switch player.activeBackend { + case .mpv: + player.mpvPlayerView + .overlay(GeometryReader { proxy in + Color.clear + .onAppear { + player.playerSize = proxy.size + } + .onChange(of: proxy.size) { _ in + player.playerSize = proxy.size + } + }) + case .appleAVPlayer: + player.avPlayerView + } + + #if !os(tvOS) + PlayerGestures() + #endif + + PlayerControls(player: player) + } + } + var fullScreenLayout: Bool { - #if !os(macOS) + #if os(iOS) playerControls.playingFullscreen || verticalSizeClass == .compact #else playerControls.playingFullscreen diff --git a/Yattee.xcodeproj/project.pbxproj b/Yattee.xcodeproj/project.pbxproj index 9e18900a..5e8c489f 100644 --- a/Yattee.xcodeproj/project.pbxproj +++ b/Yattee.xcodeproj/project.pbxproj @@ -212,7 +212,6 @@ 373CFAEF2697A78B003CB2C6 /* AddToPlaylistView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 373CFAEE2697A78B003CB2C6 /* AddToPlaylistView.swift */; }; 373CFAF02697A78B003CB2C6 /* AddToPlaylistView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 373CFAEE2697A78B003CB2C6 /* AddToPlaylistView.swift */; }; 373CFAF12697A78B003CB2C6 /* AddToPlaylistView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 373CFAEE2697A78B003CB2C6 /* AddToPlaylistView.swift */; }; - 3740457227E91A4C00DC8A64 /* StreamControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3795593527B08538007FF8F4 /* StreamControl.swift */; }; 374108D1272B11B2006C5CC8 /* PictureInPictureDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 374108D0272B11B2006C5CC8 /* PictureInPictureDelegate.swift */; }; 3741A32C27E7EFFD00D266D1 /* PlayerControls.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37030FFE27B04DCC00ECDDAA /* PlayerControls.swift */; }; 3743B86927216D3600261544 /* ChannelCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3743B86727216D3600261544 /* ChannelCell.swift */; }; @@ -2942,7 +2941,6 @@ 3743CA50270EFE3400E4D32B /* PlayerQueueRow.swift in Sources */, 376BE50827347B57009AD608 /* SettingsHeader.swift in Sources */, 37A9966026D6F9B9006E3224 /* FavoritesView.swift in Sources */, - 3740457227E91A4C00DC8A64 /* StreamControl.swift in Sources */, 37001565271B1F250049C794 /* AccountsModel.swift in Sources */, 3751B4B427836902000B7DF4 /* SearchPage.swift in Sources */, 374C0541272472C0009BDDBE /* PlayerSponsorBlock.swift in Sources */,