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

virtualdj-connect

v1.2.1

Published

Real-time VirtualDJ track metadata for Node — M3U history watcher plus a Network Control client with per-deck data, on-air detection, and sandbox awareness

Readme

virtualdj-connect

Know what VirtualDJ is playing, in real time, from Node. Point it at a running VirtualDJ and it emits a track event every time the song on air changes.

Two ways to read what's playing — pick one, or run both:

| | M3U history watcher | Network Control client | | ------------------------------ | ------------------------------------------------ | --------------------------------------------------------------------------------------------- | | How | Polls VirtualDJ's History/*.m3u files | HTTP-polls the Network Control Plugin | | Setup | None — works on any install with history enabled | Plugin must be enabled in VirtualDJ | | Latency | Written when a track is logged to history | Sub-second, straight off the deck | | Gives you | Title, artist, file path | Title, artist, album, genre, key, BPM, duration, deck number, file path | | Knows which deck is on air | No | Yes |

The Network Control client is the more capable of the two. Beyond metadata it:

  • Picks the on-air deck across all four decks rather than reporting whatever loaded last, falling back to any loaded deck so the display isn't blank while the DJ cues up.
  • Respects Sandbox mode — tracks the DJ is rehearsing in headphones are not published as now playing.
  • Detects Beatport streams, flagging them and exposing the Beatport ID instead of handing you a netsearch:// URL as a file path.
  • Survives partial VirtualDJ support — builds that don't implement a given VDJScript verb answer error:-N, which is coerced to an empty field rather than leaking a bogus string into your track data.

Runs on macOS and Windows. Verified against VirtualDJ 8.5 (build 8769); the installation detector also handles VirtualDJ 7 folder layouts.

Installation

npm install virtualdj-connect

Usage — M3U history watcher

import { VirtualDjConnect } from "virtualdj-connect";

const vdj = new VirtualDjConnect({
  pollIntervalMs: 5000,
});

vdj.on("ready", ({ basePath }) => {
  console.log(`Watching: ${basePath}`);
});

vdj.on("track", (payload) => {
  console.log(`Now playing: ${payload.artist} - ${payload.title}`);
  if (payload.filePath) console.log(`File: ${payload.filePath}`);
});

vdj.on("error", (err) => {
  console.error("Error:", err);
});

await vdj.start();

// Later...
await vdj.stop();

Usage — Network Control Plugin

Enable the plugin in VirtualDJ first: Config → Extensions → Effects → Other → Network Control. Installing it is not enough — it has to be switched on, and it binds its port at that point. Its config panel is where the port (default 8080) and the optional bearer password live. Then:

import { VirtualDjNetworkControl } from "virtualdj-connect";

const nc = new VirtualDjNetworkControl({
  host: "127.0.0.1",
  port: 8080,
  bearer: "your-password", // if configured
  pollIntervalMs: 1000,
});

nc.on("track", (payload) => {
  console.log(`Deck ${payload.deck}: ${payload.artist} - ${payload.title}`);
  console.log(`  ${payload.bpm} BPM, key ${payload.key}, ${payload.duration}s`);
});

await nc.start();

Sandbox mode

Sandbox is VirtualDJ's rehearsal mode: the active deck is pulled into a private area routed to the headphone output while the master keeps playing whatever was on air when it was engaged.

is_audible does not account for this — it reports mixer routing and knows nothing about the detoured master bus, so the deck the DJ is rehearsing on still answers "yes". Taken at face value that publishes a track the audience never heard and overwrites the real on-air one.

So each poll resolves sandbox first, with a single mixer-wide query, and skips the per-deck reads entirely while it's engaged. The last on-air track is held until sandbox is released, and no duplicate track fires if the same song is still playing when it is. Builds that don't implement the sandbox query are detected once and then left alone.

nc.on("sandbox", (active) => {
  console.log(active ? "DJ is rehearsing — holding" : "back on air");
});

nc.sandboxed; // current state, also readable synchronously

Live on-air state

track means the song changed, and is deduplicated on song identity alone. Audibility deliberately isn't part of that key — folding it in would fire a full track event every time a fader moves, and anything treating track as "a new song started" would log duplicate history rows.

Audibility gets its own event instead:

