Home Reference Source

src/demux/demuxer.js

  1. import { EventEmitter } from 'eventemitter3';
  2. import * as work from 'webworkify-webpack';
  3.  
  4. import Event from '../events';
  5. import DemuxerInline from '../demux/demuxer-inline';
  6. import { logger } from '../utils/logger';
  7. import { ErrorTypes, ErrorDetails } from '../errors';
  8. import { getMediaSource } from '../utils/mediasource-helper';
  9. import { getSelfScope } from '../utils/get-self-scope';
  10.  
  11. import { Observer } from '../observer';
  12.  
  13. // see https://stackoverflow.com/a/11237259/589493
  14. const global = getSelfScope(); // safeguard for code that might run both on worker and main thread
  15. const MediaSource = getMediaSource() || { isTypeSupported: () => false };
  16.  
  17. class Demuxer {
  18. constructor (hls, id) {
  19. this.hls = hls;
  20. this.id = id;
  21.  
  22. const observer = this.observer = new Observer();
  23. const config = hls.config;
  24.  
  25. const forwardMessage = (ev, data) => {
  26. data = data || {};
  27. data.frag = this.frag;
  28. data.id = this.id;
  29. hls.trigger(ev, data);
  30. };
  31.  
  32. // forward events to main thread
  33. observer.on(Event.FRAG_DECRYPTED, forwardMessage);
  34. observer.on(Event.FRAG_PARSING_INIT_SEGMENT, forwardMessage);
  35. observer.on(Event.FRAG_PARSING_DATA, forwardMessage);
  36. observer.on(Event.FRAG_PARSED, forwardMessage);
  37. observer.on(Event.ERROR, forwardMessage);
  38. observer.on(Event.FRAG_PARSING_METADATA, forwardMessage);
  39. observer.on(Event.FRAG_PARSING_USERDATA, forwardMessage);
  40. observer.on(Event.INIT_PTS_FOUND, forwardMessage);
  41.  
  42. const typeSupported = {
  43. mp4: MediaSource.isTypeSupported('video/mp4'),
  44. mpeg: MediaSource.isTypeSupported('audio/mpeg'),
  45. mp3: MediaSource.isTypeSupported('audio/mp4; codecs="mp3"')
  46. };
  47. // navigator.vendor is not always available in Web Worker
  48. // refer to https://developer.mozilla.org/en-US/docs/Web/API/WorkerGlobalScope/navigator
  49. const vendor = navigator.vendor;
  50. if (config.enableWorker && (typeof (Worker) !== 'undefined')) {
  51. logger.log('demuxing in webworker');
  52. let w;
  53. try {
  54. w = this.w = work(require.resolve('../demux/demuxer-worker.js'));
  55. this.onwmsg = this.onWorkerMessage.bind(this);
  56. w.addEventListener('message', this.onwmsg);
  57. w.onerror = function (event) {
  58. hls.trigger(Event.ERROR, { type: ErrorTypes.OTHER_ERROR, details: ErrorDetails.INTERNAL_EXCEPTION, fatal: true, event: 'demuxerWorker', err: { message: event.message + ' (' + event.filename + ':' + event.lineno + ')' } });
  59. };
  60. w.postMessage({ cmd: 'init', typeSupported: typeSupported, vendor: vendor, id: id, config: JSON.stringify(config) });
  61. } catch (err) {
  62. logger.warn('Error in worker:', err);
  63. logger.error('Error while initializing DemuxerWorker, fallback on DemuxerInline');
  64. if (w) {
  65. // revoke the Object URL that was used to create demuxer worker, so as not to leak it
  66. global.URL.revokeObjectURL(w.objectURL);
  67. }
  68. this.demuxer = new DemuxerInline(observer, typeSupported, config, vendor);
  69. this.w = undefined;
  70. }
  71. } else {
  72. this.demuxer = new DemuxerInline(observer, typeSupported, config, vendor);
  73. }
  74. }
  75.  
  76. destroy () {
  77. let w = this.w;
  78. if (w) {
  79. w.removeEventListener('message', this.onwmsg);
  80. w.terminate();
  81. this.w = null;
  82. } else {
  83. let demuxer = this.demuxer;
  84. if (demuxer) {
  85. demuxer.destroy();
  86. this.demuxer = null;
  87. }
  88. }
  89. const observer = this.observer;
  90. if (observer) {
  91. observer.removeAllListeners();
  92. this.observer = null;
  93. }
  94. }
  95.  
  96. push (data, initSegment, audioCodec, videoCodec, frag, duration, accurateTimeOffset, defaultInitPTS) {
  97. const w = this.w;
  98. const timeOffset = Number.isFinite(frag.startPTS) ? frag.startPTS : frag.start;
  99. const decryptdata = frag.decryptdata;
  100. const lastFrag = this.frag;
  101. const discontinuity = !(lastFrag && (frag.cc === lastFrag.cc));
  102. const trackSwitch = !(lastFrag && (frag.level === lastFrag.level));
  103. const nextSN = lastFrag && (frag.sn === (lastFrag.sn + 1));
  104. const contiguous = !trackSwitch && nextSN;
  105. if (discontinuity) {
  106. logger.log(`${this.id}:discontinuity detected`);
  107. }
  108.  
  109. if (trackSwitch) {
  110. logger.log(`${this.id}:switch detected`);
  111. }
  112.  
  113. this.frag = frag;
  114. if (w) {
  115. // post fragment payload as transferable objects for ArrayBuffer (no copy)
  116. w.postMessage({ cmd: 'demux', data, decryptdata, initSegment, audioCodec, videoCodec, timeOffset, discontinuity, trackSwitch, contiguous, duration, accurateTimeOffset, defaultInitPTS }, data instanceof ArrayBuffer ? [data] : []);
  117. } else {
  118. let demuxer = this.demuxer;
  119. if (demuxer) {
  120. demuxer.push(data, decryptdata, initSegment, audioCodec, videoCodec, timeOffset, discontinuity, trackSwitch, contiguous, duration, accurateTimeOffset, defaultInitPTS);
  121. }
  122. }
  123. }
  124.  
  125. onWorkerMessage (ev) {
  126. let data = ev.data,
  127. hls = this.hls;
  128. switch (data.event) {
  129. case 'init':
  130. // revoke the Object URL that was used to create demuxer worker, so as not to leak it
  131. global.URL.revokeObjectURL(this.w.objectURL);
  132. break;
  133. // special case for FRAG_PARSING_DATA: data1 and data2 are transferable objects
  134. case Event.FRAG_PARSING_DATA:
  135. data.data.data1 = new Uint8Array(data.data1);
  136. if (data.data2) {
  137. data.data.data2 = new Uint8Array(data.data2);
  138. }
  139.  
  140. /* falls through */
  141. default:
  142. data.data = data.data || {};
  143. data.data.frag = this.frag;
  144. data.data.id = this.id;
  145. hls.trigger(data.event, data.data);
  146. break;
  147. }
  148. }
  149. }
  150.  
  151. export default Demuxer;