npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@dsub/audio-transcoder

v0.4.2

Published

Browser-local audio inspection and transcoding engine for dsub tools.

Readme

dsub audio transcoder

Browser-local audio inspection and transcoding engine for dsub tools. Codec work runs in module Web Workers. Local Blob and File inputs are never uploaded; an explicit HTTP input instead lets the Worker fetch bounded byte ranges from a consumer-owned source. Lazy raw WebAssembly is fetched only from the asset source that the host application explicitly configures; the package never selects a CDN.

Licensed under the PolyForm Noncommercial License 1.0.0. Commercial use is not permitted by this package license.

Quick Start

pnpm add @dsub/audio-transcoder
# or: npm install @dsub/audio-transcoder

This complete path opens temporary output only after a bounded Worker slot is available. Keep the returned download cleanup function for as long as the link is usable.

import {
  AUDIO_TRANSCODER_CODEC_ASSET_MANIFEST,
  createAudioTranscoderOutputSession,
  createAudioTranscoderStreamWorkerPool,
  createSelfHostedRuntimeAssetSource,
  type AudioStreamTarget,
  type AudioTranscoderOutputArtifact,
} from '@dsub/audio-transcoder';

const pool = createAudioTranscoderStreamWorkerPool({
  codecAssets: {
    source: createSelfHostedRuntimeAssetSource(
      `/audio-transcoder-codecs/${AUDIO_TRANSCODER_CODEC_ASSET_MANIFEST.version}`,
    ),
    onStateChange: ({ assetName, phase, loadedBytes, totalBytes }) => {
      console.log({ assetName, phase, loadedBytes, totalBytes });
    },
  },
  concurrency: 1,
  maxQueued: 8,
});
const outputSession = createAudioTranscoderOutputSession({
  memoryLimitBytes: 128 * 1024 * 1024,
  namespace: 'my-audio-tool',
});

async function transcodeFile(
  file: File,
  signal: AbortSignal,
): Promise<AudioTranscoderOutputArtifact> {
  return pool.schedule(
    async (engine) => {
      const input = { blob: file, name: file.name };
      const inputSupport = await engine.probeInputSupport(input, { signal });
      if (inputSupport.status !== 'supported') {
        throw new Error(`Input support: ${inputSupport.status}`);
      }

      const { channels, sampleRate } = inputSupport.inspection;
      if (channels === null || sampleRate === null) {
        throw new Error('The input channel count or sample rate is unknown.');
      }

      const target = {
        channels,
        presetId: 'wav-pcm24',
        sampleRate,
      } as const satisfies AudioStreamTarget;
      const outputSupport = await engine.probeOutputSupport(target, { signal });
      if (outputSupport.status !== 'supported') {
        throw new Error(outputSupport.message);
      }

      const pending = await outputSession.create();
      try {
        const result = await engine.transcode(
          input,
          target,
          pending.stream,
          {
            ...(pending.maxOutputBytes === undefined
              ? {}
              : { maxOutputBytes: pending.maxOutputBytes }),
            signal,
          },
        );
        return await pending.complete({
          mimeType: result.preset.mimeType,
          name: replaceExtension(file.name, result.preset.extension),
        });
      } catch (error) {
        return cleanupAndRethrowPrimary(
          () => pending.discard(),
          error,
        );
      }
    },
    { signal },
  );
}

async function offerDownload(
  file: File,
  signal: AbortSignal,
): Promise<() => Promise<void>> {
  const artifact = await transcodeFile(file, signal);
  let url: string;
  try {
    url = URL.createObjectURL(artifact.blob);
  } catch (error) {
    return cleanupAndRethrowPrimary(
      () => artifact.dispose(),
      error,
    );
  }

  const link = document.createElement('a');
  link.download = artifact.name;
  link.href = url;
  link.textContent = `Download ${artifact.name}`;
  document.body.append(link);

  return async () => {
    link.remove();
    URL.revokeObjectURL(url);
    await artifact.dispose();
  };
}

