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
14 changes: 0 additions & 14 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion moq
Submodule moq updated 819 files
1 change: 0 additions & 1 deletion quest/m0/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,6 @@ repository.
- [Virtual camera](/quest/m0/vcam/README.md) - a canvas's program exposed as a system camera on Linux, Windows, and macOS
- [Signing identities are available](/quest/m0/signing.md) - the Apple and Windows code-signing credentials packaging and the macOS camera need
- [Packaging](/quest/m0/packaging.md) - signed installers for macOS and Windows and a Flatpak for Linux
- [moq-mux's presentation timeline is pinned](/quest/m0/pin-presentation-timeline.md) - shared playout timing, for broadcast sync
- [The persistable portal restore token is pinned](/quest/m0/pin-portal-token.md) - moq-video's restore token, for Wayland sources
- [Platform AAC encoders are pinned](/quest/m0/pin-aac-encode.md) - moq-audio's AAC encoders, for destinations
- [moq-audio's filter chain is pinned](/quest/m0/pin-audio-filters.md) - the chain audio filters configure
Expand Down
15 changes: 4 additions & 11 deletions quest/m0/broadcast-sync.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,20 +21,13 @@ Decided 2026-10-26 (maintainer):
whole broadcast. A broadcast with no audio keeps video's own short delay.
Show each source's delay in the UI.

Lift the shared part out of `moq play` into a library both binaries use,
rather than copying it. Decided 2026-11-02 (maintainer): that library is
moq-mux, which already owns the broadcast clock and the `Pacer` the timing
is built on, and it lands there first; this quest waits on its
[pin](/quest/m0/pin-presentation-timeline.md). The video side holds frames until their audio-aligned
time, and a source still repeats its last frame rather than stalling the
canvas. This replaces the live-edge behavior the studio app PR chose, where
Use the timing `moq play` shares rather than copying it: moq-mux's
`timeline` module, pinned here (decided 2026-11-02, maintainer). The video
side holds frames until their audio-aligned time, and a source still repeats
its last frame rather than stalling the canvas. This replaces the live-edge behavior the studio app PR chose, where
replaying a publisher's timestamp gaps pushed audio behind its own video. Here
video waits for audio instead. Update `README.md`.

## Required

- [moq-mux's presentation timeline is pinned](/quest/m0/pin-presentation-timeline.md) - the shared audio-anchored timing

## Related

- [Audio jitter target](https://github.com/moq-dev/moq/blob/main/quest/m1/audio-jitter-target/README.md) - the delay estimate this reuses
11 changes: 0 additions & 11 deletions quest/m0/pin-presentation-timeline.md

This file was deleted.

12 changes: 6 additions & 6 deletions rs/moq-scene/src/publish.rs
Original file line number Diff line number Diff line change
Expand Up @@ -177,8 +177,7 @@ impl<'a> Listing<'a> {
// The studio's clock, so source and program timestamps share one timeline.
let config = moq_mux::catalog::Config::default()
.with_clock(publish.catalog.clock())
.with_bandwidth(publish.bandwidth.clone())
.with_hls(None);
.with_bandwidth(publish.bandwidth.clone());
let catalog = moq_mux::catalog::Producer::new(&mut scratch, config)?;
claim.owners.lock().unwrap().catalogs.insert(claim.id, catalog.clone());
Ok(Self {
Expand All @@ -198,8 +197,9 @@ impl<'a> Listing<'a> {
let mut updates = self.catalog.consume()?;
let mut studio = self.studio.clone();
while let Some(mut catalog) = updates.next().await? {
// Already at the studio catalog's root.
catalog.clock = None;
// Media timing is the root's: the one clock, and the join interval an
// HLS export of the program numbers its segments on.
catalog.media = Default::default();
// The claim is checked inside the mutation, so a newer source's
// writes, made under the same lock, are never overwritten.
studio.mutate(|root| {
Expand Down Expand Up @@ -411,7 +411,7 @@ impl Video {
let reserved = listing
.catalog
.reserve()
.with_hls_hint(config.gop.duration(config.framerate));
.with_interval(config.gop.duration(config.framerate));
let producer = moq_video::encode::Producer::new(listing.broadcast.clone(), reserved, rendition)?;
Ok(Self {
track: Encoding::new(producer),
Expand Down Expand Up @@ -609,7 +609,7 @@ mod tests {
let snapshot = listed(&catalog, |sources| sources.len() == 2).await;
assert!(snapshot.video.renditions.is_empty(), "no source is a program rendition");
let published = &snapshot.ext.studio.sources;
assert!(published["red"].clock.is_none(), "the root carries the clock");
assert!(published["red"].media.is_empty(), "the root carries the media timing");
assert_eq!(sources.get("red").unwrap().track(), Some(Status::Live));
assert_eq!(sources.get("guest").unwrap().track(), None);
assert!(sources.get("red copy").is_none(), "a clone opens no feed, so no track");
Expand Down
6 changes: 2 additions & 4 deletions rs/moq-scene/src/replay.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,10 +136,8 @@ impl Take {
"the broadcast has described none of its tracks yet".into(),
));
}
// Listed so an export of the take finds its renditions; HLS is for live
// viewers, which a take has none of.
let config = moq_mux::catalog::Config::default().with_hls(None);
let mut listing = moq_mux::catalog::Producer::new(&mut broadcast, config)?;
// Listed so an export of the take finds its renditions.
let mut listing = moq_mux::catalog::Producer::new(&mut broadcast, Default::default())?;
listing.mutate(|listed| {
listed.video.renditions = video.clone().into_iter().collect();
listed.audio.renditions = audio.clone().into_iter().collect();
Expand Down
2 changes: 1 addition & 1 deletion rs/moq-scene/src/source.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1085,7 +1085,7 @@ pub(crate) mod test {
let mut config = moq_video::encode::Config::new(64, 64, moq_video::FrameRate::new(30, 1).unwrap());
config.kind = moq_video::encode::Kind::Software;
let rendition = config.probe().await.unwrap();
let reserved = catalog.reserve().with_hls_hint(config.gop.duration(config.framerate));
let reserved = catalog.reserve().with_interval(config.gop.duration(config.framerate));
let mut producer = moq_video::encode::Producer::new(broadcast.clone(), reserved, rendition).unwrap();
let mut encoder = moq_video::encode::Encoder::new(&config).unwrap();
let gray = vec![0x80; 64 * 64 * 4];
Expand Down
2 changes: 1 addition & 1 deletion rs/moq-studio/src/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1088,7 +1088,7 @@ impl Renditions {
let reserved = self
.timebase
.reserve()
.with_hls_hint(config.gop.duration(config.framerate));
.with_interval(config.gop.duration(config.framerate));
let producer = moq_video::encode::Producer::new(self.broadcast.clone(), reserved, video)?;
let watched = Arc::new(AtomicBool::new(false));
self.runtime.spawn(watch(producer.demand(), watched.clone()));
Expand Down
2 changes: 1 addition & 1 deletion rs/moq-studio/src/record.rs
Original file line number Diff line number Diff line change
Expand Up @@ -856,7 +856,7 @@ mod tests {
.iter()
.map(|(name, _, _, listing)| {
let mut listing = listing.snapshot();
listing.clock = None;
listing.media = Default::default();
(name.clone(), listing)
})
.collect();
Expand Down
2 changes: 1 addition & 1 deletion rs/moq-studio/src/replay.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ mod tests {
let mut config = moq_video::encode::Config::new(64, 64, moq_video::FrameRate::new(30, 1).unwrap());
config.kind = moq_video::encode::Kind::Software;
let rendition = config.probe().await.unwrap();
let reserved = catalog.reserve().with_hls_hint(config.gop.duration(config.framerate));
let reserved = catalog.reserve().with_interval(config.gop.duration(config.framerate));
let mut producer = moq_video::encode::Producer::new(broadcast.clone(), reserved, rendition).unwrap();
let mut encoder = moq_video::encode::Encoder::new(&config).unwrap();
let gray = vec![0x80; 64 * 64 * 4];
Expand Down