mirror of
				https://github.com/yattee/yattee.git
				synced 2025-11-03 22:22:02 +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,29 +138,39 @@ struct VideoPlayerView: View {
 | 
			
		||||
        Group {
 | 
			
		||||
            ZStack(alignment: .bottomLeading) {
 | 
			
		||||
                #if os(tvOS)
 | 
			
		||||
                    playerView
 | 
			
		||||
                        .ignoresSafeArea(.all, edges: .all)
 | 
			
		||||
                        .onMoveCommand { direction in
 | 
			
		||||
                            if direction == .up || direction == .down {
 | 
			
		||||
                                playerControls.show()
 | 
			
		||||
                            }
 | 
			
		||||
                    ZStack {
 | 
			
		||||
                        playerView
 | 
			
		||||
 | 
			
		||||
                            playerControls.resetTimer()
 | 
			
		||||
 | 
			
		||||
                            guard !playerControls.presentingControls else {
 | 
			
		||||
                                return
 | 
			
		||||
                            }
 | 
			
		||||
 | 
			
		||||
                            if direction == .left {
 | 
			
		||||
                                player.backend.seek(relative: .secondsInDefaultTimescale(-10))
 | 
			
		||||
                            }
 | 
			
		||||
                            if direction == .right {
 | 
			
		||||
                                player.backend.seek(relative: .secondsInDefaultTimescale(10))
 | 
			
		||||
                            }
 | 
			
		||||
                        tvControls
 | 
			
		||||
                    }
 | 
			
		||||
                    .ignoresSafeArea(.all, edges: .all)
 | 
			
		||||
                    .onMoveCommand { direction in
 | 
			
		||||
                        if direction == .up || direction == .down {
 | 
			
		||||
                            playerControls.show()
 | 
			
		||||
                        }
 | 
			
		||||
                        .onPlayPauseCommand {
 | 
			
		||||
                            player.togglePlay()
 | 
			
		||||
 | 
			
		||||
                        playerControls.resetTimer()
 | 
			
		||||
 | 
			
		||||
                        guard !playerControls.presentingControls else { return }
 | 
			
		||||
 | 
			
		||||
                        if direction == .left {
 | 
			
		||||
                            player.backend.seek(relative: .secondsInDefaultTimescale(-10))
 | 
			
		||||
                        }
 | 
			
		||||
                        if direction == .right {
 | 
			
		||||
                            player.backend.seek(relative: .secondsInDefaultTimescale(10))
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    .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()
 | 
			
		||||
                PlayerControls(player: player, thumbnails: thumbnails)
 | 
			
		||||
            #endif
 | 
			
		||||
 | 
			
		||||
            PlayerControls(player: player, thumbnails: thumbnails)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -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