@@ -35,7 +35,7 @@ function nullBuffer(execute) {
3535 execute ( "o" , text ) ;
3636 } ,
3737
38- stop ( ) { } ,
38+ stop ( ) { } ,
3939 } ;
4040}
4141
@@ -123,71 +123,141 @@ function sleep(t) {
123123 } ) ;
124124}
125125
126- function adaptiveBufferTimeProvider ( { logger } , { minTime = 25 , maxLevel = 100 , interval = 50 , windowSize = 20 , smoothingFactor = 0.2 , minImprovementDuration = 1000 } ) {
127- let bufferLevel = 0 ;
128- let bufferTime = calcBufferTime ( bufferLevel ) ;
129- let latencies = [ ] ;
130- let maxJitter = 0 ;
131- let jitterRange = 0 ;
132- let improvementTs = null ;
133-
134- function calcBufferTime ( level ) {
135- if ( level === 0 ) {
136- return minTime ;
126+ function adaptiveBufferTimeProvider (
127+ { logger } = { } ,
128+ {
129+ minBufferTime = 50 ,
130+ bufferLevelStep = 100 ,
131+ maxBufferLevel = 50 ,
132+ transitionDuration = 500 ,
133+ peakHalfLifeUp = 100 ,
134+ peakHalfLifeDown = 10000 ,
135+ floorHalfLifeUp = 5000 ,
136+ floorHalfLifeDown = 100 ,
137+ idealHalfLifeUp = 1000 ,
138+ idealHalfLifeDown = 5000 ,
139+ safetyMultiplier = 1.2 ,
140+ minImprovementDuration = 3000 ,
141+ } = { }
142+ ) {
143+ function levelToMs ( level ) {
144+ return level === 0 ? minBufferTime : bufferLevelStep * level ;
145+ }
146+
147+ let bufferLevel = 1 ;
148+ let bufferTime = levelToMs ( bufferLevel ) ;
149+ let lastUpdateTime = performance . now ( ) ;
150+ let smoothedPeakLatency = null ;
151+ let smoothedFloorLatency = null ;
152+ let smoothedIdealBufferTime = null ;
153+ let stableSince = null ;
154+ let targetBufferTime = null ;
155+ let transitionRate = null ;
156+
157+ return function ( latency ) {
158+ const now = performance . now ( ) ;
159+ const dt = Math . max ( 0 , now - lastUpdateTime ) ;
160+ lastUpdateTime = now ;
161+
162+ // adjust EMA-smoothed peak latency from current latency
163+
164+ if ( smoothedPeakLatency === null ) {
165+ smoothedPeakLatency = latency ;
166+ } else if ( latency > smoothedPeakLatency ) {
167+ const alphaUp = 1 - Math . pow ( 2 , - dt / peakHalfLifeUp ) ;
168+ smoothedPeakLatency += alphaUp * ( latency - smoothedPeakLatency ) ;
137169 } else {
138- return interval * level ;
170+ const alphaDown = 1 - Math . pow ( 2 , - dt / peakHalfLifeDown ) ;
171+ smoothedPeakLatency += alphaDown * ( latency - smoothedPeakLatency ) ;
172+ }
173+
174+ smoothedPeakLatency = Math . max ( smoothedPeakLatency , 0 ) ;
175+
176+ // adjust EMA-smoothed floor latency from current latency
177+
178+ if ( smoothedFloorLatency === null ) {
179+ smoothedFloorLatency = latency ;
180+ } else if ( latency > smoothedFloorLatency ) {
181+ const alphaUp = 1 - Math . pow ( 2 , - dt / floorHalfLifeUp ) ;
182+ smoothedFloorLatency += alphaUp * ( latency - smoothedFloorLatency ) ;
183+ } else {
184+ const alphaDown = 1 - Math . pow ( 2 , - dt / floorHalfLifeDown ) ;
185+ smoothedFloorLatency += alphaDown * ( latency - smoothedFloorLatency ) ;
186+ }
187+
188+ smoothedFloorLatency = Math . max ( smoothedFloorLatency , 0 ) ;
189+
190+ // adjust EMA-smoothed ideal buffer time
191+
192+ const jitter = smoothedPeakLatency - smoothedFloorLatency ;
193+ const idealBufferTime = safetyMultiplier * ( smoothedPeakLatency + jitter ) ;
194+
195+ if ( smoothedIdealBufferTime === null ) {
196+ smoothedIdealBufferTime = idealBufferTime ;
197+ } else if ( idealBufferTime > smoothedIdealBufferTime ) {
198+ const alphaUp = 1 - Math . pow ( 2 , - dt / idealHalfLifeUp ) ;
199+ smoothedIdealBufferTime += + alphaUp * ( idealBufferTime - smoothedIdealBufferTime ) ;
200+ } else {
201+ const alphaDown = 1 - Math . pow ( 2 , - dt / idealHalfLifeDown ) ;
202+ smoothedIdealBufferTime += + alphaDown * ( idealBufferTime - smoothedIdealBufferTime ) ;
139203 }
140- }
141204
142- return ( latency ) => {
143- latencies . push ( latency ) ;
205+ // quantize smoothed ideal buffer time to discrete buffer level
144206
145- if ( latencies . length < windowSize ) {
146- return bufferTime ;
147- } ;
207+ let newBufferLevel ;
148208
149- latencies = latencies . slice ( - windowSize ) ;
150- const currentMinJitter = min ( latencies ) ;
151- const currentMaxJitter = max ( latencies ) ;
152- const currentJitterRange = currentMaxJitter - currentMinJitter ;
153- maxJitter = currentMaxJitter * smoothingFactor + maxJitter * ( 1 - smoothingFactor ) ;
154- jitterRange = currentJitterRange * smoothingFactor + jitterRange * ( 1 - smoothingFactor ) ;
155- const minBufferTime = maxJitter + jitterRange ;
209+ if ( smoothedIdealBufferTime <= minBufferTime ) {
210+ newBufferLevel = 0 ;
211+ } else {
212+ newBufferLevel = clamp ( Math . ceil ( smoothedIdealBufferTime / bufferLevelStep ) , 1 , maxBufferLevel ) ;
213+ }
156214
157215 if ( latency > bufferTime ) {
158- logger . debug ( 'buffer underrun' , { latency, maxJitter , jitterRange , bufferTime } ) ;
216+ logger . debug ( 'buffer underrun' , { latency, bufferTime } ) ;
159217 }
160218
161- if ( bufferLevel < maxLevel && minBufferTime > bufferTime ) {
162- bufferTime = calcBufferTime ( ( bufferLevel += 1 ) ) ;
163- logger . debug ( `jitter increased, raising bufferTime` , { latency, maxJitter, jitterRange, bufferTime } ) ;
164- } else if (
165- ( bufferLevel > 1 && minBufferTime < calcBufferTime ( bufferLevel - 2 ) ) ||
166- ( bufferLevel == 1 && minBufferTime < calcBufferTime ( bufferLevel - 1 ) )
167- ) {
168- if ( improvementTs === null ) {
169- improvementTs = performance . now ( ) ;
170- } else if ( performance . now ( ) - improvementTs > minImprovementDuration ) {
171- improvementTs = performance . now ( ) ;
172- bufferTime = calcBufferTime ( ( bufferLevel -= 1 ) ) ;
173- logger . debug ( `jitter decreased, lowering bufferTime` , { latency, maxJitter, jitterRange, bufferTime } ) ;
219+ // adjust buffer level and target buffer time for new buffer level
220+
221+ if ( newBufferLevel > bufferLevel ) {
222+ if ( latency > bufferTime ) { // <- underrun - raise quickly
223+ bufferLevel = Math . min ( newBufferLevel , bufferLevel + 3 ) ;
224+ } else {
225+ bufferLevel += 1 ;
174226 }
175227
176- return bufferTime ;
228+ targetBufferTime = levelToMs ( bufferLevel ) ;
229+ transitionRate = ( targetBufferTime - bufferTime ) / transitionDuration ;
230+ stableSince = null ;
231+ logger . debug ( 'raising buffer' , { latency, bufferTime, targetBufferTime } ) ;
232+ } else if ( newBufferLevel < bufferLevel ) {
233+ if ( stableSince == null ) stableSince = now ;
234+
235+ if ( now - stableSince >= minImprovementDuration ) {
236+ bufferLevel -= 1 ;
237+ targetBufferTime = levelToMs ( bufferLevel ) ;
238+ transitionRate = ( targetBufferTime - bufferTime ) / transitionDuration ;
239+ stableSince = now ;
240+ logger . debug ( 'lowering buffer' , { latency, bufferTime, targetBufferTime } ) ;
241+ }
242+ } else {
243+ stableSince = null ;
177244 }
178245
179- improvementTs = null ;
246+ // linear transition to target buffer time
247+
248+ if ( targetBufferTime !== null ) {
249+ bufferTime += transitionRate * dt ;
250+
251+ if ( transitionRate >= 0 && bufferTime > targetBufferTime || transitionRate < 0 && bufferTime < targetBufferTime ) {
252+ bufferTime = targetBufferTime ;
253+ targetBufferTime = null ;
254+ }
255+ }
180256
181257 return bufferTime ;
182258 } ;
183259}
184260
185- function min ( numbers ) {
186- return numbers . reduce ( ( prev , cur ) => cur < prev ? cur : prev ) ;
187- }
188-
189- function max ( numbers ) {
190- return numbers . reduce ( ( prev , cur ) => cur > prev ? cur : prev ) ;
191- }
261+ function clamp ( x , lo , hi ) { return Math . min ( hi , Math . max ( lo , x ) ) ; }
192262
193263export default getBuffer ;
0 commit comments