diff options
Diffstat (limited to 'docs')
| -rw-r--r-- | docs/MESHBAY_DESIGN.md | 33 | ||||
| -rw-r--r-- | docs/MESHBAY_NODE_PROTOCOL.md | 34 |
2 files changed, 52 insertions, 15 deletions
diff --git a/docs/MESHBAY_DESIGN.md b/docs/MESHBAY_DESIGN.md index a079e50..e7d4a76 100644 --- a/docs/MESHBAY_DESIGN.md +++ b/docs/MESHBAY_DESIGN.md @@ -1102,19 +1102,30 @@ client on reload; the desktop client ships its own UI, which is why connecting and says "this version can no longer connect" rather than showing a handshake refusal nobody can act on. -**The floor being the current version is what keeps capability branches out of the -client.** `MNP_MIN_SUPPORTED` equals `MNP_VERSION`, so `check_version` refuses -every older peer at the handshake — which means **every capability is true of every -peer the client can reach**, and there is nothing to test for. An upload is sealed -or it is not sent; a transfer has a real lease or it does not run; there is one -app-directories op and no wrappers behind it. The client records the version its -peer declared, for diagnostics, and **branches on none of it**. +**Every *requirement* is true of every peer the client can reach.** The floor moves +with each MAJOR, so `check_version` refuses at the handshake any peer that cannot +meet one: an upload is sealed or it is not sent; a transfer has a real lease or it +does not run; there is one app-directories op and no wrappers behind it. The client +records the version its peer declared, for diagnostics, and **branches on none of +it**. > A capability flag on a peer whose floor already guarantees the capability is a > branch that can only ever take one path — until somebody lowers the floor, at > which point it silently takes the other. **A field kept "just in case" is how > the branches come back.** +**The floor is not the current version, and MINOR additions are why.** It is +`MNP_MIN_SUPPORTED` in `handshake.py`, it equals the last MAJOR, and 3.1 and 3.2 +have both been added above it without moving it. So a peer can be reachable and +still not do something the current version can, and the client has to cope with +that — **by reading the peer's own answer, never by comparing version numbers**. +3.2's audio tracks are the worked example: the node lists them in `stream_init`, +the client draws its selector from that list, and a node that sends no list gets no +selector. That is not the branch the box above refuses. The branch it refuses is a +flag the client sets from a version it parsed; this is the node stating what it +has, in the same message the feature already needed, and it takes exactly one path +per peer because the peer said which. + Where a break leaves data behind, a migration runs with the node stopped, backs the database up first and is idempotent. But **a migration that has to be remembered is a migration that does not happen**, so anything that *can* be a @@ -2020,6 +2031,14 @@ MSE string) fed to a source buffer, with the node holding one slot per viewer. - **Seeking restarts the source with an index seek before the input**, clamped away from the end and echoed back; the client supplies the timestamp offset, because copying timestamps does not preserve position. +- **The viewer picks the audio track, and picking one is a seek.** One ffmpeg + carries one audio track, so there is nothing to switch inside a running stream: + the node is asked again at the current position and the source buffer is reset + the way any seek resets it. It costs nothing extra — audio is transcoded on every + stream anyway — and mapping the first track unconditionally, which is what this + replaced, made a dubbed library playable in one language only. The node reports + the tracks it found and the one it used; **the client draws its selector from + that list and from no version number**, which is what keeps the addition MINOR. - **Losing a peer must stop its work, not merely forget it** — anything holding a resource is shut down on the way out, or a closed tab transcodes for the length of the credit timeout. diff --git a/docs/MESHBAY_NODE_PROTOCOL.md b/docs/MESHBAY_NODE_PROTOCOL.md index 2682820..19cced9 100644 --- a/docs/MESHBAY_NODE_PROTOCOL.md +++ b/docs/MESHBAY_NODE_PROTOCOL.md @@ -1,6 +1,6 @@ # MeshBay Node Protocol (MNP) -**Wire version:** `3.1` — `meshbay_common/__init__.py` (`MNP_VERSION`) +**Wire version:** `3.2` — `meshbay_common/__init__.py` (`MNP_VERSION`) **Oldest peer accepted:** `3.0` — `handshake.py` (`MNP_MIN_SUPPORTED`) **Normative implementation:** `meshbay-common` (`protocol.py`, `handshake.py`, `groupbox.py`, `chatbox.py`, `adminop.py`, `join.py`, `device.py`, `crypto.py`, @@ -1627,19 +1627,23 @@ array while MediaSource consumes it a segment at a time. ``` C N - |-- stream_req {v, file_id, start, credits} -------->| + |-- stream_req {v, file_id, start, credits, | + | audio_track?} -------------------------------->| | | retire this session's previous | | stream (a second request | | means the first is over) | | acquire a transcode slot (8) - | | ffprobe: codec, duration, audio + | | ffprobe: codec, duration, the + | | audio tracks | | spawn ffmpeg | | -ss before -i (index seek) | | video: copy, or libx264 when | | the browser cannot decode - | | audio: always AAC, 2 ch + | | audio: always AAC, 2 ch, + | | -map 0:a:<audio_track> | | frag_keyframe+empty_moov - |<- stream_init {v, file_id, codec, duration, start}-| + |<- stream_init {v, file_id, codec, duration, start, | + | audio_tracks[], audio_track} ------------------| | | | check MediaSource.isTypeSupported(codec) | | | @@ -1664,6 +1668,11 @@ 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` | | `start` in `stream_init` | the value actually used — seeking lands on the keyframe at or before the request, and the client adds it back as `SourceBuffer.timestampOffset` | +| `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 | +| Changing track | a new `stream_req` at the current position, exactly like a seek — one ffmpeg produces one audio track, so there is nothing to switch inside a running stream | +| Capability discovery | **the list, not the version.** A client draws its selector from `audio_tracks` and sends `audio_track` only when it has one, so a node too old to enumerate is never asked for a track it would ignore and answer in the wrong language | An ffmpeg failure before any output produces `error: Could not stream this file`; stderr stays server-side, where it belongs — it names paths on the operator's disk and @@ -1992,8 +2001,9 @@ message: ## 13. Versioning and compatibility -MNP versions independently of the package version. Current: **`3.1`**; oldest peer -accepted: **`3.0`** — 3.1 is additive, so the floor does not move with it. +MNP versions independently of the package version. Current: **`3.2`**; oldest peer +accepted: **`3.0`** — 3.1 and 3.2 are both additive, so the floor does not move with +them. The two numbers are separate on purpose. `MNP_VERSION` says what this build speaks; `MNP_MIN_SUPPORTED` says what it will talk to, and moving the second is a decision about @@ -2009,6 +2019,14 @@ whether an older peer can still do anything useful: chat. A break that touches something every session depends on cannot be confined, and the honest form is to refuse at the handshake: **a stated refusal is a bug report, a feature that quietly does not work is a support case.** +* **Discovery from the answer, not from the version number.** 3.2's audio-track + selection is the shape to copy: the node lists the tracks in `stream_init`, and the + client sends `audio_track` only when it was given a list. A peer that ignores that + field would not degrade — it would serve a different language in silence, which is a + wrong answer and not a missing feature — and what keeps the change additive is that + no client can ever put an old node in that position. This is not the opt-in switch + I10 refuses: there is no second branch on the node, which always enumerates, always + honours the request and always reports the track it used. * **A requirement is breaking even when its messages are additive** (I10). New message types and a new optional field are additive on the wire; *requiring* them is not, and an opt-in switch that enforces the requirement only for peers that speak the new @@ -2183,7 +2201,7 @@ LP(x) = uint32be(len(x)) || x every field, no exceptions | Constant | Value | Source | |---|---|---| -| `MNP_VERSION` | `3.1` | `meshbay_common/__init__.py` | +| `MNP_VERSION` | `3.2` | `meshbay_common/__init__.py` | | `MNP_MIN_SUPPORTED` | `3.0` | `handshake.py` | | `NONCE_LEN` | 32 bytes (both handshake nonces) | `handshake.py` | | `ADMIN_CHALLENGE_TTL` | 120 s | `adminop.py` | |