Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions docs/backlog/24-access-streaming-v2.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,11 +124,19 @@ P3 first vertical slice delivered (`agent/audio-v2-p3`):
- DAV2 Range reads now use bounded gateway requests, hedge the header and first
chunk against a second gateway when the first one stalls, and cache the
winning gateway per CID for the browser session.
- MSE playback now prepares the current chunk plus two future chunks while the
current clear chunk is appended. Appends stay strictly ordered, track changes
abort the bounded pipeline, and at most three container chunks are in the
preparation window (about 1.5 MiB at the current 512 KiB default).
- The browser imports the AES content key once per playback instead of once per
chunk. Known chunk ranges must return the exact byte count; truncated or
mismatched `206` responses retry another gateway instead of being
misclassified as a content-authentication failure.
- The resolver emits `dotify:dav2-startup` events for key authorization,
gateway selection, header readiness, first range, first decrypt, first
append, fallback, and error phases.
- Remaining #88 work: worker-based decryption, chunk read-ahead/pipelining,
first-chunk sizing experiments, the browser/device validation matrix, and the
- Remaining #88 work: worker-based decryption, first-chunk sizing experiments,
the browser/device validation matrix, startup telemetry export, and the
backend read-through gateway decision.

Product SDK replanning note (2026-07-14):
Expand Down
2 changes: 1 addition & 1 deletion docs/backlog/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ Deferred (needs a backend channel, deliberately not faked): persisted room mood.

| Backlog doc | Status | Goal |
| --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `24-access-streaming-v2.md` | P1/P2 delivered; P3 first vertical slice delivered with DAV2 refs, playback fallback, and startup metrics (design in `docs/design/dotify-v2-access-and-streaming.md`) | Remove the 42% preview; three-mode artist policy (free / paid / human-free via Proof of Personhood); sign-once session auth for key delivery; encrypted chunked streaming (`dotify.audio.v2`) for fast starts; Product SDK / Playground / Humanity feasibility as a later host-integration track |
| `24-access-streaming-v2.md` | P1/P2 delivered; P3 DAV2 vertical slice and bounded read-ahead delivered; real-browser/gateway validation remains (design in `docs/design/dotify-v2-access-and-streaming.md`) | Remove the 42% preview; three-mode artist policy (free / paid / human-free via Proof of Personhood); sign-once session auth for key delivery; encrypted chunked streaming (`dotify.audio.v2`) for fast starts; Product SDK / Playground / Humanity feasibility as a later host-integration track |

Ticket 24 supersedes the preview-based rows above: the 42% doctrine and the
ticket 18 preview assets are consciously retired by access model v2.
Expand Down
76 changes: 66 additions & 10 deletions docs/design/dotify-v2-access-and-streaming.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Dotify v2 - access, protected streaming, and Polkadot App alignment

Status: living design and delivery plan. Last reviewed: 2026-07-10.
Status: living design and delivery plan. Last reviewed: 2026-07-18.

This document is the source of truth for the Dotify v2 access and streaming
pivot. It should answer four questions:
Expand Down Expand Up @@ -32,9 +32,11 @@ Dotify v2 changes the production spine in three ways:
The current implementation now includes the first `dotify.audio.v2` vertical
slice: backend publish writes a chunked encrypted `DAV2` object, new registry
refs use `dotify:enc:v2:ipfs://<CID>`, and the web player recognizes v2 refs
with a Media Source Extensions path plus full-file fallback. Host playback now
emits startup timing events; the remaining P3 work is browser/device validation
against real gateways and media files.
with a Media Source Extensions path plus authenticated full-file fallback. The
MSE path uses bounded two-chunk read-ahead, ordered appends, one imported AES key
per playback, and strict chunk-range response validation. Host playback emits
startup timing events; the remaining P3 work is worker offload, field telemetry,
and browser/device validation against real gateways and media files.

## 2. Current Delivery State

Expand All @@ -45,7 +47,7 @@ against real gateways and media files.
| Free-track key delivery | Delivered | The backend verifies public access on-chain before releasing a key with no wallet or signature. |
| Artist runtime bootstrap | Delivered | Polkadot Hub EVM registration is staged: `createRuntime()` creates a minimal shell, then `installRuntimeStep()` installs one pallet per transaction before final ownership transfer and directory registration. |
| Room access doctrine | Delivered and preserved | Only the host needs access; listeners receive only the WebRTC stream. |
| Chunked encrypted streaming, P3 | First vertical slice delivered | New backend uploads publish DAV2 refs; web playback supports v2 refs with v1 fallback. Startup metrics exist; browser/device validation remains. |
| Chunked encrypted streaming, P3 | Startup pipeline delivered; field validation open | New backend uploads publish DAV2 refs; web playback supports v2 refs, bounded read-ahead, strict ordered append, and v1/full-file fallback. Startup metrics exist; browser/device validation remains. |
| Real Proof of Personhood integration, P4 | Open | Current `human-free` remains structurally ready but not backed by the live platform source. |
| Polkadot App / Triangle integration, P5 | Open | DotNS/Bulletin alignment exists conceptually; Product SDK, Host API signing, and Statement Store migration are future work. |

