UNPKG

mediabunny

Version:

Pure TypeScript media toolkit for reading, writing, and converting media files, directly in the browser.

125 lines (99 loc) 3.61 kB
/*! * 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/. */ import { buildAdtsHeaderTemplate, parseAacAudioSpecificConfig, writeAdtsFrameLength } from '../../shared/aac-misc'; import { Bitstream } from '../../shared/bitstream'; import { validateAudioChunkMetadata } from '../codec'; import { Id3V2Writer } from '../id3'; import { metadataTagsAreEmpty } from '../metadata'; import { assert, toUint8Array } from '../misc'; import { Muxer } from '../muxer'; import { Output, OutputAudioTrack } from '../output'; import { AdtsOutputFormat } from '../output-format'; import { EncodedPacket } from '../packet'; import { Writer } from '../writer'; export class AdtsMuxer extends Muxer { private format: AdtsOutputFormat; private writer!: Writer; private header: Uint8Array | null = null; private headerBitstream: Bitstream | null = null; private inputIsAdts: boolean | null = null; constructor(output: Output, format: AdtsOutputFormat) { super(output); this.format = format; } async start() { const release = await this.mutex.acquire(); this.writer = await this.output._getRootWriter(true); if (!metadataTagsAreEmpty(this.output._metadataTags)) { const id3Writer = new Id3V2Writer(this.writer); id3Writer.writeId3V2Tag(this.output._metadataTags); } release(); } async getMimeType() { return 'audio/aac'; } async addEncodedVideoPacket() { throw new Error('ADTS does not support video.'); } async addEncodedAudioPacket( track: OutputAudioTrack, packet: EncodedPacket, meta?: EncodedAudioChunkMetadata, ) { const release = await this.mutex.acquire(); try { this.validateTimestamp(track, packet.timestamp, packet.type === 'key'); // First packet - determine input format from metadata if (this.inputIsAdts === null) { validateAudioChunkMetadata(meta); const description = meta?.decoderConfig?.description; // Follows from the Mediabunny Codec Registry: this.inputIsAdts = !description; if (!this.inputIsAdts) { const config = parseAacAudioSpecificConfig(toUint8Array(description!)); const template = buildAdtsHeaderTemplate(config); this.header = template.header; this.headerBitstream = template.bitstream; } } if (this.inputIsAdts) { // Packets are already ADTS frames, write them directly const startPos = this.writer.getPos(); this.writer.write(packet.data); if (this.format._options.onFrame) { this.format._options.onFrame(packet.data, startPos); } } else { assert(this.header); // Packets are raw AAC, we gotta turn it into ADTS const frameLength = packet.data.byteLength + this.header.byteLength; writeAdtsFrameLength(this.headerBitstream!, frameLength); const startPos = this.writer.getPos(); this.writer.write(this.header); this.writer.write(packet.data); if (this.format._options.onFrame) { const frameBytes = new Uint8Array(frameLength); frameBytes.set(this.header, 0); frameBytes.set(packet.data, this.header.byteLength); this.format._options.onFrame(frameBytes, startPos); } } await this.writer.flush(); } finally { release(); } } async addSubtitleCue() { throw new Error('ADTS does not support subtitles.'); } async finalize() { const release = await this.mutex.acquire(); // Required so that finalize() can't resolve before other calls release(); } }