# terebi A native macOS player for local media, YouTube videos, and resolved Miruro streams, with compact mode, saved playback positions, and Chromecast support. ## Build and run Requires macOS 15+, Xcode command-line tools with Swift 6, Python 3.9+, and these build tools: ```sh brew install pkg-config meson ninja # Intel Macs also need: brew install nasm make make run ``` The first build downloads pinned dependencies. Build natively on Apple Silicon or Intel; cross-architecture and Rosetta builds are not supported. The resulting `.build/terebi.app` is self-contained: copy the whole app to a compatible Mac of the same architecture. No separate FFmpeg, Python, or yt-dlp installation is needed to run it. ### Install ```sh make install # builds a release and installs under ~/.local export PATH="$HOME/.local/bin:$PATH" terebi --help ``` Use `make install PREFIX="/custom/path"` to change the prefix, or `make release` for just `.build/release/terebi.app`. Installation places the app in `lib/terebi/terebi.app` and its CLI symlink in `bin/terebi`. Quit running players before updating. Builds are ad-hoc signed for personal use, not notarized. Bundled licenses and source archives are in the app's Resources; public binary redistribution requires a separate dependency/license audit, including the GPLv3+ standalone yt-dlp bundle. ## Usage Open a file with **⌘O**, drag it onto the window, or use Finder's **Open With**. Open YouTube with **⇧⌘O** or drop a video link. Media starts automatically and resumes its saved position; finished videos restart from the beginning. Live and audio-only media do not save progress. | Action | Default shortcut | | --- | --- | | Play / pause | Space or K | | Seek backward / forward 5 seconds | ← / → | | Seek backward / forward 10 seconds | J / L | | Step one frame backward / forward (pauses playback) | , / . | | Volume down / up | ↓ / ↑ | | Mute / unmute | M | | Fullscreen | F or ⌃⌘F; Esc to leave | | Compact mode | P or ⌃⌘P | | Always on top | ⇧P | | Close media | ⇧⌘W | | Quit | Q or ⌘Q | Compact mode keeps playback in the same floating window; drag to move it and double-click to restore. The titlebar pin keeps the normal window on top. macOS media keys also control playback; previous/next seek five seconds rather than changing tracks. Playback controls stay anchored at the bottom center of the video and cannot be dragged. ### YouTube Playback defaults to the best available SDR resolution up to **1080p**. Use **gear → Quality** to select another available resolution, including 4K, or enable automatic quality (off by default). **Show Buffer Info** displays the retained network buffer. Only individual videos are supported, not playlists or channels. Private, age-restricted, region-blocked, or sign-in-required videos may fail; browser cookies are not read. The extractor is bundled, so YouTube compatibility updates require rebuilding/reinstalling terebi, not updating an external yt-dlp. ### Chromecast Open media, select the **Cast** icon or **Playback → Cast to Device → Find Cast Devices…**, and choose a receiver on the same LAN. Allow macOS Local Network access and incoming firewall connections. Keep the Mac awake and connected: it relays the media to the receiver. Existing controls operate remote playback. **Play on This Mac** returns to local playback at the receiver's position. Opening different media ends casting. Frame stepping and YouTube quality changes are local-only; live Cast playback is not seekable. Cast output is limited to 1080p/30 fps. The screen-and-triangle icon does not provide AirPlay support. External Cast subtitles use white sans-serif text with a black outline, transparent background and no enclosing window. Font scale currently uses the receiver default (1.0), pending physical-TV tuning. Language selection and Off remain independent of video delivery; no subtitle burn-in is used. Cast trusts the selected LAN endpoint's self-signed certificate; use it only on a trusted network. Receiver captions and perceived A/V synchronization still need physical-device verification. ## Command line Before installation, use `.build/terebi` instead of `terebi`: ```sh terebi "/path/to/video.mp4" terebi 'https://youtu.be/jNQXAC9IVRw' terebi --new-instance --url 'https://www.youtube.com/watch?v=jNQXAC9IVRw' terebi --new-instance --no-config "/path/to/video.mp4" terebi -- -video.mp4 terebi --help ``` The CLI opens one file or video, reusing the running player by default. With no arguments it opens or activates the player without replacing media. Put `--new-instance` and `--no-config` before media arguments. Launch success means macOS accepted the request, not that playback succeeded; playback errors appear in the window. ### Resolved sources and progress reporting Miruro playback requires a freshly resolved source document from an external caller such as Tubular; terebi does not discover episodes or accept Miruro page URLs directly. ```sh terebi --new-instance --source-file '/private/session/source.json' \ --progress-file '/private/session/progress.jsonl' ``` Both options require `--new-instance`; `--progress-file` also works with YouTube. Use a private session directory and a nonexistent report destination. Keep the source file present and unchanged during playback; the caller owns cleanup. Reports contain playback facts, not watched-state decisions. See the [integration contract](docs/miruro-playback-handoff.md) for source JSON, resource-header/origin restrictions, and progress schemas. Tubular still needs to adopt Miruro's version-2 progress identity. ## Configuration The optional config is `$XDG_CONFIG_HOME/terebi/config.toml` when `XDG_CONFIG_HOME` is absolute, otherwise `~/.config/terebi/config.toml`. Omitted settings use defaults. To print all supported settings and key actions: ```sh terebi --default-config ``` Example partial configuration: ```toml [playback] automatic_quality = true [ui] buffer_info = true compact_snap_to_edges = true compact_title_progress = "percentage" # "time", "percentage", or "none" [keys] play = "space" pause = "space" quit = "cmd+q" quality_2160p = "ctrl+4" ``` Shortcut strings or arrays replace an action's defaults; `[]` unbinds it. Modifiers use `cmd`, `ctrl`, `alt`/`option`, and `shift`, joined with `+`. A chord may be shared by inverse actions such as play/pause, but not unrelated actions. Invalid settings and conflicting shortcuts are errors. Settings load once at startup. Menu changes are session-only and never write the file; start a fresh player to apply edits. `--no-config` uses built-in settings on a fresh launch, not in an already-running player, and does not clear saved progress. ## Limits - SDR output only; PQ/HLG HDR is rejected. No playlists, embedded/YouTube captions, manual audio-track selection, rotation, or deinterlacing. External resolved-source WebVTT subtitles are supported. - H.264, HEVC, and VP9 prefer VideoToolbox decoding with software fallback; other codecs, including AV1, use software. Backward frame stepping can be slow on long-GOP or unindexed media. - Network buffering is bounded and memory-only; live/non-seekable sources disable seeking and frame stepping. Expired stream URLs are not automatically refreshed. - Audio-device changes during playback are not handled. ## Development checks ```sh brew install ffmpeg # fixture generation only; not required to build or run terebi make check ``` The full suite requires a graphical macOS session and a working audio device; it launches isolated app instances and plays short test audio. Targeted `check-*` targets are listed in the [Makefile](Makefile). Visual appearance, physical media-key routing, and receiver behavior still need manual checks. `make benchmark-playback` measures cold source startup, cached/uncached seeks, and sustained native/Cast playback with repeatable loopback fixtures. It requires the same macOS session/audio device and fixture-generation FFmpeg; results exclude live extraction, WAN/TLS, and physical-receiver latency. The [benchmark runner](Checks/playback-benchmark.py) accepts `--compare-native ` for alternating native-only comparisons; add `--seek-work` to isolate cache-miss seeks when both binaries support that mode. Preserve the baseline executable before rebuilding. Playback terminology: [CONTEXT.md](CONTEXT.md).