From 29e824a0357d0fae7420519a0967c1338a27d840 Mon Sep 17 00:00:00 2001 From: Ethan Pippin Date: Sat, 1 Jan 2022 21:17:22 -0700 Subject: [PATCH] add jump forward/backward indicators tvos --- .../VideoPlayer/PlayerOverlayDelegate.swift | 3 + .../VideoPlayer/VLCPlayerViewController.swift | 88 +++++++++++++++++++ .../tvOSOverlay/tvOSVLCOverlay.swift | 36 ++++++-- Shared/Extensions/ColorExtension.swift | 1 + 4 files changed, 119 insertions(+), 9 deletions(-) diff --git a/JellyfinPlayer tvOS/Views/VideoPlayer/PlayerOverlayDelegate.swift b/JellyfinPlayer tvOS/Views/VideoPlayer/PlayerOverlayDelegate.swift index ebd02beb..bd95a0ec 100644 --- a/JellyfinPlayer tvOS/Views/VideoPlayer/PlayerOverlayDelegate.swift +++ b/JellyfinPlayer tvOS/Views/VideoPlayer/PlayerOverlayDelegate.swift @@ -28,5 +28,8 @@ protocol PlayerOverlayDelegate { func didSelectAudioStream(index: Int) func didSelectSubtitleStream(index: Int) + func didSelectPreviousItem() + func didSelectNextItem() + func didFocusOnButton() } diff --git a/JellyfinPlayer tvOS/Views/VideoPlayer/VLCPlayerViewController.swift b/JellyfinPlayer tvOS/Views/VideoPlayer/VLCPlayerViewController.swift index d7437182..79a98a05 100644 --- a/JellyfinPlayer tvOS/Views/VideoPlayer/VLCPlayerViewController.swift +++ b/JellyfinPlayer tvOS/Views/VideoPlayer/VLCPlayerViewController.swift @@ -53,6 +53,8 @@ class VLCPlayerViewController: UIViewController { } private lazy var videoContentView = makeVideoContentView() + private lazy var jumpBackwardOverlayView = makeJumpBackwardOverlayView() + private lazy var jumpForwardOverlayView = makeJumpForwardOverlayView() private var currentOverlayHostingController: UIHostingController? private var currentOverlayContentHostingController: UIHostingController? @@ -73,6 +75,11 @@ class VLCPlayerViewController: UIViewController { private func setupSubviews() { view.addSubview(videoContentView) + view.addSubview(jumpForwardOverlayView) + view.addSubview(jumpBackwardOverlayView) + + jumpBackwardOverlayView.alpha = 0 + jumpForwardOverlayView.alpha = 0 } private func setupConstraints() { @@ -82,6 +89,14 @@ class VLCPlayerViewController: UIViewController { videoContentView.leftAnchor.constraint(equalTo: view.leftAnchor), videoContentView.rightAnchor.constraint(equalTo: view.rightAnchor) ]) + NSLayoutConstraint.activate([ + jumpBackwardOverlayView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 300), + jumpBackwardOverlayView.centerYAnchor.constraint(equalTo: view.centerYAnchor) + ]) + NSLayoutConstraint.activate([ + jumpForwardOverlayView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -300), + jumpForwardOverlayView.centerYAnchor.constraint(equalTo: view.centerYAnchor) + ]) } // MARK: viewWillDisappear @@ -119,12 +134,27 @@ class VLCPlayerViewController: UIViewController { setupLeftSwipedGestureRecognizer() setupPanGestureRecognizer() + let menuPressRecognizer = UITapGestureRecognizer() + menuPressRecognizer.addTarget(self, action: #selector(menuButtonAction)) + menuPressRecognizer.allowedPressTypes = [NSNumber(value: UIPress.PressType.menu.rawValue)] + view.addGestureRecognizer(menuPressRecognizer) + let defaultNotificationCenter = NotificationCenter.default defaultNotificationCenter.addObserver(self, selector: #selector(appWillTerminate), name: UIApplication.willTerminateNotification, object: nil) defaultNotificationCenter.addObserver(self, selector: #selector(appWillResignActive), name: UIApplication.willResignActiveNotification, object: nil) defaultNotificationCenter.addObserver(self, selector: #selector(appWillResignActive), name: UIApplication.didEnterBackgroundNotification, object: nil) } + @objc private func menuButtonAction() { + if displayingOverlay { + hideOverlay() + } else { + vlcMediaPlayer.pause() + + dismiss(animated: true, completion: nil) + } + } + @objc private func appWillTerminate() { viewModel.sendStopReport() } @@ -155,6 +185,24 @@ class VLCPlayerViewController: UIViewController { return view } + private func makeJumpBackwardOverlayView() -> UIImageView { + let symbolConfig = UIImage.SymbolConfiguration(pointSize: 56) + let forwardSymbolImage = UIImage(systemName: jumpBackwardLength.forwardImageLabel, withConfiguration: symbolConfig) + let imageView = UIImageView(image: forwardSymbolImage) + imageView.translatesAutoresizingMaskIntoConstraints = false + + return imageView + } + + private func makeJumpForwardOverlayView() -> UIImageView { + let symbolConfig = UIImage.SymbolConfiguration(pointSize: 56) + let forwardSymbolImage = UIImage(systemName: jumpForwardLength.forwardImageLabel, withConfiguration: symbolConfig) + let imageView = UIImageView(image: forwardSymbolImage) + imageView.translatesAutoresizingMaskIntoConstraints = false + + return imageView + } + // MARK: pressesBegan override func pressesBegan(_ presses: Set, with event: UIPressesEvent?) { guard let buttonPress = presses.first?.type else { return } @@ -442,6 +490,42 @@ extension VLCPlayerViewController { } } +// MARK: Show/Hide Jump +extension VLCPlayerViewController { + + private func flashJumpBackwardOverlay() { + jumpBackwardOverlayView.layer.removeAllAnimations() + + UIView.animate(withDuration: 0.1) { + self.jumpBackwardOverlayView.alpha = 1 + } completion: { _ in + self.hideJumpBackwardOverlay() + } + } + + private func hideJumpBackwardOverlay() { + UIView.animate(withDuration: 0.3) { + self.jumpBackwardOverlayView.alpha = 0 + } + } + + private func flashJumpFowardOverlay() { + jumpForwardOverlayView.layer.removeAllAnimations() + + UIView.animate(withDuration: 0.1) { + self.jumpForwardOverlayView.alpha = 1 + } completion: { _ in + self.hideJumpForwardOverlay() + } + } + + private func hideJumpForwardOverlay() { + UIView.animate(withDuration: 0.3) { + self.jumpForwardOverlayView.alpha = 0 + } + } +} + // MARK: OverlayTimer extension VLCPlayerViewController { @@ -579,6 +663,8 @@ extension VLCPlayerViewController: PlayerOverlayDelegate { } func didSelectBackward() { + flashJumpBackwardOverlay() + vlcMediaPlayer.jumpBackward(jumpBackwardLength.rawValue) restartOverlayDismissTimer() @@ -589,6 +675,8 @@ extension VLCPlayerViewController: PlayerOverlayDelegate { } func didSelectForward() { + flashJumpFowardOverlay() + vlcMediaPlayer.jumpForward(jumpForwardLength.rawValue) restartOverlayDismissTimer() diff --git a/JellyfinPlayer tvOS/Views/VideoPlayer/tvOSOverlay/tvOSVLCOverlay.swift b/JellyfinPlayer tvOS/Views/VideoPlayer/tvOSOverlay/tvOSVLCOverlay.swift index 30b01da9..b6de77f9 100644 --- a/JellyfinPlayer tvOS/Views/VideoPlayer/tvOSOverlay/tvOSVLCOverlay.swift +++ b/JellyfinPlayer tvOS/Views/VideoPlayer/tvOSOverlay/tvOSVLCOverlay.swift @@ -45,7 +45,7 @@ struct tvOSVLCOverlay: View { if let subtitle = viewModel.subtitle { Text(subtitle) .font(.subheadline) - .foregroundColor(.secondarySystemFill) + .foregroundColor(.lightGray) } Text(viewModel.title) @@ -55,16 +55,34 @@ struct tvOSVLCOverlay: View { Spacer() - if viewModel.subtitlesEnabled { - SFSymbolButton(systemName: "captions.bubble.fill") { - viewModel.playerOverlayDelegate?.didSelectCaptions() - } + if viewModel.showAdjacentItems { + SFSymbolButton(systemName: "chevron.left.circle", action: { + viewModel.playerOverlayDelegate?.didSelectPreviousItem() + }) .frame(maxWidth: 30, maxHeight: 30) - } else { - SFSymbolButton(systemName: "captions.bubble") { - viewModel.playerOverlayDelegate?.didSelectCaptions() - } + .disabled(viewModel.previousItemVideoPlayerViewModel == nil) + .foregroundColor(viewModel.nextItemVideoPlayerViewModel == nil ? .gray : .white) + + SFSymbolButton(systemName: "chevron.right.circle", action: { + viewModel.playerOverlayDelegate?.didSelectNextItem() + }) .frame(maxWidth: 30, maxHeight: 30) + .disabled(viewModel.nextItemVideoPlayerViewModel == nil) + .foregroundColor(viewModel.nextItemVideoPlayerViewModel == nil ? .gray : .white) + } + + if !viewModel.subtitleStreams.isEmpty { + if viewModel.subtitlesEnabled { + SFSymbolButton(systemName: "captions.bubble.fill") { + viewModel.playerOverlayDelegate?.didSelectCaptions() + } + .frame(maxWidth: 30, maxHeight: 30) + } else { + SFSymbolButton(systemName: "captions.bubble") { + viewModel.playerOverlayDelegate?.didSelectCaptions() + } + .frame(maxWidth: 30, maxHeight: 30) + } } SFSymbolButton(systemName: "ellipsis.circle") { diff --git a/Shared/Extensions/ColorExtension.swift b/Shared/Extensions/ColorExtension.swift index 304fa1f2..edc56d4c 100644 --- a/Shared/Extensions/ColorExtension.swift +++ b/Shared/Extensions/ColorExtension.swift @@ -17,6 +17,7 @@ extension Color { public static let systemFill = Color(UIColor.white) public static let secondarySystemFill = Color(UIColor.gray) public static let tertiarySystemFill = Color(UIColor.black) + public static let lightGray = Color(UIColor.lightGray) #else public static let systemFill = Color(UIColor.systemFill) public static let secondarySystemFill = Color(UIColor.secondarySystemBackground)