coalesce-stream.js 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  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(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);
  29. // Take output from multiple
  30. this.push = function(output) {
  31. // buffer incoming captions until the associated video segment
  32. // finishes
  33. if (output.text) {
  34. return this.pendingCaptions.push(output);
  35. }
  36. // buffer incoming id3 tags until the final flush
  37. if (output.frames) {
  38. return this.pendingMetadata.push(output);
  39. }
  40. if (output.track.type === 'video') {
  41. this.videoTrack = output.track;
  42. this.videoTags = output.tags;
  43. this.pendingTracks++;
  44. }
  45. if (output.track.type === 'audio') {
  46. this.audioTrack = output.track;
  47. this.audioTags = output.tags;
  48. this.pendingTracks++;
  49. }
  50. };
  51. };
  52. CoalesceStream.prototype = new Stream();
  53. CoalesceStream.prototype.flush = function(flushSource) {
  54. var
  55. id3,
  56. caption,
  57. i,
  58. timelineStartPts,
  59. event = {
  60. tags: {},
  61. captions: [],
  62. captionStreams: {},
  63. metadata: []
  64. };
  65. if (this.pendingTracks < this.numberOfTracks) {
  66. if (flushSource !== 'VideoSegmentStream' &&
  67. flushSource !== 'AudioSegmentStream') {
  68. // Return because we haven't received a flush from a data-generating
  69. // portion of the segment (meaning that we have only recieved meta-data
  70. // or captions.)
  71. return;
  72. } else if (this.pendingTracks === 0) {
  73. // In the case where we receive a flush without any data having been
  74. // received we consider it an emitted track for the purposes of coalescing
  75. // `done` events.
  76. // We do this for the case where there is an audio and video track in the
  77. // segment but no audio data. (seen in several playlists with alternate
  78. // audio tracks and no audio present in the main TS segments.)
  79. this.processedTracks++;
  80. if (this.processedTracks < this.numberOfTracks) {
  81. return;
  82. }
  83. }
  84. }
  85. this.processedTracks += this.pendingTracks;
  86. this.pendingTracks = 0;
  87. if (this.processedTracks < this.numberOfTracks) {
  88. return;
  89. }
  90. if (this.videoTrack) {
  91. timelineStartPts = this.videoTrack.timelineStartInfo.pts;
  92. } else if (this.audioTrack) {
  93. timelineStartPts = this.audioTrack.timelineStartInfo.pts;
  94. }
  95. event.tags.videoTags = this.videoTags;
  96. event.tags.audioTags = this.audioTags;
  97. // Translate caption PTS times into second offsets into the
  98. // video timeline for the segment, and add track info
  99. for (i = 0; i < this.pendingCaptions.length; i++) {
  100. caption = this.pendingCaptions[i];
  101. caption.startTime = caption.startPts - timelineStartPts;
  102. caption.startTime /= 90e3;
  103. caption.endTime = caption.endPts - timelineStartPts;
  104. caption.endTime /= 90e3;
  105. event.captionStreams[caption.stream] = true;
  106. event.captions.push(caption);
  107. }
  108. // Translate ID3 frame PTS times into second offsets into the
  109. // video timeline for the segment
  110. for (i = 0; i < this.pendingMetadata.length; i++) {
  111. id3 = this.pendingMetadata[i];
  112. id3.cueTime = id3.pts - timelineStartPts;
  113. id3.cueTime /= 90e3;
  114. event.metadata.push(id3);
  115. }
  116. // We add this to every single emitted segment even though we only need
  117. // it for the first
  118. event.metadata.dispatchType = this.metadataStream.dispatchType;
  119. // Reset stream state
  120. this.videoTrack = null;
  121. this.audioTrack = null;
  122. this.videoTags = [];
  123. this.audioTags = [];
  124. this.pendingCaptions.length = 0;
  125. this.pendingMetadata.length = 0;
  126. this.pendingTracks = 0;
  127. this.processedTracks = 0;
  128. // Emit the final segment
  129. this.trigger('data', event);
  130. this.trigger('done');
  131. };
  132. module.exports = CoalesceStream;