Home Reference Source

src/demux/base-audio-demuxer.ts

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