Home Reference Source

src/demux/base-audio-demuxer.ts

  1. import * as ID3 from '../demux/id3';
  2. import {
  3. DemuxerResult,
  4. Demuxer,
  5. DemuxedAudioTrack,
  6. AudioFrame,
  7. DemuxedMetadataTrack,
  8. DemuxedVideoTrack,
  9. DemuxedUserdataTrack,
  10. KeyData,
  11. MetadataSchema,
  12. } from '../types/demuxer';
  13. import { dummyTrack } from './dummy-demuxed-track';
  14. import { appendUint8Array } from '../utils/mp4-tools';
  15. import { sliceUint8 } from '../utils/typed-array';
  16.  
  17. class BaseAudioDemuxer implements Demuxer {
  18. protected _audioTrack!: DemuxedAudioTrack;
  19. protected _id3Track!: DemuxedMetadataTrack;
  20. protected frameIndex: number = 0;
  21. protected cachedData: Uint8Array | null = null;
  22. protected initPTS: number | null = null;
  23.  
  24. resetInitSegment(
  25. initSegment: Uint8Array | undefined,
  26. audioCodec: string | undefined,
  27. videoCodec: string | undefined,
  28. trackDuration: number
  29. ) {
  30. this._id3Track = {
  31. type: 'id3',
  32. id: 3,
  33. pid: -1,
  34. inputTimeScale: 90000,
  35. sequenceNumber: 0,
  36. samples: [],
  37. dropped: 0,
  38. };
  39. }
  40.  
  41. resetTimeStamp() {}
  42.  
  43. resetContiguity(): void {}
  44.  
  45. canParse(data: Uint8Array, offset: number): boolean {
  46. return false;
  47. }
  48.  
  49. appendFrame(
  50. track: DemuxedAudioTrack,
  51. data: Uint8Array,
  52. offset: number
  53. ): AudioFrame | void {}
  54.  
  55. // feed incoming data to the front of the parsing pipeline
  56. demux(data: Uint8Array, timeOffset: number): DemuxerResult {
  57. if (this.cachedData) {
  58. data = appendUint8Array(this.cachedData, data);
  59. this.cachedData = null;
  60. }
  61.  
  62. let id3Data: Uint8Array | undefined = ID3.getID3Data(data, 0);
  63. let offset = id3Data ? id3Data.length : 0;
  64. let lastDataIndex;
  65. let pts;
  66. const track = this._audioTrack;
  67. const id3Track = this._id3Track;
  68. const timestamp = id3Data ? ID3.getTimeStamp(id3Data) : undefined;
  69. const length = data.length;
  70.  
  71. if (this.frameIndex === 0 || this.initPTS === null) {
  72. this.initPTS = initPTSFn(timestamp, timeOffset);
  73. }
  74.  
  75. // more expressive than alternative: id3Data?.length
  76. if (id3Data && id3Data.length > 0) {
  77. id3Track.samples.push({
  78. pts: this.initPTS,
  79. dts: this.initPTS,
  80. data: id3Data,
  81. type: MetadataSchema.audioId3,
  82. });
  83. }
  84.  
  85. pts = this.initPTS;
  86.  
  87. while (offset < length) {
  88. if (this.canParse(data, offset)) {
  89. const frame = this.appendFrame(track, data, offset);
  90. if (frame) {
  91. this.frameIndex++;
  92. pts = frame.sample.pts;
  93. offset += frame.length;
  94. lastDataIndex = offset;
  95. } else {
  96. offset = length;
  97. }
  98. } else if (ID3.canParse(data, offset)) {
  99. // after a ID3.canParse, a call to ID3.getID3Data *should* always returns some data
  100. id3Data = ID3.getID3Data(data, offset)!;
  101. id3Track.samples.push({
  102. pts: pts,
  103. dts: pts,
  104. data: id3Data,
  105. type: MetadataSchema.audioId3,
  106. });
  107. offset += id3Data.length;
  108. lastDataIndex = offset;
  109. } else {
  110. offset++;
  111. }
  112. if (offset === length && lastDataIndex !== length) {
  113. const partialData = sliceUint8(data, lastDataIndex);
  114. if (this.cachedData) {
  115. this.cachedData = appendUint8Array(this.cachedData, partialData);
  116. } else {
  117. this.cachedData = partialData;
  118. }
  119. }
  120. }
  121.  
  122. return {
  123. audioTrack: track,
  124. videoTrack: dummyTrack() as DemuxedVideoTrack,
  125. id3Track,
  126. textTrack: dummyTrack() as DemuxedUserdataTrack,
  127. };
  128. }
  129.  
  130. demuxSampleAes(
  131. data: Uint8Array,
  132. keyData: KeyData,
  133. timeOffset: number
  134. ): Promise<DemuxerResult> {
  135. return Promise.reject(
  136. new Error(`[${this}] This demuxer does not support Sample-AES decryption`)
  137. );
  138. }
  139.  
  140. flush(timeOffset: number): DemuxerResult {
  141. // Parse cache in case of remaining frames.
  142. const cachedData = this.cachedData;
  143. if (cachedData) {
  144. this.cachedData = null;
  145. this.demux(cachedData, 0);
  146. }
  147.  
  148. this.frameIndex = 0;
  149.  
  150. return {
  151. audioTrack: this._audioTrack,
  152. videoTrack: dummyTrack() as DemuxedVideoTrack,
  153. id3Track: this._id3Track,
  154. textTrack: dummyTrack() as DemuxedUserdataTrack,
  155. };
  156. }
  157.  
  158. destroy() {}
  159. }
  160.  
  161. /**
  162. * Initialize PTS
  163. * <p>
  164. * use timestamp unless it is undefined, NaN or Infinity
  165. * </p>
  166. */
  167. export const initPTSFn = (
  168. timestamp: number | undefined,
  169. timeOffset: number
  170. ): number => {
  171. return Number.isFinite(timestamp as number)
  172. ? timestamp! * 90
  173. : timeOffset * 90000;
  174. };
  175. export default BaseAudioDemuxer;