Minor tvOS controls and remote improvements

This commit is contained in:
Arkadiusz Fal 2022-06-29 23:43:39 +02:00
parent 1645c81e00
commit 97e9889682
6 changed files with 137 additions and 25 deletions

View File

@ -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(

View File

@ -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()

View File

@ -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)

View 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)")
}
}
}

View File

@ -138,29 +138,39 @@ struct VideoPlayerView: View {
Group { Group {
ZStack(alignment: .bottomLeading) { ZStack(alignment: .bottomLeading) {
#if os(tvOS) #if os(tvOS)
playerView ZStack {
.ignoresSafeArea(.all, edges: .all) playerView
.onMoveCommand { direction in
if direction == .up || direction == .down {
playerControls.show()
}
playerControls.resetTimer() tvControls
}
guard !playerControls.presentingControls else { .ignoresSafeArea(.all, edges: .all)
return .onMoveCommand { direction in
} if direction == .up || direction == .down {
playerControls.show()
if direction == .left {
player.backend.seek(relative: .secondsInDefaultTimescale(-10))
}
if direction == .right {
player.backend.seek(relative: .secondsInDefaultTimescale(10))
}
} }
.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 #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()
PlayerControls(player: player, thumbnails: thumbnails)
#endif #endif
PlayerControls(player: player, thumbnails: thumbnails)
} }
} }
@ -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 {

View File

@ -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 */,