feat: segment playback slider with resolved YouTube chapters

This commit is contained in:
2026-10-25 02:22:37 +01:00
parent 2391dadc75
commit 119fe2e737
5 changed files with 272 additions and 20 deletions
+137 -6
View File
@@ -113,12 +113,112 @@ struct ResolvedPlaybackCheck {
try await providerLifecycle(source: source)
try await dubOnlySelection(source: source)
try await queueMetadataLifecycle(source: source)
try await chapterLifecycle(source: source, file: file)
try await streamQueueLifecycle(source: source)
try await enqueueLifecycle(source: source)
try await sidebarQueueLifecycle(source: source)
}
}
static var fixtureChapters: [YouTubeChapter] {
[.init(start: 0, end: 1.25, title: "Opening"),
.init(start: 1.25, end: 2.5, title: "Ending")]
}
static func requireYouTubeChapters(_ model: PlayerModel, _ expected: [Double], _ context: String) {
require(!model.isBusy && model.state.has_video && model.state.has_audio && model.state.duration > 2.5,
"\(context): boundaries belong to decoded native media with measured duration")
require(model.sliderBoundaries == expected, "\(context): exact fractional chapter boundaries")
require(model.visibleIntervals.isEmpty && model.segmentToSkip == nil,
"\(context): YouTube chapters never become Miruro bands or skip offers")
}
static func chapterLifecycle(source: ResolvedSource, file: URL) async throws {
let directory = FileManager.default.temporaryDirectory
.appendingPathComponent("terebi.chapter-check." + UUID().uuidString)
try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true)
defer { try? FileManager.default.removeItem(at: directory) }
let report = directory.appendingPathComponent("single-launch.jsonl")
let suite = "terebi.chapter-check." + UUID().uuidString
let defaults = UserDefaults(suiteName: suite)!
defer { defaults.removePersistentDomain(forName: suite) }
let chaptered = URL(string: "https://www.youtube.com/watch?v=AAAAAAAAAAA")!
let chapterless = URL(string: "https://www.youtube.com/watch?v=BBBBBBBBBBB")!
var requests: [URL] = []
// The single-video Tubular launch uses this progress-file initialization plus openStream.
let model = PlayerModel(defaults: defaults, progressFile: report.path, youtubeResolver: { url, height in
requests.append(url)
return YouTubeVideo(title: url == chaptered ? "Chaptered launch" : "Chapterless launch",
video: .init(url: source.url, headers: source.headers), audio: nil,
duration: 0, isLive: false, webpageURL: url, height: height ?? 1080, availableHeights: [1080, 720],
chapters: url == chaptered ? fixtureChapters + [.init(start: 3, end: 500, title: nil)] : [])
}, youtubeTitleResolver: { _ in nil },
miruroMetadataResolver: { _, _ in MiruroMetadata(title: nil, intervals: []) })
defer { model.shutdown() }
require(model.progressInitializationError == nil, "single Tubular launch initializes progress reporting")
for (url, expected) in [(chaptered, [1.25, 2.5]), (chapterless, [])] {
model.openStream(url.absoluteString)
require(model.sliderBoundaries.isEmpty, "direct open hides prior chapters while resolving")
try await until("direct YouTube stream decodes", model: model) {
!model.isBusy && model.state.status == PLAYER_PLAYING && model.state.position > 0.2
}
requireYouTubeChapters(model, expected, "direct openStream")
require(model.state.duration < 500, "native duration rejects chapter edges beyond measured media")
require(model.setPlayback(playing: false), "pause before saving chapter playback")
try await until("chapter playback paused", model: model) { model.state.status == PLAYER_PAUSED }
require(model.seekRelative(0.8 - model.state.position), "save chapter playback bookmark")
try await until("chapter bookmark acknowledged", model: model) {
!model.state.seek_pending && abs(model.state.position - 0.8) < 0.1
}
model.closeFile()
require(model.sliderBoundaries.isEmpty, "close immediately removes chapter boundaries")
try await until("chapter playback closed", model: model) { model.state.status == PLAYER_EMPTY }
require(model.savedPosition(for: url).map { abs($0 - 0.8) < 0.1 } == true,
"acknowledged bookmark persists for canonical YouTube URL")
require(model.recentMedia.first?.url == url, "recent entry retains original YouTube URL")
model.open(model.recentMedia.first!.url)
try await until("recent YouTube reopen restores native bookmark", model: model) {
!model.isBusy && model.state.status == PLAYER_PLAYING && model.state.position >= 0.7
}
requireYouTubeChapters(model, expected, "saved/recent open(url)")
require(model.setPlayback(playing: false), "pause before chapter quality replacement")
try await until("chapter quality pause acknowledged", model: model) { model.state.status == PLAYER_PAUSED }
let position = model.state.position
model.setYouTubeQuality(720)
require(model.sliderBoundaries.isEmpty, "quality extraction hides prior chapter boundaries")
try await until("chapter quality replacement preserves pause and position", model: model) {
!model.isBusy && model.state.status == PLAYER_PAUSED && model.youtubeHeight == 720 &&
abs(model.state.position - position) < 0.2
}
requireYouTubeChapters(model, expected, "chaptered/chapterless quality replacement")
}
model.open(chaptered)
try await until("chaptered media replaces chapterless media", model: model) {
!model.isBusy && model.state.status == PLAYER_PLAYING && model.fileName == "Chaptered launch"
}
requireYouTubeChapters(model, [1.25, 2.5], "chaptered replacement")
model.openResolvedSource(file)
require(model.sliderBoundaries.isEmpty, "non-YouTube replacement hides prior chapters while opening")
try await until("resolved replacement decodes without YouTube chapters", model: model) {
!model.isBusy && model.state.status == PLAYER_PLAYING && model.state.has_video && model.state.has_audio
}
require(model.sliderBoundaries.isEmpty, "resolved media does not inherit YouTube chapters")
model.closeFile()
try await until("resolved replacement closes", model: model) { model.state.status == PLAYER_EMPTY }
require(model.sliderBoundaries.isEmpty, "empty player has no chapter boundaries")
require(requests == [chaptered, chaptered, chaptered, chapterless, chapterless, chapterless, chaptered],
"direct, recent, saved and quality opens each resolve fresh chapter metadata")
model.shutdown()
let rows = try String(contentsOf: report, encoding: .utf8).split(separator: "\n").map {
try JSONSerialization.jsonObject(with: Data($0.utf8)) as! [String: Any]
}
require(rows.contains {
$0["version"] as? Int == 1 && $0["media_url"] as? String == chaptered.absoluteString &&
$0["event"] as? String == "progress"
}, "single Tubular launch reports real playback under its canonical YouTube identity")
print("PASS: chaptered/chapterless direct and single Tubular launch, saved/recent reopen, quality, measured duration, replacement and close")
}
static func transportProtocol(source: ResolvedSource) async throws {
let original = URLComponents(string: source.url)!
let base = "\(original.scheme!)://\(original.host!):\(original.port!)"
@@ -298,7 +398,8 @@ struct ResolvedPlaybackCheck {
var titleRequests: [URL] = []
func video(_ url: URL, title: String, height: Int = 1080) -> YouTubeVideo {
YouTubeVideo(title: title, video: .init(url: source.url, headers: source.headers), audio: nil,
duration: 0, isLive: false, webpageURL: url, height: height, availableHeights: [1080, 720])
duration: 0, isLive: false, webpageURL: url, height: height, availableHeights: [1080, 720],
chapters: height == 720 ? [.init(start: 0.75, end: 1.75, title: nil)] : fixtureChapters)
}
let model = PlayerModel(defaults: defaults, youtubeResolver: { url, height in
if holdFirst || height != nil {
@@ -352,6 +453,7 @@ struct ResolvedPlaybackCheck {
try await until("foreground title takes precedence and native playback starts", model: model) {
model.state.status == PLAYER_PLAYING && model.queue.currentItem?.displayName == "Playing title"
}
requireYouTubeChapters(model, [1.25, 2.5], "foreground queued metadata")
require(model.setPlayback(playing: false), "pause for metadata preservation")
try await until("metadata smoke pause acknowledged", model: model) { model.state.status == PLAYER_PAUSED }
let position = model.state.position
@@ -383,10 +485,14 @@ struct ResolvedPlaybackCheck {
for _ in 0..<10 { await Task.yield() }
require(model.queue.items[1].displayName == urls[1].absoluteString,
"removed/replaced item cannot rename a new row for the same URL")
try await until("replacement native media ready for quality change", model: model) {
!model.isBusy && model.state.status == PLAYER_PLAYING && model.fileName == "Playing title"
}
require(model.setPlayback(playing: false), "pause before quality extraction")
try await until("quality smoke pause acknowledged", model: model) { model.state.status == PLAYER_PAUSED }
model.setYouTubeQuality(720)
try await until("quality extraction is held", model: model) { foreground != nil }
require(model.sliderBoundaries.isEmpty, "held quality replacement hides previous chapter boundaries")
background!.resume(returning: "Replacement title")
background = nil
try await until("title arrives while quality extraction is held", model: model) {
@@ -399,6 +505,7 @@ struct ResolvedPlaybackCheck {
try await until("quality extraction resumes paused native playback", model: model) {
!model.isBusy && model.state.status == PLAYER_PAUSED && model.fileName == "Quality title"
}
requireYouTubeChapters(model, [0.75, 1.75], "quality replacement accepts fresh chapters and preserves pause")
print("PASS: early current/queued titles during held playback and quality extraction; serial utility lookup, generic/Miruro titles, reorder, failure and stale replacement/foreground precedence")
}
@@ -424,7 +531,8 @@ struct ResolvedPlaybackCheck {
var stale: CheckedContinuation<YouTubeVideo, Never>?
let video = YouTubeVideo(title: "Queue YouTube fixture",
video: .init(url: source.url, headers: source.headers), audio: nil,
duration: 0, isLive: false, webpageURL: youtubeURL, height: nil, availableHeights: [])
duration: 0, isLive: false, webpageURL: youtubeURL, height: nil, availableHeights: [],
chapters: fixtureChapters)
let model = PlayerModel(defaults: defaults, streamQueue: queue, youtubeResolver: { _, _ in
youtubeRequests += 1
if youtubeRequests == 1 {
@@ -447,17 +555,20 @@ struct ResolvedPlaybackCheck {
try await until("manual stream selection decodes native media", model: model) {
!model.isBusy && model.state.status == PLAYER_PLAYING && model.state.position > 0.2
}
require(model.sliderBoundaries.isEmpty, "selected Miruro media has no stale YouTube chapters")
stale!.resume(returning: video)
stale = nil
// Yield to the canceled resolver; it must not open or redirect its report.
for _ in 0..<10 { await Task.yield() }
require(model.queue.currentID == secondID && !model.miruroVideoProviders.isEmpty &&
!FileManager.default.fileExists(atPath: firstReport.path), "stale extraction cannot replace selected media or report")
require(model.sliderBoundaries.isEmpty, "canceled chaptered extraction cannot add boundaries to Miruro media")
model.advanceQueue(-1)
try await until("previous resolves YouTube afresh", model: model) {
!model.isBusy && model.state.status == PLAYER_PLAYING && model.fileName == video.title
}
require(youtubeRequests == 2 && model.queue.currentID == firstID, "manual previous preserves queue and resolves current item")
requireYouTubeChapters(model, [1.25, 2.5], "batch queue previous")
require(model.setPlayback(playing: false), "pause first queued stream")
try await until("queue pause acknowledged", model: model) { model.state.status == PLAYER_PAUSED }
require(model.seekRelative(max(0, model.duration - 0.3) - model.state.position), "seek first stream near natural end")
@@ -469,6 +580,7 @@ struct ResolvedPlaybackCheck {
model.queue.currentID == secondID && !model.isBusy && model.state.status == PLAYER_PLAYING &&
!model.miruroVideoProviders.isEmpty && episodeRequests == 2
}
require(model.sliderBoundaries.isEmpty, "natural queue advance clears YouTube chapters for Miruro")
require(model.setPlayback(playing: false), "pause second stream for restoration")
try await until("second paused", model: model) { model.state.status == PLAYER_PAUSED }
require(model.seekRelative(0.8 - model.state.position), "save an acknowledged second-stream bookmark")
@@ -479,6 +591,7 @@ struct ResolvedPlaybackCheck {
try await until("revisit first report session", model: model) {
!model.isBusy && model.fileName == video.title && model.state.status == PLAYER_PLAYING
}
requireYouTubeChapters(model, [1.25, 2.5], "batch queue revisit")
model.advanceQueue(1)
try await until("second revisit resumes acknowledged bookmark", model: model) {
!model.isBusy && model.queue.currentID == secondID && model.state.status == PLAYER_PLAYING &&
@@ -530,7 +643,8 @@ struct ResolvedPlaybackCheck {
requests += 1
return YouTubeVideo(title: url.absoluteString,
video: .init(url: source.url, headers: source.headers), audio: nil,
duration: 0, isLive: false, webpageURL: url, height: nil, availableHeights: [])
duration: 0, isLive: false, webpageURL: url, height: nil, availableHeights: [],
chapters: url == first ? fixtureChapters : [])
}, youtubeTitleResolver: { _ in nil }, miruroResolver: { _, _ in episodeRequests += 1; return try providerSources(source) },
miruroMetadataResolver: { _, _ in MiruroMetadata(title: nil, intervals: []) })
defer { model.shutdown() }
@@ -545,6 +659,7 @@ struct ResolvedPlaybackCheck {
try await until("standalone stream begins before enqueue", model: model) {
model.state.status == PLAYER_PLAYING && !model.isBusy && model.state.position > 0.2
}
requireYouTubeChapters(model, [1.25, 2.5], "single stream before enqueue")
require(model.setPlayback(playing: false), "pause original single-video playback")
try await until("standalone pause acknowledged", model: model) { model.state.status == PLAYER_PAUSED }
let selected = model.queue.currentID
@@ -556,6 +671,7 @@ struct ResolvedPlaybackCheck {
model.state.position == position && model.state.revision == revision &&
model.fileName == first.absoluteString && requests == 1 && episodeRequests == 0,
"paused enqueue preserves original media, selection and decoder timeline; new items resolve lazily in supplied order")
requireYouTubeChapters(model, [1.25, 2.5], "paused enqueue preserves current chapters")
require(model.setPlayback(playing: true), "resume original playback")
try await until("original playing again", model: model) { model.state.status == PLAYER_PLAYING }
let playingRevision = model.state.revision
@@ -564,6 +680,7 @@ struct ResolvedPlaybackCheck {
model.queue.currentID == selected && model.state.revision == playingRevision &&
model.state.status == PLAYER_PLAYING,
"repeated native delivery appends while playing without reopening current media")
requireYouTubeChapters(model, [1.25, 2.5], "playing enqueue preserves current chapters")
try deliver([(third, directory.appendingPathComponent("conflict.jsonl"))])
require(model.commandError != nil && model.queue.items.count == 4 && model.queue.currentID == selected,
"conflicting identity rejects complete enqueue without changing playback")
@@ -588,10 +705,12 @@ struct ResolvedPlaybackCheck {
!model.isBusy && model.state.status == PLAYER_PLAYING && !model.miruroVideoProviders.isEmpty &&
model.queue.currentURL == second
}
require(model.sliderBoundaries.isEmpty, "enqueued Miruro advance clears current chapters")
model.advanceQueue(1)
try await until("enqueued YouTube item decodes", model: model) {
!model.isBusy && model.state.status == PLAYER_PLAYING && model.fileName == third.absoluteString
}
requireYouTubeChapters(model, [], "chapterless enqueued YouTube advance")
model.advanceQueue(-1)
try await until("enqueued Miruro report revisits", model: model) {
!model.isBusy && model.state.status == PLAYER_PLAYING && model.queue.currentURL == second
@@ -600,6 +719,7 @@ struct ResolvedPlaybackCheck {
try await until("original single video remains navigable", model: model) {
!model.isBusy && model.state.status == PLAYER_PLAYING && model.fileName == first.absoluteString
}
requireYouTubeChapters(model, [1.25, 2.5], "enqueued revisit restores chaptered YouTube")
var retiredReports: [(URL, Data, URL, URL)] = []
for (url, oldReport) in [(first, originalReport), (third, thirdReport)] {
let oldID = model.queue.items.first(where: { $0.url == url })!.id
@@ -649,7 +769,7 @@ struct ResolvedPlaybackCheck {
require(!FileManager.default.fileExists(atPath: fourthReport.path), "unselected enqueued item never owns a writer")
let empty = PlayerModel(defaults: defaults, youtubeResolver: { url, _ in
YouTubeVideo(title: "Cold enqueue", video: .init(url: source.url, headers: source.headers), audio: nil,
duration: 0, isLive: false, webpageURL: url, height: nil, availableHeights: [])
duration: 0, isLive: false, webpageURL: url, height: nil, availableHeights: [], chapters: [])
}, youtubeTitleResolver: { _ in nil })
defer { empty.shutdown() }
let coldReport = directory.appendingPathComponent("cold.jsonl")
@@ -658,6 +778,7 @@ struct ResolvedPlaybackCheck {
try await until("empty player starts first enqueued item", model: empty) {
!empty.isBusy && empty.state.status == PLAYER_PLAYING && empty.queue.currentURL == fourth
}
requireYouTubeChapters(empty, [], "cold chapterless enqueue")
print("PASS: repeated enqueue while paused/playing, standalone navigation, atomic conflicts, independent reports and empty-player start")
}
@@ -677,7 +798,8 @@ struct ResolvedPlaybackCheck {
if url == failedURL { throw CocoaError(.fileReadCorruptFile) }
let video = YouTubeVideo(title: "Sidebar \(url == first ? "first" : "second")",
video: .init(url: source.url, headers: source.headers), audio: nil,
duration: 0, isLive: false, webpageURL: url, height: nil, availableHeights: [])
duration: 0, isLive: false, webpageURL: url, height: nil, availableHeights: [],
chapters: url == first ? fixtureChapters : [])
if url == cancelledURL { return await withCheckedContinuation { pending = $0 } }
return video
}, youtubeTitleResolver: { _ in nil }, miruroResolver: { _, _ in try providerSources(source) },
@@ -688,6 +810,7 @@ struct ResolvedPlaybackCheck {
!model.isBusy && model.state.status == PLAYER_PLAYING && model.state.position > 0.2
}
require(requests == 1 && model.queue.currentURL == first, "empty addition uses fetched source without re-extracting")
requireYouTubeChapters(model, [1.25, 2.5], "sidebar first native playback")
require(model.setPlayback(playing: false), "pause before sidebar addition")
try await until("sidebar pause acknowledged", model: model) { model.state.status == PLAYER_PAUSED }
let selected = model.queue.currentID
@@ -722,18 +845,21 @@ struct ResolvedPlaybackCheck {
while pending == nil { await Task.yield() }
cancelled.cancel()
pending!.resume(returning: YouTubeVideo(title: "Cancelled", video: .init(url: source.url, headers: source.headers),
audio: nil, duration: 0, isLive: false, webpageURL: cancelledURL, height: nil, availableHeights: []))
audio: nil, duration: 0, isLive: false, webpageURL: cancelledURL, height: nil, availableHeights: [],
chapters: [.init(start: 0.75, end: 1.75, title: "Canceled metadata")]))
do {
try await cancelled.value
fatalError("FAIL: cancelled sidebar addition accepted")
} catch is CancellationError {}
require(model.queue.items.count == 4 && model.queue.currentID == selected,
"dismissed in-flight addition cannot append a late result")
requireYouTubeChapters(model, [1.25, 2.5], "canceled sidebar metadata leaves accepted chapters unchanged")
model.selectQueueItem(model.queue.items[1].id)
try await until("sidebar-added item plays through native queue selection", model: model) {
!model.isBusy && model.state.status == PLAYER_PLAYING && model.queue.currentURL == second &&
model.fileName == "Sidebar second"
}
requireYouTubeChapters(model, [], "sidebar chapterless native selection")
print("PASS: sidebar URL fetch/add, empty-player decode, paused/playing preservation, validation, failure, cancellation and selection")
}
@@ -742,12 +868,15 @@ struct ResolvedPlaybackCheck {
let ending = MiruroInterval(kind: .outro, start: 2, end: model.duration, episode: 1)
let metadata = MiruroMetadata(title: nil, intervals: [opening, ending])
require(model.segmentToSkip == nil && !model.skipSegment(), "absent metadata cannot skip")
require(model.sliderBoundaries.isEmpty, "absent Miruro metadata leaves slider unsegmented")
require(model.seekRelative(0.75 - model.state.position), "enter opening")
try await until("opening position acknowledged", model: model) {
!model.state.seek_pending && abs(model.state.position - 0.75) < 0.1
}
model.supplyMetadataForCheck(metadata, episode: 1)
require(model.segmentToSkip == opening, "late metadata offers opening at acknowledged paused position")
require(model.visibleIntervals == [opening, ending] && model.sliderBoundaries == [0.5, 1.5, 2],
"Miruro retains intro/outro bands and interior slider boundaries without the media endpoint")
require(model.skipOfferRemainingFraction == 1, "late entry starts with a full countdown background")
clock.now = 105
require(model.skipOfferRemainingFraction == 0.5, "paused countdown drains by elapsed offer time, not media position")
@@ -777,6 +906,7 @@ struct ResolvedPlaybackCheck {
require(model.segmentToSkip == nil, "ignored opening stays dismissed when revisited")
model.closeFile()
require(model.segmentToSkip == nil, "close removes offer")
require(model.sliderBoundaries.isEmpty, "closing Miruro media removes slider boundaries")
model.openResolvedSource(file)
try await until("reopened media playing", model: model) {
model.state.status == PLAYER_PLAYING && !model.state.seek_pending && !model.isBusy
@@ -787,6 +917,7 @@ struct ResolvedPlaybackCheck {
try await until("reopened opening acknowledged", model: model) {
!model.state.seek_pending && abs(model.state.position - 0.75) < 0.1
}
require(model.sliderBoundaries.isEmpty, "reopened Miruro media waits for fresh metadata")
model.supplyMetadataForCheck(metadata, episode: 1)
try await until("new open resets dismissal after seek settles", model: model) {
model.segmentToSkip == opening
+65
View File
@@ -167,6 +167,70 @@ struct YouTubeResolverChecks {
try require(try decode(emptyHeaders).video.headers.isEmpty, "absent headers serialize to an empty block")
}
static func chapters() throws {
var object = try fixture(combinedJSON)
try require(try decode(object).chapters.isEmpty, "absent chapters remain playable")
for payload in [NSNull(), [], true, 17, "chapters", [:],
[NSNull(), 17, "row", [:], ["start_time": 0],
["start_time": "0", "end_time": 2], ["start_time": 0, "end_time": false]]] as [Any] {
object["chapters"] = payload
let video = try decode(object)
try require(video.chapters.isEmpty && video.video.url == "https://media.example/combined",
"null, empty and malformed optional chapters never prevent playback")
}
object["chapters"] = [
NSNull(),
["start_time": 0, "end_time": 2.25, "title": "Opening"],
["start_time": 2.25, "end_time": 8.75],
["start_time": 8.75, "end_time": 12, "title": ["malformed"]],
["start_time": 2.25, "end_time": 8.75, "title": NSNull()]
] as [Any]
let decoded = try decode(object)
try require(decoded.chapters.count == 4 && decoded.chapters[0].title == "Opening"
&& decoded.chapters[0].end == 2.25 && decoded.chapters[1].start == 2.25,
"fractional chapters and titles survive malformed sibling rows")
try require(decoded.chapters.dropFirst().allSatisfy { $0.title == nil },
"missing, null and malformed optional titles do not discard chapters")
try require(decoded.chapterBoundaries(duration: 12) == [2.25, 8.75],
"chapter edges are sorted, deduplicated and strictly interior")
object["duration"] = 0
object["chapters"] = [["start_time": 0, "end_time": 2.25],
["start_time": 2.25, "end_time": 20.5]]
let unknownDuration = try decode(object)
try require(unknownDuration.chapterBoundaries(duration: 20.5) == [2.25],
"actual playback duration, not extractor duration, validates chapters")
for duration in [0, -1, Double.nan, .infinity, -.infinity] {
try require(unknownDuration.chapterBoundaries(duration: duration).isEmpty,
"unknown and invalid playback durations never produce boundaries")
}
let video = YouTubeVideo(title: "Chapters",
video: .init(url: "https://media.example/combined", headers: ""), audio: nil,
duration: 0, isLive: false, webpageURL: page, height: nil, availableHeights: [],
chapters: [
.init(start: 8.75, end: 12, title: nil),
.init(start: 0, end: 2.25, title: nil),
.init(start: 2.25, end: 8.75, title: nil),
.init(start: 2.25, end: 8.75, title: nil),
.init(start: -1, end: 4, title: nil),
.init(start: 3, end: 13, title: nil),
.init(start: 6, end: 6, title: nil),
.init(start: 7, end: 5, title: nil),
.init(start: .nan, end: 4, title: nil),
.init(start: .infinity, end: 4, title: nil),
.init(start: -.infinity, end: 4, title: nil),
.init(start: 3, end: .nan, title: nil),
.init(start: 3, end: .infinity, title: nil),
.init(start: 3, end: -.infinity, title: nil)
])
try require(video.chapterBoundaries(duration: 12) == [2.25, 8.75],
"invalid, zero-length, reversed and over-range rows contribute neither edge")
object["chapters"] = "malformed optional data"
object["url"] = 17
try reject("chapter tolerance must not weaken required stream validation") { _ = try decode(object) }
}
static func qualities() throws {
func video(_ height: Int, audio: Bool = false, range: String = "SDR", transport: String = "https") -> [String: Any] {
["height": height, "vcodec": "h264", "acodec": audio ? "aac" : "none",
@@ -609,6 +673,7 @@ struct YouTubeResolverChecks {
try FileManager.default.createSymbolicLink(at: runtime, withDestinationURL: URL(fileURLWithPath: "/usr/bin/true"))
try videoURLs()
try metadata()
try chapters()
try malformedMetadata()
try qualities()
try bundledHelpers(root: root)