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 {
|
2024-08-25 15:23:04 +00:00
|
|
|
// Some 16:19 and 16:10 resolutions are also used in 2:1 videos
|
|
|
|
|
|
|
|
// 8K UHD (16:9) Resolutions
|
|
|
|
case hd4320p60
|
|
|
|
case hd4320p50
|
|
|
|
case hd4320p48
|
|
|
|
case hd4320p30
|
|
|
|
case hd4320p25
|
|
|
|
case hd4320p24
|
|
|
|
|
|
|
|
// 5K (16:9) Resolutions
|
|
|
|
case hd2560p60
|
|
|
|
case hd2560p50
|
|
|
|
case hd2560p48
|
|
|
|
case hd2560p30
|
|
|
|
case hd2560p25
|
|
|
|
case hd2560p24
|
|
|
|
|
|
|
|
// 2:1 Aspect Ratio (Univisium) Resolutions
|
|
|
|
case hd2880p60
|
|
|
|
case hd2880p50
|
|
|
|
case hd2880p48
|
|
|
|
case hd2880p30
|
|
|
|
case hd2880p25
|
|
|
|
case hd2880p24
|
|
|
|
|
|
|
|
// 16:10 Resolutions
|
|
|
|
case hd2400p60
|
|
|
|
case hd2400p50
|
|
|
|
case hd2400p48
|
|
|
|
case hd2400p30
|
|
|
|
case hd2400p25
|
|
|
|
case hd2400p24
|
|
|
|
|
|
|
|
// 16:9 Resolutions
|
2022-04-09 21:19:00 +00:00
|
|
|
case hd2160p60
|
2022-04-09 21:27:26 +00:00
|
|
|
case hd2160p50
|
|
|
|
case hd2160p48
|
2022-06-17 10:52:10 +00:00
|
|
|
case hd2160p30
|
2024-08-25 15:23:04 +00:00
|
|
|
case hd2160p25
|
|
|
|
case hd2160p24
|
|
|
|
|
|
|
|
// 16:10 Resolutions
|
|
|
|
case hd1600p60
|
|
|
|
case hd1600p50
|
|
|
|
case hd1600p48
|
|
|
|
case hd1600p30
|
|
|
|
case hd1600p25
|
|
|
|
case hd1600p24
|
|
|
|
|
|
|
|
// 16:9 Resolutions
|
2022-03-27 18:59:22 +00:00
|
|
|
case hd1440p60
|
2022-04-09 21:27:26 +00:00
|
|
|
case hd1440p50
|
|
|
|
case hd1440p48
|
2022-06-17 10:52:10 +00:00
|
|
|
case hd1440p30
|
2024-08-25 15:23:04 +00:00
|
|
|
case hd1440p25
|
|
|
|
case hd1440p24
|
|
|
|
|
|
|
|
// 16:10 Resolutions
|
|
|
|
case hd1280p60
|
|
|
|
case hd1280p50
|
|
|
|
case hd1280p48
|
|
|
|
case hd1280p30
|
|
|
|
case hd1280p25
|
|
|
|
case hd1280p24
|
|
|
|
|
|
|
|
// 16:10 Resolutions
|
|
|
|
case hd1200p60
|
|
|
|
case hd1200p50
|
|
|
|
case hd1200p48
|
|
|
|
case hd1200p30
|
|
|
|
case hd1200p25
|
|
|
|
case hd1200p24
|
|
|
|
|
|
|
|
// 16:9 Resolutions
|
2022-03-27 18:59:22 +00:00
|
|
|
case hd1080p60
|
2022-04-09 21:27:26 +00:00
|
|
|
case hd1080p50
|
|
|
|
case hd1080p48
|
2022-06-17 10:52:10 +00:00
|
|
|
case hd1080p30
|
2024-08-25 15:23:04 +00:00
|
|
|
case hd1080p25
|
|
|
|
case hd1080p24
|
|
|
|
|
|
|
|
// 16:10 Resolutions
|
|
|
|
case hd1050p60
|
|
|
|
case hd1050p50
|
|
|
|
case hd1050p48
|
|
|
|
case hd1050p30
|
|
|
|
case hd1050p25
|
|
|
|
case hd1050p24
|
|
|
|
|
|
|
|
// 16:9 Resolutions
|
|
|
|
case hd960p60
|
|
|
|
case hd960p50
|
|
|
|
case hd960p48
|
|
|
|
case hd960p30
|
|
|
|
case hd960p25
|
|
|
|
case hd960p24
|
|
|
|
|
|
|
|
// 16:10 Resolutions
|
|
|
|
case hd900p60
|
|
|
|
case hd900p50
|
|
|
|
case hd900p48
|
|
|
|
case hd900p30
|
|
|
|
case hd900p25
|
|
|
|
case hd900p24
|
|
|
|
|
|
|
|
// 16:10 Resolutions
|
|
|
|
case hd800p60
|
|
|
|
case hd800p50
|
|
|
|
case hd800p48
|
|
|
|
case hd800p30
|
|
|
|
case hd800p25
|
|
|
|
case hd800p24
|
|
|
|
|
|
|
|
// 16:9 Resolutions
|
2022-03-27 18:59:22 +00:00
|
|
|
case hd720p60
|
2022-04-09 21:27:26 +00:00
|
|
|
case hd720p50
|
|
|
|
case hd720p48
|
2022-06-17 10:52:10 +00:00
|
|
|
case hd720p30
|
2024-08-25 15:23:04 +00:00
|
|
|
case hd720p25
|
|
|
|
case hd720p24
|
|
|
|
|
|
|
|
// Standard Definition (SD) Resolutions
|
|
|
|
case sd854p30
|
|
|
|
case sd854p25
|
|
|
|
case sd768p30
|
|
|
|
case sd768p25
|
|
|
|
case sd640p30
|
|
|
|
case sd640p25
|
2022-06-17 10:52:10 +00:00
|
|
|
case sd480p30
|
2024-08-25 15:23:04 +00:00
|
|
|
case sd480p25
|
|
|
|
|
|
|
|
case sd428p30
|
|
|
|
case sd428p25
|
2024-09-04 10:44:43 +00:00
|
|
|
case sd426p30
|
|
|
|
case sd426p25
|
2022-06-17 10:52:10 +00:00
|
|
|
case sd360p30
|
2024-08-25 15:23:04 +00:00
|
|
|
case sd360p25
|
|
|
|
case sd320p30
|
|
|
|
case sd320p25
|
2024-09-04 10:44:43 +00:00
|
|
|
case sd256p30
|
|
|
|
case sd256p25
|
2022-06-17 10:52:10 +00:00
|
|
|
case sd240p30
|
2024-08-25 15:23:04 +00:00
|
|
|
case sd240p25
|
|
|
|
case sd214p30
|
|
|
|
case sd214p25
|
2022-06-17 10:52:10 +00:00
|
|
|
case sd144p30
|
2024-08-25 15:23:04 +00:00
|
|
|
case sd144p25
|
|
|
|
case sd128p30
|
|
|
|
case sd128p25
|
|
|
|
|
2022-03-27 18:59:22 +00:00
|
|
|
case unknown
|
2021-10-16 22:48:58 +00:00
|
|
|
|
|
|
|
var name: String {
|
2022-06-17 10:52:10 +00:00
|
|
|
"\(height)p\(refreshRate != -1 && refreshRate != 30 ? ", \(refreshRate) fps" : "")"
|
2021-10-16 22:48:58 +00:00
|
|
|
}
|
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]
|
2022-06-17 10:52:10 +00:00
|
|
|
|
|
|
|
if refreshRatePart.isEmpty {
|
|
|
|
return 30
|
|
|
|
}
|
|
|
|
|
2021-10-16 22:48:58 +00:00
|
|
|
return Int(refreshRatePart.components(separatedBy: CharacterSet.decimalDigits.inverted).joined()) ?? -1
|
2021-07-22 12:43:13 +00:00
|
|
|
}
|
|
|
|
|
2024-05-13 05:54:24 +00:00
|
|
|
// These values are an approximation.
|
|
|
|
// https://support.google.com/youtube/answer/1722171?hl=en#zippy=%2Cbitrate
|
|
|
|
|
|
|
|
var bitrate: Int {
|
|
|
|
switch self {
|
2024-08-25 15:23:04 +00:00
|
|
|
// 8K UHD (16:9) Resolutions
|
|
|
|
case .hd4320p60, .hd4320p50, .hd4320p48, .hd4320p30, .hd4320p25, .hd4320p24:
|
|
|
|
return 85_000_000 // 85 Mbit/s
|
|
|
|
|
|
|
|
// 5K (16:9) Resolutions
|
|
|
|
case .hd2880p60, .hd2880p50, .hd2880p48, .hd2880p30, .hd2880p25, .hd2880p24:
|
|
|
|
return 45_000_000 // 45 Mbit/s
|
|
|
|
|
|
|
|
// 2:1 Aspect Ratio (Univisium) Resolutions
|
|
|
|
case .hd2560p60, .hd2560p50, .hd2560p48, .hd2560p30, .hd2560p25, .hd2560p24:
|
|
|
|
return 30_000_000 // 30 Mbit/s
|
|
|
|
|
|
|
|
// 16:10 Resolutions
|
|
|
|
case .hd2400p60, .hd2400p50, .hd2400p48, .hd2400p30, .hd2400p25, .hd2400p24:
|
|
|
|
return 35_000_000 // 35 Mbit/s
|
|
|
|
|
|
|
|
// 4K UHD (16:9) Resolutions
|
|
|
|
case .hd2160p60, .hd2160p50, .hd2160p48, .hd2160p30, .hd2160p25, .hd2160p24:
|
2024-05-16 16:28:32 +00:00
|
|
|
return 56_000_000 // 56 Mbit/s
|
2024-08-25 15:23:04 +00:00
|
|
|
|
|
|
|
// 16:10 Resolutions
|
|
|
|
case .hd1600p60, .hd1600p50, .hd1600p48, .hd1600p30, .hd1600p25, .hd1600p24:
|
|
|
|
return 20_000_000 // 20 Mbit/s
|
|
|
|
|
|
|
|
// 1440p (16:9) Resolutions
|
|
|
|
case .hd1440p60, .hd1440p50, .hd1440p48, .hd1440p30, .hd1440p25, .hd1440p24:
|
2024-05-16 16:28:32 +00:00
|
|
|
return 24_000_000 // 24 Mbit/s
|
2024-08-25 15:23:04 +00:00
|
|
|
|
|
|
|
// 1280p (16:10) Resolutions
|
|
|
|
case .hd1280p60, .hd1280p50, .hd1280p48, .hd1280p30, .hd1280p25, .hd1280p24:
|
|
|
|
return 15_000_000 // 15 Mbit/s
|
|
|
|
|
|
|
|
// 1200p (16:10) Resolutions
|
|
|
|
case .hd1200p60, .hd1200p50, .hd1200p48, .hd1200p30, .hd1200p25, .hd1200p24:
|
|
|
|
return 18_000_000 // 18 Mbit/s
|
|
|
|
|
|
|
|
// 1080p (16:9) Resolutions
|
|
|
|
case .hd1080p60, .hd1080p50, .hd1080p48, .hd1080p30, .hd1080p25, .hd1080p24:
|
2024-05-16 16:28:32 +00:00
|
|
|
return 12_000_000 // 12 Mbit/s
|
2024-08-25 15:23:04 +00:00
|
|
|
|
|
|
|
// 1050p (16:10) Resolutions
|
|
|
|
case .hd1050p60, .hd1050p50, .hd1050p48, .hd1050p30, .hd1050p25, .hd1050p24:
|
|
|
|
return 10_000_000 // 10 Mbit/s
|
|
|
|
|
|
|
|
// 960p Resolutions
|
|
|
|
case .hd960p60, .hd960p50, .hd960p48, .hd960p30, .hd960p25, .hd960p24:
|
|
|
|
return 8_000_000 // 8 Mbit/s
|
|
|
|
|
|
|
|
// 900p (16:10) Resolutions
|
|
|
|
case .hd900p60, .hd900p50, .hd900p48, .hd900p30, .hd900p25, .hd900p24:
|
|
|
|
return 7_000_000 // 7 Mbit/s
|
|
|
|
|
|
|
|
// 800p (16:10) Resolutions
|
|
|
|
case .hd800p60, .hd800p50, .hd800p48, .hd800p30, .hd800p25, .hd800p24:
|
|
|
|
return 6_000_000 // 6 Mbit/s
|
|
|
|
|
|
|
|
// 720p (16:9) Resolutions
|
|
|
|
case .hd720p60, .hd720p50, .hd720p48, .hd720p30, .hd720p25, .hd720p24:
|
2024-05-16 16:28:32 +00:00
|
|
|
return 9_500_000 // 9.5 Mbit/s
|
2024-08-25 15:23:04 +00:00
|
|
|
|
|
|
|
// Standard Definition (SD) Resolutions
|
|
|
|
case .sd854p30, .sd854p25, .sd768p30, .sd768p25, .sd640p30, .sd640p25:
|
2024-05-16 16:28:32 +00:00
|
|
|
return 4_000_000 // 4 Mbit/s
|
2024-08-25 15:23:04 +00:00
|
|
|
|
|
|
|
case .sd480p30, .sd480p25:
|
|
|
|
return 2_500_000 // 2.5 Mbit/s
|
|
|
|
|
2024-09-04 10:44:43 +00:00
|
|
|
case .sd428p30, .sd428p25, .sd426p30, .sd426p25:
|
2024-08-25 15:23:04 +00:00
|
|
|
return 2_000_000 // 2 Mbit/s
|
|
|
|
|
|
|
|
case .sd360p30, .sd360p25:
|
2024-05-16 16:28:32 +00:00
|
|
|
return 1_500_000 // 1.5 Mbit/s
|
2024-08-25 15:23:04 +00:00
|
|
|
|
|
|
|
case .sd320p30, .sd320p25:
|
|
|
|
return 1_200_000 // 1.2 Mbit/s
|
|
|
|
|
2024-09-04 10:44:43 +00:00
|
|
|
case .sd256p30, .sd256p25, .sd240p30, .sd240p25:
|
2024-05-16 16:28:32 +00:00
|
|
|
return 1_000_000 // 1 Mbit/s
|
2024-08-25 15:23:04 +00:00
|
|
|
|
|
|
|
case .sd214p30, .sd214p25:
|
|
|
|
return 800_000 // 0.8 Mbit/s
|
|
|
|
|
|
|
|
case .sd144p30, .sd144p25:
|
2024-05-16 16:28:32 +00:00
|
|
|
return 600_000 // 0.6 Mbit/s
|
2024-08-25 15:23:04 +00:00
|
|
|
|
|
|
|
case .sd128p30, .sd128p25:
|
|
|
|
return 400_000 // 0.4 Mbit/s
|
|
|
|
|
2024-05-13 05:54:24 +00:00
|
|
|
case .unknown:
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-17 12:09:51 +00:00
|
|
|
static func from(resolution: String, fps: Int? = nil) -> Self {
|
2022-06-17 10:52:10 +00:00
|
|
|
allCases.first { $0.rawValue.contains(resolution) && $0.refreshRate == (fps ?? 30) } ?? .unknown
|
2021-07-22 12:43:13 +00:00
|
|
|
}
|
|
|
|
|
2023-06-17 12:09:51 +00:00
|
|
|
static func < (lhs: Self, rhs: Self) -> Bool {
|
2022-08-22 17:03:49 +00:00
|
|
|
lhs.height == rhs.height ? (lhs.refreshRate < rhs.refreshRate) : (lhs.height < rhs.height)
|
2021-07-22 12:43:13 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
enum Kind: String, Comparable {
|
2024-04-26 10:27:25 +00:00
|
|
|
case hls, adaptive, stream
|
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
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-17 12:09:51 +00:00
|
|
|
static func < (lhs: Self, rhs: Self) -> Bool {
|
2021-07-22 12:43:13 +00:00
|
|
|
lhs.sortOrder < rhs.sortOrder
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-04-26 10:27:25 +00:00
|
|
|
enum Format: String {
|
2022-05-21 19:30:01 +00:00
|
|
|
case avc1
|
|
|
|
case mp4
|
2024-04-26 10:27:25 +00:00
|
|
|
case av1
|
|
|
|
case webm
|
|
|
|
case hls
|
|
|
|
case stream
|
2022-05-21 19:30:01 +00:00
|
|
|
case unknown
|
|
|
|
|
2022-08-21 14:28:30 +00:00
|
|
|
var description: String {
|
|
|
|
switch self {
|
|
|
|
case .webm:
|
|
|
|
return "WebM"
|
2024-04-26 10:27:25 +00:00
|
|
|
case .hls:
|
|
|
|
return "adaptive (HLS)"
|
|
|
|
case .stream:
|
|
|
|
return "Stream"
|
2022-08-21 14:28:30 +00:00
|
|
|
default:
|
|
|
|
return rawValue.uppercased()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-21 19:30:01 +00:00
|
|
|
static func from(_ string: String) -> Self {
|
|
|
|
let lowercased = string.lowercased()
|
|
|
|
|
2023-06-17 12:09:51 +00:00
|
|
|
if lowercased.contains("avc1") {
|
2022-05-21 19:30:01 +00:00
|
|
|
return .avc1
|
2023-06-17 12:09:51 +00:00
|
|
|
}
|
2024-04-26 10:27:25 +00:00
|
|
|
if lowercased.contains("mpeg_4") || lowercased.contains("mp4") {
|
|
|
|
return .mp4
|
|
|
|
}
|
2023-06-17 12:09:51 +00:00
|
|
|
if lowercased.contains("av01") {
|
2022-05-21 19:30:01 +00:00
|
|
|
return .av1
|
2023-06-17 12:09:51 +00:00
|
|
|
}
|
2024-04-26 10:27:25 +00:00
|
|
|
if lowercased.contains("webm") {
|
|
|
|
return .webm
|
|
|
|
}
|
|
|
|
if lowercased.contains("stream") {
|
|
|
|
return .stream
|
|
|
|
}
|
|
|
|
if lowercased.contains("hls") {
|
|
|
|
return .hls
|
2022-05-21 19:30:01 +00:00
|
|
|
}
|
2023-06-17 12:09:51 +00:00
|
|
|
return .unknown
|
2022-05-21 19:30:01 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-16 22:48:58 +00:00
|
|
|
let id = UUID()
|
|
|
|
|
|
|
|
var instance: Instance!
|
|
|
|
var audioAsset: AVURLAsset!
|
|
|
|
var videoAsset: AVURLAsset!
|
|
|
|
var hlsURL: URL!
|
2022-11-10 17:11:28 +00:00
|
|
|
var localURL: URL!
|
2021-06-14 18:05:02 +00:00
|
|
|
|
2021-10-16 22:48:58 +00:00
|
|
|
var resolution: Resolution!
|
|
|
|
var kind: Kind!
|
2022-05-21 19:30:01 +00:00
|
|
|
var format: Format!
|
2021-06-14 18:05:02 +00:00
|
|
|
|
2022-08-20 21:05:40 +00:00
|
|
|
var encoding: String?
|
|
|
|
var videoFormat: String?
|
2024-05-13 05:54:24 +00:00
|
|
|
var bitrate: Int?
|
Conditional proxying
I added a new feature. When instances are not proxied, Yattee first checks the URL to make sure it is not a restricted video. Usually, music videos and sports content can only be played back by the same IP address that requested the URL in the first place. That is why some videos do not play when the proxy is disabled.
This approach has multiple advantages. First and foremost, It reduced the load on Invidious/Piped instances, since users can now directly access the videos without going through the instance, which might be severely bandwidth limited. Secondly, users don't need to manually turn on the proxy when they want to watch IP address bound content, since Yattee automatically proxies such content.
Furthermore, adding the proxy option allows mitigating some severe playback issues with invidious instances. Invidious by default returns proxied URLs for videos, and due to some bug in the Invidious proxy, scrubbing or continuing playback at a random timestamp can lead to severe wait times for the users.
This should fix numerous playback issues: #666, #626, #590, #585, #498, #457, #400
2024-05-09 18:07:55 +00:00
|
|
|
var requestRange: 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,
|
2022-11-10 17:11:28 +00:00
|
|
|
localURL: URL? = nil,
|
2021-10-16 22:48:58 +00:00
|
|
|
resolution: Resolution? = nil,
|
|
|
|
kind: Kind = .hls,
|
2022-02-16 20:23:11 +00:00
|
|
|
encoding: String? = nil,
|
2024-05-13 05:54:24 +00:00
|
|
|
videoFormat: String? = nil,
|
Conditional proxying
I added a new feature. When instances are not proxied, Yattee first checks the URL to make sure it is not a restricted video. Usually, music videos and sports content can only be played back by the same IP address that requested the URL in the first place. That is why some videos do not play when the proxy is disabled.
This approach has multiple advantages. First and foremost, It reduced the load on Invidious/Piped instances, since users can now directly access the videos without going through the instance, which might be severely bandwidth limited. Secondly, users don't need to manually turn on the proxy when they want to watch IP address bound content, since Yattee automatically proxies such content.
Furthermore, adding the proxy option allows mitigating some severe playback issues with invidious instances. Invidious by default returns proxied URLs for videos, and due to some bug in the Invidious proxy, scrubbing or continuing playback at a random timestamp can lead to severe wait times for the users.
This should fix numerous playback issues: #666, #626, #590, #585, #498, #457, #400
2024-05-09 18:07:55 +00:00
|
|
|
bitrate: Int? = nil,
|
|
|
|
requestRange: 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
|
2022-11-10 17:11:28 +00:00
|
|
|
self.localURL = localURL
|
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-05-21 19:30:01 +00:00
|
|
|
format = .from(videoFormat ?? "")
|
2024-05-13 05:54:24 +00:00
|
|
|
self.bitrate = bitrate
|
Conditional proxying
I added a new feature. When instances are not proxied, Yattee first checks the URL to make sure it is not a restricted video. Usually, music videos and sports content can only be played back by the same IP address that requested the URL in the first place. That is why some videos do not play when the proxy is disabled.
This approach has multiple advantages. First and foremost, It reduced the load on Invidious/Piped instances, since users can now directly access the videos without going through the instance, which might be severely bandwidth limited. Secondly, users don't need to manually turn on the proxy when they want to watch IP address bound content, since Yattee automatically proxies such content.
Furthermore, adding the proxy option allows mitigating some severe playback issues with invidious instances. Invidious by default returns proxied URLs for videos, and due to some bug in the Invidious proxy, scrubbing or continuing playback at a random timestamp can lead to severe wait times for the users.
This should fix numerous playback issues: #666, #626, #590, #585, #498, #457, #400
2024-05-09 18:07:55 +00:00
|
|
|
self.requestRange = requestRange
|
2021-06-14 18:05:02 +00:00
|
|
|
}
|
|
|
|
|
2022-11-10 17:11:28 +00:00
|
|
|
var isLocal: Bool {
|
|
|
|
localURL != nil
|
|
|
|
}
|
|
|
|
|
2023-05-20 20:49:10 +00:00
|
|
|
var isHLS: Bool {
|
|
|
|
hlsURL != nil
|
|
|
|
}
|
|
|
|
|
2021-10-16 22:48:58 +00:00
|
|
|
var quality: String {
|
2022-11-10 17:11:28 +00:00
|
|
|
guard localURL.isNil else { return "Opened File" }
|
2024-05-09 19:15:14 +00:00
|
|
|
|
|
|
|
if kind == .hls {
|
|
|
|
return "adaptive (HLS)"
|
|
|
|
}
|
|
|
|
|
2024-04-26 10:27:25 +00:00
|
|
|
return resolution.name
|
2022-02-16 20:23:11 +00:00
|
|
|
}
|
|
|
|
|
2022-06-14 22:41:49 +00:00
|
|
|
var shortQuality: String {
|
2022-11-10 17:11:28 +00:00
|
|
|
guard localURL.isNil else { return "File" }
|
|
|
|
|
2022-06-18 12:39:49 +00:00
|
|
|
if kind == .hls {
|
2024-05-09 19:15:14 +00:00
|
|
|
return "adaptive (HLS)"
|
2024-04-26 10:27:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if kind == .stream {
|
|
|
|
return resolution.name
|
2022-06-14 22:41:49 +00:00
|
|
|
}
|
2024-04-26 10:27:25 +00:00
|
|
|
return resolutionAndFormat
|
2022-06-14 22:41:49 +00:00
|
|
|
}
|
|
|
|
|
2021-06-14 18:05:02 +00:00
|
|
|
var description: String {
|
2022-11-10 17:11:28 +00:00
|
|
|
guard localURL.isNil else { return resolutionAndFormat }
|
2022-08-21 14:28:30 +00:00
|
|
|
let instanceString = instance.isNil ? "" : " - (\(instance!.description))"
|
2024-05-09 19:15:14 +00:00
|
|
|
return format != .hls ? "\(resolutionAndFormat)\(instanceString)" : "adaptive (HLS)\(instanceString)"
|
2022-08-21 14:28:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var resolutionAndFormat: String {
|
|
|
|
let formatString = format == .unknown ? "" : " (\(format.description))"
|
|
|
|
return "\(quality)\(formatString)"
|
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? {
|
2022-11-10 17:11:28 +00:00
|
|
|
guard localURL.isNil else {
|
|
|
|
return URLBookmarkModel.shared.loadBookmark(localURL) ?? localURL
|
|
|
|
}
|
|
|
|
|
2021-10-16 22:48:58 +00:00
|
|
|
if kind == .hls {
|
|
|
|
return hlsURL
|
2023-06-17 12:09:51 +00:00
|
|
|
}
|
|
|
|
if videoAssetContainsAudio {
|
2021-10-16 22:48:58 +00:00
|
|
|
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) {
|
2023-05-20 20:49:10 +00:00
|
|
|
if let url = videoAsset?.url {
|
|
|
|
hasher.combine(url)
|
|
|
|
}
|
|
|
|
if let url = audioAsset?.url {
|
|
|
|
hasher.combine(url)
|
|
|
|
}
|
|
|
|
if let url = hlsURL {
|
|
|
|
hasher.combine(url)
|
|
|
|
}
|
2021-10-16 22:48:58 +00:00
|
|
|
}
|
2021-06-14 18:05:02 +00:00
|
|
|
}
|