hls.js 1.5.14-0.canary.10559 → 1.5.15
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 +2911 -4558
- package/dist/hls.js.d.ts +112 -186
- package/dist/hls.js.map +1 -1
- package/dist/hls.light.js +2291 -3311
- 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 +1813 -2835
- 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 +4707 -6356
- 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 +42 -42
- 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 +107 -263
- 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 +5 -5
- package/src/demux/audio/ac3-demuxer.ts +4 -5
- package/src/demux/audio/adts.ts +4 -9
- package/src/demux/audio/base-audio-demuxer.ts +15 -21
- 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/inject-worker.ts +4 -38
- package/src/demux/mp4demuxer.ts +8 -17
- package/src/demux/sample-aes.ts +0 -2
- package/src/demux/transmuxer-interface.ts +83 -106
- package/src/demux/transmuxer-worker.ts +77 -111
- package/src/demux/transmuxer.ts +22 -46
- package/src/demux/tsdemuxer.ts +65 -131
- package/src/demux/video/avc-video-parser.ts +156 -219
- package/src/demux/video/base-video-parser.ts +9 -133
- package/src/demux/video/exp-golomb.ts +208 -0
- package/src/errors.ts +0 -2
- package/src/events.ts +1 -8
- package/src/exports-named.ts +1 -1
- package/src/hls.ts +48 -97
- 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 +84 -55
- package/src/remux/passthrough-remuxer.ts +8 -23
- package/src/task-loop.ts +2 -5
- package/src/types/component-api.ts +1 -3
- package/src/types/demuxer.ts +1 -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/discontinuities.ts +47 -21
- 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/dist/hls.d.mts +0 -3179
- package/dist/hls.d.ts +0 -3179
- package/src/crypt/decrypter-aes-mode.ts +0 -4
- package/src/demux/video/hevc-video-parser.ts +0 -718
- 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/version.ts +0 -1
package/src/demux/audio/adts.ts
CHANGED
@@ -17,7 +17,6 @@ type AudioConfig = {
|
|
17
17
|
samplerate: number;
|
18
18
|
channelCount: number;
|
19
19
|
codec: string;
|
20
|
-
parsedCodec: string;
|
21
20
|
manifestCodec: string;
|
22
21
|
};
|
23
22
|
|
@@ -33,7 +32,6 @@ export function getAudioConfig(
|
|
33
32
|
audioCodec: string,
|
34
33
|
): AudioConfig | void {
|
35
34
|
let adtsObjectType: number;
|
36
|
-
let originalAdtsObjectType: number;
|
37
35
|
let adtsExtensionSamplingIndex: number;
|
38
36
|
let adtsChannelConfig: number;
|
39
37
|
let config: number[];
|
@@ -44,8 +42,7 @@ export function getAudioConfig(
|
|
44
42
|
8000, 7350,
|
45
43
|
];
|
46
44
|
// byte 2
|
47
|
-
adtsObjectType =
|
48
|
-
((data[offset + 2] & 0xc0) >>> 6) + 1;
|
45
|
+
adtsObjectType = ((data[offset + 2] & 0xc0) >>> 6) + 1;
|
49
46
|
const adtsSamplingIndex = (data[offset + 2] & 0x3c) >>> 2;
|
50
47
|
if (adtsSamplingIndex > adtsSamplingRates.length - 1) {
|
51
48
|
const error = new Error(`invalid ADTS sampling index:${adtsSamplingIndex}`);
|
@@ -64,8 +61,8 @@ export function getAudioConfig(
|
|
64
61
|
logger.log(
|
65
62
|
`manifest codec:${audioCodec}, ADTS type:${adtsObjectType}, samplingIndex:${adtsSamplingIndex}`,
|
66
63
|
);
|
67
|
-
//
|
68
|
-
if (/firefox
|
64
|
+
// firefox: freq less than 24kHz = AAC SBR (HE-AAC)
|
65
|
+
if (/firefox/i.test(userAgent)) {
|
69
66
|
if (adtsSamplingIndex >= 6) {
|
70
67
|
adtsObjectType = 5;
|
71
68
|
config = new Array(4);
|
@@ -170,7 +167,6 @@ export function getAudioConfig(
|
|
170
167
|
samplerate: adtsSamplingRates[adtsSamplingIndex],
|
171
168
|
channelCount: adtsChannelConfig,
|
172
169
|
codec: 'mp4a.40.' + adtsObjectType,
|
173
|
-
parsedCodec: 'mp4a.40.' + originalAdtsObjectType,
|
174
170
|
manifestCodec,
|
175
171
|
};
|
176
172
|
}
|
@@ -248,9 +244,8 @@ export function initTrackConfig(
|
|
248
244
|
track.channelCount = config.channelCount;
|
249
245
|
track.codec = config.codec;
|
250
246
|
track.manifestCodec = config.manifestCodec;
|
251
|
-
track.parsedCodec = config.parsedCodec;
|
252
247
|
logger.log(
|
253
|
-
`parsed codec:${track.
|
248
|
+
`parsed codec:${track.codec}, rate:${config.samplerate}, channels:${config.channelCount}`,
|
254
249
|
);
|
255
250
|
}
|
256
251
|
}
|
@@ -1,21 +1,19 @@
|
|
1
|
+
import * as ID3 from '../id3';
|
1
2
|
import {
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
3
|
+
DemuxerResult,
|
4
|
+
Demuxer,
|
5
|
+
DemuxedAudioTrack,
|
6
|
+
AudioFrame,
|
7
|
+
DemuxedMetadataTrack,
|
8
|
+
DemuxedVideoTrackBase,
|
9
|
+
DemuxedUserdataTrack,
|
10
|
+
KeyData,
|
10
11
|
MetadataSchema,
|
11
12
|
} from '../../types/demuxer';
|
12
13
|
import { dummyTrack } from '../dummy-demuxed-track';
|
13
14
|
import { appendUint8Array } from '../../utils/mp4-tools';
|
14
15
|
import { sliceUint8 } from '../../utils/typed-array';
|
15
16
|
import { RationalTimestamp } from '../../utils/timescale-conversion';
|
16
|
-
import { getId3Data } from '@svta/common-media-library/id3/getId3Data';
|
17
|
-
import { getId3Timestamp } from '@svta/common-media-library/id3/getId3Timestamp';
|
18
|
-
import { canParseId3 } from '@svta/common-media-library/id3/canParseId3';
|
19
17
|
|
20
18
|
class BaseAudioDemuxer implements Demuxer {
|
21
19
|
protected _audioTrack!: DemuxedAudioTrack;
|
@@ -71,12 +69,12 @@ class BaseAudioDemuxer implements Demuxer {
|
|
71
69
|
this.cachedData = null;
|
72
70
|
}
|
73
71
|
|
74
|
-
let id3Data: Uint8Array | undefined =
|
72
|
+
let id3Data: Uint8Array | undefined = ID3.getID3Data(data, 0);
|
75
73
|
let offset = id3Data ? id3Data.length : 0;
|
76
74
|
let lastDataIndex;
|
77
75
|
const track = this._audioTrack;
|
78
76
|
const id3Track = this._id3Track;
|
79
|
-
const timestamp = id3Data ?
|
77
|
+
const timestamp = id3Data ? ID3.getTimeStamp(id3Data) : undefined;
|
80
78
|
const length = data.length;
|
81
79
|
|
82
80
|
if (
|
@@ -113,9 +111,9 @@ class BaseAudioDemuxer implements Demuxer {
|
|
113
111
|
} else {
|
114
112
|
offset = length;
|
115
113
|
}
|
116
|
-
} else if (
|
117
|
-
// after a canParse, a call to
|
118
|
-
id3Data =
|
114
|
+
} else if (ID3.canParse(data, offset)) {
|
115
|
+
// after a ID3.canParse, a call to ID3.getID3Data *should* always returns some data
|
116
|
+
id3Data = ID3.getID3Data(data, offset)!;
|
119
117
|
id3Track.samples.push({
|
120
118
|
pts: this.lastPTS,
|
121
119
|
dts: this.lastPTS,
|
@@ -174,11 +172,7 @@ class BaseAudioDemuxer implements Demuxer {
|
|
174
172
|
};
|
175
173
|
}
|
176
174
|
|
177
|
-
destroy() {
|
178
|
-
this.cachedData = null;
|
179
|
-
// @ts-ignore
|
180
|
-
this._audioTrack = this._id3Track = undefined;
|
181
|
-
}
|
175
|
+
destroy() {}
|
182
176
|
}
|
183
177
|
|
184
178
|
/**
|
@@ -2,11 +2,10 @@
|
|
2
2
|
* MP3 demuxer
|
3
3
|
*/
|
4
4
|
import BaseAudioDemuxer from './base-audio-demuxer';
|
5
|
+
import { getID3Data, getTimeStamp } from '../id3';
|
5
6
|
import { getAudioBSID } from './dolby';
|
6
7
|
import { logger } from '../../utils/logger';
|
7
8
|
import * as MpegAudio from './mpegaudio';
|
8
|
-
import { getId3Data } from '@svta/common-media-library/id3/getId3Data';
|
9
|
-
import { getId3Timestamp } from '@svta/common-media-library/id3/getId3Timestamp';
|
10
9
|
|
11
10
|
class MP3Demuxer extends BaseAudioDemuxer {
|
12
11
|
resetInitSegment(
|
@@ -40,7 +39,7 @@ class MP3Demuxer extends BaseAudioDemuxer {
|
|
40
39
|
// Look for MPEG header | 1111 1111 | 111X XYZX | where X can be either 0 or 1 and Y or Z should be 1
|
41
40
|
// Layer bits (position 14 and 15) in header should be always different from 0 (Layer I or Layer II or Layer III)
|
42
41
|
// More info http://www.mp3-tech.org/programmer/frame_header.html
|
43
|
-
const id3Data =
|
42
|
+
const id3Data = getID3Data(data, 0);
|
44
43
|
let offset = id3Data?.length || 0;
|
45
44
|
|
46
45
|
// Check for ac-3|ec-3 sync bytes and return false if present
|
@@ -48,7 +47,7 @@ class MP3Demuxer extends BaseAudioDemuxer {
|
|
48
47
|
id3Data &&
|
49
48
|
data[offset] === 0x0b &&
|
50
49
|
data[offset + 1] === 0x77 &&
|
51
|
-
|
50
|
+
getTimeStamp(id3Data) !== undefined &&
|
52
51
|
// check the bsid to confirm ac-3 or ec-3 (not mp3)
|
53
52
|
getAudioBSID(data, offset) <= 16
|
54
53
|
) {
|
package/src/demux/id3.ts
ADDED
@@ -0,0 +1,411 @@
|
|
1
|
+
type RawFrame = { type: string; size: number; data: Uint8Array };
|
2
|
+
|
3
|
+
// breaking up those two types in order to clarify what is happening in the decoding path.
|
4
|
+
type DecodedFrame<T> = { key: string; data: T; info?: any };
|
5
|
+
export type Frame = DecodedFrame<ArrayBuffer | string>;
|
6
|
+
|
7
|
+
/**
|
8
|
+
* Returns true if an ID3 header can be found at offset in data
|
9
|
+
* @param data - The data to search
|
10
|
+
* @param offset - The offset at which to start searching
|
11
|
+
*/
|
12
|
+
export const isHeader = (data: Uint8Array, offset: number): boolean => {
|
13
|
+
/*
|
14
|
+
* http://id3.org/id3v2.3.0
|
15
|
+
* [0] = 'I'
|
16
|
+
* [1] = 'D'
|
17
|
+
* [2] = '3'
|
18
|
+
* [3,4] = {Version}
|
19
|
+
* [5] = {Flags}
|
20
|
+
* [6-9] = {ID3 Size}
|
21
|
+
*
|
22
|
+
* An ID3v2 tag can be detected with the following pattern:
|
23
|
+
* $49 44 33 yy yy xx zz zz zz zz
|
24
|
+
* Where yy is less than $FF, xx is the 'flags' byte and zz is less than $80
|
25
|
+
*/
|
26
|
+
if (offset + 10 <= data.length) {
|
27
|
+
// look for 'ID3' identifier
|
28
|
+
if (
|
29
|
+
data[offset] === 0x49 &&
|
30
|
+
data[offset + 1] === 0x44 &&
|
31
|
+
data[offset + 2] === 0x33
|
32
|
+
) {
|
33
|
+
// check version is within range
|
34
|
+
if (data[offset + 3] < 0xff && data[offset + 4] < 0xff) {
|
35
|
+
// check size is within range
|
36
|
+
if (
|
37
|
+
data[offset + 6] < 0x80 &&
|
38
|
+
data[offset + 7] < 0x80 &&
|
39
|
+
data[offset + 8] < 0x80 &&
|
40
|
+
data[offset + 9] < 0x80
|
41
|
+
) {
|
42
|
+
return true;
|
43
|
+
}
|
44
|
+
}
|
45
|
+
}
|
46
|
+
}
|
47
|
+
|
48
|
+
return false;
|
49
|
+
};
|
50
|
+
|
51
|
+
/**
|
52
|
+
* Returns true if an ID3 footer can be found at offset in data
|
53
|
+
* @param data - The data to search
|
54
|
+
* @param offset - The offset at which to start searching
|
55
|
+
*/
|
56
|
+
export const isFooter = (data: Uint8Array, offset: number): boolean => {
|
57
|
+
/*
|
58
|
+
* The footer is a copy of the header, but with a different identifier
|
59
|
+
*/
|
60
|
+
if (offset + 10 <= data.length) {
|
61
|
+
// look for '3DI' identifier
|
62
|
+
if (
|
63
|
+
data[offset] === 0x33 &&
|
64
|
+
data[offset + 1] === 0x44 &&
|
65
|
+
data[offset + 2] === 0x49
|
66
|
+
) {
|
67
|
+
// check version is within range
|
68
|
+
if (data[offset + 3] < 0xff && data[offset + 4] < 0xff) {
|
69
|
+
// check size is within range
|
70
|
+
if (
|
71
|
+
data[offset + 6] < 0x80 &&
|
72
|
+
data[offset + 7] < 0x80 &&
|
73
|
+
data[offset + 8] < 0x80 &&
|
74
|
+
data[offset + 9] < 0x80
|
75
|
+
) {
|
76
|
+
return true;
|
77
|
+
}
|
78
|
+
}
|
79
|
+
}
|
80
|
+
}
|
81
|
+
|
82
|
+
return false;
|
83
|
+
};
|
84
|
+
|
85
|
+
/**
|
86
|
+
* Returns any adjacent ID3 tags found in data starting at offset, as one block of data
|
87
|
+
* @param data - The data to search in
|
88
|
+
* @param offset - The offset at which to start searching
|
89
|
+
* @returns the block of data containing any ID3 tags found
|
90
|
+
* or *undefined* if no header is found at the starting offset
|
91
|
+
*/
|
92
|
+
export const getID3Data = (
|
93
|
+
data: Uint8Array,
|
94
|
+
offset: number,
|
95
|
+
): Uint8Array | undefined => {
|
96
|
+
const front = offset;
|
97
|
+
let length = 0;
|
98
|
+
|
99
|
+
while (isHeader(data, offset)) {
|
100
|
+
// ID3 header is 10 bytes
|
101
|
+
length += 10;
|
102
|
+
|
103
|
+
const size = readSize(data, offset + 6);
|
104
|
+
length += size;
|
105
|
+
|
106
|
+
if (isFooter(data, offset + 10)) {
|
107
|
+
// ID3 footer is 10 bytes
|
108
|
+
length += 10;
|
109
|
+
}
|
110
|
+
|
111
|
+
offset += length;
|
112
|
+
}
|
113
|
+
|
114
|
+
if (length > 0) {
|
115
|
+
return data.subarray(front, front + length);
|
116
|
+
}
|
117
|
+
|
118
|
+
return undefined;
|
119
|
+
};
|
120
|
+
|
121
|
+
const readSize = (data: Uint8Array, offset: number): number => {
|
122
|
+
let size = 0;
|
123
|
+
size = (data[offset] & 0x7f) << 21;
|
124
|
+
size |= (data[offset + 1] & 0x7f) << 14;
|
125
|
+
size |= (data[offset + 2] & 0x7f) << 7;
|
126
|
+
size |= data[offset + 3] & 0x7f;
|
127
|
+
return size;
|
128
|
+
};
|
129
|
+
|
130
|
+
export const canParse = (data: Uint8Array, offset: number): boolean => {
|
131
|
+
return (
|
132
|
+
isHeader(data, offset) &&
|
133
|
+
readSize(data, offset + 6) + 10 <= data.length - offset
|
134
|
+
);
|
135
|
+
};
|
136
|
+
|
137
|
+
/**
|
138
|
+
* Searches for the Elementary Stream timestamp found in the ID3 data chunk
|
139
|
+
* @param data - Block of data containing one or more ID3 tags
|
140
|
+
*/
|
141
|
+
export const getTimeStamp = (data: Uint8Array): number | undefined => {
|
142
|
+
const frames: Frame[] = getID3Frames(data);
|
143
|
+
|
144
|
+
for (let i = 0; i < frames.length; i++) {
|
145
|
+
const frame = frames[i];
|
146
|
+
|
147
|
+
if (isTimeStampFrame(frame)) {
|
148
|
+
return readTimeStamp(frame as DecodedFrame<ArrayBuffer>);
|
149
|
+
}
|
150
|
+
}
|
151
|
+
|
152
|
+
return undefined;
|
153
|
+
};
|
154
|
+
|
155
|
+
/**
|
156
|
+
* Returns true if the ID3 frame is an Elementary Stream timestamp frame
|
157
|
+
*/
|
158
|
+
export const isTimeStampFrame = (frame: Frame): boolean => {
|
159
|
+
return (
|
160
|
+
frame &&
|
161
|
+
frame.key === 'PRIV' &&
|
162
|
+
frame.info === 'com.apple.streaming.transportStreamTimestamp'
|
163
|
+
);
|
164
|
+
};
|
165
|
+
|
166
|
+
const getFrameData = (data: Uint8Array): RawFrame => {
|
167
|
+
/*
|
168
|
+
Frame ID $xx xx xx xx (four characters)
|
169
|
+
Size $xx xx xx xx
|
170
|
+
Flags $xx xx
|
171
|
+
*/
|
172
|
+
const type: string = String.fromCharCode(data[0], data[1], data[2], data[3]);
|
173
|
+
const size: number = readSize(data, 4);
|
174
|
+
|
175
|
+
// skip frame id, size, and flags
|
176
|
+
const offset = 10;
|
177
|
+
|
178
|
+
return { type, size, data: data.subarray(offset, offset + size) };
|
179
|
+
};
|
180
|
+
|
181
|
+
/**
|
182
|
+
* Returns an array of ID3 frames found in all the ID3 tags in the id3Data
|
183
|
+
* @param id3Data - The ID3 data containing one or more ID3 tags
|
184
|
+
*/
|
185
|
+
export const getID3Frames = (id3Data: Uint8Array): Frame[] => {
|
186
|
+
let offset = 0;
|
187
|
+
const frames: Frame[] = [];
|
188
|
+
|
189
|
+
while (isHeader(id3Data, offset)) {
|
190
|
+
const size = readSize(id3Data, offset + 6);
|
191
|
+
// skip past ID3 header
|
192
|
+
offset += 10;
|
193
|
+
const end = offset + size;
|
194
|
+
// loop through frames in the ID3 tag
|
195
|
+
while (offset + 8 < end) {
|
196
|
+
const frameData: RawFrame = getFrameData(id3Data.subarray(offset));
|
197
|
+
const frame: Frame | undefined = decodeFrame(frameData);
|
198
|
+
if (frame) {
|
199
|
+
frames.push(frame);
|
200
|
+
}
|
201
|
+
|
202
|
+
// skip frame header and frame data
|
203
|
+
offset += frameData.size + 10;
|
204
|
+
}
|
205
|
+
|
206
|
+
if (isFooter(id3Data, offset)) {
|
207
|
+
offset += 10;
|
208
|
+
}
|
209
|
+
}
|
210
|
+
|
211
|
+
return frames;
|
212
|
+
};
|
213
|
+
|
214
|
+
export const decodeFrame = (frame: RawFrame): Frame | undefined => {
|
215
|
+
if (frame.type === 'PRIV') {
|
216
|
+
return decodePrivFrame(frame);
|
217
|
+
} else if (frame.type[0] === 'W') {
|
218
|
+
return decodeURLFrame(frame);
|
219
|
+
}
|
220
|
+
|
221
|
+
return decodeTextFrame(frame);
|
222
|
+
};
|
223
|
+
|
224
|
+
const decodePrivFrame = (
|
225
|
+
frame: RawFrame,
|
226
|
+
): DecodedFrame<ArrayBuffer> | undefined => {
|
227
|
+
/*
|
228
|
+
Format: <text string>\0<binary data>
|
229
|
+
*/
|
230
|
+
if (frame.size < 2) {
|
231
|
+
return undefined;
|
232
|
+
}
|
233
|
+
|
234
|
+
const owner = utf8ArrayToStr(frame.data, true);
|
235
|
+
const privateData = new Uint8Array(frame.data.subarray(owner.length + 1));
|
236
|
+
|
237
|
+
return { key: frame.type, info: owner, data: privateData.buffer };
|
238
|
+
};
|
239
|
+
|
240
|
+
const decodeTextFrame = (frame: RawFrame): DecodedFrame<string> | undefined => {
|
241
|
+
if (frame.size < 2) {
|
242
|
+
return undefined;
|
243
|
+
}
|
244
|
+
|
245
|
+
if (frame.type === 'TXXX') {
|
246
|
+
/*
|
247
|
+
Format:
|
248
|
+
[0] = {Text Encoding}
|
249
|
+
[1-?] = {Description}\0{Value}
|
250
|
+
*/
|
251
|
+
let index = 1;
|
252
|
+
const description = utf8ArrayToStr(frame.data.subarray(index), true);
|
253
|
+
|
254
|
+
index += description.length + 1;
|
255
|
+
const value = utf8ArrayToStr(frame.data.subarray(index));
|
256
|
+
|
257
|
+
return { key: frame.type, info: description, data: value };
|
258
|
+
}
|
259
|
+
/*
|
260
|
+
Format:
|
261
|
+
[0] = {Text Encoding}
|
262
|
+
[1-?] = {Value}
|
263
|
+
*/
|
264
|
+
const text = utf8ArrayToStr(frame.data.subarray(1));
|
265
|
+
return { key: frame.type, data: text };
|
266
|
+
};
|
267
|
+
|
268
|
+
const decodeURLFrame = (frame: RawFrame): DecodedFrame<string> | undefined => {
|
269
|
+
if (frame.type === 'WXXX') {
|
270
|
+
/*
|
271
|
+
Format:
|
272
|
+
[0] = {Text Encoding}
|
273
|
+
[1-?] = {Description}\0{URL}
|
274
|
+
*/
|
275
|
+
if (frame.size < 2) {
|
276
|
+
return undefined;
|
277
|
+
}
|
278
|
+
|
279
|
+
let index = 1;
|
280
|
+
const description: string = utf8ArrayToStr(
|
281
|
+
frame.data.subarray(index),
|
282
|
+
true,
|
283
|
+
);
|
284
|
+
|
285
|
+
index += description.length + 1;
|
286
|
+
const value: string = utf8ArrayToStr(frame.data.subarray(index));
|
287
|
+
|
288
|
+
return { key: frame.type, info: description, data: value };
|
289
|
+
}
|
290
|
+
/*
|
291
|
+
Format:
|
292
|
+
[0-?] = {URL}
|
293
|
+
*/
|
294
|
+
const url: string = utf8ArrayToStr(frame.data);
|
295
|
+
return { key: frame.type, data: url };
|
296
|
+
};
|
297
|
+
|
298
|
+
const readTimeStamp = (
|
299
|
+
timeStampFrame: DecodedFrame<ArrayBuffer>,
|
300
|
+
): number | undefined => {
|
301
|
+
if (timeStampFrame.data.byteLength === 8) {
|
302
|
+
const data = new Uint8Array(timeStampFrame.data);
|
303
|
+
// timestamp is 33 bit expressed as a big-endian eight-octet number,
|
304
|
+
// with the upper 31 bits set to zero.
|
305
|
+
const pts33Bit = data[3] & 0x1;
|
306
|
+
let timestamp =
|
307
|
+
(data[4] << 23) + (data[5] << 15) + (data[6] << 7) + data[7];
|
308
|
+
timestamp /= 45;
|
309
|
+
|
310
|
+
if (pts33Bit) {
|
311
|
+
timestamp += 47721858.84;
|
312
|
+
} // 2^32 / 90
|
313
|
+
|
314
|
+
return Math.round(timestamp);
|
315
|
+
}
|
316
|
+
|
317
|
+
return undefined;
|
318
|
+
};
|
319
|
+
|
320
|
+
// http://stackoverflow.com/questions/8936984/uint8array-to-string-in-javascript/22373197
|
321
|
+
// http://www.onicos.com/staff/iz/amuse/javascript/expert/utf.txt
|
322
|
+
/* utf.js - UTF-8 <=> UTF-16 convertion
|
323
|
+
*
|
324
|
+
* Copyright (C) 1999 Masanao Izumo <iz@onicos.co.jp>
|
325
|
+
* Version: 1.0
|
326
|
+
* LastModified: Dec 25 1999
|
327
|
+
* This library is free. You can redistribute it and/or modify it.
|
328
|
+
*/
|
329
|
+
export const utf8ArrayToStr = (
|
330
|
+
array: Uint8Array,
|
331
|
+
exitOnNull: boolean = false,
|
332
|
+
): string => {
|
333
|
+
const decoder = getTextDecoder();
|
334
|
+
if (decoder) {
|
335
|
+
const decoded = decoder.decode(array);
|
336
|
+
|
337
|
+
if (exitOnNull) {
|
338
|
+
// grab up to the first null
|
339
|
+
const idx = decoded.indexOf('\0');
|
340
|
+
return idx !== -1 ? decoded.substring(0, idx) : decoded;
|
341
|
+
}
|
342
|
+
|
343
|
+
// remove any null characters
|
344
|
+
return decoded.replace(/\0/g, '');
|
345
|
+
}
|
346
|
+
|
347
|
+
const len = array.length;
|
348
|
+
let c;
|
349
|
+
let char2;
|
350
|
+
let char3;
|
351
|
+
let out = '';
|
352
|
+
let i = 0;
|
353
|
+
while (i < len) {
|
354
|
+
c = array[i++];
|
355
|
+
if (c === 0x00 && exitOnNull) {
|
356
|
+
return out;
|
357
|
+
} else if (c === 0x00 || c === 0x03) {
|
358
|
+
// If the character is 3 (END_OF_TEXT) or 0 (NULL) then skip it
|
359
|
+
continue;
|
360
|
+
}
|
361
|
+
switch (c >> 4) {
|
362
|
+
case 0:
|
363
|
+
case 1:
|
364
|
+
case 2:
|
365
|
+
case 3:
|
366
|
+
case 4:
|
367
|
+
case 5:
|
368
|
+
case 6:
|
369
|
+
case 7:
|
370
|
+
// 0xxxxxxx
|
371
|
+
out += String.fromCharCode(c);
|
372
|
+
break;
|
373
|
+
case 12:
|
374
|
+
case 13:
|
375
|
+
// 110x xxxx 10xx xxxx
|
376
|
+
char2 = array[i++];
|
377
|
+
out += String.fromCharCode(((c & 0x1f) << 6) | (char2 & 0x3f));
|
378
|
+
break;
|
379
|
+
case 14:
|
380
|
+
// 1110 xxxx 10xx xxxx 10xx xxxx
|
381
|
+
char2 = array[i++];
|
382
|
+
char3 = array[i++];
|
383
|
+
out += String.fromCharCode(
|
384
|
+
((c & 0x0f) << 12) | ((char2 & 0x3f) << 6) | ((char3 & 0x3f) << 0),
|
385
|
+
);
|
386
|
+
break;
|
387
|
+
default:
|
388
|
+
}
|
389
|
+
}
|
390
|
+
return out;
|
391
|
+
};
|
392
|
+
|
393
|
+
export const testables = {
|
394
|
+
decodeTextFrame: decodeTextFrame,
|
395
|
+
};
|
396
|
+
|
397
|
+
let decoder: TextDecoder;
|
398
|
+
|
399
|
+
function getTextDecoder() {
|
400
|
+
// On Play Station 4, TextDecoder is defined but partially implemented.
|
401
|
+
// Manual decoding option is preferable
|
402
|
+
if (navigator.userAgent.includes('PlayStation 4')) {
|
403
|
+
return;
|
404
|
+
}
|
405
|
+
|
406
|
+
if (!decoder && typeof self.TextDecoder !== 'undefined') {
|
407
|
+
decoder = new self.TextDecoder('utf-8');
|
408
|
+
}
|
409
|
+
|
410
|
+
return decoder;
|
411
|
+
}
|
@@ -1,9 +1,6 @@
|
|
1
1
|
// ensure the worker ends up in the bundle
|
2
2
|
// If the worker should not be included this gets aliased to empty.js
|
3
3
|
import './transmuxer-worker';
|
4
|
-
import { version } from '../version';
|
5
|
-
|
6
|
-
const workerStore: Record<string, WorkerContext> = {};
|
7
4
|
|
8
5
|
export function hasUMDWorker(): boolean {
|
9
6
|
return typeof __HLS_WORKER_BUNDLE__ === 'function';
|
@@ -13,15 +10,9 @@ export type WorkerContext = {
|
|
13
10
|
worker: Worker;
|
14
11
|
objectURL?: string;
|
15
12
|
scriptURL?: string;
|
16
|
-
clientCount: number;
|
17
13
|
};
|
18
14
|
|
19
15
|
export function injectWorker(): WorkerContext {
|
20
|
-
const workerContext = workerStore[version];
|
21
|
-
if (workerContext) {
|
22
|
-
workerContext.clientCount++;
|
23
|
-
return workerContext;
|
24
|
-
}
|
25
16
|
const blob = new self.Blob(
|
26
17
|
[
|
27
18
|
`var exports={};var module={exports:exports};function define(f){f()};define.amd=true;(${__HLS_WORKER_BUNDLE__.toString()})(true);`,
|
@@ -32,44 +23,19 @@ export function injectWorker(): WorkerContext {
|
|
32
23
|
);
|
33
24
|
const objectURL = self.URL.createObjectURL(blob);
|
34
25
|
const worker = new self.Worker(objectURL);
|
35
|
-
|
26
|
+
|
27
|
+
return {
|
36
28
|
worker,
|
37
29
|
objectURL,
|
38
|
-
clientCount: 1,
|
39
30
|
};
|
40
|
-
workerStore[version] = result;
|
41
|
-
return result;
|
42
31
|
}
|
43
32
|
|
44
33
|
export function loadWorker(path: string): WorkerContext {
|
45
|
-
const workerContext = workerStore[path];
|
46
|
-
if (workerContext) {
|
47
|
-
workerContext.clientCount++;
|
48
|
-
return workerContext;
|
49
|
-
}
|
50
34
|
const scriptURL = new self.URL(path, self.location.href).href;
|
51
35
|
const worker = new self.Worker(scriptURL);
|
52
|
-
|
36
|
+
|
37
|
+
return {
|
53
38
|
worker,
|
54
39
|
scriptURL,
|
55
|
-
clientCount: 1,
|
56
40
|
};
|
57
|
-
workerStore[path] = result;
|
58
|
-
return result;
|
59
|
-
}
|
60
|
-
|
61
|
-
export function removeWorkerFromStore(path?: string | null) {
|
62
|
-
const workerContext = workerStore[path || version];
|
63
|
-
if (workerContext) {
|
64
|
-
const clientCount = workerContext.clientCount--;
|
65
|
-
if (clientCount === 1) {
|
66
|
-
const { worker, objectURL } = workerContext;
|
67
|
-
delete workerStore[path || version];
|
68
|
-
if (objectURL) {
|
69
|
-
// revoke the Object URL that was used to create transmuxer worker, so as not to leak it
|
70
|
-
self.URL.revokeObjectURL(objectURL);
|
71
|
-
}
|
72
|
-
worker.terminate();
|
73
|
-
}
|
74
|
-
}
|
75
41
|
}
|
package/src/demux/mp4demuxer.ts
CHANGED
@@ -2,13 +2,13 @@
|
|
2
2
|
* MP4 demuxer
|
3
3
|
*/
|
4
4
|
import {
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
5
|
+
Demuxer,
|
6
|
+
DemuxerResult,
|
7
|
+
PassthroughTrack,
|
8
|
+
DemuxedAudioTrack,
|
9
|
+
DemuxedUserdataTrack,
|
10
|
+
DemuxedMetadataTrack,
|
11
|
+
KeyData,
|
12
12
|
MetadataSchema,
|
13
13
|
} from '../types/demuxer';
|
14
14
|
import {
|
@@ -194,16 +194,7 @@ class MP4Demuxer implements Demuxer {
|
|
194
194
|
);
|
195
195
|
}
|
196
196
|
|
197
|
-
destroy() {
|
198
|
-
// @ts-ignore
|
199
|
-
this.config = null;
|
200
|
-
this.remainderData = null;
|
201
|
-
this.videoTrack =
|
202
|
-
this.audioTrack =
|
203
|
-
this.id3Track =
|
204
|
-
this.txtTrack =
|
205
|
-
undefined;
|
206
|
-
}
|
197
|
+
destroy() {}
|
207
198
|
}
|
208
199
|
|
209
200
|
export default MP4Demuxer;
|
package/src/demux/sample-aes.ts
CHANGED
@@ -4,7 +4,6 @@
|
|
4
4
|
|
5
5
|
import { HlsConfig } from '../config';
|
6
6
|
import Decrypter from '../crypt/decrypter';
|
7
|
-
import { DecrypterAesMode } from '../crypt/decrypter-aes-mode';
|
8
7
|
import { HlsEventEmitter } from '../events';
|
9
8
|
import type {
|
10
9
|
AudioSample,
|
@@ -31,7 +30,6 @@ class SampleAesDecrypter {
|
|
31
30
|
encryptedData,
|
32
31
|
this.keyData.key.buffer,
|
33
32
|
this.keyData.iv.buffer,
|
34
|
-
DecrypterAesMode.cbc,
|
35
33
|
);
|
36
34
|
}
|
37
35
|
|