diff --git a/JellyfinPlayer tvOS/Views/VideoPlayer/PlayerOverlayDelegate.swift b/JellyfinPlayer tvOS/Views/VideoPlayer/PlayerOverlayDelegate.swift index 0a0f8cae..f80db501 100644 --- a/JellyfinPlayer tvOS/Views/VideoPlayer/PlayerOverlayDelegate.swift +++ b/JellyfinPlayer tvOS/Views/VideoPlayer/PlayerOverlayDelegate.swift @@ -12,9 +12,6 @@ import Foundation protocol PlayerOverlayDelegate { func didSelectClose() - func didSelectGoogleCast() - func didSelectAirplay() - func didSelectSubtitles() func didSelectMenu() func didDeselectMenu() @@ -30,8 +27,6 @@ protocol PlayerOverlayDelegate { func didSelectAudioStream(index: Int) func didSelectSubtitleStream(index: Int) - func didSelectPreviousItem() - func didSelectNextItem() - - func didFocusOnButton() + func didSelectPlayPreviousItem() + func didSelectPlayNextItem() } diff --git a/JellyfinPlayer tvOS/Views/VideoPlayer/VLCPlayerViewController.swift b/JellyfinPlayer tvOS/Views/VideoPlayer/VLCPlayerViewController.swift index fc46bdd0..85d6ffa7 100644 --- a/JellyfinPlayer tvOS/Views/VideoPlayer/VLCPlayerViewController.swift +++ b/JellyfinPlayer tvOS/Views/VideoPlayer/VLCPlayerViewController.swift @@ -27,7 +27,7 @@ class VLCPlayerViewController: UIViewController { private var vlcMediaPlayer = VLCMediaPlayer() private var lastPlayerTicks: Int64 = 0 private var lastProgressReportTicks: Int64 = 0 - private var viewModelReactCancellables = Set() + private var viewModelListeners = Set() private var overlayDismissTimer: Timer? private var currentPlayerTicks: Int64 { @@ -42,14 +42,6 @@ class VLCPlayerViewController: UIViewController { return currentOverlayContentHostingController?.view.alpha ?? 0 > 0 } - private var jumpForwardLength: VideoPlayerJumpLength { - return Defaults[.videoPlayerJumpForward] - } - - private var jumpBackwardLength: VideoPlayerJumpLength { - return Defaults[.videoPlayerJumpBackward] - } - private lazy var videoContentView = makeVideoContentView() private lazy var jumpBackwardOverlayView = makeJumpBackwardOverlayView() private lazy var jumpForwardOverlayView = makeJumpForwardOverlayView() @@ -170,7 +162,7 @@ class VLCPlayerViewController: UIViewController { private func makeJumpBackwardOverlayView() -> UIImageView { let symbolConfig = UIImage.SymbolConfiguration(pointSize: 72) - let forwardSymbolImage = UIImage(systemName: jumpBackwardLength.backwardImageLabel, withConfiguration: symbolConfig) + let forwardSymbolImage = UIImage(systemName: viewModel.jumpBackwardLength.backwardImageLabel, withConfiguration: symbolConfig) let imageView = UIImageView(image: forwardSymbolImage) imageView.translatesAutoresizingMaskIntoConstraints = false @@ -179,7 +171,7 @@ class VLCPlayerViewController: UIViewController { private func makeJumpForwardOverlayView() -> UIImageView { let symbolConfig = UIImage.SymbolConfiguration(pointSize: 72) - let forwardSymbolImage = UIImage(systemName: jumpForwardLength.forwardImageLabel, withConfiguration: symbolConfig) + let forwardSymbolImage = UIImage(systemName: viewModel.jumpForwardLength.forwardImageLabel, withConfiguration: symbolConfig) let imageView = UIImageView(image: forwardSymbolImage) imageView.translatesAutoresizingMaskIntoConstraints = false @@ -262,7 +254,6 @@ class VLCPlayerViewController: UIViewController { currentOverlayHostingController.view.removeFromSuperview() currentOverlayHostingController.removeFromParent() -// self.currentOverlayHostingController = nil } } @@ -346,7 +337,7 @@ extension VLCPlayerViewController { // Stop current media if there is one if vlcMediaPlayer.media != nil { - viewModelReactCancellables.forEach({ $0.cancel() }) + viewModelListeners.forEach({ $0.cancel() }) vlcMediaPlayer.stop() viewModel.sendStopReport() @@ -368,7 +359,7 @@ extension VLCPlayerViewController { newViewModel.getAdjacentEpisodes() newViewModel.playerOverlayDelegate = self - let startPercentage = viewModel.item.userData?.playedPercentage ?? 0 + let startPercentage = newViewModel.item.userData?.playedPercentage ?? 0 if startPercentage > 0 { newViewModel.sliderPercentage = startPercentage / 100 @@ -393,7 +384,7 @@ extension VLCPlayerViewController { private func setupViewModelListeners(viewModel: VideoPlayerViewModel) { viewModel.$playbackSpeed.sink { newSpeed in self.vlcMediaPlayer.rate = Float(newSpeed.rawValue) - }.store(in: &viewModelReactCancellables) + }.store(in: &viewModelListeners) viewModel.$sliderIsScrubbing.sink { sliderIsScrubbing in if sliderIsScrubbing { @@ -401,15 +392,19 @@ extension VLCPlayerViewController { } else { self.didEndScrubbing() } - }.store(in: &viewModelReactCancellables) + }.store(in: &viewModelListeners) viewModel.$selectedAudioStreamIndex.sink { newAudioStreamIndex in self.didSelectAudioStream(index: newAudioStreamIndex) - }.store(in: &viewModelReactCancellables) + }.store(in: &viewModelListeners) viewModel.$selectedSubtitleStreamIndex.sink { newSubtitleStreamIndex in self.didSelectSubtitleStream(index: newSubtitleStreamIndex) - }.store(in: &viewModelReactCancellables) + }.store(in: &viewModelListeners) + + viewModel.$subtitlesEnabled.sink { newSubtitlesEnabled in + self.didToggleSubtitles(newValue: newSubtitlesEnabled) + }.store(in: &viewModelListeners) } func setMediaPlayerTimeAtCurrentSlider() { @@ -554,8 +549,8 @@ extension VLCPlayerViewController: VLCMediaPlayerDelegate { viewModel.playerState = vlcMediaPlayer.state if vlcMediaPlayer.state == VLCMediaPlayerState.ended { - if viewModel.autoPlayNextItem && viewModel.shouldShowAutoPlayNextItem && viewModel.nextItemVideoPlayerViewModel != nil { - didSelectNextItem() + if viewModel.autoplayEnabled && viewModel.nextItemVideoPlayerViewModel != nil { + didSelectPlayNextItem() } else { didSelectClose() } @@ -565,9 +560,8 @@ extension VLCPlayerViewController: VLCMediaPlayerDelegate { // MARK: mediaPlayerTimeChanged func mediaPlayerTimeChanged(_ aNotification: Notification!) { - guard !viewModel.sliderIsScrubbing else { - lastPlayerTicks = currentPlayerTicks - return + if !viewModel.sliderIsScrubbing { + viewModel.sliderPercentage = Double(vlcMediaPlayer.position) } viewModel.sliderPercentage = Double(vlcMediaPlayer.position) @@ -581,6 +575,9 @@ extension VLCPlayerViewController: VLCMediaPlayerDelegate { // If needing to fix subtitle streams during playback if vlcMediaPlayer.currentVideoSubTitleIndex != viewModel.selectedSubtitleStreamIndex && viewModel.subtitlesEnabled { didSelectSubtitleStream(index: viewModel.selectedSubtitleStreamIndex) + } + + if vlcMediaPlayer.currentAudioTrackIndex != viewModel.selectedAudioStreamIndex { didSelectAudioStream(index: viewModel.selectedAudioStreamIndex) } @@ -606,12 +603,11 @@ extension VLCPlayerViewController: PlayerOverlayDelegate { lastProgressReportTicks = currentPlayerTicks } + /// Do not call when setting to index -1 func didSelectSubtitleStream(index: Int) { - if viewModel.subtitlesEnabled { - vlcMediaPlayer.currentVideoSubTitleIndex = Int32(index) - } else { - vlcMediaPlayer.currentVideoSubTitleIndex = -1 - } + + viewModel.subtitlesEnabled = true + vlcMediaPlayer.currentVideoSubTitleIndex = Int32(index) viewModel.sendProgressReport() @@ -626,19 +622,8 @@ extension VLCPlayerViewController: PlayerOverlayDelegate { dismiss(animated: true, completion: nil) } - func didSelectGoogleCast() { - print("didSelectCast") - } - - func didSelectAirplay() { - print("didSelectAirplay") - } - - func didSelectSubtitles() { - - viewModel.subtitlesEnabled = !viewModel.subtitlesEnabled - - if viewModel.subtitlesEnabled { + func didToggleSubtitles(newValue: Bool) { + if newValue { vlcMediaPlayer.currentVideoSubTitleIndex = Int32(viewModel.selectedSubtitleStreamIndex) } else { vlcMediaPlayer.currentVideoSubTitleIndex = -1 @@ -648,38 +633,41 @@ extension VLCPlayerViewController: PlayerOverlayDelegate { // TODO: Implement properly in overlays func didSelectMenu() { stopOverlayDismissTimer() - - hideOverlay() - showOverlayContent() } // TODO: Implement properly in overlays func didDeselectMenu() { - + restartOverlayDismissTimer() } func didSelectBackward() { + flashJumpBackwardOverlay() - vlcMediaPlayer.jumpBackward(jumpBackwardLength.rawValue) + vlcMediaPlayer.jumpBackward(viewModel.jumpBackwardLength.rawValue) - restartOverlayDismissTimer() + if displayingOverlay { + restartOverlayDismissTimer() + } viewModel.sendProgressReport() - self.lastProgressReportTicks = currentPlayerTicks + lastProgressReportTicks = currentPlayerTicks } func didSelectForward() { + flashJumpFowardOverlay() - vlcMediaPlayer.jumpForward(jumpForwardLength.rawValue) + vlcMediaPlayer.jumpForward(viewModel.jumpForwardLength.rawValue) - restartOverlayDismissTimer() + if displayingOverlay { + restartOverlayDismissTimer() + } viewModel.sendProgressReport() - self.lastProgressReportTicks = currentPlayerTicks + lastProgressReportTicks = currentPlayerTicks } func didSelectMain() { @@ -691,8 +679,7 @@ extension VLCPlayerViewController: PlayerOverlayDelegate { case .playing: viewModel.sendPauseReport(paused: true) vlcMediaPlayer.pause() - showOverlay() - restartOverlayDismissTimer(interval: 10) + restartOverlayDismissTimer(interval: 5) case .paused: viewModel.sendPauseReport(paused: false) vlcMediaPlayer.play() @@ -718,20 +705,20 @@ extension VLCPlayerViewController: PlayerOverlayDelegate { viewModel.sendProgressReport() - self.lastProgressReportTicks = currentPlayerTicks + lastProgressReportTicks = currentPlayerTicks } - func didSelectPreviousItem() { - setupMediaPlayer(newViewModel: viewModel.previousItemVideoPlayerViewModel!) - startPlayback() + func didSelectPlayPreviousItem() { + if let previousItemVideoPlayerViewModel = viewModel.previousItemVideoPlayerViewModel { + setupMediaPlayer(newViewModel: previousItemVideoPlayerViewModel) + startPlayback() + } } - func didSelectNextItem() { - setupMediaPlayer(newViewModel: viewModel.nextItemVideoPlayerViewModel!) - startPlayback() - } - - func didFocusOnButton() { - restartOverlayDismissTimer(interval: 8) + func didSelectPlayNextItem() { + if let nextItemVideoPlayerViewModel = viewModel.nextItemVideoPlayerViewModel { + setupMediaPlayer(newViewModel: nextItemVideoPlayerViewModel) + startPlayback() + } } } diff --git a/JellyfinPlayer tvOS/Views/VideoPlayer/tvOSOverlay/SmallMenuOverlay.swift b/JellyfinPlayer tvOS/Views/VideoPlayer/tvOSOverlay/SmallMenuOverlay.swift index c7f6d8e8..4dd2f855 100644 --- a/JellyfinPlayer tvOS/Views/VideoPlayer/tvOSOverlay/SmallMenuOverlay.swift +++ b/JellyfinPlayer tvOS/Views/VideoPlayer/tvOSOverlay/SmallMenuOverlay.swift @@ -12,15 +12,16 @@ import SwiftUI struct SmallMediaStreamSelectionView: View { - @State var selectedItem: MediaStream? + @Binding var selectedItem: MediaStream? + private let title: String private var items: [MediaStream] private var selectedAction: (MediaStream) -> Void - init(items: [MediaStream], selectedItem: MediaStream? = nil, selectedAction: @escaping (MediaStream) -> Void) { - self.items = items - self.selectedItem = selectedItem - self.selectedAction = selectedAction - } +// init(items: [MediaStream], selectedItem: MediaStream?, selectedAction: @escaping (MediaStream) -> Void) { +// self.items = items +// self.selectedItem = selectedItem +// self.selectedAction = selectedAction +// } var body: some View { ZStack(alignment: .bottom) { @@ -35,7 +36,7 @@ struct SmallMediaStreamSelectionView: View { Spacer() HStack { - Text("Subtitles") + Text(title) .font(.title3) Spacer() } @@ -44,7 +45,7 @@ struct SmallMediaStreamSelectionView: View { HStack { ForEach(items, id: \.self) { item in Button { -// self.selectedItem = item + self.selectedAction(item) } label: { if item == selectedItem { Label(item.displayTitle ?? "No Title", systemImage: "checkmark") diff --git a/JellyfinPlayer tvOS/Views/VideoPlayer/tvOSOverlay/tvOSOverlayContent.swift b/JellyfinPlayer tvOS/Views/VideoPlayer/tvOSOverlay/tvOSOverlayContent.swift index 1417b98f..0558eb28 100644 --- a/JellyfinPlayer tvOS/Views/VideoPlayer/tvOSOverlay/tvOSOverlayContent.swift +++ b/JellyfinPlayer tvOS/Views/VideoPlayer/tvOSOverlay/tvOSOverlayContent.swift @@ -59,31 +59,30 @@ struct tvOSOverlayContentView: View { } struct tvOSOverlayContentView_Previews: PreviewProvider { + + static let videoPlayerViewModel = VideoPlayerViewModel(item: BaseItemDto(), + title: "Glorious Purpose", + subtitle: "Loki - S1E1", + streamURL: URL(string: "www.apple.com")!, + hlsURL: URL(string: "www.apple.com")!, + response: PlaybackInfoResponse(), + audioStreams: [MediaStream(displayTitle: "English", index: -1)], + subtitleStreams: [MediaStream(displayTitle: "None", index: -1)], + selectedAudioStreamIndex: -1, + selectedSubtitleStreamIndex: -1, + subtitlesEnabled: true, + autoplayEnabled: false, + overlayType: .compact, + shouldShowPlayPreviousItem: true, + shouldShowPlayNextItem: true, + shouldShowAutoPlayNextItem: true) + static var previews: some View { ZStack { Color.red .ignoresSafeArea() - tvOSOverlayContentView(viewModel: VideoPlayerViewModel(item: BaseItemDto(runTimeTicks: 720 * 10_000_000), - title: "Glorious Purpose", - subtitle: "Loki - S1E1", - streamURL: URL(string: "www.apple.com")!, - hlsURL: URL(string: "www.apple.com")!, - response: PlaybackInfoResponse(), - audioStreams: [MediaStream(displayTitle: "English", index: -1)], - subtitleStreams: [MediaStream(displayTitle: "None", index: -1)], - defaultAudioStreamIndex: -1, - defaultSubtitleStreamIndex: -1, - playerState: .error, - shouldShowGoogleCast: false, - shouldShowAirplay: false, - subtitlesEnabled: true, - sliderPercentage: 0.432, - selectedAudioStreamIndex: -1, - selectedSubtitleStreamIndex: -1, - showAdjacentItems: true, - shouldShowAutoPlayNextItem: true, - autoPlayNextItem: true)) + tvOSOverlayContentView(viewModel: videoPlayerViewModel) } } } diff --git a/JellyfinPlayer tvOS/Views/VideoPlayer/tvOSOverlay/tvOSVLCOverlay.swift b/JellyfinPlayer tvOS/Views/VideoPlayer/tvOSOverlay/tvOSVLCOverlay.swift index ab3b8b76..9fab7783 100644 --- a/JellyfinPlayer tvOS/Views/VideoPlayer/tvOSOverlay/tvOSVLCOverlay.swift +++ b/JellyfinPlayer tvOS/Views/VideoPlayer/tvOSOverlay/tvOSVLCOverlay.swift @@ -55,16 +55,19 @@ struct tvOSVLCOverlay: View { Spacer() - if viewModel.showAdjacentItems { + + if viewModel.shouldShowPlayPreviousItem { SFSymbolButton(systemName: "chevron.left.circle", action: { - viewModel.playerOverlayDelegate?.didSelectPreviousItem() + viewModel.playerOverlayDelegate?.didSelectPlayPreviousItem() }) .frame(maxWidth: 30, maxHeight: 30) .disabled(viewModel.previousItemVideoPlayerViewModel == nil) .foregroundColor(viewModel.nextItemVideoPlayerViewModel == nil ? .gray : .white) - + } + + if viewModel.shouldShowPlayNextItem { SFSymbolButton(systemName: "chevron.right.circle", action: { - viewModel.playerOverlayDelegate?.didSelectNextItem() + viewModel.playerOverlayDelegate?.didSelectPlayNextItem() }) .frame(maxWidth: 30, maxHeight: 30) .disabled(viewModel.nextItemVideoPlayerViewModel == nil) @@ -74,12 +77,12 @@ struct tvOSVLCOverlay: View { if !viewModel.subtitleStreams.isEmpty { if viewModel.subtitlesEnabled { SFSymbolButton(systemName: "captions.bubble.fill") { - viewModel.playerOverlayDelegate?.didSelectSubtitles() + viewModel.subtitlesEnabled.toggle() } .frame(maxWidth: 30, maxHeight: 30) } else { SFSymbolButton(systemName: "captions.bubble") { - viewModel.playerOverlayDelegate?.didSelectSubtitles() + viewModel.subtitlesEnabled.toggle() } .frame(maxWidth: 30, maxHeight: 30) } @@ -121,32 +124,30 @@ struct tvOSVLCOverlay: View { } struct tvOSVLCOverlay_Previews: PreviewProvider { + + static let videoPlayerViewModel = VideoPlayerViewModel(item: BaseItemDto(), + title: "Glorious Purpose", + subtitle: "Loki - S1E1", + streamURL: URL(string: "www.apple.com")!, + hlsURL: URL(string: "www.apple.com")!, + response: PlaybackInfoResponse(), + audioStreams: [MediaStream(displayTitle: "English", index: -1)], + subtitleStreams: [MediaStream(displayTitle: "None", index: -1)], + selectedAudioStreamIndex: -1, + selectedSubtitleStreamIndex: -1, + subtitlesEnabled: true, + autoplayEnabled: false, + overlayType: .compact, + shouldShowPlayPreviousItem: true, + shouldShowPlayNextItem: true, + shouldShowAutoPlayNextItem: true) + static var previews: some View { ZStack { Color.red .ignoresSafeArea() - tvOSVLCOverlay(viewModel: VideoPlayerViewModel(item: BaseItemDto(runTimeTicks: 720 * 10_000_000), - title: "Glorious Purpose", - subtitle: "Loki - S1E1", - streamURL: URL(string: "www.apple.com")!, - hlsURL: URL(string: "www.apple.com")!, - response: PlaybackInfoResponse(), - audioStreams: [MediaStream(displayTitle: "English", index: -1)], - subtitleStreams: [MediaStream(displayTitle: "None", index: -1)], - defaultAudioStreamIndex: -1, - defaultSubtitleStreamIndex: -1, - playerState: .error, - shouldShowGoogleCast: false, - shouldShowAirplay: false, - subtitlesEnabled: true, - sliderPercentage: 0.432, - selectedAudioStreamIndex: -1, - selectedSubtitleStreamIndex: -1, - showAdjacentItems: true, - shouldShowAutoPlayNextItem: true, - autoPlayNextItem: true)) + tvOSVLCOverlay(viewModel: videoPlayerViewModel) } - .previewInterfaceOrientation(.landscapeLeft) } } diff --git a/JellyfinPlayer/Views/VideoPlayer/VLCPlayerOverlayView.swift b/JellyfinPlayer/Views/VideoPlayer/VLCPlayerOverlayView.swift index b578ecf1..5972123d 100644 --- a/JellyfinPlayer/Views/VideoPlayer/VLCPlayerOverlayView.swift +++ b/JellyfinPlayer/Views/VideoPlayer/VLCPlayerOverlayView.swift @@ -95,7 +95,7 @@ struct VLCPlayerOverlayView: View { if viewModel.autoplayEnabled { Image(systemName: "play.circle.fill") } else { - Image(systemName: "play.circle") + Image(systemName: "stop.circle") } } } diff --git a/JellyfinPlayer/Views/VideoPlayer/VLCPlayerViewController.swift b/JellyfinPlayer/Views/VideoPlayer/VLCPlayerViewController.swift index 490e242e..caabf09c 100644 --- a/JellyfinPlayer/Views/VideoPlayer/VLCPlayerViewController.swift +++ b/JellyfinPlayer/Views/VideoPlayer/VLCPlayerViewController.swift @@ -266,7 +266,7 @@ class VLCPlayerViewController: UIViewController { view.addSubview(newJumpForwardImageView) NSLayoutConstraint.activate([ - newJumpForwardImageView.leftAnchor.constraint(equalTo: view.rightAnchor, constant: -150), + newJumpForwardImageView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -150), newJumpForwardImageView.centerYAnchor.constraint(equalTo: view.centerYAnchor) ]) diff --git a/Shared/Coordinators/VideoPlayerCoordinator/tvOSVideoPlayerCoordinator.swift b/Shared/Coordinators/VideoPlayerCoordinator/tvOSVideoPlayerCoordinator.swift index 56c03fae..cffcb52f 100644 --- a/Shared/Coordinators/VideoPlayerCoordinator/tvOSVideoPlayerCoordinator.swift +++ b/Shared/Coordinators/VideoPlayerCoordinator/tvOSVideoPlayerCoordinator.swift @@ -19,7 +19,6 @@ final class VideoPlayerCoordinator: NavigationCoordinatable { @Root var start = makeStart - @Default(.nativeVideoPlayer) var nativeVideoPlayer let viewModel: VideoPlayerViewModel init(viewModel: VideoPlayerViewModel) { @@ -27,9 +26,6 @@ final class VideoPlayerCoordinator: NavigationCoordinatable { } @ViewBuilder func makeStart() -> some View { -// NativePlayerView(viewModel: viewModel) -// .navigationBarHidden(true) -// .ignoresSafeArea() VLCPlayerView(viewModel: viewModel) .navigationBarHidden(true) .ignoresSafeArea()