nc.on("onair", (active, deck) => {
  console.log(active ? `deck ${deck} is audible` : "nothing on air");
});

nc.onAir; // boolean, live
nc.onAirDeck; // audible deck number, or 0

It fires when the audible deck changes or everything goes silent, and stays quiet while Sandbox is engaged — the audience is still hearing the held track, so claiming it went off air would be wrong.

API

new VirtualDjConnect(options?)

| Option | Type | Default | Description | | ---------------- | -------- | ------------- | --------------------------------------- | | basePath | string | auto-detected | Path to VirtualDJ settings folder | | pollIntervalMs | number | 5000 | How often to poll the M3U history files | | logger | Logger | noopLogger | Logger implementation |

new VirtualDjNetworkControl(options?)

| Option | Type | Default | Description | | ---------------- | ---------- | ------------ | ------------------------------------------- | | host | string | 127.0.0.1 | Host running VirtualDJ | | port | number | 8080 | Network Control plugin port | | bearer | string | — | Bearer password if configured in the plugin | | decks | number[] | [1,2,3,4] | Decks to scan | | pollIntervalMs | number | 1000 | How often to query the plugin | | logger | Logger | noopLogger | Logger implementation |

Events

Both classes emit:

| Event | Payload | Description | | ------- | ------------------------- | ------------------ | | ready | { basePath } (M3U only) | Connector is ready | | track | VirtualDjTrackPayload | New track detected | | error | Error | An error occurred |

VirtualDjNetworkControl additionally emits:

| Event | Payload | Description | | --------- | --------------------------------- | ------------------------------------------------------- | | sandbox | boolean | Sandbox mode was engaged (true) or released (false) | | onair | (active: boolean, deck: number) | The audible deck changed, or everything went silent |

VirtualDjTrackPayload

  • title, artist, remix, album, genre, key
  • bpm — original BPM (unaffected by pitch), when known
  • duration — track length in seconds, when known
  • deck — deck number (1-4), when known
  • isOnAir — whether the deck was audible when the track was detected. This is a snapshot, not live state: a track cued silently and then played keeps isOnAir: false, because playing it is not a new song and does not re-fire track. Use the onair event for live audibility.
  • filePath, fileLocation
  • isBeatportStream, beatportId — set when the track is a Beatport stream

VirtualDjNetworkControl properties

| Property | Type | Description | | -------------- | --------- | ----------------------------------------------------------- | | running | boolean | Whether the poller is active | | baseUrl | string | Resolved http://host:port of the plugin | | pollInterval | number | Current poll interval in ms | | sandboxed | boolean | True while Sandbox mode is engaged and polling is suspended | | onAir | boolean | Whether any deck is currently audible | | onAirDeck | number | The audible deck, or 0 when nothing is on air |

Detection utilities

  • getDefaultVirtualDjPath() — Returns the default VirtualDJ settings folder for the current platform (VDJ 8 preferred, falls back to VDJ 7)
  • detectVirtualDjInstallation(customPath?) — Returns { found, path, version, hasHistory, writeHistoryEnabled }
  • pickOnAirDeck(snapshots) — Helper to pick the on-air deck from a set of Network Control snapshots

Low-level

  • VirtualDjM3uParser — Parses VirtualDJ's M3U history files directly

Related libraries

Part of a family of DJ-software and DJ-hardware connectors:

| Library | What it reads | | ------------------------------------------------------------------- | --------------------------------------------------------------------------- | | rekordbox-connect | rekordbox's SQLCipher-encrypted database, emitting change events | | serato-connect | Serato DJ history, cue points, beatgrids, crates, and the full library | | traktor-connect | Traktor Pro track metadata via its OGG Vorbis broadcast | | djay-connect | djay Pro's NowPlaying.txt, emitting track change events | | alphatheta-connect | AlphaTheta / Pioneer DJ gear over ProDJLink | | StageLinq | Denon DJ gear over the StageLinq protocol | | onelibrary-connect | rekordbox OneLibrary (exportLibrary.db) databases from Pioneer DJ devices | | metadata-connect | Audio metadata from MP3, M4A, FLAC, and AIFF, with partial-file reads |

They share an event shape, so you can run several side by side and treat them interchangeably. All of them power Now Playing — real-time track display for DJs and streamers.

License

MIT