async function cleanupAndRethrowPrimary(
  cleanup: () => Promise<void>,
  primaryError: unknown,
): Promise<never> {
  try {
    await cleanup();
  } catch (cleanupError) {
    try {
      console.error(
        'Output cleanup failed; output-session disposal will retry it.',
        { cleanupError, primaryError },
      );
    } catch {
      // Reporting must not replace the primary conversion or quota error.
    }
  }
  throw primaryError;
}

function replaceExtension(name: string, extension: string): string {
  const dot = name.lastIndexOf('.');
  return `${dot > 0 ? name.slice(0, dot) : name}.${extension}`;
}

// On route teardown: abort active controllers, release download links, then:
async function disposeAudioTool(): Promise<void> {
  await pool.dispose();
  await outputSession.dispose();
}

If eager cleanup fails, the original conversion, quota, or URL error remains the rejection. The cleanup error is reported locally, and the output session keeps the resource tracked so its later dispose() can retry removal.

Pass a File from a file input or drop event to offerDownload(). Revoke the object URL and dispose its artifact when removing the link; then await disposeAudioTool() when the tool route unmounts. Framework and pagehide ownership are covered in Browser integration.

Runtime codec assets

The default stream Worker requires an explicit codecAssets.source. WAV and AIFF use eager JavaScript and do not fetch a codec asset. Selecting or probing AAC, Ogg Opus, MP3, or FLAC fetches that codec's raw .wasm; a sample-rate change fetches only the selected fast, balanced, or best resampler. These bytes execute directly in the existing module Worker. They are not embedded in the engine package and do not create a nested or Blob Worker.

Codec assets are versioned with @dsub/audio-transcoder in the same public GitHub release tag. They are not published as a separate npm package. The convenience helper is an explicit jsDelivr opt-in:

import {
  createAudioTranscoderJsDelivrAssetSource,
  createAudioTranscoderStreamWorkerPool,
} from '@dsub/audio-transcoder';

const pool = createAudioTranscoderStreamWorkerPool({
  codecAssets: {
    // This call is the application's explicit decision to use jsDelivr.
    source: createAudioTranscoderJsDelivrAssetSource(),
  },
});

The helper always resolves the engine's baked exact version. For example, an engine at 1.2.3 resolves AAC to https://cdn.jsdelivr.net/gh/dsub-io/[email protected]/codec-assets/wasm/aac.wasm; it never uses a branch, latest, or a SemVer range. This URL is an example of the versioned contract, not a claim that an illustrative version is published. The matching public Git tag contains the exact modified source, build/relink scripts, manifests, notices, and upstream archive URLs and hashes described in codec-assets/README.md.

Self-hosting is equally explicit. A primary source and its fallbacks are tried sequentially in the configured order, never raced:

const version = AUDIO_TRANSCODER_CODEC_ASSET_MANIFEST.version;
const pool = createAudioTranscoderStreamWorkerPool({
  codecAssets: {
    source: createSelfHostedRuntimeAssetSource(
      `https://assets.example.com/audio-transcoder/${version}`,
    ),
    fallbackSources: [createAudioTranscoderJsDelivrAssetSource()],
    onStateChange: (state) => console.log(state),
  },
});

Every fallback must serve the same stable paths and exact bytes. Before any bytes are used, the runtime validates manifest schema and ABI compatibility, then checks the decoded raw response size and SHA-256 against the manifest baked into the engine. The state model begins at idle; onStateChange emits downloading, verifying, ready, or final error transitions. With compressed HTTP transfer, totalBytes can be null because Content-Length may describe transport bytes while integrity covers the decoded raw WASM.

Development

pnpm install
pnpm generate:package-metadata
pnpm generate:codec-asset-metadata
pnpm typecheck
pnpm test:coverage
pnpm build:clean
pnpm build
pnpm build:copy-runtime-assets
pnpm package:verify

CI exposes type checking, framework tests, builds, package verification, codec asset checks, and demo checks as separate named steps. Framework lifecycle and deployment details are in docs/integration.md.

Streaming API

