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>
<string>$(MARKETING_VERSION)</string>
<key>CFBundleVersion</key>
<string>1</string>
<string>36</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchScreen</key>

View File

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

View File

@ -5,8 +5,6 @@
* 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 CoreData
import KeychainSwift

View File

@ -85,36 +85,38 @@ struct ContentView: View {
JellyfinAPI.basePath = globalData.server.baseURI ?? ""
JellyfinAPI.customHeaders = ["X-Emby-Authorization": globalData.authHeader]
UserAPI.getCurrentUser()
.sink(receiveCompletion: { completion in
HandleAPIRequestCompletion(globalData: globalData, completion: completion)
loadState = loadState - 1
}, receiveValue: { response in
libraries = response.configuration?.orderedViews ?? []
librariesShowRecentlyAdded = libraries.filter { element in
return !(response.configuration?.latestItemsExcludes?.contains(element))!
}
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
DispatchQueue.global(qos: .userInitiated).async {
UserAPI.getCurrentUser()
.sink(receiveCompletion: { completion in
HandleAPIRequestCompletion(globalData: globalData, completion: completion)
loadState = loadState - 1
}, receiveValue: { response in
libraries = response.configuration?.orderedViews ?? []
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)
.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
})
if(loadState == 1) {
isLoading = false
}
})
.store(in: &globalData.pendingAPIRequests)
}
let defaults = UserDefaults.standard
if defaults.integer(forKey: "InNetworkBandwidth") == 0 {

View File

@ -37,13 +37,15 @@ struct ContinueWatchingView: View {
@State private var items: [BaseItemDto] = []
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])
.sink(receiveCompletion: { completion in
HandleAPIRequestCompletion(globalData: globalData, completion: completion)
}, receiveValue: { response in
items = response.items ?? []
})
.store(in: &globalData.pendingAPIRequests)
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])
.sink(receiveCompletion: { completion in
HandleAPIRequestCompletion(globalData: globalData, completion: completion)
}, receiveValue: { response in
items = response.items ?? []
})
.store(in: &globalData.pendingAPIRequests)
}
}
var body: some View {

View File

@ -27,7 +27,7 @@ network.</string>
<key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string>
<key>CFBundleVersion</key>
<string>33</string>
<string>36</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<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">
<plist version="1.0">
<dict>
<key>com.apple.developer.coremedia.hls.low-latency</key>
<true/>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.developer.coremedia.hls.low-latency</key>
<true/>
</dict>
</plist>

View File

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

View File

@ -30,15 +30,17 @@ struct LibrarySearchView: View {
func requestSearch(query: String) {
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)
.sink(receiveCompletion: { completion in
HandleAPIRequestCompletion(globalData: globalData, completion: completion)
}, receiveValue: { response in
items = response.items ?? []
isLoading = false
})
.store(in: &globalData.pendingAPIRequests)
DispatchQueue.global(qos: .userInitiated).async {
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)
.sink(receiveCompletion: { completion in
HandleAPIRequestCompletion(globalData: globalData, completion: completion)
}, receiveValue: { response in
items = response.items ?? []
isLoading = false
})
.store(in: &globalData.pendingAPIRequests)
}
}
//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
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
*
@ -61,17 +62,19 @@ struct LibraryView: View {
isLoading = true
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)
.sink(receiveCompletion: { completion in
HandleAPIRequestCompletion(globalData: globalData, completion: completion)
isLoading = false
}, receiveValue: { response in
let x = ceil(Double(response.totalRecordCount!) / 100.0)
totalPages = Int(x)
items = response.items ?? []
isLoading = false
})
.store(in: &globalData.pendingAPIRequests)
DispatchQueue.global(qos: .userInitiated).async {
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)
.sink(receiveCompletion: { completion in
HandleAPIRequestCompletion(globalData: globalData, completion: completion)
isLoading = false
}, receiveValue: { response in
let x = ceil(Double(response.totalRecordCount!) / 100.0)
totalPages = Int(x)
items = response.items ?? []
isLoading = false
})
.store(in: &globalData.pendingAPIRequests)
}
}
//MARK: tracks for grid

View File

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

View File

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

View File

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

View File

