From e1d2edfca4b0d2b7ee12bba345f524a3bc264ce7 Mon Sep 17 00:00:00 2001 From: Aiden Vigue Date: Tue, 22 Jun 2021 00:10:54 -0400 Subject: [PATCH] 51 prep --- JellyfinPlayer.xcodeproj/project.pbxproj | 12 +- JellyfinPlayer/EpisodeItemView.swift | 2 +- JellyfinPlayer/SettingsView.swift | 19 --- JellyfinPlayer/VideoPlayer.swift | 62 +++++----- Shared/Extensions/ImageView.swift | 2 +- Shared/Singleton/SessionManager.swift | 6 +- WidgetExtension/NextUpWidget.swift | 141 ++++++++++++----------- 7 files changed, 116 insertions(+), 128 deletions(-) diff --git a/JellyfinPlayer.xcodeproj/project.pbxproj b/JellyfinPlayer.xcodeproj/project.pbxproj index 5d0f0316..1f79ece7 100644 --- a/JellyfinPlayer.xcodeproj/project.pbxproj +++ b/JellyfinPlayer.xcodeproj/project.pbxproj @@ -1002,7 +1002,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = "JellyfinPlayer tvOS/JellyfinPlayer tvOS.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 49; + CURRENT_PROJECT_VERSION = 50; DEVELOPMENT_ASSET_PATHS = "\"JellyfinPlayer tvOS/Preview Content\""; DEVELOPMENT_TEAM = 9R8RREG67J; ENABLE_PREVIEWS = YES; @@ -1030,7 +1030,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = "JellyfinPlayer tvOS/JellyfinPlayer tvOS.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 49; + CURRENT_PROJECT_VERSION = 50; DEVELOPMENT_ASSET_PATHS = "\"JellyfinPlayer tvOS/Preview Content\""; DEVELOPMENT_TEAM = 9R8RREG67J; ENABLE_PREVIEWS = YES; @@ -1179,7 +1179,7 @@ CODE_SIGN_ENTITLEMENTS = JellyfinPlayer/JellyfinPlayer.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 49; + CURRENT_PROJECT_VERSION = 50; DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_TEAM = 9R8RREG67J; ENABLE_BITCODE = NO; @@ -1213,7 +1213,7 @@ CODE_SIGN_ENTITLEMENTS = JellyfinPlayer/JellyfinPlayer.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 49; + CURRENT_PROJECT_VERSION = 50; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_TEAM = 9R8RREG67J; @@ -1245,7 +1245,7 @@ ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; CODE_SIGN_ENTITLEMENTS = WidgetExtension/WidgetExtension.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 49; + CURRENT_PROJECT_VERSION = 50; DEVELOPMENT_TEAM = 9R8RREG67J; INFOPLIST_FILE = WidgetExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.1; @@ -1270,7 +1270,7 @@ ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; CODE_SIGN_ENTITLEMENTS = WidgetExtension/WidgetExtension.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 49; + CURRENT_PROJECT_VERSION = 50; DEVELOPMENT_TEAM = 9R8RREG67J; INFOPLIST_FILE = WidgetExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.1; diff --git a/JellyfinPlayer/EpisodeItemView.swift b/JellyfinPlayer/EpisodeItemView.swift index c686286e..ea192537 100644 --- a/JellyfinPlayer/EpisodeItemView.swift +++ b/JellyfinPlayer/EpisodeItemView.swift @@ -202,7 +202,7 @@ struct EpisodeItemView: View { self.playbackInfo.shouldShowPlayer = true } label: { HStack { - Text(viewModel.item.getItemProgressString() == "" ? "Play" : "\(viewModel.item.getItemProgressString()) left") + Text(viewModel.item.getItemProgressString() == "" ? "Play" : viewModel.item.getItemProgressString()) .foregroundColor(Color.white).font(.callout).fontWeight(.semibold) Image(systemName: "play.fill").foregroundColor(Color.white).font(.system(size: 20)) } diff --git a/JellyfinPlayer/SettingsView.swift b/JellyfinPlayer/SettingsView.swift index 0af6b472..1dd2035b 100644 --- a/JellyfinPlayer/SettingsView.swift +++ b/JellyfinPlayer/SettingsView.swift @@ -65,26 +65,7 @@ struct SettingsView: View { Text("Signed in as \(username)").foregroundColor(.primary) Spacer() Button { - let fetchRequest: NSFetchRequest = NSFetchRequest(entityName: "Server") - let deleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest) - - do { - try viewContext.execute(deleteRequest) - } catch _ as NSError { - // TODO: handle the error - } - - let fetchRequest2: NSFetchRequest = NSFetchRequest(entityName: "SignedInUser") - let deleteRequest2 = NSBatchDeleteRequest(fetchRequest: fetchRequest2) - - do { - try viewContext.execute(deleteRequest2) - } catch _ as NSError { - // TODO: handle the error - } - SessionManager.current.logout() - ServerEnvironment.current.reset() } label: { Text("Log out").font(.callout) } diff --git a/JellyfinPlayer/VideoPlayer.swift b/JellyfinPlayer/VideoPlayer.swift index 73137cd0..282cae07 100644 --- a/JellyfinPlayer/VideoPlayer.swift +++ b/JellyfinPlayer/VideoPlayer.swift @@ -129,11 +129,6 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe let secondsScrubbedTo = round(Double(seekSlider.value) * videoDuration) let offset = secondsScrubbedTo - videoPosition - print(videoPosition) - print(videoDuration) - print(secondsScrubbedTo) - print(offset) - if(playerDestination == .local) { if offset > 0 { mediaPlayer.jumpForward(Int32(offset)) @@ -178,7 +173,7 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe if(playerDestination == .local) { mediaPlayer.jumpBackward(15) } else { - + self.sendJellyfinCommand(command: "Seek", options: ["position": (remotePositionTicks/10_000_000)-15]) } } } @@ -188,6 +183,7 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe if(playerDestination == .local) { mediaPlayer.jumpForward(30) } else { + self.sendJellyfinCommand(command: "Seek", options: ["position": (remotePositionTicks/10_000_000)+30]) } } } @@ -280,15 +276,11 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe self.mainActionButton.setImage(UIImage(systemName: "pause"), for: .normal) } } - + override var supportedInterfaceOrientations: UIInterfaceOrientationMask { return .landscape } - override var shouldAutorotate: Bool { - return true - } - func setupNowPlayingCC() { let commandCenter = MPRemoteCommandCenter.shared() commandCenter.playCommand.isEnabled = true @@ -327,7 +319,7 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe self.mediaPlayer.jumpForward(30) self.sendProgressReport(eventName: "timeupdate") } else { - + self.sendJellyfinCommand(command: "Seek", options: ["position": (self.remotePositionTicks/10_000_000)+30]) } return .success } @@ -337,6 +329,8 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe if(self.playerDestination == .local) { self.mediaPlayer.jumpBackward(15) self.sendProgressReport(eventName: "timeupdate") + } else { + self.sendJellyfinCommand(command: "Seek", options: ["position": (self.remotePositionTicks/10_000_000)-15]) } return .success } @@ -389,10 +383,6 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe } super.viewDidLoad() - if !UIDevice.current.orientation.isLandscape { - let value = UIInterfaceOrientation.landscapeLeft.rawValue - UIDevice.current.setValue(value, forKey: "orientation") - } } func mediaHasStartedPlaying() { @@ -431,7 +421,13 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe super.viewDidAppear(animated) overrideUserInterfaceStyle = .dark self.tabBarController?.tabBar.isHidden = true - // View has loaded. + self.navigationController?.isNavigationBarHidden = true + self.setNeedsUpdateOfHomeIndicatorAutoHidden() + + if !UIDevice.current.orientation.isLandscape || UIDevice.current.orientation.isFlat { + let value = UIInterfaceOrientation.landscapeLeft.rawValue + UIDevice.current.setValue(value, forKey: "orientation") + } mediaPlayer.perform(Selector(("setTextRendererFontSize:")), with: 14) // mediaPlayer.wrappedValue.perform(Selector(("setTextRendererFont:")), with: "Copperplate") @@ -456,8 +452,6 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe .sink(receiveCompletion: { result in print(result) }, receiveValue: { [self] response in - videoContentView.setNeedsLayout() - videoContentView.setNeedsDisplay() playSessionId = response.playSessionId ?? "" let mediaSource = response.mediaSources!.first.self! if mediaSource.transcodingUrl != nil { @@ -471,11 +465,11 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe subtitleTrackArray.append(disableSubtitleTrack) // Loop through media streams and add to array - for stream in mediaSource.mediaStreams! { + for stream in mediaSource.mediaStreams ?? [] { if stream.type == .subtitle { var deliveryUrl: URL? if stream.deliveryMethod == .external { - deliveryUrl = URL(string: "\(ServerEnvironment.current.server.baseURI!)\(stream.deliveryUrl!)")! + deliveryUrl = URL(string: "\(ServerEnvironment.current.server.baseURI!)\(stream.deliveryUrl ?? "")")! } else { deliveryUrl = nil } @@ -505,7 +499,7 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe playbackItem = item } else { // Item will be directly played by the client. - let streamURL: URL = URL(string: "\(ServerEnvironment.current.server.baseURI!)/Videos/\(manifest.id!)/stream?Static=true&mediaSourceId=\(manifest.id!)&deviceId=\(SessionManager.current.deviceID)&api_key=\(SessionManager.current.accessToken)&Tag=\(mediaSource.eTag!)")! + let streamURL: URL = URL(string: "\(ServerEnvironment.current.server.baseURI!)/Videos/\(manifest.id!)/stream?Static=true&mediaSourceId=\(manifest.id!)&deviceId=\(SessionManager.current.deviceID)&api_key=\(SessionManager.current.accessToken)&Tag=\(mediaSource.eTag ?? "")")! let item = PlaybackItem() item.videoUrl = streamURL @@ -515,7 +509,7 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe subtitleTrackArray.append(disableSubtitleTrack) // Loop through media streams and add to array - for stream in mediaSource.mediaStreams! { + for stream in mediaSource.mediaStreams ?? [] { if stream.type == .subtitle { var deliveryUrl: URL? if stream.deliveryMethod == .external { @@ -548,7 +542,7 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe self.sendPlayReport() playbackItem = item } - dump(playbackItem) + startLocalPlaybackEngine(true) }) .store(in: &cancellables) @@ -597,6 +591,13 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe self.mediaHasStartedPlaying() delegate?.hideLoadingView(self) + videoContentView.setNeedsLayout() + videoContentView.setNeedsDisplay() + self.view.setNeedsLayout() + self.view.setNeedsDisplay() + self.videoControlsView.setNeedsLayout() + self.videoControlsView.setNeedsDisplay() + mediaPlayer.pause() mediaPlayer.play() @@ -793,11 +794,6 @@ extension PlayerViewController: VLCMediaPlayerDelegate { case .buffering : print("Video is buffering)") delegate?.showLoadingView(self) - mediaPlayer.pause() - usleep(10000) - mediaPlayer.play() - delegate?.hideLoadingView(self) - paused = false case .error : print("Video has error)") sendStopReport() @@ -925,7 +921,7 @@ extension PlayerViewController { } func sendPlayReport() { - startTime = Int(Date().timeIntervalSince1970) * 10000000 + startTime = Int(Date().timeIntervalSince1970) * 10_000_000 print("sending play report!") @@ -940,3 +936,9 @@ extension PlayerViewController { .store(in: &cancellables) } } + +extension UINavigationController { + open override var childForHomeIndicatorAutoHidden: UIViewController? { + return nil + } +} diff --git a/Shared/Extensions/ImageView.swift b/Shared/Extensions/ImageView.swift index 9bc52feb..70b084a2 100644 --- a/Shared/Extensions/ImageView.swift +++ b/Shared/Extensions/ImageView.swift @@ -31,7 +31,7 @@ struct ImageView: View { } .failure { Rectangle() - .background(Color.gray) + .fill(Color.gray) } } } diff --git a/Shared/Singleton/SessionManager.swift b/Shared/Singleton/SessionManager.swift index 19992bc6..f0f27de8 100644 --- a/Shared/Singleton/SessionManager.swift +++ b/Shared/Singleton/SessionManager.swift @@ -147,6 +147,9 @@ final class SessionManager { } func logout() { + let nc = NotificationCenter.default + nc.post(name: Notification.Name("didSignOut"), object: nil) + let keychain = KeychainSwift() keychain.accessGroup = "9R8RREG67J.me.vigue.jellyfin.sharedKeychain" keychain.delete("AccessToken_\(user?.user_id ?? "")") @@ -155,8 +158,5 @@ final class SessionManager { let deleteRequest = NSBatchDeleteRequest(objectIDs: [user.objectID]) user = nil _ = try? PersistenceController.shared.container.viewContext.execute(deleteRequest) - - let nc = NotificationCenter.default - nc.post(name: Notification.Name("didSignOut"), object: nil) } } diff --git a/WidgetExtension/NextUpWidget.swift b/WidgetExtension/NextUpWidget.swift index 108929ed..83b89c98 100644 --- a/WidgetExtension/NextUpWidget.swift +++ b/WidgetExtension/NextUpWidget.swift @@ -25,86 +25,91 @@ struct NextUpWidgetProvider: TimelineProvider { func getSnapshot(in context: Context, completion: @escaping (NextUpEntry) -> Void) { let currentDate = Date() - let server = ServerEnvironment.current.server! - let savedUser = SessionManager.current.user! + let server = ServerEnvironment.current.server + let savedUser = SessionManager.current.user var tempCancellables = Set() - - JellyfinAPI.basePath = server.baseURI ?? "" - TvShowsAPI.getNextUp(userId: savedUser.user_id, limit: 3, - fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people], - imageTypeLimit: 1, enableImageTypes: [.primary, .backdrop, .thumb]) - .subscribe(on: DispatchQueue.global(qos: .background)) - .sink(receiveCompletion: { result in - switch result { - case .finished: - break - case let .failure(error): - completion(NextUpEntry(date: currentDate, items: [], error: error)) - } - }, receiveValue: { response in - let dispatchGroup = DispatchGroup() - let items = response.items ?? [] - var downloadedItems = [(BaseItemDto, UIImage?)]() - items.enumerated().forEach { _, item in - dispatchGroup.enter() - ImagePipeline.shared.loadImage(with: item.getBackdropImage(maxWidth: 320)) { result in - guard case let .success(image) = result else { - dispatchGroup.leave() - return - } - downloadedItems.append((item, image.image)) - dispatchGroup.leave() + + if(server != nil && savedUser != nil) { + JellyfinAPI.basePath = server!.baseURI ?? "" + TvShowsAPI.getNextUp(userId: savedUser!.user_id, limit: 3, + fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people], + imageTypeLimit: 1, enableImageTypes: [.primary, .backdrop, .thumb]) + .subscribe(on: DispatchQueue.global(qos: .background)) + .sink(receiveCompletion: { result in + switch result { + case .finished: + break + case let .failure(error): + completion(NextUpEntry(date: currentDate, items: [], error: error)) + } + }, receiveValue: { response in + let dispatchGroup = DispatchGroup() + let items = response.items ?? [] + var downloadedItems = [(BaseItemDto, UIImage?)]() + items.enumerated().forEach { _, item in + dispatchGroup.enter() + ImagePipeline.shared.loadImage(with: item.getBackdropImage(maxWidth: 320)) { result in + guard case let .success(image) = result else { + dispatchGroup.leave() + return + } + downloadedItems.append((item, image.image)) + dispatchGroup.leave() + } } - } - dispatchGroup.notify(queue: .main) { - completion(NextUpEntry(date: currentDate, items: downloadedItems, error: nil)) - } - }) - .store(in: &tempCancellables) + dispatchGroup.notify(queue: .main) { + completion(NextUpEntry(date: currentDate, items: downloadedItems, error: nil)) + } + }) + .store(in: &tempCancellables) + } } func getTimeline(in context: Context, completion: @escaping (Timeline) -> Void) { let currentDate = Date() let entryDate = Calendar.current.date(byAdding: .hour, value: 1, to: currentDate)! - let server = ServerEnvironment.current.server! - let savedUser = SessionManager.current.user! + let server = ServerEnvironment.current.server + let savedUser = SessionManager.current.user var tempCancellables = Set() - JellyfinAPI.basePath = server.baseURI ?? "" - TvShowsAPI.getNextUp(userId: savedUser.user_id, limit: 3, - fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people], - imageTypeLimit: 1, enableImageTypes: [.primary, .backdrop, .thumb]) - .subscribe(on: DispatchQueue.global(qos: .background)) - .sink(receiveCompletion: { result in - switch result { - case .finished: - break - case let .failure(error): - completion(Timeline(entries: [NextUpEntry(date: currentDate, items: [], error: error)], policy: .after(entryDate))) - } - }, receiveValue: { response in - let dispatchGroup = DispatchGroup() - let items = response.items ?? [] - var downloadedItems = [(BaseItemDto, UIImage?)]() - items.enumerated().forEach { _, item in - dispatchGroup.enter() - ImagePipeline.shared.loadImage(with: item.getBackdropImage(maxWidth: 320)) { result in - guard case let .success(image) = result else { - dispatchGroup.leave() - return - } - downloadedItems.append((item, image.image)) - dispatchGroup.leave() + + if(server != nil && savedUser != nil) { + JellyfinAPI.basePath = server!.baseURI ?? "" + TvShowsAPI.getNextUp(userId: savedUser!.user_id, limit: 3, + fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people], + imageTypeLimit: 1, enableImageTypes: [.primary, .backdrop, .thumb]) + .subscribe(on: DispatchQueue.global(qos: .background)) + .sink(receiveCompletion: { result in + switch result { + case .finished: + break + case let .failure(error): + completion(Timeline(entries: [NextUpEntry(date: currentDate, items: [], error: error)], policy: .after(entryDate))) + } + }, receiveValue: { response in + let dispatchGroup = DispatchGroup() + let items = response.items ?? [] + var downloadedItems = [(BaseItemDto, UIImage?)]() + items.enumerated().forEach { _, item in + dispatchGroup.enter() + ImagePipeline.shared.loadImage(with: item.getBackdropImage(maxWidth: 320)) { result in + guard case let .success(image) = result else { + dispatchGroup.leave() + return + } + downloadedItems.append((item, image.image)) + dispatchGroup.leave() + } } - } - dispatchGroup.notify(queue: .main) { - completion(Timeline(entries: [NextUpEntry(date: currentDate, items: downloadedItems, error: nil)], - policy: .after(entryDate))) - } - }) - .store(in: &tempCancellables) + dispatchGroup.notify(queue: .main) { + completion(Timeline(entries: [NextUpEntry(date: currentDate, items: downloadedItems, error: nil)], + policy: .after(entryDate))) + } + }) + .store(in: &tempCancellables) + } } }