mirror of
				https://github.com/yattee/yattee.git
				synced 2025-10-31 12:41:57 +00:00 
			
		
		
		
	Minor tvOS controls and remote improvements
This commit is contained in:
		| @@ -1,3 +1,4 @@ | ||||
| import Combine | ||||
| import CoreMedia | ||||
| import Foundation | ||||
| import SwiftUI | ||||
| @@ -9,6 +10,10 @@ final class PlayerControlsModel: ObservableObject { | ||||
|     @Published var presentingControlsOverlay = false { didSet { handleOverlayPresentationChange() } } | ||||
|     @Published var timer: Timer? | ||||
|  | ||||
|     #if os(tvOS) | ||||
|         var reporter = PassthroughSubject<String, Never>() | ||||
|     #endif | ||||
|  | ||||
|     var player: PlayerModel! | ||||
|  | ||||
|     init( | ||||
|   | ||||
| @@ -372,7 +372,14 @@ final class PlayerModel: ObservableObject { | ||||
|         #endif | ||||
|  | ||||
|         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() | ||||
|   | ||||
| @@ -70,7 +70,7 @@ struct PlayerControls: View { | ||||
|                                         .zIndex(1) | ||||
|                                 } | ||||
|                                 #if os(tvOS) | ||||
|                                 .offset(y: -100) | ||||
|                                 .offset(y: -50) | ||||
|                                 #endif | ||||
|                                 .frame(maxWidth: 500) | ||||
|                                 .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 { | ||||
|             ZStack(alignment: .bottomLeading) { | ||||
|                 #if os(tvOS) | ||||
|                     ZStack { | ||||
|                         playerView | ||||
|  | ||||
|                         tvControls | ||||
|                     } | ||||
|                     .ignoresSafeArea(.all, edges: .all) | ||||
|                     .onMoveCommand { direction in | ||||
|                         if direction == .up || direction == .down { | ||||
| @@ -147,9 +151,7 @@ struct VideoPlayerView: View { | ||||
|  | ||||
|                         playerControls.resetTimer() | ||||
|  | ||||
|                             guard !playerControls.presentingControls else { | ||||
|                                 return | ||||
|                             } | ||||
|                         guard !playerControls.presentingControls else { return } | ||||
|  | ||||
|                         if direction == .left { | ||||
|                             player.backend.seek(relative: .secondsInDefaultTimescale(-10)) | ||||
| @@ -161,6 +163,14 @@ struct VideoPlayerView: View { | ||||
|                     .onPlayPauseCommand { | ||||
|                         player.togglePlay() | ||||
|                     } | ||||
|  | ||||
|                     .onExitCommand { | ||||
|                         if playerControls.presentingControls { | ||||
|                             playerControls.hide() | ||||
|                         } else { | ||||
|                             player.hide() | ||||
|                         } | ||||
|                     } | ||||
|                 #else | ||||
|                     GeometryReader { geometry in | ||||
|                         VStack(spacing: 0) { | ||||
| @@ -308,9 +318,8 @@ struct VideoPlayerView: View { | ||||
|  | ||||
|             #if !os(tvOS) | ||||
|                 PlayerGestures() | ||||
|             #endif | ||||
|  | ||||
|                 PlayerControls(player: player, thumbnails: thumbnails) | ||||
|             #endif | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -502,6 +511,16 @@ struct VideoPlayerView: View { | ||||
|             } | ||||
|         } | ||||
|     #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 { | ||||
|   | ||||
| @@ -343,6 +343,7 @@ | ||||
| 		3761ABFF26F0F8DE00AA496F /* EnvironmentValues.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3761ABFC26F0F8DE00AA496F /* EnvironmentValues.swift */; }; | ||||
| 		3763495126DFF59D00B9A393 /* 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 */; }; | ||||
| 		376527BC285F60F700102284 /* 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>"; }; | ||||
| 		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>"; }; | ||||
| 		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>"; }; | ||||
| 		376578882685471400D4EA09 /* Playlist.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Playlist.swift; sourceTree = "<group>"; }; | ||||
| @@ -1363,9 +1365,10 @@ | ||||
| 			isa = PBXGroup; | ||||
| 			children = ( | ||||
| 				3756C2A428610F6D00E4B059 /* OSD */, | ||||
| 				37030FFE27B04DCC00ECDDAA /* PlayerControls.swift */, | ||||
| 				37A5DBC7285E371400CA4DD1 /* ControlBackgroundModifier.swift */, | ||||
| 				37F13B61285E43C000B137E4 /* ControlsOverlay.swift */, | ||||
| 				37030FFE27B04DCC00ECDDAA /* PlayerControls.swift */, | ||||
| 				37648B68286CF5F1003D330B /* TVControls.swift */, | ||||
| 			); | ||||
| 			path = Controls; | ||||
| 			sourceTree = "<group>"; | ||||
| @@ -3016,6 +3019,7 @@ | ||||
| 				37F49BA526CAA59B00304AC0 /* Playlist+Fixtures.swift in Sources */, | ||||
| 				376CD21826FBE18D001E1AC1 /* Instance+Fixtures.swift in Sources */, | ||||
| 				37CEE4BF2677B670005A1EFE /* SingleAssetStream.swift in Sources */, | ||||
| 				37648B69286CF5F1003D330B /* TVControls.swift in Sources */, | ||||
| 				374C053D2724614F009BDDBE /* PlayerTVMenu.swift in Sources */, | ||||
| 				37BE0BD426A1D47D0092E2DB /* AppleAVPlayerView.swift in Sources */, | ||||
| 				37977585268922F600DD52A8 /* InvidiousAPI.swift in Sources */, | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Arkadiusz Fal
					Arkadiusz Fal