diff --git a/JellyfinPlayer.xcodeproj/project.pbxproj b/JellyfinPlayer.xcodeproj/project.pbxproj index 78e7efd9..038f4adb 100644 --- a/JellyfinPlayer.xcodeproj/project.pbxproj +++ b/JellyfinPlayer.xcodeproj/project.pbxproj @@ -10,14 +10,12 @@ 5302F82A2658791C00647A2E /* Sentry in Frameworks */ = {isa = PBXBuildFile; productRef = 5302F8292658791C00647A2E /* Sentry */; }; 53192D5D265AA78A008A4215 /* DeviceProfileBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53192D5C265AA78A008A4215 /* DeviceProfileBuilder.swift */; }; 53313B90265EEA6D00947AA3 /* VideoPlayer.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 53313B8F265EEA6D00947AA3 /* VideoPlayer.storyboard */; }; - 5335256E265E8D5A006CCA86 /* VideoPlayerViewRefactored.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5335256D265E8D5A006CCA86 /* VideoPlayerViewRefactored.swift */; }; 53352571265EA0A0006CCA86 /* Introspect in Frameworks */ = {isa = PBXBuildFile; productRef = 53352570265EA0A0006CCA86 /* Introspect */; }; 5338F74E263B61370014BF09 /* ConnectToServerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5338F74D263B61370014BF09 /* ConnectToServerView.swift */; }; 5338F754263B65E10014BF09 /* SwiftyRequest in Frameworks */ = {isa = PBXBuildFile; productRef = 5338F753263B65E10014BF09 /* SwiftyRequest */; }; 5338F757263B7E2E0014BF09 /* KeychainSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 5338F756263B7E2E0014BF09 /* KeychainSwift */; }; 535BAE9F2649E569005FA86D /* ItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 535BAE9E2649E569005FA86D /* ItemView.swift */; }; - 535BAEA5264A151C005FA86D /* VLCPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 535BAEA4264A151C005FA86D /* VLCPlayer.swift */; }; - 535BAEA7264A18AA005FA86D /* VideoPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 535BAEA6264A18AA005FA86D /* VideoPlayerView.swift */; }; + 535BAEA5264A151C005FA86D /* VideoPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 535BAEA4264A151C005FA86D /* VideoPlayer.swift */; }; 5377CBF5263B596A003A4E83 /* JellyfinPlayerApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5377CBF4263B596A003A4E83 /* JellyfinPlayerApp.swift */; }; 5377CBF7263B596A003A4E83 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5377CBF6263B596A003A4E83 /* ContentView.swift */; }; 5377CBF9263B596B003A4E83 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5377CBF8263B596B003A4E83 /* Assets.xcassets */; }; @@ -74,11 +72,9 @@ /* Begin PBXFileReference section */ 53192D5C265AA78A008A4215 /* DeviceProfileBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceProfileBuilder.swift; sourceTree = ""; }; 53313B8F265EEA6D00947AA3 /* VideoPlayer.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = VideoPlayer.storyboard; sourceTree = ""; }; - 5335256D265E8D5A006CCA86 /* VideoPlayerViewRefactored.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlayerViewRefactored.swift; sourceTree = ""; }; 5338F74D263B61370014BF09 /* ConnectToServerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectToServerView.swift; sourceTree = ""; }; 535BAE9E2649E569005FA86D /* ItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemView.swift; sourceTree = ""; }; - 535BAEA4264A151C005FA86D /* VLCPlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VLCPlayer.swift; sourceTree = ""; }; - 535BAEA6264A18AA005FA86D /* VideoPlayerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlayerView.swift; sourceTree = ""; }; + 535BAEA4264A151C005FA86D /* VideoPlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlayer.swift; sourceTree = ""; }; 5377CBF1263B596A003A4E83 /* JellyfinPlayer.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = JellyfinPlayer.app; sourceTree = BUILT_PRODUCTS_DIR; }; 5377CBF4263B596A003A4E83 /* JellyfinPlayerApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JellyfinPlayerApp.swift; sourceTree = ""; }; 5377CBF6263B596A003A4E83 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; @@ -171,13 +167,11 @@ 53E4E648263F725B00F67C6B /* MultiSelector.swift */, 535BAE9E2649E569005FA86D /* ItemView.swift */, 53A089CF264DA9DA00D57806 /* MovieItemView.swift */, - 535BAEA6264A18AA005FA86D /* VideoPlayerView.swift */, 53EE24E5265060780068F029 /* LibrarySearchView.swift */, 53987CA326572C1300E7EA70 /* SeasonItemView.swift */, 53987CA526572F0700E7EA70 /* SeriesItemView.swift */, 53987CA72657424A00E7EA70 /* EpisodeItemView.swift */, 53192D5C265AA78A008A4215 /* DeviceProfileBuilder.swift */, - 5335256D265E8D5A006CCA86 /* VideoPlayerViewRefactored.swift */, ); path = JellyfinPlayer; sourceTree = ""; @@ -201,7 +195,7 @@ AE8C3150265D5FE1008AA076 /* Views */ = { isa = PBXGroup; children = ( - 535BAEA4264A151C005FA86D /* VLCPlayer.swift */, + 535BAEA4264A151C005FA86D /* VideoPlayer.swift */, 539B2DA4263BA5B8007FF1A4 /* SettingsView.swift */, 53313B8F265EEA6D00947AA3 /* VideoPlayer.storyboard */, ); @@ -332,19 +326,17 @@ 53192D5D265AA78A008A4215 /* DeviceProfileBuilder.swift in Sources */, AE8C3154265D60BF008AA076 /* SettingsModel.swift in Sources */, 53892770263C25230035E14B /* NextUpView.swift in Sources */, - 535BAEA5264A151C005FA86D /* VLCPlayer.swift in Sources */, + 535BAEA5264A151C005FA86D /* VideoPlayer.swift in Sources */, 5377CC01263B596B003A4E83 /* Model.xcdatamodeld in Sources */, 53DF641E263D9C0600A7CD1A /* LibraryView.swift in Sources */, 53A089D0264DA9DA00D57806 /* MovieItemView.swift in Sources */, 53E4E649263F725B00F67C6B /* MultiSelector.swift in Sources */, - 535BAEA7264A18AA005FA86D /* VideoPlayerView.swift in Sources */, 53E4E647263F6CF100F67C6B /* LibraryFilterView.swift in Sources */, 53892777263CBB000035E14B /* JellyApiTypings.swift in Sources */, 5377CBF7263B596A003A4E83 /* ContentView.swift in Sources */, 53987CA82657424A00E7EA70 /* EpisodeItemView.swift in Sources */, 5389277C263CC3DB0035E14B /* BlurHashDecode.swift in Sources */, 53987CA626572F0700E7EA70 /* SeriesItemView.swift in Sources */, - 5335256E265E8D5A006CCA86 /* VideoPlayerViewRefactored.swift in Sources */, 539B2DA5263BA5B8007FF1A4 /* SettingsView.swift in Sources */, AE8C3156265D616A008AA076 /* SettingsViewModel.swift in Sources */, 5338F74E263B61370014BF09 /* ConnectToServerView.swift in Sources */, diff --git a/JellyfinPlayer/VideoPlayerView.swift b/JellyfinPlayer/VideoPlayerView.swift deleted file mode 100644 index aa1d4448..00000000 --- a/JellyfinPlayer/VideoPlayerView.swift +++ /dev/null @@ -1,585 +0,0 @@ -// -// VideoPlayerView.swift -// JellyfinPlayer -// -// Created by Aiden Vigue on 5/10/21. -// -/* -import SwiftUI -import SwiftyJSON -import SwiftyRequest -import AVKit -import MobileVLCKit -import Foundation -import NotificationCenter - -struct Subtitle { - var name: String; - var id: Int32; - var url: URL; - var delivery: String; - var codec: String; -} - -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)" - } -} - -struct VideoPlayerView: View { - @EnvironmentObject var globalData: GlobalData - @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(); - @State private var isPlaying = false; - @State private var subtitles: [Subtitle] = []; - @State private var audioTracks: [Subtitle] = []; - @State private var inactivity: Bool = true; - @State private var lastActivityTime: Double = 0; - @State private var scrub: Double = 0; - @State private var timeText: String = "-:--:--"; - @State private var playPauseButtonSystemName: String = "pause"; - @State private var playSessionId: String = ""; - @State private var lastPosition: Double = 0; - @State private var iterations: Int = 0; - @State private var startTime: Int = 0; - @State private var hasSentPlayReport: Bool = false; - @State private var selectedVideoQuality: Int = 0; - @State private var captionConfiguration: Bool = false { - didSet { - if(captionConfiguration == false) { - DispatchQueue.global(qos: .userInitiated).async { [self] in - vlcplayer.pause() - usleep(10000); - vlcplayer.play() - usleep(10000); - vlcplayer.pause() - usleep(10000); - vlcplayer.play() - } - } - } - }; - - @State private var playbackSettings: Bool = false; - @State private var selectedCaptionTrack: Int32 = -1; - @State private var selectedAudioTrack: Int32 = -1; - - var playing: Binding; - var item: DetailItem; - - init(item: DetailItem, playing: Binding) { - self.item = item; - self.playing = playing; - } - - @State var lastProgressReportSent: Double = CACurrentMediaTime() - - func keepUpWithPlayerState() { - if(!vlcplayer.isPlaying) { - while(!vlcplayer.isPlaying) {} - } - - sendProgressReport(eventName: "unpause") - - while(vlcplayer.state != VLCMediaPlayerState.stopped) { - _streamLoading.wrappedValue = false; - while(vlcplayer.isPlaying) { - vlcplayer.currentVideoSubTitleIndex = _selectedCaptionTrack.wrappedValue; - usleep(500000) - if(CACurrentMediaTime() - lastProgressReportSent > 10) { - sendProgressReport(eventName: "timeupdate") - _lastProgressReportSent.wrappedValue = CACurrentMediaTime() - } - if(vlcplayer.time.intValue != 0) { - _scrub.wrappedValue = Double(Double(vlcplayer.time.intValue) / Double(vlcplayer.time.intValue + abs(vlcplayer.remainingTime.intValue))); - - //Turn remainingTime into text - let remainingTime = abs(vlcplayer.remainingTime.intValue)/1000; - let hours = remainingTime / 3600; - let minutes = (remainingTime % 3600) / 60; - let seconds = (remainingTime % 3600) % 60; - if(hours != 0) { - timeText = "\(Int(hours)):\(String(Int((minutes))).leftPad(toWidth: 2, withString: "0")):\(String(Int((seconds))).leftPad(toWidth: 2, withString: "0"))"; - } else { - timeText = "\(String(Int((minutes))).leftPad(toWidth: 2, withString: "0")):\(String(Int((seconds))).leftPad(toWidth: 2, withString: "0"))"; - } - } - if(CACurrentMediaTime() - _lastActivityTime.wrappedValue > 5 && vlcplayer.state != VLCMediaPlayerState.paused) { - _inactivity.wrappedValue = true - } - if((lastPosition == Double(vlcplayer.position) && vlcplayer.state != VLCMediaPlayerState.paused)) { - if(iterations > 5) { - _iterations.wrappedValue = 0; - _streamLoading.wrappedValue = true; - } - _iterations.wrappedValue+=1; - } else { - _iterations.wrappedValue = 0; - _streamLoading.wrappedValue = false; - } - if(vlcplayer.state == VLCMediaPlayerState.error) { - playing.wrappedValue = false; - } - _lastPosition.wrappedValue = Double(vlcplayer.position) - } - } - } - - func sendProgressReport(eventName: String) { - var progressBody: String = ""; - if(pbitem.videoType == VideoType.direct) { - progressBody = "{\"VolumeLevel\":100,\"IsMuted\":false,\"IsPaused\":\(vlcplayer.state == VLCMediaPlayerState.paused ? "true" : "false"),\"RepeatMode\":\"RepeatNone\",\"ShuffleMode\":\"Sorted\",\"MaxStreamingBitrate\":120000000,\"PositionTicks\":\(Int(vlcplayer.position * Float(item.RuntimeTicks))),\"PlaybackStartTimeTicks\":\(startTime),\"AudioStreamIndex\":\(selectedAudioTrack),\"BufferedRanges\":[{\"start\":0,\"end\":569735888.888889}],\"PlayMethod\":\"DirectStream\",\"PlaySessionId\":\"\(playSessionId)\",\"PlaylistItemId\":\"playlistItem0\",\"MediaSourceId\":\"\(item.Id)\",\"CanSeek\":true,\"ItemId\":\"\(item.Id)\",\"EventName\":\"\(eventName)\"}"; - } else { - progressBody = "{\"VolumeLevel\":100,\"IsMuted\":false,\"IsPaused\":\(vlcplayer.state == VLCMediaPlayerState.paused ? "true" : "false"),\"RepeatMode\":\"RepeatNone\",\"ShuffleMode\":\"Sorted\",\"MaxStreamingBitrate\":120000000,\"PositionTicks\":\(Int(vlcplayer.position * Float(item.RuntimeTicks))),\"PlaybackStartTimeTicks\":\(startTime),\"AudioStreamIndex\":\(selectedAudioTrack),\"BufferedRanges\":[{\"start\":0,\"end\":569735888.888889}],\"PlayMethod\":\"Transcode\",\"PlaySessionId\":\"\(playSessionId)\",\"PlaylistItemId\":\"playlistItem0\",\"MediaSourceId\":\"\(item.Id)\",\"CanSeek\":true,\"ItemId\":\"\(item.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, RestError>) in - switch result { - case .success(let resp): - print(resp.body) - break - case .failure(let error): - debugPrint(error) - break - } - } - } - - func sendStopReport() { - var progressBody: String = ""; - if(pbitem.videoType == VideoType.direct) { - progressBody = "{\"VolumeLevel\":100,\"IsMuted\":false,\"IsPaused\":true,\"RepeatMode\":\"RepeatNone\",\"ShuffleMode\":\"Sorted\",\"MaxStreamingBitrate\":120000000,\"PositionTicks\":\(Int(vlcplayer.position * Float(item.RuntimeTicks))),\"PlaybackStartTimeTicks\":\(startTime),\"AudioStreamIndex\":\(selectedAudioTrack),\"BufferedRanges\":[],\"PlayMethod\":\"DirectStream\",\"PlaySessionId\":\"\(playSessionId)\",\"PlaylistItemId\":\"playlistItem0\",\"MediaSourceId\":\"\(item.Id)\",\"CanSeek\":true,\"ItemId\":\"\(item.Id)\",\"NowPlayingQueue\":[{\"Id\":\"\(item.Id)\",\"PlaylistItemId\":\"playlistItem0\"}]}"; - } else { - progressBody = "{\"VolumeLevel\":100,\"IsMuted\":false,\"IsPaused\":true,\"RepeatMode\":\"RepeatNone\",\"ShuffleMode\":\"Sorted\",\"MaxStreamingBitrate\":120000000,\"PositionTicks\":\(Int(vlcplayer.position * Float(item.RuntimeTicks))),\"PlaybackStartTimeTicks\":\(startTime),\"AudioStreamIndex\":\(selectedAudioTrack),\"BufferedRanges\":[{\"start\":0,\"end\":100000}],\"PlayMethod\":\"Transcode\",\"PlaySessionId\":\"\(playSessionId)\",\"PlaylistItemId\":\"playlistItem0\",\"MediaSourceId\":\"\(item.Id)\",\"CanSeek\":true,\"ItemId\":\"\(item.Id)\",\"NowPlayingQueue\":[{\"Id\":\"\(item.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, 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 - if(pbitem.videoType == VideoType.hls) { - progressBody = "{\"VolumeLevel\":100,\"IsMuted\":false,\"IsPaused\":false,\"RepeatMode\":\"RepeatNone\",\"ShuffleMode\":\"Sorted\",\"MaxStreamingBitrate\":120000000,\"PositionTicks\":\(Int(item.Progress)),\"PlaybackStartTimeTicks\":\(startTime),\"AudioStreamIndex\":\(selectedAudioTrack),\"BufferedRanges\":[],\"PlayMethod\":\"Transcode\",\"PlaySessionId\":\"\(playSessionId)\",\"PlaylistItemId\":\"playlistItem0\",\"MediaSourceId\":\"\(item.Id)\",\"CanSeek\":true,\"ItemId\":\"\(item.Id)\",\"NowPlayingQueue\":[{\"Id\":\"\(item.Id)\",\"PlaylistItemId\":\"playlistItem0\"}]}"; - } else { - progressBody = "{\"VolumeLevel\":100,\"IsMuted\":false,\"IsPaused\":false,\"RepeatMode\":\"RepeatNone\",\"ShuffleMode\":\"Sorted\",\"MaxStreamingBitrate\":120000000,\"PositionTicks\":\(Int(item.Progress)),\"PlaybackStartTimeTicks\":\(startTime),\"AudioStreamIndex\":\(selectedAudioTrack),\"BufferedRanges\":[],\"PlayMethod\":\"DirectStream\",\"PlaySessionId\":\"\(playSessionId)\",\"PlaylistItemId\":\"playlistItem0\",\"MediaSourceId\":\"\(item.Id)\",\"CanSeek\":true,\"ItemId\":\"\(item.Id)\",\"NowPlayingQueue\":[{\"Id\":\"\(item.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, RestError>) in - switch result { - case .success(let resp): - print(resp.body) - break - case .failure(let error): - debugPrint(error) - break - } - } - } - - func startStream() { - - 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")) - } - print(builder.bitrate) - _selectedVideoQuality.wrappedValue = builder.bitrate; - - let DeviceProfile = builder.buildProfile() - - let jsonEncoder = JSONEncoder() - let jsonData = try! jsonEncoder.encode(DeviceProfile) - let jsonString = String(data: jsonData, encoding: .ascii)! - print(jsonString) - - _streamLoading.wrappedValue = true; - let url = (globalData.server?.baseURI ?? "") + "/Items/\(item.Id)/PlaybackInfo?UserId=\(globalData.user?.user_id ?? "")&StartTimeTicks=\(Int(item.Progress))&IsPlayback=true&AutoOpenLiveStream=true&MaxStreamingBitrate=\(DeviceProfile.DeviceProfile.MaxStreamingBitrate)"; - print(url) - - let request = RestRequest(method: .post, url: url) - - request.headerParameters["X-Emby-Authorization"] = globalData.authHeader - request.contentType = "application/json" - request.acceptType = "application/json" - request.messageBody = jsonString.data(using: .ascii) - - request.responseData() { (result: Result, 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) { - print("Transcoding!") - let streamURL: URL = URL(string: "\(globalData.server?.baseURI ?? "")\((json["MediaSources"][0]["TranscodingUrl"].string ?? ""))")! - print(streamURL) - let item = PlaybackItem(videoType: VideoType.hls, videoUrl: streamURL, subtitles: []) - 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; - } - } - - let streamUrl = streamURL.absoluteString; - let segmentUrl = URL(string: streamUrl.replacingOccurrences(of: "master.m3u8", with: "hls1/main/0.ts"))! - var request2 = URLRequest(url: segmentUrl) - - request2.httpMethod = "GET" - let task = URLSession.shared.dataTask(with: request2) { (data, response2, error) in - DispatchQueue.global(qos: .utility).async { [self] in - self.sendPlayReport() - pbitem = item; - pbitem.subtitles = subtitles; - _isPlaying.wrappedValue = true; - } - } - task.resume() - } else { - 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 item = PlaybackItem(videoType: VideoType.direct, videoUrl: streamURL, subtitles: []) - 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() - pbitem = item; - pbitem.subtitles = subtitles; - _isPlaying.wrappedValue = true; - } - - DispatchQueue.global(qos: .utility).async { [self] in - self.keepUpWithPlayerState() - } - } catch { - - } - break - case .failure(let error): - debugPrint(error) - break - } - } - } - - func processScrubbingState() { - let videoDuration = Double(vlcplayer.time.intValue + abs(vlcplayer.remainingTime.intValue))/1000 - while(vlcplayer.state != VLCMediaPlayerState.paused) {} - while(vlcplayer.state == VLCMediaPlayerState.paused) { - let secondsScrubbedTo = round(_scrub.wrappedValue * videoDuration); - let scrubRemaining = videoDuration - secondsScrubbedTo; - usleep(100000) - let remainingTime = scrubRemaining; - let hours = floor(remainingTime / 3600); - let minutes = (remainingTime.truncatingRemainder(dividingBy: 3600)) / 60; - let seconds = (remainingTime.truncatingRemainder(dividingBy: 3600)).truncatingRemainder(dividingBy: 60); - if(hours != 0) { - timeText = "\(Int(hours)):\(String(Int(floor(minutes))).leftPad(toWidth: 2, withString: "0")):\(String(Int(floor(seconds))).leftPad(toWidth: 2, withString: "0"))"; - } else { - timeText = "\(String(Int(floor(minutes))).leftPad(toWidth: 2, withString: "0")):\(String(Int(floor(seconds))).leftPad(toWidth: 2, withString: "0"))"; - } - } - } - - func resetTimer() { - print("resetTimer ran") - if(_inactivity.wrappedValue == false) { - _inactivity.wrappedValue = true; - return; - } - _lastActivityTime.wrappedValue = CACurrentMediaTime() - _inactivity.wrappedValue = false; - } - - var body: some View { - LoadingView(isShowing: ($streamLoading)) { - VLCPlayer(url: $pbitem, player: $vlcplayer, startTime: Int(item.Progress)).onDisappear(perform: { - _isPlaying.wrappedValue = false; - vlcplayer.stop() - }).padding(EdgeInsets(top: 0, leading: UIDevice.current.hasNotch ? 30 : 0, bottom: 0, trailing: UIDevice.current.hasNotch ? 30 : 0)) - } - .overlay( - VStack() { - HStack() { - HStack() { - Button() { - sendStopReport() - self.playing.wrappedValue = false; - } label: { - HStack() { - Image(systemName: "chevron.left").font(.system(size: 20)).foregroundColor(.white) - } - }.frame(width: 20) - Spacer() - Text(item.Name).font(.headline).fontWeight(.semibold).foregroundColor(.white).offset(x:20) - 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() { - vlcplayer.pause() - self.captionConfiguration = true; - } 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() { - vlcplayer.jumpBackward(15) - } label: { - Image(systemName: "gobackward.15").font(.system(size: 40)).foregroundColor(.white) - }.padding(20) - Spacer() - Button() { - if(vlcplayer.state != VLCMediaPlayerState.paused) { - vlcplayer.pause() - playPauseButtonSystemName = "play" - sendProgressReport(eventName: "pause") - } else { - vlcplayer.play() - playPauseButtonSystemName = "pause" - sendProgressReport(eventName: "unpause") - } - } label: { - Image(systemName: playPauseButtonSystemName).font(.system(size: 55)).foregroundColor(.white) - }.padding(20).frame(width: 60, height: 60) - Spacer() - Button() { - vlcplayer.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(vlcplayer.time.intValue) - let videoDuration = Double(vlcplayer.time.intValue + abs(vlcplayer.remainingTime.intValue)) - if(bool == true) { - vlcplayer.pause() - sendProgressReport(eventName: "pause") - DispatchQueue.global(qos: .utility).async { [self] in - self.processScrubbingState() - } - } 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") - vlcplayer.play() - if(offset > 0) { - vlcplayer.jumpForward(Int32(offset)/1000); - } else { - vlcplayer.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(maxWidth: .infinity, maxHeight: .infinity) - .background(Color(.black).opacity(0.4)) - , alignment: .topLeading) - .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity) - .onAppear(perform: startStream) - .navigationBarHidden(true) - .overrideViewPreference(.dark) - .preferredColorScheme(.dark) - .navigationBarBackButtonHidden(true) - .edgesIgnoringSafeArea(.all) - .withHostingWindow { window in - if let vc = window?.rootViewController { - let preferenceHost = vc as! PreferenceUIHostingController - preferenceHost._viewPreference = .dark - } - } - .statusBar(hidden: true) - .onTapGesture(perform: resetTimer) - .fullScreenCover(isPresented: self.$captionConfiguration) { - NavigationView() { - VStack() { - Form() { - Picker("Closed Captions", selection: $selectedCaptionTrack) { - ForEach(subtitles, id: \.id) { caption in - Text(caption.name).tag(caption.id) - } - }.onChange(of: selectedCaptionTrack) { track in - vlcplayer.currentVideoSubTitleIndex = track; - } - Picker("Audio Track", selection: $selectedAudioTrack) { - ForEach(audioTracks, id: \.id) { caption in - Text(caption.name).tag(caption.id) - } - }.onChange(of: selectedAudioTrack) { track in - vlcplayer.currentAudioTrackIndex = track; - } - } - Text("Subtitles may take a few moments to appear once selected.") - .font(.callout) - .foregroundColor(.secondary) - Spacer() - } - .navigationBarTitle("Audio & Captions", displayMode: .inline) - .toolbar { - ToolbarItemGroup(placement: .navigationBarLeading) { - Button { - captionConfiguration = false; - playPauseButtonSystemName = "pause"; - } label: { - HStack() { - Text("Back").font(.callout) - } - } - } - } - }.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) - } - } -} -*/ diff --git a/JellyfinPlayer/VideoPlayerViewRefactored.swift b/JellyfinPlayer/VideoPlayerViewRefactored.swift deleted file mode 100644 index fd295ff1..00000000 --- a/JellyfinPlayer/VideoPlayerViewRefactored.swift +++ /dev/null @@ -1,371 +0,0 @@ -// -// VideoPlayerViewRefactored.swift -// JellyfinPlayer -// -// Created by Aiden Vigue on 5/26/21. -// - -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 = true; - @State private var show: Bool = true; - - @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 { - if(show) { - LoadingView(isShowing: $shouldShowLoadingView) { - EmptyView() - .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() - VLCPlayerObj.stop() - 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: { - if(VLCPlayerObj.state == .paused) { - Image(systemName: "play").font(.system(size: 55)).foregroundColor(.white) - } else { - Image(systemName: "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) - .prefersHomeIndicatorAutoHidden(true) - .preferredColorScheme(.dark) - .edgesIgnoringSafeArea(.all) - .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity) - .overrideViewPreference(.unspecified) - .supportedOrientations(.landscape) - .onAppear(perform: onAppear) - } else { - Text("test").onAppear(perform: { - print("ev appear") - usleep(10000); - _show.wrappedValue = true; - }) - } - } - - 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, 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, RestError>) in - switch result { - case .success(let resp): - print(resp.body) - break - case .failure(let error): - debugPrint(error) - break - } - } - } - - func resetTimer() { - print("rt running") - show = false; - if(shouldOverlayShow == true) { - shouldOverlayShow = false - return; - } - shouldOverlayShow = 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, 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, RestError>) in - switch result { - case .success(let resp): - print(resp.body) - break - case .failure(let error): - debugPrint(error) - break - } - } - } -} diff --git a/JellyfinPlayer/Views/VideoPlayer.storyboard b/JellyfinPlayer/Views/VideoPlayer.storyboard index 7ba3f95c..acea156d 100644 --- a/JellyfinPlayer/Views/VideoPlayer.storyboard +++ b/JellyfinPlayer/Views/VideoPlayer.storyboard @@ -51,7 +51,7 @@ - + + + + @@ -109,6 +138,7 @@ + @@ -121,6 +151,8 @@ + + @@ -146,6 +178,8 @@ + + diff --git a/JellyfinPlayer/Views/VLCPlayer.swift b/JellyfinPlayer/Views/VideoPlayer.swift similarity index 88% rename from JellyfinPlayer/Views/VLCPlayer.swift rename to JellyfinPlayer/Views/VideoPlayer.swift index 2897333e..de21ae0b 100644 --- a/JellyfinPlayer/Views/VLCPlayer.swift +++ b/JellyfinPlayer/Views/VideoPlayer.swift @@ -1,13 +1,10 @@ // -// VLCPlayer.swift +// VideoPlayer.swift // JellyfinPlayer // -// Created by Aiden Vigue on 5/10/21. +// Created by Aiden Vigue on 5/26/21. // -//me realizing i shouldve just written the whole app in the mvvm system bc it makes so much more sense -//Please don't touch this ifle - import SwiftUI import MobileVLCKit import SwiftyJSON @@ -55,7 +52,9 @@ class PlayerViewController: UIViewController, VLCMediaDelegate, VLCMediaPlayerDe @IBOutlet weak var videoControlsView: UIView! @IBOutlet weak var seekSlider: UISlider! @IBOutlet weak var titleLabel: UILabel! - + @IBOutlet weak var jumpBackButton: UIButton! + @IBOutlet weak var jumpForwardButton: UIButton! + var shouldShowLoadingScreen: Bool = false; var ssTargetValueOffset: Int = 0; var ssStartValue: Int = 0; @@ -63,6 +62,8 @@ class PlayerViewController: UIViewController, VLCMediaDelegate, VLCMediaPlayerDe var paused: Bool = true; var lastTime: Float = 0.0; var startTime: Int = 0; + var controlsAppearTime: Double = 0; + var selectedAudioTrack: Int32 = 0; var selectedCaptionTrack: Int32 = 0; var playSessionId: String = ""; @@ -115,13 +116,27 @@ class PlayerViewController: UIViewController, VLCMediaDelegate, VLCMediaPlayerDe } @IBAction func controlViewTapped(_ sender: Any) { - videoControlsView.isHidden = !videoControlsView.isHidden + videoControlsView.isHidden = true } @IBAction func contentViewTapped(_ sender: Any) { - videoControlsView.isHidden = !videoControlsView.isHidden + videoControlsView.isHidden = false + controlsAppearTime = CACurrentMediaTime() } + @IBAction func jumpBackTapped(_ sender: Any) { + if(paused == false) { + mediaPlayer.jumpBackward(15) + } + } + + @IBAction func jumpForwardTapped(_ sender: Any) { + if(paused == false) { + mediaPlayer.jumpForward(15) + } + } + + @IBOutlet weak var mainActionButton: UIButton! @IBAction func mainActionButtonPressed(_ sender: Any) { @@ -335,6 +350,11 @@ class PlayerViewController: UIViewController, VLCMediaDelegate, VLCMediaPlayerDe timeTextStr = "\(String(Int((minutes))).leftPad(toWidth: 2, withString: "0")):\(String(Int((seconds))).leftPad(toWidth: 2, withString: "0"))"; } timeText.text = timeTextStr + + if(CACurrentMediaTime() - controlsAppearTime > 5) { + videoControlsView.isHidden = true; + controlsAppearTime = 10000000000000000000000; + } } else { paused = true; } @@ -474,71 +494,3 @@ struct VLCPlayerWithControls: UIViewControllerRepresentable { func updateUIViewController(_ uiViewController: VLCPlayerWithControls.UIViewControllerType, context: UIViewControllerRepresentableContext) { } } - -/* -struct VLCPlayer: UIViewRepresentable{ - var url: Binding; - var player: Binding; - var startTime: Int; - - func updateUIView(_ uiView: PlayerUIView, context: UIViewRepresentableContext) { - uiView.url = self.url - if(self.url.wrappedValue.videoUrl.absoluteString != "https://example.com") { - uiView.videoSetup() - } - } - - func makeUIView(context: Context) -> PlayerUIView { - return PlayerUIView(frame: .zero, url: url, player: self.player, startTime: self.startTime); - } -} - -class PlayerUIView: UIView, VLCMediaPlayerDelegate { - - private var mediaPlayer: Binding; - var url:Binding - var lastUrl: PlaybackItem? - var startTime: Int - - init(frame: CGRect, url: Binding, player: Binding, startTime: Int) { - self.mediaPlayer = player; - self.url = url; - self.startTime = startTime; - super.init(frame: frame) - mediaPlayer.wrappedValue.delegate = self - mediaPlayer.wrappedValue.drawable = self - } - - func videoSetup() { - if(lastUrl == nil || lastUrl?.videoUrl != url.wrappedValue.videoUrl) { - lastUrl = url.wrappedValue - mediaPlayer.wrappedValue.stop() - mediaPlayer.wrappedValue.media = VLCMedia(url: url.wrappedValue.videoUrl) - self.url.wrappedValue.subtitles.forEach() { sub in - if(sub.id != -1 && sub.delivery == "External" && sub.codec != "subrip") { - mediaPlayer.wrappedValue.addPlaybackSlave(sub.url, type: .subtitle, enforce: false) - } - } - - mediaPlayer.wrappedValue.perform(Selector(("setTextRendererFontSize:")), with: 14) - //mediaPlayer.wrappedValue.perform(Selector(("setTextRendererFont:")), with: "Copperplate") - - DispatchQueue.global(qos: .utility).async { [weak self] in - self?.mediaPlayer.wrappedValue.play() - if(self?.startTime != 0) { - print(self?.startTime ?? "") - self?.mediaPlayer.wrappedValue.jumpForward(Int32(self!.startTime/10000000)) - } - } - } - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func layoutSubviews() { - super.layoutSubviews() - } -} - */