bugs bugs bugs *squash*

This commit is contained in:
Aiden Vigue 2021-06-10 17:28:05 -07:00
parent 6cf0bc1f11
commit 18cc3ac15d
No known key found for this signature in database
GPG Key ID: 86E07E6D0CC00721
15 changed files with 210 additions and 133 deletions

View File

@ -677,7 +677,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image"; ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image";
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 36; CURRENT_PROJECT_VERSION = 41;
DEVELOPMENT_ASSET_PATHS = "\"JellyfinPlayer tvOS/Preview Content\""; DEVELOPMENT_ASSET_PATHS = "\"JellyfinPlayer tvOS/Preview Content\"";
DEVELOPMENT_TEAM = 9R8RREG67J; DEVELOPMENT_TEAM = 9R8RREG67J;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
@ -706,7 +706,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image"; ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image";
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 36; CURRENT_PROJECT_VERSION = 41;
DEVELOPMENT_ASSET_PATHS = "\"JellyfinPlayer tvOS/Preview Content\""; DEVELOPMENT_ASSET_PATHS = "\"JellyfinPlayer tvOS/Preview Content\"";
DEVELOPMENT_TEAM = 9R8RREG67J; DEVELOPMENT_TEAM = 9R8RREG67J;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
@ -857,7 +857,7 @@
CODE_SIGN_ENTITLEMENTS = JellyfinPlayer/JellyfinPlayer.entitlements; CODE_SIGN_ENTITLEMENTS = JellyfinPlayer/JellyfinPlayer.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 36; CURRENT_PROJECT_VERSION = 41;
DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_ASSET_PATHS = "";
DEVELOPMENT_TEAM = 9R8RREG67J; DEVELOPMENT_TEAM = 9R8RREG67J;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
@ -889,7 +889,7 @@
CODE_SIGN_ENTITLEMENTS = JellyfinPlayer/JellyfinPlayer.entitlements; CODE_SIGN_ENTITLEMENTS = JellyfinPlayer/JellyfinPlayer.entitlements;
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 36; CURRENT_PROJECT_VERSION = 41;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_ASSET_PATHS = "";
DEVELOPMENT_TEAM = 9R8RREG67J; DEVELOPMENT_TEAM = 9R8RREG67J;
@ -920,7 +920,7 @@
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
CODE_SIGN_ENTITLEMENTS = WidgetExtension.entitlements; CODE_SIGN_ENTITLEMENTS = WidgetExtension.entitlements;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 36; CURRENT_PROJECT_VERSION = 41;
DEVELOPMENT_TEAM = 9R8RREG67J; DEVELOPMENT_TEAM = 9R8RREG67J;
INFOPLIST_FILE = WidgetExtension/Info.plist; INFOPLIST_FILE = WidgetExtension/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 14.1; IPHONEOS_DEPLOYMENT_TARGET = 14.1;
@ -945,7 +945,7 @@
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
CODE_SIGN_ENTITLEMENTS = WidgetExtension.entitlements; CODE_SIGN_ENTITLEMENTS = WidgetExtension.entitlements;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 36; CURRENT_PROJECT_VERSION = 41;
DEVELOPMENT_TEAM = 9R8RREG67J; DEVELOPMENT_TEAM = 9R8RREG67J;
INFOPLIST_FILE = WidgetExtension/Info.plist; INFOPLIST_FILE = WidgetExtension/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 14.1; IPHONEOS_DEPLOYMENT_TARGET = 14.1;

View File

