@zenvor/hls.js 1.0.0
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/LICENSE +28 -0
- package/README.md +472 -0
- package/dist/hls-demo.js +26995 -0
- package/dist/hls-demo.js.map +1 -0
- package/dist/hls.d.mts +4204 -0
- package/dist/hls.d.ts +4204 -0
- package/dist/hls.js +40050 -0
- package/dist/hls.js.d.ts +4204 -0
- package/dist/hls.js.map +1 -0
- package/dist/hls.light.js +27145 -0
- package/dist/hls.light.js.map +1 -0
- package/dist/hls.light.min.js +2 -0
- package/dist/hls.light.min.js.map +1 -0
- package/dist/hls.light.mjs +26392 -0
- package/dist/hls.light.mjs.map +1 -0
- package/dist/hls.min.js +2 -0
- package/dist/hls.min.js.map +1 -0
- package/dist/hls.mjs +38956 -0
- package/dist/hls.mjs.map +1 -0
- package/dist/hls.worker.js +2 -0
- package/dist/hls.worker.js.map +1 -0
- package/package.json +143 -0
- package/src/config.ts +794 -0
- package/src/controller/abr-controller.ts +1019 -0
- package/src/controller/algo-data-controller.ts +794 -0
- package/src/controller/audio-stream-controller.ts +1099 -0
- package/src/controller/audio-track-controller.ts +454 -0
- package/src/controller/base-playlist-controller.ts +438 -0
- package/src/controller/base-stream-controller.ts +2526 -0
- package/src/controller/buffer-controller.ts +2015 -0
- package/src/controller/buffer-operation-queue.ts +159 -0
- package/src/controller/cap-level-controller.ts +367 -0
- package/src/controller/cmcd-controller.ts +422 -0
- package/src/controller/content-steering-controller.ts +622 -0
- package/src/controller/eme-controller.ts +1617 -0
- package/src/controller/error-controller.ts +627 -0
- package/src/controller/fps-controller.ts +146 -0
- package/src/controller/fragment-finders.ts +256 -0
- package/src/controller/fragment-tracker.ts +567 -0
- package/src/controller/gap-controller.ts +719 -0
- package/src/controller/id3-track-controller.ts +488 -0
- package/src/controller/interstitial-player.ts +302 -0
- package/src/controller/interstitials-controller.ts +2895 -0
- package/src/controller/interstitials-schedule.ts +698 -0
- package/src/controller/latency-controller.ts +294 -0
- package/src/controller/level-controller.ts +776 -0
- package/src/controller/stream-controller.ts +1597 -0
- package/src/controller/subtitle-stream-controller.ts +508 -0
- package/src/controller/subtitle-track-controller.ts +617 -0
- package/src/controller/timeline-controller.ts +677 -0
- package/src/crypt/aes-crypto.ts +36 -0
- package/src/crypt/aes-decryptor.ts +339 -0
- package/src/crypt/decrypter-aes-mode.ts +4 -0
- package/src/crypt/decrypter.ts +225 -0
- package/src/crypt/fast-aes-key.ts +39 -0
- package/src/define-plugin.d.ts +17 -0
- package/src/demux/audio/aacdemuxer.ts +126 -0
- package/src/demux/audio/ac3-demuxer.ts +170 -0
- package/src/demux/audio/adts.ts +249 -0
- package/src/demux/audio/base-audio-demuxer.ts +205 -0
- package/src/demux/audio/dolby.ts +21 -0
- package/src/demux/audio/mp3demuxer.ts +85 -0
- package/src/demux/audio/mpegaudio.ts +177 -0
- package/src/demux/chunk-cache.ts +42 -0
- package/src/demux/dummy-demuxed-track.ts +13 -0
- package/src/demux/inject-worker.ts +75 -0
- package/src/demux/mp4demuxer.ts +234 -0
- package/src/demux/sample-aes.ts +198 -0
- package/src/demux/transmuxer-interface.ts +449 -0
- package/src/demux/transmuxer-worker.ts +221 -0
- package/src/demux/transmuxer.ts +560 -0
- package/src/demux/tsdemuxer.ts +1256 -0
- package/src/demux/video/avc-video-parser.ts +401 -0
- package/src/demux/video/base-video-parser.ts +198 -0
- package/src/demux/video/exp-golomb.ts +153 -0
- package/src/demux/video/hevc-video-parser.ts +736 -0
- package/src/empty-es.js +5 -0
- package/src/empty.js +3 -0
- package/src/errors.ts +107 -0
- package/src/events.ts +548 -0
- package/src/exports-default.ts +3 -0
- package/src/exports-named.ts +81 -0
- package/src/hls.ts +1613 -0
- package/src/is-supported.ts +54 -0
- package/src/loader/date-range.ts +207 -0
- package/src/loader/fragment-loader.ts +403 -0
- package/src/loader/fragment.ts +487 -0
- package/src/loader/interstitial-asset-list.ts +162 -0
- package/src/loader/interstitial-event.ts +337 -0
- package/src/loader/key-loader.ts +439 -0
- package/src/loader/level-details.ts +203 -0
- package/src/loader/level-key.ts +259 -0
- package/src/loader/load-stats.ts +17 -0
- package/src/loader/m3u8-parser.ts +1072 -0
- package/src/loader/playlist-loader.ts +839 -0
- package/src/polyfills/number.ts +15 -0
- package/src/remux/aac-helper.ts +81 -0
- package/src/remux/mp4-generator.ts +1380 -0
- package/src/remux/mp4-remuxer.ts +1261 -0
- package/src/remux/passthrough-remuxer.ts +434 -0
- package/src/task-loop.ts +130 -0
- package/src/types/algo.ts +44 -0
- package/src/types/buffer.ts +105 -0
- package/src/types/component-api.ts +20 -0
- package/src/types/demuxer.ts +208 -0
- package/src/types/events.ts +574 -0
- package/src/types/fragment-tracker.ts +23 -0
- package/src/types/level.ts +268 -0
- package/src/types/loader.ts +198 -0
- package/src/types/media-playlist.ts +92 -0
- package/src/types/network-details.ts +3 -0
- package/src/types/remuxer.ts +104 -0
- package/src/types/track.ts +12 -0
- package/src/types/transmuxer.ts +46 -0
- package/src/types/tuples.ts +6 -0
- package/src/types/vtt.ts +11 -0
- package/src/utils/arrays.ts +22 -0
- package/src/utils/attr-list.ts +192 -0
- package/src/utils/binary-search.ts +46 -0
- package/src/utils/buffer-helper.ts +173 -0
- package/src/utils/cea-608-parser.ts +1413 -0
- package/src/utils/chunker.ts +41 -0
- package/src/utils/codecs.ts +314 -0
- package/src/utils/cues.ts +96 -0
- package/src/utils/discontinuities.ts +174 -0
- package/src/utils/encryption-methods-util.ts +21 -0
- package/src/utils/error-helper.ts +95 -0
- package/src/utils/event-listener-helper.ts +16 -0
- package/src/utils/ewma-bandwidth-estimator.ts +97 -0
- package/src/utils/ewma.ts +43 -0
- package/src/utils/fetch-loader.ts +331 -0
- package/src/utils/global.ts +2 -0
- package/src/utils/hash.ts +10 -0
- package/src/utils/hdr.ts +67 -0
- package/src/utils/hex.ts +32 -0
- package/src/utils/imsc1-ttml-parser.ts +261 -0
- package/src/utils/keysystem-util.ts +45 -0
- package/src/utils/level-helper.ts +629 -0
- package/src/utils/logger.ts +120 -0
- package/src/utils/media-option-attributes.ts +49 -0
- package/src/utils/mediacapabilities-helper.ts +301 -0
- package/src/utils/mediakeys-helper.ts +210 -0
- package/src/utils/mediasource-helper.ts +37 -0
- package/src/utils/mp4-tools.ts +1473 -0
- package/src/utils/number.ts +3 -0
- package/src/utils/numeric-encoding-utils.ts +26 -0
- package/src/utils/output-filter.ts +46 -0
- package/src/utils/rendition-helper.ts +505 -0
- package/src/utils/safe-json-stringify.ts +22 -0
- package/src/utils/texttrack-utils.ts +164 -0
- package/src/utils/time-ranges.ts +17 -0
- package/src/utils/timescale-conversion.ts +46 -0
- package/src/utils/utf8-utils.ts +18 -0
- package/src/utils/variable-substitution.ts +105 -0
- package/src/utils/vttcue.ts +384 -0
- package/src/utils/vttparser.ts +497 -0
- package/src/utils/webvtt-parser.ts +166 -0
- package/src/utils/xhr-loader.ts +337 -0
- package/src/version.ts +1 -0
|
@@ -0,0 +1,560 @@
|
|
|
1
|
+
import AACDemuxer from './audio/aacdemuxer';
|
|
2
|
+
import { AC3Demuxer } from './audio/ac3-demuxer';
|
|
3
|
+
import MP3Demuxer from './audio/mp3demuxer';
|
|
4
|
+
import Decrypter from '../crypt/decrypter';
|
|
5
|
+
import MP4Demuxer from '../demux/mp4demuxer';
|
|
6
|
+
import TSDemuxer from '../demux/tsdemuxer';
|
|
7
|
+
import { ErrorDetails, ErrorTypes } from '../errors';
|
|
8
|
+
import { Events } from '../events';
|
|
9
|
+
import MP4Remuxer from '../remux/mp4-remuxer';
|
|
10
|
+
import PassThroughRemuxer from '../remux/passthrough-remuxer';
|
|
11
|
+
import { PlaylistLevelType } from '../types/loader';
|
|
12
|
+
import {
|
|
13
|
+
getAesModeFromFullSegmentMethod,
|
|
14
|
+
isFullSegmentEncryption,
|
|
15
|
+
} from '../utils/encryption-methods-util';
|
|
16
|
+
import type { HlsConfig } from '../config';
|
|
17
|
+
import type { HlsEventEmitter } from '../events';
|
|
18
|
+
import type { DecryptData } from '../loader/level-key';
|
|
19
|
+
import type { Demuxer, DemuxerResult, KeyData } from '../types/demuxer';
|
|
20
|
+
import type { Remuxer } from '../types/remuxer';
|
|
21
|
+
import type { ChunkMetadata, TransmuxerResult } from '../types/transmuxer';
|
|
22
|
+
import type { TypeSupported } from '../utils/codecs';
|
|
23
|
+
import type { ILogger } from '../utils/logger';
|
|
24
|
+
import type { TimestampOffset } from '../utils/timescale-conversion';
|
|
25
|
+
|
|
26
|
+
let now: () => number;
|
|
27
|
+
// performance.now() not available on WebWorker, at least on Safari Desktop
|
|
28
|
+
try {
|
|
29
|
+
now = self.performance.now.bind(self.performance);
|
|
30
|
+
} catch (err) {
|
|
31
|
+
now = Date.now;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
type MuxConfig =
|
|
35
|
+
| { demux: typeof MP4Demuxer; remux: typeof PassThroughRemuxer }
|
|
36
|
+
| { demux: typeof TSDemuxer; remux: typeof MP4Remuxer }
|
|
37
|
+
| { demux: typeof AC3Demuxer; remux: typeof MP4Remuxer }
|
|
38
|
+
| { demux: typeof AACDemuxer; remux: typeof MP4Remuxer }
|
|
39
|
+
| { demux: typeof MP3Demuxer; remux: typeof MP4Remuxer };
|
|
40
|
+
|
|
41
|
+
const muxConfig: MuxConfig[] = [
|
|
42
|
+
{ demux: MP4Demuxer, remux: PassThroughRemuxer },
|
|
43
|
+
{ demux: TSDemuxer, remux: MP4Remuxer },
|
|
44
|
+
{ demux: AACDemuxer, remux: MP4Remuxer },
|
|
45
|
+
{ demux: MP3Demuxer, remux: MP4Remuxer },
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
if (__USE_M2TS_ADVANCED_CODECS__) {
|
|
49
|
+
muxConfig.splice(2, 0, { demux: AC3Demuxer, remux: MP4Remuxer });
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export default class Transmuxer {
|
|
53
|
+
private asyncResult: boolean = false;
|
|
54
|
+
private logger: ILogger;
|
|
55
|
+
private observer: HlsEventEmitter;
|
|
56
|
+
private typeSupported: TypeSupported;
|
|
57
|
+
private config: HlsConfig;
|
|
58
|
+
private id: PlaylistLevelType;
|
|
59
|
+
private demuxer?: Demuxer;
|
|
60
|
+
private remuxer?: Remuxer;
|
|
61
|
+
private decrypter?: Decrypter;
|
|
62
|
+
private probe!: Function;
|
|
63
|
+
private decryptionPromise: Promise<TransmuxerResult> | null = null;
|
|
64
|
+
private transmuxConfig!: TransmuxConfig;
|
|
65
|
+
private currentTransmuxState!: TransmuxState;
|
|
66
|
+
|
|
67
|
+
constructor(
|
|
68
|
+
observer: HlsEventEmitter,
|
|
69
|
+
typeSupported: TypeSupported,
|
|
70
|
+
config: HlsConfig,
|
|
71
|
+
vendor: string,
|
|
72
|
+
id: PlaylistLevelType,
|
|
73
|
+
logger: ILogger,
|
|
74
|
+
) {
|
|
75
|
+
this.observer = observer;
|
|
76
|
+
this.typeSupported = typeSupported;
|
|
77
|
+
this.config = config;
|
|
78
|
+
this.id = id;
|
|
79
|
+
this.logger = logger;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
configure(transmuxConfig: TransmuxConfig) {
|
|
83
|
+
this.transmuxConfig = transmuxConfig;
|
|
84
|
+
if (this.decrypter) {
|
|
85
|
+
this.decrypter.reset();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
push(
|
|
90
|
+
data: ArrayBuffer,
|
|
91
|
+
decryptdata: DecryptData | null,
|
|
92
|
+
chunkMeta: ChunkMetadata,
|
|
93
|
+
state?: TransmuxState,
|
|
94
|
+
): TransmuxerResult | Promise<TransmuxerResult> {
|
|
95
|
+
const stats = chunkMeta.transmuxing;
|
|
96
|
+
stats.executeStart = now();
|
|
97
|
+
|
|
98
|
+
let uintData: Uint8Array<ArrayBuffer> = new Uint8Array(data);
|
|
99
|
+
const { currentTransmuxState, transmuxConfig } = this;
|
|
100
|
+
if (state) {
|
|
101
|
+
this.currentTransmuxState = state;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const {
|
|
105
|
+
contiguous,
|
|
106
|
+
discontinuity,
|
|
107
|
+
trackSwitch,
|
|
108
|
+
accurateTimeOffset,
|
|
109
|
+
timeOffset,
|
|
110
|
+
initSegmentChange,
|
|
111
|
+
} = state || currentTransmuxState;
|
|
112
|
+
const {
|
|
113
|
+
audioCodec,
|
|
114
|
+
videoCodec,
|
|
115
|
+
defaultInitPts,
|
|
116
|
+
duration,
|
|
117
|
+
initSegmentData,
|
|
118
|
+
} = transmuxConfig;
|
|
119
|
+
|
|
120
|
+
const keyData = getEncryptionType(uintData, decryptdata);
|
|
121
|
+
if (keyData && isFullSegmentEncryption(keyData.method)) {
|
|
122
|
+
const decrypter = this.getDecrypter();
|
|
123
|
+
const aesMode = getAesModeFromFullSegmentMethod(keyData.method);
|
|
124
|
+
|
|
125
|
+
// Software decryption is synchronous; webCrypto is not
|
|
126
|
+
if (decrypter.isSync()) {
|
|
127
|
+
// Software decryption is progressive. Progressive decryption may not return a result on each call. Any cached
|
|
128
|
+
// data is handled in the flush() call
|
|
129
|
+
let decryptedData = decrypter.softwareDecrypt(
|
|
130
|
+
uintData,
|
|
131
|
+
keyData.key.buffer,
|
|
132
|
+
keyData.iv.buffer,
|
|
133
|
+
aesMode,
|
|
134
|
+
);
|
|
135
|
+
// For Low-Latency HLS Parts, decrypt in place, since part parsing is expected on push progress
|
|
136
|
+
const loadingParts = chunkMeta.part > -1;
|
|
137
|
+
if (loadingParts) {
|
|
138
|
+
const data = decrypter.flush();
|
|
139
|
+
decryptedData = data ? data.buffer : data;
|
|
140
|
+
}
|
|
141
|
+
if (!decryptedData) {
|
|
142
|
+
stats.executeEnd = now();
|
|
143
|
+
return emptyResult(chunkMeta);
|
|
144
|
+
}
|
|
145
|
+
uintData = new Uint8Array(decryptedData);
|
|
146
|
+
} else {
|
|
147
|
+
this.asyncResult = true;
|
|
148
|
+
this.decryptionPromise = decrypter
|
|
149
|
+
.webCryptoDecrypt(
|
|
150
|
+
uintData,
|
|
151
|
+
keyData.key.buffer,
|
|
152
|
+
keyData.iv.buffer,
|
|
153
|
+
aesMode,
|
|
154
|
+
)
|
|
155
|
+
.then((decryptedData): TransmuxerResult => {
|
|
156
|
+
// Calling push here is important; if flush() is called while this is still resolving, this ensures that
|
|
157
|
+
// the decrypted data has been transmuxed
|
|
158
|
+
const result = this.push(
|
|
159
|
+
decryptedData,
|
|
160
|
+
null,
|
|
161
|
+
chunkMeta,
|
|
162
|
+
) as TransmuxerResult;
|
|
163
|
+
this.decryptionPromise = null;
|
|
164
|
+
return result;
|
|
165
|
+
});
|
|
166
|
+
return this.decryptionPromise;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const resetMuxers = this.needsProbing(discontinuity, trackSwitch);
|
|
171
|
+
if (resetMuxers) {
|
|
172
|
+
const error = this.configureTransmuxer(uintData);
|
|
173
|
+
if (error) {
|
|
174
|
+
this.logger.warn(`[transmuxer] ${error.message}`);
|
|
175
|
+
this.observer.emit(Events.ERROR, Events.ERROR, {
|
|
176
|
+
type: ErrorTypes.MEDIA_ERROR,
|
|
177
|
+
details: ErrorDetails.FRAG_PARSING_ERROR,
|
|
178
|
+
fatal: false,
|
|
179
|
+
error,
|
|
180
|
+
reason: error.message,
|
|
181
|
+
});
|
|
182
|
+
stats.executeEnd = now();
|
|
183
|
+
return emptyResult(chunkMeta);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (discontinuity || trackSwitch || initSegmentChange || resetMuxers) {
|
|
188
|
+
this.resetInitSegment(
|
|
189
|
+
initSegmentData,
|
|
190
|
+
audioCodec,
|
|
191
|
+
videoCodec,
|
|
192
|
+
duration,
|
|
193
|
+
decryptdata,
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (discontinuity || initSegmentChange || resetMuxers) {
|
|
198
|
+
this.resetInitialTimestamp(defaultInitPts);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (!contiguous) {
|
|
202
|
+
this.resetContiguity();
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const result = this.transmux(
|
|
206
|
+
uintData,
|
|
207
|
+
keyData,
|
|
208
|
+
timeOffset,
|
|
209
|
+
accurateTimeOffset,
|
|
210
|
+
chunkMeta,
|
|
211
|
+
);
|
|
212
|
+
this.asyncResult = isPromise(result);
|
|
213
|
+
|
|
214
|
+
const currentState = this.currentTransmuxState;
|
|
215
|
+
|
|
216
|
+
currentState.contiguous = true;
|
|
217
|
+
currentState.discontinuity = false;
|
|
218
|
+
currentState.trackSwitch = false;
|
|
219
|
+
|
|
220
|
+
stats.executeEnd = now();
|
|
221
|
+
return result;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Due to data caching, flush calls can produce more than one TransmuxerResult (hence the Array type)
|
|
225
|
+
flush(
|
|
226
|
+
chunkMeta: ChunkMetadata,
|
|
227
|
+
): TransmuxerResult[] | Promise<TransmuxerResult[]> {
|
|
228
|
+
const stats = chunkMeta.transmuxing;
|
|
229
|
+
stats.executeStart = now();
|
|
230
|
+
|
|
231
|
+
const { decrypter, currentTransmuxState, decryptionPromise } = this;
|
|
232
|
+
|
|
233
|
+
if (decryptionPromise) {
|
|
234
|
+
this.asyncResult = true;
|
|
235
|
+
// Upon resolution, the decryption promise calls push() and returns its TransmuxerResult up the stack. Therefore
|
|
236
|
+
// only flushing is required for async decryption
|
|
237
|
+
return decryptionPromise.then(() => {
|
|
238
|
+
return this.flush(chunkMeta);
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const transmuxResults: TransmuxerResult[] = [];
|
|
243
|
+
const { timeOffset } = currentTransmuxState;
|
|
244
|
+
if (decrypter) {
|
|
245
|
+
// The decrypter may have data cached, which needs to be demuxed. In this case we'll have two TransmuxResults
|
|
246
|
+
// This happens in the case that we receive only 1 push call for a segment (either for non-progressive downloads,
|
|
247
|
+
// or for progressive downloads with small segments)
|
|
248
|
+
const decryptedData = decrypter.flush();
|
|
249
|
+
if (decryptedData) {
|
|
250
|
+
// Push always returns a TransmuxerResult if decryptdata is null
|
|
251
|
+
transmuxResults.push(
|
|
252
|
+
this.push(decryptedData.buffer, null, chunkMeta) as TransmuxerResult,
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const { demuxer, remuxer } = this;
|
|
258
|
+
if (!demuxer || !remuxer) {
|
|
259
|
+
// If probing failed, then Hls.js has been given content its not able to handle
|
|
260
|
+
stats.executeEnd = now();
|
|
261
|
+
const emptyResults = [emptyResult(chunkMeta)];
|
|
262
|
+
if (this.asyncResult) {
|
|
263
|
+
return Promise.resolve(emptyResults);
|
|
264
|
+
}
|
|
265
|
+
return emptyResults;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const demuxResultOrPromise = demuxer.flush(timeOffset);
|
|
269
|
+
if (isPromise(demuxResultOrPromise)) {
|
|
270
|
+
this.asyncResult = true;
|
|
271
|
+
// Decrypt final SAMPLE-AES samples
|
|
272
|
+
return demuxResultOrPromise.then((demuxResult) => {
|
|
273
|
+
this.flushRemux(transmuxResults, demuxResult, chunkMeta);
|
|
274
|
+
return transmuxResults;
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
this.flushRemux(transmuxResults, demuxResultOrPromise, chunkMeta);
|
|
279
|
+
if (this.asyncResult) {
|
|
280
|
+
return Promise.resolve(transmuxResults);
|
|
281
|
+
}
|
|
282
|
+
return transmuxResults;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
private flushRemux(
|
|
286
|
+
transmuxResults: TransmuxerResult[],
|
|
287
|
+
demuxResult: DemuxerResult,
|
|
288
|
+
chunkMeta: ChunkMetadata,
|
|
289
|
+
) {
|
|
290
|
+
const { audioTrack, videoTrack, id3Track, textTrack } = demuxResult;
|
|
291
|
+
const { accurateTimeOffset, timeOffset } = this.currentTransmuxState;
|
|
292
|
+
this.logger.log(
|
|
293
|
+
`[transmuxer.ts]: Flushed ${this.id} sn: ${chunkMeta.sn}${
|
|
294
|
+
chunkMeta.part > -1 ? ' part: ' + chunkMeta.part : ''
|
|
295
|
+
} of ${this.id === PlaylistLevelType.MAIN ? 'level' : 'track'} ${chunkMeta.level}`,
|
|
296
|
+
);
|
|
297
|
+
const remuxResult = this.remuxer!.remux(
|
|
298
|
+
audioTrack,
|
|
299
|
+
videoTrack,
|
|
300
|
+
id3Track,
|
|
301
|
+
textTrack,
|
|
302
|
+
timeOffset,
|
|
303
|
+
accurateTimeOffset,
|
|
304
|
+
true,
|
|
305
|
+
this.id,
|
|
306
|
+
);
|
|
307
|
+
transmuxResults.push({
|
|
308
|
+
remuxResult,
|
|
309
|
+
chunkMeta,
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
chunkMeta.transmuxing.executeEnd = now();
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
resetInitialTimestamp(defaultInitPts: TimestampOffset | null) {
|
|
316
|
+
const { demuxer, remuxer } = this;
|
|
317
|
+
if (!demuxer || !remuxer) {
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
demuxer.resetTimeStamp(defaultInitPts);
|
|
321
|
+
remuxer.resetTimeStamp(defaultInitPts);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
resetContiguity() {
|
|
325
|
+
const { demuxer, remuxer } = this;
|
|
326
|
+
if (!demuxer || !remuxer) {
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
demuxer.resetContiguity();
|
|
330
|
+
remuxer.resetNextTimestamp();
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
resetInitSegment(
|
|
334
|
+
initSegmentData: Uint8Array | undefined,
|
|
335
|
+
audioCodec: string | undefined,
|
|
336
|
+
videoCodec: string | undefined,
|
|
337
|
+
trackDuration: number,
|
|
338
|
+
decryptdata: DecryptData | null,
|
|
339
|
+
) {
|
|
340
|
+
const { demuxer, remuxer } = this;
|
|
341
|
+
if (!demuxer || !remuxer) {
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
demuxer.resetInitSegment(
|
|
345
|
+
initSegmentData,
|
|
346
|
+
audioCodec,
|
|
347
|
+
videoCodec,
|
|
348
|
+
trackDuration,
|
|
349
|
+
);
|
|
350
|
+
remuxer.resetInitSegment(
|
|
351
|
+
initSegmentData,
|
|
352
|
+
audioCodec,
|
|
353
|
+
videoCodec,
|
|
354
|
+
decryptdata,
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
destroy(): void {
|
|
359
|
+
if (this.demuxer) {
|
|
360
|
+
this.demuxer.destroy();
|
|
361
|
+
this.demuxer = undefined;
|
|
362
|
+
}
|
|
363
|
+
if (this.remuxer) {
|
|
364
|
+
this.remuxer.destroy();
|
|
365
|
+
this.remuxer = undefined;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
private transmux(
|
|
370
|
+
data: Uint8Array,
|
|
371
|
+
keyData: KeyData | null,
|
|
372
|
+
timeOffset: number,
|
|
373
|
+
accurateTimeOffset: boolean,
|
|
374
|
+
chunkMeta: ChunkMetadata,
|
|
375
|
+
): TransmuxerResult | Promise<TransmuxerResult> {
|
|
376
|
+
let result: TransmuxerResult | Promise<TransmuxerResult>;
|
|
377
|
+
if (keyData?.method === 'SAMPLE-AES') {
|
|
378
|
+
result = this.transmuxSampleAes(
|
|
379
|
+
data,
|
|
380
|
+
keyData,
|
|
381
|
+
timeOffset,
|
|
382
|
+
accurateTimeOffset,
|
|
383
|
+
chunkMeta,
|
|
384
|
+
);
|
|
385
|
+
} else {
|
|
386
|
+
result = this.transmuxUnencrypted(
|
|
387
|
+
data,
|
|
388
|
+
timeOffset,
|
|
389
|
+
accurateTimeOffset,
|
|
390
|
+
chunkMeta,
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
return result;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
private transmuxUnencrypted(
|
|
397
|
+
data: Uint8Array,
|
|
398
|
+
timeOffset: number,
|
|
399
|
+
accurateTimeOffset: boolean,
|
|
400
|
+
chunkMeta: ChunkMetadata,
|
|
401
|
+
): TransmuxerResult {
|
|
402
|
+
const { audioTrack, videoTrack, id3Track, textTrack } = (
|
|
403
|
+
this.demuxer as Demuxer
|
|
404
|
+
).demux(data, timeOffset, false, !this.config.progressive);
|
|
405
|
+
const remuxResult = this.remuxer!.remux(
|
|
406
|
+
audioTrack,
|
|
407
|
+
videoTrack,
|
|
408
|
+
id3Track,
|
|
409
|
+
textTrack,
|
|
410
|
+
timeOffset,
|
|
411
|
+
accurateTimeOffset,
|
|
412
|
+
false,
|
|
413
|
+
this.id,
|
|
414
|
+
);
|
|
415
|
+
return {
|
|
416
|
+
remuxResult,
|
|
417
|
+
chunkMeta,
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
private transmuxSampleAes(
|
|
422
|
+
data: Uint8Array,
|
|
423
|
+
decryptData: KeyData,
|
|
424
|
+
timeOffset: number,
|
|
425
|
+
accurateTimeOffset: boolean,
|
|
426
|
+
chunkMeta: ChunkMetadata,
|
|
427
|
+
): Promise<TransmuxerResult> {
|
|
428
|
+
return (this.demuxer as Demuxer)
|
|
429
|
+
.demuxSampleAes(data, decryptData, timeOffset)
|
|
430
|
+
.then((demuxResult) => {
|
|
431
|
+
const remuxResult = this.remuxer!.remux(
|
|
432
|
+
demuxResult.audioTrack,
|
|
433
|
+
demuxResult.videoTrack,
|
|
434
|
+
demuxResult.id3Track,
|
|
435
|
+
demuxResult.textTrack,
|
|
436
|
+
timeOffset,
|
|
437
|
+
accurateTimeOffset,
|
|
438
|
+
false,
|
|
439
|
+
this.id,
|
|
440
|
+
);
|
|
441
|
+
return {
|
|
442
|
+
remuxResult,
|
|
443
|
+
chunkMeta,
|
|
444
|
+
};
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
private configureTransmuxer(data: Uint8Array): void | Error {
|
|
449
|
+
const { config, observer, typeSupported } = this;
|
|
450
|
+
// probe for content type
|
|
451
|
+
let mux;
|
|
452
|
+
for (let i = 0, len = muxConfig.length; i < len; i++) {
|
|
453
|
+
if (muxConfig[i].demux?.probe(data, this.logger)) {
|
|
454
|
+
mux = muxConfig[i];
|
|
455
|
+
break;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
if (!mux) {
|
|
459
|
+
return new Error('Failed to find demuxer by probing fragment data');
|
|
460
|
+
}
|
|
461
|
+
// so let's check that current remuxer and demuxer are still valid
|
|
462
|
+
const demuxer = this.demuxer;
|
|
463
|
+
const remuxer = this.remuxer;
|
|
464
|
+
const Remuxer: MuxConfig['remux'] = mux.remux;
|
|
465
|
+
const Demuxer: MuxConfig['demux'] = mux.demux;
|
|
466
|
+
if (!remuxer || !(remuxer instanceof Remuxer)) {
|
|
467
|
+
this.remuxer = new Remuxer(observer, config, typeSupported, this.logger);
|
|
468
|
+
}
|
|
469
|
+
if (!demuxer || !(demuxer instanceof Demuxer)) {
|
|
470
|
+
this.demuxer = new Demuxer(observer, config, typeSupported, this.logger);
|
|
471
|
+
this.probe = Demuxer.probe;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
private needsProbing(discontinuity: boolean, trackSwitch: boolean): boolean {
|
|
476
|
+
// in case of continuity change, or track switch
|
|
477
|
+
// we might switch from content type (AAC container to TS container, or TS to fmp4 for example)
|
|
478
|
+
return !this.demuxer || !this.remuxer || discontinuity || trackSwitch;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
private getDecrypter(): Decrypter {
|
|
482
|
+
let decrypter = this.decrypter;
|
|
483
|
+
if (!decrypter) {
|
|
484
|
+
decrypter = this.decrypter = new Decrypter(this.config);
|
|
485
|
+
}
|
|
486
|
+
return decrypter;
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
function getEncryptionType(
|
|
491
|
+
data: Uint8Array,
|
|
492
|
+
decryptData: DecryptData | null,
|
|
493
|
+
): KeyData | null {
|
|
494
|
+
let encryptionType: KeyData | null = null;
|
|
495
|
+
if (
|
|
496
|
+
data.byteLength > 0 &&
|
|
497
|
+
decryptData?.key != null &&
|
|
498
|
+
decryptData.iv !== null &&
|
|
499
|
+
decryptData.method != null
|
|
500
|
+
) {
|
|
501
|
+
encryptionType = decryptData as KeyData;
|
|
502
|
+
}
|
|
503
|
+
return encryptionType;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
const emptyResult = (chunkMeta): TransmuxerResult => ({
|
|
507
|
+
remuxResult: {},
|
|
508
|
+
chunkMeta,
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
export function isPromise<T>(p: Promise<T> | any): p is Promise<T> {
|
|
512
|
+
return 'then' in p && p.then instanceof Function;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
export class TransmuxConfig {
|
|
516
|
+
public audioCodec?: string;
|
|
517
|
+
public videoCodec?: string;
|
|
518
|
+
public initSegmentData?: Uint8Array;
|
|
519
|
+
public duration: number;
|
|
520
|
+
public defaultInitPts: TimestampOffset | null;
|
|
521
|
+
|
|
522
|
+
constructor(
|
|
523
|
+
audioCodec: string | undefined,
|
|
524
|
+
videoCodec: string | undefined,
|
|
525
|
+
initSegmentData: Uint8Array | undefined,
|
|
526
|
+
duration: number,
|
|
527
|
+
defaultInitPts?: TimestampOffset,
|
|
528
|
+
) {
|
|
529
|
+
this.audioCodec = audioCodec;
|
|
530
|
+
this.videoCodec = videoCodec;
|
|
531
|
+
this.initSegmentData = initSegmentData;
|
|
532
|
+
this.duration = duration;
|
|
533
|
+
this.defaultInitPts = defaultInitPts || null;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
export class TransmuxState {
|
|
538
|
+
public discontinuity: boolean;
|
|
539
|
+
public contiguous: boolean;
|
|
540
|
+
public accurateTimeOffset: boolean;
|
|
541
|
+
public trackSwitch: boolean;
|
|
542
|
+
public timeOffset: number;
|
|
543
|
+
public initSegmentChange: boolean;
|
|
544
|
+
|
|
545
|
+
constructor(
|
|
546
|
+
discontinuity: boolean,
|
|
547
|
+
contiguous: boolean,
|
|
548
|
+
accurateTimeOffset: boolean,
|
|
549
|
+
trackSwitch: boolean,
|
|
550
|
+
timeOffset: number,
|
|
551
|
+
initSegmentChange: boolean,
|
|
552
|
+
) {
|
|
553
|
+
this.discontinuity = discontinuity;
|
|
554
|
+
this.contiguous = contiguous;
|
|
555
|
+
this.accurateTimeOffset = accurateTimeOffset;
|
|
556
|
+
this.trackSwitch = trackSwitch;
|
|
557
|
+
this.timeOffset = timeOffset;
|
|
558
|
+
this.initSegmentChange = initSegmentChange;
|
|
559
|
+
}
|
|
560
|
+
}
|