feat: add a native CLI launcher for local and YouTube playback
Build .build/terebi beside the app. Parse a single local file or YouTube URL, --url, --, and help; reject invalid commands before GUI launch. Resolve the adjacent app through symlinks and launch through NSWorkspace, reusing its process/window and saved progress. Launch the app without media before delivering URL events. Raw media argv suppress SwiftUI's initial singleton window. Own warm document and internet Apple events because SwiftUI's default external-event routing closes that window; retain initial Finder document delivery to create it. Verified actual CLI 0/64/69 exit behavior, PATH/symlink discovery from another directory, cold local/YouTube playback, warm local/YouTube replacement in the same native window/process, resume, Finder-style cold launch, and make all check.
This commit is contained in:
@@ -16,6 +16,24 @@ The local app is `.build/terebi.app`. It links the installed FFmpeg libraries; i
|
||||
|
||||
Open a local file with **⌘O**, drop it onto the window, or use Finder's **Open With**. Successfully opened files start playback automatically, including when replacing the current file. **⇧⌘W** closes the file; closing the window quits and joins the worker.
|
||||
|
||||
## Command line
|
||||
|
||||
`make` also builds `.build/terebi`, a native CLI launcher:
|
||||
|
||||
```sh
|
||||
.build/terebi "/path/to/video.mp4"
|
||||
.build/terebi 'https://youtu.be/jNQXAC9IVRw'
|
||||
.build/terebi --url 'https://www.youtube.com/watch?v=jNQXAC9IVRw'
|
||||
.build/terebi -- -video.mp4
|
||||
.build/terebi --help
|
||||
```
|
||||
|
||||
One local file (including a local `file://` URL) or YouTube video URL is accepted. Paths are relative to the shell's current directory; quote spaces and URL query strings. `--` treats the following argument as a filename. With no arguments, the CLI opens or activates the player without replacing its media. To use `terebi` by name, add the build directory to your shell's PATH: `export PATH="$PWD/.build:$PATH"`.
|
||||
|
||||
The launcher finds `terebi.app` beside its own executable, following symlinks, and uses macOS application/URL delivery to reuse the running player and its existing window. Keep the executable and app bundle together; this is not a standalone distribution. Media opens use the same autoplay and saved-progress behavior as the UI. Relaunch an older running build once to load the new external-open handling.
|
||||
|
||||
Help exits with status **0** without opening the GUI. Invalid arguments, unsupported URLs, missing files, and directories report to stderr and exit **64**; application-launch failures exit **69**. A successful launch exits **0** immediately after macOS accepts the request, not when playback finishes. Later extraction/decoding failures appear in the player window; this is a GUI launcher, not a headless player or remote playback-control interface.
|
||||
|
||||
## YouTube
|
||||
|
||||
YouTube playback optionally requires [yt-dlp](https://github.com/yt-dlp/yt-dlp), its EJS components, and a supported JavaScript runtime. Homebrew provides the extractor and its components; Deno is recommended:
|
||||
@@ -80,6 +98,7 @@ Only implemented actions have controls; there are no placeholder playlist, subti
|
||||
- `Sources/App/PlaybackProgress.swift`: local per-video position persistence and canonical file identities, using injectable UserDefaults. The player model checkpoints authoritative positions and defers resumed autoplay until the saved seek is queued against the ready source.
|
||||
- `Sources/App/CompactWindow.swift`: same-window compact geometry, saved normal window state, asynchronous fullscreen transitions and failure recovery, all-Spaces/floating behavior, magnetic edge snapping, and its persisted preference.
|
||||
- `Sources/App/YouTubeResolver.swift`: YouTube URL and metadata validation, external yt-dlp/runtime discovery, SDR stream selection and headers, bounded nonblocking process output, sanitized extraction errors, and request-local cancellation.
|
||||
- `Sources/CLI/PlayerCommand.swift` and `Sources/CLI/TerebiCLI.swift`: command-line validation/help and native application discovery/launching. Launch the app without media first to establish its singleton window, then deliver the selected URL; the app owns Apple events to prevent SwiftUI's default external-document routing from replacing that window.
|
||||
- `Makefile`: compiler/linker commands and app bundle construction. No generated Xcode project or additional build dependency.
|
||||
|
||||
## Runnable check
|
||||
@@ -100,6 +119,8 @@ Generates short fixtures and exercises the real playback interface and native ou
|
||||
|
||||
`make check-progress` verifies persisted fractional positions, independent local/YouTube identities, symlink aliases, completion clearing, invalid-position rejection, and isolated preference domains. It is included in `make check` and does not touch real playback history.
|
||||
|
||||
`make check-cli` checks relative/escaped paths, option-like filenames, YouTube identity, argument cardinality, unknown options, missing files/directories, non-local file URLs, and unsupported stream URLs. It is included in `make check`.
|
||||
|
||||
Continuous audio uses resampled sample counts for its output timeline; packet timestamp quantization and rounded resampler delay must not insert silence or drop samples between blocks.
|
||||
|
||||
Volume persists across open, seek, and close. Live gain changes ramp over five milliseconds without callback allocation or locking; a stopped output adopts the requested gain before playback starts, so muted startup cannot leak audio.
|
||||
|
||||
Reference in New Issue
Block a user