This commit is contained in:
Aiden Vigue 2021-06-09 21:25:50 -07:00
parent a16a41acbf
commit 988377e0ea
19 changed files with 276 additions and 242 deletions

View File

@ -19,7 +19,7 @@
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string> <string>$(MARKETING_VERSION)</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>1</string> <string>36</string>
<key>LSRequiresIPhoneOS</key> <key>LSRequiresIPhoneOS</key>
<true/> <true/>
<key>UILaunchScreen</key> <key>UILaunchScreen</key>

View File

@ -8,6 +8,7 @@
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
53192D5D265AA78A008A4215 /* DeviceProfileBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53192D5C265AA78A008A4215 /* DeviceProfileBuilder.swift */; }; 53192D5D265AA78A008A4215 /* DeviceProfileBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53192D5C265AA78A008A4215 /* DeviceProfileBuilder.swift */; };
5321753B2671BCFC005491E6 /* SettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5321753A2671BCFC005491E6 /* SettingsViewModel.swift */; };
53313B90265EEA6D00947AA3 /* VideoPlayer.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 53313B8F265EEA6D00947AA3 /* VideoPlayer.storyboard */; }; 53313B90265EEA6D00947AA3 /* VideoPlayer.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 53313B8F265EEA6D00947AA3 /* VideoPlayer.storyboard */; };
53352571265EA0A0006CCA86 /* Introspect in Frameworks */ = {isa = PBXBuildFile; productRef = 53352570265EA0A0006CCA86 /* Introspect */; }; 53352571265EA0A0006CCA86 /* Introspect in Frameworks */ = {isa = PBXBuildFile; productRef = 53352570265EA0A0006CCA86 /* Introspect */; };
5338F74E263B61370014BF09 /* ConnectToServerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5338F74D263B61370014BF09 /* ConnectToServerView.swift */; }; 5338F74E263B61370014BF09 /* ConnectToServerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5338F74D263B61370014BF09 /* ConnectToServerView.swift */; };
@ -108,6 +109,7 @@
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
53192D5C265AA78A008A4215 /* DeviceProfileBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceProfileBuilder.swift; sourceTree = "<group>"; }; 53192D5C265AA78A008A4215 /* DeviceProfileBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceProfileBuilder.swift; sourceTree = "<group>"; };
5321753A2671BCFC005491E6 /* SettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewModel.swift; sourceTree = "<group>"; };
53313B8F265EEA6D00947AA3 /* VideoPlayer.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = VideoPlayer.storyboard; sourceTree = "<group>"; }; 53313B8F265EEA6D00947AA3 /* VideoPlayer.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = VideoPlayer.storyboard; sourceTree = "<group>"; };
5338F74D263B61370014BF09 /* ConnectToServerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectToServerView.swift; sourceTree = "<group>"; }; 5338F74D263B61370014BF09 /* ConnectToServerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectToServerView.swift; sourceTree = "<group>"; };
535870602669D21600D05A09 /* JellyfinPlayer tvOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "JellyfinPlayer tvOS.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 535870602669D21600D05A09 /* JellyfinPlayer tvOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "JellyfinPlayer tvOS.app"; sourceTree = BUILT_PRODUCTS_DIR; };
@ -187,6 +189,14 @@
/* End PBXFrameworksBuildPhase section */ /* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */ /* Begin PBXGroup section */
532175392671BCED005491E6 /* ViewModel */ = {
isa = PBXGroup;
children = (
5321753A2671BCFC005491E6 /* SettingsViewModel.swift */,
);
path = ViewModel;
sourceTree = "<group>";
};
535870612669D21600D05A09 /* JellyfinPlayer tvOS */ = { 535870612669D21600D05A09 /* JellyfinPlayer tvOS */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -212,6 +222,7 @@
535870752669D60C00D05A09 /* Shared */ = { 535870752669D60C00D05A09 /* Shared */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
532175392671BCED005491E6 /* ViewModel */,
621338912660106C00A81A2A /* Extensions */, 621338912660106C00A81A2A /* Extensions */,
AE8C3157265D6F5E008AA076 /* Resources */, AE8C3157265D6F5E008AA076 /* Resources */,
535870AB2669D8D300D05A09 /* Typings */, 535870AB2669D8D300D05A09 /* Typings */,
@ -484,6 +495,7 @@
62133890265F83A900A81A2A /* LibraryListView.swift in Sources */, 62133890265F83A900A81A2A /* LibraryListView.swift in Sources */,
53892770263C25230035E14B /* NextUpView.swift in Sources */, 53892770263C25230035E14B /* NextUpView.swift in Sources */,
535BAEA5264A151C005FA86D /* VideoPlayer.swift in Sources */, 535BAEA5264A151C005FA86D /* VideoPlayer.swift in Sources */,
5321753B2671BCFC005491E6 /* SettingsViewModel.swift in Sources */,
5377CC01263B596B003A4E83 /* Model.xcdatamodeld in Sources */, 5377CC01263B596B003A4E83 /* Model.xcdatamodeld in Sources */,
53DF641E263D9C0600A7CD1A /* LibraryView.swift in Sources */, 53DF641E263D9C0600A7CD1A /* LibraryView.swift in Sources */,
53A089D0264DA9DA00D57806 /* MovieItemView.swift in Sources */, 53A089D0264DA9DA00D57806 /* MovieItemView.swift in Sources */,
@ -687,8 +699,9 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = JellyfinPlayer/JellyfinPlayer.entitlements; CODE_SIGN_ENTITLEMENTS = JellyfinPlayer/JellyfinPlayer.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 33; CURRENT_PROJECT_VERSION = 36;
DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_ASSET_PATHS = "";
DEVELOPMENT_TEAM = 9R8RREG67J; DEVELOPMENT_TEAM = 9R8RREG67J;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
@ -703,6 +716,7 @@
MARKETING_VERSION = 1.0.0; MARKETING_VERSION = 1.0.0;
PRODUCT_BUNDLE_IDENTIFIER = me.vigue.jellyfin; PRODUCT_BUNDLE_IDENTIFIER = me.vigue.jellyfin;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SUPPORTS_MACCATALYST = NO; SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
@ -716,8 +730,9 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = JellyfinPlayer/JellyfinPlayer.entitlements; CODE_SIGN_ENTITLEMENTS = JellyfinPlayer/JellyfinPlayer.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 33; CURRENT_PROJECT_VERSION = 36;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_ASSET_PATHS = "";
DEVELOPMENT_TEAM = 9R8RREG67J; DEVELOPMENT_TEAM = 9R8RREG67J;
@ -733,6 +748,7 @@
MARKETING_VERSION = 1.0.0; MARKETING_VERSION = 1.0.0;
PRODUCT_BUNDLE_IDENTIFIER = me.vigue.jellyfin; PRODUCT_BUNDLE_IDENTIFIER = me.vigue.jellyfin;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SUPPORTS_MACCATALYST = NO; SUPPORTS_MACCATALYST = NO;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;

View File

@ -5,8 +5,6 @@
* Copyright 2021 Aiden Vigue & Jellyfin Contributors * Copyright 2021 Aiden Vigue & Jellyfin Contributors
*/ */
//MARK: refactor this file! it's the first swift file I ever wrote and it clearly shows.
import SwiftUI import SwiftUI
import CoreData import CoreData
import KeychainSwift import KeychainSwift

View File

@ -85,36 +85,38 @@ struct ContentView: View {
JellyfinAPI.basePath = globalData.server.baseURI ?? "" JellyfinAPI.basePath = globalData.server.baseURI ?? ""
JellyfinAPI.customHeaders = ["X-Emby-Authorization": globalData.authHeader] JellyfinAPI.customHeaders = ["X-Emby-Authorization": globalData.authHeader]
UserAPI.getCurrentUser() DispatchQueue.global(qos: .userInitiated).async {
.sink(receiveCompletion: { completion in UserAPI.getCurrentUser()
HandleAPIRequestCompletion(globalData: globalData, completion: completion) .sink(receiveCompletion: { completion in
loadState = loadState - 1 HandleAPIRequestCompletion(globalData: globalData, completion: completion)
}, receiveValue: { response in loadState = loadState - 1
libraries = response.configuration?.orderedViews ?? [] }, receiveValue: { response in
librariesShowRecentlyAdded = libraries.filter { element in libraries = response.configuration?.orderedViews ?? []
return !(response.configuration?.latestItemsExcludes?.contains(element))! librariesShowRecentlyAdded = libraries.filter { element in
} return !(response.configuration?.latestItemsExcludes?.contains(element))!
}
if(loadState == 1) {
isLoading = false if(loadState == 1) {
} isLoading = false
}) }
.store(in: &globalData.pendingAPIRequests)
UserViewsAPI.getUserViews(userId: globalData.user.user_id ?? "")
.sink(receiveCompletion: { completion in
HandleAPIRequestCompletion(globalData: globalData, completion: completion)
loadState = loadState - 1
}, receiveValue: { response in
response.items?.forEach({ item in
library_names[item.id ?? ""] = item.name
}) })
.store(in: &globalData.pendingAPIRequests)
if(loadState == 1) {
isLoading = false UserViewsAPI.getUserViews(userId: globalData.user.user_id ?? "")
} .sink(receiveCompletion: { completion in
}) HandleAPIRequestCompletion(globalData: globalData, completion: completion)
.store(in: &globalData.pendingAPIRequests) loadState = loadState - 1
}, receiveValue: { response in
response.items?.forEach({ item in
library_names[item.id ?? ""] = item.name
})
if(loadState == 1) {
isLoading = false
}
})
.store(in: &globalData.pendingAPIRequests)
}
let defaults = UserDefaults.standard let defaults = UserDefaults.standard
if defaults.integer(forKey: "InNetworkBandwidth") == 0 { if defaults.integer(forKey: "InNetworkBandwidth") == 0 {

View File

@ -37,13 +37,15 @@ struct ContinueWatchingView: View {
@State private var items: [BaseItemDto] = [] @State private var items: [BaseItemDto] = []
func onAppear() { func onAppear() {
ItemsAPI.getResumeItems(userId: globalData.user.user_id ?? "", limit: 12, fields: [.primaryImageAspectRatio,.seriesPrimaryImage,.seasonUserData,.overview,.genres,.people], mediaTypes: ["Video"], imageTypeLimit: 1, enableImageTypes: [.primary,.backdrop,.thumb]) DispatchQueue.global(qos: .userInitiated).async {
.sink(receiveCompletion: { completion in ItemsAPI.getResumeItems(userId: globalData.user.user_id ?? "", limit: 12, fields: [.primaryImageAspectRatio,.seriesPrimaryImage,.seasonUserData,.overview,.genres,.people], mediaTypes: ["Video"], imageTypeLimit: 1, enableImageTypes: [.primary,.backdrop,.thumb])
HandleAPIRequestCompletion(globalData: globalData, completion: completion) .sink(receiveCompletion: { completion in
}, receiveValue: { response in HandleAPIRequestCompletion(globalData: globalData, completion: completion)
items = response.items ?? [] }, receiveValue: { response in
}) items = response.items ?? []
.store(in: &globalData.pendingAPIRequests) })
.store(in: &globalData.pendingAPIRequests)
}
} }
var body: some View { var body: some View {

View File

@ -27,7 +27,7 @@ network.</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string> <string>$(MARKETING_VERSION)</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>33</string> <string>36</string>
<key>ITSAppUsesNonExemptEncryption</key> <key>ITSAppUsesNonExemptEncryption</key>
<false/> <false/>
<key>LSRequiresIPhoneOS</key> <key>LSRequiresIPhoneOS</key>

View File

@ -2,11 +2,11 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>com.apple.developer.coremedia.hls.low-latency</key>
<true/>
<key>com.apple.security.app-sandbox</key> <key>com.apple.security.app-sandbox</key>
<true/> <true/>
<key>com.apple.security.network.client</key> <key>com.apple.security.network.client</key>
<true/> <true/>
<key>com.apple.developer.coremedia.hls.low-latency</key>
<true/>
</dict> </dict>
</plist> </plist>

View File

@ -26,13 +26,15 @@ struct LatestMediaView: View {
} }
viewDidLoad = true; viewDidLoad = true;
UserLibraryAPI.getLatestMedia(userId: globalData.user.user_id!, parentId: library_id, fields: [.primaryImageAspectRatio,.seriesPrimaryImage,.seasonUserData,.overview,.genres,.people], enableUserData: true, limit: 12) DispatchQueue.global(qos: .userInitiated).async {
.sink(receiveCompletion: { completion in UserLibraryAPI.getLatestMedia(userId: globalData.user.user_id!, parentId: library_id, fields: [.primaryImageAspectRatio,.seriesPrimaryImage,.seasonUserData,.overview,.genres,.people], enableUserData: true, limit: 12)
HandleAPIRequestCompletion(globalData: globalData, completion: completion) .sink(receiveCompletion: { completion in
}, receiveValue: { response in HandleAPIRequestCompletion(globalData: globalData, completion: completion)
items = response }, receiveValue: { response in
}) items = response
.store(in: &globalData.pendingAPIRequests) })
.store(in: &globalData.pendingAPIRequests)
}
} }
var body: some View { var body: some View {

View File

@ -30,15 +30,17 @@ struct LibrarySearchView: View {
func requestSearch(query: String) { func requestSearch(query: String) {
isLoading = true isLoading = true
print(usingParentID)
ItemsAPI.getItemsByUserId(userId: globalData.user.user_id!, limit: 60, recursive: true, searchTerm: query, sortOrder: [.ascending], parentId: (usingParentID != "" ? usingParentID : nil), fields: [.primaryImageAspectRatio,.seriesPrimaryImage,.seasonUserData,.overview,.genres,.people], includeItemTypes: ["Movie","Series"], sortBy: ["SortName"], enableUserData: true, enableImages: true) DispatchQueue.global(qos: .userInitiated).async {
.sink(receiveCompletion: { completion in ItemsAPI.getItemsByUserId(userId: globalData.user.user_id!, limit: 60, recursive: true, searchTerm: query, sortOrder: [.ascending], parentId: (usingParentID != "" ? usingParentID : nil), fields: [.primaryImageAspectRatio,.seriesPrimaryImage,.seasonUserData,.overview,.genres,.people], includeItemTypes: ["Movie","Series"], sortBy: ["SortName"], enableUserData: true, enableImages: true)
HandleAPIRequestCompletion(globalData: globalData, completion: completion) .sink(receiveCompletion: { completion in
}, receiveValue: { response in HandleAPIRequestCompletion(globalData: globalData, completion: completion)
items = response.items ?? [] }, receiveValue: { response in
isLoading = false items = response.items ?? []
}) isLoading = false
.store(in: &globalData.pendingAPIRequests) })
.store(in: &globalData.pendingAPIRequests)
}
} }
//MARK: tracks for grid //MARK: tracks for grid

View File

@ -1,4 +1,5 @@
/* JellyfinPlayer/Swiftfin is subject to the terms of the Mozilla Public /*
* JellyfinPlayer/Swiftfin is subject to the terms of the Mozilla Public
* License, v2.0. If a copy of the MPL was not distributed with this * License, v2.0. If a copy of the MPL was not distributed with this
* file, you can obtain one at https://mozilla.org/MPL/2.0/. * file, you can obtain one at https://mozilla.org/MPL/2.0/.
* *
@ -61,17 +62,19 @@ struct LibraryView: View {
isLoading = true isLoading = true
items = [] items = []
ItemsAPI.getItemsByUserId(userId: globalData.user.user_id!, startIndex: currentPage * 100, limit: 100, recursive: true, searchTerm: nil, sortOrder: filters.sortOrder, parentId: (usingParentID != "" ? usingParentID : nil), fields: [.primaryImageAspectRatio,.seriesPrimaryImage,.seasonUserData,.overview,.genres,.people], includeItemTypes: ["Movie","Series"], filters: filters.filters, sortBy: filters.sortBy, enableUserData: true, personIds: (personId == "" ? nil : [personId]), studioIds: (studio == "" ? nil : [studio]), genreIds: (genre == "" ? nil : [genre]), enableImages: true) DispatchQueue.global(qos: .userInitiated).async {
.sink(receiveCompletion: { completion in ItemsAPI.getItemsByUserId(userId: globalData.user.user_id!, startIndex: currentPage * 100, limit: 100, recursive: true, searchTerm: nil, sortOrder: filters.sortOrder, parentId: (usingParentID != "" ? usingParentID : nil), fields: [.primaryImageAspectRatio,.seriesPrimaryImage,.seasonUserData,.overview,.genres,.people], includeItemTypes: ["Movie","Series"], filters: filters.filters, sortBy: filters.sortBy, enableUserData: true, personIds: (personId == "" ? nil : [personId]), studioIds: (studio == "" ? nil : [studio]), genreIds: (genre == "" ? nil : [genre]), enableImages: true)
HandleAPIRequestCompletion(globalData: globalData, completion: completion) .sink(receiveCompletion: { completion in
isLoading = false HandleAPIRequestCompletion(globalData: globalData, completion: completion)
}, receiveValue: { response in isLoading = false
let x = ceil(Double(response.totalRecordCount!) / 100.0) }, receiveValue: { response in
totalPages = Int(x) let x = ceil(Double(response.totalRecordCount!) / 100.0)
items = response.items ?? [] totalPages = Int(x)
isLoading = false items = response.items ?? []
}) isLoading = false
.store(in: &globalData.pendingAPIRequests) })
.store(in: &globalData.pendingAPIRequests)
}
} }
//MARK: tracks for grid //MARK: tracks for grid

View File

@ -21,13 +21,15 @@ struct NextUpView: View {
} }
viewDidLoad = true; viewDidLoad = true;
TvShowsAPI.getNextUp(userId: globalData.user.user_id!, limit: 12, fields: [.primaryImageAspectRatio,.seriesPrimaryImage,.seasonUserData,.overview,.genres,.people]) DispatchQueue.global(qos: .userInitiated).async {
.sink(receiveCompletion: { completion in TvShowsAPI.getNextUp(userId: globalData.user.user_id!, limit: 12, fields: [.primaryImageAspectRatio,.seriesPrimaryImage,.seasonUserData,.overview,.genres,.people])
HandleAPIRequestCompletion(globalData: globalData, completion: completion) .sink(receiveCompletion: { completion in
}, receiveValue: { response in HandleAPIRequestCompletion(globalData: globalData, completion: completion)
items = response.items ?? [] }, receiveValue: { response in
}) items = response.items ?? []
.store(in: &globalData.pendingAPIRequests) })
.store(in: &globalData.pendingAPIRequests)
}
} }
var body: some View { var body: some View {

View File

@ -28,15 +28,17 @@ struct SeasonItemView: View {
return return
} }
TvShowsAPI.getEpisodes(seriesId: item.seriesId!, userId: globalData.user.user_id!, fields: [.primaryImageAspectRatio,.seriesPrimaryImage,.seasonUserData,.overview,.genres,.people], seasonId: item.id!) DispatchQueue.global(qos: .userInitiated).async {
.sink(receiveCompletion: { completion in TvShowsAPI.getEpisodes(seriesId: item.seriesId!, userId: globalData.user.user_id!, fields: [.primaryImageAspectRatio,.seriesPrimaryImage,.seasonUserData,.overview,.genres,.people], seasonId: item.id!)
HandleAPIRequestCompletion(globalData: globalData, completion: completion) .sink(receiveCompletion: { completion in
isLoading = false HandleAPIRequestCompletion(globalData: globalData, completion: completion)
}, receiveValue: { response in isLoading = false
viewDidLoad = true }, receiveValue: { response in
episodes = response.items ?? [] viewDidLoad = true
}) episodes = response.items ?? []
.store(in: &globalData.pendingAPIRequests) })
.store(in: &globalData.pendingAPIRequests)
}
} }
@ViewBuilder @ViewBuilder

View File

@ -26,15 +26,18 @@ struct SeriesItemView: View {
} }
isLoading = true isLoading = true
TvShowsAPI.getSeasons(seriesId: item.id ?? "", fields: [.primaryImageAspectRatio,.seriesPrimaryImage,.seasonUserData,.overview,.genres,.people])
.sink(receiveCompletion: { completion in DispatchQueue.global(qos: .userInitiated).async {
HandleAPIRequestCompletion(globalData: globalData, completion: completion) TvShowsAPI.getSeasons(seriesId: item.id ?? "", fields: [.primaryImageAspectRatio,.seriesPrimaryImage,.seasonUserData,.overview,.genres,.people])
}, receiveValue: { response in .sink(receiveCompletion: { completion in
isLoading = false HandleAPIRequestCompletion(globalData: globalData, completion: completion)
viewDidLoad = true }, receiveValue: { response in
seasons = response.items ?? [] isLoading = false
}) viewDidLoad = true
.store(in: &globalData.pendingAPIRequests) seasons = response.items ?? []
})
.store(in: &globalData.pendingAPIRequests)
}
} }
//MARK: Grid tracks //MARK: Grid tracks

View File

@ -9,30 +9,20 @@ import CoreData
import SwiftUI import SwiftUI
struct SettingsView: View { struct SettingsView: View {
@ObservedObject @Environment(\.managedObjectContext) private var viewContext
var viewModel: SettingsViewModel
@EnvironmentObject var globalData: GlobalData
@EnvironmentObject var jsi: justSignedIn
@ObservedObject var viewModel: SettingsViewModel
@Binding @Binding var close: Bool
var close: Bool @State private var inNetworkStreamBitrate: Int = 40_000_000
@Environment(\.managedObjectContext) @State private var outOfNetworkStreamBitrate: Int = 40_000_000
private var viewContext @State private var autoSelectSubtitles: Bool = false
@EnvironmentObject @State private var autoSelectSubtitlesLangcode: String = "none"
var globalData: GlobalData
@EnvironmentObject
var jsi: justSignedIn
@State
private var username: String = ""
@State
private var inNetworkStreamBitrate: Int = 40_000_000
@State
private var outOfNetworkStreamBitrate: Int = 40_000_000
@State
private var autoSelectSubtitles: Bool = false
@State
private var autoSelectSubtitlesLangcode: String = "none"
func onAppear() { func onAppear() {
_username.wrappedValue = globalData.user.username ?? ""
let defaults = UserDefaults.standard let defaults = UserDefaults.standard
_inNetworkStreamBitrate.wrappedValue = defaults.integer(forKey: "InNetworkBandwidth") _inNetworkStreamBitrate.wrappedValue = defaults.integer(forKey: "InNetworkBandwidth")
_outOfNetworkStreamBitrate.wrappedValue = defaults.integer(forKey: "OutOfNetworkBandwidth") _outOfNetworkStreamBitrate.wrappedValue = defaults.integer(forKey: "OutOfNetworkBandwidth")
@ -73,7 +63,7 @@ struct SettingsView: View {
Section { Section {
HStack { HStack {
Text("Signed in as \(username)").foregroundColor(.primary) Text("Signed in as \(globalData.user.username!)").foregroundColor(.primary)
Spacer() Spacer()
Button { Button {
let fetchRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest(entityName: "Server") let fetchRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest(entityName: "Server")
@ -121,36 +111,3 @@ struct SettingsView: View {
}.onAppear(perform: onAppear) }.onAppear(perform: onAppear)
} }
} }
struct UserSettings: Decodable {
var LocalMaxBitrate: Int;
var RemoteMaxBitrate: Int;
var AutoSelectSubtitles: Bool;
var AutoSelectSubtitlesLangcode: String;
var SubtitlePositionOffset: Int;
var SubtitleFontName: String;
}
struct Bitrates: Codable, Hashable {
public var name: String
public var value: Int
}
final class SettingsViewModel: ObservableObject {
var bitrates: [Bitrates] = []
init() {
let url = Bundle.main.url(forResource: "bitrates", withExtension: "json")!
do {
let jsonData = try Data(contentsOf: url, options: .mappedIfSafe)
do {
self.bitrates = try JSONDecoder().decode([Bitrates].self, from: jsonData)
} catch {
print(error)
}
} catch {
print(error)
}
}
}

View File

@ -287,88 +287,88 @@ class PlayerViewController: UIViewController, VLCMediaDelegate, VLCMediaPlayerDe
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)
MediaInfoAPI.getPostedPlaybackInfo(itemId: manifest.id!, userId: globalData.user.user_id, maxStreamingBitrate: Int(maxBitrate), startTimeTicks: manifest.userData?.playbackPositionTicks ?? 0, autoOpenLiveStream: true, playbackInfoDto: playbackInfo) DispatchQueue.global(qos: .userInitiated).async { [self] in
.sink(receiveCompletion: { completion in MediaInfoAPI.getPostedPlaybackInfo(itemId: manifest.id!, userId: globalData.user.user_id, maxStreamingBitrate: Int(maxBitrate), startTimeTicks: manifest.userData?.playbackPositionTicks ?? 0, autoOpenLiveStream: true, playbackInfoDto: playbackInfo)
HandleAPIRequestCompletion(globalData: self.globalData, completion: completion) .sink(receiveCompletion: { completion in
}, receiveValue: { [self] response in HandleAPIRequestCompletion(globalData: self.globalData, completion: completion)
playSessionId = response.playSessionId! }, receiveValue: { [self] response in
let mediaSource = response.mediaSources!.first.self! playSessionId = response.playSessionId!
if(mediaSource.transcodingUrl != nil) { let mediaSource = response.mediaSources!.first.self!
//Item is being transcoded by request of server if(mediaSource.transcodingUrl != nil) {
let streamURL = URL(string: "\(globalData.server.baseURI!)\(mediaSource.transcodingUrl!)") //Item is being transcoded by request of server
let item = PlaybackItem() let streamURL = URL(string: "\(globalData.server.baseURI!)\(mediaSource.transcodingUrl!)")
item.videoType = .transcode let item = PlaybackItem()
item.videoUrl = streamURL! item.videoType = .transcode
item.videoUrl = streamURL!
let disableSubtitleTrack = Subtitle(name: "Disabled", id: -1, url: URL(string: "https://example.com")!, delivery: .embed, codec: "")
subtitleTrackArray.append(disableSubtitleTrack); let disableSubtitleTrack = Subtitle(name: "Disabled", id: -1, url: URL(string: "https://example.com")!, delivery: .embed, codec: "")
subtitleTrackArray.append(disableSubtitleTrack);
//Loop through media streams and add to array
for stream in mediaSource.mediaStreams! { //Loop through media streams and add to array
if(stream.type == .subtitle) { for stream in mediaSource.mediaStreams! {
let deliveryUrl = URL(string: "\(globalData.server.baseURI!)\(stream.deliveryUrl!)")! if(stream.type == .subtitle) {
let subtitle = Subtitle(name: stream.displayTitle!, id: Int32(stream.index!), url: deliveryUrl, delivery: stream.deliveryMethod!, codec: stream.codec!) let deliveryUrl = URL(string: "\(globalData.server.baseURI!)\(stream.deliveryUrl!)")!
subtitleTrackArray.append(subtitle); let subtitle = Subtitle(name: stream.displayTitle!, id: Int32(stream.index!), url: deliveryUrl, delivery: stream.deliveryMethod!, codec: stream.codec!)
subtitleTrackArray.append(subtitle);
}
if(stream.type == .audio) {
let subtitle = AudioTrack(name: stream.displayTitle!, id: Int32(stream.index!))
if(stream.isDefault! == true) {
selectedAudioTrack = Int32(stream.index!);
}
audioTrackArray.append(subtitle);
}
} }
if(stream.type == .audio) { if(selectedAudioTrack == -1) {
let subtitle = AudioTrack(name: stream.displayTitle!, id: Int32(stream.index!)) if(audioTrackArray.count > 0) {
if(stream.isDefault! == true) { selectedAudioTrack = audioTrackArray[0].id;
selectedAudioTrack = Int32(stream.index!);
} }
audioTrackArray.append(subtitle);
}
}
if(selectedAudioTrack == -1) {
if(audioTrackArray.count > 0) {
selectedAudioTrack = audioTrackArray[0].id;
}
}
self.sendPlayReport()
playbackItem = item;
} else {
//Item will be directly played by the client.
let streamURL: URL = URL(string: "\(globalData.server.baseURI!)/Videos/\(manifest.id!)/stream?Static=true&mediaSourceId=\(manifest.id!)&deviceId=\(globalData.user.device_uuid!)&api_key=\(globalData.authToken)&Tag=\(mediaSource.eTag!)")!;
let item = PlaybackItem()
item.videoUrl = streamURL
item.videoType = .directPlay
let disableSubtitleTrack = Subtitle(name: "Disabled", id: -1, url: URL(string: "https://example.com")!, 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!)
subtitleTrackArray.append(subtitle);
} }
if(stream.type == .audio) { self.sendPlayReport()
let subtitle = AudioTrack(name: stream.displayTitle!, id: Int32(stream.index!)) playbackItem = item;
if(stream.isDefault! == true) { } else {
selectedAudioTrack = Int32(stream.index!); //Item will be directly played by the client.
let streamURL: URL = URL(string: "\(globalData.server.baseURI!)/Videos/\(manifest.id!)/stream?Static=true&mediaSourceId=\(manifest.id!)&deviceId=\(globalData.user.device_uuid!)&api_key=\(globalData.authToken)&Tag=\(mediaSource.eTag!)")!;
let item = PlaybackItem()
item.videoUrl = streamURL
item.videoType = .directPlay
let disableSubtitleTrack = Subtitle(name: "Disabled", id: -1, url: URL(string: "https://example.com")!, 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!)
subtitleTrackArray.append(subtitle);
}
if(stream.type == .audio) {
let subtitle = AudioTrack(name: stream.displayTitle!, id: Int32(stream.index!))
if(stream.isDefault! == true) {
selectedAudioTrack = Int32(stream.index!);
}
audioTrackArray.append(subtitle);
} }
audioTrackArray.append(subtitle);
} }
if(selectedAudioTrack == -1) {
if(audioTrackArray.count > 0) {
selectedAudioTrack = audioTrackArray[0].id;
}
}
self.sendPlayReport()
playbackItem = item;
} }
if(selectedAudioTrack == -1) { self.setupNowPlayingCC()
if(audioTrackArray.count > 0) {
selectedAudioTrack = audioTrackArray[0].id;
}
}
self.sendPlayReport()
playbackItem = item;
}
self.setupNowPlayingCC()
DispatchQueue.global(qos: .background).async {
mediaPlayer.media = VLCMedia(url: playbackItem.videoUrl) mediaPlayer.media = VLCMedia(url: playbackItem.videoUrl)
mediaPlayer.play() mediaPlayer.play()
print(manifest.userData?.playbackPositionTicks ?? 0) print(manifest.userData?.playbackPositionTicks ?? 0)
@ -385,9 +385,9 @@ class PlayerViewController: UIViewController, VLCMediaDelegate, VLCMediaPlayerDe
mediaPlayer.currentVideoSubTitleIndex = selectedCaptionTrack; mediaPlayer.currentVideoSubTitleIndex = selectedCaptionTrack;
mediaPlayer.pause() mediaPlayer.pause()
mediaPlayer.play() mediaPlayer.play()
} })
}) .store(in: &globalData.pendingAPIRequests)
.store(in: &globalData.pendingAPIRequests) }
} }
override func viewWillAppear(_ animated: Bool) { override func viewWillAppear(_ animated: Bool) {

View File

@ -34,7 +34,7 @@ class GlobalData: ObservableObject {
@Published var isInNetwork: Bool = true; @Published var isInNetwork: Bool = true;
@Published var networkError: Bool = false; @Published var networkError: Bool = false;
@Published var expiredCredentials: Bool = false; @Published var expiredCredentials: Bool = false;
@Published var pendingAPIRequests = Set<AnyCancellable>(); var pendingAPIRequests = Set<AnyCancellable>();
} }
extension GlobalData: Equatable { extension GlobalData: Equatable {

View File

@ -0,0 +1,43 @@
//
/*
* SwiftFin is subject to the terms of the Mozilla Public
* License, v2.0. If a copy of the MPL was not distributed with this
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
*
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
*/
import Foundation
struct UserSettings: Decodable {
var LocalMaxBitrate: Int;
var RemoteMaxBitrate: Int;
var AutoSelectSubtitles: Bool;
var AutoSelectSubtitlesLangcode: String;
var SubtitlePositionOffset: Int;
var SubtitleFontName: String;
}
struct Bitrates: Codable, Hashable {
public var name: String
public var value: Int
}
final class SettingsViewModel: ObservableObject {
var bitrates: [Bitrates] = []
init() {
let url = Bundle.main.url(forResource: "bitrates", withExtension: "json")!
do {
let jsonData = try Data(contentsOf: url, options: .mappedIfSafe)
do {
self.bitrates = try JSONDecoder().decode([Bitrates].self, from: jsonData)
} catch {
print(error)
}
} catch {
print(error)
}
}
}

View File

@ -3,14 +3,16 @@ update_fastlane
default_platform(:ios) default_platform(:ios)
platform :ios do platform :ios do
desc "Build targets"
lane :build do
gym
end
desc "Push a new beta build to TestFlight" desc "Push a new beta build to TestFlight"
lane :beta do lane :beta_ios do
increment_build_number(xcodeproj: "JellyfinPlayer.xcodeproj") increment_build_number(xcodeproj: "JellyfinPlayer.xcodeproj")
gym(output_name: "JellyfinPlayer.ipa") gym(output_name: "JellyfinPlayer.ipa", scheme: "JellyfinPlayer")
upload_to_testflight upload_to_testflight
end end
end desc "Push a new beta build to TestFlight (tvOS)"
lane :beta_tvos do
increment_build_number(xcodeproj: "JellyfinPlayer.xcodeproj")
gym(output_name: "JellyfinPlayer.ipa", scheme: "JellyfinPlayer tvOS")
upload_to_testflight
end
end

View File

@ -16,16 +16,16 @@ or alternatively using `brew install fastlane`
# Available Actions # Available Actions
## iOS ## iOS
### ios build ### ios beta_ios
``` ```
fastlane ios build fastlane ios beta_ios
```
Build targets
### ios beta
```
fastlane ios beta
``` ```
Push a new beta build to TestFlight Push a new beta build to TestFlight
### ios beta_tvos
```
fastlane ios beta_tvos
```
Push a new beta build to TestFlight (tvOS)
---- ----