html-media-source.js.html 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="utf-8">
  5. <title>JSDoc: Source: html-media-source.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: html-media-source.js</h1>
  17. <section>
  18. <article>
  19. <pre class="prettyprint source linenums"><code>/**
  20. * @file html-media-source.js
  21. */
  22. import window from 'global/window';
  23. import document from 'global/document';
  24. import videojs from 'video.js';
  25. import VirtualSourceBuffer from './virtual-source-buffer';
  26. import {durationOfVideo} from './add-text-track-data';
  27. import {
  28. isAudioCodec,
  29. isVideoCodec,
  30. parseContentType,
  31. translateLegacyCodecs
  32. } from './codec-utils';
  33. /**
  34. * Our MediaSource implementation in HTML, mimics native
  35. * MediaSource where/if possible.
  36. *
  37. * @link https://developer.mozilla.org/en-US/docs/Web/API/MediaSource
  38. * @class HtmlMediaSource
  39. * @extends videojs.EventTarget
  40. */
  41. export default class HtmlMediaSource extends videojs.EventTarget {
  42. constructor() {
  43. super();
  44. let property;
  45. this.nativeMediaSource_ = new window.MediaSource();
  46. // delegate to the native MediaSource's methods by default
  47. for (property in this.nativeMediaSource_) {
  48. if (!(property in HtmlMediaSource.prototype) &amp;&amp;
  49. typeof this.nativeMediaSource_[property] === 'function') {
  50. this[property] = this.nativeMediaSource_[property].bind(this.nativeMediaSource_);
  51. }
  52. }
  53. // emulate `duration` and `seekable` until seeking can be
  54. // handled uniformly for live streams
  55. // see https://github.com/w3c/media-source/issues/5
  56. this.duration_ = NaN;
  57. Object.defineProperty(this, 'duration', {
  58. get() {
  59. if (this.duration_ === Infinity) {
  60. return this.duration_;
  61. }
  62. return this.nativeMediaSource_.duration;
  63. },
  64. set(duration) {
  65. this.duration_ = duration;
  66. if (duration !== Infinity) {
  67. this.nativeMediaSource_.duration = duration;
  68. return;
  69. }
  70. }
  71. });
  72. Object.defineProperty(this, 'seekable', {
  73. get() {
  74. if (this.duration_ === Infinity) {
  75. return videojs.createTimeRanges([[0, this.nativeMediaSource_.duration]]);
  76. }
  77. return this.nativeMediaSource_.seekable;
  78. }
  79. });
  80. Object.defineProperty(this, 'readyState', {
  81. get() {
  82. return this.nativeMediaSource_.readyState;
  83. }
  84. });
  85. Object.defineProperty(this, 'activeSourceBuffers', {
  86. get() {
  87. return this.activeSourceBuffers_;
  88. }
  89. });
  90. // the list of virtual and native SourceBuffers created by this
  91. // MediaSource
  92. this.sourceBuffers = [];
  93. this.activeSourceBuffers_ = [];
  94. /**
  95. * update the list of active source buffers based upon various
  96. * imformation from HLS and video.js
  97. *
  98. * @private
  99. */
  100. this.updateActiveSourceBuffers_ = () => {
  101. // Retain the reference but empty the array
  102. this.activeSourceBuffers_.length = 0;
  103. // If there is only one source buffer, then it will always be active and audio will
  104. // be disabled based on the codec of the source buffer
  105. if (this.sourceBuffers.length === 1) {
  106. let sourceBuffer = this.sourceBuffers[0];
  107. sourceBuffer.appendAudioInitSegment_ = true;
  108. sourceBuffer.audioDisabled_ = !sourceBuffer.audioCodec_;
  109. this.activeSourceBuffers_.push(sourceBuffer);
  110. return;
  111. }
  112. // There are 2 source buffers, a combined (possibly video only) source buffer and
  113. // and an audio only source buffer.
  114. // By default, the audio in the combined virtual source buffer is enabled
  115. // and the audio-only source buffer (if it exists) is disabled.
  116. let disableCombined = false;
  117. let disableAudioOnly = true;
  118. // TODO: maybe we can store the sourcebuffers on the track objects?
  119. // safari may do something like this
  120. for (let i = 0; i &lt; this.player_.audioTracks().length; i++) {
  121. let track = this.player_.audioTracks()[i];
  122. if (track.enabled &amp;&amp; track.kind !== 'main') {
  123. // The enabled track is an alternate audio track so disable the audio in
  124. // the combined source buffer and enable the audio-only source buffer.
  125. disableCombined = true;
  126. disableAudioOnly = false;
  127. break;
  128. }
  129. }
  130. this.sourceBuffers.forEach((sourceBuffer) => {
  131. /* eslinst-disable */
  132. // TODO once codecs are required, we can switch to using the codecs to determine
  133. // what stream is the video stream, rather than relying on videoTracks
  134. /* eslinst-enable */
  135. sourceBuffer.appendAudioInitSegment_ = true;
  136. if (sourceBuffer.videoCodec_ &amp;&amp; sourceBuffer.audioCodec_) {
  137. // combined
  138. sourceBuffer.audioDisabled_ = disableCombined;
  139. } else if (sourceBuffer.videoCodec_ &amp;&amp; !sourceBuffer.audioCodec_) {
  140. // If the "combined" source buffer is video only, then we do not want
  141. // disable the audio-only source buffer (this is mostly for demuxed
  142. // audio and video hls)
  143. sourceBuffer.audioDisabled_ = true;
  144. disableAudioOnly = false;
  145. } else if (!sourceBuffer.videoCodec_ &amp;&amp; sourceBuffer.audioCodec_) {
  146. // audio only
  147. sourceBuffer.audioDisabled_ = disableAudioOnly;
  148. if (disableAudioOnly) {
  149. return;
  150. }
  151. }
  152. this.activeSourceBuffers_.push(sourceBuffer);
  153. });
  154. };
  155. this.onPlayerMediachange_ = () => {
  156. this.sourceBuffers.forEach((sourceBuffer) => {
  157. sourceBuffer.appendAudioInitSegment_ = true;
  158. });
  159. };
  160. this.onHlsReset_ = () => {
  161. this.sourceBuffers.forEach((sourceBuffer) => {
  162. if (sourceBuffer.transmuxer_) {
  163. sourceBuffer.transmuxer_.postMessage({action: 'resetCaptions'});
  164. }
  165. });
  166. };
  167. this.onHlsSegmentTimeMapping_ = (event) => {
  168. this.sourceBuffers.forEach(buffer => buffer.timeMapping_ = event.mapping);
  169. };
  170. // Re-emit MediaSource events on the polyfill
  171. [
  172. 'sourceopen',
  173. 'sourceclose',
  174. 'sourceended'
  175. ].forEach(function(eventName) {
  176. this.nativeMediaSource_.addEventListener(eventName, this.trigger.bind(this));
  177. }, this);
  178. // capture the associated player when the MediaSource is
  179. // successfully attached
  180. this.on('sourceopen', (event) => {
  181. // Get the player this MediaSource is attached to
  182. let video = document.querySelector('[src="' + this.url_ + '"]');
  183. if (!video) {
  184. return;
  185. }
  186. this.player_ = videojs(video.parentNode);
  187. // hls-reset is fired by videojs.Hls on to the tech after the main SegmentLoader
  188. // resets its state and flushes the buffer
  189. this.player_.tech_.on('hls-reset', this.onHlsReset_);
  190. // hls-segment-time-mapping is fired by videojs.Hls on to the tech after the main
  191. // SegmentLoader inspects an MTS segment and has an accurate stream to display
  192. // time mapping
  193. this.player_.tech_.on('hls-segment-time-mapping', this.onHlsSegmentTimeMapping_);
  194. if (this.player_.audioTracks &amp;&amp; this.player_.audioTracks()) {
  195. this.player_.audioTracks().on('change', this.updateActiveSourceBuffers_);
  196. this.player_.audioTracks().on('addtrack', this.updateActiveSourceBuffers_);
  197. this.player_.audioTracks().on('removetrack', this.updateActiveSourceBuffers_);
  198. }
  199. this.player_.on('mediachange', this.onPlayerMediachange_);
  200. });
  201. this.on('sourceended', (event) => {
  202. let duration = durationOfVideo(this.duration);
  203. for (let i = 0; i &lt; this.sourceBuffers.length; i++) {
  204. let sourcebuffer = this.sourceBuffers[i];
  205. let cues = sourcebuffer.metadataTrack_ &amp;&amp; sourcebuffer.metadataTrack_.cues;
  206. if (cues &amp;&amp; cues.length) {
  207. cues[cues.length - 1].endTime = duration;
  208. }
  209. }
  210. });
  211. // explicitly terminate any WebWorkers that were created
  212. // by SourceHandlers
  213. this.on('sourceclose', function(event) {
  214. this.sourceBuffers.forEach(function(sourceBuffer) {
  215. if (sourceBuffer.transmuxer_) {
  216. sourceBuffer.transmuxer_.terminate();
  217. }
  218. });
  219. this.sourceBuffers.length = 0;
  220. if (!this.player_) {
  221. return;
  222. }
  223. if (this.player_.audioTracks &amp;&amp; this.player_.audioTracks()) {
  224. this.player_.audioTracks().off('change', this.updateActiveSourceBuffers_);
  225. this.player_.audioTracks().off('addtrack', this.updateActiveSourceBuffers_);
  226. this.player_.audioTracks().off('removetrack', this.updateActiveSourceBuffers_);
  227. }
  228. // We can only change this if the player hasn't been disposed of yet
  229. // because `off` eventually tries to use the el_ property. If it has
  230. // been disposed of, then don't worry about it because there are no
  231. // event handlers left to unbind anyway
  232. if (this.player_.el_) {
  233. this.player_.off('mediachange', this.onPlayerMediachange_);
  234. this.player_.tech_.off('hls-reset', this.onHlsReset_);
  235. this.player_.tech_.off('hls-segment-time-mapping', this.onHlsSegmentTimeMapping_);
  236. }
  237. });
  238. }
  239. /**
  240. * Add a range that that can now be seeked to.
  241. *
  242. * @param {Double} start where to start the addition
  243. * @param {Double} end where to end the addition
  244. * @private
  245. */
  246. addSeekableRange_(start, end) {
  247. let error;
  248. if (this.duration !== Infinity) {
  249. error = new Error('MediaSource.addSeekableRange() can only be invoked ' +
  250. 'when the duration is Infinity');
  251. error.name = 'InvalidStateError';
  252. error.code = 11;
  253. throw error;
  254. }
  255. if (end > this.nativeMediaSource_.duration ||
  256. isNaN(this.nativeMediaSource_.duration)) {
  257. this.nativeMediaSource_.duration = end;
  258. }
  259. }
  260. /**
  261. * Add a source buffer to the media source.
  262. *
  263. * @link https://developer.mozilla.org/en-US/docs/Web/API/MediaSource/addSourceBuffer
  264. * @param {String} type the content-type of the content
  265. * @return {Object} the created source buffer
  266. */
  267. addSourceBuffer(type) {
  268. let buffer;
  269. let parsedType = parseContentType(type);
  270. // Create a VirtualSourceBuffer to transmux MPEG-2 transport
  271. // stream segments into fragmented MP4s
  272. if ((/^(video|audio)\/mp2t$/i).test(parsedType.type)) {
  273. let codecs = [];
  274. if (parsedType.parameters &amp;&amp; parsedType.parameters.codecs) {
  275. codecs = parsedType.parameters.codecs.split(',');
  276. codecs = translateLegacyCodecs(codecs);
  277. codecs = codecs.filter((codec) => {
  278. return (isAudioCodec(codec) || isVideoCodec(codec));
  279. });
  280. }
  281. if (codecs.length === 0) {
  282. codecs = ['avc1.4d400d', 'mp4a.40.2'];
  283. }
  284. buffer = new VirtualSourceBuffer(this, codecs);
  285. if (this.sourceBuffers.length !== 0) {
  286. // If another VirtualSourceBuffer already exists, then we are creating a
  287. // SourceBuffer for an alternate audio track and therefore we know that
  288. // the source has both an audio and video track.
  289. // That means we should trigger the manual creation of the real
  290. // SourceBuffers instead of waiting for the transmuxer to return data
  291. this.sourceBuffers[0].createRealSourceBuffers_();
  292. buffer.createRealSourceBuffers_();
  293. // Automatically disable the audio on the first source buffer if
  294. // a second source buffer is ever created
  295. this.sourceBuffers[0].audioDisabled_ = true;
  296. }
  297. } else {
  298. // delegate to the native implementation
  299. buffer = this.nativeMediaSource_.addSourceBuffer(type);
  300. }
  301. this.sourceBuffers.push(buffer);
  302. return buffer;
  303. }
  304. }
  305. </code></pre>
  306. </article>
  307. </section>
  308. </div>
  309. <nav>
  310. <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>
  311. </nav>
  312. <br class="clear">
  313. <footer>
  314. 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)
  315. </footer>
  316. <script> prettyPrint(); </script>
  317. <script src="scripts/linenumber.js"> </script>
  318. </body>
  319. </html>