Expand Down Expand Up @@ -253,9 +255,26 @@ The target browser path:

1. Fetch and parse the header.
2. Fetch encrypted chunks using HTTP Range requests.
3. Decrypt each chunk with Web Crypto.
4. Append decrypted bytes to a Media Source Extensions `SourceBuffer`.
5. Start playback after the first appendable segment.
3. Import the temporary content key once, then decrypt each chunk with Web
Crypto.
4. Prepare the current chunk plus two future chunks concurrently while keeping
at most three prepared chunks in memory.
5. Append decrypted bytes to a Media Source Extensions `SourceBuffer` in strict
chunk order.
6. Start playback after the first appendable segment.

The range layer requires `206 Partial Content`. Known encrypted chunk ranges
must return exactly the requested bytes, and an exposed `Content-Range` header
must agree with the request. Header reads may be shorter only when the response
reaches the end of a small object.

MSE treats successive appends as one logical byte stream and retains incomplete
parser input across appends. That makes DAV2's encryption boundaries compatible
with incremental parsing, but it does not make every uploaded file a valid MSE
stream. The bytes still have to conform to the registered format for their MIME
type. `MediaSource.isTypeSupported()` is a necessary capability check, not proof
that a particular file is correctly packaged; recoverable append failures keep
the authenticated full-file path available.

Fallback:

Expand Down Expand Up @@ -313,7 +332,41 @@ Browser validation remains required before marking P3 release-ready:
- host switches v1 -> v2 and v2 -> v1 tracks;
- listener joins before and after first audio;
- mobile autoplay fallback still shows the explicit "start audio" action;
- `replaceTrack()` behavior remains stable when the source changes.
- source switches preserve listener audio through renegotiation, while
same-source sender replacement remains stable.

### Current streaming coherence review (2026-07-18)

The playback and room layers remain intentionally separate:

- DAV2 Range/decrypt/MSE runs only on the authorized host or individual
listener. It never moves source bytes or content keys into the guest room
path.
- A persistent host `<audio>` element feeds one captured `MediaStream`; room
guests receive that stream over WebRTC and attach it to an autoplay-aware
listener element.
- Native media-element capture is preferred. The Web Audio fallback keeps a
stable `MediaStreamAudioDestinationNode` for browsers without
`captureStream()`, resumes its context after user/media activity, and keeps
host-local mute separate from the outbound room stream.
- Same-source play, pause, and seek reuse the live sender. Source changes
deliberately renegotiate each listener after browser testing showed that
replacing tracks from a changing media-element capture can produce a
live-looking but silent sender. This costs more signaling than the ideal
`replaceTrack()` path, but preserves audible shared presence.
- Listener media uses `autoplay` and `playsInline`, while blocked playback is
surfaced as an explicit user action. New tracks are not assumed to resume
merely because a `MediaStream` gained a track.

This is coherent for the current small-room production spine, not the final
scale architecture. Host-to-listener mesh fan-out, optional rather than
mandatory TURN, missing ICE restart/quality telemetry, and per-source
renegotiation belong to room reliability issue #89. They should be improved
without moving key delivery to guests, making wallets a room-entry condition,
or coupling DAV2 startup to a future SFU. The next optimization decision remains
evidence-led: validate first sound and room continuity on real devices, then use
the measurements to choose backend read-through, media normalization, TURN, or
SFU work.

## 8. Polkadot App Stack Alignment

Expand Down Expand Up @@ -349,7 +402,7 @@ P5 should start with a capability matrix, not a rewrite.
| --- | --- | --- | --- |
| P1 | Delivered | Access model v2 in contracts, backend, and UI; remove preview playback. | Free tracks play with no wallet; unauthorized protected tracks show gates with no audio; artists can change future key-release policy without changing app flow. |
| P2 | Delivered | Session auth for key service. | One signature opens a session; later protected listens do not prompt again; every key request still checks chain access. |
| P3 | First vertical slice delivered | `dotify.audio.v2` chunked container, publish path, playback path, v1 fallback, startup metrics. | New protected uploads produce v2 refs; v1 assets still play; browser/device validation must confirm time-to-first-sound. |
| P3 | Startup pipeline delivered; field validation open | `dotify.audio.v2` chunked container, publish path, bounded read-ahead playback, v1/full-file fallback, startup metrics. | New protected uploads produce v2 refs; v1 assets still play; browser/device validation must confirm time-to-first-sound and supported media packaging. |
| P4 | Open | Real Proof of Personhood integration for `human-free`. | No admin mock in production; denied PoP checks fail closed; UI explains the exact missing proof. |
| P5 | Open | Triangle/Host API/Product SDK alignment and Statement Store design. | Dotify runs as a Host-compatible Product without assuming direct wallet/RPC access; room social state has a migration design. |

