* Rename ExperimentalSettingsView.swift to PlaybackQualitySettingsView.swift Fix Merge * Rename MaximumBitrateSettingsView.swift to PlaybackQualitySettingsView.swift fix merge * Re-implement on Main. Should now have all the Main changed. Added a new change to use the Device Profile as a Transcoding Profile. * Part 1 -> Making VideoPlayerType into a struct (I Hope) correctly * Part 1.1 -> Making VideoPlayerType into a struct (I Hope) correctly * Remove unneeded Files * Missing file + CustomDeviceProfileSelection -> CustomDeviceProfileAction Rename * Change + to Appending * Attempt to add StorageValues+User. Not sure if this is correct? * Move the Array unwrapping to funcitons. Not required but this should help prevent accidently doing this wrong. Add subtitles back into the custom profiles since that somehow got dropped. Added a PlaybackCompatibility enum. This might need to work for more than just video * Complete rewrite to allow multiple profiles, compatibility mode, and directplay. * Hardward -> Hardware * Update CustomDeviceProfileSettingsView.swift Double Licensing * It was actually really easy to implement iOS... Trash cans still look weird and small. * Swipe to Delete instead of the edit button * wip * wip * Linting * tvOS Implementation * wip * wip * cleanup * Create Package.resolved --------- Co-authored-by: Joseph Kribs <joseph@kribs.net> Co-authored-by: Ethan Pippin <ethanpippin2343@gmail.com>
136 lines
5.3 KiB
Swift
136 lines
5.3 KiB
Swift
//
|
|
// 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 (c) 2024 Jellyfin & Jellyfin Contributors
|
|
//
|
|
|
|
import Defaults
|
|
import Factory
|
|
import Foundation
|
|
import JellyfinAPI
|
|
import Logging
|
|
|
|
extension BaseItemDto {
|
|
|
|
func videoPlayerViewModel(with mediaSource: MediaSourceInfo) async throws -> VideoPlayerViewModel {
|
|
|
|
let currentVideoPlayerType = Defaults[.VideoPlayer.videoPlayerType]
|
|
let currentVideoBitrate = Defaults[.VideoPlayer.Playback.appMaximumBitrate]
|
|
let compatibilityMode = Defaults[.VideoPlayer.Playback.compatibilityMode]
|
|
|
|
let maxBitrate = try await getMaxBitrate(for: currentVideoBitrate)
|
|
let profile = DeviceProfile.build(
|
|
for: currentVideoPlayerType,
|
|
compatibilityMode: compatibilityMode,
|
|
maxBitrate: maxBitrate
|
|
)
|
|
|
|
let userSession = Container.shared.currentUserSession()!
|
|
|
|
let playbackInfo = PlaybackInfoDto(deviceProfile: profile)
|
|
let playbackInfoParameters = Paths.GetPostedPlaybackInfoParameters(
|
|
userID: userSession.user.id,
|
|
maxStreamingBitrate: maxBitrate
|
|
)
|
|
|
|
let request = Paths.getPostedPlaybackInfo(
|
|
itemID: self.id!,
|
|
parameters: playbackInfoParameters,
|
|
playbackInfo
|
|
)
|
|
|
|
let response = try await userSession.client.send(request)
|
|
|
|
guard let matchingMediaSource = response.value.mediaSources?
|
|
.first(where: { $0.eTag == mediaSource.eTag && $0.id == mediaSource.id })
|
|
else {
|
|
throw JellyfinAPIError("Matching media source not in playback info")
|
|
}
|
|
|
|
return try matchingMediaSource.videoPlayerViewModel(with: self, playSessionID: response.value.playSessionID!)
|
|
}
|
|
|
|
func liveVideoPlayerViewModel(with mediaSource: MediaSourceInfo, logger: Logger) async throws -> VideoPlayerViewModel {
|
|
|
|
let currentVideoPlayerType = Defaults[.VideoPlayer.videoPlayerType]
|
|
let currentVideoBitrate = Defaults[.VideoPlayer.Playback.appMaximumBitrate]
|
|
let compatibilityMode = Defaults[.VideoPlayer.Playback.compatibilityMode]
|
|
|
|
let maxBitrate = try await getMaxBitrate(for: currentVideoBitrate)
|
|
let profile = DeviceProfile.build(
|
|
for: currentVideoPlayerType,
|
|
compatibilityMode: compatibilityMode,
|
|
maxBitrate: maxBitrate
|
|
)
|
|
|
|
let userSession = Container.shared.currentUserSession()!
|
|
|
|
let playbackInfo = PlaybackInfoDto(deviceProfile: profile)
|
|
let playbackInfoParameters = Paths.GetPostedPlaybackInfoParameters(
|
|
userID: userSession.user.id,
|
|
maxStreamingBitrate: maxBitrate
|
|
)
|
|
|
|
let request = Paths.getPostedPlaybackInfo(
|
|
itemID: self.id!,
|
|
parameters: playbackInfoParameters,
|
|
playbackInfo
|
|
)
|
|
|
|
let response = try await userSession.client.send(request)
|
|
logger.debug("liveVideoPlayerViewModel response received")
|
|
|
|
var matchingMediaSource: MediaSourceInfo?
|
|
if let responseMediaSources = response.value.mediaSources {
|
|
for responseMediaSource in responseMediaSources {
|
|
if let openToken = responseMediaSource.openToken, let mediaSourceId = mediaSource.id {
|
|
if openToken.contains(mediaSourceId) {
|
|
logger.debug("liveVideoPlayerViewModel found mediaSource with through openToken mediaSourceId match")
|
|
matchingMediaSource = responseMediaSource
|
|
}
|
|
}
|
|
}
|
|
if matchingMediaSource == nil && !responseMediaSources.isEmpty {
|
|
// Didn't find a match, but maybe we can just grab the first item in the response
|
|
matchingMediaSource = responseMediaSources.first
|
|
logger.debug("liveVideoPlayerViewModel resorting to first media source in the response")
|
|
}
|
|
}
|
|
guard let matchingMediaSource else {
|
|
logger.debug("liveVideoPlayerViewModel no matchingMediaSource found, throwing error")
|
|
throw JellyfinAPIError("Matching media source not in playback info")
|
|
}
|
|
|
|
logger.debug("liveVideoPlayerViewModel matchingMediaSource being returned")
|
|
return try matchingMediaSource.liveVideoPlayerViewModel(
|
|
with: self,
|
|
playSessionID: response.value.playSessionID!
|
|
)
|
|
}
|
|
|
|
private func getMaxBitrate(for bitrate: PlaybackBitrate) async throws -> Int {
|
|
let settingBitrate = Defaults[.VideoPlayer.Playback.appMaximumBitrateTest]
|
|
|
|
guard bitrate != .auto else {
|
|
return try await testBitrate(with: settingBitrate.rawValue)
|
|
}
|
|
return bitrate.rawValue
|
|
}
|
|
|
|
private func testBitrate(with testSize: Int) async throws -> Int {
|
|
precondition(testSize > 0, "testSize must be greater than zero")
|
|
|
|
let userSession = Container.shared.currentUserSession()!
|
|
|
|
let testStartTime = Date()
|
|
try await userSession.client.send(Paths.getBitrateTestBytes(size: testSize))
|
|
let testDuration = Date().timeIntervalSince(testStartTime)
|
|
let testSizeBits = Double(testSize * 8)
|
|
let testBitrate = testSizeBits / testDuration
|
|
|
|
return Int(testBitrate)
|
|
}
|
|
}
|