src/remux/passthrough-remuxer.ts
import type { InitData } from '../utils/mp4-tools';
import {
getDuration,
getStartDTS,
offsetStartDTS,
parseInitSegment,
} from '../utils/mp4-tools';
import { logger } from '../utils/logger';
import type { TrackSet } from '../types/track';
import type {
InitSegmentData,
RemuxedTrack,
Remuxer,
RemuxerResult,
} from '../types/remuxer';
import type {
DemuxedAudioTrack,
DemuxedTrack,
PassthroughVideoTrack,
} from '../types/demuxer';
class PassThroughRemuxer implements Remuxer {
private emitInitSegment: boolean = false;
private audioCodec?: string;
private videoCodec?: string;
private initData?: InitData;
private initPTS?: number;
private initTracks?: TrackSet;
private lastEndDTS: number | null = null;
destroy() {}
resetTimeStamp(defaultInitPTS) {
this.initPTS = defaultInitPTS;
this.lastEndDTS = null;
}
resetNextTimestamp() {
this.lastEndDTS = null;
}
resetInitSegment(
initSegment: Uint8Array,
audioCodec: string | undefined,
videoCodec: string | undefined
) {
this.audioCodec = audioCodec;
this.videoCodec = videoCodec;
this.generateInitSegment(initSegment);
this.emitInitSegment = true;
}
generateInitSegment(initSegment: Uint8Array): void {
let { audioCodec, videoCodec } = this;
if (!initSegment || !initSegment.byteLength) {
this.initTracks = undefined;
this.initData = undefined;
return;
}
const initData = (this.initData = parseInitSegment(initSegment));
// default audio codec if nothing specified
// TODO : extract that from initsegment
if (!audioCodec) {
audioCodec = 'mp4a.40.5';
}
if (!videoCodec) {
videoCodec = 'avc1.42e01e';
}
const tracks: TrackSet = {};
if (initData.audio && initData.video) {
tracks.audiovideo = {
container: 'video/mp4',
codec: audioCodec + ',' + videoCodec,
initSegment,
id: 'main',
};
} else if (initData.audio) {
tracks.audio = {
container: 'audio/mp4',
codec: audioCodec,
initSegment,
id: 'audio',
};
} else if (initData.video) {
tracks.video = {
container: 'video/mp4',
codec: videoCodec,
initSegment,
id: 'main',
};
} else {
logger.warn(
'[passthrough-remuxer.ts]: initSegment does not contain moov or trak boxes.'
);
}
this.initTracks = tracks;
}
remux(
audioTrack: DemuxedAudioTrack,
videoTrack: PassthroughVideoTrack,
id3Track: DemuxedTrack,
textTrack: DemuxedTrack,
timeOffset: number
): RemuxerResult {
let { initPTS, lastEndDTS } = this;
const result: RemuxerResult = {
audio: undefined,
video: undefined,
text: textTrack,
id3: id3Track,
initSegment: undefined,
};
// If we haven't yet set a lastEndDTS, or it was reset, set it to the provided timeOffset. We want to use the
// lastEndDTS over timeOffset whenever possible; during progressive playback, the media source will not update
// the media duration (which is what timeOffset is provided as) before we need to process the next chunk.
if (!Number.isFinite(lastEndDTS!)) {
lastEndDTS = this.lastEndDTS = timeOffset || 0;
}
// The binary segment data is added to the videoTrack in the mp4demuxer. We don't check to see if the data is only
// audio or video (or both); adding it to video was an arbitrary choice.
const data = videoTrack.samples;
if (!data || !data.length) {
return result;
}
const initSegment: InitSegmentData = {
initPTS: undefined,
timescale: 1,
};
let initData = this.initData;
if (!initData || !initData.length) {
this.generateInitSegment(data);
initData = this.initData;
}
if (!initData || !initData.length) {
// We can't remux if the initSegment could not be generated
logger.warn('[passthrough-remuxer.ts]: Failed to generate initSegment.');
return result;
}
if (this.emitInitSegment) {
initSegment.tracks = this.initTracks as TrackSet;
this.emitInitSegment = false;
}
if (!Number.isFinite(initPTS!)) {
this.initPTS = initSegment.initPTS = initPTS = computeInitPTS(
initData,
data,
lastEndDTS
);
}
const duration = getDuration(data, initData);
const startDTS = lastEndDTS as number;
const endDTS = duration + startDTS;
offsetStartDTS(initData, data, initPTS as number);
if (duration > 0) {
this.lastEndDTS = endDTS;
} else {
logger.warn('Duration parsed from mp4 should be greater than zero');
this.resetNextTimestamp();
}
const hasAudio = !!initData.audio;
const hasVideo = !!initData.video;
let type: any = '';
if (hasAudio) {
type += 'audio';
}
if (hasVideo) {
type += 'video';
}
const track: RemuxedTrack = {
data1: data,
startPTS: startDTS,
startDTS,
endPTS: endDTS,
endDTS,
type,
hasAudio,
hasVideo,
nb: 1,
dropped: 0,
};
result.audio = track.type === 'audio' ? track : undefined;
result.video = track.type !== 'audio' ? track : undefined;
result.text = textTrack;
result.id3 = id3Track;
result.initSegment = initSegment;
return result;
}
}
const computeInitPTS = (initData, data, timeOffset) =>
getStartDTS(initData, data) - timeOffset;
export default PassThroughRemuxer;