hls.js 1.5.5 → 1.5.6-0.canary.10003
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/README.md +1 -0
- package/dist/hls-demo.js +10 -0
- package/dist/hls-demo.js.map +1 -1
- package/dist/hls.js +2075 -1166
- package/dist/hls.js.d.ts +65 -50
- package/dist/hls.js.map +1 -1
- package/dist/hls.light.js +1148 -859
- 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 +984 -696
- 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 +1757 -863
- 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 +20 -20
- package/src/config.ts +3 -2
- package/src/controller/abr-controller.ts +21 -20
- package/src/controller/audio-stream-controller.ts +15 -16
- package/src/controller/audio-track-controller.ts +1 -1
- package/src/controller/base-playlist-controller.ts +20 -8
- package/src/controller/base-stream-controller.ts +149 -33
- package/src/controller/buffer-controller.ts +11 -11
- package/src/controller/cap-level-controller.ts +1 -2
- package/src/controller/cmcd-controller.ts +27 -6
- package/src/controller/content-steering-controller.ts +8 -6
- package/src/controller/eme-controller.ts +9 -22
- package/src/controller/error-controller.ts +6 -8
- package/src/controller/fps-controller.ts +2 -3
- package/src/controller/gap-controller.ts +43 -16
- package/src/controller/latency-controller.ts +9 -11
- package/src/controller/level-controller.ts +12 -18
- package/src/controller/stream-controller.ts +25 -32
- package/src/controller/subtitle-stream-controller.ts +13 -14
- package/src/controller/subtitle-track-controller.ts +5 -3
- package/src/controller/timeline-controller.ts +23 -30
- package/src/crypt/aes-crypto.ts +21 -2
- package/src/crypt/decrypter-aes-mode.ts +4 -0
- package/src/crypt/decrypter.ts +32 -18
- package/src/crypt/fast-aes-key.ts +24 -5
- package/src/demux/audio/adts.ts +9 -4
- package/src/demux/sample-aes.ts +2 -0
- package/src/demux/transmuxer-interface.ts +4 -12
- package/src/demux/transmuxer-worker.ts +4 -4
- package/src/demux/transmuxer.ts +16 -3
- package/src/demux/tsdemuxer.ts +71 -37
- package/src/demux/video/avc-video-parser.ts +208 -119
- package/src/demux/video/base-video-parser.ts +134 -2
- package/src/demux/video/exp-golomb.ts +0 -208
- package/src/demux/video/hevc-video-parser.ts +746 -0
- package/src/events.ts +7 -0
- package/src/hls.ts +42 -34
- package/src/loader/fragment-loader.ts +9 -2
- package/src/loader/key-loader.ts +2 -0
- package/src/loader/level-key.ts +10 -9
- package/src/loader/playlist-loader.ts +4 -5
- package/src/remux/mp4-generator.ts +196 -1
- package/src/remux/mp4-remuxer.ts +23 -7
- package/src/task-loop.ts +5 -2
- package/src/types/component-api.ts +2 -0
- package/src/types/demuxer.ts +3 -0
- package/src/types/events.ts +4 -0
- package/src/utils/codecs.ts +33 -4
- package/src/utils/encryption-methods-util.ts +21 -0
- package/src/utils/logger.ts +54 -24
@@ -1,12 +1,15 @@
|
|
1
1
|
import TaskLoop from '../task-loop';
|
2
2
|
import { FragmentState } from './fragment-tracker';
|
3
3
|
import { Bufferable, BufferHelper, BufferInfo } from '../utils/buffer-helper';
|
4
|
-
import { logger } from '../utils/logger';
|
5
4
|
import { Events } from '../events';
|
6
5
|
import { ErrorDetails, ErrorTypes } from '../errors';
|
7
6
|
import { ChunkMetadata } from '../types/transmuxer';
|
8
7
|
import { appendUint8Array } from '../utils/mp4-tools';
|
9
8
|
import { alignStream } from '../utils/discontinuities';
|
9
|
+
import {
|
10
|
+
isFullSegmentEncryption,
|
11
|
+
getAesModeFromFullSegmentMethod,
|
12
|
+
} from '../utils/encryption-methods-util';
|
10
13
|
import {
|
11
14
|
findFragmentByPDT,
|
12
15
|
findFragmentByPTS,
|
@@ -97,12 +100,8 @@ export default class BaseStreamController
|
|
97
100
|
protected startFragRequested: boolean = false;
|
98
101
|
protected decrypter: Decrypter;
|
99
102
|
protected initPTS: RationalTimestamp[] = [];
|
100
|
-
protected
|
101
|
-
|
102
|
-
|
103
|
-
private readonly logPrefix: string = '';
|
104
|
-
protected log: (msg: any) => void;
|
105
|
-
protected warn: (msg: any) => void;
|
103
|
+
protected buffering: boolean = true;
|
104
|
+
private loadingParts: boolean = false;
|
106
105
|
|
107
106
|
constructor(
|
108
107
|
hls: Hls,
|
@@ -111,18 +110,32 @@ export default class BaseStreamController
|
|
111
110
|
logPrefix: string,
|
112
111
|
playlistType: PlaylistLevelType,
|
113
112
|
) {
|
114
|
-
super();
|
113
|
+
super(logPrefix, hls.logger);
|
115
114
|
this.playlistType = playlistType;
|
116
|
-
this.logPrefix = logPrefix;
|
117
|
-
this.log = logger.log.bind(logger, `${logPrefix}:`);
|
118
|
-
this.warn = logger.warn.bind(logger, `${logPrefix}:`);
|
119
115
|
this.hls = hls;
|
120
116
|
this.fragmentLoader = new FragmentLoader(hls.config);
|
121
117
|
this.keyLoader = keyLoader;
|
122
118
|
this.fragmentTracker = fragmentTracker;
|
123
119
|
this.config = hls.config;
|
124
120
|
this.decrypter = new Decrypter(hls.config);
|
121
|
+
}
|
122
|
+
|
123
|
+
protected registerListeners() {
|
124
|
+
const { hls } = this;
|
125
|
+
hls.on(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
126
|
+
hls.on(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
127
|
+
hls.on(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
125
128
|
hls.on(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
|
129
|
+
hls.on(Events.ERROR, this.onError, this);
|
130
|
+
}
|
131
|
+
|
132
|
+
protected unregisterListeners() {
|
133
|
+
const { hls } = this;
|
134
|
+
hls.off(Events.MEDIA_ATTACHED, this.onMediaAttached, this);
|
135
|
+
hls.off(Events.MEDIA_DETACHING, this.onMediaDetaching, this);
|
136
|
+
hls.off(Events.MANIFEST_LOADING, this.onManifestLoading, this);
|
137
|
+
hls.off(Events.MANIFEST_LOADED, this.onManifestLoaded, this);
|
138
|
+
hls.off(Events.ERROR, this.onError, this);
|
126
139
|
}
|
127
140
|
|
128
141
|
protected doTick() {
|
@@ -150,6 +163,14 @@ export default class BaseStreamController
|
|
150
163
|
this.state = State.STOPPED;
|
151
164
|
}
|
152
165
|
|
166
|
+
public pauseBuffering() {
|
167
|
+
this.buffering = false;
|
168
|
+
}
|
169
|
+
|
170
|
+
public resumeBuffering() {
|
171
|
+
this.buffering = true;
|
172
|
+
}
|
173
|
+
|
153
174
|
protected _streamEnded(
|
154
175
|
bufferInfo: BufferInfo,
|
155
176
|
levelDetails: LevelDetails,
|
@@ -197,10 +218,8 @@ export default class BaseStreamController
|
|
197
218
|
data: MediaAttachedData,
|
198
219
|
) {
|
199
220
|
const media = (this.media = this.mediaBuffer = data.media);
|
200
|
-
|
201
|
-
|
202
|
-
media.addEventListener('seeking', this.onvseeking);
|
203
|
-
media.addEventListener('ended', this.onvended);
|
221
|
+
media.addEventListener('seeking', this.onMediaSeeking);
|
222
|
+
media.addEventListener('ended', this.onMediaEnded);
|
204
223
|
const config = this.config;
|
205
224
|
if (this.levels && config.autoStartLoad && this.state === State.STOPPED) {
|
206
225
|
this.startLoad(config.startPosition);
|
@@ -215,10 +234,9 @@ export default class BaseStreamController
|
|
215
234
|
}
|
216
235
|
|
217
236
|
// remove video listeners
|
218
|
-
if (media
|
219
|
-
media.removeEventListener('seeking', this.
|
220
|
-
media.removeEventListener('ended', this.
|
221
|
-
this.onvseeking = this.onvended = null;
|
237
|
+
if (media) {
|
238
|
+
media.removeEventListener('seeking', this.onMediaSeeking);
|
239
|
+
media.removeEventListener('ended', this.onMediaEnded);
|
222
240
|
}
|
223
241
|
if (this.keyLoader) {
|
224
242
|
this.keyLoader.detach();
|
@@ -229,7 +247,11 @@ export default class BaseStreamController
|
|
229
247
|
this.stopLoad();
|
230
248
|
}
|
231
249
|
|
232
|
-
protected
|
250
|
+
protected onManifestLoading() {}
|
251
|
+
|
252
|
+
protected onError(event: Events.ERROR, data: ErrorData) {}
|
253
|
+
|
254
|
+
protected onMediaSeeking = () => {
|
233
255
|
const { config, fragCurrent, media, mediaBuffer, state } = this;
|
234
256
|
const currentTime: number = media ? media.currentTime : 0;
|
235
257
|
const bufferInfo = BufferHelper.bufferInfo(
|
@@ -283,6 +305,21 @@ export default class BaseStreamController
|
|
283
305
|
);
|
284
306
|
|
285
307
|
this.lastCurrentTime = currentTime;
|
308
|
+
if (!this.loadingParts) {
|
309
|
+
const bufferEnd = Math.max(bufferInfo.end, currentTime);
|
310
|
+
const shouldLoadParts = this.shouldLoadParts(
|
311
|
+
this.getLevelDetails(),
|
312
|
+
bufferEnd,
|
313
|
+
);
|
314
|
+
if (shouldLoadParts) {
|
315
|
+
this.log(
|
316
|
+
`LL-Part loading ON after seeking to ${currentTime.toFixed(
|
317
|
+
2,
|
318
|
+
)} with buffer @${bufferEnd.toFixed(2)}`,
|
319
|
+
);
|
320
|
+
this.loadingParts = shouldLoadParts;
|
321
|
+
}
|
322
|
+
}
|
286
323
|
}
|
287
324
|
|
288
325
|
// in case seeking occurs although no media buffered, adjust startPosition and nextLoadPosition to seek target
|
@@ -292,12 +329,17 @@ export default class BaseStreamController
|
|
292
329
|
|
293
330
|
// Async tick to speed up processing
|
294
331
|
this.tickImmediate();
|
295
|
-
}
|
332
|
+
};
|
296
333
|
|
297
|
-
protected onMediaEnded() {
|
334
|
+
protected onMediaEnded = () => {
|
298
335
|
// reset startPosition and lastCurrentTime to restart playback @ stream beginning
|
299
336
|
this.startPosition = this.lastCurrentTime = 0;
|
300
|
-
|
337
|
+
if (this.playlistType === PlaylistLevelType.MAIN) {
|
338
|
+
this.hls.trigger(Events.MEDIA_ENDED, {
|
339
|
+
stalled: false,
|
340
|
+
});
|
341
|
+
}
|
342
|
+
};
|
301
343
|
|
302
344
|
protected onManifestLoaded(
|
303
345
|
event: Events.MANIFEST_LOADED,
|
@@ -312,7 +354,7 @@ export default class BaseStreamController
|
|
312
354
|
this.stopLoad();
|
313
355
|
super.onHandlerDestroying();
|
314
356
|
// @ts-ignore
|
315
|
-
this.hls = null;
|
357
|
+
this.hls = this.onMediaSeeking = this.onMediaEnded = null;
|
316
358
|
}
|
317
359
|
|
318
360
|
protected onHandlerDestroyed() {
|
@@ -486,7 +528,7 @@ export default class BaseStreamController
|
|
486
528
|
payload.byteLength > 0 &&
|
487
529
|
decryptData?.key &&
|
488
530
|
decryptData.iv &&
|
489
|
-
decryptData.method
|
531
|
+
isFullSegmentEncryption(decryptData.method)
|
490
532
|
) {
|
491
533
|
const startTime = self.performance.now();
|
492
534
|
// decrypt init segment data
|
@@ -495,6 +537,7 @@ export default class BaseStreamController
|
|
495
537
|
new Uint8Array(payload),
|
496
538
|
decryptData.key.buffer,
|
497
539
|
decryptData.iv.buffer,
|
540
|
+
getAesModeFromFullSegmentMethod(decryptData.method),
|
498
541
|
)
|
499
542
|
.catch((err) => {
|
500
543
|
hls.trigger(Events.ERROR, {
|
@@ -649,7 +692,7 @@ export default class BaseStreamController
|
|
649
692
|
if (frag.encrypted && !frag.decryptdata?.key) {
|
650
693
|
this.log(
|
651
694
|
`Loading key for ${frag.sn} of [${details.startSN}-${details.endSN}], ${
|
652
|
-
this.
|
695
|
+
this.playlistType === PlaylistLevelType.MAIN ? 'level' : 'track'
|
653
696
|
} ${frag.level}`,
|
654
697
|
);
|
655
698
|
this.state = State.KEY_LOADING;
|
@@ -673,8 +716,23 @@ export default class BaseStreamController
|
|
673
716
|
this.keyLoader.loadClear(frag, details.encryptedFragments);
|
674
717
|
}
|
675
718
|
|
719
|
+
const fragPrevious = this.fragPrevious;
|
720
|
+
if (
|
721
|
+
frag.sn !== 'initSegment' &&
|
722
|
+
(!fragPrevious || frag.sn !== fragPrevious.sn)
|
723
|
+
) {
|
724
|
+
const shouldLoadParts = this.shouldLoadParts(level.details, frag.end);
|
725
|
+
if (shouldLoadParts !== this.loadingParts) {
|
726
|
+
this.log(
|
727
|
+
`LL-Part loading ${
|
728
|
+
shouldLoadParts ? 'ON' : 'OFF'
|
729
|
+
} loading sn ${fragPrevious?.sn}->${frag.sn}`,
|
730
|
+
);
|
731
|
+
this.loadingParts = shouldLoadParts;
|
732
|
+
}
|
733
|
+
}
|
676
734
|
targetBufferTime = Math.max(frag.start, targetBufferTime || 0);
|
677
|
-
if (this.
|
735
|
+
if (this.loadingParts && frag.sn !== 'initSegment') {
|
678
736
|
const partList = details.partList;
|
679
737
|
if (partList && progressCallback) {
|
680
738
|
if (targetBufferTime > frag.end && details.fragmentHint) {
|
@@ -689,7 +747,7 @@ export default class BaseStreamController
|
|
689
747
|
} of playlist [${details.startSN}-${
|
690
748
|
details.endSN
|
691
749
|
}] parts [0-${partIndex}-${partList.length - 1}] ${
|
692
|
-
this.
|
750
|
+
this.playlistType === PlaylistLevelType.MAIN ? 'level' : 'track'
|
693
751
|
}: ${frag.level}, target: ${parseFloat(
|
694
752
|
targetBufferTime.toFixed(3),
|
695
753
|
)}`,
|
@@ -745,10 +803,22 @@ export default class BaseStreamController
|
|
745
803
|
}
|
746
804
|
}
|
747
805
|
|
806
|
+
if (frag.sn !== 'initSegment' && this.loadingParts) {
|
807
|
+
this.log(
|
808
|
+
`LL-Part loading OFF after next part miss @${targetBufferTime.toFixed(
|
809
|
+
2,
|
810
|
+
)}`,
|
811
|
+
);
|
812
|
+
this.loadingParts = false;
|
813
|
+
} else if (!frag.url) {
|
814
|
+
// Selected fragment hint for part but not loading parts
|
815
|
+
return Promise.resolve(null);
|
816
|
+
}
|
817
|
+
|
748
818
|
this.log(
|
749
819
|
`Loading fragment ${frag.sn} cc: ${frag.cc} ${
|
750
820
|
details ? 'of [' + details.startSN + '-' + details.endSN + '] ' : ''
|
751
|
-
}${this.
|
821
|
+
}${this.playlistType === PlaylistLevelType.MAIN ? 'level' : 'track'}: ${
|
752
822
|
frag.level
|
753
823
|
}, target: ${parseFloat(targetBufferTime.toFixed(3))}`,
|
754
824
|
);
|
@@ -872,9 +942,50 @@ export default class BaseStreamController
|
|
872
942
|
if (part) {
|
873
943
|
part.stats.parsing.end = now;
|
874
944
|
}
|
945
|
+
// See if part loading should be disabled/enabled based on buffer and playback position.
|
946
|
+
if (frag.sn !== 'initSegment') {
|
947
|
+
const levelDetails = this.getLevelDetails();
|
948
|
+
const loadingPartsAtEdge = levelDetails && frag.sn > levelDetails.endSN;
|
949
|
+
const shouldLoadParts =
|
950
|
+
loadingPartsAtEdge || this.shouldLoadParts(levelDetails, frag.end);
|
951
|
+
if (shouldLoadParts !== this.loadingParts) {
|
952
|
+
this.log(
|
953
|
+
`LL-Part loading ${
|
954
|
+
shouldLoadParts ? 'ON' : 'OFF'
|
955
|
+
} after parsing segment ending @${frag.end.toFixed(2)}`,
|
956
|
+
);
|
957
|
+
this.loadingParts = shouldLoadParts;
|
958
|
+
}
|
959
|
+
}
|
960
|
+
|
875
961
|
this.updateLevelTiming(frag, part, level, chunkMeta.partial);
|
876
962
|
}
|
877
963
|
|
964
|
+
private shouldLoadParts(
|
965
|
+
details: LevelDetails | undefined,
|
966
|
+
bufferEnd: number,
|
967
|
+
): boolean {
|
968
|
+
if (this.config.lowLatencyMode) {
|
969
|
+
if (!details) {
|
970
|
+
return this.loadingParts;
|
971
|
+
}
|
972
|
+
if (details?.partList) {
|
973
|
+
// Buffer must be ahead of first part + duration of parts after last segment
|
974
|
+
// and playback must be at or past segment adjacent to part list
|
975
|
+
const firstPart = details.partList[0];
|
976
|
+
const safePartStart =
|
977
|
+
firstPart.end + (details.fragmentHint?.duration || 0);
|
978
|
+
if (
|
979
|
+
bufferEnd >= safePartStart &&
|
980
|
+
this.lastCurrentTime > firstPart.start - firstPart.fragment.duration
|
981
|
+
) {
|
982
|
+
return true;
|
983
|
+
}
|
984
|
+
}
|
985
|
+
}
|
986
|
+
return false;
|
987
|
+
}
|
988
|
+
|
878
989
|
protected getCurrentContext(
|
879
990
|
chunkMeta: ChunkMetadata,
|
880
991
|
): { frag: Fragment; part: Part | null; level: Level } | null {
|
@@ -1056,7 +1167,8 @@ export default class BaseStreamController
|
|
1056
1167
|
// find fragment index, contiguous with end of buffer position
|
1057
1168
|
const { config } = this;
|
1058
1169
|
const start = fragments[0].start;
|
1059
|
-
|
1170
|
+
const canLoadParts = config.lowLatencyMode && !!levelDetails.partList;
|
1171
|
+
let frag: Fragment | null = null;
|
1060
1172
|
|
1061
1173
|
if (levelDetails.live) {
|
1062
1174
|
const initialLiveManifestSize = config.initialLiveManifestSize;
|
@@ -1076,6 +1188,10 @@ export default class BaseStreamController
|
|
1076
1188
|
this.startPosition === -1) ||
|
1077
1189
|
pos < start
|
1078
1190
|
) {
|
1191
|
+
if (canLoadParts && !this.loadingParts) {
|
1192
|
+
this.log(`LL-Part loading ON for initial live fragment`);
|
1193
|
+
this.loadingParts = true;
|
1194
|
+
}
|
1079
1195
|
frag = this.getInitialLiveFragment(levelDetails, fragments);
|
1080
1196
|
this.startPosition = this.nextLoadPosition = frag
|
1081
1197
|
? this.hls.liveSyncPosition || frag.start
|
@@ -1088,7 +1204,7 @@ export default class BaseStreamController
|
|
1088
1204
|
|
1089
1205
|
// If we haven't run into any special cases already, just load the fragment most closely matching the requested position
|
1090
1206
|
if (!frag) {
|
1091
|
-
const end =
|
1207
|
+
const end = this.loadingParts
|
1092
1208
|
? levelDetails.partEnd
|
1093
1209
|
: levelDetails.fragmentEnd;
|
1094
1210
|
frag = this.getFragmentAtPosition(pos, end, levelDetails);
|
@@ -1271,7 +1387,7 @@ export default class BaseStreamController
|
|
1271
1387
|
const partList = levelDetails.partList;
|
1272
1388
|
|
1273
1389
|
const loadingParts = !!(
|
1274
|
-
|
1390
|
+
this.loadingParts &&
|
1275
1391
|
partList?.length &&
|
1276
1392
|
fragmentHint
|
1277
1393
|
);
|
@@ -1550,7 +1666,7 @@ export default class BaseStreamController
|
|
1550
1666
|
errorAction.resolved = true;
|
1551
1667
|
}
|
1552
1668
|
} else {
|
1553
|
-
|
1669
|
+
this.warn(
|
1554
1670
|
`${data.details} reached or exceeded max retry (${retryCount})`,
|
1555
1671
|
);
|
1556
1672
|
return;
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import { Events } from '../events';
|
2
|
-
import {
|
2
|
+
import { Logger } from '../utils/logger';
|
3
3
|
import { ErrorDetails, ErrorTypes } from '../errors';
|
4
4
|
import { BufferHelper } from '../utils/buffer-helper';
|
5
5
|
import {
|
@@ -42,7 +42,7 @@ interface BufferedChangeEvent extends Event {
|
|
42
42
|
readonly removedRanges?: TimeRanges;
|
43
43
|
}
|
44
44
|
|
45
|
-
export default class BufferController implements ComponentAPI {
|
45
|
+
export default class BufferController extends Logger implements ComponentAPI {
|
46
46
|
// The level details used to determine duration, target-duration and live
|
47
47
|
private details: LevelDetails | null = null;
|
48
48
|
// cache the self generated object url to detect hijack of video tag
|
@@ -82,17 +82,10 @@ export default class BufferController implements ComponentAPI {
|
|
82
82
|
public pendingTracks: TrackSet = {};
|
83
83
|
public sourceBuffer!: SourceBuffers;
|
84
84
|
|
85
|
-
protected log: (msg: any) => void;
|
86
|
-
protected warn: (msg: any, obj?: any) => void;
|
87
|
-
protected error: (msg: any, obj?: any) => void;
|
88
|
-
|
89
85
|
constructor(hls: Hls) {
|
86
|
+
super('buffer-controller', hls.logger);
|
90
87
|
this.hls = hls;
|
91
|
-
const logPrefix = '[buffer-controller]';
|
92
88
|
this.appendSource = hls.config.preferManagedMediaSource;
|
93
|
-
this.log = logger.log.bind(logger, logPrefix);
|
94
|
-
this.warn = logger.warn.bind(logger, logPrefix);
|
95
|
-
this.error = logger.error.bind(logger, logPrefix);
|
96
89
|
this._initSourceBuffer();
|
97
90
|
this.registerListeners();
|
98
91
|
}
|
@@ -110,6 +103,12 @@ export default class BufferController implements ComponentAPI {
|
|
110
103
|
this.lastMpegAudioChunk = null;
|
111
104
|
// @ts-ignore
|
112
105
|
this.hls = null;
|
106
|
+
// @ts-ignore
|
107
|
+
this._onMediaSourceOpen = this._onMediaSourceClose = null;
|
108
|
+
// @ts-ignore
|
109
|
+
this._onMediaSourceEnded = null;
|
110
|
+
// @ts-ignore
|
111
|
+
this._onStartStreaming = this._onEndStreaming = null;
|
113
112
|
}
|
114
113
|
|
115
114
|
protected registerListeners() {
|
@@ -296,6 +295,7 @@ export default class BufferController implements ComponentAPI {
|
|
296
295
|
this.resetBuffer(type);
|
297
296
|
});
|
298
297
|
this._initSourceBuffer();
|
298
|
+
this.hls.resumeBuffering();
|
299
299
|
}
|
300
300
|
|
301
301
|
private resetBuffer(type: SourceBufferName) {
|
@@ -1004,7 +1004,7 @@ export default class BufferController implements ComponentAPI {
|
|
1004
1004
|
private _onMediaEmptied = () => {
|
1005
1005
|
const { mediaSrc, _objectUrl } = this;
|
1006
1006
|
if (mediaSrc !== _objectUrl) {
|
1007
|
-
|
1007
|
+
this.error(
|
1008
1008
|
`Media element src was set while attaching MediaSource (${_objectUrl} > ${mediaSrc})`,
|
1009
1009
|
);
|
1010
1010
|
}
|
@@ -12,7 +12,6 @@ import type {
|
|
12
12
|
LevelsUpdatedData,
|
13
13
|
} from '../types/events';
|
14
14
|
import StreamController from './stream-controller';
|
15
|
-
import { logger } from '../utils/logger';
|
16
15
|
import type { ComponentAPI } from '../types/component-api';
|
17
16
|
import type Hls from '../hls';
|
18
17
|
|
@@ -152,7 +151,7 @@ class CapLevelController implements ComponentAPI {
|
|
152
151
|
const hls = this.hls;
|
153
152
|
const maxLevel = this.getMaxLevel(levels.length - 1);
|
154
153
|
if (maxLevel !== this.autoLevelCapping) {
|
155
|
-
logger.log(
|
154
|
+
hls.logger.log(
|
156
155
|
`Setting autoLevelCapping to ${maxLevel}: ${levels[maxLevel].height}p@${levels[maxLevel].bitrate} for media ${this.mediaWidth}x${this.mediaHeight}`,
|
157
156
|
);
|
158
157
|
}
|
@@ -5,9 +5,9 @@ import { CmcdObjectType } from '@svta/common-media-library/cmcd/CmcdObjectType';
|
|
5
5
|
import { CmcdStreamingFormat } from '@svta/common-media-library/cmcd/CmcdStreamingFormat';
|
6
6
|
import { appendCmcdHeaders } from '@svta/common-media-library/cmcd/appendCmcdHeaders';
|
7
7
|
import { appendCmcdQuery } from '@svta/common-media-library/cmcd/appendCmcdQuery';
|
8
|
+
import type { CmcdEncodeOptions } from '@svta/common-media-library/cmcd/CmcdEncodeOptions';
|
8
9
|
import { uuid } from '@svta/common-media-library/utils/uuid';
|
9
10
|
import { BufferHelper } from '../utils/buffer-helper';
|
10
|
-
import { logger } from '../utils/logger';
|
11
11
|
import type { ComponentAPI } from '../types/component-api';
|
12
12
|
import type { Fragment } from '../loader/fragment';
|
13
13
|
import type { BufferCreatedData, MediaAttachedData } from '../types/events';
|
@@ -165,7 +165,7 @@ export default class CMCDController implements ComponentAPI {
|
|
165
165
|
data.su = this.buffering;
|
166
166
|
}
|
167
167
|
|
168
|
-
// TODO: Implement rtp, nrr,
|
168
|
+
// TODO: Implement rtp, nrr, dl
|
169
169
|
|
170
170
|
const { includeKeys } = this;
|
171
171
|
if (includeKeys) {
|
@@ -175,14 +175,18 @@ export default class CMCDController implements ComponentAPI {
|
|
175
175
|
}, {});
|
176
176
|
}
|
177
177
|
|
178
|
+
const options: CmcdEncodeOptions = {
|
179
|
+
baseUrl: context.url,
|
180
|
+
};
|
181
|
+
|
178
182
|
if (this.useHeaders) {
|
179
183
|
if (!context.headers) {
|
180
184
|
context.headers = {};
|
181
185
|
}
|
182
186
|
|
183
|
-
appendCmcdHeaders(context.headers, data);
|
187
|
+
appendCmcdHeaders(context.headers, data, options);
|
184
188
|
} else {
|
185
|
-
context.url = appendCmcdQuery(context.url, data);
|
189
|
+
context.url = appendCmcdQuery(context.url, data, options);
|
186
190
|
}
|
187
191
|
}
|
188
192
|
|
@@ -196,7 +200,7 @@ export default class CMCDController implements ComponentAPI {
|
|
196
200
|
su: !this.initialized,
|
197
201
|
});
|
198
202
|
} catch (error) {
|
199
|
-
logger.warn('Could not generate manifest CMCD data.', error);
|
203
|
+
this.hls.logger.warn('Could not generate manifest CMCD data.', error);
|
200
204
|
}
|
201
205
|
};
|
202
206
|
|
@@ -223,12 +227,29 @@ export default class CMCDController implements ComponentAPI {
|
|
223
227
|
data.bl = this.getBufferLength(ot);
|
224
228
|
}
|
225
229
|
|
230
|
+
const next = this.getNextFrag(fragment);
|
231
|
+
if (next) {
|
232
|
+
if (next.url && next.url !== fragment.url) {
|
233
|
+
data.nor = next.url;
|
234
|
+
}
|
235
|
+
}
|
236
|
+
|
226
237
|
this.apply(context, data);
|
227
238
|
} catch (error) {
|
228
|
-
logger.warn('Could not generate segment CMCD data.', error);
|
239
|
+
this.hls.logger.warn('Could not generate segment CMCD data.', error);
|
229
240
|
}
|
230
241
|
};
|
231
242
|
|
243
|
+
private getNextFrag(fragment: Fragment): Fragment | undefined {
|
244
|
+
const levelDetails = this.hls.levels[fragment.level]?.details;
|
245
|
+
if (levelDetails) {
|
246
|
+
const index = (fragment.sn as number) - levelDetails.startSN;
|
247
|
+
return levelDetails.fragments[index + 1];
|
248
|
+
}
|
249
|
+
|
250
|
+
return undefined;
|
251
|
+
}
|
252
|
+
|
232
253
|
/**
|
233
254
|
* The CMCD object type.
|
234
255
|
*/
|
@@ -3,7 +3,7 @@ import { Level } from '../types/level';
|
|
3
3
|
import { reassignFragmentLevelIndexes } from '../utils/level-helper';
|
4
4
|
import { AttrList } from '../utils/attr-list';
|
5
5
|
import { ErrorActionFlags, NetworkErrorAction } from './error-controller';
|
6
|
-
import {
|
6
|
+
import { Logger } from '../utils/logger';
|
7
7
|
import {
|
8
8
|
PlaylistContextType,
|
9
9
|
type Loader,
|
@@ -48,9 +48,11 @@ export type UriReplacement = {
|
|
48
48
|
|
49
49
|
const PATHWAY_PENALTY_DURATION_MS = 300000;
|
50
50
|
|
51
|
-
export default class ContentSteeringController
|
51
|
+
export default class ContentSteeringController
|
52
|
+
extends Logger
|
53
|
+
implements NetworkComponentAPI
|
54
|
+
{
|
52
55
|
private readonly hls: Hls;
|
53
|
-
private log: (msg: any) => void;
|
54
56
|
private loader: Loader<LoaderContext> | null = null;
|
55
57
|
private uri: string | null = null;
|
56
58
|
private pathwayId: string = '.';
|
@@ -66,8 +68,8 @@ export default class ContentSteeringController implements NetworkComponentAPI {
|
|
66
68
|
private penalizedPathways: { [pathwayId: string]: number } = {};
|
67
69
|
|
68
70
|
constructor(hls: Hls) {
|
71
|
+
super('content-steering', hls.logger);
|
69
72
|
this.hls = hls;
|
70
|
-
this.log = logger.log.bind(logger, `[content-steering]:`);
|
71
73
|
this.registerListeners();
|
72
74
|
}
|
73
75
|
|
@@ -203,7 +205,7 @@ export default class ContentSteeringController implements NetworkComponentAPI {
|
|
203
205
|
errorAction.resolved = this.pathwayId !== errorPathway;
|
204
206
|
}
|
205
207
|
if (!errorAction.resolved) {
|
206
|
-
|
208
|
+
this.warn(
|
207
209
|
`Could not resolve ${data.details} ("${
|
208
210
|
data.error.message
|
209
211
|
}") with content-steering for Pathway: ${errorPathway} levels: ${
|
@@ -442,7 +444,7 @@ export default class ContentSteeringController implements NetworkComponentAPI {
|
|
442
444
|
) => {
|
443
445
|
this.log(`Loaded steering manifest: "${url}"`);
|
444
446
|
const steeringData = response.data as SteeringManifest;
|
445
|
-
if (steeringData
|
447
|
+
if (steeringData?.VERSION !== 1) {
|
446
448
|
this.log(`Steering VERSION ${steeringData.VERSION} not supported!`);
|
447
449
|
return;
|
448
450
|
}
|
@@ -5,7 +5,7 @@
|
|
5
5
|
*/
|
6
6
|
import { Events } from '../events';
|
7
7
|
import { ErrorTypes, ErrorDetails } from '../errors';
|
8
|
-
import {
|
8
|
+
import { Logger } from '../utils/logger';
|
9
9
|
import {
|
10
10
|
getKeySystemsForConfig,
|
11
11
|
getSupportedMediaKeySystemConfigurations,
|
@@ -41,9 +41,6 @@ import type {
|
|
41
41
|
LoaderConfiguration,
|
42
42
|
LoaderContext,
|
43
43
|
} from '../types/loader';
|
44
|
-
|
45
|
-
const LOGGER_PREFIX = '[eme]';
|
46
|
-
|
47
44
|
interface KeySystemAccessPromises {
|
48
45
|
keySystemAccess: Promise<MediaKeySystemAccess>;
|
49
46
|
mediaKeys?: Promise<MediaKeys>;
|
@@ -68,7 +65,7 @@ export interface MediaKeySessionContext {
|
|
68
65
|
* @class
|
69
66
|
* @constructor
|
70
67
|
*/
|
71
|
-
class EMEController implements ComponentAPI {
|
68
|
+
class EMEController extends Logger implements ComponentAPI {
|
72
69
|
public static CDMCleanupPromise: Promise<void> | void;
|
73
70
|
|
74
71
|
private readonly hls: Hls;
|
@@ -90,15 +87,9 @@ class EMEController implements ComponentAPI {
|
|
90
87
|
private setMediaKeysQueue: Promise<void>[] = EMEController.CDMCleanupPromise
|
91
88
|
? [EMEController.CDMCleanupPromise]
|
92
89
|
: [];
|
93
|
-
private onMediaEncrypted = this._onMediaEncrypted.bind(this);
|
94
|
-
private onWaitingForKey = this._onWaitingForKey.bind(this);
|
95
|
-
|
96
|
-
private debug: (msg: any) => void = logger.debug.bind(logger, LOGGER_PREFIX);
|
97
|
-
private log: (msg: any) => void = logger.log.bind(logger, LOGGER_PREFIX);
|
98
|
-
private warn: (msg: any) => void = logger.warn.bind(logger, LOGGER_PREFIX);
|
99
|
-
private error: (msg: any) => void = logger.error.bind(logger, LOGGER_PREFIX);
|
100
90
|
|
101
91
|
constructor(hls: Hls) {
|
92
|
+
super('eme', hls.logger);
|
102
93
|
this.hls = hls;
|
103
94
|
this.config = hls.config;
|
104
95
|
this.registerListeners();
|
@@ -113,13 +104,9 @@ class EMEController implements ComponentAPI {
|
|
113
104
|
config.licenseXhrSetup = config.licenseResponseCallback = undefined;
|
114
105
|
config.drmSystems = config.drmSystemOptions = {};
|
115
106
|
// @ts-ignore
|
116
|
-
this.hls =
|
117
|
-
this.onMediaEncrypted =
|
118
|
-
this.onWaitingForKey =
|
119
|
-
this.keyIdToKeySessionPromise =
|
120
|
-
null as any;
|
107
|
+
this.hls = this.config = this.keyIdToKeySessionPromise = null;
|
121
108
|
// @ts-ignore
|
122
|
-
this.
|
109
|
+
this.onMediaEncrypted = this.onWaitingForKey = null;
|
123
110
|
}
|
124
111
|
|
125
112
|
private registerListeners() {
|
@@ -523,7 +510,7 @@ class EMEController implements ComponentAPI {
|
|
523
510
|
return this.attemptKeySystemAccess(keySystemsToAttempt);
|
524
511
|
}
|
525
512
|
|
526
|
-
private
|
513
|
+
private onMediaEncrypted = (event: MediaEncryptedEvent) => {
|
527
514
|
const { initDataType, initData } = event;
|
528
515
|
this.debug(`"${event.type}" event: init data type: "${initDataType}"`);
|
529
516
|
|
@@ -639,11 +626,11 @@ class EMEController implements ComponentAPI {
|
|
639
626
|
);
|
640
627
|
}
|
641
628
|
keySessionContextPromise.catch((error) => this.handleError(error));
|
642
|
-
}
|
629
|
+
};
|
643
630
|
|
644
|
-
private
|
631
|
+
private onWaitingForKey = (event: Event) => {
|
645
632
|
this.log(`"${event.type}" event`);
|
646
|
-
}
|
633
|
+
};
|
647
634
|
|
648
635
|
private attemptSetMediaKeys(
|
649
636
|
keySystem: KeySystems,
|
@@ -8,7 +8,7 @@ import {
|
|
8
8
|
} from '../utils/error-helper';
|
9
9
|
import { findFragmentByPTS } from './fragment-finders';
|
10
10
|
import { HdcpLevel, HdcpLevels } from '../types/level';
|
11
|
-
import {
|
11
|
+
import { Logger } from '../utils/logger';
|
12
12
|
import type Hls from '../hls';
|
13
13
|
import type { RetryConfig } from '../config';
|
14
14
|
import type { NetworkComponentAPI } from '../types/component-api';
|
@@ -50,19 +50,17 @@ type PenalizedRendition = {
|
|
50
50
|
|
51
51
|
type PenalizedRenditions = { [key: number]: PenalizedRendition };
|
52
52
|
|
53
|
-
export default class ErrorController
|
53
|
+
export default class ErrorController
|
54
|
+
extends Logger
|
55
|
+
implements NetworkComponentAPI
|
56
|
+
{
|
54
57
|
private readonly hls: Hls;
|
55
58
|
private playlistError: number = 0;
|
56
59
|
private penalizedRenditions: PenalizedRenditions = {};
|
57
|
-
private log: (msg: any) => void;
|
58
|
-
private warn: (msg: any) => void;
|
59
|
-
private error: (msg: any) => void;
|
60
60
|
|
61
61
|
constructor(hls: Hls) {
|
62
|
+
super('error-controller', hls.logger);
|
62
63
|
this.hls = hls;
|
63
|
-
this.log = logger.log.bind(logger, `[info]:`);
|
64
|
-
this.warn = logger.warn.bind(logger, `[warning]:`);
|
65
|
-
this.error = logger.error.bind(logger, `[error]:`);
|
66
64
|
this.registerListeners();
|
67
65
|
}
|
68
66
|
|