summaryrefslogtreecommitdiffstats
path: root/docs/MESHBAY_NODE_PROTOCOL.md
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-09-17 16:18:30 +0200
committerChristophe Besson <cbesson@gmail.com>2026-09-17 16:18:30 +0200
commit0fed6786fe304195df66776787c5b5b82d321bcf (patch)
treefdce22ba79b830efe9a2802958aa9b8e7131ca23 /docs/MESHBAY_NODE_PROTOCOL.md
parent289a00afa33c6b7d2f77e46cdae56a593b21cf9a (diff)
downloadmeshbay-0fed6786fe304195df66776787c5b5b82d321bcf.tar.gz
fix(node): measure where a seek lands instead of predicting it
The previous fix read ffprobe's key frames and took the last one at or before the request. It was wrong twice, and a viewer felt the difference: subtitles went from 5 s early to 2–3 s late. Matroska's Cues index only some keyframes, so an index seek backs off to an indexed one that the frame list does not single out. And the landing point moves with **which streams are mapped**, because the container is positioned where every mapped stream has data — on the reported title, a seek to 4913.7 s landed at 4909.863 with video alone and at 4907.236 with the second audio track mapped beside it. The frame scan gave the first number; the stream delivered the second; the gap was 2.65 s, and the measured audio displacement in the served stream was 2.65 s. So the node asks ffmpeg instead: the same seek, the same mapping, one copied frame under `-copyts`, and the answer read back off the result. 0.06–0.07 s, cheaper than the scan it replaces. The `-ss` argument stays at the request, so the bytes served are exactly the ones served before — only the number naming them changes. The probe runs after the audio track is resolved, because it cannot be right before that is known. An answer after the request, or further before it than any real keyframe gap, is discarded in favour of the old label: a number wrong by seconds beats a fabricated one. Found by decoding the served stream and locating its first frame in the source, which put it at 4907.213 s against an announced 4909.863 s. The test does the same thing rather than comparing the announced number against a second reading of the same probe. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UGY17EPph5LsLzePPXhUVc
Diffstat (limited to 'docs/MESHBAY_NODE_PROTOCOL.md')
-rw-r--r--docs/MESHBAY_NODE_PROTOCOL.md2
1 files changed, 1 insertions, 1 deletions
diff --git a/docs/MESHBAY_NODE_PROTOCOL.md b/docs/MESHBAY_NODE_PROTOCOL.md
index 2aede26..a53d494 100644
--- a/docs/MESHBAY_NODE_PROTOCOL.md
+++ b/docs/MESHBAY_NODE_PROTOCOL.md
@@ -1671,7 +1671,7 @@ array while MediaSource consumes it a segment at a time.
| Concurrent transcodes | 8 node-wide, semaphore on the transport context |
| Seeking | a new `stream_req` with `start`; the previous stream is retired first, ffmpeg respawned with `-ss` |
| Accurate seek | **off when the video is copied, on when it is re-encoded.** Copied video has to begin on a keyframe and cannot be trimmed to the request; re-encoded audio can, and is. Leaving both at the default put a whole GOP of silence at the head of every seek and left sound and picture a GOP apart — with correct timestamps throughout, so nothing downstream could detect it |
-| `start` in `stream_init` | the value actually used, and on the copy path that is the **keyframe at or before the request**, resolved by a bounded look-up before ffmpeg is spawned (0.12–0.51 s, measured). The client adds it back as `SourceBuffer.timestampOffset`; a subtitle cue carries the source's absolute time, so reporting the request instead would put every line on screen one GOP before it is spoken |
+| `start` in `stream_init` | the value actually used. On the copy path it is **measured** before the stream is served — the same seek with the same stream mapping, one frame copied under `-copyts`, 0.06–0.07 s — because an index seek lands on an indexed keyframe that the frame list cannot predict and that moves with which audio track is mapped. The client adds it back as `SourceBuffer.timestampOffset`; a subtitle cue carries the source's absolute time, so reporting anything else puts every line on screen away from the voice |
| `audio_tracks` in `stream_init` | every audio track: `i` (the **audio ordinal**, what `-map 0:a:<n>` takes, never the container stream index), `lang`, `title`, `codec`, `ch`. Empty for a file with no audio |
| `audio_track` in `stream_req` | which ordinal to map. Absent, out of range or malformed is the first track |
| `audio_track` in `stream_init` | the ordinal actually used, for the same reason `start` is reported: a list drawn before the file was replaced on disk can name a track that is no longer there, and the client must show what is playing rather than what it asked for. `null` when the file has no audio |