From 467d0d4937445701042083f37b4d402ae387ef75 Mon Sep 17 00:00:00 2001 From: Ethan Pippin Date: Tue, 28 Dec 2021 14:48:43 -0700 Subject: [PATCH] Move createVideoPlayerViewModel --- JellyfinPlayer.xcodeproj/project.pbxproj | 6 + .../BaseItemDto+VideoPlayerViewModel.swift | 104 ++++++++++++++++++ Shared/ViewModels/ItemViewModel.swift | 94 +--------------- 3 files changed, 111 insertions(+), 93 deletions(-) create mode 100644 Shared/Extensions/JellyfinAPIExtensions/BaseItemDto+VideoPlayerViewModel.swift diff --git a/JellyfinPlayer.xcodeproj/project.pbxproj b/JellyfinPlayer.xcodeproj/project.pbxproj index 217961ba..6a711e6e 100644 --- a/JellyfinPlayer.xcodeproj/project.pbxproj +++ b/JellyfinPlayer.xcodeproj/project.pbxproj @@ -237,6 +237,8 @@ E10EAA4F277BBCC4000269ED /* CGSizeExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E10EAA4E277BBCC4000269ED /* CGSizeExtensions.swift */; }; E10EAA50277BBCC4000269ED /* CGSizeExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E10EAA4E277BBCC4000269ED /* CGSizeExtensions.swift */; }; E10EAA51277BBCC4000269ED /* CGSizeExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E10EAA4E277BBCC4000269ED /* CGSizeExtensions.swift */; }; + E10EAA53277BBD17000269ED /* BaseItemDto+VideoPlayerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E10EAA52277BBD17000269ED /* BaseItemDto+VideoPlayerViewModel.swift */; }; + E10EAA54277BBD17000269ED /* BaseItemDto+VideoPlayerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E10EAA52277BBD17000269ED /* BaseItemDto+VideoPlayerViewModel.swift */; }; E11B1B6C2718CD68006DA3E8 /* JellyfinAPIError.swift in Sources */ = {isa = PBXBuildFile; fileRef = E11B1B6B2718CD68006DA3E8 /* JellyfinAPIError.swift */; }; E11B1B6D2718CD68006DA3E8 /* JellyfinAPIError.swift in Sources */ = {isa = PBXBuildFile; fileRef = E11B1B6B2718CD68006DA3E8 /* JellyfinAPIError.swift */; }; E11B1B6E2718CDBA006DA3E8 /* JellyfinAPIError.swift in Sources */ = {isa = PBXBuildFile; fileRef = E11B1B6B2718CD68006DA3E8 /* JellyfinAPIError.swift */; }; @@ -564,6 +566,7 @@ E100720626BDABC100CE3E31 /* MediaPlayButtonRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPlayButtonRowView.swift; sourceTree = ""; }; E10EAA49277BB6F5000269ED /* VideoPlayerOverlay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlayerOverlay.swift; sourceTree = ""; }; E10EAA4E277BBCC4000269ED /* CGSizeExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CGSizeExtensions.swift; sourceTree = ""; }; + E10EAA52277BBD17000269ED /* BaseItemDto+VideoPlayerViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BaseItemDto+VideoPlayerViewModel.swift"; sourceTree = ""; }; E11B1B6B2718CD68006DA3E8 /* JellyfinAPIError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JellyfinAPIError.swift; sourceTree = ""; }; E11D224127378428003F9CB3 /* ServerDetailCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerDetailCoordinator.swift; sourceTree = ""; }; E1267D3D271A1F46003C492E /* PreferenceUIHostingController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferenceUIHostingController.swift; sourceTree = ""; }; @@ -1340,6 +1343,7 @@ children = ( E18845F426DD631E00B0C5B7 /* BaseItemDto+Stackable.swift */, E1AD104C26D96CE3003E4A08 /* BaseItemDtoExtensions.swift */, + E10EAA52277BBD17000269ED /* BaseItemDto+VideoPlayerViewModel.swift */, 5364F454266CA0DC0026ECBA /* BaseItemPersonExtensions.swift */, E11B1B6B2718CD68006DA3E8 /* JellyfinAPIError.swift */, E1AD105E26D9ADDD003E4A08 /* NameGUIDPairExtensions.swift */, @@ -1868,6 +1872,7 @@ 536D3D81267BDFC60004248C /* PortraitItemElement.swift in Sources */, 62E1DCC4273CE19800C9AE76 /* URLExtensions.swift in Sources */, 5398514726B64E4100101B49 /* SearchBarView.swift in Sources */, + E10EAA54277BBD17000269ED /* BaseItemDto+VideoPlayerViewModel.swift in Sources */, 091B5A8D268315D400D78B61 /* ServerDiscovery.swift in Sources */, E1D4BF882719D27100A11E64 /* Bitrates.swift in Sources */, E193D5432719407E00900D82 /* tvOSMainCoordinator.swift in Sources */, @@ -2029,6 +2034,7 @@ 5D64683D277B1649009E09AE /* PreferenceUIHostingSwizzling.swift in Sources */, C40CD922271F8CD8000FB198 /* MoviesLibrariesCoordinator.swift in Sources */, E13DD3C827164B1E009D4DAF /* UIDeviceExtensions.swift in Sources */, + E10EAA53277BBD17000269ED /* BaseItemDto+VideoPlayerViewModel.swift in Sources */, E1AD104D26D96CE3003E4A08 /* BaseItemDtoExtensions.swift in Sources */, E13DD3BF27163DD7009D4DAF /* AppDelegate.swift in Sources */, 535870AD2669D8DD00D05A09 /* Typings.swift in Sources */, diff --git a/Shared/Extensions/JellyfinAPIExtensions/BaseItemDto+VideoPlayerViewModel.swift b/Shared/Extensions/JellyfinAPIExtensions/BaseItemDto+VideoPlayerViewModel.swift new file mode 100644 index 00000000..f3065cd0 --- /dev/null +++ b/Shared/Extensions/JellyfinAPIExtensions/BaseItemDto+VideoPlayerViewModel.swift @@ -0,0 +1,104 @@ +// + /* + * SwiftFin is subject to the terms of the Mozilla Public + * License, v2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * Copyright 2021 Aiden Vigue & Jellyfin Contributors + */ + +import Combine +import JellyfinAPI +import UIKit + +extension BaseItemDto { + func createVideoPlayerViewModel() -> AnyPublisher { + let builder = DeviceProfileBuilder() + // TODO: fix bitrate settings + builder.setMaxBitrate(bitrate: 60000000) + let profile = builder.buildProfile() + + let playbackInfo = PlaybackInfoDto(userId: SessionManager.main.currentLogin.user.id, + maxStreamingBitrate: 60000000, + startTimeTicks: self.userData?.playbackPositionTicks ?? 0, + deviceProfile: profile, + autoOpenLiveStream: true) + + return MediaInfoAPI.getPostedPlaybackInfo(itemId: self.id!, + userId: SessionManager.main.currentLogin.user.id, + maxStreamingBitrate: 60000000, + startTimeTicks: self.userData?.playbackPositionTicks ?? 0, + autoOpenLiveStream: true, + playbackInfoDto: playbackInfo) + .map({ response -> VideoPlayerViewModel in + let mediaSource = response.mediaSources!.first! + + let audioStreams = mediaSource.mediaStreams?.filter({ $0.type == .audio }) ?? [] + let subtitleStreams = mediaSource.mediaStreams?.filter({ $0.type == .subtitle }) ?? [] + + let defaultAudioStream = audioStreams.first(where: { $0.index! == mediaSource.defaultAudioStreamIndex! }) + + let defaultSubtitleStream = subtitleStreams.first(where: { $0.index! == mediaSource.defaultSubtitleStreamIndex ?? -1 }) + + let videoStream = mediaSource.mediaStreams!.first(where: { $0.type! == MediaStreamType.video }) + + let audioCodecs = mediaSource.mediaStreams!.filter({ $0.type! == MediaStreamType.audio }).map({ $0.codec! }) + + // MARK: basic stream + var streamURL = URLComponents(string: SessionManager.main.currentLogin.server.currentURI)! + streamURL.path = "/Videos/\(self.id!)/stream" + + streamURL.addQueryItem(name: "Static", value: "true") + streamURL.addQueryItem(name: "MediaSourceId", value: self.id!) + streamURL.addQueryItem(name: "Tag", value: self.etag) + streamURL.addQueryItem(name: "MinSegments", value: "6") + + // MARK: hls stream + var hlsURL = URLComponents(string: SessionManager.main.currentLogin.server.currentURI)! + hlsURL.path = "/videos/\(self.id!)/master.m3u8" + + hlsURL.addQueryItem(name: "DeviceId", value: UIDevice.vendorUUIDString) + hlsURL.addQueryItem(name: "MediaSourceId", value: self.id!) + hlsURL.addQueryItem(name: "VideoCodec", value: videoStream?.codec!) + hlsURL.addQueryItem(name: "AudioCodec", value: audioCodecs.joined(separator: ",")) + hlsURL.addQueryItem(name: "AudioStreamIndex", value: "\(defaultAudioStream!.index!)") + hlsURL.addQueryItem(name: "VideoBitrate", value: "\(videoStream!.bitRate!)") + hlsURL.addQueryItem(name: "AudioBitrate", value: "\(defaultAudioStream!.bitRate!)") + hlsURL.addQueryItem(name: "PlaySessionId", value: response.playSessionId!) + hlsURL.addQueryItem(name: "TranscodingMaxAudioChannels", value: "6") + hlsURL.addQueryItem(name: "RequireAvc", value: "false") + hlsURL.addQueryItem(name: "Tag", value: mediaSource.eTag!) + hlsURL.addQueryItem(name: "SegmentContainer", value: "ts") + hlsURL.addQueryItem(name: "MinSegments", value: "2") + hlsURL.addQueryItem(name: "BreakOnNonKeyFrames", value: "true") + hlsURL.addQueryItem(name: "TranscodeReasons", value: "VideoCodecNotSupported,AudioCodecNotSupported") + hlsURL.addQueryItem(name: "api_key", value: SessionManager.main.currentLogin.user.accessToken) + + if defaultSubtitleStream?.index != nil { + hlsURL.addQueryItem(name: "SubtitleMethod", value: "Encode") + hlsURL.addQueryItem(name: "SubtitleStreamIndex", value: "\(defaultSubtitleStream!.index!)") + } + + let videoPlayerViewModel = VideoPlayerViewModel(item: self, + title: self.name!, + subtitle: self.seriesName, + streamURL: streamURL.url!, + hlsURL: hlsURL.url!, + response: response, + audioStreams: audioStreams, + subtitleStreams: subtitleStreams, + defaultAudioStreamIndex: defaultAudioStream?.index ?? -1, + defaultSubtitleStreamIndex: defaultSubtitleStream?.index ?? -1, + playerState: .playing, + shouldShowGoogleCast: false, + shouldShowAirplay: false, + subtitlesEnabled: defaultAudioStream?.index != nil, + sliderPercentage: (self.userData?.playedPercentage ?? 0) / 100, + selectedAudioStreamIndex: defaultAudioStream?.index ?? -1, + selectedSubtitleStreamIndex: defaultSubtitleStream?.index ?? -1) + + return videoPlayerViewModel + }) + .eraseToAnyPublisher() + } +} diff --git a/Shared/ViewModels/ItemViewModel.swift b/Shared/ViewModels/ItemViewModel.swift index 27fd6542..0a0b6664 100644 --- a/Shared/ViewModels/ItemViewModel.swift +++ b/Shared/ViewModels/ItemViewModel.swift @@ -36,7 +36,7 @@ class ItemViewModel: ViewModel { getSimilarItems() - self.createVideoPlayerViewModel(item: item) + item.createVideoPlayerViewModel() .sink { completion in self.handleAPIRequestError(completion: completion) } receiveValue: { videoPlayerViewModel in @@ -111,96 +111,4 @@ class ItemViewModel: ViewModel { .store(in: &cancellables) } } - - func createVideoPlayerViewModel(item: BaseItemDto) -> AnyPublisher { - let builder = DeviceProfileBuilder() - // TODO: fix bitrate settings - builder.setMaxBitrate(bitrate: 60000000) - let profile = builder.buildProfile() - - let playbackInfo = PlaybackInfoDto(userId: SessionManager.main.currentLogin.user.id, - maxStreamingBitrate: 60000000, - startTimeTicks: item.userData?.playbackPositionTicks ?? 0, - deviceProfile: profile, - autoOpenLiveStream: true) - - return MediaInfoAPI.getPostedPlaybackInfo(itemId: item.id!, - userId: SessionManager.main.currentLogin.user.id, - maxStreamingBitrate: 60000000, - startTimeTicks: item.userData?.playbackPositionTicks ?? 0, - autoOpenLiveStream: true, - playbackInfoDto: playbackInfo) - .map({ response -> VideoPlayerViewModel in - let mediaSource = response.mediaSources!.first! - - let audioStreams = mediaSource.mediaStreams?.filter({ $0.type == .audio }) ?? [] - let subtitleStreams = mediaSource.mediaStreams?.filter({ $0.type == .subtitle }) ?? [] - - let defaultAudioStream = audioStreams.first(where: { $0.index! == mediaSource.defaultAudioStreamIndex! }) - - let defaultSubtitleStream = subtitleStreams.first(where: { $0.index! == mediaSource.defaultSubtitleStreamIndex ?? -1 }) - - let videoStream = mediaSource.mediaStreams!.first(where: { $0.type! == MediaStreamType.video }) - - let audioCodecs = mediaSource.mediaStreams!.filter({ $0.type! == MediaStreamType.audio }).map({ $0.codec! }) - - // MARK: basic stream - var streamURL = URLComponents(string: SessionManager.main.currentLogin.server.currentURI)! - streamURL.path = "/Videos/\(item.id!)/stream" - - streamURL.addQueryItem(name: "Static", value: "true") - streamURL.addQueryItem(name: "MediaSourceId", value: item.id!) - streamURL.addQueryItem(name: "Tag", value: item.etag) - streamURL.addQueryItem(name: "MinSegments", value: "6") - - // MARK: hls stream - var hlsURL = URLComponents(string: SessionManager.main.currentLogin.server.currentURI)! - hlsURL.path = "/videos/\(item.id!)/master.m3u8" - - hlsURL.addQueryItem(name: "DeviceId", value: UIDevice.vendorUUIDString) - hlsURL.addQueryItem(name: "MediaSourceId", value: item.id!) - hlsURL.addQueryItem(name: "VideoCodec", value: videoStream?.codec!) - hlsURL.addQueryItem(name: "AudioCodec", value: audioCodecs.joined(separator: ",")) - hlsURL.addQueryItem(name: "AudioStreamIndex", value: "\(defaultAudioStream!.index!)") - hlsURL.addQueryItem(name: "VideoBitrate", value: "\(videoStream!.bitRate!)") - hlsURL.addQueryItem(name: "AudioBitrate", value: "\(defaultAudioStream!.bitRate!)") - hlsURL.addQueryItem(name: "PlaySessionId", value: response.playSessionId!) - hlsURL.addQueryItem(name: "TranscodingMaxAudioChannels", value: "6") - hlsURL.addQueryItem(name: "RequireAvc", value: "false") - hlsURL.addQueryItem(name: "Tag", value: mediaSource.eTag!) - hlsURL.addQueryItem(name: "SegmentContainer", value: "ts") - hlsURL.addQueryItem(name: "MinSegments", value: "2") - hlsURL.addQueryItem(name: "BreakOnNonKeyFrames", value: "true") - hlsURL.addQueryItem(name: "TranscodeReasons", value: "VideoCodecNotSupported,AudioCodecNotSupported") - hlsURL.addQueryItem(name: "api_key", value: SessionManager.main.currentLogin.user.accessToken) - - if defaultSubtitleStream?.index != nil { - hlsURL.addQueryItem(name: "SubtitleMethod", value: "Encode") - hlsURL.addQueryItem(name: "SubtitleStreamIndex", value: "\(defaultSubtitleStream!.index!)") - } - -// startURL.queryItems?.append(URLQueryItem(name: "SubtitleCodec", value: "\(defaultSubtitleStream!.codec!)")) - - let videoPlayerViewModel = VideoPlayerViewModel(item: item, - title: item.name!, - subtitle: item.seriesName, - streamURL: streamURL.url!, - hlsURL: hlsURL.url!, - response: response, - audioStreams: audioStreams, - subtitleStreams: subtitleStreams, - defaultAudioStreamIndex: defaultAudioStream?.index ?? -1, - defaultSubtitleStreamIndex: defaultSubtitleStream?.index ?? -1, - playerState: .playing, - shouldShowGoogleCast: false, - shouldShowAirplay: false, - subtitlesEnabled: defaultAudioStream?.index != nil, - sliderPercentage: (item.userData?.playedPercentage ?? 0) / 100, - selectedAudioStreamIndex: defaultAudioStream?.index ?? -1, - selectedSubtitleStreamIndex: defaultSubtitleStream?.index ?? -1) - - return videoPlayerViewModel - }) - .eraseToAnyPublisher() - } }