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

vidpickr-mux

v0.1.0

Published

Stream-merge video + audio into MP4 entirely in the browser. Used in production by VidPickr; published standalone so anyone can build a download tool without a server queue.

Readme

vidpickr-mux

Stream-merge a video track and an audio track into a single MP4 entirely in the browser — no server queue, no transcode, no intermediate file on disk. The bytes go from the source URLs through the muxer and into a WritableStream that the browser writes to disk as the merge progresses.

This is the muxing core that powers VidPickr's download flow, extracted as a standalone library.

import { createMuxSession, pickDownloadDestination } from 'vidpickr-mux';

const output = await pickDownloadDestination('movie.mp4');

const session = createMuxSession({
  output,
  video: { codec: 'avc', width: 1920, height: 1080, description: avcC },
  audio: { codec: 'aac', sampleRate: 44100, numberOfChannels: 2 },
  fastStart: 'fragmented',
});

// You feed in EncodedVideoChunk / EncodedAudioChunk objects from
// whatever decode/demux pipeline you have:
session.addVideoChunk(chunk);
session.addAudioChunk(chunk);

await session.finalize();
// Done — the file is on disk.

Why this exists

Every "free YouTube downloader" works the same way: send the URL to a backend, the backend fetches the streams, re-encodes them, and pipes the result to you. That model has three problems:

  1. Quality loss — server-side re-encode loses information vs the original.
  2. Speed — you wait for upload, transcode, then download. The tool's queue is your queue.
  3. Privacy — your video URL passes through someone else's server, and the file briefly exists on their disk.

In modern browsers (WebCodecs, fragmented MP4 muxing, fetch streaming, the File System Access API) all of this can run on the user's own machine. The bytes you save are the exact bytes the source served — bit-identical, no quality loss, fast.

vidpickr-mux is the part that's reusable across any tool that needs in-browser MP4 muxing. The YouTube-specific glue (cookies, SAPISIDHASH, multi-language audio detection, etc.) stays in the VidPickr server.


Install

npm install vidpickr-mux
# or
pnpm add vidpickr-mux
yarn add vidpickr-mux

Peer dependency mp4-muxer is bundled. The package is ESM-only.


Browser support

  • Chrome / Edge / Opera 113+ — full support, including a real streaming download to disk via showSaveFilePicker. Multi-GB downloads use ~constant RAM.
  • Firefox 130+ / Safari 17+ — works, but the destination falls back to a Blob accumulation + <a download> trigger, so the file lives in RAM until finalize. Fine for files under a few GB.
  • Older browsers — won't work; WebCodecs and WritableStream are hard dependencies.

API

pickDownloadDestination(filename: string): Promise<WritableStream<Uint8Array>>

Returns a writable stream that lands as a downloaded file. On browsers that support the File System Access API, this triggers a native save dialog and writes incrementally to disk. Elsewhere it falls back to a blob-then-anchor download.

The returned stream is yours; close it when you're done writing.

createMuxSession(opts): MuxSession

Low-level mux session. The caller has already produced EncodedVideoChunk / EncodedAudioChunk objects somehow (e.g. from a WebCodecs decoder, a custom demuxer, etc.) and just needs to interleave them into an MP4.

interface MuxSessionOptions {
  output: WritableStream<Uint8Array>;
  video: {
    codec: 'avc' | 'hevc' | 'vp9' | 'av1';
    width: number;
    height: number;
    description?: Uint8Array; // avcC / hvcC / etc, required for AVC + HEVC
    frameRate?: number;
  };
  audio: {
    codec: 'aac' | 'opus';
    sampleRate: number;
    numberOfChannels: number;
    description?: Uint8Array;
  } | null; // null for video-only
  fastStart?: 'fragmented' | 'in-memory';
}

interface MuxSession {
  addVideoChunk(chunk: EncodedVideoChunk, meta?: EncodedVideoChunkMetadata): void;
  addAudioChunk(chunk: EncodedAudioChunk, meta?: EncodedAudioChunkMetadata): void;
  finalize(): Promise<void>;
}

Use fastStart: 'fragmented' (the default) for streaming output — each fragment is written as it's produced, and the file is playable mid-write. Use 'in-memory' if you need a single moov box at the end (smaller header, but the whole thing buffers).

muxStreams(opts): Promise<void> (planned, v0.2)

High-level convenience wrapper:

await muxStreams({
  videoUrl: 'https://…',
  audioUrl: 'https://…',
  filename: 'output.mp4',
  onProgress: (p) => console.log(p),
});

Currently throws — the v0.1 release ships the muxer + destination layer plus the session API. The fully-wired demuxer pipeline is on the v0.2 roadmap; in the meantime see examples/youtube-style.ts for a working sketch using createMuxSession directly.


Example: YouTube-style mux

The examples/youtube-style.ts file shows a complete pipeline:

  1. Sniff the MP4 container of each URL to learn codec + track parameters.
  2. Open a createMuxSession with that config.
  3. Stream both URLs in parallel, parse fragments, and feed them into the session.
  4. finalize() — file is on disk.

Run the live demo by serving the examples/ directory:

cd examples
npx serve .
# → http://localhost:3000/basic.html

What's intentionally out of scope

  • Source-specific extractors. Getting the actual stream URLs out of YouTube / Vimeo / wherever is a different problem with different rules per source. This library is the muxing layer only.
  • Re-encoding. If you want to change resolution, bitrate, or codec on the way through, that's WebCodecs + a video encoder, not a muxer. We pass bytes through.
  • DRM. No interest, no support.

License

MIT — see LICENSE.

Built and maintained by the VidPickr team. If this saves you time and you find yourself running a YouTube downloader, consider sending people our way.