start rewriting videoplayerview
|
@ -102,7 +102,6 @@
|
||||||
"size" : "83.5x83.5"
|
"size" : "83.5x83.5"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"filename" : "Icon.png",
|
|
||||||
"idiom" : "ios-marketing",
|
"idiom" : "ios-marketing",
|
||||||
"scale" : "1x",
|
"scale" : "1x",
|
||||||
"size" : "1024x1024"
|
"size" : "1024x1024"
|
||||||
|
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 8.1 KiB After Width: | Height: | Size: 6.9 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 9.0 KiB |
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 3.7 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 9.9 KiB |
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 5.4 KiB |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 2.7 KiB |
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 4.3 KiB |
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 4.0 KiB |
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 4.0 KiB |
Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 6.4 KiB |
Before Width: | Height: | Size: 2.0 KiB |
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 232 KiB |
|
@ -206,7 +206,6 @@ struct ContentView: View {
|
||||||
}
|
}
|
||||||
Spacer().frame(height: 7)
|
Spacer().frame(height: 7)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
.navigationTitle("Home")
|
.navigationTitle("Home")
|
||||||
.toolbar {
|
.toolbar {
|
||||||
ToolbarItemGroup(placement: .navigationBarTrailing) {
|
ToolbarItemGroup(placement: .navigationBarTrailing) {
|
||||||
|
@ -218,6 +217,7 @@ struct ContentView: View {
|
||||||
}
|
}
|
||||||
}.fullScreenCover( isPresented: $showSettingsPopover) { SettingsView(viewModel: SettingsViewModel(), close: $showSettingsPopover) }
|
}.fullScreenCover( isPresented: $showSettingsPopover) { SettingsView(viewModel: SettingsViewModel(), close: $showSettingsPopover) }
|
||||||
}
|
}
|
||||||
|
}
|
||||||
.navigationViewStyle(StackNavigationViewStyle())
|
.navigationViewStyle(StackNavigationViewStyle())
|
||||||
.tabItem({
|
.tabItem({
|
||||||
Text("Home")
|
Text("Home")
|
||||||
|
|
|
@ -11,13 +11,13 @@ import SwiftyJSON
|
||||||
import SDWebImageSwiftUI
|
import SDWebImageSwiftUI
|
||||||
|
|
||||||
struct EpisodeItemView: View {
|
struct EpisodeItemView: View {
|
||||||
@EnvironmentObject var globalData: GlobalData
|
@EnvironmentObject private var globalData: GlobalData
|
||||||
@State private var isLoading: Bool = true;
|
@EnvironmentObject private var orientationInfo: OrientationInfo
|
||||||
|
@EnvironmentObject private var playbackInfo: ItemPlayback
|
||||||
var item: ResumeItem;
|
var item: ResumeItem;
|
||||||
@EnvironmentObject var orientationInfo: OrientationInfo
|
|
||||||
var fullItem: DetailItem;
|
var fullItem: DetailItem;
|
||||||
@State private var playing: Bool = false;
|
|
||||||
|
|
||||||
|
@State private var isLoading: Bool = true;
|
||||||
@State private var progressString: String = "";
|
@State private var progressString: String = "";
|
||||||
@State private var viewDidLoad: Bool = false;
|
@State private var viewDidLoad: Bool = false;
|
||||||
|
|
||||||
|
@ -195,7 +195,7 @@ struct EpisodeItemView: View {
|
||||||
if(orientationInfo.orientation == .portrait) {
|
if(orientationInfo.orientation == .portrait) {
|
||||||
GeometryReader { geometry in
|
GeometryReader { geometry in
|
||||||
VStack() {
|
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
|
.resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size
|
||||||
.placeholder {
|
.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))!)
|
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) {
|
VStack(alignment: .leading) {
|
||||||
HStack() {
|
HStack() {
|
||||||
//Play button
|
//Play button
|
||||||
NavigationLink(destination: VideoPlayerViewRefactored()) {
|
Button {
|
||||||
|
self.playbackInfo.itemToPlay = fullItem;
|
||||||
|
self.playbackInfo.shouldPlay = true;
|
||||||
|
} label: {
|
||||||
HStack() {
|
HStack() {
|
||||||
Text(fullItem.Progress == 0 ? "Play" : "\(progressString) left").foregroundColor(Color.white).font(.callout).fontWeight(.semibold)
|
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))
|
Image(systemName: "play.fill").foregroundColor(Color.white).font(.system(size: 20))
|
||||||
|
@ -271,7 +274,8 @@ struct EpisodeItemView: View {
|
||||||
.frame(width: 120, height: 35)
|
.frame(width: 120, height: 35)
|
||||||
.background(Color(red: 172/255, green: 92/255, blue: 195/255))
|
.background(Color(red: 172/255, green: 92/255, blue: 195/255))
|
||||||
.cornerRadius(10)
|
.cornerRadius(10)
|
||||||
}
|
}.buttonStyle(PlainButtonStyle())
|
||||||
|
.frame(width: 120, height: 35)
|
||||||
Spacer()
|
Spacer()
|
||||||
HStack() {
|
HStack() {
|
||||||
Button() {
|
Button() {
|
||||||
|
@ -324,15 +328,15 @@ struct EpisodeItemView: View {
|
||||||
WebImage(url: cast.Image)
|
WebImage(url: cast.Image)
|
||||||
.resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size
|
.resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size
|
||||||
.placeholder {
|
.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()
|
.resizable()
|
||||||
.aspectRatio(contentMode: .fill)
|
.aspectRatio(contentMode: .fill)
|
||||||
.frame(width: 70, height: 70)
|
.frame(width: 100, height: 100)
|
||||||
.cornerRadius(10)
|
.cornerRadius(10)
|
||||||
}
|
}
|
||||||
.aspectRatio(contentMode: .fill)
|
.aspectRatio(contentMode: .fill)
|
||||||
.frame(width: 100, height: 100)
|
.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)
|
Text(cast.Name).font(.footnote).fontWeight(.regular).lineLimit(1).frame(width: 100).foregroundColor(Color.primary)
|
||||||
if(cast.Role != "") {
|
if(cast.Role != "") {
|
||||||
Text(cast.Role).font(.caption).fontWeight(.medium).lineLimit(1).foregroundColor(Color.secondary).frame(width: 100)
|
Text(cast.Role).font(.caption).fontWeight(.medium).lineLimit(1).foregroundColor(Color.secondary).frame(width: 100)
|
||||||
|
@ -399,7 +403,10 @@ struct EpisodeItemView: View {
|
||||||
.cornerRadius(10)
|
.cornerRadius(10)
|
||||||
.shadow(radius: 5)
|
.shadow(radius: 5)
|
||||||
Spacer().frame(height: 15)
|
Spacer().frame(height: 15)
|
||||||
NavigationLink(destination: VideoPlayerViewRefactored()) {
|
Button {
|
||||||
|
self.playbackInfo.itemToPlay = fullItem;
|
||||||
|
self.playbackInfo.shouldPlay = true;
|
||||||
|
} label: {
|
||||||
HStack() {
|
HStack() {
|
||||||
Text(fullItem.Progress == 0 ? "Play" : "\(progressString) left").foregroundColor(Color.white).font(.callout).fontWeight(.semibold)
|
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))
|
Image(systemName: "play.fill").foregroundColor(Color.white).font(.system(size: 20))
|
||||||
|
@ -407,7 +414,8 @@ struct EpisodeItemView: View {
|
||||||
.frame(width: 120, height: 35)
|
.frame(width: 120, height: 35)
|
||||||
.background(Color(red: 172/255, green: 92/255, blue: 195/255))
|
.background(Color(red: 172/255, green: 92/255, blue: 195/255))
|
||||||
.cornerRadius(10)
|
.cornerRadius(10)
|
||||||
}
|
}.buttonStyle(PlainButtonStyle())
|
||||||
|
.frame(width: 120, height: 35)
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
ScrollView() {
|
ScrollView() {
|
||||||
|
@ -504,7 +512,7 @@ struct EpisodeItemView: View {
|
||||||
WebImage(url: cast.Image)
|
WebImage(url: cast.Image)
|
||||||
.resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size
|
.resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size
|
||||||
.placeholder {
|
.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()
|
.resizable()
|
||||||
.aspectRatio(contentMode: .fill)
|
.aspectRatio(contentMode: .fill)
|
||||||
.frame(width: 100, height: 100)
|
.frame(width: 100, height: 100)
|
||||||
|
@ -512,7 +520,7 @@ struct EpisodeItemView: View {
|
||||||
}
|
}
|
||||||
.aspectRatio(contentMode: .fill)
|
.aspectRatio(contentMode: .fill)
|
||||||
.frame(width: 100, height: 100)
|
.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)
|
Text(cast.Name).font(.footnote).fontWeight(.regular).lineLimit(1).frame(width: 100).foregroundColor(Color.primary)
|
||||||
if(cast.Role != "") {
|
if(cast.Role != "") {
|
||||||
Text(cast.Role).font(.caption).fontWeight(.medium).lineLimit(1).foregroundColor(Color.secondary).frame(width: 100)
|
Text(cast.Role).font(.caption).fontWeight(.medium).lineLimit(1).foregroundColor(Color.secondary).frame(width: 100)
|
||||||
|
|
|
@ -8,17 +8,24 @@
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import Introspect
|
import Introspect
|
||||||
|
|
||||||
|
class ItemPlayback: ObservableObject {
|
||||||
|
@Published var shouldPlay: Bool = false;
|
||||||
|
@Published var itemToPlay: DetailItem = DetailItem();
|
||||||
|
}
|
||||||
|
|
||||||
struct ItemView: View {
|
struct ItemView: View {
|
||||||
var item: ResumeItem;
|
var item: ResumeItem;
|
||||||
|
@StateObject private var playback: ItemPlayback = ItemPlayback()
|
||||||
|
|
||||||
init(item: ResumeItem) {
|
init(item: ResumeItem) {
|
||||||
self.item = item;
|
self.item = item;
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
if(playback.shouldPlay) {
|
||||||
|
VideoPlayerViewRefactored(itemPlayback: playback)
|
||||||
|
} else {
|
||||||
Group {
|
Group {
|
||||||
NavigationLink(destination: EmptyView(), label: {})
|
|
||||||
NavigationLink(destination: EmptyView(), label: {})
|
|
||||||
if(item.Type == "Movie") {
|
if(item.Type == "Movie") {
|
||||||
MovieItemView(item: self.item)
|
MovieItemView(item: self.item)
|
||||||
} else if(item.Type == "Season") {
|
} else if(item.Type == "Season") {
|
||||||
|
@ -42,5 +49,7 @@ struct ItemView: View {
|
||||||
.edgesIgnoringSafeArea([])
|
.edgesIgnoringSafeArea([])
|
||||||
.overrideViewPreference(.unspecified)
|
.overrideViewPreference(.unspecified)
|
||||||
.supportedOrientations(.allButUpsideDown)
|
.supportedOrientations(.allButUpsideDown)
|
||||||
|
.environmentObject(playback)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
@main
|
||||||
struct JellyfinPlayerApp: App {
|
struct JellyfinPlayerApp: App {
|
||||||
let persistenceController = PersistenceController.shared
|
let persistenceController = PersistenceController.shared
|
||||||
|
|
|
@ -60,16 +60,17 @@ class CastMember: ObservableObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
struct MovieItemView: View {
|
struct MovieItemView: View {
|
||||||
@EnvironmentObject var globalData: GlobalData
|
@EnvironmentObject private var globalData: GlobalData
|
||||||
@EnvironmentObject var orientationInfo: OrientationInfo
|
@EnvironmentObject private var orientationInfo: OrientationInfo
|
||||||
|
@EnvironmentObject private var playbackInfo: ItemPlayback
|
||||||
|
|
||||||
@State private var isLoading: Bool = true;
|
@State private var isLoading: Bool = true;
|
||||||
|
|
||||||
var item: ResumeItem;
|
var item: ResumeItem;
|
||||||
var fullItem: DetailItem;
|
var fullItem: DetailItem;
|
||||||
@State private var playing: Bool = false;
|
|
||||||
|
|
||||||
@State private var progressString: String = "";
|
@State private var progressString: String = "";
|
||||||
@State private var viewDidLoad: Bool = false;
|
@State private var viewDidLoad: Bool = false;
|
||||||
|
|
||||||
@State private var watched: Bool = false {
|
@State private var watched: Bool = false {
|
||||||
didSet {
|
didSet {
|
||||||
if(watched == true) {
|
if(watched == true) {
|
||||||
|
@ -242,7 +243,7 @@ struct MovieItemView: View {
|
||||||
if(orientationInfo.orientation == .portrait) {
|
if(orientationInfo.orientation == .portrait) {
|
||||||
GeometryReader { geometry in
|
GeometryReader { geometry in
|
||||||
VStack() {
|
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
|
.resizable() // Resizable like SwiftUI.Image, you must use this modifier or the view will use the image bitmap size
|
||||||
.placeholder {
|
.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))!)
|
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) {
|
VStack(alignment: .leading) {
|
||||||
HStack() {
|
HStack() {
|
||||||
//Play button
|
//Play button
|
||||||
NavigationLink(destination: VideoPlayerViewRefactored()) {
|
Button {
|
||||||
|
self.playbackInfo.itemToPlay = fullItem;
|
||||||
|
self.playbackInfo.shouldPlay = true;
|
||||||
|
} label: {
|
||||||
HStack() {
|
HStack() {
|
||||||
Text(fullItem.Progress == 0 ? "Play" : "\(progressString) left").foregroundColor(Color.white).font(.callout).fontWeight(.semibold)
|
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))
|
Image(systemName: "play.fill").foregroundColor(Color.white).font(.system(size: 20))
|
||||||
|
@ -318,7 +322,8 @@ struct MovieItemView: View {
|
||||||
.frame(width: 120, height: 35)
|
.frame(width: 120, height: 35)
|
||||||
.background(Color(red: 172/255, green: 92/255, blue: 195/255))
|
.background(Color(red: 172/255, green: 92/255, blue: 195/255))
|
||||||
.cornerRadius(10)
|
.cornerRadius(10)
|
||||||
}
|
}.buttonStyle(PlainButtonStyle())
|
||||||
|
.frame(width: 120, height: 35)
|
||||||
Spacer()
|
Spacer()
|
||||||
HStack() {
|
HStack() {
|
||||||
Button() {
|
Button() {
|
||||||
|
@ -379,7 +384,7 @@ struct MovieItemView: View {
|
||||||
}
|
}
|
||||||
.aspectRatio(contentMode: .fill)
|
.aspectRatio(contentMode: .fill)
|
||||||
.frame(width: 100, height: 100)
|
.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)
|
Text(cast.Name).font(.footnote).fontWeight(.regular).lineLimit(1).frame(width: 100).foregroundColor(Color.primary)
|
||||||
if(cast.Role != "") {
|
if(cast.Role != "") {
|
||||||
Text(cast.Role).font(.caption).fontWeight(.medium).lineLimit(1).foregroundColor(Color.secondary).frame(width: 100)
|
Text(cast.Role).font(.caption).fontWeight(.medium).lineLimit(1).foregroundColor(Color.secondary).frame(width: 100)
|
||||||
|
@ -445,7 +450,10 @@ struct MovieItemView: View {
|
||||||
.cornerRadius(10)
|
.cornerRadius(10)
|
||||||
.shadow(radius: 5)
|
.shadow(radius: 5)
|
||||||
Spacer().frame(height: 15)
|
Spacer().frame(height: 15)
|
||||||
NavigationLink(destination: VideoPlayerViewRefactored()) {
|
Button {
|
||||||
|
self.playbackInfo.itemToPlay = fullItem;
|
||||||
|
self.playbackInfo.shouldPlay = true;
|
||||||
|
} label: {
|
||||||
HStack() {
|
HStack() {
|
||||||
Text(fullItem.Progress == 0 ? "Play" : "\(progressString) left").foregroundColor(Color.white).font(.callout).fontWeight(.semibold)
|
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))
|
Image(systemName: "play.fill").foregroundColor(Color.white).font(.system(size: 20))
|
||||||
|
@ -453,7 +461,8 @@ struct MovieItemView: View {
|
||||||
.frame(width: 120, height: 35)
|
.frame(width: 120, height: 35)
|
||||||
.background(Color(red: 172/255, green: 92/255, blue: 195/255))
|
.background(Color(red: 172/255, green: 92/255, blue: 195/255))
|
||||||
.cornerRadius(10)
|
.cornerRadius(10)
|
||||||
}
|
}.buttonStyle(PlainButtonStyle())
|
||||||
|
.frame(width: 120, height: 35)
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
ScrollView() {
|
ScrollView() {
|
||||||
|
@ -558,7 +567,7 @@ struct MovieItemView: View {
|
||||||
}
|
}
|
||||||
.aspectRatio(contentMode: .fill)
|
.aspectRatio(contentMode: .fill)
|
||||||
.frame(width: 100, height: 100)
|
.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)
|
Text(cast.Name).font(.footnote).fontWeight(.regular).lineLimit(1).frame(width: 100).foregroundColor(Color.primary)
|
||||||
if(cast.Role != "") {
|
if(cast.Role != "") {
|
||||||
Text(cast.Role).font(.caption).fontWeight(.medium).lineLimit(1).foregroundColor(Color.secondary).frame(width: 100)
|
Text(cast.Role).font(.caption).fontWeight(.medium).lineLimit(1).foregroundColor(Color.secondary).frame(width: 100)
|
||||||
|
|
|
@ -17,10 +17,18 @@ enum VideoType {
|
||||||
case direct;
|
case direct;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct PlaybackItem {
|
struct Subtitle {
|
||||||
var videoType: VideoType;
|
var name: String;
|
||||||
var videoUrl: URL;
|
var id: Int32;
|
||||||
var subtitles: [Subtitle];
|
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{
|
struct VLCPlayer: UIViewRepresentable{
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
//
|
//
|
||||||
// Created by Aiden Vigue on 5/10/21.
|
// Created by Aiden Vigue on 5/10/21.
|
||||||
//
|
//
|
||||||
|
/*
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import SwiftyJSON
|
import SwiftyJSON
|
||||||
import SwiftyRequest
|
import SwiftyRequest
|
||||||
|
@ -41,7 +41,6 @@ extension String {
|
||||||
|
|
||||||
struct VideoPlayerView: View {
|
struct VideoPlayerView: View {
|
||||||
@EnvironmentObject var globalData: GlobalData
|
@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 pbitem: PlaybackItem = PlaybackItem(videoType: VideoType.direct, videoUrl: URL(string: "https://example.com")!, subtitles: []);
|
||||||
@State private var streamLoading = false;
|
@State private var streamLoading = false;
|
||||||
@State private var vlcplayer: VLCMediaPlayer = VLCMediaPlayer();
|
@State private var vlcplayer: VLCMediaPlayer = VLCMediaPlayer();
|
||||||
|
@ -76,10 +75,11 @@ struct VideoPlayerView: View {
|
||||||
};
|
};
|
||||||
|
|
||||||
@State private var playbackSettings: Bool = false;
|
@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>;
|
||||||
|
var item: DetailItem;
|
||||||
|
|
||||||
init(item: DetailItem, playing: Binding<Bool>) {
|
init(item: DetailItem, playing: Binding<Bool>) {
|
||||||
self.item = item;
|
self.item = item;
|
||||||
|
@ -582,3 +582,4 @@ struct VideoPlayerView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
|
@ -8,17 +8,137 @@
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import MobileVLCKit
|
import MobileVLCKit
|
||||||
import Introspect
|
import Introspect
|
||||||
|
import SwiftyJSON
|
||||||
|
import SwiftyRequest
|
||||||
|
|
||||||
struct VideoPlayerViewRefactored: View {
|
struct VideoPlayerViewRefactored: View {
|
||||||
|
@EnvironmentObject private var globalData: GlobalData;
|
||||||
|
|
||||||
@State private var shouldShowLoadingView: Bool = true;
|
@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 {
|
var body: some View {
|
||||||
LoadingView(isShowing: $shouldShowLoadingView) {
|
LoadingView(isShowing: $shouldShowLoadingView) {
|
||||||
Text("content")
|
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
|
.introspectTabBarController { (UITabBarController) in
|
||||||
UITabBarController.tabBar.isHidden = true
|
UITabBarController.tabBar.isHidden = true
|
||||||
}
|
}
|
||||||
}
|
.onTapGesture(perform: resetTimer)
|
||||||
.navigationBarHidden(true)
|
.navigationBarHidden(true)
|
||||||
.navigationBarBackButtonHidden(true)
|
.navigationBarBackButtonHidden(true)
|
||||||
.statusBar(hidden: true)
|
.statusBar(hidden: true)
|
||||||
|
@ -28,5 +148,211 @@ struct VideoPlayerViewRefactored: View {
|
||||||
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
|
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
|
||||||
.overrideViewPreference(.unspecified)
|
.overrideViewPreference(.unspecified)
|
||||||
.supportedOrientations(.landscape)
|
.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
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|