From 158edb3b5f4c43a8bb3b35bee7ffe4fa57ff662b Mon Sep 17 00:00:00 2001 From: Ethan Pippin Date: Fri, 7 Jan 2022 15:37:19 -0700 Subject: [PATCH 01/13] support external subtitles --- .../MediaStreamExtension.swift | 22 ++++++++++ Shared/ViewModels/VideoPlayerViewModel.swift | 12 ++++++ .../VideoPlayer/VLCPlayerViewController.swift | 43 ++++++++++++++----- Swiftfin.xcodeproj/project.pbxproj | 26 ++++++----- .../VideoPlayer/VLCPlayerViewController.swift | 38 ++++++++++------ 5 files changed, 109 insertions(+), 32 deletions(-) create mode 100644 Shared/Extensions/JellyfinAPIExtensions/MediaStreamExtension.swift diff --git a/Shared/Extensions/JellyfinAPIExtensions/MediaStreamExtension.swift b/Shared/Extensions/JellyfinAPIExtensions/MediaStreamExtension.swift new file mode 100644 index 00000000..cb0084bf --- /dev/null +++ b/Shared/Extensions/JellyfinAPIExtensions/MediaStreamExtension.swift @@ -0,0 +1,22 @@ +// + /* + * 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 +import JellyfinAPI + +extension MediaStream { + + func externalURL(base: String) -> URL? { + guard let deliveryURL = deliveryUrl else { return nil } + var baseComponents = URLComponents(string: base) + baseComponents?.path += deliveryURL + + return baseComponents?.url + } +} diff --git a/Shared/ViewModels/VideoPlayerViewModel.swift b/Shared/ViewModels/VideoPlayerViewModel.swift index ee3d4cba..aaa3311e 100644 --- a/Shared/ViewModels/VideoPlayerViewModel.swift +++ b/Shared/ViewModels/VideoPlayerViewModel.swift @@ -67,6 +67,8 @@ final class VideoPlayerViewModel: ViewModel { } @Published var autoplayEnabled: Bool { willSet { + previousItemVideoPlayerViewModel?.autoplayEnabled = newValue + nextItemVideoPlayerViewModel?.autoplayEnabled = newValue Defaults[.autoplayEnabled] = newValue } } @@ -115,6 +117,16 @@ final class VideoPlayerViewModel: ViewModel { return Int64(currentSeconds) * 10_000_000 } + // MARK: Helpers + + var currentAudioStream: MediaStream? { + return audioStreams.first(where: { $0.index == selectedAudioStreamIndex }) + } + + var currentSubtitleStream: MediaStream? { + return subtitleStreams.first(where: { $0.index == selectedSubtitleStreamIndex }) + } + // Necessary PassthroughSubject to capture manual scrubbing from sliders let sliderScrubbingSubject = PassthroughSubject() diff --git a/Swiftfin tvOS/Views/VideoPlayer/VLCPlayerViewController.swift b/Swiftfin tvOS/Views/VideoPlayer/VLCPlayerViewController.swift index e335d271..ea106ca7 100644 --- a/Swiftfin tvOS/Views/VideoPlayer/VLCPlayerViewController.swift +++ b/Swiftfin tvOS/Views/VideoPlayer/VLCPlayerViewController.swift @@ -24,7 +24,7 @@ class VLCPlayerViewController: UIViewController { // MARK: variables private var viewModel: VideoPlayerViewModel - private var vlcMediaPlayer = VLCMediaPlayer() + private var vlcMediaPlayer: VLCMediaPlayer private var lastPlayerTicks: Int64 = 0 private var lastProgressReportTicks: Int64 = 0 private var viewModelListeners = Set() @@ -59,6 +59,7 @@ class VLCPlayerViewController: UIViewController { init(viewModel: VideoPlayerViewModel) { self.viewModel = viewModel + self.vlcMediaPlayer = VLCMediaPlayer() super.init(nibName: nil, bundle: nil) @@ -118,14 +119,6 @@ class VLCPlayerViewController: UIViewController { view.backgroundColor = .black - // Outside of 'setupMediaPlayer' such that they - // aren't unnecessarily set more than once - vlcMediaPlayer.delegate = self - vlcMediaPlayer.drawable = videoContentView - - // TODO: custom font sizes - vlcMediaPlayer.perform(Selector(("setTextRendererFontSize:")), with: 16) - setupMediaPlayer(newViewModel: viewModel) setupPanGestureRecognizer() @@ -387,6 +380,28 @@ extension VLCPlayerViewController { /// Use case for this is setting new media within the same VLCPlayerViewController func setupMediaPlayer(newViewModel: VideoPlayerViewModel) { + // remove old player + + if vlcMediaPlayer.media != nil { + viewModelListeners.forEach({ $0.cancel() }) + + vlcMediaPlayer.stop() + viewModel.sendStopReport() + viewModel.playerOverlayDelegate = nil + } + + vlcMediaPlayer = VLCMediaPlayer() + + // setup with new player and view model + + vlcMediaPlayer = VLCMediaPlayer() + + vlcMediaPlayer.delegate = self + vlcMediaPlayer.drawable = videoContentView + + // TODO: Custom subtitle sizes + vlcMediaPlayer.perform(Selector(("setTextRendererFontSize:")), with: 16) + stopOverlayDismissTimer() // Stop current media if there is one @@ -436,6 +451,13 @@ extension VLCPlayerViewController { func startPlayback() { vlcMediaPlayer.play() + // 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) + } + } + setMediaPlayerTimeAtCurrentSlider() viewModel.sendPlayReport() @@ -672,7 +694,8 @@ extension VLCPlayerViewController: VLCMediaPlayerDelegate { } // If needing to fix subtitle streams during playback - if vlcMediaPlayer.currentVideoSubTitleIndex != viewModel.selectedSubtitleStreamIndex && viewModel.subtitlesEnabled { + if vlcMediaPlayer.currentVideoSubTitleIndex != viewModel.selectedSubtitleStreamIndex && + viewModel.subtitlesEnabled { didSelectSubtitleStream(index: viewModel.selectedSubtitleStreamIndex) } diff --git a/Swiftfin.xcodeproj/project.pbxproj b/Swiftfin.xcodeproj/project.pbxproj index 2413e1ea..70f53719 100644 --- a/Swiftfin.xcodeproj/project.pbxproj +++ b/Swiftfin.xcodeproj/project.pbxproj @@ -274,6 +274,8 @@ E1218C9C271A26C400EA0737 /* Nuke in Frameworks */ = {isa = PBXBuildFile; productRef = E1218C9B271A26C400EA0737 /* Nuke */; }; E1218C9E271A2CD600EA0737 /* CombineExt in Frameworks */ = {isa = PBXBuildFile; productRef = E1218C9D271A2CD600EA0737 /* CombineExt */; }; E1218CA0271A2CF200EA0737 /* Nuke in Frameworks */ = {isa = PBXBuildFile; productRef = E1218C9F271A2CF200EA0737 /* Nuke */; }; + E122A9132788EAAD0060FA63 /* MediaStreamExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = E122A9122788EAAD0060FA63 /* MediaStreamExtension.swift */; }; + E122A9142788EAAD0060FA63 /* MediaStreamExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = E122A9122788EAAD0060FA63 /* MediaStreamExtension.swift */; }; E1267D3E271A1F46003C492E /* PreferenceUIHostingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1267D3D271A1F46003C492E /* PreferenceUIHostingController.swift */; }; E131691726C583BC0074BFEE /* LogConstructor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E131691626C583BC0074BFEE /* LogConstructor.swift */; }; E131691826C583BC0074BFEE /* LogConstructor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E131691626C583BC0074BFEE /* LogConstructor.swift */; }; @@ -645,6 +647,7 @@ E10EAA52277BBD17000269ED /* BaseItemDto+VideoPlayerViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BaseItemDto+VideoPlayerViewModel.swift"; sourceTree = ""; }; E11B1B6B2718CD68006DA3E8 /* JellyfinAPIError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JellyfinAPIError.swift; sourceTree = ""; }; E11D224127378428003F9CB3 /* ServerDetailCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerDetailCoordinator.swift; sourceTree = ""; }; + E122A9122788EAAD0060FA63 /* MediaStreamExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaStreamExtension.swift; sourceTree = ""; }; E1267D3D271A1F46003C492E /* PreferenceUIHostingController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferenceUIHostingController.swift; sourceTree = ""; }; E131691626C583BC0074BFEE /* LogConstructor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogConstructor.swift; sourceTree = ""; }; E1384943278036C70024FB48 /* VLCPlayerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VLCPlayerViewController.swift; sourceTree = ""; }; @@ -1502,10 +1505,11 @@ isa = PBXGroup; children = ( E18845F426DD631E00B0C5B7 /* BaseItemDto+Stackable.swift */, - E1AD104C26D96CE3003E4A08 /* BaseItemDtoExtensions.swift */, E10EAA52277BBD17000269ED /* BaseItemDto+VideoPlayerViewModel.swift */, + E1AD104C26D96CE3003E4A08 /* BaseItemDtoExtensions.swift */, 5364F454266CA0DC0026ECBA /* BaseItemPersonExtensions.swift */, E11B1B6B2718CD68006DA3E8 /* JellyfinAPIError.swift */, + E122A9122788EAAD0060FA63 /* MediaStreamExtension.swift */, E1AD105E26D9ADDD003E4A08 /* NameGUIDPairExtensions.swift */, ); path = JellyfinAPIExtensions; @@ -1759,7 +1763,7 @@ 536D3D82267BEA550004248C /* XCRemoteSwiftPackageReference "ParallaxView" */, 53649AAB269CFAEA00A2D8B7 /* XCRemoteSwiftPackageReference "Puppy" */, 62C29E9A26D0FE4100C1D2E7 /* XCRemoteSwiftPackageReference "stinsen" */, - E13DD3C42716499E009D4DAF /* XCRemoteSwiftPackageReference "CoreStore.git" */, + E13DD3C42716499E009D4DAF /* XCRemoteSwiftPackageReference "CoreStore" */, E13DD3D127168E65009D4DAF /* XCRemoteSwiftPackageReference "Defaults" */, E1267D42271A212C003C492E /* XCRemoteSwiftPackageReference "CombineExt" */, E1C16B89271A2180009A5D25 /* XCRemoteSwiftPackageReference "SwiftyJSON" */, @@ -2051,6 +2055,7 @@ E1C812CC277AE40A00918266 /* VideoPlayerView.swift in Sources */, 53CD2A40268A49C2002ABD4E /* ItemView.swift in Sources */, 53CD2A42268A4B38002ABD4E /* MovieItemView.swift in Sources */, + E122A9142788EAAD0060FA63 /* MediaStreamExtension.swift in Sources */, E178859E2780F53B0094FBCF /* SliderView.swift in Sources */, 536D3D7F267BDF100004248C /* LatestMediaView.swift in Sources */, E1384944278036C70024FB48 /* VLCPlayerViewController.swift in Sources */, @@ -2191,6 +2196,7 @@ E13DD3F227179378009D4DAF /* UserSignInCoordinator.swift in Sources */, 621338932660107500A81A2A /* StringExtensions.swift in Sources */, 53FF7F2A263CF3F500585C35 /* LatestMediaView.swift in Sources */, + E122A9132788EAAD0060FA63 /* MediaStreamExtension.swift in Sources */, E1C812C5277A90B200918266 /* URLComponentsExtensions.swift in Sources */, 62E632EC267D410B0063E547 /* SeriesItemViewModel.swift in Sources */, 625CB5732678C32A00530A6E /* HomeViewModel.swift in Sources */, @@ -2701,7 +2707,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 66; DEVELOPMENT_ASSET_PATHS = ""; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = TY84JMYEFE; ENABLE_BITCODE = NO; ENABLE_PREVIEWS = YES; EXCLUDED_ARCHS = ""; @@ -2738,7 +2744,7 @@ CURRENT_PROJECT_VERSION = 66; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = TY84JMYEFE; ENABLE_BITCODE = NO; ENABLE_PREVIEWS = YES; EXCLUDED_ARCHS = ""; @@ -2769,7 +2775,7 @@ ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 66; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = TY84JMYEFE; INFOPLIST_FILE = WidgetExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( @@ -2796,7 +2802,7 @@ ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 66; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = TY84JMYEFE; INFOPLIST_FILE = WidgetExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( @@ -2936,7 +2942,7 @@ minimumVersion = 1.0.0; }; }; - E13DD3C42716499E009D4DAF /* XCRemoteSwiftPackageReference "CoreStore.git" */ = { + E13DD3C42716499E009D4DAF /* XCRemoteSwiftPackageReference "CoreStore" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/JohnEstropia/CoreStore.git"; requirement = { @@ -3060,17 +3066,17 @@ }; E13DD3C52716499E009D4DAF /* CoreStore */ = { isa = XCSwiftPackageProductDependency; - package = E13DD3C42716499E009D4DAF /* XCRemoteSwiftPackageReference "CoreStore.git" */; + package = E13DD3C42716499E009D4DAF /* XCRemoteSwiftPackageReference "CoreStore" */; productName = CoreStore; }; E13DD3CC27164CA7009D4DAF /* CoreStore */ = { isa = XCSwiftPackageProductDependency; - package = E13DD3C42716499E009D4DAF /* XCRemoteSwiftPackageReference "CoreStore.git" */; + package = E13DD3C42716499E009D4DAF /* XCRemoteSwiftPackageReference "CoreStore" */; productName = CoreStore; }; E13DD3CE27164E1F009D4DAF /* CoreStore */ = { isa = XCSwiftPackageProductDependency; - package = E13DD3C42716499E009D4DAF /* XCRemoteSwiftPackageReference "CoreStore.git" */; + package = E13DD3C42716499E009D4DAF /* XCRemoteSwiftPackageReference "CoreStore" */; productName = CoreStore; }; E13DD3D227168E65009D4DAF /* Defaults */ = { diff --git a/Swiftfin/Views/VideoPlayer/VLCPlayerViewController.swift b/Swiftfin/Views/VideoPlayer/VLCPlayerViewController.swift index 7f1c6216..ce1ba728 100644 --- a/Swiftfin/Views/VideoPlayer/VLCPlayerViewController.swift +++ b/Swiftfin/Views/VideoPlayer/VLCPlayerViewController.swift @@ -24,7 +24,7 @@ class VLCPlayerViewController: UIViewController { // MARK: variables private var viewModel: VideoPlayerViewModel - private var vlcMediaPlayer = VLCMediaPlayer() + private var vlcMediaPlayer: VLCMediaPlayer private var lastPlayerTicks: Int64 = 0 private var lastProgressReportTicks: Int64 = 0 private var viewModelListeners = Set() @@ -49,6 +49,7 @@ class VLCPlayerViewController: UIViewController { init(viewModel: VideoPlayerViewModel) { self.viewModel = viewModel + self.vlcMediaPlayer = VLCMediaPlayer() super.init(nibName: nil, bundle: nil) @@ -97,14 +98,6 @@ class VLCPlayerViewController: UIViewController { view.backgroundColor = .black - // These are kept outside of 'setupMediaPlayer' such that - // they aren't unnecessarily set more than once - vlcMediaPlayer.delegate = self - vlcMediaPlayer.drawable = videoContentView - - // TODO: Custom subtitle sizes - vlcMediaPlayer.perform(Selector(("setTextRendererFontSize:")), with: 14) - setupMediaPlayer(newViewModel: viewModel) refreshJumpBackwardOverlayView(with: viewModel.jumpBackwardLength) @@ -287,9 +280,8 @@ extension VLCPlayerViewController { /// Use case for this is setting new media within the same VLCPlayerViewController func setupMediaPlayer(newViewModel: VideoPlayerViewModel) { - stopOverlayDismissTimer() + // remove old player - // Stop current media if there is one if vlcMediaPlayer.media != nil { viewModelListeners.forEach({ $0.cancel() }) @@ -298,6 +290,20 @@ extension VLCPlayerViewController { viewModel.playerOverlayDelegate = nil } + vlcMediaPlayer = VLCMediaPlayer() + + // setup with new player and view model + + vlcMediaPlayer = VLCMediaPlayer() + + vlcMediaPlayer.delegate = self + vlcMediaPlayer.drawable = videoContentView + + // TODO: Custom subtitle sizes + vlcMediaPlayer.perform(Selector(("setTextRendererFontSize:")), with: 14) + + stopOverlayDismissTimer() + lastPlayerTicks = newViewModel.item.userData?.playbackPositionTicks ?? 0 lastProgressReportTicks = newViewModel.item.userData?.playbackPositionTicks ?? 0 @@ -334,6 +340,13 @@ extension VLCPlayerViewController { func startPlayback() { vlcMediaPlayer.play() + // 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) + } + } + setMediaPlayerTimeAtCurrentSlider() viewModel.sendPlayReport() @@ -526,7 +539,8 @@ extension VLCPlayerViewController: VLCMediaPlayerDelegate { } // If needing to fix subtitle streams during playback - if vlcMediaPlayer.currentVideoSubTitleIndex != viewModel.selectedSubtitleStreamIndex && viewModel.subtitlesEnabled { + if vlcMediaPlayer.currentVideoSubTitleIndex != viewModel.selectedSubtitleStreamIndex && + viewModel.subtitlesEnabled { didSelectSubtitleStream(index: viewModel.selectedSubtitleStreamIndex) } From 8d604827ea52983bcaf0b5b70caac7bfeeb48104 Mon Sep 17 00:00:00 2001 From: Ethan Pippin Date: Fri, 7 Jan 2022 15:55:35 -0700 Subject: [PATCH 02/13] alignment fixes --- .../Views/ContinueWatchingView/ContinueWatchingCard.swift | 4 ++++ .../Views/ContinueWatchingView/ContinueWatchingView.swift | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Swiftfin tvOS/Views/ContinueWatchingView/ContinueWatchingCard.swift b/Swiftfin tvOS/Views/ContinueWatchingView/ContinueWatchingCard.swift index 979561f6..93cf3fb0 100644 --- a/Swiftfin tvOS/Views/ContinueWatchingView/ContinueWatchingCard.swift +++ b/Swiftfin tvOS/Views/ContinueWatchingView/ContinueWatchingCard.swift @@ -57,6 +57,7 @@ struct ContinueWatchingCard: View { .fontWeight(.semibold) .foregroundColor(.primary) .lineLimit(1) + .frame(width: 500, alignment: .leading) if item.itemType == .episode { Text(item.getEpisodeLocator() ?? "") @@ -64,8 +65,11 @@ struct ContinueWatchingCard: View { .fontWeight(.medium) .foregroundColor(.secondary) .lineLimit(1) + } else { + Text("") } } } + .padding(.vertical) } } diff --git a/Swiftfin tvOS/Views/ContinueWatchingView/ContinueWatchingView.swift b/Swiftfin tvOS/Views/ContinueWatchingView/ContinueWatchingView.swift index b1b80cbd..af6fc2c2 100644 --- a/Swiftfin tvOS/Views/ContinueWatchingView/ContinueWatchingView.swift +++ b/Swiftfin tvOS/Views/ContinueWatchingView/ContinueWatchingView.swift @@ -25,7 +25,7 @@ struct ContinueWatchingView: View { .padding(.leading, 50) ScrollView(.horizontal, showsIndicators: false) { - LazyHStack { + LazyHStack(alignment: .top) { ForEach(items, id: \.self) { item in ContinueWatchingCard(item: item) } From 2bdaf173a475853170f03ccae04722c985531bd7 Mon Sep 17 00:00:00 2001 From: Ethan Pippin Date: Fri, 7 Jan 2022 17:58:51 -0700 Subject: [PATCH 03/13] cache image attempt --- Shared/Views/ImageView.swift | 30 +++++-- Swiftfin.xcodeproj/project.pbxproj | 87 +++++++++++-------- .../xcshareddata/swiftpm/Package.resolved | 9 ++ 3 files changed, 84 insertions(+), 42 deletions(-) diff --git a/Shared/Views/ImageView.swift b/Shared/Views/ImageView.swift index 42d13995..460e77a5 100644 --- a/Shared/Views/ImageView.swift +++ b/Shared/Views/ImageView.swift @@ -7,6 +7,7 @@ * Copyright 2021 Aiden Vigue & Jellyfin Contributors */ +import CachedAsyncImage import SwiftUI struct ImageView: View { @@ -31,7 +32,7 @@ struct ImageView: View { private var failureImage: some View { ZStack { Rectangle() - .foregroundColor(Color.systemFill) + .foregroundColor(Color(UIColor.darkGray)) Text(failureInitials) .font(.largeTitle) @@ -40,21 +41,36 @@ struct ImageView: View { } var body: some View { - AsyncImage(url: source) { phase in - if let image = phase.image { + CachedAsyncImage(url: source, urlCache: .imageCache, transaction: Transaction(animation: .easeInOut)) { phase in + switch phase { + case .success(let image): image .resizable() .aspectRatio(contentMode: .fill) - } else if phase.error != nil { + case .failure(_): failureImage - } else { + default: // TODO: remove once placeholder hash image fixed + + #if os(tvOS) ZStack { - Color.gray.ignoresSafeArea() - + Color.black.ignoresSafeArea() + ProgressView() } + #else + ZStack { + Color.gray.ignoresSafeArea() + + ProgressView() + } + #endif } } } } + +extension URLCache { + + static let imageCache = URLCache(memoryCapacity: 512*1000*1000, diskCapacity: 10*1000*1000*1000) +} diff --git a/Swiftfin.xcodeproj/project.pbxproj b/Swiftfin.xcodeproj/project.pbxproj index 70f53719..d46163c6 100644 --- a/Swiftfin.xcodeproj/project.pbxproj +++ b/Swiftfin.xcodeproj/project.pbxproj @@ -270,10 +270,7 @@ E11D224227378428003F9CB3 /* ServerDetailCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E11D224127378428003F9CB3 /* ServerDetailCoordinator.swift */; }; E11D224327378428003F9CB3 /* ServerDetailCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E11D224127378428003F9CB3 /* ServerDetailCoordinator.swift */; }; E12186DE2718F1C50010884C /* Defaults in Frameworks */ = {isa = PBXBuildFile; productRef = E12186DD2718F1C50010884C /* Defaults */; }; - E1218C9A271A26BA00EA0737 /* Nuke in Frameworks */ = {isa = PBXBuildFile; productRef = E1218C99271A26BA00EA0737 /* Nuke */; }; - E1218C9C271A26C400EA0737 /* Nuke in Frameworks */ = {isa = PBXBuildFile; productRef = E1218C9B271A26C400EA0737 /* Nuke */; }; E1218C9E271A2CD600EA0737 /* CombineExt in Frameworks */ = {isa = PBXBuildFile; productRef = E1218C9D271A2CD600EA0737 /* CombineExt */; }; - E1218CA0271A2CF200EA0737 /* Nuke in Frameworks */ = {isa = PBXBuildFile; productRef = E1218C9F271A2CF200EA0737 /* Nuke */; }; E122A9132788EAAD0060FA63 /* MediaStreamExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = E122A9122788EAAD0060FA63 /* MediaStreamExtension.swift */; }; E122A9142788EAAD0060FA63 /* MediaStreamExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = E122A9122788EAAD0060FA63 /* MediaStreamExtension.swift */; }; E1267D3E271A1F46003C492E /* PreferenceUIHostingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1267D3D271A1F46003C492E /* PreferenceUIHostingController.swift */; }; @@ -374,6 +371,8 @@ E1AD105C26D9ABDD003E4A08 /* PillHStackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1AD105B26D9ABDD003E4A08 /* PillHStackView.swift */; }; E1AD105F26D9ADDD003E4A08 /* NameGUIDPairExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1AD105E26D9ADDD003E4A08 /* NameGUIDPairExtensions.swift */; }; E1AD106226D9B7CD003E4A08 /* ItemPortraitHeaderOverlayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1AD106126D9B7CD003E4A08 /* ItemPortraitHeaderOverlayView.swift */; }; + E1AE8E7C2789135A00FBDDAA /* Nuke in Frameworks */ = {isa = PBXBuildFile; productRef = E1AE8E7B2789135A00FBDDAA /* Nuke */; }; + E1AE8E7E2789136D00FBDDAA /* Nuke in Frameworks */ = {isa = PBXBuildFile; productRef = E1AE8E7D2789136D00FBDDAA /* Nuke */; }; E1B59FD52786ADE500A5287E /* ContinueWatchingCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1B59FD42786ADE500A5287E /* ContinueWatchingCard.swift */; }; E1B59FD92786AE4600A5287E /* NextUpCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1B59FD82786AE4600A5287E /* NextUpCard.swift */; }; E1B6DCE8271A23780015B715 /* CombineExt in Frameworks */ = {isa = PBXBuildFile; productRef = E1B6DCE7271A23780015B715 /* CombineExt */; }; @@ -408,6 +407,8 @@ 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 */; }; + E1E0F4D8278911680084F701 /* CachedAsyncImage in Frameworks */ = {isa = PBXBuildFile; productRef = E1E0F4D7278911680084F701 /* CachedAsyncImage */; }; + E1E0F4DA278911A30084F701 /* CachedAsyncImage in Frameworks */ = {isa = PBXBuildFile; productRef = E1E0F4D9278911A30084F701 /* CachedAsyncImage */; }; 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 */; }; @@ -751,13 +752,14 @@ files = ( 53649AAF269CFAF600A2D8B7 /* Puppy in Frameworks */, E1218C9E271A2CD600EA0737 /* CombineExt in Frameworks */, - E1218CA0271A2CF200EA0737 /* Nuke in Frameworks */, 6220D0C926D63F3700B8E046 /* Stinsen in Frameworks */, 535870912669D7A800D05A09 /* Introspect in Frameworks */, 536D3D84267BEA550004248C /* ParallaxView in Frameworks */, 53ABFDDC267972BF00886593 /* TVServices.framework in Frameworks */, E1A9999B271A343C008E78C0 /* SwiftUICollection in Frameworks */, E13DD3CD27164CA7009D4DAF /* CoreStore in Frameworks */, + E1E0F4DA278911A30084F701 /* CachedAsyncImage in Frameworks */, + E1AE8E7E2789136D00FBDDAA /* Nuke in Frameworks */, E178857D278037FD0094FBCF /* JellyfinAPI in Frameworks */, E12186DE2718F1C50010884C /* Defaults in Frameworks */, 53ABFDED26799D7700886593 /* ActivityIndicator in Frameworks */, @@ -774,10 +776,11 @@ E10EAA4D277BB716000269ED /* Sliders in Frameworks */, 62C29E9C26D0FE4200C1D2E7 /* Stinsen in Frameworks */, E1A99999271A3429008E78C0 /* SwiftUICollection in Frameworks */, - E1218C9A271A26BA00EA0737 /* Nuke in Frameworks */, E1B6DCEA271A23880015B715 /* SwiftyJSON in Frameworks */, 53352571265EA0A0006CCA86 /* Introspect in Frameworks */, E13DD3C62716499E009D4DAF /* CoreStore in Frameworks */, + E1E0F4D8278911680084F701 /* CachedAsyncImage in Frameworks */, + E1AE8E7C2789135A00FBDDAA /* Nuke in Frameworks */, 625CB57A2678C4A400530A6E /* ActivityIndicator in Frameworks */, E1B6DCE8271A23780015B715 /* CombineExt in Frameworks */, E10EAA45277BB646000269ED /* JellyfinAPI in Frameworks */, @@ -795,7 +798,6 @@ 53649AB5269D423A00A2D8B7 /* Puppy in Frameworks */, 536D3D7D267BD5F90004248C /* ActivityIndicator in Frameworks */, E13DD3CF27164E1F009D4DAF /* CoreStore in Frameworks */, - E1218C9C271A26C400EA0737 /* Nuke in Frameworks */, E10EAA47277BB670000269ED /* JellyfinAPI in Frameworks */, 3B8BA25B211CA261017ABA16 /* Pods_Swiftfin_Widget.framework in Frameworks */, ); @@ -1638,9 +1640,10 @@ E13DD3CC27164CA7009D4DAF /* CoreStore */, E12186DD2718F1C50010884C /* Defaults */, E1218C9D271A2CD600EA0737 /* CombineExt */, - E1218C9F271A2CF200EA0737 /* Nuke */, E1A9999A271A343C008E78C0 /* SwiftUICollection */, E178857C278037FD0094FBCF /* JellyfinAPI */, + E1E0F4D9278911A30084F701 /* CachedAsyncImage */, + E1AE8E7D2789136D00FBDDAA /* Nuke */, ); productName = "JellyfinPlayer tvOS"; productReference = 535870602669D21600D05A09 /* Swiftfin tvOS.app */; @@ -1675,10 +1678,11 @@ E13DD3D227168E65009D4DAF /* Defaults */, E1B6DCE7271A23780015B715 /* CombineExt */, E1B6DCE9271A23880015B715 /* SwiftyJSON */, - E1218C99271A26BA00EA0737 /* Nuke */, E1A99998271A3429008E78C0 /* SwiftUICollection */, E10EAA44277BB646000269ED /* JellyfinAPI */, E10EAA4C277BB716000269ED /* Sliders */, + E1E0F4D7278911680084F701 /* CachedAsyncImage */, + E1AE8E7B2789135A00FBDDAA /* Nuke */, ); productName = JellyfinPlayer; productReference = 5377CBF1263B596A003A4E83 /* Swiftfin iOS.app */; @@ -1703,7 +1707,6 @@ 53649AB4269D423A00A2D8B7 /* Puppy */, E13DD3CE27164E1F009D4DAF /* CoreStore */, E13DD3DC27175CE3009D4DAF /* Defaults */, - E1218C9B271A26C400EA0737 /* Nuke */, E10EAA46277BB670000269ED /* JellyfinAPI */, ); productName = WidgetExtensionExtension; @@ -1767,10 +1770,11 @@ E13DD3D127168E65009D4DAF /* XCRemoteSwiftPackageReference "Defaults" */, E1267D42271A212C003C492E /* XCRemoteSwiftPackageReference "CombineExt" */, E1C16B89271A2180009A5D25 /* XCRemoteSwiftPackageReference "SwiftyJSON" */, - E1218C98271A26BA00EA0737 /* XCRemoteSwiftPackageReference "Nuke" */, C4BFD4E327167B63007739E3 /* XCRemoteSwiftPackageReference "SwiftUICollection" */, E10EAA43277BB646000269ED /* XCRemoteSwiftPackageReference "jellyfin-sdk-swift" */, E10EAA4B277BB716000269ED /* XCRemoteSwiftPackageReference "swiftui-sliders" */, + E1E0F4D6278911680084F701 /* XCRemoteSwiftPackageReference "SwiftUI-CachedAsyncImage" */, + E1AE8E7A2789135A00FBDDAA /* XCRemoteSwiftPackageReference "Nuke" */, ); productRefGroup = 5377CBF2263B596A003A4E83 /* Products */; projectDirPath = ""; @@ -2525,7 +2529,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 66; DEVELOPMENT_ASSET_PATHS = "\"Swiftfin tvOS/Preview Content\""; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = TY84JMYEFE; ENABLE_PREVIEWS = YES; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = "Swiftfin tvOS/Info.plist"; @@ -2555,7 +2559,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 66; DEVELOPMENT_ASSET_PATHS = "\"Swiftfin tvOS/Preview Content\""; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = TY84JMYEFE; ENABLE_PREVIEWS = YES; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = "Swiftfin tvOS/Info.plist"; @@ -2926,14 +2930,6 @@ kind = branch; }; }; - E1218C98271A26BA00EA0737 /* XCRemoteSwiftPackageReference "Nuke" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/kean/Nuke"; - requirement = { - kind = upToNextMajorVersion; - minimumVersion = 9.0.0; - }; - }; E1267D42271A212C003C492E /* XCRemoteSwiftPackageReference "CombineExt" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/CombineCommunity/CombineExt"; @@ -2958,6 +2954,14 @@ minimumVersion = 6.0.0; }; }; + E1AE8E7A2789135A00FBDDAA /* XCRemoteSwiftPackageReference "Nuke" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/kean/Nuke"; + requirement = { + kind = upToNextMinorVersion; + minimumVersion = 9.6.0; + }; + }; E1C16B89271A2180009A5D25 /* XCRemoteSwiftPackageReference "SwiftyJSON" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/SwiftyJSON/SwiftyJSON"; @@ -2966,6 +2970,14 @@ minimumVersion = 5.0.0; }; }; + E1E0F4D6278911680084F701 /* XCRemoteSwiftPackageReference "SwiftUI-CachedAsyncImage" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/lorenzofiamingo/SwiftUI-CachedAsyncImage"; + requirement = { + branch = main; + kind = branch; + }; + }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ @@ -3044,26 +3056,11 @@ package = E13DD3D127168E65009D4DAF /* XCRemoteSwiftPackageReference "Defaults" */; productName = Defaults; }; - E1218C99271A26BA00EA0737 /* Nuke */ = { - isa = XCSwiftPackageProductDependency; - package = E1218C98271A26BA00EA0737 /* XCRemoteSwiftPackageReference "Nuke" */; - productName = Nuke; - }; - E1218C9B271A26C400EA0737 /* Nuke */ = { - isa = XCSwiftPackageProductDependency; - package = E1218C98271A26BA00EA0737 /* XCRemoteSwiftPackageReference "Nuke" */; - productName = Nuke; - }; E1218C9D271A2CD600EA0737 /* CombineExt */ = { isa = XCSwiftPackageProductDependency; package = E1267D42271A212C003C492E /* XCRemoteSwiftPackageReference "CombineExt" */; productName = CombineExt; }; - E1218C9F271A2CF200EA0737 /* Nuke */ = { - isa = XCSwiftPackageProductDependency; - package = E1218C98271A26BA00EA0737 /* XCRemoteSwiftPackageReference "Nuke" */; - productName = Nuke; - }; E13DD3C52716499E009D4DAF /* CoreStore */ = { isa = XCSwiftPackageProductDependency; package = E13DD3C42716499E009D4DAF /* XCRemoteSwiftPackageReference "CoreStore" */; @@ -3104,6 +3101,16 @@ package = C4BFD4E327167B63007739E3 /* XCRemoteSwiftPackageReference "SwiftUICollection" */; productName = SwiftUICollection; }; + E1AE8E7B2789135A00FBDDAA /* Nuke */ = { + isa = XCSwiftPackageProductDependency; + package = E1AE8E7A2789135A00FBDDAA /* XCRemoteSwiftPackageReference "Nuke" */; + productName = Nuke; + }; + E1AE8E7D2789136D00FBDDAA /* Nuke */ = { + isa = XCSwiftPackageProductDependency; + package = E1AE8E7A2789135A00FBDDAA /* XCRemoteSwiftPackageReference "Nuke" */; + productName = Nuke; + }; E1B6DCE7271A23780015B715 /* CombineExt */ = { isa = XCSwiftPackageProductDependency; package = E1267D42271A212C003C492E /* XCRemoteSwiftPackageReference "CombineExt" */; @@ -3114,6 +3121,16 @@ package = E1C16B89271A2180009A5D25 /* XCRemoteSwiftPackageReference "SwiftyJSON" */; productName = SwiftyJSON; }; + E1E0F4D7278911680084F701 /* CachedAsyncImage */ = { + isa = XCSwiftPackageProductDependency; + package = E1E0F4D6278911680084F701 /* XCRemoteSwiftPackageReference "SwiftUI-CachedAsyncImage" */; + productName = CachedAsyncImage; + }; + E1E0F4D9278911A30084F701 /* CachedAsyncImage */ = { + isa = XCSwiftPackageProductDependency; + package = E1E0F4D6278911680084F701 /* XCRemoteSwiftPackageReference "SwiftUI-CachedAsyncImage" */; + productName = CachedAsyncImage; + }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 5377CBE9263B596A003A4E83 /* Project object */; diff --git a/Swiftfin.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Swiftfin.xcworkspace/xcshareddata/swiftpm/Package.resolved index 3ed0ea18..cf80eed4 100644 --- a/Swiftfin.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Swiftfin.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -100,6 +100,15 @@ "version": "1.4.2" } }, + { + "package": "CachedAsyncImage", + "repositoryURL": "https://github.com/lorenzofiamingo/SwiftUI-CachedAsyncImage", + "state": { + "branch": "main", + "revision": "eb489a699be1f6e6c1a19fecdd6bfdc556474fd6", + "version": null + } + }, { "package": "Introspect", "repositoryURL": "https://github.com/siteline/SwiftUI-Introspect", From e6d31bc281c4488fae4eff427f9cc050254d4538 Mon Sep 17 00:00:00 2001 From: Ethan Pippin Date: Fri, 7 Jan 2022 18:38:31 -0700 Subject: [PATCH 04/13] final touch ups --- .../Components/EpisodesRowView.swift | 2 +- .../ContinueWatchingCard.swift | 9 ++- .../CinematicEpisodeItemView.swift | 1 + Swiftfin tvOS/Views/LatestMediaView.swift | 58 +++++++++++++++++-- .../Views/NextUpView/NextUpCard.swift | 9 ++- .../VideoPlayer/VLCPlayerViewController.swift | 9 +-- .../tvOSOverlay/tvOSVLCOverlay.swift | 2 +- .../VideoPlayer/tvOSSLider/tvOSSlider.swift | 6 +- 8 files changed, 78 insertions(+), 18 deletions(-) diff --git a/Swiftfin tvOS/Components/EpisodesRowView.swift b/Swiftfin tvOS/Components/EpisodesRowView.swift index 735216a0..bf8dca08 100644 --- a/Swiftfin tvOS/Components/EpisodesRowView.swift +++ b/Swiftfin tvOS/Components/EpisodesRowView.swift @@ -84,7 +84,7 @@ struct EpisodesRowView: View { HStack(alignment: .top) { VStack(alignment: .leading) { - ImageView(src: episode.getBackdropImage(maxWidth: 445), + ImageView(src: episode.getBackdropImage(maxWidth: 500), bh: episode.getBackdropImageBlurHash()) .mask(Rectangle().frame(width: 500, height: 280)) .frame(width: 500, height: 280) diff --git a/Swiftfin tvOS/Views/ContinueWatchingView/ContinueWatchingCard.swift b/Swiftfin tvOS/Views/ContinueWatchingView/ContinueWatchingCard.swift index 93cf3fb0..99b400ff 100644 --- a/Swiftfin tvOS/Views/ContinueWatchingView/ContinueWatchingCard.swift +++ b/Swiftfin tvOS/Views/ContinueWatchingView/ContinueWatchingCard.swift @@ -22,8 +22,13 @@ struct ContinueWatchingCard: View { } label: { ZStack(alignment: .bottom) { - ImageView(src: item.getBackdropImage(maxWidth: 500)) - .frame(width: 500, height: 281.25) + if item.itemType == .episode { + ImageView(src: item.getSeriesBackdropImage(maxWidth: 500)) + .frame(width: 500, height: 281.25) + } else { + ImageView(src: item.getBackdropImage(maxWidth: 500)) + .frame(width: 500, height: 281.25) + } VStack(alignment: .leading, spacing: 0) { Text(item.getItemProgressString() ?? "") diff --git a/Swiftfin tvOS/Views/ItemView/CinematicItemView/CinematicEpisodeItemView.swift b/Swiftfin tvOS/Views/ItemView/CinematicItemView/CinematicEpisodeItemView.swift index 8d30bc43..3942ec11 100644 --- a/Swiftfin tvOS/Views/ItemView/CinematicItemView/CinematicEpisodeItemView.swift +++ b/Swiftfin tvOS/Views/ItemView/CinematicItemView/CinematicEpisodeItemView.swift @@ -31,6 +31,7 @@ struct CinematicEpisodeItemView: View { ImageView(src: viewModel.item.getBackdropImage(maxWidth: 1920), bh: viewModel.item.getBackdropImageBlurHash()) + .frame(height: UIScreen.main.bounds.height - 10) .ignoresSafeArea() ScrollView { diff --git a/Swiftfin tvOS/Views/LatestMediaView.swift b/Swiftfin tvOS/Views/LatestMediaView.swift index 5f5d14ea..68fbe395 100644 --- a/Swiftfin tvOS/Views/LatestMediaView.swift +++ b/Swiftfin tvOS/Views/LatestMediaView.swift @@ -16,10 +16,60 @@ struct LatestMediaView: View { @Default(.showPosterLabels) var showPosterLabels var body: some View { - PortraitItemsRowView(rowTitle: L10n.latestWithString(viewModel.library.name ?? ""), - items: viewModel.items, - showItemTitles: showPosterLabels) { item in - homeRouter.route(to: \.modalItem, item) + VStack(alignment: .leading) { + + L10n.latestWithString(viewModel.library.name ?? "").text + .font(.title3) + .padding(.horizontal, 50) + + ScrollView(.horizontal) { + HStack(alignment: .top) { + ForEach(viewModel.items, id: \.self) { item in + + VStack(spacing: 15) { + Button { + homeRouter.route(to: \.modalItem, item) + } label: { + ImageView(src: item.portraitHeaderViewURL(maxWidth: 257)) + .frame(width: 257, height: 380) + } + .frame(height: 380) + .buttonStyle(PlainButtonStyle()) + + if showPosterLabels { + Text(item.title) + .lineLimit(2) + .frame(width: 257) + } + } + } + + Button { + homeRouter.route(to: \.library, (viewModel: .init(parentID: viewModel.library.id!, + filters: LibraryFilters(filters: [], sortOrder: [.descending], sortBy: [.dateAdded])), + title: viewModel.library.name ?? "")) + } label: { + ZStack { + Color(UIColor.darkGray) + .opacity(0.5) + + VStack(spacing: 20) { + Image(systemName: "chevron.right") + .font(.title) + + L10n.seeAll.text + .font(.title3) + } + } + } + .frame(width: 257, height: 380) + .buttonStyle(PlainButtonStyle()) + } + .padding(.horizontal, 50) + .padding(.vertical) + } + .edgesIgnoringSafeArea(.horizontal) } + .focusSection() } } diff --git a/Swiftfin tvOS/Views/NextUpView/NextUpCard.swift b/Swiftfin tvOS/Views/NextUpView/NextUpCard.swift index b9668785..53094ac3 100644 --- a/Swiftfin tvOS/Views/NextUpView/NextUpCard.swift +++ b/Swiftfin tvOS/Views/NextUpView/NextUpCard.swift @@ -20,8 +20,13 @@ struct NextUpCard: View { Button { homeRouter.route(to: \.modalItem, item) } label: { - ImageView(src: item.getBackdropImage(maxWidth: 500)) - .frame(width: 500, height: 281.25) + if item.itemType == .episode { + ImageView(src: item.getSeriesBackdropImage(maxWidth: 500)) + .frame(width: 500, height: 281.25) + } else { + ImageView(src: item.getBackdropImage(maxWidth: 500)) + .frame(width: 500, height: 281.25) + } } .buttonStyle(CardButtonStyle()) .padding(.top) diff --git a/Swiftfin tvOS/Views/VideoPlayer/VLCPlayerViewController.swift b/Swiftfin tvOS/Views/VideoPlayer/VLCPlayerViewController.swift index ea106ca7..26f2eb80 100644 --- a/Swiftfin tvOS/Views/VideoPlayer/VLCPlayerViewController.swift +++ b/Swiftfin tvOS/Views/VideoPlayer/VLCPlayerViewController.swift @@ -204,20 +204,20 @@ class VLCPlayerViewController: UIViewController { hideConfirmCloseOverlay() if Defaults[.downActionShowsMenu] { - if !displayingContentOverlay { + if !displayingContentOverlay && !displayingOverlay { didSelectMenu() } } case .leftArrow: hideConfirmCloseOverlay() - if !displayingContentOverlay { + if !displayingContentOverlay && !displayingOverlay { didSelectBackward() } case .rightArrow: hideConfirmCloseOverlay() - if !displayingContentOverlay { + if !displayingContentOverlay && !displayingOverlay { didSelectForward() } case .pageUp: () @@ -239,9 +239,6 @@ class VLCPlayerViewController: UIViewController { hideOverlay() } else if displayingContentOverlay { hideOverlayContent() - - showOverlay() - restartOverlayDismissTimer() } else if viewModel.confirmClose && !displayingConfirmClose { showConfirmCloseOverlay() diff --git a/Swiftfin tvOS/Views/VideoPlayer/tvOSOverlay/tvOSVLCOverlay.swift b/Swiftfin tvOS/Views/VideoPlayer/tvOSOverlay/tvOSVLCOverlay.swift index 03c859cb..bce30b04 100644 --- a/Swiftfin tvOS/Views/VideoPlayer/tvOSOverlay/tvOSVLCOverlay.swift +++ b/Swiftfin tvOS/Views/VideoPlayer/tvOSOverlay/tvOSVLCOverlay.swift @@ -47,7 +47,7 @@ struct tvOSVLCOverlay: View { if let subtitle = viewModel.subtitle { Text(subtitle) .font(.subheadline) - .foregroundColor(.lightGray) + .foregroundColor(.white) } Text(viewModel.title) diff --git a/Swiftfin tvOS/Views/VideoPlayer/tvOSSLider/tvOSSlider.swift b/Swiftfin tvOS/Views/VideoPlayer/tvOSSLider/tvOSSlider.swift index 79db94e5..8405ffa8 100644 --- a/Swiftfin tvOS/Views/VideoPlayer/tvOSSLider/tvOSSlider.swift +++ b/Swiftfin tvOS/Views/VideoPlayer/tvOSSLider/tvOSSlider.swift @@ -527,12 +527,14 @@ public final class TvOSSlider: UIControl { @objc private func leftTapWasTriggered() { - setValue(value-stepValue, animated: true) +// setValue(value-stepValue, animated: true) + viewModel.playerOverlayDelegate?.didSelectBackward() } @objc private func rightTapWasTriggered() { - setValue(value+stepValue, animated: true) +// setValue(value+stepValue, animated: true) + viewModel.playerOverlayDelegate?.didSelectForward() } public override func pressesBegan(_ presses: Set, with event: UIPressesEvent?) { From a25e26d4a63eda5664a1aed3037e8781b20922ad Mon Sep 17 00:00:00 2001 From: Ethan Pippin Date: Fri, 7 Jan 2022 18:51:14 -0700 Subject: [PATCH 05/13] fix and remove dev team --- Swiftfin.xcodeproj/project.pbxproj | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/Swiftfin.xcodeproj/project.pbxproj b/Swiftfin.xcodeproj/project.pbxproj index d46163c6..ed41be2f 100644 --- a/Swiftfin.xcodeproj/project.pbxproj +++ b/Swiftfin.xcodeproj/project.pbxproj @@ -404,6 +404,7 @@ 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 */; }; + E1D7E5A827892566009D0EF7 /* Nuke in Frameworks */ = {isa = PBXBuildFile; productRef = E1D7E5A727892566009D0EF7 /* Nuke */; }; 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 */; }; @@ -792,6 +793,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + E1D7E5A827892566009D0EF7 /* Nuke in Frameworks */, 628B95242670CABD0091AF3B /* SwiftUI.framework in Frameworks */, 531ABF6C2671F5CC00C0FE20 /* WidgetKit.framework in Frameworks */, E13DD3DD27175CE3009D4DAF /* Defaults in Frameworks */, @@ -1708,6 +1710,7 @@ E13DD3CE27164E1F009D4DAF /* CoreStore */, E13DD3DC27175CE3009D4DAF /* Defaults */, E10EAA46277BB670000269ED /* JellyfinAPI */, + E1D7E5A727892566009D0EF7 /* Nuke */, ); productName = WidgetExtensionExtension; productReference = 628B95202670CABD0091AF3B /* Swiftfin Widget.appex */; @@ -2529,7 +2532,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 66; DEVELOPMENT_ASSET_PATHS = "\"Swiftfin tvOS/Preview Content\""; - DEVELOPMENT_TEAM = TY84JMYEFE; + DEVELOPMENT_TEAM = ""; ENABLE_PREVIEWS = YES; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = "Swiftfin tvOS/Info.plist"; @@ -2559,7 +2562,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 66; DEVELOPMENT_ASSET_PATHS = "\"Swiftfin tvOS/Preview Content\""; - DEVELOPMENT_TEAM = TY84JMYEFE; + DEVELOPMENT_TEAM = ""; ENABLE_PREVIEWS = YES; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = "Swiftfin tvOS/Info.plist"; @@ -2711,7 +2714,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 66; DEVELOPMENT_ASSET_PATHS = ""; - DEVELOPMENT_TEAM = TY84JMYEFE; + DEVELOPMENT_TEAM = ""; ENABLE_BITCODE = NO; ENABLE_PREVIEWS = YES; EXCLUDED_ARCHS = ""; @@ -2748,7 +2751,7 @@ CURRENT_PROJECT_VERSION = 66; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; - DEVELOPMENT_TEAM = TY84JMYEFE; + DEVELOPMENT_TEAM = ""; ENABLE_BITCODE = NO; ENABLE_PREVIEWS = YES; EXCLUDED_ARCHS = ""; @@ -2779,7 +2782,7 @@ ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 66; - DEVELOPMENT_TEAM = TY84JMYEFE; + DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = WidgetExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( @@ -2806,7 +2809,7 @@ ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 66; - DEVELOPMENT_TEAM = TY84JMYEFE; + DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = WidgetExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( @@ -3121,6 +3124,11 @@ package = E1C16B89271A2180009A5D25 /* XCRemoteSwiftPackageReference "SwiftyJSON" */; productName = SwiftyJSON; }; + E1D7E5A727892566009D0EF7 /* Nuke */ = { + isa = XCSwiftPackageProductDependency; + package = E1AE8E7A2789135A00FBDDAA /* XCRemoteSwiftPackageReference "Nuke" */; + productName = Nuke; + }; E1E0F4D7278911680084F701 /* CachedAsyncImage */ = { isa = XCSwiftPackageProductDependency; package = E1E0F4D6278911680084F701 /* XCRemoteSwiftPackageReference "SwiftUI-CachedAsyncImage" */; From 03a8282d1f99206ceb9d269c3ae39a6ee993280e Mon Sep 17 00:00:00 2001 From: Ethan Pippin Date: Fri, 7 Jan 2022 20:40:17 -0700 Subject: [PATCH 06/13] comment out quality to avoid issues --- .../Views/SettingsView/SettingsView.swift | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/Swiftfin/Views/SettingsView/SettingsView.swift b/Swiftfin/Views/SettingsView/SettingsView.swift index 81ac1b09..be11081d 100644 --- a/Swiftfin/Views/SettingsView/SettingsView.swift +++ b/Swiftfin/Views/SettingsView/SettingsView.swift @@ -63,19 +63,20 @@ struct SettingsView: View { } } - 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) - } - } - } + // 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) +// } +// } +// } Section(header: Text("Video Player")) { Picker("Jump Forward Length", selection: $jumpForwardLength) { From d291069c6165699eba7f895509df93d988b65d5e Mon Sep 17 00:00:00 2001 From: Ethan Pippin Date: Fri, 7 Jan 2022 21:44:00 -0700 Subject: [PATCH 07/13] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7658c508..cb98d167 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@

