| ; | |
| /*! | |
| * Copyright (c) 2026-present, Vanilagy and contributors | |
| * | |
| * This Source Code Form is subject to the terms of the Mozilla Public | |
| * License, v. 2.0. If a copy of the MPL was not distributed with this | |
| * file, You can obtain one at https://mozilla.org/MPL/2.0/. | |
| */ | |
| var __importDefault = (this && this.__importDefault) || function (mod) { | |
| return (mod && mod.__esModule) ? mod : { "default": mod }; | |
| }; | |
| Object.defineProperty(exports, "__esModule", { value: true }); | |
| exports.assertNever = exports.registerFlacEncoder = void 0; | |
| const mediabunny_1 = require("mediabunny"); | |
| // @ts-expect-error An esbuild plugin handles this, TypeScript doesn't need to understand | |
| const encode_worker_1 = __importDefault(require("./encode.worker")); | |
| const FLAC_SAMPLE_RATES = [ | |
| 8000, 16000, 22050, 24000, 32000, 44100, 48000, 88200, 96000, 176400, 192000, | |
| ]; | |
| class FlacEncoder extends mediabunny_1.CustomAudioEncoder { | |
| constructor() { | |
| super(...arguments); | |
| this.worker = null; | |
| this.workerError = null; | |
| this.nextMessageId = 0; | |
| this.pendingMessages = new Map(); | |
| this.ctx = null; | |
| this.chunkMetadata = {}; | |
| this.description = null; | |
| this.nextTimestampInSamples = null; | |
| } | |
| static supports(codec, config) { | |
| return codec === 'flac' | |
| && config.numberOfChannels >= 1 | |
| && config.numberOfChannels <= 8 | |
| && FLAC_SAMPLE_RATES.includes(config.sampleRate); | |
| } | |
| async init() { | |
| // eslint-disable-next-line @typescript-eslint/no-unsafe-call | |
| this.worker = (await (0, encode_worker_1.default)()); | |
| const onMessage = (data) => { | |
| const pending = this.pendingMessages.get(data.id); | |
| assert(pending !== undefined); | |
| this.pendingMessages.delete(data.id); | |
| if (data.success) { | |
| pending.resolve(data.data); | |
| } | |
| else { | |
| pending.reject(data.error); | |
| } | |
| }; | |
| // Treats any worker error (like CSP errors) as fatal, rejecting pending and future commands | |
| const onError = (error) => { | |
| this.workerError = error; | |
| for (const pending of this.pendingMessages.values()) { | |
| pending.reject(error); | |
| } | |
| this.pendingMessages.clear(); | |
| this.worker?.terminate(); | |
| this.worker = null; | |
| }; | |
| if (this.worker.addEventListener) { | |
| this.worker.addEventListener('message', event => onMessage(event.data)); | |
| this.worker.addEventListener('error', (event) => { | |
| onError(new Error(event.message || 'FLAC encoder worker failed to load or crashed.')); | |
| }); | |
| } | |
| else { | |
| const nodeWorker = this.worker; | |
| nodeWorker.on('message', onMessage); | |
| nodeWorker.on('error', onError); | |
| } | |
| } | |
| resetInternalState() { | |
| this.nextTimestampInSamples = null; | |
| this.chunkMetadata = { | |
| decoderConfig: { | |
| codec: 'flac', | |
| numberOfChannels: this.config.numberOfChannels, | |
| sampleRate: this.config.sampleRate, | |
| description: this.description, | |
| }, | |
| }; | |
| } | |
| async encode(audioSample) { | |
| if (this.ctx === null) { | |
| // This is the first sample, let's do some init | |
| let bitsPerSample; | |
| switch (audioSample.format) { | |
| case 'u8': | |
| case 'u8-planar': | |
| case 's16': | |
| case 's16-planar': | |
| bitsPerSample = 16; | |
| break; | |
| case 's32': | |
| case 's32-planar': | |
| case 'f32': | |
| case 'f32-planar': | |
| bitsPerSample = 24; | |
| break; | |
| default: | |
| (0, exports.assertNever)(audioSample.format); | |
| assert(false); | |
| } | |
| const result = await this.sendCommand({ | |
| type: 'init', | |
| data: { | |
| numberOfChannels: this.config.numberOfChannels, | |
| sampleRate: this.config.sampleRate, | |
| bitsPerSample, | |
| }, | |
| }); | |
| this.ctx = result.ctx; | |
| this.description = new Uint8Array(result.header); | |
| this.resetInternalState(); | |
| } | |
| if (this.nextTimestampInSamples === null) { | |
| this.nextTimestampInSamples = Math.round(audioSample.timestamp * this.config.sampleRate); | |
| } | |
| const totalBytes = audioSample.allocationSize({ format: 's32', planeIndex: 0 }); | |
| const audioData = new ArrayBuffer(totalBytes); | |
| audioSample.copyTo(audioData, { format: 's32', planeIndex: 0 }); | |
| const result = await this.sendCommand({ | |
| type: 'encode', | |
| data: { | |
| ctx: this.ctx, | |
| audioData, | |
| numSamples: audioSample.numberOfFrames, | |
| }, | |
| }, [audioData]); | |
| this.emitPackets(result.packets); | |
| } | |
| async flush() { | |
| if (this.ctx === null) { | |
| return; | |
| } | |
| const result = await this.sendCommand({ type: 'flush', data: { ctx: this.ctx } }); | |
| this.emitPackets(result.packets); | |
| this.resetInternalState(); | |
| } | |
| close() { | |
| this.worker?.terminate(); | |
| } | |
| emitPackets(packets) { | |
| assert(this.nextTimestampInSamples !== null); | |
| for (const p of packets) { | |
| const data = new Uint8Array(p.encodedData); | |
| const packet = new mediabunny_1.EncodedPacket(data, 'key', this.nextTimestampInSamples / this.config.sampleRate, p.samples / this.config.sampleRate); | |
| this.nextTimestampInSamples += p.samples; | |
| this.onPacket(packet, this.chunkMetadata); | |
| this.chunkMetadata = {}; | |
| } | |
| } | |
| sendCommand(command, transferables) { | |
| return new Promise((resolve, reject) => { | |
| if (this.workerError) { | |
| reject(this.workerError); | |
| return; | |
| } | |
| const id = this.nextMessageId++; | |
| this.pendingMessages.set(id, { | |
| resolve: resolve, | |
| reject, | |
| }); | |
| assert(this.worker); | |
| if (transferables) { | |
| this.worker.postMessage({ id, command }, transferables); | |
| } | |
| else { | |
| this.worker.postMessage({ id, command }); | |
| } | |
| }); | |
| } | |
| } | |
| let registered = false; | |
| /** | |
| * Registers the FLAC encoder, which Mediabunny will then use automatically when applicable. Make sure to call this | |
| * function before starting any encoding task. The FLAC encoder will automatically determine the output bit depth | |
| * (16 or 24) based on the sample format of incoming `AudioSample` instances. | |
| * | |
| * Preferably, wrap the call in a condition to avoid overriding any native FLAC encoder: | |
| * | |
| * ```ts | |
| * import { canEncodeAudio } from 'mediabunny'; | |
| * import { registerFlacEncoder } from '@mediabunny/flac-encoder'; | |
| * | |
| * if (!(await canEncodeAudio('flac'))) { | |
| * registerFlacEncoder(); | |
| * } | |
| * ``` | |
| * | |
| * @group \@mediabunny/flac-encoder | |
| * @public | |
| */ | |
| const registerFlacEncoder = () => { | |
| if (registered) { | |
| return; | |
| } | |
| registered = true; | |
| (0, mediabunny_1.registerEncoder)(FlacEncoder); | |
| }; | |
| exports.registerFlacEncoder = registerFlacEncoder; | |
| function assert(x) { | |
| if (!x) { | |
| throw new Error('Assertion failed.'); | |
| } | |
| } | |
| const assertNever = (x) => { | |
| // eslint-disable-next-line @typescript-eslint/restrict-template-expressions | |
| throw new Error(`Unexpected value: ${x}`); | |
| }; | |
| exports.assertNever = assertNever; | |
Xet Storage Details
- Size:
- 7.74 kB
- Xet hash:
- 66e386cb5ad59836c87873b25bf6afbbb826ce5a9c96fa821d84db29ffc5e55f
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.