hls.js 1.5.13-0.canary.10411 → 1.5.13
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 +3 -4
- package/dist/hls-demo.js +38 -41
- package/dist/hls-demo.js.map +1 -1
- package/dist/hls.js +2702 -4247
- package/dist/hls.js.d.ts +110 -179
- package/dist/hls.js.map +1 -1
- package/dist/hls.light.js +1994 -2914
- 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 +3458 -4388
- 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 +4504 -6059
- 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 +2 -5
- package/src/controller/abr-controller.ts +25 -39
- package/src/controller/audio-stream-controller.ts +136 -156
- package/src/controller/audio-track-controller.ts +1 -1
- package/src/controller/base-playlist-controller.ts +10 -27
- package/src/controller/base-stream-controller.ts +91 -235
- package/src/controller/buffer-controller.ts +97 -250
- package/src/controller/buffer-operation-queue.ts +19 -16
- package/src/controller/cap-level-controller.ts +2 -3
- package/src/controller/cmcd-controller.ts +14 -51
- package/src/controller/content-steering-controller.ts +15 -29
- package/src/controller/eme-controller.ts +23 -10
- package/src/controller/error-controller.ts +22 -28
- package/src/controller/fps-controller.ts +3 -8
- package/src/controller/fragment-finders.ts +16 -44
- package/src/controller/fragment-tracker.ts +25 -58
- package/src/controller/gap-controller.ts +16 -43
- package/src/controller/id3-track-controller.ts +35 -45
- package/src/controller/latency-controller.ts +13 -18
- package/src/controller/level-controller.ts +19 -37
- package/src/controller/stream-controller.ts +83 -100
- package/src/controller/subtitle-stream-controller.ts +47 -35
- package/src/controller/subtitle-track-controller.ts +3 -5
- package/src/controller/timeline-controller.ts +22 -20
- package/src/crypt/aes-crypto.ts +2 -21
- package/src/crypt/decrypter.ts +16 -32
- package/src/crypt/fast-aes-key.ts +5 -28
- package/src/demux/audio/aacdemuxer.ts +2 -2
- package/src/demux/audio/ac3-demuxer.ts +3 -4
- package/src/demux/audio/adts.ts +4 -9
- package/src/demux/audio/base-audio-demuxer.ts +14 -16
- package/src/demux/audio/mp3demuxer.ts +3 -4
- package/src/demux/audio/mpegaudio.ts +1 -1
- package/src/demux/id3.ts +411 -0
- package/src/demux/mp4demuxer.ts +7 -7
- package/src/demux/sample-aes.ts +0 -2
- package/src/demux/transmuxer-interface.ts +16 -8
- package/src/demux/transmuxer-worker.ts +4 -4
- package/src/demux/transmuxer.ts +3 -16
- package/src/demux/tsdemuxer.ts +38 -75
- package/src/demux/video/avc-video-parser.ts +121 -210
- package/src/demux/video/base-video-parser.ts +2 -135
- package/src/demux/video/exp-golomb.ts +208 -0
- package/src/events.ts +1 -8
- package/src/exports-named.ts +1 -1
- package/src/hls.ts +47 -84
- package/src/loader/date-range.ts +5 -71
- package/src/loader/fragment-loader.ts +21 -23
- package/src/loader/fragment.ts +4 -8
- package/src/loader/key-loader.ts +1 -3
- package/src/loader/level-details.ts +6 -6
- package/src/loader/level-key.ts +9 -10
- package/src/loader/m3u8-parser.ts +144 -138
- package/src/loader/playlist-loader.ts +7 -5
- package/src/remux/mp4-generator.ts +1 -196
- package/src/remux/mp4-remuxer.ts +62 -32
- package/src/remux/passthrough-remuxer.ts +1 -1
- package/src/task-loop.ts +2 -5
- package/src/types/component-api.ts +1 -3
- package/src/types/demuxer.ts +0 -3
- package/src/types/events.ts +6 -19
- package/src/types/fragment-tracker.ts +2 -2
- package/src/types/general.ts +6 -0
- package/src/types/media-playlist.ts +1 -9
- package/src/types/remuxer.ts +1 -1
- package/src/utils/attr-list.ts +9 -96
- package/src/utils/buffer-helper.ts +31 -12
- package/src/utils/cea-608-parser.ts +3 -1
- package/src/utils/codecs.ts +5 -34
- package/src/utils/fetch-loader.ts +1 -1
- package/src/utils/hdr.ts +7 -4
- package/src/utils/imsc1-ttml-parser.ts +1 -1
- package/src/utils/keysystem-util.ts +6 -1
- package/src/utils/level-helper.ts +44 -71
- package/src/utils/logger.ts +23 -58
- package/src/utils/mp4-tools.ts +3 -5
- package/src/utils/rendition-helper.ts +74 -100
- package/src/utils/variable-substitution.ts +19 -0
- package/src/utils/webvtt-parser.ts +12 -2
- package/src/crypt/decrypter-aes-mode.ts +0 -4
- package/src/demux/video/hevc-video-parser.ts +0 -749
- package/src/utils/encryption-methods-util.ts +0 -21
- package/src/utils/hash.ts +0 -10
- package/src/utils/utf8-utils.ts +0 -18
package/src/utils/attr-list.ts
CHANGED
@@ -1,8 +1,3 @@
|
|
1
|
-
import type { LevelDetails } from '../loader/level-details';
|
2
|
-
import type { ParsedMultivariantPlaylist } from '../loader/m3u8-parser';
|
3
|
-
import { logger } from './logger';
|
4
|
-
import { substituteVariables } from './variable-substitution';
|
5
|
-
|
6
1
|
const DECIMAL_RESOLUTION_REGEX = /^(\d+)x(\d+)$/;
|
7
2
|
const ATTR_LIST_REGEX = /(.+?)=(".*?"|.*?)(?:,|$)/g;
|
8
3
|
|
@@ -10,15 +5,9 @@ const ATTR_LIST_REGEX = /(.+?)=(".*?"|.*?)(?:,|$)/g;
|
|
10
5
|
export class AttrList {
|
11
6
|
[key: string]: any;
|
12
7
|
|
13
|
-
constructor(
|
14
|
-
attrs: string | Record<string, any>,
|
15
|
-
parsed?: Pick<
|
16
|
-
ParsedMultivariantPlaylist | LevelDetails,
|
17
|
-
'variableList' | 'hasVariableRefs' | 'playlistParsingError'
|
18
|
-
>,
|
19
|
-
) {
|
8
|
+
constructor(attrs: string | Record<string, any>) {
|
20
9
|
if (typeof attrs === 'string') {
|
21
|
-
attrs = AttrList.parseAttrList(attrs
|
10
|
+
attrs = AttrList.parseAttrList(attrs);
|
22
11
|
}
|
23
12
|
Object.assign(this, attrs);
|
24
13
|
}
|
@@ -74,20 +63,6 @@ export class AttrList {
|
|
74
63
|
return this[attrName];
|
75
64
|
}
|
76
65
|
|
77
|
-
enumeratedStringList<T extends { [key: string]: boolean }>(
|
78
|
-
attrName: string,
|
79
|
-
dict: T,
|
80
|
-
): { [key in keyof T]: boolean } {
|
81
|
-
const attrValue = this[attrName];
|
82
|
-
return (attrValue ? attrValue.split(/[ ,]+/) : []).reduce(
|
83
|
-
(result: { [key in keyof T]: boolean }, identifier: string) => {
|
84
|
-
result[identifier.toLowerCase() as keyof T] = true;
|
85
|
-
return result;
|
86
|
-
},
|
87
|
-
dict,
|
88
|
-
);
|
89
|
-
}
|
90
|
-
|
91
66
|
bool(attrName: string): boolean {
|
92
67
|
return this[attrName] === 'YES';
|
93
68
|
}
|
@@ -109,83 +84,21 @@ export class AttrList {
|
|
109
84
|
};
|
110
85
|
}
|
111
86
|
|
112
|
-
static parseAttrList(
|
113
|
-
|
114
|
-
parsed?: Pick<
|
115
|
-
ParsedMultivariantPlaylist | LevelDetails,
|
116
|
-
'variableList' | 'hasVariableRefs' | 'playlistParsingError'
|
117
|
-
>,
|
118
|
-
): Record<string, string> {
|
119
|
-
let match: RegExpExecArray | null;
|
87
|
+
static parseAttrList(input: string): Record<string, any> {
|
88
|
+
let match;
|
120
89
|
const attrs = {};
|
121
90
|
const quote = '"';
|
122
91
|
ATTR_LIST_REGEX.lastIndex = 0;
|
123
92
|
while ((match = ATTR_LIST_REGEX.exec(input)) !== null) {
|
124
|
-
const name = match[1].trim();
|
125
93
|
let value = match[2];
|
126
|
-
|
94
|
+
|
95
|
+
if (
|
127
96
|
value.indexOf(quote) === 0 &&
|
128
|
-
value.lastIndexOf(quote) === value.length - 1
|
129
|
-
|
130
|
-
if (quotedString) {
|
97
|
+
value.lastIndexOf(quote) === value.length - 1
|
98
|
+
) {
|
131
99
|
value = value.slice(1, -1);
|
132
|
-
} else {
|
133
|
-
switch (name) {
|
134
|
-
case 'IV':
|
135
|
-
case 'SCTE35-CMD':
|
136
|
-
case 'SCTE35-IN':
|
137
|
-
case 'SCTE35-OUT':
|
138
|
-
hexadecimalSequence = true;
|
139
|
-
}
|
140
|
-
}
|
141
|
-
if (parsed && (quotedString || hexadecimalSequence)) {
|
142
|
-
if (__USE_VARIABLE_SUBSTITUTION__) {
|
143
|
-
value = substituteVariables(parsed, value);
|
144
|
-
}
|
145
|
-
} else if (!hexadecimalSequence && !quotedString) {
|
146
|
-
switch (name) {
|
147
|
-
case 'CLOSED-CAPTIONS':
|
148
|
-
if (value === 'NONE') {
|
149
|
-
break;
|
150
|
-
}
|
151
|
-
// falls through
|
152
|
-
case 'ALLOWED-CPC':
|
153
|
-
case 'CLASS':
|
154
|
-
case 'ASSOC-LANGUAGE':
|
155
|
-
case 'AUDIO':
|
156
|
-
case 'BYTERANGE':
|
157
|
-
case 'CHANNELS':
|
158
|
-
case 'CHARACTERISTICS':
|
159
|
-
case 'CODECS':
|
160
|
-
case 'DATA-ID':
|
161
|
-
case 'END-DATE':
|
162
|
-
case 'GROUP-ID':
|
163
|
-
case 'ID':
|
164
|
-
case 'IMPORT':
|
165
|
-
case 'INSTREAM-ID':
|
166
|
-
case 'KEYFORMAT':
|
167
|
-
case 'KEYFORMATVERSIONS':
|
168
|
-
case 'LANGUAGE':
|
169
|
-
case 'NAME':
|
170
|
-
case 'PATHWAY-ID':
|
171
|
-
case 'QUERYPARAM':
|
172
|
-
case 'RECENTLY-REMOVED-DATERANGES':
|
173
|
-
case 'SERVER-URI':
|
174
|
-
case 'STABLE-RENDITION-ID':
|
175
|
-
case 'STABLE-VARIANT-ID':
|
176
|
-
case 'START-DATE':
|
177
|
-
case 'SUBTITLES':
|
178
|
-
case 'SUPPLEMENTAL-CODECS':
|
179
|
-
case 'URI':
|
180
|
-
case 'VALUE':
|
181
|
-
case 'VIDEO':
|
182
|
-
case 'X-ASSET-LIST':
|
183
|
-
case 'X-ASSET-URI':
|
184
|
-
// Since we are not checking tag:attribute combination, just warn rather than ignoring attribute
|
185
|
-
logger.warn(`${input}: attribute ${name} is missing quotes`);
|
186
|
-
// continue;
|
187
|
-
}
|
188
100
|
}
|
101
|
+
const name = match[1].trim();
|
189
102
|
attrs[name] = value;
|
190
103
|
}
|
191
104
|
return attrs;
|
@@ -35,13 +35,19 @@ 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
|
-
|
38
|
+
try {
|
39
|
+
if (media) {
|
40
|
+
const buffered = BufferHelper.getBuffered(media);
|
41
|
+
for (let i = 0; i < buffered.length; i++) {
|
42
|
+
if (position >= buffered.start(i) && position <= buffered.end(i)) {
|
43
|
+
return true;
|
44
|
+
}
|
43
45
|
}
|
44
46
|
}
|
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
|
45
51
|
}
|
46
52
|
return false;
|
47
53
|
}
|
@@ -51,15 +57,21 @@ export class BufferHelper {
|
|
51
57
|
pos: number,
|
52
58
|
maxHoleDuration: number,
|
53
59
|
): BufferInfo {
|
54
|
-
|
55
|
-
|
56
|
-
|
60
|
+
try {
|
61
|
+
if (media) {
|
62
|
+
const vbuffered = BufferHelper.getBuffered(media);
|
57
63
|
const buffered: BufferTimeRange[] = [];
|
58
|
-
|
64
|
+
let i: number;
|
65
|
+
for (i = 0; i < vbuffered.length; i++) {
|
59
66
|
buffered.push({ start: vbuffered.start(i), end: vbuffered.end(i) });
|
60
67
|
}
|
61
|
-
|
68
|
+
|
69
|
+
return this.bufferedInfo(buffered, pos, maxHoleDuration);
|
62
70
|
}
|
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
|
63
75
|
}
|
64
76
|
return { len: 0, start: pos, end: pos, nextStart: undefined };
|
65
77
|
}
|
@@ -76,7 +88,14 @@ export class BufferHelper {
|
|
76
88
|
} {
|
77
89
|
pos = Math.max(0, pos);
|
78
90
|
// sort on buffer.start/smaller end (IE does not always return sorted buffered range)
|
79
|
-
buffered.sort((a, b)
|
91
|
+
buffered.sort(function (a, b) {
|
92
|
+
const diff = a.start - b.start;
|
93
|
+
if (diff) {
|
94
|
+
return diff;
|
95
|
+
} else {
|
96
|
+
return b.end - a.end;
|
97
|
+
}
|
98
|
+
});
|
80
99
|
|
81
100
|
let buffered2: BufferTimeRange[] = [];
|
82
101
|
if (maxHoleDuration) {
|
@@ -145,7 +164,7 @@ export class BufferHelper {
|
|
145
164
|
*/
|
146
165
|
static getBuffered(media: Bufferable): TimeRanges {
|
147
166
|
try {
|
148
|
-
return media.buffered
|
167
|
+
return media.buffered;
|
149
168
|
} catch (e) {
|
150
169
|
logger.log('failed to get media.buffered', e);
|
151
170
|
return noopBuffered;
|
@@ -1331,7 +1331,9 @@ class Cea608Parser {
|
|
1331
1331
|
if (charCodes) {
|
1332
1332
|
this.logger.log(
|
1333
1333
|
VerboseLevel.DEBUG,
|
1334
|
-
() =>
|
1334
|
+
() =>
|
1335
|
+
'Char codes = ' +
|
1336
|
+
numArrayToHexArray(charCodes as number[]).join(','),
|
1335
1337
|
);
|
1336
1338
|
}
|
1337
1339
|
return charCodes;
|
package/src/utils/codecs.ts
CHANGED
@@ -147,15 +147,12 @@ 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
|
150
153
|
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'],
|
159
156
|
}[lowerCaseCodec];
|
160
157
|
|
161
158
|
for (let i = 0; i < codecsToCheck.length; i++) {
|
@@ -168,18 +165,13 @@ function getCodecCompatibleNameLower(
|
|
168
165
|
) {
|
169
166
|
CODEC_COMPATIBLE_NAMES[lowerCaseCodec] = codecsToCheck[i];
|
170
167
|
return codecsToCheck[i];
|
171
|
-
} else if (
|
172
|
-
codecsToCheck[i] === 'mp3' &&
|
173
|
-
getMediaSource(preferManagedMediaSource)?.isTypeSupported('audio/mpeg')
|
174
|
-
) {
|
175
|
-
return '';
|
176
168
|
}
|
177
169
|
}
|
178
170
|
|
179
171
|
return lowerCaseCodec;
|
180
172
|
}
|
181
173
|
|
182
|
-
const AUDIO_CODEC_REGEXP = /flac|opus
|
174
|
+
const AUDIO_CODEC_REGEXP = /flac|opus/i;
|
183
175
|
export function getCodecCompatibleName(
|
184
176
|
codec: string,
|
185
177
|
preferManagedMediaSource = true,
|
@@ -193,7 +185,7 @@ export function getCodecCompatibleName(
|
|
193
185
|
}
|
194
186
|
|
195
187
|
export function pickMostCompleteCodecName(
|
196
|
-
parsedCodec: string
|
188
|
+
parsedCodec: string,
|
197
189
|
levelCodec: string | undefined,
|
198
190
|
): string | undefined {
|
199
191
|
// Parsing of mp4a codecs strings in mp4-tools from media is incomplete as of d8c6c7a
|
@@ -221,24 +213,3 @@ export function convertAVC1ToAVCOTI(codec: string) {
|
|
221
213
|
}
|
222
214
|
return codecs.join(',');
|
223
215
|
}
|
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
|
-
}
|
package/src/utils/hdr.ts
CHANGED
@@ -49,13 +49,16 @@ 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;
|
54
52
|
preferHDR =
|
55
53
|
videoPreference.preferHDR !== undefined
|
56
54
|
? videoPreference.preferHDR
|
57
|
-
:
|
58
|
-
|
55
|
+
: isHdrSupported();
|
56
|
+
|
57
|
+
if (preferHDR) {
|
58
|
+
allowedVideoRanges = allowedVideoRanges.filter(
|
59
|
+
(range: VideoRange) => range !== 'SDR',
|
60
|
+
);
|
61
|
+
} else {
|
59
62
|
allowedVideoRanges = ['SDR'];
|
60
63
|
}
|
61
64
|
}
|
@@ -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 '../demux/id3';
|
5
5
|
import {
|
6
6
|
RationalTimestamp,
|
7
7
|
toTimescaleFromScale,
|
@@ -1,5 +1,4 @@
|
|
1
1
|
import { base64Decode } from './numeric-encoding-utils';
|
2
|
-
import { strToUtf8array } from './utf8-utils';
|
3
2
|
|
4
3
|
function getKeyIdBytes(str: string): Uint8Array {
|
5
4
|
const keyIdbytes = strToUtf8array(str).subarray(0, 16);
|
@@ -41,3 +40,9 @@ export function convertDataUriToArrayBytes(uri: string): Uint8Array | null {
|
|
41
40
|
}
|
42
41
|
return keydata;
|
43
42
|
}
|
43
|
+
|
44
|
+
export function strToUtf8array(str: string): Uint8Array {
|
45
|
+
return Uint8Array.from(unescape(encodeURIComponent(str)), (c) =>
|
46
|
+
c.charCodeAt(0),
|
47
|
+
);
|
48
|
+
}
|
@@ -3,17 +3,16 @@
|
|
3
3
|
*/
|
4
4
|
|
5
5
|
import { logger } from './logger';
|
6
|
-
import {
|
7
|
-
import {
|
8
|
-
import type { Fragment, MediaFragment, Part } from '../loader/fragment';
|
9
|
-
import type { LevelDetails } from '../loader/level-details';
|
6
|
+
import { Fragment, Part } from '../loader/fragment';
|
7
|
+
import { LevelDetails } from '../loader/level-details';
|
10
8
|
import type { Level } from '../types/level';
|
9
|
+
import { DateRange } from '../loader/date-range';
|
11
10
|
|
12
11
|
type FragmentIntersection = (oldFrag: Fragment, newFrag: Fragment) => void;
|
13
12
|
type PartIntersection = (oldPart: Part, newPart: Part) => void;
|
14
13
|
|
15
14
|
export function updatePTS(
|
16
|
-
fragments:
|
15
|
+
fragments: Fragment[],
|
17
16
|
fromIdx: number,
|
18
17
|
toIdx: number,
|
19
18
|
): void {
|
@@ -22,7 +21,7 @@ export function updatePTS(
|
|
22
21
|
updateFromToPTS(fragFrom, fragTo);
|
23
22
|
}
|
24
23
|
|
25
|
-
function updateFromToPTS(fragFrom:
|
24
|
+
function updateFromToPTS(fragFrom: Fragment, fragTo: Fragment) {
|
26
25
|
const fragToPTS = fragTo.startPTS as number;
|
27
26
|
// if we know startPTS[toIdx]
|
28
27
|
if (Number.isFinite(fragToPTS)) {
|
@@ -56,7 +55,7 @@ function updateFromToPTS(fragFrom: MediaFragment, fragTo: MediaFragment) {
|
|
56
55
|
|
57
56
|
export function updateFragPTSDTS(
|
58
57
|
details: LevelDetails | undefined,
|
59
|
-
frag:
|
58
|
+
frag: Fragment,
|
60
59
|
startPTS: number,
|
61
60
|
endPTS: number,
|
62
61
|
startDTS: number,
|
@@ -83,11 +82,11 @@ export function updateFragPTSDTS(
|
|
83
82
|
|
84
83
|
maxStartPTS = Math.max(startPTS, fragStartPts);
|
85
84
|
startPTS = Math.min(startPTS, fragStartPts);
|
86
|
-
startDTS = Math.min(startDTS, frag.startDTS
|
85
|
+
startDTS = Math.min(startDTS, frag.startDTS);
|
87
86
|
|
88
87
|
minEndPTS = Math.min(endPTS, fragEndPts);
|
89
88
|
endPTS = Math.max(endPTS, fragEndPts);
|
90
|
-
endDTS = Math.max(endDTS, frag.endDTS
|
89
|
+
endDTS = Math.max(endDTS, frag.endDTS);
|
91
90
|
}
|
92
91
|
|
93
92
|
const drift = startPTS - frag.start;
|
@@ -102,7 +101,7 @@ export function updateFragPTSDTS(
|
|
102
101
|
frag.minEndPTS = minEndPTS;
|
103
102
|
frag.endDTS = endDTS;
|
104
103
|
|
105
|
-
const sn = frag.sn;
|
104
|
+
const sn = frag.sn as number; // 'initSegment'
|
106
105
|
// exit if sn out of range
|
107
106
|
if (!details || sn < details.startSN || sn > details.endSN) {
|
108
107
|
return 0;
|
@@ -197,10 +196,10 @@ export function mergeDetails(
|
|
197
196
|
},
|
198
197
|
);
|
199
198
|
|
200
|
-
const fragmentsToCheck = newDetails.fragmentHint
|
201
|
-
? newDetails.fragments.concat(newDetails.fragmentHint)
|
202
|
-
: newDetails.fragments;
|
203
199
|
if (currentInitSegment) {
|
200
|
+
const fragmentsToCheck = newDetails.fragmentHint
|
201
|
+
? newDetails.fragments.concat(newDetails.fragmentHint)
|
202
|
+
: newDetails.fragments;
|
204
203
|
fragmentsToCheck.forEach((frag) => {
|
205
204
|
if (
|
206
205
|
frag &&
|
@@ -221,30 +220,14 @@ export function mergeDetails(
|
|
221
220
|
for (let i = newDetails.skippedSegments; i--; ) {
|
222
221
|
newDetails.fragments.shift();
|
223
222
|
}
|
224
|
-
newDetails.startSN = newDetails.fragments[0].sn;
|
223
|
+
newDetails.startSN = newDetails.fragments[0].sn as number;
|
225
224
|
newDetails.startCC = newDetails.fragments[0].cc;
|
226
|
-
} else {
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
);
|
232
|
-
}
|
233
|
-
const programDateTimes = oldDetails.fragments.filter(
|
234
|
-
(frag) => frag.rawProgramDateTime,
|
225
|
+
} else if (newDetails.canSkipDateRanges) {
|
226
|
+
newDetails.dateRanges = mergeDateRanges(
|
227
|
+
oldDetails.dateRanges,
|
228
|
+
newDetails.dateRanges,
|
229
|
+
newDetails.recentlyRemovedDateranges,
|
235
230
|
);
|
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);
|
248
231
|
}
|
249
232
|
}
|
250
233
|
|
@@ -310,38 +293,27 @@ export function mergeDetails(
|
|
310
293
|
|
311
294
|
function mergeDateRanges(
|
312
295
|
oldDateRanges: Record<string, DateRange>,
|
313
|
-
|
296
|
+
deltaDateRanges: Record<string, DateRange>,
|
297
|
+
recentlyRemovedDateranges: string[] | undefined,
|
314
298
|
): Record<string, DateRange> {
|
315
|
-
const { dateRanges: deltaDateRanges, recentlyRemovedDateranges } = newDetails;
|
316
299
|
const dateRanges = Object.assign({}, oldDateRanges);
|
317
300
|
if (recentlyRemovedDateranges) {
|
318
301
|
recentlyRemovedDateranges.forEach((id) => {
|
319
302
|
delete dateRanges[id];
|
320
303
|
});
|
321
304
|
}
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
305
|
+
Object.keys(deltaDateRanges).forEach((id) => {
|
306
|
+
const dateRange = new DateRange(deltaDateRanges[id].attr, dateRanges[id]);
|
307
|
+
if (dateRange.isValid) {
|
308
|
+
dateRanges[id] = dateRange;
|
309
|
+
} else {
|
310
|
+
logger.warn(
|
311
|
+
`Ignoring invalid Playlist Delta Update DATERANGE tag: "${JSON.stringify(
|
312
|
+
deltaDateRanges[id].attr,
|
313
|
+
)}"`,
|
330
314
|
);
|
331
|
-
|
332
|
-
|
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
|
-
}
|
315
|
+
}
|
316
|
+
});
|
345
317
|
return dateRanges;
|
346
318
|
}
|
347
319
|
|
@@ -394,7 +366,7 @@ export function mapFragmentIntersection(
|
|
394
366
|
for (let i = start; i <= end; i++) {
|
395
367
|
const oldFrag = oldFrags[delta + i];
|
396
368
|
let newFrag = newFrags[i];
|
397
|
-
if (skippedSegments && !newFrag &&
|
369
|
+
if (skippedSegments && !newFrag && i < skippedSegments) {
|
398
370
|
// Fill in skipped segments in delta playlist
|
399
371
|
newFrag = newDetails.fragments[i] = oldFrag;
|
400
372
|
}
|
@@ -461,37 +433,38 @@ export function computeReloadInterval(
|
|
461
433
|
}
|
462
434
|
|
463
435
|
export function getFragmentWithSN(
|
464
|
-
|
436
|
+
level: Level,
|
465
437
|
sn: number,
|
466
438
|
fragCurrent: Fragment | null,
|
467
|
-
):
|
468
|
-
if (!details) {
|
439
|
+
): Fragment | null {
|
440
|
+
if (!level?.details) {
|
469
441
|
return null;
|
470
442
|
}
|
471
|
-
|
472
|
-
|
443
|
+
const levelDetails = level.details;
|
444
|
+
let fragment: Fragment | undefined =
|
445
|
+
levelDetails.fragments[sn - levelDetails.startSN];
|
473
446
|
if (fragment) {
|
474
447
|
return fragment;
|
475
448
|
}
|
476
|
-
fragment =
|
449
|
+
fragment = levelDetails.fragmentHint;
|
477
450
|
if (fragment && fragment.sn === sn) {
|
478
451
|
return fragment;
|
479
452
|
}
|
480
|
-
if (sn <
|
481
|
-
return fragCurrent
|
453
|
+
if (sn < levelDetails.startSN && fragCurrent && fragCurrent.sn === sn) {
|
454
|
+
return fragCurrent;
|
482
455
|
}
|
483
456
|
return null;
|
484
457
|
}
|
485
458
|
|
486
459
|
export function getPartWith(
|
487
|
-
|
460
|
+
level: Level,
|
488
461
|
sn: number,
|
489
462
|
partIndex: number,
|
490
463
|
): Part | null {
|
491
|
-
if (!details) {
|
464
|
+
if (!level?.details) {
|
492
465
|
return null;
|
493
466
|
}
|
494
|
-
return findPart(details
|
467
|
+
return findPart(level.details?.partList, sn, partIndex);
|
495
468
|
}
|
496
469
|
|
497
470
|
export function findPart(
|