11import BaseStreamController , { State } from './base-stream-controller' ;
22import { Events } from '../events' ;
3- import { Bufferable , BufferHelper } from '../utils/buffer-helper' ;
43import { FragmentState } from './fragment-tracker' ;
54import { Level } from '../types/level' ;
65import { PlaylistContextType , PlaylistLevelType } from '../types/loader' ;
@@ -13,7 +12,7 @@ import {
1312import ChunkCache from '../demux/chunk-cache' ;
1413import TransmuxerInterface from '../demux/transmuxer-interface' ;
1514import { ChunkMetadata } from '../types/transmuxer' ;
16- import { fragmentWithinToleranceTest } from './fragment-finders' ;
15+ import { findFragWithCC , findNearestWithCC } from './fragment-finders' ;
1716import { alignMediaPlaylistByPDT } from '../utils/discontinuities' ;
1817import { mediaAttributesIdentical } from '../utils/media-option-attributes' ;
1918import { ErrorDetails } from '../errors' ;
@@ -39,6 +38,8 @@ import type {
3938 FragBufferedData ,
4039 ErrorData ,
4140 BufferFlushingData ,
41+ BufferCodecsData ,
42+ FragLoadingData ,
4243} from '../types/events' ;
4344import type { MediaPlaylist } from '../types/media-playlist' ;
4445
@@ -55,9 +56,8 @@ class AudioStreamController
5556 extends BaseStreamController
5657 implements NetworkComponentAPI
5758{
58- private videoBuffer : Bufferable | null = null ;
59- private videoTrackCC : number = - 1 ;
60- private waitingVideoCC : number = - 1 ;
59+ private videoAnchor : MediaFragment | null = null ;
60+ private mainFragLoading : FragLoadingData | null = null ;
6161 private bufferedTrack : MediaPlaylist | null = null ;
6262 private switchingTrack : MediaPlaylist | null = null ;
6363 private trackId : number = - 1 ;
@@ -102,6 +102,7 @@ class AudioStreamController
102102 hls . on ( Events . BUFFER_FLUSHING , this . onBufferFlushing , this ) ;
103103 hls . on ( Events . BUFFER_FLUSHED , this . onBufferFlushed , this ) ;
104104 hls . on ( Events . INIT_PTS_FOUND , this . onInitPtsFound , this ) ;
105+ hls . on ( Events . FRAG_LOADING , this . onFragLoading , this ) ;
105106 hls . on ( Events . FRAG_BUFFERED , this . onFragBuffered , this ) ;
106107 }
107108
@@ -120,6 +121,7 @@ class AudioStreamController
120121 hls . off ( Events . BUFFER_FLUSHING , this . onBufferFlushing , this ) ;
121122 hls . off ( Events . BUFFER_FLUSHED , this . onBufferFlushed , this ) ;
122123 hls . off ( Events . INIT_PTS_FOUND , this . onInitPtsFound , this ) ;
124+ hls . on ( Events . FRAG_LOADING , this . onFragLoading , this ) ;
123125 hls . off ( Events . FRAG_BUFFERED , this . onFragBuffered , this ) ;
124126 }
125127
@@ -130,20 +132,44 @@ class AudioStreamController
130132 ) {
131133 // Always update the new INIT PTS
132134 // Can change due level switch
133- if ( id === 'main' ) {
135+ if ( id === PlaylistLevelType . MAIN ) {
134136 const cc = frag . cc ;
135- this . initPTS [ frag . cc ] = { baseTime : initPTS , timescale } ;
137+ const inFlightFrag = this . fragCurrent ;
138+ this . initPTS [ cc ] = { baseTime : initPTS , timescale } ;
136139 this . log (
137140 `InitPTS for cc: ${ cc } found from main: ${ initPTS } /${ timescale } ` ,
138141 ) ;
139- this . videoTrackCC = cc ;
142+ this . videoAnchor = frag ;
140143 // If we are waiting, tick immediately to unblock audio fragment transmuxing
141144 if ( this . state === State . WAITING_INIT_PTS ) {
145+ const waitingData = this . waitingData ;
146+ if ( ! waitingData || waitingData . frag . cc !== cc ) {
147+ this . nextLoadPosition = this . findSyncFrag ( frag ) . start ;
148+ }
142149 this . tick ( ) ;
150+ } else if (
151+ ! this . loadedmetadata &&
152+ inFlightFrag &&
153+ inFlightFrag . cc !== cc
154+ ) {
155+ this . startFragRequested = false ;
156+ this . nextLoadPosition = this . findSyncFrag ( frag ) . start ;
157+ inFlightFrag . abortRequests ( ) ;
158+ this . resetLoadingState ( ) ;
143159 }
144160 }
145161 }
146162
163+ private findSyncFrag ( mainFrag : MediaFragment ) : MediaFragment {
164+ const trackDetails = this . getLevelDetails ( ) ;
165+ const cc = mainFrag . cc ;
166+ return (
167+ findNearestWithCC ( trackDetails , cc , mainFrag ) ||
168+ ( trackDetails && findFragWithCC ( trackDetails . fragments , cc ) ) ||
169+ mainFrag
170+ ) ;
171+ }
172+
147173 startLoad ( startPosition : number ) {
148174 if ( ! this . levels ) {
149175 this . startPosition = startPosition ;
@@ -206,9 +232,9 @@ class AudioStreamController
206232 const waitingData = this . waitingData ;
207233 if ( waitingData ) {
208234 const { frag, part, cache, complete } = waitingData ;
235+ const videoAnchor = this . videoAnchor ;
209236 if ( this . initPTS [ frag . cc ] !== undefined ) {
210237 this . waitingData = null ;
211- this . waitingVideoCC = - 1 ;
212238 this . state = State . FRAG_LOADING ;
213239 const payload = cache . flush ( ) ;
214240 const data : FragLoadedData = {
@@ -221,33 +247,15 @@ class AudioStreamController
221247 if ( complete ) {
222248 super . _handleFragmentLoadComplete ( data ) ;
223249 }
224- } else if ( this . videoTrackCC !== this . waitingVideoCC ) {
250+ } else if ( videoAnchor && videoAnchor . cc !== waitingData . frag . cc ) {
225251 // Drop waiting fragment if videoTrackCC has changed since waitingFragment was set and initPTS was not found
226252 this . log (
227- `Waiting fragment cc (${ frag . cc } ) cancelled because video is at cc ${ this . videoTrackCC } ` ,
253+ `Waiting fragment cc (${ frag . cc } ) cancelled because video is at cc ${ videoAnchor . cc } ` ,
228254 ) ;
255+ this . nextLoadPosition = this . findSyncFrag ( videoAnchor ) . start ;
229256 this . clearWaitingFragment ( ) ;
230- } else {
231- // Drop waiting fragment if an earlier fragment is needed
232- const pos = this . getLoadPosition ( ) ;
233- const bufferInfo = BufferHelper . bufferInfo (
234- this . mediaBuffer ,
235- pos ,
236- this . config . maxBufferHole ,
237- ) ;
238- const waitingFragmentAtPosition = fragmentWithinToleranceTest (
239- bufferInfo . end ,
240- this . config . maxFragLookUpTolerance ,
241- frag ,
242- ) ;
243- if ( waitingFragmentAtPosition < 0 ) {
244- this . log (
245- `Waiting fragment cc (${ frag . cc } ) @ ${ frag . start } cancelled because another fragment at ${ bufferInfo . end } is needed` ,
246- ) ;
247- this . clearWaitingFragment ( ) ;
248- }
249257 }
250- } else {
258+ } else if ( this . state !== State . STOPPED ) {
251259 this . state = State . IDLE ;
252260 }
253261 }
@@ -259,9 +267,12 @@ class AudioStreamController
259267 clearWaitingFragment ( ) {
260268 const waitingData = this . waitingData ;
261269 if ( waitingData ) {
270+ if ( ! this . loadedmetadata ) {
271+ // Load overlapping fragment on start when discontinuity start times are not aligned
272+ this . startFragRequested = false ;
273+ }
262274 this . fragmentTracker . removeFragment ( waitingData . frag ) ;
263275 this . waitingData = null ;
264- this . waitingVideoCC = - 1 ;
265276 if ( this . state !== State . STOPPED ) {
266277 this . state = State . IDLE ;
267278 }
@@ -343,12 +354,11 @@ class AudioStreamController
343354
344355 const fragments = trackDetails . fragments ;
345356 const start = fragments [ 0 ] . start ;
346- let targetBufferTime = this . flushing
347- ? this . getLoadPosition ( )
348- : bufferInfo . end ;
357+ const loadPosition = this . getLoadPosition ( ) ;
358+ let targetBufferTime = this . flushing ? loadPosition : bufferInfo . end ;
349359
350360 if ( switchingTrack && media ) {
351- const pos = this . getLoadPosition ( ) ;
361+ const pos = loadPosition ;
352362 // STABLE
353363 if (
354364 bufferedTrack &&
@@ -378,10 +388,8 @@ class AudioStreamController
378388 }
379389
380390 let frag = this . getNextFragment ( targetBufferTime , trackDetails ) ;
381- let atGap = false ;
382391 // Avoid loop loading by using nextLoadPosition set for backtracking and skipping consecutive GAP tags
383392 if ( frag && this . isLoopLoading ( frag , targetBufferTime ) ) {
384- atGap = ! ! frag . gap ;
385393 frag = this . getNextFragmentLoopLoading (
386394 frag ,
387395 trackDetails ,
@@ -395,39 +403,26 @@ class AudioStreamController
395403 return ;
396404 }
397405
398- if ( ! trackDetails . live || targetBufferTime < this . hls . liveSyncPosition ! ) {
406+ if (
407+ this . startFragRequested &&
408+ ( ! trackDetails . live || targetBufferTime < this . hls . liveSyncPosition ! )
409+ ) {
399410 // Request audio segments up to one fragment ahead of main buffer
400- const mainBufferInfo = this . getFwdBufferInfo (
401- this . videoBuffer ? this . videoBuffer : this . media ,
402- PlaylistLevelType . MAIN ,
403- ) ;
411+ const mainFragLoading = this . mainFragLoading ;
412+ const mainTargetBufferEnd = mainFragLoading
413+ ? ( mainFragLoading . part || mainFragLoading . frag ) . end
414+ : null ;
404415 const atBufferSyncLimit =
405- ! ! mainBufferInfo && frag . start > mainBufferInfo . end + frag . duration ;
406- if ( atBufferSyncLimit ) {
407- // Check fragment-tracker for main fragments since GAP segments do not show up in bufferInfo
408- const mainFrag = this . fragmentTracker . getFragAtPos (
409- frag . start ,
410- PlaylistLevelType . MAIN ,
411- ) ;
412- if ( mainFrag === null ) {
413- return ;
414- }
415- // Bridge gaps in main buffer (also prevents loop loading at gaps)
416- atGap ||= ! ! mainFrag . gap || mainBufferInfo . len === 0 ;
417- if (
418- ! atGap ||
419- ( bufferInfo . nextStart && bufferInfo . nextStart < mainFrag . end )
420- ) {
421- return ;
422- }
416+ mainTargetBufferEnd !== null && frag . start > mainTargetBufferEnd ;
417+ if ( atBufferSyncLimit && ! frag . endList ) {
418+ return ;
423419 }
424420 }
425421
426422 this . loadFragment ( frag , levelInfo , targetBufferTime ) ;
427423 }
428424
429425 protected onMediaDetaching ( ) {
430- this . videoBuffer = null ;
431426 this . bufferFlushed = this . flushing = false ;
432427 super . onMediaDetaching ( ) ;
433428 }
@@ -477,18 +472,16 @@ class AudioStreamController
477472 }
478473
479474 protected onManifestLoading ( ) {
480- this . fragmentTracker . removeAllFragments ( ) ;
481- this . startPosition = this . lastCurrentTime = 0 ;
475+ super . onManifestLoading ( ) ;
482476 this . bufferFlushed = this . flushing = false ;
483- this . levels =
484- this . mainDetails =
477+ this . mainDetails =
485478 this . waitingData =
479+ this . videoAnchor =
486480 this . bufferedTrack =
487481 this . cachedTrackLoadedData =
488482 this . switchingTrack =
489483 null ;
490- this . startFragRequested = false ;
491- this . trackId = this . videoTrackCC = this . waitingVideoCC = - 1 ;
484+ this . trackId = - 1 ;
492485 }
493486
494487 private onLevelLoaded ( event : Events . LEVEL_LOADED , data : LevelLoadedData ) {
@@ -643,7 +636,6 @@ class AudioStreamController
643636 complete : false ,
644637 } ) ;
645638 cache . push ( new Uint8Array ( payload ) ) ;
646- this . waitingVideoCC = this . videoTrackCC ;
647639 this . state = State . WAITING_INIT_PTS ;
648640 }
649641 }
@@ -658,7 +650,7 @@ class AudioStreamController
658650
659651 private onBufferReset ( /* event: Events.BUFFER_RESET */ ) {
660652 // reset reference to sourcebuffers
661- this . mediaBuffer = this . videoBuffer = null ;
653+ this . mediaBuffer = null ;
662654 this . loadedmetadata = false ;
663655 }
664656
@@ -670,21 +662,30 @@ class AudioStreamController
670662 if ( audioTrack ) {
671663 this . mediaBuffer = audioTrack . buffer || null ;
672664 }
673- if ( data . tracks . video ) {
674- this . videoBuffer = data . tracks . video . buffer || null ;
665+ }
666+
667+ private onFragLoading ( event : Events . FRAG_LOADING , data : FragLoadingData ) {
668+ if (
669+ data . frag . type === PlaylistLevelType . MAIN &&
670+ data . frag . sn !== 'initSegment'
671+ ) {
672+ this . mainFragLoading = data ;
673+ if ( this . state === State . IDLE ) {
674+ this . tick ( ) ;
675+ }
675676 }
676677 }
677678
678679 private onFragBuffered ( event : Events . FRAG_BUFFERED , data : FragBufferedData ) {
679680 const { frag, part } = data ;
680681 if ( frag . type !== PlaylistLevelType . AUDIO ) {
681682 if ( ! this . loadedmetadata && frag . type === PlaylistLevelType . MAIN ) {
682- const bufferable = this . videoBuffer || this . media ;
683- if ( bufferable ) {
684- const bufferedTimeRanges = BufferHelper . getBuffered ( bufferable ) ;
685- if ( bufferedTimeRanges . length ) {
686- this . loadedmetadata = true ;
687- }
683+ const bufferedState = this . fragmentTracker . getState ( frag ) ;
684+ if (
685+ bufferedState === FragmentState . OK ||
686+ bufferedState === FragmentState . PARTIAL
687+ ) {
688+ this . loadedmetadata = true ;
688689 }
689690 }
690691 return ;
@@ -889,12 +890,15 @@ class AudioStreamController
889890 if ( tracks . video ) {
890891 delete tracks . video ;
891892 }
893+ if ( tracks . audiovideo ) {
894+ delete tracks . audiovideo ;
895+ }
892896
893897 // include levelCodec in audio and video tracks
894- const track = tracks . audio ;
895- if ( ! track ) {
898+ if ( ! tracks . audio ) {
896899 return ;
897900 }
901+ const track = tracks . audio ;
898902
899903 track . id = 'audio' ;
900904
@@ -906,7 +910,7 @@ class AudioStreamController
906910 if ( variantAudioCodecs && variantAudioCodecs . split ( ',' ) . length === 1 ) {
907911 track . levelCodec = variantAudioCodecs ;
908912 }
909- this . hls . trigger ( Events . BUFFER_CODECS , tracks ) ;
913+ this . hls . trigger ( Events . BUFFER_CODECS , tracks as BufferCodecsData ) ;
910914 const initSegment = track . initSegment ;
911915 if ( initSegment ?. byteLength ) {
912916 const segment : BufferAppendingData = {
@@ -930,7 +934,6 @@ class AudioStreamController
930934 ) {
931935 // only load if fragment is not loaded or if in audio switch
932936 const fragState = this . fragmentTracker . getState ( frag ) ;
933- this . fragCurrent = frag ;
934937
935938 // we force a frag loading in audio switch as fragment tracker might not have evicted previous frags in case of quick audio switch
936939 if (
@@ -953,7 +956,6 @@ class AudioStreamController
953956 alignMediaPlaylistByPDT ( track . details , mainDetails ) ;
954957 }
955958 } else {
956- this . startFragRequested = true ;
957959 super . loadFragment ( frag , track , targetBufferTime ) ;
958960 }
959961 } else {
0 commit comments