From 18cc3ac15d7264fc32773f267e9ff924fdd22760 Mon Sep 17 00:00:00 2001 From: Aiden Vigue Date: Thu, 10 Jun 2021 17:28:05 -0700 Subject: [PATCH] bugs bugs bugs *squash* --- JellyfinPlayer.xcodeproj/project.pbxproj | 12 +- JellyfinPlayer/ConnectToServerView.swift | 12 +- JellyfinPlayer/ContentView.swift | 117 +++++++++--------- JellyfinPlayer/ContinueWatchingView.swift | 8 +- JellyfinPlayer/Info.plist | 2 + JellyfinPlayer/LatestMediaView.swift | 17 +-- JellyfinPlayer/LibrarySearchView.swift | 12 +- JellyfinPlayer/LibraryView.swift | 12 +- JellyfinPlayer/MovieItemView.swift | 22 ++-- JellyfinPlayer/SeasonItemView.swift | 10 +- JellyfinPlayer/SeriesItemView.swift | 12 +- JellyfinPlayer/SettingsView.swift | 6 +- JellyfinPlayer/VideoPlayer.swift | 58 ++++++--- Shared/Extensions/APIExtensions.swift | 23 ++-- .../WidgetBackground.colorset/Contents.json | 20 +++ 15 files changed, 210 insertions(+), 133 deletions(-) create mode 100644 WidgetExtension/Assets.xcassets/WidgetBackground.colorset/Contents.json diff --git a/JellyfinPlayer.xcodeproj/project.pbxproj b/JellyfinPlayer.xcodeproj/project.pbxproj index 5b62592f..79754d12 100644 --- a/JellyfinPlayer.xcodeproj/project.pbxproj +++ b/JellyfinPlayer.xcodeproj/project.pbxproj @@ -677,7 +677,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image"; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 36; + CURRENT_PROJECT_VERSION = 41; DEVELOPMENT_ASSET_PATHS = "\"JellyfinPlayer tvOS/Preview Content\""; DEVELOPMENT_TEAM = 9R8RREG67J; ENABLE_PREVIEWS = YES; @@ -706,7 +706,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image"; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 36; + CURRENT_PROJECT_VERSION = 41; DEVELOPMENT_ASSET_PATHS = "\"JellyfinPlayer tvOS/Preview Content\""; DEVELOPMENT_TEAM = 9R8RREG67J; ENABLE_PREVIEWS = YES; @@ -857,7 +857,7 @@ CODE_SIGN_ENTITLEMENTS = JellyfinPlayer/JellyfinPlayer.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 36; + CURRENT_PROJECT_VERSION = 41; DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_TEAM = 9R8RREG67J; ENABLE_BITCODE = NO; @@ -889,7 +889,7 @@ CODE_SIGN_ENTITLEMENTS = JellyfinPlayer/JellyfinPlayer.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 36; + CURRENT_PROJECT_VERSION = 41; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_TEAM = 9R8RREG67J; @@ -920,7 +920,7 @@ ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; CODE_SIGN_ENTITLEMENTS = WidgetExtension.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 36; + CURRENT_PROJECT_VERSION = 41; DEVELOPMENT_TEAM = 9R8RREG67J; INFOPLIST_FILE = WidgetExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.1; @@ -945,7 +945,7 @@ ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; CODE_SIGN_ENTITLEMENTS = WidgetExtension.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 36; + CURRENT_PROJECT_VERSION = 41; DEVELOPMENT_TEAM = 9R8RREG67J; INFOPLIST_FILE = WidgetExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.1; diff --git a/JellyfinPlayer/ConnectToServerView.swift b/JellyfinPlayer/ConnectToServerView.swift index 5f2473e5..4be78c01 100644 --- a/JellyfinPlayer/ConnectToServerView.swift +++ b/JellyfinPlayer/ConnectToServerView.swift @@ -274,23 +274,23 @@ struct ConnectToServerView: View { ForEach(publicUsers, id: \.id) { publicUser in HStack { Button() { - if publicUser.hasPassword! { + if (publicUser.hasPassword ?? true) { lastPublicUsers = publicUsers - username = publicUser.name! + username = publicUser.name ?? "" usernameDisabled = true publicUsers = [] } else { publicUsers = [] password = "" - username = publicUser.name! + username = publicUser.name ?? "" doLogin() } } label: { HStack { - Text(publicUser.name!).font(.subheadline).fontWeight(.semibold) + Text(publicUser.name ?? "").font(.subheadline).fontWeight(.semibold) Spacer() - if publicUser.primaryImageTag != "" { - LazyImage(source: URL(string: "\(uri)/Users/\(publicUser.id!)/Images/Primary?width=200&quality=80&tag=\(publicUser.primaryImageTag!)")) + if publicUser.primaryImageTag != nil { + LazyImage(source: URL(string: "\(uri)/Users/\(publicUser.id ?? "")/Images/Primary?width=200&quality=80&tag=\(publicUser.primaryImageTag!)")) .contentMode(.aspectFill) .frame(width: 60, height: 60) .cornerRadius(30.0) diff --git a/JellyfinPlayer/ContentView.swift b/JellyfinPlayer/ContentView.swift index 2397805f..ed5e2e46 100644 --- a/JellyfinPlayer/ContentView.swift +++ b/JellyfinPlayer/ContentView.swift @@ -147,74 +147,77 @@ struct ContentView: View { .environmentObject(globalData) } else { if !jsi.did { - LoadingView(isShowing: $isLoading) { + if(isLoading) { + ProgressView() + } else { VStack { - if loadState == 0 { - TabView(selection: $tabSelection) { - NavigationView { - VStack(alignment: .leading) { - ScrollView { - Spacer().frame(height: orientationInfo.orientation == .portrait ? 0 : 16) - ContinueWatchingView() - NextUpView() - ForEach(librariesShowRecentlyAdded, id: \.self) { library_id in - VStack(alignment: .leading) { - HStack { - Text("Latest \(library_names[library_id] ?? "")").font(.title2).fontWeight(.bold) - .padding(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 16)) - Spacer() - NavigationLink(destination: LazyView { - LibraryView(usingParentID: library_id, - title: library_names[library_id] ?? "", usingFilters: recentFilterSet) - }) { + TabView(selection: $tabSelection) { + NavigationView { + VStack(alignment: .leading) { + ScrollView { + Spacer().frame(height: orientationInfo.orientation == .portrait ? 0 : 16) + ContinueWatchingView() + NextUpView() + + ForEach(librariesShowRecentlyAdded, id: \.self) { library_id in + VStack(alignment: .leading) { + HStack { + Text("Latest \(library_names[library_id] ?? "")").font(.title2).fontWeight(.bold) + .padding(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 16)) + Spacer() + NavigationLink(destination: LazyView { + LibraryView(usingParentID: library_id, + title: library_names[library_id] ?? "", usingFilters: recentFilterSet) + }) { + 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(usingParentID: library_id) - }.padding(EdgeInsets(top: 4, leading: 0, bottom: 0, trailing: 0)) - } - Spacer().frame(height: UIDevice.current.userInterfaceIdiom == .phone ? 20 : 30) + } + }.padding(EdgeInsets(top: 0, leading: 16, bottom: 0, trailing: 16)) + LatestMediaView(usingParentID: library_id) + }.padding(EdgeInsets(top: 4, leading: 0, bottom: 0, trailing: 0)) } - .navigationTitle("Home") - .toolbar { - ToolbarItemGroup(placement: .navigationBarTrailing) { - Button { - showSettingsPopover = true - } label: { - Image(systemName: "gear") - } + + Spacer().frame(height: UIDevice.current.userInterfaceIdiom == .phone ? 20 : 30) + } + .navigationTitle("Home") + .toolbar { + ToolbarItemGroup(placement: .navigationBarTrailing) { + Button { + showSettingsPopover = true + } label: { + Image(systemName: "gear") } } - .fullScreenCover(isPresented: $showSettingsPopover) { - SettingsView(viewModel: SettingsViewModel(), close: $showSettingsPopover) - } + } + .fullScreenCover(isPresented: $showSettingsPopover) { + SettingsView(viewModel: SettingsViewModel(), close: $showSettingsPopover) } } - .navigationViewStyle(StackNavigationViewStyle()) - .tabItem { - Text("Home") - Image(systemName: "house") - } - .tag("Home") - NavigationView { - LibraryListView(libraries: library_names) - } - .navigationViewStyle(StackNavigationViewStyle()) - .tabItem { - Text("All Media") - Image(systemName: "folder") - } - .tag("All Media") } - } else { - Text("Loading...") + .navigationViewStyle(StackNavigationViewStyle()) + .tabItem { + Text("Home") + Image(systemName: "house") + } + .tag("Home") + NavigationView { + LibraryListView(libraries: library_names) + } + .navigationViewStyle(StackNavigationViewStyle()) + .tabItem { + Text("All Media") + Image(systemName: "folder") + } + .tag("All Media") } } - } - .environmentObject(globalData) - .onAppear(perform: startup) - .alert(isPresented: $globalData.networkError) { - Alert(title: Text("Network Error"), message: Text("An error occured while performing a network request"), dismissButton: .default(Text("Ok"))) + .environmentObject(globalData) + .onAppear(perform: startup) + .alert(isPresented: $globalData.networkError) { + Alert(title: Text("Network Error"), message: Text("An error occured while performing a network request"), dismissButton: .default(Text("Ok"))) + } } } else { Text("Please wait...") diff --git a/JellyfinPlayer/ContinueWatchingView.swift b/JellyfinPlayer/ContinueWatchingView.swift index fe57b654..f68c7981 100644 --- a/JellyfinPlayer/ContinueWatchingView.swift +++ b/JellyfinPlayer/ContinueWatchingView.swift @@ -37,7 +37,7 @@ struct ContinueWatchingView: View { func onAppear() { DispatchQueue.global(qos: .userInitiated).async { - ItemsAPI.getResumeItems(userId: globalData.user.user_id ?? "", limit: 12, fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people], mediaTypes: ["Video"], imageTypeLimit: 1, enableImageTypes: [.primary, .backdrop, .thumb]) + ItemsAPI.getResumeItems(userId: globalData.user.user_id!, limit: 12, fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people], mediaTypes: ["Video"], imageTypeLimit: 1, enableImageTypes: [.primary, .backdrop, .thumb]) .sink(receiveCompletion: { completion in HandleAPIRequestCompletion(globalData: globalData, completion: completion) }, receiveValue: { response in @@ -56,7 +56,7 @@ struct ContinueWatchingView: View { NavigationLink(destination: ItemView(item: item)) { VStack(alignment: .leading) { Spacer().frame(height: 10) - LazyImage(source: item.getBackdropImage(baseURL: globalData.server.baseURI ?? "", maxWidth: 320)) + LazyImage(source: item.getBackdropImage(baseURL: globalData.server.baseURI!, maxWidth: 320)) .placeholderAndFailure { Image(uiImage: UIImage(blurHash: item.getBackdropImageBlurHash(), size: CGSize(width: 48, height: 32))!) .resizable() @@ -70,7 +70,7 @@ struct ContinueWatchingView: View { .overlay( Group { if item.type == "Episode" { - Text("\(item.name!)") + Text("\(item.name ?? "")") .font(.caption) .padding(6) .foregroundColor(.white) @@ -84,7 +84,7 @@ struct ContinueWatchingView: View { Rectangle() .fill(Color(red: 172/255, green: 92/255, blue: 195/255)) .mask(ProgressBar()) - .frame(width: CGFloat(item.userData!.playedPercentage!*3.2), height: 7) + .frame(width: CGFloat(item.userData?.playedPercentage ?? 0 * 3.2), height: 7) .padding(0), alignment: .bottomLeading ) Text(item.seriesName ?? item.name ?? "") diff --git a/JellyfinPlayer/Info.plist b/JellyfinPlayer/Info.plist index 8b4d6202..c4597a3f 100644 --- a/JellyfinPlayer/Info.plist +++ b/JellyfinPlayer/Info.plist @@ -46,6 +46,8 @@ network. UILaunchScreen + UILaunchStoryboardName + VideoPlayer UIRequiredDeviceCapabilities armv7 diff --git a/JellyfinPlayer/LatestMediaView.swift b/JellyfinPlayer/LatestMediaView.swift index d16cbcf9..21140641 100644 --- a/JellyfinPlayer/LatestMediaView.swift +++ b/JellyfinPlayer/LatestMediaView.swift @@ -46,9 +46,9 @@ struct LatestMediaView: View { NavigationLink(destination: ItemView(item: item)) { VStack(alignment: .leading) { Spacer().frame(height: 10) - LazyImage(source: item.getSeriesPrimaryImage(baseURL: globalData.server.baseURI!, maxWidth: 100)) + LazyImage(source: item.getPrimaryImage(baseURL: globalData.server.baseURI!, maxWidth: 100)) .placeholderAndFailure { - Image(uiImage: UIImage(blurHash: item.getSeriesPrimaryImageBlurHash(), size: CGSize(width: 16, height: 20))!) + Image(uiImage: UIImage(blurHash: item.getPrimaryImageBlurHash(), size: CGSize(width: 16, height: 20))!) .resizable() .frame(width: 100, height: 150) .cornerRadius(10) @@ -61,11 +61,14 @@ struct LatestMediaView: View { .fontWeight(.semibold) .foregroundColor(.primary) .lineLimit(1) - Text(String(item.productionYear ?? 0)) - .font(.caption) - .fontWeight(.semibold) - .foregroundColor(.secondary) - .lineLimit(1) + if(item.productionYear != nil) { + Text(String(item.productionYear ?? 0)) + .foregroundColor(.secondary) + .font(.caption) + .fontWeight(.medium) + } else { + Text(item.type!) + } }.frame(width: 100) Spacer().frame(width: 15) } diff --git a/JellyfinPlayer/LibrarySearchView.swift b/JellyfinPlayer/LibrarySearchView.swift index 9e2a9993..25adb040 100644 --- a/JellyfinPlayer/LibrarySearchView.swift +++ b/JellyfinPlayer/LibrarySearchView.swift @@ -84,10 +84,14 @@ struct LibrarySearchView: View { .fontWeight(.semibold) .foregroundColor(.primary) .lineLimit(1) - Text(String(item.productionYear ?? 0)) - .foregroundColor(.secondary) - .font(.caption) - .fontWeight(.medium) + if(item.productionYear != nil) { + Text(String(item.productionYear ?? 0)) + .foregroundColor(.secondary) + .font(.caption) + .fontWeight(.medium) + } else { + Text(item.type!) + } }.frame(width: 100) } } diff --git a/JellyfinPlayer/LibraryView.swift b/JellyfinPlayer/LibraryView.swift index 08812122..fdfcd64a 100644 --- a/JellyfinPlayer/LibraryView.swift +++ b/JellyfinPlayer/LibraryView.swift @@ -122,10 +122,14 @@ struct LibraryView: View { .fontWeight(.semibold) .foregroundColor(.primary) .lineLimit(1) - Text(String(item.productionYear ?? 0)) - .foregroundColor(.secondary) - .font(.caption) - .fontWeight(.medium) + if(item.productionYear != nil) { + Text(String(item.productionYear ?? 0)) + .foregroundColor(.secondary) + .font(.caption) + .fontWeight(.medium) + } else { + Text(item.type!) + } }.frame(width: 100) } } diff --git a/JellyfinPlayer/MovieItemView.swift b/JellyfinPlayer/MovieItemView.swift index 103c62a8..742203d1 100644 --- a/JellyfinPlayer/MovieItemView.swift +++ b/JellyfinPlayer/MovieItemView.swift @@ -96,15 +96,17 @@ struct MovieItemView: View { .fixedSize(horizontal: false, vertical: true) .offset(y: -4) HStack { - Text(String(item.productionYear ?? 0)).font(.subheadline) - .fontWeight(.medium) - .foregroundColor(.secondary) - .lineLimit(1) + if(item.productionYear != nil) { + Text(String(item.productionYear ?? 0)).font(.subheadline) + .fontWeight(.medium) + .foregroundColor(.secondary) + .lineLimit(1) + } Text(item.getItemRuntime()).font(.subheadline) .fontWeight(.medium) .foregroundColor(.secondary) .lineLimit(1) - if item.officialRating != "" { + if item.officialRating != nil { Text(item.officialRating!).font(.subheadline) .fontWeight(.semibold) .foregroundColor(.secondary) @@ -311,10 +313,12 @@ struct MovieItemView: View { .offset(x: 14, y: 0) Spacer().frame(height: 1) HStack { - Text(String(item.productionYear ?? 0)).font(.subheadline) - .fontWeight(.medium) - .foregroundColor(.secondary) - .lineLimit(1) + if(item.productionYear != nil) { + Text(String(item.productionYear ?? 0)).font(.subheadline) + .fontWeight(.medium) + .foregroundColor(.secondary) + .lineLimit(1) + } Text(item.getItemRuntime()).font(.subheadline) .fontWeight(.medium) .foregroundColor(.secondary) diff --git a/JellyfinPlayer/SeasonItemView.swift b/JellyfinPlayer/SeasonItemView.swift index 00e2bec8..62853887 100644 --- a/JellyfinPlayer/SeasonItemView.swift +++ b/JellyfinPlayer/SeasonItemView.swift @@ -296,11 +296,13 @@ struct SeasonItemView: View { } var body: some View { - LoadingView(isShowing: $isLoading) { + if(isLoading) { + ProgressView() + .onAppear(perform: onAppear) + } else { innerBody + .navigationBarTitleDisplayMode(.inline) + .navigationTitle("\(item.name ?? "") - \(item.seriesName ?? "")") } - .onAppear(perform: onAppear) - .navigationBarTitleDisplayMode(.inline) - .navigationTitle("\(item.name ?? "") - \(item.seriesName ?? "")") } } diff --git a/JellyfinPlayer/SeriesItemView.swift b/JellyfinPlayer/SeriesItemView.swift index 25ebf127..e44d0125 100644 --- a/JellyfinPlayer/SeriesItemView.swift +++ b/JellyfinPlayer/SeriesItemView.swift @@ -28,7 +28,7 @@ struct SeriesItemView: View { isLoading = true DispatchQueue.global(qos: .userInitiated).async { - TvShowsAPI.getSeasons(seriesId: item.id ?? "", fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people], isMissing: false) + TvShowsAPI.getSeasons(seriesId: item.id ?? "", fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people]) .sink(receiveCompletion: { completion in HandleAPIRequestCompletion(globalData: globalData, completion: completion) }, receiveValue: { response in @@ -51,7 +51,10 @@ struct SeriesItemView: View { @State private var tracks: [GridItem] = [] var body: some View { - LoadingView(isShowing: $isLoading) { + if(isLoading) { + ProgressView() + .onAppear(perform: onAppear) + } else { ScrollView(.vertical) { Spacer().frame(height: 16) LazyVGrid(columns: tracks) { @@ -87,9 +90,8 @@ struct SeriesItemView: View { recalcTracks() } } + .overrideViewPreference(.unspecified) + .navigationTitle(item.name ?? "") } - .overrideViewPreference(.unspecified) - .onAppear(perform: onAppear) - .navigationTitle(item.name ?? "") } } diff --git a/JellyfinPlayer/SettingsView.swift b/JellyfinPlayer/SettingsView.swift index ebeb3783..4b9964c3 100644 --- a/JellyfinPlayer/SettingsView.swift +++ b/JellyfinPlayer/SettingsView.swift @@ -21,9 +21,11 @@ struct SettingsView: View { @State private var outOfNetworkStreamBitrate: Int = 40_000_000 @State private var autoSelectSubtitles: Bool = false @State private var autoSelectSubtitlesLangcode: String = "none" + @State private var username: String = "" func onAppear() { let defaults = UserDefaults.standard + _username.wrappedValue = globalData.user.username! _inNetworkStreamBitrate.wrappedValue = defaults.integer(forKey: "InNetworkBandwidth") _outOfNetworkStreamBitrate.wrappedValue = defaults.integer(forKey: "OutOfNetworkBandwidth") _autoSelectSubtitles.wrappedValue = defaults.bool(forKey: "AutoSelectSubtitles") @@ -63,7 +65,7 @@ struct SettingsView: View { Section { HStack { - Text("Signed in as \(globalData.user.username!)").foregroundColor(.primary) + Text("Signed in as \(username)").foregroundColor(.primary) Spacer() Button { let fetchRequest: NSFetchRequest = NSFetchRequest(entityName: "Server") @@ -97,7 +99,7 @@ struct SettingsView: View { } } } - + .padding(.top, 12) .navigationBarTitle("Settings", displayMode: .inline) .toolbar { ToolbarItemGroup(placement: .navigationBarLeading) { diff --git a/JellyfinPlayer/VideoPlayer.swift b/JellyfinPlayer/VideoPlayer.swift index 0eb08659..419d5920 100644 --- a/JellyfinPlayer/VideoPlayer.swift +++ b/JellyfinPlayer/VideoPlayer.swift @@ -13,7 +13,7 @@ import MediaPlayer struct Subtitle { var name: String var id: Int32 - var url: URL + var url: URL? var delivery: SubtitleDeliveryMethod var codec: String } @@ -197,6 +197,7 @@ class PlayerViewController: UIViewController, VLCMediaDelegate, VLCMediaPlayerDe commandCenter.pauseCommand.addTarget { _ in self.mediaPlayer.pause() self.sendProgressReport(eventName: "pause") + self.mainActionButton.setImage(UIImage(systemName: "play"), for: .normal) return .success } @@ -204,6 +205,7 @@ class PlayerViewController: UIViewController, VLCMediaDelegate, VLCMediaPlayerDe commandCenter.playCommand.addTarget { _ in self.mediaPlayer.play() self.sendProgressReport(eventName: "unpause") + self.mainActionButton.setImage(UIImage(systemName: "pause"), for: .normal) return .success } @@ -260,6 +262,7 @@ class PlayerViewController: UIViewController, VLCMediaDelegate, VLCMediaPlayerDe // View has loaded. // Rotate to landscape only if necessary + UIViewController.attemptRotationToDeviceOrientation() mediaPlayer.perform(Selector(("setTextRendererFontSize:")), with: 14) @@ -269,9 +272,9 @@ class PlayerViewController: UIViewController, VLCMediaDelegate, VLCMediaPlayerDe mediaPlayer.drawable = videoContentView if manifest.type == "Movie" { - titleLabel.text = manifest.name + titleLabel.text = manifest.name ?? "" } else { - titleLabel.text = "S\(String(manifest.parentIndexNumber!)):E\(String(manifest.indexNumber!)) “\(manifest.name!)”" + titleLabel.text = "S\(String(manifest.parentIndexNumber ?? 0)):E\(String(manifest.indexNumber ?? 0)) “\(manifest.name ?? "")”" } // Fetch max bitrate from UserDefaults depending on current connection mode @@ -283,14 +286,15 @@ class PlayerViewController: UIViewController, VLCMediaDelegate, VLCMediaPlayerDe builder.setMaxBitrate(bitrate: maxBitrate) let profile = builder.buildProfile() - let playbackInfo = PlaybackInfoDto(userId: globalData.user.user_id, maxStreamingBitrate: Int(maxBitrate), startTimeTicks: manifest.userData?.playbackPositionTicks ?? 0, deviceProfile: profile, autoOpenLiveStream: true) + let playbackInfo = PlaybackInfoDto(userId: globalData.user.user_id!, maxStreamingBitrate: Int(maxBitrate), startTimeTicks: manifest.userData?.playbackPositionTicks ?? 0, deviceProfile: profile, autoOpenLiveStream: true) DispatchQueue.global(qos: .userInitiated).async { [self] in - MediaInfoAPI.getPostedPlaybackInfo(itemId: manifest.id!, userId: globalData.user.user_id, maxStreamingBitrate: Int(maxBitrate), startTimeTicks: manifest.userData?.playbackPositionTicks ?? 0, autoOpenLiveStream: true, playbackInfoDto: playbackInfo) + delegate?.showLoadingView(self) + MediaInfoAPI.getPostedPlaybackInfo(itemId: manifest.id!, userId: globalData.user.user_id!, maxStreamingBitrate: Int(maxBitrate), startTimeTicks: manifest.userData?.playbackPositionTicks ?? 0, autoOpenLiveStream: true, playbackInfoDto: playbackInfo) .sink(receiveCompletion: { completion in HandleAPIRequestCompletion(globalData: self.globalData, completion: completion) }, receiveValue: { [self] response in - playSessionId = response.playSessionId! + playSessionId = response.playSessionId ?? "" let mediaSource = response.mediaSources!.first.self! if mediaSource.transcodingUrl != nil { // Item is being transcoded by request of server @@ -299,14 +303,19 @@ class PlayerViewController: UIViewController, VLCMediaDelegate, VLCMediaPlayerDe item.videoType = .transcode item.videoUrl = streamURL! - let disableSubtitleTrack = Subtitle(name: "Disabled", id: -1, url: URL(string: "https://example.com")!, delivery: .embed, codec: "") + let disableSubtitleTrack = Subtitle(name: "Disabled", id: -1, url: nil, delivery: .embed, codec: "") subtitleTrackArray.append(disableSubtitleTrack) // Loop through media streams and add to array for stream in mediaSource.mediaStreams! { if stream.type == .subtitle { - let deliveryUrl = URL(string: "\(globalData.server.baseURI!)\(stream.deliveryUrl!)")! - let subtitle = Subtitle(name: stream.displayTitle!, id: Int32(stream.index!), url: deliveryUrl, delivery: stream.deliveryMethod!, codec: stream.codec!) + var deliveryUrl: URL? + if(stream.deliveryMethod == .external) { + deliveryUrl = URL(string: "\(globalData.server.baseURI!)\(stream.deliveryUrl!)")! + } else { + deliveryUrl = nil + } + let subtitle = Subtitle(name: stream.displayTitle ?? "Unknown", id: Int32(stream.index!), url: deliveryUrl, delivery: stream.deliveryMethod!, codec: stream.codec ?? "webvtt") subtitleTrackArray.append(subtitle) } @@ -335,14 +344,19 @@ class PlayerViewController: UIViewController, VLCMediaDelegate, VLCMediaPlayerDe item.videoUrl = streamURL item.videoType = .directPlay - let disableSubtitleTrack = Subtitle(name: "Disabled", id: -1, url: URL(string: "https://example.com")!, delivery: .embed, codec: "") + let disableSubtitleTrack = Subtitle(name: "Disabled", id: -1, url: nil, delivery: .embed, codec: "") subtitleTrackArray.append(disableSubtitleTrack) // Loop through media streams and add to array for stream in mediaSource.mediaStreams! { if stream.type == .subtitle { - let deliveryUrl = URL(string: "\(globalData.server.baseURI!)\(stream.deliveryUrl!)")! - let subtitle = Subtitle(name: stream.displayTitle!, id: Int32(stream.index!), url: deliveryUrl, delivery: stream.deliveryMethod!, codec: stream.codec!) + var deliveryUrl: URL? + if(stream.deliveryMethod == .external) { + deliveryUrl = URL(string: "\(globalData.server.baseURI!)\(stream.deliveryUrl!)")! + } else { + deliveryUrl = nil + } + let subtitle = Subtitle(name: stream.displayTitle ?? "Unknown", id: Int32(stream.index!), url: deliveryUrl, delivery: stream.deliveryMethod!, codec: stream.codec ?? "webvtt") subtitleTrackArray.append(subtitle) } @@ -365,21 +379,28 @@ class PlayerViewController: UIViewController, VLCMediaDelegate, VLCMediaPlayerDe playbackItem = item } - self.setupNowPlayingCC() - mediaPlayer.media = VLCMedia(url: playbackItem.videoUrl) mediaPlayer.play() - print(manifest.userData?.playbackPositionTicks ?? 0) - mediaPlayer.jumpForward(Int32(manifest.userData?.playbackPositionTicks ?? 0/10000000)) + + //1 second = 10,000,000 ticks + + let startTicks = round(Double(manifest.userData?.playbackPositionTicks ?? 0), toNearest: 10_000_000) + let startSeconds = Int32(startTicks) / 10_000_000 + mediaPlayer.jumpForward(startSeconds) + + //Pause and load captions into memory. mediaPlayer.pause() subtitleTrackArray.forEach { sub in if sub.id != -1 && sub.delivery == .external && sub.codec != "subrip" { - print("adding subs for id: \(sub.id) w/ url: \(sub.url)") - mediaPlayer.addPlaybackSlave(sub.url, type: .subtitle, enforce: false) + mediaPlayer.addPlaybackSlave(sub.url!, type: .subtitle, enforce: false) } } + + //Wait for captions to load delegate?.showLoadingView(self) while mediaPlayer.numberOfSubtitlesTracks != subtitleTrackArray.count - 1 {} + + //Select default track & resume playback mediaPlayer.currentVideoSubTitleIndex = selectedCaptionTrack mediaPlayer.pause() mediaPlayer.play() @@ -414,6 +435,7 @@ class PlayerViewController: UIViewController, VLCMediaDelegate, VLCMediaPlayerDe break case .playing : print("Video is playing") + self.setupNowPlayingCC() sendProgressReport(eventName: "unpause") delegate?.hideLoadingView(self) paused = false diff --git a/Shared/Extensions/APIExtensions.swift b/Shared/Extensions/APIExtensions.swift index ec41d52e..8ebc990f 100644 --- a/Shared/Extensions/APIExtensions.swift +++ b/Shared/Extensions/APIExtensions.swift @@ -52,7 +52,9 @@ extension BaseItemDto { if self.primaryImageAspectRatio ?? 0.0 < 1.0 { imageType = "Backdrop" - imageTag = (self.backdropImageTags ?? [""])[0] + if(!(self.backdropImageTags?.isEmpty ?? true)) { + imageTag = (self.backdropImageTags ?? [""])[0] + } } else { imageType = "Primary" imageTag = self.imageTags?["Primary"] ?? "" @@ -60,10 +62,13 @@ extension BaseItemDto { if imageTag == "" { imageType = "Backdrop" - imageTag = self.parentBackdropImageTags?[0] ?? "" + if(!(self.parentBackdropImageTags?.isEmpty ?? true)) { + imageTag = (self.parentBackdropImageTags ?? [""])[0] + } } + let x = UIScreen.main.nativeScale * CGFloat(maxWidth) - let urlString = "\(baseURL)/Items/\(self.id ?? "")/Images/\(imageType)?maxWidth=\(String(Int(x)))&quality=85&tag=\(imageTag)" + let urlString = "\(baseURL)/Items/\(self.id ?? "")/Images/\(imageType)?maxWidth=\(String(Int(x)))&quality=60&tag=\(imageTag)" return URL(string: urlString)! } @@ -75,7 +80,7 @@ extension BaseItemDto { print(imageTag) let x = UIScreen.main.nativeScale * CGFloat(maxWidth) - let urlString = "\(baseURL)/Items/\(self.parentBackdropItemId ?? "")/Images/\(imageType)?maxWidth=\(String(Int(x)))&quality=85&tag=\(imageTag)" + let urlString = "\(baseURL)/Items/\(self.parentBackdropItemId ?? "")/Images/\(imageType)?maxWidth=\(String(Int(x)))&quality=60&tag=\(imageTag)" return URL(string: urlString)! } @@ -83,7 +88,7 @@ extension BaseItemDto { let imageType = "Primary" let imageTag = self.seriesPrimaryImageTag ?? "" let x = UIScreen.main.nativeScale * CGFloat(maxWidth) - let urlString = "\(baseURL)/Items/\(self.seriesId ?? "")/Images/\(imageType)?maxWidth=\(String(Int(x)))&quality=85&tag=\(imageTag)" + let urlString = "\(baseURL)/Items/\(self.seriesId ?? "")/Images/\(imageType)?maxWidth=\(String(Int(x)))&quality=60&tag=\(imageTag)" return URL(string: urlString)! } @@ -96,7 +101,7 @@ extension BaseItemDto { } let x = UIScreen.main.nativeScale * CGFloat(maxWidth) - let urlString = "\(baseURL)/Items/\(self.id ?? "")/Images/\(imageType)?maxWidth=\(String(Int(x)))&quality=85&tag=\(imageTag)" + let urlString = "\(baseURL)/Items/\(self.id ?? "")/Images/\(imageType)?maxWidth=\(String(Int(x)))&quality=60&tag=\(imageTag)" return URL(string: urlString)! } @@ -128,6 +133,10 @@ extension BaseItemDto { } } +func round(_ value: Double, toNearest: Double) -> Double { + return round(value / toNearest) * toNearest +} + extension BaseItemPerson { func getImage(baseURL: String, maxWidth: Int) -> URL { let imageType = "Primary" @@ -135,7 +144,7 @@ extension BaseItemPerson { let x = UIScreen.main.nativeScale * CGFloat(maxWidth) - let urlString = "\(baseURL)/Items/\(self.id ?? "")/Images/\(imageType)?maxWidth=\(String(Int(x)))&quality=85&tag=\(imageTag)" + let urlString = "\(baseURL)/Items/\(self.id ?? "")/Images/\(imageType)?maxWidth=\(String(Int(x)))&quality=60&tag=\(imageTag)" return URL(string: urlString)! } diff --git a/WidgetExtension/Assets.xcassets/WidgetBackground.colorset/Contents.json b/WidgetExtension/Assets.xcassets/WidgetBackground.colorset/Contents.json new file mode 100644 index 00000000..274babba --- /dev/null +++ b/WidgetExtension/Assets.xcassets/WidgetBackground.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +}