virtual-source-buffer.js.html 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="utf-8">
  5. <title>JSDoc: Source: virtual-source-buffer.js</title>
  6. <script src="scripts/prettify/prettify.js"> </script>
  7. <script src="scripts/prettify/lang-css.js"> </script>
  8. <!--[if lt IE 9]>
  9. <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
  10. <![endif]-->
  11. <link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
  12. <link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
  13. </head>
  14. <body>
  15. <div id="main">
  16. <h1 class="page-title">Source: virtual-source-buffer.js</h1>
  17. <section>
  18. <article>
  19. <pre class="prettyprint source linenums"><code>/**
  20. * @file virtual-source-buffer.js
  21. */
  22. import videojs from 'video.js';
  23. import createTextTracksIfNecessary from './create-text-tracks-if-necessary';
  24. import removeCuesFromTrack from './remove-cues-from-track';
  25. import {addTextTrackData} from './add-text-track-data';
  26. import work from 'webworkify';
  27. import transmuxWorker from './transmuxer-worker';
  28. import {isAudioCodec, isVideoCodec} from './codec-utils';
  29. // We create a wrapper around the SourceBuffer so that we can manage the
  30. // state of the `updating` property manually. We have to do this because
  31. // Firefox changes `updating` to false long before triggering `updateend`
  32. // events and that was causing strange problems in videojs-contrib-hls
  33. const makeWrappedSourceBuffer = function(mediaSource, mimeType) {
  34. const sourceBuffer = mediaSource.addSourceBuffer(mimeType);
  35. const wrapper = Object.create(null);
  36. wrapper.updating = false;
  37. wrapper.realBuffer_ = sourceBuffer;
  38. for (let key in sourceBuffer) {
  39. if (typeof sourceBuffer[key] === 'function') {
  40. wrapper[key] = (...params) => sourceBuffer[key](...params);
  41. } else if (typeof wrapper[key] === 'undefined') {
  42. Object.defineProperty(wrapper, key, {
  43. get: () => sourceBuffer[key],
  44. set: (v) => sourceBuffer[key] = v
  45. });
  46. }
  47. }
  48. return wrapper;
  49. };
  50. /**
  51. * Returns a list of gops in the buffer that have a pts value of 3 seconds or more in
  52. * front of current time.
  53. *
  54. * @param {Array} buffer
  55. * The current buffer of gop information
  56. * @param {Player} player
  57. * The player instance
  58. * @param {Double} mapping
  59. * Offset to map display time to stream presentation time
  60. * @return {Array}
  61. * List of gops considered safe to append over
  62. */
  63. export const gopsSafeToAlignWith = (buffer, player, mapping) => {
  64. if (!player || !buffer.length) {
  65. return [];
  66. }
  67. // pts value for current time + 3 seconds to give a bit more wiggle room
  68. const currentTimePts = Math.ceil((player.currentTime() - mapping + 3) * 90000);
  69. let i;
  70. for (i = 0; i &lt; buffer.length; i++) {
  71. if (buffer[i].pts > currentTimePts) {
  72. break;
  73. }
  74. }
  75. return buffer.slice(i);
  76. };
  77. /**
  78. * Appends gop information (timing and byteLength) received by the transmuxer for the
  79. * gops appended in the last call to appendBuffer
  80. *
  81. * @param {Array} buffer
  82. * The current buffer of gop information
  83. * @param {Array} gops
  84. * List of new gop information
  85. * @param {boolean} replace
  86. * If true, replace the buffer with the new gop information. If false, append the
  87. * new gop information to the buffer in the right location of time.
  88. * @return {Array}
  89. * Updated list of gop information
  90. */
  91. export const updateGopBuffer = (buffer, gops, replace) => {
  92. if (!gops.length) {
  93. return buffer;
  94. }
  95. if (replace) {
  96. // If we are in safe append mode, then completely overwrite the gop buffer
  97. // with the most recent appeneded data. This will make sure that when appending
  98. // future segments, we only try to align with gops that are both ahead of current
  99. // time and in the last segment appended.
  100. return gops.slice();
  101. }
  102. const start = gops[0].pts;
  103. let i = 0;
  104. for (i; i &lt; buffer.length; i++) {
  105. if (buffer[i].pts >= start) {
  106. break;
  107. }
  108. }
  109. return buffer.slice(0, i).concat(gops);
  110. };
  111. /**
  112. * Removes gop information in buffer that overlaps with provided start and end
  113. *
  114. * @param {Array} buffer
  115. * The current buffer of gop information
  116. * @param {Double} start
  117. * position to start the remove at
  118. * @param {Double} end
  119. * position to end the remove at
  120. * @param {Double} mapping
  121. * Offset to map display time to stream presentation time
  122. */
  123. export const removeGopBuffer = (buffer, start, end, mapping) => {
  124. const startPts = Math.ceil((start - mapping) * 90000);
  125. const endPts = Math.ceil((end - mapping) * 90000);
  126. const updatedBuffer = buffer.slice();
  127. let i = buffer.length;
  128. while (i--) {
  129. if (buffer[i].pts &lt;= endPts) {
  130. break;
  131. }
  132. }
  133. if (i === -1) {
  134. // no removal because end of remove range is before start of buffer
  135. return updatedBuffer;
  136. }
  137. let j = i + 1;
  138. while (j--) {
  139. if (buffer[j].pts &lt;= startPts) {
  140. break;
  141. }
  142. }
  143. // clamp remove range start to 0 index
  144. j = Math.max(j, 0);
  145. updatedBuffer.splice(j, i - j + 1);
  146. return updatedBuffer;
  147. };
  148. /**
  149. * VirtualSourceBuffers exist so that we can transmux non native formats
  150. * into a native format, but keep the same api as a native source buffer.
  151. * It creates a transmuxer, that works in its own thread (a web worker) and
  152. * that transmuxer muxes the data into a native format. VirtualSourceBuffer will
  153. * then send all of that data to the naive sourcebuffer so that it is
  154. * indestinguishable from a natively supported format.
  155. *
  156. * @param {HtmlMediaSource} mediaSource the parent mediaSource
  157. * @param {Array} codecs array of codecs that we will be dealing with
  158. * @class VirtualSourceBuffer
  159. * @extends video.js.EventTarget
  160. */
  161. export default class VirtualSourceBuffer extends videojs.EventTarget {
  162. constructor(mediaSource, codecs) {
  163. super(videojs.EventTarget);
  164. this.timestampOffset_ = 0;
  165. this.pendingBuffers_ = [];
  166. this.bufferUpdating_ = false;
  167. this.mediaSource_ = mediaSource;
  168. this.codecs_ = codecs;
  169. this.audioCodec_ = null;
  170. this.videoCodec_ = null;
  171. this.audioDisabled_ = false;
  172. this.appendAudioInitSegment_ = true;
  173. this.gopBuffer_ = [];
  174. this.timeMapping_ = 0;
  175. this.safeAppend_ = videojs.browser.IE_VERSION >= 11;
  176. let options = {
  177. remux: false,
  178. alignGopsAtEnd: this.safeAppend_
  179. };
  180. this.codecs_.forEach((codec) => {
  181. if (isAudioCodec(codec)) {
  182. this.audioCodec_ = codec;
  183. } else if (isVideoCodec(codec)) {
  184. this.videoCodec_ = codec;
  185. }
  186. });
  187. // append muxed segments to their respective native buffers as
  188. // soon as they are available
  189. this.transmuxer_ = work(transmuxWorker);
  190. this.transmuxer_.postMessage({action: 'init', options });
  191. this.transmuxer_.onmessage = (event) => {
  192. if (event.data.action === 'data') {
  193. return this.data_(event);
  194. }
  195. if (event.data.action === 'done') {
  196. return this.done_(event);
  197. }
  198. if (event.data.action === 'gopInfo') {
  199. return this.appendGopInfo_(event);
  200. }
  201. };
  202. // this timestampOffset is a property with the side-effect of resetting
  203. // baseMediaDecodeTime in the transmuxer on the setter
  204. Object.defineProperty(this, 'timestampOffset', {
  205. get() {
  206. return this.timestampOffset_;
  207. },
  208. set(val) {
  209. if (typeof val === 'number' &amp;&amp; val >= 0) {
  210. this.timestampOffset_ = val;
  211. this.appendAudioInitSegment_ = true;
  212. // reset gop buffer on timestampoffset as this signals a change in timeline
  213. this.gopBuffer_.length = 0;
  214. this.timeMapping_ = 0;
  215. // We have to tell the transmuxer to set the baseMediaDecodeTime to
  216. // the desired timestampOffset for the next segment
  217. this.transmuxer_.postMessage({
  218. action: 'setTimestampOffset',
  219. timestampOffset: val
  220. });
  221. }
  222. }
  223. });
  224. // setting the append window affects both source buffers
  225. Object.defineProperty(this, 'appendWindowStart', {
  226. get() {
  227. return (this.videoBuffer_ || this.audioBuffer_).appendWindowStart;
  228. },
  229. set(start) {
  230. if (this.videoBuffer_) {
  231. this.videoBuffer_.appendWindowStart = start;
  232. }
  233. if (this.audioBuffer_) {
  234. this.audioBuffer_.appendWindowStart = start;
  235. }
  236. }
  237. });
  238. // this buffer is "updating" if either of its native buffers are
  239. Object.defineProperty(this, 'updating', {
  240. get() {
  241. return !!(this.bufferUpdating_ ||
  242. (!this.audioDisabled_ &amp;&amp; this.audioBuffer_ &amp;&amp; this.audioBuffer_.updating) ||
  243. (this.videoBuffer_ &amp;&amp; this.videoBuffer_.updating));
  244. }
  245. });
  246. // the buffered property is the intersection of the buffered
  247. // ranges of the native source buffers
  248. Object.defineProperty(this, 'buffered', {
  249. get() {
  250. let start = null;
  251. let end = null;
  252. let arity = 0;
  253. let extents = [];
  254. let ranges = [];
  255. // neither buffer has been created yet
  256. if (!this.videoBuffer_ &amp;&amp; !this.audioBuffer_) {
  257. return videojs.createTimeRange();
  258. }
  259. // only one buffer is configured
  260. if (!this.videoBuffer_) {
  261. return this.audioBuffer_.buffered;
  262. }
  263. if (!this.audioBuffer_) {
  264. return this.videoBuffer_.buffered;
  265. }
  266. // both buffers are configured
  267. if (this.audioDisabled_) {
  268. return this.videoBuffer_.buffered;
  269. }
  270. // both buffers are empty
  271. if (this.videoBuffer_.buffered.length === 0 &amp;&amp;
  272. this.audioBuffer_.buffered.length === 0) {
  273. return videojs.createTimeRange();
  274. }
  275. // Handle the case where we have both buffers and create an
  276. // intersection of the two
  277. let videoBuffered = this.videoBuffer_.buffered;
  278. let audioBuffered = this.audioBuffer_.buffered;
  279. let count = videoBuffered.length;
  280. // A) Gather up all start and end times
  281. while (count--) {
  282. extents.push({time: videoBuffered.start(count), type: 'start'});
  283. extents.push({time: videoBuffered.end(count), type: 'end'});
  284. }
  285. count = audioBuffered.length;
  286. while (count--) {
  287. extents.push({time: audioBuffered.start(count), type: 'start'});
  288. extents.push({time: audioBuffered.end(count), type: 'end'});
  289. }
  290. // B) Sort them by time
  291. extents.sort(function(a, b) {
  292. return a.time - b.time;
  293. });
  294. // C) Go along one by one incrementing arity for start and decrementing
  295. // arity for ends
  296. for (count = 0; count &lt; extents.length; count++) {
  297. if (extents[count].type === 'start') {
  298. arity++;
  299. // D) If arity is ever incremented to 2 we are entering an
  300. // overlapping range
  301. if (arity === 2) {
  302. start = extents[count].time;
  303. }
  304. } else if (extents[count].type === 'end') {
  305. arity--;
  306. // E) If arity is ever decremented to 1 we leaving an
  307. // overlapping range
  308. if (arity === 1) {
  309. end = extents[count].time;
  310. }
  311. }
  312. // F) Record overlapping ranges
  313. if (start !== null &amp;&amp; end !== null) {
  314. ranges.push([start, end]);
  315. start = null;
  316. end = null;
  317. }
  318. }
  319. return videojs.createTimeRanges(ranges);
  320. }
  321. });
  322. }
  323. /**
  324. * When we get a data event from the transmuxer
  325. * we call this function and handle the data that
  326. * was sent to us
  327. *
  328. * @private
  329. * @param {Event} event the data event from the transmuxer
  330. */
  331. data_(event) {
  332. let segment = event.data.segment;
  333. // Cast ArrayBuffer to TypedArray
  334. segment.data = new Uint8Array(
  335. segment.data,
  336. event.data.byteOffset,
  337. event.data.byteLength
  338. );
  339. segment.initSegment = new Uint8Array(
  340. segment.initSegment.data,
  341. segment.initSegment.byteOffset,
  342. segment.initSegment.byteLength
  343. );
  344. createTextTracksIfNecessary(this, this.mediaSource_, segment);
  345. // Add the segments to the pendingBuffers array
  346. this.pendingBuffers_.push(segment);
  347. return;
  348. }
  349. /**
  350. * When we get a done event from the transmuxer
  351. * we call this function and we process all
  352. * of the pending data that we have been saving in the
  353. * data_ function
  354. *
  355. * @private
  356. * @param {Event} event the done event from the transmuxer
  357. */
  358. done_(event) {
  359. // Don't process and append data if the mediaSource is closed
  360. if (this.mediaSource_.readyState === 'closed') {
  361. this.pendingBuffers_.length = 0;
  362. return;
  363. }
  364. // All buffers should have been flushed from the muxer
  365. // start processing anything we have received
  366. this.processPendingSegments_();
  367. return;
  368. }
  369. /**
  370. * Create our internal native audio/video source buffers and add
  371. * event handlers to them with the following conditions:
  372. * 1. they do not already exist on the mediaSource
  373. * 2. this VSB has a codec for them
  374. *
  375. * @private
  376. */
  377. createRealSourceBuffers_() {
  378. let types = ['audio', 'video'];
  379. types.forEach((type) => {
  380. // Don't create a SourceBuffer of this type if we don't have a
  381. // codec for it
  382. if (!this[`${type}Codec_`]) {
  383. return;
  384. }
  385. // Do nothing if a SourceBuffer of this type already exists
  386. if (this[`${type}Buffer_`]) {
  387. return;
  388. }
  389. let buffer = null;
  390. // If the mediasource already has a SourceBuffer for the codec
  391. // use that
  392. if (this.mediaSource_[`${type}Buffer_`]) {
  393. buffer = this.mediaSource_[`${type}Buffer_`];
  394. // In multiple audio track cases, the audio source buffer is disabled
  395. // on the main VirtualSourceBuffer by the HTMLMediaSource much earlier
  396. // than createRealSourceBuffers_ is called to create the second
  397. // VirtualSourceBuffer because that happens as a side-effect of
  398. // videojs-contrib-hls starting the audioSegmentLoader. As a result,
  399. // the audioBuffer is essentially "ownerless" and no one will toggle
  400. // the `updating` state back to false once the `updateend` event is received
  401. //
  402. // Setting `updating` to false manually will work around this
  403. // situation and allow work to continue
  404. buffer.updating = false;
  405. } else {
  406. const codecProperty = `${type}Codec_`;
  407. const mimeType = `${type}/mp4;codecs="${this[codecProperty]}"`;
  408. buffer = makeWrappedSourceBuffer(this.mediaSource_.nativeMediaSource_, mimeType);
  409. this.mediaSource_[`${type}Buffer_`] = buffer;
  410. }
  411. this[`${type}Buffer_`] = buffer;
  412. // Wire up the events to the SourceBuffer
  413. ['update', 'updatestart', 'updateend'].forEach((event) => {
  414. buffer.addEventListener(event, () => {
  415. // if audio is disabled
  416. if (type === 'audio' &amp;&amp; this.audioDisabled_) {
  417. return;
  418. }
  419. if (event === 'updateend') {
  420. this[`${type}Buffer_`].updating = false;
  421. }
  422. let shouldTrigger = types.every((t) => {
  423. // skip checking audio's updating status if audio
  424. // is not enabled
  425. if (t === 'audio' &amp;&amp; this.audioDisabled_) {
  426. return true;
  427. }
  428. // if the other type if updating we don't trigger
  429. if (type !== t &amp;&amp;
  430. this[`${t}Buffer_`] &amp;&amp;
  431. this[`${t}Buffer_`].updating) {
  432. return false;
  433. }
  434. return true;
  435. });
  436. if (shouldTrigger) {
  437. return this.trigger(event);
  438. }
  439. });
  440. });
  441. });
  442. }
  443. /**
  444. * Emulate the native mediasource function, but our function will
  445. * send all of the proposed segments to the transmuxer so that we
  446. * can transmux them before we append them to our internal
  447. * native source buffers in the correct format.
  448. *
  449. * @link https://developer.mozilla.org/en-US/docs/Web/API/SourceBuffer/appendBuffer
  450. * @param {Uint8Array} segment the segment to append to the buffer
  451. */
  452. appendBuffer(segment) {
  453. // Start the internal "updating" state
  454. this.bufferUpdating_ = true;
  455. if (this.audioBuffer_ &amp;&amp; this.audioBuffer_.buffered.length) {
  456. let audioBuffered = this.audioBuffer_.buffered;
  457. this.transmuxer_.postMessage({
  458. action: 'setAudioAppendStart',
  459. appendStart: audioBuffered.end(audioBuffered.length - 1)
  460. });
  461. }
  462. if (this.videoBuffer_) {
  463. this.transmuxer_.postMessage({
  464. action: 'alignGopsWith',
  465. gopsToAlignWith: gopsSafeToAlignWith(this.gopBuffer_,
  466. this.mediaSource_.player_,
  467. this.timeMapping_)
  468. });
  469. }
  470. this.transmuxer_.postMessage({
  471. action: 'push',
  472. // Send the typed-array of data as an ArrayBuffer so that
  473. // it can be sent as a "Transferable" and avoid the costly
  474. // memory copy
  475. data: segment.buffer,
  476. // To recreate the original typed-array, we need information
  477. // about what portion of the ArrayBuffer it was a view into
  478. byteOffset: segment.byteOffset,
  479. byteLength: segment.byteLength
  480. },
  481. [segment.buffer]);
  482. this.transmuxer_.postMessage({action: 'flush'});
  483. }
  484. /**
  485. * Appends gop information (timing and byteLength) received by the transmuxer for the
  486. * gops appended in the last call to appendBuffer
  487. *
  488. * @param {Event} event
  489. * The gopInfo event from the transmuxer
  490. * @param {Array} event.data.gopInfo
  491. * List of gop info to append
  492. */
  493. appendGopInfo_(event) {
  494. this.gopBuffer_ = updateGopBuffer(this.gopBuffer_,
  495. event.data.gopInfo,
  496. this.safeAppend_);
  497. }
  498. /**
  499. * Emulate the native mediasource function and remove parts
  500. * of the buffer from any of our internal buffers that exist
  501. *
  502. * @link https://developer.mozilla.org/en-US/docs/Web/API/SourceBuffer/remove
  503. * @param {Double} start position to start the remove at
  504. * @param {Double} end position to end the remove at
  505. */
  506. remove(start, end) {
  507. if (this.videoBuffer_) {
  508. this.videoBuffer_.updating = true;
  509. this.videoBuffer_.remove(start, end);
  510. this.gopBuffer_ = removeGopBuffer(this.gopBuffer_, start, end, this.timeMapping_);
  511. }
  512. if (!this.audioDisabled_ &amp;&amp; this.audioBuffer_) {
  513. this.audioBuffer_.updating = true;
  514. this.audioBuffer_.remove(start, end);
  515. }
  516. // Remove Metadata Cues (id3)
  517. removeCuesFromTrack(start, end, this.metadataTrack_);
  518. // Remove Any Captions
  519. if (this.inbandTextTracks_) {
  520. for (let track in this.inbandTextTracks_) {
  521. removeCuesFromTrack(start, end, this.inbandTextTracks_[track]);
  522. }
  523. }
  524. }
  525. /**
  526. * Process any segments that the muxer has output
  527. * Concatenate segments together based on type and append them into
  528. * their respective sourceBuffers
  529. *
  530. * @private
  531. */
  532. processPendingSegments_() {
  533. let sortedSegments = {
  534. video: {
  535. segments: [],
  536. bytes: 0
  537. },
  538. audio: {
  539. segments: [],
  540. bytes: 0
  541. },
  542. captions: [],
  543. metadata: []
  544. };
  545. // Sort segments into separate video/audio arrays and
  546. // keep track of their total byte lengths
  547. sortedSegments = this.pendingBuffers_.reduce(function(segmentObj, segment) {
  548. let type = segment.type;
  549. let data = segment.data;
  550. let initSegment = segment.initSegment;
  551. segmentObj[type].segments.push(data);
  552. segmentObj[type].bytes += data.byteLength;
  553. segmentObj[type].initSegment = initSegment;
  554. // Gather any captions into a single array
  555. if (segment.captions) {
  556. segmentObj.captions = segmentObj.captions.concat(segment.captions);
  557. }
  558. if (segment.info) {
  559. segmentObj[type].info = segment.info;
  560. }
  561. // Gather any metadata into a single array
  562. if (segment.metadata) {
  563. segmentObj.metadata = segmentObj.metadata.concat(segment.metadata);
  564. }
  565. return segmentObj;
  566. }, sortedSegments);
  567. // Create the real source buffers if they don't exist by now since we
  568. // finally are sure what tracks are contained in the source
  569. if (!this.videoBuffer_ &amp;&amp; !this.audioBuffer_) {
  570. // Remove any codecs that may have been specified by default but
  571. // are no longer applicable now
  572. if (sortedSegments.video.bytes === 0) {
  573. this.videoCodec_ = null;
  574. }
  575. if (sortedSegments.audio.bytes === 0) {
  576. this.audioCodec_ = null;
  577. }
  578. this.createRealSourceBuffers_();
  579. }
  580. if (sortedSegments.audio.info) {
  581. this.mediaSource_.trigger({type: 'audioinfo', info: sortedSegments.audio.info});
  582. }
  583. if (sortedSegments.video.info) {
  584. this.mediaSource_.trigger({type: 'videoinfo', info: sortedSegments.video.info});
  585. }
  586. if (this.appendAudioInitSegment_) {
  587. if (!this.audioDisabled_ &amp;&amp; this.audioBuffer_) {
  588. sortedSegments.audio.segments.unshift(sortedSegments.audio.initSegment);
  589. sortedSegments.audio.bytes += sortedSegments.audio.initSegment.byteLength;
  590. }
  591. this.appendAudioInitSegment_ = false;
  592. }
  593. let triggerUpdateend = false;
  594. // Merge multiple video and audio segments into one and append
  595. if (this.videoBuffer_ &amp;&amp; sortedSegments.video.bytes) {
  596. sortedSegments.video.segments.unshift(sortedSegments.video.initSegment);
  597. sortedSegments.video.bytes += sortedSegments.video.initSegment.byteLength;
  598. this.concatAndAppendSegments_(sortedSegments.video, this.videoBuffer_);
  599. // TODO: are video tracks the only ones with text tracks?
  600. addTextTrackData(this, sortedSegments.captions, sortedSegments.metadata);
  601. } else if (this.videoBuffer_ &amp;&amp; (this.audioDisabled_ || !this.audioBuffer_)) {
  602. // The transmuxer did not return any bytes of video, meaning it was all trimmed
  603. // for gop alignment. Since we have a video buffer and audio is disabled, updateend
  604. // will never be triggered by this source buffer, which will cause contrib-hls
  605. // to be stuck forever waiting for updateend. If audio is not disabled, updateend
  606. // will be triggered by the audio buffer, which will be sent upwards since the video
  607. // buffer will not be in an updating state.
  608. triggerUpdateend = true;
  609. }
  610. if (!this.audioDisabled_ &amp;&amp; this.audioBuffer_) {
  611. this.concatAndAppendSegments_(sortedSegments.audio, this.audioBuffer_);
  612. }
  613. this.pendingBuffers_.length = 0;
  614. if (triggerUpdateend) {
  615. this.trigger('updateend');
  616. }
  617. // We are no longer in the internal "updating" state
  618. this.bufferUpdating_ = false;
  619. }
  620. /**
  621. * Combine all segments into a single Uint8Array and then append them
  622. * to the destination buffer
  623. *
  624. * @param {Object} segmentObj
  625. * @param {SourceBuffer} destinationBuffer native source buffer to append data to
  626. * @private
  627. */
  628. concatAndAppendSegments_(segmentObj, destinationBuffer) {
  629. let offset = 0;
  630. let tempBuffer;
  631. if (segmentObj.bytes) {
  632. tempBuffer = new Uint8Array(segmentObj.bytes);
  633. // Combine the individual segments into one large typed-array
  634. segmentObj.segments.forEach(function(segment) {
  635. tempBuffer.set(segment, offset);
  636. offset += segment.byteLength;
  637. });
  638. try {
  639. destinationBuffer.updating = true;
  640. destinationBuffer.appendBuffer(tempBuffer);
  641. } catch (error) {
  642. if (this.mediaSource_.player_) {
  643. this.mediaSource_.player_.error({
  644. code: -3,
  645. type: 'APPEND_BUFFER_ERR',
  646. message: error.message,
  647. originalError: error
  648. });
  649. }
  650. }
  651. }
  652. }
  653. /**
  654. * Emulate the native mediasource function. abort any soureBuffer
  655. * actions and throw out any un-appended data.
  656. *
  657. * @link https://developer.mozilla.org/en-US/docs/Web/API/SourceBuffer/abort
  658. */
  659. abort() {
  660. if (this.videoBuffer_) {
  661. this.videoBuffer_.abort();
  662. }
  663. if (!this.audioDisabled_ &amp;&amp; this.audioBuffer_) {
  664. this.audioBuffer_.abort();
  665. }
  666. if (this.transmuxer_) {
  667. this.transmuxer_.postMessage({action: 'reset'});
  668. }
  669. this.pendingBuffers_.length = 0;
  670. this.bufferUpdating_ = false;
  671. }
  672. }
  673. </code></pre>
  674. </article>
  675. </section>
  676. </div>
  677. <nav>
  678. <h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="FlashMediaSource.html">FlashMediaSource</a></li><li><a href="FlashSourceBuffer.html">FlashSourceBuffer</a></li><li><a href="HtmlMediaSource.html">HtmlMediaSource</a></li><li><a href="MessageHandlers.html">MessageHandlers</a></li><li><a href="VirtualSourceBuffer.html">VirtualSourceBuffer</a></li></ul><h3>Global</h3><ul><li><a href="global.html#abort">abort</a></li><li><a href="global.html#addSourceBuffer">addSourceBuffer</a></li><li><a href="global.html#appendBuffer">appendBuffer</a></li><li><a href="global.html#appendGopInfo_">appendGopInfo_</a></li><li><a href="global.html#endOfStream">endOfStream</a></li><li><a href="global.html#FlashTransmuxerWorker">FlashTransmuxerWorker</a></li><li><a href="global.html#get">get</a></li><li><a href="global.html#gopsSafeToAlignWith">gopsSafeToAlignWith</a></li><li><a href="global.html#MediaSource">MediaSource</a></li><li><a href="global.html#open">open</a></li><li><a href="global.html#remove">remove</a></li><li><a href="global.html#removeGopBuffer">removeGopBuffer</a></li><li><a href="global.html#set">set</a></li><li><a href="global.html#supportsNativeMediaSources">supportsNativeMediaSources</a></li><li><a href="global.html#TransmuxerWorker">TransmuxerWorker</a></li><li><a href="global.html#updateGopBuffer">updateGopBuffer</a></li><li><a href="global.html#URL">URL</a></li></ul>
  679. </nav>
  680. <br class="clear">
  681. <footer>
  682. Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.4</a> on Thu Nov 02 2017 12:03:25 GMT-0400 (EDT)
  683. </footer>
  684. <script> prettyPrint(); </script>
  685. <script src="scripts/linenumber.js"> </script>
  686. </body>
  687. </html>