# Miruro video-segment feasibility Investigated **2026-10-22**, scoped to **https://barelystarted.miruro.tv**. Investigation only: no production changes, media playback, playlist/segment/subtitle downloads, browser testing, or remote mutations. **Conclusion (updated after the approved ownership split):** Miruro supplies optional episode-relative opening/ending intervals. Terebi must acquire and display them through its native episode-URL resolver; Tubular owns catalog/selection/collections/watched decisions only. Current Terebi does not display these intervals. The former source-file recommendation below is superseded by the [URL-playback handoff](terebi-url-playback-handoff.md). Availability is sparse; actual media-duration bounds are necessary but do not prove alignment across cuts. Evidence labels: **Observed** = responses obtained today; **Frontend** = today's deployed first-party JavaScript, not a server guarantee; **Earlier** = the 2026-10-12 investigation in [miruro-api.md](miruro-api.md); **Recommendation** = proposed minimum behavior, not implemented. ## Live metadata **Observed — historical native Tubular probe before URL cutover:** the then-existing `Client::detail`, `Client::sources`, and `SourceDocument::from_sources` path ran against the [detail route][detail] and [sources endpoint][sources]. The temporary example was removed afterward; `cargo run --locked --example miruro_segments_probe` describes that investigation, **not a permanent runnable command**. Source resolution/projection has since been removed from Tubular. Safe output projection: ```text detail: 3 episodes episode 1: absolute_number=49 duration_seconds=1500 skip_times=[{kind:op,start_seconds:63.313,end_seconds:153.237}, {kind:ed,start_seconds:1335.252,end_seconds:1425.384}] episode 2: absolute_number=50 duration_seconds=1500 skip_times=[] episode 3: absolute_number=51 duration_seconds=1500 skip_times=[] sources top-level keys: config episodeNumber providerOrder tracks projected source-document keys: headers identity subtitles title url version ``` Those are episode **1** intervals, not absolute episode 49 or a provider-specific episode identifier. Two empty arrays show why metadata must remain optional; they do not establish that those episodes have no opening/ending. **Observed — watch metadata:** JavaScript `fetch` of the [watch route][watch] returned **HTTP 200**, **`text/sveltekit-data`**, 33,483 text characters. The newline-delimited SvelteKit response contained an `episodesData` promise chunk; decoding its indexed table yielded: ```json { "episodes": [ {"number": 1, "duration": 1500}, {"number": 2, "duration": 1500}, {"number": 3, "duration": 1500} ], "currentEpisode": {"number": 1}, "prevEpisode": null, "nextEpisode": {"number": 2}, "aniskip": [ {"episode": 1, "type": "op", "start": 63.313, "end": 153.237}, {"episode": 1, "type": "ed", "start": 1335.252, "end": 1425.384} ] } ``` This is an abbreviated projection: episode objects have additional descriptive fields. The two `aniskip` objects are complete and have **no `provider`, `votes`, or `episode_length`**. Both routes express these intervals in seconds, retaining fractional precision; `skip_times` uses `kind/start_seconds/end_seconds`, whereas watch uses `type/start/end` plus the episode number. The sampled values match across both routes. `/api/sources` contains no segment field; a source-only request cannot recover these sampled intervals. **Earlier vs current:** [miruro-api.md](miruro-api.md#detail-episodes-and-watch-data) recorded two available episodes on 2026-10-12; today's probes found three. Its detail/watch shapes and episode-1 intervals are revalidated above. Its old hashed frontend links are not today's deployment. The current watch Content-Type is `text/sveltekit-data`, so do not assume every route-data response is `application/json`. Native `ureq` success and previous curl/Cloudflare failure are transport-specific observations, not permission or an availability guarantee. ## Deployed first-party normalization **Frontend:** today's [homepage][home] imports [`app.Bj5KeT2s.js`][app], whose route dictionary maps `/watch/[id]/[slug]` to node 11, [`11.DKyvW9qB.js`][client]. In that watch bundle, function `wn` and sets `xn`/`Sn` implement this complete mapping: | Input `type` | Emitted player segment | | --- | --- | | `op`, `mixed-op` | `{kind:"intro", start, end}` | | `ed`, `mixed-ed` | `{kind:"outro", start, end}` | | Any other value, including `recap` | No segment from this normalizer | Only `op` and `ed` occurred in today's metadata. The mixed variants are **frontend-supported**, not live-observed API records. There is no evidenced exhaustive server enum, recap interval, preview interval, provider-specific interval, or mixed-kind decomposition. In particular, `mixed-op` is treated as one whole intro interval, not split into opening and recap portions. The algorithm in `wn(records, episodeNumber, duration)` is: 1. Return `[]` when records are empty, the episode is null, or duration is absent/nonpositive. 2. Reject a record when its optional `provider`, lowercased, contains `crunchyroll`; its `episode` is not strictly equal to the selected episode number; or `(votes ?? 0) < 0`. 3. Require finite `start` and `end`, `start >= 0`, `end > start`, and `end <= duration` (an end exactly at duration is allowed). 4. When `episode_length` is a positive number, require `abs(episode_length - duration) / duration <= 0.05`. Missing/non-number/nonpositive lengths bypass this comparison. This optional field was absent in the sample, so catalog duration 1500 does **not** trigger that 5% check by itself. 5. Sort survivors by descending `(votes ?? 0)`. Take the first opening-kind record and the first ending-kind record. Emit intro first, then outro; at most two segments. It does not merge, scale, shift, trim, or resolve overlapping intro/outro intervals. Useful anchors in the minified [bundle][client]: `Cn=.05`, `function wn`, `provider?.toLowerCase().includes(\`crunchyroll\`)`, `episode_length`, and `strmcx-duration-change`. The provider check concerns the **metadata record's provenance**, not equality with the currently selected stream provider; the algorithm has no selected-provider argument. Vote/provider/duration-length rules are consumer behavior, not evidence that Miruro currently returns those optional fields. **Frontend timing/lifecycle:** the player component starts with empty segments and resets them, together with duration, during source resolution. A `strmcx-duration-change` event with positive numeric duration invokes `wn` using player-reported duration, the selected episode number, and watch `aniskip`. The normalized array is passed as `segments` to the strm.cx session, separately from the `options.autoskip` preference. Catalog `episode.duration` is not used as the normalization duration. Iframe playback takes a separate embedded-URL path rather than this direct-player session; the inspected code does not establish segment handoff to an arbitrary iframe provider. ## Limits and minimum recommendation - **Observed:** metadata exists and is not tied to an expiring source URL. **Unknown:** coverage across the catalog, a stable/public metadata contract, how timings were generated, non-opening/ending availability, and alignment across providers/dubs/cuts. Merely being in bounds does not prove synchronization with a particular cut. - **Recommendation:** reuse the existing detail episode metadata; retain selected episode identity, optional raw kinds, and fractional second boundaries alongside it. Normalize only the four evidenced opening/ending kinds for initial intro/outro behavior. Do not relabel recap or unknown kinds as intro/outro or invent missing intervals. - **Approved seam (supersedes the earlier source-session recommendation):** Terebi's existing episode-URL resolver owns optional metadata acquisition and validation against actual media duration. Direct and Tubular launches must use the same path. No source-document version change, separate segment file, new endpoint or Tubular metadata persistence is required. Preserve optional provenance/reference-length fields if available; do not invent fields absent from the sample or claim bounds prove synchronization. - **Recommendation:** address every launch path, including saved/watch-later/currently-watching reopening, rather than enriching only a currently open catalog episode. ## Tubular → Terebi seam The approved implementation contract and acceptance scenarios are consolidated in [Terebi URL-playback handoff](terebi-url-playback-handoff.md). Tubular launches the stable opaque ID and returned episode number through `PlayerLaunch::start`, using a non-identifying `episode` watch slug. Old/offline snapshots need no new fields or migration. Terebi owns canonical-route resolution, title enrichment and optional timeline metadata; title text is not identity. The requested UI is **intro/outro bands in the controls progress bar only**, not automatic skipping or skip buttons. Validate episode binding, finite ordered nonnegative times and actual-duration bounds; clear metadata on media replacement. Empty, unavailable or invalid metadata must not prevent ordinary playback, and unknown kinds must not be relabeled. ### Historical source-file decoder evidence (superseded transport) Before this cutover, Tubular retained `skip_times` in browsing metadata but omitted it from generic `Video` snapshots and source documents. Terebi's source-v1 decoder did not retain intervals. The following isolated decoder observation explains why adding JSON fields alone would not have implemented timeline display; it is not a recommendation to restore source files. **Observed — actual decoder smoke:** compiled the existing `ResolvedSource.swift` with a temporary Foundation-only harness, then called `ResolvedSource.load(from:)` on synthetic local source documents. No player/network/GUI was started. ```text interval-array: accepted; segments absent from ResolvedSource wrong-type: accepted; segments absent from ResolvedSource version-2: rejected ``` Thus merely adding `segments` to a version-1 JSON file would silently discard it, even with a malformed value. Changing the version alone also does not work. This is a source-document version, independent of the existing version-2 progress records. The earlier recommendation to introduce a new source-document version and carry intervals through Tubular is **superseded**. No source-v2 or segment file is required. Terebi's native URL resolver already owns `/api/sources`; it must additionally acquire title and optional episode metadata via canonical route data for all callers. See the handoff for lifecycle and acceptance requirements. Existing source-v1 decoder observations above remain historical evidence only. ## Fetch record All requests were read-only. Static source was inspected, not executed as a webpage. No expiring source URL/token was printed or saved. ```javascript // Research eval JavaScript transport; each response's status/type was inspected. await fetch("https://barelystarted.miruro.tv/"); await fetch("https://barelystarted.miruro.tv/_app/immutable/entry/app.Bj5KeT2s.js"); await fetch("https://barelystarted.miruro.tv/_app/immutable/nodes/11.DKyvW9qB.js"); await fetch("https://barelystarted.miruro.tv/watch/OERe_a0YS4h-DlsBDOeXwX7HOO6316tw/the-apothecary-diaries-season-3/__data.json?ep=1"); ``` The homepage/app were first fetched with the static `read` tool (`:raw` for imports); the watch bundle was read and then fetched with eval: **200 `text/javascript`**, 84,045 characters. Watch route: **200 `text/sveltekit-data`**, 33,483 characters; only decoded episode/segment metadata was printed, never `sourcesData`. The native detail/source probe is documented above. The decoder smoke used `swiftc -parse-as-library ../terebi/Sources/App/ResolvedSource.swift -o `, followed by running that binary. Both temporary source harnesses and the decoder's generated fixture/binary were removed. No test suites, linters, formatters, full application builds or UI smoke tests were run; only the native metadata probe and isolated decoder harness were compiled and executed. [home]: https://barelystarted.miruro.tv/ [app]: https://barelystarted.miruro.tv/_app/immutable/entry/app.Bj5KeT2s.js [client]: https://barelystarted.miruro.tv/_app/immutable/nodes/11.DKyvW9qB.js [detail]: https://barelystarted.miruro.tv/info/OERe_a0YS4h-DlsBDOeXwX7HOO6316tw/the-apothecary-diaries-season-3/__data.json [watch]: https://barelystarted.miruro.tv/watch/OERe_a0YS4h-DlsBDOeXwX7HOO6316tw/the-apothecary-diaries-season-3/__data.json?ep=1 [sources]: https://barelystarted.miruro.tv/api/sources?id=OERe_a0YS4h-DlsBDOeXwX7HOO6316tw&n=1