hls.js 1.5.13 → 1.5.14-0.canary.10415
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 +4 -3
- package/dist/hls-demo.js +41 -38
- package/dist/hls-demo.js.map +1 -1
- package/dist/hls.js +4211 -2666
- package/dist/hls.js.d.ts +179 -110
- package/dist/hls.js.map +1 -1
- package/dist/hls.light.js +2841 -1921
- 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 +2569 -1639
- 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 +3572 -2017
- 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 +38 -38
- package/src/config.ts +5 -2
- package/src/controller/abr-controller.ts +39 -25
- package/src/controller/audio-stream-controller.ts +156 -136
- package/src/controller/audio-track-controller.ts +1 -1
- package/src/controller/base-playlist-controller.ts +27 -10
- package/src/controller/base-stream-controller.ts +234 -89
- package/src/controller/buffer-controller.ts +250 -97
- package/src/controller/buffer-operation-queue.ts +16 -19
- package/src/controller/cap-level-controller.ts +3 -2
- package/src/controller/cmcd-controller.ts +51 -14
- package/src/controller/content-steering-controller.ts +29 -15
- package/src/controller/eme-controller.ts +10 -23
- package/src/controller/error-controller.ts +28 -22
- package/src/controller/fps-controller.ts +8 -3
- package/src/controller/fragment-finders.ts +44 -16
- package/src/controller/fragment-tracker.ts +58 -25
- package/src/controller/gap-controller.ts +43 -16
- package/src/controller/id3-track-controller.ts +45 -35
- package/src/controller/latency-controller.ts +18 -13
- package/src/controller/level-controller.ts +37 -19
- package/src/controller/stream-controller.ts +100 -83
- package/src/controller/subtitle-stream-controller.ts +35 -47
- package/src/controller/subtitle-track-controller.ts +5 -3
- package/src/controller/timeline-controller.ts +20 -22
- package/src/crypt/aes-crypto.ts +21 -2
- package/src/crypt/decrypter-aes-mode.ts +4 -0
- package/src/crypt/decrypter.ts +32 -16
- package/src/crypt/fast-aes-key.ts +28 -5
- package/src/demux/audio/aacdemuxer.ts +2 -2
- package/src/demux/audio/ac3-demuxer.ts +4 -3
- package/src/demux/audio/adts.ts +9 -4
- package/src/demux/audio/base-audio-demuxer.ts +16 -14
- package/src/demux/audio/mp3demuxer.ts +4 -3
- package/src/demux/audio/mpegaudio.ts +1 -1
- package/src/demux/mp4demuxer.ts +7 -7
- package/src/demux/sample-aes.ts +2 -0
- package/src/demux/transmuxer-interface.ts +8 -16
- package/src/demux/transmuxer-worker.ts +4 -4
- package/src/demux/transmuxer.ts +16 -3
- package/src/demux/tsdemuxer.ts +75 -38
- package/src/demux/video/avc-video-parser.ts +210 -121
- package/src/demux/video/base-video-parser.ts +135 -2
- package/src/demux/video/exp-golomb.ts +0 -208
- package/src/demux/video/hevc-video-parser.ts +749 -0
- package/src/events.ts +8 -1
- package/src/exports-named.ts +1 -1
- package/src/hls.ts +84 -47
- package/src/loader/date-range.ts +71 -5
- package/src/loader/fragment-loader.ts +23 -21
- package/src/loader/fragment.ts +8 -4
- package/src/loader/key-loader.ts +3 -1
- package/src/loader/level-details.ts +6 -6
- package/src/loader/level-key.ts +10 -9
- package/src/loader/m3u8-parser.ts +138 -144
- package/src/loader/playlist-loader.ts +5 -7
- package/src/remux/mp4-generator.ts +196 -1
- package/src/remux/mp4-remuxer.ts +32 -62
- package/src/remux/passthrough-remuxer.ts +1 -1
- package/src/task-loop.ts +5 -2
- package/src/types/component-api.ts +3 -1
- package/src/types/demuxer.ts +3 -0
- package/src/types/events.ts +19 -6
- package/src/types/fragment-tracker.ts +2 -2
- package/src/types/media-playlist.ts +9 -1
- package/src/types/remuxer.ts +1 -1
- package/src/utils/attr-list.ts +96 -9
- package/src/utils/buffer-helper.ts +12 -31
- package/src/utils/cea-608-parser.ts +1 -3
- package/src/utils/codecs.ts +34 -5
- package/src/utils/encryption-methods-util.ts +21 -0
- package/src/utils/fetch-loader.ts +1 -1
- package/src/utils/hash.ts +10 -0
- package/src/utils/hdr.ts +4 -7
- package/src/utils/imsc1-ttml-parser.ts +1 -1
- package/src/utils/keysystem-util.ts +1 -6
- package/src/utils/level-helper.ts +71 -44
- package/src/utils/logger.ts +58 -23
- package/src/utils/mp4-tools.ts +5 -3
- package/src/utils/rendition-helper.ts +100 -74
- package/src/utils/utf8-utils.ts +18 -0
- package/src/utils/variable-substitution.ts +0 -19
- package/src/utils/webvtt-parser.ts +2 -12
- package/src/demux/id3.ts +0 -411
- package/src/types/general.ts +0 -6
@@ -35,19 +35,13 @@ export class BufferHelper {
|
|
35
35
|
* Return true if `media`'s buffered include `position`
|
36
36
|
*/
|
37
37
|
static isBuffered(media: Bufferable, position: number): boolean {
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
return true;
|
44
|
-
}
|
38
|
+
if (media) {
|
39
|
+
const buffered = BufferHelper.getBuffered(media);
|
40
|
+
for (let i = buffered.length; i--; ) {
|
41
|
+
if (position >= buffered.start(i) && position <= buffered.end(i)) {
|
42
|
+
return true;
|
45
43
|
}
|
46
44
|
}
|
47
|
-
} catch (error) {
|
48
|
-
// this is to catch
|
49
|
-
// InvalidStateError: Failed to read the 'buffered' property from 'SourceBuffer':
|
50
|
-
// This SourceBuffer has been removed from the parent media source
|
51
45
|
}
|
52
46
|
return false;
|
53
47
|
}
|
@@ -57,21 +51,15 @@ export class BufferHelper {
|
|
57
51
|
pos: number,
|
58
52
|
maxHoleDuration: number,
|
59
53
|
): BufferInfo {
|
60
|
-
|
61
|
-
|
62
|
-
|
54
|
+
if (media) {
|
55
|
+
const vbuffered = BufferHelper.getBuffered(media);
|
56
|
+
if (vbuffered.length) {
|
63
57
|
const buffered: BufferTimeRange[] = [];
|
64
|
-
let i
|
65
|
-
for (i = 0; i < vbuffered.length; i++) {
|
58
|
+
for (let i = 0; i < vbuffered.length; i++) {
|
66
59
|
buffered.push({ start: vbuffered.start(i), end: vbuffered.end(i) });
|
67
60
|
}
|
68
|
-
|
69
|
-
return this.bufferedInfo(buffered, pos, maxHoleDuration);
|
61
|
+
return BufferHelper.bufferedInfo(buffered, pos, maxHoleDuration);
|
70
62
|
}
|
71
|
-
} catch (error) {
|
72
|
-
// this is to catch
|
73
|
-
// InvalidStateError: Failed to read the 'buffered' property from 'SourceBuffer':
|
74
|
-
// This SourceBuffer has been removed from the parent media source
|
75
63
|
}
|
76
64
|
return { len: 0, start: pos, end: pos, nextStart: undefined };
|
77
65
|
}
|
@@ -88,14 +76,7 @@ export class BufferHelper {
|
|
88
76
|
} {
|
89
77
|
pos = Math.max(0, pos);
|
90
78
|
// sort on buffer.start/smaller end (IE does not always return sorted buffered range)
|
91
|
-
buffered.sort(
|
92
|
-
const diff = a.start - b.start;
|
93
|
-
if (diff) {
|
94
|
-
return diff;
|
95
|
-
} else {
|
96
|
-
return b.end - a.end;
|
97
|
-
}
|
98
|
-
});
|
79
|
+
buffered.sort((a, b) => a.start - b.start || b.end - a.end);
|
99
80
|
|
100
81
|
let buffered2: BufferTimeRange[] = [];
|
101
82
|
if (maxHoleDuration) {
|
@@ -164,7 +145,7 @@ export class BufferHelper {
|
|
164
145
|
*/
|
165
146
|
static getBuffered(media: Bufferable): TimeRanges {
|
166
147
|
try {
|
167
|
-
return media.buffered;
|
148
|
+
return media.buffered || noopBuffered;
|
168
149
|
} catch (e) {
|
169
150
|
logger.log('failed to get media.buffered', e);
|
170
151
|
return noopBuffered;
|
@@ -1331,9 +1331,7 @@ class Cea608Parser {
|
|
1331
1331
|
if (charCodes) {
|
1332
1332
|
this.logger.log(
|
1333
1333
|
VerboseLevel.DEBUG,
|
1334
|
-
() =>
|
1335
|
-
'Char codes = ' +
|
1336
|
-
numArrayToHexArray(charCodes as number[]).join(','),
|
1334
|
+
() => 'Char codes = ' + numArrayToHexArray(charCodes).join(','),
|
1337
1335
|
);
|
1338
1336
|
}
|
1339
1337
|
return charCodes;
|
package/src/utils/codecs.ts
CHANGED
@@ -147,12 +147,15 @@ function getCodecCompatibleNameLower(
|
|
147
147
|
return CODEC_COMPATIBLE_NAMES[lowerCaseCodec]!;
|
148
148
|
}
|
149
149
|
|
150
|
-
// Idealy fLaC and Opus would be first (spec-compliant) but
|
151
|
-
// some browsers will report that fLaC is supported then fail.
|
152
|
-
// see: https://bugs.chromium.org/p/chromium/issues/detail?id=1422728
|
153
150
|
const codecsToCheck = {
|
151
|
+
// Idealy fLaC and Opus would be first (spec-compliant) but
|
152
|
+
// some browsers will report that fLaC is supported then fail.
|
153
|
+
// see: https://bugs.chromium.org/p/chromium/issues/detail?id=1422728
|
154
154
|
flac: ['flac', 'fLaC', 'FLAC'],
|
155
155
|
opus: ['opus', 'Opus'],
|
156
|
+
// Replace audio codec info if browser does not support mp4a.40.34,
|
157
|
+
// and demuxer can fallback to 'audio/mpeg' or 'audio/mp4;codecs="mp3"'
|
158
|
+
'mp4a.40.34': ['mp3'],
|
156
159
|
}[lowerCaseCodec];
|
157
160
|
|
158
161
|
for (let i = 0; i < codecsToCheck.length; i++) {
|
@@ -165,13 +168,18 @@ function getCodecCompatibleNameLower(
|
|
165
168
|
) {
|
166
169
|
CODEC_COMPATIBLE_NAMES[lowerCaseCodec] = codecsToCheck[i];
|
167
170
|
return codecsToCheck[i];
|
171
|
+
} else if (
|
172
|
+
codecsToCheck[i] === 'mp3' &&
|
173
|
+
getMediaSource(preferManagedMediaSource)?.isTypeSupported('audio/mpeg')
|
174
|
+
) {
|
175
|
+
return '';
|
168
176
|
}
|
169
177
|
}
|
170
178
|
|
171
179
|
return lowerCaseCodec;
|
172
180
|
}
|
173
181
|
|
174
|
-
const AUDIO_CODEC_REGEXP = /flac|opus/i;
|
182
|
+
const AUDIO_CODEC_REGEXP = /flac|opus|mp4a\.40\.34/i;
|
175
183
|
export function getCodecCompatibleName(
|
176
184
|
codec: string,
|
177
185
|
preferManagedMediaSource = true,
|
@@ -185,7 +193,7 @@ export function getCodecCompatibleName(
|
|
185
193
|
}
|
186
194
|
|
187
195
|
export function pickMostCompleteCodecName(
|
188
|
-
parsedCodec: string,
|
196
|
+
parsedCodec: string | undefined,
|
189
197
|
levelCodec: string | undefined,
|
190
198
|
): string | undefined {
|
191
199
|
// Parsing of mp4a codecs strings in mp4-tools from media is incomplete as of d8c6c7a
|
@@ -213,3 +221,24 @@ export function convertAVC1ToAVCOTI(codec: string) {
|
|
213
221
|
}
|
214
222
|
return codecs.join(',');
|
215
223
|
}
|
224
|
+
|
225
|
+
export interface TypeSupported {
|
226
|
+
mpeg: boolean;
|
227
|
+
mp3: boolean;
|
228
|
+
ac3: boolean;
|
229
|
+
}
|
230
|
+
|
231
|
+
export function getM2TSSupportedAudioTypes(
|
232
|
+
preferManagedMediaSource: boolean,
|
233
|
+
): TypeSupported {
|
234
|
+
const MediaSource = getMediaSource(preferManagedMediaSource) || {
|
235
|
+
isTypeSupported: () => false,
|
236
|
+
};
|
237
|
+
return {
|
238
|
+
mpeg: MediaSource.isTypeSupported('audio/mpeg'),
|
239
|
+
mp3: MediaSource.isTypeSupported('audio/mp4; codecs="mp3"'),
|
240
|
+
ac3: __USE_M2TS_ADVANCED_CODECS__
|
241
|
+
? MediaSource.isTypeSupported('audio/mp4; codecs="ac-3"')
|
242
|
+
: false,
|
243
|
+
};
|
244
|
+
}
|
@@ -0,0 +1,21 @@
|
|
1
|
+
import { DecrypterAesMode } from '../crypt/decrypter-aes-mode';
|
2
|
+
|
3
|
+
export function isFullSegmentEncryption(method: string): boolean {
|
4
|
+
return (
|
5
|
+
method === 'AES-128' || method === 'AES-256' || method === 'AES-256-CTR'
|
6
|
+
);
|
7
|
+
}
|
8
|
+
|
9
|
+
export function getAesModeFromFullSegmentMethod(
|
10
|
+
method: string,
|
11
|
+
): DecrypterAesMode {
|
12
|
+
switch (method) {
|
13
|
+
case 'AES-128':
|
14
|
+
case 'AES-256':
|
15
|
+
return DecrypterAesMode.cbc;
|
16
|
+
case 'AES-256-CTR':
|
17
|
+
return DecrypterAesMode.ctr;
|
18
|
+
default:
|
19
|
+
throw new Error(`invalid full segment method ${method}`);
|
20
|
+
}
|
21
|
+
}
|
package/src/utils/hdr.ts
CHANGED
@@ -49,16 +49,13 @@ export function getVideoSelectionOptions(
|
|
49
49
|
if (videoPreference) {
|
50
50
|
allowedVideoRanges =
|
51
51
|
videoPreference.allowedVideoRanges || VideoRangeValues.slice(0);
|
52
|
+
const allowAutoPreferHDR =
|
53
|
+
allowedVideoRanges.join('') !== 'SDR' && !videoPreference.videoCodec;
|
52
54
|
preferHDR =
|
53
55
|
videoPreference.preferHDR !== undefined
|
54
56
|
? videoPreference.preferHDR
|
55
|
-
: isHdrSupported();
|
56
|
-
|
57
|
-
if (preferHDR) {
|
58
|
-
allowedVideoRanges = allowedVideoRanges.filter(
|
59
|
-
(range: VideoRange) => range !== 'SDR',
|
60
|
-
);
|
61
|
-
} else {
|
57
|
+
: allowAutoPreferHDR && isHdrSupported();
|
58
|
+
if (!preferHDR) {
|
62
59
|
allowedVideoRanges = ['SDR'];
|
63
60
|
}
|
64
61
|
}
|
@@ -1,7 +1,7 @@
|
|
1
1
|
import { findBox } from './mp4-tools';
|
2
2
|
import { parseTimeStamp } from './vttparser';
|
3
3
|
import VTTCue from './vttcue';
|
4
|
-
import { utf8ArrayToStr } from '
|
4
|
+
import { utf8ArrayToStr } from '@svta/common-media-library/utils/utf8ArrayToStr';
|
5
5
|
import {
|
6
6
|
RationalTimestamp,
|
7
7
|
toTimescaleFromScale,
|
@@ -1,4 +1,5 @@
|
|
1
1
|
import { base64Decode } from './numeric-encoding-utils';
|
2
|
+
import { strToUtf8array } from './utf8-utils';
|
2
3
|
|
3
4
|
function getKeyIdBytes(str: string): Uint8Array {
|
4
5
|
const keyIdbytes = strToUtf8array(str).subarray(0, 16);
|
@@ -40,9 +41,3 @@ export function convertDataUriToArrayBytes(uri: string): Uint8Array | null {
|
|
40
41
|
}
|
41
42
|
return keydata;
|
42
43
|
}
|
43
|
-
|
44
|
-
export function strToUtf8array(str: string): Uint8Array {
|
45
|
-
return Uint8Array.from(unescape(encodeURIComponent(str)), (c) =>
|
46
|
-
c.charCodeAt(0),
|
47
|
-
);
|
48
|
-
}
|
@@ -3,16 +3,17 @@
|
|
3
3
|
*/
|
4
4
|
|
5
5
|
import { logger } from './logger';
|
6
|
-
import { Fragment, Part } from '../loader/fragment';
|
7
|
-
import { LevelDetails } from '../loader/level-details';
|
8
|
-
import type { Level } from '../types/level';
|
9
6
|
import { DateRange } from '../loader/date-range';
|
7
|
+
import { assignProgramDateTime, mapDateRanges } from '../loader/m3u8-parser';
|
8
|
+
import type { Fragment, MediaFragment, Part } from '../loader/fragment';
|
9
|
+
import type { LevelDetails } from '../loader/level-details';
|
10
|
+
import type { Level } from '../types/level';
|
10
11
|
|
11
12
|
type FragmentIntersection = (oldFrag: Fragment, newFrag: Fragment) => void;
|
12
13
|
type PartIntersection = (oldPart: Part, newPart: Part) => void;
|
13
14
|
|
14
15
|
export function updatePTS(
|
15
|
-
fragments:
|
16
|
+
fragments: MediaFragment[],
|
16
17
|
fromIdx: number,
|
17
18
|
toIdx: number,
|
18
19
|
): void {
|
@@ -21,7 +22,7 @@ export function updatePTS(
|
|
21
22
|
updateFromToPTS(fragFrom, fragTo);
|
22
23
|
}
|
23
24
|
|
24
|
-
function updateFromToPTS(fragFrom:
|
25
|
+
function updateFromToPTS(fragFrom: MediaFragment, fragTo: MediaFragment) {
|
25
26
|
const fragToPTS = fragTo.startPTS as number;
|
26
27
|
// if we know startPTS[toIdx]
|
27
28
|
if (Number.isFinite(fragToPTS)) {
|
@@ -55,7 +56,7 @@ function updateFromToPTS(fragFrom: Fragment, fragTo: Fragment) {
|
|
55
56
|
|
56
57
|
export function updateFragPTSDTS(
|
57
58
|
details: LevelDetails | undefined,
|
58
|
-
frag:
|
59
|
+
frag: MediaFragment,
|
59
60
|
startPTS: number,
|
60
61
|
endPTS: number,
|
61
62
|
startDTS: number,
|
@@ -82,11 +83,11 @@ export function updateFragPTSDTS(
|
|
82
83
|
|
83
84
|
maxStartPTS = Math.max(startPTS, fragStartPts);
|
84
85
|
startPTS = Math.min(startPTS, fragStartPts);
|
85
|
-
startDTS = Math.min(startDTS, frag.startDTS);
|
86
|
+
startDTS = Math.min(startDTS, frag.startDTS as number);
|
86
87
|
|
87
88
|
minEndPTS = Math.min(endPTS, fragEndPts);
|
88
89
|
endPTS = Math.max(endPTS, fragEndPts);
|
89
|
-
endDTS = Math.max(endDTS, frag.endDTS);
|
90
|
+
endDTS = Math.max(endDTS, frag.endDTS as number);
|
90
91
|
}
|
91
92
|
|
92
93
|
const drift = startPTS - frag.start;
|
@@ -101,7 +102,7 @@ export function updateFragPTSDTS(
|
|
101
102
|
frag.minEndPTS = minEndPTS;
|
102
103
|
frag.endDTS = endDTS;
|
103
104
|
|
104
|
-
const sn = frag.sn
|
105
|
+
const sn = frag.sn;
|
105
106
|
// exit if sn out of range
|
106
107
|
if (!details || sn < details.startSN || sn > details.endSN) {
|
107
108
|
return 0;
|
@@ -196,10 +197,10 @@ export function mergeDetails(
|
|
196
197
|
},
|
197
198
|
);
|
198
199
|
|
200
|
+
const fragmentsToCheck = newDetails.fragmentHint
|
201
|
+
? newDetails.fragments.concat(newDetails.fragmentHint)
|
202
|
+
: newDetails.fragments;
|
199
203
|
if (currentInitSegment) {
|
200
|
-
const fragmentsToCheck = newDetails.fragmentHint
|
201
|
-
? newDetails.fragments.concat(newDetails.fragmentHint)
|
202
|
-
: newDetails.fragments;
|
203
204
|
fragmentsToCheck.forEach((frag) => {
|
204
205
|
if (
|
205
206
|
frag &&
|
@@ -220,14 +221,30 @@ export function mergeDetails(
|
|
220
221
|
for (let i = newDetails.skippedSegments; i--; ) {
|
221
222
|
newDetails.fragments.shift();
|
222
223
|
}
|
223
|
-
newDetails.startSN = newDetails.fragments[0].sn
|
224
|
+
newDetails.startSN = newDetails.fragments[0].sn;
|
224
225
|
newDetails.startCC = newDetails.fragments[0].cc;
|
225
|
-
} else
|
226
|
-
newDetails.
|
227
|
-
|
228
|
-
|
229
|
-
|
226
|
+
} else {
|
227
|
+
if (newDetails.canSkipDateRanges) {
|
228
|
+
newDetails.dateRanges = mergeDateRanges(
|
229
|
+
oldDetails.dateRanges,
|
230
|
+
newDetails,
|
231
|
+
);
|
232
|
+
}
|
233
|
+
const programDateTimes = oldDetails.fragments.filter(
|
234
|
+
(frag) => frag.rawProgramDateTime,
|
230
235
|
);
|
236
|
+
if (oldDetails.hasProgramDateTime && !newDetails.hasProgramDateTime) {
|
237
|
+
for (let i = 1; i < fragmentsToCheck.length; i++) {
|
238
|
+
if (fragmentsToCheck[i].programDateTime === null) {
|
239
|
+
assignProgramDateTime(
|
240
|
+
fragmentsToCheck[i],
|
241
|
+
fragmentsToCheck[i - 1],
|
242
|
+
programDateTimes,
|
243
|
+
);
|
244
|
+
}
|
245
|
+
}
|
246
|
+
}
|
247
|
+
mapDateRanges(programDateTimes, newDetails);
|
231
248
|
}
|
232
249
|
}
|
233
250
|
|
@@ -293,27 +310,38 @@ export function mergeDetails(
|
|
293
310
|
|
294
311
|
function mergeDateRanges(
|
295
312
|
oldDateRanges: Record<string, DateRange>,
|
296
|
-
|
297
|
-
recentlyRemovedDateranges: string[] | undefined,
|
313
|
+
newDetails: LevelDetails,
|
298
314
|
): Record<string, DateRange> {
|
315
|
+
const { dateRanges: deltaDateRanges, recentlyRemovedDateranges } = newDetails;
|
299
316
|
const dateRanges = Object.assign({}, oldDateRanges);
|
300
317
|
if (recentlyRemovedDateranges) {
|
301
318
|
recentlyRemovedDateranges.forEach((id) => {
|
302
319
|
delete dateRanges[id];
|
303
320
|
});
|
304
321
|
}
|
305
|
-
Object.keys(
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
)}"`,
|
322
|
+
const mergeIds = Object.keys(dateRanges);
|
323
|
+
const mergeCount = mergeIds.length;
|
324
|
+
if (mergeCount) {
|
325
|
+
Object.keys(deltaDateRanges).forEach((id) => {
|
326
|
+
const mergedDateRange = dateRanges[id];
|
327
|
+
const dateRange = new DateRange(
|
328
|
+
deltaDateRanges[id].attr,
|
329
|
+
mergedDateRange,
|
314
330
|
);
|
315
|
-
|
316
|
-
|
331
|
+
if (dateRange.isValid) {
|
332
|
+
dateRanges[id] = dateRange;
|
333
|
+
if (!mergedDateRange) {
|
334
|
+
dateRange.tagOrder += mergeCount;
|
335
|
+
}
|
336
|
+
} else {
|
337
|
+
logger.warn(
|
338
|
+
`Ignoring invalid Playlist Delta Update DATERANGE tag: "${JSON.stringify(
|
339
|
+
deltaDateRanges[id].attr,
|
340
|
+
)}"`,
|
341
|
+
);
|
342
|
+
}
|
343
|
+
});
|
344
|
+
}
|
317
345
|
return dateRanges;
|
318
346
|
}
|
319
347
|
|
@@ -366,7 +394,7 @@ export function mapFragmentIntersection(
|
|
366
394
|
for (let i = start; i <= end; i++) {
|
367
395
|
const oldFrag = oldFrags[delta + i];
|
368
396
|
let newFrag = newFrags[i];
|
369
|
-
if (skippedSegments && !newFrag &&
|
397
|
+
if (skippedSegments && !newFrag && oldFrag) {
|
370
398
|
// Fill in skipped segments in delta playlist
|
371
399
|
newFrag = newDetails.fragments[i] = oldFrag;
|
372
400
|
}
|
@@ -433,38 +461,37 @@ export function computeReloadInterval(
|
|
433
461
|
}
|
434
462
|
|
435
463
|
export function getFragmentWithSN(
|
436
|
-
|
464
|
+
details: LevelDetails | undefined,
|
437
465
|
sn: number,
|
438
466
|
fragCurrent: Fragment | null,
|
439
|
-
):
|
440
|
-
if (!
|
467
|
+
): MediaFragment | null {
|
468
|
+
if (!details) {
|
441
469
|
return null;
|
442
470
|
}
|
443
|
-
|
444
|
-
|
445
|
-
levelDetails.fragments[sn - levelDetails.startSN];
|
471
|
+
let fragment: MediaFragment | undefined =
|
472
|
+
details.fragments[sn - details.startSN];
|
446
473
|
if (fragment) {
|
447
474
|
return fragment;
|
448
475
|
}
|
449
|
-
fragment =
|
476
|
+
fragment = details.fragmentHint;
|
450
477
|
if (fragment && fragment.sn === sn) {
|
451
478
|
return fragment;
|
452
479
|
}
|
453
|
-
if (sn <
|
454
|
-
return fragCurrent;
|
480
|
+
if (sn < details.startSN && fragCurrent && fragCurrent.sn === sn) {
|
481
|
+
return fragCurrent as MediaFragment;
|
455
482
|
}
|
456
483
|
return null;
|
457
484
|
}
|
458
485
|
|
459
486
|
export function getPartWith(
|
460
|
-
|
487
|
+
details: LevelDetails | undefined,
|
461
488
|
sn: number,
|
462
489
|
partIndex: number,
|
463
490
|
): Part | null {
|
464
|
-
if (!
|
491
|
+
if (!details) {
|
465
492
|
return null;
|
466
493
|
}
|
467
|
-
return findPart(
|
494
|
+
return findPart(details.partList, sn, partIndex);
|
468
495
|
}
|
469
496
|
|
470
497
|
export function findPart(
|
package/src/utils/logger.ts
CHANGED
@@ -11,6 +11,25 @@ export interface ILogger {
|
|
11
11
|
error: ILogFunction;
|
12
12
|
}
|
13
13
|
|
14
|
+
export class Logger implements ILogger {
|
15
|
+
trace: ILogFunction;
|
16
|
+
debug: ILogFunction;
|
17
|
+
log: ILogFunction;
|
18
|
+
warn: ILogFunction;
|
19
|
+
info: ILogFunction;
|
20
|
+
error: ILogFunction;
|
21
|
+
|
22
|
+
constructor(label: string, logger: ILogger) {
|
23
|
+
const lb = `[${label}]:`;
|
24
|
+
this.trace = noop;
|
25
|
+
this.debug = logger.debug.bind(null, lb);
|
26
|
+
this.log = logger.log.bind(null, lb);
|
27
|
+
this.warn = logger.warn.bind(null, lb);
|
28
|
+
this.info = logger.info.bind(null, lb);
|
29
|
+
this.error = logger.error.bind(null, lb);
|
30
|
+
}
|
31
|
+
}
|
32
|
+
|
14
33
|
const noop: ILogFunction = function () {};
|
15
34
|
|
16
35
|
const fakeLogger: ILogger = {
|
@@ -22,7 +41,9 @@ const fakeLogger: ILogger = {
|
|
22
41
|
error: noop,
|
23
42
|
};
|
24
43
|
|
25
|
-
|
44
|
+
function createLogger() {
|
45
|
+
return Object.assign({}, fakeLogger);
|
46
|
+
}
|
26
47
|
|
27
48
|
// let lastCallTime;
|
28
49
|
// function formatMsgWithTimeInfo(type, msg) {
|
@@ -33,33 +54,37 @@ let exportedLogger: ILogger = fakeLogger;
|
|
33
54
|
// return msg;
|
34
55
|
// }
|
35
56
|
|
36
|
-
function consolePrintFn(type: string): ILogFunction {
|
57
|
+
function consolePrintFn(type: string, id: string | undefined): ILogFunction {
|
37
58
|
const func: ILogFunction = self.console[type];
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
return noop;
|
59
|
+
return func
|
60
|
+
? func.bind(self.console, `${id ? '[' + id + '] ' : ''}[${type}] >`)
|
61
|
+
: noop;
|
42
62
|
}
|
43
63
|
|
44
|
-
function
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
});
|
64
|
+
function getLoggerFn(
|
65
|
+
key: string,
|
66
|
+
debugConfig: boolean | Partial<ILogger>,
|
67
|
+
id?: string,
|
68
|
+
): ILogFunction {
|
69
|
+
return debugConfig[key]
|
70
|
+
? debugConfig[key].bind(debugConfig)
|
71
|
+
: consolePrintFn(key, id);
|
53
72
|
}
|
54
73
|
|
55
|
-
|
74
|
+
const exportedLogger: ILogger = createLogger();
|
75
|
+
|
76
|
+
export function enableLogs(
|
77
|
+
debugConfig: boolean | ILogger,
|
78
|
+
context: string,
|
79
|
+
id?: string | undefined,
|
80
|
+
): ILogger {
|
56
81
|
// check that console is available
|
82
|
+
const newLogger = createLogger();
|
57
83
|
if (
|
58
84
|
(typeof console === 'object' && debugConfig === true) ||
|
59
85
|
typeof debugConfig === 'object'
|
60
86
|
) {
|
61
|
-
|
62
|
-
debugConfig,
|
87
|
+
const keys: (keyof ILogger)[] = [
|
63
88
|
// Remove out from list here to hard-disable a log-level
|
64
89
|
// 'trace',
|
65
90
|
'debug',
|
@@ -67,19 +92,29 @@ export function enableLogs(debugConfig: boolean | ILogger, id: string): void {
|
|
67
92
|
'info',
|
68
93
|
'warn',
|
69
94
|
'error',
|
70
|
-
|
95
|
+
];
|
96
|
+
keys.forEach((key) => {
|
97
|
+
newLogger[key] = getLoggerFn(key, debugConfig, id);
|
98
|
+
});
|
71
99
|
// Some browsers don't allow to use bind on console object anyway
|
72
100
|
// fallback to default if needed
|
73
101
|
try {
|
74
|
-
|
75
|
-
`Debug logs enabled for "${
|
102
|
+
newLogger.log(
|
103
|
+
`Debug logs enabled for "${context}" in hls.js version ${__VERSION__}`,
|
76
104
|
);
|
77
105
|
} catch (e) {
|
78
|
-
|
106
|
+
/* log fn threw an exception. All logger methods are no-ops. */
|
107
|
+
return createLogger();
|
79
108
|
}
|
109
|
+
// global exported logger uses the same functions as new logger without `id`
|
110
|
+
keys.forEach((key) => {
|
111
|
+
exportedLogger[key] = getLoggerFn(key, debugConfig);
|
112
|
+
});
|
80
113
|
} else {
|
81
|
-
|
114
|
+
// Reset global exported logger
|
115
|
+
Object.assign(exportedLogger, newLogger);
|
82
116
|
}
|
117
|
+
return newLogger;
|
83
118
|
}
|
84
119
|
|
85
120
|
export const logger: ILogger = exportedLogger;
|
package/src/utils/mp4-tools.ts
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
import { ElementaryStreamTypes } from '../loader/fragment';
|
2
2
|
import { sliceUint8 } from './typed-array';
|
3
|
-
import { utf8ArrayToStr } from '
|
3
|
+
import { utf8ArrayToStr } from '@svta/common-media-library/utils/utf8ArrayToStr';
|
4
4
|
import { logger } from '../utils/logger';
|
5
5
|
import Hex from './hex';
|
6
6
|
import type { PassthroughTrack, UserdataSample } from '../types/demuxer';
|
@@ -327,7 +327,7 @@ function parseStsd(stsd: Uint8Array): { codec: string; encrypted: boolean } {
|
|
327
327
|
case 'mp4a': {
|
328
328
|
const codecBox = findBox(sampleEntries, [fourCC])[0];
|
329
329
|
const esdsBox = findBox(codecBox.subarray(28), ['esds'])[0];
|
330
|
-
if (esdsBox && esdsBox.length >
|
330
|
+
if (esdsBox && esdsBox.length > 7) {
|
331
331
|
let i = 4;
|
332
332
|
// ES Descriptor tag
|
333
333
|
if (esdsBox[i++] !== 0x03) {
|
@@ -478,7 +478,9 @@ function parseStsd(stsd: Uint8Array): { codec: string; encrypted: boolean } {
|
|
478
478
|
|
479
479
|
function skipBERInteger(bytes: Uint8Array, i: number): number {
|
480
480
|
const limit = i + 5;
|
481
|
-
while (bytes[i++] & 0x80 && i < limit) {
|
481
|
+
while (bytes[i++] & 0x80 && i < limit) {
|
482
|
+
/* do nothing */
|
483
|
+
}
|
482
484
|
return i;
|
483
485
|
}
|
484
486
|
|