178 lines
15 KiB
Markdown
178 lines
15 KiB
Markdown
# 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.
|
||
|
||
### 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 positive player-reported duration, and reject nonfinite, negative, reversed, wrong-episode or out-of-bounds timings; an end equal to duration is valid. Duration changes revalidate raw intervals without trimming, shifting or scaling. Optional provenance/votes/reference-length checks follow the documented frontend evidence; missing fields are not invented. Highest-vote eligible opening and ending win, with response order breaking ties. Overlaps are not merged.
|
||
|
||
The controls label the bands **Opening** and **Ending**, with accessibility descriptions; neither automatic seeking nor skip buttons are added. 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.
|
||
|
||
## Remaining limits
|
||
|
||
- Confirm title readability and labeled bands in normal/compact controls, buffer/playhead visibility and subtitle alignment in the visible app using Tubular's real launch path.
|
||
- 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.
|