Home Reference Source

src/demux/mp4demuxer.ts

  1. /**
  2. * MP4 demuxer
  3. */
  4. import {
  5. Demuxer,
  6. DemuxerResult,
  7. PassthroughTrack,
  8. DemuxedAudioTrack,
  9. DemuxedUserdataTrack,
  10. DemuxedMetadataTrack,
  11. KeyData,
  12. } from '../types/demuxer';
  13. import {
  14. findBox,
  15. segmentValidRange,
  16. appendUint8Array,
  17. parseEmsg,
  18. parseSamples,
  19. parseInitSegment,
  20. RemuxerTrackIdConfig,
  21. } from '../utils/mp4-tools';
  22. import { dummyTrack } from './dummy-demuxed-track';
  23. import type { HlsEventEmitter } from '../events';
  24. import type { HlsConfig } from '../config';
  25.  
  26. const emsgSchemePattern = /\/emsg[-/]ID3/i;
  27.  
  28. class MP4Demuxer implements Demuxer {
  29. static readonly minProbeByteLength = 1024;
  30. private remainderData: Uint8Array | null = null;
  31. private timeOffset: number = 0;
  32. private config: HlsConfig;
  33. private videoTrack?: PassthroughTrack;
  34. private audioTrack?: DemuxedAudioTrack;
  35. private id3Track?: DemuxedMetadataTrack;
  36. private txtTrack?: DemuxedUserdataTrack;
  37.  
  38. constructor(observer: HlsEventEmitter, config: HlsConfig) {
  39. this.config = config;
  40. }
  41.  
  42. public resetTimeStamp() {}
  43.  
  44. public resetInitSegment(
  45. initSegment: Uint8Array,
  46. audioCodec: string | undefined,
  47. videoCodec: string | undefined,
  48. trackDuration: number
  49. ) {
  50. const initData = parseInitSegment(initSegment);
  51. const videoTrack = (this.videoTrack = dummyTrack(
  52. 'video',
  53. 1
  54. ) as PassthroughTrack);
  55. const audioTrack = (this.audioTrack = dummyTrack(
  56. 'audio',
  57. 1
  58. ) as DemuxedAudioTrack);
  59. const captionTrack = (this.txtTrack = dummyTrack(
  60. 'text',
  61. 1
  62. ) as DemuxedUserdataTrack);
  63.  
  64. this.id3Track = dummyTrack('id3', 1) as DemuxedMetadataTrack;
  65. this.timeOffset = 0;
  66.  
  67. if (initData.video) {
  68. const { id, timescale, codec } = initData.video;
  69. videoTrack.id = id;
  70. videoTrack.timescale = captionTrack.timescale = timescale;
  71. videoTrack.codec = codec;
  72. }
  73.  
  74. if (initData.audio) {
  75. const { id, timescale, codec } = initData.audio;
  76. audioTrack.id = id;
  77. audioTrack.timescale = timescale;
  78. audioTrack.codec = codec;
  79. }
  80.  
  81. captionTrack.id = RemuxerTrackIdConfig.text;
  82. videoTrack.sampleDuration = 0;
  83. videoTrack.duration = audioTrack.duration = trackDuration;
  84. }
  85.  
  86. public resetContiguity(): void {}
  87.  
  88. static probe(data: Uint8Array) {
  89. // ensure we find a moof box in the first 16 kB
  90. data = data.length > 16384 ? data.subarray(0, 16384) : data;
  91. return findBox(data, ['moof']).length > 0;
  92. }
  93.  
  94. public demux(data: Uint8Array, timeOffset: number): DemuxerResult {
  95. this.timeOffset = timeOffset;
  96. // Load all data into the avc track. The CMAF remuxer will look for the data in the samples object; the rest of the fields do not matter
  97. let videoSamples = data;
  98. const videoTrack = this.videoTrack as PassthroughTrack;
  99. const textTrack = this.txtTrack as DemuxedUserdataTrack;
  100. if (this.config.progressive) {
  101. // Split the bytestream into two ranges: one encompassing all data up until the start of the last moof, and everything else.
  102. // This is done to guarantee that we're sending valid data to MSE - when demuxing progressively, we have no guarantee
  103. // that the fetch loader gives us flush moof+mdat pairs. If we push jagged data to MSE, it will throw an exception.
  104. if (this.remainderData) {
  105. videoSamples = appendUint8Array(this.remainderData, data);
  106. }
  107. const segmentedData = segmentValidRange(videoSamples);
  108. this.remainderData = segmentedData.remainder;
  109. videoTrack.samples = segmentedData.valid || new Uint8Array();
  110. } else {
  111. videoTrack.samples = videoSamples;
  112. }
  113.  
  114. const id3Track = this.extractID3Track(videoTrack, timeOffset);
  115. textTrack.samples = parseSamples(timeOffset, videoTrack);
  116.  
  117. return {
  118. videoTrack,
  119. audioTrack: this.audioTrack as DemuxedAudioTrack,
  120. id3Track,
  121. textTrack: this.txtTrack as DemuxedUserdataTrack,
  122. };
  123. }
  124.  
  125. public flush() {
  126. const timeOffset = this.timeOffset;
  127. const videoTrack = this.videoTrack as PassthroughTrack;
  128. const textTrack = this.txtTrack as DemuxedUserdataTrack;
  129. videoTrack.samples = this.remainderData || new Uint8Array();
  130. this.remainderData = null;
  131.  
  132. const id3Track = this.extractID3Track(videoTrack, this.timeOffset);
  133. textTrack.samples = parseSamples(timeOffset, videoTrack);
  134.  
  135. return {
  136. videoTrack,
  137. audioTrack: dummyTrack() as DemuxedAudioTrack,
  138. id3Track,
  139. textTrack: dummyTrack() as DemuxedUserdataTrack,
  140. };
  141. }
  142.  
  143. private extractID3Track(
  144. videoTrack: PassthroughTrack,
  145. timeOffset: number
  146. ): DemuxedMetadataTrack {
  147. const id3Track = this.id3Track as DemuxedMetadataTrack;
  148. if (videoTrack.samples.length) {
  149. const emsgs = findBox(videoTrack.samples, ['emsg']);
  150. if (emsgs) {
  151. emsgs.forEach((data: Uint8Array) => {
  152. const emsgInfo = parseEmsg(data);
  153. if (emsgSchemePattern.test(emsgInfo.schemeIdUri)) {
  154. const pts = Number.isFinite(emsgInfo.presentationTime)
  155. ? emsgInfo.presentationTime! / emsgInfo.timeScale
  156. : timeOffset +
  157. emsgInfo.presentationTimeDelta! / emsgInfo.timeScale;
  158. const payload = emsgInfo.payload;
  159. id3Track.samples.push({
  160. data: payload,
  161. len: payload.byteLength,
  162. dts: pts,
  163. pts: pts,
  164. });
  165. }
  166. });
  167. }
  168. }
  169. return id3Track;
  170. }
  171.  
  172. demuxSampleAes(
  173. data: Uint8Array,
  174. keyData: KeyData,
  175. timeOffset: number
  176. ): Promise<DemuxerResult> {
  177. return Promise.reject(
  178. new Error('The MP4 demuxer does not support SAMPLE-AES decryption')
  179. );
  180. }
  181.  
  182. destroy() {}
  183. }
  184.  
  185. export default MP4Demuxer;