Expand All @@ -373,6 +426,9 @@ P5 should start with a capability matrix, not a rewrite.
- resolver chooses v2 playback for `dotify:enc:v2:ipfs://...` and v1 playback
for `dotify:enc:ipfs://...`;
- MSE path can append the first supported decrypted segment and start playback;
- bounded preparation overlaps range/decrypt work while preserving append order
and aborting on track changes;
- truncated or mismatched chunk ranges retry another gateway before decryption;
- full-file fallback works when MSE or Range support is unavailable;
- room e2e measures that host source loading no longer creates multi-second
silence on ordinary protected tracks.
Expand Down
4 changes: 4 additions & 0 deletions docs/explanation/content-protection.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,10 @@ audioRef = "dotify:enc:v2:ipfs://<CID>"

The same backend derivation is used when an authorized key request succeeds, so
the delivered per-track key decrypts bytes encrypted by the upload route.
During MSE playback, the browser imports that temporary key once and prepares a
bounded two-chunk look-ahead while appending clear chunks in order. This changes
startup latency, not authorization: room guests still receive only the host's
ephemeral WebRTC stream and never receive the key or source bytes.
The exact `DAV2` binary layout and browser playback contract are documented in
[audio-v2-container.md](../reference/audio-v2-container.md).

Expand Down
6 changes: 3 additions & 3 deletions docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -1528,9 +1528,9 @@ <h3>Core access, upload, room, and test paths</h3>
<h3>Operate the spine and validate first sound</h3>
<p>
Publication is reopened on the audited deployment. Hosted signaling and production
environment evidence, and public wallet/device validation are closed; the active gates
are DAV2 browser and gateway validation plus the backend read-through decision for
reliable first sound.
environment evidence, and public wallet/device validation are closed. DAV2 now has a
bounded gateway and read-ahead startup pipeline; the active gates are real-browser and
gateway validation plus the backend read-through decision for reliable first sound.
</p>
</li>
<li>
Expand Down
34 changes: 33 additions & 1 deletion docs/reference/audio-v2-container.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,8 @@ The web client resolves refs in this order:
one stalls, and the winning gateway is cached per CID for the browser
session.
3. If the browser supports `MediaSource.isTypeSupported(header.mediaMime)`,
decrypt chunks and append them to a `SourceBuffer`.
import the content key once, prepare the current chunk plus two future chunks,
and append clear chunks to a `SourceBuffer` in strict index order.
4. If Range or MSE is unavailable before streaming starts, or a recoverable
gateway/MSE append error happens while streaming, fetch the full encrypted
object, decrypt it, and play from a Blob URL.
Expand All @@ -86,6 +87,37 @@ The web client resolves refs in this order:
Legacy refs with `dotify:enc:ipfs://<CID>` remain supported through the v1
full-file decrypt path.

## Range and MSE Boundary

DAV2 chunk reads require `206 Partial Content`. A known encrypted chunk must
return exactly the requested byte count. When `Content-Range` is visible through
CORS, its start, end, and returned length must also match. A visible mismatch
makes the client try another configured gateway before decrypting; an absent
header is accepted after the byte-count check because some gateways do not expose
it through CORS. Header ranges may be shorter only when a small object ends before
the requested upper bound.

The read-ahead window is deliberately small: the current chunk and two future
chunks may be prepared, while only one clear chunk is appended at a time. Track
selection cancellation aborts the pipeline and its in-flight gateway requests.
This lowers dead air without turning public gateways into an unbounded fan-out
or giving room guests any source/key access.

MSE concatenates appends into a logical byte stream and retains incomplete input
between appends. The underlying clear bytes must still conform to the MSE byte
stream format registered for `mediaMime`. Browser type support is not a guarantee
that a particular original upload is packaged correctly, so real-browser format
validation and authenticated full-file recovery remain part of the production
contract. DAV3 should normalize media segments only if DAV2 field metrics prove
that original-file packaging is the remaining bottleneck.

The current [W3C MSE byte-stream registry](https://w3c.github.io/mse-byte-stream-format-registry/)
maps audio streaming formats for `audio/mpeg`, `audio/aac`, `audio/mp4`, and
`audio/webm`; support still varies by browser, codec, and exact packaging.
Uploads such as WAV, FLAC, or Ogg remain valid DAV2 assets but should use the
authenticated full-file playback path unless the running browser explicitly
accepts their MIME type and the real file passes append validation.

## Startup Metrics

The DAV2 resolver emits a browser event for the pre-source startup path:
Expand Down
Loading
Loading