mirror of
				https://github.com/yattee/yattee.git
				synced 2025-10-31 04:31:54 +00:00 
			
		
		
		
	Minor tvOS controls and remote improvements
This commit is contained in:
		| @@ -1,3 +1,4 @@ | |||||||
|  | import Combine | ||||||
| import CoreMedia | import CoreMedia | ||||||
| import Foundation | import Foundation | ||||||
| import SwiftUI | import SwiftUI | ||||||
| @@ -9,6 +10,10 @@ final class PlayerControlsModel: ObservableObject { | |||||||
|     @Published var presentingControlsOverlay = false { didSet { handleOverlayPresentationChange() } } |     @Published var presentingControlsOverlay = false { didSet { handleOverlayPresentationChange() } } | ||||||
|     @Published var timer: Timer? |     @Published var timer: Timer? | ||||||
|  |  | ||||||
|  |     #if os(tvOS) | ||||||
|  |         var reporter = PassthroughSubject<String, Never>() | ||||||
|  |     #endif | ||||||
|  |  | ||||||
|     var player: PlayerModel! |     var player: PlayerModel! | ||||||
|  |  | ||||||
|     init( |     init( | ||||||
|   | |||||||
| @@ -372,7 +372,14 @@ final class PlayerModel: ObservableObject { | |||||||
|         #endif |         #endif | ||||||
|  |  | ||||||
|         DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [weak self] in |         DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [weak self] in | ||||||
|             self?.backend.setNeedsDrawing(self?.presentingPlayer ?? false) |             guard let self = self else { return } | ||||||
|  |             self.backend.setNeedsDrawing(self.presentingPlayer) | ||||||
|  |  | ||||||
|  |             #if os(tvOS) | ||||||
|  |                 if self.presentingPlayer { | ||||||
|  |                     self.controls.show() | ||||||
|  |                 } | ||||||
|  |             #endif | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         controls.hide() |         controls.hide() | ||||||
|   | |||||||
| @@ -70,7 +70,7 @@ struct PlayerControls: View { | |||||||
|                                         .zIndex(1) |                                         .zIndex(1) | ||||||
|                                 } |                                 } | ||||||
|                                 #if os(tvOS) |                                 #if os(tvOS) | ||||||
|                                 .offset(y: -100) |                                 .offset(y: -50) | ||||||
|                                 #endif |                                 #endif | ||||||
|                                 .frame(maxWidth: 500) |                                 .frame(maxWidth: 500) | ||||||
|                                 .padding(.bottom, 2) |                                 .padding(.bottom, 2) | ||||||
|   | |||||||
							
								
								
									
										77
									
								
								Shared/Player/Controls/TVControls.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								Shared/Player/Controls/TVControls.swift
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,77 @@ | |||||||
|  | import Combine | ||||||
|  | import SwiftUI | ||||||
|  |  | ||||||
|  | struct TVControls: UIViewRepresentable { | ||||||
|  |     var model: PlayerControlsModel! | ||||||
|  |     var player: PlayerModel! | ||||||
|  |     var thumbnails: ThumbnailsModel! | ||||||
|  |  | ||||||
|  |     @State private var direction = "" | ||||||
|  |     @State private var controlsArea = UIView() | ||||||
|  |  | ||||||
|  |     func makeUIView(context: Context) -> UIView { | ||||||
|  |         let tapGesture = UITapGestureRecognizer(target: context.coordinator, action: #selector(Coordinator.handleTap(sender:))) | ||||||
|  |  | ||||||
|  |         let leftSwipe = UISwipeGestureRecognizer(target: context.coordinator, action: #selector(Coordinator.handleSwipe(sender:))) | ||||||
|  |         leftSwipe.direction = .left | ||||||
|  |  | ||||||
|  |         let rightSwipe = UISwipeGestureRecognizer(target: context.coordinator, action: #selector(Coordinator.handleSwipe(sender:))) | ||||||
|  |         rightSwipe.direction = .right | ||||||
|  |  | ||||||
|  |         let upSwipe = UISwipeGestureRecognizer(target: context.coordinator, action: #selector(Coordinator.handleSwipe(sender:))) | ||||||
|  |         upSwipe.direction = .up | ||||||
|  |  | ||||||
|  |         let downSwipe = UISwipeGestureRecognizer(target: context.coordinator, action: #selector(Coordinator.handleSwipe(sender:))) | ||||||
|  |         downSwipe.direction = .down | ||||||
|  |  | ||||||
|  |         controlsArea.addGestureRecognizer(tapGesture) | ||||||
|  |         controlsArea.addGestureRecognizer(leftSwipe) | ||||||
|  |         controlsArea.addGestureRecognizer(rightSwipe) | ||||||
|  |         controlsArea.addGestureRecognizer(upSwipe) | ||||||
|  |         controlsArea.addGestureRecognizer(downSwipe) | ||||||
|  |  | ||||||
|  |         let controls = UIHostingController(rootView: PlayerControls(player: player, thumbnails: thumbnails)) | ||||||
|  |         controls.view.frame = .init( | ||||||
|  |             origin: .zero, | ||||||
|  |             size: .init(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height) | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         controlsArea.addSubview(controls.view) | ||||||
|  |  | ||||||
|  |         return controlsArea | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     func updateUIView(_: UIView, context _: Context) {} | ||||||
|  |  | ||||||
|  |     func makeCoordinator() -> TVControls.Coordinator { | ||||||
|  |         Coordinator(controlsArea, model: model) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     final class Coordinator: NSObject { | ||||||
|  |         private let view: UIView | ||||||
|  |         private let model: PlayerControlsModel | ||||||
|  |  | ||||||
|  |         init(_ view: UIView, model: PlayerControlsModel) { | ||||||
|  |             self.view = view | ||||||
|  |             self.model = model | ||||||
|  |             super.init() | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         @available(*, unavailable) | ||||||
|  |         required init?(coder _: NSCoder) { | ||||||
|  |             fatalError("init(coder:) has not been implemented") | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         @objc func handleTap(sender: UITapGestureRecognizer) { | ||||||
|  |             let location = sender.location(in: view) | ||||||
|  |             model.reporter.send("tap \(location)") | ||||||
|  |             print("tap \(location)") | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         @objc func handleSwipe(sender: UISwipeGestureRecognizer) { | ||||||
|  |             let location = sender.location(in: view) | ||||||
|  |             model.reporter.send("swipe \(location)") | ||||||
|  |             print("swipe \(location)") | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -138,7 +138,11 @@ struct VideoPlayerView: View { | |||||||
|         Group { |         Group { | ||||||
|             ZStack(alignment: .bottomLeading) { |             ZStack(alignment: .bottomLeading) { | ||||||
|                 #if os(tvOS) |                 #if os(tvOS) | ||||||
|  |                     ZStack { | ||||||
|                         playerView |                         playerView | ||||||
|  |  | ||||||
|  |                         tvControls | ||||||
|  |                     } | ||||||
|                     .ignoresSafeArea(.all, edges: .all) |                     .ignoresSafeArea(.all, edges: .all) | ||||||
|                     .onMoveCommand { direction in |                     .onMoveCommand { direction in | ||||||
|                         if direction == .up || direction == .down { |                         if direction == .up || direction == .down { | ||||||
| @@ -147,9 +151,7 @@ struct VideoPlayerView: View { | |||||||
|  |  | ||||||
|                         playerControls.resetTimer() |                         playerControls.resetTimer() | ||||||
|  |  | ||||||
|                             guard !playerControls.presentingControls else { |                         guard !playerControls.presentingControls else { return } | ||||||
|                                 return |  | ||||||
|                             } |  | ||||||
|  |  | ||||||
|                         if direction == .left { |                         if direction == .left { | ||||||
|                             player.backend.seek(relative: .secondsInDefaultTimescale(-10)) |                             player.backend.seek(relative: .secondsInDefaultTimescale(-10)) | ||||||
| @@ -161,6 +163,14 @@ struct VideoPlayerView: View { | |||||||
|                     .onPlayPauseCommand { |                     .onPlayPauseCommand { | ||||||
|                         player.togglePlay() |                         player.togglePlay() | ||||||
|                     } |                     } | ||||||
|  |  | ||||||
|  |                     .onExitCommand { | ||||||
|  |                         if playerControls.presentingControls { | ||||||
|  |                             playerControls.hide() | ||||||
|  |                         } else { | ||||||
|  |                             player.hide() | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|                 #else |                 #else | ||||||
|                     GeometryReader { geometry in |                     GeometryReader { geometry in | ||||||
|                         VStack(spacing: 0) { |                         VStack(spacing: 0) { | ||||||
| @@ -308,9 +318,8 @@ struct VideoPlayerView: View { | |||||||
|  |  | ||||||
|             #if !os(tvOS) |             #if !os(tvOS) | ||||||
|                 PlayerGestures() |                 PlayerGestures() | ||||||
|             #endif |  | ||||||
|  |  | ||||||
|                 PlayerControls(player: player, thumbnails: thumbnails) |                 PlayerControls(player: player, thumbnails: thumbnails) | ||||||
|  |             #endif | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -502,6 +511,16 @@ struct VideoPlayerView: View { | |||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     #endif |     #endif | ||||||
|  |  | ||||||
|  |     #if os(tvOS) | ||||||
|  |         var tvControls: some View { | ||||||
|  |             TVControls(model: playerControls, player: player, thumbnails: thumbnails) | ||||||
|  |                 .onReceive(playerControls.reporter) { _ in | ||||||
|  |                     playerControls.show() | ||||||
|  |                     playerControls.resetTimer() | ||||||
|  |                 } | ||||||
|  |         } | ||||||
|  |     #endif | ||||||
| } | } | ||||||
|  |  | ||||||
| struct VideoPlayerView_Previews: PreviewProvider { | struct VideoPlayerView_Previews: PreviewProvider { | ||||||
|   | |||||||
| @@ -343,6 +343,7 @@ | |||||||
| 		3761ABFF26F0F8DE00AA496F /* EnvironmentValues.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3761ABFC26F0F8DE00AA496F /* EnvironmentValues.swift */; }; | 		3761ABFF26F0F8DE00AA496F /* EnvironmentValues.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3761ABFC26F0F8DE00AA496F /* EnvironmentValues.swift */; }; | ||||||
| 		3763495126DFF59D00B9A393 /* AppSidebarRecents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3763495026DFF59D00B9A393 /* AppSidebarRecents.swift */; }; | 		3763495126DFF59D00B9A393 /* AppSidebarRecents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3763495026DFF59D00B9A393 /* AppSidebarRecents.swift */; }; | ||||||
| 		3763495226DFF59D00B9A393 /* AppSidebarRecents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3763495026DFF59D00B9A393 /* AppSidebarRecents.swift */; }; | 		3763495226DFF59D00B9A393 /* AppSidebarRecents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3763495026DFF59D00B9A393 /* AppSidebarRecents.swift */; }; | ||||||
|  | 		37648B69286CF5F1003D330B /* TVControls.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37648B68286CF5F1003D330B /* TVControls.swift */; }; | ||||||
| 		376527BB285F60F700102284 /* PlayerTimeModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 376527BA285F60F700102284 /* PlayerTimeModel.swift */; }; | 		376527BB285F60F700102284 /* PlayerTimeModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 376527BA285F60F700102284 /* PlayerTimeModel.swift */; }; | ||||||
| 		376527BC285F60F700102284 /* PlayerTimeModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 376527BA285F60F700102284 /* PlayerTimeModel.swift */; }; | 		376527BC285F60F700102284 /* PlayerTimeModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 376527BA285F60F700102284 /* PlayerTimeModel.swift */; }; | ||||||
| 		376527BD285F60F700102284 /* PlayerTimeModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 376527BA285F60F700102284 /* PlayerTimeModel.swift */; }; | 		376527BD285F60F700102284 /* PlayerTimeModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 376527BA285F60F700102284 /* PlayerTimeModel.swift */; }; | ||||||
| @@ -969,6 +970,7 @@ | |||||||
| 		375E45F727B1AC4700BA7902 /* PlayerControlsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerControlsModel.swift; sourceTree = "<group>"; }; | 		375E45F727B1AC4700BA7902 /* PlayerControlsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerControlsModel.swift; sourceTree = "<group>"; }; | ||||||
| 		3761ABFC26F0F8DE00AA496F /* EnvironmentValues.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EnvironmentValues.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>"; }; | 		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>"; }; | ||||||
| 		376527BA285F60F700102284 /* PlayerTimeModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerTimeModel.swift; sourceTree = "<group>"; }; | 		376527BA285F60F700102284 /* PlayerTimeModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerTimeModel.swift; sourceTree = "<group>"; }; | ||||||
| 		376578842685429C00D4EA09 /* CaseIterable+Next.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CaseIterable+Next.swift"; sourceTree = "<group>"; }; | 		376578842685429C00D4EA09 /* CaseIterable+Next.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CaseIterable+Next.swift"; sourceTree = "<group>"; }; | ||||||
| 		376578882685471400D4EA09 /* Playlist.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Playlist.swift; sourceTree = "<group>"; }; | 		376578882685471400D4EA09 /* Playlist.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Playlist.swift; sourceTree = "<group>"; }; | ||||||
| @@ -1363,9 +1365,10 @@ | |||||||
| 			isa = PBXGroup; | 			isa = PBXGroup; | ||||||
| 			children = ( | 			children = ( | ||||||
| 				3756C2A428610F6D00E4B059 /* OSD */, | 				3756C2A428610F6D00E4B059 /* OSD */, | ||||||
| 				37030FFE27B04DCC00ECDDAA /* PlayerControls.swift */, |  | ||||||
| 				37A5DBC7285E371400CA4DD1 /* ControlBackgroundModifier.swift */, | 				37A5DBC7285E371400CA4DD1 /* ControlBackgroundModifier.swift */, | ||||||
| 				37F13B61285E43C000B137E4 /* ControlsOverlay.swift */, | 				37F13B61285E43C000B137E4 /* ControlsOverlay.swift */, | ||||||
|  | 				37030FFE27B04DCC00ECDDAA /* PlayerControls.swift */, | ||||||
|  | 				37648B68286CF5F1003D330B /* TVControls.swift */, | ||||||
| 			); | 			); | ||||||
| 			path = Controls; | 			path = Controls; | ||||||
| 			sourceTree = "<group>"; | 			sourceTree = "<group>"; | ||||||
| @@ -3016,6 +3019,7 @@ | |||||||
| 				37F49BA526CAA59B00304AC0 /* Playlist+Fixtures.swift in Sources */, | 				37F49BA526CAA59B00304AC0 /* Playlist+Fixtures.swift in Sources */, | ||||||
| 				376CD21826FBE18D001E1AC1 /* Instance+Fixtures.swift in Sources */, | 				376CD21826FBE18D001E1AC1 /* Instance+Fixtures.swift in Sources */, | ||||||
| 				37CEE4BF2677B670005A1EFE /* SingleAssetStream.swift in Sources */, | 				37CEE4BF2677B670005A1EFE /* SingleAssetStream.swift in Sources */, | ||||||
|  | 				37648B69286CF5F1003D330B /* TVControls.swift in Sources */, | ||||||
| 				374C053D2724614F009BDDBE /* PlayerTVMenu.swift in Sources */, | 				374C053D2724614F009BDDBE /* PlayerTVMenu.swift in Sources */, | ||||||
| 				37BE0BD426A1D47D0092E2DB /* AppleAVPlayerView.swift in Sources */, | 				37BE0BD426A1D47D0092E2DB /* AppleAVPlayerView.swift in Sources */, | ||||||
| 				37977585268922F600DD52A8 /* InvidiousAPI.swift in Sources */, | 				37977585268922F600DD52A8 /* InvidiousAPI.swift in Sources */, | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Arkadiusz Fal
					Arkadiusz Fal