Use the streaming Worker API for production transcoding. It reads local Blob/File or explicit HTTP input in bounded chunks and writes random-access output chunks instead of materializing the complete input, decoded PCM, and output in JavaScript memory at once.

HTTP range input

Use an HTTP descriptor when another package or application endpoint owns media acquisition. The descriptor is plain structured-cloneable data, so it crosses the existing module Worker boundary without transferring a callback:

const input = {
  http: {
    credentials: 'include',
    size: resolvedSource.size,
    url: resolvedSource.rangeUrl,
  },
  name: resolvedSource.fileName,
} as const;

const support = await engine.probeInputSupport(input, { signal });

The consumer owns URL resolution, authorization, expiry, and source lifetime. The URL must be absolute HTTP(S), contain no embedded credentials, and serve the declared byte length. Partial requests must return 206 with an exact Content-Range; a 200 response is accepted only when the Worker requested the complete source. The engine owns the Range header and rejects a caller override. credentials defaults to same-origin; optional headers must be a serializable string record. Cross-origin sources additionally need CORS to allow the Worker origin and the Range header.

Prefer a same-origin authenticated range endpoint with an opaque, short-lived source ticket. Do not place long-lived credentials or an upstream media URL in the descriptor. The transcoder neither discovers remote media nor provides an open proxy; it only performs the bounded reads requested by its parsers and decoders.

Input discovery and probing

getCapabilities().inputFormats provides extension and MIME hints for picker UI. They are not support assertions. Every concrete file must pass probeInputSupport() before conversion is enabled.

| Candidate container | Common extension hints | Decision path | | --- | --- | --- | | CAF LPCM | .caf | Built-in PCM | | AIFF/AIFC PCM | .aif, .aiff, .aifc | Built-in PCM | | MP4/QuickTime | .m4a, .mp4, .mov, .qt | Runtime-probed | | Matroska/WebM | .mka, .mkv, .webm | Runtime-probed | | WAV | .wav, .wave | Runtime-probed | | Ogg | .oga, .ogg, .opus | Runtime-probed | | FLAC / MP3 | .flac, .mp3 | Runtime-probed | | ADTS / MPEG-TS | .aac, .adts, .ts | Runtime-probed |

recognized-unsupported means a parser recognized the container but the current runtime cannot decode its codec. unsupported means no installed parser recognized the file headers. Probing reads metadata and decoder support; runtime decoders validate at most the first decoded sample within the configured inputReadBytes read budget. If that budget is too small to reach a verdict, probeInputSupport() rejects with AudioTranscoderError.code === 'RESOURCE_LIMIT_EXCEEDED'; this is not an unsupported verdict. Increase the budget within its documented limit or ask the user to choose a different file instead of disabling the format globally.

The engine adds no wall-clock deadline. Browser codec APIs are runtime-owned and can stall, so UI probes should compose their lifecycle AbortSignal with an application deadline. Treat a rejected or aborted probe as retryable rather than permanently disabling the input format.

Every built-in inspection also exposes structured sourceEncoding. Use it instead of parsing the human-readable codec field. PCM sources identify integer versus float representation, bit depth, signedness, and endianness; compressed sources identify lossless/lossy codec metadata when available. This lets a consumer render, for example, 32-bit float and 32-bit signed integer as distinct source formats.

Output capabilities and runtime probing

getCapabilities().outputFormats is the immutable candidate manifest declared by the package. It describes installed presets and their static constraints; it does not prove that the current browser can initialize every codec runtime. implementation: 'runtime-asset' means the encoder bytes are delivered separately through the application's explicit asset provider, never bundled in the core Worker. The current manifest contains:

