UNPKG

241 kBJavaScriptView Raw
1/*! @name @videojs/http-streaming @version 2.1.0 @license Apache-2.0 */
2var transmuxerWorker = (function () {
3 'use strict';
4
5 /**
6 * mux.js
7 *
8 * Copyright (c) Brightcove
9 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
10 *
11 * A lightweight readable stream implemention that handles event dispatching.
12 * Objects that inherit from streams should call init in their constructors.
13 */
14
15 var Stream = function() {
16 this.init = function() {
17 var listeners = {};
18 /**
19 * Add a listener for a specified event type.
20 * @param type {string} the event name
21 * @param listener {function} the callback to be invoked when an event of
22 * the specified type occurs
23 */
24 this.on = function(type, listener) {
25 if (!listeners[type]) {
26 listeners[type] = [];
27 }
28 listeners[type] = listeners[type].concat(listener);
29 };
30 /**
31 * Remove a listener for a specified event type.
32 * @param type {string} the event name
33 * @param listener {function} a function previously registered for this
34 * type of event through `on`
35 */
36 this.off = function(type, listener) {
37 var index;
38 if (!listeners[type]) {
39 return false;
40 }
41 index = listeners[type].indexOf(listener);
42 listeners[type] = listeners[type].slice();
43 listeners[type].splice(index, 1);
44 return index > -1;
45 };
46 /**
47 * Trigger an event of the specified type on this stream. Any additional
48 * arguments to this function are passed as parameters to event listeners.
49 * @param type {string} the event name
50 */
51 this.trigger = function(type) {
52 var callbacks, i, length, args;
53 callbacks = listeners[type];
54 if (!callbacks) {
55 return;
56 }
57 // Slicing the arguments on every invocation of this method
58 // can add a significant amount of overhead. Avoid the
59 // intermediate object creation for the common case of a
60 // single callback argument
61 if (arguments.length === 2) {
62 length = callbacks.length;
63 for (i = 0; i < length; ++i) {
64 callbacks[i].call(this, arguments[1]);
65 }
66 } else {
67 args = [];
68 i = arguments.length;
69 for (i = 1; i < arguments.length; ++i) {
70 args.push(arguments[i]);
71 }
72 length = callbacks.length;
73 for (i = 0; i < length; ++i) {
74 callbacks[i].apply(this, args);
75 }
76 }
77 };
78 /**
79 * Destroys the stream and cleans up.
80 */
81 this.dispose = function() {
82 listeners = {};
83 };
84 };
85 };
86
87 /**
88 * Forwards all `data` events on this stream to the destination stream. The
89 * destination stream should provide a method `push` to receive the data
90 * events as they arrive.
91 * @param destination {stream} the stream that will receive all `data` events
92 * @param autoFlush {boolean} if false, we will not call `flush` on the destination
93 * when the current stream emits a 'done' event
94 * @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
95 */
96 Stream.prototype.pipe = function(destination) {
97 this.on('data', function(data) {
98 destination.push(data);
99 });
100
101 this.on('done', function(flushSource) {
102 destination.flush(flushSource);
103 });
104
105 this.on('partialdone', function(flushSource) {
106 destination.partialFlush(flushSource);
107 });
108
109 this.on('endedtimeline', function(flushSource) {
110 destination.endTimeline(flushSource);
111 });
112
113 this.on('reset', function(flushSource) {
114 destination.reset(flushSource);
115 });
116
117 return destination;
118 };
119
120 // Default stream functions that are expected to be overridden to perform
121 // actual work. These are provided by the prototype as a sort of no-op
122 // implementation so that we don't have to check for their existence in the
123 // `pipe` function above.
124 Stream.prototype.push = function(data) {
125 this.trigger('data', data);
126 };
127
128 Stream.prototype.flush = function(flushSource) {
129 this.trigger('done', flushSource);
130 };
131
132 Stream.prototype.partialFlush = function(flushSource) {
133 this.trigger('partialdone', flushSource);
134 };
135
136 Stream.prototype.endTimeline = function(flushSource) {
137 this.trigger('endedtimeline', flushSource);
138 };
139
140 Stream.prototype.reset = function(flushSource) {
141 this.trigger('reset', flushSource);
142 };
143
144 var stream = Stream;
145
146 /**
147 * mux.js
148 *
149 * Copyright (c) Brightcove
150 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
151 *
152 * Functions that generate fragmented MP4s suitable for use with Media
153 * Source Extensions.
154 */
155
156 var UINT32_MAX = Math.pow(2, 32) - 1;
157
158 var box, dinf, esds, ftyp, mdat, mfhd, minf, moof, moov, mvex, mvhd,
159 trak, tkhd, mdia, mdhd, hdlr, sdtp, stbl, stsd, traf, trex,
160 trun, types, MAJOR_BRAND, MINOR_VERSION, AVC1_BRAND, VIDEO_HDLR,
161 AUDIO_HDLR, HDLR_TYPES, VMHD, SMHD, DREF, STCO, STSC, STSZ, STTS;
162
163 // pre-calculate constants
164 (function() {
165 var i;
166 types = {
167 avc1: [], // codingname
168 avcC: [],
169 btrt: [],
170 dinf: [],
171 dref: [],
172 esds: [],
173 ftyp: [],
174 hdlr: [],
175 mdat: [],
176 mdhd: [],
177 mdia: [],
178 mfhd: [],
179 minf: [],
180 moof: [],
181 moov: [],
182 mp4a: [], // codingname
183 mvex: [],
184 mvhd: [],
185 pasp: [],
186 sdtp: [],
187 smhd: [],
188 stbl: [],
189 stco: [],
190 stsc: [],
191 stsd: [],
192 stsz: [],
193 stts: [],
194 styp: [],
195 tfdt: [],
196 tfhd: [],
197 traf: [],
198 trak: [],
199 trun: [],
200 trex: [],
201 tkhd: [],
202 vmhd: []
203 };
204
205 // In environments where Uint8Array is undefined (e.g., IE8), skip set up so that we
206 // don't throw an error
207 if (typeof Uint8Array === 'undefined') {
208 return;
209 }
210
211 for (i in types) {
212 if (types.hasOwnProperty(i)) {
213 types[i] = [
214 i.charCodeAt(0),
215 i.charCodeAt(1),
216 i.charCodeAt(2),
217 i.charCodeAt(3)
218 ];
219 }
220 }
221
222 MAJOR_BRAND = new Uint8Array([
223 'i'.charCodeAt(0),
224 's'.charCodeAt(0),
225 'o'.charCodeAt(0),
226 'm'.charCodeAt(0)
227 ]);
228 AVC1_BRAND = new Uint8Array([
229 'a'.charCodeAt(0),
230 'v'.charCodeAt(0),
231 'c'.charCodeAt(0),
232 '1'.charCodeAt(0)
233 ]);
234 MINOR_VERSION = new Uint8Array([0, 0, 0, 1]);
235 VIDEO_HDLR = new Uint8Array([
236 0x00, // version 0
237 0x00, 0x00, 0x00, // flags
238 0x00, 0x00, 0x00, 0x00, // pre_defined
239 0x76, 0x69, 0x64, 0x65, // handler_type: 'vide'
240 0x00, 0x00, 0x00, 0x00, // reserved
241 0x00, 0x00, 0x00, 0x00, // reserved
242 0x00, 0x00, 0x00, 0x00, // reserved
243 0x56, 0x69, 0x64, 0x65,
244 0x6f, 0x48, 0x61, 0x6e,
245 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'VideoHandler'
246 ]);
247 AUDIO_HDLR = new Uint8Array([
248 0x00, // version 0
249 0x00, 0x00, 0x00, // flags
250 0x00, 0x00, 0x00, 0x00, // pre_defined
251 0x73, 0x6f, 0x75, 0x6e, // handler_type: 'soun'
252 0x00, 0x00, 0x00, 0x00, // reserved
253 0x00, 0x00, 0x00, 0x00, // reserved
254 0x00, 0x00, 0x00, 0x00, // reserved
255 0x53, 0x6f, 0x75, 0x6e,
256 0x64, 0x48, 0x61, 0x6e,
257 0x64, 0x6c, 0x65, 0x72, 0x00 // name: 'SoundHandler'
258 ]);
259 HDLR_TYPES = {
260 video: VIDEO_HDLR,
261 audio: AUDIO_HDLR
262 };
263 DREF = new Uint8Array([
264 0x00, // version 0
265 0x00, 0x00, 0x00, // flags
266 0x00, 0x00, 0x00, 0x01, // entry_count
267 0x00, 0x00, 0x00, 0x0c, // entry_size
268 0x75, 0x72, 0x6c, 0x20, // 'url' type
269 0x00, // version 0
270 0x00, 0x00, 0x01 // entry_flags
271 ]);
272 SMHD = new Uint8Array([
273 0x00, // version
274 0x00, 0x00, 0x00, // flags
275 0x00, 0x00, // balance, 0 means centered
276 0x00, 0x00 // reserved
277 ]);
278 STCO = new Uint8Array([
279 0x00, // version
280 0x00, 0x00, 0x00, // flags
281 0x00, 0x00, 0x00, 0x00 // entry_count
282 ]);
283 STSC = STCO;
284 STSZ = new Uint8Array([
285 0x00, // version
286 0x00, 0x00, 0x00, // flags
287 0x00, 0x00, 0x00, 0x00, // sample_size
288 0x00, 0x00, 0x00, 0x00 // sample_count
289 ]);
290 STTS = STCO;
291 VMHD = new Uint8Array([
292 0x00, // version
293 0x00, 0x00, 0x01, // flags
294 0x00, 0x00, // graphicsmode
295 0x00, 0x00,
296 0x00, 0x00,
297 0x00, 0x00 // opcolor
298 ]);
299 }());
300
301 box = function(type) {
302 var
303 payload = [],
304 size = 0,
305 i,
306 result,
307 view;
308
309 for (i = 1; i < arguments.length; i++) {
310 payload.push(arguments[i]);
311 }
312
313 i = payload.length;
314
315 // calculate the total size we need to allocate
316 while (i--) {
317 size += payload[i].byteLength;
318 }
319 result = new Uint8Array(size + 8);
320 view = new DataView(result.buffer, result.byteOffset, result.byteLength);
321 view.setUint32(0, result.byteLength);
322 result.set(type, 4);
323
324 // copy the payload into the result
325 for (i = 0, size = 8; i < payload.length; i++) {
326 result.set(payload[i], size);
327 size += payload[i].byteLength;
328 }
329 return result;
330 };
331
332 dinf = function() {
333 return box(types.dinf, box(types.dref, DREF));
334 };
335
336 esds = function(track) {
337 return box(types.esds, new Uint8Array([
338 0x00, // version
339 0x00, 0x00, 0x00, // flags
340
341 // ES_Descriptor
342 0x03, // tag, ES_DescrTag
343 0x19, // length
344 0x00, 0x00, // ES_ID
345 0x00, // streamDependenceFlag, URL_flag, reserved, streamPriority
346
347 // DecoderConfigDescriptor
348 0x04, // tag, DecoderConfigDescrTag
349 0x11, // length
350 0x40, // object type
351 0x15, // streamType
352 0x00, 0x06, 0x00, // bufferSizeDB
353 0x00, 0x00, 0xda, 0xc0, // maxBitrate
354 0x00, 0x00, 0xda, 0xc0, // avgBitrate
355
356 // DecoderSpecificInfo
357 0x05, // tag, DecoderSpecificInfoTag
358 0x02, // length
359 // ISO/IEC 14496-3, AudioSpecificConfig
360 // for samplingFrequencyIndex see ISO/IEC 13818-7:2006, 8.1.3.2.2, Table 35
361 (track.audioobjecttype << 3) | (track.samplingfrequencyindex >>> 1),
362 (track.samplingfrequencyindex << 7) | (track.channelcount << 3),
363 0x06, 0x01, 0x02 // GASpecificConfig
364 ]));
365 };
366
367 ftyp = function() {
368 return box(types.ftyp, MAJOR_BRAND, MINOR_VERSION, MAJOR_BRAND, AVC1_BRAND);
369 };
370
371 hdlr = function(type) {
372 return box(types.hdlr, HDLR_TYPES[type]);
373 };
374 mdat = function(data) {
375 return box(types.mdat, data);
376 };
377 mdhd = function(track) {
378 var result = new Uint8Array([
379 0x00, // version 0
380 0x00, 0x00, 0x00, // flags
381 0x00, 0x00, 0x00, 0x02, // creation_time
382 0x00, 0x00, 0x00, 0x03, // modification_time
383 0x00, 0x01, 0x5f, 0x90, // timescale, 90,000 "ticks" per second
384
385 (track.duration >>> 24) & 0xFF,
386 (track.duration >>> 16) & 0xFF,
387 (track.duration >>> 8) & 0xFF,
388 track.duration & 0xFF, // duration
389 0x55, 0xc4, // 'und' language (undetermined)
390 0x00, 0x00
391 ]);
392
393 // Use the sample rate from the track metadata, when it is
394 // defined. The sample rate can be parsed out of an ADTS header, for
395 // instance.
396 if (track.samplerate) {
397 result[12] = (track.samplerate >>> 24) & 0xFF;
398 result[13] = (track.samplerate >>> 16) & 0xFF;
399 result[14] = (track.samplerate >>> 8) & 0xFF;
400 result[15] = (track.samplerate) & 0xFF;
401 }
402
403 return box(types.mdhd, result);
404 };
405 mdia = function(track) {
406 return box(types.mdia, mdhd(track), hdlr(track.type), minf(track));
407 };
408 mfhd = function(sequenceNumber) {
409 return box(types.mfhd, new Uint8Array([
410 0x00,
411 0x00, 0x00, 0x00, // flags
412 (sequenceNumber & 0xFF000000) >> 24,
413 (sequenceNumber & 0xFF0000) >> 16,
414 (sequenceNumber & 0xFF00) >> 8,
415 sequenceNumber & 0xFF // sequence_number
416 ]));
417 };
418 minf = function(track) {
419 return box(types.minf,
420 track.type === 'video' ? box(types.vmhd, VMHD) : box(types.smhd, SMHD),
421 dinf(),
422 stbl(track));
423 };
424 moof = function(sequenceNumber, tracks) {
425 var
426 trackFragments = [],
427 i = tracks.length;
428 // build traf boxes for each track fragment
429 while (i--) {
430 trackFragments[i] = traf(tracks[i]);
431 }
432 return box.apply(null, [
433 types.moof,
434 mfhd(sequenceNumber)
435 ].concat(trackFragments));
436 };
437 /**
438 * Returns a movie box.
439 * @param tracks {array} the tracks associated with this movie
440 * @see ISO/IEC 14496-12:2012(E), section 8.2.1
441 */
442 moov = function(tracks) {
443 var
444 i = tracks.length,
445 boxes = [];
446
447 while (i--) {
448 boxes[i] = trak(tracks[i]);
449 }
450
451 return box.apply(null, [types.moov, mvhd(0xffffffff)].concat(boxes).concat(mvex(tracks)));
452 };
453 mvex = function(tracks) {
454 var
455 i = tracks.length,
456 boxes = [];
457
458 while (i--) {
459 boxes[i] = trex(tracks[i]);
460 }
461 return box.apply(null, [types.mvex].concat(boxes));
462 };
463 mvhd = function(duration) {
464 var
465 bytes = new Uint8Array([
466 0x00, // version 0
467 0x00, 0x00, 0x00, // flags
468 0x00, 0x00, 0x00, 0x01, // creation_time
469 0x00, 0x00, 0x00, 0x02, // modification_time
470 0x00, 0x01, 0x5f, 0x90, // timescale, 90,000 "ticks" per second
471 (duration & 0xFF000000) >> 24,
472 (duration & 0xFF0000) >> 16,
473 (duration & 0xFF00) >> 8,
474 duration & 0xFF, // duration
475 0x00, 0x01, 0x00, 0x00, // 1.0 rate
476 0x01, 0x00, // 1.0 volume
477 0x00, 0x00, // reserved
478 0x00, 0x00, 0x00, 0x00, // reserved
479 0x00, 0x00, 0x00, 0x00, // reserved
480 0x00, 0x01, 0x00, 0x00,
481 0x00, 0x00, 0x00, 0x00,
482 0x00, 0x00, 0x00, 0x00,
483 0x00, 0x00, 0x00, 0x00,
484 0x00, 0x01, 0x00, 0x00,
485 0x00, 0x00, 0x00, 0x00,
486 0x00, 0x00, 0x00, 0x00,
487 0x00, 0x00, 0x00, 0x00,
488 0x40, 0x00, 0x00, 0x00, // transformation: unity matrix
489 0x00, 0x00, 0x00, 0x00,
490 0x00, 0x00, 0x00, 0x00,
491 0x00, 0x00, 0x00, 0x00,
492 0x00, 0x00, 0x00, 0x00,
493 0x00, 0x00, 0x00, 0x00,
494 0x00, 0x00, 0x00, 0x00, // pre_defined
495 0xff, 0xff, 0xff, 0xff // next_track_ID
496 ]);
497 return box(types.mvhd, bytes);
498 };
499
500 sdtp = function(track) {
501 var
502 samples = track.samples || [],
503 bytes = new Uint8Array(4 + samples.length),
504 flags,
505 i;
506
507 // leave the full box header (4 bytes) all zero
508
509 // write the sample table
510 for (i = 0; i < samples.length; i++) {
511 flags = samples[i].flags;
512
513 bytes[i + 4] = (flags.dependsOn << 4) |
514 (flags.isDependedOn << 2) |
515 (flags.hasRedundancy);
516 }
517
518 return box(types.sdtp,
519 bytes);
520 };
521
522 stbl = function(track) {
523 return box(types.stbl,
524 stsd(track),
525 box(types.stts, STTS),
526 box(types.stsc, STSC),
527 box(types.stsz, STSZ),
528 box(types.stco, STCO));
529 };
530
531 (function() {
532 var videoSample, audioSample;
533
534 stsd = function(track) {
535
536 return box(types.stsd, new Uint8Array([
537 0x00, // version 0
538 0x00, 0x00, 0x00, // flags
539 0x00, 0x00, 0x00, 0x01
540 ]), track.type === 'video' ? videoSample(track) : audioSample(track));
541 };
542
543 videoSample = function(track) {
544 var
545 sps = track.sps || [],
546 pps = track.pps || [],
547 sequenceParameterSets = [],
548 pictureParameterSets = [],
549 i,
550 avc1Box;
551
552 // assemble the SPSs
553 for (i = 0; i < sps.length; i++) {
554 sequenceParameterSets.push((sps[i].byteLength & 0xFF00) >>> 8);
555 sequenceParameterSets.push((sps[i].byteLength & 0xFF)); // sequenceParameterSetLength
556 sequenceParameterSets = sequenceParameterSets.concat(Array.prototype.slice.call(sps[i])); // SPS
557 }
558
559 // assemble the PPSs
560 for (i = 0; i < pps.length; i++) {
561 pictureParameterSets.push((pps[i].byteLength & 0xFF00) >>> 8);
562 pictureParameterSets.push((pps[i].byteLength & 0xFF));
563 pictureParameterSets = pictureParameterSets.concat(Array.prototype.slice.call(pps[i]));
564 }
565
566 avc1Box = [
567 types.avc1, new Uint8Array([
568 0x00, 0x00, 0x00,
569 0x00, 0x00, 0x00, // reserved
570 0x00, 0x01, // data_reference_index
571 0x00, 0x00, // pre_defined
572 0x00, 0x00, // reserved
573 0x00, 0x00, 0x00, 0x00,
574 0x00, 0x00, 0x00, 0x00,
575 0x00, 0x00, 0x00, 0x00, // pre_defined
576 (track.width & 0xff00) >> 8,
577 track.width & 0xff, // width
578 (track.height & 0xff00) >> 8,
579 track.height & 0xff, // height
580 0x00, 0x48, 0x00, 0x00, // horizresolution
581 0x00, 0x48, 0x00, 0x00, // vertresolution
582 0x00, 0x00, 0x00, 0x00, // reserved
583 0x00, 0x01, // frame_count
584 0x13,
585 0x76, 0x69, 0x64, 0x65,
586 0x6f, 0x6a, 0x73, 0x2d,
587 0x63, 0x6f, 0x6e, 0x74,
588 0x72, 0x69, 0x62, 0x2d,
589 0x68, 0x6c, 0x73, 0x00,
590 0x00, 0x00, 0x00, 0x00,
591 0x00, 0x00, 0x00, 0x00,
592 0x00, 0x00, 0x00, // compressorname
593 0x00, 0x18, // depth = 24
594 0x11, 0x11 // pre_defined = -1
595 ]),
596 box(types.avcC, new Uint8Array([
597 0x01, // configurationVersion
598 track.profileIdc, // AVCProfileIndication
599 track.profileCompatibility, // profile_compatibility
600 track.levelIdc, // AVCLevelIndication
601 0xff // lengthSizeMinusOne, hard-coded to 4 bytes
602 ].concat(
603 [sps.length], // numOfSequenceParameterSets
604 sequenceParameterSets, // "SPS"
605 [pps.length], // numOfPictureParameterSets
606 pictureParameterSets // "PPS"
607 ))),
608 box(types.btrt, new Uint8Array([
609 0x00, 0x1c, 0x9c, 0x80, // bufferSizeDB
610 0x00, 0x2d, 0xc6, 0xc0, // maxBitrate
611 0x00, 0x2d, 0xc6, 0xc0 // avgBitrate
612 ]))
613 ];
614
615 if (track.sarRatio) {
616 var
617 hSpacing = track.sarRatio[0],
618 vSpacing = track.sarRatio[1];
619
620 avc1Box.push(
621 box(types.pasp, new Uint8Array([
622 (hSpacing & 0xFF000000) >> 24,
623 (hSpacing & 0xFF0000) >> 16,
624 (hSpacing & 0xFF00) >> 8,
625 hSpacing & 0xFF,
626 (vSpacing & 0xFF000000) >> 24,
627 (vSpacing & 0xFF0000) >> 16,
628 (vSpacing & 0xFF00) >> 8,
629 vSpacing & 0xFF
630 ]))
631 );
632 }
633
634 return box.apply(null, avc1Box);
635 };
636
637 audioSample = function(track) {
638 return box(types.mp4a, new Uint8Array([
639
640 // SampleEntry, ISO/IEC 14496-12
641 0x00, 0x00, 0x00,
642 0x00, 0x00, 0x00, // reserved
643 0x00, 0x01, // data_reference_index
644
645 // AudioSampleEntry, ISO/IEC 14496-12
646 0x00, 0x00, 0x00, 0x00, // reserved
647 0x00, 0x00, 0x00, 0x00, // reserved
648 (track.channelcount & 0xff00) >> 8,
649 (track.channelcount & 0xff), // channelcount
650
651 (track.samplesize & 0xff00) >> 8,
652 (track.samplesize & 0xff), // samplesize
653 0x00, 0x00, // pre_defined
654 0x00, 0x00, // reserved
655
656 (track.samplerate & 0xff00) >> 8,
657 (track.samplerate & 0xff),
658 0x00, 0x00 // samplerate, 16.16
659
660 // MP4AudioSampleEntry, ISO/IEC 14496-14
661 ]), esds(track));
662 };
663 }());
664
665 tkhd = function(track) {
666 var result = new Uint8Array([
667 0x00, // version 0
668 0x00, 0x00, 0x07, // flags
669 0x00, 0x00, 0x00, 0x00, // creation_time
670 0x00, 0x00, 0x00, 0x00, // modification_time
671 (track.id & 0xFF000000) >> 24,
672 (track.id & 0xFF0000) >> 16,
673 (track.id & 0xFF00) >> 8,
674 track.id & 0xFF, // track_ID
675 0x00, 0x00, 0x00, 0x00, // reserved
676 (track.duration & 0xFF000000) >> 24,
677 (track.duration & 0xFF0000) >> 16,
678 (track.duration & 0xFF00) >> 8,
679 track.duration & 0xFF, // duration
680 0x00, 0x00, 0x00, 0x00,
681 0x00, 0x00, 0x00, 0x00, // reserved
682 0x00, 0x00, // layer
683 0x00, 0x00, // alternate_group
684 0x01, 0x00, // non-audio track volume
685 0x00, 0x00, // reserved
686 0x00, 0x01, 0x00, 0x00,
687 0x00, 0x00, 0x00, 0x00,
688 0x00, 0x00, 0x00, 0x00,
689 0x00, 0x00, 0x00, 0x00,
690 0x00, 0x01, 0x00, 0x00,
691 0x00, 0x00, 0x00, 0x00,
692 0x00, 0x00, 0x00, 0x00,
693 0x00, 0x00, 0x00, 0x00,
694 0x40, 0x00, 0x00, 0x00, // transformation: unity matrix
695 (track.width & 0xFF00) >> 8,
696 track.width & 0xFF,
697 0x00, 0x00, // width
698 (track.height & 0xFF00) >> 8,
699 track.height & 0xFF,
700 0x00, 0x00 // height
701 ]);
702
703 return box(types.tkhd, result);
704 };
705
706 /**
707 * Generate a track fragment (traf) box. A traf box collects metadata
708 * about tracks in a movie fragment (moof) box.
709 */
710 traf = function(track) {
711 var trackFragmentHeader, trackFragmentDecodeTime, trackFragmentRun,
712 sampleDependencyTable, dataOffset,
713 upperWordBaseMediaDecodeTime, lowerWordBaseMediaDecodeTime;
714
715 trackFragmentHeader = box(types.tfhd, new Uint8Array([
716 0x00, // version 0
717 0x00, 0x00, 0x3a, // flags
718 (track.id & 0xFF000000) >> 24,
719 (track.id & 0xFF0000) >> 16,
720 (track.id & 0xFF00) >> 8,
721 (track.id & 0xFF), // track_ID
722 0x00, 0x00, 0x00, 0x01, // sample_description_index
723 0x00, 0x00, 0x00, 0x00, // default_sample_duration
724 0x00, 0x00, 0x00, 0x00, // default_sample_size
725 0x00, 0x00, 0x00, 0x00 // default_sample_flags
726 ]));
727
728 upperWordBaseMediaDecodeTime = Math.floor(track.baseMediaDecodeTime / (UINT32_MAX + 1));
729 lowerWordBaseMediaDecodeTime = Math.floor(track.baseMediaDecodeTime % (UINT32_MAX + 1));
730
731 trackFragmentDecodeTime = box(types.tfdt, new Uint8Array([
732 0x01, // version 1
733 0x00, 0x00, 0x00, // flags
734 // baseMediaDecodeTime
735 (upperWordBaseMediaDecodeTime >>> 24) & 0xFF,
736 (upperWordBaseMediaDecodeTime >>> 16) & 0xFF,
737 (upperWordBaseMediaDecodeTime >>> 8) & 0xFF,
738 upperWordBaseMediaDecodeTime & 0xFF,
739 (lowerWordBaseMediaDecodeTime >>> 24) & 0xFF,
740 (lowerWordBaseMediaDecodeTime >>> 16) & 0xFF,
741 (lowerWordBaseMediaDecodeTime >>> 8) & 0xFF,
742 lowerWordBaseMediaDecodeTime & 0xFF
743 ]));
744
745 // the data offset specifies the number of bytes from the start of
746 // the containing moof to the first payload byte of the associated
747 // mdat
748 dataOffset = (32 + // tfhd
749 20 + // tfdt
750 8 + // traf header
751 16 + // mfhd
752 8 + // moof header
753 8); // mdat header
754
755 // audio tracks require less metadata
756 if (track.type === 'audio') {
757 trackFragmentRun = trun(track, dataOffset);
758 return box(types.traf,
759 trackFragmentHeader,
760 trackFragmentDecodeTime,
761 trackFragmentRun);
762 }
763
764 // video tracks should contain an independent and disposable samples
765 // box (sdtp)
766 // generate one and adjust offsets to match
767 sampleDependencyTable = sdtp(track);
768 trackFragmentRun = trun(track,
769 sampleDependencyTable.length + dataOffset);
770 return box(types.traf,
771 trackFragmentHeader,
772 trackFragmentDecodeTime,
773 trackFragmentRun,
774 sampleDependencyTable);
775 };
776
777 /**
778 * Generate a track box.
779 * @param track {object} a track definition
780 * @return {Uint8Array} the track box
781 */
782 trak = function(track) {
783 track.duration = track.duration || 0xffffffff;
784 return box(types.trak,
785 tkhd(track),
786 mdia(track));
787 };
788
789 trex = function(track) {
790 var result = new Uint8Array([
791 0x00, // version 0
792 0x00, 0x00, 0x00, // flags
793 (track.id & 0xFF000000) >> 24,
794 (track.id & 0xFF0000) >> 16,
795 (track.id & 0xFF00) >> 8,
796 (track.id & 0xFF), // track_ID
797 0x00, 0x00, 0x00, 0x01, // default_sample_description_index
798 0x00, 0x00, 0x00, 0x00, // default_sample_duration
799 0x00, 0x00, 0x00, 0x00, // default_sample_size
800 0x00, 0x01, 0x00, 0x01 // default_sample_flags
801 ]);
802 // the last two bytes of default_sample_flags is the sample
803 // degradation priority, a hint about the importance of this sample
804 // relative to others. Lower the degradation priority for all sample
805 // types other than video.
806 if (track.type !== 'video') {
807 result[result.length - 1] = 0x00;
808 }
809
810 return box(types.trex, result);
811 };
812
813 (function() {
814 var audioTrun, videoTrun, trunHeader;
815
816 // This method assumes all samples are uniform. That is, if a
817 // duration is present for the first sample, it will be present for
818 // all subsequent samples.
819 // see ISO/IEC 14496-12:2012, Section 8.8.8.1
820 trunHeader = function(samples, offset) {
821 var durationPresent = 0, sizePresent = 0,
822 flagsPresent = 0, compositionTimeOffset = 0;
823
824 // trun flag constants
825 if (samples.length) {
826 if (samples[0].duration !== undefined) {
827 durationPresent = 0x1;
828 }
829 if (samples[0].size !== undefined) {
830 sizePresent = 0x2;
831 }
832 if (samples[0].flags !== undefined) {
833 flagsPresent = 0x4;
834 }
835 if (samples[0].compositionTimeOffset !== undefined) {
836 compositionTimeOffset = 0x8;
837 }
838 }
839
840 return [
841 0x00, // version 0
842 0x00,
843 durationPresent | sizePresent | flagsPresent | compositionTimeOffset,
844 0x01, // flags
845 (samples.length & 0xFF000000) >>> 24,
846 (samples.length & 0xFF0000) >>> 16,
847 (samples.length & 0xFF00) >>> 8,
848 samples.length & 0xFF, // sample_count
849 (offset & 0xFF000000) >>> 24,
850 (offset & 0xFF0000) >>> 16,
851 (offset & 0xFF00) >>> 8,
852 offset & 0xFF // data_offset
853 ];
854 };
855
856 videoTrun = function(track, offset) {
857 var bytesOffest, bytes, header, samples, sample, i;
858
859 samples = track.samples || [];
860 offset += 8 + 12 + (16 * samples.length);
861 header = trunHeader(samples, offset);
862 bytes = new Uint8Array(header.length + samples.length * 16);
863 bytes.set(header);
864 bytesOffest = header.length;
865
866 for (i = 0; i < samples.length; i++) {
867 sample = samples[i];
868
869 bytes[bytesOffest++] = (sample.duration & 0xFF000000) >>> 24;
870 bytes[bytesOffest++] = (sample.duration & 0xFF0000) >>> 16;
871 bytes[bytesOffest++] = (sample.duration & 0xFF00) >>> 8;
872 bytes[bytesOffest++] = sample.duration & 0xFF; // sample_duration
873 bytes[bytesOffest++] = (sample.size & 0xFF000000) >>> 24;
874 bytes[bytesOffest++] = (sample.size & 0xFF0000) >>> 16;
875 bytes[bytesOffest++] = (sample.size & 0xFF00) >>> 8;
876 bytes[bytesOffest++] = sample.size & 0xFF; // sample_size
877 bytes[bytesOffest++] = (sample.flags.isLeading << 2) | sample.flags.dependsOn;
878 bytes[bytesOffest++] = (sample.flags.isDependedOn << 6) |
879 (sample.flags.hasRedundancy << 4) |
880 (sample.flags.paddingValue << 1) |
881 sample.flags.isNonSyncSample;
882 bytes[bytesOffest++] = sample.flags.degradationPriority & 0xF0 << 8;
883 bytes[bytesOffest++] = sample.flags.degradationPriority & 0x0F; // sample_flags
884 bytes[bytesOffest++] = (sample.compositionTimeOffset & 0xFF000000) >>> 24;
885 bytes[bytesOffest++] = (sample.compositionTimeOffset & 0xFF0000) >>> 16;
886 bytes[bytesOffest++] = (sample.compositionTimeOffset & 0xFF00) >>> 8;
887 bytes[bytesOffest++] = sample.compositionTimeOffset & 0xFF; // sample_composition_time_offset
888 }
889 return box(types.trun, bytes);
890 };
891
892 audioTrun = function(track, offset) {
893 var bytes, bytesOffest, header, samples, sample, i;
894
895 samples = track.samples || [];
896 offset += 8 + 12 + (8 * samples.length);
897
898 header = trunHeader(samples, offset);
899 bytes = new Uint8Array(header.length + samples.length * 8);
900 bytes.set(header);
901 bytesOffest = header.length;
902
903 for (i = 0; i < samples.length; i++) {
904 sample = samples[i];
905 bytes[bytesOffest++] = (sample.duration & 0xFF000000) >>> 24;
906 bytes[bytesOffest++] = (sample.duration & 0xFF0000) >>> 16;
907 bytes[bytesOffest++] = (sample.duration & 0xFF00) >>> 8;
908 bytes[bytesOffest++] = sample.duration & 0xFF; // sample_duration
909 bytes[bytesOffest++] = (sample.size & 0xFF000000) >>> 24;
910 bytes[bytesOffest++] = (sample.size & 0xFF0000) >>> 16;
911 bytes[bytesOffest++] = (sample.size & 0xFF00) >>> 8;
912 bytes[bytesOffest++] = sample.size & 0xFF; // sample_size
913 }
914
915 return box(types.trun, bytes);
916 };
917
918 trun = function(track, offset) {
919 if (track.type === 'audio') {
920 return audioTrun(track, offset);
921 }
922
923 return videoTrun(track, offset);
924 };
925 }());
926
927 var mp4Generator = {
928 ftyp: ftyp,
929 mdat: mdat,
930 moof: moof,
931 moov: moov,
932 initSegment: function(tracks) {
933 var
934 fileType = ftyp(),
935 movie = moov(tracks),
936 result;
937
938 result = new Uint8Array(fileType.byteLength + movie.byteLength);
939 result.set(fileType);
940 result.set(movie, fileType.byteLength);
941 return result;
942 }
943 };
944
945 /**
946 * mux.js
947 *
948 * Copyright (c) Brightcove
949 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
950 */
951 // Convert an array of nal units into an array of frames with each frame being
952 // composed of the nal units that make up that frame
953 // Also keep track of cummulative data about the frame from the nal units such
954 // as the frame duration, starting pts, etc.
955 var groupNalsIntoFrames = function(nalUnits) {
956 var
957 i,
958 currentNal,
959 currentFrame = [],
960 frames = [];
961
962 // TODO added for LHLS, make sure this is OK
963 frames.byteLength = 0;
964 frames.nalCount = 0;
965 frames.duration = 0;
966
967 currentFrame.byteLength = 0;
968
969 for (i = 0; i < nalUnits.length; i++) {
970 currentNal = nalUnits[i];
971
972 // Split on 'aud'-type nal units
973 if (currentNal.nalUnitType === 'access_unit_delimiter_rbsp') {
974 // Since the very first nal unit is expected to be an AUD
975 // only push to the frames array when currentFrame is not empty
976 if (currentFrame.length) {
977 currentFrame.duration = currentNal.dts - currentFrame.dts;
978 // TODO added for LHLS, make sure this is OK
979 frames.byteLength += currentFrame.byteLength;
980 frames.nalCount += currentFrame.length;
981 frames.duration += currentFrame.duration;
982 frames.push(currentFrame);
983 }
984 currentFrame = [currentNal];
985 currentFrame.byteLength = currentNal.data.byteLength;
986 currentFrame.pts = currentNal.pts;
987 currentFrame.dts = currentNal.dts;
988 } else {
989 // Specifically flag key frames for ease of use later
990 if (currentNal.nalUnitType === 'slice_layer_without_partitioning_rbsp_idr') {
991 currentFrame.keyFrame = true;
992 }
993 currentFrame.duration = currentNal.dts - currentFrame.dts;
994 currentFrame.byteLength += currentNal.data.byteLength;
995 currentFrame.push(currentNal);
996 }
997 }
998
999 // For the last frame, use the duration of the previous frame if we
1000 // have nothing better to go on
1001 if (frames.length &&
1002 (!currentFrame.duration ||
1003 currentFrame.duration <= 0)) {
1004 currentFrame.duration = frames[frames.length - 1].duration;
1005 }
1006
1007 // Push the final frame
1008 // TODO added for LHLS, make sure this is OK
1009 frames.byteLength += currentFrame.byteLength;
1010 frames.nalCount += currentFrame.length;
1011 frames.duration += currentFrame.duration;
1012
1013 frames.push(currentFrame);
1014 return frames;
1015 };
1016
1017 // Convert an array of frames into an array of Gop with each Gop being composed
1018 // of the frames that make up that Gop
1019 // Also keep track of cummulative data about the Gop from the frames such as the
1020 // Gop duration, starting pts, etc.
1021 var groupFramesIntoGops = function(frames) {
1022 var
1023 i,
1024 currentFrame,
1025 currentGop = [],
1026 gops = [];
1027
1028 // We must pre-set some of the values on the Gop since we
1029 // keep running totals of these values
1030 currentGop.byteLength = 0;
1031 currentGop.nalCount = 0;
1032 currentGop.duration = 0;
1033 currentGop.pts = frames[0].pts;
1034 currentGop.dts = frames[0].dts;
1035
1036 // store some metadata about all the Gops
1037 gops.byteLength = 0;
1038 gops.nalCount = 0;
1039 gops.duration = 0;
1040 gops.pts = frames[0].pts;
1041 gops.dts = frames[0].dts;
1042
1043 for (i = 0; i < frames.length; i++) {
1044 currentFrame = frames[i];
1045
1046 if (currentFrame.keyFrame) {
1047 // Since the very first frame is expected to be an keyframe
1048 // only push to the gops array when currentGop is not empty
1049 if (currentGop.length) {
1050 gops.push(currentGop);
1051 gops.byteLength += currentGop.byteLength;
1052 gops.nalCount += currentGop.nalCount;
1053 gops.duration += currentGop.duration;
1054 }
1055
1056 currentGop = [currentFrame];
1057 currentGop.nalCount = currentFrame.length;
1058 currentGop.byteLength = currentFrame.byteLength;
1059 currentGop.pts = currentFrame.pts;
1060 currentGop.dts = currentFrame.dts;
1061 currentGop.duration = currentFrame.duration;
1062 } else {
1063 currentGop.duration += currentFrame.duration;
1064 currentGop.nalCount += currentFrame.length;
1065 currentGop.byteLength += currentFrame.byteLength;
1066 currentGop.push(currentFrame);
1067 }
1068 }
1069
1070 if (gops.length && currentGop.duration <= 0) {
1071 currentGop.duration = gops[gops.length - 1].duration;
1072 }
1073 gops.byteLength += currentGop.byteLength;
1074 gops.nalCount += currentGop.nalCount;
1075 gops.duration += currentGop.duration;
1076
1077 // push the final Gop
1078 gops.push(currentGop);
1079 return gops;
1080 };
1081
1082 /*
1083 * Search for the first keyframe in the GOPs and throw away all frames
1084 * until that keyframe. Then extend the duration of the pulled keyframe
1085 * and pull the PTS and DTS of the keyframe so that it covers the time
1086 * range of the frames that were disposed.
1087 *
1088 * @param {Array} gops video GOPs
1089 * @returns {Array} modified video GOPs
1090 */
1091 var extendFirstKeyFrame = function(gops) {
1092 var currentGop;
1093
1094 if (!gops[0][0].keyFrame && gops.length > 1) {
1095 // Remove the first GOP
1096 currentGop = gops.shift();
1097
1098 gops.byteLength -= currentGop.byteLength;
1099 gops.nalCount -= currentGop.nalCount;
1100
1101 // Extend the first frame of what is now the
1102 // first gop to cover the time period of the
1103 // frames we just removed
1104 gops[0][0].dts = currentGop.dts;
1105 gops[0][0].pts = currentGop.pts;
1106 gops[0][0].duration += currentGop.duration;
1107 }
1108
1109 return gops;
1110 };
1111
1112 /**
1113 * Default sample object
1114 * see ISO/IEC 14496-12:2012, section 8.6.4.3
1115 */
1116 var createDefaultSample = function() {
1117 return {
1118 size: 0,
1119 flags: {
1120 isLeading: 0,
1121 dependsOn: 1,
1122 isDependedOn: 0,
1123 hasRedundancy: 0,
1124 degradationPriority: 0,
1125 isNonSyncSample: 1
1126 }
1127 };
1128 };
1129
1130 /*
1131 * Collates information from a video frame into an object for eventual
1132 * entry into an MP4 sample table.
1133 *
1134 * @param {Object} frame the video frame
1135 * @param {Number} dataOffset the byte offset to position the sample
1136 * @return {Object} object containing sample table info for a frame
1137 */
1138 var sampleForFrame = function(frame, dataOffset) {
1139 var sample = createDefaultSample();
1140
1141 sample.dataOffset = dataOffset;
1142 sample.compositionTimeOffset = frame.pts - frame.dts;
1143 sample.duration = frame.duration;
1144 sample.size = 4 * frame.length; // Space for nal unit size
1145 sample.size += frame.byteLength;
1146
1147 if (frame.keyFrame) {
1148 sample.flags.dependsOn = 2;
1149 sample.flags.isNonSyncSample = 0;
1150 }
1151
1152 return sample;
1153 };
1154
1155 // generate the track's sample table from an array of gops
1156 var generateSampleTable = function(gops, baseDataOffset) {
1157 var
1158 h, i,
1159 sample,
1160 currentGop,
1161 currentFrame,
1162 dataOffset = baseDataOffset || 0,
1163 samples = [];
1164
1165 for (h = 0; h < gops.length; h++) {
1166 currentGop = gops[h];
1167
1168 for (i = 0; i < currentGop.length; i++) {
1169 currentFrame = currentGop[i];
1170
1171 sample = sampleForFrame(currentFrame, dataOffset);
1172
1173 dataOffset += sample.size;
1174
1175 samples.push(sample);
1176 }
1177 }
1178 return samples;
1179 };
1180
1181 // generate the track's raw mdat data from an array of gops
1182 var concatenateNalData = function(gops) {
1183 var
1184 h, i, j,
1185 currentGop,
1186 currentFrame,
1187 currentNal,
1188 dataOffset = 0,
1189 nalsByteLength = gops.byteLength,
1190 numberOfNals = gops.nalCount,
1191 totalByteLength = nalsByteLength + 4 * numberOfNals,
1192 data = new Uint8Array(totalByteLength),
1193 view = new DataView(data.buffer);
1194
1195 // For each Gop..
1196 for (h = 0; h < gops.length; h++) {
1197 currentGop = gops[h];
1198
1199 // For each Frame..
1200 for (i = 0; i < currentGop.length; i++) {
1201 currentFrame = currentGop[i];
1202
1203 // For each NAL..
1204 for (j = 0; j < currentFrame.length; j++) {
1205 currentNal = currentFrame[j];
1206
1207 view.setUint32(dataOffset, currentNal.data.byteLength);
1208 dataOffset += 4;
1209 data.set(currentNal.data, dataOffset);
1210 dataOffset += currentNal.data.byteLength;
1211 }
1212 }
1213 }
1214 return data;
1215 };
1216
1217 // generate the track's sample table from a frame
1218 var generateSampleTableForFrame = function(frame, baseDataOffset) {
1219 var
1220 sample,
1221 dataOffset = baseDataOffset || 0,
1222 samples = [];
1223
1224 sample = sampleForFrame(frame, dataOffset);
1225 samples.push(sample);
1226
1227 return samples;
1228 };
1229
1230 // generate the track's raw mdat data from a frame
1231 var concatenateNalDataForFrame = function(frame) {
1232 var
1233 i,
1234 currentNal,
1235 dataOffset = 0,
1236 nalsByteLength = frame.byteLength,
1237 numberOfNals = frame.length,
1238 totalByteLength = nalsByteLength + 4 * numberOfNals,
1239 data = new Uint8Array(totalByteLength),
1240 view = new DataView(data.buffer);
1241
1242 // For each NAL..
1243 for (i = 0; i < frame.length; i++) {
1244 currentNal = frame[i];
1245
1246 view.setUint32(dataOffset, currentNal.data.byteLength);
1247 dataOffset += 4;
1248 data.set(currentNal.data, dataOffset);
1249 dataOffset += currentNal.data.byteLength;
1250 }
1251
1252 return data;
1253 };
1254
1255 var frameUtils = {
1256 groupNalsIntoFrames: groupNalsIntoFrames,
1257 groupFramesIntoGops: groupFramesIntoGops,
1258 extendFirstKeyFrame: extendFirstKeyFrame,
1259 generateSampleTable: generateSampleTable,
1260 concatenateNalData: concatenateNalData,
1261 generateSampleTableForFrame: generateSampleTableForFrame,
1262 concatenateNalDataForFrame: concatenateNalDataForFrame
1263 };
1264
1265 /**
1266 * mux.js
1267 *
1268 * Copyright (c) Brightcove
1269 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
1270 */
1271 var highPrefix = [33, 16, 5, 32, 164, 27];
1272 var lowPrefix = [33, 65, 108, 84, 1, 2, 4, 8, 168, 2, 4, 8, 17, 191, 252];
1273 var zeroFill = function(count) {
1274 var a = [];
1275 while (count--) {
1276 a.push(0);
1277 }
1278 return a;
1279 };
1280
1281 var makeTable = function(metaTable) {
1282 return Object.keys(metaTable).reduce(function(obj, key) {
1283 obj[key] = new Uint8Array(metaTable[key].reduce(function(arr, part) {
1284 return arr.concat(part);
1285 }, []));
1286 return obj;
1287 }, {});
1288 };
1289
1290
1291 var silence;
1292
1293 var silence_1 = function() {
1294 if (!silence) {
1295 // Frames-of-silence to use for filling in missing AAC frames
1296 var coneOfSilence = {
1297 96000: [highPrefix, [227, 64], zeroFill(154), [56]],
1298 88200: [highPrefix, [231], zeroFill(170), [56]],
1299 64000: [highPrefix, [248, 192], zeroFill(240), [56]],
1300 48000: [highPrefix, [255, 192], zeroFill(268), [55, 148, 128], zeroFill(54), [112]],
1301 44100: [highPrefix, [255, 192], zeroFill(268), [55, 163, 128], zeroFill(84), [112]],
1302 32000: [highPrefix, [255, 192], zeroFill(268), [55, 234], zeroFill(226), [112]],
1303 24000: [highPrefix, [255, 192], zeroFill(268), [55, 255, 128], zeroFill(268), [111, 112], zeroFill(126), [224]],
1304 16000: [highPrefix, [255, 192], zeroFill(268), [55, 255, 128], zeroFill(268), [111, 255], zeroFill(269), [223, 108], zeroFill(195), [1, 192]],
1305 12000: [lowPrefix, zeroFill(268), [3, 127, 248], zeroFill(268), [6, 255, 240], zeroFill(268), [13, 255, 224], zeroFill(268), [27, 253, 128], zeroFill(259), [56]],
1306 11025: [lowPrefix, zeroFill(268), [3, 127, 248], zeroFill(268), [6, 255, 240], zeroFill(268), [13, 255, 224], zeroFill(268), [27, 255, 192], zeroFill(268), [55, 175, 128], zeroFill(108), [112]],
1307 8000: [lowPrefix, zeroFill(268), [3, 121, 16], zeroFill(47), [7]]
1308 };
1309 silence = makeTable(coneOfSilence);
1310 }
1311 return silence;
1312 };
1313
1314 /**
1315 * mux.js
1316 *
1317 * Copyright (c) Brightcove
1318 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
1319 */
1320 var
1321 ONE_SECOND_IN_TS = 90000, // 90kHz clock
1322 secondsToVideoTs,
1323 secondsToAudioTs,
1324 videoTsToSeconds,
1325 audioTsToSeconds,
1326 audioTsToVideoTs,
1327 videoTsToAudioTs,
1328 metadataTsToSeconds;
1329
1330 secondsToVideoTs = function(seconds) {
1331 return seconds * ONE_SECOND_IN_TS;
1332 };
1333
1334 secondsToAudioTs = function(seconds, sampleRate) {
1335 return seconds * sampleRate;
1336 };
1337
1338 videoTsToSeconds = function(timestamp) {
1339 return timestamp / ONE_SECOND_IN_TS;
1340 };
1341
1342 audioTsToSeconds = function(timestamp, sampleRate) {
1343 return timestamp / sampleRate;
1344 };
1345
1346 audioTsToVideoTs = function(timestamp, sampleRate) {
1347 return secondsToVideoTs(audioTsToSeconds(timestamp, sampleRate));
1348 };
1349
1350 videoTsToAudioTs = function(timestamp, sampleRate) {
1351 return secondsToAudioTs(videoTsToSeconds(timestamp), sampleRate);
1352 };
1353
1354 /**
1355 * Adjust ID3 tag or caption timing information by the timeline pts values
1356 * (if keepOriginalTimestamps is false) and convert to seconds
1357 */
1358 metadataTsToSeconds = function(timestamp, timelineStartPts, keepOriginalTimestamps) {
1359 return videoTsToSeconds(keepOriginalTimestamps ? timestamp : timestamp - timelineStartPts);
1360 };
1361
1362 var clock = {
1363 ONE_SECOND_IN_TS: ONE_SECOND_IN_TS,
1364 secondsToVideoTs: secondsToVideoTs,
1365 secondsToAudioTs: secondsToAudioTs,
1366 videoTsToSeconds: videoTsToSeconds,
1367 audioTsToSeconds: audioTsToSeconds,
1368 audioTsToVideoTs: audioTsToVideoTs,
1369 videoTsToAudioTs: videoTsToAudioTs,
1370 metadataTsToSeconds: metadataTsToSeconds
1371 };
1372 var clock_2 = clock.secondsToVideoTs;
1373 var clock_4 = clock.videoTsToSeconds;
1374
1375 /**
1376 * mux.js
1377 *
1378 * Copyright (c) Brightcove
1379 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
1380 */
1381
1382
1383
1384 /**
1385 * Sum the `byteLength` properties of the data in each AAC frame
1386 */
1387 var sumFrameByteLengths = function(array) {
1388 var
1389 i,
1390 currentObj,
1391 sum = 0;
1392
1393 // sum the byteLength's all each nal unit in the frame
1394 for (i = 0; i < array.length; i++) {
1395 currentObj = array[i];
1396 sum += currentObj.data.byteLength;
1397 }
1398
1399 return sum;
1400 };
1401
1402 // Possibly pad (prefix) the audio track with silence if appending this track
1403 // would lead to the introduction of a gap in the audio buffer
1404 var prefixWithSilence = function(
1405 track,
1406 frames,
1407 audioAppendStartTs,
1408 videoBaseMediaDecodeTime
1409 ) {
1410 var
1411 baseMediaDecodeTimeTs,
1412 frameDuration = 0,
1413 audioGapDuration = 0,
1414 audioFillFrameCount = 0,
1415 audioFillDuration = 0,
1416 silentFrame,
1417 i,
1418 firstFrame;
1419
1420 if (!frames.length) {
1421 return;
1422 }
1423
1424 baseMediaDecodeTimeTs =
1425 clock.audioTsToVideoTs(track.baseMediaDecodeTime, track.samplerate);
1426 // determine frame clock duration based on sample rate, round up to avoid overfills
1427 frameDuration = Math.ceil(clock.ONE_SECOND_IN_TS / (track.samplerate / 1024));
1428
1429 if (audioAppendStartTs && videoBaseMediaDecodeTime) {
1430 // insert the shortest possible amount (audio gap or audio to video gap)
1431 audioGapDuration =
1432 baseMediaDecodeTimeTs - Math.max(audioAppendStartTs, videoBaseMediaDecodeTime);
1433 // number of full frames in the audio gap
1434 audioFillFrameCount = Math.floor(audioGapDuration / frameDuration);
1435 audioFillDuration = audioFillFrameCount * frameDuration;
1436 }
1437
1438 // don't attempt to fill gaps smaller than a single frame or larger
1439 // than a half second
1440 if (audioFillFrameCount < 1 || audioFillDuration > clock.ONE_SECOND_IN_TS / 2) {
1441 return;
1442 }
1443
1444 silentFrame = silence_1()[track.samplerate];
1445
1446 if (!silentFrame) {
1447 // we don't have a silent frame pregenerated for the sample rate, so use a frame
1448 // from the content instead
1449 silentFrame = frames[0].data;
1450 }
1451
1452 for (i = 0; i < audioFillFrameCount; i++) {
1453 firstFrame = frames[0];
1454
1455 frames.splice(0, 0, {
1456 data: silentFrame,
1457 dts: firstFrame.dts - frameDuration,
1458 pts: firstFrame.pts - frameDuration
1459 });
1460 }
1461
1462 track.baseMediaDecodeTime -=
1463 Math.floor(clock.videoTsToAudioTs(audioFillDuration, track.samplerate));
1464 };
1465
1466 // If the audio segment extends before the earliest allowed dts
1467 // value, remove AAC frames until starts at or after the earliest
1468 // allowed DTS so that we don't end up with a negative baseMedia-
1469 // DecodeTime for the audio track
1470 var trimAdtsFramesByEarliestDts = function(adtsFrames, track, earliestAllowedDts) {
1471 if (track.minSegmentDts >= earliestAllowedDts) {
1472 return adtsFrames;
1473 }
1474
1475 // We will need to recalculate the earliest segment Dts
1476 track.minSegmentDts = Infinity;
1477
1478 return adtsFrames.filter(function(currentFrame) {
1479 // If this is an allowed frame, keep it and record it's Dts
1480 if (currentFrame.dts >= earliestAllowedDts) {
1481 track.minSegmentDts = Math.min(track.minSegmentDts, currentFrame.dts);
1482 track.minSegmentPts = track.minSegmentDts;
1483 return true;
1484 }
1485 // Otherwise, discard it
1486 return false;
1487 });
1488 };
1489
1490 // generate the track's raw mdat data from an array of frames
1491 var generateSampleTable$1 = function(frames) {
1492 var
1493 i,
1494 currentFrame,
1495 samples = [];
1496
1497 for (i = 0; i < frames.length; i++) {
1498 currentFrame = frames[i];
1499 samples.push({
1500 size: currentFrame.data.byteLength,
1501 duration: 1024 // For AAC audio, all samples contain 1024 samples
1502 });
1503 }
1504 return samples;
1505 };
1506
1507 // generate the track's sample table from an array of frames
1508 var concatenateFrameData = function(frames) {
1509 var
1510 i,
1511 currentFrame,
1512 dataOffset = 0,
1513 data = new Uint8Array(sumFrameByteLengths(frames));
1514
1515 for (i = 0; i < frames.length; i++) {
1516 currentFrame = frames[i];
1517
1518 data.set(currentFrame.data, dataOffset);
1519 dataOffset += currentFrame.data.byteLength;
1520 }
1521 return data;
1522 };
1523
1524 var audioFrameUtils = {
1525 prefixWithSilence: prefixWithSilence,
1526 trimAdtsFramesByEarliestDts: trimAdtsFramesByEarliestDts,
1527 generateSampleTable: generateSampleTable$1,
1528 concatenateFrameData: concatenateFrameData
1529 };
1530
1531 /**
1532 * mux.js
1533 *
1534 * Copyright (c) Brightcove
1535 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
1536 */
1537 var ONE_SECOND_IN_TS$1 = clock.ONE_SECOND_IN_TS;
1538
1539 /**
1540 * Store information about the start and end of the track and the
1541 * duration for each frame/sample we process in order to calculate
1542 * the baseMediaDecodeTime
1543 */
1544 var collectDtsInfo = function(track, data) {
1545 if (typeof data.pts === 'number') {
1546 if (track.timelineStartInfo.pts === undefined) {
1547 track.timelineStartInfo.pts = data.pts;
1548 }
1549
1550 if (track.minSegmentPts === undefined) {
1551 track.minSegmentPts = data.pts;
1552 } else {
1553 track.minSegmentPts = Math.min(track.minSegmentPts, data.pts);
1554 }
1555
1556 if (track.maxSegmentPts === undefined) {
1557 track.maxSegmentPts = data.pts;
1558 } else {
1559 track.maxSegmentPts = Math.max(track.maxSegmentPts, data.pts);
1560 }
1561 }
1562
1563 if (typeof data.dts === 'number') {
1564 if (track.timelineStartInfo.dts === undefined) {
1565 track.timelineStartInfo.dts = data.dts;
1566 }
1567
1568 if (track.minSegmentDts === undefined) {
1569 track.minSegmentDts = data.dts;
1570 } else {
1571 track.minSegmentDts = Math.min(track.minSegmentDts, data.dts);
1572 }
1573
1574 if (track.maxSegmentDts === undefined) {
1575 track.maxSegmentDts = data.dts;
1576 } else {
1577 track.maxSegmentDts = Math.max(track.maxSegmentDts, data.dts);
1578 }
1579 }
1580 };
1581
1582 /**
1583 * Clear values used to calculate the baseMediaDecodeTime between
1584 * tracks
1585 */
1586 var clearDtsInfo = function(track) {
1587 delete track.minSegmentDts;
1588 delete track.maxSegmentDts;
1589 delete track.minSegmentPts;
1590 delete track.maxSegmentPts;
1591 };
1592
1593 /**
1594 * Calculate the track's baseMediaDecodeTime based on the earliest
1595 * DTS the transmuxer has ever seen and the minimum DTS for the
1596 * current track
1597 * @param track {object} track metadata configuration
1598 * @param keepOriginalTimestamps {boolean} If true, keep the timestamps
1599 * in the source; false to adjust the first segment to start at 0.
1600 */
1601 var calculateTrackBaseMediaDecodeTime = function(track, keepOriginalTimestamps) {
1602 var
1603 baseMediaDecodeTime,
1604 scale,
1605 minSegmentDts = track.minSegmentDts;
1606
1607 // Optionally adjust the time so the first segment starts at zero.
1608 if (!keepOriginalTimestamps) {
1609 minSegmentDts -= track.timelineStartInfo.dts;
1610 }
1611
1612 // track.timelineStartInfo.baseMediaDecodeTime is the location, in time, where
1613 // we want the start of the first segment to be placed
1614 baseMediaDecodeTime = track.timelineStartInfo.baseMediaDecodeTime;
1615
1616 // Add to that the distance this segment is from the very first
1617 baseMediaDecodeTime += minSegmentDts;
1618
1619 // baseMediaDecodeTime must not become negative
1620 baseMediaDecodeTime = Math.max(0, baseMediaDecodeTime);
1621
1622 if (track.type === 'audio') {
1623 // Audio has a different clock equal to the sampling_rate so we need to
1624 // scale the PTS values into the clock rate of the track
1625 scale = track.samplerate / ONE_SECOND_IN_TS$1;
1626 baseMediaDecodeTime *= scale;
1627 baseMediaDecodeTime = Math.floor(baseMediaDecodeTime);
1628 }
1629
1630 return baseMediaDecodeTime;
1631 };
1632
1633 var trackDecodeInfo = {
1634 clearDtsInfo: clearDtsInfo,
1635 calculateTrackBaseMediaDecodeTime: calculateTrackBaseMediaDecodeTime,
1636 collectDtsInfo: collectDtsInfo
1637 };
1638
1639 /**
1640 * mux.js
1641 *
1642 * Copyright (c) Brightcove
1643 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
1644 *
1645 * Reads in-band caption information from a video elementary
1646 * stream. Captions must follow the CEA-708 standard for injection
1647 * into an MPEG-2 transport streams.
1648 * @see https://en.wikipedia.org/wiki/CEA-708
1649 * @see https://www.gpo.gov/fdsys/pkg/CFR-2007-title47-vol1/pdf/CFR-2007-title47-vol1-sec15-119.pdf
1650 */
1651
1652 // Supplemental enhancement information (SEI) NAL units have a
1653 // payload type field to indicate how they are to be
1654 // interpreted. CEAS-708 caption content is always transmitted with
1655 // payload type 0x04.
1656 var USER_DATA_REGISTERED_ITU_T_T35 = 4,
1657 RBSP_TRAILING_BITS = 128;
1658
1659 /**
1660 * Parse a supplemental enhancement information (SEI) NAL unit.
1661 * Stops parsing once a message of type ITU T T35 has been found.
1662 *
1663 * @param bytes {Uint8Array} the bytes of a SEI NAL unit
1664 * @return {object} the parsed SEI payload
1665 * @see Rec. ITU-T H.264, 7.3.2.3.1
1666 */
1667 var parseSei = function(bytes) {
1668 var
1669 i = 0,
1670 result = {
1671 payloadType: -1,
1672 payloadSize: 0
1673 },
1674 payloadType = 0,
1675 payloadSize = 0;
1676
1677 // go through the sei_rbsp parsing each each individual sei_message
1678 while (i < bytes.byteLength) {
1679 // stop once we have hit the end of the sei_rbsp
1680 if (bytes[i] === RBSP_TRAILING_BITS) {
1681 break;
1682 }
1683
1684 // Parse payload type
1685 while (bytes[i] === 0xFF) {
1686 payloadType += 255;
1687 i++;
1688 }
1689 payloadType += bytes[i++];
1690
1691 // Parse payload size
1692 while (bytes[i] === 0xFF) {
1693 payloadSize += 255;
1694 i++;
1695 }
1696 payloadSize += bytes[i++];
1697
1698 // this sei_message is a 608/708 caption so save it and break
1699 // there can only ever be one caption message in a frame's sei
1700 if (!result.payload && payloadType === USER_DATA_REGISTERED_ITU_T_T35) {
1701 var userIdentifier = String.fromCharCode(
1702 bytes[i + 3],
1703 bytes[i + 4],
1704 bytes[i + 5],
1705 bytes[i + 6]);
1706
1707 if (userIdentifier === 'GA94') {
1708 result.payloadType = payloadType;
1709 result.payloadSize = payloadSize;
1710 result.payload = bytes.subarray(i, i + payloadSize);
1711 break;
1712 } else {
1713 result.payload = void 0;
1714 }
1715 }
1716
1717 // skip the payload and parse the next message
1718 i += payloadSize;
1719 payloadType = 0;
1720 payloadSize = 0;
1721 }
1722
1723 return result;
1724 };
1725
1726 // see ANSI/SCTE 128-1 (2013), section 8.1
1727 var parseUserData = function(sei) {
1728 // itu_t_t35_contry_code must be 181 (United States) for
1729 // captions
1730 if (sei.payload[0] !== 181) {
1731 return null;
1732 }
1733
1734 // itu_t_t35_provider_code should be 49 (ATSC) for captions
1735 if (((sei.payload[1] << 8) | sei.payload[2]) !== 49) {
1736 return null;
1737 }
1738
1739 // the user_identifier should be "GA94" to indicate ATSC1 data
1740 if (String.fromCharCode(sei.payload[3],
1741 sei.payload[4],
1742 sei.payload[5],
1743 sei.payload[6]) !== 'GA94') {
1744 return null;
1745 }
1746
1747 // finally, user_data_type_code should be 0x03 for caption data
1748 if (sei.payload[7] !== 0x03) {
1749 return null;
1750 }
1751
1752 // return the user_data_type_structure and strip the trailing
1753 // marker bits
1754 return sei.payload.subarray(8, sei.payload.length - 1);
1755 };
1756
1757 // see CEA-708-D, section 4.4
1758 var parseCaptionPackets = function(pts, userData) {
1759 var results = [], i, count, offset, data;
1760
1761 // if this is just filler, return immediately
1762 if (!(userData[0] & 0x40)) {
1763 return results;
1764 }
1765
1766 // parse out the cc_data_1 and cc_data_2 fields
1767 count = userData[0] & 0x1f;
1768 for (i = 0; i < count; i++) {
1769 offset = i * 3;
1770 data = {
1771 type: userData[offset + 2] & 0x03,
1772 pts: pts
1773 };
1774
1775 // capture cc data when cc_valid is 1
1776 if (userData[offset + 2] & 0x04) {
1777 data.ccData = (userData[offset + 3] << 8) | userData[offset + 4];
1778 results.push(data);
1779 }
1780 }
1781 return results;
1782 };
1783
1784 var discardEmulationPreventionBytes = function(data) {
1785 var
1786 length = data.byteLength,
1787 emulationPreventionBytesPositions = [],
1788 i = 1,
1789 newLength, newData;
1790
1791 // Find all `Emulation Prevention Bytes`
1792 while (i < length - 2) {
1793 if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0x03) {
1794 emulationPreventionBytesPositions.push(i + 2);
1795 i += 2;
1796 } else {
1797 i++;
1798 }
1799 }
1800
1801 // If no Emulation Prevention Bytes were found just return the original
1802 // array
1803 if (emulationPreventionBytesPositions.length === 0) {
1804 return data;
1805 }
1806
1807 // Create a new array to hold the NAL unit data
1808 newLength = length - emulationPreventionBytesPositions.length;
1809 newData = new Uint8Array(newLength);
1810 var sourceIndex = 0;
1811
1812 for (i = 0; i < newLength; sourceIndex++, i++) {
1813 if (sourceIndex === emulationPreventionBytesPositions[0]) {
1814 // Skip this byte
1815 sourceIndex++;
1816 // Remove this position index
1817 emulationPreventionBytesPositions.shift();
1818 }
1819 newData[i] = data[sourceIndex];
1820 }
1821
1822 return newData;
1823 };
1824
1825 // exports
1826 var captionPacketParser = {
1827 parseSei: parseSei,
1828 parseUserData: parseUserData,
1829 parseCaptionPackets: parseCaptionPackets,
1830 discardEmulationPreventionBytes: discardEmulationPreventionBytes,
1831 USER_DATA_REGISTERED_ITU_T_T35: USER_DATA_REGISTERED_ITU_T_T35
1832 };
1833
1834 // -----------------
1835 // Link To Transport
1836 // -----------------
1837
1838
1839
1840
1841 var CaptionStream = function() {
1842
1843 CaptionStream.prototype.init.call(this);
1844
1845 this.captionPackets_ = [];
1846
1847 this.ccStreams_ = [
1848 new Cea608Stream(0, 0), // eslint-disable-line no-use-before-define
1849 new Cea608Stream(0, 1), // eslint-disable-line no-use-before-define
1850 new Cea608Stream(1, 0), // eslint-disable-line no-use-before-define
1851 new Cea608Stream(1, 1) // eslint-disable-line no-use-before-define
1852 ];
1853
1854 this.reset();
1855
1856 // forward data and done events from CCs to this CaptionStream
1857 this.ccStreams_.forEach(function(cc) {
1858 cc.on('data', this.trigger.bind(this, 'data'));
1859 cc.on('partialdone', this.trigger.bind(this, 'partialdone'));
1860 cc.on('done', this.trigger.bind(this, 'done'));
1861 }, this);
1862
1863 };
1864
1865 CaptionStream.prototype = new stream();
1866 CaptionStream.prototype.push = function(event) {
1867 var sei, userData, newCaptionPackets;
1868
1869 // only examine SEI NALs
1870 if (event.nalUnitType !== 'sei_rbsp') {
1871 return;
1872 }
1873
1874 // parse the sei
1875 sei = captionPacketParser.parseSei(event.escapedRBSP);
1876
1877 // ignore everything but user_data_registered_itu_t_t35
1878 if (sei.payloadType !== captionPacketParser.USER_DATA_REGISTERED_ITU_T_T35) {
1879 return;
1880 }
1881
1882 // parse out the user data payload
1883 userData = captionPacketParser.parseUserData(sei);
1884
1885 // ignore unrecognized userData
1886 if (!userData) {
1887 return;
1888 }
1889
1890 // Sometimes, the same segment # will be downloaded twice. To stop the
1891 // caption data from being processed twice, we track the latest dts we've
1892 // received and ignore everything with a dts before that. However, since
1893 // data for a specific dts can be split across packets on either side of
1894 // a segment boundary, we need to make sure we *don't* ignore the packets
1895 // from the *next* segment that have dts === this.latestDts_. By constantly
1896 // tracking the number of packets received with dts === this.latestDts_, we
1897 // know how many should be ignored once we start receiving duplicates.
1898 if (event.dts < this.latestDts_) {
1899 // We've started getting older data, so set the flag.
1900 this.ignoreNextEqualDts_ = true;
1901 return;
1902 } else if ((event.dts === this.latestDts_) && (this.ignoreNextEqualDts_)) {
1903 this.numSameDts_--;
1904 if (!this.numSameDts_) {
1905 // We've received the last duplicate packet, time to start processing again
1906 this.ignoreNextEqualDts_ = false;
1907 }
1908 return;
1909 }
1910
1911 // parse out CC data packets and save them for later
1912 newCaptionPackets = captionPacketParser.parseCaptionPackets(event.pts, userData);
1913 this.captionPackets_ = this.captionPackets_.concat(newCaptionPackets);
1914 if (this.latestDts_ !== event.dts) {
1915 this.numSameDts_ = 0;
1916 }
1917 this.numSameDts_++;
1918 this.latestDts_ = event.dts;
1919 };
1920
1921 CaptionStream.prototype.flushCCStreams = function(flushType) {
1922 this.ccStreams_.forEach(function(cc) {
1923 return flushType === 'flush' ? cc.flush() : cc.partialFlush();
1924 }, this);
1925 };
1926
1927 CaptionStream.prototype.flushStream = function(flushType) {
1928 // make sure we actually parsed captions before proceeding
1929 if (!this.captionPackets_.length) {
1930 this.flushCCStreams(flushType);
1931 return;
1932 }
1933
1934 // In Chrome, the Array#sort function is not stable so add a
1935 // presortIndex that we can use to ensure we get a stable-sort
1936 this.captionPackets_.forEach(function(elem, idx) {
1937 elem.presortIndex = idx;
1938 });
1939
1940 // sort caption byte-pairs based on their PTS values
1941 this.captionPackets_.sort(function(a, b) {
1942 if (a.pts === b.pts) {
1943 return a.presortIndex - b.presortIndex;
1944 }
1945 return a.pts - b.pts;
1946 });
1947
1948 this.captionPackets_.forEach(function(packet) {
1949 if (packet.type < 2) {
1950 // Dispatch packet to the right Cea608Stream
1951 this.dispatchCea608Packet(packet);
1952 }
1953 // this is where an 'else' would go for a dispatching packets
1954 // to a theoretical Cea708Stream that handles SERVICEn data
1955 }, this);
1956
1957 this.captionPackets_.length = 0;
1958 this.flushCCStreams(flushType);
1959 };
1960
1961 CaptionStream.prototype.flush = function() {
1962 return this.flushStream('flush');
1963 };
1964
1965 // Only called if handling partial data
1966 CaptionStream.prototype.partialFlush = function() {
1967 return this.flushStream('partialFlush');
1968 };
1969
1970 CaptionStream.prototype.reset = function() {
1971 this.latestDts_ = null;
1972 this.ignoreNextEqualDts_ = false;
1973 this.numSameDts_ = 0;
1974 this.activeCea608Channel_ = [null, null];
1975 this.ccStreams_.forEach(function(ccStream) {
1976 ccStream.reset();
1977 });
1978 };
1979
1980 // From the CEA-608 spec:
1981 /*
1982 * When XDS sub-packets are interleaved with other services, the end of each sub-packet shall be followed
1983 * by a control pair to change to a different service. When any of the control codes from 0x10 to 0x1F is
1984 * used to begin a control code pair, it indicates the return to captioning or Text data. The control code pair
1985 * and subsequent data should then be processed according to the FCC rules. It may be necessary for the
1986 * line 21 data encoder to automatically insert a control code pair (i.e. RCL, RU2, RU3, RU4, RDC, or RTD)
1987 * to switch to captioning or Text.
1988 */
1989 // With that in mind, we ignore any data between an XDS control code and a
1990 // subsequent closed-captioning control code.
1991 CaptionStream.prototype.dispatchCea608Packet = function(packet) {
1992 // NOTE: packet.type is the CEA608 field
1993 if (this.setsTextOrXDSActive(packet)) {
1994 this.activeCea608Channel_[packet.type] = null;
1995 } else if (this.setsChannel1Active(packet)) {
1996 this.activeCea608Channel_[packet.type] = 0;
1997 } else if (this.setsChannel2Active(packet)) {
1998 this.activeCea608Channel_[packet.type] = 1;
1999 }
2000 if (this.activeCea608Channel_[packet.type] === null) {
2001 // If we haven't received anything to set the active channel, or the
2002 // packets are Text/XDS data, discard the data; we don't want jumbled
2003 // captions
2004 return;
2005 }
2006 this.ccStreams_[(packet.type << 1) + this.activeCea608Channel_[packet.type]].push(packet);
2007 };
2008
2009 CaptionStream.prototype.setsChannel1Active = function(packet) {
2010 return ((packet.ccData & 0x7800) === 0x1000);
2011 };
2012 CaptionStream.prototype.setsChannel2Active = function(packet) {
2013 return ((packet.ccData & 0x7800) === 0x1800);
2014 };
2015 CaptionStream.prototype.setsTextOrXDSActive = function(packet) {
2016 return ((packet.ccData & 0x7100) === 0x0100) ||
2017 ((packet.ccData & 0x78fe) === 0x102a) ||
2018 ((packet.ccData & 0x78fe) === 0x182a);
2019 };
2020
2021 // ----------------------
2022 // Session to Application
2023 // ----------------------
2024
2025 // This hash maps non-ASCII, special, and extended character codes to their
2026 // proper Unicode equivalent. The first keys that are only a single byte
2027 // are the non-standard ASCII characters, which simply map the CEA608 byte
2028 // to the standard ASCII/Unicode. The two-byte keys that follow are the CEA608
2029 // character codes, but have their MSB bitmasked with 0x03 so that a lookup
2030 // can be performed regardless of the field and data channel on which the
2031 // character code was received.
2032 var CHARACTER_TRANSLATION = {
2033 0x2a: 0xe1, // á
2034 0x5c: 0xe9, // é
2035 0x5e: 0xed, // í
2036 0x5f: 0xf3, // ó
2037 0x60: 0xfa, // ú
2038 0x7b: 0xe7, // ç
2039 0x7c: 0xf7, // ÷
2040 0x7d: 0xd1, // Ñ
2041 0x7e: 0xf1, // ñ
2042 0x7f: 0x2588, // █
2043 0x0130: 0xae, // ®
2044 0x0131: 0xb0, // °
2045 0x0132: 0xbd, // ½
2046 0x0133: 0xbf, // ¿
2047 0x0134: 0x2122, // ™
2048 0x0135: 0xa2, // ¢
2049 0x0136: 0xa3, // £
2050 0x0137: 0x266a, // ♪
2051 0x0138: 0xe0, // à
2052 0x0139: 0xa0, //
2053 0x013a: 0xe8, // è
2054 0x013b: 0xe2, // â
2055 0x013c: 0xea, // ê
2056 0x013d: 0xee, // î
2057 0x013e: 0xf4, // ô
2058 0x013f: 0xfb, // û
2059 0x0220: 0xc1, // Á
2060 0x0221: 0xc9, // É
2061 0x0222: 0xd3, // Ó
2062 0x0223: 0xda, // Ú
2063 0x0224: 0xdc, // Ü
2064 0x0225: 0xfc, // ü
2065 0x0226: 0x2018, // ‘
2066 0x0227: 0xa1, // ¡
2067 0x0228: 0x2a, // *
2068 0x0229: 0x27, // '
2069 0x022a: 0x2014, // —
2070 0x022b: 0xa9, // ©
2071 0x022c: 0x2120, // ℠
2072 0x022d: 0x2022, // •
2073 0x022e: 0x201c, // “
2074 0x022f: 0x201d, // ”
2075 0x0230: 0xc0, // À
2076 0x0231: 0xc2, // Â
2077 0x0232: 0xc7, // Ç
2078 0x0233: 0xc8, // È
2079 0x0234: 0xca, // Ê
2080 0x0235: 0xcb, // Ë
2081 0x0236: 0xeb, // ë
2082 0x0237: 0xce, // Î
2083 0x0238: 0xcf, // Ï
2084 0x0239: 0xef, // ï
2085 0x023a: 0xd4, // Ô
2086 0x023b: 0xd9, // Ù
2087 0x023c: 0xf9, // ù
2088 0x023d: 0xdb, // Û
2089 0x023e: 0xab, // «
2090 0x023f: 0xbb, // »
2091 0x0320: 0xc3, // Ã
2092 0x0321: 0xe3, // ã
2093 0x0322: 0xcd, // Í
2094 0x0323: 0xcc, // Ì
2095 0x0324: 0xec, // ì
2096 0x0325: 0xd2, // Ò
2097 0x0326: 0xf2, // ò
2098 0x0327: 0xd5, // Õ
2099 0x0328: 0xf5, // õ
2100 0x0329: 0x7b, // {
2101 0x032a: 0x7d, // }
2102 0x032b: 0x5c, // \
2103 0x032c: 0x5e, // ^
2104 0x032d: 0x5f, // _
2105 0x032e: 0x7c, // |
2106 0x032f: 0x7e, // ~
2107 0x0330: 0xc4, // Ä
2108 0x0331: 0xe4, // ä
2109 0x0332: 0xd6, // Ö
2110 0x0333: 0xf6, // ö
2111 0x0334: 0xdf, // ß
2112 0x0335: 0xa5, // ¥
2113 0x0336: 0xa4, // ¤
2114 0x0337: 0x2502, // │
2115 0x0338: 0xc5, // Å
2116 0x0339: 0xe5, // å
2117 0x033a: 0xd8, // Ø
2118 0x033b: 0xf8, // ø
2119 0x033c: 0x250c, // ┌
2120 0x033d: 0x2510, // ┐
2121 0x033e: 0x2514, // └
2122 0x033f: 0x2518 // ┘
2123 };
2124
2125 var getCharFromCode = function(code) {
2126 if (code === null) {
2127 return '';
2128 }
2129 code = CHARACTER_TRANSLATION[code] || code;
2130 return String.fromCharCode(code);
2131 };
2132
2133 // the index of the last row in a CEA-608 display buffer
2134 var BOTTOM_ROW = 14;
2135
2136 // This array is used for mapping PACs -> row #, since there's no way of
2137 // getting it through bit logic.
2138 var ROWS = [0x1100, 0x1120, 0x1200, 0x1220, 0x1500, 0x1520, 0x1600, 0x1620,
2139 0x1700, 0x1720, 0x1000, 0x1300, 0x1320, 0x1400, 0x1420];
2140
2141 // CEA-608 captions are rendered onto a 34x15 matrix of character
2142 // cells. The "bottom" row is the last element in the outer array.
2143 var createDisplayBuffer = function() {
2144 var result = [], i = BOTTOM_ROW + 1;
2145 while (i--) {
2146 result.push('');
2147 }
2148 return result;
2149 };
2150
2151 var Cea608Stream = function(field, dataChannel) {
2152 Cea608Stream.prototype.init.call(this);
2153
2154 this.field_ = field || 0;
2155 this.dataChannel_ = dataChannel || 0;
2156
2157 this.name_ = 'CC' + (((this.field_ << 1) | this.dataChannel_) + 1);
2158
2159 this.setConstants();
2160 this.reset();
2161
2162 this.push = function(packet) {
2163 var data, swap, char0, char1, text;
2164 // remove the parity bits
2165 data = packet.ccData & 0x7f7f;
2166
2167 // ignore duplicate control codes; the spec demands they're sent twice
2168 if (data === this.lastControlCode_) {
2169 this.lastControlCode_ = null;
2170 return;
2171 }
2172
2173 // Store control codes
2174 if ((data & 0xf000) === 0x1000) {
2175 this.lastControlCode_ = data;
2176 } else if (data !== this.PADDING_) {
2177 this.lastControlCode_ = null;
2178 }
2179
2180 char0 = data >>> 8;
2181 char1 = data & 0xff;
2182
2183 if (data === this.PADDING_) {
2184 return;
2185
2186 } else if (data === this.RESUME_CAPTION_LOADING_) {
2187 this.mode_ = 'popOn';
2188
2189 } else if (data === this.END_OF_CAPTION_) {
2190 // If an EOC is received while in paint-on mode, the displayed caption
2191 // text should be swapped to non-displayed memory as if it was a pop-on
2192 // caption. Because of that, we should explicitly switch back to pop-on
2193 // mode
2194 this.mode_ = 'popOn';
2195 this.clearFormatting(packet.pts);
2196 // if a caption was being displayed, it's gone now
2197 this.flushDisplayed(packet.pts);
2198
2199 // flip memory
2200 swap = this.displayed_;
2201 this.displayed_ = this.nonDisplayed_;
2202 this.nonDisplayed_ = swap;
2203
2204 // start measuring the time to display the caption
2205 this.startPts_ = packet.pts;
2206
2207 } else if (data === this.ROLL_UP_2_ROWS_) {
2208 this.rollUpRows_ = 2;
2209 this.setRollUp(packet.pts);
2210 } else if (data === this.ROLL_UP_3_ROWS_) {
2211 this.rollUpRows_ = 3;
2212 this.setRollUp(packet.pts);
2213 } else if (data === this.ROLL_UP_4_ROWS_) {
2214 this.rollUpRows_ = 4;
2215 this.setRollUp(packet.pts);
2216 } else if (data === this.CARRIAGE_RETURN_) {
2217 this.clearFormatting(packet.pts);
2218 this.flushDisplayed(packet.pts);
2219 this.shiftRowsUp_();
2220 this.startPts_ = packet.pts;
2221
2222 } else if (data === this.BACKSPACE_) {
2223 if (this.mode_ === 'popOn') {
2224 this.nonDisplayed_[this.row_] = this.nonDisplayed_[this.row_].slice(0, -1);
2225 } else {
2226 this.displayed_[this.row_] = this.displayed_[this.row_].slice(0, -1);
2227 }
2228 } else if (data === this.ERASE_DISPLAYED_MEMORY_) {
2229 this.flushDisplayed(packet.pts);
2230 this.displayed_ = createDisplayBuffer();
2231 } else if (data === this.ERASE_NON_DISPLAYED_MEMORY_) {
2232 this.nonDisplayed_ = createDisplayBuffer();
2233
2234 } else if (data === this.RESUME_DIRECT_CAPTIONING_) {
2235 if (this.mode_ !== 'paintOn') {
2236 // NOTE: This should be removed when proper caption positioning is
2237 // implemented
2238 this.flushDisplayed(packet.pts);
2239 this.displayed_ = createDisplayBuffer();
2240 }
2241 this.mode_ = 'paintOn';
2242 this.startPts_ = packet.pts;
2243
2244 // Append special characters to caption text
2245 } else if (this.isSpecialCharacter(char0, char1)) {
2246 // Bitmask char0 so that we can apply character transformations
2247 // regardless of field and data channel.
2248 // Then byte-shift to the left and OR with char1 so we can pass the
2249 // entire character code to `getCharFromCode`.
2250 char0 = (char0 & 0x03) << 8;
2251 text = getCharFromCode(char0 | char1);
2252 this[this.mode_](packet.pts, text);
2253 this.column_++;
2254
2255 // Append extended characters to caption text
2256 } else if (this.isExtCharacter(char0, char1)) {
2257 // Extended characters always follow their "non-extended" equivalents.
2258 // IE if a "è" is desired, you'll always receive "eè"; non-compliant
2259 // decoders are supposed to drop the "è", while compliant decoders
2260 // backspace the "e" and insert "è".
2261
2262 // Delete the previous character
2263 if (this.mode_ === 'popOn') {
2264 this.nonDisplayed_[this.row_] = this.nonDisplayed_[this.row_].slice(0, -1);
2265 } else {
2266 this.displayed_[this.row_] = this.displayed_[this.row_].slice(0, -1);
2267 }
2268
2269 // Bitmask char0 so that we can apply character transformations
2270 // regardless of field and data channel.
2271 // Then byte-shift to the left and OR with char1 so we can pass the
2272 // entire character code to `getCharFromCode`.
2273 char0 = (char0 & 0x03) << 8;
2274 text = getCharFromCode(char0 | char1);
2275 this[this.mode_](packet.pts, text);
2276 this.column_++;
2277
2278 // Process mid-row codes
2279 } else if (this.isMidRowCode(char0, char1)) {
2280 // Attributes are not additive, so clear all formatting
2281 this.clearFormatting(packet.pts);
2282
2283 // According to the standard, mid-row codes
2284 // should be replaced with spaces, so add one now
2285 this[this.mode_](packet.pts, ' ');
2286 this.column_++;
2287
2288 if ((char1 & 0xe) === 0xe) {
2289 this.addFormatting(packet.pts, ['i']);
2290 }
2291
2292 if ((char1 & 0x1) === 0x1) {
2293 this.addFormatting(packet.pts, ['u']);
2294 }
2295
2296 // Detect offset control codes and adjust cursor
2297 } else if (this.isOffsetControlCode(char0, char1)) {
2298 // Cursor position is set by indent PAC (see below) in 4-column
2299 // increments, with an additional offset code of 1-3 to reach any
2300 // of the 32 columns specified by CEA-608. So all we need to do
2301 // here is increment the column cursor by the given offset.
2302 this.column_ += (char1 & 0x03);
2303
2304 // Detect PACs (Preamble Address Codes)
2305 } else if (this.isPAC(char0, char1)) {
2306
2307 // There's no logic for PAC -> row mapping, so we have to just
2308 // find the row code in an array and use its index :(
2309 var row = ROWS.indexOf(data & 0x1f20);
2310
2311 // Configure the caption window if we're in roll-up mode
2312 if (this.mode_ === 'rollUp') {
2313 // This implies that the base row is incorrectly set.
2314 // As per the recommendation in CEA-608(Base Row Implementation), defer to the number
2315 // of roll-up rows set.
2316 if (row - this.rollUpRows_ + 1 < 0) {
2317 row = this.rollUpRows_ - 1;
2318 }
2319
2320 this.setRollUp(packet.pts, row);
2321 }
2322
2323 if (row !== this.row_) {
2324 // formatting is only persistent for current row
2325 this.clearFormatting(packet.pts);
2326 this.row_ = row;
2327 }
2328 // All PACs can apply underline, so detect and apply
2329 // (All odd-numbered second bytes set underline)
2330 if ((char1 & 0x1) && (this.formatting_.indexOf('u') === -1)) {
2331 this.addFormatting(packet.pts, ['u']);
2332 }
2333
2334 if ((data & 0x10) === 0x10) {
2335 // We've got an indent level code. Each successive even number
2336 // increments the column cursor by 4, so we can get the desired
2337 // column position by bit-shifting to the right (to get n/2)
2338 // and multiplying by 4.
2339 this.column_ = ((data & 0xe) >> 1) * 4;
2340 }
2341
2342 if (this.isColorPAC(char1)) {
2343 // it's a color code, though we only support white, which
2344 // can be either normal or italicized. white italics can be
2345 // either 0x4e or 0x6e depending on the row, so we just
2346 // bitwise-and with 0xe to see if italics should be turned on
2347 if ((char1 & 0xe) === 0xe) {
2348 this.addFormatting(packet.pts, ['i']);
2349 }
2350 }
2351
2352 // We have a normal character in char0, and possibly one in char1
2353 } else if (this.isNormalChar(char0)) {
2354 if (char1 === 0x00) {
2355 char1 = null;
2356 }
2357 text = getCharFromCode(char0);
2358 text += getCharFromCode(char1);
2359 this[this.mode_](packet.pts, text);
2360 this.column_ += text.length;
2361
2362 } // finish data processing
2363
2364 };
2365 };
2366 Cea608Stream.prototype = new stream();
2367 // Trigger a cue point that captures the current state of the
2368 // display buffer
2369 Cea608Stream.prototype.flushDisplayed = function(pts) {
2370 var content = this.displayed_
2371 // remove spaces from the start and end of the string
2372 .map(function(row) {
2373 try {
2374 return row.trim();
2375 } catch (e) {
2376 // Ordinarily, this shouldn't happen. However, caption
2377 // parsing errors should not throw exceptions and
2378 // break playback.
2379 // eslint-disable-next-line no-console
2380 console.error('Skipping malformed caption.');
2381 return '';
2382 }
2383 })
2384 // combine all text rows to display in one cue
2385 .join('\n')
2386 // and remove blank rows from the start and end, but not the middle
2387 .replace(/^\n+|\n+$/g, '');
2388
2389 if (content.length) {
2390 this.trigger('data', {
2391 startPts: this.startPts_,
2392 endPts: pts,
2393 text: content,
2394 stream: this.name_
2395 });
2396 }
2397 };
2398
2399 /**
2400 * Zero out the data, used for startup and on seek
2401 */
2402 Cea608Stream.prototype.reset = function() {
2403 this.mode_ = 'popOn';
2404 // When in roll-up mode, the index of the last row that will
2405 // actually display captions. If a caption is shifted to a row
2406 // with a lower index than this, it is cleared from the display
2407 // buffer
2408 this.topRow_ = 0;
2409 this.startPts_ = 0;
2410 this.displayed_ = createDisplayBuffer();
2411 this.nonDisplayed_ = createDisplayBuffer();
2412 this.lastControlCode_ = null;
2413
2414 // Track row and column for proper line-breaking and spacing
2415 this.column_ = 0;
2416 this.row_ = BOTTOM_ROW;
2417 this.rollUpRows_ = 2;
2418
2419 // This variable holds currently-applied formatting
2420 this.formatting_ = [];
2421 };
2422
2423 /**
2424 * Sets up control code and related constants for this instance
2425 */
2426 Cea608Stream.prototype.setConstants = function() {
2427 // The following attributes have these uses:
2428 // ext_ : char0 for mid-row codes, and the base for extended
2429 // chars (ext_+0, ext_+1, and ext_+2 are char0s for
2430 // extended codes)
2431 // control_: char0 for control codes, except byte-shifted to the
2432 // left so that we can do this.control_ | CONTROL_CODE
2433 // offset_: char0 for tab offset codes
2434 //
2435 // It's also worth noting that control codes, and _only_ control codes,
2436 // differ between field 1 and field2. Field 2 control codes are always
2437 // their field 1 value plus 1. That's why there's the "| field" on the
2438 // control value.
2439 if (this.dataChannel_ === 0) {
2440 this.BASE_ = 0x10;
2441 this.EXT_ = 0x11;
2442 this.CONTROL_ = (0x14 | this.field_) << 8;
2443 this.OFFSET_ = 0x17;
2444 } else if (this.dataChannel_ === 1) {
2445 this.BASE_ = 0x18;
2446 this.EXT_ = 0x19;
2447 this.CONTROL_ = (0x1c | this.field_) << 8;
2448 this.OFFSET_ = 0x1f;
2449 }
2450
2451 // Constants for the LSByte command codes recognized by Cea608Stream. This
2452 // list is not exhaustive. For a more comprehensive listing and semantics see
2453 // http://www.gpo.gov/fdsys/pkg/CFR-2010-title47-vol1/pdf/CFR-2010-title47-vol1-sec15-119.pdf
2454 // Padding
2455 this.PADDING_ = 0x0000;
2456 // Pop-on Mode
2457 this.RESUME_CAPTION_LOADING_ = this.CONTROL_ | 0x20;
2458 this.END_OF_CAPTION_ = this.CONTROL_ | 0x2f;
2459 // Roll-up Mode
2460 this.ROLL_UP_2_ROWS_ = this.CONTROL_ | 0x25;
2461 this.ROLL_UP_3_ROWS_ = this.CONTROL_ | 0x26;
2462 this.ROLL_UP_4_ROWS_ = this.CONTROL_ | 0x27;
2463 this.CARRIAGE_RETURN_ = this.CONTROL_ | 0x2d;
2464 // paint-on mode
2465 this.RESUME_DIRECT_CAPTIONING_ = this.CONTROL_ | 0x29;
2466 // Erasure
2467 this.BACKSPACE_ = this.CONTROL_ | 0x21;
2468 this.ERASE_DISPLAYED_MEMORY_ = this.CONTROL_ | 0x2c;
2469 this.ERASE_NON_DISPLAYED_MEMORY_ = this.CONTROL_ | 0x2e;
2470 };
2471
2472 /**
2473 * Detects if the 2-byte packet data is a special character
2474 *
2475 * Special characters have a second byte in the range 0x30 to 0x3f,
2476 * with the first byte being 0x11 (for data channel 1) or 0x19 (for
2477 * data channel 2).
2478 *
2479 * @param {Integer} char0 The first byte
2480 * @param {Integer} char1 The second byte
2481 * @return {Boolean} Whether the 2 bytes are an special character
2482 */
2483 Cea608Stream.prototype.isSpecialCharacter = function(char0, char1) {
2484 return (char0 === this.EXT_ && char1 >= 0x30 && char1 <= 0x3f);
2485 };
2486
2487 /**
2488 * Detects if the 2-byte packet data is an extended character
2489 *
2490 * Extended characters have a second byte in the range 0x20 to 0x3f,
2491 * with the first byte being 0x12 or 0x13 (for data channel 1) or
2492 * 0x1a or 0x1b (for data channel 2).
2493 *
2494 * @param {Integer} char0 The first byte
2495 * @param {Integer} char1 The second byte
2496 * @return {Boolean} Whether the 2 bytes are an extended character
2497 */
2498 Cea608Stream.prototype.isExtCharacter = function(char0, char1) {
2499 return ((char0 === (this.EXT_ + 1) || char0 === (this.EXT_ + 2)) &&
2500 (char1 >= 0x20 && char1 <= 0x3f));
2501 };
2502
2503 /**
2504 * Detects if the 2-byte packet is a mid-row code
2505 *
2506 * Mid-row codes have a second byte in the range 0x20 to 0x2f, with
2507 * the first byte being 0x11 (for data channel 1) or 0x19 (for data
2508 * channel 2).
2509 *
2510 * @param {Integer} char0 The first byte
2511 * @param {Integer} char1 The second byte
2512 * @return {Boolean} Whether the 2 bytes are a mid-row code
2513 */
2514 Cea608Stream.prototype.isMidRowCode = function(char0, char1) {
2515 return (char0 === this.EXT_ && (char1 >= 0x20 && char1 <= 0x2f));
2516 };
2517
2518 /**
2519 * Detects if the 2-byte packet is an offset control code
2520 *
2521 * Offset control codes have a second byte in the range 0x21 to 0x23,
2522 * with the first byte being 0x17 (for data channel 1) or 0x1f (for
2523 * data channel 2).
2524 *
2525 * @param {Integer} char0 The first byte
2526 * @param {Integer} char1 The second byte
2527 * @return {Boolean} Whether the 2 bytes are an offset control code
2528 */
2529 Cea608Stream.prototype.isOffsetControlCode = function(char0, char1) {
2530 return (char0 === this.OFFSET_ && (char1 >= 0x21 && char1 <= 0x23));
2531 };
2532
2533 /**
2534 * Detects if the 2-byte packet is a Preamble Address Code
2535 *
2536 * PACs have a first byte in the range 0x10 to 0x17 (for data channel 1)
2537 * or 0x18 to 0x1f (for data channel 2), with the second byte in the
2538 * range 0x40 to 0x7f.
2539 *
2540 * @param {Integer} char0 The first byte
2541 * @param {Integer} char1 The second byte
2542 * @return {Boolean} Whether the 2 bytes are a PAC
2543 */
2544 Cea608Stream.prototype.isPAC = function(char0, char1) {
2545 return (char0 >= this.BASE_ && char0 < (this.BASE_ + 8) &&
2546 (char1 >= 0x40 && char1 <= 0x7f));
2547 };
2548
2549 /**
2550 * Detects if a packet's second byte is in the range of a PAC color code
2551 *
2552 * PAC color codes have the second byte be in the range 0x40 to 0x4f, or
2553 * 0x60 to 0x6f.
2554 *
2555 * @param {Integer} char1 The second byte
2556 * @return {Boolean} Whether the byte is a color PAC
2557 */
2558 Cea608Stream.prototype.isColorPAC = function(char1) {
2559 return ((char1 >= 0x40 && char1 <= 0x4f) || (char1 >= 0x60 && char1 <= 0x7f));
2560 };
2561
2562 /**
2563 * Detects if a single byte is in the range of a normal character
2564 *
2565 * Normal text bytes are in the range 0x20 to 0x7f.
2566 *
2567 * @param {Integer} char The byte
2568 * @return {Boolean} Whether the byte is a normal character
2569 */
2570 Cea608Stream.prototype.isNormalChar = function(char) {
2571 return (char >= 0x20 && char <= 0x7f);
2572 };
2573
2574 /**
2575 * Configures roll-up
2576 *
2577 * @param {Integer} pts Current PTS
2578 * @param {Integer} newBaseRow Used by PACs to slide the current window to
2579 * a new position
2580 */
2581 Cea608Stream.prototype.setRollUp = function(pts, newBaseRow) {
2582 // Reset the base row to the bottom row when switching modes
2583 if (this.mode_ !== 'rollUp') {
2584 this.row_ = BOTTOM_ROW;
2585 this.mode_ = 'rollUp';
2586 // Spec says to wipe memories when switching to roll-up
2587 this.flushDisplayed(pts);
2588 this.nonDisplayed_ = createDisplayBuffer();
2589 this.displayed_ = createDisplayBuffer();
2590 }
2591
2592 if (newBaseRow !== undefined && newBaseRow !== this.row_) {
2593 // move currently displayed captions (up or down) to the new base row
2594 for (var i = 0; i < this.rollUpRows_; i++) {
2595 this.displayed_[newBaseRow - i] = this.displayed_[this.row_ - i];
2596 this.displayed_[this.row_ - i] = '';
2597 }
2598 }
2599
2600 if (newBaseRow === undefined) {
2601 newBaseRow = this.row_;
2602 }
2603
2604 this.topRow_ = newBaseRow - this.rollUpRows_ + 1;
2605 };
2606
2607 // Adds the opening HTML tag for the passed character to the caption text,
2608 // and keeps track of it for later closing
2609 Cea608Stream.prototype.addFormatting = function(pts, format) {
2610 this.formatting_ = this.formatting_.concat(format);
2611 var text = format.reduce(function(text, format) {
2612 return text + '<' + format + '>';
2613 }, '');
2614 this[this.mode_](pts, text);
2615 };
2616
2617 // Adds HTML closing tags for current formatting to caption text and
2618 // clears remembered formatting
2619 Cea608Stream.prototype.clearFormatting = function(pts) {
2620 if (!this.formatting_.length) {
2621 return;
2622 }
2623 var text = this.formatting_.reverse().reduce(function(text, format) {
2624 return text + '</' + format + '>';
2625 }, '');
2626 this.formatting_ = [];
2627 this[this.mode_](pts, text);
2628 };
2629
2630 // Mode Implementations
2631 Cea608Stream.prototype.popOn = function(pts, text) {
2632 var baseRow = this.nonDisplayed_[this.row_];
2633
2634 // buffer characters
2635 baseRow += text;
2636 this.nonDisplayed_[this.row_] = baseRow;
2637 };
2638
2639 Cea608Stream.prototype.rollUp = function(pts, text) {
2640 var baseRow = this.displayed_[this.row_];
2641
2642 baseRow += text;
2643 this.displayed_[this.row_] = baseRow;
2644
2645 };
2646
2647 Cea608Stream.prototype.shiftRowsUp_ = function() {
2648 var i;
2649 // clear out inactive rows
2650 for (i = 0; i < this.topRow_; i++) {
2651 this.displayed_[i] = '';
2652 }
2653 for (i = this.row_ + 1; i < BOTTOM_ROW + 1; i++) {
2654 this.displayed_[i] = '';
2655 }
2656 // shift displayed rows up
2657 for (i = this.topRow_; i < this.row_; i++) {
2658 this.displayed_[i] = this.displayed_[i + 1];
2659 }
2660 // clear out the bottom row
2661 this.displayed_[this.row_] = '';
2662 };
2663
2664 Cea608Stream.prototype.paintOn = function(pts, text) {
2665 var baseRow = this.displayed_[this.row_];
2666
2667 baseRow += text;
2668 this.displayed_[this.row_] = baseRow;
2669 };
2670
2671 // exports
2672 var captionStream = {
2673 CaptionStream: CaptionStream,
2674 Cea608Stream: Cea608Stream
2675 };
2676
2677 /**
2678 * mux.js
2679 *
2680 * Copyright (c) Brightcove
2681 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
2682 */
2683
2684 var streamTypes = {
2685 H264_STREAM_TYPE: 0x1B,
2686 ADTS_STREAM_TYPE: 0x0F,
2687 METADATA_STREAM_TYPE: 0x15
2688 };
2689
2690 var MAX_TS = 8589934592;
2691
2692 var RO_THRESH = 4294967296;
2693
2694 var TYPE_SHARED = 'shared';
2695
2696 var handleRollover = function(value, reference) {
2697 var direction = 1;
2698
2699 if (value > reference) {
2700 // If the current timestamp value is greater than our reference timestamp and we detect a
2701 // timestamp rollover, this means the roll over is happening in the opposite direction.
2702 // Example scenario: Enter a long stream/video just after a rollover occurred. The reference
2703 // point will be set to a small number, e.g. 1. The user then seeks backwards over the
2704 // rollover point. In loading this segment, the timestamp values will be very large,
2705 // e.g. 2^33 - 1. Since this comes before the data we loaded previously, we want to adjust
2706 // the time stamp to be `value - 2^33`.
2707 direction = -1;
2708 }
2709
2710 // Note: A seek forwards or back that is greater than the RO_THRESH (2^32, ~13 hours) will
2711 // cause an incorrect adjustment.
2712 while (Math.abs(reference - value) > RO_THRESH) {
2713 value += (direction * MAX_TS);
2714 }
2715
2716 return value;
2717 };
2718
2719 var TimestampRolloverStream = function(type) {
2720 var lastDTS, referenceDTS;
2721
2722 TimestampRolloverStream.prototype.init.call(this);
2723
2724 // The "shared" type is used in cases where a stream will contain muxed
2725 // video and audio. We could use `undefined` here, but having a string
2726 // makes debugging a little clearer.
2727 this.type_ = type || TYPE_SHARED;
2728
2729 this.push = function(data) {
2730
2731 // Any "shared" rollover streams will accept _all_ data. Otherwise,
2732 // streams will only accept data that matches their type.
2733 if (this.type_ !== TYPE_SHARED && data.type !== this.type_) {
2734 return;
2735 }
2736
2737 if (referenceDTS === undefined) {
2738 referenceDTS = data.dts;
2739 }
2740
2741 data.dts = handleRollover(data.dts, referenceDTS);
2742 data.pts = handleRollover(data.pts, referenceDTS);
2743
2744 lastDTS = data.dts;
2745
2746 this.trigger('data', data);
2747 };
2748
2749 this.flush = function() {
2750 referenceDTS = lastDTS;
2751 this.trigger('done');
2752 };
2753
2754 this.endTimeline = function() {
2755 this.flush();
2756 this.trigger('endedtimeline');
2757 };
2758
2759 this.discontinuity = function() {
2760 referenceDTS = void 0;
2761 lastDTS = void 0;
2762 };
2763
2764 this.reset = function() {
2765 this.discontinuity();
2766 this.trigger('reset');
2767 };
2768 };
2769
2770 TimestampRolloverStream.prototype = new stream();
2771
2772 var timestampRolloverStream = {
2773 TimestampRolloverStream: TimestampRolloverStream,
2774 handleRollover: handleRollover
2775 };
2776
2777 var
2778 percentEncode = function(bytes, start, end) {
2779 var i, result = '';
2780 for (i = start; i < end; i++) {
2781 result += '%' + ('00' + bytes[i].toString(16)).slice(-2);
2782 }
2783 return result;
2784 },
2785 // return the string representation of the specified byte range,
2786 // interpreted as UTf-8.
2787 parseUtf8 = function(bytes, start, end) {
2788 return decodeURIComponent(percentEncode(bytes, start, end));
2789 },
2790 // return the string representation of the specified byte range,
2791 // interpreted as ISO-8859-1.
2792 parseIso88591 = function(bytes, start, end) {
2793 return unescape(percentEncode(bytes, start, end)); // jshint ignore:line
2794 },
2795 parseSyncSafeInteger = function(data) {
2796 return (data[0] << 21) |
2797 (data[1] << 14) |
2798 (data[2] << 7) |
2799 (data[3]);
2800 },
2801 tagParsers = {
2802 TXXX: function(tag) {
2803 var i;
2804 if (tag.data[0] !== 3) {
2805 // ignore frames with unrecognized character encodings
2806 return;
2807 }
2808
2809 for (i = 1; i < tag.data.length; i++) {
2810 if (tag.data[i] === 0) {
2811 // parse the text fields
2812 tag.description = parseUtf8(tag.data, 1, i);
2813 // do not include the null terminator in the tag value
2814 tag.value = parseUtf8(tag.data, i + 1, tag.data.length).replace(/\0*$/, '');
2815 break;
2816 }
2817 }
2818 tag.data = tag.value;
2819 },
2820 WXXX: function(tag) {
2821 var i;
2822 if (tag.data[0] !== 3) {
2823 // ignore frames with unrecognized character encodings
2824 return;
2825 }
2826
2827 for (i = 1; i < tag.data.length; i++) {
2828 if (tag.data[i] === 0) {
2829 // parse the description and URL fields
2830 tag.description = parseUtf8(tag.data, 1, i);
2831 tag.url = parseUtf8(tag.data, i + 1, tag.data.length);
2832 break;
2833 }
2834 }
2835 },
2836 PRIV: function(tag) {
2837 var i;
2838
2839 for (i = 0; i < tag.data.length; i++) {
2840 if (tag.data[i] === 0) {
2841 // parse the description and URL fields
2842 tag.owner = parseIso88591(tag.data, 0, i);
2843 break;
2844 }
2845 }
2846 tag.privateData = tag.data.subarray(i + 1);
2847 tag.data = tag.privateData;
2848 }
2849 },
2850 MetadataStream;
2851
2852 MetadataStream = function(options) {
2853 var
2854 settings = {
2855 debug: !!(options && options.debug),
2856
2857 // the bytes of the program-level descriptor field in MP2T
2858 // see ISO/IEC 13818-1:2013 (E), section 2.6 "Program and
2859 // program element descriptors"
2860 descriptor: options && options.descriptor
2861 },
2862 // the total size in bytes of the ID3 tag being parsed
2863 tagSize = 0,
2864 // tag data that is not complete enough to be parsed
2865 buffer = [],
2866 // the total number of bytes currently in the buffer
2867 bufferSize = 0,
2868 i;
2869
2870 MetadataStream.prototype.init.call(this);
2871
2872 // calculate the text track in-band metadata track dispatch type
2873 // https://html.spec.whatwg.org/multipage/embedded-content.html#steps-to-expose-a-media-resource-specific-text-track
2874 this.dispatchType = streamTypes.METADATA_STREAM_TYPE.toString(16);
2875 if (settings.descriptor) {
2876 for (i = 0; i < settings.descriptor.length; i++) {
2877 this.dispatchType += ('00' + settings.descriptor[i].toString(16)).slice(-2);
2878 }
2879 }
2880
2881 this.push = function(chunk) {
2882 var tag, frameStart, frameSize, frame, i, frameHeader;
2883 if (chunk.type !== 'timed-metadata') {
2884 return;
2885 }
2886
2887 // if data_alignment_indicator is set in the PES header,
2888 // we must have the start of a new ID3 tag. Assume anything
2889 // remaining in the buffer was malformed and throw it out
2890 if (chunk.dataAlignmentIndicator) {
2891 bufferSize = 0;
2892 buffer.length = 0;
2893 }
2894
2895 // ignore events that don't look like ID3 data
2896 if (buffer.length === 0 &&
2897 (chunk.data.length < 10 ||
2898 chunk.data[0] !== 'I'.charCodeAt(0) ||
2899 chunk.data[1] !== 'D'.charCodeAt(0) ||
2900 chunk.data[2] !== '3'.charCodeAt(0))) {
2901 if (settings.debug) {
2902 // eslint-disable-next-line no-console
2903 console.log('Skipping unrecognized metadata packet');
2904 }
2905 return;
2906 }
2907
2908 // add this chunk to the data we've collected so far
2909
2910 buffer.push(chunk);
2911 bufferSize += chunk.data.byteLength;
2912
2913 // grab the size of the entire frame from the ID3 header
2914 if (buffer.length === 1) {
2915 // the frame size is transmitted as a 28-bit integer in the
2916 // last four bytes of the ID3 header.
2917 // The most significant bit of each byte is dropped and the
2918 // results concatenated to recover the actual value.
2919 tagSize = parseSyncSafeInteger(chunk.data.subarray(6, 10));
2920
2921 // ID3 reports the tag size excluding the header but it's more
2922 // convenient for our comparisons to include it
2923 tagSize += 10;
2924 }
2925
2926 // if the entire frame has not arrived, wait for more data
2927 if (bufferSize < tagSize) {
2928 return;
2929 }
2930
2931 // collect the entire frame so it can be parsed
2932 tag = {
2933 data: new Uint8Array(tagSize),
2934 frames: [],
2935 pts: buffer[0].pts,
2936 dts: buffer[0].dts
2937 };
2938 for (i = 0; i < tagSize;) {
2939 tag.data.set(buffer[0].data.subarray(0, tagSize - i), i);
2940 i += buffer[0].data.byteLength;
2941 bufferSize -= buffer[0].data.byteLength;
2942 buffer.shift();
2943 }
2944
2945 // find the start of the first frame and the end of the tag
2946 frameStart = 10;
2947 if (tag.data[5] & 0x40) {
2948 // advance the frame start past the extended header
2949 frameStart += 4; // header size field
2950 frameStart += parseSyncSafeInteger(tag.data.subarray(10, 14));
2951
2952 // clip any padding off the end
2953 tagSize -= parseSyncSafeInteger(tag.data.subarray(16, 20));
2954 }
2955
2956 // parse one or more ID3 frames
2957 // http://id3.org/id3v2.3.0#ID3v2_frame_overview
2958 do {
2959 // determine the number of bytes in this frame
2960 frameSize = parseSyncSafeInteger(tag.data.subarray(frameStart + 4, frameStart + 8));
2961 if (frameSize < 1) {
2962 // eslint-disable-next-line no-console
2963 return console.log('Malformed ID3 frame encountered. Skipping metadata parsing.');
2964 }
2965 frameHeader = String.fromCharCode(tag.data[frameStart],
2966 tag.data[frameStart + 1],
2967 tag.data[frameStart + 2],
2968 tag.data[frameStart + 3]);
2969
2970
2971 frame = {
2972 id: frameHeader,
2973 data: tag.data.subarray(frameStart + 10, frameStart + frameSize + 10)
2974 };
2975 frame.key = frame.id;
2976 if (tagParsers[frame.id]) {
2977 tagParsers[frame.id](frame);
2978
2979 // handle the special PRIV frame used to indicate the start
2980 // time for raw AAC data
2981 if (frame.owner === 'com.apple.streaming.transportStreamTimestamp') {
2982 var
2983 d = frame.data,
2984 size = ((d[3] & 0x01) << 30) |
2985 (d[4] << 22) |
2986 (d[5] << 14) |
2987 (d[6] << 6) |
2988 (d[7] >>> 2);
2989
2990 size *= 4;
2991 size += d[7] & 0x03;
2992 frame.timeStamp = size;
2993 // in raw AAC, all subsequent data will be timestamped based
2994 // on the value of this frame
2995 // we couldn't have known the appropriate pts and dts before
2996 // parsing this ID3 tag so set those values now
2997 if (tag.pts === undefined && tag.dts === undefined) {
2998 tag.pts = frame.timeStamp;
2999 tag.dts = frame.timeStamp;
3000 }
3001 this.trigger('timestamp', frame);
3002 }
3003 }
3004 tag.frames.push(frame);
3005
3006 frameStart += 10; // advance past the frame header
3007 frameStart += frameSize; // advance past the frame body
3008 } while (frameStart < tagSize);
3009 this.trigger('data', tag);
3010 };
3011 };
3012 MetadataStream.prototype = new stream();
3013
3014 var metadataStream = MetadataStream;
3015
3016 var TimestampRolloverStream$1 = timestampRolloverStream.TimestampRolloverStream;
3017
3018 // object types
3019 var TransportPacketStream, TransportParseStream, ElementaryStream;
3020
3021 // constants
3022 var
3023 MP2T_PACKET_LENGTH = 188, // bytes
3024 SYNC_BYTE = 0x47;
3025
3026 /**
3027 * Splits an incoming stream of binary data into MPEG-2 Transport
3028 * Stream packets.
3029 */
3030 TransportPacketStream = function() {
3031 var
3032 buffer = new Uint8Array(MP2T_PACKET_LENGTH),
3033 bytesInBuffer = 0;
3034
3035 TransportPacketStream.prototype.init.call(this);
3036
3037 // Deliver new bytes to the stream.
3038
3039 /**
3040 * Split a stream of data into M2TS packets
3041 **/
3042 this.push = function(bytes) {
3043 var
3044 startIndex = 0,
3045 endIndex = MP2T_PACKET_LENGTH,
3046 everything;
3047
3048 // If there are bytes remaining from the last segment, prepend them to the
3049 // bytes that were pushed in
3050 if (bytesInBuffer) {
3051 everything = new Uint8Array(bytes.byteLength + bytesInBuffer);
3052 everything.set(buffer.subarray(0, bytesInBuffer));
3053 everything.set(bytes, bytesInBuffer);
3054 bytesInBuffer = 0;
3055 } else {
3056 everything = bytes;
3057 }
3058
3059 // While we have enough data for a packet
3060 while (endIndex < everything.byteLength) {
3061 // Look for a pair of start and end sync bytes in the data..
3062 if (everything[startIndex] === SYNC_BYTE && everything[endIndex] === SYNC_BYTE) {
3063 // We found a packet so emit it and jump one whole packet forward in
3064 // the stream
3065 this.trigger('data', everything.subarray(startIndex, endIndex));
3066 startIndex += MP2T_PACKET_LENGTH;
3067 endIndex += MP2T_PACKET_LENGTH;
3068 continue;
3069 }
3070 // If we get here, we have somehow become de-synchronized and we need to step
3071 // forward one byte at a time until we find a pair of sync bytes that denote
3072 // a packet
3073 startIndex++;
3074 endIndex++;
3075 }
3076
3077 // If there was some data left over at the end of the segment that couldn't
3078 // possibly be a whole packet, keep it because it might be the start of a packet
3079 // that continues in the next segment
3080 if (startIndex < everything.byteLength) {
3081 buffer.set(everything.subarray(startIndex), 0);
3082 bytesInBuffer = everything.byteLength - startIndex;
3083 }
3084 };
3085
3086 /**
3087 * Passes identified M2TS packets to the TransportParseStream to be parsed
3088 **/
3089 this.flush = function() {
3090 // If the buffer contains a whole packet when we are being flushed, emit it
3091 // and empty the buffer. Otherwise hold onto the data because it may be
3092 // important for decoding the next segment
3093 if (bytesInBuffer === MP2T_PACKET_LENGTH && buffer[0] === SYNC_BYTE) {
3094 this.trigger('data', buffer);
3095 bytesInBuffer = 0;
3096 }
3097 this.trigger('done');
3098 };
3099
3100 this.endTimeline = function() {
3101 this.flush();
3102 this.trigger('endedtimeline');
3103 };
3104
3105 this.reset = function() {
3106 bytesInBuffer = 0;
3107 this.trigger('reset');
3108 };
3109 };
3110 TransportPacketStream.prototype = new stream();
3111
3112 /**
3113 * Accepts an MP2T TransportPacketStream and emits data events with parsed
3114 * forms of the individual transport stream packets.
3115 */
3116 TransportParseStream = function() {
3117 var parsePsi, parsePat, parsePmt, self;
3118 TransportParseStream.prototype.init.call(this);
3119 self = this;
3120
3121 this.packetsWaitingForPmt = [];
3122 this.programMapTable = undefined;
3123
3124 parsePsi = function(payload, psi) {
3125 var offset = 0;
3126
3127 // PSI packets may be split into multiple sections and those
3128 // sections may be split into multiple packets. If a PSI
3129 // section starts in this packet, the payload_unit_start_indicator
3130 // will be true and the first byte of the payload will indicate
3131 // the offset from the current position to the start of the
3132 // section.
3133 if (psi.payloadUnitStartIndicator) {
3134 offset += payload[offset] + 1;
3135 }
3136
3137 if (psi.type === 'pat') {
3138 parsePat(payload.subarray(offset), psi);
3139 } else {
3140 parsePmt(payload.subarray(offset), psi);
3141 }
3142 };
3143
3144 parsePat = function(payload, pat) {
3145 pat.section_number = payload[7]; // eslint-disable-line camelcase
3146 pat.last_section_number = payload[8]; // eslint-disable-line camelcase
3147
3148 // skip the PSI header and parse the first PMT entry
3149 self.pmtPid = (payload[10] & 0x1F) << 8 | payload[11];
3150 pat.pmtPid = self.pmtPid;
3151 };
3152
3153 /**
3154 * Parse out the relevant fields of a Program Map Table (PMT).
3155 * @param payload {Uint8Array} the PMT-specific portion of an MP2T
3156 * packet. The first byte in this array should be the table_id
3157 * field.
3158 * @param pmt {object} the object that should be decorated with
3159 * fields parsed from the PMT.
3160 */
3161 parsePmt = function(payload, pmt) {
3162 var sectionLength, tableEnd, programInfoLength, offset;
3163
3164 // PMTs can be sent ahead of the time when they should actually
3165 // take effect. We don't believe this should ever be the case
3166 // for HLS but we'll ignore "forward" PMT declarations if we see
3167 // them. Future PMT declarations have the current_next_indicator
3168 // set to zero.
3169 if (!(payload[5] & 0x01)) {
3170 return;
3171 }
3172
3173 // overwrite any existing program map table
3174 self.programMapTable = {
3175 video: null,
3176 audio: null,
3177 'timed-metadata': {}
3178 };
3179
3180 // the mapping table ends at the end of the current section
3181 sectionLength = (payload[1] & 0x0f) << 8 | payload[2];
3182 tableEnd = 3 + sectionLength - 4;
3183
3184 // to determine where the table is, we have to figure out how
3185 // long the program info descriptors are
3186 programInfoLength = (payload[10] & 0x0f) << 8 | payload[11];
3187
3188 // advance the offset to the first entry in the mapping table
3189 offset = 12 + programInfoLength;
3190 while (offset < tableEnd) {
3191 var streamType = payload[offset];
3192 var pid = (payload[offset + 1] & 0x1F) << 8 | payload[offset + 2];
3193
3194 // only map a single elementary_pid for audio and video stream types
3195 // TODO: should this be done for metadata too? for now maintain behavior of
3196 // multiple metadata streams
3197 if (streamType === streamTypes.H264_STREAM_TYPE &&
3198 self.programMapTable.video === null) {
3199 self.programMapTable.video = pid;
3200 } else if (streamType === streamTypes.ADTS_STREAM_TYPE &&
3201 self.programMapTable.audio === null) {
3202 self.programMapTable.audio = pid;
3203 } else if (streamType === streamTypes.METADATA_STREAM_TYPE) {
3204 // map pid to stream type for metadata streams
3205 self.programMapTable['timed-metadata'][pid] = streamType;
3206 }
3207
3208 // move to the next table entry
3209 // skip past the elementary stream descriptors, if present
3210 offset += ((payload[offset + 3] & 0x0F) << 8 | payload[offset + 4]) + 5;
3211 }
3212
3213 // record the map on the packet as well
3214 pmt.programMapTable = self.programMapTable;
3215 };
3216
3217 /**
3218 * Deliver a new MP2T packet to the next stream in the pipeline.
3219 */
3220 this.push = function(packet) {
3221 var
3222 result = {},
3223 offset = 4;
3224
3225 result.payloadUnitStartIndicator = !!(packet[1] & 0x40);
3226
3227 // pid is a 13-bit field starting at the last bit of packet[1]
3228 result.pid = packet[1] & 0x1f;
3229 result.pid <<= 8;
3230 result.pid |= packet[2];
3231
3232 // if an adaption field is present, its length is specified by the
3233 // fifth byte of the TS packet header. The adaptation field is
3234 // used to add stuffing to PES packets that don't fill a complete
3235 // TS packet, and to specify some forms of timing and control data
3236 // that we do not currently use.
3237 if (((packet[3] & 0x30) >>> 4) > 0x01) {
3238 offset += packet[offset] + 1;
3239 }
3240
3241 // parse the rest of the packet based on the type
3242 if (result.pid === 0) {
3243 result.type = 'pat';
3244 parsePsi(packet.subarray(offset), result);
3245 this.trigger('data', result);
3246 } else if (result.pid === this.pmtPid) {
3247 result.type = 'pmt';
3248 parsePsi(packet.subarray(offset), result);
3249 this.trigger('data', result);
3250
3251 // if there are any packets waiting for a PMT to be found, process them now
3252 while (this.packetsWaitingForPmt.length) {
3253 this.processPes_.apply(this, this.packetsWaitingForPmt.shift());
3254 }
3255 } else if (this.programMapTable === undefined) {
3256 // When we have not seen a PMT yet, defer further processing of
3257 // PES packets until one has been parsed
3258 this.packetsWaitingForPmt.push([packet, offset, result]);
3259 } else {
3260 this.processPes_(packet, offset, result);
3261 }
3262 };
3263
3264 this.processPes_ = function(packet, offset, result) {
3265 // set the appropriate stream type
3266 if (result.pid === this.programMapTable.video) {
3267 result.streamType = streamTypes.H264_STREAM_TYPE;
3268 } else if (result.pid === this.programMapTable.audio) {
3269 result.streamType = streamTypes.ADTS_STREAM_TYPE;
3270 } else {
3271 // if not video or audio, it is timed-metadata or unknown
3272 // if unknown, streamType will be undefined
3273 result.streamType = this.programMapTable['timed-metadata'][result.pid];
3274 }
3275
3276 result.type = 'pes';
3277 result.data = packet.subarray(offset);
3278 this.trigger('data', result);
3279 };
3280 };
3281 TransportParseStream.prototype = new stream();
3282 TransportParseStream.STREAM_TYPES = {
3283 h264: 0x1b,
3284 adts: 0x0f
3285 };
3286
3287 /**
3288 * Reconsistutes program elementary stream (PES) packets from parsed
3289 * transport stream packets. That is, if you pipe an
3290 * mp2t.TransportParseStream into a mp2t.ElementaryStream, the output
3291 * events will be events which capture the bytes for individual PES
3292 * packets plus relevant metadata that has been extracted from the
3293 * container.
3294 */
3295 ElementaryStream = function() {
3296 var
3297 self = this,
3298 // PES packet fragments
3299 video = {
3300 data: [],
3301 size: 0
3302 },
3303 audio = {
3304 data: [],
3305 size: 0
3306 },
3307 timedMetadata = {
3308 data: [],
3309 size: 0
3310 },
3311 programMapTable,
3312 parsePes = function(payload, pes) {
3313 var ptsDtsFlags;
3314
3315 // get the packet length, this will be 0 for video
3316 pes.packetLength = 6 + ((payload[4] << 8) | payload[5]);
3317
3318 // find out if this packets starts a new keyframe
3319 pes.dataAlignmentIndicator = (payload[6] & 0x04) !== 0;
3320 // PES packets may be annotated with a PTS value, or a PTS value
3321 // and a DTS value. Determine what combination of values is
3322 // available to work with.
3323 ptsDtsFlags = payload[7];
3324
3325 // PTS and DTS are normally stored as a 33-bit number. Javascript
3326 // performs all bitwise operations on 32-bit integers but javascript
3327 // supports a much greater range (52-bits) of integer using standard
3328 // mathematical operations.
3329 // We construct a 31-bit value using bitwise operators over the 31
3330 // most significant bits and then multiply by 4 (equal to a left-shift
3331 // of 2) before we add the final 2 least significant bits of the
3332 // timestamp (equal to an OR.)
3333 if (ptsDtsFlags & 0xC0) {
3334 // the PTS and DTS are not written out directly. For information
3335 // on how they are encoded, see
3336 // http://dvd.sourceforge.net/dvdinfo/pes-hdr.html
3337 pes.pts = (payload[9] & 0x0E) << 27 |
3338 (payload[10] & 0xFF) << 20 |
3339 (payload[11] & 0xFE) << 12 |
3340 (payload[12] & 0xFF) << 5 |
3341 (payload[13] & 0xFE) >>> 3;
3342 pes.pts *= 4; // Left shift by 2
3343 pes.pts += (payload[13] & 0x06) >>> 1; // OR by the two LSBs
3344 pes.dts = pes.pts;
3345 if (ptsDtsFlags & 0x40) {
3346 pes.dts = (payload[14] & 0x0E) << 27 |
3347 (payload[15] & 0xFF) << 20 |
3348 (payload[16] & 0xFE) << 12 |
3349 (payload[17] & 0xFF) << 5 |
3350 (payload[18] & 0xFE) >>> 3;
3351 pes.dts *= 4; // Left shift by 2
3352 pes.dts += (payload[18] & 0x06) >>> 1; // OR by the two LSBs
3353 }
3354 }
3355 // the data section starts immediately after the PES header.
3356 // pes_header_data_length specifies the number of header bytes
3357 // that follow the last byte of the field.
3358 pes.data = payload.subarray(9 + payload[8]);
3359 },
3360 /**
3361 * Pass completely parsed PES packets to the next stream in the pipeline
3362 **/
3363 flushStream = function(stream, type, forceFlush) {
3364 var
3365 packetData = new Uint8Array(stream.size),
3366 event = {
3367 type: type
3368 },
3369 i = 0,
3370 offset = 0,
3371 packetFlushable = false,
3372 fragment;
3373
3374 // do nothing if there is not enough buffered data for a complete
3375 // PES header
3376 if (!stream.data.length || stream.size < 9) {
3377 return;
3378 }
3379 event.trackId = stream.data[0].pid;
3380
3381 // reassemble the packet
3382 for (i = 0; i < stream.data.length; i++) {
3383 fragment = stream.data[i];
3384
3385 packetData.set(fragment.data, offset);
3386 offset += fragment.data.byteLength;
3387 }
3388
3389 // parse assembled packet's PES header
3390 parsePes(packetData, event);
3391
3392 // non-video PES packets MUST have a non-zero PES_packet_length
3393 // check that there is enough stream data to fill the packet
3394 packetFlushable = type === 'video' || event.packetLength <= stream.size;
3395
3396 // flush pending packets if the conditions are right
3397 if (forceFlush || packetFlushable) {
3398 stream.size = 0;
3399 stream.data.length = 0;
3400 }
3401
3402 // only emit packets that are complete. this is to avoid assembling
3403 // incomplete PES packets due to poor segmentation
3404 if (packetFlushable) {
3405 self.trigger('data', event);
3406 }
3407 };
3408
3409 ElementaryStream.prototype.init.call(this);
3410
3411 /**
3412 * Identifies M2TS packet types and parses PES packets using metadata
3413 * parsed from the PMT
3414 **/
3415 this.push = function(data) {
3416 ({
3417 pat: function() {
3418 // we have to wait for the PMT to arrive as well before we
3419 // have any meaningful metadata
3420 },
3421 pes: function() {
3422 var stream, streamType;
3423
3424 switch (data.streamType) {
3425 case streamTypes.H264_STREAM_TYPE:
3426 stream = video;
3427 streamType = 'video';
3428 break;
3429 case streamTypes.ADTS_STREAM_TYPE:
3430 stream = audio;
3431 streamType = 'audio';
3432 break;
3433 case streamTypes.METADATA_STREAM_TYPE:
3434 stream = timedMetadata;
3435 streamType = 'timed-metadata';
3436 break;
3437 default:
3438 // ignore unknown stream types
3439 return;
3440 }
3441
3442 // if a new packet is starting, we can flush the completed
3443 // packet
3444 if (data.payloadUnitStartIndicator) {
3445 flushStream(stream, streamType, true);
3446 }
3447
3448 // buffer this fragment until we are sure we've received the
3449 // complete payload
3450 stream.data.push(data);
3451 stream.size += data.data.byteLength;
3452 },
3453 pmt: function() {
3454 var
3455 event = {
3456 type: 'metadata',
3457 tracks: []
3458 };
3459
3460 programMapTable = data.programMapTable;
3461
3462 // translate audio and video streams to tracks
3463 if (programMapTable.video !== null) {
3464 event.tracks.push({
3465 timelineStartInfo: {
3466 baseMediaDecodeTime: 0
3467 },
3468 id: +programMapTable.video,
3469 codec: 'avc',
3470 type: 'video'
3471 });
3472 }
3473 if (programMapTable.audio !== null) {
3474 event.tracks.push({
3475 timelineStartInfo: {
3476 baseMediaDecodeTime: 0
3477 },
3478 id: +programMapTable.audio,
3479 codec: 'adts',
3480 type: 'audio'
3481 });
3482 }
3483
3484 self.trigger('data', event);
3485 }
3486 })[data.type]();
3487 };
3488
3489 this.reset = function() {
3490 video.size = 0;
3491 video.data.length = 0;
3492 audio.size = 0;
3493 audio.data.length = 0;
3494 this.trigger('reset');
3495 };
3496
3497 /**
3498 * Flush any remaining input. Video PES packets may be of variable
3499 * length. Normally, the start of a new video packet can trigger the
3500 * finalization of the previous packet. That is not possible if no
3501 * more video is forthcoming, however. In that case, some other
3502 * mechanism (like the end of the file) has to be employed. When it is
3503 * clear that no additional data is forthcoming, calling this method
3504 * will flush the buffered packets.
3505 */
3506 this.flushStreams_ = function() {
3507 // !!THIS ORDER IS IMPORTANT!!
3508 // video first then audio
3509 flushStream(video, 'video');
3510 flushStream(audio, 'audio');
3511 flushStream(timedMetadata, 'timed-metadata');
3512 };
3513
3514 this.flush = function() {
3515 this.flushStreams_();
3516 this.trigger('done');
3517 };
3518 };
3519 ElementaryStream.prototype = new stream();
3520
3521 var m2ts = {
3522 PAT_PID: 0x0000,
3523 MP2T_PACKET_LENGTH: MP2T_PACKET_LENGTH,
3524 TransportPacketStream: TransportPacketStream,
3525 TransportParseStream: TransportParseStream,
3526 ElementaryStream: ElementaryStream,
3527 TimestampRolloverStream: TimestampRolloverStream$1,
3528 CaptionStream: captionStream.CaptionStream,
3529 Cea608Stream: captionStream.Cea608Stream,
3530 MetadataStream: metadataStream
3531 };
3532
3533 for (var type in streamTypes) {
3534 if (streamTypes.hasOwnProperty(type)) {
3535 m2ts[type] = streamTypes[type];
3536 }
3537 }
3538
3539 var m2ts_1 = m2ts;
3540
3541 var ONE_SECOND_IN_TS$2 = clock.ONE_SECOND_IN_TS;
3542
3543 var AdtsStream;
3544
3545 var
3546 ADTS_SAMPLING_FREQUENCIES = [
3547 96000,
3548 88200,
3549 64000,
3550 48000,
3551 44100,
3552 32000,
3553 24000,
3554 22050,
3555 16000,
3556 12000,
3557 11025,
3558 8000,
3559 7350
3560 ];
3561
3562 /*
3563 * Accepts a ElementaryStream and emits data events with parsed
3564 * AAC Audio Frames of the individual packets. Input audio in ADTS
3565 * format is unpacked and re-emitted as AAC frames.
3566 *
3567 * @see http://wiki.multimedia.cx/index.php?title=ADTS
3568 * @see http://wiki.multimedia.cx/?title=Understanding_AAC
3569 */
3570 AdtsStream = function(handlePartialSegments) {
3571 var
3572 buffer,
3573 frameNum = 0;
3574
3575 AdtsStream.prototype.init.call(this);
3576
3577 this.push = function(packet) {
3578 var
3579 i = 0,
3580 frameLength,
3581 protectionSkipBytes,
3582 frameEnd,
3583 oldBuffer,
3584 sampleCount,
3585 adtsFrameDuration;
3586
3587 if (!handlePartialSegments) {
3588 frameNum = 0;
3589 }
3590
3591 if (packet.type !== 'audio') {
3592 // ignore non-audio data
3593 return;
3594 }
3595
3596 // Prepend any data in the buffer to the input data so that we can parse
3597 // aac frames the cross a PES packet boundary
3598 if (buffer) {
3599 oldBuffer = buffer;
3600 buffer = new Uint8Array(oldBuffer.byteLength + packet.data.byteLength);
3601 buffer.set(oldBuffer);
3602 buffer.set(packet.data, oldBuffer.byteLength);
3603 } else {
3604 buffer = packet.data;
3605 }
3606
3607 // unpack any ADTS frames which have been fully received
3608 // for details on the ADTS header, see http://wiki.multimedia.cx/index.php?title=ADTS
3609 while (i + 5 < buffer.length) {
3610
3611 // Look for the start of an ADTS header..
3612 if ((buffer[i] !== 0xFF) || (buffer[i + 1] & 0xF6) !== 0xF0) {
3613 // If a valid header was not found, jump one forward and attempt to
3614 // find a valid ADTS header starting at the next byte
3615 i++;
3616 continue;
3617 }
3618
3619 // The protection skip bit tells us if we have 2 bytes of CRC data at the
3620 // end of the ADTS header
3621 protectionSkipBytes = (~buffer[i + 1] & 0x01) * 2;
3622
3623 // Frame length is a 13 bit integer starting 16 bits from the
3624 // end of the sync sequence
3625 frameLength = ((buffer[i + 3] & 0x03) << 11) |
3626 (buffer[i + 4] << 3) |
3627 ((buffer[i + 5] & 0xe0) >> 5);
3628
3629 sampleCount = ((buffer[i + 6] & 0x03) + 1) * 1024;
3630 adtsFrameDuration = (sampleCount * ONE_SECOND_IN_TS$2) /
3631 ADTS_SAMPLING_FREQUENCIES[(buffer[i + 2] & 0x3c) >>> 2];
3632
3633 frameEnd = i + frameLength;
3634
3635 // If we don't have enough data to actually finish this ADTS frame, return
3636 // and wait for more data
3637 if (buffer.byteLength < frameEnd) {
3638 return;
3639 }
3640
3641 // Otherwise, deliver the complete AAC frame
3642 this.trigger('data', {
3643 pts: packet.pts + (frameNum * adtsFrameDuration),
3644 dts: packet.dts + (frameNum * adtsFrameDuration),
3645 sampleCount: sampleCount,
3646 audioobjecttype: ((buffer[i + 2] >>> 6) & 0x03) + 1,
3647 channelcount: ((buffer[i + 2] & 1) << 2) |
3648 ((buffer[i + 3] & 0xc0) >>> 6),
3649 samplerate: ADTS_SAMPLING_FREQUENCIES[(buffer[i + 2] & 0x3c) >>> 2],
3650 samplingfrequencyindex: (buffer[i + 2] & 0x3c) >>> 2,
3651 // assume ISO/IEC 14496-12 AudioSampleEntry default of 16
3652 samplesize: 16,
3653 data: buffer.subarray(i + 7 + protectionSkipBytes, frameEnd)
3654 });
3655
3656 frameNum++;
3657
3658 // If the buffer is empty, clear it and return
3659 if (buffer.byteLength === frameEnd) {
3660 buffer = undefined;
3661 return;
3662 }
3663
3664 // Remove the finished frame from the buffer and start the process again
3665 buffer = buffer.subarray(frameEnd);
3666 }
3667 };
3668
3669 this.flush = function() {
3670 frameNum = 0;
3671 this.trigger('done');
3672 };
3673
3674 this.reset = function() {
3675 buffer = void 0;
3676 this.trigger('reset');
3677 };
3678
3679 this.endTimeline = function() {
3680 buffer = void 0;
3681 this.trigger('endedtimeline');
3682 };
3683 };
3684
3685 AdtsStream.prototype = new stream();
3686
3687 var adts = AdtsStream;
3688
3689 /**
3690 * mux.js
3691 *
3692 * Copyright (c) Brightcove
3693 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
3694 */
3695
3696 var ExpGolomb;
3697
3698 /**
3699 * Parser for exponential Golomb codes, a variable-bitwidth number encoding
3700 * scheme used by h264.
3701 */
3702 ExpGolomb = function(workingData) {
3703 var
3704 // the number of bytes left to examine in workingData
3705 workingBytesAvailable = workingData.byteLength,
3706
3707 // the current word being examined
3708 workingWord = 0, // :uint
3709
3710 // the number of bits left to examine in the current word
3711 workingBitsAvailable = 0; // :uint;
3712
3713 // ():uint
3714 this.length = function() {
3715 return (8 * workingBytesAvailable);
3716 };
3717
3718 // ():uint
3719 this.bitsAvailable = function() {
3720 return (8 * workingBytesAvailable) + workingBitsAvailable;
3721 };
3722
3723 // ():void
3724 this.loadWord = function() {
3725 var
3726 position = workingData.byteLength - workingBytesAvailable,
3727 workingBytes = new Uint8Array(4),
3728 availableBytes = Math.min(4, workingBytesAvailable);
3729
3730 if (availableBytes === 0) {
3731 throw new Error('no bytes available');
3732 }
3733
3734 workingBytes.set(workingData.subarray(position,
3735 position + availableBytes));
3736 workingWord = new DataView(workingBytes.buffer).getUint32(0);
3737
3738 // track the amount of workingData that has been processed
3739 workingBitsAvailable = availableBytes * 8;
3740 workingBytesAvailable -= availableBytes;
3741 };
3742
3743 // (count:int):void
3744 this.skipBits = function(count) {
3745 var skipBytes; // :int
3746 if (workingBitsAvailable > count) {
3747 workingWord <<= count;
3748 workingBitsAvailable -= count;
3749 } else {
3750 count -= workingBitsAvailable;
3751 skipBytes = Math.floor(count / 8);
3752
3753 count -= (skipBytes * 8);
3754 workingBytesAvailable -= skipBytes;
3755
3756 this.loadWord();
3757
3758 workingWord <<= count;
3759 workingBitsAvailable -= count;
3760 }
3761 };
3762
3763 // (size:int):uint
3764 this.readBits = function(size) {
3765 var
3766 bits = Math.min(workingBitsAvailable, size), // :uint
3767 valu = workingWord >>> (32 - bits); // :uint
3768 // if size > 31, handle error
3769 workingBitsAvailable -= bits;
3770 if (workingBitsAvailable > 0) {
3771 workingWord <<= bits;
3772 } else if (workingBytesAvailable > 0) {
3773 this.loadWord();
3774 }
3775
3776 bits = size - bits;
3777 if (bits > 0) {
3778 return valu << bits | this.readBits(bits);
3779 }
3780 return valu;
3781 };
3782
3783 // ():uint
3784 this.skipLeadingZeros = function() {
3785 var leadingZeroCount; // :uint
3786 for (leadingZeroCount = 0; leadingZeroCount < workingBitsAvailable; ++leadingZeroCount) {
3787 if ((workingWord & (0x80000000 >>> leadingZeroCount)) !== 0) {
3788 // the first bit of working word is 1
3789 workingWord <<= leadingZeroCount;
3790 workingBitsAvailable -= leadingZeroCount;
3791 return leadingZeroCount;
3792 }
3793 }
3794
3795 // we exhausted workingWord and still have not found a 1
3796 this.loadWord();
3797 return leadingZeroCount + this.skipLeadingZeros();
3798 };
3799
3800 // ():void
3801 this.skipUnsignedExpGolomb = function() {
3802 this.skipBits(1 + this.skipLeadingZeros());
3803 };
3804
3805 // ():void
3806 this.skipExpGolomb = function() {
3807 this.skipBits(1 + this.skipLeadingZeros());
3808 };
3809
3810 // ():uint
3811 this.readUnsignedExpGolomb = function() {
3812 var clz = this.skipLeadingZeros(); // :uint
3813 return this.readBits(clz + 1) - 1;
3814 };
3815
3816 // ():int
3817 this.readExpGolomb = function() {
3818 var valu = this.readUnsignedExpGolomb(); // :int
3819 if (0x01 & valu) {
3820 // the number is odd if the low order bit is set
3821 return (1 + valu) >>> 1; // add 1 to make it even, and divide by 2
3822 }
3823 return -1 * (valu >>> 1); // divide by two then make it negative
3824 };
3825
3826 // Some convenience functions
3827 // :Boolean
3828 this.readBoolean = function() {
3829 return this.readBits(1) === 1;
3830 };
3831
3832 // ():int
3833 this.readUnsignedByte = function() {
3834 return this.readBits(8);
3835 };
3836
3837 this.loadWord();
3838 };
3839
3840 var expGolomb = ExpGolomb;
3841
3842 var H264Stream, NalByteStream;
3843 var PROFILES_WITH_OPTIONAL_SPS_DATA;
3844
3845 /**
3846 * Accepts a NAL unit byte stream and unpacks the embedded NAL units.
3847 */
3848 NalByteStream = function() {
3849 var
3850 syncPoint = 0,
3851 i,
3852 buffer;
3853 NalByteStream.prototype.init.call(this);
3854
3855 /*
3856 * Scans a byte stream and triggers a data event with the NAL units found.
3857 * @param {Object} data Event received from H264Stream
3858 * @param {Uint8Array} data.data The h264 byte stream to be scanned
3859 *
3860 * @see H264Stream.push
3861 */
3862 this.push = function(data) {
3863 var swapBuffer;
3864
3865 if (!buffer) {
3866 buffer = data.data;
3867 } else {
3868 swapBuffer = new Uint8Array(buffer.byteLength + data.data.byteLength);
3869 swapBuffer.set(buffer);
3870 swapBuffer.set(data.data, buffer.byteLength);
3871 buffer = swapBuffer;
3872 }
3873 var len = buffer.byteLength;
3874
3875 // Rec. ITU-T H.264, Annex B
3876 // scan for NAL unit boundaries
3877
3878 // a match looks like this:
3879 // 0 0 1 .. NAL .. 0 0 1
3880 // ^ sync point ^ i
3881 // or this:
3882 // 0 0 1 .. NAL .. 0 0 0
3883 // ^ sync point ^ i
3884
3885 // advance the sync point to a NAL start, if necessary
3886 for (; syncPoint < len - 3; syncPoint++) {
3887 if (buffer[syncPoint + 2] === 1) {
3888 // the sync point is properly aligned
3889 i = syncPoint + 5;
3890 break;
3891 }
3892 }
3893
3894 while (i < len) {
3895 // look at the current byte to determine if we've hit the end of
3896 // a NAL unit boundary
3897 switch (buffer[i]) {
3898 case 0:
3899 // skip past non-sync sequences
3900 if (buffer[i - 1] !== 0) {
3901 i += 2;
3902 break;
3903 } else if (buffer[i - 2] !== 0) {
3904 i++;
3905 break;
3906 }
3907
3908 // deliver the NAL unit if it isn't empty
3909 if (syncPoint + 3 !== i - 2) {
3910 this.trigger('data', buffer.subarray(syncPoint + 3, i - 2));
3911 }
3912
3913 // drop trailing zeroes
3914 do {
3915 i++;
3916 } while (buffer[i] !== 1 && i < len);
3917 syncPoint = i - 2;
3918 i += 3;
3919 break;
3920 case 1:
3921 // skip past non-sync sequences
3922 if (buffer[i - 1] !== 0 ||
3923 buffer[i - 2] !== 0) {
3924 i += 3;
3925 break;
3926 }
3927
3928 // deliver the NAL unit
3929 this.trigger('data', buffer.subarray(syncPoint + 3, i - 2));
3930 syncPoint = i - 2;
3931 i += 3;
3932 break;
3933 default:
3934 // the current byte isn't a one or zero, so it cannot be part
3935 // of a sync sequence
3936 i += 3;
3937 break;
3938 }
3939 }
3940 // filter out the NAL units that were delivered
3941 buffer = buffer.subarray(syncPoint);
3942 i -= syncPoint;
3943 syncPoint = 0;
3944 };
3945
3946 this.reset = function() {
3947 buffer = null;
3948 syncPoint = 0;
3949 this.trigger('reset');
3950 };
3951
3952 this.flush = function() {
3953 // deliver the last buffered NAL unit
3954 if (buffer && buffer.byteLength > 3) {
3955 this.trigger('data', buffer.subarray(syncPoint + 3));
3956 }
3957 // reset the stream state
3958 buffer = null;
3959 syncPoint = 0;
3960 this.trigger('done');
3961 };
3962
3963 this.endTimeline = function() {
3964 this.flush();
3965 this.trigger('endedtimeline');
3966 };
3967 };
3968 NalByteStream.prototype = new stream();
3969
3970 // values of profile_idc that indicate additional fields are included in the SPS
3971 // see Recommendation ITU-T H.264 (4/2013),
3972 // 7.3.2.1.1 Sequence parameter set data syntax
3973 PROFILES_WITH_OPTIONAL_SPS_DATA = {
3974 100: true,
3975 110: true,
3976 122: true,
3977 244: true,
3978 44: true,
3979 83: true,
3980 86: true,
3981 118: true,
3982 128: true,
3983 138: true,
3984 139: true,
3985 134: true
3986 };
3987
3988 /**
3989 * Accepts input from a ElementaryStream and produces H.264 NAL unit data
3990 * events.
3991 */
3992 H264Stream = function() {
3993 var
3994 nalByteStream = new NalByteStream(),
3995 self,
3996 trackId,
3997 currentPts,
3998 currentDts,
3999
4000 discardEmulationPreventionBytes,
4001 readSequenceParameterSet,
4002 skipScalingList;
4003
4004 H264Stream.prototype.init.call(this);
4005 self = this;
4006
4007 /*
4008 * Pushes a packet from a stream onto the NalByteStream
4009 *
4010 * @param {Object} packet - A packet received from a stream
4011 * @param {Uint8Array} packet.data - The raw bytes of the packet
4012 * @param {Number} packet.dts - Decode timestamp of the packet
4013 * @param {Number} packet.pts - Presentation timestamp of the packet
4014 * @param {Number} packet.trackId - The id of the h264 track this packet came from
4015 * @param {('video'|'audio')} packet.type - The type of packet
4016 *
4017 */
4018 this.push = function(packet) {
4019 if (packet.type !== 'video') {
4020 return;
4021 }
4022 trackId = packet.trackId;
4023 currentPts = packet.pts;
4024 currentDts = packet.dts;
4025
4026 nalByteStream.push(packet);
4027 };
4028
4029 /*
4030 * Identify NAL unit types and pass on the NALU, trackId, presentation and decode timestamps
4031 * for the NALUs to the next stream component.
4032 * Also, preprocess caption and sequence parameter NALUs.
4033 *
4034 * @param {Uint8Array} data - A NAL unit identified by `NalByteStream.push`
4035 * @see NalByteStream.push
4036 */
4037 nalByteStream.on('data', function(data) {
4038 var
4039 event = {
4040 trackId: trackId,
4041 pts: currentPts,
4042 dts: currentDts,
4043 data: data
4044 };
4045
4046 switch (data[0] & 0x1f) {
4047 case 0x05:
4048 event.nalUnitType = 'slice_layer_without_partitioning_rbsp_idr';
4049 break;
4050 case 0x06:
4051 event.nalUnitType = 'sei_rbsp';
4052 event.escapedRBSP = discardEmulationPreventionBytes(data.subarray(1));
4053 break;
4054 case 0x07:
4055 event.nalUnitType = 'seq_parameter_set_rbsp';
4056 event.escapedRBSP = discardEmulationPreventionBytes(data.subarray(1));
4057 event.config = readSequenceParameterSet(event.escapedRBSP);
4058 break;
4059 case 0x08:
4060 event.nalUnitType = 'pic_parameter_set_rbsp';
4061 break;
4062 case 0x09:
4063 event.nalUnitType = 'access_unit_delimiter_rbsp';
4064 break;
4065 }
4066 // This triggers data on the H264Stream
4067 self.trigger('data', event);
4068 });
4069 nalByteStream.on('done', function() {
4070 self.trigger('done');
4071 });
4072 nalByteStream.on('partialdone', function() {
4073 self.trigger('partialdone');
4074 });
4075 nalByteStream.on('reset', function() {
4076 self.trigger('reset');
4077 });
4078 nalByteStream.on('endedtimeline', function() {
4079 self.trigger('endedtimeline');
4080 });
4081
4082 this.flush = function() {
4083 nalByteStream.flush();
4084 };
4085
4086 this.partialFlush = function() {
4087 nalByteStream.partialFlush();
4088 };
4089
4090 this.reset = function() {
4091 nalByteStream.reset();
4092 };
4093
4094 this.endTimeline = function() {
4095 nalByteStream.endTimeline();
4096 };
4097
4098 /**
4099 * Advance the ExpGolomb decoder past a scaling list. The scaling
4100 * list is optionally transmitted as part of a sequence parameter
4101 * set and is not relevant to transmuxing.
4102 * @param count {number} the number of entries in this scaling list
4103 * @param expGolombDecoder {object} an ExpGolomb pointed to the
4104 * start of a scaling list
4105 * @see Recommendation ITU-T H.264, Section 7.3.2.1.1.1
4106 */
4107 skipScalingList = function(count, expGolombDecoder) {
4108 var
4109 lastScale = 8,
4110 nextScale = 8,
4111 j,
4112 deltaScale;
4113
4114 for (j = 0; j < count; j++) {
4115 if (nextScale !== 0) {
4116 deltaScale = expGolombDecoder.readExpGolomb();
4117 nextScale = (lastScale + deltaScale + 256) % 256;
4118 }
4119
4120 lastScale = (nextScale === 0) ? lastScale : nextScale;
4121 }
4122 };
4123
4124 /**
4125 * Expunge any "Emulation Prevention" bytes from a "Raw Byte
4126 * Sequence Payload"
4127 * @param data {Uint8Array} the bytes of a RBSP from a NAL
4128 * unit
4129 * @return {Uint8Array} the RBSP without any Emulation
4130 * Prevention Bytes
4131 */
4132 discardEmulationPreventionBytes = function(data) {
4133 var
4134 length = data.byteLength,
4135 emulationPreventionBytesPositions = [],
4136 i = 1,
4137 newLength, newData;
4138
4139 // Find all `Emulation Prevention Bytes`
4140 while (i < length - 2) {
4141 if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 0x03) {
4142 emulationPreventionBytesPositions.push(i + 2);
4143 i += 2;
4144 } else {
4145 i++;
4146 }
4147 }
4148
4149 // If no Emulation Prevention Bytes were found just return the original
4150 // array
4151 if (emulationPreventionBytesPositions.length === 0) {
4152 return data;
4153 }
4154
4155 // Create a new array to hold the NAL unit data
4156 newLength = length - emulationPreventionBytesPositions.length;
4157 newData = new Uint8Array(newLength);
4158 var sourceIndex = 0;
4159
4160 for (i = 0; i < newLength; sourceIndex++, i++) {
4161 if (sourceIndex === emulationPreventionBytesPositions[0]) {
4162 // Skip this byte
4163 sourceIndex++;
4164 // Remove this position index
4165 emulationPreventionBytesPositions.shift();
4166 }
4167 newData[i] = data[sourceIndex];
4168 }
4169
4170 return newData;
4171 };
4172
4173 /**
4174 * Read a sequence parameter set and return some interesting video
4175 * properties. A sequence parameter set is the H264 metadata that
4176 * describes the properties of upcoming video frames.
4177 * @param data {Uint8Array} the bytes of a sequence parameter set
4178 * @return {object} an object with configuration parsed from the
4179 * sequence parameter set, including the dimensions of the
4180 * associated video frames.
4181 */
4182 readSequenceParameterSet = function(data) {
4183 var
4184 frameCropLeftOffset = 0,
4185 frameCropRightOffset = 0,
4186 frameCropTopOffset = 0,
4187 frameCropBottomOffset = 0,
4188 sarScale = 1,
4189 expGolombDecoder, profileIdc, levelIdc, profileCompatibility,
4190 chromaFormatIdc, picOrderCntType,
4191 numRefFramesInPicOrderCntCycle, picWidthInMbsMinus1,
4192 picHeightInMapUnitsMinus1,
4193 frameMbsOnlyFlag,
4194 scalingListCount,
4195 sarRatio,
4196 aspectRatioIdc,
4197 i;
4198
4199 expGolombDecoder = new expGolomb(data);
4200 profileIdc = expGolombDecoder.readUnsignedByte(); // profile_idc
4201 profileCompatibility = expGolombDecoder.readUnsignedByte(); // constraint_set[0-5]_flag
4202 levelIdc = expGolombDecoder.readUnsignedByte(); // level_idc u(8)
4203 expGolombDecoder.skipUnsignedExpGolomb(); // seq_parameter_set_id
4204
4205 // some profiles have more optional data we don't need
4206 if (PROFILES_WITH_OPTIONAL_SPS_DATA[profileIdc]) {
4207 chromaFormatIdc = expGolombDecoder.readUnsignedExpGolomb();
4208 if (chromaFormatIdc === 3) {
4209 expGolombDecoder.skipBits(1); // separate_colour_plane_flag
4210 }
4211 expGolombDecoder.skipUnsignedExpGolomb(); // bit_depth_luma_minus8
4212 expGolombDecoder.skipUnsignedExpGolomb(); // bit_depth_chroma_minus8
4213 expGolombDecoder.skipBits(1); // qpprime_y_zero_transform_bypass_flag
4214 if (expGolombDecoder.readBoolean()) { // seq_scaling_matrix_present_flag
4215 scalingListCount = (chromaFormatIdc !== 3) ? 8 : 12;
4216 for (i = 0; i < scalingListCount; i++) {
4217 if (expGolombDecoder.readBoolean()) { // seq_scaling_list_present_flag[ i ]
4218 if (i < 6) {
4219 skipScalingList(16, expGolombDecoder);
4220 } else {
4221 skipScalingList(64, expGolombDecoder);
4222 }
4223 }
4224 }
4225 }
4226 }
4227
4228 expGolombDecoder.skipUnsignedExpGolomb(); // log2_max_frame_num_minus4
4229 picOrderCntType = expGolombDecoder.readUnsignedExpGolomb();
4230
4231 if (picOrderCntType === 0) {
4232 expGolombDecoder.readUnsignedExpGolomb(); // log2_max_pic_order_cnt_lsb_minus4
4233 } else if (picOrderCntType === 1) {
4234 expGolombDecoder.skipBits(1); // delta_pic_order_always_zero_flag
4235 expGolombDecoder.skipExpGolomb(); // offset_for_non_ref_pic
4236 expGolombDecoder.skipExpGolomb(); // offset_for_top_to_bottom_field
4237 numRefFramesInPicOrderCntCycle = expGolombDecoder.readUnsignedExpGolomb();
4238 for (i = 0; i < numRefFramesInPicOrderCntCycle; i++) {
4239 expGolombDecoder.skipExpGolomb(); // offset_for_ref_frame[ i ]
4240 }
4241 }
4242
4243 expGolombDecoder.skipUnsignedExpGolomb(); // max_num_ref_frames
4244 expGolombDecoder.skipBits(1); // gaps_in_frame_num_value_allowed_flag
4245
4246 picWidthInMbsMinus1 = expGolombDecoder.readUnsignedExpGolomb();
4247 picHeightInMapUnitsMinus1 = expGolombDecoder.readUnsignedExpGolomb();
4248
4249 frameMbsOnlyFlag = expGolombDecoder.readBits(1);
4250 if (frameMbsOnlyFlag === 0) {
4251 expGolombDecoder.skipBits(1); // mb_adaptive_frame_field_flag
4252 }
4253
4254 expGolombDecoder.skipBits(1); // direct_8x8_inference_flag
4255 if (expGolombDecoder.readBoolean()) { // frame_cropping_flag
4256 frameCropLeftOffset = expGolombDecoder.readUnsignedExpGolomb();
4257 frameCropRightOffset = expGolombDecoder.readUnsignedExpGolomb();
4258 frameCropTopOffset = expGolombDecoder.readUnsignedExpGolomb();
4259 frameCropBottomOffset = expGolombDecoder.readUnsignedExpGolomb();
4260 }
4261 if (expGolombDecoder.readBoolean()) {
4262 // vui_parameters_present_flag
4263 if (expGolombDecoder.readBoolean()) {
4264 // aspect_ratio_info_present_flag
4265 aspectRatioIdc = expGolombDecoder.readUnsignedByte();
4266 switch (aspectRatioIdc) {
4267 case 1: sarRatio = [1, 1]; break;
4268 case 2: sarRatio = [12, 11]; break;
4269 case 3: sarRatio = [10, 11]; break;
4270 case 4: sarRatio = [16, 11]; break;
4271 case 5: sarRatio = [40, 33]; break;
4272 case 6: sarRatio = [24, 11]; break;
4273 case 7: sarRatio = [20, 11]; break;
4274 case 8: sarRatio = [32, 11]; break;
4275 case 9: sarRatio = [80, 33]; break;
4276 case 10: sarRatio = [18, 11]; break;
4277 case 11: sarRatio = [15, 11]; break;
4278 case 12: sarRatio = [64, 33]; break;
4279 case 13: sarRatio = [160, 99]; break;
4280 case 14: sarRatio = [4, 3]; break;
4281 case 15: sarRatio = [3, 2]; break;
4282 case 16: sarRatio = [2, 1]; break;
4283 case 255: {
4284 sarRatio = [expGolombDecoder.readUnsignedByte() << 8 |
4285 expGolombDecoder.readUnsignedByte(),
4286 expGolombDecoder.readUnsignedByte() << 8 |
4287 expGolombDecoder.readUnsignedByte() ];
4288 break;
4289 }
4290 }
4291 if (sarRatio) {
4292 sarScale = sarRatio[0] / sarRatio[1];
4293 }
4294 }
4295 }
4296 return {
4297 profileIdc: profileIdc,
4298 levelIdc: levelIdc,
4299 profileCompatibility: profileCompatibility,
4300 width: Math.ceil((((picWidthInMbsMinus1 + 1) * 16) - frameCropLeftOffset * 2 - frameCropRightOffset * 2) * sarScale),
4301 height: ((2 - frameMbsOnlyFlag) * (picHeightInMapUnitsMinus1 + 1) * 16) - (frameCropTopOffset * 2) - (frameCropBottomOffset * 2),
4302 sarRatio: sarRatio
4303 };
4304 };
4305
4306 };
4307 H264Stream.prototype = new stream();
4308
4309 var h264 = {
4310 H264Stream: H264Stream,
4311 NalByteStream: NalByteStream
4312 };
4313
4314 /**
4315 * mux.js
4316 *
4317 * Copyright (c) Brightcove
4318 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
4319 *
4320 * Utilities to detect basic properties and metadata about Aac data.
4321 */
4322
4323 var ADTS_SAMPLING_FREQUENCIES$1 = [
4324 96000,
4325 88200,
4326 64000,
4327 48000,
4328 44100,
4329 32000,
4330 24000,
4331 22050,
4332 16000,
4333 12000,
4334 11025,
4335 8000,
4336 7350
4337 ];
4338
4339 var parseId3TagSize = function(header, byteIndex) {
4340 var
4341 returnSize = (header[byteIndex + 6] << 21) |
4342 (header[byteIndex + 7] << 14) |
4343 (header[byteIndex + 8] << 7) |
4344 (header[byteIndex + 9]),
4345 flags = header[byteIndex + 5],
4346 footerPresent = (flags & 16) >> 4;
4347
4348 if (footerPresent) {
4349 return returnSize + 20;
4350 }
4351 return returnSize + 10;
4352 };
4353
4354 // TODO: use vhs-utils
4355 var isLikelyAacData = function(data) {
4356 var offset = 0;
4357
4358 if (data.length > 10 && (data[0] === 'I'.charCodeAt(0)) &&
4359 (data[1] === 'D'.charCodeAt(0)) &&
4360 (data[2] === '3'.charCodeAt(0))) {
4361 offset = parseId3TagSize(data, 0);
4362 }
4363
4364 return data.length >= offset + 2 &&
4365 (data[offset] & 0xFF) === 0xFF &&
4366 (data[offset + 1] & 0xF0) === 0xF0 &&
4367 // verify that the 2 layer bits are 0, aka this
4368 // is not mp3 data but aac data.
4369 (data[offset + 1] & 0x16) === 0x10;
4370 };
4371
4372 var parseSyncSafeInteger$1 = function(data) {
4373 return (data[0] << 21) |
4374 (data[1] << 14) |
4375 (data[2] << 7) |
4376 (data[3]);
4377 };
4378
4379 // return a percent-encoded representation of the specified byte range
4380 // @see http://en.wikipedia.org/wiki/Percent-encoding
4381 var percentEncode$1 = function(bytes, start, end) {
4382 var i, result = '';
4383 for (i = start; i < end; i++) {
4384 result += '%' + ('00' + bytes[i].toString(16)).slice(-2);
4385 }
4386 return result;
4387 };
4388
4389 // return the string representation of the specified byte range,
4390 // interpreted as ISO-8859-1.
4391 var parseIso88591$1 = function(bytes, start, end) {
4392 return unescape(percentEncode$1(bytes, start, end)); // jshint ignore:line
4393 };
4394
4395 var parseAdtsSize = function(header, byteIndex) {
4396 var
4397 lowThree = (header[byteIndex + 5] & 0xE0) >> 5,
4398 middle = header[byteIndex + 4] << 3,
4399 highTwo = header[byteIndex + 3] & 0x3 << 11;
4400
4401 return (highTwo | middle) | lowThree;
4402 };
4403
4404 var parseType = function(header, byteIndex) {
4405 if ((header[byteIndex] === 'I'.charCodeAt(0)) &&
4406 (header[byteIndex + 1] === 'D'.charCodeAt(0)) &&
4407 (header[byteIndex + 2] === '3'.charCodeAt(0))) {
4408 return 'timed-metadata';
4409 } else if ((header[byteIndex] & 0xff === 0xff) &&
4410 ((header[byteIndex + 1] & 0xf0) === 0xf0)) {
4411 return 'audio';
4412 }
4413 return null;
4414 };
4415
4416 var parseSampleRate = function(packet) {
4417 var i = 0;
4418
4419 while (i + 5 < packet.length) {
4420 if (packet[i] !== 0xFF || (packet[i + 1] & 0xF6) !== 0xF0) {
4421 // If a valid header was not found, jump one forward and attempt to
4422 // find a valid ADTS header starting at the next byte
4423 i++;
4424 continue;
4425 }
4426 return ADTS_SAMPLING_FREQUENCIES$1[(packet[i + 2] & 0x3c) >>> 2];
4427 }
4428
4429 return null;
4430 };
4431
4432 var parseAacTimestamp = function(packet) {
4433 var frameStart, frameSize, frame, frameHeader;
4434
4435 // find the start of the first frame and the end of the tag
4436 frameStart = 10;
4437 if (packet[5] & 0x40) {
4438 // advance the frame start past the extended header
4439 frameStart += 4; // header size field
4440 frameStart += parseSyncSafeInteger$1(packet.subarray(10, 14));
4441 }
4442
4443 // parse one or more ID3 frames
4444 // http://id3.org/id3v2.3.0#ID3v2_frame_overview
4445 do {
4446 // determine the number of bytes in this frame
4447 frameSize = parseSyncSafeInteger$1(packet.subarray(frameStart + 4, frameStart + 8));
4448 if (frameSize < 1) {
4449 return null;
4450 }
4451 frameHeader = String.fromCharCode(packet[frameStart],
4452 packet[frameStart + 1],
4453 packet[frameStart + 2],
4454 packet[frameStart + 3]);
4455
4456 if (frameHeader === 'PRIV') {
4457 frame = packet.subarray(frameStart + 10, frameStart + frameSize + 10);
4458
4459 for (var i = 0; i < frame.byteLength; i++) {
4460 if (frame[i] === 0) {
4461 var owner = parseIso88591$1(frame, 0, i);
4462 if (owner === 'com.apple.streaming.transportStreamTimestamp') {
4463 var d = frame.subarray(i + 1);
4464 var size = ((d[3] & 0x01) << 30) |
4465 (d[4] << 22) |
4466 (d[5] << 14) |
4467 (d[6] << 6) |
4468 (d[7] >>> 2);
4469 size *= 4;
4470 size += d[7] & 0x03;
4471
4472 return size;
4473 }
4474 break;
4475 }
4476 }
4477 }
4478
4479 frameStart += 10; // advance past the frame header
4480 frameStart += frameSize; // advance past the frame body
4481 } while (frameStart < packet.byteLength);
4482 return null;
4483 };
4484
4485 var utils = {
4486 isLikelyAacData: isLikelyAacData,
4487 parseId3TagSize: parseId3TagSize,
4488 parseAdtsSize: parseAdtsSize,
4489 parseType: parseType,
4490 parseSampleRate: parseSampleRate,
4491 parseAacTimestamp: parseAacTimestamp
4492 };
4493
4494 // Constants
4495 var AacStream;
4496
4497 /**
4498 * Splits an incoming stream of binary data into ADTS and ID3 Frames.
4499 */
4500
4501 AacStream = function() {
4502 var
4503 everything = new Uint8Array(),
4504 timeStamp = 0;
4505
4506 AacStream.prototype.init.call(this);
4507
4508 this.setTimestamp = function(timestamp) {
4509 timeStamp = timestamp;
4510 };
4511
4512 this.push = function(bytes) {
4513 var
4514 frameSize = 0,
4515 byteIndex = 0,
4516 bytesLeft,
4517 chunk,
4518 packet,
4519 tempLength;
4520
4521 // If there are bytes remaining from the last segment, prepend them to the
4522 // bytes that were pushed in
4523 if (everything.length) {
4524 tempLength = everything.length;
4525 everything = new Uint8Array(bytes.byteLength + tempLength);
4526 everything.set(everything.subarray(0, tempLength));
4527 everything.set(bytes, tempLength);
4528 } else {
4529 everything = bytes;
4530 }
4531
4532 while (everything.length - byteIndex >= 3) {
4533 if ((everything[byteIndex] === 'I'.charCodeAt(0)) &&
4534 (everything[byteIndex + 1] === 'D'.charCodeAt(0)) &&
4535 (everything[byteIndex + 2] === '3'.charCodeAt(0))) {
4536
4537 // Exit early because we don't have enough to parse
4538 // the ID3 tag header
4539 if (everything.length - byteIndex < 10) {
4540 break;
4541 }
4542
4543 // check framesize
4544 frameSize = utils.parseId3TagSize(everything, byteIndex);
4545
4546 // Exit early if we don't have enough in the buffer
4547 // to emit a full packet
4548 // Add to byteIndex to support multiple ID3 tags in sequence
4549 if (byteIndex + frameSize > everything.length) {
4550 break;
4551 }
4552 chunk = {
4553 type: 'timed-metadata',
4554 data: everything.subarray(byteIndex, byteIndex + frameSize)
4555 };
4556 this.trigger('data', chunk);
4557 byteIndex += frameSize;
4558 continue;
4559 } else if (((everything[byteIndex] & 0xff) === 0xff) &&
4560 ((everything[byteIndex + 1] & 0xf0) === 0xf0)) {
4561
4562 // Exit early because we don't have enough to parse
4563 // the ADTS frame header
4564 if (everything.length - byteIndex < 7) {
4565 break;
4566 }
4567
4568 frameSize = utils.parseAdtsSize(everything, byteIndex);
4569
4570 // Exit early if we don't have enough in the buffer
4571 // to emit a full packet
4572 if (byteIndex + frameSize > everything.length) {
4573 break;
4574 }
4575
4576 packet = {
4577 type: 'audio',
4578 data: everything.subarray(byteIndex, byteIndex + frameSize),
4579 pts: timeStamp,
4580 dts: timeStamp
4581 };
4582 this.trigger('data', packet);
4583 byteIndex += frameSize;
4584 continue;
4585 }
4586 byteIndex++;
4587 }
4588 bytesLeft = everything.length - byteIndex;
4589
4590 if (bytesLeft > 0) {
4591 everything = everything.subarray(byteIndex);
4592 } else {
4593 everything = new Uint8Array();
4594 }
4595 };
4596
4597 this.reset = function() {
4598 everything = new Uint8Array();
4599 this.trigger('reset');
4600 };
4601
4602 this.endTimeline = function() {
4603 everything = new Uint8Array();
4604 this.trigger('endedtimeline');
4605 };
4606 };
4607
4608 AacStream.prototype = new stream();
4609
4610 var aac = AacStream;
4611
4612 // constants
4613 var AUDIO_PROPERTIES = [
4614 'audioobjecttype',
4615 'channelcount',
4616 'samplerate',
4617 'samplingfrequencyindex',
4618 'samplesize'
4619 ];
4620
4621 var audioProperties = AUDIO_PROPERTIES;
4622
4623 var VIDEO_PROPERTIES = [
4624 'width',
4625 'height',
4626 'profileIdc',
4627 'levelIdc',
4628 'profileCompatibility',
4629 'sarRatio'
4630 ];
4631
4632
4633 var videoProperties = VIDEO_PROPERTIES;
4634
4635 var H264Stream$1 = h264.H264Stream;
4636
4637 var isLikelyAacData$1 = utils.isLikelyAacData;
4638 var ONE_SECOND_IN_TS$3 = clock.ONE_SECOND_IN_TS;
4639
4640
4641
4642 // object types
4643 var VideoSegmentStream, AudioSegmentStream, Transmuxer, CoalesceStream;
4644
4645 /**
4646 * Compare two arrays (even typed) for same-ness
4647 */
4648 var arrayEquals = function(a, b) {
4649 var
4650 i;
4651
4652 if (a.length !== b.length) {
4653 return false;
4654 }
4655
4656 // compare the value of each element in the array
4657 for (i = 0; i < a.length; i++) {
4658 if (a[i] !== b[i]) {
4659 return false;
4660 }
4661 }
4662
4663 return true;
4664 };
4665
4666 var generateVideoSegmentTimingInfo = function(
4667 baseMediaDecodeTime,
4668 startDts,
4669 startPts,
4670 endDts,
4671 endPts,
4672 prependedContentDuration
4673 ) {
4674 var
4675 ptsOffsetFromDts = startPts - startDts,
4676 decodeDuration = endDts - startDts,
4677 presentationDuration = endPts - startPts;
4678
4679 // The PTS and DTS values are based on the actual stream times from the segment,
4680 // however, the player time values will reflect a start from the baseMediaDecodeTime.
4681 // In order to provide relevant values for the player times, base timing info on the
4682 // baseMediaDecodeTime and the DTS and PTS durations of the segment.
4683 return {
4684 start: {
4685 dts: baseMediaDecodeTime,
4686 pts: baseMediaDecodeTime + ptsOffsetFromDts
4687 },
4688 end: {
4689 dts: baseMediaDecodeTime + decodeDuration,
4690 pts: baseMediaDecodeTime + presentationDuration
4691 },
4692 prependedContentDuration: prependedContentDuration,
4693 baseMediaDecodeTime: baseMediaDecodeTime
4694 };
4695 };
4696
4697 /**
4698 * Constructs a single-track, ISO BMFF media segment from AAC data
4699 * events. The output of this stream can be fed to a SourceBuffer
4700 * configured with a suitable initialization segment.
4701 * @param track {object} track metadata configuration
4702 * @param options {object} transmuxer options object
4703 * @param options.keepOriginalTimestamps {boolean} If true, keep the timestamps
4704 * in the source; false to adjust the first segment to start at 0.
4705 */
4706 AudioSegmentStream = function(track, options) {
4707 var
4708 adtsFrames = [],
4709 sequenceNumber = 0,
4710 earliestAllowedDts = 0,
4711 audioAppendStartTs = 0,
4712 videoBaseMediaDecodeTime = Infinity;
4713
4714 options = options || {};
4715
4716 AudioSegmentStream.prototype.init.call(this);
4717
4718 this.push = function(data) {
4719 trackDecodeInfo.collectDtsInfo(track, data);
4720
4721 if (track) {
4722 audioProperties.forEach(function(prop) {
4723 track[prop] = data[prop];
4724 });
4725 }
4726
4727 // buffer audio data until end() is called
4728 adtsFrames.push(data);
4729 };
4730
4731 this.setEarliestDts = function(earliestDts) {
4732 earliestAllowedDts = earliestDts;
4733 };
4734
4735 this.setVideoBaseMediaDecodeTime = function(baseMediaDecodeTime) {
4736 videoBaseMediaDecodeTime = baseMediaDecodeTime;
4737 };
4738
4739 this.setAudioAppendStart = function(timestamp) {
4740 audioAppendStartTs = timestamp;
4741 };
4742
4743 this.flush = function() {
4744 var
4745 frames,
4746 moof,
4747 mdat,
4748 boxes,
4749 frameDuration;
4750
4751 // return early if no audio data has been observed
4752 if (adtsFrames.length === 0) {
4753 this.trigger('done', 'AudioSegmentStream');
4754 return;
4755 }
4756
4757 frames = audioFrameUtils.trimAdtsFramesByEarliestDts(
4758 adtsFrames, track, earliestAllowedDts);
4759 track.baseMediaDecodeTime = trackDecodeInfo.calculateTrackBaseMediaDecodeTime(
4760 track, options.keepOriginalTimestamps);
4761
4762 audioFrameUtils.prefixWithSilence(
4763 track, frames, audioAppendStartTs, videoBaseMediaDecodeTime);
4764
4765 // we have to build the index from byte locations to
4766 // samples (that is, adts frames) in the audio data
4767 track.samples = audioFrameUtils.generateSampleTable(frames);
4768
4769 // concatenate the audio data to constuct the mdat
4770 mdat = mp4Generator.mdat(audioFrameUtils.concatenateFrameData(frames));
4771
4772 adtsFrames = [];
4773
4774 moof = mp4Generator.moof(sequenceNumber, [track]);
4775 boxes = new Uint8Array(moof.byteLength + mdat.byteLength);
4776
4777 // bump the sequence number for next time
4778 sequenceNumber++;
4779
4780 boxes.set(moof);
4781 boxes.set(mdat, moof.byteLength);
4782
4783 trackDecodeInfo.clearDtsInfo(track);
4784
4785 frameDuration = Math.ceil(ONE_SECOND_IN_TS$3 * 1024 / track.samplerate);
4786
4787 // TODO this check was added to maintain backwards compatibility (particularly with
4788 // tests) on adding the timingInfo event. However, it seems unlikely that there's a
4789 // valid use-case where an init segment/data should be triggered without associated
4790 // frames. Leaving for now, but should be looked into.
4791 if (frames.length) {
4792 this.trigger('timingInfo', {
4793 start: frames[0].pts,
4794 end: frames[0].pts + (frames.length * frameDuration)
4795 });
4796 }
4797 this.trigger('data', {track: track, boxes: boxes});
4798 this.trigger('done', 'AudioSegmentStream');
4799 };
4800
4801 this.reset = function() {
4802 trackDecodeInfo.clearDtsInfo(track);
4803 adtsFrames = [];
4804 this.trigger('reset');
4805 };
4806 };
4807
4808 AudioSegmentStream.prototype = new stream();
4809
4810 /**
4811 * Constructs a single-track, ISO BMFF media segment from H264 data
4812 * events. The output of this stream can be fed to a SourceBuffer
4813 * configured with a suitable initialization segment.
4814 * @param track {object} track metadata configuration
4815 * @param options {object} transmuxer options object
4816 * @param options.alignGopsAtEnd {boolean} If true, start from the end of the
4817 * gopsToAlignWith list when attempting to align gop pts
4818 * @param options.keepOriginalTimestamps {boolean} If true, keep the timestamps
4819 * in the source; false to adjust the first segment to start at 0.
4820 */
4821 VideoSegmentStream = function(track, options) {
4822 var
4823 sequenceNumber = 0,
4824 nalUnits = [],
4825 gopsToAlignWith = [],
4826 config,
4827 pps;
4828
4829 options = options || {};
4830
4831 VideoSegmentStream.prototype.init.call(this);
4832
4833 delete track.minPTS;
4834
4835 this.gopCache_ = [];
4836
4837 /**
4838 * Constructs a ISO BMFF segment given H264 nalUnits
4839 * @param {Object} nalUnit A data event representing a nalUnit
4840 * @param {String} nalUnit.nalUnitType
4841 * @param {Object} nalUnit.config Properties for a mp4 track
4842 * @param {Uint8Array} nalUnit.data The nalUnit bytes
4843 * @see lib/codecs/h264.js
4844 **/
4845 this.push = function(nalUnit) {
4846 trackDecodeInfo.collectDtsInfo(track, nalUnit);
4847
4848 // record the track config
4849 if (nalUnit.nalUnitType === 'seq_parameter_set_rbsp' && !config) {
4850 config = nalUnit.config;
4851 track.sps = [nalUnit.data];
4852
4853 videoProperties.forEach(function(prop) {
4854 track[prop] = config[prop];
4855 }, this);
4856 }
4857
4858 if (nalUnit.nalUnitType === 'pic_parameter_set_rbsp' &&
4859 !pps) {
4860 pps = nalUnit.data;
4861 track.pps = [nalUnit.data];
4862 }
4863
4864 // buffer video until flush() is called
4865 nalUnits.push(nalUnit);
4866 };
4867
4868 /**
4869 * Pass constructed ISO BMFF track and boxes on to the
4870 * next stream in the pipeline
4871 **/
4872 this.flush = function() {
4873 var
4874 frames,
4875 gopForFusion,
4876 gops,
4877 moof,
4878 mdat,
4879 boxes,
4880 prependedContentDuration = 0,
4881 firstGop,
4882 lastGop;
4883
4884 // Throw away nalUnits at the start of the byte stream until
4885 // we find the first AUD
4886 while (nalUnits.length) {
4887 if (nalUnits[0].nalUnitType === 'access_unit_delimiter_rbsp') {
4888 break;
4889 }
4890 nalUnits.shift();
4891 }
4892
4893 // Return early if no video data has been observed
4894 if (nalUnits.length === 0) {
4895 this.resetStream_();
4896 this.trigger('done', 'VideoSegmentStream');
4897 return;
4898 }
4899
4900 // Organize the raw nal-units into arrays that represent
4901 // higher-level constructs such as frames and gops
4902 // (group-of-pictures)
4903 frames = frameUtils.groupNalsIntoFrames(nalUnits);
4904 gops = frameUtils.groupFramesIntoGops(frames);
4905
4906 // If the first frame of this fragment is not a keyframe we have
4907 // a problem since MSE (on Chrome) requires a leading keyframe.
4908 //
4909 // We have two approaches to repairing this situation:
4910 // 1) GOP-FUSION:
4911 // This is where we keep track of the GOPS (group-of-pictures)
4912 // from previous fragments and attempt to find one that we can
4913 // prepend to the current fragment in order to create a valid
4914 // fragment.
4915 // 2) KEYFRAME-PULLING:
4916 // Here we search for the first keyframe in the fragment and
4917 // throw away all the frames between the start of the fragment
4918 // and that keyframe. We then extend the duration and pull the
4919 // PTS of the keyframe forward so that it covers the time range
4920 // of the frames that were disposed of.
4921 //
4922 // #1 is far prefereable over #2 which can cause "stuttering" but
4923 // requires more things to be just right.
4924 if (!gops[0][0].keyFrame) {
4925 // Search for a gop for fusion from our gopCache
4926 gopForFusion = this.getGopForFusion_(nalUnits[0], track);
4927
4928 if (gopForFusion) {
4929 // in order to provide more accurate timing information about the segment, save
4930 // the number of seconds prepended to the original segment due to GOP fusion
4931 prependedContentDuration = gopForFusion.duration;
4932
4933 gops.unshift(gopForFusion);
4934 // Adjust Gops' metadata to account for the inclusion of the
4935 // new gop at the beginning
4936 gops.byteLength += gopForFusion.byteLength;
4937 gops.nalCount += gopForFusion.nalCount;
4938 gops.pts = gopForFusion.pts;
4939 gops.dts = gopForFusion.dts;
4940 gops.duration += gopForFusion.duration;
4941 } else {
4942 // If we didn't find a candidate gop fall back to keyframe-pulling
4943 gops = frameUtils.extendFirstKeyFrame(gops);
4944 }
4945 }
4946
4947 // Trim gops to align with gopsToAlignWith
4948 if (gopsToAlignWith.length) {
4949 var alignedGops;
4950
4951 if (options.alignGopsAtEnd) {
4952 alignedGops = this.alignGopsAtEnd_(gops);
4953 } else {
4954 alignedGops = this.alignGopsAtStart_(gops);
4955 }
4956
4957 if (!alignedGops) {
4958 // save all the nals in the last GOP into the gop cache
4959 this.gopCache_.unshift({
4960 gop: gops.pop(),
4961 pps: track.pps,
4962 sps: track.sps
4963 });
4964
4965 // Keep a maximum of 6 GOPs in the cache
4966 this.gopCache_.length = Math.min(6, this.gopCache_.length);
4967
4968 // Clear nalUnits
4969 nalUnits = [];
4970
4971 // return early no gops can be aligned with desired gopsToAlignWith
4972 this.resetStream_();
4973 this.trigger('done', 'VideoSegmentStream');
4974 return;
4975 }
4976
4977 // Some gops were trimmed. clear dts info so minSegmentDts and pts are correct
4978 // when recalculated before sending off to CoalesceStream
4979 trackDecodeInfo.clearDtsInfo(track);
4980
4981 gops = alignedGops;
4982 }
4983
4984 trackDecodeInfo.collectDtsInfo(track, gops);
4985
4986 // First, we have to build the index from byte locations to
4987 // samples (that is, frames) in the video data
4988 track.samples = frameUtils.generateSampleTable(gops);
4989
4990 // Concatenate the video data and construct the mdat
4991 mdat = mp4Generator.mdat(frameUtils.concatenateNalData(gops));
4992
4993 track.baseMediaDecodeTime = trackDecodeInfo.calculateTrackBaseMediaDecodeTime(
4994 track, options.keepOriginalTimestamps);
4995
4996 this.trigger('processedGopsInfo', gops.map(function(gop) {
4997 return {
4998 pts: gop.pts,
4999 dts: gop.dts,
5000 byteLength: gop.byteLength
5001 };
5002 }));
5003
5004 firstGop = gops[0];
5005 lastGop = gops[gops.length - 1];
5006
5007 this.trigger(
5008 'segmentTimingInfo',
5009 generateVideoSegmentTimingInfo(
5010 track.baseMediaDecodeTime,
5011 firstGop.dts,
5012 firstGop.pts,
5013 lastGop.dts + lastGop.duration,
5014 lastGop.pts + lastGop.duration,
5015 prependedContentDuration));
5016
5017 this.trigger('timingInfo', {
5018 start: gops[0].pts,
5019 end: gops[gops.length - 1].pts + gops[gops.length - 1].duration
5020 });
5021
5022 // save all the nals in the last GOP into the gop cache
5023 this.gopCache_.unshift({
5024 gop: gops.pop(),
5025 pps: track.pps,
5026 sps: track.sps
5027 });
5028
5029 // Keep a maximum of 6 GOPs in the cache
5030 this.gopCache_.length = Math.min(6, this.gopCache_.length);
5031
5032 // Clear nalUnits
5033 nalUnits = [];
5034
5035 this.trigger('baseMediaDecodeTime', track.baseMediaDecodeTime);
5036 this.trigger('timelineStartInfo', track.timelineStartInfo);
5037
5038 moof = mp4Generator.moof(sequenceNumber, [track]);
5039
5040 // it would be great to allocate this array up front instead of
5041 // throwing away hundreds of media segment fragments
5042 boxes = new Uint8Array(moof.byteLength + mdat.byteLength);
5043
5044 // Bump the sequence number for next time
5045 sequenceNumber++;
5046
5047 boxes.set(moof);
5048 boxes.set(mdat, moof.byteLength);
5049
5050 this.trigger('data', {track: track, boxes: boxes});
5051
5052 this.resetStream_();
5053
5054 // Continue with the flush process now
5055 this.trigger('done', 'VideoSegmentStream');
5056 };
5057
5058 this.reset = function() {
5059 this.resetStream_();
5060 nalUnits = [];
5061 this.gopCache_.length = 0;
5062 gopsToAlignWith.length = 0;
5063 this.trigger('reset');
5064 };
5065
5066 this.resetStream_ = function() {
5067 trackDecodeInfo.clearDtsInfo(track);
5068
5069 // reset config and pps because they may differ across segments
5070 // for instance, when we are rendition switching
5071 config = undefined;
5072 pps = undefined;
5073 };
5074
5075 // Search for a candidate Gop for gop-fusion from the gop cache and
5076 // return it or return null if no good candidate was found
5077 this.getGopForFusion_ = function(nalUnit) {
5078 var
5079 halfSecond = 45000, // Half-a-second in a 90khz clock
5080 allowableOverlap = 10000, // About 3 frames @ 30fps
5081 nearestDistance = Infinity,
5082 dtsDistance,
5083 nearestGopObj,
5084 currentGop,
5085 currentGopObj,
5086 i;
5087
5088 // Search for the GOP nearest to the beginning of this nal unit
5089 for (i = 0; i < this.gopCache_.length; i++) {
5090 currentGopObj = this.gopCache_[i];
5091 currentGop = currentGopObj.gop;
5092
5093 // Reject Gops with different SPS or PPS
5094 if (!(track.pps && arrayEquals(track.pps[0], currentGopObj.pps[0])) ||
5095 !(track.sps && arrayEquals(track.sps[0], currentGopObj.sps[0]))) {
5096 continue;
5097 }
5098
5099 // Reject Gops that would require a negative baseMediaDecodeTime
5100 if (currentGop.dts < track.timelineStartInfo.dts) {
5101 continue;
5102 }
5103
5104 // The distance between the end of the gop and the start of the nalUnit
5105 dtsDistance = (nalUnit.dts - currentGop.dts) - currentGop.duration;
5106
5107 // Only consider GOPS that start before the nal unit and end within
5108 // a half-second of the nal unit
5109 if (dtsDistance >= -allowableOverlap &&
5110 dtsDistance <= halfSecond) {
5111
5112 // Always use the closest GOP we found if there is more than
5113 // one candidate
5114 if (!nearestGopObj ||
5115 nearestDistance > dtsDistance) {
5116 nearestGopObj = currentGopObj;
5117 nearestDistance = dtsDistance;
5118 }
5119 }
5120 }
5121
5122 if (nearestGopObj) {
5123 return nearestGopObj.gop;
5124 }
5125 return null;
5126 };
5127
5128 // trim gop list to the first gop found that has a matching pts with a gop in the list
5129 // of gopsToAlignWith starting from the START of the list
5130 this.alignGopsAtStart_ = function(gops) {
5131 var alignIndex, gopIndex, align, gop, byteLength, nalCount, duration, alignedGops;
5132
5133 byteLength = gops.byteLength;
5134 nalCount = gops.nalCount;
5135 duration = gops.duration;
5136 alignIndex = gopIndex = 0;
5137
5138 while (alignIndex < gopsToAlignWith.length && gopIndex < gops.length) {
5139 align = gopsToAlignWith[alignIndex];
5140 gop = gops[gopIndex];
5141
5142 if (align.pts === gop.pts) {
5143 break;
5144 }
5145
5146 if (gop.pts > align.pts) {
5147 // this current gop starts after the current gop we want to align on, so increment
5148 // align index
5149 alignIndex++;
5150 continue;
5151 }
5152
5153 // current gop starts before the current gop we want to align on. so increment gop
5154 // index
5155 gopIndex++;
5156 byteLength -= gop.byteLength;
5157 nalCount -= gop.nalCount;
5158 duration -= gop.duration;
5159 }
5160
5161 if (gopIndex === 0) {
5162 // no gops to trim
5163 return gops;
5164 }
5165
5166 if (gopIndex === gops.length) {
5167 // all gops trimmed, skip appending all gops
5168 return null;
5169 }
5170
5171 alignedGops = gops.slice(gopIndex);
5172 alignedGops.byteLength = byteLength;
5173 alignedGops.duration = duration;
5174 alignedGops.nalCount = nalCount;
5175 alignedGops.pts = alignedGops[0].pts;
5176 alignedGops.dts = alignedGops[0].dts;
5177
5178 return alignedGops;
5179 };
5180
5181 // trim gop list to the first gop found that has a matching pts with a gop in the list
5182 // of gopsToAlignWith starting from the END of the list
5183 this.alignGopsAtEnd_ = function(gops) {
5184 var alignIndex, gopIndex, align, gop, alignEndIndex, matchFound;
5185
5186 alignIndex = gopsToAlignWith.length - 1;
5187 gopIndex = gops.length - 1;
5188 alignEndIndex = null;
5189 matchFound = false;
5190
5191 while (alignIndex >= 0 && gopIndex >= 0) {
5192 align = gopsToAlignWith[alignIndex];
5193 gop = gops[gopIndex];
5194
5195 if (align.pts === gop.pts) {
5196 matchFound = true;
5197 break;
5198 }
5199
5200 if (align.pts > gop.pts) {
5201 alignIndex--;
5202 continue;
5203 }
5204
5205 if (alignIndex === gopsToAlignWith.length - 1) {
5206 // gop.pts is greater than the last alignment candidate. If no match is found
5207 // by the end of this loop, we still want to append gops that come after this
5208 // point
5209 alignEndIndex = gopIndex;
5210 }
5211
5212 gopIndex--;
5213 }
5214
5215 if (!matchFound && alignEndIndex === null) {
5216 return null;
5217 }
5218
5219 var trimIndex;
5220
5221 if (matchFound) {
5222 trimIndex = gopIndex;
5223 } else {
5224 trimIndex = alignEndIndex;
5225 }
5226
5227 if (trimIndex === 0) {
5228 return gops;
5229 }
5230
5231 var alignedGops = gops.slice(trimIndex);
5232 var metadata = alignedGops.reduce(function(total, gop) {
5233 total.byteLength += gop.byteLength;
5234 total.duration += gop.duration;
5235 total.nalCount += gop.nalCount;
5236 return total;
5237 }, { byteLength: 0, duration: 0, nalCount: 0 });
5238
5239 alignedGops.byteLength = metadata.byteLength;
5240 alignedGops.duration = metadata.duration;
5241 alignedGops.nalCount = metadata.nalCount;
5242 alignedGops.pts = alignedGops[0].pts;
5243 alignedGops.dts = alignedGops[0].dts;
5244
5245 return alignedGops;
5246 };
5247
5248 this.alignGopsWith = function(newGopsToAlignWith) {
5249 gopsToAlignWith = newGopsToAlignWith;
5250 };
5251 };
5252
5253 VideoSegmentStream.prototype = new stream();
5254
5255 /**
5256 * A Stream that can combine multiple streams (ie. audio & video)
5257 * into a single output segment for MSE. Also supports audio-only
5258 * and video-only streams.
5259 * @param options {object} transmuxer options object
5260 * @param options.keepOriginalTimestamps {boolean} If true, keep the timestamps
5261 * in the source; false to adjust the first segment to start at media timeline start.
5262 */
5263 CoalesceStream = function(options, metadataStream) {
5264 // Number of Tracks per output segment
5265 // If greater than 1, we combine multiple
5266 // tracks into a single segment
5267 this.numberOfTracks = 0;
5268 this.metadataStream = metadataStream;
5269
5270 options = options || {};
5271
5272 if (typeof options.remux !== 'undefined') {
5273 this.remuxTracks = !!options.remux;
5274 } else {
5275 this.remuxTracks = true;
5276 }
5277
5278 if (typeof options.keepOriginalTimestamps === 'boolean') {
5279 this.keepOriginalTimestamps = options.keepOriginalTimestamps;
5280 } else {
5281 this.keepOriginalTimestamps = false;
5282 }
5283
5284 this.pendingTracks = [];
5285 this.videoTrack = null;
5286 this.pendingBoxes = [];
5287 this.pendingCaptions = [];
5288 this.pendingMetadata = [];
5289 this.pendingBytes = 0;
5290 this.emittedTracks = 0;
5291
5292 CoalesceStream.prototype.init.call(this);
5293
5294 // Take output from multiple
5295 this.push = function(output) {
5296 // buffer incoming captions until the associated video segment
5297 // finishes
5298 if (output.text) {
5299 return this.pendingCaptions.push(output);
5300 }
5301 // buffer incoming id3 tags until the final flush
5302 if (output.frames) {
5303 return this.pendingMetadata.push(output);
5304 }
5305
5306 // Add this track to the list of pending tracks and store
5307 // important information required for the construction of
5308 // the final segment
5309 this.pendingTracks.push(output.track);
5310 this.pendingBytes += output.boxes.byteLength;
5311
5312 // TODO: is there an issue for this against chrome?
5313 // We unshift audio and push video because
5314 // as of Chrome 75 when switching from
5315 // one init segment to another if the video
5316 // mdat does not appear after the audio mdat
5317 // only audio will play for the duration of our transmux.
5318 if (output.track.type === 'video') {
5319 this.videoTrack = output.track;
5320 this.pendingBoxes.push(output.boxes);
5321 }
5322 if (output.track.type === 'audio') {
5323 this.audioTrack = output.track;
5324 this.pendingBoxes.unshift(output.boxes);
5325 }
5326 };
5327 };
5328
5329 CoalesceStream.prototype = new stream();
5330 CoalesceStream.prototype.flush = function(flushSource) {
5331 var
5332 offset = 0,
5333 event = {
5334 captions: [],
5335 captionStreams: {},
5336 metadata: [],
5337 info: {}
5338 },
5339 caption,
5340 id3,
5341 initSegment,
5342 timelineStartPts = 0,
5343 i;
5344
5345 if (this.pendingTracks.length < this.numberOfTracks) {
5346 if (flushSource !== 'VideoSegmentStream' &&
5347 flushSource !== 'AudioSegmentStream') {
5348 // Return because we haven't received a flush from a data-generating
5349 // portion of the segment (meaning that we have only recieved meta-data
5350 // or captions.)
5351 return;
5352 } else if (this.remuxTracks) {
5353 // Return until we have enough tracks from the pipeline to remux (if we
5354 // are remuxing audio and video into a single MP4)
5355 return;
5356 } else if (this.pendingTracks.length === 0) {
5357 // In the case where we receive a flush without any data having been
5358 // received we consider it an emitted track for the purposes of coalescing
5359 // `done` events.
5360 // We do this for the case where there is an audio and video track in the
5361 // segment but no audio data. (seen in several playlists with alternate
5362 // audio tracks and no audio present in the main TS segments.)
5363 this.emittedTracks++;
5364
5365 if (this.emittedTracks >= this.numberOfTracks) {
5366 this.trigger('done');
5367 this.emittedTracks = 0;
5368 }
5369 return;
5370 }
5371 }
5372
5373 if (this.videoTrack) {
5374 timelineStartPts = this.videoTrack.timelineStartInfo.pts;
5375 videoProperties.forEach(function(prop) {
5376 event.info[prop] = this.videoTrack[prop];
5377 }, this);
5378 } else if (this.audioTrack) {
5379 timelineStartPts = this.audioTrack.timelineStartInfo.pts;
5380 audioProperties.forEach(function(prop) {
5381 event.info[prop] = this.audioTrack[prop];
5382 }, this);
5383 }
5384
5385 if (this.videoTrack || this.audioTrack) {
5386 if (this.pendingTracks.length === 1) {
5387 event.type = this.pendingTracks[0].type;
5388 } else {
5389 event.type = 'combined';
5390 }
5391
5392 this.emittedTracks += this.pendingTracks.length;
5393
5394 initSegment = mp4Generator.initSegment(this.pendingTracks);
5395
5396 // Create a new typed array to hold the init segment
5397 event.initSegment = new Uint8Array(initSegment.byteLength);
5398
5399 // Create an init segment containing a moov
5400 // and track definitions
5401 event.initSegment.set(initSegment);
5402
5403 // Create a new typed array to hold the moof+mdats
5404 event.data = new Uint8Array(this.pendingBytes);
5405
5406 // Append each moof+mdat (one per track) together
5407 for (i = 0; i < this.pendingBoxes.length; i++) {
5408 event.data.set(this.pendingBoxes[i], offset);
5409 offset += this.pendingBoxes[i].byteLength;
5410 }
5411
5412 // Translate caption PTS times into second offsets to match the
5413 // video timeline for the segment, and add track info
5414 for (i = 0; i < this.pendingCaptions.length; i++) {
5415 caption = this.pendingCaptions[i];
5416 caption.startTime = clock.metadataTsToSeconds(
5417 caption.startPts, timelineStartPts, this.keepOriginalTimestamps);
5418 caption.endTime = clock.metadataTsToSeconds(
5419 caption.endPts, timelineStartPts, this.keepOriginalTimestamps);
5420
5421 event.captionStreams[caption.stream] = true;
5422 event.captions.push(caption);
5423 }
5424
5425 // Translate ID3 frame PTS times into second offsets to match the
5426 // video timeline for the segment
5427 for (i = 0; i < this.pendingMetadata.length; i++) {
5428 id3 = this.pendingMetadata[i];
5429 id3.cueTime = clock.metadataTsToSeconds(
5430 id3.pts, timelineStartPts, this.keepOriginalTimestamps);
5431
5432 event.metadata.push(id3);
5433 }
5434
5435 // We add this to every single emitted segment even though we only need
5436 // it for the first
5437 event.metadata.dispatchType = this.metadataStream.dispatchType;
5438
5439 // Reset stream state
5440 this.pendingTracks.length = 0;
5441 this.videoTrack = null;
5442 this.pendingBoxes.length = 0;
5443 this.pendingCaptions.length = 0;
5444 this.pendingBytes = 0;
5445 this.pendingMetadata.length = 0;
5446
5447 // Emit the built segment
5448 // We include captions and ID3 tags for backwards compatibility,
5449 // ideally we should send only video and audio in the data event
5450 this.trigger('data', event);
5451 // Emit each caption to the outside world
5452 // Ideally, this would happen immediately on parsing captions,
5453 // but we need to ensure that video data is sent back first
5454 // so that caption timing can be adjusted to match video timing
5455 for (i = 0; i < event.captions.length; i++) {
5456 caption = event.captions[i];
5457
5458 this.trigger('caption', caption);
5459 }
5460 // Emit each id3 tag to the outside world
5461 // Ideally, this would happen immediately on parsing the tag,
5462 // but we need to ensure that video data is sent back first
5463 // so that ID3 frame timing can be adjusted to match video timing
5464 for (i = 0; i < event.metadata.length; i++) {
5465 id3 = event.metadata[i];
5466
5467 this.trigger('id3Frame', id3);
5468 }
5469 }
5470
5471 // Only emit `done` if all tracks have been flushed and emitted
5472 if (this.emittedTracks >= this.numberOfTracks) {
5473 this.trigger('done');
5474 this.emittedTracks = 0;
5475 }
5476 };
5477
5478 CoalesceStream.prototype.setRemux = function(val) {
5479 this.remuxTracks = val;
5480 };
5481 /**
5482 * A Stream that expects MP2T binary data as input and produces
5483 * corresponding media segments, suitable for use with Media Source
5484 * Extension (MSE) implementations that support the ISO BMFF byte
5485 * stream format, like Chrome.
5486 */
5487 Transmuxer = function(options) {
5488 var
5489 self = this,
5490 hasFlushed = true,
5491 videoTrack,
5492 audioTrack;
5493
5494 Transmuxer.prototype.init.call(this);
5495
5496 options = options || {};
5497 this.baseMediaDecodeTime = options.baseMediaDecodeTime || 0;
5498 this.transmuxPipeline_ = {};
5499
5500 this.setupAacPipeline = function() {
5501 var pipeline = {};
5502 this.transmuxPipeline_ = pipeline;
5503
5504 pipeline.type = 'aac';
5505 pipeline.metadataStream = new m2ts_1.MetadataStream();
5506
5507 // set up the parsing pipeline
5508 pipeline.aacStream = new aac();
5509 pipeline.audioTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('audio');
5510 pipeline.timedMetadataTimestampRolloverStream = new m2ts_1.TimestampRolloverStream('timed-metadata');
5511 pipeline.adtsStream = new adts();
5512 pipeline.coalesceStream = new CoalesceStream(options, pipeline.metadataStream);
5513 pipeline.headOfPipeline = pipeline.aacStream;
5514
5515 pipeline.aacStream
5516 .pipe(pipeline.audioTimestampRolloverStream)
5517 .pipe(pipeline.adtsStream);
5518 pipeline.aacStream
5519 .pipe(pipeline.timedMetadataTimestampRolloverStream)
5520 .pipe(pipeline.metadataStream)
5521 .pipe(pipeline.coalesceStream);
5522
5523 pipeline.metadataStream.on('timestamp', function(frame) {
5524 pipeline.aacStream.setTimestamp(frame.timeStamp);
5525 });
5526
5527 pipeline.aacStream.on('data', function(data) {
5528 if ((data.type !== 'timed-metadata' && data.type !== 'audio') || pipeline.audioSegmentStream) {
5529 return;
5530 }
5531
5532 audioTrack = audioTrack || {
5533 timelineStartInfo: {
5534 baseMediaDecodeTime: self.baseMediaDecodeTime
5535 },
5536 codec: 'adts',
5537 type: 'audio'
5538 };
5539 // hook up the audio segment stream to the first track with aac data
5540 pipeline.coalesceStream.numberOfTracks++;
5541 pipeline.audioSegmentStream = new AudioSegmentStream(audioTrack, options);
5542
5543 pipeline.audioSegmentStream.on('timingInfo',
5544 self.trigger.bind(self, 'audioTimingInfo'));
5545
5546 // Set up the final part of the audio pipeline
5547 pipeline.adtsStream
5548 .pipe(pipeline.audioSegmentStream)
5549 .pipe(pipeline.coalesceStream);
5550
5551 // emit pmt info
5552 self.trigger('trackinfo', {
5553 hasAudio: !!audioTrack,
5554 hasVideo: !!videoTrack
5555 });
5556 });
5557
5558 // Re-emit any data coming from the coalesce stream to the outside world
5559 pipeline.coalesceStream.on('data', this.trigger.bind(this, 'data'));
5560 // Let the consumer know we have finished flushing the entire pipeline
5561 pipeline.coalesceStream.on('done', this.trigger.bind(this, 'done'));
5562 };
5563
5564 this.setupTsPipeline = function() {
5565 var pipeline = {};
5566 this.transmuxPipeline_ = pipeline;
5567
5568 pipeline.type = 'ts';
5569 pipeline.metadataStream = new m2ts_1.MetadataStream();
5570
5571 // set up the parsing pipeline
5572 pipeline.packetStream = new m2ts_1.TransportPacketStream();
5573 pipeline.parseStream = new m2ts_1.TransportParseStream();
5574 pipeline.elementaryStream = new m2ts_1.ElementaryStream();
5575 pipeline.timestampRolloverStream = new m2ts_1.TimestampRolloverStream();
5576 pipeline.adtsStream = new adts();
5577 pipeline.h264Stream = new H264Stream$1();
5578 pipeline.captionStream = new m2ts_1.CaptionStream();
5579 pipeline.coalesceStream = new CoalesceStream(options, pipeline.metadataStream);
5580 pipeline.headOfPipeline = pipeline.packetStream;
5581
5582 // disassemble MPEG2-TS packets into elementary streams
5583 pipeline.packetStream
5584 .pipe(pipeline.parseStream)
5585 .pipe(pipeline.elementaryStream)
5586 .pipe(pipeline.timestampRolloverStream);
5587
5588 // !!THIS ORDER IS IMPORTANT!!
5589 // demux the streams
5590 pipeline.timestampRolloverStream
5591 .pipe(pipeline.h264Stream);
5592
5593 pipeline.timestampRolloverStream
5594 .pipe(pipeline.adtsStream);
5595
5596 pipeline.timestampRolloverStream
5597 .pipe(pipeline.metadataStream)
5598 .pipe(pipeline.coalesceStream);
5599
5600 // Hook up CEA-608/708 caption stream
5601 pipeline.h264Stream.pipe(pipeline.captionStream)
5602 .pipe(pipeline.coalesceStream);
5603
5604 pipeline.elementaryStream.on('data', function(data) {
5605 var i;
5606
5607 if (data.type === 'metadata') {
5608 i = data.tracks.length;
5609
5610 // scan the tracks listed in the metadata
5611 while (i--) {
5612 if (!videoTrack && data.tracks[i].type === 'video') {
5613 videoTrack = data.tracks[i];
5614 videoTrack.timelineStartInfo.baseMediaDecodeTime = self.baseMediaDecodeTime;
5615 } else if (!audioTrack && data.tracks[i].type === 'audio') {
5616 audioTrack = data.tracks[i];
5617 audioTrack.timelineStartInfo.baseMediaDecodeTime = self.baseMediaDecodeTime;
5618 }
5619 }
5620
5621 // hook up the video segment stream to the first track with h264 data
5622 if (videoTrack && !pipeline.videoSegmentStream) {
5623 pipeline.coalesceStream.numberOfTracks++;
5624 pipeline.videoSegmentStream = new VideoSegmentStream(videoTrack, options);
5625
5626 pipeline.videoSegmentStream.on('timelineStartInfo', function(timelineStartInfo) {
5627 // When video emits timelineStartInfo data after a flush, we forward that
5628 // info to the AudioSegmentStream, if it exists, because video timeline
5629 // data takes precedence. Do not do this if keepOriginalTimestamps is set,
5630 // because this is a particularly subtle form of timestamp alteration.
5631 if (audioTrack && !options.keepOriginalTimestamps) {
5632 audioTrack.timelineStartInfo = timelineStartInfo;
5633 // On the first segment we trim AAC frames that exist before the
5634 // very earliest DTS we have seen in video because Chrome will
5635 // interpret any video track with a baseMediaDecodeTime that is
5636 // non-zero as a gap.
5637 pipeline.audioSegmentStream.setEarliestDts(timelineStartInfo.dts - self.baseMediaDecodeTime);
5638 }
5639 });
5640
5641 pipeline.videoSegmentStream.on('processedGopsInfo',
5642 self.trigger.bind(self, 'gopInfo'));
5643 pipeline.videoSegmentStream.on('segmentTimingInfo',
5644 self.trigger.bind(self, 'videoSegmentTimingInfo'));
5645
5646 pipeline.videoSegmentStream.on('baseMediaDecodeTime', function(baseMediaDecodeTime) {
5647 if (audioTrack) {
5648 pipeline.audioSegmentStream.setVideoBaseMediaDecodeTime(baseMediaDecodeTime);
5649 }
5650 });
5651
5652 pipeline.videoSegmentStream.on('timingInfo',
5653 self.trigger.bind(self, 'videoTimingInfo'));
5654
5655 // Set up the final part of the video pipeline
5656 pipeline.h264Stream
5657 .pipe(pipeline.videoSegmentStream)
5658 .pipe(pipeline.coalesceStream);
5659 }
5660
5661 if (audioTrack && !pipeline.audioSegmentStream) {
5662 // hook up the audio segment stream to the first track with aac data
5663 pipeline.coalesceStream.numberOfTracks++;
5664 pipeline.audioSegmentStream = new AudioSegmentStream(audioTrack, options);
5665
5666 pipeline.audioSegmentStream.on('timingInfo',
5667 self.trigger.bind(self, 'audioTimingInfo'));
5668
5669 // Set up the final part of the audio pipeline
5670 pipeline.adtsStream
5671 .pipe(pipeline.audioSegmentStream)
5672 .pipe(pipeline.coalesceStream);
5673 }
5674
5675 // emit pmt info
5676 self.trigger('trackinfo', {
5677 hasAudio: !!audioTrack,
5678 hasVideo: !!videoTrack
5679 });
5680 }
5681 });
5682
5683 // Re-emit any data coming from the coalesce stream to the outside world
5684 pipeline.coalesceStream.on('data', this.trigger.bind(this, 'data'));
5685 pipeline.coalesceStream.on('id3Frame', function(id3Frame) {
5686 id3Frame.dispatchType = pipeline.metadataStream.dispatchType;
5687
5688 self.trigger('id3Frame', id3Frame);
5689 });
5690 pipeline.coalesceStream.on('caption', this.trigger.bind(this, 'caption'));
5691 // Let the consumer know we have finished flushing the entire pipeline
5692 pipeline.coalesceStream.on('done', this.trigger.bind(this, 'done'));
5693 };
5694
5695 // hook up the segment streams once track metadata is delivered
5696 this.setBaseMediaDecodeTime = function(baseMediaDecodeTime) {
5697 var pipeline = this.transmuxPipeline_;
5698
5699 if (!options.keepOriginalTimestamps) {
5700 this.baseMediaDecodeTime = baseMediaDecodeTime;
5701 }
5702
5703 if (audioTrack) {
5704 audioTrack.timelineStartInfo.dts = undefined;
5705 audioTrack.timelineStartInfo.pts = undefined;
5706 trackDecodeInfo.clearDtsInfo(audioTrack);
5707 if (pipeline.audioTimestampRolloverStream) {
5708 pipeline.audioTimestampRolloverStream.discontinuity();
5709 }
5710 }
5711 if (videoTrack) {
5712 if (pipeline.videoSegmentStream) {
5713 pipeline.videoSegmentStream.gopCache_ = [];
5714 }
5715 videoTrack.timelineStartInfo.dts = undefined;
5716 videoTrack.timelineStartInfo.pts = undefined;
5717 trackDecodeInfo.clearDtsInfo(videoTrack);
5718 pipeline.captionStream.reset();
5719 }
5720
5721 if (pipeline.timestampRolloverStream) {
5722 pipeline.timestampRolloverStream.discontinuity();
5723 }
5724 };
5725
5726 this.setAudioAppendStart = function(timestamp) {
5727 if (audioTrack) {
5728 this.transmuxPipeline_.audioSegmentStream.setAudioAppendStart(timestamp);
5729 }
5730 };
5731
5732 this.setRemux = function(val) {
5733 var pipeline = this.transmuxPipeline_;
5734
5735 options.remux = val;
5736
5737 if (pipeline && pipeline.coalesceStream) {
5738 pipeline.coalesceStream.setRemux(val);
5739 }
5740 };
5741
5742 this.alignGopsWith = function(gopsToAlignWith) {
5743 if (videoTrack && this.transmuxPipeline_.videoSegmentStream) {
5744 this.transmuxPipeline_.videoSegmentStream.alignGopsWith(gopsToAlignWith);
5745 }
5746 };
5747
5748 // feed incoming data to the front of the parsing pipeline
5749 this.push = function(data) {
5750 if (hasFlushed) {
5751 var isAac = isLikelyAacData$1(data);
5752
5753 if (isAac && this.transmuxPipeline_.type !== 'aac') {
5754 this.setupAacPipeline();
5755 } else if (!isAac && this.transmuxPipeline_.type !== 'ts') {
5756 this.setupTsPipeline();
5757 }
5758 hasFlushed = false;
5759 }
5760 this.transmuxPipeline_.headOfPipeline.push(data);
5761 };
5762
5763 // flush any buffered data
5764 this.flush = function() {
5765 hasFlushed = true;
5766 // Start at the top of the pipeline and flush all pending work
5767 this.transmuxPipeline_.headOfPipeline.flush();
5768 };
5769
5770 this.endTimeline = function() {
5771 this.transmuxPipeline_.headOfPipeline.endTimeline();
5772 };
5773
5774 this.reset = function() {
5775 if (this.transmuxPipeline_.headOfPipeline) {
5776 this.transmuxPipeline_.headOfPipeline.reset();
5777 }
5778 };
5779
5780 // Caption data has to be reset when seeking outside buffered range
5781 this.resetCaptions = function() {
5782 if (this.transmuxPipeline_.captionStream) {
5783 this.transmuxPipeline_.captionStream.reset();
5784 }
5785 };
5786
5787 };
5788 Transmuxer.prototype = new stream();
5789
5790 var transmuxer = {
5791 Transmuxer: Transmuxer,
5792 VideoSegmentStream: VideoSegmentStream,
5793 AudioSegmentStream: AudioSegmentStream,
5794 AUDIO_PROPERTIES: audioProperties,
5795 VIDEO_PROPERTIES: videoProperties,
5796 // exported for testing
5797 generateVideoSegmentTimingInfo: generateVideoSegmentTimingInfo
5798 };
5799 var transmuxer_1 = transmuxer.Transmuxer;
5800
5801 /**
5802 * mux.js
5803 *
5804 * Copyright (c) Brightcove
5805 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
5806 */
5807 var codecs = {
5808 Adts: adts,
5809 h264: h264
5810 };
5811
5812 var ONE_SECOND_IN_TS$4 = clock.ONE_SECOND_IN_TS;
5813
5814
5815 /**
5816 * Constructs a single-track, ISO BMFF media segment from AAC data
5817 * events. The output of this stream can be fed to a SourceBuffer
5818 * configured with a suitable initialization segment.
5819 */
5820 var AudioSegmentStream$1 = function(track, options) {
5821 var
5822 adtsFrames = [],
5823 sequenceNumber = 0,
5824 earliestAllowedDts = 0,
5825 audioAppendStartTs = 0,
5826 videoBaseMediaDecodeTime = Infinity,
5827 segmentStartPts = null,
5828 segmentEndPts = null;
5829
5830 options = options || {};
5831
5832 AudioSegmentStream$1.prototype.init.call(this);
5833
5834 this.push = function(data) {
5835 trackDecodeInfo.collectDtsInfo(track, data);
5836
5837 if (track) {
5838 audioProperties.forEach(function(prop) {
5839 track[prop] = data[prop];
5840 });
5841 }
5842
5843 // buffer audio data until end() is called
5844 adtsFrames.push(data);
5845 };
5846
5847 this.setEarliestDts = function(earliestDts) {
5848 earliestAllowedDts = earliestDts;
5849 };
5850
5851 this.setVideoBaseMediaDecodeTime = function(baseMediaDecodeTime) {
5852 videoBaseMediaDecodeTime = baseMediaDecodeTime;
5853 };
5854
5855 this.setAudioAppendStart = function(timestamp) {
5856 audioAppendStartTs = timestamp;
5857 };
5858
5859 this.processFrames_ = function() {
5860 var
5861 frames,
5862 moof,
5863 mdat,
5864 boxes,
5865 timingInfo;
5866
5867 // return early if no audio data has been observed
5868 if (adtsFrames.length === 0) {
5869 return;
5870 }
5871
5872 frames = audioFrameUtils.trimAdtsFramesByEarliestDts(
5873 adtsFrames, track, earliestAllowedDts);
5874 if (frames.length === 0) {
5875 // return early if the frames are all after the earliest allowed DTS
5876 // TODO should we clear the adtsFrames?
5877 return;
5878 }
5879
5880 track.baseMediaDecodeTime = trackDecodeInfo.calculateTrackBaseMediaDecodeTime(
5881 track, options.keepOriginalTimestamps);
5882
5883 audioFrameUtils.prefixWithSilence(
5884 track, frames, audioAppendStartTs, videoBaseMediaDecodeTime);
5885
5886 // we have to build the index from byte locations to
5887 // samples (that is, adts frames) in the audio data
5888 track.samples = audioFrameUtils.generateSampleTable(frames);
5889
5890 // concatenate the audio data to constuct the mdat
5891 mdat = mp4Generator.mdat(audioFrameUtils.concatenateFrameData(frames));
5892
5893 adtsFrames = [];
5894
5895 moof = mp4Generator.moof(sequenceNumber, [track]);
5896
5897 // bump the sequence number for next time
5898 sequenceNumber++;
5899
5900 track.initSegment = mp4Generator.initSegment([track]);
5901
5902 // it would be great to allocate this array up front instead of
5903 // throwing away hundreds of media segment fragments
5904 boxes = new Uint8Array(moof.byteLength + mdat.byteLength);
5905
5906 boxes.set(moof);
5907 boxes.set(mdat, moof.byteLength);
5908
5909 trackDecodeInfo.clearDtsInfo(track);
5910
5911 if (segmentStartPts === null) {
5912 segmentEndPts = segmentStartPts = frames[0].pts;
5913 }
5914
5915 segmentEndPts += frames.length * (ONE_SECOND_IN_TS$4 * 1024 / track.samplerate);
5916
5917 timingInfo = { start: segmentStartPts };
5918
5919 this.trigger('timingInfo', timingInfo);
5920 this.trigger('data', {track: track, boxes: boxes});
5921 };
5922
5923 this.flush = function() {
5924 this.processFrames_();
5925 // trigger final timing info
5926 this.trigger('timingInfo', {
5927 start: segmentStartPts,
5928 end: segmentEndPts
5929 });
5930 this.resetTiming_();
5931 this.trigger('done', 'AudioSegmentStream');
5932 };
5933
5934 this.partialFlush = function() {
5935 this.processFrames_();
5936 this.trigger('partialdone', 'AudioSegmentStream');
5937 };
5938
5939 this.endTimeline = function() {
5940 this.flush();
5941 this.trigger('endedtimeline', 'AudioSegmentStream');
5942 };
5943
5944 this.resetTiming_ = function() {
5945 trackDecodeInfo.clearDtsInfo(track);
5946 segmentStartPts = null;
5947 segmentEndPts = null;
5948 };
5949
5950 this.reset = function() {
5951 this.resetTiming_();
5952 adtsFrames = [];
5953 this.trigger('reset');
5954 };
5955 };
5956
5957 AudioSegmentStream$1.prototype = new stream();
5958
5959 var audioSegmentStream = AudioSegmentStream$1;
5960
5961 var VideoSegmentStream$1 = function(track, options) {
5962 var
5963 sequenceNumber = 0,
5964 nalUnits = [],
5965 frameCache = [],
5966 // gopsToAlignWith = [],
5967 config,
5968 pps,
5969 segmentStartPts = null,
5970 segmentEndPts = null,
5971 gops,
5972 ensureNextFrameIsKeyFrame = true;
5973
5974 options = options || {};
5975
5976 VideoSegmentStream$1.prototype.init.call(this);
5977
5978 this.push = function(nalUnit) {
5979 trackDecodeInfo.collectDtsInfo(track, nalUnit);
5980 if (typeof track.timelineStartInfo.dts === 'undefined') {
5981 track.timelineStartInfo.dts = nalUnit.dts;
5982 }
5983
5984 // record the track config
5985 if (nalUnit.nalUnitType === 'seq_parameter_set_rbsp' && !config) {
5986 config = nalUnit.config;
5987 track.sps = [nalUnit.data];
5988
5989 videoProperties.forEach(function(prop) {
5990 track[prop] = config[prop];
5991 }, this);
5992 }
5993
5994 if (nalUnit.nalUnitType === 'pic_parameter_set_rbsp' &&
5995 !pps) {
5996 pps = nalUnit.data;
5997 track.pps = [nalUnit.data];
5998 }
5999
6000 // buffer video until flush() is called
6001 nalUnits.push(nalUnit);
6002 };
6003
6004 this.processNals_ = function(cacheLastFrame) {
6005 var i;
6006
6007 nalUnits = frameCache.concat(nalUnits);
6008
6009 // Throw away nalUnits at the start of the byte stream until
6010 // we find the first AUD
6011 while (nalUnits.length) {
6012 if (nalUnits[0].nalUnitType === 'access_unit_delimiter_rbsp') {
6013 break;
6014 }
6015 nalUnits.shift();
6016 }
6017
6018 // Return early if no video data has been observed
6019 if (nalUnits.length === 0) {
6020 return;
6021 }
6022
6023 var frames = frameUtils.groupNalsIntoFrames(nalUnits);
6024
6025 if (!frames.length) {
6026 return;
6027 }
6028
6029 // note that the frame cache may also protect us from cases where we haven't
6030 // pushed data for the entire first or last frame yet
6031 frameCache = frames[frames.length - 1];
6032
6033 if (cacheLastFrame) {
6034 frames.pop();
6035 frames.duration -= frameCache.duration;
6036 frames.nalCount -= frameCache.length;
6037 frames.byteLength -= frameCache.byteLength;
6038 }
6039
6040 if (!frames.length) {
6041 nalUnits = [];
6042 return;
6043 }
6044
6045 this.trigger('timelineStartInfo', track.timelineStartInfo);
6046
6047 if (ensureNextFrameIsKeyFrame) {
6048 gops = frameUtils.groupFramesIntoGops(frames);
6049
6050 if (!gops[0][0].keyFrame) {
6051 gops = frameUtils.extendFirstKeyFrame(gops);
6052
6053 if (!gops[0][0].keyFrame) {
6054 // we haven't yet gotten a key frame, so reset nal units to wait for more nal
6055 // units
6056 nalUnits = ([].concat.apply([], frames)).concat(frameCache);
6057 frameCache = [];
6058 return;
6059 }
6060
6061 frames = [].concat.apply([], gops);
6062 frames.duration = gops.duration;
6063 }
6064 ensureNextFrameIsKeyFrame = false;
6065 }
6066
6067 if (segmentStartPts === null) {
6068 segmentStartPts = frames[0].pts;
6069 segmentEndPts = segmentStartPts;
6070 }
6071
6072 segmentEndPts += frames.duration;
6073
6074 this.trigger('timingInfo', {
6075 start: segmentStartPts,
6076 end: segmentEndPts
6077 });
6078
6079 for (i = 0; i < frames.length; i++) {
6080 var frame = frames[i];
6081
6082 track.samples = frameUtils.generateSampleTableForFrame(frame);
6083
6084 var mdat = mp4Generator.mdat(frameUtils.concatenateNalDataForFrame(frame));
6085
6086 trackDecodeInfo.clearDtsInfo(track);
6087 trackDecodeInfo.collectDtsInfo(track, frame);
6088
6089 track.baseMediaDecodeTime = trackDecodeInfo.calculateTrackBaseMediaDecodeTime(
6090 track, options.keepOriginalTimestamps);
6091
6092 var moof = mp4Generator.moof(sequenceNumber, [track]);
6093
6094 sequenceNumber++;
6095
6096 track.initSegment = mp4Generator.initSegment([track]);
6097
6098 var boxes = new Uint8Array(moof.byteLength + mdat.byteLength);
6099
6100 boxes.set(moof);
6101 boxes.set(mdat, moof.byteLength);
6102
6103 this.trigger('data', {
6104 track: track,
6105 boxes: boxes,
6106 sequence: sequenceNumber,
6107 videoFrameDts: frame.dts,
6108 videoFramePts: frame.pts
6109 });
6110 }
6111
6112 nalUnits = [];
6113 };
6114
6115 this.resetTimingAndConfig_ = function() {
6116 config = undefined;
6117 pps = undefined;
6118 segmentStartPts = null;
6119 segmentEndPts = null;
6120 };
6121
6122 this.partialFlush = function() {
6123 this.processNals_(true);
6124 this.trigger('partialdone', 'VideoSegmentStream');
6125 };
6126
6127 this.flush = function() {
6128 this.processNals_(false);
6129 // reset config and pps because they may differ across segments
6130 // for instance, when we are rendition switching
6131 this.resetTimingAndConfig_();
6132 this.trigger('done', 'VideoSegmentStream');
6133 };
6134
6135 this.endTimeline = function() {
6136 this.flush();
6137 this.trigger('endedtimeline', 'VideoSegmentStream');
6138 };
6139
6140 this.reset = function() {
6141 this.resetTimingAndConfig_();
6142 frameCache = [];
6143 nalUnits = [];
6144 ensureNextFrameIsKeyFrame = true;
6145 this.trigger('reset');
6146 };
6147 };
6148
6149 VideoSegmentStream$1.prototype = new stream();
6150
6151 var videoSegmentStream = VideoSegmentStream$1;
6152
6153 var isLikelyAacData$2 = utils.isLikelyAacData;
6154
6155
6156
6157
6158 var createPipeline = function(object) {
6159 object.prototype = new stream();
6160 object.prototype.init.call(object);
6161
6162 return object;
6163 };
6164
6165 var tsPipeline = function(options) {
6166 var
6167 pipeline = {
6168 type: 'ts',
6169 tracks: {
6170 audio: null,
6171 video: null
6172 },
6173 packet: new m2ts_1.TransportPacketStream(),
6174 parse: new m2ts_1.TransportParseStream(),
6175 elementary: new m2ts_1.ElementaryStream(),
6176 timestampRollover: new m2ts_1.TimestampRolloverStream(),
6177 adts: new codecs.Adts(),
6178 h264: new codecs.h264.H264Stream(),
6179 captionStream: new m2ts_1.CaptionStream(),
6180 metadataStream: new m2ts_1.MetadataStream()
6181 };
6182
6183 pipeline.headOfPipeline = pipeline.packet;
6184
6185 // Transport Stream
6186 pipeline.packet
6187 .pipe(pipeline.parse)
6188 .pipe(pipeline.elementary)
6189 .pipe(pipeline.timestampRollover);
6190
6191 // H264
6192 pipeline.timestampRollover
6193 .pipe(pipeline.h264);
6194
6195 // Hook up CEA-608/708 caption stream
6196 pipeline.h264
6197 .pipe(pipeline.captionStream);
6198
6199 pipeline.timestampRollover
6200 .pipe(pipeline.metadataStream);
6201
6202 // ADTS
6203 pipeline.timestampRollover
6204 .pipe(pipeline.adts);
6205
6206 pipeline.elementary.on('data', function(data) {
6207 if (data.type !== 'metadata') {
6208 return;
6209 }
6210
6211 for (var i = 0; i < data.tracks.length; i++) {
6212 if (!pipeline.tracks[data.tracks[i].type]) {
6213 pipeline.tracks[data.tracks[i].type] = data.tracks[i];
6214 pipeline.tracks[data.tracks[i].type].timelineStartInfo.baseMediaDecodeTime = options.baseMediaDecodeTime;
6215 }
6216 }
6217
6218 if (pipeline.tracks.video && !pipeline.videoSegmentStream) {
6219 pipeline.videoSegmentStream = new videoSegmentStream(pipeline.tracks.video, options);
6220
6221 pipeline.videoSegmentStream.on('timelineStartInfo', function(timelineStartInfo) {
6222 if (pipeline.tracks.audio && !options.keepOriginalTimestamps) {
6223 pipeline.audioSegmentStream.setEarliestDts(timelineStartInfo.dts - options.baseMediaDecodeTime);
6224 }
6225 });
6226
6227 pipeline.videoSegmentStream.on('timingInfo',
6228 pipeline.trigger.bind(pipeline, 'videoTimingInfo'));
6229
6230 pipeline.videoSegmentStream.on('data', function(data) {
6231 pipeline.trigger('data', {
6232 type: 'video',
6233 data: data
6234 });
6235 });
6236
6237 pipeline.videoSegmentStream.on('done',
6238 pipeline.trigger.bind(pipeline, 'done'));
6239 pipeline.videoSegmentStream.on('partialdone',
6240 pipeline.trigger.bind(pipeline, 'partialdone'));
6241 pipeline.videoSegmentStream.on('endedtimeline',
6242 pipeline.trigger.bind(pipeline, 'endedtimeline'));
6243
6244 pipeline.h264
6245 .pipe(pipeline.videoSegmentStream);
6246 }
6247
6248 if (pipeline.tracks.audio && !pipeline.audioSegmentStream) {
6249 pipeline.audioSegmentStream = new audioSegmentStream(pipeline.tracks.audio, options);
6250
6251 pipeline.audioSegmentStream.on('data', function(data) {
6252 pipeline.trigger('data', {
6253 type: 'audio',
6254 data: data
6255 });
6256 });
6257
6258 pipeline.audioSegmentStream.on('done',
6259 pipeline.trigger.bind(pipeline, 'done'));
6260 pipeline.audioSegmentStream.on('partialdone',
6261 pipeline.trigger.bind(pipeline, 'partialdone'));
6262 pipeline.audioSegmentStream.on('endedtimeline',
6263 pipeline.trigger.bind(pipeline, 'endedtimeline'));
6264
6265 pipeline.audioSegmentStream.on('timingInfo',
6266 pipeline.trigger.bind(pipeline, 'audioTimingInfo'));
6267
6268 pipeline.adts
6269 .pipe(pipeline.audioSegmentStream);
6270 }
6271
6272 // emit pmt info
6273 pipeline.trigger('trackinfo', {
6274 hasAudio: !!pipeline.tracks.audio,
6275 hasVideo: !!pipeline.tracks.video
6276 });
6277 });
6278
6279 pipeline.captionStream.on('data', function(caption) {
6280 var timelineStartPts;
6281
6282 if (pipeline.tracks.video) {
6283 timelineStartPts = pipeline.tracks.video.timelineStartInfo.pts || 0;
6284 } else {
6285 // This will only happen if we encounter caption packets before
6286 // video data in a segment. This is an unusual/unlikely scenario,
6287 // so we assume the timeline starts at zero for now.
6288 timelineStartPts = 0;
6289 }
6290
6291 // Translate caption PTS times into second offsets into the
6292 // video timeline for the segment
6293 caption.startTime = clock.metadataTsToSeconds(caption.startPts, timelineStartPts, options.keepOriginalTimestamps);
6294 caption.endTime = clock.metadataTsToSeconds(caption.endPts, timelineStartPts, options.keepOriginalTimestamps);
6295
6296 pipeline.trigger('caption', caption);
6297 });
6298
6299 pipeline = createPipeline(pipeline);
6300
6301 pipeline.metadataStream.on('data', pipeline.trigger.bind(pipeline, 'id3Frame'));
6302
6303 return pipeline;
6304 };
6305
6306 var aacPipeline = function(options) {
6307 var
6308 pipeline = {
6309 type: 'aac',
6310 tracks: {
6311 audio: null
6312 },
6313 metadataStream: new m2ts_1.MetadataStream(),
6314 aacStream: new aac(),
6315 audioRollover: new m2ts_1.TimestampRolloverStream('audio'),
6316 timedMetadataRollover: new m2ts_1.TimestampRolloverStream('timed-metadata'),
6317 adtsStream: new adts(true)
6318 };
6319
6320 // set up the parsing pipeline
6321 pipeline.headOfPipeline = pipeline.aacStream;
6322
6323 pipeline.aacStream
6324 .pipe(pipeline.audioRollover)
6325 .pipe(pipeline.adtsStream);
6326 pipeline.aacStream
6327 .pipe(pipeline.timedMetadataRollover)
6328 .pipe(pipeline.metadataStream);
6329
6330 pipeline.metadataStream.on('timestamp', function(frame) {
6331 pipeline.aacStream.setTimestamp(frame.timeStamp);
6332 });
6333
6334 pipeline.aacStream.on('data', function(data) {
6335 if ((data.type !== 'timed-metadata' && data.type !== 'audio') || pipeline.audioSegmentStream) {
6336 return;
6337 }
6338
6339 pipeline.tracks.audio = pipeline.tracks.audio || {
6340 timelineStartInfo: {
6341 baseMediaDecodeTime: options.baseMediaDecodeTime
6342 },
6343 codec: 'adts',
6344 type: 'audio'
6345 };
6346
6347 // hook up the audio segment stream to the first track with aac data
6348 pipeline.audioSegmentStream = new audioSegmentStream(pipeline.tracks.audio, options);
6349
6350 pipeline.audioSegmentStream.on('data', function(data) {
6351 pipeline.trigger('data', {
6352 type: 'audio',
6353 data: data
6354 });
6355 });
6356
6357 pipeline.audioSegmentStream.on('partialdone',
6358 pipeline.trigger.bind(pipeline, 'partialdone'));
6359 pipeline.audioSegmentStream.on('done', pipeline.trigger.bind(pipeline, 'done'));
6360 pipeline.audioSegmentStream.on('endedtimeline',
6361 pipeline.trigger.bind(pipeline, 'endedtimeline'));
6362 pipeline.audioSegmentStream.on('timingInfo',
6363 pipeline.trigger.bind(pipeline, 'audioTimingInfo'));
6364
6365 // Set up the final part of the audio pipeline
6366 pipeline.adtsStream
6367 .pipe(pipeline.audioSegmentStream);
6368
6369 pipeline.trigger('trackinfo', {
6370 hasAudio: !!pipeline.tracks.audio,
6371 hasVideo: !!pipeline.tracks.video
6372 });
6373 });
6374
6375 // set the pipeline up as a stream before binding to get access to the trigger function
6376 pipeline = createPipeline(pipeline);
6377
6378 pipeline.metadataStream.on('data', pipeline.trigger.bind(pipeline, 'id3Frame'));
6379
6380 return pipeline;
6381 };
6382
6383 var setupPipelineListeners = function(pipeline, transmuxer) {
6384 pipeline.on('data', transmuxer.trigger.bind(transmuxer, 'data'));
6385 pipeline.on('done', transmuxer.trigger.bind(transmuxer, 'done'));
6386 pipeline.on('partialdone', transmuxer.trigger.bind(transmuxer, 'partialdone'));
6387 pipeline.on('endedtimeline', transmuxer.trigger.bind(transmuxer, 'endedtimeline'));
6388 pipeline.on('audioTimingInfo', transmuxer.trigger.bind(transmuxer, 'audioTimingInfo'));
6389 pipeline.on('videoTimingInfo', transmuxer.trigger.bind(transmuxer, 'videoTimingInfo'));
6390 pipeline.on('trackinfo', transmuxer.trigger.bind(transmuxer, 'trackinfo'));
6391 pipeline.on('id3Frame', function(event) {
6392 // add this to every single emitted segment even though it's only needed for the first
6393 event.dispatchType = pipeline.metadataStream.dispatchType;
6394 // keep original time, can be adjusted if needed at a higher level
6395 event.cueTime = clock.videoTsToSeconds(event.pts);
6396
6397 transmuxer.trigger('id3Frame', event);
6398 });
6399 pipeline.on('caption', function(event) {
6400 transmuxer.trigger('caption', event);
6401 });
6402 };
6403
6404 var Transmuxer$1 = function(options) {
6405 var
6406 pipeline = null,
6407 hasFlushed = true;
6408
6409 options = options || {};
6410
6411 Transmuxer$1.prototype.init.call(this);
6412 options.baseMediaDecodeTime = options.baseMediaDecodeTime || 0;
6413
6414 this.push = function(bytes) {
6415 if (hasFlushed) {
6416 var isAac = isLikelyAacData$2(bytes);
6417
6418 if (isAac && (!pipeline || pipeline.type !== 'aac')) {
6419 pipeline = aacPipeline(options);
6420 setupPipelineListeners(pipeline, this);
6421 } else if (!isAac && (!pipeline || pipeline.type !== 'ts')) {
6422 pipeline = tsPipeline(options);
6423 setupPipelineListeners(pipeline, this);
6424 }
6425 hasFlushed = false;
6426 }
6427
6428 pipeline.headOfPipeline.push(bytes);
6429 };
6430
6431 this.flush = function() {
6432 if (!pipeline) {
6433 return;
6434 }
6435
6436 hasFlushed = true;
6437 pipeline.headOfPipeline.flush();
6438 };
6439
6440 this.partialFlush = function() {
6441 if (!pipeline) {
6442 return;
6443 }
6444
6445 pipeline.headOfPipeline.partialFlush();
6446 };
6447
6448 this.endTimeline = function() {
6449 if (!pipeline) {
6450 return;
6451 }
6452
6453 pipeline.headOfPipeline.endTimeline();
6454 };
6455
6456 this.reset = function() {
6457 if (!pipeline) {
6458 return;
6459 }
6460
6461 pipeline.headOfPipeline.reset();
6462 };
6463
6464 this.setBaseMediaDecodeTime = function(baseMediaDecodeTime) {
6465 if (!options.keepOriginalTimestamps) {
6466 options.baseMediaDecodeTime = baseMediaDecodeTime;
6467 }
6468
6469 if (!pipeline) {
6470 return;
6471 }
6472
6473 if (pipeline.tracks.audio) {
6474 pipeline.tracks.audio.timelineStartInfo.dts = undefined;
6475 pipeline.tracks.audio.timelineStartInfo.pts = undefined;
6476 trackDecodeInfo.clearDtsInfo(pipeline.tracks.audio);
6477 if (pipeline.audioRollover) {
6478 pipeline.audioRollover.discontinuity();
6479 }
6480 }
6481 if (pipeline.tracks.video) {
6482 if (pipeline.videoSegmentStream) {
6483 pipeline.videoSegmentStream.gopCache_ = [];
6484 }
6485 pipeline.tracks.video.timelineStartInfo.dts = undefined;
6486 pipeline.tracks.video.timelineStartInfo.pts = undefined;
6487 trackDecodeInfo.clearDtsInfo(pipeline.tracks.video);
6488 // pipeline.captionStream.reset();
6489 }
6490
6491 if (pipeline.timestampRollover) {
6492 pipeline.timestampRollover.discontinuity();
6493
6494 }
6495 };
6496
6497 this.setRemux = function(val) {
6498 options.remux = val;
6499
6500 if (pipeline && pipeline.coalesceStream) {
6501 pipeline.coalesceStream.setRemux(val);
6502 }
6503 };
6504
6505
6506 this.setAudioAppendStart = function(audioAppendStart) {
6507 if (!pipeline || !pipeline.tracks.audio || !pipeline.audioSegmentStream) {
6508 return;
6509 }
6510
6511 pipeline.audioSegmentStream.setAudioAppendStart(audioAppendStart);
6512 };
6513
6514 // TODO GOP alignment support
6515 // Support may be a bit trickier than with full segment appends, as GOPs may be split
6516 // and processed in a more granular fashion
6517 this.alignGopsWith = function(gopsToAlignWith) {
6518 return;
6519 };
6520 };
6521
6522 Transmuxer$1.prototype = new stream();
6523
6524 var transmuxer$1 = Transmuxer$1;
6525
6526 /**
6527 * mux.js
6528 *
6529 * Copyright (c) Brightcove
6530 * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
6531 */
6532 var toUnsigned = function(value) {
6533 return value >>> 0;
6534 };
6535
6536 var toHexString = function(value) {
6537 return ('00' + value.toString(16)).slice(-2);
6538 };
6539
6540 var bin = {
6541 toUnsigned: toUnsigned,
6542 toHexString: toHexString
6543 };
6544
6545 var parseType$1 = function(buffer) {
6546 var result = '';
6547 result += String.fromCharCode(buffer[0]);
6548 result += String.fromCharCode(buffer[1]);
6549 result += String.fromCharCode(buffer[2]);
6550 result += String.fromCharCode(buffer[3]);
6551 return result;
6552 };
6553
6554
6555 var parseType_1 = parseType$1;
6556
6557 var toUnsigned$1 = bin.toUnsigned;
6558
6559
6560 var findBox = function(data, path) {
6561 var results = [],
6562 i, size, type, end, subresults;
6563
6564 if (!path.length) {
6565 // short-circuit the search for empty paths
6566 return null;
6567 }
6568
6569 for (i = 0; i < data.byteLength;) {
6570 size = toUnsigned$1(data[i] << 24 |
6571 data[i + 1] << 16 |
6572 data[i + 2] << 8 |
6573 data[i + 3]);
6574
6575 type = parseType_1(data.subarray(i + 4, i + 8));
6576
6577 end = size > 1 ? i + size : data.byteLength;
6578
6579 if (type === path[0]) {
6580 if (path.length === 1) {
6581 // this is the end of the path and we've found the box we were
6582 // looking for
6583 results.push(data.subarray(i + 8, end));
6584 } else {
6585 // recursively search for the next box along the path
6586 subresults = findBox(data.subarray(i + 8, end), path.slice(1));
6587 if (subresults.length) {
6588 results = results.concat(subresults);
6589 }
6590 }
6591 }
6592 i = end;
6593 }
6594
6595 // we've finished searching all of data
6596 return results;
6597 };
6598
6599 var findBox_1 = findBox;
6600
6601 var toUnsigned$2 = bin.toUnsigned;
6602
6603 var tfdt = function(data) {
6604 var result = {
6605 version: data[0],
6606 flags: new Uint8Array(data.subarray(1, 4)),
6607 baseMediaDecodeTime: toUnsigned$2(data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7])
6608 };
6609 if (result.version === 1) {
6610 result.baseMediaDecodeTime *= Math.pow(2, 32);
6611 result.baseMediaDecodeTime += toUnsigned$2(data[8] << 24 | data[9] << 16 | data[10] << 8 | data[11]);
6612 }
6613 return result;
6614 };
6615
6616 var parseTfdt = tfdt;
6617
6618 var parseSampleFlags = function(flags) {
6619 return {
6620 isLeading: (flags[0] & 0x0c) >>> 2,
6621 dependsOn: flags[0] & 0x03,
6622 isDependedOn: (flags[1] & 0xc0) >>> 6,
6623 hasRedundancy: (flags[1] & 0x30) >>> 4,
6624 paddingValue: (flags[1] & 0x0e) >>> 1,
6625 isNonSyncSample: flags[1] & 0x01,
6626 degradationPriority: (flags[2] << 8) | flags[3]
6627 };
6628 };
6629
6630 var parseSampleFlags_1 = parseSampleFlags;
6631
6632 var trun$1 = function(data) {
6633 var
6634 result = {
6635 version: data[0],
6636 flags: new Uint8Array(data.subarray(1, 4)),
6637 samples: []
6638 },
6639 view = new DataView(data.buffer, data.byteOffset, data.byteLength),
6640 // Flag interpretation
6641 dataOffsetPresent = result.flags[2] & 0x01, // compare with 2nd byte of 0x1
6642 firstSampleFlagsPresent = result.flags[2] & 0x04, // compare with 2nd byte of 0x4
6643 sampleDurationPresent = result.flags[1] & 0x01, // compare with 2nd byte of 0x100
6644 sampleSizePresent = result.flags[1] & 0x02, // compare with 2nd byte of 0x200
6645 sampleFlagsPresent = result.flags[1] & 0x04, // compare with 2nd byte of 0x400
6646 sampleCompositionTimeOffsetPresent = result.flags[1] & 0x08, // compare with 2nd byte of 0x800
6647 sampleCount = view.getUint32(4),
6648 offset = 8,
6649 sample;
6650
6651 if (dataOffsetPresent) {
6652 // 32 bit signed integer
6653 result.dataOffset = view.getInt32(offset);
6654 offset += 4;
6655 }
6656
6657 // Overrides the flags for the first sample only. The order of
6658 // optional values will be: duration, size, compositionTimeOffset
6659 if (firstSampleFlagsPresent && sampleCount) {
6660 sample = {
6661 flags: parseSampleFlags_1(data.subarray(offset, offset + 4))
6662 };
6663 offset += 4;
6664 if (sampleDurationPresent) {
6665 sample.duration = view.getUint32(offset);
6666 offset += 4;
6667 }
6668 if (sampleSizePresent) {
6669 sample.size = view.getUint32(offset);
6670 offset += 4;
6671 }
6672 if (sampleCompositionTimeOffsetPresent) {
6673 if (result.version === 1) {
6674 sample.compositionTimeOffset = view.getInt32(offset);
6675 } else {
6676 sample.compositionTimeOffset = view.getUint32(offset);
6677 }
6678 offset += 4;
6679 }
6680 result.samples.push(sample);
6681 sampleCount--;
6682 }
6683
6684 while (sampleCount--) {
6685 sample = {};
6686 if (sampleDurationPresent) {
6687 sample.duration = view.getUint32(offset);
6688 offset += 4;
6689 }
6690 if (sampleSizePresent) {
6691 sample.size = view.getUint32(offset);
6692 offset += 4;
6693 }
6694 if (sampleFlagsPresent) {
6695 sample.flags = parseSampleFlags_1(data.subarray(offset, offset + 4));
6696 offset += 4;
6697 }
6698 if (sampleCompositionTimeOffsetPresent) {
6699 if (result.version === 1) {
6700 sample.compositionTimeOffset = view.getInt32(offset);
6701 } else {
6702 sample.compositionTimeOffset = view.getUint32(offset);
6703 }
6704 offset += 4;
6705 }
6706 result.samples.push(sample);
6707 }
6708 return result;
6709 };
6710
6711 var parseTrun = trun$1;
6712
6713 var tfhd = function(data) {
6714 var
6715 view = new DataView(data.buffer, data.byteOffset, data.byteLength),
6716 result = {
6717 version: data[0],
6718 flags: new Uint8Array(data.subarray(1, 4)),
6719 trackId: view.getUint32(4)
6720 },
6721 baseDataOffsetPresent = result.flags[2] & 0x01,
6722 sampleDescriptionIndexPresent = result.flags[2] & 0x02,
6723 defaultSampleDurationPresent = result.flags[2] & 0x08,
6724 defaultSampleSizePresent = result.flags[2] & 0x10,
6725 defaultSampleFlagsPresent = result.flags[2] & 0x20,
6726 durationIsEmpty = result.flags[0] & 0x010000,
6727 defaultBaseIsMoof = result.flags[0] & 0x020000,
6728 i;
6729
6730 i = 8;
6731 if (baseDataOffsetPresent) {
6732 i += 4; // truncate top 4 bytes
6733 // FIXME: should we read the full 64 bits?
6734 result.baseDataOffset = view.getUint32(12);
6735 i += 4;
6736 }
6737 if (sampleDescriptionIndexPresent) {
6738 result.sampleDescriptionIndex = view.getUint32(i);
6739 i += 4;
6740 }
6741 if (defaultSampleDurationPresent) {
6742 result.defaultSampleDuration = view.getUint32(i);
6743 i += 4;
6744 }
6745 if (defaultSampleSizePresent) {
6746 result.defaultSampleSize = view.getUint32(i);
6747 i += 4;
6748 }
6749 if (defaultSampleFlagsPresent) {
6750 result.defaultSampleFlags = view.getUint32(i);
6751 }
6752 if (durationIsEmpty) {
6753 result.durationIsEmpty = true;
6754 }
6755 if (!baseDataOffsetPresent && defaultBaseIsMoof) {
6756 result.baseDataOffsetIsMoof = true;
6757 }
6758 return result;
6759 };
6760
6761 var parseTfhd = tfhd;
6762
6763 var discardEmulationPreventionBytes$1 = captionPacketParser.discardEmulationPreventionBytes;
6764 var CaptionStream$1 = captionStream.CaptionStream;
6765
6766
6767
6768
6769
6770 /**
6771 * Maps an offset in the mdat to a sample based on the the size of the samples.
6772 * Assumes that `parseSamples` has been called first.
6773 *
6774 * @param {Number} offset - The offset into the mdat
6775 * @param {Object[]} samples - An array of samples, parsed using `parseSamples`
6776 * @return {?Object} The matching sample, or null if no match was found.
6777 *
6778 * @see ISO-BMFF-12/2015, Section 8.8.8
6779 **/
6780 var mapToSample = function(offset, samples) {
6781 var approximateOffset = offset;
6782
6783 for (var i = 0; i < samples.length; i++) {
6784 var sample = samples[i];
6785
6786 if (approximateOffset < sample.size) {
6787 return sample;
6788 }
6789
6790 approximateOffset -= sample.size;
6791 }
6792
6793 return null;
6794 };
6795
6796 /**
6797 * Finds SEI nal units contained in a Media Data Box.
6798 * Assumes that `parseSamples` has been called first.
6799 *
6800 * @param {Uint8Array} avcStream - The bytes of the mdat
6801 * @param {Object[]} samples - The samples parsed out by `parseSamples`
6802 * @param {Number} trackId - The trackId of this video track
6803 * @return {Object[]} seiNals - the parsed SEI NALUs found.
6804 * The contents of the seiNal should match what is expected by
6805 * CaptionStream.push (nalUnitType, size, data, escapedRBSP, pts, dts)
6806 *
6807 * @see ISO-BMFF-12/2015, Section 8.1.1
6808 * @see Rec. ITU-T H.264, 7.3.2.3.1
6809 **/
6810 var findSeiNals = function(avcStream, samples, trackId) {
6811 var
6812 avcView = new DataView(avcStream.buffer, avcStream.byteOffset, avcStream.byteLength),
6813 result = [],
6814 seiNal,
6815 i,
6816 length,
6817 lastMatchedSample;
6818
6819 for (i = 0; i + 4 < avcStream.length; i += length) {
6820 length = avcView.getUint32(i);
6821 i += 4;
6822
6823 // Bail if this doesn't appear to be an H264 stream
6824 if (length <= 0) {
6825 continue;
6826 }
6827
6828 switch (avcStream[i] & 0x1F) {
6829 case 0x06:
6830 var data = avcStream.subarray(i + 1, i + 1 + length);
6831 var matchingSample = mapToSample(i, samples);
6832
6833 seiNal = {
6834 nalUnitType: 'sei_rbsp',
6835 size: length,
6836 data: data,
6837 escapedRBSP: discardEmulationPreventionBytes$1(data),
6838 trackId: trackId
6839 };
6840
6841 if (matchingSample) {
6842 seiNal.pts = matchingSample.pts;
6843 seiNal.dts = matchingSample.dts;
6844 lastMatchedSample = matchingSample;
6845 } else if (lastMatchedSample) {
6846 // If a matching sample cannot be found, use the last
6847 // sample's values as they should be as close as possible
6848 seiNal.pts = lastMatchedSample.pts;
6849 seiNal.dts = lastMatchedSample.dts;
6850 } else {
6851 // eslint-disable-next-line no-console
6852 console.log("We've encountered a nal unit without data. See mux.js#233.");
6853 break;
6854 }
6855
6856 result.push(seiNal);
6857 break;
6858 }
6859 }
6860
6861 return result;
6862 };
6863
6864 /**
6865 * Parses sample information out of Track Run Boxes and calculates
6866 * the absolute presentation and decode timestamps of each sample.
6867 *
6868 * @param {Array<Uint8Array>} truns - The Trun Run boxes to be parsed
6869 * @param {Number} baseMediaDecodeTime - base media decode time from tfdt
6870 @see ISO-BMFF-12/2015, Section 8.8.12
6871 * @param {Object} tfhd - The parsed Track Fragment Header
6872 * @see inspect.parseTfhd
6873 * @return {Object[]} the parsed samples
6874 *
6875 * @see ISO-BMFF-12/2015, Section 8.8.8
6876 **/
6877 var parseSamples = function(truns, baseMediaDecodeTime, tfhd) {
6878 var currentDts = baseMediaDecodeTime;
6879 var defaultSampleDuration = tfhd.defaultSampleDuration || 0;
6880 var defaultSampleSize = tfhd.defaultSampleSize || 0;
6881 var trackId = tfhd.trackId;
6882 var allSamples = [];
6883
6884 truns.forEach(function(trun) {
6885 // Note: We currently do not parse the sample table as well
6886 // as the trun. It's possible some sources will require this.
6887 // moov > trak > mdia > minf > stbl
6888 var trackRun = parseTrun(trun);
6889 var samples = trackRun.samples;
6890
6891 samples.forEach(function(sample) {
6892 if (sample.duration === undefined) {
6893 sample.duration = defaultSampleDuration;
6894 }
6895 if (sample.size === undefined) {
6896 sample.size = defaultSampleSize;
6897 }
6898 sample.trackId = trackId;
6899 sample.dts = currentDts;
6900 if (sample.compositionTimeOffset === undefined) {
6901 sample.compositionTimeOffset = 0;
6902 }
6903 sample.pts = currentDts + sample.compositionTimeOffset;
6904
6905 currentDts += sample.duration;
6906 });
6907
6908 allSamples = allSamples.concat(samples);
6909 });
6910
6911 return allSamples;
6912 };
6913
6914 /**
6915 * Parses out caption nals from an FMP4 segment's video tracks.
6916 *
6917 * @param {Uint8Array} segment - The bytes of a single segment
6918 * @param {Number} videoTrackId - The trackId of a video track in the segment
6919 * @return {Object.<Number, Object[]>} A mapping of video trackId to
6920 * a list of seiNals found in that track
6921 **/
6922 var parseCaptionNals = function(segment, videoTrackId) {
6923 // To get the samples
6924 var trafs = findBox_1(segment, ['moof', 'traf']);
6925 // To get SEI NAL units
6926 var mdats = findBox_1(segment, ['mdat']);
6927 var captionNals = {};
6928 var mdatTrafPairs = [];
6929
6930 // Pair up each traf with a mdat as moofs and mdats are in pairs
6931 mdats.forEach(function(mdat, index) {
6932 var matchingTraf = trafs[index];
6933 mdatTrafPairs.push({
6934 mdat: mdat,
6935 traf: matchingTraf
6936 });
6937 });
6938
6939 mdatTrafPairs.forEach(function(pair) {
6940 var mdat = pair.mdat;
6941 var traf = pair.traf;
6942 var tfhd = findBox_1(traf, ['tfhd']);
6943 // Exactly 1 tfhd per traf
6944 var headerInfo = parseTfhd(tfhd[0]);
6945 var trackId = headerInfo.trackId;
6946 var tfdt = findBox_1(traf, ['tfdt']);
6947 // Either 0 or 1 tfdt per traf
6948 var baseMediaDecodeTime = (tfdt.length > 0) ? parseTfdt(tfdt[0]).baseMediaDecodeTime : 0;
6949 var truns = findBox_1(traf, ['trun']);
6950 var samples;
6951 var seiNals;
6952
6953 // Only parse video data for the chosen video track
6954 if (videoTrackId === trackId && truns.length > 0) {
6955 samples = parseSamples(truns, baseMediaDecodeTime, headerInfo);
6956
6957 seiNals = findSeiNals(mdat, samples, trackId);
6958
6959 if (!captionNals[trackId]) {
6960 captionNals[trackId] = [];
6961 }
6962
6963 captionNals[trackId] = captionNals[trackId].concat(seiNals);
6964 }
6965 });
6966
6967 return captionNals;
6968 };
6969
6970 /**
6971 * Parses out inband captions from an MP4 container and returns
6972 * caption objects that can be used by WebVTT and the TextTrack API.
6973 * @see https://developer.mozilla.org/en-US/docs/Web/API/VTTCue
6974 * @see https://developer.mozilla.org/en-US/docs/Web/API/TextTrack
6975 * Assumes that `probe.getVideoTrackIds` and `probe.timescale` have been called first
6976 *
6977 * @param {Uint8Array} segment - The fmp4 segment containing embedded captions
6978 * @param {Number} trackId - The id of the video track to parse
6979 * @param {Number} timescale - The timescale for the video track from the init segment
6980 *
6981 * @return {?Object[]} parsedCaptions - A list of captions or null if no video tracks
6982 * @return {Number} parsedCaptions[].startTime - The time to show the caption in seconds
6983 * @return {Number} parsedCaptions[].endTime - The time to stop showing the caption in seconds
6984 * @return {String} parsedCaptions[].text - The visible content of the caption
6985 **/
6986 var parseEmbeddedCaptions = function(segment, trackId, timescale) {
6987 var seiNals;
6988
6989 // the ISO-BMFF spec says that trackId can't be zero, but there's some broken content out there
6990 if (trackId === null) {
6991 return null;
6992 }
6993
6994 seiNals = parseCaptionNals(segment, trackId);
6995
6996 return {
6997 seiNals: seiNals[trackId],
6998 timescale: timescale
6999 };
7000 };
7001
7002 /**
7003 * Converts SEI NALUs into captions that can be used by video.js
7004 **/
7005 var CaptionParser = function() {
7006 var isInitialized = false;
7007 var captionStream;
7008
7009 // Stores segments seen before trackId and timescale are set
7010 var segmentCache;
7011 // Stores video track ID of the track being parsed
7012 var trackId;
7013 // Stores the timescale of the track being parsed
7014 var timescale;
7015 // Stores captions parsed so far
7016 var parsedCaptions;
7017 // Stores whether we are receiving partial data or not
7018 var parsingPartial;
7019
7020 /**
7021 * A method to indicate whether a CaptionParser has been initalized
7022 * @returns {Boolean}
7023 **/
7024 this.isInitialized = function() {
7025 return isInitialized;
7026 };
7027
7028 /**
7029 * Initializes the underlying CaptionStream, SEI NAL parsing
7030 * and management, and caption collection
7031 **/
7032 this.init = function(options) {
7033 captionStream = new CaptionStream$1();
7034 isInitialized = true;
7035 parsingPartial = options ? options.isPartial : false;
7036
7037 // Collect dispatched captions
7038 captionStream.on('data', function(event) {
7039 // Convert to seconds in the source's timescale
7040 event.startTime = event.startPts / timescale;
7041 event.endTime = event.endPts / timescale;
7042
7043 parsedCaptions.captions.push(event);
7044 parsedCaptions.captionStreams[event.stream] = true;
7045 });
7046 };
7047
7048 /**
7049 * Determines if a new video track will be selected
7050 * or if the timescale changed
7051 * @return {Boolean}
7052 **/
7053 this.isNewInit = function(videoTrackIds, timescales) {
7054 if ((videoTrackIds && videoTrackIds.length === 0) ||
7055 (timescales && typeof timescales === 'object' &&
7056 Object.keys(timescales).length === 0)) {
7057 return false;
7058 }
7059
7060 return trackId !== videoTrackIds[0] ||
7061 timescale !== timescales[trackId];
7062 };
7063
7064 /**
7065 * Parses out SEI captions and interacts with underlying
7066 * CaptionStream to return dispatched captions
7067 *
7068 * @param {Uint8Array} segment - The fmp4 segment containing embedded captions
7069 * @param {Number[]} videoTrackIds - A list of video tracks found in the init segment
7070 * @param {Object.<Number, Number>} timescales - The timescales found in the init segment
7071 * @see parseEmbeddedCaptions
7072 * @see m2ts/caption-stream.js
7073 **/
7074 this.parse = function(segment, videoTrackIds, timescales) {
7075 var parsedData;
7076
7077 if (!this.isInitialized()) {
7078 return null;
7079
7080 // This is not likely to be a video segment
7081 } else if (!videoTrackIds || !timescales) {
7082 return null;
7083
7084 } else if (this.isNewInit(videoTrackIds, timescales)) {
7085 // Use the first video track only as there is no
7086 // mechanism to switch to other video tracks
7087 trackId = videoTrackIds[0];
7088 timescale = timescales[trackId];
7089
7090 // If an init segment has not been seen yet, hold onto segment
7091 // data until we have one.
7092 // the ISO-BMFF spec says that trackId can't be zero, but there's some broken content out there
7093 } else if (trackId === null || !timescale) {
7094 segmentCache.push(segment);
7095 return null;
7096 }
7097
7098 // Now that a timescale and trackId is set, parse cached segments
7099 while (segmentCache.length > 0) {
7100 var cachedSegment = segmentCache.shift();
7101
7102 this.parse(cachedSegment, videoTrackIds, timescales);
7103 }
7104
7105 parsedData = parseEmbeddedCaptions(segment, trackId, timescale);
7106
7107 if (parsedData === null || !parsedData.seiNals) {
7108 return null;
7109 }
7110
7111 this.pushNals(parsedData.seiNals);
7112 // Force the parsed captions to be dispatched
7113 this.flushStream();
7114
7115 return parsedCaptions;
7116 };
7117
7118 /**
7119 * Pushes SEI NALUs onto CaptionStream
7120 * @param {Object[]} nals - A list of SEI nals parsed using `parseCaptionNals`
7121 * Assumes that `parseCaptionNals` has been called first
7122 * @see m2ts/caption-stream.js
7123 **/
7124 this.pushNals = function(nals) {
7125 if (!this.isInitialized() || !nals || nals.length === 0) {
7126 return null;
7127 }
7128
7129 nals.forEach(function(nal) {
7130 captionStream.push(nal);
7131 });
7132 };
7133
7134 /**
7135 * Flushes underlying CaptionStream to dispatch processed, displayable captions
7136 * @see m2ts/caption-stream.js
7137 **/
7138 this.flushStream = function() {
7139 if (!this.isInitialized()) {
7140 return null;
7141 }
7142
7143 if (!parsingPartial) {
7144 captionStream.flush();
7145 } else {
7146 captionStream.partialFlush();
7147 }
7148 };
7149
7150 /**
7151 * Reset caption buckets for new data
7152 **/
7153 this.clearParsedCaptions = function() {
7154 parsedCaptions.captions = [];
7155 parsedCaptions.captionStreams = {};
7156 };
7157
7158 /**
7159 * Resets underlying CaptionStream
7160 * @see m2ts/caption-stream.js
7161 **/
7162 this.resetCaptionStream = function() {
7163 if (!this.isInitialized()) {
7164 return null;
7165 }
7166
7167 captionStream.reset();
7168 };
7169
7170 /**
7171 * Convenience method to clear all captions flushed from the
7172 * CaptionStream and still being parsed
7173 * @see m2ts/caption-stream.js
7174 **/
7175 this.clearAllCaptions = function() {
7176 this.clearParsedCaptions();
7177 this.resetCaptionStream();
7178 };
7179
7180 /**
7181 * Reset caption parser
7182 **/
7183 this.reset = function() {
7184 segmentCache = [];
7185 trackId = null;
7186 timescale = null;
7187
7188 if (!parsedCaptions) {
7189 parsedCaptions = {
7190 captions: [],
7191 // CC1, CC2, CC3, CC4
7192 captionStreams: {}
7193 };
7194 } else {
7195 this.clearParsedCaptions();
7196 }
7197
7198 this.resetCaptionStream();
7199 };
7200
7201 this.reset();
7202 };
7203
7204 var captionParser = CaptionParser;
7205
7206 /* global self */
7207
7208 var typeFromStreamString = function typeFromStreamString(streamString) {
7209 if (streamString === 'AudioSegmentStream') {
7210 return 'audio';
7211 }
7212
7213 return streamString === 'VideoSegmentStream' ? 'video' : '';
7214 };
7215 /**
7216 * Re-emits transmuxer events by converting them into messages to the
7217 * world outside the worker.
7218 *
7219 * @param {Object} transmuxer the transmuxer to wire events on
7220 * @private
7221 */
7222
7223
7224 var wireFullTransmuxerEvents = function wireFullTransmuxerEvents(self, transmuxer) {
7225 transmuxer.on('data', function (segment) {
7226 // transfer ownership of the underlying ArrayBuffer
7227 // instead of doing a copy to save memory
7228 // ArrayBuffers are transferable but generic TypedArrays are not
7229 // @link https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers#Passing_data_by_transferring_ownership_(transferable_objects)
7230 var initArray = segment.initSegment;
7231 segment.initSegment = {
7232 data: initArray.buffer,
7233 byteOffset: initArray.byteOffset,
7234 byteLength: initArray.byteLength
7235 };
7236 var typedArray = segment.data;
7237 segment.data = typedArray.buffer;
7238 self.postMessage({
7239 action: 'data',
7240 segment: segment,
7241 byteOffset: typedArray.byteOffset,
7242 byteLength: typedArray.byteLength
7243 }, [segment.data]);
7244 });
7245 transmuxer.on('done', function (data) {
7246 self.postMessage({
7247 action: 'done'
7248 });
7249 });
7250 transmuxer.on('gopInfo', function (gopInfo) {
7251 self.postMessage({
7252 action: 'gopInfo',
7253 gopInfo: gopInfo
7254 });
7255 });
7256 transmuxer.on('videoSegmentTimingInfo', function (timingInfo) {
7257 var videoSegmentTimingInfo = {
7258 start: {
7259 decode: clock_4(timingInfo.start.dts),
7260 presentation: clock_4(timingInfo.start.pts)
7261 },
7262 end: {
7263 decode: clock_4(timingInfo.end.dts),
7264 presentation: clock_4(timingInfo.end.pts)
7265 },
7266 baseMediaDecodeTime: clock_4(timingInfo.baseMediaDecodeTime)
7267 };
7268
7269 if (timingInfo.prependedContentDuration) {
7270 videoSegmentTimingInfo.prependedContentDuration = clock_4(timingInfo.prependedContentDuration);
7271 }
7272
7273 self.postMessage({
7274 action: 'videoSegmentTimingInfo',
7275 videoSegmentTimingInfo: videoSegmentTimingInfo
7276 });
7277 });
7278 transmuxer.on('id3Frame', function (id3Frame) {
7279 self.postMessage({
7280 action: 'id3Frame',
7281 id3Frame: id3Frame
7282 });
7283 });
7284 transmuxer.on('caption', function (caption) {
7285 self.postMessage({
7286 action: 'caption',
7287 caption: caption
7288 });
7289 });
7290 transmuxer.on('trackinfo', function (trackInfo) {
7291 self.postMessage({
7292 action: 'trackinfo',
7293 trackInfo: trackInfo
7294 });
7295 });
7296 transmuxer.on('audioTimingInfo', function (audioTimingInfo) {
7297 // convert to video TS since we prioritize video time over audio
7298 self.postMessage({
7299 action: 'audioTimingInfo',
7300 audioTimingInfo: {
7301 start: clock_4(audioTimingInfo.start),
7302 end: clock_4(audioTimingInfo.end)
7303 }
7304 });
7305 });
7306 transmuxer.on('videoTimingInfo', function (videoTimingInfo) {
7307 self.postMessage({
7308 action: 'videoTimingInfo',
7309 videoTimingInfo: {
7310 start: clock_4(videoTimingInfo.start),
7311 end: clock_4(videoTimingInfo.end)
7312 }
7313 });
7314 });
7315 };
7316
7317 var wirePartialTransmuxerEvents = function wirePartialTransmuxerEvents(self, transmuxer) {
7318 transmuxer.on('data', function (event) {
7319 // transfer ownership of the underlying ArrayBuffer
7320 // instead of doing a copy to save memory
7321 // ArrayBuffers are transferable but generic TypedArrays are not
7322 // @link https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers#Passing_data_by_transferring_ownership_(transferable_objects)
7323 var initSegment = {
7324 data: event.data.track.initSegment.buffer,
7325 byteOffset: event.data.track.initSegment.byteOffset,
7326 byteLength: event.data.track.initSegment.byteLength
7327 };
7328 var boxes = {
7329 data: event.data.boxes.buffer,
7330 byteOffset: event.data.boxes.byteOffset,
7331 byteLength: event.data.boxes.byteLength
7332 };
7333 var segment = {
7334 boxes: boxes,
7335 initSegment: initSegment,
7336 type: event.type,
7337 sequence: event.data.sequence
7338 };
7339
7340 if (typeof event.data.videoFrameDts !== 'undefined') {
7341 segment.videoFrameDtsTime = clock_4(event.data.videoFrameDts);
7342 }
7343
7344 if (typeof event.data.videoFramePts !== 'undefined') {
7345 segment.videoFramePtsTime = clock_4(event.data.videoFramePts);
7346 }
7347
7348 self.postMessage({
7349 action: 'data',
7350 segment: segment
7351 }, [segment.boxes.data, segment.initSegment.data]);
7352 });
7353 transmuxer.on('id3Frame', function (id3Frame) {
7354 self.postMessage({
7355 action: 'id3Frame',
7356 id3Frame: id3Frame
7357 });
7358 });
7359 transmuxer.on('caption', function (caption) {
7360 self.postMessage({
7361 action: 'caption',
7362 caption: caption
7363 });
7364 });
7365 transmuxer.on('done', function (data) {
7366 self.postMessage({
7367 action: 'done',
7368 type: typeFromStreamString(data)
7369 });
7370 });
7371 transmuxer.on('partialdone', function (data) {
7372 self.postMessage({
7373 action: 'partialdone',
7374 type: typeFromStreamString(data)
7375 });
7376 });
7377 transmuxer.on('endedsegment', function (data) {
7378 self.postMessage({
7379 action: 'endedSegment',
7380 type: typeFromStreamString(data)
7381 });
7382 });
7383 transmuxer.on('trackinfo', function (trackInfo) {
7384 self.postMessage({
7385 action: 'trackinfo',
7386 trackInfo: trackInfo
7387 });
7388 });
7389 transmuxer.on('audioTimingInfo', function (audioTimingInfo) {
7390 // This can happen if flush is called when no
7391 // audio has been processed. This should be an
7392 // unusual case, but if it does occur should not
7393 // result in valid data being returned
7394 if (audioTimingInfo.start === null) {
7395 self.postMessage({
7396 action: 'audioTimingInfo',
7397 audioTimingInfo: audioTimingInfo
7398 });
7399 return;
7400 } // convert to video TS since we prioritize video time over audio
7401
7402
7403 var timingInfoInSeconds = {
7404 start: clock_4(audioTimingInfo.start)
7405 };
7406
7407 if (audioTimingInfo.end) {
7408 timingInfoInSeconds.end = clock_4(audioTimingInfo.end);
7409 }
7410
7411 self.postMessage({
7412 action: 'audioTimingInfo',
7413 audioTimingInfo: timingInfoInSeconds
7414 });
7415 });
7416 transmuxer.on('videoTimingInfo', function (videoTimingInfo) {
7417 var timingInfoInSeconds = {
7418 start: clock_4(videoTimingInfo.start)
7419 };
7420
7421 if (videoTimingInfo.end) {
7422 timingInfoInSeconds.end = clock_4(videoTimingInfo.end);
7423 }
7424
7425 self.postMessage({
7426 action: 'videoTimingInfo',
7427 videoTimingInfo: timingInfoInSeconds
7428 });
7429 });
7430 };
7431 /**
7432 * All incoming messages route through this hash. If no function exists
7433 * to handle an incoming message, then we ignore the message.
7434 *
7435 * @class MessageHandlers
7436 * @param {Object} options the options to initialize with
7437 */
7438
7439
7440 var MessageHandlers = /*#__PURE__*/function () {
7441 function MessageHandlers(self, options) {
7442 this.options = options || {};
7443 this.self = self;
7444 this.init();
7445 }
7446 /**
7447 * initialize our web worker and wire all the events.
7448 */
7449
7450
7451 var _proto = MessageHandlers.prototype;
7452
7453 _proto.init = function init() {
7454 if (this.transmuxer) {
7455 this.transmuxer.dispose();
7456 }
7457
7458 this.transmuxer = this.options.handlePartialData ? new transmuxer$1(this.options) : new transmuxer_1(this.options);
7459
7460 if (this.options.handlePartialData) {
7461 wirePartialTransmuxerEvents(this.self, this.transmuxer);
7462 } else {
7463 wireFullTransmuxerEvents(this.self, this.transmuxer);
7464 }
7465 };
7466
7467 _proto.pushMp4Captions = function pushMp4Captions(data) {
7468 if (!this.captionParser) {
7469 this.captionParser = new captionParser();
7470 this.captionParser.init();
7471 }
7472
7473 var segment = new Uint8Array(data.data, data.byteOffset, data.byteLength);
7474 var parsed = this.captionParser.parse(segment, data.trackIds, data.timescales);
7475 this.self.postMessage({
7476 action: 'mp4Captions',
7477 captions: parsed && parsed.captions || [],
7478 data: segment.buffer
7479 }, [segment.buffer]);
7480 };
7481
7482 _proto.clearAllMp4Captions = function clearAllMp4Captions() {
7483 if (this.captionParser) {
7484 this.captionParser.clearAllCaptions();
7485 }
7486 };
7487
7488 _proto.clearParsedMp4Captions = function clearParsedMp4Captions() {
7489 if (this.captionParser) {
7490 this.captionParser.clearParsedCaptions();
7491 }
7492 }
7493 /**
7494 * Adds data (a ts segment) to the start of the transmuxer pipeline for
7495 * processing.
7496 *
7497 * @param {ArrayBuffer} data data to push into the muxer
7498 */
7499 ;
7500
7501 _proto.push = function push(data) {
7502 // Cast array buffer to correct type for transmuxer
7503 var segment = new Uint8Array(data.data, data.byteOffset, data.byteLength);
7504 this.transmuxer.push(segment);
7505 }
7506 /**
7507 * Recreate the transmuxer so that the next segment added via `push`
7508 * start with a fresh transmuxer.
7509 */
7510 ;
7511
7512 _proto.reset = function reset() {
7513 this.transmuxer.reset();
7514 }
7515 /**
7516 * Set the value that will be used as the `baseMediaDecodeTime` time for the
7517 * next segment pushed in. Subsequent segments will have their `baseMediaDecodeTime`
7518 * set relative to the first based on the PTS values.
7519 *
7520 * @param {Object} data used to set the timestamp offset in the muxer
7521 */
7522 ;
7523
7524 _proto.setTimestampOffset = function setTimestampOffset(data) {
7525 var timestampOffset = data.timestampOffset || 0;
7526 this.transmuxer.setBaseMediaDecodeTime(Math.round(clock_2(timestampOffset)));
7527 };
7528
7529 _proto.setAudioAppendStart = function setAudioAppendStart(data) {
7530 this.transmuxer.setAudioAppendStart(Math.ceil(clock_2(data.appendStart)));
7531 };
7532
7533 _proto.setRemux = function setRemux(data) {
7534 this.transmuxer.setRemux(data.remux);
7535 }
7536 /**
7537 * Forces the pipeline to finish processing the last segment and emit it's
7538 * results.
7539 *
7540 * @param {Object} data event data, not really used
7541 */
7542 ;
7543
7544 _proto.flush = function flush(data) {
7545 this.transmuxer.flush(); // transmuxed done action is fired after both audio/video pipelines are flushed
7546
7547 self.postMessage({
7548 action: 'done',
7549 type: 'transmuxed'
7550 });
7551 };
7552
7553 _proto.partialFlush = function partialFlush(data) {
7554 this.transmuxer.partialFlush(); // transmuxed partialdone action is fired after both audio/video pipelines are flushed
7555
7556 self.postMessage({
7557 action: 'partialdone',
7558 type: 'transmuxed'
7559 });
7560 };
7561
7562 _proto.endTimeline = function endTimeline() {
7563 this.transmuxer.endTimeline(); // transmuxed endedtimeline action is fired after both audio/video pipelines end their
7564 // timelines
7565
7566 self.postMessage({
7567 action: 'endedtimeline',
7568 type: 'transmuxed'
7569 });
7570 };
7571
7572 _proto.alignGopsWith = function alignGopsWith(data) {
7573 this.transmuxer.alignGopsWith(data.gopsToAlignWith.slice());
7574 };
7575
7576 return MessageHandlers;
7577 }();
7578 /**
7579 * Our web worker interface so that things can talk to mux.js
7580 * that will be running in a web worker. the scope is passed to this by
7581 * webworkify.
7582 *
7583 * @param {Object} self the scope for the web worker
7584 */
7585
7586
7587 var TransmuxerWorker = function TransmuxerWorker(self) {
7588 self.onmessage = function (event) {
7589 if (event.data.action === 'init' && event.data.options) {
7590 this.messageHandlers = new MessageHandlers(self, event.data.options);
7591 return;
7592 }
7593
7594 if (!this.messageHandlers) {
7595 this.messageHandlers = new MessageHandlers(self);
7596 }
7597
7598 if (event.data && event.data.action && event.data.action !== 'init') {
7599 if (this.messageHandlers[event.data.action]) {
7600 this.messageHandlers[event.data.action](event.data);
7601 }
7602 }
7603 };
7604 };
7605
7606 var transmuxerWorker = new TransmuxerWorker(self);
7607
7608 return transmuxerWorker;
7609
7610}());