162 lines
11 KiB
Markdown
162 lines
11 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 integration is still required. Terebi's source/progress contracts below are implemented, not merely proposed; Tubular has not yet adopted them in this work.
|
||
|
||
## Ownership
|
||
|
||
- **Tubular:** discovery, episode/provider/language selection, fresh source resolution, source-document creation, session cleanup and watched-state decisions.
|
||
- **Terebi:** native playback, request-header propagation, subtitle loading/selection/rendering, stable-identity bookmarks and acknowledged progress reporting.
|
||
|
||
No Miruro scraping, `strm.cx` embedding, cookies, impersonation or protection-bypass machinery was added to Terebi.
|
||
|
||
## Launch contract
|
||
|
||
```sh
|
||
terebi --new-instance --source-file '/private/session/source.json' \
|
||
--progress-file '/private/session/progress.jsonl'
|
||
```
|
||
|
||
`--source-file` requires `--new-instance`; `--progress-file` is optional and retains its existing behavior. Relative paths resolve against the invoking shell's directory. Each launch creates an independent player; the CLI passes absolute paths to the GUI, not media URLs/header values in arguments.
|
||
|
||
Create a unique session directory with mode `0700`, write the source document with mode `0600`, and supply a **nonexistent** progress-file destination. Terebi exclusively creates the report with mode `0600`; it rejects existing destinations and symlinks. Keep the source file unchanged and present for the player session. CLI success means macOS accepted the launch, not that media opened or playback completed. Tubular owns session-file cleanup.
|
||
|
||
### 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.
|
||
|
||
### Projection from Tubular's Miruro response
|
||
|
||
Use the selected direct Sun server/provider from `src/miruro.rs`:
|
||
|
||
| Terebi field | Tubular resolved source |
|
||
| --- | --- |
|
||
| `url` | Selected `server.streams[].url`, unchanged |
|
||
| `headers` | Selected server's header map, including Origin and Referer |
|
||
| `identity.media_id` | Miruro opaque catalog ID, not AniList/MAL ID or slug |
|
||
| `identity.episode` | Selected returned episode number used for source resolution, not absolute episode number or array index |
|
||
| `subtitles[].url` | Selected provider subtitle's `file` |
|
||
| `format`, `language`, `label`, `default` | Selected subtitle metadata; restrict this launch to VTT and at most one default |
|
||
| `subtitles[].headers` | Explicit headers appropriate to that subtitle resource |
|
||
|
||
Subtitle headers **do not inherit** stream headers. The verified Sun smoke explicitly supplied its server headers to the English subtitle resource; do not assume this applies to other providers or resources. `identity.provider` is the catalog namespace `miruro`, not the playback provider `sun`.
|
||
|
||
## 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.
|
||
|
||
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.
|
||
|
||
**Required in Tubular `src/video_state.rs`:** retain canonical YouTube/version-1 validation, add 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 |
|
||
| 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.
|
||
|
||
### 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. For an **explicit live smoke**, resolve fresh in Tubular, project the private source document above, then run:
|
||
|
||
```sh
|
||
make .build/resolved-playback-check
|
||
.build/resolved-playback-check '/absolute/private/session/source.json'
|
||
```
|
||
|
||
This checks native decode and acknowledged subtitle/seek state, not visual appearance or perceived A/V synchronization. Use the production launch command for that human check. Tubular's existing `cargo run --locked --example miruro_probe` verifies source resolution/master/subtitle retrieval, not Terebi playback.
|
||
|
||
## Remaining work and limits
|
||
|
||
1. Tubular must create the source document, launch Terebi with it and implement v2 progress consumption; no Tubular implementation files were changed by this work.
|
||
2. Confirm this identity/reporting contract on the Tubular side; external adoption is not yet agreement.
|
||
3. For end-to-end release verification, check English subtitle appearance and synchronization in the visible app before/after seeking, using Tubular's actual launch path.
|
||
4. Sources requiring media redirects or cross-origin HLS resources will fail explicitly under the current conservative policy. Expired sources must be freshly resolved by Tubular; Terebi does not refresh automatically.
|
||
|
||
Other providers' proxies/HLS rewrites, iframe providers, downloads, thumbnails, intro/outro controls, 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.
|