2021-06-14 18:05:02 +00:00
|
|
|
import AVFoundation
|
2021-09-25 08:18:22 +00:00
|
|
|
import Defaults
|
2021-06-14 18:05:02 +00:00
|
|
|
import Foundation
|
|
|
|
|
|
|
|
// swiftlint:disable:next final_class
|
2021-10-16 22:48:58 +00:00
|
|
|
class Stream: Equatable, Hashable, Identifiable {
|
2021-09-25 08:18:22 +00:00
|
|
|
enum Resolution: String, CaseIterable, Comparable, Defaults.Serializable {
|
2022-03-27 18:59:22 +00:00
|
|
|
case hd2160p
|
|
|
|
case hd1440p60
|
|
|
|
case hd1440p
|
|
|
|
case hd1080p60
|
|
|
|
case hd1080p
|
|
|
|
case hd720p60
|
|
|
|
case hd720p
|
|
|
|
case sd480p
|
|
|
|
case sd360p
|
|
|
|
case sd240p
|
|
|
|
case sd144p
|
|
|
|
case unknown
|
2021-10-16 22:48:58 +00:00
|
|
|
|
|
|
|
var name: String {
|
|
|
|
"\(height)p\(refreshRate != -1 ? ", \(refreshRate) fps" : "")"
|
|
|
|
}
|
2021-07-22 12:43:13 +00:00
|
|
|
|
|
|
|
var height: Int {
|
2021-10-16 22:48:58 +00:00
|
|
|
if self == .unknown {
|
|
|
|
return -1
|
|
|
|
}
|
|
|
|
|
|
|
|
let resolutionPart = rawValue.components(separatedBy: "p").first!
|
|
|
|
return Int(resolutionPart.components(separatedBy: CharacterSet.decimalDigits.inverted).joined())!
|
|
|
|
}
|
|
|
|
|
|
|
|
var refreshRate: Int {
|
|
|
|
if self == .unknown {
|
|
|
|
return -1
|
|
|
|
}
|
|
|
|
|
|
|
|
let refreshRatePart = rawValue.components(separatedBy: "p")[1]
|
|
|
|
return Int(refreshRatePart.components(separatedBy: CharacterSet.decimalDigits.inverted).joined()) ?? -1
|
2021-07-22 12:43:13 +00:00
|
|
|
}
|
|
|
|
|
2021-10-16 22:48:58 +00:00
|
|
|
static func from(resolution: String) -> Resolution {
|
|
|
|
allCases.first { "\($0)".contains(resolution) } ?? .unknown
|
2021-07-22 12:43:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static func < (lhs: Resolution, rhs: Resolution) -> Bool {
|
|
|
|
lhs.height < rhs.height
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
enum Kind: String, Comparable {
|
2021-10-16 22:48:58 +00:00
|
|
|
case stream, adaptive, hls
|
2021-07-22 12:43:13 +00:00
|
|
|
|
|
|
|
private var sortOrder: Int {
|
|
|
|
switch self {
|
2021-10-16 22:48:58 +00:00
|
|
|
case .hls:
|
2021-07-22 12:43:13 +00:00
|
|
|
return 0
|
2021-10-16 22:48:58 +00:00
|
|
|
case .stream:
|
2021-07-22 12:43:13 +00:00
|
|
|
return 1
|
2021-10-16 22:48:58 +00:00
|
|
|
case .adaptive:
|
|
|
|
return 2
|
2021-07-22 12:43:13 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static func < (lhs: Kind, rhs: Kind) -> Bool {
|
|
|
|
lhs.sortOrder < rhs.sortOrder
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-16 22:48:58 +00:00
|
|
|
let id = UUID()
|
|
|
|
|
|
|
|
var instance: Instance!
|
|
|
|
var audioAsset: AVURLAsset!
|
|
|
|
var videoAsset: AVURLAsset!
|
|
|
|
var hlsURL: URL!
|
2021-06-14 18:05:02 +00:00
|
|
|
|
2021-10-16 22:48:58 +00:00
|
|
|
var resolution: Resolution!
|
|
|
|
var kind: Kind!
|
2021-06-14 18:05:02 +00:00
|
|
|
|
2021-10-16 22:48:58 +00:00
|
|
|
var encoding: String!
|
2022-02-16 20:23:11 +00:00
|
|
|
var videoFormat: String!
|
2021-06-14 18:05:02 +00:00
|
|
|
|
2021-10-16 22:48:58 +00:00
|
|
|
init(
|
|
|
|
instance: Instance? = nil,
|
|
|
|
audioAsset: AVURLAsset? = nil,
|
|
|
|
videoAsset: AVURLAsset? = nil,
|
|
|
|
hlsURL: URL? = nil,
|
|
|
|
resolution: Resolution? = nil,
|
|
|
|
kind: Kind = .hls,
|
2022-02-16 20:23:11 +00:00
|
|
|
encoding: String? = nil,
|
|
|
|
videoFormat: String? = nil
|
2021-10-16 22:48:58 +00:00
|
|
|
) {
|
|
|
|
self.instance = instance
|
2021-06-14 18:05:02 +00:00
|
|
|
self.audioAsset = audioAsset
|
|
|
|
self.videoAsset = videoAsset
|
2021-10-16 22:48:58 +00:00
|
|
|
self.hlsURL = hlsURL
|
2021-06-14 18:05:02 +00:00
|
|
|
self.resolution = resolution
|
2021-07-22 12:43:13 +00:00
|
|
|
self.kind = kind
|
2021-06-14 18:05:02 +00:00
|
|
|
self.encoding = encoding
|
2022-02-16 20:23:11 +00:00
|
|
|
self.videoFormat = videoFormat
|
2021-06-14 18:05:02 +00:00
|
|
|
}
|
|
|
|
|
2021-10-16 22:48:58 +00:00
|
|
|
var quality: String {
|
2022-02-16 20:23:11 +00:00
|
|
|
if resolution == .hd2160p {
|
|
|
|
return "4K (2160p)"
|
|
|
|
}
|
|
|
|
|
|
|
|
return kind == .hls ? "adaptive (HLS)" : "\(resolution.name)\(kind == .stream ? " (\(kind.rawValue))" : "")"
|
|
|
|
}
|
|
|
|
|
|
|
|
var format: String {
|
|
|
|
let lowercasedFormat = (videoFormat ?? "unknown").lowercased()
|
|
|
|
if lowercasedFormat.contains("webm") {
|
|
|
|
return "WEBM"
|
|
|
|
} else if lowercasedFormat.contains("avc1") {
|
|
|
|
return "avc1"
|
|
|
|
} else if lowercasedFormat.contains("av01") {
|
|
|
|
return "AV1"
|
|
|
|
} else if lowercasedFormat.contains("mpeg_4") || lowercasedFormat.contains("mp4") {
|
|
|
|
return "MP4"
|
|
|
|
} else {
|
|
|
|
return lowercasedFormat
|
|
|
|
}
|
2021-10-16 22:48:58 +00:00
|
|
|
}
|
|
|
|
|
2021-06-14 18:05:02 +00:00
|
|
|
var description: String {
|
2022-02-16 20:23:11 +00:00
|
|
|
let formatString = format == "unknown" ? "" : " (\(format))"
|
|
|
|
return "\(quality)\(formatString) - \(instance?.description ?? "")"
|
2021-06-14 18:05:02 +00:00
|
|
|
}
|
|
|
|
|
2021-06-15 16:35:21 +00:00
|
|
|
var assets: [AVURLAsset] {
|
|
|
|
[audioAsset, videoAsset]
|
|
|
|
}
|
|
|
|
|
2021-10-16 22:48:58 +00:00
|
|
|
var videoAssetContainsAudio: Bool {
|
2021-10-05 20:20:09 +00:00
|
|
|
assets.dropFirst().allSatisfy { $0.url == assets.first!.url }
|
2021-07-22 12:43:13 +00:00
|
|
|
}
|
|
|
|
|
2021-10-16 22:48:58 +00:00
|
|
|
var singleAssetURL: URL? {
|
|
|
|
if kind == .hls {
|
|
|
|
return hlsURL
|
|
|
|
} else if videoAssetContainsAudio {
|
|
|
|
return videoAsset.url
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-06-14 18:05:02 +00:00
|
|
|
static func == (lhs: Stream, rhs: Stream) -> Bool {
|
2021-10-16 22:48:58 +00:00
|
|
|
lhs.id == rhs.id
|
2021-07-22 12:43:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func hash(into hasher: inout Hasher) {
|
2021-10-16 22:48:58 +00:00
|
|
|
hasher.combine(videoAsset?.url)
|
|
|
|
hasher.combine(audioAsset?.url)
|
|
|
|
hasher.combine(hlsURL)
|
|
|
|
}
|
2021-06-14 18:05:02 +00:00
|
|
|
}
|