Skip to content

Commit 507d100

Browse files
kixelatedclaude
andauthored
Miscellaneous fixes (#1291)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent e4d2377 commit 507d100

File tree

6 files changed

+32
-55
lines changed

6 files changed

+32
-55
lines changed

cdn/relay/flake.lock

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

doc/public/icon.png

5.82 KB
Loading

js/lite/src/connection/reload.ts

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -153,15 +153,11 @@ export class Reload {
153153

154154
effect.cleanup(() => this.#announced.set(new Set()));
155155

156-
// Warn if the relay doesn't support announcements (e.g. Cloudflare)
156+
// Cloudflare's relay does not yet support SUBSCRIBE_NAMESPACE, so
157+
// skip announce subscriptions entirely for those hosts.
157158
if (conn.url.hostname.endsWith("mediaoverquic.com")) {
158-
effect.timer(() => {
159-
if (this.#announced.peek().size === 0) {
160-
console.warn(
161-
"Cloudflare relay does not support the reload feature yet. Remove the `reload` attribute to connect without waiting for announcements.",
162-
);
163-
}
164-
}, 1000);
159+
console.warn("Cloudflare relay does not support broadcast discovery yet; skipping subscribe_namespace.");
160+
return;
165161
}
166162

167163
const announced = conn.announced(emptyPath());

js/watch/src/audio/source.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import type { Sync } from "../sync";
88
const WORKLET_QUANTUM = 128;
99

1010
// Extra milliseconds for decode + postMessage latency between main thread and worklet.
11-
const POST_MESSAGE_OVERHEAD = 5;
11+
const POST_MESSAGE_OVERHEAD = 10;
1212

1313
export type Target = {
1414
// Optional manual override for the selected rendition name.

js/watch/src/element.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import * as Moq from "@moq/lite";
33
import { Effect, Signal } from "@moq/signals";
44
import { MultiBackend } from "./backend";
55
import { Broadcast } from "./broadcast";
6-
import { type Latency, Sync } from "./sync";
6+
import type { Latency } from "./sync";
77

88
const OBSERVED = ["url", "name", "paused", "volume", "muted", "reload", "latency", "jitter"] as const;
99
type Observed = (typeof OBSERVED)[number];
@@ -23,9 +23,6 @@ export default class MoqWatch extends HTMLElement {
2323
// The broadcast being watched.
2424
broadcast: Broadcast;
2525

26-
// Used to sync audio and video playback at a target latency.
27-
sync = new Sync();
28-
2926
// The backend that powers this element.
3027
backend: MultiBackend;
3128

js/watch/src/sync.ts

Lines changed: 23 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,6 @@ import { Effect, Signal } from "@moq/signals";
44
/** Latency: `"real-time"` auto-computes jitter from RTT; a `Time.Milli` sets a fixed jitter. */
55
export type Latency = "real-time" | Time.Milli;
66

7-
interface Label {
8-
ref: Time.Milli;
9-
lateCount: number;
10-
lateMaxMs: number;
11-
}
12-
137
const MIN_JITTER = 10 as Time.Milli;
148
const FALLBACK_JITTER = 100 as Time.Milli;
159

@@ -49,10 +43,8 @@ export class Sync {
4943
// The media timestamp of the most recently received frame.
5044
readonly timestamp = new Signal<Time.Milli | undefined>(undefined);
5145

52-
// Per-label tracking: minimum reference and late-frame stats.
53-
// The effective #reference is the max across all labels,
54-
// ensuring the slowest track sets the pace (late audio stalls early video).
55-
#labels = new Map<string, Label>();
46+
// Per-label late-frame tracking: accumulate count and max lateness, flush on recovery.
47+
#late = new Map<string, { count: number; maxMs: number }>();
5648

5749
// RTT signal from the connection (PROBE or getStats).
5850
rtt?: Signal<number | undefined>;
@@ -118,51 +110,43 @@ export class Sync {
118110
this.#update = Promise.withResolvers();
119111
}
120112

121-
// Update the reference if this is the earliest frame we've seen for this label, relative to its timestamp.
122-
// The effective reference is the max across all labels so the slowest track sets the pace.
113+
// Update the reference if this is the earliest frame we've seen, relative to its timestamp.
123114
received(timestamp: Time.Milli, label = ""): void {
124115
this.timestamp.update((current) => (current === undefined || timestamp > current ? timestamp : current));
125116
const now = Time.Milli.now();
126117
const ref = Time.Milli.sub(now, timestamp);
127118
const currentRef = this.#reference.peek();
128119

129-
let entry = this.#labels.get(label);
130-
if (!entry) {
131-
entry = { ref, lateCount: 0, lateMaxMs: 0 };
132-
this.#labels.set(label, entry);
133-
}
134-
135120
if (currentRef !== undefined) {
136121
// Check if `wait()` would not sleep at all.
137122
// NOTE: We check here instead of in `wait()` so we can identify when frames are received late.
138123
// Otherwise, chained `wait()` calls would cause a false-positive during CPU starvation.
139124
const sleep = Time.Milli.add(Time.Milli.sub(currentRef, ref), this.#buffer.peek());
140125
if (sleep < 0) {
141-
entry.lateCount++;
142-
entry.lateMaxMs = Math.max(entry.lateMaxMs, -sleep);
143-
} else if (entry.lateCount > 0) {
144-
const prefix = label ? `sync[${label}]` : "sync";
145-
const behind = Sync.#formatDuration(entry.lateMaxMs);
146-
console.debug(`${prefix}: ${entry.lateCount} late frame(s), max ${behind} behind`);
147-
entry.lateCount = 0;
148-
entry.lateMaxMs = 0;
126+
const entry = this.#late.get(label);
127+
if (entry) {
128+
entry.count++;
129+
entry.maxMs = Math.max(entry.maxMs, -sleep);
130+
} else {
131+
this.#late.set(label, { count: 1, maxMs: -sleep });
132+
}
133+
} else {
134+
const entry = this.#late.get(label);
135+
if (entry) {
136+
const prefix = label ? `sync[${label}]` : "sync";
137+
const behind = Sync.#formatDuration(entry.maxMs);
138+
console.debug(`${prefix}: ${entry.count} late frame(s), max ${behind} behind`);
139+
this.#late.delete(label);
140+
}
149141
}
150-
}
151142

152-
// Update per-label reference (keep the minimum ref for each label).
153-
if (ref >= entry.ref) return;
154-
entry.ref = ref;
155-
156-
// Recompute effective reference as max of all per-label references.
157-
// Using max ensures the slowest track (e.g. late audio) stalls faster tracks (e.g. early video).
158-
let effectiveRef = ref;
159-
for (const { ref: r } of this.#labels.values()) {
160-
if (r > effectiveRef) effectiveRef = r;
143+
if (ref >= currentRef) {
144+
// Our frame was not relatively newer than any other frame.
145+
return;
146+
}
161147
}
162148

163-
if (currentRef === effectiveRef) return;
164-
165-
this.#reference.set(effectiveRef);
149+
this.#reference.set(ref);
166150
this.#update.resolve();
167151
this.#update = Promise.withResolvers();
168152
}

0 commit comments

Comments
 (0)