# Handoff: Miruro playback in Terebi ## Status Terebi now supports resolved Miruro HTTP(S) sources with per-source headers, external WebVTT and stable episode progress. Direct **`ssub` / `sun`** playback was exercised with a freshly resolved source: native video/audio advanced, default English subtitles loaded, and the model's cue text matched expected subtitles after forward and backward acknowledged seeks. The user also reported that visible playback appears to work; this is not a comprehensive visual synchronization sign-off. Tubular launches native episode URLs. Terebi resolves fresh direct sources and independently acquires optional descriptive titles and opening/ending metadata. Metadata never gates source playback: missing or invalid enrichment retains a generic title and no bands. ## Ownership - **Tubular:** catalog/detail browsing, actual returned episode selection, collections, private reporting sessions, cleanup and watched-state decisions. - **Terebi:** episode-URL validation, fresh source resolution, headers/subtitles, titles, optional timeline bands, stable-identity resume and progress reporting. Native source resolution selects direct **`ssub` / `sun`**. Other providers, iframe embedding, cookies, impersonation and protection-bypass machinery are not supported. ## Launch contract ```sh terebi --new-instance \ --url 'https://barelystarted.miruro.tv/watch/OPAQUE_ID/episode?ep=1' \ --progress-file '/private/session/progress.jsonl' ``` The slug `episode` is deliberately non-identifying. Use the returned episode number, not the absolute episode, array index or an external catalog ID. Slug/title changes never change resume or progress identity. Create a private reporting-session directory (`0700`) and supply a **nonexistent** progress-file destination. Terebi exclusively creates the report (`0600`), rejecting existing destinations and symlinks. CLI success means macOS accepted the launch, not that playback succeeded. Tubular consumes reporting independently of launcher lifetime; launcher exit, a final record or Tubular shutdown does not establish that the player stopped. Preserve retained sessions and restart/partial-line recovery. ### Ordered stream queues ```sh terebi --new-instance --queue-file '/absolute/private/session/queue.json' # Append to the running player, or launch and play if none is running: terebi --enqueue --queue-file '/absolute/private/session/queue.json' ``` The manifest is bounded to 8 MiB, version 1, and 1–4096 entries in playback order: ```json { "version": 1, "items": [ {"url": "https://www.youtube.com/watch?v=jNQXAC9IVRw", "progress_file": "/absolute/private/youtube-session/progress.jsonl"}, {"url": "https://barelystarted.miruro.tv/watch/OPAQUE_ID/episode?ep=1", "progress_file": "/absolute/private/episode-session/progress.jsonl"} ] } ``` Use canonical YouTube video pages or supported Miruro episode pages, never expiring source addresses. Stable identities must be unique even across alternate YouTube forms or different Miruro slugs. Reports must have distinct absolute nonexistent destinations, each under a canonical, nonsymlink, user-owned `0700` directory. Tubular retains each item's existing identity registration/session and stores the manifest in the first session. The CLI validates the complete document before launching; the GUI validates it again before opening any media. Invalid queues fail rather than falling back to a single stream. Queue launch requires either `--new-instance` (replace in a separate player) or `--enqueue` (append to the running player), accepts `--no-config`, and cannot combine with `--url`, `--source-file` or `--progress-file`. `--enqueue` and `--new-instance` are mutually exclusive. The supplied order is preserved (local-file batches still use natural filename order). Existing sidebar reorder/remove/play and previous/next navigation apply. Natural end advances without clearing the queue. Unselected labels are enriched serially at utility priority after playback loading settles; buffering, selection and quality changes cancel background work. yt-dlp extraction supplies page titles; Miruro uses optional watch-route metadata without fetching playback sources. Failures keep the supplied label and do not block later entries or surface playback errors. Results follow item IDs across reordering and cannot rename removed/replaced rows. Only titles are retained: playback resolves fresh on selection/revisits, and canceled/stale results cannot replace a newer selection. Enqueue uses native macOS URL-open delivery to the explicit application, not launch arguments: repeated requests reach the existing process. Both one-item and multi-item requests use the same manifest. The current selection, playback/pause state, timeline, pending resolution and metadata stay intact; an empty player selects and starts the first entry. Original single-file, single-stream and resolved-source playback remain navigable when items are appended. Enqueue never silently deduplicates: any identity still in the queue, or any report-path collision, rejects the complete batch with an error in the player. Removed videos can be re-added with fresh report destinations, including the original standalone video. Old report paths remain reserved for the process lifetime; at most 4096 queue-report destinations may be registered, counting retired sessions. Existing files are never adopted as reporting sessions. Keep manifests and private sessions available until delivery; CLI success acknowledges macOS delivery, not GUI acceptance or playback. Each report is immutably bound to its stable identity in the native decoder. Registration is atomic for a complete batch and can occur during playback without redirecting unrelated current reporting. Re-registering a removed identity drains/closes its old writer before replacing the session; the old report is never overwritten or appended to afterward, and the fresh report is created only when the re-added video begins. Switching entries drains/closes the previous writer before beginning the next; revisits within a session append only after verifying the original private report inode, owner, permissions and link count. An unrelated or replaced file is never reopened as an owned session, and an unregistered identity is never written to a previously selected queue report. Only the active item owns a running writer/open report descriptor. Retiring a failed writer preserves its reporting error. Reporting failures remain separate from acknowledged playback/completion semantics. An original single-stream `--progress-file` keeps its destination when navigating back from appended queue entries, unless its row was removed and re-added with a fresh destination. Runnable headless queue checks: `make check-cli check-queue check-reporting check-resolved`. The existing `check-resolved` local HTTP/HLS/VTT harness drives a real native `PlayerModel` through mixed YouTube/Miruro queues with injected fixture resolution, stale-result cancellation, manual navigation, natural-end advancement, fresh revisits/resume and isolated v1/v2 reports. It also delivers repeated enqueue URLs while paused and playing, checks original standalone selection/timeline preservation, lazy append ordering, atomic invalid/conflicting requests, independent single/queue report revisits, removal/re-addition with fresh reports and unchanged retired files, and empty-player startup. These checks require no browser or live streaming service. Actual LaunchServices/UI delivery remains a separate user-owned verification step; no GUI smoke result is claimed for enqueue. Background-title verification: `make check-resolved check-queue check-youtube all` passed. The native local-HLS smoke exercises unplayed YouTube/Miruro titles, serial utility-priority enrichment, unchanged paused selection/position/title, reordering during lookup, failure fallbacks, fresh playback preemption, and stale removal/replacement results. No browser or live service was used; sidebar appearance remains a user-owned visual check. Removal/re-addition verification: the new native registration regression failed before the fix and passed afterward. `make check-reporting check-queue .build/resolved-playback-check all` passed. A focused `.build/resolved-playback-check /absolute/source.json --enqueue-only` smoke using local video-only H.264 HLS passed paused/playing append, natural-end advancement, atomic conflicts/rollback, removal and re-addition of the current standalone video and an inactive queued video, fresh-session playback, byte-for-byte retired-report preservation, and empty-player startup. The source document requires `headers` and `subtitles` (empty values are valid). No full A/V-suite or packaged-GUI/LaunchServices pass is claimed; the app was built, not installed. Actual UI confirmation remains user-owned; temporary fixture/server files were removed. ### Optional native metadata The native resolver requests watch-route data and safely follows bounded same-origin, same-ID, same-episode canonicalization envelopes, including HTTP-200 redirects. It accepts JSON and newline-delimited `text/sveltekit-data`, decoding only the needed response-local tables and episode promise. Bodies and traversal are bounded; unrelated source promises are not used for playback. Titles come from returned series/selected-episode metadata. Only `op`/`mixed-op` and `ed`/`mixed-ed` become opening/ending bands. Bands retain fractional seconds, require finite positive player-reported duration, and reject nonfinite timestamps, originally reversed/zero-length intervals and wrong-episode timings. Display intervals are clipped to `[0, duration]`; intervals with no positive-length overlap are hidden. Raw timestamps remain unchanged, and duration changes recompute clipping without shifting or scaling. Optional provenance/votes/reference-length checks remain in force; a finite positive reference length differing from actual duration by more than 5% rejects the interval, and missing fields are not invented. Highest-vote eligible opening and ending win, with response order breaking ties. Overlaps are not merged. The controls mark interval boundaries with small rounded gaps in the slider track, without visible labels or boxes. Buffer rendering shares the segmented track; accessibility descriptions retain opening/ending names and times. While acknowledged, seekable video playback is inside a band, a **Skip Opening** or **Skip Ending** button offers a seek to its end for up to ten seconds of monotonic time, including while paused, or until playback leaves the band. Each kind is offered once until close or media-identity replacement; pointer movement and seeking back do not reoffer ignored segments. Pending seeks and slider previews cannot expose a stale offer. The same action routes to local or Cast playback and retains playback intent. No automatic skipping is performed. Bounds do not prove synchronization across cuts. Metadata tasks are bound to identity and lifecycle generation; replacement clears intervals and rejects stale results, including A → B → A. ### External resolved-source compatibility `--source-file PATH --new-instance` remains available for callers supplying already-resolved sources; it is not Tubular's current launch path and does not acquire URL metadata. Keep private source files unchanged and present for their player session. Their version-1 contract below is unchanged. ### Source document, version 1 Example only: replace CDN URLs with freshly resolved resources, preserving complete addresses and tokens. ```json { "version": 1, "url": "https://cdn.example/master.m3u8?token=fresh", "headers": { "Origin": "https://ultracloud.cc", "Referer": "https://ultracloud.cc/" }, "identity": { "provider": "miruro", "media_id": "OERe_a0YS4h-DlsBDOeXwX7HOO6316tw", "episode": 1 }, "title": "The Apothecary Diaries — episode 1", "subtitles": [{ "url": "https://cdn.example/english.jpeg?token=fresh", "format": "vtt", "language": "en", "label": "English", "default": true, "headers": { "Origin": "https://ultracloud.cc", "Referer": "https://ultracloud.cc/" } }] } ``` All shown fields except `title` are required. `headers: {}` and `subtitles: []` are valid; at most one subtitle may be default. Only `vtt` is supported for external subtitles in this pass. Limits: source document 1 MiB; 32 subtitle tracks; 64 headers per resource; Miruro media ID up to 512 ASCII letters/digits/`-`/`_`; episode integer 1–2147483647. Invalid URLs, HTTP header names, case-insensitive duplicate header names and control characters are rejected with sanitized errors. Header values are not trimmed or rewritten. ### External source projection External source-document producers must preserve selected direct stream URLs and complete server headers, use Miruro's opaque catalog ID and returned episode number for identity, and carry explicit per-subtitle headers. Subtitle headers **do not inherit** stream headers. The verified Sun path supplies server headers to its VTT resources; do not generalize this to other providers. `identity.provider` is the catalog namespace `miruro`, not the stream provider `sun`. Titles, slugs and expiring CDN URLs are not identity. Tubular no longer projects source documents. ## Propagation and subtitle behavior - Stream headers reach the master, nested variants, media segments, AES-128 keys and fMP4 init resources. Relative references use native HLS resolution. Existing query tokens are preserved; Terebi does not invent query inheritance or provider-specific URL rewrites. - Resolved media requests must stay on the initial stream's origin: scheme, host and effective port. **All media redirects, including same-origin redirects, are rejected before a second request.** This conservative policy prevents FFmpeg from forwarding supplied private headers to another destination. Cross-origin HLS resources are not supported in this contract. - Sun serves MPEG-TS segments as `.jpg` / `image/jpeg`. Resolved playback probes actual media content instead of enforcing filename-extension matching; it does not change the URLs. HTTP(S)-only, origin and redirect restrictions remain in force. - External VTT classification uses explicit format and content, not filename extension or MIME type. `.jpeg` subtitle URLs work. Labels/languages appear in the captions menu; the declared default is selected, and the user can choose Off or another track. - Cues follow acknowledged player position, including seeking backwards. Zero-duration cues are permanently inactive rather than invalidating the track. Rendering is plain text; WebVTT CSS, positioning and typographic styling are not applied. - Subtitle HTTP requests use their own headers. Same-origin redirects retain those headers; cross-origin redirects are rejected. Responses are capped at 4 MiB, with 15-second request and 20-second resource timeouts. - Media errors remain visible with the operation/native error. Subtitle failures appear separately beside the captions menu, including when other controls are hidden. FFmpeg diagnostics redact private details; source/subtitle errors do not interpolate URLs or header values. - Casting uses the same episode identity and progress schema; receiver-confirmed positions replace paused local-decoder facts. The Mac relays media/VTT without exposing source URLs or credentials to the receiver. See [Chromecast controls, transport limits and verification](../README.md#chromecast); physical receiver playback/captions remain unverified. YouTube's existing launch, version-1 progress and transport behavior remain supported. ## Progress contract and Tubular change YouTube records remain **version 1** with canonical `media_url`. Miruro records are **version 2**, with structured episode identity and no CDN address: ```json {"version":2,"event":"progress","media_identity":{"provider":"miruro","media_id":"OERe_a0YS4h-DlsBDOeXwX7HOO6316tw","episode":1},"position_seconds":123.5,"duration_seconds":1500.0,"completion_eligible":true} ``` `event` is `progress`, `ended` or `closed`. Position is finite, nonnegative and acknowledged by playback, not a pending seek/slider target. Duration is positive and finite or `null`. Completion eligibility remains false for live/non-seekable media, unknown duration and audio-only sources. Reporting includes ready state after restoration, approximately five-second playback intervals, acknowledged pause/seek transitions, the first eligible 95% crossing, native end and final close/quit snapshots. Launch success and window closure do not establish completion. Consumers must tolerate duplicates, partial trailing lines and abrupt termination; a `closed` record is not guaranteed. Tubular integration must retain canonical YouTube/version-1 validation, accept version-2 validation against the episode identity expected for that launched session, and key Miruro watched state by `(miruro, opaque media ID, episode)`. Never use stream URL, token, slug or selected playback provider as identity. Retain the current completion decision for matching identities: ```text completion_eligible && duration_seconds > 0 && position_seconds / duration_seconds >= 0.95 ``` Handle `null` duration as ineligible. This measures position, not viewing coverage; seeking near the end can satisfy it. Terebi also uses stable episode identity for its own resume bookmarks. ## Verification evidence | Scenario | Observed result | | --- | --- | | Explicit fresh direct Sun native smoke | Video/audio decode and advancing playback; default English VTT; expected cue text after forward/backward acknowledged seeks | | Live Sun audio continuity | Dragon Ball episode 1: actual Core Audio PCM diverged from FFmpeg after about 0.6 seconds before the fix; after tolerating millisecond-scale timestamp jitter, all 960,008 stereo frames compared across twenty seconds and multiple HLS segments matched exactly | | Audio regression | Deterministic AAC/MPEG-TS timestamp-jitter fixture failed before the shared continuity fix and matched FFmpeg exactly afterward; existing authored-gap, resampling, gain and stepped-resume checks passed | | Local resolved playback model | Independent stream/subtitle headers, nested playlist tokens, video/audio playback, cue timing and stable v2 progress | | Local native network checks | Encrypted MPEG-TS and fMP4; image-suffixed resources; headers on master/variant/segment/key/init; seeks/frame steps; redirect/foreign-origin isolation; diagnostic redaction; existing YouTube redirects | | Actual CLI-to-GUI handoff | Two independent Miruro episode windows; authenticated encrypted HLS; resource-specific default VTT; v2 progress and quit draining | | Full `make check-startup` rerun | Passed, including held-key suppression, YouTube progress/resume/quality/replacement/end and independent launches | | Other exercised checks | CLI, external subtitles, progress persistence, JSONL reporting, YouTube resolver and system playback passed | | Visible-app observation | User reported playback appears to work; comprehensive visual subtitle/seek synchronization sign-off remains limited | Earlier startup runs failed held-Space and YouTube end-transition assertions. The user reported possible manual interaction. The full rerun passed without playback-code changes; causality of the earlier failures was not established. No full aggregate `make check` pass is claimed. Streaming-input verification (2026-10-19): native URLSession resolved a live Miruro episode with nine VTT tracks; native URL-to-playback exercised video/audio, pause, default captions and forward/backward acknowledged subtitle seeks. Offline stream parsing/projection/security, CLI/config, resolved playback and YouTube resolver/adaptive checks passed. The actual source-file CLI launch emitted version-2 progress and ended records, but the startup check timed out waiting for GUI state snapshots; this run does not establish the complete two-window handoff scenario. The live probe's original synchronous wait blocked async resolution; yielding the actor fixed the probe without playback-code changes. ### Reproduction commands from Terebi ```sh make check-cli check-subtitles check-resolved check-network check-progress check-reporting make check-youtube check-media make check-startup ``` For only the actual Miruro launch scenario: ```sh TEREBI_CHECK_RESOLVED_ONLY=1 make check-startup ``` Default checks use local fixtures and do not contact Miruro. Optional metadata protocol/security/normalization checks: ```sh make check-miruro-metadata make .build/resolved-playback-check .build/resolved-playback-check --metadata-lifecycle ``` Explicit live native URL smoke (contacts Miruro and media/subtitle resources): ```sh .build/miruro-metadata-check --live 'https://barelystarted.miruro.tv/watch/OERe_a0YS4h-DlsBDOeXwX7HOO6316tw/episode?ep=1' .build/resolved-playback-check 'https://barelystarted.miruro.tv/watch/OERe_a0YS4h-DlsBDOeXwX7HOO6316tw/episode?ep=1' ``` URL smoke expects a fixture episode with available title, bands and default subtitles. It checks native decode and acknowledged subtitle/seek state, not visual appearance or perceived A/V synchronization. Tubular's catalog probe does not run Terebi or retrieve sources. Metadata implementation verification (2026-10-22): protocol/security/bounds checks, lifecycle replacement/A-B-A stale results/duration changes, stream resolver and CLI checks passed. The live placeholder resolved the descriptive title “The Apothecary Diaries Season 3 — Episode 1: Locusts,” opening 63.313–153.237 seconds and ending 1335.252–1425.384 seconds; native video/audio advanced, measured-duration bands appeared in model state, and default VTT plus forward/backward acknowledged cue seeks passed. FFmpeg emitted redacted diagnostics during this successful run; no visual synchronization or visible-band sign-off is claimed. Controls alignment correction: removed the obsolete label-height reservation after switching to gapped segments. A native offscreen SwiftUI/AppKit layout smoke reproduced a 9-point top-row shift when intervals appeared; after restoring the standard 14-point slider frame, volume and timeline geometry match exactly with and without intervals. Production build passed; visible confirmation remains a human check. Segment-skip verification (2026-10-22): local HLS/VTT playback exercised late metadata, the controlled-clock three-second boundary while paused, persistent dismissal, pending-seek suppression, opening/ending seeks, exact media-end seeking, close/reopen reset and playing/paused intent. Exact-end HLS seeks initially failed with `Operation not permitted`; the shared native seek path now finishes network inputs at EOF instead of asking the demuxer to seek beyond its final packet. Network playback regression checks, metadata checks, production build and TLS loopback Cast segment skipping/restoration passed. Rapid play-then-seek probes encountered Core Audio startup errors on the active Bluetooth output; the settled-playback smoke waits for actual advancing playback and passed. Visible button placement, rapid audio transitions and physical-TV behavior remain unverified. ## Remaining limits - Confirm title readability and gapped slider segments in normal/compact controls, buffer/playhead visibility and subtitle alignment in the visible app using Tubular's real launch path. Offscreen native rasterization at compact/normal widths confirmed all four interval-boundary gaps remain clear even with full buffer coverage. - Tubular collection launches, retained-report recovery and watched decisions remain its acceptance responsibility; this implementation changes no collection or progress schema. - Media redirects/cross-origin HLS resources remain unsupported. Native URL launches resolve fresh; externally supplied source documents must be refreshed by their producer. Automatic expired-source refresh is not implemented. - Other providers' proxies/HLS rewrites, iframe providers, downloads, thumbnails, automatic skipping, embedded captions and additional external-subtitle formats remain out of scope. The implementation and this handoff are local; no remote publication or Tubular issue/comment update was performed.