| Format | Preset IDs | Implementation | Channels | Encoder target sample rates | | --- | --- | --- | ---: | --- | | WAV | wav-pcm16, wav-pcm24, wav-pcm32, wav-float32 | Built in, eager | 1-32 | 8,000-384,000 Hz | | AIFF | aiff-pcm16, aiff-pcm24 | Built in, eager | 1-32 | 8,000-384,000 Hz | | AAC-LC (ADTS) | aac-96kbps, aac-128kbps, aac-192kbps, aac-256kbps | External raw WASM, lazy | 1-2 | 32,000, 44,100, or 48,000 Hz | | Ogg Opus | ogg-opus-64kbps, ogg-opus-96kbps, ogg-opus-128kbps, ogg-opus-192kbps | External raw WASM, lazy | 1-2 | 48,000 Hz | | MP3 | mp3-128kbps | External raw WASM, lazy | 1-2 | 16,000, 22,050, 24,000, 32,000, 44,100, or 48,000 Hz | | MP3 | mp3-192kbps, mp3-256kbps, mp3-320kbps | External raw WASM, lazy | 1-2 | 32,000, 44,100, or 48,000 Hz | | FLAC | flac-16bit, flac-24bit | External raw WASM, lazy | 1-8 | 8,000, 16,000, 22,050, 24,000, 32,000, 44,100, 48,000, 88,200, 96,000, 176,400, or 192,000 Hz |

Do not copy this table into application logic. Build format-specific controls with getAudioStreamOutputParameters(): WAV exposes sample format and bit depth, AIFF/FLAC expose bit depth, and AAC/Ogg Opus/MP3 expose bitrate. Pass the semantic selection and inspected source to resolveAudioStreamSourceAwareFormatTarget() with sampleRate: 'automatic' to preserve a valid source rate or select the nearest valid discrete encoder rate. Use getAudioStreamOutputSampleRateOptions() when presenting the exact pass-through and resampling choices for one preset. Its discriminated result reports format, preset, source-inspection, and source-channel failures before returning any options. Then call probeOutputSupport() for the resolved exact target. Render checking while it runs and enable conversion only for supported. Gray or omit explicit unsupported-configuration and runtime-unavailable results. A rejected Promise is a retryable operation, resource, queue, cancellation, or programmer error state, not an unsupported verdict.

The runtime probe performs a tiny real encode into a bounded discard sink. It does not read an input, create OPFS state or an artifact, or load the resampler. A static mismatch does not load a lazy codec. Identical requests are coalesced; one probe runs at a time, at most eight unique targets wait, and up to 32 successful exact targets are cached. Unsupported results and rejected probes are not cached. Cancelling the last subscriber stops shared work.

Do not infer codec support from the user agent, navigator.hardwareConcurrency, or navigator.deviceMemory. Probe each concrete input with probeInputSupport() and each exact output configuration with probeOutputSupport(). Device hints may tune queue or concurrency only after measurement; the default and recommended concurrency is 1.

Probe the selected exact target first. Preflight other offered exact targets sequentially and apply each verdict only to that target. Probing AAC, Ogg Opus, MP3, or FLAC intentionally downloads that format's raw codec asset, so defer those probes for a smaller initial transfer. Never start every probe concurrently. See Browser integration for the full UI and framework lifecycle guidance.

loading: 'lazy' describes asset ownership; it is not an engine-wide live loading flag. Set the selected control to checking before awaiting probeOutputSupport(), and set a job to preparing before calling transcode(). Those host states cover Worker startup, dynamic import, WASM compilation, and codec initialization before the first package progress event. There is deliberately no eager ready() call that downloads every codec.

Ogg Opus always encodes at its 48 kHz codec clock. Automatic selection keeps a 48 kHz source and otherwise selects 48 kHz only when both source and target are inside the global resampling range. Lower-rate MP3 combinations are intentionally absent. LAME can silently encode some requested high-bitrate, low-sample-rate combinations at a lower bitrate; the public presets reject those combinations instead of misreporting the result. Always use each preset's exact discrete rate array.

The target constraint describes the encoder. When the target rate differs from the source rate, also respect capabilities.limits.sampleRate.resampling; the default runtime allows conversion only from 8,000 through 192,000 Hz. Same-rate pass-through uses limits.sampleRate.passThrough, currently 8,000 through 384,000 Hz.

