refactor: concentrate playback transition ownership
This commit is contained in:
@@ -75,7 +75,80 @@ struct AutomaticQualityPlaybackChecks {
|
||||
qualityClock: { clock.now })
|
||||
require(manual.automaticYouTubeQuality, "manual session override does not replace startup configuration")
|
||||
manual.shutdown()
|
||||
// Control only the external extractor; media decoding and all model transitions stay real.
|
||||
guard let controlPath = ProcessInfo.processInfo.environment["TEREBI_QUALITY_CONTROL"] else {
|
||||
require(false, "isolated extraction controls are available")
|
||||
return
|
||||
}
|
||||
let control = URL(fileURLWithPath: controlPath, isDirectory: true)
|
||||
func mark(_ name: String) {
|
||||
require(FileManager.default.createFile(atPath: control.appendingPathComponent(name).path,
|
||||
contents: Data()), "publish extraction control " + name)
|
||||
}
|
||||
func marked(_ name: String) -> Bool {
|
||||
FileManager.default.fileExists(atPath: control.appendingPathComponent(name).path)
|
||||
}
|
||||
model.setYouTubeQuality(720)
|
||||
until(model, "manual selection opens real healthy 720p media") {
|
||||
model.youtubeHeight == 720 && model.canChangeYouTubeQuality && model.state.status == PLAYER_PLAYING &&
|
||||
!model.state.seek_pending && (model.bufferedRange.map { $0.upperBound - model.state.position >= 20 } ?? false)
|
||||
}
|
||||
let selectedTitle = model.fileName
|
||||
let selectedQuality = model.youtubeQuality
|
||||
mark("gate-1080")
|
||||
clock.now = 3000
|
||||
model.setAutomaticYouTubeQuality(true)
|
||||
polled(model)
|
||||
let healthyPosition = model.state.position
|
||||
until(model, "healthy native playback continues before the controlled upshift") {
|
||||
model.state.position > healthyPosition + 0.02
|
||||
}
|
||||
clock.now = 3060
|
||||
until(model, "automatic upshift reaches the selected extraction gate") {
|
||||
marked("entered-1080") && model.resolvingYouTube
|
||||
}
|
||||
require(model.setPlayback(playing: false), "pause is accepted during automatic extraction")
|
||||
mark("release-1080")
|
||||
until(model, "cancelled extraction finishes and native pause is acknowledged") {
|
||||
marked("finished-1080") && !model.resolvingYouTube && model.canTogglePlayback &&
|
||||
model.state.status == PLAYER_PAUSED && !model.state.seek_pending
|
||||
}
|
||||
let cancelledPosition = model.state.position
|
||||
for _ in 0..<3 {
|
||||
polled(model)
|
||||
require(model.state.status == PLAYER_PAUSED && abs(model.state.position - cancelledPosition) < 0.05 &&
|
||||
model.state.has_video && model.state.has_audio &&
|
||||
model.youtubeHeight == 720 && model.youtubeQuality == selectedQuality &&
|
||||
model.fileName == selectedTitle && model.errorText == nil,
|
||||
"late extraction cannot reopen different quality or resume deliberately paused media")
|
||||
}
|
||||
try! FileManager.default.removeItem(at: control.appendingPathComponent("gate-1080"))
|
||||
require(model.seekRelative(37 - model.state.position), "original paused media accepts a nonzero seek")
|
||||
until(model, "native original media acknowledges the seek") {
|
||||
model.canChangeYouTubeQuality && model.state.status == PLAYER_PAUSED &&
|
||||
!model.state.seek_pending && abs(model.state.position - 37) < 0.05
|
||||
}
|
||||
mark("fail-1080")
|
||||
for playing in [false, true] {
|
||||
require(model.setPlayback(playing: playing), "original playback state is selectable before quality failure")
|
||||
until(model, "original native playback reaches the requested state before quality failure") {
|
||||
model.canChangeYouTubeQuality && model.state.status == (playing ? PLAYER_PLAYING : PLAYER_PAUSED) &&
|
||||
(!playing || model.state.position > 37.1)
|
||||
}
|
||||
let originalPosition = model.state.position
|
||||
model.setYouTubeQuality(1080)
|
||||
until(model, "failed manual quality switch restores the original native playback state") {
|
||||
model.errorText != nil && !model.resolvingYouTube && model.canTogglePlayback &&
|
||||
model.state.status == (playing ? PLAYER_PLAYING : PLAYER_PAUSED) &&
|
||||
(!playing || model.state.position > originalPosition + 0.1)
|
||||
}
|
||||
require(model.state.has_video && model.state.has_audio && model.youtubeHeight == 720 &&
|
||||
model.youtubeQuality == selectedQuality && model.fileName == selectedTitle &&
|
||||
model.state.position >= originalPosition - 0.05 &&
|
||||
model.state.position < originalPosition + (playing ? 2 : 0.05),
|
||||
"failed manual quality switch retains original selected media and playback progress")
|
||||
}
|
||||
model.closeFile()
|
||||
print("PASS native adaptive playback: default-off/stalled downshift/healthy upshift/progress preservation/current+default manual override/session-only settings")
|
||||
print("PASS native adaptive playback: default-off/stalled downshift/healthy upshift/progress preservation/current+default manual override/session-only settings/cancelled extraction pause/failed manual quality playing+paused preservation")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,11 +42,30 @@ def main():
|
||||
metadata = root / "info.json"
|
||||
metadata.write_text(json.dumps(info))
|
||||
wrapper = root / "yt-dlp"
|
||||
wrapper.write_text("#!/bin/bash\nset -eu\nargs=(\"$@\")\nexec " + shlex.quote(extractor)
|
||||
control = root / "control"
|
||||
control.mkdir()
|
||||
wrapper.write_text("#!/bin/bash\nset -eu\nargs=(\"$@\")\n"
|
||||
+ "control=" + shlex.quote(str(control)) + "\n"
|
||||
+ 'if [[ "$*" == *"[height=1080]"* ]]; then\n'
|
||||
+ ' if [[ -f "$control/fail-1080" ]]; then\n'
|
||||
+ ' echo "Fixture extraction deliberately failed" >&2; exit 1\n'
|
||||
+ ' fi\n'
|
||||
+ ' if [[ -f "$control/gate-1080" ]]; then\n'
|
||||
+ " " + shlex.quote(extractor) + " --load-info-json " + shlex.quote(str(metadata))
|
||||
+ ' "${args[@]:0:${#args[@]}-2}" > "$control/output-1080.json"\n'
|
||||
+ ' trap \': > "$control/finished-1080"; exit 0\' TERM\n'
|
||||
+ ' : > "$control/entered-1080"\n'
|
||||
+ ' while [[ ! -f "$control/release-1080" ]]; do sleep 0.01; done\n'
|
||||
+ ' cat "$control/output-1080.json" || true\n'
|
||||
+ ' : > "$control/finished-1080"\n'
|
||||
+ ' exit 0\n'
|
||||
+ ' fi\n'
|
||||
+ 'fi\nexec ' + shlex.quote(extractor)
|
||||
+ " --load-info-json " + shlex.quote(str(metadata))
|
||||
+ ' "${args[@]:0:${#args[@]}-2}"\n')
|
||||
wrapper.chmod(0o755)
|
||||
environment = dict(os.environ, PATH=str(root) + os.pathsep + os.environ.get("PATH", ""))
|
||||
environment = dict(os.environ, PATH=str(root) + os.pathsep + os.environ.get("PATH", ""),
|
||||
TEREBI_QUALITY_CONTROL=str(control))
|
||||
try:
|
||||
result = subprocess.run([str(args.binary.resolve())], env=environment, timeout=90, check=False)
|
||||
except subprocess.TimeoutExpired:
|
||||
|
||||
@@ -63,13 +63,24 @@ struct SystemPlaybackChecks {
|
||||
require(center.nowPlayingInfo?[MPNowPlayingInfoPropertyPlaybackRate] as? Double == 0,
|
||||
"paused system timeline does not extrapolate")
|
||||
let playbackKey = try! KeyChord("space")
|
||||
let oppositeIntentRevision = model.state.revision
|
||||
require(model.handleKey(playbackKey) && model.effectivePlaying,
|
||||
"play binding records intent before backend acknowledgement")
|
||||
require(model.handleKey(playbackKey) && !model.effectivePlaying,
|
||||
"same binding resolves pause against pending play intent")
|
||||
require(model.handleKey(playbackKey, isRepeat: true) && !model.effectivePlaying,
|
||||
"held paired shortcut is consumed without oscillating")
|
||||
until("rapid opposite intents settle to paused") { model.state.status == PLAYER_PAUSED }
|
||||
until("rapid opposite intents acknowledge play before the final pause") {
|
||||
model.state.revision != oppositeIntentRevision && model.state.status == PLAYER_PLAYING
|
||||
}
|
||||
let playingRevision = model.state.revision
|
||||
until("rapid opposite intents settle to an acknowledged native pause") {
|
||||
model.state.revision != playingRevision && model.state.status == PLAYER_PAUSED &&
|
||||
!model.effectivePlaying && center.playbackState == .paused
|
||||
}
|
||||
require(center.nowPlayingInfo?[MPNowPlayingInfoPropertyPlaybackRate] as? Double == 0,
|
||||
"latest pause intent stops the system timeline after native acknowledgement")
|
||||
let settledPosition = model.state.position
|
||||
require(!model.perform(.pause) && !model.effectivePlaying,
|
||||
"explicit pause cannot resume an already paused player")
|
||||
|
||||
@@ -87,7 +98,7 @@ struct SystemPlaybackChecks {
|
||||
require(model.seekRelative(1), "relative seek accepted")
|
||||
until("paused seek acknowledged") {
|
||||
model.state.status == PLAYER_PAUSED && !model.state.seek_pending &&
|
||||
abs(model.state.position - pausedPosition - 1) < 0.05
|
||||
abs(model.state.position - settledPosition - 1) < 0.05
|
||||
}
|
||||
require(model.seekRelative(-100), "backward boundary seek accepted")
|
||||
until("backward seek clamps at start") { !model.state.seek_pending && model.state.position == 0 }
|
||||
@@ -114,6 +125,64 @@ struct SystemPlaybackChecks {
|
||||
"accepted close releases system playback immediately")
|
||||
require(!commands.togglePlayPauseCommand.isEnabled && !commands.nextTrackCommand.isEnabled,
|
||||
"closed source cannot receive system playback or seek commands")
|
||||
|
||||
let localURL = directory.appendingPathComponent("av.mp4")
|
||||
let progress = PlaybackProgress(defaults: defaults)
|
||||
let savedPosition = seekableState.duration / 2
|
||||
progress.save(savedPosition, for: localURL)
|
||||
var openRevision = model.state.revision
|
||||
model.open(localURL)
|
||||
until("local saved position is restored before native autoplay") {
|
||||
guard model.state.revision != openRevision else { return false }
|
||||
if model.state.status == PLAYER_PLAYING && !model.state.seek_pending {
|
||||
require(model.state.position >= savedPosition,
|
||||
"local autoplay never acknowledges playback before the saved position")
|
||||
return model.state.position < savedPosition + 0.5
|
||||
}
|
||||
require(progress.position(for: localURL) == savedPosition,
|
||||
"pre-restoration snapshots cannot overwrite saved history")
|
||||
return false
|
||||
}
|
||||
require(model.setPlayback(playing: false), "restored local playback can pause")
|
||||
until("restored local pause acknowledged") { model.state.status == PLAYER_PAUSED && !model.state.seek_pending }
|
||||
let restoredPosition = model.state.position
|
||||
model.closeFile()
|
||||
require(progress.position(for: localURL) == restoredPosition,
|
||||
"close persists acknowledged restored position")
|
||||
openRevision = model.state.revision
|
||||
model.open(localURL)
|
||||
until("close and reopen resumes the persisted native position") {
|
||||
guard model.state.revision != openRevision && model.state.status == PLAYER_PLAYING &&
|
||||
!model.state.seek_pending else { return false }
|
||||
require(model.state.position >= restoredPosition,
|
||||
"reopened local autoplay starts at acknowledged persisted progress")
|
||||
return model.state.position < restoredPosition + 0.5
|
||||
}
|
||||
model.closeFile()
|
||||
let invalidPosition = seekableState.duration + 1
|
||||
progress.save(invalidPosition, for: localURL)
|
||||
openRevision = model.state.revision
|
||||
model.open(localURL)
|
||||
until("duration-exceeded bookmark is discarded and native playback advances") {
|
||||
model.state.revision != openRevision && model.state.status == PLAYER_PLAYING &&
|
||||
!model.state.seek_pending && model.state.position > 0.1 && model.state.position < savedPosition
|
||||
}
|
||||
require((progress.position(for: localURL) ?? 0) < seekableState.duration,
|
||||
"duration-exceeded bookmark is removed from exposed history")
|
||||
model.closeFile()
|
||||
let replacementPosition = progress.position(for: localURL)!
|
||||
require(replacementPosition > 0 && replacementPosition < savedPosition,
|
||||
"close replaces invalid history with acknowledged playback progress")
|
||||
openRevision = model.state.revision
|
||||
model.open(localURL)
|
||||
until("reopening no longer repeats a duration-exceeded restoration") {
|
||||
guard model.state.revision != openRevision && model.state.status == PLAYER_PLAYING &&
|
||||
!model.state.seek_pending else { return false }
|
||||
require(model.state.position >= replacementPosition,
|
||||
"replacement bookmark restores before reopened autoplay")
|
||||
return model.state.position < replacementPosition + 0.5
|
||||
}
|
||||
model.closeFile()
|
||||
model.open(directory.appendingPathComponent("hdr.mkv"))
|
||||
until("decode error acknowledged") { model.state.status == PLAYER_ERROR && !model.isBusy }
|
||||
require(center.nowPlayingInfo == nil && !commands.playCommand.isEnabled,
|
||||
|
||||
Reference in New Issue
Block a user