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

av-drift-detector

v0.6.1

Published

Measure audio/video sync to a fraction of a millisecond, frame by frame (Node.js bindings)

Readme

av-drift-detector (Node.js)

Measure audio/video sync to a fraction of a millisecond, frame by frame.

Node.js bindings to av-drift-detector, the Rust library behind the av-drift command. Generate a test video whose every frame, and every frame's worth of audio, carries its own frame number. Play it through whatever you want to test (an encoder, a streaming pipeline, a TV, a video call), record the result, and measure how far the sound has drifted from the picture at every frame.

import avd from 'av-drift-detector'

await avd.generate({ output: 'sync.mp4', fps: '29.97', duration: 10, audioOffset: 0.04 })
// ... play sync.mp4 through the system under test and record it as captured.mp4 ...
const report = await avd.analyze('captured.mp4')
console.log(report.summary.meanMs) // positive means the audio is late

Install

npm install av-drift-detector

Node.js 20 or later, on macOS (Apple silicon and Intel), Linux (x64 and arm64, glibc 2.17+) or Windows (x64). TypeScript types are included.

Reading and writing files runs the ffmpeg and ffprobe programs, so install FFmpeg 5.1 or later (brew install ffmpeg, sudo apt install ffmpeg, winget install Gyan.FFmpeg) and keep them on your PATH, or set AV_DRIFT_FFMPEG and AV_DRIFT_FFPROBE to their paths. Writing H.264 needs an FFmpeg with libx264, as those packages have. The in-memory functions below don't need FFmpeg.

Files

  • generate(options, onProgress?) writes a test video and resolves to a summary. Only output is required; the others default to fps: '30', duration: 10 (seconds), 1280×720, sampleRate: 48000, videoCodec: 'h264', audioCodec: 'aac', audioOffset: 0 (seconds; positive delays the audio) and music: true (a tune under the tones that doesn't affect analysis). onProgress({ framesDone, framesTotal }) is called about once a percent. FFV1 video and PCM audio need .mov or .mkv.
  • analyze(path, options?) measures every frame of a capture. Options: offset and duration (a window, in seconds), fps (override the pattern's rate), useQr, and capture: 'auto', 'file' (a digital copy) or 'camera' (a phone filming a screen).

Both run on libuv's thread pool and return promises. Reports have the content of av-drift analyze --json (format) with camelCase names:

const { meanMs, stddevMs, skewPpm } = report.summary
for (const f of report.frames) {
  if (f.status === 'ok') console.log(f.time, f.videoFrame, f.driftMs)
}

Frames and audio from elsewhere

If you already have decoded frames and audio (a capture card, WebRTC, a camera), measure them without files:

const reader = new avd.StampReader()
for (const { time, luma } of myFrames) {
  reader.read(luma, width, height, time)      // 8-bit luma (Y) plane, time in seconds
}
const report = await avd.analyzeFrames({
  frames: reader.finish(),
  audio: { sampleRate: 48000, startTime: t0, samples: monoFloat32Array },
  width,
  height,
})

Frame times and startTime must be on the same clock, in seconds.

  • new StampReader(tryQr?) reads the frames of one capture in order. read(luma, width, height, time, stride?, exposure?, readout?) returns the frame ({ time, stamp, qrLocated, exposure, readout }); finish() returns every frame read, with any that could only be identified from later frames filled in. It follows the QR code from frame to frame, which is faster than reading each frame alone, and it reads pictures a camera caught while the screen switched frames, measuring how far through the switch each was (the stamp's blend). Use it for camera footage. For a camera, time is when the picture's top row began its exposure, as phones' camera APIs record it; if the camera reports each frame's exposure time and rolling-shutter readout (seconds), pass them too, and a camera locked to the pattern's rate is timed to a millisecond or two.
  • readFrame(luma, width, height, stride?, tryQr?) reads one frame's stamp (barcode, then QR code), or returns null.
  • new PatternRenderer({ fps, width, height, sampleRate, duration }).render(frameId) draws a test-pattern frame as a luma Buffer. With duration (seconds), the info line shows the file's length.
  • synthesizeAudio({ count, fps, sampleRate, delay, start, music }) makes the test audio as a Float32Array, with the music unless music is false.

Async functions reject on bad arguments; readFrame and the renderer throw.

More

The repository has the guides: measuring with a camera, the test pattern specification, and how the analysis works. Licensed under the BSD-2-Clause Plus Patent License.