Automatic selection never changes the source-owned channel layout and validates it before selecting a rate. It first preserves the source when the preset and pass-through path allow it. Otherwise it considers only a discrete preset's declared rates, chooses the smallest absolute Hz distance, and chooses the higher rate on a tie. Range presets do not supply invented fallback rates; callers may pass their own exact candidates only to the option enumerator. Lossy capability descriptors report bitrateMode: AAC and Ogg Opus are variable, while MP3 is constant.

The processing pipeline is interleaved Float32 PCM. wav-pcm32 writes a 32-bit signed-integer WAV container, but its effective retained integer precision is 24 bits, as reported by descriptor.processingPrecision.effectiveIntegerPrecisionBits. It must not be presented as preserving 32-bit integer source precision.

Local output sessions

createAudioTranscoderOutputSession() provides a seekable temporary output without requiring a directory picker. It prefers the origin private file system (OPFS) and falls back to paged memory with a hard aggregate reservation limit.

The Quick Start shows the complete ownership path. An OPFS artifact remains readable only until artifact.dispose() starts. The memory fallback's complete() materializes a Blob and reserves copy headroom; creating an object URL from that Blob adds no application-level copy. Revoke the URL before awaiting artifact disposal. Failed cleanup remains session-tracked and is retried by outputSession.dispose().

Create the destination first, then pass pending.maxOutputBytes to transcode() when it is present. The value belongs to the actual destination: OPFS omits it because browser quota is not stable, while a memory destination atomically leases enough session budget for its paged source and Blob materialization. This prevents concurrent pending outputs from promising the same capacity. Pass maxMemoryArtifactBytes to session.create() when concurrent memory-backed outputs need separate nonzero capacities. Omitting it reserves the largest capacity currently safe for that one pending output. Known WAV and AIFF container sizes that cannot fit reject during preparation; every format is also checked while writing. Settle each pending output promptly.

For example, only callers that intentionally keep multiple pending outputs open at once should split the memory budget explicitly:

const firstPending = await outputSession.create({
  maxMemoryArtifactBytes: 24 * 1024 * 1024,
});
const secondPending = await outputSession.create({
  maxMemoryArtifactBytes: 24 * 1024 * 1024,
});

Each explicit capacity also reserves page-rounded source storage and Blob materialization headroom. A sequential queue that retains completed artifacts, including the Vite demo, should keep using create() without this option so each new pending output derives its capacity from the session's current remaining budget.

Storage-capacity failures use code === 'RESOURCE_LIMIT_EXCEEDED' with reason === 'output-storage-limit', including native OPFS quota failures. Target container or encoder representability failures use code === 'UNSUPPORTED_OUTPUT' with reason === 'target-size-limit'. Other failures must not be mapped from a code alone.

Known AudioTranscoderError values retain code, optional reason, and message across Workers. An unclassified Worker Error retains its name, message, and stack when available without receiving a fabricated package code. A destination-local failure keeps the original local thrown value when its Worker representation returns. Log that fallback diagnostic; do not branch on message text.

0.3.0 memory lease migration

The memory fallback now reserves page storage and Blob materialization headroom atomically when session.create() resolves. A no-argument call reserves the largest currently safe artifact capacity, so a second concurrent pending memory output may receive maxOutputBytes: 0. Sequential callers can keep the existing no-argument call, including when completed artifacts remain available for download. Concurrent callers should pass a non-negative safe integer maxMemoryArtifactBytes for every pending output and size the session budget for all leases. OPFS ignores this option because it does not consume the memory budget. This behavioral change warrants 0.3.0; Release Please owns the version update.

OPFS data does not automatically disappear when a tab closes. Explicit, awaited disposal is authoritative. An integrated transcoder should keep disposeOnPageHide at its default false and install one app-level handler so best-effort cleanup starts in the strict order of pool first and output session second. The session option is only a convenience when no active writer is owned independently of the session. Browsers do not wait for an async pagehide handler, and BFCache transitions (event.persisted) intentionally keep the runtime alive. A new session sweeps managed orphans at startup. Web Locks protect active sessions in other tabs; where locks are unavailable, only stale lease directories older than the conservative fallback age are reclaimed.

