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
+ }
+}