Merge branch 'main' into server

This commit is contained in:
Vanilagy
2026-05-11 21:36:38 +02:00
22 changed files with 104 additions and 75 deletions
+1 -1
View File
@@ -60,7 +60,7 @@ export class AdtsMuxer extends Muxer {
const release = await this.mutex.acquire();
try {
this.validateAndNormalizeTimestamp(track, packet.timestamp, packet.type === 'key');
this.validateTimestamp(track, packet.timestamp, packet.type === 'key');
// First packet - determine input format from metadata
if (this.inputIsAdts === null) {
+1
View File
@@ -1754,6 +1754,7 @@ export class Conversion {
startTime: this._startTimestamp,
endTime: this._endTimestamp,
onSample: async (sample) => {
assert(sample.timestamp >= this._startTimestamp);
sample.setTimestamp(sample.timestamp - this._startTimestamp);
await this._registerAudioSample(trackOptions, outputTrackId, source, sample);
+1 -1
View File
@@ -214,7 +214,7 @@ export class FlacMuxer extends Muxer {
const release = await this.mutex.acquire();
try {
this.validateAndNormalizeTimestamp(
this.validateTimestamp(
track,
packet.timestamp,
packet.type === 'key',
+8 -12
View File
@@ -614,17 +614,15 @@ export class HlsMuxer extends Muxer {
const release = await playlist.mutex.acquire();
try {
const timestamp = this.validateAndNormalizeTimestamp(track, packet.timestamp, packet.type === 'key');
const adjustedPacket = packet.clone({ timestamp });
trackData.packets.push(adjustedPacket);
this.validateTimestamp(track, packet.timestamp, packet.type === 'key');
trackData.packets.push(packet);
if (playlist.currentSegmentStartTimestamp === null) {
playlist.currentSegmentStartTimestamp = adjustedPacket.timestamp;
playlist.currentSegmentStartTimestamp = packet.timestamp;
} else if (!playlist.currentSegmentStartTimestampIsFixed) {
playlist.currentSegmentStartTimestamp = Math.min(
playlist.currentSegmentStartTimestamp,
adjustedPacket.timestamp,
packet.timestamp,
);
}
@@ -645,17 +643,15 @@ export class HlsMuxer extends Muxer {
const release = await playlist.mutex.acquire();
try {
const timestamp = this.validateAndNormalizeTimestamp(track, packet.timestamp, packet.type === 'key');
const adjustedPacket = packet.clone({ timestamp });
trackData.packets.push(adjustedPacket);
this.validateTimestamp(track, packet.timestamp, packet.type === 'key');
trackData.packets.push(packet);
if (playlist.currentSegmentStartTimestamp === null) {
playlist.currentSegmentStartTimestamp = adjustedPacket.timestamp;
playlist.currentSegmentStartTimestamp = packet.timestamp;
} else if (!playlist.currentSegmentStartTimestampIsFixed) {
playlist.currentSegmentStartTimestamp = Math.min(
playlist.currentSegmentStartTimestamp,
adjustedPacket.timestamp,
packet.timestamp,
);
}
+6 -4
View File
@@ -600,7 +600,7 @@ export class IsobmffMuxer extends Muxer {
packetData = concatNalUnitsInLengthPrefixed(nalUnits, 4);
}
const timestamp = this.validateAndNormalizeTimestamp(
this.validateTimestamp(
trackData.track,
packet.timestamp,
packet.type === 'key',
@@ -608,7 +608,7 @@ export class IsobmffMuxer extends Muxer {
const internalSample = this.createSampleForTrack(
trackData,
packetData,
timestamp,
packet.timestamp,
packet.duration,
packet.type,
);
@@ -638,11 +638,13 @@ export class IsobmffMuxer extends Muxer {
packetData = packetData.subarray(headerLength);
}
let timestamp = this.validateAndNormalizeTimestamp(
this.validateTimestamp(
trackData.track,
packet.timestamp,
packet.type === 'key',
);
let timestamp = packet.timestamp;
let duration = packet.duration;
if (trackData.info.requiresPcmTransformation) {
@@ -718,7 +720,7 @@ export class IsobmffMuxer extends Muxer {
try {
const trackData = this.getSubtitleTrackData(track, meta);
this.validateAndNormalizeTimestamp(trackData.track, cue.timestamp, true);
this.validateTimestamp(trackData.track, cue.timestamp, true);
if (track.source._codec === 'webvtt') {
trackData.cueQueue.push(cue);
+8 -6
View File
@@ -907,7 +907,9 @@ export class MatroskaMuxer extends Muxer {
const trackData = this.getVideoTrackData(track, packet, meta);
const isKeyFrame = packet.type === 'key';
let timestamp = this.validateAndNormalizeTimestamp(trackData.track, packet.timestamp, isKeyFrame);
this.validateTimestamp(trackData.track, packet.timestamp, isKeyFrame);
let timestamp = packet.timestamp;
let duration = packet.duration;
if (track.metadata.frameRate !== undefined) {
@@ -950,9 +952,9 @@ export class MatroskaMuxer extends Muxer {
}
const isKeyFrame = packet.type === 'key';
const timestamp = this.validateAndNormalizeTimestamp(trackData.track, packet.timestamp, isKeyFrame);
const audioChunk = this.createInternalChunk(packetData, timestamp, packet.duration, packet.type);
this.validateTimestamp(trackData.track, packet.timestamp, isKeyFrame);
const audioChunk = this.createInternalChunk(packetData, packet.timestamp, packet.duration, packet.type);
trackData.chunkQueue.push(audioChunk);
await this.interleaveChunks();
} finally {
@@ -966,10 +968,10 @@ export class MatroskaMuxer extends Muxer {
try {
const trackData = this.getSubtitleTrackData(track, meta);
const timestamp = this.validateAndNormalizeTimestamp(trackData.track, cue.timestamp, true);
this.validateTimestamp(trackData.track, cue.timestamp, true);
let bodyText = cue.text;
const timestampMs = Math.round(timestamp * 1000);
const timestampMs = Math.round(cue.timestamp * 1000);
// Replace in-body timestamps so that they're relative to the cue start time
inlineTimestampRegex.lastIndex = 0;
@@ -985,7 +987,7 @@ export class MatroskaMuxer extends Muxer {
const subtitleChunk = this.createInternalChunk(
body,
timestamp,
cue.timestamp,
cue.duration,
'key',
additions.trim() ? textEncoder.encode(additions) : null,
+8 -17
View File
@@ -78,11 +78,6 @@ export abstract class MediaSource {
_closingPromise: Promise<void> | null = null;
/** @internal */
_closed = false;
/**
* @internal
* A time offset in seconds that is added to all timestamps generated by this source.
*/
_timestampOffset = 0;
/** @internal */
_ensureValidAdd() {
@@ -1664,7 +1659,7 @@ export class MediaStreamVideoTrackSource extends VideoSource {
let frameCount = 0;
let errored = false;
let lastSampleTimestamp: number | null = null;
let pauseOffset = 0;
let timestampOffset = 0;
const tick = () => {
assert(frameRate !== null);
@@ -1732,9 +1727,7 @@ export class MediaStreamVideoTrackSource extends VideoSource {
// pause. Doing it like this instead of simply keeping track of the paused time is better since
// it retains the frame rate of the underlying source.
const timeDelta = currentTimestamp - lastSampleTimestamp;
// We modify this field instead of _timestampOffset since we still might have data in flight
// in the encoder, with which we don't want to mess.
pauseOffset -= timeDelta;
timestampOffset -= timeDelta;
}
lastSampleTimestamp = currentTimestamp;
}
@@ -1762,7 +1755,7 @@ export class MediaStreamVideoTrackSource extends VideoSource {
}
}
this._timestampOffset = target - firstVideoFrameTimestamp;
timestampOffset = target - firstVideoFrameTimestamp;
}
lastSampleTimestamp = currentTimestamp;
@@ -1774,7 +1767,7 @@ export class MediaStreamVideoTrackSource extends VideoSource {
}
const sample = new VideoSample(videoFrame, {
timestamp: currentTimestamp + pauseOffset,
timestamp: currentTimestamp + timestampOffset,
});
void this._encoder.add(sample, true)
@@ -2763,7 +2756,7 @@ export class MediaStreamAudioTrackSource extends AudioSource {
let firstAudioDataTimestamp: number | null = null;
let errored = false;
let lastSampleTimestamp: number | null = null;
let pauseOffset = 0;
let timestampOffset = 0;
const onAudioSample = (audioSample: AudioSample) => {
if (errored) {
@@ -2781,9 +2774,7 @@ export class MediaStreamAudioTrackSource extends AudioSource {
// the pause. Doing it like this instead of simply keeping track of the paused time is better
// since it retains the sample rate of the underlying source.
const timeDelta = currentTimestamp - lastSampleTimestamp;
// We modify this field instead of _timestampOffset since we still might have data in flight
// in the encoder, with which we don't want to mess.
pauseOffset -= timeDelta;
timestampOffset -= timeDelta;
}
lastSampleTimestamp = currentTimestamp;
}
@@ -2811,7 +2802,7 @@ export class MediaStreamAudioTrackSource extends AudioSource {
}
}
this._timestampOffset = target - firstAudioDataTimestamp;
timestampOffset = target - firstAudioDataTimestamp;
}
lastSampleTimestamp = currentTimestamp;
@@ -2822,7 +2813,7 @@ export class MediaStreamAudioTrackSource extends AudioSource {
return;
}
audioSample.setTimestamp(currentTimestamp + pauseOffset);
audioSample.setTimestamp(currentTimestamp + timestampOffset);
void this._encoder.add(audioSample, true)
.catch((error) => {
+1 -1
View File
@@ -110,7 +110,7 @@ export class Mp3Muxer extends Muxer {
this.frameCount++;
}
this.validateAndNormalizeTimestamp(track, packet.timestamp, packet.type === 'key');
this.validateTimestamp(track, packet.timestamp, packet.type === 'key');
if (writeXingHeader) {
this.framePositions.push(this.writer.getPos());
+4 -4
View File
@@ -227,7 +227,7 @@ export class MpegTsMuxer extends Muxer {
try {
const trackData = this.getVideoTrackData(track, meta);
const timestamp = this.validateAndNormalizeTimestamp(
this.validateTimestamp(
trackData.track,
packet.timestamp,
packet.type === 'key',
@@ -241,7 +241,7 @@ export class MpegTsMuxer extends Muxer {
trackData.timestampProcessingQueue.push({
data: preparedData,
presentationTimestamp: timestamp,
presentationTimestamp: packet.timestamp,
decodeTimestamp: null,
isKeyframe: packet.type === 'key',
});
@@ -260,7 +260,7 @@ export class MpegTsMuxer extends Muxer {
try {
const trackData = this.getAudioTrackData(track, meta);
const timestamp = this.validateAndNormalizeTimestamp(
this.validateTimestamp(
trackData.track,
packet.timestamp,
packet.type === 'key',
@@ -274,7 +274,7 @@ export class MpegTsMuxer extends Muxer {
trackData.timestampProcessingQueue.push({
data: preparedData,
presentationTimestamp: timestamp,
presentationTimestamp: packet.timestamp,
decodeTimestamp: null,
isKeyframe: packet.type === 'key',
});
+1 -5
View File
@@ -42,9 +42,7 @@ export abstract class Muxer {
maxTimestampBeforeLastKeyPacket: number | null;
}>();
protected validateAndNormalizeTimestamp(track: OutputTrack, timestampInSeconds: number, isKeyPacket: boolean) {
timestampInSeconds += track.source._timestampOffset;
protected validateTimestamp(track: OutputTrack, timestampInSeconds: number, isKeyPacket: boolean) {
if (timestampInSeconds < 0) {
throw new Error(`Timestamps must be non-negative (got ${timestampInSeconds}s).`);
}
@@ -78,7 +76,5 @@ export abstract class Muxer {
timestampInfo.maxTimestamp = Math.max(timestampInfo.maxTimestamp, timestampInSeconds);
}
return timestampInSeconds;
}
}
+1 -1
View File
@@ -267,7 +267,7 @@ export class OggMuxer extends Muxer {
try {
const trackData = this.getTrackData(track, meta);
this.validateAndNormalizeTimestamp(trackData.track, packet.timestamp, packet.type === 'key');
this.validateTimestamp(trackData.track, packet.timestamp, packet.type === 'key');
const currentTimestampInSamples = trackData.currentTimestampInSamples;
+7 -4
View File
@@ -20,7 +20,6 @@ export class AudioResampler {
targetSampleRate: number;
sourceNumberOfChannels: number | null = null;
targetNumberOfChannels: number;
startTime: number;
endTime: number;
onSample: (sample: AudioSample) => Promise<void>;
@@ -33,6 +32,7 @@ export class AudioResampler {
maxWrittenFrame: number | null = null;
channelMixer!: (sourceData: Float32Array, sourceFrameIndex: number, targetChannelIndex: number) => number;
tempSourceBuffer!: Float32Array;
timestampOffset: number;
constructor(options: {
targetSampleRate: number;
@@ -43,7 +43,6 @@ export class AudioResampler {
}) {
this.targetSampleRate = options.targetSampleRate;
this.targetNumberOfChannels = options.targetNumberOfChannels;
this.startTime = options.startTime;
this.endTime = options.endTime;
this.onSample = options.onSample;
@@ -51,7 +50,11 @@ export class AudioResampler {
this.bufferSizeInSamples = this.bufferSizeInFrames * this.targetNumberOfChannels;
this.outputBuffer = new Float32Array(this.bufferSizeInSamples);
this.bufferStartFrame = Math.floor(this.startTime * this.targetSampleRate);
this.bufferStartFrame = Math.floor(options.startTime * this.targetSampleRate);
// Set to ensure that if the buffer start frame lands on a fractional sample, that the first timestamp still
// comes out as exactly startTime
this.timestampOffset = options.startTime - this.bufferStartFrame / this.targetSampleRate;
}
/**
@@ -273,7 +276,7 @@ export class AudioResampler {
format: 'f32',
sampleRate: this.targetSampleRate,
numberOfChannels: this.targetNumberOfChannels,
timestamp: timestampSeconds,
timestamp: timestampSeconds + this.timestampOffset,
data: outputData,
});
+1 -1
View File
@@ -79,7 +79,7 @@ export class WaveMuxer extends Muxer {
this.headerWritten = true;
}
this.validateAndNormalizeTimestamp(track, packet.timestamp, packet.type === 'key');
this.validateTimestamp(track, packet.timestamp, packet.type === 'key');
if (!this.isRf64 && this.writer.getPos() + packet.data.byteLength >= 2 ** 32) {
throw new Error(