@@ -44,6 +44,7 @@ export type IErrorAction = {
44
44
export default class ErrorController implements NetworkComponentAPI {
45
45
private readonly hls : Hls ;
46
46
private playlistError : number = 0 ;
47
+ private failoverError ?: ErrorData ;
47
48
private log : ( msg : any ) => void ;
48
49
private warn : ( msg : any ) => void ;
49
50
private error : ( msg : any ) => void ;
@@ -146,11 +147,9 @@ export default class ErrorController implements NetworkComponentAPI {
146
147
if (
147
148
level &&
148
149
( ( context . type === PlaylistContextType . AUDIO_TRACK &&
149
- level . audioGroupIds &&
150
- context . groupId === level . audioGroupIds [ level . urlId ] ) ||
150
+ context . groupId === level . audioGroupId ) ||
151
151
( context . type === PlaylistContextType . SUBTITLE_TRACK &&
152
- level . textGroupIds &&
153
- context . groupId === level . textGroupIds [ level . urlId ] ) )
152
+ context . groupId === level . textGroupId ) )
154
153
) {
155
154
// Perform Pathway switch or Redundant failover if possible for fastest recovery
156
155
// otherwise allow playlist retry count to reach max error retries
@@ -310,23 +309,23 @@ export default class ErrorController implements NetworkComponentAPI {
310
309
if ( data . details !== ErrorDetails . FRAG_GAP ) {
311
310
level . loadError ++ ;
312
311
}
313
- const redundantLevels = level . url . length ;
314
- // Try redundant fail-over until level.loadError reaches redundantLevels
315
- if ( redundantLevels > 1 && level . loadError < redundantLevels ) {
316
- data . levelRetry = true ;
317
- } else if ( hls . autoLevelEnabled ) {
312
+ if ( hls . autoLevelEnabled ) {
318
313
// Search for next level to retry
319
314
let nextLevel = - 1 ;
320
315
const levels = hls . levels ;
316
+ const fragErrorType = data . frag ?. type ;
317
+ const { type : playlistErrorType , groupId : playlistErrorGroupId } =
318
+ data . context ?? { } ;
321
319
for ( let i = levels . length ; i -- ; ) {
322
320
const candidate = ( i + hls . loadLevel ) % levels . length ;
323
321
if (
324
322
candidate !== hls . loadLevel &&
325
323
levels [ candidate ] . loadError === 0
326
324
) {
325
+ const levelCandidate = levels [ candidate ] ;
327
326
// Skip level switch if GAP tag is found in next level at same position
328
327
if ( data . details === ErrorDetails . FRAG_GAP && data . frag ) {
329
- const levelDetails = hls . levels [ candidate ] . details ;
328
+ const levelDetails = levels [ candidate ] . details ;
330
329
if ( levelDetails ) {
331
330
const fragCandidate = findFragmentByPTS (
332
331
data . frag ,
@@ -337,6 +336,22 @@ export default class ErrorController implements NetworkComponentAPI {
337
336
continue ;
338
337
}
339
338
}
339
+ } else if (
340
+ ( playlistErrorType === PlaylistContextType . AUDIO_TRACK &&
341
+ playlistErrorGroupId === levelCandidate . audioGroupId ) ||
342
+ ( playlistErrorType === PlaylistContextType . SUBTITLE_TRACK &&
343
+ playlistErrorGroupId === levelCandidate . textGroupId )
344
+ ) {
345
+ // For audio/subs playlist errors find another group ID or fallthrough to redundant fail-over
346
+ continue ;
347
+ } else if (
348
+ ( fragErrorType === PlaylistLevelType . AUDIO &&
349
+ level . audioGroupId === levelCandidate . audioGroupId ) ||
350
+ ( fragErrorType === PlaylistLevelType . SUBTITLE &&
351
+ level . textGroupId === levelCandidate . textGroupId )
352
+ ) {
353
+ // For audio/subs frag errors find another group ID or fallthrough to redundant fail-over
354
+ continue ;
340
355
}
341
356
nextLevel = candidate ;
342
357
break ;
@@ -366,7 +381,10 @@ export default class ErrorController implements NetworkComponentAPI {
366
381
break ;
367
382
case NetworkErrorAction . SendAlternateToPenaltyBox :
368
383
this . sendAlternateToPenaltyBox ( data ) ;
369
- if ( ! data . errorAction . resolved ) {
384
+ if (
385
+ ! data . errorAction . resolved &&
386
+ data . details !== ErrorDetails . FRAG_GAP
387
+ ) {
370
388
data . fatal = true ;
371
389
}
372
390
break ;
@@ -395,13 +413,9 @@ export default class ErrorController implements NetworkComponentAPI {
395
413
break ;
396
414
case ErrorActionFlags . MoveAllAlternatesMatchingHost :
397
415
{
398
- const levelIndex =
399
- data . parent === PlaylistLevelType . MAIN
400
- ? ( data . level as number )
401
- : hls . loadLevel ;
402
- // Handle Redundant Levels here. Patway switching is handled by content-steering-controller
416
+ // Handle Redundant Levels here. Pathway switching is handled by content-steering-controller
403
417
if ( ! errorAction . resolved ) {
404
- errorAction . resolved = this . redundantFailover ( levelIndex ) ;
418
+ errorAction . resolved = this . redundantFailover ( data ) ;
405
419
}
406
420
}
407
421
break ;
@@ -431,13 +445,27 @@ export default class ErrorController implements NetworkComponentAPI {
431
445
}
432
446
}
433
447
434
- private redundantFailover ( levelIndex : number ) : boolean {
435
- const hls = this . hls ;
448
+ private redundantFailover ( data : ErrorData ) : boolean {
449
+ const { hls, failoverError } = this ;
450
+ const levelIndex : number =
451
+ data . parent === PlaylistLevelType . MAIN
452
+ ? ( data . level as number )
453
+ : hls . loadLevel ;
436
454
const level = hls . levels [ levelIndex ] ;
437
455
const redundantLevels = level . url . length ;
438
- if ( redundantLevels > 1 ) {
456
+ const newUrlId = ( level . urlId + 1 ) % redundantLevels ;
457
+ if (
458
+ redundantLevels > 1 &&
459
+ // FIXME: Don't loop back to first redundant renditions unless the last gap is more than 60 seconds ago
460
+ // TODO: We throw out information about the other redundant renditions but should keep track of gap ranges to avoid looping back to the same problem
461
+ ( newUrlId !== 0 ||
462
+ ! failoverError ||
463
+ ( failoverError . frag &&
464
+ data . frag &&
465
+ Math . abs ( data . frag . start - failoverError . frag . start ) > 60 ) )
466
+ ) {
467
+ this . failoverError = data ;
439
468
// Update the url id of all levels so that we stay on the same set of variants when level switching
440
- const newUrlId = ( level . urlId + 1 ) % redundantLevels ;
441
469
this . log (
442
470
`Switching to Redundant Stream ${ newUrlId + 1 } /${ redundantLevels } : "${
443
471
level . url [ newUrlId ]
0 commit comments