import AppKit @main @MainActor struct ResolvedPlaybackCheck { final class Clock { var now = 100.0 } static func require(_ condition: @autoclosure () -> Bool, _ message: String) { if !condition() { fatalError("FAIL: " + message) } } static func until(_ message: String, model: PlayerModel, _ predicate: () -> Bool) async throws { let deadline = ContinuousClock.now.advanced(by: .seconds(45)) while !predicate(), ContinuousClock.now < deadline { if model.state.status == PLAYER_ERROR || model.commandError != nil { fatalError("FAIL: \(message): " + (model.errorText ?? "Playback failed")) } try await Task.sleep(for: .milliseconds(10)) } require(predicate(), message) } static func main() async { do { try await run() } catch { fatalError("FAIL: " + error.localizedDescription) } } static func run() async throws { PlayerModel.metadataLifecycleCheck() if CommandLine.arguments[1] == "--metadata-lifecycle" { print("PASS: metadata lifecycle replacement, A-B-A stale results, and actual-duration changes") return } _ = NSApplication.shared NSApp.setActivationPolicy(.prohibited) sponsorPreferenceCheck() if CommandLine.arguments[1] == "--sponsor-preference" { return } let input = CommandLine.arguments[1] let file = URL(fileURLWithPath: input) let preference = CommandLine.arguments.first(where: { $0.hasPrefix("--provider=") }).map { String($0.dropFirst(11)) } ?? "" let source: ResolvedSource if input.hasPrefix("https://") { guard case .miruro(let mediaID, let episode) = try StreamURL.parse(input) else { fatalError("FAIL: URL smoke requires a Miruro episode") } source = try await MiruroResolver.resolve(mediaID: mediaID, episode: episode).select(videoProvider: preference).source } else { source = try ResolvedSource.load(from: file) } if CommandLine.arguments.contains("--zoko-intervals") { try await transportProtocol(source: source) try await zokoIntervalLifecycle(source: source) return } if CommandLine.arguments.contains("--enqueue-only") { try await enqueueLifecycle(source: source) return } if CommandLine.arguments.contains("--sponsors-only") { try await chapterLifecycle(source: source, file: file) try await sponsorLifecycle(source: source) try await streamQueueLifecycle(source: source) try await enqueueLifecycle(source: source) return } let suite = "terebi.resolved-check." + UUID().uuidString let defaults = UserDefaults(suiteName: suite)! defer { defaults.removePersistentDomain(forName: suite) } let clock = Clock() let report = CommandLine.arguments.count > 2 && !CommandLine.arguments[2].hasPrefix("--") ? CommandLine.arguments[2] : nil let model = PlayerModel(defaults: defaults, configuration: .init(miruroVideoProvider: preference), progressFile: report, miruroMetadataResolver: { id, episode in if input.hasPrefix("https://") { return try await MiruroMetadataResolver.resolve(mediaID: id, episode: episode) } return MiruroMetadata(title: nil, intervals: []) }, skipClock: { clock.now }) defer { model.shutdown() } if input.hasPrefix("https://") { model.openStream(input) } else { model.openResolvedSource(file) } try await until("video/audio decode and advancing playback", model: model) { model.state.status == PLAYER_PLAYING && model.state.has_video && model.state.has_audio && model.state.position > 0.2 } print("PASS: resolved HLS decodes video/audio and advances") fflush(stdout) if input.hasPrefix("https://") { try await until("descriptive title and measured-duration metadata bands", model: model) { model.fileName != source.title && !model.visibleIntervals.isEmpty } let intervals = model.visibleIntervals require(intervals.allSatisfy { $0.start.isFinite && $0.end.isFinite && $0.start >= 0 && $0.end > $0.start && $0.end <= model.state.duration }, "bands use finite in-bounds actual media duration") require(intervals.filter { $0.kind == .intro }.count <= 1 && intervals.filter { $0.kind == .outro }.count <= 1, "at most one band of each kind") print("PASS: Miruro title '\(model.fileName!)' and \(intervals.count) measured-duration bands") } require(model.setPlayback(playing: false), "pause accepted") try await until("pause acknowledged", model: model) { model.state.status == PLAYER_PAUSED } require(model.state.can_seek && model.state.duration > 0, "seekable measured duration") guard let index = source.subtitles.firstIndex(where: \.isDefault) else { fatalError("FAIL: smoke requires a default subtitle") } let track = source.subtitles[index] let preparedTrack = try await MiruroTransport.subtitle(track) let document = try await WebVTTDocument.load(url: preparedTrack.url, headers: preparedTrack.headers) let playableCues = document.cues.filter { !$0.text.isEmpty && $0.start + 0.1 < model.state.duration } require(playableCues.count >= 2, "smoke requires two visible subtitle cues") let target = playableCues[1].start + min(0.1, (playableCues[1].end - playableCues[1].start) / 2) require(!document.text(at: target).isEmpty, "forward seek targets a visible English cue") require(model.seekRelative(target - model.state.position), "seek accepted") try await until("seek acknowledged and subtitle synchronized", model: model) { !model.state.seek_pending && abs(model.state.position - target) < 0.2 && model.subtitleText == document.text(at: model.state.position) && model.subtitleError == nil } require(model.selectedSubtitle == index && model.subtitleTracks[index].label == track.label, "default selection and label retained") let backward = playableCues[0].start + min(0.1, (playableCues[0].end - playableCues[0].start) / 2) require(model.seekRelative(backward - model.state.position), "backward seek accepted") try await until("backward seek subtitle synchronized", model: model) { !model.state.seek_pending && abs(model.state.position - backward) < 0.2 && model.subtitleText == document.text(at: model.state.position) } print("PASS: default VTT selected, forward/backward seek and acknowledged cue timing") if !input.hasPrefix("https://") { try await segmentSkips(model, clock: clock, file: file) } if !input.hasPrefix("https://") { try await transportProtocol(source: source) model.shutdown() try await automaticSegmentSkips(file: file) try await providerLifecycle(source: source) try await dubOnlySelection(source: source) try await queueMetadataLifecycle(source: source) try await chapterLifecycle(source: source, file: file) try await sponsorLifecycle(source: source) try await streamQueueLifecycle(source: source) try await enqueueLifecycle(source: source) try await sidebarQueueLifecycle(source: source) try await zokoIntervalLifecycle(source: source) } } static func zokoIntervalLifecycle(source: ResolvedSource) async throws { let directory = FileManager.default.temporaryDirectory .appendingPathComponent("terebi.zoko-interval-check." + UUID().uuidString) try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true) defer { try? FileManager.default.removeItem(at: directory) } let suite = "terebi.zoko-interval-check." + UUID().uuidString let defaults = UserDefaults(suiteName: suite)! defer { defaults.removePersistentDomain(forName: suite) } func url(_ episode: Int) -> URL { URL(string: "https://zokoanime.video/stream/ani/1/\(episode)/sub")! } let rows: [ProviderInterval] = [ .init(kind: .intro, start: 0.5, end: 1.5), .init(kind: .outro, start: 2.25, end: 3.25), ] let expected = [ MiruroInterval(kind: .intro, start: 0.5, end: 1.5, episode: 1), MiruroInterval(kind: .outro, start: 2.25, end: 3.25, episode: 1), ] func resolved(_ episode: Int) -> ResolvedSource { var result = ResolvedSource(url: source.url, headers: source.headers, identity: "anilist:1:\(episode)", title: "Zoko interval fixture \(episode)", subtitles: []) result.intervals = episode == 2 ? [] : rows if episode == 3 { result.intervals[1] = .init(kind: .outro, start: 2.25, end: 500) } return result } var requests: [Int: Int] = [:] var held: CheckedContinuation? var heldMetadata: CheckedContinuation? var staleCompleted = false var metadataRequests = 0 let clock = Clock() let model = PlayerModel(defaults: defaults, progressFile: directory.appendingPathComponent("direct.jsonl").path, miruroResolver: { _, _ in try providerSources(source) }, zokoResolver: { _, episode in requests[episode, default: 0] += 1 if episode == 4 { let result = await withCheckedContinuation { held = $0 } staleCompleted = true return result } return resolved(episode) }, miruroMetadataResolver: { _, _ in metadataRequests += 1 return await withCheckedContinuation { heldMetadata = $0 } }, skipClock: { clock.now }) defer { model.shutdown() } model.openStream(url(1).absoluteString) require(model.visibleIntervals.isEmpty, "direct launch hides bands until native media is acknowledged") try await until("direct Zoko native decode and provider bands", model: model) { !model.isBusy && model.state.status == PLAYER_PLAYING && model.state.position > 0.2 && model.visibleIntervals == expected } require(model.state.has_audio && model.state.has_video && model.state.duration >= 3.25, "local fixture supplies real video/audio and measured duration") require(model.sliderBoundaries == [0.5, 1.5, 2.25, 3.25] && model.sliderSegmentTitle(at: 0.75) == "Opening" && model.sliderSegmentTitle(at: 2.5) == "Ending", "provider bands reuse existing slider labels and fractional boundaries") require(model.setPlayback(playing: false), "pause provider playback") try await until("provider pause acknowledged", model: model) { model.state.status == PLAYER_PAUSED } require(model.seekRelative(0.8 - model.state.position), "save provider bookmark inside opening") try await until("provider bookmark and skip offer acknowledged", model: model) { !model.state.seek_pending && abs(model.state.position - 0.8) < 0.1 && model.segmentToSkip == expected[0] } require(model.skipButtonTitle == "Skip Opening", "provider timing feeds unchanged manual skip policy") model.closeFile() require(model.visibleIntervals.isEmpty && model.sliderBoundaries.isEmpty, "close clears provider bands immediately") try await until("provider playback closed", model: model) { model.state.status == PLAYER_EMPTY } require(model.savedPosition(for: URL(string: "anilist:1:1")!).map { abs($0 - 0.8) < 0.1 } == true, "provider bookmark is saved under stable episode identity") let queue = StreamQueue(entries: (1...3).map { episode in .init(url: url(episode), identity: "anilist:1:\(episode)", progressFile: directory.appendingPathComponent("queue-\(episode).jsonl"), label: "Episode \(episode)") }) let reopened = PlayerModel(defaults: UserDefaults(suiteName: suite)!, streamQueue: queue, zokoResolver: { _, episode in requests[episode, default: 0] += 1 return resolved(episode) }, miruroMetadataResolver: { _, _ in metadataRequests += 1 return MiruroMetadata(title: nil, intervals: []) }, skipClock: { clock.now }) defer { reopened.shutdown() } reopened.openStreamQueue(queue) try await until("fresh model reopens saved Zoko episode with newly resolved bands", model: reopened) { !reopened.isBusy && reopened.state.status == PLAYER_PLAYING && reopened.state.position >= 0.7 && reopened.visibleIntervals == expected } require(requests[1] == 2 && requests[2] == nil, "saved queued reopen resolves only selected episode afresh") reopened.advanceQueue(1) require(reopened.visibleIntervals.isEmpty && reopened.segmentToSkip == nil, "queue replacement clears opening offer and bands") try await until("queued episode without timing still decodes", model: reopened) { !reopened.isBusy && reopened.state.status == PLAYER_PLAYING && reopened.fileName == resolved(2).title } require(reopened.visibleIntervals.isEmpty && reopened.sliderBoundaries.isEmpty, "missing timing never inherits previous episode") reopened.advanceQueue(1) try await until("out-of-duration provider range does not block queued playback", model: reopened) { !reopened.isBusy && reopened.state.status == PLAYER_PLAYING && reopened.fileName == resolved(3).title } require(reopened.visibleIntervals == [MiruroInterval(kind: .intro, start: 0.5, end: 1.5, episode: 3)], "actual native duration rejects oversized provider ending without clipping or losing valid opening") reopened.advanceQueue(-2) try await until("queue revisit restores fresh first-episode bands", model: reopened) { !reopened.isBusy && reopened.state.status == PLAYER_PLAYING && reopened.visibleIntervals == expected } require(requests[1] == 3, "queue revisit resolves again instead of retaining stale timing") reopened.closeFile() try await until("queued provider playback closed", model: reopened) { reopened.state.status == PLAYER_EMPTY } model.openStream(url(4).absoluteString) try await until("stale provider resolution held", model: model) { held != nil } model.openStream(url(2).absoluteString) held!.resume(returning: resolved(4)) held = nil try await until("late canceled provider resolution cannot replace new episode", model: model) { staleCompleted && !model.isBusy && model.state.status == PLAYER_PLAYING && model.fileName == resolved(2).title } require(model.visibleIntervals.isEmpty && model.sliderBoundaries.isEmpty, "late source timing cannot paint replacement episode") model.closeFile() try await until("empty player before sidebar provider launch", model: model) { model.state.status == PLAYER_EMPTY } try await model.addStreamToQueue(url(1).absoluteString) try await until("cold sidebar provider launch preserves prepared source timing", model: model) { !model.isBusy && model.state.status == PLAYER_PLAYING && model.visibleIntervals == expected } require(metadataRequests == 0, "AniList paths never request Miruro timing metadata") model.openStream("https://barelystarted.miruro.tv/watch/fixture-ID/episode?ep=1") require(model.visibleIntervals.isEmpty, "switching away from provider timing clears bands immediately") try await until("Miruro metadata lookup held", model: model) { heldMetadata != nil } model.openStream(url(1).absoluteString) heldMetadata!.resume(returning: MiruroMetadata(title: "Stale Miruro title", intervals: [.init(kind: .intro, start: 0.1, end: 0.4, episode: 1)])) heldMetadata = nil try await until("replacement Zoko episode keeps its own provider metadata", model: model) { !model.isBusy && model.state.status == PLAYER_PLAYING && model.visibleIntervals == expected } for _ in 0..<10 { await Task.yield() } require(metadataRequests == 1 && model.fileName == resolved(1).title && model.visibleIntervals == expected, "canceled Miruro enrichment cannot rename or paint provider replacement") print("PASS: Zoko direct/sidebar launch, fractional bands, manual skip, saved fresh-model reopen, queue/revisit, missing/out-of-duration timing and stale source/metadata rejection") } static func sponsorPreferenceCheck() { let suite = "terebi.sponsor-preference." + UUID().uuidString let defaults = UserDefaults(suiteName: suite)! defer { defaults.removePersistentDomain(forName: suite) } func launch(_ configured: Bool = false, autoSkip: Bool = false, openingEnding: Bool = false) -> PlayerModel { PlayerModel(defaults: UserDefaults(suiteName: suite)!, configuration: .init(sponsorBlock: configured, sponsorBlockAutoSkip: autoSkip, openingEndingAutoSkip: openingEnding)) } let first = launch() require(!first.sponsorBlockEnabled, "unconfigured SponsorBlock defaults to disabled") require(!first.sponsorBlockAutoSkip, "sponsors are not skipped without opt-in") require(!first.openingEndingAutoSkip, "openings and endings are not skipped without opt-in") first.setOpeningEndingAutoSkip(true) first.setSponsorBlockAutoSkip(true) first.setSponsorBlockEnabled(true) first.shutdown() let second = launch() require(second.sponsorBlockEnabled, "enabled preference survives a fresh model/defaults instance") require(second.sponsorBlockAutoSkip, "autoskip preference survives restart") require(second.openingEndingAutoSkip, "opening/ending autoskip preference survives restart") second.setOpeningEndingAutoSkip(false) second.setSponsorBlockAutoSkip(false) second.setSponsorBlockEnabled(false) second.shutdown() let third = launch(true, autoSkip: true, openingEnding: true) require(!third.sponsorBlockEnabled && !third.sponsorBlockAutoSkip && !third.openingEndingAutoSkip, "saved disabled skip preferences override config") third.shutdown() defaults.removePersistentDomain(forName: suite) let configured = launch(true, autoSkip: true, openingEnding: true) require(configured.sponsorBlockEnabled && configured.sponsorBlockAutoSkip && configured.openingEndingAutoSkip, "config supplies unsaved skip choices") configured.shutdown() print("PASS: sponsor and opening/ending skip preferences persist across launches, with config fallback") } static var fixtureSponsors: [SponsorSegment] { [.init(start: 0.5, end: 1.5, videoDuration: 0)] } static func sponsorLifecycle(source: ResolvedSource) async throws { let suite = "terebi.sponsor-check." + UUID().uuidString let defaults = UserDefaults(suiteName: suite)! defer { defaults.removePersistentDomain(forName: suite) } let url = URL(string: "https://www.youtube.com/watch?v=AAAAAAAAAAA")! var extraction: CheckedContinuation? var held: [CheckedContinuation<[SponsorSegment], Never>] = [] var extractionCount = 0 let clock = Clock() let model = PlayerModel(defaults: defaults, youtubeResolver: { url, height in extractionCount += 1 let video = YouTubeVideo(title: "Sponsor fixture", video: .init(url: source.url, headers: source.headers), audio: nil, duration: 0, isLive: false, webpageURL: url, height: height ?? 1080, availableHeights: [1080, 720], chapters: fixtureChapters) if extractionCount == 1 { return await withCheckedContinuation { extraction = $0 } } return video }, youtubeTitleResolver: { _ in nil }, sponsorResolver: { id in if id == "BBBBBBBBBBB" { throw URLError(.timedOut) } return await withCheckedContinuation { held.append($0) } }, skipClock: { clock.now }) defer { model.shutdown() } model.open(url) try await until("playback extraction held with SponsorBlock disabled", model: model) { extraction != nil } require(held.isEmpty, "opted-out playback makes no SponsorBlock request") model.setSponsorBlockEnabled(true) try await until("SponsorBlock starts independently of held playback extraction", model: model) { held.count == 1 } held[0].resume(returning: fixtureSponsors) extraction!.resume(returning: YouTubeVideo(title: "Sponsor fixture", video: .init(url: source.url, headers: source.headers), audio: nil, duration: 0, isLive: false, webpageURL: url, height: 1080, availableHeights: [1080, 720], chapters: fixtureChapters)) extraction = nil try await until("sponsor metadata appears against native measured duration", model: model) { !model.isBusy && model.state.status == PLAYER_PLAYING && model.sponsoredRanges == [0.5...1.5] } requireYouTubeChapters(model, [1.25, 2.5], "sponsor paint crossing chapter boundary") require(model.setPlayback(playing: false), "pause sponsored playback") try await until("sponsor playback paused", model: model) { model.state.status == PLAYER_PAUSED } require(model.seekRelative(0.75 - model.state.position), "seek within sponsor range") try await until("sponsor seek acknowledged without automatic skipping", model: model) { !model.state.seek_pending && abs(model.state.position - 0.75) < 0.1 } clock.now += 60 require(model.skipSegment(), "sponsor skip stays available beyond the opening/ending timeout") try await until("manual sponsor skip reaches range end while paused", model: model) { !model.state.seek_pending && abs(model.state.position - 1.5) < 0.1 } require(!model.effectivePlaying, "manual sponsor skip preserves paused intent") model.setSponsorBlockAutoSkip(true) require(model.seekRelative(0.75 - model.state.position), "deliberately revisit sponsor with autoskip enabled") try await until("intentional seek into sponsor settles", model: model) { !model.state.seek_pending && abs(model.state.position - 0.75) < 0.1 } require(model.setPlayback(playing: true), "play deliberately selected sponsor") try await until("deliberately selected sponsor plays", model: model) { model.state.position >= 1 } require(model.state.position < 1.5, "autoskip does not fight a seek into the sponsor") require(model.setPlayback(playing: false), "pause before seeking just before sponsor") try await until("sponsor paused again", model: model) { model.state.status == PLAYER_PAUSED } model.isSeeking = true model.seekPosition = 0.25 model.finishSeeking() try await until("slider seek just before sponsor settles", model: model) { !model.state.seek_pending && abs(model.state.position - 0.25) < 0.1 } require(model.setPlayback(playing: true), "play from just before sponsor") try await until("sponsor entered after nearby slider seek", model: model) { model.state.position >= 0.9 } require(model.state.position < 1.5, "autoskip does not fight a nearby slider seek") require(model.skipButtonTitle == "Skip Sponsor", "suppressed autoskip keeps the manual button") require(model.setPlayback(playing: false), "pause before metadata lifecycle checks") try await until("sponsor lifecycle playback paused", model: model) { model.state.status == PLAYER_PAUSED } model.setSponsorBlockEnabled(false) require(model.sponsoredRanges.isEmpty, "disabling immediately clears paint") model.setSponsorBlockEnabled(true) try await until("re-enabled lookup held", model: model) { held.count == 2 } model.setYouTubeQuality(720) try await until("quality changes without awaiting optional metadata", model: model) { !model.isBusy && model.state.status == PLAYER_PAUSED && model.youtubeHeight == 720 } require(held.count == 2, "quality replacement coalesces the in-flight current-video lookup") held[1].resume(returning: fixtureSponsors) try await until("pending metadata survives quality replacement", model: model) { model.sponsoredRanges == [0.5...1.5] } model.setYouTubeQuality(1080) try await until("completed metadata survives quality replacement", model: model) { !model.isBusy && model.youtubeHeight == 1080 && model.sponsoredRanges == [0.5...1.5] } require(held.count == 2, "quality replacement reuses completed metadata") model.open(url) try await until("same-video reload has its own lookup", model: model) { held.count == 3 && !model.isBusy && model.state.status == PLAYER_PLAYING } model.open(url) try await until("second same-video reload has a new generation", model: model) { held.count == 4 && !model.isBusy && model.state.status == PLAYER_PLAYING } held[2].resume(returning: [.init(start: 2, end: 3, videoDuration: 0)]) for _ in 0..<10 { await Task.yield() } require(model.sponsoredRanges.isEmpty, "late same-video response cannot paint a newer load") held[3].resume(returning: fixtureSponsors) try await until("current reload accepts its own metadata", model: model) { model.sponsoredRanges == [0.5...1.5] } model.open(url) try await until("lookup pending before close", model: model) { held.count == 5 && !model.isBusy && model.state.status == PLAYER_PLAYING } model.closeFile() held[4].resume(returning: fixtureSponsors) try await until("close clears sponsorship", model: model) { model.state.status == PLAYER_EMPTY } require(model.sponsoredRanges.isEmpty, "closed player rejects late metadata") model.open(URL(string: "https://www.youtube.com/watch?v=BBBBBBBBBBB")!) try await until("SponsorBlock timeout never blocks native playback", model: model) { !model.isBusy && model.state.status == PLAYER_PLAYING && model.state.has_audio } require(model.commandError == nil && model.sponsoredRanges.isEmpty, "optional failure leaves unmarked playback") model.open(URL(string: "https://example.org/generic-video")!) try await until("generic extractor site remains playable without SponsorBlock", model: model) { !model.isBusy && model.state.status == PLAYER_PLAYING && model.state.has_audio } require(held.count == 5 && model.sponsoredRanges.isEmpty, "generic sites never send a SponsorBlock lookup") print("PASS: persistent manual sponsor skip, deliberate-seek protection, opt-in, quality reuse, cancellation and failure isolation") try await sponsorAutoSkip(source: source) } static func sponsorAutoSkip(source: ResolvedSource) async throws { let suite = "terebi.sponsor-autoskip." + UUID().uuidString let defaults = UserDefaults(suiteName: suite)! defer { defaults.removePersistentDomain(forName: suite) } let model = PlayerModel(defaults: defaults, configuration: .init(sponsorBlock: true, sponsorBlockAutoSkip: true), youtubeResolver: { url, _ in YouTubeVideo(title: "Autoskip fixture", video: .init(url: source.url, headers: source.headers), audio: nil, duration: 0, isLive: false, webpageURL: url, height: 720, availableHeights: [720], chapters: []) }, youtubeTitleResolver: { _ in nil }, sponsorResolver: { _ in fixtureSponsors }) defer { model.shutdown() } model.open(URL(string: "https://www.youtube.com/watch?v=CCCCCCCCCCC")!) try await until("natural sponsor entry queues exact-end autoskip", model: model) { model.seekPosition == 1.5 } try await until("native player acknowledges autoskip and keeps playing", model: model) { !model.state.seek_pending && model.state.position >= 1.5 && model.state.status == PLAYER_PLAYING } require(model.effectivePlaying, "autoskip preserves playing intent") print("PASS: natural sponsor entry automatically seeks to range end in the native player") } 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, configuration: .init(sponsorBlock: true), 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 }, sponsorResolver: { id in id == "AAAAAAAAAAA" ? fixtureSponsors : [] }, 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") try await until("direct launch sponsorship metadata", model: model) { url != chaptered || model.sponsoredRanges == [0.5...1.5] } require(model.sponsoredRanges == (url == chaptered ? [0.5...1.5] : []), "single launch sponsorship") require(model.sliderSegmentTitle(at: 0.5) == (url == chaptered ? "Opening" : nil) && model.sliderSegmentTitle(at: 1.25) == (url == chaptered ? "Ending" : nil) && model.sliderSegmentTitle(at: 2.5) == nil, "hover names match the chapter under the pointer, with no invented gap names") 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)") try await until("saved-item sponsorship metadata", model: model) { url != chaptered || model.sponsoredRanges == [0.5...1.5] } 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") require(model.sponsoredRanges == (url == chaptered ? [0.5...1.5] : []), "quality replacement retains paint") } 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") require(model.sponsoredRanges.isEmpty, "resolved replacement clears sponsorship") 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!)" let deployment = MiruroProxyDeployment(base: URL(string: base.replacingOccurrences(of: "127.0.0.1", with: "localhost") + "/")!, key: [1, 2, 3]) var input = ResolvedSource(url: base + "/transport-master.m3u8?token=private", headers: "Referer: https://transport.example/\r\nX-Secret: private\r\n", identity: source.identity, title: source.title, subtitles: []) input.intervals = [.init(kind: .intro, start: 0.25, end: 1.75)] input.requirements = .init(proxy: .init(rotate: false, segments: true), segmentOrigin: base, playlistQuery: ["skip": "fixture"]) let prepared = try await MiruroTransport.prepare(input, deployment: deployment) require(prepared.intervals == input.intervals, "transport source reconstruction preserves provider intervals") let (master, _) = try await URLSession.shared.data(from: URL(string: prepared.url)!) let nested = String(data: master, encoding: .utf8)!.components(separatedBy: .newlines) .first { $0.hasPrefix("http://") }! let (variant, _) = try await URLSession.shared.data(from: URL(string: nested)!) let lines = String(data: variant, encoding: .utf8)!.components(separatedBy: .newlines) let segment = lines.first { $0.hasPrefix("http://") }! let (body, response) = try await URLSession.shared.data(from: URL(string: segment)!) require((response as? HTTPURLResponse)?.statusCode == 200 && body == Data((0..<256).map(UInt8.init)), "proxied nested playlists decode once; query and segment-origin rules fetch real resource bytes") let keyLine = lines.first { $0.hasPrefix("#EXT-X-KEY:") }! let key = keyLine.components(separatedBy: "\"")[1] let (_, blocked) = try await URLSession.shared.data(from: URL(string: key)!) require((blocked as? HTTPURLResponse)?.statusCode == 502, "manifest key cannot make the relay fetch a foreign loopback destination") withExtendedLifetime(prepared) {} print("PASS: proxy codec, already-proxied nested HLS, query/origin rules, credential isolation and private-destination rejection") } static func providerSources(_ source: ResolvedSource, alternate: Bool = false, track: String = "ssub") throws -> MiruroSources { func headers(_ text: String) -> [String: String] { Dictionary(uniqueKeysWithValues: text.components(separatedBy: "\r\n").filter { !$0.isEmpty }.map { let parts = $0.split(separator: ":", maxSplits: 1) return (String(parts[0]), String(parts[1]).trimmingCharacters(in: .whitespaces)) }) } func provider(_ name: String, url: String, header: [String: String], subtitles: [[String: Any]]) -> [String: Any] { ["provider": name, "servers": [["headers": header, "streams": [["url": url, "format": "hls"]]]], "subtitles": subtitles] } let subtitles: [[String: Any]] = source.subtitles.map { ["file": $0.url, "format": $0.format, "language": $0.language, "label": $0.label, "default": $0.isDefault] } // Source-file fixtures allow independent headers; Miruro returns each set on its own provider. let video = provider("sun", url: source.url, header: headers(source.headers), subtitles: []) let captions = provider("captions", url: source.url.replacingOccurrences(of: "/master.m3u8", with: "/captions/master.m3u8"), header: headers(source.subtitles[0].headers), subtitles: subtitles) var providers = [video, captions] var config: [String: Any] = ["sun": ["player": "native", "proxy": false], "captions": ["player": "native", "proxy": false]] if alternate { providers.append(provider("alternate", url: source.url.replacingOccurrences(of: "/master.m3u8", with: "/alternate/master.m3u8"), header: ["X-Resource": "alternate-private"], subtitles: [["file": source.subtitles[0].url.replacingOccurrences(of: "/english.jpeg", with: "/alternate/english.jpeg"), "format": "vtt", "language": "en", "label": "Alternate English", "default": true]])) let origin = URLComponents(string: source.url)! let segmentOrigin = "\(origin.scheme!)://\(origin.host!):\(origin.port!)" config["alternate"] = ["player": "native", "proxy": false, "hls": ["query": ["enabled": true, "params": ["rewrite": "1"]], "segments": ["enabled": true, "origin": segmentOrigin]]] } return try MiruroResolver.decode(JSONSerialization.data(withJSONObject: [ "episodeNumber": 1, "providerOrder": ["sun", "captions", "alternate"], "config": config, "tracks": [["track": track, "providers": providers]] ]), mediaID: "fixture-ID", episode: 1) } static func providerLifecycle(source: ResolvedSource) async throws { let providers = try providerSources(source, alternate: true) let suite = "terebi.provider-check." + UUID().uuidString let defaults = UserDefaults(suiteName: suite)! defer { defaults.removePersistentDomain(forName: suite) } var requests = 0 var pending: CheckedContinuation? var completed = 0 let model = PlayerModel(defaults: defaults, configuration: .init(miruroVideoProvider: "unavailable", miruroCaptionProvider: "captions"), miruroResolver: { _, _ in requests += 1 defer { completed += 1 } if requests == 4 { throw CocoaError(.fileReadCorruptFile) } if requests == 5 { return await withCheckedContinuation { pending = $0 } } return providers }, miruroMetadataResolver: { _, _ in MiruroMetadata(title: nil, intervals: []) }) defer { model.shutdown() } let url = "https://barelystarted.miruro.tv/watch/fixture-ID/episode?ep=1" model.openStream(url) try await until("configured missing host falls back with independent captions", model: model) { model.canChangeMiruroProvider && model.state.position > 0.2 && !model.subtitleText.isEmpty } require(model.selectedMiruroVideoProvider == "sun:ssub" && model.selectedMiruroCaptionProvider == "captions:ssub", "startup preferences select independent providers with host fallback") require(model.setPlayback(playing: false), "pause before provider switches") try await until("provider pause acknowledged", model: model) { model.state.status == PLAYER_PAUSED } let position = model.state.position let revision = model.state.revision model.setMiruroCaptionProvider("alternate:ssub") try await until("alternate provider's own VTT is loaded", model: model) { model.subtitleText == "Alternate English cue" } require(model.state.revision == revision && model.state.position == position && requests == 1, "caption selection does not reopen video, seek or refetch sources") model.setMiruroVideoProvider("alternate:ssub") try await until("alternate host preserves paused position", model: model) { model.canChangeMiruroProvider && model.state.status == PLAYER_PAUSED && model.state.revision != revision && abs(model.state.position - position) < 0.2 } require(model.selectedMiruroVideoProvider == "alternate:ssub" && model.selectedMiruroCaptionProvider == "alternate:ssub", "host and captions remain independently selected") let preparedRevision = model.state.revision model.setMiruroCaptionProvider("captions:ssub") try await until("caption change retains prepared video transport", model: model) { !model.subtitleText.isEmpty && model.selectedMiruroCaptionProvider == "captions:ssub" } require(model.state.revision == preparedRevision, "prepared video is not reopened for captions") model.selectSubtitle(nil) model.setMiruroCaptionProvider("") require(model.selectedSubtitle == nil && model.subtitleText.isEmpty, "caption provider respects explicit Off") require(model.setPlayback(playing: true), "resume before automatic host choice") try await until("playback resumes before host switch", model: model) { model.state.status == PLAYER_PLAYING } model.setMiruroVideoProvider("") try await until("automatic host returns to Sun and preserves playing intent", model: model) { model.canChangeMiruroProvider && model.state.status == PLAYER_PLAYING && model.selectedMiruroVideoProvider == "sun:ssub" } require(model.selectedSubtitle == nil, "host changes preserve explicit subtitle Off") model.setMiruroVideoProvider("alternate:ssub") let deadline = ContinuousClock.now.advanced(by: .seconds(5)) while (model.resolvingStream || model.state.status != PLAYER_PLAYING), ContinuousClock.now < deadline { try await Task.sleep(for: .milliseconds(10)) } require(model.commandError != nil && model.selectedMiruroVideoProvider == "sun:ssub" && model.state.status == PLAYER_PLAYING && model.canChangeMiruroProvider, "failed host fetch retains previous source and resumes playback") model.setMiruroVideoProvider("") try await until("replacement provider request is held", model: model) { pending != nil } model.closeFile() pending!.resume(returning: providers) try await until("closed player discards late provider results", model: model) { completed == requests && model.state.status == PLAYER_EMPTY } require(model.miruroVideoProviders.isEmpty && model.selectedMiruroCaptionProvider == nil, "closing clears episode provider state") print("PASS: provider config fallback, independent caption headers, in-place captions, paused/playing host switches and failure retention") } static func dubOnlySelection(source: ResolvedSource) async throws { let providers = try providerSources(source, track: "dub") let suite = "terebi.dub-check." + UUID().uuidString let defaults = UserDefaults(suiteName: suite)! defer { defaults.removePersistentDomain(forName: suite) } let model = PlayerModel(defaults: defaults, miruroResolver: { _, _ in providers }, miruroMetadataResolver: { _, _ in MiruroMetadata(title: nil, intervals: []) }) defer { model.shutdown() } model.openStream("https://barelystarted.miruro.tv/watch/fixture-ID/episode?ep=1") let deadline = ContinuousClock.now.advanced(by: .seconds(5)) while model.resolvingStream, ContinuousClock.now < deadline { try await Task.sleep(for: .milliseconds(10)) } require(model.commandError != nil && model.miruroVideoProviders.contains("sun:dub") && model.canChangeMiruroProvider, "dub-only episode exposes explicit choices without automatic language fallback") model.setMiruroVideoProvider("sun:dub") try await until("explicit dub-only choice decodes", model: model) { model.state.has_video && model.state.position > 0.2 && model.selectedMiruroVideoProvider == "sun:dub" } require(model.selectedMiruroCaptionProvider == "captions:dub", "automatic captions follow explicit dub track") print("PASS: dub-only choices remain accessible and explicit selection plays") } static func queueMetadataLifecycle(source: ResolvedSource) async throws { let suite = "terebi.queue-metadata-check." + UUID().uuidString let defaults = UserDefaults(suiteName: suite)! defer { defaults.removePersistentDomain(forName: suite) } let urls = ["AAAAAAAAAAA", "BBBBBBBBBBB", "CCCCCCCCCCC"].map { URL(string: "https://www.youtube.com/watch?v=\($0)")! } + [URL(string: "https://video.example/watch/fixture")!] let episode = URL(string: "https://barelystarted.miruro.tv/watch/fixture-ID/episode?ep=1")! var foreground: CheckedContinuation? var background: CheckedContinuation? var holdFirst = true var episodeRequests = 0 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], 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 { holdFirst = false return await withCheckedContinuation { foreground = $0 } } return video(url, title: url == urls[0] ? "Playing title" : "Fresh playback title") }, youtubeTitleResolver: { url in require(Task.currentPriority <= .utility, "title-only lookups use lower priority") titleRequests.append(url) if url == urls[1] { return await withCheckedContinuation { background = $0 } } if url == urls[2] { throw CocoaError(.fileReadCorruptFile) } return url == urls[0] ? "Early current title" : "Generic page title" }, miruroResolver: { _, _ in fatalError("FAIL: sidebar enrichment must not fetch Miruro playback sources") }, miruroMetadataResolver: { _, _ in episodeRequests += 1 return MiruroMetadata(title: "Queued episode", intervals: []) }) defer { model.shutdown() } func queue(_ entries: [URL]) -> StreamQueue { StreamQueue(entries: entries.map { url in .init(url: url, identity: url == episode ? source.identity : url.absoluteString, progressFile: FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString), label: url.absoluteString) }) } model.openStreamQueue(queue([urls[0], urls[1], episode, urls[2], urls[3]])) try await until("early current title and next title request while full extraction is held", model: model) { foreground != nil && background != nil && model.queue.currentItem?.displayName == "Early current title" } require(model.resolvingStream && model.fileName == nil && episodeRequests == 0 && titleRequests == [urls[0], urls[1]], "serial sidebar work is independent of foreground extraction") let selected = model.queue.currentID let pendingID = model.queue.items[1].id model.moveQueueItems(fromOffsets: IndexSet(integer: 1), toOffset: 5) background!.resume(returning: "Queued title") background = nil try await until("mixed queue titles continue before foreground extraction finishes", model: model) { model.queue.items.last?.displayName == "Queued title" && model.queue.items.first { $0.url == episode }?.displayName == "Queued episode" && model.queue.items.first { $0.url == urls[3] }?.displayName == "Generic page title" } require(model.queue.items.last?.id == pendingID && model.queue.currentID == selected && model.resolvingStream && model.fileName == nil, "reordered metadata updates only its original row") require(model.queue.items.first { $0.url == urls[2] }?.displayName == urls[2].absoluteString && model.queue.items.first { $0.url == urls[2] }?.metadataAttempted == true && model.commandError == nil, "failed title preserves fallback and does not block later titles") foreground!.resume(returning: video(urls[0], title: "Playing title")) foreground = nil 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 for _ in 0..<10 { await Task.yield() } require(model.queue.currentID == selected && model.state.position == position && model.fileName == "Playing title", "title-only metadata preserves native selection, position and current title") model.openStreamQueue(queue([urls[0], urls[1]])) try await until("next title-only lookup starts", model: model) { background != nil } let selectedLookup = background! background = nil model.selectQueueItem(model.queue.items[1].id) try await until("selection resolves fresh playback independently", model: model) { !model.isBusy && model.state.status == PLAYER_PLAYING && model.fileName == "Fresh playback title" } selectedLookup.resume(returning: "Stale title") for _ in 0..<10 { await Task.yield() } require(model.queue.currentItem?.displayName == "Fresh playback title" && model.fileName == "Fresh playback title", "late title-only result cannot overwrite foreground metadata") model.openStreamQueue(queue([urls[0], urls[1]])) try await until("removable title-only lookup starts", model: model) { background != nil } let removed = background! background = nil model.removeQueueItem(model.queue.items[1].id) model.openStreamQueue(queue([urls[0], urls[1]])) try await until("replacement title-only lookup starts", model: model) { background != nil } removed.resume(returning: "Removed title") 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) { model.queue.items[1].displayName == "Replacement title" } require(model.resolvingStream && model.fileName == "Playing title" && model.queue.currentItem?.displayName == "Playing title", "title work does not replace current quality metadata") foreground!.resume(returning: video(urls[0], title: "Quality title", height: 720)) foreground = nil 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") } static func streamQueueLifecycle(source: ResolvedSource) async throws { let directory = FileManager.default.temporaryDirectory .appendingPathComponent("terebi.queue-check." + UUID().uuidString).resolvingSymlinksInPath() try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true, attributes: [.posixPermissions: 0o700]) defer { try? FileManager.default.removeItem(at: directory) } let youtubeURL = URL(string: "https://www.youtube.com/watch?v=AAAAAAAAAAA")! let episodeURL = URL(string: "https://barelystarted.miruro.tv/watch/fixture-ID/episode?ep=1")! let firstReport = directory.appendingPathComponent("youtube.jsonl") let secondReport = directory.appendingPathComponent("miruro.jsonl") let queue = StreamQueue(entries: [ .init(url: youtubeURL, identity: youtubeURL.absoluteString, progressFile: firstReport, label: "First"), .init(url: episodeURL, identity: source.identity, progressFile: secondReport, label: "Second") ]) let suite = "terebi.queue-check." + UUID().uuidString let defaults = UserDefaults(suiteName: suite)! defer { defaults.removePersistentDomain(forName: suite) } var youtubeRequests = 0 var episodeRequests = 0 var stale: CheckedContinuation? 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: [], chapters: fixtureChapters) let model = PlayerModel(defaults: defaults, configuration: .init(sponsorBlock: true), streamQueue: queue, youtubeResolver: { _, _ in youtubeRequests += 1 if youtubeRequests == 1 { return await withCheckedContinuation { stale = $0 } } return video }, youtubeTitleResolver: { _ in nil }, sponsorResolver: { _ in fixtureSponsors }, miruroResolver: { _, _ in episodeRequests += 1 return try providerSources(source) }, miruroMetadataResolver: { _, _ in MiruroMetadata(title: nil, intervals: []) }) defer { model.shutdown() } require(model.progressInitializationError == nil, "queue report registration") model.openStreamQueue(queue) try await until("only selected first stream resolves", model: model) { stale != nil } require(episodeRequests == 0 && model.queue.items.map(\.url) == [youtubeURL, episodeURL], "stream queue is lazy and preserves supplied order") let firstID = model.queue.items[0].id let secondID = model.queue.items[1].id model.selectQueueItem(secondID) 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") try await until("batch launch sponsor paint", model: model) { model.sponsoredRanges == [0.5...1.5] } 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") try await until("end seek acknowledged", model: model) { !model.state.seek_pending && !model.isBusy && abs(model.state.position - max(0, model.duration - 0.3)) < 0.1 } require(model.setPlayback(playing: true), "play first stream to natural end") try await until("natural end advances and freshly resolves second stream", model: model) { 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.sponsoredRanges.isEmpty, "natural advance clears sponsor paint 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") try await until("second bookmark acknowledged", model: model) { !model.state.seek_pending && abs(model.state.position - 0.8) < 0.1 } model.advanceQueue(-1) 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 && model.state.position >= 0.7 } require(youtubeRequests == 3 && episodeRequests == 3, "every revisit resolves fresh transport metadata") model.shutdown() func records(_ file: URL) throws -> [[String: Any]] { try String(contentsOf: file, encoding: .utf8).split(separator: "\n").map { try JSONSerialization.jsonObject(with: Data($0.utf8)) as! [String: Any] } } let youtubeRows = try records(firstReport) let episodeRows = try records(secondReport) require(youtubeRows.count >= 4 && youtubeRows.allSatisfy { $0["version"] as? Int == 1 && $0["media_url"] as? String == youtubeURL.absoluteString }, "YouTube report appends on revisit without another identity") require(youtubeRows.contains { $0["event"] as? String == "ended" }, "natural end retains its acknowledged completion snapshot") require(episodeRows.count >= 6 && episodeRows.allSatisfy { let identity = $0["media_identity"] as? [String: Any] return $0["version"] as? Int == 2 && identity?["media_id"] as? String == "fixture-ID" && identity?["episode"] as? Int == 1 }, "Miruro report is isolated through selection, advance and revisits") print("PASS: ordered lazy mixed stream queue, stale cancellation, native end advance, fresh revisits/resume and report isolation") } static func enqueueLifecycle(source: ResolvedSource) async throws { let directory = FileManager.default.temporaryDirectory .appendingPathComponent("terebi.enqueue-check." + UUID().uuidString).resolvingSymlinksInPath() try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true, attributes: [.posixPermissions: 0o700]) defer { try? FileManager.default.removeItem(at: directory) } let first = URL(string: "https://www.youtube.com/watch?v=AAAAAAAAAAA")! let second = URL(string: "https://barelystarted.miruro.tv/watch/fixture-ID/episode?ep=1")! let third = URL(string: "https://www.youtube.com/watch?v=BBBBBBBBBBB")! let fourth = URL(string: "https://www.youtube.com/watch?v=CCCCCCCCCCC")! let originalReport = directory.appendingPathComponent("original.jsonl") let secondReport = directory.appendingPathComponent("second.jsonl") let thirdReport = directory.appendingPathComponent("third.jsonl") let fourthReport = directory.appendingPathComponent("fourth.jsonl") let manifest = directory.appendingPathComponent("queue & #.json") var requests = 0 var episodeRequests = 0 let suite = "terebi.enqueue-check." + UUID().uuidString let defaults = UserDefaults(suiteName: suite)! defer { defaults.removePersistentDomain(forName: suite) } let model = PlayerModel(defaults: defaults, configuration: .init(sponsorBlock: true), progressFile: originalReport.path, youtubeResolver: { url, _ in 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: [], chapters: url == first ? fixtureChapters : []) }, youtubeTitleResolver: { _ in nil }, sponsorResolver: { id in id == "AAAAAAAAAAA" ? fixtureSponsors : [] }, miruroResolver: { _, _ in episodeRequests += 1; return try providerSources(source) }, miruroMetadataResolver: { _, _ in MiruroMetadata(title: nil, intervals: []) }) defer { model.shutdown() } func deliver(_ items: [(URL, URL)]) throws { let document: [String: Any] = ["version": 1, "items": items.map { ["url": $0.0.absoluteString, "progress_file": $0.1.path] }] try JSONSerialization.data(withJSONObject: document).write(to: manifest) model.receiveEnqueueURL(StreamQueue.enqueueURL(for: manifest)) } model.openStream(first.absoluteString) 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") try await until("initial single launch sponsor paint", model: model) { model.sponsoredRanges == [0.5...1.5] } 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 let position = model.state.position let revision = model.state.revision try deliver([(second, secondReport), (third, thirdReport)]) require(model.queue.items.map(\.url) == [first, second, third] && model.queue.currentID == selected && model.state.status == PLAYER_PAUSED && 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.sponsoredRanges == [0.5...1.5], "paused enqueue preserves current sponsor paint") 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 try deliver([(fourth, fourthReport)]) require(model.queue.items.map(\.url) == [first, second, third, fourth] && 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") require(model.sponsoredRanges == [0.5...1.5], "playing enqueue preserves current sponsor paint") 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") try deliver([(URL(string: "https://www.youtube.com/watch?v=DDDDDDDDDDD")!, fourthReport)]) require(model.commandError != nil && model.queue.items.count == 4, "registered report-path conflicts reject complete enqueue before files exist") try deliver([(URL(string: "https://www.youtube.com/watch?v=DDDDDDDDDDD")!, directory.appendingPathComponent("valid.jsonl")), (URL(string: "file:///not-a-stream")!, directory.appendingPathComponent("invalid.jsonl"))]) require(model.commandError != nil && model.queue.items.count == 4, "invalid later manifest entry never partially appends or registers reports") try deliver([(URL(string: "https://www.youtube.com/watch?v=DDDDDDDDDDD")!, directory.appendingPathComponent("valid.jsonl"))]) require(model.commandError == nil && model.queue.items.count == 5, "failed batches leave no registrations behind") require(model.setPlayback(playing: false), "pause original before natural-end advance") try await until("original pause before end seek", model: model) { model.state.status == PLAYER_PAUSED } let ending = max(0, model.duration - 0.3) require(model.seekRelative(ending - model.state.position), "seek original near natural end") try await until("original end seek acknowledged", model: model) { !model.state.seek_pending && !model.isBusy && abs(model.state.position - ending) < 0.1 } require(model.setPlayback(playing: true), "resume original through natural end") try await until("natural end advances to enqueued Miruro item", model: model) { !model.isBusy && model.state.status == PLAYER_PLAYING && !model.miruroVideoProviders.isEmpty && model.queue.currentURL == second } require(model.sliderBoundaries.isEmpty, "enqueued Miruro advance clears current chapters") require(model.sponsoredRanges.isEmpty, "enqueue advance to Miruro clears sponsor paint") 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") require(model.sponsoredRanges.isEmpty, "enqueued no-result video has no sponsor paint") model.advanceQueue(-1) try await until("enqueued Miruro report revisits", model: model) { !model.isBusy && model.state.status == PLAYER_PLAYING && model.queue.currentURL == second } model.advanceQueue(-1) 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 model.removeQueueItem(oldID) let currentID = model.queue.currentID let freshReport = directory.appendingPathComponent(oldReport.lastPathComponent + ".fresh") try deliver([(url, freshReport)]) require(model.commandError == nil && model.queue.items.count == 5 && model.queue.currentID == currentID, "removed video re-enqueues without interrupting current playback") let retiredBytes = try Data(contentsOf: oldReport) retiredReports.append((oldReport, retiredBytes, freshReport, url)) require(!FileManager.default.fileExists(atPath: freshReport.path), "re-added report remains lazy") model.selectQueueItem(model.queue.items.last!.id) try await until("re-added video decodes with a fresh session", model: model) { !model.isBusy && model.state.status == PLAYER_PLAYING && model.queue.currentURL == url && model.state.position > 0.2 } require(model.setPlayback(playing: false), "pause re-added video") try await until("re-added pause acknowledged", model: model) { model.state.status == PLAYER_PAUSED } } model.shutdown() for (oldReport, retiredBytes, freshReport, url) in retiredReports { let finalBytes = try Data(contentsOf: oldReport) require(finalBytes == retiredBytes, "re-added playback leaves the retired report unchanged") let freshRows = try String(contentsOf: freshReport, encoding: .utf8).split(separator: "\n").map { try JSONSerialization.jsonObject(with: Data($0.utf8)) as! [String: Any] } require(freshRows.contains { $0["event"] as? String == "closed" } && freshRows.contains { $0["event"] as? String == "progress" } && freshRows.allSatisfy { $0["media_url"] as? String == url.absoluteString }, "re-added video writes its acknowledged progress and close to the fresh report only") } for (file, expected) in [(originalReport, first.absoluteString), (thirdReport, third.absoluteString)] { let rows = try String(contentsOf: file, encoding: .utf8).split(separator: "\n").map { try JSONSerialization.jsonObject(with: Data($0.utf8)) as! [String: Any] } require(!rows.isEmpty && rows.allSatisfy { $0["media_url"] as? String == expected }, "original and appended YouTube reports retain independent identities") } let episodeRows = try String(contentsOf: secondReport, encoding: .utf8).split(separator: "\n").map { try JSONSerialization.jsonObject(with: Data($0.utf8)) as! [String: Any] } require(!episodeRows.isEmpty && episodeRows.allSatisfy { let identity = $0["media_identity"] as? [String: Any] return identity?["media_id"] as? String == "fixture-ID" && identity?["episode"] as? Int == 1 }, "appended Miruro report remains isolated on revisit") 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: [], chapters: []) }, youtubeTitleResolver: { _ in nil }) defer { empty.shutdown() } let coldReport = directory.appendingPathComponent("cold.jsonl") empty.enqueueStreamQueue(StreamQueue(entries: [.init(url: fourth, identity: fourth.absoluteString, progressFile: coldReport, label: "Cold enqueue")])) 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") } static func sidebarQueueLifecycle(source: ResolvedSource) async throws { let suite = "terebi.sidebar-queue-check." + UUID().uuidString let defaults = UserDefaults(suiteName: suite)! defer { defaults.removePersistentDomain(forName: suite) } let first = URL(string: "https://www.youtube.com/watch?v=AAAAAAAAAAA")! let second = URL(string: "https://www.youtube.com/watch?v=BBBBBBBBBBB")! let episode = URL(string: "https://barelystarted.miruro.tv/watch/fixture-ID/episode?ep=1")! let failedURL = URL(string: "https://example.invalid/failure")! let cancelledURL = URL(string: "https://example.invalid/cancelled")! var requests = 0 var pending: CheckedContinuation? let model = PlayerModel(defaults: defaults, youtubeResolver: { url, _ in requests += 1 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: [], chapters: url == first ? fixtureChapters : []) if url == cancelledURL { return await withCheckedContinuation { pending = $0 } } return video }, youtubeTitleResolver: { _ in nil }, miruroResolver: { _, _ in try providerSources(source) }, miruroMetadataResolver: { _, _ in MiruroMetadata(title: nil, intervals: []) }) defer { model.shutdown() } try await model.addStreamToQueue(first.absoluteString) try await until("empty sidebar addition starts fetched video", model: model) { !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 let revision = model.state.revision let position = model.state.position try await model.addStreamToQueue(" https://youtu.be/BBBBBBBBBBB \n") try await model.addStreamToQueue(episode.absoluteString) require(model.queue.items.map(\.url) == [first, second, episode] && model.queue.items[1].displayName == "Sidebar second" && model.queue.currentID == selected && model.state.revision == revision && model.state.position == position && model.state.status == PLAYER_PAUSED, "fetched YouTube and Miruro additions preserve paused playback, order and titles") for text in ["not a URL", "file:///tmp/video.mp4"] { do { try await model.addStreamToQueue(text) fatalError("FAIL: invalid sidebar URL accepted") } catch {} } do { try await model.addStreamToQueue(failedURL.absoluteString) fatalError("FAIL: failed extraction accepted") } catch {} require(model.queue.items.map(\.url) == [first, second, episode] && model.queue.currentID == selected, "invalid URLs and failed extraction leave queue untouched") require(model.setPlayback(playing: true), "resume before sidebar addition") try await until("sidebar resume acknowledged", model: model) { model.state.status == PLAYER_PLAYING } let playingRevision = model.state.revision try await model.addStreamToQueue(second.absoluteString) require(model.state.revision == playingRevision && model.queue.currentID == selected && model.state.status == PLAYER_PLAYING, "addition does not restart playing media") let cancelled = Task { try await model.addStreamToQueue(cancelledURL.absoluteString) } 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: [], 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") } static func segmentSkips(_ model: PlayerModel, clock: Clock, file: URL) async throws { let opening = MiruroInterval(kind: .intro, start: 0.5, end: 1.5, episode: 1) 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") clock.now = 109.999 require(model.segmentToSkip == opening, "opening remains available before ten seconds") clock.now = 110 require(model.segmentToSkip == nil && !model.skipSegment(), "offer expires at ten seconds while paused") require(model.skipOfferRemainingFraction == 0, "expired offer has no remaining background") clock.now = 111 try await Task.sleep(for: .milliseconds(100)) require(model.segmentToSkip == nil, "polling does not reoffer ignored opening") require(model.seekRelative(2.25 - model.state.position), "enter ending") require(model.segmentToSkip == nil, "pending seek cannot offer stale opening") try await until("ending offer", model: model) { !model.state.seek_pending && model.segmentToSkip == ending } require(model.skipSegment(), "ending skip accepted") require(model.segmentToSkip == nil, "accepted skip immediately removes button") try await until("ending skip reaches exact media end", model: model) { !model.state.seek_pending && abs(model.state.position - ending.end) < 0.1 } require(!model.effectivePlaying, "paused skip does not start playback") require(model.seekRelative(0.75 - model.state.position), "revisit ignored opening") try await until("opening revisit acknowledged", model: model) { !model.state.seek_pending && abs(model.state.position - 0.75) < 0.1 } 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 } require(model.setPlayback(playing: false), "pause reopened media") try await until("reopened media paused", model: model) { model.state.status == PLAYER_PAUSED } require(model.seekRelative(0.75 - model.state.position), "reenter opening after reopen") 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 } require(model.setPlayback(playing: true), "resume inside opening") try await until("playing opening offer", model: model) { model.state.status == PLAYER_PLAYING && !model.state.buffering && model.state.position > 0.85 && model.segmentToSkip == opening } require(model.skipSegment(), "opening skip accepted") try await until("opening skip reaches segment end", model: model) { !model.state.seek_pending && abs(model.state.position - opening.end) < 0.1 } require(model.segmentToSkip == nil, "segment end is outside opening") require(model.effectivePlaying, "opening skip preserves playing intent") print("PASS: timed opening/ending offers, dismissal, pending seeks, exact-end skips and reopen reset") } static func automaticSegmentSkips(file: URL) async throws { for seekTarget: Double? in [nil, 0.75, 0.25] { let suite = "terebi.segment-autoskip." + UUID().uuidString let defaults = UserDefaults(suiteName: suite)! defer { defaults.removePersistentDomain(forName: suite) } let model = PlayerModel(defaults: defaults, configuration: .init(openingEndingAutoSkip: true), miruroMetadataResolver: { _, _ in MiruroMetadata(title: nil, intervals: []) }, skipClock: { 100 }) defer { model.shutdown() } model.openResolvedSource(file) try await until("automatic segment fixture plays", model: model) { model.state.status == PLAYER_PLAYING && model.canSeek && !model.isBusy } let opening = MiruroInterval(kind: .intro, start: 0.5, end: 1.5, episode: 1) let ending = MiruroInterval(kind: .outro, start: 2, end: model.duration, episode: 1) if let seekTarget { require(model.setPlayback(playing: false), "pause before intentional opening seek") try await until("automatic segment fixture pauses", model: model) { model.state.status == PLAYER_PAUSED } if seekTarget == 0.25 { model.isSeeking = true model.seekPosition = seekTarget model.finishSeeking() } else { require(model.seekRelative(seekTarget - model.state.position), "seek into opening") } try await until("intentional opening seek settles", model: model) { !model.state.seek_pending && abs(model.state.position - seekTarget) < 0.1 } } model.supplyMetadataForCheck(.init(title: nil, intervals: [opening, ending]), episode: 1) if let seekTarget { require(abs(model.seekPosition - seekTarget) < 0.1 && model.state.status == PLAYER_PAUSED, "late metadata does not autoskip paused playback") require(model.setPlayback(playing: true), "resume deliberately selected opening") try await until("deliberately selected opening plays", model: model) { model.state.position >= 1 } require(model.state.position < opening.end && model.skipButtonTitle == "Skip Opening", "opening autoskip respects seeking into or just before it and keeps the manual button") continue } try await until("opening autoskip queues its exact end", model: model) { model.seekPosition == opening.end } try await until("native player acknowledges opening autoskip", model: model) { !model.state.seek_pending && model.state.position >= opening.end && model.state.status == PLAYER_PLAYING } try await until("ending autoskip queues exact media end before playing through ending", model: model) { model.seekPosition == ending.end && model.state.position < ending.end - 0.5 } try await until("native player acknowledges ending autoskip", model: model) { !model.state.seek_pending && abs(model.state.position - ending.end) < 0.1 } } print("PASS: native opening/ending autoskip, paused/late metadata and deliberate relative/slider seek protection") } }