start rewriting videoplayerview

This commit is contained in:
Aiden Vigue 2021-05-26 12:58:07 -04:00
parent d2f652b014
commit 1ee7905e4c
No known key found for this signature in database
GPG Key ID: E7570472648F4544
30 changed files with 449 additions and 71 deletions

View File

@ -102,7 +102,6 @@
"size" : "83.5x83.5"
},
{
"filename" : "Icon.png",
"idiom" : "ios-marketing",
"scale" : "1x",
"size" : "1024x1024"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 232 KiB

View File

@ -206,17 +206,17 @@ struct ContentView: View {
}
Spacer().frame(height: 7)
}
}
.navigationTitle("Home")
.toolbar {
ToolbarItemGroup(placement: .navigationBarTrailing) {
Button {
showSettingsPopover = true;
} label: {
Image(systemName: "gear")
.navigationTitle("Home")
.toolbar {
ToolbarItemGroup(placement: .navigationBarTrailing) {
Button {
showSettingsPopover = true;
} label: {
Image(systemName: "gear")
}
}
}
}.fullScreenCover( isPresented: $showSettingsPopover) { SettingsView(viewModel: SettingsViewModel(), close: $showSettingsPopover) }
}.fullScreenCover( isPresented: $showSettingsPopover) { SettingsView(viewModel: SettingsViewModel(), close: $showSettingsPopover) }
}
}
.navigationViewStyle(StackNavigationViewStyle())
.tabItem({

View File

@ -11,13 +11,13 @@ import SwiftyJSON
import SDWebImageSwiftUI
struct EpisodeItemView: View {
@EnvironmentObject var globalData: GlobalData
@State private var isLoading: Bool = true;
@EnvironmentObject private var globalData: GlobalData
@EnvironmentObject private var orientationInfo: OrientationInfo
@EnvironmentObject private var playbackInfo: ItemPlayback
var item: ResumeItem;
@EnvironmentObject var orientationInfo: OrientationInfo
var fullItem: DetailItem;
@State private var playing: Bool = false;
@State private var isLoading: Bool = true;
@State private var progressString: String = "";
@State private var viewDidLoad: Bool = false;
@ -195,7 +195,7 @@ struct EpisodeItemView: View {
if(orientationInfo.orientation == .portrait) {
GeometryReader { geometry in
VStack() {
WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(fullItem.ParentBackdropItemId)/Images/Backdrop?maxWidth=450&quality=90&tag=\(fullItem.Backdrop)")!)
WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(fullItem.ParentBackdropItemId)/Images/Backdrop?maxWidth=550&quality=90&tag=\(fullItem.Backdrop)")!)
.resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size
.placeholder {
Image(uiImage: UIImage(blurHash: (fullItem.BackdropBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : fullItem.BackdropBlurHash), size: CGSize(width: 32, height: 32))!)
@ -263,7 +263,10 @@ struct EpisodeItemView: View {
VStack(alignment: .leading) {
HStack() {
//Play button
NavigationLink(destination: VideoPlayerViewRefactored()) {
Button {
self.playbackInfo.itemToPlay = fullItem;
self.playbackInfo.shouldPlay = true;
} label: {
HStack() {
Text(fullItem.Progress == 0 ? "Play" : "\(progressString) left").foregroundColor(Color.white).font(.callout).fontWeight(.semibold)
Image(systemName: "play.fill").foregroundColor(Color.white).font(.system(size: 20))
@ -271,7 +274,8 @@ struct EpisodeItemView: View {
.frame(width: 120, height: 35)
.background(Color(red: 172/255, green: 92/255, blue: 195/255))
.cornerRadius(10)
}
}.buttonStyle(PlainButtonStyle())
.frame(width: 120, height: 35)
Spacer()
HStack() {
Button() {
@ -324,15 +328,15 @@ struct EpisodeItemView: View {
WebImage(url: cast.Image)
.resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size
.placeholder {
Image(uiImage: UIImage(blurHash: (cast.ImageBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : cast.ImageBlurHash), size: CGSize(width: 4, height: 4))!)
Image(uiImage: UIImage(blurHash: (cast.ImageBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : cast.ImageBlurHash), size: CGSize(width: 16, height: 16))!)
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 70, height: 70)
.frame(width: 100, height: 100)
.cornerRadius(10)
}
.aspectRatio(contentMode: .fill)
.frame(width: 100, height: 100)
.cornerRadius(10).shadow(radius: 6)
.cornerRadius(10)
Text(cast.Name).font(.footnote).fontWeight(.regular).lineLimit(1).frame(width: 100).foregroundColor(Color.primary)
if(cast.Role != "") {
Text(cast.Role).font(.caption).fontWeight(.medium).lineLimit(1).foregroundColor(Color.secondary).frame(width: 100)
@ -399,7 +403,10 @@ struct EpisodeItemView: View {
.cornerRadius(10)
.shadow(radius: 5)
Spacer().frame(height: 15)
NavigationLink(destination: VideoPlayerViewRefactored()) {
Button {
self.playbackInfo.itemToPlay = fullItem;
self.playbackInfo.shouldPlay = true;
} label: {
HStack() {
Text(fullItem.Progress == 0 ? "Play" : "\(progressString) left").foregroundColor(Color.white).font(.callout).fontWeight(.semibold)
Image(systemName: "play.fill").foregroundColor(Color.white).font(.system(size: 20))
@ -407,7 +414,8 @@ struct EpisodeItemView: View {
.frame(width: 120, height: 35)
.background(Color(red: 172/255, green: 92/255, blue: 195/255))
.cornerRadius(10)
}
}.buttonStyle(PlainButtonStyle())
.frame(width: 120, height: 35)
Spacer()
}
ScrollView() {
@ -504,7 +512,7 @@ struct EpisodeItemView: View {
WebImage(url: cast.Image)
.resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size
.placeholder {
Image(uiImage: UIImage(blurHash: (cast.ImageBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : cast.ImageBlurHash), size: CGSize(width: 32, height: 32))!)
Image(uiImage: UIImage(blurHash: (cast.ImageBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : cast.ImageBlurHash), size: CGSize(width: 16, height: 16))!)
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 100, height: 100)
@ -512,7 +520,7 @@ struct EpisodeItemView: View {
}
.aspectRatio(contentMode: .fill)
.frame(width: 100, height: 100)
.cornerRadius(10).shadow(radius: 6)
.cornerRadius(10)
Text(cast.Name).font(.footnote).fontWeight(.regular).lineLimit(1).frame(width: 100).foregroundColor(Color.primary)
if(cast.Role != "") {
Text(cast.Role).font(.caption).fontWeight(.medium).lineLimit(1).foregroundColor(Color.secondary).frame(width: 100)

View File

@ -8,39 +8,48 @@
import SwiftUI
import Introspect
class ItemPlayback: ObservableObject {
@Published var shouldPlay: Bool = false;
@Published var itemToPlay: DetailItem = DetailItem();
}
struct ItemView: View {
var item: ResumeItem;
@StateObject private var playback: ItemPlayback = ItemPlayback()
init(item: ResumeItem) {
self.item = item;
}
var body: some View {
Group {
NavigationLink(destination: EmptyView(), label: {})
NavigationLink(destination: EmptyView(), label: {})
if(item.Type == "Movie") {
MovieItemView(item: self.item)
} else if(item.Type == "Season") {
SeasonItemView(item: self.item)
} else if(item.Type == "Series") {
SeriesItemView(item: self.item)
} else if(item.Type == "Episode") {
EpisodeItemView(item: self.item)
} else {
Text("Type: \(item.Type) not implemented yet :(")
if(playback.shouldPlay) {
VideoPlayerViewRefactored(itemPlayback: playback)
} else {
Group {
if(item.Type == "Movie") {
MovieItemView(item: self.item)
} else if(item.Type == "Season") {
SeasonItemView(item: self.item)
} else if(item.Type == "Series") {
SeriesItemView(item: self.item)
} else if(item.Type == "Episode") {
EpisodeItemView(item: self.item)
} else {
Text("Type: \(item.Type) not implemented yet :(")
}
}
.introspectTabBarController { (UITabBarController) in
UITabBarController.tabBar.isHidden = false
}
.navigationBarHidden(false)
.navigationBarBackButtonHidden(false)
.statusBar(hidden: false)
.prefersHomeIndicatorAutoHidden(false)
.preferredColorScheme(.none)
.edgesIgnoringSafeArea([])
.overrideViewPreference(.unspecified)
.supportedOrientations(.allButUpsideDown)
.environmentObject(playback)
}
.introspectTabBarController { (UITabBarController) in
UITabBarController.tabBar.isHidden = false
}
.navigationBarHidden(false)
.navigationBarBackButtonHidden(false)
.statusBar(hidden: false)
.prefersHomeIndicatorAutoHidden(false)
.preferredColorScheme(.none)
.edgesIgnoringSafeArea([])
.overrideViewPreference(.unspecified)
.supportedOrientations(.allButUpsideDown)
}
}

View File

@ -181,6 +181,24 @@ extension View {
}
}
extension String {
public func leftPad(toWidth width: Int, withString string: String?) -> String {
let paddingString = string ?? " "
if self.count >= width {
return self
}
let remainingLength: Int = width - self.count
var padString = String()
for _ in 0 ..< remainingLength {
padString += paddingString
}
return "\(padString)\(self)"
}
}
@main
struct JellyfinPlayerApp: App {
let persistenceController = PersistenceController.shared

View File

@ -60,16 +60,17 @@ class CastMember: ObservableObject {
}
struct MovieItemView: View {
@EnvironmentObject var globalData: GlobalData
@EnvironmentObject var orientationInfo: OrientationInfo
@EnvironmentObject private var globalData: GlobalData
@EnvironmentObject private var orientationInfo: OrientationInfo
@EnvironmentObject private var playbackInfo: ItemPlayback
@State private var isLoading: Bool = true;
var item: ResumeItem;
var fullItem: DetailItem;
@State private var playing: Bool = false;
@State private var progressString: String = "";
@State private var viewDidLoad: Bool = false;
@State private var watched: Bool = false {
didSet {
if(watched == true) {
@ -242,7 +243,7 @@ struct MovieItemView: View {
if(orientationInfo.orientation == .portrait) {
GeometryReader { geometry in
VStack() {
WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(fullItem.Id)/Images/Backdrop?maxWidth=450&quality=90&tag=\(fullItem.Backdrop)")!)
WebImage(url: URL(string: "\(globalData.server?.baseURI ?? "")/Items/\(fullItem.Id)/Images/Backdrop?maxWidth=550&quality=90&tag=\(fullItem.Backdrop)")!)
.resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size
.placeholder {
Image(uiImage: UIImage(blurHash: (fullItem.BackdropBlurHash == "" ? "W$H.4}D%bdo#a#xbtpxVW?W?jXWsXVt7Rjf5axWqxbWXnhada{s-" : fullItem.BackdropBlurHash), size: CGSize(width: 32, height: 32))!)
@ -310,7 +311,10 @@ struct MovieItemView: View {
VStack(alignment: .leading) {
HStack() {
//Play button
NavigationLink(destination: VideoPlayerViewRefactored()) {
Button {
self.playbackInfo.itemToPlay = fullItem;
self.playbackInfo.shouldPlay = true;
} label: {
HStack() {
Text(fullItem.Progress == 0 ? "Play" : "\(progressString) left").foregroundColor(Color.white).font(.callout).fontWeight(.semibold)
Image(systemName: "play.fill").foregroundColor(Color.white).font(.system(size: 20))
@ -318,7 +322,8 @@ struct MovieItemView: View {
.frame(width: 120, height: 35)
.background(Color(red: 172/255, green: 92/255, blue: 195/255))
.cornerRadius(10)
}
}.buttonStyle(PlainButtonStyle())
.frame(width: 120, height: 35)
Spacer()
HStack() {
Button() {
@ -379,7 +384,7 @@ struct MovieItemView: View {
}
.aspectRatio(contentMode: .fill)
.frame(width: 100, height: 100)
.cornerRadius(10).shadow(radius: 6)
.cornerRadius(10)
Text(cast.Name).font(.footnote).fontWeight(.regular).lineLimit(1).frame(width: 100).foregroundColor(Color.primary)
if(cast.Role != "") {
Text(cast.Role).font(.caption).fontWeight(.medium).lineLimit(1).foregroundColor(Color.secondary).frame(width: 100)
@ -445,7 +450,10 @@ struct MovieItemView: View {
.cornerRadius(10)
.shadow(radius: 5)
Spacer().frame(height: 15)
NavigationLink(destination: VideoPlayerViewRefactored()) {
Button {
self.playbackInfo.itemToPlay = fullItem;
self.playbackInfo.shouldPlay = true;
} label: {
HStack() {
Text(fullItem.Progress == 0 ? "Play" : "\(progressString) left").foregroundColor(Color.white).font(.callout).fontWeight(.semibold)
Image(systemName: "play.fill").foregroundColor(Color.white).font(.system(size: 20))
@ -453,7 +461,8 @@ struct MovieItemView: View {
.frame(width: 120, height: 35)
.background(Color(red: 172/255, green: 92/255, blue: 195/255))
.cornerRadius(10)
}
}.buttonStyle(PlainButtonStyle())
.frame(width: 120, height: 35)
Spacer()
}
ScrollView() {
@ -558,7 +567,7 @@ struct MovieItemView: View {
}
.aspectRatio(contentMode: .fill)
.frame(width: 100, height: 100)
.cornerRadius(10).shadow(radius: 6)
.cornerRadius(10)
Text(cast.Name).font(.footnote).fontWeight(.regular).lineLimit(1).frame(width: 100).foregroundColor(Color.primary)
if(cast.Role != "") {
Text(cast.Role).font(.caption).fontWeight(.medium).lineLimit(1).foregroundColor(Color.secondary).frame(width: 100)

View File

@ -17,10 +17,18 @@ enum VideoType {
case direct;
}
struct PlaybackItem {
var videoType: VideoType;
var videoUrl: URL;
var subtitles: [Subtitle];
struct Subtitle {
var name: String;
var id: Int32;
var url: URL;
var delivery: String;
var codec: String;
}
class PlaybackItem: ObservableObject {
@Published var videoType: VideoType = .hls;
@Published var videoUrl: URL = URL(string: "https://example.com")!;
@Published var subtitles: [Subtitle] = [];
}
struct VLCPlayer: UIViewRepresentable{

View File

@ -4,7 +4,7 @@
//
// Created by Aiden Vigue on 5/10/21.
//
/*
import SwiftUI
import SwiftyJSON
import SwiftyRequest
@ -41,7 +41,6 @@ extension String {
struct VideoPlayerView: View {
@EnvironmentObject var globalData: GlobalData
var item: DetailItem;
@State private var pbitem: PlaybackItem = PlaybackItem(videoType: VideoType.direct, videoUrl: URL(string: "https://example.com")!, subtitles: []);
@State private var streamLoading = false;
@State private var vlcplayer: VLCMediaPlayer = VLCMediaPlayer();
@ -76,10 +75,11 @@ struct VideoPlayerView: View {
};
@State private var playbackSettings: Bool = false;
@State private var selectedCaptionTrack: Int32 = -1;
@State private var selectedAudioTrack: Int32 = -1;
var playing: Binding<Bool>;
var item: DetailItem;
init(item: DetailItem, playing: Binding<Bool>) {
self.item = item;
@ -582,3 +582,4 @@ struct VideoPlayerView: View {
}
}
}
*/

View File

@ -8,17 +8,137 @@
import SwiftUI
import MobileVLCKit
import Introspect
import SwiftyJSON
import SwiftyRequest
struct VideoPlayerViewRefactored: View {
@EnvironmentObject private var globalData: GlobalData;
@State private var shouldShowLoadingView: Bool = true;
@State private var itemPlayback: ItemPlayback;
@State private var VLCPlayerObj = VLCMediaPlayer()
@State private var scrub: Double = 0; // storage value for scrubbing
@State private var timeText: String = "-:--:--"; //shows time text on play overlay
@State private var startTime: Int = 0; //ticks since 1970
@State private var selectedAudioTrack: Int32 = 0;
@State private var selectedCaptionTrack: Int32 = 0;
@State private var playSessionId: String = "";
@State private var shouldOverlayShow: Bool = false;
@State private var subtitles: [Subtitle] = [];
@State private var audioTracks: [Subtitle] = []; // can reuse the same struct
@State private var VLCItem: PlaybackItem = PlaybackItem();
init(itemPlayback: ItemPlayback) {
self.itemPlayback = itemPlayback
}
var body: some View {
LoadingView(isShowing: $shouldShowLoadingView) {
Text("content")
.introspectTabBarController { (UITabBarController) in
UITabBarController.tabBar.isHidden = true
}
VLCPlayer(url: $VLCItem, player: $VLCPlayerObj, startTime: Int(itemPlayback.itemToPlay.Progress)).onDisappear(perform: {
VLCPlayerObj.stop()
})
.padding(EdgeInsets(top: 0, leading: UIDevice.current.hasNotch ? 30 : 0, bottom: 0, trailing: UIDevice.current.hasNotch ? 30 : 0))
}
.overlay(
Group {
if(shouldOverlayShow) {
VStack() {
HStack() {
HStack() {
Button() {
sendStopReport()
self.itemPlayback.shouldPlay = false;
} label: {
HStack() {
Image(systemName: "chevron.left").font(.system(size: 20)).foregroundColor(.white)
}
}.frame(width: 20)
Spacer()
Text(itemPlayback.itemToPlay.Name).font(.headline).fontWeight(.semibold).foregroundColor(.white).offset(x:20)
Spacer()
Button() {
VLCPlayerObj.pause()
} label: {
HStack() {
Image(systemName: "gear").font(.system(size: 20)).foregroundColor(.white)
}
}.frame(width: 20).padding(.trailing,15)
Button() {
VLCPlayerObj.pause()
} label: {
HStack() {
Image(systemName: "captions.bubble").font(.system(size: 20)).foregroundColor(.white)
}
}.frame(width: 20)
}
Spacer()
}.padding(EdgeInsets(top: 55, leading: 40, bottom: 0, trailing: 40))
Spacer()
HStack() {
Spacer()
Button() {
VLCPlayerObj.jumpBackward(15)
} label: {
Image(systemName: "gobackward.15").font(.system(size: 40)).foregroundColor(.white)
}.padding(20)
Spacer()
Button() {
if(VLCPlayerObj.state != .paused) {
VLCPlayerObj.pause()
sendProgressReport(eventName: "pause")
} else {
VLCPlayerObj.play()
sendProgressReport(eventName: "unpause")
}
} label: {
Image(systemName: VLCPlayerObj.state == .paused ? "play" : "pause").font(.system(size: 55)).foregroundColor(.white)
}.padding(20).frame(width: 60, height: 60)
Spacer()
Button() {
VLCPlayerObj.jumpForward(15)
} label: {
Image(systemName: "goforward.15").font(.system(size: 40)).foregroundColor(.white)
}.padding(20)
Spacer()
}.padding(.leading, -20)
Spacer()
HStack() {
Slider(value: $scrub, onEditingChanged: { bool in
let videoPosition = Double(VLCPlayerObj.time.intValue)
let videoDuration = Double(VLCPlayerObj.time.intValue + abs(VLCPlayerObj.remainingTime.intValue))
if(bool == true) {
VLCPlayerObj.pause()
sendProgressReport(eventName: "pause")
} else {
//Scrub is value from 0..1 - find position in video and add / or remove.
let secondsScrubbedTo = round(_scrub.wrappedValue * videoDuration);
let offset = secondsScrubbedTo - videoPosition;
sendProgressReport(eventName: "unpause")
VLCPlayerObj.play()
if(offset > 0) {
VLCPlayerObj.jumpForward(Int32(offset)/1000);
} else {
VLCPlayerObj.jumpBackward(Int32(abs(offset))/1000);
}
}
})
.accentColor(Color(red: 172/255, green: 92/255, blue: 195/255))
Text(timeText).fontWeight(.semibold).frame(width: 80).foregroundColor(.white)
}.padding(EdgeInsets(top: -20, leading: 44, bottom: 42, trailing: 40))
}
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
.background(Color(.black).opacity(0.4))
}
}
, alignment: .topLeading)
.introspectTabBarController { (UITabBarController) in
UITabBarController.tabBar.isHidden = true
}
.onTapGesture(perform: resetTimer)
.navigationBarHidden(true)
.navigationBarBackButtonHidden(true)
.statusBar(hidden: true)
@ -28,5 +148,211 @@ struct VideoPlayerViewRefactored: View {
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
.overrideViewPreference(.unspecified)
.supportedOrientations(.landscape)
.onAppear(perform: onAppear)
}
func onAppear() {
shouldShowLoadingView = true;
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"))
}
let DeviceProfile = builder.buildProfile()
let jsonEncoder = JSONEncoder()
let jsonData = try! jsonEncoder.encode(DeviceProfile)
let url = (globalData.server?.baseURI ?? "") + "/Items/\(itemPlayback.itemToPlay.Id)/PlaybackInfo?UserId=\(globalData.user?.user_id ?? "")&StartTimeTicks=\(Int(itemPlayback.itemToPlay.Progress))&IsPlayback=true&AutoOpenLiveStream=true&MaxStreamingBitrate=\(DeviceProfile.DeviceProfile.MaxStreamingBitrate)";
let request = RestRequest(method: .post, url: url)
request.headerParameters["X-Emby-Authorization"] = globalData.authHeader
request.contentType = "application/json"
request.acceptType = "application/json"
request.messageBody = jsonData
request.responseData() { (result: Result<RestResponse<Data>, RestError>) in
switch result {
case .success(let response):
let body = response.body
do {
let json = try JSON(data: body)
_playSessionId.wrappedValue = json["PlaySessionId"].string ?? "";
if(json["MediaSources"][0]["TranscodingUrl"].string != nil) {
let streamURL: URL = URL(string: "\(globalData.server?.baseURI ?? "")\((json["MediaSources"][0]["TranscodingUrl"].string ?? ""))")!
let item = PlaybackItem()
item.videoType = .hls
item.videoUrl = streamURL
let disableSubtitleTrack = Subtitle(name: "Disabled", id: -1, url: URL(string: "https://example.com")!, delivery: "Embed", codec: "")
_subtitles.wrappedValue.append(disableSubtitleTrack);
for (_,stream):(String, JSON) in json["MediaSources"][0]["MediaStreams"] {
if(stream["Type"].string == "Subtitle") { //ignore ripped subtitles - we don't want to extract subtitles
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 ?? "", codec: stream["Codec"].string ?? "")
_subtitles.wrappedValue.append(subtitle);
}
if(stream["Type"].string == "Audio") {
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", codec: stream["Codec"].string ?? "")
if(stream["IsDefault"].boolValue) {
_selectedAudioTrack.wrappedValue = Int32(stream["Index"].int ?? 0);
}
_audioTracks.wrappedValue.append(subtitle);
}
}
if(_selectedAudioTrack.wrappedValue == -1) {
if(_audioTracks.wrappedValue.count > 0) {
_selectedAudioTrack.wrappedValue = _audioTracks.wrappedValue[0].id;
}
}
self.sendPlayReport()
VLCItem = item;
VLCItem.subtitles = subtitles;
} else {
print("Direct playing!");
let streamURL: URL = URL(string: "\(globalData.server?.baseURI ?? "")/Videos/\(itemPlayback.itemToPlay.Id)/stream?Static=true&mediaSourceId=\(itemPlayback.itemToPlay.Id)&deviceId=\(globalData.user?.device_uuid ?? "")&api_key=\(globalData.authToken)&Tag=\(json["MediaSources"][0]["ETag"])")!;
let item = PlaybackItem()
item.videoUrl = streamURL
item.videoType = .direct
let disableSubtitleTrack = Subtitle(name: "Disabled", id: -1, url: URL(string: "https://example.com")!, delivery: "Embed", codec: "")
_subtitles.wrappedValue.append(disableSubtitleTrack);
for (_,stream):(String, JSON) in json["MediaSources"][0]["MediaStreams"] {
if(stream["Type"].string == "Subtitle") {
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 ?? "", codec: stream["Codec"].string ?? "")
_subtitles.wrappedValue.append(subtitle);
}
if(stream["Type"].string == "Audio") {
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", codec: stream["Codec"].string ?? "")
if(stream["IsDefault"].boolValue) {
_selectedAudioTrack.wrappedValue = Int32(stream["Index"].int ?? 0);
}
_audioTracks.wrappedValue.append(subtitle);
}
}
if(_selectedAudioTrack.wrappedValue == -1) {
_selectedAudioTrack.wrappedValue = _audioTracks.wrappedValue[0].id;
}
sendPlayReport()
_VLCItem.wrappedValue = item;
_VLCItem.wrappedValue.subtitles = subtitles;
}
shouldShowLoadingView = false;
/*
DispatchQueue.global(qos: .utility).async { [self] in
self.keepUpWithPlayerState()
}
*/
} catch {
}
break
case .failure(let error):
debugPrint(error)
break
}
}
}
func sendProgressReport(eventName: String) {
var progressBody: String = "";
progressBody = "{\"VolumeLevel\":100,\"IsMuted\":false,\"IsPaused\":\(VLCPlayerObj.state == .paused ? "true" : "false"),\"RepeatMode\":\"RepeatNone\",\"ShuffleMode\":\"Sorted\",\"MaxStreamingBitrate\":120000000,\"PositionTicks\":\(Int(VLCPlayerObj.position * Float(itemPlayback.itemToPlay.RuntimeTicks))),\"PlaybackStartTimeTicks\":\(startTime),\"AudioStreamIndex\":\(selectedAudioTrack),\"BufferedRanges\":[{\"start\":0,\"end\":569735888.888889}],\"PlayMethod\":\"\(VLCItem.videoType == .hls ? "Transcode" : "DirectStream")\",\"PlaySessionId\":\"\(playSessionId)\",\"PlaylistItemId\":\"playlistItem0\",\"MediaSourceId\":\"\(itemPlayback.itemToPlay.Id)\",\"CanSeek\":true,\"ItemId\":\"\(itemPlayback.itemToPlay.Id)\",\"EventName\":\"\(eventName)\"}";
print("");
print("Sending progress report")
print(progressBody)
let request = RestRequest(method: .post, url: (globalData.server?.baseURI ?? "") + "/Sessions/Playing/Progress")
request.headerParameters["X-Emby-Authorization"] = globalData.authHeader
request.contentType = "application/json"
request.acceptType = "application/json"
request.messageBody = progressBody.data(using: .ascii);
request.responseData() { (result: Result<RestResponse<Data>, RestError>) in
switch result {
case .success(let resp):
print(resp.body)
break
case .failure(let error):
debugPrint(error)
break
}
}
}
func resetTimer() {
print("rt running")
if(_shouldOverlayShow.wrappedValue == true) {
_shouldOverlayShow.wrappedValue = false
return;
}
_shouldOverlayShow.wrappedValue = true;
}
func sendStopReport() {
var progressBody: String = "";
progressBody = "{\"VolumeLevel\":100,\"IsMuted\":false,\"IsPaused\":true,\"RepeatMode\":\"RepeatNone\",\"ShuffleMode\":\"Sorted\",\"MaxStreamingBitrate\":120000000,\"PositionTicks\":\(Int(VLCPlayerObj.position * Float(itemPlayback.itemToPlay.RuntimeTicks))),\"PlaybackStartTimeTicks\":\(startTime),\"AudioStreamIndex\":\(selectedAudioTrack),\"BufferedRanges\":[{\"start\":0,\"end\":100000}],\"PlayMethod\":\"\(VLCItem.videoType == .hls ? "Transcode" : "DirectStream")\",\"PlaySessionId\":\"\(playSessionId)\",\"PlaylistItemId\":\"playlistItem0\",\"MediaSourceId\":\"\(itemPlayback.itemToPlay.Id)\",\"CanSeek\":true,\"ItemId\":\"\(itemPlayback.itemToPlay.Id)\",\"NowPlayingQueue\":[{\"Id\":\"\(itemPlayback.itemToPlay.Id)\",\"PlaylistItemId\":\"playlistItem0\"}]}";
print("");
print("Sending stop report")
print(progressBody)
let request = RestRequest(method: .post, url: (globalData.server?.baseURI ?? "") + "/Sessions/Playing/Stopped")
request.headerParameters["X-Emby-Authorization"] = globalData.authHeader
request.contentType = "application/json"
request.acceptType = "application/json"
request.messageBody = progressBody.data(using: .ascii);
request.responseData() { (result: Result<RestResponse<Data>, RestError>) in
switch result {
case .success(let resp):
print(resp.body)
break
case .failure(let error):
debugPrint(error)
break
}
}
}
func sendPlayReport() {
var progressBody: String = "";
_startTime.wrappedValue = Int(Date().timeIntervalSince1970) * 10000000
progressBody = "{\"VolumeLevel\":100,\"IsMuted\":false,\"IsPaused\":false,\"RepeatMode\":\"RepeatNone\",\"ShuffleMode\":\"Sorted\",\"MaxStreamingBitrate\":120000000,\"PositionTicks\":\(Int(itemPlayback.itemToPlay.Progress)),\"PlaybackStartTimeTicks\":\(startTime),\"AudioStreamIndex\":\(selectedAudioTrack),\"BufferedRanges\":[],\"PlayMethod\":\"\(VLCItem.videoType == .hls ? "Transcode" : "DirectStream")\",\"PlaySessionId\":\"\(playSessionId)\",\"PlaylistItemId\":\"playlistItem0\",\"MediaSourceId\":\"\(itemPlayback.itemToPlay.Id)\",\"CanSeek\":true,\"ItemId\":\"\(itemPlayback.itemToPlay.Id)\",\"NowPlayingQueue\":[{\"Id\":\"\(itemPlayback.itemToPlay.Id)\",\"PlaylistItemId\":\"playlistItem0\"}]}";
print("");
print("Sending play report")
print(progressBody)
let request = RestRequest(method: .post, url: (globalData.server?.baseURI ?? "") + "/Sessions/Playing")
request.headerParameters["X-Emby-Authorization"] = globalData.authHeader
request.contentType = "application/json"
request.acceptType = "application/json"
request.messageBody = progressBody.data(using: .ascii);
request.responseData() { (result: Result<RestResponse<Data>, RestError>) in
switch result {
case .success(let resp):
print(resp.body)
break
case .failure(let error):
debugPrint(error)
break
}
}
}
}