@ -9,30 +9,20 @@ import CoreData
import SwiftUI
struct SettingsView: View {
@ObservedObject
var viewModel: SettingsViewModel
@Environment(\.managedObjectContext) private var viewContext
@EnvironmentObject var globalData: GlobalData
@EnvironmentObject var jsi: justSignedIn
@ObservedObject var viewModel: SettingsViewModel
@Binding
var close: Bool
@Environment(\.managedObjectContext)
private var viewContext
@EnvironmentObject
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"
@Binding var close: Bool
@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() {
_username.wrappedValue = globalData.user.username ?? ""
let defaults = UserDefaults.standard
_inNetworkStreamBitrate.wrappedValue = defaults.integer(forKey: "InNetworkBandwidth")
_outOfNetworkStreamBitrate.wrappedValue = defaults.integer(forKey: "OutOfNetworkBandwidth")
@ -73,7 +63,7 @@ struct SettingsView: View {
Section {
HStack {
Text("Signed in as \(username)").foregroundColor(.primary)
Text("Signed in as \(globalData.user.username!)").foregroundColor(.primary)
Spacer()
Button {
let fetchRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest(entityName: "Server")
@ -121,36 +111,3 @@ struct SettingsView: View {
}.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)
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!
let mediaSource = response.mediaSources!.first.self!
if(mediaSource.transcodingUrl != nil) {
//Item is being transcoded by request of server
let streamURL = URL(string: "\(globalData.server.baseURI!)\(mediaSource.transcodingUrl!)")
let item = PlaybackItem()
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);
//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);
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)
.sink(receiveCompletion: { completion in
HandleAPIRequestCompletion(globalData: self.globalData, completion: completion)
}, receiveValue: { [self] response in
playSessionId = response.playSessionId!
let mediaSource = response.mediaSources!.first.self!
if(mediaSource.transcodingUrl != nil) {
//Item is being transcoded by request of server
let streamURL = URL(string: "\(globalData.server.baseURI!)\(mediaSource.transcodingUrl!)")
let item = PlaybackItem()
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);
//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);
}
}
if(stream.type == .audio) {
let subtitle = AudioTrack(name: stream.displayTitle!, id: Int32(stream.index!))
if(stream.isDefault! == true) {
selectedAudioTrack = Int32(stream.index!);
if(selectedAudioTrack == -1) {
if(audioTrackArray.count > 0) {
selectedAudioTrack = audioTrackArray[0].id;
}
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) {
let subtitle = AudioTrack(name: stream.displayTitle!, id: Int32(stream.index!))
if(stream.isDefault! == true) {
selectedAudioTrack = Int32(stream.index!);
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) {
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) {
if(audioTrackArray.count > 0) {
selectedAudioTrack = audioTrackArray[0].id;
}
}
self.setupNowPlayingCC()
self.sendPlayReport()
playbackItem = item;
}
self.setupNowPlayingCC()
DispatchQueue.global(qos: .background).async {
mediaPlayer.media = VLCMedia(url: playbackItem.videoUrl)
mediaPlayer.play()
print(manifest.userData?.playbackPositionTicks ?? 0)
@ -385,9 +385,9 @@ class PlayerViewController: UIViewController, VLCMediaDelegate, VLCMediaPlayerDe
mediaPlayer.currentVideoSubTitleIndex = selectedCaptionTrack;
mediaPlayer.pause()
mediaPlayer.play()
}
})
.store(in: &globalData.pendingAPIRequests)
})
.store(in: &globalData.pendingAPIRequests)
}
}
override func viewWillAppear(_ animated: Bool) {

View File

@ -34,7 +34,7 @@ class GlobalData: ObservableObject {
@Published var isInNetwork: Bool = true;
@Published var networkError: Bool = false;
@Published var expiredCredentials: Bool = false;
@Published var pendingAPIRequests = Set<AnyCancellable>();
var pendingAPIRequests = Set<AnyCancellable>();
}
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)
platform :ios do
desc "Build targets"
lane :build do
gym
end
desc "Push a new beta build to TestFlight"
lane :beta do
lane :beta_ios do
increment_build_number(xcodeproj: "JellyfinPlayer.xcodeproj")
gym(output_name: "JellyfinPlayer.ipa")
gym(output_name: "JellyfinPlayer.ipa", scheme: "JellyfinPlayer")
upload_to_testflight
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
## iOS
### ios build
### ios beta_ios
```
fastlane ios build
```
Build targets
### ios beta
```
fastlane ios beta
fastlane ios beta_ios
```
Push a new beta build to TestFlight
### ios beta_tvos
```
fastlane ios beta_tvos
```
Push a new beta build to TestFlight (tvOS)
----