feat: offer timed opening and ending skips
This commit is contained in:
@@ -211,7 +211,7 @@ struct CastPlaybackCheck {
|
||||
let suite = "terebi.cast-playback-check." + UUID().uuidString
|
||||
let defaults = UserDefaults(suiteName: suite)!
|
||||
defer { defaults.removePersistentDomain(forName: suite) }
|
||||
let model = PlayerModel(defaults: defaults)
|
||||
let model = PlayerModel(defaults: defaults, skipClock: { 100 })
|
||||
defer { model.shutdown() }
|
||||
if CommandLine.arguments.count == 5 {
|
||||
if CommandLine.arguments[3] == "queue" {
|
||||
@@ -249,6 +249,17 @@ struct CastPlaybackCheck {
|
||||
require(model.setPlayback(playing: false), "remote pause accepted")
|
||||
try await until(model, "receiver pause acknowledged") { model.state.status == PLAYER_PAUSED && model.playbackIntent == nil }
|
||||
requirePlaybackPower(system: false, display: false, "receiver pause permits sleep")
|
||||
let opening = MiruroInterval(kind: .intro, start: 23, end: 24.25, episode: 1)
|
||||
model.supplyMetadataForCheck(MiruroMetadata(title: nil, intervals: [opening]), episode: 1)
|
||||
require(model.seekRelative(24 - model.state.position), "enter opening on receiver")
|
||||
try await until(model, "receiver offers opening only after acknowledging seek") {
|
||||
!model.state.seek_pending && model.segmentToSkip == opening
|
||||
}
|
||||
require(model.skipSegment(), "segment skip queues receiver seek")
|
||||
try await until(model, "receiver acknowledges segment end without starting playback") {
|
||||
!model.state.seek_pending && abs(model.state.position - 24.25) < 0.15 &&
|
||||
model.state.status == PLAYER_PAUSED && model.segmentToSkip == nil
|
||||
}
|
||||
model.stopCasting()
|
||||
try await until(model, "local decoder restores receiver position paused") {
|
||||
model.castDevice == nil && model.state.status == PLAYER_PAUSED && !model.state.seek_pending &&
|
||||
|
||||
@@ -3,6 +3,7 @@ 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) }
|
||||
}
|
||||
@@ -10,7 +11,7 @@ struct ResolvedPlaybackCheck {
|
||||
let deadline = ContinuousClock.now.advanced(by: .seconds(45))
|
||||
while !predicate(), ContinuousClock.now < deadline {
|
||||
if model.state.status == PLAYER_ERROR || model.commandError != nil {
|
||||
fatalError("FAIL: " + (model.errorText ?? "Playback failed"))
|
||||
fatalError("FAIL: \(message): " + (model.errorText ?? "Playback failed"))
|
||||
}
|
||||
try await Task.sleep(for: .milliseconds(10))
|
||||
}
|
||||
@@ -43,7 +44,9 @@ struct ResolvedPlaybackCheck {
|
||||
let suite = "terebi.resolved-check." + UUID().uuidString
|
||||
let defaults = UserDefaults(suiteName: suite)!
|
||||
defer { defaults.removePersistentDomain(forName: suite) }
|
||||
let model = PlayerModel(defaults: defaults, progressFile: CommandLine.arguments.count > 2 ? CommandLine.arguments[2] : nil)
|
||||
let clock = Clock()
|
||||
let model = PlayerModel(defaults: defaults, progressFile: CommandLine.arguments.count > 2 ? CommandLine.arguments[2] : nil,
|
||||
skipClock: { clock.now })
|
||||
defer { model.shutdown() }
|
||||
if input.hasPrefix("https://") { model.openStream(input) }
|
||||
else { model.openResolvedSource(file) }
|
||||
@@ -90,5 +93,70 @@ struct ResolvedPlaybackCheck {
|
||||
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) }
|
||||
}
|
||||
|
||||
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.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")
|
||||
clock.now = 102.999
|
||||
require(model.segmentToSkip == opening, "opening remains available before three seconds")
|
||||
clock.now = 103
|
||||
require(model.segmentToSkip == nil && !model.skipSegment(), "offer expires at three seconds while paused")
|
||||
clock.now = 104
|
||||
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")
|
||||
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
|
||||
}
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user