coalesce-stream.js 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. /**
  2. * mux.js
  3. *
  4. * Copyright (c) Brightcove
  5. * Licensed Apache-2.0 https://github.com/videojs/mux.js/blob/master/LICENSE
  6. */
  7. 'use strict';
  8. var Stream = require('../utils/stream.js');
  9. /**
  10. * The final stage of the transmuxer that emits the flv tags
  11. * for audio, video, and metadata. Also tranlates in time and
  12. * outputs caption data and id3 cues.
  13. */
  14. var CoalesceStream = function CoalesceStream(options) {
  15. // Number of Tracks per output segment
  16. // If greater than 1, we combine multiple
  17. // tracks into a single segment
  18. this.numberOfTracks = 0;
  19. this.metadataStream = options.metadataStream;
  20. this.videoTags = [];
  21. this.audioTags = [];
  22. this.videoTrack = null;
  23. this.audioTrack = null;
  24. this.pendingCaptions = [];
  25. this.pendingMetadata = [];
  26. this.pendingTracks = 0;
  27. this.processedTracks = 0;
  28. CoalesceStream.prototype.init.call(this); // Take output from multiple
  29. this.push = function (output) {
  30. // buffer incoming captions until the associated video segment
  31. // finishes
  32. if (output.text) {
  33. return this.pendingCaptions.push(output);
  34. } // buffer incoming id3 tags until the final flush
  35. if (output.frames) {
  36. return this.pendingMetadata.push(output);
  37. }
  38. if (output.track.type === 'video') {
  39. this.videoTrack = output.track;
  40. this.videoTags = output.tags;
  41. this.pendingTracks++;
  42. }
  43. if (output.track.type === 'audio') {
  44. this.audioTrack = output.track;
  45. this.audioTags = output.tags;
  46. this.pendingTracks++;
  47. }
  48. };
  49. };
  50. CoalesceStream.prototype = new Stream();
  51. CoalesceStream.prototype.flush = function (flushSource) {
  52. var id3,
  53. caption,
  54. i,
  55. timelineStartPts,
  56. event = {
  57. tags: {},
  58. captions: [],
  59. captionStreams: {},
  60. metadata: []
  61. };
  62. if (this.pendingTracks < this.numberOfTracks) {
  63. if (flushSource !== 'VideoSegmentStream' && flushSource !== 'AudioSegmentStream') {
  64. // Return because we haven't received a flush from a data-generating
  65. // portion of the segment (meaning that we have only recieved meta-data
  66. // or captions.)
  67. return;
  68. } else if (this.pendingTracks === 0) {
  69. // In the case where we receive a flush without any data having been
  70. // received we consider it an emitted track for the purposes of coalescing
  71. // `done` events.
  72. // We do this for the case where there is an audio and video track in the
  73. // segment but no audio data. (seen in several playlists with alternate
  74. // audio tracks and no audio present in the main TS segments.)
  75. this.processedTracks++;
  76. if (this.processedTracks < this.numberOfTracks) {
  77. return;
  78. }
  79. }
  80. }
  81. this.processedTracks += this.pendingTracks;
  82. this.pendingTracks = 0;
  83. if (this.processedTracks < this.numberOfTracks) {
  84. return;
  85. }
  86. if (this.videoTrack) {
  87. timelineStartPts = this.videoTrack.timelineStartInfo.pts;
  88. } else if (this.audioTrack) {
  89. timelineStartPts = this.audioTrack.timelineStartInfo.pts;
  90. }
  91. event.tags.videoTags = this.videoTags;
  92. event.tags.audioTags = this.audioTags; // Translate caption PTS times into second offsets into the
  93. // video timeline for the segment, and add track info
  94. for (i = 0; i < this.pendingCaptions.length; i++) {
  95. caption = this.pendingCaptions[i];
  96. caption.startTime = caption.startPts - timelineStartPts;
  97. caption.startTime /= 90e3;
  98. caption.endTime = caption.endPts - timelineStartPts;
  99. caption.endTime /= 90e3;
  100. event.captionStreams[caption.stream] = true;
  101. event.captions.push(caption);
  102. } // Translate ID3 frame PTS times into second offsets into the
  103. // video timeline for the segment
  104. for (i = 0; i < this.pendingMetadata.length; i++) {
  105. id3 = this.pendingMetadata[i];
  106. id3.cueTime = id3.pts - timelineStartPts;
  107. id3.cueTime /= 90e3;
  108. event.metadata.push(id3);
  109. } // We add this to every single emitted segment even though we only need
  110. // it for the first
  111. event.metadata.dispatchType = this.metadataStream.dispatchType; // Reset stream state
  112. this.videoTrack = null;
  113. this.audioTrack = null;
  114. this.videoTags = [];
  115. this.audioTags = [];
  116. this.pendingCaptions.length = 0;
  117. this.pendingMetadata.length = 0;
  118. this.pendingTracks = 0;
  119. this.processedTracks = 0; // Emit the final segment
  120. this.trigger('data', event);
  121. this.trigger('done');
  122. };
  123. module.exports = CoalesceStream;