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
13 changes: 8 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -409,9 +409,10 @@ it is unhealthy. Below that it shows:

- **Relays**: each relay's state, and the protocol its session negotiated.
- **Uplink**: the round trip to the first connected relay, the bandwidth the
connection estimates it can carry, the media every track writes, and the
share lost and resent. Loss and headroom are graded only while someone
watches, since the relay pulls nothing otherwise.
connection estimates it can carry, the media the program's tracks write, and
the share lost and resent. Loss and headroom are graded only while someone
watches, since the relay pulls nothing otherwise. Source tracks are left out
of the media, since they encode whether or not anyone pulls them.
- **Watching**: the sessions subscribed to the broadcast, from the relays'
`.stats` broadcasts. A relay shares those only with a connection allowed to
read them, so most show "unknown". In a cluster, a relay forwarding the
Expand All @@ -420,8 +421,10 @@ it is unhealthy. Below that it shows:
the newer instance, or the first relay's when they are one instance.
- **Tracks**: each track's bitrate and frame rate, and, from viewers that
report, the lowest bitrate any received, its worst grade, and the latency
from the program clock to the slowest viewer. Latency compares the two
hosts' wall clocks, so keep both synced.
from the program clock to the slowest viewer. The program's tracks come
first, then each source's own tracks, grouped by source; a still image or
color carries no media to grade. Latency compares the two hosts' wall
clocks, so keep both synced.
- **Viewers**: each viewer that reports, with its round trip, latency, and
playout delay. The studio reads 64 at most, a sample the grades rest on: it
counts the rest, and a reader one frees goes to the next viewer to arrive.
Expand Down
1 change: 0 additions & 1 deletion quest/m0/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,6 @@ repository.
- [Broadcast sources play in sync](/quest/m0/broadcast-sync.md) - subscribed broadcasts and guests play audio and video on the publisher's timestamps, with `moq play`'s delay
- [Wayland display and window sources](/quest/m0/wayland-sources.md) - portal-picked sources that reopen without the picker
- [Live encode control](/quest/m0/encode-control.md) - simulcast rungs that follow the bandwidth grant, live bitrate and resolution, keyframes on request
- [Health grades source tracks](/quest/m0/health-sources.md) - the Health panel covers each published source track, not only the program
- [Audio sources report stalls](/quest/m0/audio-stall.md) - a dead microphone or system audio reads stalled like a camera
- [Instant replay and DVR](/quest/m0/replay.md) - replays and clips from held groups, and viewer scrubbing through the archive
- [HTML/CSS overlays](/quest/m0/overlays.md) - script-free HTML and CSS graphics through Blitz natively and the DOM in the browser
Expand Down
22 changes: 0 additions & 22 deletions quest/m0/health-sources.md

This file was deleted.

31 changes: 30 additions & 1 deletion rs/moq-scene/src/publish.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,13 @@ pub struct Publish {
owners: Arc<Mutex<Owners>>,
}

/// Which [`Claim`] owns each source name's entry in the studio catalog.
/// Which [`Claim`] owns each source name's entry in the studio catalog, and
/// each open [`Listing`]'s catalog by its claim.
#[derive(Default)]
struct Owners {
next: u64,
by_name: BTreeMap<String, u64>,
catalogs: BTreeMap<u64, moq_mux::catalog::Producer>,
}

impl Publish {
Expand All @@ -90,6 +92,17 @@ impl Publish {
}
}

/// What each source track the studio catalog lists has written, keyed by
/// rendition ID, as [`Catalog::stats`](moq_mux::catalog::Producer::stats)
/// reports the program's. The broadcast names every track uniquely, so all
/// sources share one map. A replaced source's tracks drop out with its
/// catalog entry, even while it is still stopping.
pub fn stats(&self) -> BTreeMap<String, hang::stats::Rendition> {
let owners = self.owners.lock().unwrap();
let listed = owners.by_name.values().filter_map(|id| owners.catalogs.get(id));
listed.flat_map(|catalog| catalog.stats()).collect()
}

/// Claim `name`'s entry in the studio catalog for a source that is opening,
/// taking it from any older source of that name.
pub(crate) fn claim(&self, name: &str) -> Claim {
Expand Down Expand Up @@ -155,6 +168,7 @@ impl<'a> Listing<'a> {
.with_bandwidth(publish.bandwidth.clone())
.with_hls(None);
let catalog = moq_mux::catalog::Producer::new(&mut scratch, config)?;
claim.owners.lock().unwrap().catalogs.insert(claim.id, catalog.clone());
Ok(Self {
claim,
kind: publish.kind.clone(),
Expand Down Expand Up @@ -194,6 +208,7 @@ impl<'a> Listing<'a> {

impl Drop for Listing<'_> {
fn drop(&mut self) {
self.claim.owners.lock().unwrap().catalogs.remove(&self.claim.id);
// Fails only once the studio catalog closed, which took the entry with it.
let _ = self.studio.mutate(|root| {
if self.claim.owned() {
Expand Down Expand Up @@ -539,6 +554,20 @@ mod tests {
}
} => {}
}

// Every frame written counts for the Health panel, while the
// studio catalog lists the source.
let stats = publish.stats();
assert_eq!(
stats.values().map(|rendition| rendition.frames).sum::<u64>(),
5,
"{stats:?}"
);
// A replacement takes the listing, and the old tracks with it.
let _newer = publish.claim("camera");
assert!(publish.stats().is_empty());
drop(listing);
assert!(publish.owners.lock().unwrap().catalogs.is_empty());
})
.await;
}
Expand Down
1 change: 1 addition & 0 deletions rs/moq-studio/src/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -693,6 +693,7 @@ async fn publish(
relays: links,
feedback,
watching,
sources: sources.clone(),
};

let output = Output {
Expand Down
Loading