- Swiftfin + Swiftfin

Swiftfin

From 6f7f83730ede426e39ac30a2fecdb3220771266e Mon Sep 17 00:00:00 2001 From: Ethan Pippin Date: Sat, 8 Jan 2022 00:16:31 -0700 Subject: [PATCH 08/13] add contributing guidelines --- README.md | 20 ++++++++++---------- contributing.md | 50 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 10 deletions(-) create mode 100644 contributing.md diff --git a/README.md b/README.md index 7658c508..cb129b8e 100644 --- a/README.md +++ b/README.md @@ -32,17 +32,17 @@ Check out our [Weblate instance](https://translate.jellyfin.org/projects/swiftfi ## ⚙️ Development -Xcode 13.0 with command line tools. -### Build Process -```bash -# install Cocoapods (if not installed) -$ sudo gem install cocoapods +## Intended Behaviors Due to Technical Limitations -# install dependencies -$ pod install +The following behaviors are intended due to technical limitations: -# open workspace and build it -$ open Swiftfin.xcworkspace -``` +- Pausing playback when app is backgrounded + - Due to VLCKit pausing video output at the same moment + +- Audio delay after un-pausing + - Due to VLCKit, may be fixed in VLCKit v4 + +- No aspect fill + - VLCKit doesn't have the ability to aspect fill the view that the video output occupies diff --git a/contributing.md b/contributing.md new file mode 100644 index 00000000..106052a6 --- /dev/null +++ b/contributing.md @@ -0,0 +1,50 @@ +# Contributing to Swiftfin + +> Thank you for your interest in contributing to the Jellyfin (Swiftfin) project! This page and its children describe the ways you can contribute, as well as some of our policies. This should help guide you through your first Issue or PR. + +> Even if you can't contribute code, you can still help Jellyfin (Swiftfin)! The two main things you can help with are testing and creating issues. Contributing to code, ..., and other non-code components are all outlined in the sections below. + +## Setup + +Fork the Swiftfin repo and install the necessary CocoaPods with Xcode 13: + +```bash +# install Cocoapods (if not installed) +$ sudo gem install cocoapods + +# install dependencies +$ pod install + +# open workspace and build +$ open Swiftfin.xcworkspace +``` + +## Git Flow + +Pull Requests must be created from branch that is _**not**_ your fork's `main` branch. This is to prevent many potential problems that come from unnecessary or irrelevant commits and rebasing your fork. + +If your Pull Request relates to an Issue, link the issue or mention it in the Issue itself. + +Pull Requests must pass the automated `iOS` and `tvOS` builds in order to be merged and cannot have your developer account attached. + +Swiftfin follows the same Pull Request Guidelines as outlined in the [official Jellyfin contribution guidelines](https://jellyfin.org/docs/general/contributing/development.html#pull-request-guidelines). + +## Architecture + +Swiftfin is developed using SwiftUI with some UIKit components where deemed necessary, as SwiftUI is still in relatively early development. Swiftfin consists of both the iOS and tvOS Jellyfin clients with a shared general underlying structure where each client has their own respective views. Because of this architecture, keep in mind while developing you may have to work for both clients. + +Playback is done using [VLCKit](https://code.videolan.org/videolan/VLCKit) for its great codec support. + +While there are no design guidelines for UI/UX features, Swiftfin has the goal to use native SwiftUI components with specific theming to Jellyfin. If your feature creates new UI/UX components, you are welcome to introduce a general design that may receive feedback during the PR process or may be re-designed later on. Some UI/UX features are intended to be user customizable but not every item should be to keep to some idea of Swiftfin's own design. Taking inspiration, but not always copying, from other applications is encouraged. + +## New Features + +If you would like to develop a new feature, create an issue with a description of the feature such that a discussion can be made for its possibility, whether it belongs in Swiftfin, and finally its general implementation. Leave a comment when you start work on an approved feature such that duplicate work among developers doesn't conflict. + +## Other Code Work + +Other code work like bug fixes, issues with `Developer` tags, and localization efforts are welcome to be picked up anytime. Just leave a comment when you start work on a bug fix or `Developer` issue. + +If you notice undesirable behavior or would like to make a UI/UX tweak, create an issue or ask in the iOS Matrix/Discord channel and a discussion will be made. + +If you have a question about any existing implementations, ask the iOS Matrix/Discord channel for developer insights. From 9c34c39c48029f10fbe526615f6888f6f8c409d3 Mon Sep 17 00:00:00 2001 From: Ethan Pippin Date: Sat, 8 Jan 2022 00:18:22 -0700 Subject: [PATCH 09/13] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index cb129b8e..ac297ac3 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@

- Swiftfin + Swiftfin

Swiftfin

@@ -32,7 +32,7 @@ Check out our [Weblate instance](https://translate.jellyfin.org/projects/swiftfi ## ⚙️ Development - +Thank you for your interest in Swiftfin, please check out the [Contribution Guidelines](https://github.com/jellyfin/SwiftFin/contributing.md). ## Intended Behaviors Due to Technical Limitations From c20866286b4384d7d3669b298aadace55ada0b9a Mon Sep 17 00:00:00 2001 From: Ethan Pippin Date: Sat, 8 Jan 2022 00:34:47 -0700 Subject: [PATCH 10/13] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ac297ac3..f9349ba6 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ Check out our [Weblate instance](https://translate.jellyfin.org/projects/swiftfi Thank you for your interest in Swiftfin, please check out the [Contribution Guidelines](https://github.com/jellyfin/SwiftFin/contributing.md). -## Intended Behaviors Due to Technical Limitations +### Intended Behaviors Due to Technical Limitations The following behaviors are intended due to technical limitations: From 880b2697edc01ee172b64555895a201e17fd6052 Mon Sep 17 00:00:00 2001 From: Ethan Pippin Date: Sat, 8 Jan 2022 00:35:55 -0700 Subject: [PATCH 11/13] Update README.md --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f9349ba6..582ba69f 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,9 @@ Check out our [Weblate instance](https://translate.jellyfin.org/projects/swiftfi ## ⚙️ Development -Thank you for your interest in Swiftfin, please check out the [Contribution Guidelines](https://github.com/jellyfin/SwiftFin/contributing.md). +Thank you for your interest in Swiftfin, please check out the [Contribution Guidelines](https://github.com/jellyfin/SwiftFin/contributing.md) to get started. + +----- ### Intended Behaviors Due to Technical Limitations From 3dae6769f92a5d38a0d94fee24bd77fe5728a1e1 Mon Sep 17 00:00:00 2001 From: jameskimmel <17176225+jameskimmel@users.noreply.github.com> Date: Sat, 8 Jan 2022 10:13:00 +0100 Subject: [PATCH 12/13] Update README.md Changed AppIcon link to save path. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7658c508..ea073353 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@

- Swiftfin + Swiftfin

Swiftfin

From 1c2ca5178ab1b65baa96d591fc0591881db7f21d Mon Sep 17 00:00:00 2001 From: Ethan Pippin Date: Sat, 8 Jan 2022 11:21:20 -0700 Subject: [PATCH 13/13] Update README.md Co-authored-by: Shadowghost --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 25f0ea72..4f4f3b5c 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ Check out our [Weblate instance](https://translate.jellyfin.org/projects/swiftfi ## ⚙️ Development -Thank you for your interest in Swiftfin, please check out the [Contribution Guidelines](https://github.com/jellyfin/SwiftFin/contributing.md) to get started. +Thank you for your interest in Swiftfin, please check out the [Contribution Guidelines](https://github.com/jellyfin/Swiftfin/contributing.md) to get started. -----