hls.js 1.5.12-0.canary.10366 → 1.5.12-0.canary.10367
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/hls.js +293 -194
- package/dist/hls.js.d.ts +14 -8
- package/dist/hls.js.map +1 -1
- package/dist/hls.light.js +211 -131
- package/dist/hls.light.js.map +1 -1
- package/dist/hls.light.min.js +1 -1
- package/dist/hls.light.min.js.map +1 -1
- package/dist/hls.light.mjs +208 -129
- package/dist/hls.light.mjs.map +1 -1
- package/dist/hls.min.js +1 -1
- package/dist/hls.min.js.map +1 -1
- package/dist/hls.mjs +290 -192
- package/dist/hls.mjs.map +1 -1
- package/dist/hls.worker.js +1 -1
- package/dist/hls.worker.js.map +1 -1
- package/package.json +1 -1
- package/src/controller/abr-controller.ts +1 -1
- package/src/controller/audio-stream-controller.ts +86 -84
- package/src/controller/base-stream-controller.ts +50 -41
- package/src/controller/buffer-controller.ts +20 -5
- package/src/controller/error-controller.ts +22 -14
- package/src/controller/fragment-finders.ts +33 -3
- package/src/controller/fragment-tracker.ts +30 -1
- package/src/controller/stream-controller.ts +54 -47
- package/src/controller/subtitle-stream-controller.ts +4 -4
- package/src/controller/timeline-controller.ts +1 -1
- package/src/hls.ts +1 -1
- package/src/loader/fragment-loader.ts +14 -19
- package/src/loader/fragment.ts +2 -2
- package/src/loader/level-details.ts +1 -2
- package/src/loader/playlist-loader.ts +1 -2
- package/src/remux/mp4-remuxer.ts +12 -8
- package/src/remux/passthrough-remuxer.ts +1 -1
- package/src/types/events.ts +13 -1
- package/src/utils/level-helper.ts +4 -5
- package/src/utils/rendition-helper.ts +84 -79
@@ -13,7 +13,7 @@ import type Hls from '../hls';
|
|
13
13
|
import type { RetryConfig } from '../config';
|
14
14
|
import type { NetworkComponentAPI } from '../types/component-api';
|
15
15
|
import type { ErrorData } from '../types/events';
|
16
|
-
import type { Fragment } from '../loader/fragment';
|
16
|
+
import type { Fragment, MediaFragment } from '../loader/fragment';
|
17
17
|
import type { LevelDetails } from '../loader/level-details';
|
18
18
|
|
19
19
|
export const enum NetworkErrorAction {
|
@@ -127,10 +127,7 @@ export default class ErrorController
|
|
127
127
|
case ErrorDetails.FRAG_PARSING_ERROR:
|
128
128
|
// ignore empty segment errors marked as gap
|
129
129
|
if (data.frag?.gap) {
|
130
|
-
data.errorAction =
|
131
|
-
action: NetworkErrorAction.DoNothing,
|
132
|
-
flags: ErrorActionFlags.None,
|
133
|
-
};
|
130
|
+
data.errorAction = createDoNothingErrorAction();
|
134
131
|
return;
|
135
132
|
}
|
136
133
|
// falls through
|
@@ -218,10 +215,13 @@ export default class ErrorController
|
|
218
215
|
case ErrorDetails.BUFFER_ADD_CODEC_ERROR:
|
219
216
|
case ErrorDetails.REMUX_ALLOC_ERROR:
|
220
217
|
case ErrorDetails.BUFFER_APPEND_ERROR:
|
221
|
-
|
222
|
-
|
223
|
-
data.
|
224
|
-
|
218
|
+
// Buffer-controller can set errorAction when append errors can be ignored or resolved locally
|
219
|
+
if (!data.errorAction) {
|
220
|
+
data.errorAction = this.getLevelSwitchAction(
|
221
|
+
data,
|
222
|
+
data.level ?? hls.loadLevel,
|
223
|
+
);
|
224
|
+
}
|
225
225
|
return;
|
226
226
|
case ErrorDetails.INTERNAL_EXCEPTION:
|
227
227
|
case ErrorDetails.BUFFER_APPENDING_ERROR:
|
@@ -230,10 +230,7 @@ export default class ErrorController
|
|
230
230
|
case ErrorDetails.BUFFER_STALLED_ERROR:
|
231
231
|
case ErrorDetails.BUFFER_SEEK_OVER_HOLE:
|
232
232
|
case ErrorDetails.BUFFER_NUDGE_ON_STALL:
|
233
|
-
data.errorAction =
|
234
|
-
action: NetworkErrorAction.DoNothing,
|
235
|
-
flags: ErrorActionFlags.None,
|
236
|
-
};
|
233
|
+
data.errorAction = createDoNothingErrorAction();
|
237
234
|
return;
|
238
235
|
}
|
239
236
|
|
@@ -387,7 +384,7 @@ export default class ErrorController
|
|
387
384
|
const levelDetails = levels[candidate].details;
|
388
385
|
if (levelDetails) {
|
389
386
|
const fragCandidate = findFragmentByPTS(
|
390
|
-
data.frag,
|
387
|
+
data.frag as MediaFragment,
|
391
388
|
levelDetails.fragments,
|
392
389
|
data.frag.start,
|
393
390
|
);
|
@@ -511,3 +508,14 @@ export default class ErrorController
|
|
511
508
|
}
|
512
509
|
}
|
513
510
|
}
|
511
|
+
|
512
|
+
export function createDoNothingErrorAction(resolved?: boolean): IErrorAction {
|
513
|
+
const errorAction: IErrorAction = {
|
514
|
+
action: NetworkErrorAction.DoNothing,
|
515
|
+
flags: ErrorActionFlags.None,
|
516
|
+
};
|
517
|
+
if (resolved) {
|
518
|
+
errorAction.resolved = true;
|
519
|
+
}
|
520
|
+
return errorAction;
|
521
|
+
}
|
@@ -1,5 +1,6 @@
|
|
1
1
|
import BinarySearch from '../utils/binary-search';
|
2
2
|
import type { Fragment, MediaFragment } from '../loader/fragment';
|
3
|
+
import type { LevelDetails } from '../loader/level-details';
|
3
4
|
|
4
5
|
/**
|
5
6
|
* Returns first fragment whose endPdt value exceeds the given PDT, or null.
|
@@ -54,7 +55,7 @@ export function findFragmentByPDT(
|
|
54
55
|
* @returns a matching fragment or null
|
55
56
|
*/
|
56
57
|
export function findFragmentByPTS(
|
57
|
-
fragPrevious:
|
58
|
+
fragPrevious: MediaFragment | null,
|
58
59
|
fragments: MediaFragment[],
|
59
60
|
bufferEnd: number = 0,
|
60
61
|
maxFragLookUpTolerance: number = 0,
|
@@ -62,13 +63,19 @@ export function findFragmentByPTS(
|
|
62
63
|
): MediaFragment | null {
|
63
64
|
let fragNext: MediaFragment | null = null;
|
64
65
|
if (fragPrevious) {
|
65
|
-
fragNext =
|
66
|
-
fragments[(fragPrevious.sn as number) - fragments[0].sn + 1] || null;
|
66
|
+
fragNext = fragments[1 + fragPrevious.sn - fragments[0].sn] || null;
|
67
67
|
// check for buffer-end rounding error
|
68
68
|
const bufferEdgeError = (fragPrevious.endDTS as number) - bufferEnd;
|
69
69
|
if (bufferEdgeError > 0 && bufferEdgeError < 0.0000015) {
|
70
70
|
bufferEnd += 0.0000015;
|
71
71
|
}
|
72
|
+
if (
|
73
|
+
fragNext &&
|
74
|
+
fragPrevious.level !== fragNext.level &&
|
75
|
+
fragNext.end <= fragPrevious.end
|
76
|
+
) {
|
77
|
+
fragNext = fragments[2 + fragPrevious.sn - fragments[0].sn] || null;
|
78
|
+
}
|
72
79
|
} else if (bufferEnd === 0 && fragments[0].start === 0) {
|
73
80
|
fragNext = fragments[0];
|
74
81
|
}
|
@@ -214,3 +221,26 @@ export function findFragWithCC(
|
|
214
221
|
}
|
215
222
|
});
|
216
223
|
}
|
224
|
+
|
225
|
+
export function findNearestWithCC(
|
226
|
+
details: LevelDetails | undefined,
|
227
|
+
cc: number,
|
228
|
+
fragment: MediaFragment,
|
229
|
+
): MediaFragment | null {
|
230
|
+
if (details) {
|
231
|
+
if (details.startCC <= cc && details.endCC >= cc) {
|
232
|
+
const start = fragment.start;
|
233
|
+
const end = fragment.end;
|
234
|
+
return BinarySearch.search(details.fragments, (candidate) => {
|
235
|
+
if (candidate.cc < cc || candidate.end <= start) {
|
236
|
+
return 1;
|
237
|
+
} else if (candidate.cc > cc || candidate.start >= end) {
|
238
|
+
return -1;
|
239
|
+
} else {
|
240
|
+
return 0;
|
241
|
+
}
|
242
|
+
});
|
243
|
+
}
|
244
|
+
}
|
245
|
+
return null;
|
246
|
+
}
|
@@ -47,6 +47,7 @@ export class FragmentTracker implements ComponentAPI {
|
|
47
47
|
|
48
48
|
private _registerListeners() {
|
49
49
|
const { hls } = this;
|
50
|
+
hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
50
51
|
hls.on(Events.BUFFER_APPENDED, this.onBufferAppended, this);
|
51
52
|
hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
|
52
53
|
hls.on(Events.FRAG_LOADED, this.onFragLoaded, this);
|
@@ -54,6 +55,7 @@ export class FragmentTracker implements ComponentAPI {
|
|
54
55
|
|
55
56
|
private _unregisterListeners() {
|
56
57
|
const { hls } = this;
|
58
|
+
hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
57
59
|
hls.off(Events.BUFFER_APPENDED, this.onBufferAppended, this);
|
58
60
|
hls.off(Events.FRAG_BUFFERED, this.onFragBuffered, this);
|
59
61
|
hls.off(Events.FRAG_LOADED, this.onFragLoaded, this);
|
@@ -143,6 +145,7 @@ export class FragmentTracker implements ComponentAPI {
|
|
143
145
|
timeRange: TimeRanges,
|
144
146
|
playlistType: PlaylistLevelType,
|
145
147
|
appendedPart?: Part | null,
|
148
|
+
removeAppending?: boolean,
|
146
149
|
) {
|
147
150
|
if (this.timeRanges) {
|
148
151
|
this.timeRanges[elementaryStream] = timeRange;
|
@@ -158,7 +161,10 @@ export class FragmentTracker implements ComponentAPI {
|
|
158
161
|
if (appendedPartSn >= fragmentEntity.body.sn) {
|
159
162
|
return;
|
160
163
|
}
|
161
|
-
if (
|
164
|
+
if (
|
165
|
+
!fragmentEntity.buffered &&
|
166
|
+
(!fragmentEntity.loaded || removeAppending)
|
167
|
+
) {
|
162
168
|
if (fragmentEntity.body.type === playlistType) {
|
163
169
|
this.removeFragment(fragmentEntity.body);
|
164
170
|
}
|
@@ -168,6 +174,10 @@ export class FragmentTracker implements ComponentAPI {
|
|
168
174
|
if (!esData) {
|
169
175
|
return;
|
170
176
|
}
|
177
|
+
if (esData.time.length === 0) {
|
178
|
+
this.removeFragment(fragmentEntity.body);
|
179
|
+
return;
|
180
|
+
}
|
171
181
|
esData.time.some((time: FragmentTimeRange) => {
|
172
182
|
const isNotBuffered = !this.isTimeBuffered(
|
173
183
|
time.startPTS,
|
@@ -387,6 +397,10 @@ export class FragmentTracker implements ComponentAPI {
|
|
387
397
|
return false;
|
388
398
|
}
|
389
399
|
|
400
|
+
private onManifestLoading() {
|
401
|
+
this.removeAllFragments();
|
402
|
+
}
|
403
|
+
|
390
404
|
private onFragLoaded(event: Events.FRAG_LOADED, data: FragLoadedData) {
|
391
405
|
// don't track initsegment (for which sn is not a number)
|
392
406
|
// don't track frags used for bitrateTest, they're irrelevant.
|
@@ -439,6 +453,21 @@ export class FragmentTracker implements ComponentAPI {
|
|
439
453
|
return !!this.fragments[fragKey];
|
440
454
|
}
|
441
455
|
|
456
|
+
public hasFragments(type?: PlaylistLevelType): boolean {
|
457
|
+
const { fragments } = this;
|
458
|
+
const keys = Object.keys(fragments);
|
459
|
+
if (!type) {
|
460
|
+
return keys.length > 0;
|
461
|
+
}
|
462
|
+
for (let i = keys.length; i--; ) {
|
463
|
+
const fragmentEntity = fragments[keys[i]];
|
464
|
+
if (fragmentEntity?.body.type === type) {
|
465
|
+
return true;
|
466
|
+
}
|
467
|
+
}
|
468
|
+
return false;
|
469
|
+
}
|
470
|
+
|
442
471
|
public hasParts(type: PlaylistLevelType): boolean {
|
443
472
|
return !!this.activePartLists[type]?.length;
|
444
473
|
}
|
@@ -2,6 +2,7 @@ import BaseStreamController, { State } from './base-stream-controller';
|
|
2
2
|
import { changeTypeSupported } from '../is-supported';
|
3
3
|
import { Events } from '../events';
|
4
4
|
import { BufferHelper, BufferInfo } from '../utils/buffer-helper';
|
5
|
+
import { findFragmentByPTS } from './fragment-finders';
|
5
6
|
import { FragmentState } from './fragment-tracker';
|
6
7
|
import { PlaylistContextType, PlaylistLevelType } from '../types/loader';
|
7
8
|
import {
|
@@ -16,15 +17,15 @@ import { ErrorDetails } from '../errors';
|
|
16
17
|
import type { NetworkComponentAPI } from '../types/component-api';
|
17
18
|
import type Hls from '../hls';
|
18
19
|
import type { Level } from '../types/level';
|
19
|
-
import type { LevelDetails } from '../loader/level-details';
|
20
20
|
import type { FragmentTracker } from './fragment-tracker';
|
21
21
|
import type KeyLoader from '../loader/key-loader';
|
22
22
|
import type { TransmuxerResult } from '../types/transmuxer';
|
23
|
-
import type { TrackSet } from '../types/track';
|
23
|
+
import type { Track, TrackSet } from '../types/track';
|
24
24
|
import type { SourceBufferName } from '../types/buffer';
|
25
25
|
import type {
|
26
26
|
AudioTrackSwitchedData,
|
27
27
|
AudioTrackSwitchingData,
|
28
|
+
BufferCodecsData,
|
28
29
|
BufferCreatedData,
|
29
30
|
BufferEOSData,
|
30
31
|
BufferFlushedData,
|
@@ -364,7 +365,6 @@ export default class StreamController
|
|
364
365
|
) {
|
365
366
|
// Check if fragment is not loaded
|
366
367
|
const fragState = this.fragmentTracker.getState(frag);
|
367
|
-
this.fragCurrent = frag;
|
368
368
|
if (
|
369
369
|
fragState === FragmentState.NOT_LOADED ||
|
370
370
|
fragState === FragmentState.PARTIAL
|
@@ -377,7 +377,6 @@ export default class StreamController
|
|
377
377
|
);
|
378
378
|
this._loadBitrateTestFrag(frag, level);
|
379
379
|
} else {
|
380
|
-
this.startFragRequested = true;
|
381
380
|
super.loadFragment(frag, level, targetBufferTime);
|
382
381
|
}
|
383
382
|
} else {
|
@@ -570,18 +569,14 @@ export default class StreamController
|
|
570
569
|
};
|
571
570
|
|
572
571
|
protected onManifestLoading() {
|
572
|
+
super.onManifestLoading();
|
573
573
|
// reset buffer on manifest loading
|
574
574
|
this.log('Trigger BUFFER_RESET');
|
575
575
|
this.hls.trigger(Events.BUFFER_RESET, undefined);
|
576
|
-
this.fragmentTracker.removeAllFragments();
|
577
576
|
this.couldBacktrack = false;
|
578
|
-
this.
|
579
|
-
this.
|
580
|
-
|
581
|
-
this.backtrackFragment =
|
582
|
-
this.levelLastLoaded =
|
583
|
-
null;
|
584
|
-
this.altAudio = this.audioOnly = this.startFragRequested = false;
|
577
|
+
this.fragLastKbps = 0;
|
578
|
+
this.fragPlaying = this.backtrackFragment = null;
|
579
|
+
this.altAudio = this.audioOnly = false;
|
585
580
|
}
|
586
581
|
|
587
582
|
private onManifestParsed(
|
@@ -705,7 +700,7 @@ export default class StreamController
|
|
705
700
|
return;
|
706
701
|
}
|
707
702
|
const currentLevel = levels[frag.level];
|
708
|
-
const details = currentLevel.details
|
703
|
+
const details = currentLevel.details;
|
709
704
|
if (!details) {
|
710
705
|
this.warn(
|
711
706
|
`Dropping fragment ${frag.sn} of level ${frag.level} after level details were reset`,
|
@@ -878,7 +873,7 @@ export default class StreamController
|
|
878
873
|
(8 * stats.total) / (stats.buffering.end - stats.loading.first),
|
879
874
|
);
|
880
875
|
if (frag.sn !== 'initSegment') {
|
881
|
-
this.fragPrevious = frag;
|
876
|
+
this.fragPrevious = frag as MediaFragment;
|
882
877
|
}
|
883
878
|
this.fragBufferedComplete(frag, part);
|
884
879
|
}
|
@@ -956,7 +951,7 @@ export default class StreamController
|
|
956
951
|
// in that case, reset startFragRequested flag
|
957
952
|
if (!this.loadedmetadata) {
|
958
953
|
this.startFragRequested = false;
|
959
|
-
this.nextLoadPosition = this.
|
954
|
+
this.nextLoadPosition = this.lastCurrentTime;
|
960
955
|
}
|
961
956
|
this.tickImmediate();
|
962
957
|
}
|
@@ -1068,7 +1063,7 @@ export default class StreamController
|
|
1068
1063
|
}
|
1069
1064
|
|
1070
1065
|
private _handleTransmuxComplete(transmuxResult: TransmuxerResult) {
|
1071
|
-
const id =
|
1066
|
+
const id = this.playlistType;
|
1072
1067
|
const { hls } = this;
|
1073
1068
|
const { remuxResult, chunkMeta } = transmuxResult;
|
1074
1069
|
|
@@ -1308,6 +1303,7 @@ export default class StreamController
|
|
1308
1303
|
currentLevel.audioCodec || ''
|
1309
1304
|
}/${audio.codec}]`,
|
1310
1305
|
);
|
1306
|
+
delete tracks.audiovideo;
|
1311
1307
|
}
|
1312
1308
|
if (video) {
|
1313
1309
|
video.levelCodec = currentLevel.videoCodec;
|
@@ -1319,28 +1315,34 @@ export default class StreamController
|
|
1319
1315
|
video.codec
|
1320
1316
|
}]`,
|
1321
1317
|
);
|
1318
|
+
delete tracks.audiovideo;
|
1322
1319
|
}
|
1323
1320
|
if (audiovideo) {
|
1324
1321
|
this.log(
|
1325
1322
|
`Init audiovideo buffer, container:${audiovideo.container}, codecs[level/parsed]=[${currentLevel.codecs}/${audiovideo.codec}]`,
|
1326
1323
|
);
|
1324
|
+
delete tracks.video;
|
1325
|
+
delete tracks.audio;
|
1326
|
+
}
|
1327
|
+
const trackTypes = Object.keys(tracks);
|
1328
|
+
if (trackTypes.length) {
|
1329
|
+
this.hls.trigger(Events.BUFFER_CODECS, tracks as BufferCodecsData);
|
1330
|
+
// loop through tracks that are going to be provided to bufferController
|
1331
|
+
trackTypes.forEach((trackName) => {
|
1332
|
+
const track = tracks[trackName] as Track;
|
1333
|
+
const initSegment = track.initSegment;
|
1334
|
+
if (initSegment?.byteLength) {
|
1335
|
+
this.hls.trigger(Events.BUFFER_APPENDING, {
|
1336
|
+
type: trackName as SourceBufferName,
|
1337
|
+
data: initSegment,
|
1338
|
+
frag,
|
1339
|
+
part: null,
|
1340
|
+
chunkMeta,
|
1341
|
+
parent: frag.type,
|
1342
|
+
});
|
1343
|
+
}
|
1344
|
+
});
|
1327
1345
|
}
|
1328
|
-
this.hls.trigger(Events.BUFFER_CODECS, tracks);
|
1329
|
-
// loop through tracks that are going to be provided to bufferController
|
1330
|
-
Object.keys(tracks).forEach((trackName) => {
|
1331
|
-
const track = tracks[trackName];
|
1332
|
-
const initSegment = track.initSegment;
|
1333
|
-
if (initSegment?.byteLength) {
|
1334
|
-
this.hls.trigger(Events.BUFFER_APPENDING, {
|
1335
|
-
type: trackName as SourceBufferName,
|
1336
|
-
data: initSegment,
|
1337
|
-
frag,
|
1338
|
-
part: null,
|
1339
|
-
chunkMeta,
|
1340
|
-
parent: frag.type,
|
1341
|
-
});
|
1342
|
-
}
|
1343
|
-
});
|
1344
1346
|
// trigger handler right now
|
1345
1347
|
this.tickImmediate();
|
1346
1348
|
}
|
@@ -1425,26 +1427,31 @@ export default class StreamController
|
|
1425
1427
|
}
|
1426
1428
|
|
1427
1429
|
get currentFrag(): Fragment | null {
|
1428
|
-
|
1429
|
-
|
1430
|
-
|
1430
|
+
if (this.fragPlaying) {
|
1431
|
+
return this.fragPlaying;
|
1432
|
+
}
|
1433
|
+
const currentTime = this.media?.currentTime || this.lastCurrentTime;
|
1434
|
+
if (Number.isFinite(currentTime)) {
|
1435
|
+
return this.getAppendedFrag(currentTime);
|
1431
1436
|
}
|
1432
1437
|
return null;
|
1433
1438
|
}
|
1434
1439
|
|
1435
1440
|
get currentProgramDateTime(): Date | null {
|
1436
|
-
const
|
1437
|
-
if (
|
1438
|
-
const
|
1439
|
-
const frag =
|
1440
|
-
|
1441
|
-
|
1442
|
-
|
1443
|
-
|
1444
|
-
) {
|
1445
|
-
const
|
1446
|
-
|
1447
|
-
|
1441
|
+
const currentTime = this.media?.currentTime || this.lastCurrentTime;
|
1442
|
+
if (Number.isFinite(currentTime)) {
|
1443
|
+
const details = this.getLevelDetails();
|
1444
|
+
const frag =
|
1445
|
+
this.currentFrag ||
|
1446
|
+
(details
|
1447
|
+
? findFragmentByPTS(null, details.fragments, currentTime)
|
1448
|
+
: null);
|
1449
|
+
if (frag) {
|
1450
|
+
const programDateTime = frag.programDateTime;
|
1451
|
+
if (programDateTime !== null) {
|
1452
|
+
const epocMs = programDateTime + (currentTime - frag.start) * 1000;
|
1453
|
+
return new Date(epocMs);
|
1454
|
+
}
|
1448
1455
|
}
|
1449
1456
|
}
|
1450
1457
|
return null;
|
@@ -106,8 +106,8 @@ export class SubtitleStreamController
|
|
106
106
|
}
|
107
107
|
|
108
108
|
protected onManifestLoading() {
|
109
|
+
super.onManifestLoading();
|
109
110
|
this.mainDetails = null;
|
110
|
-
this.fragmentTracker.removeAllFragments();
|
111
111
|
}
|
112
112
|
|
113
113
|
protected onMediaDetaching(): void {
|
@@ -124,7 +124,9 @@ export class SubtitleStreamController
|
|
124
124
|
data: SubtitleFragProcessed,
|
125
125
|
) {
|
126
126
|
const { frag, success } = data;
|
127
|
-
|
127
|
+
if (frag.sn !== 'initSegment') {
|
128
|
+
this.fragPrevious = frag as MediaFragment;
|
129
|
+
}
|
128
130
|
this.state = State.IDLE;
|
129
131
|
if (!success) {
|
130
132
|
return;
|
@@ -491,11 +493,9 @@ export class SubtitleStreamController
|
|
491
493
|
level: Level,
|
492
494
|
targetBufferTime: number,
|
493
495
|
) {
|
494
|
-
this.fragCurrent = frag;
|
495
496
|
if (frag.sn === 'initSegment') {
|
496
497
|
this._loadInitSegment(frag, level);
|
497
498
|
} else {
|
498
|
-
this.startFragRequested = true;
|
499
499
|
super.loadFragment(frag, level, targetBufferTime);
|
500
500
|
}
|
501
501
|
}
|
@@ -192,7 +192,7 @@ export class TimelineController implements ComponentAPI {
|
|
192
192
|
{ frag, id, initPTS, timescale }: InitPTSFoundData,
|
193
193
|
) {
|
194
194
|
const { unparsedVttFrags } = this;
|
195
|
-
if (id ===
|
195
|
+
if (id === PlaylistLevelType.MAIN) {
|
196
196
|
this.initPTS[frag.cc] = { baseTime: initPTS, timescale };
|
197
197
|
}
|
198
198
|
|
package/src/hls.ts
CHANGED
@@ -232,7 +232,7 @@ export default class Hls implements HlsEventEmitter {
|
|
232
232
|
new AudioStreamControllerClass(this, fragmentTracker, keyLoader),
|
233
233
|
);
|
234
234
|
}
|
235
|
-
// subtitleTrackController
|
235
|
+
// Instantiate subtitleTrackController before SubtitleStreamController to receive level events first
|
236
236
|
this.subtitleTrackController = this.createController(
|
237
237
|
config.subtitleTrackController,
|
238
238
|
networkControllers,
|
@@ -1,18 +1,17 @@
|
|
1
1
|
import { ErrorTypes, ErrorDetails } from '../errors';
|
2
|
-
import { Fragment } from './fragment';
|
3
|
-
import type {
|
4
|
-
Loader,
|
5
|
-
LoaderConfiguration,
|
6
|
-
FragmentLoaderContext,
|
7
|
-
} from '../types/loader';
|
8
2
|
import { getLoaderConfigWithoutReties } from '../utils/error-helper';
|
9
3
|
import type { HlsConfig } from '../config';
|
10
|
-
import type { BaseSegment, Part } from './fragment';
|
4
|
+
import type { BaseSegment, Fragment, Part } from './fragment';
|
11
5
|
import type {
|
12
6
|
ErrorData,
|
13
7
|
FragLoadedData,
|
14
8
|
PartsLoadedData,
|
15
9
|
} from '../types/events';
|
10
|
+
import type {
|
11
|
+
Loader,
|
12
|
+
LoaderConfiguration,
|
13
|
+
FragmentLoaderContext,
|
14
|
+
} from '../types/loader';
|
16
15
|
|
17
16
|
const MIN_CHUNK_SIZE = Math.pow(2, 17); // 128kb
|
18
17
|
|
@@ -77,13 +76,11 @@ export default class FragmentLoader {
|
|
77
76
|
frag.gap = false;
|
78
77
|
}
|
79
78
|
}
|
80
|
-
const loader =
|
81
|
-
(
|
82
|
-
|
83
|
-
FragmentILoader
|
84
|
-
? new FragmentILoader(config)
|
85
|
-
: (new DefaultILoader(config) as Loader<FragmentLoaderContext>));
|
79
|
+
const loader = (this.loader = FragmentILoader
|
80
|
+
? new FragmentILoader(config)
|
81
|
+
: (new DefaultILoader(config) as Loader<FragmentLoaderContext>));
|
86
82
|
const loaderContext = createLoaderContext(frag);
|
83
|
+
frag.loader = loader;
|
87
84
|
const loadPolicy = getLoaderConfigWithoutReties(
|
88
85
|
config.fragLoadPolicy.default,
|
89
86
|
);
|
@@ -188,13 +185,11 @@ export default class FragmentLoader {
|
|
188
185
|
reject(createGapLoadError(frag, part));
|
189
186
|
return;
|
190
187
|
}
|
191
|
-
const loader =
|
192
|
-
(
|
193
|
-
|
194
|
-
FragmentILoader
|
195
|
-
? new FragmentILoader(config)
|
196
|
-
: (new DefaultILoader(config) as Loader<FragmentLoaderContext>));
|
188
|
+
const loader = (this.loader = FragmentILoader
|
189
|
+
? new FragmentILoader(config)
|
190
|
+
: (new DefaultILoader(config) as Loader<FragmentLoaderContext>));
|
197
191
|
const loaderContext = createLoaderContext(frag, part);
|
192
|
+
frag.loader = loader;
|
198
193
|
// Should we define another load policy for parts?
|
199
194
|
const loadPolicy = getLoaderConfigWithoutReties(
|
200
195
|
config.fragLoadPolicy.default,
|
package/src/loader/fragment.ts
CHANGED
@@ -127,9 +127,9 @@ export class Fragment extends BaseSegment {
|
|
127
127
|
// The ending Presentation Time Stamp (PTS) of the fragment. Set after transmux complete.
|
128
128
|
public endPTS?: number;
|
129
129
|
// The starting Decode Time Stamp (DTS) of the fragment. Set after transmux complete.
|
130
|
-
public startDTS
|
130
|
+
public startDTS?: number;
|
131
131
|
// The ending Decode Time Stamp (DTS) of the fragment. Set after transmux complete.
|
132
|
-
public endDTS
|
132
|
+
public endDTS?: number;
|
133
133
|
// The start time of the fragment, as listed in the manifest. Updated after transmux complete.
|
134
134
|
public start: number = 0;
|
135
135
|
// Set by `updateFragPTSDTS` in level-helper
|
@@ -1,6 +1,5 @@
|
|
1
|
-
import type { Part } from './fragment';
|
1
|
+
import type { Fragment, MediaFragment, Part } from './fragment';
|
2
2
|
import type { DateRange } from './date-range';
|
3
|
-
import type { Fragment, MediaFragment } from './fragment';
|
4
3
|
import type { AttrList } from '../utils/attr-list';
|
5
4
|
import type { VariableMap } from '../types/level';
|
6
5
|
|
@@ -452,7 +452,6 @@ class PlaylistLoader implements NetworkComponentAPI {
|
|
452
452
|
const { id, level, type } = context;
|
453
453
|
|
454
454
|
const url = getResponseUrl(response, context);
|
455
|
-
const levelUrlId = 0;
|
456
455
|
const levelId = Number.isFinite(level as number)
|
457
456
|
? (level as number)
|
458
457
|
: Number.isFinite(id as number)
|
@@ -464,7 +463,7 @@ class PlaylistLoader implements NetworkComponentAPI {
|
|
464
463
|
url,
|
465
464
|
levelId,
|
466
465
|
levelType,
|
467
|
-
|
466
|
+
0,
|
468
467
|
this.variableList,
|
469
468
|
);
|
470
469
|
|
package/src/remux/mp4-remuxer.ts
CHANGED
@@ -160,15 +160,18 @@ export default class MP4Remuxer implements Remuxer {
|
|
160
160
|
if (this.ISGenerated) {
|
161
161
|
const config = this.videoTrackConfig;
|
162
162
|
if (
|
163
|
-
config &&
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
163
|
+
(config &&
|
164
|
+
(videoTrack.width !== config.width ||
|
165
|
+
videoTrack.height !== config.height ||
|
166
|
+
videoTrack.pixelRatio?.[0] !== config.pixelRatio?.[0] ||
|
167
|
+
videoTrack.pixelRatio?.[1] !== config.pixelRatio?.[1])) ||
|
168
|
+
(!config && enoughVideoSamples) ||
|
169
|
+
(this.nextAudioPts === null && enoughAudioSamples)
|
168
170
|
) {
|
169
171
|
this.resetInitSegment();
|
170
172
|
}
|
171
|
-
}
|
173
|
+
}
|
174
|
+
if (!this.ISGenerated) {
|
172
175
|
initSegment = this.generateIS(
|
173
176
|
audioTrack,
|
174
177
|
videoTrack,
|
@@ -1085,8 +1088,9 @@ export default class MP4Remuxer implements Remuxer {
|
|
1085
1088
|
const startDTS: number =
|
1086
1089
|
(nextAudioPts !== null
|
1087
1090
|
? nextAudioPts
|
1088
|
-
: videoData.startDTS * inputTimeScale) + init90kHz;
|
1089
|
-
const endDTS: number =
|
1091
|
+
: (videoData.startDTS as number) * inputTimeScale) + init90kHz;
|
1092
|
+
const endDTS: number =
|
1093
|
+
(videoData.endDTS as number) * inputTimeScale + init90kHz;
|
1090
1094
|
// one sample's duration value
|
1091
1095
|
const frameDuration: number = scaleFactor * AAC_SAMPLES_PER_FRAME;
|
1092
1096
|
// samples count of this segment's duration
|
@@ -178,7 +178,7 @@ class PassThroughRemuxer implements Remuxer {
|
|
178
178
|
initSegment.initPTS = decodeTime - timeOffset;
|
179
179
|
if (initPTS && initPTS.timescale === 1) {
|
180
180
|
logger.warn(
|
181
|
-
`Adjusting initPTS
|
181
|
+
`Adjusting initPTS @${timeOffset} from ${initPTS.baseTime / initPTS.timescale} to ${initSegment.initPTS}`,
|
182
182
|
);
|
183
183
|
}
|
184
184
|
this.initPTS = initPTS = {
|
package/src/types/events.ts
CHANGED
@@ -192,6 +192,18 @@ export interface LevelUpdatedData {
|
|
192
192
|
level: number;
|
193
193
|
}
|
194
194
|
|
195
|
+
export interface AudioTrackUpdatedData {
|
196
|
+
details: LevelDetails;
|
197
|
+
id: number;
|
198
|
+
groupId: string;
|
199
|
+
}
|
200
|
+
|
201
|
+
export interface SubtitleTrackUpdatedData {
|
202
|
+
details: LevelDetails;
|
203
|
+
id: number;
|
204
|
+
groupId: string;
|
205
|
+
}
|
206
|
+
|
195
207
|
export interface LevelPTSUpdatedData {
|
196
208
|
details: LevelDetails;
|
197
209
|
level: Level;
|
@@ -317,7 +329,7 @@ export interface NonNativeTextTracksData {
|
|
317
329
|
}
|
318
330
|
|
319
331
|
export interface InitPTSFoundData {
|
320
|
-
id:
|
332
|
+
id: PlaylistLevelType;
|
321
333
|
frag: MediaFragment;
|
322
334
|
initPTS: number;
|
323
335
|
timescale: number;
|
@@ -461,11 +461,10 @@ export function computeReloadInterval(
|
|
461
461
|
}
|
462
462
|
|
463
463
|
export function getFragmentWithSN(
|
464
|
-
|
464
|
+
details: LevelDetails | undefined,
|
465
465
|
sn: number,
|
466
466
|
fragCurrent: Fragment | null,
|
467
467
|
): MediaFragment | null {
|
468
|
-
const details = level?.details;
|
469
468
|
if (!details) {
|
470
469
|
return null;
|
471
470
|
}
|
@@ -485,14 +484,14 @@ export function getFragmentWithSN(
|
|
485
484
|
}
|
486
485
|
|
487
486
|
export function getPartWith(
|
488
|
-
|
487
|
+
details: LevelDetails | undefined,
|
489
488
|
sn: number,
|
490
489
|
partIndex: number,
|
491
490
|
): Part | null {
|
492
|
-
if (!
|
491
|
+
if (!details) {
|
493
492
|
return null;
|
494
493
|
}
|
495
|
-
return findPart(
|
494
|
+
return findPart(details.partList, sn, partIndex);
|
496
495
|
}
|
497
496
|
|
498
497
|
export function findPart(
|