2020-01-21 06:02:29 +03:00
/ * *
* @ module LevelHelper
*
* Providing methods dealing with playlist sliding and drift
*
* TODO : Create an actual ` Level ` class / model that deals with all this logic in an object - oriented - manner .
*
* * /
2020-01-24 07:54:29 +03:00
export function addGroupId ( level , type , id ) {
2020-01-21 06:02:29 +03:00
switch ( type ) {
2020-01-24 07:54:29 +03:00
case 'audio' :
if ( ! level . audioGroupIds ) {
level . audioGroupIds = [ ] ;
}
level . audioGroupIds . push ( id ) ;
break ;
case 'text' :
if ( ! level . textGroupIds ) {
level . textGroupIds = [ ] ;
}
level . textGroupIds . push ( id ) ;
break ;
2020-01-21 06:02:29 +03:00
}
}
2020-01-24 07:54:29 +03:00
export function updatePTS ( fragments , fromIdx , toIdx ) {
2020-01-21 06:02:29 +03:00
let fragFrom = fragments [ fromIdx ] , fragTo = fragments [ toIdx ] , fragToPTS = fragTo . startPTS ;
// if we know startPTS[toIdx]
if ( Number . isFinite ( fragToPTS ) ) {
// update fragment duration.
// it helps to fix drifts between playlist reported duration and fragment real duration
if ( toIdx > fromIdx ) {
fragFrom . duration = fragToPTS - fragFrom . start ;
if ( fragFrom . duration < 0 ) {
2020-01-24 07:54:29 +03:00
console . warn ( ` negative duration computed for frag ${ fragFrom . sn } ,level ${ fragFrom . level } , there should be some duration drift between playlist and fragment! ` ) ;
2020-01-21 06:02:29 +03:00
}
} else {
fragTo . duration = fragFrom . start - fragToPTS ;
if ( fragTo . duration < 0 ) {
2020-01-24 07:54:29 +03:00
console . warn ( ` negative duration computed for frag ${ fragTo . sn } ,level ${ fragTo . level } , there should be some duration drift between playlist and fragment! ` ) ;
2020-01-21 06:02:29 +03:00
}
}
} else {
// we dont know startPTS[toIdx]
if ( toIdx > fromIdx ) {
fragTo . start = fragFrom . start + fragFrom . duration ;
} else {
fragTo . start = Math . max ( fragFrom . start - fragTo . duration , 0 ) ;
}
}
}
2020-01-24 07:54:29 +03:00
export function updateFragPTSDTS ( details , frag , startPTS , endPTS , startDTS , endDTS ) {
2020-01-21 06:02:29 +03:00
// update frag PTS/DTS
let maxStartPTS = startPTS ;
if ( Number . isFinite ( frag . startPTS ) ) {
// delta PTS between audio and video
let deltaPTS = Math . abs ( frag . startPTS - startPTS ) ;
if ( ! Number . isFinite ( frag . deltaPTS ) ) {
frag . deltaPTS = deltaPTS ;
} else {
frag . deltaPTS = Math . max ( deltaPTS , frag . deltaPTS ) ;
}
maxStartPTS = Math . max ( startPTS , frag . startPTS ) ;
startPTS = Math . min ( startPTS , frag . startPTS ) ;
endPTS = Math . max ( endPTS , frag . endPTS ) ;
startDTS = Math . min ( startDTS , frag . startDTS ) ;
endDTS = Math . max ( endDTS , frag . endDTS ) ;
}
const drift = startPTS - frag . start ;
frag . start = frag . startPTS = startPTS ;
frag . maxStartPTS = maxStartPTS ;
frag . endPTS = endPTS ;
frag . startDTS = startDTS ;
frag . endDTS = endDTS ;
frag . duration = endPTS - startPTS ;
const sn = frag . sn ;
// exit if sn out of range
if ( ! details || sn < details . startSN || sn > details . endSN ) {
return 0 ;
}
let fragIdx , fragments , i ;
fragIdx = sn - details . startSN ;
fragments = details . fragments ;
// update frag reference in fragments array
// rationale is that fragments array might not contain this frag object.
// this will happen if playlist has been refreshed between frag loading and call to updateFragPTSDTS()
// if we don't update frag, we won't be able to propagate PTS info on the playlist
// resulting in invalid sliding computation
fragments [ fragIdx ] = frag ;
// adjust fragment PTS/duration from seqnum-1 to frag 0
for ( i = fragIdx ; i > 0 ; i -- ) {
updatePTS ( fragments , i , i - 1 ) ;
}
// adjust fragment PTS/duration from seqnum to last frag
for ( i = fragIdx ; i < fragments . length - 1 ; i ++ ) {
updatePTS ( fragments , i , i + 1 ) ;
}
details . PTSKnown = true ;
return drift ;
}
2020-01-24 07:54:29 +03:00
export function mergeDetails ( oldDetails , newDetails ) {
2020-01-21 06:02:29 +03:00
// potentially retrieve cached initsegment
if ( newDetails . initSegment && oldDetails . initSegment ) {
newDetails . initSegment = oldDetails . initSegment ;
}
// check if old/new playlists have fragments in common
// loop through overlapping SN and update startPTS , cc, and duration if any found
let ccOffset = 0 ;
let PTSFrag ;
mapFragmentIntersection ( oldDetails , newDetails , ( oldFrag , newFrag ) => {
ccOffset = oldFrag . cc - newFrag . cc ;
if ( Number . isFinite ( oldFrag . startPTS ) ) {
newFrag . start = newFrag . startPTS = oldFrag . startPTS ;
newFrag . endPTS = oldFrag . endPTS ;
newFrag . duration = oldFrag . duration ;
newFrag . backtracked = oldFrag . backtracked ;
newFrag . dropped = oldFrag . dropped ;
PTSFrag = newFrag ;
}
// PTS is known when there are overlapping segments
newDetails . PTSKnown = true ;
} ) ;
if ( ! newDetails . PTSKnown ) {
return ;
}
if ( ccOffset ) {
2020-01-24 07:54:29 +03:00
console . log ( 'discontinuity sliding from playlist, take drift into account' ) ;
2020-01-21 06:02:29 +03:00
const newFragments = newDetails . fragments ;
for ( let i = 0 ; i < newFragments . length ; i ++ ) {
newFragments [ i ] . cc += ccOffset ;
}
}
// if at least one fragment contains PTS info, recompute PTS information for all fragments
if ( PTSFrag ) {
updateFragPTSDTS ( newDetails , PTSFrag , PTSFrag . startPTS , PTSFrag . endPTS , PTSFrag . startDTS , PTSFrag . endDTS ) ;
} else {
// ensure that delta is within oldFragments range
// also adjust sliding in case delta is 0 (we could have old=[50-60] and new=old=[50-61])
// in that case we also need to adjust start offset of all fragments
adjustSliding ( oldDetails , newDetails ) ;
}
// if we are here, it means we have fragments overlapping between
// old and new level. reliable PTS info is thus relying on old level
newDetails . PTSKnown = oldDetails . PTSKnown ;
}
2020-01-24 07:54:29 +03:00
export function mergeSubtitlePlaylists ( oldPlaylist , newPlaylist , referenceStart = 0 ) {
2020-01-21 06:02:29 +03:00
let lastIndex = - 1 ;
mapFragmentIntersection ( oldPlaylist , newPlaylist , ( oldFrag , newFrag , index ) => {
newFrag . start = oldFrag . start ;
lastIndex = index ;
} ) ;
const frags = newPlaylist . fragments ;
if ( lastIndex < 0 ) {
frags . forEach ( frag => {
frag . start += referenceStart ;
} ) ;
return ;
}
for ( let i = lastIndex + 1 ; i < frags . length ; i ++ ) {
frags [ i ] . start = ( frags [ i - 1 ] . start + frags [ i - 1 ] . duration ) ;
}
}
2020-01-24 07:54:29 +03:00
export function mapFragmentIntersection ( oldPlaylist , newPlaylist , intersectionFn ) {
2020-01-21 06:02:29 +03:00
if ( ! oldPlaylist || ! newPlaylist ) {
return ;
}
const start = Math . max ( oldPlaylist . startSN , newPlaylist . startSN ) - newPlaylist . startSN ;
const end = Math . min ( oldPlaylist . endSN , newPlaylist . endSN ) - newPlaylist . startSN ;
const delta = newPlaylist . startSN - oldPlaylist . startSN ;
for ( let i = start ; i <= end ; i ++ ) {
const oldFrag = oldPlaylist . fragments [ delta + i ] ;
const newFrag = newPlaylist . fragments [ i ] ;
if ( ! oldFrag || ! newFrag ) {
break ;
}
intersectionFn ( oldFrag , newFrag , i ) ;
}
}
2020-01-24 07:54:29 +03:00
export function adjustSliding ( oldPlaylist , newPlaylist ) {
2020-01-21 06:02:29 +03:00
const delta = newPlaylist . startSN - oldPlaylist . startSN ;
const oldFragments = oldPlaylist . fragments ;
const newFragments = newPlaylist . fragments ;
if ( delta < 0 || delta > oldFragments . length ) {
return ;
}
for ( let i = 0 ; i < newFragments . length ; i ++ ) {
newFragments [ i ] . start += oldFragments [ delta ] . start ;
}
}
2020-01-24 07:54:29 +03:00
export function computeReloadInterval ( currentPlaylist , newPlaylist , lastRequestTime ) {
2020-01-21 06:02:29 +03:00
let reloadInterval = 1000 * ( newPlaylist . averagetargetduration ? newPlaylist . averagetargetduration : newPlaylist . targetduration ) ;
const minReloadInterval = reloadInterval / 2 ;
if ( currentPlaylist && newPlaylist . endSN === currentPlaylist . endSN ) {
// follow HLS Spec, If the client reloads a Playlist file and finds that it has not
// changed then it MUST wait for a period of one-half the target
// duration before retrying.
reloadInterval = minReloadInterval ;
}
if ( lastRequestTime ) {
reloadInterval = Math . max ( minReloadInterval , reloadInterval - ( window . performance . now ( ) - lastRequestTime ) ) ;
}
// in any case, don't reload more than half of target duration
return Math . round ( reloadInterval ) ;
}