Repository navigation
quest(archive): Timeline-indexed MoQ archives #4034
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
0631611
3f42dce
62a4821
00282ce
9737274
a03664b
eb55c52
fc61e33
c6644ea
25a852d
c9ab185
fb1f7f6
a20a784
2ae4607
d605937
bc40446
e272228
e356f68
70bafea
d83b380
6e319ad
8d12d56
0d0346d
9032c86
305b0d9
4922ca9
6dca9b5
ca88b75
c9db432
383c9d3
58c3a4f
44f0645
a367ee8
0bfdc97
d2ce3e1
3de0dd6
772f031
7728a49
1bf32fa
5ee1c0a
9c1780e
8542548
9b763ac
1e80e88
d73d59d
b608c04
2c29bdd
2c1d0ab
4635835
0065120
4005e0b
dc93549
64473d9
4aa7062
3937cc6
6115c6f
519e7bb
fd96c7c
f981fd9
0f89d64
c72b7fc
95caad2
59659b7
4e17a6c
cb8876a
a535c61
1ce3e2c
a80f912
d544e01
74bbaef
3272d7a
ecafd41
b6126e6
afab42f
490814d
0146e62
b7ad18e
bea5585
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,34 +17,63 @@ moq --connect https://relay.example.com/anon --broadcast my-stream.hang export h | |
| moq --connect https://relay.example.com/anon --broadcast my-stream.hang import hls https://example.com/live/master.m3u8 | ||
| ``` | ||
|
|
||
| Export never subscribes to media. It reads the broadcast's | ||
| [timeline track](/concept/hang#catalog), a log of complete segments aligned | ||
| across every rendition, to build playlists, then fetches exactly the groups a | ||
| requested segment covers from the relay's cache and transmuxes them to CMAF on | ||
| demand. So a segment is servable for as long as the relay's | ||
| Export never subscribes to media. It reads each rendition's | ||
| [timeline track](/concept/hang#catalog), a small index of that rendition's | ||
| groups, to build playlists, then fetches exactly the frames a requested segment | ||
| covers from the relay's cache and transmuxes them to CMAF on demand. So a | ||
| segment is servable for as long as the relay's | ||
| [cache](/bin/relay/config#cache) retains it, and idle renditions cost nothing. | ||
| Because segments are aligned, the same number names the same span of content in | ||
| every media playlist; a record with nothing for a rendition renders as | ||
| `EXT-X-GAP` and a jump in content time as `EXT-X-DISCONTINUITY`. A broadcast | ||
| whose catalog advertises no timeline is skipped. One server exposes every | ||
| Segment boundaries come from one reference rendition (the first video | ||
| rendition by name, or the first audio one without video) and are numbered by | ||
|
Comment on lines
+26
to
+27
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Specify that the reference rendition must have a timeline. The implementation selects the first video rendition with a timeline. It selects audio only when no video rendition has a timeline. The current wording can lead readers to expect the wrong segment numbering and 🤖 Prompt for AI Agents |
||
| its records, so every edge and every reload agree. Every other video rendition | ||
| snaps each boundary to its nearest keyframe within about a second, and a | ||
| segment with none in range renders as `EXT-X-GAP`; audio takes every frame | ||
| inside the segment's span. A jump in content time renders as | ||
| `EXT-X-DISCONTINUITY`. Gaps are a fallback: a publisher wanting clean HLS | ||
| export should align video GOPs across renditions. A timeline that fails | ||
| (malformed, refused, or lost) is not retried, since the relay already rides out | ||
| transient source failures. The reference timeline failing ends every playlist | ||
| with `EXT-X-ENDLIST`, and any other rendition's timeline failing ends that | ||
| rendition's playlist at the last segment it covers. A broadcast | ||
| whose catalog advertises no timelines is skipped. One server exposes every | ||
| broadcast by path: | ||
|
|
||
| ```text | ||
| /{broadcast}/master.m3u8 | ||
| /{broadcast}/manifest.mpd | ||
| /{broadcast}/{video|audio}/{rendition}/media.m3u8 | ||
| /{broadcast}/{video|audio}/{rendition}/init.{hash}.mp4 | ||
| /{broadcast}/{video|audio}/{rendition}/seg/{segment}.m4s | ||
| /{broadcast}/{video|audio}/{rendition}/seg/t{pts}.m4s | ||
| /{broadcast}/{video|audio}/{rendition}/seg/{reference}.{segment}.m4s | ||
| /{broadcast}/{video|audio}/{rendition}/seg/{reference}.t{pts}.m4s | ||
| ``` | ||
|
|
||
| Segment boundaries come from one reference rendition's timeline, and its records | ||
| number the segments. `{reference}` is a short hash of that rendition's kind and | ||
| name, so every edge derives the same URL, and a reference that changes (a new | ||
| first video rendition) starts a new numbering under new URLs rather than reusing | ||
| the old ones for other content. | ||
|
|
||
| A [`moq-archive`](https://docs.rs/moq-archive) recording replayed through its | ||
| `Reader` is served the same way, with no second stored copy. Playlists come | ||
| from the replayed timelines alone, and a segment GETs only its rendition's | ||
| stored objects, so switching renditions never downloads both. An | ||
| inline-parameter-set codec with no catalog `description` is the exception: | ||
| the first playlist render GETs one keyframe group to build the init segment, | ||
| then caches it. Out-of-band configs need no media GET. When the catalog's | ||
| `archive` entry names a `store` and no `replay` path, its spans are durable on | ||
| this broadcast, so the playlists list the whole retained timeline and only the | ||
| recording's own retention trims them; DASH `timeShiftBufferDepth` is the listed | ||
| span. The playlist ends with `EXT-X-ENDLIST` only once the reader's caller | ||
| declares the recording finished; the store holds no completion marker. | ||
|
|
||
| The init URL carries a hash of its bytes, so a reconfigured rendition gets a | ||
| new one. An embedder of the library can also label the publisher's run with | ||
| `Broadcaster::set_generation`. Every segment URL then carries it | ||
| (`seg/{generation}.{segment}.m4s`), since a restarted publisher reuses segment | ||
| (`seg/{generation}.{reference}.{segment}.m4s`), since a restarted publisher reuses segment | ||
| numbers for different media. | ||
|
|
||
| `--window` sets the playlist duration (default 16 s), | ||
| `--window` sets the live playlist duration (default 16 s) and caps segment | ||
| `Cache-Control: max-age` for every broadcast, | ||
| `--listen-tls-cert`/`--listen-tls-key` or `--listen-tls-generate` serve HTTPS, | ||
| and `--cors-origin` opens it to browsers. | ||
| H.264/H.265 and AAC/Opus renditions are served. Import handles classic HLS; | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
🔎 Supported by static analysis
🏁 Script executed:
Repository: moq-dev/moq
Length of output: 42674
🏁 Script executed:
Repository: moq-dev/moq
Length of output: 42927
🏁 Script executed:
Repository: moq-dev/moq
Length of output: 12101
Document the sequence-reset limitation for archive continuation.
When a source restarts below the recovered floor,
handlerejects groups whose end is at or before that floor. New groups can be dropped until their sequence catches up. State that this case needs a new store prefix.🐛 Suggested fix
📝 Committable suggestion
🤖 Prompt for AI Agents