From 6a7c4c0a5b62e44749034739dd4387acd134495a Mon Sep 17 00:00:00 2001 From: Ethan Pippin Date: Wed, 5 Jan 2022 13:38:15 -0700 Subject: [PATCH] resume offset and live tv moved to experimental --- .../Components/MediaPlayButtonRowView.swift | 2 +- .../Views/LibraryListView.swift | 70 +++++++++++++------ .../ExperimentalSettingsView.swift | 3 + .../Views/SettingsView/SettingsView.swift | 3 + .../VideoPlayer/VLCPlayerViewController.swift | 10 ++- JellyfinPlayer.xcodeproj/project.pbxproj | 8 +++ .../Views/SettingsView/SettingsView.swift | 3 + .../VideoPlayer/VLCPlayerViewController.swift | 11 ++- Shared/Extensions/DoubleExtensions.swift | 23 ++++++ .../SwiftfinStore/SwiftfinStoreDefaults.swift | 2 + Shared/ViewModels/VideoPlayerViewModel.swift | 3 + 11 files changed, 113 insertions(+), 25 deletions(-) create mode 100644 Shared/Extensions/DoubleExtensions.swift diff --git a/JellyfinPlayer tvOS/Components/MediaPlayButtonRowView.swift b/JellyfinPlayer tvOS/Components/MediaPlayButtonRowView.swift index 23c4e862..9e4e67e2 100644 --- a/JellyfinPlayer tvOS/Components/MediaPlayButtonRowView.swift +++ b/JellyfinPlayer tvOS/Components/MediaPlayButtonRowView.swift @@ -23,7 +23,7 @@ struct MediaPlayButtonRowView: View { MediaViewActionButton(icon: "play.fill", scrollView: $wrappedScrollView) } - Text(viewModel.item.getItemProgressString() != "" ? "\(viewModel.item.getItemProgressString()) left" : L10n.play) + Text(viewModel.item.getItemProgressString() != "" ? "\(viewModel.item.getItemProgressString() ?? "") left" : L10n.play) .font(.caption) } VStack { diff --git a/JellyfinPlayer tvOS/Views/LibraryListView.swift b/JellyfinPlayer tvOS/Views/LibraryListView.swift index 57e9efae..3332bbe5 100644 --- a/JellyfinPlayer tvOS/Views/LibraryListView.swift +++ b/JellyfinPlayer tvOS/Views/LibraryListView.swift @@ -7,6 +7,7 @@ * Copyright 2021 Aiden Vigue & Jellyfin Contributors */ +import Defaults import Foundation import SwiftUI @@ -14,6 +15,8 @@ struct LibraryListView: View { @EnvironmentObject var mainCoordinator: MainCoordinator.Router @EnvironmentObject var libraryListRouter: LibraryListCoordinator.Router @StateObject var viewModel = LibraryListViewModel() + + @Default(.Experimental.liveTVAlphaEnabled) var liveTVAlphaEnabled var body: some View { ScrollView { @@ -23,32 +26,55 @@ struct LibraryListView: View { if library.collectionType ?? "" == "movies" || library.collectionType ?? "" == "tvshows" || library.collectionType ?? "" == "music" { EmptyView() } else { - Button() { - if library.collectionType == "livetv" { - self.mainCoordinator.root(\.liveTV) - } else { + if library.collectionType == "livetv" { + if liveTVAlphaEnabled { + Button() { + self.mainCoordinator.root(\.liveTV) + } + label: { + ZStack { + HStack { + Spacer() + VStack { + Text(library.name ?? "") + .foregroundColor(.white) + .font(.title2) + .fontWeight(.semibold) + } + Spacer() + }.padding(32) + } + .frame(minWidth: 100, maxWidth: .infinity) + .frame(height: 100) + } + .cornerRadius(10) + .shadow(radius: 5) + .padding(.bottom, 5) + } + } else { + Button() { self.libraryListRouter.route(to: \.library, (viewModel: LibraryViewModel(), title: library.name ?? "")) } - } - label: { - ZStack { - HStack { - Spacer() - VStack { - Text(library.name ?? "") - .foregroundColor(.white) - .font(.title2) - .fontWeight(.semibold) - } - Spacer() - }.padding(32) + label: { + ZStack { + HStack { + Spacer() + VStack { + Text(library.name ?? "") + .foregroundColor(.white) + .font(.title2) + .fontWeight(.semibold) + } + Spacer() + }.padding(32) + } + .frame(minWidth: 100, maxWidth: .infinity) + .frame(height: 100) } - .frame(minWidth: 100, maxWidth: .infinity) - .frame(height: 100) + .cornerRadius(10) + .shadow(radius: 5) + .padding(.bottom, 5) } - .cornerRadius(10) - .shadow(radius: 5) - .padding(.bottom, 5) } } } else { diff --git a/JellyfinPlayer tvOS/Views/SettingsView/ExperimentalSettingsView.swift b/JellyfinPlayer tvOS/Views/SettingsView/ExperimentalSettingsView.swift index 179b1889..b79b53c0 100644 --- a/JellyfinPlayer tvOS/Views/SettingsView/ExperimentalSettingsView.swift +++ b/JellyfinPlayer tvOS/Views/SettingsView/ExperimentalSettingsView.swift @@ -13,6 +13,7 @@ import SwiftUI struct ExperimentalSettingsView: View { @Default(.Experimental.syncSubtitleStateWithAdjacent) var syncSubtitleStateWithAdjacent + @Default(.Experimental.liveTVAlphaEnabled) var liveTVAlphaEnabled var body: some View { Form { @@ -20,6 +21,8 @@ struct ExperimentalSettingsView: View { Toggle("Sync Subtitles with Adjacent Episodes", isOn: $syncSubtitleStateWithAdjacent) + Toggle("Live TV (Alpha)", isOn: $liveTVAlphaEnabled) + } header: { Text("Experimental") } diff --git a/JellyfinPlayer tvOS/Views/SettingsView/SettingsView.swift b/JellyfinPlayer tvOS/Views/SettingsView/SettingsView.swift index f3753a1e..fd6d3d3c 100644 --- a/JellyfinPlayer tvOS/Views/SettingsView/SettingsView.swift +++ b/JellyfinPlayer tvOS/Views/SettingsView/SettingsView.swift @@ -23,6 +23,7 @@ struct SettingsView: View { @Default(.tvOSEpisodeItemCinematicView) var tvOSEpisodeItemCinematicView @Default(.tvOSMovieItemCinematicView) var tvOSMovieItemCinematicView @Default(.showPosterLabels) var showPosterLabels + @Default(.resumeOffset) var resumeOffset var body: some View { GeometryReader { reader in @@ -80,6 +81,8 @@ struct SettingsView: View { } } + Toggle("Resume 5 Second Offset", isOn: $resumeOffset) + Toggle("Press Down for Menu", isOn: $downActionShowsMenu) Toggle("Confirm Close", isOn: $confirmClose) diff --git a/JellyfinPlayer tvOS/Views/VideoPlayer/VLCPlayerViewController.swift b/JellyfinPlayer tvOS/Views/VideoPlayer/VLCPlayerViewController.swift index 5a59e095..e335d271 100644 --- a/JellyfinPlayer tvOS/Views/VideoPlayer/VLCPlayerViewController.swift +++ b/JellyfinPlayer tvOS/Views/VideoPlayer/VLCPlayerViewController.swift @@ -418,7 +418,15 @@ extension VLCPlayerViewController { let startPercentage = newViewModel.item.userData?.playedPercentage ?? 0 if startPercentage > 0 { - newViewModel.sliderPercentage = startPercentage / 100 + 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 diff --git a/JellyfinPlayer.xcodeproj/project.pbxproj b/JellyfinPlayer.xcodeproj/project.pbxproj index 4c54ff53..39af1ab1 100644 --- a/JellyfinPlayer.xcodeproj/project.pbxproj +++ b/JellyfinPlayer.xcodeproj/project.pbxproj @@ -395,6 +395,9 @@ E1D4BF8C2719F39F00A11E64 /* AppAppearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1D4BF802719D22800A11E64 /* AppAppearance.swift */; }; E1D4BF8D2719F3A300A11E64 /* VideoPlayerJumpLength.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1F0204D26CCCA74001C1C3B /* VideoPlayerJumpLength.swift */; }; E1D4BF8F271A079A00A11E64 /* BasicAppSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1D4BF8E271A079A00A11E64 /* BasicAppSettingsView.swift */; }; + E1E00A35278628A40022235B /* DoubleExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E00A34278628A40022235B /* DoubleExtensions.swift */; }; + E1E00A36278628A40022235B /* DoubleExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E00A34278628A40022235B /* DoubleExtensions.swift */; }; + E1E00A37278628A40022235B /* DoubleExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E00A34278628A40022235B /* DoubleExtensions.swift */; }; E1E48CC9271E6D410021A2F9 /* RefreshHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E48CC8271E6D410021A2F9 /* RefreshHelper.swift */; }; E1E5D5372783A52C00692DFE /* CinematicEpisodeItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E5D5362783A52C00692DFE /* CinematicEpisodeItemView.swift */; }; E1E5D5392783A56B00692DFE /* EpisodesRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E5D5382783A56B00692DFE /* EpisodesRowView.swift */; }; @@ -695,6 +698,7 @@ E1D4BF862719D27100A11E64 /* Bitrates.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bitrates.swift; sourceTree = ""; }; E1D4BF892719D3D000A11E64 /* BasicAppSettingsCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BasicAppSettingsCoordinator.swift; sourceTree = ""; }; E1D4BF8E271A079A00A11E64 /* BasicAppSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BasicAppSettingsView.swift; sourceTree = ""; }; + E1E00A34278628A40022235B /* DoubleExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DoubleExtensions.swift; sourceTree = ""; }; E1E48CC8271E6D410021A2F9 /* RefreshHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RefreshHelper.swift; sourceTree = ""; }; E1E5D5362783A52C00692DFE /* CinematicEpisodeItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CinematicEpisodeItemView.swift; sourceTree = ""; }; E1E5D5382783A56B00692DFE /* EpisodesRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EpisodesRowView.swift; sourceTree = ""; }; @@ -1179,6 +1183,7 @@ E10EAA4E277BBCC4000269ED /* CGSizeExtensions.swift */, 6267B3D526710B8900A7371D /* CollectionExtensions.swift */, E173DA5126D04AAF00CC4EB7 /* ColorExtension.swift */, + E1E00A34278628A40022235B /* DoubleExtensions.swift */, 6267B3D92671138200A7371D /* ImageExtensions.swift */, E1AD105226D96D5F003E4A08 /* JellyfinAPIExtensions */, 621338922660107500A81A2A /* StringExtensions.swift */, @@ -2060,6 +2065,7 @@ 53272537268C1DBB0035FBF1 /* SeasonItemView.swift in Sources */, 09389CC526814E4500AE350E /* DeviceProfileBuilder.swift in Sources */, E1C812D2277AE50A00918266 /* URLComponentsExtensions.swift in Sources */, + E1E00A36278628A40022235B /* DoubleExtensions.swift in Sources */, E1FA2F7427818A8800B4C270 /* SmallMenuOverlay.swift in Sources */, E193D53C27193F9500900D82 /* UserListCoordinator.swift in Sources */, E13DD3C927164B1E009D4DAF /* UIDeviceExtensions.swift in Sources */, @@ -2231,6 +2237,7 @@ 6220D0BA26D6092100B8E046 /* FilterCoordinator.swift in Sources */, E1E5D54C2783E27200692DFE /* ExperimentalSettingsView.swift in Sources */, 6267B3DA2671138200A7371D /* ImageExtensions.swift in Sources */, + E1E00A35278628A40022235B /* DoubleExtensions.swift in Sources */, 62EC353426766B03000E9F2D /* DeviceRotationViewModifier.swift in Sources */, 5389277C263CC3DB0035E14B /* BlurHashDecode.swift in Sources */, E1D4BF8A2719D3D000A11E64 /* BasicAppSettingsCoordinator.swift in Sources */, @@ -2259,6 +2266,7 @@ E13DD3CB27164BA8009D4DAF /* UIDeviceExtensions.swift in Sources */, 6264E88A27384A6F0081A12A /* NetworkError.swift in Sources */, E19169D0272514760085832A /* HTTPScheme.swift in Sources */, + E1E00A37278628A40022235B /* DoubleExtensions.swift in Sources */, 6267B3D726710B9700A7371D /* CollectionExtensions.swift in Sources */, 628B953C2670D2430091AF3B /* StringExtensions.swift in Sources */, 6267B3DB2671139400A7371D /* ImageExtensions.swift in Sources */, diff --git a/JellyfinPlayer/Views/SettingsView/SettingsView.swift b/JellyfinPlayer/Views/SettingsView/SettingsView.swift index ab98e490..81ac1b09 100644 --- a/JellyfinPlayer/Views/SettingsView/SettingsView.swift +++ b/JellyfinPlayer/Views/SettingsView/SettingsView.swift @@ -27,6 +27,7 @@ struct SettingsView: View { @Default(.jumpGesturesEnabled) var jumpGesturesEnabled @Default(.showPosterLabels) var showPosterLabels @Default(.showCastAndCrew) var showCastAndCrew + @Default(.resumeOffset) var resumeOffset var body: some View { Form { @@ -91,6 +92,8 @@ struct SettingsView: View { Toggle("Jump Gestures Enabled", isOn: $jumpGesturesEnabled) + Toggle("Resume 5 Second Offset", isOn: $resumeOffset) + Button { settingsRouter.route(to: \.overlaySettings) } label: { diff --git a/JellyfinPlayer/Views/VideoPlayer/VLCPlayerViewController.swift b/JellyfinPlayer/Views/VideoPlayer/VLCPlayerViewController.swift index 325ada14..7f1c6216 100644 --- a/JellyfinPlayer/Views/VideoPlayer/VLCPlayerViewController.swift +++ b/JellyfinPlayer/Views/VideoPlayer/VLCPlayerViewController.swift @@ -316,7 +316,15 @@ extension VLCPlayerViewController { let startPercentage = newViewModel.item.userData?.playedPercentage ?? 0 if startPercentage > 0 { - newViewModel.sliderPercentage = startPercentage / 100 + 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 @@ -522,6 +530,7 @@ extension VLCPlayerViewController: VLCMediaPlayerDelegate { didSelectSubtitleStream(index: viewModel.selectedSubtitleStreamIndex) } + // If needing to fix audio stream during playback if vlcMediaPlayer.currentAudioTrackIndex != viewModel.selectedAudioStreamIndex { didSelectAudioStream(index: viewModel.selectedAudioStreamIndex) } diff --git a/Shared/Extensions/DoubleExtensions.swift b/Shared/Extensions/DoubleExtensions.swift new file mode 100644 index 00000000..4d1dc42b --- /dev/null +++ b/Shared/Extensions/DoubleExtensions.swift @@ -0,0 +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 + */ + +import Foundation + +extension Double { + + func subtract(_ other: Double, floor: Double) -> Double { + var v = self - other + + if v < floor { + v += abs(floor - v) + } + + return v + } +} diff --git a/Shared/SwiftfinStore/SwiftfinStoreDefaults.swift b/Shared/SwiftfinStore/SwiftfinStoreDefaults.swift index 5805882b..d74fd68c 100644 --- a/Shared/SwiftfinStore/SwiftfinStoreDefaults.swift +++ b/Shared/SwiftfinStore/SwiftfinStoreDefaults.swift @@ -48,6 +48,7 @@ extension Defaults.Keys { 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) // Should show video player items static let shouldShowPlayPreviousItem = Key("shouldShowPreviousItem", default: true, suite: SwiftfinStore.Defaults.generalSuite) @@ -60,6 +61,7 @@ extension Defaults.Keys { // 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) } // tvos specific diff --git a/Shared/ViewModels/VideoPlayerViewModel.swift b/Shared/ViewModels/VideoPlayerViewModel.swift index 1793ad39..ee3d4cba 100644 --- a/Shared/ViewModels/VideoPlayerViewModel.swift +++ b/Shared/ViewModels/VideoPlayerViewModel.swift @@ -88,6 +88,7 @@ final class VideoPlayerViewModel: ViewModel { let subtitleStreams: [MediaStream] let overlayType: OverlayType let jumpGesturesEnabled: Bool + let resumeOffset: Bool // MARK: Experimental let syncSubtitleStateWithAdjacent: Bool @@ -162,6 +163,8 @@ final class VideoPlayerViewModel: ViewModel { self.jumpGesturesEnabled = Defaults[.jumpGesturesEnabled] self.shouldShowJumpButtonsInOverlayMenu = Defaults[.shouldShowJumpButtonsInOverlayMenu] + self.resumeOffset = Defaults[.resumeOffset] + self.syncSubtitleStateWithAdjacent = Defaults[.Experimental.syncSubtitleStateWithAdjacent] self.confirmClose = Defaults[.confirmClose]