@ -274,23 +274,23 @@ struct ConnectToServerView: View {
ForEach(publicUsers, id: \.id) { publicUser in ForEach(publicUsers, id: \.id) { publicUser in
HStack { HStack {
Button() { Button() {
if publicUser.hasPassword! { if (publicUser.hasPassword ?? true) {
lastPublicUsers = publicUsers lastPublicUsers = publicUsers
username = publicUser.name! username = publicUser.name ?? ""
usernameDisabled = true usernameDisabled = true
publicUsers = [] publicUsers = []
} else { } else {
publicUsers = [] publicUsers = []
password = "" password = ""
username = publicUser.name! username = publicUser.name ?? ""
doLogin() doLogin()
} }
} label: { } label: {
HStack { HStack {
Text(publicUser.name!).font(.subheadline).fontWeight(.semibold) Text(publicUser.name ?? "").font(.subheadline).fontWeight(.semibold)
Spacer() Spacer()
if publicUser.primaryImageTag != "" { if publicUser.primaryImageTag != nil {
LazyImage(source: URL(string: "\(uri)/Users/\(publicUser.id!)/Images/Primary?width=200&quality=80&tag=\(publicUser.primaryImageTag!)")) LazyImage(source: URL(string: "\(uri)/Users/\(publicUser.id ?? "")/Images/Primary?width=200&quality=80&tag=\(publicUser.primaryImageTag!)"))
.contentMode(.aspectFill) .contentMode(.aspectFill)
.frame(width: 60, height: 60) .frame(width: 60, height: 60)
.cornerRadius(30.0) .cornerRadius(30.0)

View File

@ -147,74 +147,77 @@ struct ContentView: View {
.environmentObject(globalData) .environmentObject(globalData)
} else { } else {
if !jsi.did { if !jsi.did {
LoadingView(isShowing: $isLoading) { if(isLoading) {
ProgressView()
} else {
VStack { VStack {
if loadState == 0 { TabView(selection: $tabSelection) {
TabView(selection: $tabSelection) { NavigationView {
NavigationView { VStack(alignment: .leading) {
VStack(alignment: .leading) { ScrollView {
ScrollView { Spacer().frame(height: orientationInfo.orientation == .portrait ? 0 : 16)
Spacer().frame(height: orientationInfo.orientation == .portrait ? 0 : 16) ContinueWatchingView()
ContinueWatchingView() NextUpView()
NextUpView()
ForEach(librariesShowRecentlyAdded, id: \.self) { library_id in ForEach(librariesShowRecentlyAdded, id: \.self) { library_id in
VStack(alignment: .leading) { VStack(alignment: .leading) {
HStack { HStack {
Text("Latest \(library_names[library_id] ?? "")").font(.title2).fontWeight(.bold) Text("Latest \(library_names[library_id] ?? "")").font(.title2).fontWeight(.bold)
.padding(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 16)) .padding(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 16))
Spacer() Spacer()
NavigationLink(destination: LazyView { NavigationLink(destination: LazyView {
LibraryView(usingParentID: library_id, LibraryView(usingParentID: library_id,
title: library_names[library_id] ?? "", usingFilters: recentFilterSet) title: library_names[library_id] ?? "", usingFilters: recentFilterSet)
}) { }) {
HStack() {
Text("See All").font(.subheadline).fontWeight(.bold) 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: 0, leading: 16, bottom: 0, trailing: 16))
}.padding(EdgeInsets(top: 4, leading: 0, bottom: 0, trailing: 0)) LatestMediaView(usingParentID: library_id)
} }.padding(EdgeInsets(top: 4, leading: 0, bottom: 0, trailing: 0))
Spacer().frame(height: UIDevice.current.userInterfaceIdiom == .phone ? 20 : 30)
} }
.navigationTitle("Home")
.toolbar { Spacer().frame(height: UIDevice.current.userInterfaceIdiom == .phone ? 20 : 30)
ToolbarItemGroup(placement: .navigationBarTrailing) { }
Button { .navigationTitle("Home")
showSettingsPopover = true .toolbar {
} label: { ToolbarItemGroup(placement: .navigationBarTrailing) {
Image(systemName: "gear") 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 { .navigationViewStyle(StackNavigationViewStyle())
Text("Loading...") .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)
.environmentObject(globalData) .onAppear(perform: startup)
.onAppear(perform: startup) .alert(isPresented: $globalData.networkError) {
.alert(isPresented: $globalData.networkError) { Alert(title: Text("Network Error"), message: Text("An error occured while performing a network request"), dismissButton: .default(Text("Ok")))
Alert(title: Text("Network Error"), message: Text("An error occured while performing a network request"), dismissButton: .default(Text("Ok"))) }
} }
} else { } else {
Text("Please wait...") Text("Please wait...")

View File

@ -37,7 +37,7 @@ struct ContinueWatchingView: View {
func onAppear() { func onAppear() {
DispatchQueue.global(qos: .userInitiated).async { 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 .sink(receiveCompletion: { completion in
HandleAPIRequestCompletion(globalData: globalData, completion: completion) HandleAPIRequestCompletion(globalData: globalData, completion: completion)
}, receiveValue: { response in }, receiveValue: { response in
@ -56,7 +56,7 @@ struct ContinueWatchingView: View {
NavigationLink(destination: ItemView(item: item)) { NavigationLink(destination: ItemView(item: item)) {
VStack(alignment: .leading) { VStack(alignment: .leading) {
Spacer().frame(height: 10) 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 { .placeholderAndFailure {
Image(uiImage: UIImage(blurHash: item.getBackdropImageBlurHash(), size: CGSize(width: 48, height: 32))!) Image(uiImage: UIImage(blurHash: item.getBackdropImageBlurHash(), size: CGSize(width: 48, height: 32))!)
.resizable() .resizable()
@ -70,7 +70,7 @@ struct ContinueWatchingView: View {
.overlay( .overlay(
Group { Group {
if item.type == "Episode" { if item.type == "Episode" {
Text("\(item.name!)") Text("\(item.name ?? "")")
.font(.caption) .font(.caption)
.padding(6) .padding(6)
.foregroundColor(.white) .foregroundColor(.white)
@ -84,7 +84,7 @@ struct ContinueWatchingView: View {
Rectangle() Rectangle()
.fill(Color(red: 172/255, green: 92/255, blue: 195/255)) .fill(Color(red: 172/255, green: 92/255, blue: 195/255))
.mask(ProgressBar()) .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 .padding(0), alignment: .bottomLeading
) )
Text(item.seriesName ?? item.name ?? "") Text(item.seriesName ?? item.name ?? "")

View File

@ -46,6 +46,8 @@ network.</string>
<true/> <true/>
<key>UILaunchScreen</key> <key>UILaunchScreen</key>
<dict/> <dict/>
<key>UILaunchStoryboardName</key>
<string>VideoPlayer</string>
<key>UIRequiredDeviceCapabilities</key> <key>UIRequiredDeviceCapabilities</key>
<array> <array>
<string>armv7</string> <string>armv7</string>

View File

@ -46,9 +46,9 @@ struct LatestMediaView: View {
NavigationLink(destination: ItemView(item: item)) { NavigationLink(destination: ItemView(item: item)) {
VStack(alignment: .leading) { VStack(alignment: .leading) {
Spacer().frame(height: 10) 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 { .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() .resizable()
.frame(width: 100, height: 150) .frame(width: 100, height: 150)
.cornerRadius(10) .cornerRadius(10)
@ -61,11 +61,14 @@ struct LatestMediaView: View {
.fontWeight(.semibold) .fontWeight(.semibold)
.foregroundColor(.primary) .foregroundColor(.primary)
.lineLimit(1) .lineLimit(1)
Text(String(item.productionYear ?? 0)) if(item.productionYear != nil) {
.font(.caption) Text(String(item.productionYear ?? 0))
.fontWeight(.semibold) .foregroundColor(.secondary)
.foregroundColor(.secondary) .font(.caption)
.lineLimit(1) .fontWeight(.medium)
} else {
Text(item.type!)
}
}.frame(width: 100) }.frame(width: 100)
Spacer().frame(width: 15) Spacer().frame(width: 15)
} }

View File

@ -84,10 +84,14 @@ struct LibrarySearchView: View {
.fontWeight(.semibold) .fontWeight(.semibold)
.foregroundColor(.primary) .foregroundColor(.primary)
.lineLimit(1) .lineLimit(1)
Text(String(item.productionYear ?? 0)) if(item.productionYear != nil) {
.foregroundColor(.secondary) Text(String(item.productionYear ?? 0))
.font(.caption) .foregroundColor(.secondary)
.fontWeight(.medium) .font(.caption)
.fontWeight(.medium)
} else {
Text(item.type!)
}
}.frame(width: 100) }.frame(width: 100)
} }
} }

View File

@ -122,10 +122,14 @@ struct LibraryView: View {
.fontWeight(.semibold) .fontWeight(.semibold)
.foregroundColor(.primary) .foregroundColor(.primary)
.lineLimit(1) .lineLimit(1)
Text(String(item.productionYear ?? 0)) if(item.productionYear != nil) {
.foregroundColor(.secondary) Text(String(item.productionYear ?? 0))
.font(.caption) .foregroundColor(.secondary)
.fontWeight(.medium) .font(.caption)
.fontWeight(.medium)
} else {
Text(item.type!)
}
}.frame(width: 100) }.frame(width: 100)
} }
} }

View File

@ -96,15 +96,17 @@ struct MovieItemView: View {
.fixedSize(horizontal: false, vertical: true) .fixedSize(horizontal: false, vertical: true)
.offset(y: -4) .offset(y: -4)
HStack { HStack {
Text(String(item.productionYear ?? 0)).font(.subheadline) if(item.productionYear != nil) {
.fontWeight(.medium) Text(String(item.productionYear ?? 0)).font(.subheadline)
.foregroundColor(.secondary) .fontWeight(.medium)
.lineLimit(1) .foregroundColor(.secondary)
.lineLimit(1)
}
Text(item.getItemRuntime()).font(.subheadline) Text(item.getItemRuntime()).font(.subheadline)
.fontWeight(.medium) .fontWeight(.medium)
.foregroundColor(.secondary) .foregroundColor(.secondary)
.lineLimit(1) .lineLimit(1)
if item.officialRating != "" { if item.officialRating != nil {
Text(item.officialRating!).font(.subheadline) Text(item.officialRating!).font(.subheadline)
.fontWeight(.semibold) .fontWeight(.semibold)
.foregroundColor(.secondary) .foregroundColor(.secondary)
@ -311,10 +313,12 @@ struct MovieItemView: View {
.offset(x: 14, y: 0) .offset(x: 14, y: 0)
Spacer().frame(height: 1) Spacer().frame(height: 1)
HStack { HStack {
Text(String(item.productionYear ?? 0)).font(.subheadline) if(item.productionYear != nil) {
.fontWeight(.medium) Text(String(item.productionYear ?? 0)).font(.subheadline)
.foregroundColor(.secondary) .fontWeight(.medium)
.lineLimit(1) .foregroundColor(.secondary)
.lineLimit(1)
}
Text(item.getItemRuntime()).font(.subheadline) Text(item.getItemRuntime()).font(.subheadline)
.fontWeight(.medium) .fontWeight(.medium)
.foregroundColor(.secondary) .foregroundColor(.secondary)

View File

@ -296,11 +296,13 @@ struct SeasonItemView: View {
} }
var body: some View { var body: some View {
LoadingView(isShowing: $isLoading) { if(isLoading) {
ProgressView()
.onAppear(perform: onAppear)
} else {
innerBody innerBody
.navigationBarTitleDisplayMode(.inline)
.navigationTitle("\(item.name ?? "") - \(item.seriesName ?? "")")
} }
.onAppear(perform: onAppear)
.navigationBarTitleDisplayMode(.inline)
.navigationTitle("\(item.name ?? "") - \(item.seriesName ?? "")")
} }
} }

View File

@ -28,7 +28,7 @@ struct SeriesItemView: View {
isLoading = true isLoading = true
DispatchQueue.global(qos: .userInitiated).async { 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 .sink(receiveCompletion: { completion in
HandleAPIRequestCompletion(globalData: globalData, completion: completion) HandleAPIRequestCompletion(globalData: globalData, completion: completion)
}, receiveValue: { response in }, receiveValue: { response in
@ -51,7 +51,10 @@ struct SeriesItemView: View {
@State private var tracks: [GridItem] = [] @State private var tracks: [GridItem] = []
var body: some View { var body: some View {
LoadingView(isShowing: $isLoading) { if(isLoading) {
ProgressView()
.onAppear(perform: onAppear)
} else {
ScrollView(.vertical) { ScrollView(.vertical) {
Spacer().frame(height: 16) Spacer().frame(height: 16)
LazyVGrid(columns: tracks) { LazyVGrid(columns: tracks) {
@ -87,9 +90,8 @@ struct SeriesItemView: View {
recalcTracks() recalcTracks()
} }
} }
.overrideViewPreference(.unspecified)
.navigationTitle(item.name ?? "")
} }
.overrideViewPreference(.unspecified)
.onAppear(perform: onAppear)
.navigationTitle(item.name ?? "")
} }
} }

View File

@ -21,9 +21,11 @@ struct SettingsView: View {
@State private var outOfNetworkStreamBitrate: Int = 40_000_000 @State private var outOfNetworkStreamBitrate: Int = 40_000_000
@State private var autoSelectSubtitles: Bool = false @State private var autoSelectSubtitles: Bool = false
@State private var autoSelectSubtitlesLangcode: String = "none" @State private var autoSelectSubtitlesLangcode: String = "none"
@State private var username: String = ""
func onAppear() { func onAppear() {
let defaults = UserDefaults.standard let defaults = UserDefaults.standard
_username.wrappedValue = globalData.user.username!
_inNetworkStreamBitrate.wrappedValue = defaults.integer(forKey: "InNetworkBandwidth") _inNetworkStreamBitrate.wrappedValue = defaults.integer(forKey: "InNetworkBandwidth")
_outOfNetworkStreamBitrate.wrappedValue = defaults.integer(forKey: "OutOfNetworkBandwidth") _outOfNetworkStreamBitrate.wrappedValue = defaults.integer(forKey: "OutOfNetworkBandwidth")
_autoSelectSubtitles.wrappedValue = defaults.bool(forKey: "AutoSelectSubtitles") _autoSelectSubtitles.wrappedValue = defaults.bool(forKey: "AutoSelectSubtitles")
@ -63,7 +65,7 @@ struct SettingsView: View {
Section { Section {
HStack { HStack {
Text("Signed in as \(globalData.user.username!)").foregroundColor(.primary) Text("Signed in as \(username)").foregroundColor(.primary)
Spacer() Spacer()
Button { Button {
let fetchRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest(entityName: "Server") let fetchRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest(entityName: "Server")
@ -97,7 +99,7 @@ struct SettingsView: View {
} }
} }
} }
.padding(.top, 12)
.navigationBarTitle("Settings", displayMode: .inline) .navigationBarTitle("Settings", displayMode: .inline)
.toolbar { .toolbar {
ToolbarItemGroup(placement: .navigationBarLeading) { ToolbarItemGroup(placement: .navigationBarLeading) {

View File

@ -13,7 +13,7 @@ import MediaPlayer
struct Subtitle { struct Subtitle {
var name: String var name: String
var id: Int32 var id: Int32
var url: URL var url: URL?
var delivery: SubtitleDeliveryMethod var delivery: SubtitleDeliveryMethod
var codec: String var codec: String
} }
@ -197,6 +197,7 @@ class PlayerViewController: UIViewController, VLCMediaDelegate, VLCMediaPlayerDe
commandCenter.pauseCommand.addTarget { _ in commandCenter.pauseCommand.addTarget { _ in
self.mediaPlayer.pause() self.mediaPlayer.pause()
self.sendProgressReport(eventName: "pause") self.sendProgressReport(eventName: "pause")
self.mainActionButton.setImage(UIImage(systemName: "play"), for: .normal)
return .success return .success
} }
@ -204,6 +205,7 @@ class PlayerViewController: UIViewController, VLCMediaDelegate, VLCMediaPlayerDe
commandCenter.playCommand.addTarget { _ in commandCenter.playCommand.addTarget { _ in
self.mediaPlayer.play() self.mediaPlayer.play()
self.sendProgressReport(eventName: "unpause") self.sendProgressReport(eventName: "unpause")
self.mainActionButton.setImage(UIImage(systemName: "pause"), for: .normal)
return .success return .success
} }
@ -260,6 +262,7 @@ class PlayerViewController: UIViewController, VLCMediaDelegate, VLCMediaPlayerDe
// View has loaded. // View has loaded.
// Rotate to landscape only if necessary // Rotate to landscape only if necessary
UIViewController.attemptRotationToDeviceOrientation() UIViewController.attemptRotationToDeviceOrientation()
mediaPlayer.perform(Selector(("setTextRendererFontSize:")), with: 14) mediaPlayer.perform(Selector(("setTextRendererFontSize:")), with: 14)
@ -269,9 +272,9 @@ class PlayerViewController: UIViewController, VLCMediaDelegate, VLCMediaPlayerDe
mediaPlayer.drawable = videoContentView mediaPlayer.drawable = videoContentView
if manifest.type == "Movie" { if manifest.type == "Movie" {
titleLabel.text = manifest.name titleLabel.text = manifest.name ?? ""
} else { } 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 // Fetch max bitrate from UserDefaults depending on current connection mode
@ -283,14 +286,15 @@ class PlayerViewController: UIViewController, VLCMediaDelegate, VLCMediaPlayerDe
builder.setMaxBitrate(bitrate: maxBitrate) builder.setMaxBitrate(bitrate: maxBitrate)
let profile = builder.buildProfile() 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 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 .sink(receiveCompletion: { completion in
HandleAPIRequestCompletion(globalData: self.globalData, completion: completion) HandleAPIRequestCompletion(globalData: self.globalData, completion: completion)
}, receiveValue: { [self] response in }, receiveValue: { [self] response in
playSessionId = response.playSessionId! playSessionId = response.playSessionId ?? ""
let mediaSource = response.mediaSources!.first.self! let mediaSource = response.mediaSources!.first.self!
if mediaSource.transcodingUrl != nil { if mediaSource.transcodingUrl != nil {
// Item is being transcoded by request of server // Item is being transcoded by request of server
@ -299,14 +303,19 @@ class PlayerViewController: UIViewController, VLCMediaDelegate, VLCMediaPlayerDe
item.videoType = .transcode item.videoType = .transcode
item.videoUrl = streamURL! 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) subtitleTrackArray.append(disableSubtitleTrack)
// Loop through media streams and add to array // Loop through media streams and add to array
for stream in mediaSource.mediaStreams! { for stream in mediaSource.mediaStreams! {
if stream.type == .subtitle { if stream.type == .subtitle {
let deliveryUrl = URL(string: "\(globalData.server.baseURI!)\(stream.deliveryUrl!)")! var deliveryUrl: URL?
let subtitle = Subtitle(name: stream.displayTitle!, id: Int32(stream.index!), url: deliveryUrl, delivery: stream.deliveryMethod!, codec: stream.codec!) 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) subtitleTrackArray.append(subtitle)
} }
@ -335,14 +344,19 @@ class PlayerViewController: UIViewController, VLCMediaDelegate, VLCMediaPlayerDe
item.videoUrl = streamURL item.videoUrl = streamURL
item.videoType = .directPlay 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) subtitleTrackArray.append(disableSubtitleTrack)
// Loop through media streams and add to array // Loop through media streams and add to array
for stream in mediaSource.mediaStreams! { for stream in mediaSource.mediaStreams! {
if stream.type == .subtitle { if stream.type == .subtitle {
let deliveryUrl = URL(string: "\(globalData.server.baseURI!)\(stream.deliveryUrl!)")! var deliveryUrl: URL?
let subtitle = Subtitle(name: stream.displayTitle!, id: Int32(stream.index!), url: deliveryUrl, delivery: stream.deliveryMethod!, codec: stream.codec!) 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) subtitleTrackArray.append(subtitle)
} }
@ -365,21 +379,28 @@ class PlayerViewController: UIViewController, VLCMediaDelegate, VLCMediaPlayerDe
playbackItem = item playbackItem = item
} }
self.setupNowPlayingCC()
mediaPlayer.media = VLCMedia(url: playbackItem.videoUrl) mediaPlayer.media = VLCMedia(url: playbackItem.videoUrl)
mediaPlayer.play() 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() mediaPlayer.pause()
subtitleTrackArray.forEach { sub in subtitleTrackArray.forEach { sub in
if sub.id != -1 && sub.delivery == .external && sub.codec != "subrip" { 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) delegate?.showLoadingView(self)
while mediaPlayer.numberOfSubtitlesTracks != subtitleTrackArray.count - 1 {} while mediaPlayer.numberOfSubtitlesTracks != subtitleTrackArray.count - 1 {}
//Select default track & resume playback
mediaPlayer.currentVideoSubTitleIndex = selectedCaptionTrack mediaPlayer.currentVideoSubTitleIndex = selectedCaptionTrack
mediaPlayer.pause() mediaPlayer.pause()
mediaPlayer.play() mediaPlayer.play()
@ -414,6 +435,7 @@ class PlayerViewController: UIViewController, VLCMediaDelegate, VLCMediaPlayerDe
break break
case .playing : case .playing :
print("Video is playing") print("Video is playing")
self.setupNowPlayingCC()
sendProgressReport(eventName: "unpause") sendProgressReport(eventName: "unpause")
delegate?.hideLoadingView(self) delegate?.hideLoadingView(self)
paused = false paused = false

View File

@ -52,7 +52,9 @@ extension BaseItemDto {
if self.primaryImageAspectRatio ?? 0.0 < 1.0 { if self.primaryImageAspectRatio ?? 0.0 < 1.0 {
imageType = "Backdrop" imageType = "Backdrop"
imageTag = (self.backdropImageTags ?? [""])[0] if(!(self.backdropImageTags?.isEmpty ?? true)) {
imageTag = (self.backdropImageTags ?? [""])[0]
}
} else { } else {
imageType = "Primary" imageType = "Primary"
imageTag = self.imageTags?["Primary"] ?? "" imageTag = self.imageTags?["Primary"] ?? ""
@ -60,10 +62,13 @@ extension BaseItemDto {
if imageTag == "" { if imageTag == "" {
imageType = "Backdrop" imageType = "Backdrop"
imageTag = self.parentBackdropImageTags?[0] ?? "" if(!(self.parentBackdropImageTags?.isEmpty ?? true)) {
imageTag = (self.parentBackdropImageTags ?? [""])[0]
}
} }
let x = UIScreen.main.nativeScale * CGFloat(maxWidth) 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)! return URL(string: urlString)!
} }
@ -75,7 +80,7 @@ extension BaseItemDto {
print(imageTag) print(imageTag)
let x = UIScreen.main.nativeScale * CGFloat(maxWidth) 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)! return URL(string: urlString)!
} }
@ -83,7 +88,7 @@ extension BaseItemDto {
let imageType = "Primary" let imageType = "Primary"
let imageTag = self.seriesPrimaryImageTag ?? "" let imageTag = self.seriesPrimaryImageTag ?? ""
let x = UIScreen.main.nativeScale * CGFloat(maxWidth) 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)! return URL(string: urlString)!
} }
@ -96,7 +101,7 @@ extension BaseItemDto {
} }
let x = UIScreen.main.nativeScale * CGFloat(maxWidth) 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)! return URL(string: urlString)!
} }
@ -128,6 +133,10 @@ extension BaseItemDto {
} }
} }
func round(_ value: Double, toNearest: Double) -> Double {
return round(value / toNearest) * toNearest
}
extension BaseItemPerson { extension BaseItemPerson {
func getImage(baseURL: String, maxWidth: Int) -> URL { func getImage(baseURL: String, maxWidth: Int) -> URL {
let imageType = "Primary" let imageType = "Primary"
@ -135,7 +144,7 @@ extension BaseItemPerson {
let x = UIScreen.main.nativeScale * CGFloat(maxWidth) 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)! return URL(string: urlString)!
} }

View File

@ -0,0 +1,20 @@
{
"colors" : [
{
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}