Home Reference Source

src/controller/id3-track-controller.js

  1. /*
  2. * id3 metadata track controller
  3. */
  4.  
  5. import Event from '../events';
  6. import EventHandler from '../event-handler';
  7. import ID3 from '../demux/id3';
  8. import { logger } from '../utils/logger';
  9. import { sendAddTrackEvent, clearCurrentCues, getClosestCue } from '../utils/texttrack-utils';
  10.  
  11. class ID3TrackController extends EventHandler {
  12. constructor (hls) {
  13. super(hls,
  14. Event.MEDIA_ATTACHED,
  15. Event.MEDIA_DETACHING,
  16. Event.FRAG_PARSING_METADATA,
  17. Event.LIVE_BACK_BUFFER_REACHED
  18. );
  19. this.id3Track = undefined;
  20. this.media = undefined;
  21. }
  22.  
  23. destroy () {
  24. EventHandler.prototype.destroy.call(this);
  25. }
  26.  
  27. // Add ID3 metatadata text track.
  28. onMediaAttached (data) {
  29. this.media = data.media;
  30. if (!this.media) {
  31.  
  32. }
  33. }
  34.  
  35. onMediaDetaching () {
  36. clearCurrentCues(this.id3Track);
  37. this.id3Track = undefined;
  38. this.media = undefined;
  39. }
  40.  
  41. getID3Track (textTracks) {
  42. for (let i = 0; i < textTracks.length; i++) {
  43. let textTrack = textTracks[i];
  44. if (textTrack.kind === 'metadata' && textTrack.label === 'id3') {
  45. // send 'addtrack' when reusing the textTrack for metadata,
  46. // same as what we do for captions
  47. sendAddTrackEvent(textTrack, this.media);
  48.  
  49. return textTrack;
  50. }
  51. }
  52. return this.media.addTextTrack('metadata', 'id3');
  53. }
  54.  
  55. onFragParsingMetadata (data) {
  56. const fragment = data.frag;
  57. const samples = data.samples;
  58.  
  59. // create track dynamically
  60. if (!this.id3Track) {
  61. this.id3Track = this.getID3Track(this.media.textTracks);
  62. this.id3Track.mode = 'hidden';
  63. }
  64.  
  65. // Attempt to recreate Safari functionality by creating
  66. // WebKitDataCue objects when available and store the decoded
  67. // ID3 data in the value property of the cue
  68. let Cue = window.WebKitDataCue || window.VTTCue || window.TextTrackCue;
  69.  
  70. for (let i = 0; i < samples.length; i++) {
  71. const frames = ID3.getID3Frames(samples[i].data);
  72. if (frames) {
  73. // Ensure the pts is positive - sometimes it's reported as a small negative number
  74. let startTime = Math.max(samples[i].pts, 0);
  75. let endTime = i < samples.length - 1 ? samples[i + 1].pts : fragment.endPTS;
  76. if (!endTime) {
  77. endTime = fragment.start + fragment.duration;
  78. }
  79. if (startTime === endTime) {
  80. // Give a slight bump to the endTime if it's equal to startTime to avoid a SyntaxError in IE
  81. endTime += 0.0001;
  82. } else if (startTime > endTime) {
  83. logger.warn('detected an id3 sample with endTime < startTime, adjusting endTime to (startTime + 0.25)');
  84. endTime = startTime + 0.25;
  85. }
  86.  
  87. for (let j = 0; j < frames.length; j++) {
  88. const frame = frames[j];
  89. // Safari doesn't put the timestamp frame in the TextTrack
  90. if (!ID3.isTimeStampFrame(frame)) {
  91. const cue = new Cue(startTime, endTime, '');
  92. cue.value = frame;
  93. this.id3Track.addCue(cue);
  94. }
  95. }
  96. }
  97. }
  98. }
  99.  
  100. onLiveBackBufferReached ({ bufferEnd }) {
  101. const { id3Track } = this;
  102. if (!id3Track || !id3Track.cues || !id3Track.cues.length) {
  103. return;
  104. }
  105. const foundCue = getClosestCue(id3Track.cues, bufferEnd);
  106. if (!foundCue) {
  107. return;
  108. }
  109. while (id3Track.cues[0] !== foundCue) {
  110. id3Track.removeCue(id3Track.cues[0]);
  111. }
  112. }
  113. }
  114.  
  115. export default ID3TrackController;