204 lines
12 KiB
Swift
204 lines
12 KiB
Swift
/* JellyfinPlayer/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
|
|
*/
|
|
|
|
//lol can someone buy me a coffee this took forever :|
|
|
|
|
import Foundation
|
|
import JellyfinAPI
|
|
|
|
enum CPUModel {
|
|
case A4
|
|
case A5
|
|
case A5X
|
|
case A6
|
|
case A6X
|
|
case A7
|
|
case A7X
|
|
case A8
|
|
case A8X
|
|
case A9
|
|
case A9X
|
|
case A10
|
|
case A10X
|
|
case A11
|
|
case A12
|
|
case A12X
|
|
case A12Z
|
|
case A13
|
|
case A14
|
|
case A99
|
|
}
|
|
|
|
class DeviceProfileBuilder {
|
|
public var bitrate: Int = 0;
|
|
|
|
public func setMaxBitrate(bitrate: Int) {
|
|
self.bitrate = bitrate
|
|
}
|
|
|
|
public func buildProfile() -> DeviceProfile {
|
|
let maxStreamingBitrate = bitrate;
|
|
let maxStaticBitrate = bitrate;
|
|
let musicStreamingTranscodingBitrate = 384000;
|
|
|
|
//Build direct play profiles
|
|
var directPlayProfiles: [DirectPlayProfile] = [];
|
|
directPlayProfiles = [DirectPlayProfile(container: "mov,mp4,mkv", audioCodec: "aac,mp3,wav", videoCodec: "h264", type: .video)]
|
|
|
|
//Device supports Dolby Digital (AC3, EAC3)
|
|
if(supportsFeature(minimumSupported: .A8X)) {
|
|
if(supportsFeature(minimumSupported: .A10)) {
|
|
directPlayProfiles = [DirectPlayProfile(container: "mov,mp4,mkv", audioCodec: "aac,mp3,wav,ac3,eac3,flac,opus", videoCodec: "hevc,h264,hev1", type: .video)] //HEVC/H.264 with Dolby Digital
|
|
} else {
|
|
directPlayProfiles = [DirectPlayProfile(container: "mov,mp4,mkv", audioCodec: "ac3,eac3,aac,mp3,wav,opus", videoCodec: "h264", type: .video)] //H.264 with Dolby Digital
|
|
}
|
|
}
|
|
|
|
//Device supports Dolby Vision?
|
|
if(supportsFeature(minimumSupported: .A10X)) {
|
|
directPlayProfiles = [DirectPlayProfile(container: "mov,mp4,mkv", audioCodec: "aac,mp3,wav,ac3,eac3,flac,opus", videoCodec: "dvhe,dvh1,dva1,dvav,h264,hevc,hev1", type: .video)] //H.264/HEVC with Dolby Digital - No Atmos - Vision
|
|
}
|
|
|
|
//Device supports Dolby Atmos?
|
|
if(supportsFeature(minimumSupported: .A12)) {
|
|
directPlayProfiles = [DirectPlayProfile(container: "mov,mp4,mkv", audioCodec: "aac,mp3,wav,ac3,eac3,flac,truehd,dts,dca,opus", videoCodec: "h264,hevc,dvhe,dvh1,dva1,dvav,h264,hevc,hev1", type: .video)] //H.264/HEVC with Dolby Digital & Atmos - Vision
|
|
}
|
|
|
|
//Build transcoding profiles
|
|
var transcodingProfiles: [TranscodingProfile] = [];
|
|
transcodingProfiles = [TranscodingProfile(container: "ts", type: .video, videoCodec: "h264", audioCodec: "aac,mp3,wav")]
|
|
|
|
//Device supports Dolby Digital (AC3, EAC3)
|
|
if(supportsFeature(minimumSupported: .A8X)) {
|
|
if(supportsFeature(minimumSupported: .A10)) {
|
|
transcodingProfiles = [TranscodingProfile(container: "ts", type: .video, videoCodec: "aac,mp3,wav,eac3,ac3,flac,opus", audioCodec: "h264,hevc,hev1", _protocol: "hls", context: .streaming, maxAudioChannels: "6", minSegments: 2, breakOnNonKeyFrames: true)]
|
|
} else {
|
|
transcodingProfiles = [TranscodingProfile(container: "ts", type: .video, videoCodec: "h264", audioCodec: "aac,mp3,wav,eac3,ac3,opus", _protocol: "hls", context: .streaming, maxAudioChannels: "6", minSegments: 2, breakOnNonKeyFrames: true)]
|
|
}
|
|
}
|
|
|
|
//Device supports Dolby Vision?
|
|
if(supportsFeature(minimumSupported: .A10X)) {
|
|
transcodingProfiles = [TranscodingProfile(container: "ts", type: .video, videoCodec: "dva1,dvav,dvhe,dvh1,hevc,h264,hev1", audioCodec: "aac,mp3,wav,ac3,eac3,flac,opus", _protocol: "hls", context: .streaming, maxAudioChannels: "6", minSegments: 2, breakOnNonKeyFrames: true)]
|
|
}
|
|
|
|
//Device supports Dolby Atmos?
|
|
if(supportsFeature(minimumSupported: .A12)) {
|
|
transcodingProfiles = [TranscodingProfile(container: "ts", type: .video, videoCodec: "dva1,dvav,dvhe,dvh1,hevc,h264,hev1", audioCodec: "aac,mp3,wav,ac3,eac3,flac,dts,truehd,dca,opus", _protocol: "hls", context: .streaming, maxAudioChannels: "6", minSegments: 2, breakOnNonKeyFrames: true)]
|
|
}
|
|
|
|
var codecProfiles: [CodecProfile] = []
|
|
|
|
let h264CodecConditions: [ProfileCondition] = [
|
|
ProfileCondition(condition: .notEquals, property: .isAnamorphic, value: "true", isRequired: false),
|
|
ProfileCondition(condition: .equalsAny, property: .videoProfile, value: "high|main|baseline|constrained baseline", isRequired: false),
|
|
ProfileCondition(condition: .lessThanEqual, property: .videoLevel, value: "60", isRequired: false),
|
|
ProfileCondition(condition: .notEquals, property: .isInterlaced, value: "true", isRequired: false)]
|
|
let hevcCodecConditions: [ProfileCondition] = [
|
|
ProfileCondition(condition: .notEquals, property: .isAnamorphic, value: "true", isRequired: false),
|
|
ProfileCondition(condition: .equalsAny, property: .videoProfile, value: "main|main 10", isRequired: false),
|
|
ProfileCondition(condition: .lessThanEqual, property: .videoLevel, value: "160", isRequired: false),
|
|
ProfileCondition(condition: .notEquals, property: .isInterlaced, value: "true", isRequired: false)]
|
|
|
|
codecProfiles.append(CodecProfile(type: .video, applyConditions: h264CodecConditions, codec: "h264"))
|
|
|
|
if(supportsFeature(minimumSupported: .A10)) {
|
|
codecProfiles.append(CodecProfile(type: .video, applyConditions: hevcCodecConditions,codec: "hevc"))
|
|
}
|
|
|
|
var subtitleProfiles: [SubtitleProfile] = []
|
|
subtitleProfiles.append(SubtitleProfile(format: "vtt", method: .external))
|
|
subtitleProfiles.append(SubtitleProfile(format: "ass", method: .external))
|
|
subtitleProfiles.append(SubtitleProfile(format: "ssa", method: .external))
|
|
subtitleProfiles.append(SubtitleProfile(format: "pgssub", method: .embed))
|
|
subtitleProfiles.append(SubtitleProfile(format: "sub", method: .embed))
|
|
subtitleProfiles.append(SubtitleProfile(format: "rip", method: .embed))
|
|
subtitleProfiles.append(SubtitleProfile(format: "srt", method: .embed))
|
|
subtitleProfiles.append(SubtitleProfile(format: "pgs", method: .embed))
|
|
|
|
let responseProfiles: [ResponseProfile] = [ResponseProfile(container: "m4v", type: .video, mimeType: "video/mp4")]
|
|
|
|
let profile = DeviceProfile(maxStreamingBitrate: maxStreamingBitrate, maxStaticBitrate: maxStaticBitrate, musicStreamingTranscodingBitrate: musicStreamingTranscodingBitrate, directPlayProfiles: directPlayProfiles, transcodingProfiles: transcodingProfiles, containerProfiles: [], codecProfiles: codecProfiles, responseProfiles: responseProfiles, subtitleProfiles: subtitleProfiles)
|
|
|
|
return profile
|
|
}
|
|
|
|
private func supportsFeature(minimumSupported: CPUModel) -> Bool {
|
|
let intValues: [CPUModel: Int] = [.A4: 1, .A5: 2, .A5X: 3, .A6: 4, .A6X: 5, .A7: 6, .A7X: 7, .A8: 8, .A8X: 9, .A9: 10, .A9X: 11, .A10: 12, .A10X: 13, .A11: 14, .A12: 15, .A12X: 16, .A12Z: 16, .A13: 17, .A14: 18, .A99: 99]
|
|
return intValues[CPUinfo()] ?? 0 >= intValues[minimumSupported] ?? 0
|
|
}
|
|
|
|
/**********************************************
|
|
* CPUInfo():
|
|
* Returns a hardcoded value of the current
|
|
* devices CPU name.
|
|
***********************************************/
|
|
private func CPUinfo() -> CPUModel {
|
|
|
|
#if targetEnvironment(simulator)
|
|
let identifier = ProcessInfo().environment["SIMULATOR_MODEL_IDENTIFIER"]!
|
|
#else
|
|
|
|
var systemInfo = utsname()
|
|
uname(&systemInfo)
|
|
let machineMirror = Mirror(reflecting: systemInfo.machine)
|
|
let identifier = machineMirror.children.reduce("") { identifier, element in
|
|
guard let value = element.value as? Int8 , value != 0 else { return identifier }
|
|
return identifier + String(UnicodeScalar(UInt8(value)))
|
|
}
|
|
#endif
|
|
|
|
switch identifier {
|
|
case "iPod5,1": return .A5
|
|
case "iPod7,1": return .A8
|
|
case "iPod9,1": return .A10
|
|
case "iPhone3,1", "iPhone3,2", "iPhone3,3": return .A4
|
|
case "iPhone4,1": return .A5
|
|
case "iPhone5,1", "iPhone5,2": return .A6
|
|
case "iPhone5,3", "iPhone5,4": return .A6
|
|
case "iPhone6,1", "iPhone6,2": return .A7
|
|
case "iPhone7,2": return .A8
|
|
case "iPhone7,1": return .A8
|
|
case "iPhone8,1": return .A9
|
|
case "iPhone8,2", "iPhone8,4": return .A9
|
|
case "iPhone9,1", "iPhone9,3": return .A10
|
|
case "iPhone9,2", "iPhone9,4": return .A10
|
|
case "iPhone10,1", "iPhone10,4": return .A11
|
|
case "iPhone10,2", "iPhone10,5": return .A11
|
|
case "iPhone10,3", "iPhone10,6": return .A11
|
|
case "iPhone11,2", "iPhone11,6", "iPhone11,8": return .A12
|
|
case "iPhone12,1", "iPhone12,3", "iPhone12,5", "iPhone12,8": return .A13
|
|
case "iPhone13,1", "iPhone13,2", "iPhone13,3", "iPhone13,4": return .A14
|
|
case "iPad2,1", "iPad2,2", "iPad2,3", "iPad2,4": return .A5
|
|
case "iPad3,1", "iPad3,2", "iPad3,3": return .A5X
|
|
case "iPad3,4", "iPad3,5", "iPad3,6": return .A6X
|
|
case "iPad4,1", "iPad4,2", "iPad4,3": return .A7
|
|
case "iPad5,3", "iPad5,4": return .A8X
|
|
case "iPad6,11", "iPad6,12": return .A9
|
|
case "iPad2,5", "iPad2,6", "iPad2,7": return .A5
|
|
case "iPad4,4", "iPad4,5", "iPad4,6": return .A7
|
|
case "iPad4,7", "iPad4,8", "iPad4,9": return .A7
|
|
case "iPad5,1", "iPad5,2": return .A8
|
|
case "iPad11,1", "iPad11,2": return .A12
|
|
case "iPad6,3", "iPad6,4": return .A9X
|
|
case "iPad6,7", "iPad6,8": return .A9X
|
|
case "iPad7,1", "iPad7,2": return .A10X
|
|
case "iPad7,3", "iPad7,4": return .A10X
|
|
case "iPad7,5", "iPad7,6", "iPad7,11", "iPad7,12": return .A10
|
|
case "iPad8,1", "iPad8,2" ,"iPad8,3", "iPad8,4": return .A12X
|
|
case "iPad8,5", "iPad8,6" ,"iPad8,7", "iPad8,8": return .A12X
|
|
case "iPad8,9", "iPad8,10", "iPad8,11", "iPad8,12": return .A12Z
|
|
case "iPad11,3", "iPad11,4" ,"iPad11,6", "iPad11,7": return .A12
|
|
case "iPad13,1", "iPad13,2": return .A14
|
|
case "AppleTV5,3": return .A8
|
|
case "AppleTV6,2": return .A10X
|
|
case "AudioAccessory1,1": return .A8
|
|
default: return .A99
|
|
}
|
|
}
|
|
}
|