@@ -10,6 +10,42 @@ import getMonotonicTimeStamp from "../../../utils/monotonic_timestamp";
1010import type SegmentSinksStore from "../../segment_sinks" ;
1111import type { IBufferedChunk } from "../../segment_sinks" ;
1212
13+ /**
14+ * "Freezing" is a complex situation indicating that playback is not advancing
15+ * despite no valid reason for it not to.
16+ *
17+ * Technically, there's multiple scenarios in which the RxPlayer will consider
18+ * the stream as "freezing" and try to fix it.
19+ * One of those scenarios is when there's a
20+ * `HTMLMediaElement.prototype.readyState` set to `1` (which is how the browser
21+ * tells us that it doesn't have any data to play), despite the fact that
22+ * there's actually data in the buffer.
23+ *
24+ * The `MINIMUM_BUFFER_GAP_AT_READY_STATE_1_BEFORE_FREEZING` is the minimum
25+ * buffer size in seconds after which we will suspect a "freezing" scenario if
26+ * the `readyState` is still at `1`.
27+ */
28+ const MINIMUM_BUFFER_GAP_AT_READY_STATE_1_BEFORE_FREEZING = 6 ;
29+
30+ /**
31+ * To avoid handling freezes (e.g. "reloading" or "seeking") in a loop when
32+ * things go wrong, we have a security delay in milliseconds, this
33+ * `MINIMUM_TIME_BETWEEN_FREEZE_HANDLING` constant, which we'll await between
34+ * un-freezing attempts.
35+ */
36+ const MINIMUM_TIME_BETWEEN_FREEZE_HANDLING = 6000 ;
37+
38+ /**
39+ * We maintain here a short-term history of what segments have been played
40+ * recently, to then implement heuristics detecting if a freeze was due to a
41+ * particular quality or track.
42+ *
43+ * To avoid growing that history indefinitely in size, we only save data
44+ * corresponding to the last `MAXIMUM_SEGMENT_HISTORY_RETENTION_TIME`
45+ * milliseconds from now.
46+ */
47+ const MAXIMUM_SEGMENT_HISTORY_RETENTION_TIME = 60000 ;
48+
1349/**
1450 * Set when there is a freeze which seems to be specifically linked to a,
1551 * or multiple, content's `Representation` despite no attribute of it
@@ -139,9 +175,9 @@ export default class FreezeResolver {
139175
140176 /**
141177 * Check that playback is not freezing, and if it is, return a solution that
142- * should be atempted to unfreeze it.
178+ * should be attempted to unfreeze it.
143179 *
144- * Returns `null` either when there's no freeze is happening or if there's one
180+ * Returns `null` either when there's no freeze happening or if there's one
145181 * but there's nothing we should do about it yet.
146182 *
147183 * Refer to the returned type's definition for more information.
@@ -178,7 +214,10 @@ export default class FreezeResolver {
178214 // playback. Yet, rebuffering occurences can also be abnormal, such as
179215 // when enough buffer is constructed but with a low readyState (those are
180216 // generally decryption issues).
181- ( rebuffering !== null && readyState === 1 && ( bufferGap >= 6 || fullyLoaded ) ) ;
217+ ( rebuffering !== null &&
218+ readyState === 1 &&
219+ ( bufferGap >= MINIMUM_BUFFER_GAP_AT_READY_STATE_1_BEFORE_FREEZING ||
220+ fullyLoaded ) ) ;
182221
183222 if ( ! isFrozen ) {
184223 this . _decipherabilityFreezeStartingTimestamp = null ;
@@ -202,7 +241,7 @@ export default class FreezeResolver {
202241 if ( recentFlushAttemptFailed ) {
203242 const secondUnfreezeStrat = this . _getStrategyIfFlushingFails ( freezingPosition ) ;
204243 this . _decipherabilityFreezeStartingTimestamp = null ;
205- this . _ignoreFreezeUntil = now + 6000 ;
244+ this . _ignoreFreezeUntil = now + MINIMUM_TIME_BETWEEN_FREEZE_HANDLING ;
206245 return secondUnfreezeStrat ;
207246 }
208247
@@ -222,7 +261,7 @@ export default class FreezeResolver {
222261 log . debug ( "FR: trying to flush to un-freeze" ) ;
223262
224263 this . _decipherabilityFreezeStartingTimestamp = null ;
225- this . _ignoreFreezeUntil = now + 6000 ;
264+ this . _ignoreFreezeUntil = now + MINIMUM_TIME_BETWEEN_FREEZE_HANDLING ;
226265 return {
227266 type : "flush" ,
228267 value : { relativeSeek : UNFREEZING_DELTA_POSITION } ,
@@ -280,7 +319,7 @@ export default class FreezeResolver {
280319 if ( representation . decipherable === false ) {
281320 log . warn ( "FR: we have undecipherable segments left in the buffer, reloading" ) ;
282321 this . _decipherabilityFreezeStartingTimestamp = null ;
283- this . _ignoreFreezeUntil = now + 6000 ;
322+ this . _ignoreFreezeUntil = now + MINIMUM_TIME_BETWEEN_FREEZE_HANDLING ;
284323 return { type : "reload" , value : null } ;
285324 } else if ( representation . contentProtections !== undefined ) {
286325 isClear = false ;
@@ -298,7 +337,7 @@ export default class FreezeResolver {
298337 "segments left in the buffer, reloading" ,
299338 ) ;
300339 this . _decipherabilityFreezeStartingTimestamp = null ;
301- this . _ignoreFreezeUntil = now + 6000 ;
340+ this . _ignoreFreezeUntil = now + MINIMUM_TIME_BETWEEN_FREEZE_HANDLING ;
302341 return { type : "reload" , value : null } ;
303342 }
304343 return null ;
@@ -459,7 +498,7 @@ export default class FreezeResolver {
459498 this . _lastSegmentInfo [ ttype ] . splice ( 0 , toRemove ) ;
460499 }
461500
462- const removalTs = currentTimestamp - 60000 ;
501+ const removalTs = currentTimestamp - MAXIMUM_SEGMENT_HISTORY_RETENTION_TIME ;
463502 let i ;
464503 for ( i = 0 ; i < this . _lastSegmentInfo [ ttype ] . length ; i ++ ) {
465504 if ( this . _lastSegmentInfo [ ttype ] [ i ] . timestamp > removalTs ) {
0 commit comments