get ready for adaptable bitrate streaming
This commit is contained in:
parent
901a60639d
commit
96beaa5771
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue