diff --git a/dev/convert.html b/dev/convert.html
index f85cc61..c94dbb9 100644
--- a/dev/convert.html
+++ b/dev/convert.html
@@ -96,7 +96,8 @@
input,
output,
audio: {
- discard: true,
+ codec: 'aac',
+ forceTranscode: true,
//forceTranscode: true,
//sampleFormat: 's16',
},
@@ -125,7 +126,7 @@
},
*/
video: {
- //discard: true,
+ discard: true,
},
tags: {} ?? {
title: 'Bigggy',
@@ -145,7 +146,9 @@
}
},
trim: {
- end: 10,
+ start: 300.14984567374756 - 100,
+ end: 310.1548298151939 - 100,
+ //end: 10,
//start: startTime,
//end: startTime + 2,
},
diff --git a/docs/index.md b/docs/index.md
index 94351ff..a67c670 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -113,6 +113,7 @@ const sponsors = {
],
individual: [
{ image: '/sponsors/pintura-labs.png', name: 'Pintura Labs', url: 'https://pqina.nl/pintura/' },
+ { image: 'https://avatars.githubusercontent.com/u/189556080', name: 'Cursorful', url: 'https://github.com/cursorful' },
{ image: 'https://avatars.githubusercontent.com/u/11537072', name: 'Threema', url: 'https://threema.com/en' },
{ image: 'https://avatars.githubusercontent.com/u/82552321', name: 'Polotno', url: 'https://github.com/polotno-project' },
{ image: 'https://avatars.githubusercontent.com/u/489051', name: 'Roman Rädle', url: 'https://github.com/raedle' },
@@ -134,6 +135,7 @@ const sponsors = {
{ image: 'https://avatars.githubusercontent.com/u/255616819', name: 'cronischarles-del', url: 'https://github.com/cronischarles-del' },
{ image: 'https://avatars.githubusercontent.com/u/37973863', name: 'Gomi', url: 'https://github.com/gxy5202' },
{ image: 'https://avatars.githubusercontent.com/u/36898190', name: 'jepcd', url: 'https://github.com/jepcd' },
+ { image: 'https://avatars.githubusercontent.com/u/695166', name: 'Takenori Nakagawa', url: 'https://github.com/ww24' },
{ image: 'https://avatars.githubusercontent.com/u/63088713', name: 'taf2000', url: 'https://github.com/taf2000' },
{ image: 'https://avatars.githubusercontent.com/u/58149663', name: 'H7GhosT', url: 'https://github.com/H7GhosT' },
{ image: 'https://avatars.githubusercontent.com/u/91711202', name: 'ihasq', url: 'https://github.com/ihasq' },
diff --git a/package-lock.json b/package-lock.json
index 0e4eb7d..11177a6 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "mediabunny",
- "version": "1.43.1",
+ "version": "1.44.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "mediabunny",
- "version": "1.43.1",
+ "version": "1.44.2",
"license": "MPL-2.0",
"workspaces": [
"packages/*"
@@ -8278,9 +8278,9 @@
}
},
"node_modules/mediabunny": {
- "version": "1.43.0",
- "resolved": "https://registry.npmjs.org/mediabunny/-/mediabunny-1.43.0.tgz",
- "integrity": "sha512-QGj6PcXXQvhs+29Pl25z3THv17g31k18zHUrJ25UQc5KWVN0vMKbMbB8KuHXNcseALIhbs6nh+wskimaeH/EEw==",
+ "version": "1.44.0",
+ "resolved": "https://registry.npmjs.org/mediabunny/-/mediabunny-1.44.0.tgz",
+ "integrity": "sha512-rs54ixLAe5MSa6q35MVsVXyXZ+aYkEZYhAw57bseil+mmxFuqAS8YrELaWBcsqAJLCTb8Tse4AoYEhtkwCHYlQ==",
"license": "MPL-2.0",
"peer": true,
"workspaces": [
@@ -12851,7 +12851,7 @@
},
"packages/aac-encoder": {
"name": "@mediabunny/aac-encoder",
- "version": "1.43.1",
+ "version": "1.44.1",
"license": "MPL-2.0",
"devDependencies": {
"@types/emscripten": "^1.40.1"
@@ -12866,7 +12866,7 @@
},
"packages/ac3": {
"name": "@mediabunny/ac3",
- "version": "1.43.1",
+ "version": "1.44.1",
"license": "MPL-2.0",
"devDependencies": {
"@types/emscripten": "^1.40.1"
@@ -12881,7 +12881,7 @@
},
"packages/flac-encoder": {
"name": "@mediabunny/flac-encoder",
- "version": "1.43.1",
+ "version": "1.44.1",
"license": "MPL-2.0",
"devDependencies": {
"@types/emscripten": "^1.40.1"
@@ -12896,7 +12896,7 @@
},
"packages/mp3-encoder": {
"name": "@mediabunny/mp3-encoder",
- "version": "1.43.1",
+ "version": "1.44.1",
"license": "MPL-2.0",
"devDependencies": {
"@types/emscripten": "^1.40.1"
diff --git a/package.json b/package.json
index 9199a69..bf48225 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "mediabunny",
"author": "Vanilagy",
- "version": "1.43.1",
+ "version": "1.44.2",
"description": "Pure TypeScript media toolkit for reading, writing, and converting media files, directly in the browser.",
"type": "module",
"workspaces": [
diff --git a/packages/aac-encoder/package.json b/packages/aac-encoder/package.json
index 40b21e5..9a770f4 100644
--- a/packages/aac-encoder/package.json
+++ b/packages/aac-encoder/package.json
@@ -1,7 +1,7 @@
{
"name": "@mediabunny/aac-encoder",
"author": "Vanilagy",
- "version": "1.43.1",
+ "version": "1.44.2",
"description": "AAC encoder extension for Mediabunny, based on FFmpeg.",
"main": "./dist/bundles/mediabunny-aac-encoder.mjs",
"module": "./dist/bundles/mediabunny-aac-encoder.mjs",
diff --git a/packages/ac3/package.json b/packages/ac3/package.json
index 1c483fe..973d354 100644
--- a/packages/ac3/package.json
+++ b/packages/ac3/package.json
@@ -1,7 +1,7 @@
{
"name": "@mediabunny/ac3",
"author": "Vanilagy",
- "version": "1.43.1",
+ "version": "1.44.2",
"description": "AC-3 and E-AC-3 (Dolby Digital) decoder and encoder extension for Mediabunny, based on FFmpeg.",
"main": "./dist/bundles/mediabunny-ac3.mjs",
"module": "./dist/bundles/mediabunny-ac3.mjs",
diff --git a/packages/flac-encoder/package.json b/packages/flac-encoder/package.json
index f0d2bd7..a0d7950 100644
--- a/packages/flac-encoder/package.json
+++ b/packages/flac-encoder/package.json
@@ -1,7 +1,7 @@
{
"name": "@mediabunny/flac-encoder",
"author": "Vanilagy",
- "version": "1.43.1",
+ "version": "1.44.2",
"description": "FLAC encoder extension for Mediabunny, based on libFLAC.",
"main": "./dist/bundles/mediabunny-flac-encoder.mjs",
"module": "./dist/bundles/mediabunny-flac-encoder.mjs",
diff --git a/packages/mp3-encoder/package.json b/packages/mp3-encoder/package.json
index 619e2bd..97b4dc1 100644
--- a/packages/mp3-encoder/package.json
+++ b/packages/mp3-encoder/package.json
@@ -1,7 +1,7 @@
{
"name": "@mediabunny/mp3-encoder",
"author": "Vanilagy",
- "version": "1.43.1",
+ "version": "1.44.2",
"description": "MP3 encoder extension for Mediabunny, based on LAME.",
"main": "./dist/bundles/mediabunny-mp3-encoder.mjs",
"module": "./dist/bundles/mediabunny-mp3-encoder.mjs",
diff --git a/src/adts/adts-muxer.ts b/src/adts/adts-muxer.ts
index def99c2..d0e49a0 100644
--- a/src/adts/adts-muxer.ts
+++ b/src/adts/adts-muxer.ts
@@ -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) {
diff --git a/src/conversion.ts b/src/conversion.ts
index 36948f2..f286ade 100644
--- a/src/conversion.ts
+++ b/src/conversion.ts
@@ -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);
diff --git a/src/flac/flac-muxer.ts b/src/flac/flac-muxer.ts
index b022dbe..02e2067 100644
--- a/src/flac/flac-muxer.ts
+++ b/src/flac/flac-muxer.ts
@@ -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',
diff --git a/src/hls/hls-muxer.ts b/src/hls/hls-muxer.ts
index f5d88e9..fb2e39e 100644
--- a/src/hls/hls-muxer.ts
+++ b/src/hls/hls-muxer.ts
@@ -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,
);
}
diff --git a/src/isobmff/isobmff-muxer.ts b/src/isobmff/isobmff-muxer.ts
index d456d04..42c6ce8 100644
--- a/src/isobmff/isobmff-muxer.ts
+++ b/src/isobmff/isobmff-muxer.ts
@@ -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);
diff --git a/src/matroska/matroska-muxer.ts b/src/matroska/matroska-muxer.ts
index da60565..dabeaa4 100644
--- a/src/matroska/matroska-muxer.ts
+++ b/src/matroska/matroska-muxer.ts
@@ -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,
diff --git a/src/media-source.ts b/src/media-source.ts
index c24fd79..c739cc2 100644
--- a/src/media-source.ts
+++ b/src/media-source.ts
@@ -78,11 +78,6 @@ export abstract class MediaSource {
_closingPromise: Promise | 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) => {
diff --git a/src/mp3/mp3-muxer.ts b/src/mp3/mp3-muxer.ts
index 97546b3..f1a1413 100644
--- a/src/mp3/mp3-muxer.ts
+++ b/src/mp3/mp3-muxer.ts
@@ -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());
diff --git a/src/mpeg-ts/mpeg-ts-muxer.ts b/src/mpeg-ts/mpeg-ts-muxer.ts
index dfa0f7e..014918a 100644
--- a/src/mpeg-ts/mpeg-ts-muxer.ts
+++ b/src/mpeg-ts/mpeg-ts-muxer.ts
@@ -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',
});
diff --git a/src/muxer.ts b/src/muxer.ts
index fc9df5c..b945770 100644
--- a/src/muxer.ts
+++ b/src/muxer.ts
@@ -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;
}
}
diff --git a/src/ogg/ogg-muxer.ts b/src/ogg/ogg-muxer.ts
index 987eef8..7798f84 100644
--- a/src/ogg/ogg-muxer.ts
+++ b/src/ogg/ogg-muxer.ts
@@ -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;
diff --git a/src/resample.ts b/src/resample.ts
index 727492d..4686a9d 100644
--- a/src/resample.ts
+++ b/src/resample.ts
@@ -20,7 +20,6 @@ export class AudioResampler {
targetSampleRate: number;
sourceNumberOfChannels: number | null = null;
targetNumberOfChannels: number;
- startTime: number;
endTime: number;
onSample: (sample: AudioSample) => Promise;
@@ -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,
});
diff --git a/src/wave/wave-muxer.ts b/src/wave/wave-muxer.ts
index 7e43a0f..68b1072 100644
--- a/src/wave/wave-muxer.ts
+++ b/src/wave/wave-muxer.ts
@@ -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(
diff --git a/test/browser/conversion.test.ts b/test/browser/conversion.test.ts
index 561fe4f..ceab56c 100644
--- a/test/browser/conversion.test.ts
+++ b/test/browser/conversion.test.ts
@@ -1,6 +1,12 @@
import { ALL_FORMATS } from '../../src/input-format.js';
import { Input } from '../../src/input.js';
-import { AdtsOutputFormat, HlsOutputFormat, Mp4OutputFormat, MpegTsOutputFormat } from '../../src/output-format.js';
+import {
+ AdtsOutputFormat,
+ HlsOutputFormat,
+ Mp4OutputFormat,
+ MpegTsOutputFormat,
+ WavOutputFormat,
+} from '../../src/output-format.js';
import { Output, OutputTrackGroup } from '../../src/output.js';
import { BufferSource, CustomPathedSource, UrlSource } from '../../src/source.js';
import { expect, test } from 'vitest';
@@ -369,3 +375,30 @@ test('HLS track assignability can be overridden', async () => {
expect(newMasterPlayist).not.toBe(masterPlayist);
expect(newMasterPlayist.match(/\.m3u8/g)?.length).toBe(1);
});
+
+test('Fractional audio sample boundary', async () => {
+ using input = new Input({
+ source: new UrlSource('/trim-buck-bunny-ffmpeg.ts'),
+ formats: ALL_FORMATS,
+ });
+
+ const output = new Output({
+ format: new WavOutputFormat(),
+ target: new BufferTarget(),
+ });
+
+ const conversion = await Conversion.init({
+ input,
+ output,
+ video: {
+ discard: true,
+ },
+ audio: {
+ forceTranscode: true,
+ },
+ trim: {
+ start: 0.4 / 48000,
+ },
+ });
+ await conversion.execute();
+});