From 4e41863d23a5f3d27786c50636c139ce9574a5df Mon Sep 17 00:00:00 2001 From: Ethan Pippin Date: Tue, 17 Aug 2021 23:41:08 -0600 Subject: [PATCH] Add custom time skip durations --- JellyfinPlayer.xcodeproj/project.pbxproj | 12 +++-- JellyfinPlayer/SettingsView.swift | 14 +++++ JellyfinPlayer/VideoPlayer.swift | 21 ++++++-- Shared/Extensions/DefaultsExtension.swift | 2 + Shared/{Typings => Objects}/Typings.swift | 0 Shared/Objects/VideoPlayerJumpLength.swift | 59 ++++++++++++++++++++++ Shared/ViewModels/SettingsViewModel.swift | 1 + 7 files changed, 103 insertions(+), 6 deletions(-) rename Shared/{Typings => Objects}/Typings.swift (100%) create mode 100644 Shared/Objects/VideoPlayerJumpLength.swift diff --git a/JellyfinPlayer.xcodeproj/project.pbxproj b/JellyfinPlayer.xcodeproj/project.pbxproj index 62c35ceb..1bb21ac6 100644 --- a/JellyfinPlayer.xcodeproj/project.pbxproj +++ b/JellyfinPlayer.xcodeproj/project.pbxproj @@ -200,6 +200,8 @@ E131691726C583BC0074BFEE /* LogConstructor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E131691626C583BC0074BFEE /* LogConstructor.swift */; }; E131691826C583BC0074BFEE /* LogConstructor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E131691626C583BC0074BFEE /* LogConstructor.swift */; }; E131691926C583BC0074BFEE /* LogConstructor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E131691626C583BC0074BFEE /* LogConstructor.swift */; }; + E1F0204E26CCCA74001C1C3B /* VideoPlayerJumpLength.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1F0204D26CCCA74001C1C3B /* VideoPlayerJumpLength.swift */; }; + E1F0204F26CCCA74001C1C3B /* VideoPlayerJumpLength.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1F0204D26CCCA74001C1C3B /* VideoPlayerJumpLength.swift */; }; E1FCD08826C35A0D007C8DCF /* NetworkError.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1FCD08726C35A0D007C8DCF /* NetworkError.swift */; }; E1FCD08926C35A0D007C8DCF /* NetworkError.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1FCD08726C35A0D007C8DCF /* NetworkError.swift */; }; E1FCD09626C47118007C8DCF /* ErrorMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1FCD09526C47118007C8DCF /* ErrorMessage.swift */; }; @@ -386,6 +388,7 @@ DE5004F745B19E28744A7DE7 /* Pods-JellyfinPlayer tvOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-JellyfinPlayer tvOS.debug.xcconfig"; path = "Target Support Files/Pods-JellyfinPlayer tvOS/Pods-JellyfinPlayer tvOS.debug.xcconfig"; sourceTree = ""; }; E100720626BDABC100CE3E31 /* MediaPlayButtonRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPlayButtonRowView.swift; sourceTree = ""; }; E131691626C583BC0074BFEE /* LogConstructor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogConstructor.swift; sourceTree = ""; }; + E1F0204D26CCCA74001C1C3B /* VideoPlayerJumpLength.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlayerJumpLength.swift; sourceTree = ""; }; E1FCD08726C35A0D007C8DCF /* NetworkError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkError.swift; sourceTree = ""; }; E1FCD09526C47118007C8DCF /* ErrorMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorMessage.swift; sourceTree = ""; }; EBFE1F64394BCC2EFFF1610D /* Pods_JellyfinPlayer_tvOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_JellyfinPlayer_tvOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -575,17 +578,18 @@ 532175392671BCED005491E6 /* ViewModels */, 621338912660106C00A81A2A /* Extensions */, AE8C3157265D6F5E008AA076 /* Resources */, - 535870AB2669D8D300D05A09 /* Typings */, + 535870AB2669D8D300D05A09 /* Objects */, ); path = Shared; sourceTree = ""; }; - 535870AB2669D8D300D05A09 /* Typings */ = { + 535870AB2669D8D300D05A09 /* Objects */ = { isa = PBXGroup; children = ( 535870AC2669D8DD00D05A09 /* Typings.swift */, + E1F0204D26CCCA74001C1C3B /* VideoPlayerJumpLength.swift */, ); - path = Typings; + path = Objects; sourceTree = ""; }; 536D3D77267BB9650004248C /* Components */ = { @@ -1137,6 +1141,7 @@ 09389CC526814E4500AE350E /* DeviceProfileBuilder.swift in Sources */, 535870A62669D8AE00D05A09 /* LazyView.swift in Sources */, 5321753E2671DE9C005491E6 /* Typings.swift in Sources */, + E1F0204F26CCCA74001C1C3B /* VideoPlayerJumpLength.swift in Sources */, 53ABFDEB2679753200886593 /* ConnectToServerView.swift in Sources */, 536D3D76267BA9BB0004248C /* MainTabViewModel.swift in Sources */, 5310695C2684E7EE00CFFDBA /* VideoPlayerViewController.swift in Sources */, @@ -1191,6 +1196,7 @@ 091B5A8B2683142E00D78B61 /* UDPBroadCastConnection.swift in Sources */, 6267B3D626710B8900A7371D /* CollectionExtensions.swift in Sources */, 53A089D0264DA9DA00D57806 /* MovieItemView.swift in Sources */, + E1F0204E26CCCA74001C1C3B /* VideoPlayerJumpLength.swift in Sources */, 53649AB1269CFB1900A2D8B7 /* LogManager.swift in Sources */, 62E632E9267D3FF50063E547 /* SeasonItemViewModel.swift in Sources */, 625CB56A2678B71200530A6E /* SplashViewModel.swift in Sources */, diff --git a/JellyfinPlayer/SettingsView.swift b/JellyfinPlayer/SettingsView.swift index a3ac4103..5637d055 100644 --- a/JellyfinPlayer/SettingsView.swift +++ b/JellyfinPlayer/SettingsView.swift @@ -21,6 +21,8 @@ struct SettingsView: View { @Default(.autoSelectSubtitlesLangCode) var autoSelectSubtitlesLangcode @Default(.autoSelectAudioLangCode) var autoSelectAudioLangcode @Default(.appAppearance) var appAppearance + @Default(.videoPlayerJumpForward) var jumpForwardLength + @Default(.videoPlayerJumpBackward) var jumpBackwardLength @State private var username: String = "" func onAppear() { @@ -42,6 +44,18 @@ struct SettingsView: View { Text(bitrate.name).tag(bitrate.value) } } + + Picker("Jump Forward Length", selection: $jumpForwardLength) { + ForEach(self.viewModel.videoPlayerJumpLengths, id: \.self) { length in + Text(length.label).tag(length.rawValue) + } + } + + Picker("Jump Backward Length", selection: $jumpBackwardLength) { + ForEach(self.viewModel.videoPlayerJumpLengths, id: \.self) { length in + Text(length.label).tag(length.rawValue) + } + } } Section(header: Text("Accessibility")) { diff --git a/JellyfinPlayer/VideoPlayer.swift b/JellyfinPlayer/VideoPlayer.swift index fd51b0f8..fba23e70 100644 --- a/JellyfinPlayer/VideoPlayer.swift +++ b/JellyfinPlayer/VideoPlayer.swift @@ -76,7 +76,15 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe var subtitleTrackArray: [Subtitle] = [] var audioTrackArray: [AudioTrack] = [] let playbackSpeeds: [Float] = [0.25, 0.5, 0.75, 1.0, 1.25, 1.5, 1.75, 2.0] - + var jumpForwardLength: VideoPlayerJumpLength { + let storedJumpForwardLength = Defaults[.videoPlayerJumpForward] + return VideoPlayerJumpLength(rawValue: storedJumpForwardLength)! + } + var jumpBackwardLength: VideoPlayerJumpLength { + let storedJumpBackwardLength = Defaults[.videoPlayerJumpBackward] + return VideoPlayerJumpLength(rawValue: storedJumpBackwardLength)! + } + var manifest: BaseItemDto = BaseItemDto() var playbackItem = PlaybackItem() var remoteTimeUpdateTimer: Timer? @@ -161,7 +169,7 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe @IBAction func jumpBackTapped(_ sender: Any) { if paused == false { if playerDestination == .local { - mediaPlayer.jumpBackward(15) + mediaPlayer.jumpBackward(jumpBackwardLength.rawValue) } else { self.sendJellyfinCommand(command: "Seek", options: ["position": (remotePositionTicks/10_000_000)-15]) } @@ -171,7 +179,7 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe @IBAction func jumpForwardTapped(_ sender: Any) { if paused == false { if playerDestination == .local { - mediaPlayer.jumpForward(30) + mediaPlayer.jumpForward(jumpForwardLength.rawValue) } else { self.sendJellyfinCommand(command: "Seek", options: ["position": (remotePositionTicks/10_000_000)+30]) } @@ -475,6 +483,7 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe mediaPlayer.drawable = videoContentView setupMediaPlayer() + setupJumpLengthButtons() } func setupMediaPlayer() { @@ -607,6 +616,12 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe .store(in: &cancellables) } } + + private func setupJumpLengthButtons() { + let buttonFont = UIFont.systemFont(ofSize: 35, weight: .regular) + jumpForwardButton.setImage(jumpForwardLength.generateForwardImage(with: buttonFont), for: .normal) + jumpBackButton.setImage(jumpBackwardLength.generateBackwardImage(with: buttonFont), for: .normal) + } func setupTracksForPreferredDefaults() { subtitleTrackArray.forEach { subtitle in diff --git a/Shared/Extensions/DefaultsExtension.swift b/Shared/Extensions/DefaultsExtension.swift index c19c3b50..246775d8 100644 --- a/Shared/Extensions/DefaultsExtension.swift +++ b/Shared/Extensions/DefaultsExtension.swift @@ -17,4 +17,6 @@ extension Defaults.Keys { static let autoSelectSubtitlesLangCode = Key("AutoSelectSubtitlesLangCode", default: "Auto") static let autoSelectAudioLangCode = Key("AutoSelectAudioLangCode", default: "Auto") static let appAppearance = Key("appAppearance", default: AppAppearance.system.rawValue) + static let videoPlayerJumpForward = Key("videoPlayerJumpForward", default: VideoPlayerJumpLength.thirty.rawValue) + static let videoPlayerJumpBackward = Key("videoPlayerJumpBackward", default: VideoPlayerJumpLength.thirty.rawValue) } diff --git a/Shared/Typings/Typings.swift b/Shared/Objects/Typings.swift similarity index 100% rename from Shared/Typings/Typings.swift rename to Shared/Objects/Typings.swift diff --git a/Shared/Objects/VideoPlayerJumpLength.swift b/Shared/Objects/VideoPlayerJumpLength.swift new file mode 100644 index 00000000..394ec551 --- /dev/null +++ b/Shared/Objects/VideoPlayerJumpLength.swift @@ -0,0 +1,59 @@ +// + /* + * 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 + */ + +import UIKit + +enum VideoPlayerJumpLength: Int32, CaseIterable { + case thirty = 30 + case fifteen = 15 + case ten = 10 + + // TODO - Uncomment once iOS 15 released +// case five = 5 + + var label: String { + return "\(self.rawValue) seconds" + } + + func generateForwardImage(with font: UIFont) -> UIImage { + let config = UIImage.SymbolConfiguration(font: font) + let systemName: String + + switch self { + case .thirty: + systemName = "goforward.30" + case .fifteen: + systemName = "goforward.15" + case .ten: + systemName = "goforward.10" +// case .five: +// systemName = "goforward.5" + } + + return UIImage(systemName: systemName, withConfiguration: config)! + } + + func generateBackwardImage(with font: UIFont) -> UIImage { + let config = UIImage.SymbolConfiguration(font: font) + let systemName: String + + switch self { + case .thirty: + systemName = "gobackward.30" + case .fifteen: + systemName = "gobackward.15" + case .ten: + systemName = "gobackward.10" +// case .five: +// systemName = "gobackward.5" + } + + return UIImage(systemName: systemName, withConfiguration: config)! + } +} diff --git a/Shared/ViewModels/SettingsViewModel.swift b/Shared/ViewModels/SettingsViewModel.swift index a2f70d41..bdb0755d 100644 --- a/Shared/ViewModels/SettingsViewModel.swift +++ b/Shared/ViewModels/SettingsViewModel.swift @@ -57,6 +57,7 @@ final class SettingsViewModel: ObservableObject { var bitrates: [Bitrates] = [] var langs = [TrackLanguage]() let appearances = AppAppearance.allCases + let videoPlayerJumpLengths = VideoPlayerJumpLength.allCases init() { let url = Bundle.main.url(forResource: "bitrates", withExtension: "json")!