get ready for adaptable bitrate streaming

This commit is contained in:
Aiden Vigue 2021-05-24 23:04:58 -04:00
parent 901a60639d
commit 96beaa5771
6 changed files with 114 additions and 46 deletions

View File

@ -313,6 +313,13 @@ struct ContentView: View {
SentrySDK.capture(error: error) SentrySDK.capture(error: error)
break break
} }
let defaults = UserDefaults.standard;
if(defaults.integer(forKey: "InNetworkBandwidth") == 0) {
defaults.setValue(40000000, forKey: "inNetworkBandwidth")
}
if(defaults.integer(forKey: "OutOfNetworkBandwidth") == 0) {
defaults.setValue(40000000, forKey: "OutOfNetworkBandwidth")
}
_isLoading.wrappedValue = false; _isLoading.wrappedValue = false;
} }
} catch { } catch {

View File

@ -93,9 +93,15 @@ struct DeviceProfileRoot: Codable {
} }
class DeviceProfileBuilder { class DeviceProfileBuilder {
public var bitrate: Int = 0;
public func setMaxBitrate(bitrate: Int) {
self.bitrate = bitrate
}
public func buildProfile() -> DeviceProfileRoot { public func buildProfile() -> DeviceProfileRoot {
let MaxStreamingBitrate = 120000000; let MaxStreamingBitrate = bitrate;
let MaxStaticBitrate = 100000000 let MaxStaticBitrate = bitrate;
let MusicStreamingTranscodingBitrate = 384000; let MusicStreamingTranscodingBitrate = 384000;
//Build direct play profiles //Build direct play profiles

View File

@ -19,10 +19,6 @@ struct LibraryView: View {
@State private var selected_library_id: String = ""; @State private var selected_library_id: String = "";
@State private var isLoading: Bool = true; @State private var isLoading: Bool = true;
@State private var startIndex: Int = 0;
@State private var endIndex: Int = 60;
@State private var totalItems: Int = 0;
@State private var viewDidLoad: Bool = false; @State private var viewDidLoad: Bool = false;
@State private var filterString: String = "&SortBy=SortName&SortOrder=Descending"; @State private var filterString: String = "&SortBy=SortName&SortOrder=Descending";
@State private var showFiltersPopover: Bool = false; @State private var showFiltersPopover: Bool = false;
@ -92,7 +88,6 @@ struct LibraryView: View {
let body = response.body let body = response.body
do { do {
let json = try JSON(data: body) let json = try JSON(data: body)
_totalItems.wrappedValue = json["TotalRecordCount"].int ?? 0;
for (_,item):(String, JSON) in json["Items"] { for (_,item):(String, JSON) in json["Items"] {
// Do something you want // Do something you want
let itemObj = ResumeItem() let itemObj = ResumeItem()
@ -229,21 +224,6 @@ struct LibraryView: View {
}.frame(width: 100) }.frame(width: 100)
} }
} }
if(startIndex + endIndex < totalItems) {
HStack() {
Spacer()
Button() {
startIndex += endIndex;
loadItems()
} label: {
HStack() {
Text("Load more").font(.callout)
Image(systemName: "arrow.clockwise")
}
}
Spacer()
}
}
Spacer().frame(height: 2) Spacer().frame(height: 2)
} }
} }
@ -255,8 +235,6 @@ struct LibraryView: View {
.onAppear(perform: onAppear) .onAppear(perform: onAppear)
.onChange(of: filterString) { tag in .onChange(of: filterString) { tag in
isLoading = true; isLoading = true;
startIndex = 0;
totalItems = 0;
items = []; items = [];
loadItems(); loadItems();
} }

View File

@ -8,6 +8,13 @@
import SwiftUI import SwiftUI
import CoreData import CoreData
struct UserSettings: Codable {
var LocalMaxBitrate: Int;
var RemoteMaxBitrate: Int;
var AutoSelectSubtitles: Bool;
var AutoSelectSubtitlesLangcode: String;
}
struct SettingsView: View { struct SettingsView: View {
@Binding var close: Bool; @Binding var close: Bool;
@Environment(\.managedObjectContext) private var viewContext @Environment(\.managedObjectContext) private var viewContext
@ -15,9 +22,13 @@ struct SettingsView: View {
@EnvironmentObject var jsi: justSignedIn @EnvironmentObject var jsi: justSignedIn
@State private var username: String = ""; @State private var username: String = "";
@State private var inNetworkStreamBitrate: Int = 40000000; @State private var inNetworkStreamBitrate: Int = 40000000;
@State private var outOfNetworkStreamBitrate: Int = 40000000;
func onAppear() { func onAppear() {
_username.wrappedValue = globalData.user?.username ?? ""; _username.wrappedValue = globalData.user?.username ?? "";
let defaults = UserDefaults.standard
_inNetworkStreamBitrate.wrappedValue = defaults.integer(forKey: "InNetworkBandwidth");
_outOfNetworkStreamBitrate.wrappedValue = defaults.integer(forKey: "OutOfNetworkBandwidth");
} }
var body: some View { var body: some View {
@ -33,16 +44,19 @@ struct SettingsView: View {
Text("1080p - 10 Mbps").tag(10000000) Text("1080p - 10 Mbps").tag(10000000)
} }
Group { Group {
Text("720p - 8 Mbps").tag(80000000) Text("720p - 8 Mbps").tag(8000000)
Text("720p - 6 Mbps").tag(60000000) Text("720p - 6 Mbps").tag(6000000)
Text("720p - 4 Mbps").tag(40000000) Text("720p - 4 Mbps").tag(4000000)
} }
Text("480p - 3 Mbps").tag(30000000) Text("480p - 3 Mbps").tag(3000000)
Text("480p - 1.5 Mbps").tag(20000000) Text("480p - 1.5 Mbps").tag(2000000)
Text("480p - 740 Kbps").tag(10000000) Text("480p - 740 Kbps").tag(1000000)
}.onChange(of: inNetworkStreamBitrate) { _ in
let defaults = UserDefaults.standard
defaults.setValue(_inNetworkStreamBitrate.wrappedValue, forKey: "InNetworkBandwidth")
} }
Picker("Default remote playback bitrate", selection: $inNetworkStreamBitrate) { Picker("Default remote playback bitrate", selection: $outOfNetworkStreamBitrate) {
Group { Group {
Text("1080p - 60 Mbps").tag(60000000) Text("1080p - 60 Mbps").tag(60000000)
Text("1080p - 40 Mbps").tag(40000000) Text("1080p - 40 Mbps").tag(40000000)
@ -51,13 +65,16 @@ struct SettingsView: View {
Text("1080p - 10 Mbps").tag(10000000) Text("1080p - 10 Mbps").tag(10000000)
} }
Group { Group {
Text("720p - 8 Mbps").tag(80000000) Text("720p - 8 Mbps").tag(8000000)
Text("720p - 6 Mbps").tag(60000000) Text("720p - 6 Mbps").tag(6000000)
Text("720p - 4 Mbps").tag(40000000) Text("720p - 4 Mbps").tag(4000000)
} }
Text("480p - 3 Mbps").tag(30000000) Text("480p - 3 Mbps").tag(3000000)
Text("480p - 1.5 Mbps").tag(20000000) Text("480p - 1.5 Mbps").tag(2000000)
Text("480p - 740 Kbps").tag(10000000) Text("480p - 740 Kbps").tag(1000000)
}.onChange(of: outOfNetworkStreamBitrate) { _ in
let defaults = UserDefaults.standard
defaults.setValue(_outOfNetworkStreamBitrate.wrappedValue, forKey: "OutOfNetworkBandwidth")
} }
} }

View File

@ -62,7 +62,7 @@ class PlayerUIView: UIView, VLCMediaPlayerDelegate {
mediaPlayer.wrappedValue.stop() mediaPlayer.wrappedValue.stop()
mediaPlayer.wrappedValue.media = VLCMedia(url: url.wrappedValue.videoUrl) mediaPlayer.wrappedValue.media = VLCMedia(url: url.wrappedValue.videoUrl)
self.url.wrappedValue.subtitles.forEach() { sub in self.url.wrappedValue.subtitles.forEach() { sub in
if(sub.id != -1 && sub.delivery == "External") { if(sub.id != -1 && sub.delivery == "External" && sub.codec != "subrip") {
mediaPlayer.wrappedValue.addPlaybackSlave(sub.url, type: .subtitle, enforce: false) mediaPlayer.wrappedValue.addPlaybackSlave(sub.url, type: .subtitle, enforce: false)
} }
} }

View File

@ -18,6 +18,7 @@ struct Subtitle {
var id: Int32; var id: Int32;
var url: URL; var url: URL;
var delivery: String; var delivery: String;
var codec: String;
} }
extension String { extension String {
@ -57,6 +58,7 @@ struct VideoPlayerView: View {
@State private var iterations: Int = 0; @State private var iterations: Int = 0;
@State private var startTime: Int = 0; @State private var startTime: Int = 0;
@State private var hasSentPlayReport: Bool = false; @State private var hasSentPlayReport: Bool = false;
@State private var selectedVideoQuality: Int = 0;
@State private var captionConfiguration: Bool = false { @State private var captionConfiguration: Bool = false {
didSet { didSet {
if(captionConfiguration == false) { if(captionConfiguration == false) {
@ -72,6 +74,9 @@ struct VideoPlayerView: View {
} }
} }
}; };
@State private var playbackSettings: Bool = false;
@State private var selectedCaptionTrack: Int32 = -1; @State private var selectedCaptionTrack: Int32 = -1;
@State private var selectedAudioTrack: Int32 = -1; @State private var selectedAudioTrack: Int32 = -1;
var playing: Binding<Bool>; var playing: Binding<Bool>;
@ -224,6 +229,15 @@ struct VideoPlayerView: View {
func startStream() { func startStream() {
let builder = DeviceProfileBuilder() let builder = DeviceProfileBuilder()
let defaults = UserDefaults.standard;
if(globalData.isInNetwork) {
builder.setMaxBitrate(bitrate: defaults.integer(forKey: "InNetworkBandwidth"))
} else {
builder.setMaxBitrate(bitrate: defaults.integer(forKey: "OutOfNetworkBandwidth"))
}
_selectedVideoQuality.wrappedValue = builder.bitrate;
let DeviceProfile = builder.buildProfile() let DeviceProfile = builder.buildProfile()
let jsonEncoder = JSONEncoder() let jsonEncoder = JSONEncoder()
@ -254,18 +268,18 @@ struct VideoPlayerView: View {
let streamURL: URL = URL(string: "\(globalData.server?.baseURI ?? "")\((json["MediaSources"][0]["TranscodingUrl"].string ?? ""))")! let streamURL: URL = URL(string: "\(globalData.server?.baseURI ?? "")\((json["MediaSources"][0]["TranscodingUrl"].string ?? ""))")!
print(streamURL) print(streamURL)
let item = PlaybackItem(videoType: VideoType.hls, videoUrl: streamURL, subtitles: []) let item = PlaybackItem(videoType: VideoType.hls, videoUrl: streamURL, subtitles: [])
let disableSubtitleTrack = Subtitle(name: "Disabled", id: -1, url: URL(string: "https://example.com")!, delivery: "Embed") let disableSubtitleTrack = Subtitle(name: "Disabled", id: -1, url: URL(string: "https://example.com")!, delivery: "Embed", codec: "")
_subtitles.wrappedValue.append(disableSubtitleTrack); _subtitles.wrappedValue.append(disableSubtitleTrack);
for (_,stream):(String, JSON) in json["MediaSources"][0]["MediaStreams"] { for (_,stream):(String, JSON) in json["MediaSources"][0]["MediaStreams"] {
if(stream["Type"].string == "Subtitle" && stream["Codec"] != "subrip") { //ignore ripped subtitles - we don't want to extract subtitles if(stream["Type"].string == "Subtitle" && stream["Codec"] != "subrip") { //ignore ripped subtitles - we don't want to extract subtitles
let deliveryUrl = URL(string: "\(globalData.server?.baseURI ?? "")\(stream["DeliveryUrl"].string ?? "")")! let deliveryUrl = URL(string: "\(globalData.server?.baseURI ?? "")\(stream["DeliveryUrl"].string ?? "")")!
let subtitle = Subtitle(name: stream["DisplayTitle"].string ?? "", id: Int32(stream["Index"].int ?? 0), url: deliveryUrl, delivery: stream["DeliveryMethod"].string ?? "") let subtitle = Subtitle(name: stream["DisplayTitle"].string ?? "", id: Int32(stream["Index"].int ?? 0), url: deliveryUrl, delivery: stream["DeliveryMethod"].string ?? "", codec: stream["Codec"].string ?? "")
_subtitles.wrappedValue.append(subtitle); _subtitles.wrappedValue.append(subtitle);
} }
if(stream["Type"].string == "Audio") { if(stream["Type"].string == "Audio") {
let deliveryUrl = URL(string: "https://example.com")! let deliveryUrl = URL(string: "https://example.com")!
let subtitle = Subtitle(name: stream["DisplayTitle"].string ?? "", id: Int32(stream["Index"].int ?? 0), url: deliveryUrl, delivery: stream["IsExternal"].boolValue ? "External" : "Embed") let subtitle = Subtitle(name: stream["DisplayTitle"].string ?? "", id: Int32(stream["Index"].int ?? 0), url: deliveryUrl, delivery: stream["IsExternal"].boolValue ? "External" : "Embed", codec: stream["Codec"].string ?? "")
if(stream["IsDefault"].boolValue) { if(stream["IsDefault"].boolValue) {
_selectedAudioTrack.wrappedValue = Int32(stream["Index"].int ?? 0); _selectedAudioTrack.wrappedValue = Int32(stream["Index"].int ?? 0);
} }
@ -281,7 +295,6 @@ struct VideoPlayerView: View {
let streamUrl = streamURL.absoluteString; let streamUrl = streamURL.absoluteString;
let segmentUrl = URL(string: streamUrl.replacingOccurrences(of: "master.m3u8", with: "hls1/main/0.ts"))! let segmentUrl = URL(string: streamUrl.replacingOccurrences(of: "master.m3u8", with: "hls1/main/0.ts"))!
print(segmentUrl)
var request2 = URLRequest(url: segmentUrl) var request2 = URLRequest(url: segmentUrl)
request2.httpMethod = "GET" request2.httpMethod = "GET"
@ -298,18 +311,18 @@ struct VideoPlayerView: View {
print("Direct playing!"); print("Direct playing!");
let streamURL: URL = URL(string: "\(globalData.server?.baseURI ?? "")/Videos/\(item.Id)/stream?Static=true&mediaSourceId=\(item.Id)&deviceId=\(globalData.user?.device_uuid ?? "")&api_key=\(globalData.authToken)&Tag=\(json["MediaSources"][0]["ETag"])")!; let streamURL: URL = URL(string: "\(globalData.server?.baseURI ?? "")/Videos/\(item.Id)/stream?Static=true&mediaSourceId=\(item.Id)&deviceId=\(globalData.user?.device_uuid ?? "")&api_key=\(globalData.authToken)&Tag=\(json["MediaSources"][0]["ETag"])")!;
let item = PlaybackItem(videoType: VideoType.direct, videoUrl: streamURL, subtitles: []) let item = PlaybackItem(videoType: VideoType.direct, videoUrl: streamURL, subtitles: [])
let disableSubtitleTrack = Subtitle(name: "Disabled", id: -1, url: URL(string: "https://example.com")!, delivery: "Embed") let disableSubtitleTrack = Subtitle(name: "Disabled", id: -1, url: URL(string: "https://example.com")!, delivery: "Embed", codec: "")
_subtitles.wrappedValue.append(disableSubtitleTrack); _subtitles.wrappedValue.append(disableSubtitleTrack);
for (_,stream):(String, JSON) in json["MediaSources"][0]["MediaStreams"] { for (_,stream):(String, JSON) in json["MediaSources"][0]["MediaStreams"] {
if(stream["Type"].string == "Subtitle") { if(stream["Type"].string == "Subtitle" && stream["Codec"] != "subrip") {
let deliveryUrl = URL(string: "\(globalData.server?.baseURI ?? "")\(stream["DeliveryUrl"].string ?? "")")! let deliveryUrl = URL(string: "\(globalData.server?.baseURI ?? "")\(stream["DeliveryUrl"].string ?? "")")!
let subtitle = Subtitle(name: stream["DisplayTitle"].string ?? "", id: Int32(stream["Index"].int ?? 0), url: deliveryUrl, delivery: stream["DeliveryMethod"].string ?? "") let subtitle = Subtitle(name: stream["DisplayTitle"].string ?? "", id: Int32(stream["Index"].int ?? 0), url: deliveryUrl, delivery: stream["DeliveryMethod"].string ?? "", codec: stream["Codec"].string ?? "")
_subtitles.wrappedValue.append(subtitle); _subtitles.wrappedValue.append(subtitle);
} }
if(stream["Type"].string == "Audio") { if(stream["Type"].string == "Audio") {
let deliveryUrl = URL(string: "https://example.com")! let deliveryUrl = URL(string: "https://example.com")!
let subtitle = Subtitle(name: stream["DisplayTitle"].string ?? "", id: Int32(stream["Index"].int ?? 0), url: deliveryUrl, delivery: stream["IsExternal"].boolValue ? "External" : "Embed") let subtitle = Subtitle(name: stream["DisplayTitle"].string ?? "", id: Int32(stream["Index"].int ?? 0), url: deliveryUrl, delivery: stream["IsExternal"].boolValue ? "External" : "Embed", codec: stream["Codec"].string ?? "")
if(stream["IsDefault"].boolValue) { if(stream["IsDefault"].boolValue) {
_selectedAudioTrack.wrappedValue = Int32(stream["Index"].int ?? 0); _selectedAudioTrack.wrappedValue = Int32(stream["Index"].int ?? 0);
} }
@ -391,6 +404,14 @@ struct VideoPlayerView: View {
Spacer() Spacer()
Text(item.Name).font(.headline).fontWeight(.semibold).foregroundColor(.white).offset(x:-4) Text(item.Name).font(.headline).fontWeight(.semibold).foregroundColor(.white).offset(x:-4)
Spacer() Spacer()
Button() {
vlcplayer.pause()
self.playbackSettings = true;
} label: {
HStack() {
Image(systemName: "gear").font(.system(size: 20)).foregroundColor(.white)
}
}.frame(width: 20).padding(.trailing,15)
Button() { Button() {
vlcplayer.pause() vlcplayer.pause()
self.captionConfiguration = true; self.captionConfiguration = true;
@ -516,5 +537,44 @@ struct VideoPlayerView: View {
} }
}.edgesIgnoringSafeArea(.bottom) }.edgesIgnoringSafeArea(.bottom)
} }
EmptyView()
.fullScreenCover(isPresented: $playbackSettings) {
NavigationView() {
Form() {
Picker("Quality", selection: $selectedVideoQuality) {
Group {
Text("1080p - 60 Mbps").tag(60000000)
Text("1080p - 40 Mbps").tag(40000000)
Text("1080p - 20 Mbps").tag(20000000)
Text("1080p - 15 Mbps").tag(15000000)
Text("1080p - 10 Mbps").tag(10000000)
}
Group {
Text("720p - 8 Mbps").tag(8000000)
Text("720p - 6 Mbps").tag(6000000)
Text("720p - 4 Mbps").tag(4000000)
}
Text("480p - 3 Mbps").tag(3000000)
Text("480p - 1.5 Mbps").tag(2000000)
Text("480p - 740 Kbps").tag(1000000)
}.onChange(of: selectedVideoQuality) { quality in
print(quality)
}
}
.navigationBarTitle("Playback Settings", displayMode: .inline)
.toolbar {
ToolbarItemGroup(placement: .navigationBarLeading) {
Button {
playbackSettings = false;
playPauseButtonSystemName = "pause";
} label: {
HStack() {
Text("Back").font(.callout)
}
}
}
}
}.edgesIgnoringSafeArea(.bottom)
}
} }
} }