@@ -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. */
55export type Latency = "real-time" | Time . Milli ;
66
7- interface Label {
8- ref : Time . Milli ;
9- lateCount : number ;
10- lateMaxMs : number ;
11- }
12-
137const MIN_JITTER = 10 as Time . Milli ;
148const 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