Merge branch 'main' into release

This commit is contained in:
Vanilagy
2025-08-15 21:17:34 +02:00
9 changed files with 68 additions and 22 deletions
+1
View File
@@ -97,6 +97,7 @@ const sponsors = {
{ image: 'https://avatars.githubusercontent.com/u/5913254', name: 'Brandon McConnell', url: 'https://github.com/brandonmcconnell' }, { image: 'https://avatars.githubusercontent.com/u/5913254', name: 'Brandon McConnell', url: 'https://github.com/brandonmcconnell' },
{ image: 'https://avatars.githubusercontent.com/u/9549394', name: 'studnitz', url: 'https://github.com/studnitz' }, { image: 'https://avatars.githubusercontent.com/u/9549394', name: 'studnitz', url: 'https://github.com/studnitz' },
{ image: 'https://avatars.githubusercontent.com/u/30229596', name: 'Pablo Bonilla', url: 'https://github.com/devPablo' }, { image: 'https://avatars.githubusercontent.com/u/30229596', name: 'Pablo Bonilla', url: 'https://github.com/devPablo' },
{ 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/58149663', name: 'H7GhosT', url: 'https://github.com/H7GhosT' },
{ image: 'https://avatars.githubusercontent.com/u/91711202', name: 'ihasq', url: 'https://github.com/ihasq' }, { image: 'https://avatars.githubusercontent.com/u/91711202', name: 'ihasq', url: 'https://github.com/ihasq' },
{ image: 'https://avatars.githubusercontent.com/u/61233224', name: 'Allwhy', url: 'https://github.com/Allwhy' }, { image: 'https://avatars.githubusercontent.com/u/61233224', name: 'Allwhy', url: 'https://github.com/Allwhy' },
+6 -6
View File
@@ -1,12 +1,12 @@
{ {
"name": "mediabunny", "name": "mediabunny",
"version": "1.7.6", "version": "1.8.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "mediabunny", "name": "mediabunny",
"version": "1.7.6", "version": "1.8.0",
"license": "MPL-2.0", "license": "MPL-2.0",
"workspaces": [ "workspaces": [
"packages/*" "packages/*"
@@ -5900,9 +5900,9 @@
} }
}, },
"node_modules/mediabunny": { "node_modules/mediabunny": {
"version": "1.7.5", "version": "1.7.6",
"resolved": "https://registry.npmjs.org/mediabunny/-/mediabunny-1.7.5.tgz", "resolved": "https://registry.npmjs.org/mediabunny/-/mediabunny-1.7.6.tgz",
"integrity": "sha512-H7dH2KCOzP/QDHaaNmgo52pDOAuHfC0aa9w9XKPOzcY6x923vGE2CTOqO9W/WEmf8DCRKReGwupSlWuq9NMLYg==", "integrity": "sha512-PtWscl5vdsBYwtNSO0J21lAGUiweudmTmHabz4NWuQ+mAUvtG1ocupyYpT18IflteVtdtNMqxKKPPbvlcHuk0w==",
"license": "MPL-2.0", "license": "MPL-2.0",
"peer": true, "peer": true,
"workspaces": [ "workspaces": [
@@ -9017,7 +9017,7 @@
}, },
"packages/mp3-encoder": { "packages/mp3-encoder": {
"name": "@mediabunny/mp3-encoder", "name": "@mediabunny/mp3-encoder",
"version": "1.7.6", "version": "1.8.0",
"license": "MPL-2.0", "license": "MPL-2.0",
"devDependencies": { "devDependencies": {
"@types/emscripten": "^1.40.1" "@types/emscripten": "^1.40.1"
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "mediabunny", "name": "mediabunny",
"author": "Vanilagy", "author": "Vanilagy",
"version": "1.7.6", "version": "1.8.0",
"description": "Pure TypeScript media toolkit for reading, writing, and converting media files, directly in the browser.", "description": "Pure TypeScript media toolkit for reading, writing, and converting media files, directly in the browser.",
"type": "module", "type": "module",
"workspaces": [ "workspaces": [
+2 -1
View File
@@ -108,7 +108,7 @@ For simplicity, all built WASM artifacts are included in the repo, since these r
### Prerequisites ### Prerequisites
[Install Emscripten](https://emscripten.org/docs/getting_started/downloads.html). The recommended way is using the emsdk, which involves cloning a repo and running a few commands. [Install Emscripten](https://emscripten.org/docs/getting_started/downloads.html). The recommended way is using the emsdk, which involves cloning a repo and running a few commands. The following commands assume Emscripten is sourced in.
### Compiling LAME: ### Compiling LAME:
@@ -139,6 +139,7 @@ emcc src/lame-bridge.c build/libmp3lame.a \
-s MODULARIZE=1 \ -s MODULARIZE=1 \
-s EXPORT_ES6=1 \ -s EXPORT_ES6=1 \
-s SINGLE_FILE=1 \ -s SINGLE_FILE=1 \
-s ALLOW_MEMORY_GROWTH=1 \
-s ENVIRONMENT=web,worker \ -s ENVIRONMENT=web,worker \
-s EXPORTED_RUNTIME_METHODS=cwrap,HEAPU8 \ -s EXPORTED_RUNTIME_METHODS=cwrap,HEAPU8 \
-s EXPORTED_FUNCTIONS=_malloc,_free \ -s EXPORTED_FUNCTIONS=_malloc,_free \
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "@mediabunny/mp3-encoder", "name": "@mediabunny/mp3-encoder",
"author": "Vanilagy", "author": "Vanilagy",
"version": "1.7.6", "version": "1.8.0",
"description": "MP3 encoder extension for Mediabunny, based on LAME.", "description": "MP3 encoder extension for Mediabunny, based on LAME.",
"main": "./dist/bundles/mediabunny-mp3-encoder.mjs", "main": "./dist/bundles/mediabunny-mp3-encoder.mjs",
"module": "./dist/bundles/mediabunny-mp3-encoder.mjs", "module": "./dist/bundles/mediabunny-mp3-encoder.mjs",
+2 -2
View File
@@ -7,7 +7,7 @@
*/ */
import { CustomAudioEncoder, AudioCodec, AudioSample, EncodedPacket, registerEncoder } from 'mediabunny'; import { CustomAudioEncoder, AudioCodec, AudioSample, EncodedPacket, registerEncoder } from 'mediabunny';
import { FRAME_HEADER_SIZE, readFrameHeader } from '../../../shared/mp3-misc'; import { FRAME_HEADER_SIZE, readFrameHeader, SAMPLING_RATES } from '../../../shared/mp3-misc';
import type { WorkerCommand, WorkerResponse, WorkerResponseData } from './shared'; import type { WorkerCommand, WorkerResponse, WorkerResponseData } from './shared';
// @ts-expect-error An esbuild plugin handles this, TypeScript doesn't need to understand // @ts-expect-error An esbuild plugin handles this, TypeScript doesn't need to understand
import createWorker from './encode.worker'; import createWorker from './encode.worker';
@@ -28,7 +28,7 @@ class Mp3Encoder extends CustomAudioEncoder {
static override supports(codec: AudioCodec, config: AudioDecoderConfig): boolean { static override supports(codec: AudioCodec, config: AudioDecoderConfig): boolean {
return codec === 'mp3' return codec === 'mp3'
&& (config.numberOfChannels === 1 || config.numberOfChannels === 2) && (config.numberOfChannels === 1 || config.numberOfChannels === 2)
&& (config.sampleRate === 32000 || config.sampleRate === 44100 || config.sampleRate === 48000); && Object.values(SAMPLING_RATES).some(x => x.includes(config.sampleRate));
} }
async init() { async init() {
+8 -7
View File
@@ -1334,16 +1334,17 @@ export class AudioBufferSource extends AudioSource {
* @returns A Promise that resolves once the output is ready to receive more samples. You should await this Promise * @returns A Promise that resolves once the output is ready to receive more samples. You should await this Promise
* to respect writer and encoder backpressure. * to respect writer and encoder backpressure.
*/ */
add(audioBuffer: AudioBuffer) { async add(audioBuffer: AudioBuffer) {
if (!(audioBuffer instanceof AudioBuffer)) { if (!(audioBuffer instanceof AudioBuffer)) {
throw new TypeError('audioBuffer must be an AudioBuffer.'); throw new TypeError('audioBuffer must be an AudioBuffer.');
} }
const audioSamples = AudioSample.fromAudioBuffer(audioBuffer, this._accumulatedTime); const iterator = AudioSample._fromAudioBuffer(audioBuffer, this._accumulatedTime);
const promises = audioSamples.map(sample => this._encoder.add(sample, true));
this._accumulatedTime += audioBuffer.duration; this._accumulatedTime += audioBuffer.duration;
return Promise.all(promises);
for (const audioSample of iterator) {
await this._encoder.add(audioSample, true);
}
} }
/** @internal */ /** @internal */
@@ -1466,10 +1467,10 @@ export class MediaStreamAudioTrackSource extends AudioSource {
let totalDuration = 0; let totalDuration = 0;
this._scriptProcessorNode.onaudioprocess = (event) => { this._scriptProcessorNode.onaudioprocess = (event) => {
const audioSamples = AudioSample.fromAudioBuffer(event.inputBuffer, totalDuration); const iterator = AudioSample._fromAudioBuffer(event.inputBuffer, totalDuration);
totalDuration += event.inputBuffer.duration; totalDuration += event.inputBuffer.duration;
for (const audioSample of audioSamples) { for (const audioSample of iterator) {
if (!audioReceived) { if (!audioReceived) {
audioReceived = true; audioReceived = true;
+46 -3
View File
@@ -1067,6 +1067,49 @@ export class AudioSample {
(this.timestamp as number) = newTimestamp; (this.timestamp as number) = newTimestamp;
} }
/** @internal */
static* _fromAudioBuffer(audioBuffer: AudioBuffer, timestamp: number) {
if (!(audioBuffer instanceof AudioBuffer)) {
throw new TypeError('audioBuffer must be an AudioBuffer.');
}
const MAX_FLOAT_COUNT = 48000 * 5; // 5 seconds of mono 48 kHz audio per sample
const numberOfChannels = audioBuffer.numberOfChannels;
const sampleRate = audioBuffer.sampleRate;
const totalFrames = audioBuffer.length;
const maxFramesPerChunk = Math.floor(MAX_FLOAT_COUNT / numberOfChannels);
let currentRelativeFrame = 0;
let remainingFrames = totalFrames;
// Create AudioSamples in a chunked fashion so we don't create huge Float32Arrays
while (remainingFrames > 0) {
const framesToCopy = Math.min(maxFramesPerChunk, remainingFrames);
const chunkData = new Float32Array(numberOfChannels * framesToCopy);
for (let channel = 0; channel < numberOfChannels; channel++) {
audioBuffer.copyFromChannel(
chunkData.subarray(channel * framesToCopy, (channel + 1) * framesToCopy),
channel,
currentRelativeFrame,
);
}
yield new AudioSample({
format: 'f32-planar',
sampleRate,
numberOfFrames: framesToCopy,
numberOfChannels,
timestamp: timestamp + currentRelativeFrame / sampleRate,
data: chunkData,
});
currentRelativeFrame += framesToCopy;
remainingFrames -= framesToCopy;
}
}
/** /**
* Creates AudioSamples from an AudioBuffer, starting at the given timestamp in seconds. Typically creates exactly * Creates AudioSamples from an AudioBuffer, starting at the given timestamp in seconds. Typically creates exactly
* one sample, but may create multiple if the AudioBuffer is exceedingly large. * one sample, but may create multiple if the AudioBuffer is exceedingly large.
@@ -1076,7 +1119,7 @@ export class AudioSample {
throw new TypeError('audioBuffer must be an AudioBuffer.'); throw new TypeError('audioBuffer must be an AudioBuffer.');
} }
const MAX_FLOAT_COUNT = 64 * 1024 * 1024; const MAX_FLOAT_COUNT = 48000 * 5; // 5 seconds of mono 48 kHz audio per sample
const numberOfChannels = audioBuffer.numberOfChannels; const numberOfChannels = audioBuffer.numberOfChannels;
const sampleRate = audioBuffer.sampleRate; const sampleRate = audioBuffer.sampleRate;
@@ -1088,14 +1131,14 @@ export class AudioSample {
const result: AudioSample[] = []; const result: AudioSample[] = [];
// Create AudioData in a chunked fashion so we don't create huge Float32Arrays // Create AudioSamples in a chunked fashion so we don't create huge Float32Arrays
while (remainingFrames > 0) { while (remainingFrames > 0) {
const framesToCopy = Math.min(maxFramesPerChunk, remainingFrames); const framesToCopy = Math.min(maxFramesPerChunk, remainingFrames);
const chunkData = new Float32Array(numberOfChannels * framesToCopy); const chunkData = new Float32Array(numberOfChannels * framesToCopy);
for (let channel = 0; channel < numberOfChannels; channel++) { for (let channel = 0; channel < numberOfChannels; channel++) {
audioBuffer.copyFromChannel( audioBuffer.copyFromChannel(
chunkData.subarray(channel * framesToCopy, channel * framesToCopy + framesToCopy), chunkData.subarray(channel * framesToCopy, (channel + 1) * framesToCopy),
channel, channel,
currentRelativeFrame, currentRelativeFrame,
); );