import AppKit import MediaPlayer import TerebiConfiguration @main @MainActor struct SystemPlaybackChecks { static func require(_ condition: @autoclosure () -> Bool, _ message: String) { if !condition() { fatalError("FAIL: " + message) } } static func until(_ message: String, _ condition: () -> Bool) { let deadline = ContinuousClock.now.advanced(by: .seconds(10)) while !condition(), ContinuousClock.now < deadline { RunLoop.main.run(until: Date(timeIntervalSinceNow: 0.01)) } require(condition(), message) } static func main() { _ = NSApplication.shared NSApp.setActivationPolicy(.prohibited) let suite = "terebi.system-playback-check.\(UUID().uuidString)" let defaults = UserDefaults(suiteName: suite)! defer { defaults.removePersistentDomain(forName: suite) } let initialModel = PlayerModel(defaults: defaults) require(!initialModel.showsBufferInfo, "buffer readout defaults off before playback") initialModel.setShowsBufferInfo(true) require(initialModel.showsBufferInfo && initialModel.controlsVisible, "buffer readout can be enabled before playback and reveals controls") initialModel.shutdown() let restoredModel = PlayerModel(defaults: defaults) require(!restoredModel.showsBufferInfo, "session opt-in does not survive model recreation") restoredModel.shutdown() let configuredModel = PlayerModel(defaults: defaults, configuration: PlayerConfiguration(showBufferInfo: true, automaticQuality: true, snapToEdges: false)) require(configuredModel.showsBufferInfo && configuredModel.automaticYouTubeQuality && !configuredModel.compactWindow.snappingEnabled, "configuration initializes all three controls") configuredModel.setShowsBufferInfo(false) configuredModel.setAutomaticYouTubeQuality(false) configuredModel.compactWindow.snappingEnabled = true configuredModel.shutdown() let model = PlayerModel(defaults: defaults) defer { model.shutdown() } require(!model.showsBufferInfo && !model.automaticYouTubeQuality && model.compactWindow.snappingEnabled, "session overrides never replace built-in settings") model.setVolume(0) let directory = URL(fileURLWithPath: CommandLine.arguments[1], isDirectory: true) let center = MPNowPlayingInfoCenter.default() let commands = MPRemoteCommandCenter.shared() let window = NSWindow(contentRect: NSRect(x: -10_000, y: -10_000, width: 640, height: 360), styleMask: [.titled, .closable, .miniaturizable, .resizable], backing: .buffered, defer: false) window.isReleasedWhenClosed = false defer { window.close() } model.attachWindow(window) model.open(directory.appendingPathComponent("anamorphic.mp4")) require(model.isBusy && model.perform(.enterCompact) && model.isCompact, "compact entry is available before native media opening acknowledges") until("decoded anamorphic metadata replaces provisional compact geometry") { model.state.status == PLAYER_PLAYING && model.isCompact && !model.compactWindow.isTransitioning && abs(window.frame.width / window.frame.height - 8.0 / 3.0) < 0.01 } model.closeFile() require(!model.isCompact && !model.compactWindow.isTransitioning, "closing loading/loaded media restores immediately") until("anamorphic source closes before the ordinary playback checks") { model.state.status == PLAYER_EMPTY } require(!model.canPerform(.enterCompact), "empty playback does not enable compact entry") model.open(directory.appendingPathComponent("av.mp4")) if !NSWorkspace.shared.isVoiceOverEnabled { model.setPointerInsideWindow(false) require(!model.controlsVisible, "pointer exit hides playback chrome while media is loading") model.setPointerInsideWindow(true) } until("open starts authoritative playback") { model.canTogglePlayback && model.state.status == PLAYER_PLAYING } require(center.playbackState == .playing, "playing publishes macOS playback ownership") require(model.setPlayback(playing: false), "explicit pause accepted") until("pause acknowledged") { model.state.status == PLAYER_PAUSED } let pausedPosition = model.state.position if !NSWorkspace.shared.isVoiceOverEnabled { model.setControlsHovered(true) model.setTitleHovered(true) model.setPointerInsideWindow(false) require(!model.controlsVisible, "pointer exit hides paused playback chrome despite stale hover and inactive window") model.setControlsHovered(false) model.setTitleHovered(false) model.updateWindowState() require(!model.controlsVisible, "late hover/focus updates cannot reshow chrome while the pointer is outside") model.revealControls(forKeyboardNavigation: true) model.revealControls() require(model.controlsVisible, "Tab navigation can reveal controls without moving the pointer") model.setPointerInsideWindow(false) require(!model.controlsVisible, "another pointer exit dismisses keyboard-revealed controls") model.setPointerInsideWindow(true) require(model.controlsVisible, "pointer reentry reveals paused playback chrome") } require(model.setPlayback(playing: false), "repeated pause accepted") until("idempotent pause does not restart playback") { model.state.status == PLAYER_PAUSED && center.playbackState == .paused } require(model.state.position == pausedPosition, "repeated pause preserves position") 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 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") let seekableState = model.state var nonseekableState = seekableState nonseekableState.can_seek = false model.applyState(nonseekableState) require(commands.playCommand.isEnabled && !commands.nextTrackCommand.isEnabled && !commands.skipBackwardCommand.isEnabled && !model.seekRelative(1), "nonseekable input retains playback but rejects system seeking") model.applyState(seekableState) require(commands.nextTrackCommand.isEnabled, "seekability changes re-enable commands even when playback metadata is unchanged") require(model.seekRelative(1), "relative seek accepted") until("paused seek acknowledged") { model.state.status == PLAYER_PAUSED && !model.state.seek_pending && 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 } require(!model.seekRelative(.nan), "nonfinite external seek rejected") require(model.setPlayback(playing: true), "explicit play accepted") until("play resumes advancing timeline") { model.state.status == PLAYER_PLAYING && model.state.position > 0.1 } require(model.setPlayback(playing: true), "repeated play accepted") until("repeated play keeps playing") { model.state.status == PLAYER_PLAYING && model.state.position > 0.2 } // Closing must hide the last polled retained range before the next state poll. var retainedSnapshot = model.state retainedSnapshot.buffered_start = retainedSnapshot.position retainedSnapshot.buffered_end = min(retainedSnapshot.duration, retainedSnapshot.position + 1) model.applyState(retainedSnapshot) require(model.bufferedRange != nil, "loaded selection exposes retained media") let retainedRange = model.bufferedRange model.setShowsBufferInfo(true) require(model.bufferedRange == retainedRange, "enabling buffer readout preserves retained media") model.setShowsBufferInfo(false) require(model.bufferedRange == retainedRange, "disabling buffer readout preserves retained media") model.closeFile() require(model.bufferedRange == nil, "accepted close immediately hides a stale retained-media snapshot") require(center.nowPlayingInfo == nil && center.playbackState == .stopped, "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, "failed source does not claim playback keys") model.shutdown() require(center.nowPlayingInfo == nil && !commands.pauseCommand.isEnabled, "shutdown releases system playback") print("System playback checks passed") } }