src/demux/base-audio-demuxer.ts
import * as ID3 from '../demux/id3';
import type {
DemuxerResult,
Demuxer,
DemuxedAudioTrack,
AppendedAudioFrame,
DemuxedMetadataTrack,
DemuxedAvcTrack,
DemuxedUserdataTrack,
KeyData,
} from '../types/demuxer';
import { dummyTrack } from './dummy-demuxed-track';
import { appendUint8Array } from '../utils/mp4-tools';
import { sliceUint8 } from '../utils/typed-array';
class BaseAudioDemuxer implements Demuxer {
protected _audioTrack!: DemuxedAudioTrack;
protected _id3Track!: DemuxedMetadataTrack;
protected frameIndex: number = 0;
protected cachedData: Uint8Array | null = null;
protected initPTS: number | null = null;
resetInitSegment(audioCodec: string, videoCodec: string, duration: number) {
this._id3Track = {
type: 'id3',
id: 0,
pid: -1,
inputTimeScale: 90000,
sequenceNumber: 0,
samples: [],
dropped: 0,
};
}
resetTimeStamp() {}
resetContiguity(): void {}
canParse(data: Uint8Array, offset: number): boolean {
return false;
}
appendFrame(
track: DemuxedAudioTrack,
data: Uint8Array,
offset: number
): AppendedAudioFrame | void {}
// feed incoming data to the front of the parsing pipeline
demux(data: Uint8Array, timeOffset: number): DemuxerResult {
if (this.cachedData) {
data = appendUint8Array(this.cachedData, data);
this.cachedData = null;
}
let id3Data: Uint8Array | undefined = ID3.getID3Data(data, 0);
let offset = id3Data ? id3Data.length : 0;
let lastDataIndex;
let pts;
const track = this._audioTrack;
const id3Track = this._id3Track;
const timestamp = id3Data ? ID3.getTimeStamp(id3Data) : undefined;
const length = data.length;
if (this.frameIndex === 0 || this.initPTS === null) {
this.initPTS = initPTSFn(timestamp, timeOffset);
}
// more expressive than alternative: id3Data?.length
if (id3Data && id3Data.length > 0) {
id3Track.samples.push({
pts: this.initPTS,
dts: this.initPTS,
data: id3Data,
});
}
pts = this.initPTS;
while (offset < length) {
if (this.canParse(data, offset)) {
const frame = this.appendFrame(track, data, offset);
if (frame) {
this.frameIndex++;
pts = frame.sample.pts;
offset += frame.length;
lastDataIndex = offset;
} else {
offset = length;
}
} else if (ID3.canParse(data, offset)) {
// after a ID3.canParse, a call to ID3.getID3Data *should* always returns some data
id3Data = ID3.getID3Data(data, offset)!;
id3Track.samples.push({ pts: pts, dts: pts, data: id3Data });
offset += id3Data.length;
lastDataIndex = offset;
} else {
offset++;
}
if (offset === length && lastDataIndex !== length) {
const partialData = sliceUint8(data, lastDataIndex);
if (this.cachedData) {
this.cachedData = appendUint8Array(this.cachedData, partialData);
} else {
this.cachedData = partialData;
}
}
}
return {
audioTrack: track,
avcTrack: dummyTrack() as DemuxedAvcTrack,
id3Track,
textTrack: dummyTrack() as DemuxedUserdataTrack,
};
}
demuxSampleAes(
data: Uint8Array,
keyData: KeyData,
timeOffset: number
): Promise<DemuxerResult> {
return Promise.reject(
new Error(`[${this}] This demuxer does not support Sample-AES decryption`)
);
}
flush(timeOffset: number): DemuxerResult {
// Parse cache in case of remaining frames.
if (this.cachedData) {
this.demux(this.cachedData, 0);
}
this.frameIndex = 0;
this.cachedData = null;
return {
audioTrack: this._audioTrack,
avcTrack: dummyTrack() as DemuxedAvcTrack,
id3Track: this._id3Track,
textTrack: dummyTrack() as DemuxedUserdataTrack,
};
}
destroy() {}
}
/**
* Initialize PTS
* <p>
* use timestamp unless it is undefined, NaN or Infinity
* </p>
*/
export const initPTSFn = (
timestamp: number | undefined,
timeOffset: number
): number => {
return Number.isFinite(timestamp as number)
? timestamp! * 90
: timeOffset * 90000;
};
export default BaseAudioDemuxer;