From e12da2cf0736ed93bdf1c45966de198655994b39 Mon Sep 17 00:00:00 2001 From: Ethan Pippin Date: Mon, 10 Jan 2022 13:34:03 -0700 Subject: [PATCH] format merge --- Shared/Extensions/VLCPlayer+subtitles.swift | 33 +-- Shared/Objects/SubtitleSize.swift | 85 +++--- .../SwiftfinStore/SwiftfinStoreDefaults.swift | 77 +++--- .../Views/SettingsView/SettingsView.swift | 215 ++++++++------- .../VideoPlayer/VLCPlayerViewController.swift | 199 +++++++------- Swiftfin.xcodeproj/project.pbxproj | 8 - .../Views/SettingsView/SettingsView.swift | 256 ++++++++++-------- .../VideoPlayer/VLCPlayerViewController.swift | 195 ++++++------- 8 files changed, 544 insertions(+), 524 deletions(-) diff --git a/Shared/Extensions/VLCPlayer+subtitles.swift b/Shared/Extensions/VLCPlayer+subtitles.swift index f1e9b6bd..76d46c9a 100644 --- a/Shared/Extensions/VLCPlayer+subtitles.swift +++ b/Shared/Extensions/VLCPlayer+subtitles.swift @@ -1,26 +1,23 @@ // - /* - * SwiftFin is subject to the terms of the Mozilla Public - * License, v2.0. If a copy of the MPL was not distributed with this - * file, you can obtain one at https://mozilla.org/MPL/2.0/. - * - * Copyright 2021 Aiden Vigue & Jellyfin Contributors - */ +// Swiftfin is subject to the terms of the Mozilla Public +// License, v2.0. If a copy of the MPL was not distributed with this +// file, you can obtain one at https://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2022 Jellyfin & Jellyfin Contributors +// #if os(tvOS) -import TVVLCKit + import TVVLCKit #else -import MobileVLCKit + import MobileVLCKit #endif extension VLCMediaPlayer { - /// Applies font size to the player - /// - /// This is pretty hacky until VLCKit 4 has a public API to support this - func setSubtitleSize(_ size: SubtitleSize) { - perform( - Selector(("setTextRendererFontSize:")), - with: size.textRendererFontSize - ) - } + /// Applies font size to the player + /// + /// This is pretty hacky until VLCKit 4 has a public API to support this + func setSubtitleSize(_ size: SubtitleSize) { + perform(Selector(("setTextRendererFontSize:")), + with: size.textRendererFontSize) + } } diff --git a/Shared/Objects/SubtitleSize.swift b/Shared/Objects/SubtitleSize.swift index 7b944efc..bd583aef 100644 --- a/Shared/Objects/SubtitleSize.swift +++ b/Shared/Objects/SubtitleSize.swift @@ -1,59 +1,58 @@ // - /* - * SwiftFin is subject to the terms of the Mozilla Public - * License, v2.0. If a copy of the MPL was not distributed with this - * file, you can obtain one at https://mozilla.org/MPL/2.0/. - * - * Copyright 2021 Aiden Vigue & Jellyfin Contributors - */ +// Swiftfin is subject to the terms of the Mozilla Public +// License, v2.0. If a copy of the MPL was not distributed with this +// file, you can obtain one at https://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2022 Jellyfin & Jellyfin Contributors +// import Defaults enum SubtitleSize: Int32, CaseIterable, Defaults.Serializable { - case smallest - case smaller - case regular - case larger - case largest + case smallest + case smaller + case regular + case larger + case largest } // MARK: - appearance extension SubtitleSize { - var label: String { - switch self { - case .smallest: - return "Smallest" - case .smaller: - return "Smaller" - case .regular: - return "Regular" - case .larger: - return "Larger" - case .largest: - return "Largest" - } - } + var label: String { + switch self { + case .smallest: + return "Smallest" + case .smaller: + return "Smaller" + case .regular: + return "Regular" + case .larger: + return "Larger" + case .largest: + return "Largest" + } + } } // MARK: - sizing for VLC extension SubtitleSize { - /// Value to be passed to VLCKit (via hacky internal property, until VLCKit 4) - /// - /// note that it doesn't correspond to actual font sizes; a smaller int creates bigger text - var textRendererFontSize: Int { - switch self { - case .smallest: - return 24 - case .smaller: - return 20 - case .regular: - return 16 - case .larger: - return 12 - case .largest: - return 8 - } - } + /// Value to be passed to VLCKit (via hacky internal property, until VLCKit 4) + /// + /// note that it doesn't correspond to actual font sizes; a smaller int creates bigger text + var textRendererFontSize: Int { + switch self { + case .smallest: + return 24 + case .smaller: + return 20 + case .regular: + return 16 + case .larger: + return 12 + case .largest: + return 8 + } + } } diff --git a/Shared/SwiftfinStore/SwiftfinStoreDefaults.swift b/Shared/SwiftfinStore/SwiftfinStoreDefaults.swift index 9ab7725f..01502ce3 100644 --- a/Shared/SwiftfinStore/SwiftfinStoreDefaults.swift +++ b/Shared/SwiftfinStore/SwiftfinStoreDefaults.swift @@ -25,47 +25,52 @@ extension SwiftfinStore { extension Defaults.Keys { - // Universal settings - static let defaultHTTPScheme = Key("defaultHTTPScheme", default: .http, suite: SwiftfinStore.Defaults.universalSuite) - static let appAppearance = Key("appAppearance", default: .system, suite: SwiftfinStore.Defaults.universalSuite) + // Universal settings + static let defaultHTTPScheme = Key("defaultHTTPScheme", default: .http, suite: SwiftfinStore.Defaults.universalSuite) + static let appAppearance = Key("appAppearance", default: .system, suite: SwiftfinStore.Defaults.universalSuite) - // General settings - static let lastServerUserID = Defaults.Key("lastServerUserID", suite: SwiftfinStore.Defaults.generalSuite) - static let inNetworkBandwidth = Key("InNetworkBandwidth", default: 40_000_000, suite: SwiftfinStore.Defaults.generalSuite) - static let outOfNetworkBandwidth = Key("OutOfNetworkBandwidth", default: 40_000_000, suite: SwiftfinStore.Defaults.generalSuite) - static let isAutoSelectSubtitles = Key("isAutoSelectSubtitles", default: false, suite: SwiftfinStore.Defaults.generalSuite) - static let autoSelectSubtitlesLangCode = Key("AutoSelectSubtitlesLangCode", default: "Auto", suite: SwiftfinStore.Defaults.generalSuite) - static let autoSelectAudioLangCode = Key("AutoSelectAudioLangCode", default: "Auto", suite: SwiftfinStore.Defaults.generalSuite) + // General settings + static let lastServerUserID = Defaults.Key("lastServerUserID", suite: SwiftfinStore.Defaults.generalSuite) + static let inNetworkBandwidth = Key("InNetworkBandwidth", default: 40_000_000, suite: SwiftfinStore.Defaults.generalSuite) + static let outOfNetworkBandwidth = Key("OutOfNetworkBandwidth", default: 40_000_000, suite: SwiftfinStore.Defaults.generalSuite) + static let isAutoSelectSubtitles = Key("isAutoSelectSubtitles", default: false, suite: SwiftfinStore.Defaults.generalSuite) + static let autoSelectSubtitlesLangCode = Key("AutoSelectSubtitlesLangCode", default: "Auto", + suite: SwiftfinStore.Defaults.generalSuite) + static let autoSelectAudioLangCode = Key("AutoSelectAudioLangCode", default: "Auto", suite: SwiftfinStore.Defaults.generalSuite) - // Customize settings - static let showPosterLabels = Key("showPosterLabels", default: true, suite: SwiftfinStore.Defaults.generalSuite) - static let showCastAndCrew = Key("showCastAndCrew", default: true, suite: SwiftfinStore.Defaults.generalSuite) + // Customize settings + static let showPosterLabels = Key("showPosterLabels", default: true, suite: SwiftfinStore.Defaults.generalSuite) + static let showCastAndCrew = Key("showCastAndCrew", default: true, suite: SwiftfinStore.Defaults.generalSuite) - // Video player / overlay settings - static let overlayType = Key("overlayType", default: .normal, suite: SwiftfinStore.Defaults.generalSuite) - static let jumpGesturesEnabled = Key("gesturesEnabled", default: true, suite: SwiftfinStore.Defaults.generalSuite) - static let videoPlayerJumpForward = Key("videoPlayerJumpForward", default: .fifteen, suite: SwiftfinStore.Defaults.generalSuite) - static let videoPlayerJumpBackward = Key("videoPlayerJumpBackward", default: .fifteen, suite: SwiftfinStore.Defaults.generalSuite) - static let autoplayEnabled = Key("autoPlayNextItem", default: true, suite: SwiftfinStore.Defaults.generalSuite) - static let resumeOffset = Key("resumeOffset", default: false, suite: SwiftfinStore.Defaults.generalSuite) - static let subtitleSize = Key("subtitleSize", default: .regular, suite: SwiftfinStore.Defaults.generalSuite) + // Video player / overlay settings + static let overlayType = Key("overlayType", default: .normal, suite: SwiftfinStore.Defaults.generalSuite) + static let jumpGesturesEnabled = Key("gesturesEnabled", default: true, suite: SwiftfinStore.Defaults.generalSuite) + static let videoPlayerJumpForward = Key("videoPlayerJumpForward", default: .fifteen, + suite: SwiftfinStore.Defaults.generalSuite) + static let videoPlayerJumpBackward = Key("videoPlayerJumpBackward", default: .fifteen, + suite: SwiftfinStore.Defaults.generalSuite) + static let autoplayEnabled = Key("autoPlayNextItem", default: true, suite: SwiftfinStore.Defaults.generalSuite) + static let resumeOffset = Key("resumeOffset", default: false, suite: SwiftfinStore.Defaults.generalSuite) + static let subtitleSize = Key("subtitleSize", default: .regular, suite: SwiftfinStore.Defaults.generalSuite) - // Should show video player items - static let shouldShowPlayPreviousItem = Key("shouldShowPreviousItem", default: true, suite: SwiftfinStore.Defaults.generalSuite) - static let shouldShowPlayNextItem = Key("shouldShowNextItem", default: true, suite: SwiftfinStore.Defaults.generalSuite) - static let shouldShowAutoPlay = Key("shouldShowAutoPlayNextItem", default: true, suite: SwiftfinStore.Defaults.generalSuite) + // Should show video player items + static let shouldShowPlayPreviousItem = Key("shouldShowPreviousItem", default: true, suite: SwiftfinStore.Defaults.generalSuite) + static let shouldShowPlayNextItem = Key("shouldShowNextItem", default: true, suite: SwiftfinStore.Defaults.generalSuite) + static let shouldShowAutoPlay = Key("shouldShowAutoPlayNextItem", default: true, suite: SwiftfinStore.Defaults.generalSuite) - // Should show video player items in overlay menu - static let shouldShowJumpButtonsInOverlayMenu = Key("shouldShowJumpButtonsInMenu", default: true, suite: SwiftfinStore.Defaults.generalSuite) + // Should show video player items in overlay menu + static let shouldShowJumpButtonsInOverlayMenu = Key("shouldShowJumpButtonsInMenu", default: true, + suite: SwiftfinStore.Defaults.generalSuite) - // Experimental settings - struct Experimental { - static let syncSubtitleStateWithAdjacent = Key("experimental.syncSubtitleState", default: false, suite: SwiftfinStore.Defaults.generalSuite) - static let liveTVAlphaEnabled = Key("liveTVAlphaEnabled", default: false, suite: SwiftfinStore.Defaults.generalSuite) - } + // Experimental settings + enum Experimental { + static let syncSubtitleStateWithAdjacent = Key("experimental.syncSubtitleState", default: false, + suite: SwiftfinStore.Defaults.generalSuite) + static let liveTVAlphaEnabled = Key("liveTVAlphaEnabled", default: false, suite: SwiftfinStore.Defaults.generalSuite) + } - // tvos specific - static let downActionShowsMenu = Key("downActionShowsMenu", default: true, suite: SwiftfinStore.Defaults.generalSuite) - static let confirmClose = Key("confirmClose", default: false, suite: SwiftfinStore.Defaults.generalSuite) - static let tvOSCinematicViews = Key("tvOSCinematicViews", default: false, suite: SwiftfinStore.Defaults.generalSuite) + // tvos specific + static let downActionShowsMenu = Key("downActionShowsMenu", default: true, suite: SwiftfinStore.Defaults.generalSuite) + static let confirmClose = Key("confirmClose", default: false, suite: SwiftfinStore.Defaults.generalSuite) + static let tvOSCinematicViews = Key("tvOSCinematicViews", default: false, suite: SwiftfinStore.Defaults.generalSuite) } diff --git a/Swiftfin tvOS/Views/SettingsView/SettingsView.swift b/Swiftfin tvOS/Views/SettingsView/SettingsView.swift index c9e6db47..e2fbd299 100644 --- a/Swiftfin tvOS/Views/SettingsView/SettingsView.swift +++ b/Swiftfin tvOS/Views/SettingsView/SettingsView.swift @@ -13,127 +13,136 @@ import SwiftUI struct SettingsView: View { - @EnvironmentObject var settingsRouter: SettingsCoordinator.Router - @ObservedObject var viewModel: SettingsViewModel + @EnvironmentObject + var settingsRouter: SettingsCoordinator.Router + @ObservedObject + var viewModel: SettingsViewModel - @Default(.autoSelectAudioLangCode) var autoSelectAudioLangcode - @Default(.videoPlayerJumpForward) var jumpForwardLength - @Default(.videoPlayerJumpBackward) var jumpBackwardLength - @Default(.downActionShowsMenu) var downActionShowsMenu - @Default(.confirmClose) var confirmClose - @Default(.tvOSCinematicViews) var tvOSCinematicViews - @Default(.showPosterLabels) var showPosterLabels - @Default(.resumeOffset) var resumeOffset - @Default(.subtitleSize) var subtitleSize + @Default(.autoSelectAudioLangCode) + var autoSelectAudioLangcode + @Default(.videoPlayerJumpForward) + var jumpForwardLength + @Default(.videoPlayerJumpBackward) + var jumpBackwardLength + @Default(.downActionShowsMenu) + var downActionShowsMenu + @Default(.confirmClose) + var confirmClose + @Default(.tvOSCinematicViews) + var tvOSCinematicViews + @Default(.showPosterLabels) + var showPosterLabels + @Default(.resumeOffset) + var resumeOffset + @Default(.subtitleSize) + var subtitleSize - var body: some View { - GeometryReader { reader in - HStack { + var body: some View { + GeometryReader { reader in + HStack { - Image(uiImage: UIImage(named: "App Icon")!) - .cornerRadius(30) - .scaleEffect(2) - .frame(width: reader.size.width / 2) + Image(uiImage: UIImage(named: "App Icon")!) + .cornerRadius(30) + .scaleEffect(2) + .frame(width: reader.size.width / 2) - Form { - Section(header: EmptyView()) { + Form { + Section(header: EmptyView()) { - Button { + Button {} label: { + HStack { + Text("User") + Spacer() + Text(viewModel.user.username) + .foregroundColor(.jellyfinPurple) + } + } - } label: { - HStack { - Text("User") - Spacer() - Text(viewModel.user.username) - .foregroundColor(.jellyfinPurple) - } - } + Button { + settingsRouter.route(to: \.serverDetail) + } label: { + HStack { + Text("Server") + .foregroundColor(.primary) + Spacer() + Text(viewModel.server.name) + .foregroundColor(.jellyfinPurple) - Button { - settingsRouter.route(to: \.serverDetail) - } label: { - HStack { - Text("Server") - .foregroundColor(.primary) - Spacer() - Text(viewModel.server.name) - .foregroundColor(.jellyfinPurple) + Image(systemName: "chevron.right") + .foregroundColor(.jellyfinPurple) + } + } - Image(systemName: "chevron.right") - .foregroundColor(.jellyfinPurple) - } - } + Button { + SessionManager.main.logout() + } label: { + Text("Switch User") + .foregroundColor(Color.jellyfinPurple) + .font(.callout) + } + } - Button { - SessionManager.main.logout() - } label: { - Text("Switch User") - .foregroundColor(Color.jellyfinPurple) - .font(.callout) - } - } + Section(header: Text("Video Player")) { + Picker("Jump Forward Length", selection: $jumpForwardLength) { + ForEach(VideoPlayerJumpLength.allCases, id: \.self) { length in + Text(length.label).tag(length.rawValue) + } + } - Section(header: Text("Video Player")) { - Picker("Jump Forward Length", selection: $jumpForwardLength) { - ForEach(VideoPlayerJumpLength.allCases, id: \.self) { length in - Text(length.label).tag(length.rawValue) - } - } + Picker("Jump Backward Length", selection: $jumpBackwardLength) { + ForEach(VideoPlayerJumpLength.allCases, id: \.self) { length in + Text(length.label).tag(length.rawValue) + } + } - Picker("Jump Backward Length", selection: $jumpBackwardLength) { - ForEach(VideoPlayerJumpLength.allCases, id: \.self) { length in - Text(length.label).tag(length.rawValue) - } - } + Toggle("Resume 5 Second Offset", isOn: $resumeOffset) - Toggle("Resume 5 Second Offset", isOn: $resumeOffset) + Toggle("Press Down for Menu", isOn: $downActionShowsMenu) - Toggle("Press Down for Menu", isOn: $downActionShowsMenu) + Toggle("Confirm Close", isOn: $confirmClose) - Toggle("Confirm Close", isOn: $confirmClose) + Button { + settingsRouter.route(to: \.overlaySettings) + } label: { + HStack { + Text("Overlay") + .foregroundColor(.primary) + Spacer() + Image(systemName: "chevron.right") + } + } - Button { - settingsRouter.route(to: \.overlaySettings) - } label: { - HStack { - Text("Overlay") - .foregroundColor(.primary) - Spacer() - Image(systemName: "chevron.right") - } - } + Button { + settingsRouter.route(to: \.experimentalSettings) + } label: { + HStack { + Text("Experimental") + .foregroundColor(.primary) + Spacer() + Image(systemName: "chevron.right") + } + } + } - Button { - settingsRouter.route(to: \.experimentalSettings) - } label: { - HStack { - Text("Experimental") - .foregroundColor(.primary) - Spacer() - Image(systemName: "chevron.right") - } - } - } + Section { + Toggle("Cinematic Views", isOn: $tvOSCinematicViews) + } header: { + Text("Appearance") + } - Section { - Toggle("Cinematic Views", isOn: $tvOSCinematicViews) - } header: { - Text("Appearance") - } + Section(header: L10n.accessibility.text) { + Toggle("Show Poster Labels", isOn: $showPosterLabels) - Section(header: L10n.accessibility.text) { - Toggle("Show Poster Labels", isOn: $showPosterLabels) - - Picker("Subtitle size", selection: $subtitleSize) { - ForEach(SubtitleSize.allCases, id: \.self) { size in - Text(size.label).tag(size.rawValue) - } - } - } - } - } - } - } + Picker("Subtitle size", selection: $subtitleSize) { + ForEach(SubtitleSize.allCases, id: \.self) { size in + Text(size.label).tag(size.rawValue) + } + } + } + } + } + } + } } struct SettingsView_Previews: PreviewProvider { diff --git a/Swiftfin tvOS/Views/VideoPlayer/VLCPlayerViewController.swift b/Swiftfin tvOS/Views/VideoPlayer/VLCPlayerViewController.swift index 88e82a14..97809fe0 100644 --- a/Swiftfin tvOS/Views/VideoPlayer/VLCPlayerViewController.swift +++ b/Swiftfin tvOS/Views/VideoPlayer/VLCPlayerViewController.swift @@ -382,138 +382,139 @@ class VLCPlayerViewController: UIViewController { extension VLCPlayerViewController { - /// Main function that handles setting up the media player with the current VideoPlayerViewModel - /// and also takes the role of setting the 'viewModel' property with the given viewModel - /// - /// Use case for this is setting new media within the same VLCPlayerViewController - func setupMediaPlayer(newViewModel: VideoPlayerViewModel) { + /// Main function that handles setting up the media player with the current VideoPlayerViewModel + /// and also takes the role of setting the 'viewModel' property with the given viewModel + /// + /// Use case for this is setting new media within the same VLCPlayerViewController + func setupMediaPlayer(newViewModel: VideoPlayerViewModel) { - // remove old player + // remove old player - if vlcMediaPlayer.media != nil { - viewModelListeners.forEach({ $0.cancel() }) + if vlcMediaPlayer.media != nil { + viewModelListeners.forEach { $0.cancel() } - vlcMediaPlayer.stop() - viewModel.sendStopReport() - viewModel.playerOverlayDelegate = nil - } + vlcMediaPlayer.stop() + viewModel.sendStopReport() + viewModel.playerOverlayDelegate = nil + } - vlcMediaPlayer = VLCMediaPlayer() + vlcMediaPlayer = VLCMediaPlayer() - // setup with new player and view model + // setup with new player and view model - vlcMediaPlayer = VLCMediaPlayer() + vlcMediaPlayer = VLCMediaPlayer() - vlcMediaPlayer.delegate = self - vlcMediaPlayer.drawable = videoContentView + vlcMediaPlayer.delegate = self + vlcMediaPlayer.drawable = videoContentView - vlcMediaPlayer.setSubtitleSize(Defaults[.subtitleSize]) + vlcMediaPlayer.setSubtitleSize(Defaults[.subtitleSize]) - stopOverlayDismissTimer() + stopOverlayDismissTimer() - // Stop current media if there is one - if vlcMediaPlayer.media != nil { - viewModelListeners.forEach({ $0.cancel() }) + // Stop current media if there is one + if vlcMediaPlayer.media != nil { + viewModelListeners.forEach { $0.cancel() } - vlcMediaPlayer.stop() - viewModel.sendStopReport() - viewModel.playerOverlayDelegate = nil - } + vlcMediaPlayer.stop() + viewModel.sendStopReport() + viewModel.playerOverlayDelegate = nil + } - lastPlayerTicks = newViewModel.item.userData?.playbackPositionTicks ?? 0 - lastProgressReportTicks = newViewModel.item.userData?.playbackPositionTicks ?? 0 + lastPlayerTicks = newViewModel.item.userData?.playbackPositionTicks ?? 0 + lastProgressReportTicks = newViewModel.item.userData?.playbackPositionTicks ?? 0 - // TODO: Custom buffer/cache amounts + // TODO: Custom buffer/cache amounts - let media = VLCMedia(url: newViewModel.streamURL) - media.addOption("--prefetch-buffer-size=1048576") - media.addOption("--network-caching=5000") + let media = VLCMedia(url: newViewModel.streamURL) + media.addOption("--prefetch-buffer-size=1048576") + media.addOption("--network-caching=5000") - vlcMediaPlayer.media = media + vlcMediaPlayer.media = media - setupOverlayHostingController(viewModel: newViewModel) - setupViewModelListeners(viewModel: newViewModel) + setupOverlayHostingController(viewModel: newViewModel) + setupViewModelListeners(viewModel: newViewModel) - newViewModel.getAdjacentEpisodes() - newViewModel.playerOverlayDelegate = self + newViewModel.getAdjacentEpisodes() + newViewModel.playerOverlayDelegate = self - let startPercentage = newViewModel.item.userData?.playedPercentage ?? 0 + let startPercentage = newViewModel.item.userData?.playedPercentage ?? 0 - if startPercentage > 0 { - if viewModel.resumeOffset { - let videoDurationSeconds = Double(viewModel.item.runTimeTicks! / 10_000_000) - var startSeconds = round((startPercentage / 100) * videoDurationSeconds) - startSeconds = startSeconds.subtract(5, floor: 0) - let newStartPercentage = startSeconds / videoDurationSeconds - newViewModel.sliderPercentage = newStartPercentage - } else { - newViewModel.sliderPercentage = startPercentage / 100 - } - } + if startPercentage > 0 { + if viewModel.resumeOffset { + let videoDurationSeconds = Double(viewModel.item.runTimeTicks! / 10_000_000) + var startSeconds = round((startPercentage / 100) * videoDurationSeconds) + startSeconds = startSeconds.subtract(5, floor: 0) + let newStartPercentage = startSeconds / videoDurationSeconds + newViewModel.sliderPercentage = newStartPercentage + } else { + newViewModel.sliderPercentage = startPercentage / 100 + } + } - viewModel = newViewModel - } + viewModel = newViewModel + } - // MARK: startPlayback - func startPlayback() { - vlcMediaPlayer.play() + // MARK: startPlayback - // Setup external subtitles - for externalSubtitle in viewModel.subtitleStreams.filter({ $0.deliveryMethod == .external }) { - if let deliveryURL = externalSubtitle.externalURL(base: SessionManager.main.currentLogin.server.currentURI) { - vlcMediaPlayer.addPlaybackSlave(deliveryURL, type: .subtitle, enforce: false) - } - } + func startPlayback() { + vlcMediaPlayer.play() - setMediaPlayerTimeAtCurrentSlider() + // Setup external subtitles + for externalSubtitle in viewModel.subtitleStreams.filter({ $0.deliveryMethod == .external }) { + if let deliveryURL = externalSubtitle.externalURL(base: SessionManager.main.currentLogin.server.currentURI) { + vlcMediaPlayer.addPlaybackSlave(deliveryURL, type: .subtitle, enforce: false) + } + } - viewModel.sendPlayReport() + setMediaPlayerTimeAtCurrentSlider() - restartOverlayDismissTimer(interval: 5) - } + viewModel.sendPlayReport() - // MARK: setupViewModelListeners + restartOverlayDismissTimer(interval: 5) + } - private func setupViewModelListeners(viewModel: VideoPlayerViewModel) { - viewModel.$playbackSpeed.sink { newSpeed in - self.vlcMediaPlayer.rate = Float(newSpeed.rawValue) - }.store(in: &viewModelListeners) + // MARK: setupViewModelListeners - viewModel.$sliderIsScrubbing.sink { sliderIsScrubbing in - if sliderIsScrubbing { - self.didBeginScrubbing() - } else { - self.didEndScrubbing() - } - }.store(in: &viewModelListeners) + private func setupViewModelListeners(viewModel: VideoPlayerViewModel) { + viewModel.$playbackSpeed.sink { newSpeed in + self.vlcMediaPlayer.rate = Float(newSpeed.rawValue) + }.store(in: &viewModelListeners) - viewModel.$selectedAudioStreamIndex.sink { newAudioStreamIndex in - self.didSelectAudioStream(index: newAudioStreamIndex) - }.store(in: &viewModelListeners) + viewModel.$sliderIsScrubbing.sink { sliderIsScrubbing in + if sliderIsScrubbing { + self.didBeginScrubbing() + } else { + self.didEndScrubbing() + } + }.store(in: &viewModelListeners) - viewModel.$selectedSubtitleStreamIndex.sink { newSubtitleStreamIndex in - self.didSelectSubtitleStream(index: newSubtitleStreamIndex) - }.store(in: &viewModelListeners) + viewModel.$selectedAudioStreamIndex.sink { newAudioStreamIndex in + self.didSelectAudioStream(index: newAudioStreamIndex) + }.store(in: &viewModelListeners) - viewModel.$subtitlesEnabled.sink { newSubtitlesEnabled in - self.didToggleSubtitles(newValue: newSubtitlesEnabled) - }.store(in: &viewModelListeners) - } + viewModel.$selectedSubtitleStreamIndex.sink { newSubtitleStreamIndex in + self.didSelectSubtitleStream(index: newSubtitleStreamIndex) + }.store(in: &viewModelListeners) - func setMediaPlayerTimeAtCurrentSlider() { - // Necessary math as VLCMediaPlayer doesn't work well - // by just setting the position - let videoPosition = Double(vlcMediaPlayer.time.intValue / 1000) - let videoDuration = Double(viewModel.item.runTimeTicks! / 10_000_000) - let secondsScrubbedTo = round(viewModel.sliderPercentage * videoDuration) - let newPositionOffset = secondsScrubbedTo - videoPosition + viewModel.$subtitlesEnabled.sink { newSubtitlesEnabled in + self.didToggleSubtitles(newValue: newSubtitlesEnabled) + }.store(in: &viewModelListeners) + } - if newPositionOffset > 0 { - vlcMediaPlayer.jumpForward(Int32(newPositionOffset)) - } else { - vlcMediaPlayer.jumpBackward(Int32(abs(newPositionOffset))) - } - } + func setMediaPlayerTimeAtCurrentSlider() { + // Necessary math as VLCMediaPlayer doesn't work well + // by just setting the position + let videoPosition = Double(vlcMediaPlayer.time.intValue / 1000) + let videoDuration = Double(viewModel.item.runTimeTicks! / 10_000_000) + let secondsScrubbedTo = round(viewModel.sliderPercentage * videoDuration) + let newPositionOffset = secondsScrubbedTo - videoPosition + + if newPositionOffset > 0 { + vlcMediaPlayer.jumpForward(Int32(newPositionOffset)) + } else { + vlcMediaPlayer.jumpBackward(Int32(abs(newPositionOffset))) + } + } } // MARK: Show/Hide Overlay diff --git a/Swiftfin.xcodeproj/project.pbxproj b/Swiftfin.xcodeproj/project.pbxproj index 161b0935..efcba84f 100644 --- a/Swiftfin.xcodeproj/project.pbxproj +++ b/Swiftfin.xcodeproj/project.pbxproj @@ -1043,7 +1043,6 @@ 62ECA01926FA6D6900E8EBB7 /* AppURLHandler */, 5377CBF8263B596B003A4E83 /* Assets.xcassets */, 53F866422687A45400DCD1D7 /* Components */, - 5D160401278A41BA00D22B99 /* Extensions */, 5377CC02263B596B003A4E83 /* Info.plist */, E13D02842788B634000FCB04 /* Swiftfin.entitlements */, 5377CBFA263B596B003A4E83 /* Preview Content */, @@ -1224,13 +1223,6 @@ path = Components; sourceTree = ""; }; - 5D160401278A41BA00D22B99 /* Extensions */ = { - isa = PBXGroup; - children = ( - ); - path = Extensions; - sourceTree = ""; - }; 5D64683B277B15E4009E09AE /* PreferenceUIHosting */ = { isa = PBXGroup; children = ( diff --git a/Swiftfin/Views/SettingsView/SettingsView.swift b/Swiftfin/Views/SettingsView/SettingsView.swift index 7606bc32..7cb0e00a 100644 --- a/Swiftfin/Views/SettingsView/SettingsView.swift +++ b/Swiftfin/Views/SettingsView/SettingsView.swift @@ -13,139 +13,155 @@ import SwiftUI struct SettingsView: View { - @EnvironmentObject var settingsRouter: SettingsCoordinator.Router - @ObservedObject var viewModel: SettingsViewModel + @EnvironmentObject + var settingsRouter: SettingsCoordinator.Router + @ObservedObject + var viewModel: SettingsViewModel - @Default(.inNetworkBandwidth) var inNetworkStreamBitrate - @Default(.outOfNetworkBandwidth) var outOfNetworkStreamBitrate - @Default(.isAutoSelectSubtitles) var isAutoSelectSubtitles - @Default(.autoSelectSubtitlesLangCode) var autoSelectSubtitlesLangcode - @Default(.autoSelectAudioLangCode) var autoSelectAudioLangcode - @Default(.appAppearance) var appAppearance - @Default(.overlayType) var overlayType - @Default(.videoPlayerJumpForward) var jumpForwardLength - @Default(.videoPlayerJumpBackward) var jumpBackwardLength - @Default(.jumpGesturesEnabled) var jumpGesturesEnabled - @Default(.showPosterLabels) var showPosterLabels - @Default(.showCastAndCrew) var showCastAndCrew - @Default(.resumeOffset) var resumeOffset - @Default(.subtitleSize) var subtitleSize + @Default(.inNetworkBandwidth) + var inNetworkStreamBitrate + @Default(.outOfNetworkBandwidth) + var outOfNetworkStreamBitrate + @Default(.isAutoSelectSubtitles) + var isAutoSelectSubtitles + @Default(.autoSelectSubtitlesLangCode) + var autoSelectSubtitlesLangcode + @Default(.autoSelectAudioLangCode) + var autoSelectAudioLangcode + @Default(.appAppearance) + var appAppearance + @Default(.overlayType) + var overlayType + @Default(.videoPlayerJumpForward) + var jumpForwardLength + @Default(.videoPlayerJumpBackward) + var jumpBackwardLength + @Default(.jumpGesturesEnabled) + var jumpGesturesEnabled + @Default(.showPosterLabels) + var showPosterLabels + @Default(.showCastAndCrew) + var showCastAndCrew + @Default(.resumeOffset) + var resumeOffset + @Default(.subtitleSize) + var subtitleSize - var body: some View { - Form { - Section(header: EmptyView()) { - HStack { - Text("User") - Spacer() - Text(viewModel.user.username) - .foregroundColor(.jellyfinPurple) - } + var body: some View { + Form { + Section(header: EmptyView()) { + HStack { + Text("User") + Spacer() + Text(viewModel.user.username) + .foregroundColor(.jellyfinPurple) + } - Button { - settingsRouter.route(to: \.serverDetail) - } label: { - HStack { - Text("Server") - .foregroundColor(.primary) - Spacer() - Text(viewModel.server.name) - .foregroundColor(.jellyfinPurple) + Button { + settingsRouter.route(to: \.serverDetail) + } label: { + HStack { + Text("Server") + .foregroundColor(.primary) + Spacer() + Text(viewModel.server.name) + .foregroundColor(.jellyfinPurple) - Image(systemName: "chevron.right") - } - } + Image(systemName: "chevron.right") + } + } - Button { - settingsRouter.dismissCoordinator { - SessionManager.main.logout() - } - } label: { - Text("Switch User") - .font(.callout) - } - } + Button { + settingsRouter.dismissCoordinator { + SessionManager.main.logout() + } + } label: { + Text("Switch User") + .font(.callout) + } + } - // TODO: Implement these for playback -// Section(header: Text("Networking")) { -// Picker("Default local quality", selection: $inNetworkStreamBitrate) { -// ForEach(self.viewModel.bitrates, id: \.self) { bitrate in -// Text(bitrate.name).tag(bitrate.value) -// } -// } + // TODO: Implement these for playback + // Section(header: Text("Networking")) { + // Picker("Default local quality", selection: $inNetworkStreamBitrate) { + // ForEach(self.viewModel.bitrates, id: \.self) { bitrate in + // Text(bitrate.name).tag(bitrate.value) + // } + // } // -// Picker("Default remote quality", selection: $outOfNetworkStreamBitrate) { -// ForEach(self.viewModel.bitrates, id: \.self) { bitrate in -// Text(bitrate.name).tag(bitrate.value) -// } -// } -// } + // Picker("Default remote quality", selection: $outOfNetworkStreamBitrate) { + // ForEach(self.viewModel.bitrates, id: \.self) { bitrate in + // Text(bitrate.name).tag(bitrate.value) + // } + // } + // } - Section(header: Text("Video Player")) { - Picker("Jump Forward Length", selection: $jumpForwardLength) { - ForEach(VideoPlayerJumpLength.allCases, id: \.self) { length in - Text(length.label).tag(length.rawValue) - } - } + Section(header: Text("Video Player")) { + Picker("Jump Forward Length", selection: $jumpForwardLength) { + ForEach(VideoPlayerJumpLength.allCases, id: \.self) { length in + Text(length.label).tag(length.rawValue) + } + } - Picker("Jump Backward Length", selection: $jumpBackwardLength) { - ForEach(VideoPlayerJumpLength.allCases, id: \.self) { length in - Text(length.label).tag(length.rawValue) - } - } + Picker("Jump Backward Length", selection: $jumpBackwardLength) { + ForEach(VideoPlayerJumpLength.allCases, id: \.self) { length in + Text(length.label).tag(length.rawValue) + } + } - Toggle("Jump Gestures Enabled", isOn: $jumpGesturesEnabled) + Toggle("Jump Gestures Enabled", isOn: $jumpGesturesEnabled) - Toggle("Resume 5 Second Offset", isOn: $resumeOffset) + Toggle("Resume 5 Second Offset", isOn: $resumeOffset) - Button { - settingsRouter.route(to: \.overlaySettings) - } label: { - HStack { - Text("Overlay") - .foregroundColor(.primary) - Spacer() - Text(overlayType.label) - Image(systemName: "chevron.right") - } - } + Button { + settingsRouter.route(to: \.overlaySettings) + } label: { + HStack { + Text("Overlay") + .foregroundColor(.primary) + Spacer() + Text(overlayType.label) + Image(systemName: "chevron.right") + } + } - Button { - settingsRouter.route(to: \.experimentalSettings) - } label: { - HStack { - Text("Experimental") - .foregroundColor(.primary) - Spacer() - Image(systemName: "chevron.right") - } - } - } + Button { + settingsRouter.route(to: \.experimentalSettings) + } label: { + HStack { + Text("Experimental") + .foregroundColor(.primary) + Spacer() + Image(systemName: "chevron.right") + } + } + } - Section(header: L10n.accessibility.text) { - Toggle("Show Poster Labels", isOn: $showPosterLabels) - Toggle("Show Cast and Crew", isOn: $showCastAndCrew) + Section(header: L10n.accessibility.text) { + Toggle("Show Poster Labels", isOn: $showPosterLabels) + Toggle("Show Cast and Crew", isOn: $showCastAndCrew) - Picker(L10n.appearance, selection: $appAppearance) { - ForEach(AppAppearance.allCases, id: \.self) { appearance in - Text(appearance.localizedName).tag(appearance.rawValue) - } - } - Picker("Subtitle size", selection: $subtitleSize) { - ForEach(SubtitleSize.allCases, id: \.self) { size in - Text(size.label).tag(size.rawValue) - } - } - } - } - .navigationBarTitle("Settings", displayMode: .inline) - .toolbar { - ToolbarItemGroup(placement: .navigationBarLeading) { - Button { - settingsRouter.dismissCoordinator() - } label: { - Image(systemName: "xmark.circle.fill") - } - } - } - } + Picker(L10n.appearance, selection: $appAppearance) { + ForEach(AppAppearance.allCases, id: \.self) { appearance in + Text(appearance.localizedName).tag(appearance.rawValue) + } + } + Picker("Subtitle size", selection: $subtitleSize) { + ForEach(SubtitleSize.allCases, id: \.self) { size in + Text(size.label).tag(size.rawValue) + } + } + } + } + .navigationBarTitle("Settings", displayMode: .inline) + .toolbar { + ToolbarItemGroup(placement: .navigationBarLeading) { + Button { + settingsRouter.dismissCoordinator() + } label: { + Image(systemName: "xmark.circle.fill") + } + } + } + } } diff --git a/Swiftfin/Views/VideoPlayer/VLCPlayerViewController.swift b/Swiftfin/Views/VideoPlayer/VLCPlayerViewController.swift index 554a3807..cf03075b 100644 --- a/Swiftfin/Views/VideoPlayer/VLCPlayerViewController.swift +++ b/Swiftfin/Views/VideoPlayer/VLCPlayerViewController.swift @@ -284,136 +284,137 @@ class VLCPlayerViewController: UIViewController { extension VLCPlayerViewController { - /// Main function that handles setting up the media player with the current VideoPlayerViewModel - /// and also takes the role of setting the 'viewModel' property with the given viewModel - /// - /// Use case for this is setting new media within the same VLCPlayerViewController - func setupMediaPlayer(newViewModel: VideoPlayerViewModel) { + /// Main function that handles setting up the media player with the current VideoPlayerViewModel + /// and also takes the role of setting the 'viewModel' property with the given viewModel + /// + /// Use case for this is setting new media within the same VLCPlayerViewController + func setupMediaPlayer(newViewModel: VideoPlayerViewModel) { - // remove old player + // remove old player - if vlcMediaPlayer.media != nil { - viewModelListeners.forEach({ $0.cancel() }) + if vlcMediaPlayer.media != nil { + viewModelListeners.forEach { $0.cancel() } - vlcMediaPlayer.stop() - viewModel.sendStopReport() - viewModel.playerOverlayDelegate = nil - } + vlcMediaPlayer.stop() + viewModel.sendStopReport() + viewModel.playerOverlayDelegate = nil + } - vlcMediaPlayer = VLCMediaPlayer() + vlcMediaPlayer = VLCMediaPlayer() - // setup with new player and view model + // setup with new player and view model - vlcMediaPlayer = VLCMediaPlayer() + vlcMediaPlayer = VLCMediaPlayer() - vlcMediaPlayer.delegate = self - vlcMediaPlayer.drawable = videoContentView + vlcMediaPlayer.delegate = self + vlcMediaPlayer.drawable = videoContentView - vlcMediaPlayer.setSubtitleSize(Defaults[.subtitleSize]) + vlcMediaPlayer.setSubtitleSize(Defaults[.subtitleSize]) - stopOverlayDismissTimer() + stopOverlayDismissTimer() - lastPlayerTicks = newViewModel.item.userData?.playbackPositionTicks ?? 0 - lastProgressReportTicks = newViewModel.item.userData?.playbackPositionTicks ?? 0 + lastPlayerTicks = newViewModel.item.userData?.playbackPositionTicks ?? 0 + lastProgressReportTicks = newViewModel.item.userData?.playbackPositionTicks ?? 0 - let media = VLCMedia(url: newViewModel.streamURL) - media.addOption("--prefetch-buffer-size=1048576") - media.addOption("--network-caching=5000") + let media = VLCMedia(url: newViewModel.streamURL) + media.addOption("--prefetch-buffer-size=1048576") + media.addOption("--network-caching=5000") - vlcMediaPlayer.media = media + vlcMediaPlayer.media = media - setupOverlayHostingController(viewModel: newViewModel) - setupViewModelListeners(viewModel: newViewModel) + setupOverlayHostingController(viewModel: newViewModel) + setupViewModelListeners(viewModel: newViewModel) - newViewModel.getAdjacentEpisodes() - newViewModel.playerOverlayDelegate = self + newViewModel.getAdjacentEpisodes() + newViewModel.playerOverlayDelegate = self - let startPercentage = newViewModel.item.userData?.playedPercentage ?? 0 + let startPercentage = newViewModel.item.userData?.playedPercentage ?? 0 - if startPercentage > 0 { - if viewModel.resumeOffset { - let videoDurationSeconds = Double(viewModel.item.runTimeTicks! / 10_000_000) - var startSeconds = round((startPercentage / 100) * videoDurationSeconds) - startSeconds = startSeconds.subtract(5, floor: 0) - let newStartPercentage = startSeconds / videoDurationSeconds - newViewModel.sliderPercentage = newStartPercentage - } else { - newViewModel.sliderPercentage = startPercentage / 100 - } - } + if startPercentage > 0 { + if viewModel.resumeOffset { + let videoDurationSeconds = Double(viewModel.item.runTimeTicks! / 10_000_000) + var startSeconds = round((startPercentage / 100) * videoDurationSeconds) + startSeconds = startSeconds.subtract(5, floor: 0) + let newStartPercentage = startSeconds / videoDurationSeconds + newViewModel.sliderPercentage = newStartPercentage + } else { + newViewModel.sliderPercentage = startPercentage / 100 + } + } - viewModel = newViewModel - } + viewModel = newViewModel + } - // MARK: startPlayback - func startPlayback() { - vlcMediaPlayer.play() + // MARK: startPlayback - // Setup external subtitles - for externalSubtitle in viewModel.subtitleStreams.filter({ $0.deliveryMethod == .external }) { - if let deliveryURL = externalSubtitle.externalURL(base: SessionManager.main.currentLogin.server.currentURI) { - vlcMediaPlayer.addPlaybackSlave(deliveryURL, type: .subtitle, enforce: false) - } - } + func startPlayback() { + vlcMediaPlayer.play() - setMediaPlayerTimeAtCurrentSlider() + // Setup external subtitles + for externalSubtitle in viewModel.subtitleStreams.filter({ $0.deliveryMethod == .external }) { + if let deliveryURL = externalSubtitle.externalURL(base: SessionManager.main.currentLogin.server.currentURI) { + vlcMediaPlayer.addPlaybackSlave(deliveryURL, type: .subtitle, enforce: false) + } + } - viewModel.sendPlayReport() + setMediaPlayerTimeAtCurrentSlider() - restartOverlayDismissTimer() - } + viewModel.sendPlayReport() - // MARK: setupViewModelListeners + restartOverlayDismissTimer() + } - private func setupViewModelListeners(viewModel: VideoPlayerViewModel) { + // MARK: setupViewModelListeners - viewModel.$playbackSpeed.sink { newSpeed in - self.vlcMediaPlayer.rate = Float(newSpeed.rawValue) - }.store(in: &viewModelListeners) + private func setupViewModelListeners(viewModel: VideoPlayerViewModel) { - viewModel.$sliderIsScrubbing.sink { sliderIsScrubbing in - if sliderIsScrubbing { - self.didBeginScrubbing() - } else { - self.didEndScrubbing() - } - }.store(in: &viewModelListeners) + viewModel.$playbackSpeed.sink { newSpeed in + self.vlcMediaPlayer.rate = Float(newSpeed.rawValue) + }.store(in: &viewModelListeners) - viewModel.$selectedAudioStreamIndex.sink { newAudioStreamIndex in - self.didSelectAudioStream(index: newAudioStreamIndex) - }.store(in: &viewModelListeners) + viewModel.$sliderIsScrubbing.sink { sliderIsScrubbing in + if sliderIsScrubbing { + self.didBeginScrubbing() + } else { + self.didEndScrubbing() + } + }.store(in: &viewModelListeners) - viewModel.$selectedSubtitleStreamIndex.sink { newSubtitleStreamIndex in - self.didSelectSubtitleStream(index: newSubtitleStreamIndex) - }.store(in: &viewModelListeners) + viewModel.$selectedAudioStreamIndex.sink { newAudioStreamIndex in + self.didSelectAudioStream(index: newAudioStreamIndex) + }.store(in: &viewModelListeners) - viewModel.$subtitlesEnabled.sink { newSubtitlesEnabled in - self.didToggleSubtitles(newValue: newSubtitlesEnabled) - }.store(in: &viewModelListeners) + viewModel.$selectedSubtitleStreamIndex.sink { newSubtitleStreamIndex in + self.didSelectSubtitleStream(index: newSubtitleStreamIndex) + }.store(in: &viewModelListeners) - viewModel.$jumpBackwardLength.sink { newJumpBackwardLength in - self.refreshJumpBackwardOverlayView(with: newJumpBackwardLength) - }.store(in: &viewModelListeners) + viewModel.$subtitlesEnabled.sink { newSubtitlesEnabled in + self.didToggleSubtitles(newValue: newSubtitlesEnabled) + }.store(in: &viewModelListeners) - viewModel.$jumpForwardLength.sink { newJumpForwardLength in - self.refreshJumpForwardOverlayView(with: newJumpForwardLength) - }.store(in: &viewModelListeners) - } + viewModel.$jumpBackwardLength.sink { newJumpBackwardLength in + self.refreshJumpBackwardOverlayView(with: newJumpBackwardLength) + }.store(in: &viewModelListeners) - func setMediaPlayerTimeAtCurrentSlider() { - // Necessary math as VLCMediaPlayer doesn't work well - // by just setting the position - let videoPosition = Double(vlcMediaPlayer.time.intValue / 1000) - let videoDuration = Double(viewModel.item.runTimeTicks! / 10_000_000) - let secondsScrubbedTo = round(viewModel.sliderPercentage * videoDuration) - let newPositionOffset = secondsScrubbedTo - videoPosition + viewModel.$jumpForwardLength.sink { newJumpForwardLength in + self.refreshJumpForwardOverlayView(with: newJumpForwardLength) + }.store(in: &viewModelListeners) + } - if newPositionOffset > 0 { - vlcMediaPlayer.jumpForward(Int32(newPositionOffset)) - } else { - vlcMediaPlayer.jumpBackward(Int32(abs(newPositionOffset))) - } - } + func setMediaPlayerTimeAtCurrentSlider() { + // Necessary math as VLCMediaPlayer doesn't work well + // by just setting the position + let videoPosition = Double(vlcMediaPlayer.time.intValue / 1000) + let videoDuration = Double(viewModel.item.runTimeTicks! / 10_000_000) + let secondsScrubbedTo = round(viewModel.sliderPercentage * videoDuration) + let newPositionOffset = secondsScrubbedTo - videoPosition + + if newPositionOffset > 0 { + vlcMediaPlayer.jumpForward(Int32(newPositionOffset)) + } else { + vlcMediaPlayer.jumpBackward(Int32(abs(newPositionOffset))) + } + } } // MARK: Show/Hide Overlay