Queue and memory limits

The stream Worker engine and pool retain at most maxQueued waiting operations. The default is 8, the maximum is 64, and active operations are excluded. When full, a new operation rejects with AudioTranscoderError.code === 'QUEUE_CAPACITY_EXCEEDED'. Pool concurrency defaults to 1 and is limited to 4; increase it only after measuring peak memory on target devices. getQueueSnapshot() exposes active and queued counts.

Open each pending output inside pool.schedule() so queued jobs do not retain writable destinations. A queued callback can retain its source File or Blob, but does not read it into an ArrayBuffer; output storage starts only after a Worker slot is available. Direct pool.transcode() calls accept an already-open output and abort it if admission, queued cancellation, disposal, or Worker startup fails. Pass the same AbortSignal to schedule(), probing, and transcoding.

The destination remains abortable through encoder finalization and, on the Worker path, until both Worker-side output completion and the success result arrive. The final destination close() call is the explicit irreversible commit point. Cancellation or disposal requested after that call starts waits for its success or failure instead of reporting an abort for output that may already have committed.

When running work rejects with OPERATION_ABORTED, the pool terminates that Worker before reusing the slot, so a stalled native decoder cannot accumulate across retries. Inside schedule(), discard owned output and rethrow the abort. If using a standalone Worker engine instead of the pool, await dispose() before creating a replacement and retrying.

inputReadBytes, pcmChunkBytes, and outputChunkBytes bound one source read, decoded PCM yield, and encoded output chunk. They are not total heap limits. MediaBunny caches, browser decoder state, the module Worker, WASM heaps, source Blob objects, and output storage consume additional memory. Browsers provide neither portable forced garbage collection nor a reliable available-memory budget.

Use await engine.dispose() or await pool.dispose() whenever output abort and writer-lock release must be complete before removing OPFS entries. terminate() remains a fire-and-forget compatibility method and is not an awaited cleanup barrier.

Lazy codec assets and resamplers

WAV and AIFF encoding are built in and load eagerly. AAC-LC uses a pinned minimal FFmpeg build, Ogg Opus uses pinned libopusenc/libopus/libogg, MP3 uses pinned LAME, and FLAC uses pinned libFLAC. Their bridges load raw WASM from the application's required codecAssets configuration only when a matching preset is first selected or probed. The runtime has no implicit CDN, embedded WASM payload, nested Worker, or server-side media processing path.

Sample-rate conversion uses three package-owned libsamplerate WASM modules, one for each existing best, balanced, and fast quality contract. A conversion loads only the selected module. best retains libsamplerate's complete highest-quality sinc coefficient table; no coefficient, passband, or supported rate/channel range is reduced to save bytes. A job whose target sample rate equals its source rate takes the pass-through path and loads no resampler.

Vite production builds must preserve the Worker as an ES module so lazy JavaScript glue remains split from the initial Worker:

import { defineConfig } from 'vite';

export default defineConfig({
  worker: { format: 'es' },
});

Without worker.format: 'es', Vite's IIFE Worker output can inline lazy JavaScript glue. Raw WASM still comes from the explicitly configured asset source, but the initial Worker resource can grow.

For strict CSP, the module Worker needs worker-src 'self' and raw WASM needs WebAssembly compilation. Fetches from a same-origin asset path need connect-src 'self'; explicit jsDelivr use additionally needs connect-src https://cdn.jsdelivr.net:

Content-Security-Policy:
  script-src 'self' 'wasm-unsafe-eval';
  worker-src 'self';
  connect-src 'self' https://cdn.jsdelivr.net

See MDN's worker-src and script-src references. Remove the jsDelivr origin when all assets are same-origin; include every configured cross-origin fallback in connect-src. A cross-origin asset host must allow CORS. Serve .wasm as application/wasm and enable HTTP gzip or Brotli as transport compression. The CDN or asset host sees only asset requests; input audio remains local unless the application uploads it.

Whole-buffer API

