@@ -24,7 +24,11 @@ import {
2424 getAesModeFromFullSegmentMethod ,
2525 isFullSegmentEncryption ,
2626} from '../utils/encryption-methods-util' ;
27- import { getRetryDelay } from '../utils/error-helper' ;
27+ import { getRetryDelay , offlineHttpStatus } from '../utils/error-helper' ;
28+ import {
29+ addEventListener ,
30+ removeEventListener ,
31+ } from '../utils/event-listener-helper' ;
2832import {
2933 findPart ,
3034 getFragmentWithSN ,
@@ -284,10 +288,8 @@ export default class BaseStreamController
284288 data : MediaAttachedData ,
285289 ) {
286290 const media = ( this . media = this . mediaBuffer = data . media ) ;
287- media . removeEventListener ( 'seeking' , this . onMediaSeeking ) ;
288- media . removeEventListener ( 'ended' , this . onMediaEnded ) ;
289- media . addEventListener ( 'seeking' , this . onMediaSeeking ) ;
290- media . addEventListener ( 'ended' , this . onMediaEnded ) ;
291+ addEventListener ( media , 'seeking' , this . onMediaSeeking ) ;
292+ addEventListener ( media , 'ended' , this . onMediaEnded ) ;
291293 const config = this . config ;
292294 if ( this . levels && config . autoStartLoad && this . state === State . STOPPED ) {
293295 this . startLoad ( config . startPosition ) ;
@@ -309,8 +311,8 @@ export default class BaseStreamController
309311 }
310312
311313 // remove video listeners
312- media . removeEventListener ( 'seeking' , this . onMediaSeeking ) ;
313- media . removeEventListener ( 'ended' , this . onMediaEnded ) ;
314+ removeEventListener ( media , 'seeking' , this . onMediaSeeking ) ;
315+ removeEventListener ( media , 'ended' , this . onMediaEnded ) ;
314316
315317 if ( this . keyLoader && ! transferringMedia ) {
316318 this . keyLoader . detach ( ) ;
@@ -424,8 +426,10 @@ export default class BaseStreamController
424426 }
425427 }
426428
427- // Async tick to speed up processing
428- this . tickImmediate ( ) ;
429+ if ( noFowardBuffer && this . state === State . IDLE ) {
430+ // Async tick to speed up processing
431+ this . tickImmediate ( ) ;
432+ }
429433 } ;
430434
431435 protected onMediaEnded = ( ) => {
@@ -1883,38 +1887,49 @@ export default class BaseStreamController
18831887 }
18841888 // keep retrying until the limit will be reached
18851889 const errorAction = data . errorAction ;
1886- const { action, flags, retryCount = 0 , retryConfig } = errorAction || { } ;
1887- const couldRetry = ! ! errorAction && ! ! retryConfig ;
1890+ if ( ! errorAction ) {
1891+ this . state = State . ERROR ;
1892+ return ;
1893+ }
1894+ const { action, flags, retryCount = 0 , retryConfig } = errorAction ;
1895+ const couldRetry = ! ! retryConfig ;
18881896 const retry = couldRetry && action === NetworkErrorAction . RetryRequest ;
18891897 const noAlternate =
18901898 couldRetry &&
18911899 ! errorAction . resolved &&
18921900 flags === ErrorActionFlags . MoveAllAlternatesMatchingHost ;
1893- const httpStatus = data . response ?. code || 0 ;
1901+ const live = this . hls . latestLevelDetails ?. live ;
18941902 if (
18951903 ! retry &&
18961904 noAlternate &&
18971905 isMediaFragment ( frag ) &&
18981906 ! frag . endList &&
1899- httpStatus !== 0
1907+ live
19001908 ) {
19011909 this . resetFragmentErrors ( filterType ) ;
19021910 this . treatAsGap ( frag ) ;
19031911 errorAction . resolved = true ;
19041912 } else if ( ( retry || noAlternate ) && retryCount < retryConfig . maxNumRetry ) {
1905- this . resetStartWhenNotLoaded ( this . levelLastLoaded ) ;
1913+ const offlineStatus = offlineHttpStatus ( data . response ?. code ) ;
19061914 const delay = getRetryDelay ( retryConfig , retryCount ) ;
1915+ this . resetStartWhenNotLoaded ( ) ;
1916+ this . retryDate = self . performance . now ( ) + delay ;
1917+ this . state = State . FRAG_LOADING_WAITING_RETRY ;
1918+ errorAction . resolved = true ;
1919+ if ( offlineStatus ) {
1920+ this . log ( `Waiting for connection (offline)` ) ;
1921+ this . retryDate = Infinity ;
1922+ data . reason = 'offline' ;
1923+ return ;
1924+ }
19071925 this . warn (
19081926 `Fragment ${ frag . sn } of ${ filterType } ${ frag . level } errored with ${
19091927 data . details
19101928 } , retrying loading ${ retryCount + 1 } /${
19111929 retryConfig . maxNumRetry
19121930 } in ${ delay } ms`,
19131931 ) ;
1914- errorAction . resolved = true ;
1915- this . retryDate = self . performance . now ( ) + delay ;
1916- this . state = State . FRAG_LOADING_WAITING_RETRY ;
1917- } else if ( retryConfig && errorAction ) {
1932+ } else if ( retryConfig ) {
19181933 this . resetFragmentErrors ( filterType ) ;
19191934 if ( retryCount < retryConfig . maxNumRetry ) {
19201935 // Network retry is skipped when level switch is preferred
@@ -1939,6 +1954,24 @@ export default class BaseStreamController
19391954 this . tickImmediate ( ) ;
19401955 }
19411956
1957+ protected checkRetryDate ( ) {
1958+ const now = self . performance . now ( ) ;
1959+ const retryDate = this . retryDate ;
1960+ // if current time is gt than retryDate, or if media seeking let's switch to IDLE state to retry loading
1961+ const waitingForConnection = retryDate === Infinity ;
1962+ if (
1963+ ! retryDate ||
1964+ now >= retryDate ||
1965+ ( waitingForConnection && ! offlineHttpStatus ( 0 ) )
1966+ ) {
1967+ if ( waitingForConnection ) {
1968+ this . log ( `Connection restored (online)` ) ;
1969+ }
1970+ this . resetStartWhenNotLoaded ( ) ;
1971+ this . state = State . IDLE ;
1972+ }
1973+ }
1974+
19421975 protected reduceLengthAndFlushBuffer ( data : ErrorData ) : boolean {
19431976 // if in appending state
19441977 if ( this . state === State . PARSING || this . state === State . PARSED ) {
@@ -2018,11 +2051,12 @@ export default class BaseStreamController
20182051 }
20192052 }
20202053
2021- protected resetStartWhenNotLoaded ( level : Level | null ) : void {
2054+ private resetStartWhenNotLoaded ( ) {
20222055 // if loadedmetadata is not set, it means that first frag request failed
20232056 // in that case, reset startFragRequested flag
20242057 if ( ! this . hls . hasEnoughToStart ) {
20252058 this . startFragRequested = false ;
2059+ const level = this . levelLastLoaded ;
20262060 const details = level ? level . details : null ;
20272061 if ( details ?. live ) {
20282062 // Update the start position and return to IDLE to recover live start
@@ -2041,7 +2075,7 @@ export default class BaseStreamController
20412075 `Loading context changed while buffering sn ${ chunkMeta . sn } of ${ this . playlistLabel ( ) } ${ chunkMeta . level === - 1 ? '<removed>' : chunkMeta . level } . This chunk will not be buffered.` ,
20422076 ) ;
20432077 this . removeUnbufferedFrags ( ) ;
2044- this . resetStartWhenNotLoaded ( this . levelLastLoaded ) ;
2078+ this . resetStartWhenNotLoaded ( ) ;
20452079 this . resetLoadingState ( ) ;
20462080 }
20472081
@@ -2176,7 +2210,7 @@ export default class BaseStreamController
21762210 this . transmuxer . destroy ( ) ;
21772211 this . transmuxer = null ;
21782212 }
2179- this . resetStartWhenNotLoaded ( this . levelLastLoaded ) ;
2213+ this . resetStartWhenNotLoaded ( ) ;
21802214 this . resetLoadingState ( ) ;
21812215 }
21822216 }
0 commit comments