From e1d2edfca4b0d2b7ee12bba345f524a3bc264ce7 Mon Sep 17 00:00:00 2001 From: Aiden Vigue Date: Tue, 22 Jun 2021 00:10:54 -0400 Subject: [PATCH 1/5] 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) + } } } From e09c6d1da4ad30156a206a23ce0ee76b9cfab94c Mon Sep 17 00:00:00 2001 From: Aiden Vigue Date: Wed, 23 Jun 2021 13:08:02 -0400 Subject: [PATCH 2/5] idk --- JellyfinPlayer/SearchBarView.swift | 2 +- JellyfinPlayer/VideoPlayer.swift | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/JellyfinPlayer/SearchBarView.swift b/JellyfinPlayer/SearchBarView.swift index cb0c3513..50121818 100644 --- a/JellyfinPlayer/SearchBarView.swift +++ b/JellyfinPlayer/SearchBarView.swift @@ -17,7 +17,7 @@ struct SearchBar: View { var body: some View { HStack { - TextField("Search ...", text: $text) + TextField("Search...", text: $text) .padding(7) .padding(.horizontal, 16) .background(Color(.systemGray6)) diff --git a/JellyfinPlayer/VideoPlayer.swift b/JellyfinPlayer/VideoPlayer.swift index 282cae07..a8a64a06 100644 --- a/JellyfinPlayer/VideoPlayer.swift +++ b/JellyfinPlayer/VideoPlayer.swift @@ -280,6 +280,10 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe override var supportedInterfaceOrientations: UIInterfaceOrientationMask { return .landscape } + + override var shouldAutorotate: Bool { + return false + } func setupNowPlayingCC() { let commandCenter = MPRemoteCommandCenter.shared() @@ -370,10 +374,6 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe UIApplication.shared.beginReceivingRemoteControlEvents() } - override func remoteControlReceived(with event: UIEvent?) { - dump(event) - } - // MARK: viewDidLoad override func viewDidLoad() { if manifest.type == "Movie" { From 7c9b443bc72ea30fa3f7e2307b034c71aff7c2dd Mon Sep 17 00:00:00 2001 From: Aiden Vigue Date: Wed, 23 Jun 2021 14:50:30 -0400 Subject: [PATCH 3/5] Get rid of spacers on home screen Also, fancify the list view of libraries. --- JellyfinPlayer/ContinueWatchingView.swift | 15 ++-- JellyfinPlayer/HomeView.swift | 40 +++++------ JellyfinPlayer/LatestMediaView.swift | 20 +++--- JellyfinPlayer/LibraryListView.swift | 75 ++++++++++++++++---- JellyfinPlayer/NextUpView.swift | 12 ++-- JellyfinPlayer/VideoPlayer.swift | 14 ---- Shared/ViewModels/LibraryListViewModel.swift | 2 - 7 files changed, 99 insertions(+), 79 deletions(-) diff --git a/JellyfinPlayer/ContinueWatchingView.swift b/JellyfinPlayer/ContinueWatchingView.swift index 13ed6492..f9556d9a 100644 --- a/JellyfinPlayer/ContinueWatchingView.swift +++ b/JellyfinPlayer/ContinueWatchingView.swift @@ -36,14 +36,13 @@ struct ContinueWatchingView: View { var body: some View { ScrollView(.horizontal, showsIndicators: false) { LazyHStack { - Spacer().frame(width: 14) ForEach(items, id: \.id) { item in - NavigationLink(destination: ItemView(item: item)) { + NavigationLink(destination: LazyView { ItemView(item: item) }) { VStack(alignment: .leading) { - Spacer().frame(height: 10) ImageView(src: item.getBackdropImage(maxWidth: 320), bh: item.getBackdropImageBlurHash()) .frame(width: 320, height: 180) .cornerRadius(10) + .shadow(radius: 4) .overlay( Group { if item.type == "Episode" { @@ -70,14 +69,12 @@ struct ContinueWatchingView: View { .foregroundColor(.primary) .lineLimit(1) .frame(width: 320, alignment: .leading) - Spacer().frame(height: 5) - } + }.padding(.top, 10) + .padding(.bottom, 5) } - Spacer().frame(width: 16) - } - Spacer().frame(width: 2) + }.padding(.trailing, 16) }.frame(height: 215) - .padding(.bottom, 10) + .padding(EdgeInsets(top: 8, leading: 20, bottom: 10, trailing: 2)) } } } diff --git a/JellyfinPlayer/HomeView.swift b/JellyfinPlayer/HomeView.swift index ec9b6236..afeec96e 100644 --- a/JellyfinPlayer/HomeView.swift +++ b/JellyfinPlayer/HomeView.swift @@ -12,8 +12,6 @@ import SwiftUI struct HomeView: View { @StateObject var viewModel = HomeViewModel() - @Environment(\.horizontalSizeClass) var hSizeClass - @Environment(\.verticalSizeClass) var vSizeClass @State var showingSettings = false @ViewBuilder @@ -25,36 +23,32 @@ struct HomeView: View { LazyVStack(alignment: .leading) { if !viewModel.resumeItems.isEmpty { ContinueWatchingView(items: viewModel.resumeItems) - .padding(.top, hSizeClass == .compact && vSizeClass == .regular ? 0 : 16) } if !viewModel.nextUpItems.isEmpty { NextUpView(items: viewModel.nextUpItems) } if !viewModel.librariesShowRecentlyAddedIDs.isEmpty { ForEach(viewModel.librariesShowRecentlyAddedIDs, id: \.self) { libraryID in - VStack(alignment: .leading) { - let library = viewModel.libraries.first(where: { $0.id == libraryID }) - HStack { - Text("Latest \(library?.name ?? "")") - .font(.title2) - .fontWeight(.bold) - .padding(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 16)) - Spacer() - NavigationLink(destination: LazyView { - LibraryView(viewModel: .init(parentID: libraryID, filters: viewModel.recentFilterSet), title: library?.name ?? "") - }) { - HStack { - Text("See All").font(.subheadline).fontWeight(.bold) - Image(systemName: "chevron.right").font(Font.subheadline.bold()) - } + let library = viewModel.libraries.first(where: { $0.id == libraryID }) + HStack { + Text("Latest \(library?.name ?? "")") + .font(.title2) + .fontWeight(.bold) + Spacer() + NavigationLink(destination: LazyView { + LibraryView(viewModel: .init(parentID: libraryID, filters: viewModel.recentFilterSet), title: library?.name ?? "") + }) { + HStack { + Text("See All").font(.subheadline).fontWeight(.bold) + Image(systemName: "chevron.right").font(Font.subheadline.bold()) } - }.padding(EdgeInsets(top: 0, leading: 16, bottom: 0, trailing: 16)) - LatestMediaView(viewModel: .init(libraryID: libraryID)) - }.padding(EdgeInsets(top: 4, leading: 0, bottom: 0, trailing: 0)) + } + }.padding(.leading, 16) + .padding(.trailing, 16) + LatestMediaView(viewModel: .init(libraryID: libraryID)) } } } - .padding(.top, hSizeClass == .compact && vSizeClass == .regular ? 0 : 16) .padding(.bottom, UIDevice.current.userInterfaceIdiom == .phone ? 20 : 30) } } @@ -63,6 +57,7 @@ struct HomeView: View { var body: some View { innerBody .navigationTitle(MainTabView.Tab.home.localized) + /* .toolbar { ToolbarItemGroup(placement: .navigationBarTrailing) { Button { @@ -75,5 +70,6 @@ struct HomeView: View { .fullScreenCover(isPresented: $showingSettings) { SettingsView(viewModel: SettingsViewModel(), close: $showingSettings) } + */ } } diff --git a/JellyfinPlayer/LatestMediaView.swift b/JellyfinPlayer/LatestMediaView.swift index 0b351c7e..6fa24521 100644 --- a/JellyfinPlayer/LatestMediaView.swift +++ b/JellyfinPlayer/LatestMediaView.swift @@ -8,21 +8,19 @@ import SwiftUI struct LatestMediaView: View { - @ObservedObject var viewModel: LatestMediaViewModel + @StateObject var viewModel: LatestMediaViewModel var body: some View { ScrollView(.horizontal, showsIndicators: false) { LazyHStack { - Spacer().frame(width: 16) ForEach(viewModel.items, id: \.id) { item in if item.type == "Series" || item.type == "Movie" { - NavigationLink(destination: ItemView(item: item)) { + NavigationLink(destination: LazyView { ItemView(item: item) }) { VStack(alignment: .leading) { - Spacer().frame(height: 10) ImageView(src: item.getPrimaryImage(maxWidth: 100), bh: item.getPrimaryImageBlurHash()) .frame(width: 100, height: 150) .cornerRadius(10) - Spacer().frame(height: 5) + .shadow(radius: 4) Text(item.seriesName ?? item.name ?? "") .font(.caption) .fontWeight(.semibold) @@ -35,15 +33,15 @@ struct LatestMediaView: View { .fontWeight(.medium) } else { Text(item.type!) + .foregroundColor(.secondary) + .font(.caption) + .fontWeight(.medium) } }.frame(width: 100) - Spacer().frame(width: 15) } } - } - } - .frame(height: 190) - } - .padding(EdgeInsets(top: -2, leading: 0, bottom: 0, trailing: 0)).frame(height: 190) + }.padding(.trailing, 16) + }.padding(.leading, 20) + }.frame(height: 195) } } diff --git a/JellyfinPlayer/LibraryListView.swift b/JellyfinPlayer/LibraryListView.swift index a4afa775..e527841d 100644 --- a/JellyfinPlayer/LibraryListView.swift +++ b/JellyfinPlayer/LibraryListView.swift @@ -12,27 +12,74 @@ struct LibraryListView: View { @StateObject var viewModel = LibraryListViewModel() var body: some View { - List(viewModel.libraries, id: \.self) { library in - switch library.id { - case "favorites": + ScrollView { + LazyVStack() { NavigationLink(destination: LazyView { - LibraryView(viewModel: .init(filters: viewModel.withFavorites), title: library.name ?? "") + LibraryView(viewModel: .init(filters: viewModel.withFavorites), title: "Favorites") }) { - Text(library.name ?? "") + ZStack() { + HStack() { + Spacer() + Text("Your Favorites") + .foregroundColor(.black) + .font(.subheadline) + .fontWeight(.semibold) + Spacer() + } + } + .padding(16) + .background(Color.white) + .frame(minWidth: 100, maxWidth: .infinity) } - case "genres": + .cornerRadius(10) + .shadow(radius: 5) + .padding(.bottom, 5) + NavigationLink(destination: LazyView { - EmptyView() + Text("WIP") }) { - Text(library.name ?? "") + ZStack() { + HStack() { + Spacer() + Text("All Genres") + .foregroundColor(.black) + .font(.subheadline) + .fontWeight(.semibold) + Spacer() + } + } + .padding(16) + .background(Color.white) + .frame(minWidth: 100, maxWidth: .infinity) } - default: - NavigationLink(destination: LazyView { - LibraryView(viewModel: .init(parentID: library.id), title: library.name ?? "") - }) { - Text(library.name ?? "") + .cornerRadius(10) + .shadow(radius: 5) + .padding(.bottom, 15) + + ForEach(viewModel.libraries, id: \.id) { library in + NavigationLink(destination: LazyView { + LibraryView(viewModel: .init(parentID: library.id), title: library.name ?? "") + }) { + ZStack() { + ImageView(src: library.getPrimaryImage(maxWidth: 500)) + .opacity(0.4) + HStack() { + Spacer() + Text(library.name ?? "") + .foregroundColor(.white) + .font(.title2) + .fontWeight(.semibold) + Spacer() + }.padding(32) + }.background(Color.black) + .frame(minWidth: 100, maxWidth: .infinity) + } + .cornerRadius(10) + .shadow(radius: 5) + .padding(.bottom, 5) } - } + }.padding(.leading, 16) + .padding(.trailing, 16) } .navigationTitle("All Media") .toolbar { diff --git a/JellyfinPlayer/NextUpView.swift b/JellyfinPlayer/NextUpView.swift index affe57f4..411a1a37 100644 --- a/JellyfinPlayer/NextUpView.swift +++ b/JellyfinPlayer/NextUpView.swift @@ -18,17 +18,16 @@ struct NextUpView: View { Text("Next Up") .font(.title2) .fontWeight(.bold) - .padding(EdgeInsets(top: 0, leading: 16, bottom: 0, trailing: 16)) + .padding(.leading, 16) ScrollView(.horizontal, showsIndicators: false) { LazyHStack { - Spacer().frame(width: 16) ForEach(items, id: \.id) { item in - NavigationLink(destination: ItemView(item: item)) { + NavigationLink(destination: LazyView { ItemView(item: item) }) { VStack(alignment: .leading) { ImageView(src: item.getSeriesPrimaryImage(maxWidth: 100), bh: item.getSeriesPrimaryImageBlurHash()) .frame(width: 100, height: 150) .cornerRadius(10) - Spacer().frame(height: 5) + .shadow(radius: 4) Text(item.seriesName!) .font(.caption) .fontWeight(.semibold) @@ -40,13 +39,12 @@ struct NextUpView: View { .foregroundColor(.secondary) .lineLimit(1) }.frame(width: 100) - Spacer().frame(width: 16) } - } + }.padding(.trailing, 16) } + .padding(.leading, 20) } .frame(height: 200) } - .padding(EdgeInsets(top: 4, leading: 0, bottom: 0, trailing: 0)) } } diff --git a/JellyfinPlayer/VideoPlayer.swift b/JellyfinPlayer/VideoPlayer.swift index a8a64a06..f6b11316 100644 --- a/JellyfinPlayer/VideoPlayer.swift +++ b/JellyfinPlayer/VideoPlayer.swift @@ -276,14 +276,6 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe self.mainActionButton.setImage(UIImage(systemName: "pause"), for: .normal) } } - - override var supportedInterfaceOrientations: UIInterfaceOrientationMask { - return .landscape - } - - override var shouldAutorotate: Bool { - return false - } func setupNowPlayingCC() { let commandCenter = MPRemoteCommandCenter.shared() @@ -422,12 +414,6 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe overrideUserInterfaceStyle = .dark self.tabBarController?.tabBar.isHidden = true 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") diff --git a/Shared/ViewModels/LibraryListViewModel.swift b/Shared/ViewModels/LibraryListViewModel.swift index 6fcffe23..a25ae3aa 100644 --- a/Shared/ViewModels/LibraryListViewModel.swift +++ b/Shared/ViewModels/LibraryListViewModel.swift @@ -20,8 +20,6 @@ final class LibraryListViewModel: ViewModel { override init() { super.init() - libraries.append(.init(name: "Favorites", id: "favorites")) - libraries.append(.init(name: "Genres", id: "genres")) requestLibraries() } From d6f6378bda7307b91723125304da2245f999a7a6 Mon Sep 17 00:00:00 2001 From: Aiden Vigue Date: Wed, 23 Jun 2021 15:21:44 -0400 Subject: [PATCH 4/5] Refactor list view. Fix some images not appearing. --- JellyfinPlayer/LibraryListView.swift | 42 +++++++++++++++------------ Shared/Extensions/APIExtensions.swift | 15 ++++++---- 2 files changed, 33 insertions(+), 24 deletions(-) diff --git a/JellyfinPlayer/LibraryListView.swift b/JellyfinPlayer/LibraryListView.swift index e527841d..9cf11ecd 100644 --- a/JellyfinPlayer/LibraryListView.swift +++ b/JellyfinPlayer/LibraryListView.swift @@ -57,26 +57,30 @@ struct LibraryListView: View { .padding(.bottom, 15) ForEach(viewModel.libraries, id: \.id) { library in - NavigationLink(destination: LazyView { - LibraryView(viewModel: .init(parentID: library.id), title: library.name ?? "") - }) { - ZStack() { - ImageView(src: library.getPrimaryImage(maxWidth: 500)) - .opacity(0.4) - HStack() { - Spacer() - Text(library.name ?? "") - .foregroundColor(.white) - .font(.title2) - .fontWeight(.semibold) - Spacer() - }.padding(32) - }.background(Color.black) - .frame(minWidth: 100, maxWidth: .infinity) + if(library.collectionType ?? "" == "movies" || library.collectionType ?? "" == "tvshows") { + NavigationLink(destination: LazyView { + LibraryView(viewModel: .init(parentID: library.id), title: library.name ?? "") + }) { + ZStack() { + ImageView(src: library.getPrimaryImage(maxWidth: 500), bh: library.getPrimaryImageBlurHash()) + .opacity(0.4) + HStack() { + Spacer() + Text(library.name ?? "") + .foregroundColor(.white) + .font(.title2) + .fontWeight(.semibold) + Spacer() + }.padding(32) + }.background(Color.black) + .frame(minWidth: 100, maxWidth: .infinity) + } + .cornerRadius(10) + .shadow(radius: 5) + .padding(.bottom, 5) + } else { + EmptyView() } - .cornerRadius(10) - .shadow(radius: 5) - .padding(.bottom, 5) } }.padding(.leading, 16) .padding(.trailing, 16) diff --git a/Shared/Extensions/APIExtensions.swift b/Shared/Extensions/APIExtensions.swift index 03d865e5..139842f2 100644 --- a/Shared/Extensions/APIExtensions.swift +++ b/Shared/Extensions/APIExtensions.swift @@ -49,6 +49,7 @@ extension BaseItemDto { func getBackdropImage(maxWidth: Int) -> URL { var imageType = "" var imageTag = "" + var imageItemId = self.id ?? "" if self.primaryImageAspectRatio ?? 0.0 < 1.0 { imageType = "Backdrop" @@ -60,15 +61,16 @@ extension BaseItemDto { imageTag = self.imageTags?["Primary"] ?? "" } - if imageTag == "" { + if imageTag == "" || imageItemId == "" { imageType = "Backdrop" if !(self.parentBackdropImageTags?.isEmpty ?? true) { imageTag = (self.parentBackdropImageTags ?? [""])[0] + imageItemId = self.parentBackdropItemId ?? "" } } let x = UIScreen.main.nativeScale * CGFloat(maxWidth) - let urlString = "\(ServerEnvironment.current.server.baseURI!)/Items/\(self.id ?? "")/Images/\(imageType)?maxWidth=\(String(Int(x)))&quality=60&tag=\(imageTag)" + let urlString = "\(ServerEnvironment.current.server.baseURI!)/Items/\(imageItemId)/Images/\(imageType)?maxWidth=\(String(Int(x)))&quality=60&tag=\(imageTag)" return URL(string: urlString)! } @@ -92,13 +94,16 @@ extension BaseItemDto { func getPrimaryImage(maxWidth: Int) -> URL { let imageType = "Primary" var imageTag = self.imageTags?["Primary"] ?? "" - - if imageTag == "" { + var imageItemId = self.id ?? "" + + if imageTag == "" || imageItemId == "" { imageTag = self.seriesPrimaryImageTag ?? "" + imageItemId = self.seriesId ?? "" } + let x = UIScreen.main.nativeScale * CGFloat(maxWidth) - let urlString = "\(ServerEnvironment.current.server.baseURI!)/Items/\(self.id ?? "")/Images/\(imageType)?maxWidth=\(String(Int(x)))&quality=60&tag=\(imageTag)" + let urlString = "\(ServerEnvironment.current.server.baseURI!)/Items/\(imageItemId)/Images/\(imageType)?maxWidth=\(String(Int(x)))&quality=60&tag=\(imageTag)" return URL(string: urlString)! } From 8d9108999b4eb9947c32e5a6fcacf1122eaa15f7 Mon Sep 17 00:00:00 2001 From: Aiden Vigue Date: Thu, 24 Jun 2021 00:06:35 -0400 Subject: [PATCH 5/5] build 53 --- JellyfinPlayer.xcodeproj/project.pbxproj | 12 ++++----- JellyfinPlayer/ItemView.swift | 2 +- JellyfinPlayer/JellyfinPlayerApp.swift | 2 +- JellyfinPlayer/VideoPlayer.swift | 32 +++++++++++++++++++++--- 4 files changed, 37 insertions(+), 11 deletions(-) diff --git a/JellyfinPlayer.xcodeproj/project.pbxproj b/JellyfinPlayer.xcodeproj/project.pbxproj index 1f79ece7..77839676 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 = 50; + CURRENT_PROJECT_VERSION = 53; 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 = 50; + CURRENT_PROJECT_VERSION = 53; 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 = 50; + CURRENT_PROJECT_VERSION = 53; 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 = 50; + CURRENT_PROJECT_VERSION = 53; 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 = 50; + CURRENT_PROJECT_VERSION = 53; 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 = 50; + CURRENT_PROJECT_VERSION = 53; DEVELOPMENT_TEAM = 9R8RREG67J; INFOPLIST_FILE = WidgetExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.1; diff --git a/JellyfinPlayer/ItemView.swift b/JellyfinPlayer/ItemView.swift index a60937cc..2eb8f9a2 100644 --- a/JellyfinPlayer/ItemView.swift +++ b/JellyfinPlayer/ItemView.swift @@ -34,7 +34,7 @@ struct ItemView: View { .statusBar(hidden: true) .edgesIgnoringSafeArea(.all) .prefersHomeIndicatorAutoHidden(true) - }, isActive: $videoPlayerItem.shouldShowPlayer) { + }.supportedOrientations(.landscape), isActive: $videoPlayerItem.shouldShowPlayer) { EmptyView() } VStack { diff --git a/JellyfinPlayer/JellyfinPlayerApp.swift b/JellyfinPlayer/JellyfinPlayerApp.swift index 1afbf8a2..29723dcb 100644 --- a/JellyfinPlayer/JellyfinPlayerApp.swift +++ b/JellyfinPlayer/JellyfinPlayerApp.swift @@ -103,10 +103,10 @@ class PreferenceUIHostingController: UIHostingController { public var _orientations: UIInterfaceOrientationMask = .allButUpsideDown { didSet { - UIViewController.attemptRotationToDeviceOrientation() if _orientations == .landscape { let value = UIInterfaceOrientation.landscapeRight.rawValue UIDevice.current.setValue(value, forKey: "orientation") + UIViewController.attemptRotationToDeviceOrientation() } } } diff --git a/JellyfinPlayer/VideoPlayer.swift b/JellyfinPlayer/VideoPlayer.swift index f6b11316..16fa0fbb 100644 --- a/JellyfinPlayer/VideoPlayer.swift +++ b/JellyfinPlayer/VideoPlayer.swift @@ -373,7 +373,12 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe } else { titleLabel.text = "S\(String(manifest.parentIndexNumber ?? 0)):E\(String(manifest.indexNumber ?? 0)) “\(manifest.name ?? "")”" } - + + if(!UIDevice.current.orientation.isLandscape || UIDevice.current.orientation.isFlat) { + let value = UIInterfaceOrientation.landscapeRight.rawValue + UIDevice.current.setValue(value, forKey: "orientation") + UIViewController.attemptRotationToDeviceOrientation() + } super.viewDidLoad() } @@ -408,6 +413,13 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe } } + override func viewWillDisappear(_ animated: Bool) { + self.tabBarController?.tabBar.isHidden = false + self.navigationController?.isNavigationBarHidden = false + overrideUserInterfaceStyle = .unspecified + super.viewWillDisappear(animated) + } + //MARK: viewDidAppear override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) @@ -435,8 +447,22 @@ class PlayerViewController: UIViewController, GCKDiscoveryManagerListener, GCKRe DispatchQueue.global(qos: .userInitiated).async { [self] in delegate?.showLoadingView(self) MediaInfoAPI.getPostedPlaybackInfo(itemId: manifest.id!, userId: SessionManager.current.user.user_id!, maxStreamingBitrate: Int(maxBitrate), startTimeTicks: manifest.userData?.playbackPositionTicks ?? 0, autoOpenLiveStream: true, playbackInfoDto: playbackInfo) - .sink(receiveCompletion: { result in - print(result) + .sink(receiveCompletion: { completion in + switch completion { + case .finished: + break + case .failure(let error): + if let err = error as? ErrorResponse { + switch err { + case .error(401, _, _, _): + self.delegate?.exitPlayer(self) + SessionManager.current.logout() + case .error: + self.delegate?.exitPlayer(self) + } + } + break + } }, receiveValue: { [self] response in playSessionId = response.playSessionId ?? "" let mediaSource = response.mediaSources!.first.self!