The whole-buffer API remains available for short inputs and compatibility. It accepts a complete ArrayBuffer and can make the input, decoded Float32 PCM, and encoded output coexist in memory.

Built-in capabilities are:

  • Header inspection: WAV, AIFF/AIFC, CAF, FLAC, and MP3.
  • PCM decode: WAV, uncompressed AIFF/AIFC, and LPCM CAF.
  • PCM encode: WAV integer 16/24/32-bit and WAV float32; AIFF integer 16/24-bit.
  • Transcode: a built-in PCM decoder followed by a built-in encoder.

The streaming AAC/Ogg Opus/MP3/FLAC encoders do not change the whole-buffer plugin boundary. Whole-buffer encoding for those formats still requires a plugin.

Whole-buffer operations use a 64 MiB safety guard and fail with RESOURCE_LIMIT_EXCEEDED. Built-in WAV, AIFF/AIFC, and CAF decoders estimate the expanded planar Float32 size from headers and reject oversized output before allocating it. A plugin decoder can provide estimateDecodedPcm() for the same preflight; without that hook, decoded PCM can only be checked after the plugin has allocated it. unsafeAllowLargeBuffers: true explicitly bypasses the guard but cannot prevent an out-of-memory failure.

import { createAudioTranscoderEngine } from '@dsub/audio-transcoder';

const engine = createAudioTranscoderEngine();

async function transcodeShortFile(file: File, signal: AbortSignal) {
  const input = {
    data: await file.arrayBuffer(),
    name: file.name,
    size: file.size,
  };

  const inspection = engine.inspect(input);
  const encoded = await engine.transcode(input, 'wav-pcm24', {
    signal,
    onProgress: ({ phase, progress }) => console.log(phase, progress),
  });

  return { encoded, inspection };
}

Whole-buffer Worker engines and pools use maxQueued: 8 and maxQueuedBytes: 64 * 1024 * 1024 by default. The byte budget counts only waiting operations: complete input buffers and unique PCM backing buffers are counted conservatively, while active work is excluded. unsafeAllowLargeBuffers does not bypass this aggregate waiting-queue limit. Pool snapshots expose maxQueuedBytes and queuedBytes.

The pool cannot measure data captured by an arbitrary schedule() callback, so that callback contributes zero to queuedBytes. Load the file inside the callback after a slot is available. Whole-buffer APIs retain the synchronous terminate() lifecycle because they do not own the streaming output locks managed by the stream API.

Version and release

Release Please owns semantic versioning, changelog updates, GitHub releases, and package version changes. Runtime consumers can use audioTranscoder.getVersion(), getVersion(), or AUDIO_TRANSCODER_VERSION.

Full CI runs once for ordinary pull requests targeting main; it does not run on main, Release Please pull requests, or manual dispatches. Release Please runs on main pushes and requires the repository GH_PAT secret. The browser validation matrix remains Chromium, Firefox, and WebKit; npm publication uses Trusted Publishing (OIDC).

The engine and its codec-assets/ tree are one exact-version release. Release Please owns the version, public tag, and GitHub release. The Release workflow checks the tagged manifest and all seven raw WASM files through the exact jsDelivr /gh/dsub-io/audio-transcoder@v<version>/codec-assets path, then publishes only @dsub/audio-transcoder through npm Trusted Publishing (OIDC). There is no separate codec npm package, manual publish path, recovery publish, or manually created tag. The engine publication gate fails closed until the tagged CDN assets pass their size, SHA-256, ABI, schema, and WebAssembly checks.

License

Required Notice: Copyright 2026 dsub.io. All rights reserved.

This software is source-available under the PolyForm Noncommercial License 1.0.0. Use, modification, and redistribution are permitted only for noncommercial purposes. Commercial use is prohibited. Third-party dependency notices are in THIRD_PARTY_NOTICES.md, with complete license texts in THIRD_PARTY_LICENSES and source/build materials under codec-build/ and vendor/. Released binaries identify their corresponding source by the public Git tag matching the package version. Web distributors should retain the required notices and that exact source/relink reference for the raw assets they deploy.