An audio server for the things you're building.
Documentation · API Reference · Configuration · NodeLink vs Lavalink · Troubleshooting
Your application connects directly to NodeLink through its HTTP API or WebSocket gateway. The standard player and session flow is compatible with the Lavalink v4 protocol, so existing clients can connect when they implement the expected requests, events and payloads. NodeLink also has its own additional capabilities, which need client-side support.
The matrix below keeps the structure and notes from the previous README. Its v3 supported?, NodeLink Features? and NodeLink major version columns are historical project notes, not a fresh compatibility test against the current NodeLink build. They should not be read as confirmation of current v4 support.
| Client | Platform | v3 supported? | NodeLink Features? | NodeLink major version | Notes |
|---|---|---|---|---|---|
| Lavalink-Client | JVM | unknown | No | v1 and v2 | |
| Lavalink.kt | Kotlin | unknown | No | v1 | |
| DisGoLink | Go | unknown | No | v1 and v2 | |
| Lavalink.py | Python | Yes | No | v1, v2, v3 | |
| Lavaplay.py | Python | Yes | No | v3 | |
| Mafic | Python | No | No | v1 and v2 | |
| Wavelink | Python | Yes | No | v1, v2, v3 | |
| Sonolink | Python | Yes | No | v3 | |
| Pomice | Python | unknown | No | v1 and v2 | |
| lava-lyra | Python | Yes | Yes | v3 | |
| Hikari-ongaku | Python | unknown | No | v1 and v2 | |
| Moonlink.js | TypeScript | Yes | Yes | v1, v2, v3 | |
| Magmastream | TypeScript | Yes | Yes | v1 and v3 | |
| Lavacord | TypeScript | No | No | v1 and v2 | |
| Shoukaku | TypeScript | Yes | No | v1, v2, v3 | |
| Hoshimi | TypeScript | Yes | No | v1, v2, v3 | ;P |
| Lavalink-Client | TypeScript | Yes | Yes | v1, v3 | |
| Rainlink | TypeScript | unknown | No | v1 and v2 | |
| Poru | TypeScript | unknown | No | v1 and v2 | |
| Blue.ts | TypeScript | unknown | No | v1 and v2 | |
| FastLink | Node.js | Yes | No | v1, v2, v3 | |
| Riffy | Node.js | Yes | No | v1, v2, v3 | |
| TsumiLink | Node.js | unknown | No | v1 and v2 | |
| AquaLink | JavaScript | Yes | Yes | v1, v2, v3 | |
| DisCatSharp | .NET | unknown | No | v1 and v2 | |
| Lavalink4NET | .NET | unknown | No | v1 and v2 | |
| Nomia | .NET | unknown | No | v1 and v2 | |
| CogLink | C | unknown | No | v1 and v2 | |
| Lavalink-rs | Rust, Python | unknown | No | v1 and v2 | |
| nyxx_lavalink | Dart | unknown | No | v1 | |
| Lavalink.lua | Lua | Yes | Yes | v1, v2, v3 | Mambo |
The legacy columns above have not been revalidated against the current NodeLink build. For the requirements a client must implement to use NodeLink-specific capabilities, see Minimum Client Requirements.
Quite a bit happens between asking for a track and hearing it in a voice channel. NodeLink brings those pieces together, but you can start using it without learning the whole pipeline first.
- Sources: 52 source modules are present in the repository, with 49 enabled in the default configuration. Some provide playable streams directly; others resolve metadata and delegate audio retrieval. Credentials or provider-specific setup may still be required.
- Audio processing: decoding paths for Opus, AAC, MP3, FLAC, Ogg Vorbis, WAV/PCM and ALAC, followed by resampling and processing. The production runtime does not depend on FFmpeg.
- Playback tools: 19 filter implementations, layered audio mixing, ducking, fades and transition components. Their availability depends on the filter configuration and the relevant client support.
- Worker-based execution: in cluster mode, NodeLink distributes playback across workers. Each worker can own multiple players; it is not one process per player.
- Extra capabilities: lyrics, chapters, music meaning, SponsorBlock, incoming voice frames, YouTube live chat and stream access endpoints.
- Monitoring: Prometheus metrics and a built-in profiler interface for inspecting the running server.
The sections below show how these parts fit together. For the full set of options and feature-specific instructions, head to the documentation.
|
Enough talking. Let's make some noise. NodeLink runs on Node.js and the default server port is |
Before we start, make sure you have:
- Node.js
>=22.22.2 - Git
git clone https://github.com/PerformanC/NodeLink.git
cd NodeLinknpm installOn Linux and macOS:
cp config.default.ts config.ts
npm run startOn Windows PowerShell:
Copy-Item config.default.ts config.ts
npm run startThat's it. NodeLink will start using the settings in config.ts. To adjust sources, filters, workers, authentication or the listening address, take a look at the Configuration Guide.
The default container port is 3000.
Using Docker Compose:
docker compose up -dOr run a published image directly:
docker run -d --name nodelink -p 3000:3000 ghcr.io/performanc/nodelink:latestFor development builds:
docker run -d --name nodelink-dev -p 3000:3000 ghcr.io/performanc/nodelink:devFor additional deployment details, use the Configuration Guide.
You don't need to know how every part works to use NodeLink. But if you're curious, here's the short version.
NodeLink can run in a single process or in cluster mode. With clustering enabled, the work is split between the primary process and workers.
| Part | What happens there |
|---|---|
| Primary process | Handles HTTP, WebSocket connections, sessions, admission controls and worker coordination. |
| Playback workers | Own players and their audio pipelines, including decoding, DSP and Discord voice connections. One worker can handle multiple players. |
| Specialized Source Worker | An optional process for source-related work such as search, resolution and metadata tasks. |
The usual cluster path looks like this:
- Your application connects to NodeLink through its HTTP API or WebSocket gateway.
- In cluster mode, the primary process manages the session and routes player work to a playback worker.
- The playback worker owns the player's audio pipeline and sends voice packets to Discord.
- When enabled, the Specialized Source Worker handles supported search, resolution and metadata tasks, then returns the results to NodeLink.
The primary and playback workers communicate through local IPC sockets. Source-related tasks use the configured source-worker path when that component is enabled. In single-process mode, the server and playback components run together instead of being split across the cluster.
One important detail: restarting a worker is not the same as seamlessly recovering playback. The voice connection, decoder state and playback buffers live with the worker that owns the player. If that worker exits, audio in progress does not continue transparently; the client needs to reconnect voice and issue playback state again.
A track does not jump straight from a URL to Discord. NodeLink has to find the media, open the right kind of stream, extract its audio, turn it into a common processing format, and then prepare the output for a voice connection. Some sources take different routes through these stages, especially live streams and segmented media.
A request enters through NodeLink's API. The source manager selects a matching source module based on the identifier or search prefix. A source may return a playable stream itself, or it may return track information and hand audio retrieval to another source. Those are different jobs: resolving a title and its metadata does not necessarily mean that the same provider supplies the audio bytes.
When cluster mode and the specialized source worker are enabled, supported search, resolution, lyrics, chapters and other source-related tasks can be handled outside the playback worker. Otherwise, the work follows the configured in-process path.
For ordinary HTTP media, NodeLink can read a progressive response or request specific byte ranges. The range path is used for seekable streams, so a seek does not always require downloading the entire file first. Local-file playback reads from the configured local base path.
Segmented streams have their own handling:
- HLS: NodeLink parses master and media playlists, fetches the referenced segments and includes an AES-128 decryption path for encrypted segments. The handler has paths for both VOD playlists and live playlists with a moving window.
- DASH: the DASH handler reads MPD manifests and handles fragmented MP4 media. The current implementation selects AAC-LC representations; do not assume every codec advertised by a DASH manifest is usable.
The stream processor also has to deal with buffering and backpressure. This keeps incoming data from being treated as if the complete media file were already available in memory.
A container is the package that holds the media tracks and their packets. NodeLink has separate demuxing paths for the containers it encounters:
- MP4 / ISOBMFF / M4A / fragmented MP4: handled with
mp4box, including extraction of AAC and Opus samples. - WebM / Matroska: the WebM demuxer extracts Opus packets from the container blocks.
- FLV: the FLV demuxer reads audio tags containing AAC or MP3 data.
- Ogg: packet handling is used for Ogg streams containing Vorbis or Opus.
This is why container and codec names should not be mixed. MP4, WebM, FLV and Ogg describe how media is packaged; AAC, Opus, MP3 and Vorbis describe how audio is encoded.
After demuxing, the encoded audio packets are sent to the decoder that handles that codec. The current code contains these decoding paths:
| Audio codec / format | Decoder used |
|---|---|
| Opus | @toddynnn/voice-opus |
| AAC | @ecliptia/faad2-wasm (FAAD2 compiled to WebAssembly) |
| MP3, FLAC, Ogg Vorbis, WAV/PCM and ALAC | @toddynnn/symphonia-decoder |
These are the implementations present in the codebase. The list is not a claim that every possible profile, malformed file or provider stream has been tested.
The processing path uses 48 kHz, stereo, signed 16-bit little-endian PCM. Inputs with a different sample rate are resampled using @alexanderolsen/libsamplerate-js, which is compiled to WebAssembly. Bringing decoded audio to a common format lets the filters and mixer work on the same kind of samples instead of having to handle every source format separately.
The PCM stream can pass through the enabled DSP filters and playback components. The filter engine includes equalization, timescale changes, tremolo, vibrato, karaoke, channel mixing, low-pass and high-pass filtering, chorus, compression, echo, phaser and other implementations. Timescale processing uses a WSOLA-based path; animated filter transitions can interpolate parameter changes instead of switching every value abruptly.
The playback path also contains layered audio mixing, ducking, fades and transition components. The mixer combines additional audio layers into a player's output. Ducking can reduce the music level when incoming voice activity is detected. Which components are available to a client depends on the server configuration and the client's implementation.
The processed PCM is encoded to Opus using @toddynnn/voice-opus. The encoded frames are handed to NodeLink's voice transport, which handles the Discord voice connection and sends the packets over the encrypted UDP voice path.
At 48 kHz, a 20 ms PCM frame contains 960 samples per channel. In the internal 16-bit stereo format, that is 3,840 bytes before Opus compression. The stream processor's configured highWaterMark is 19,200 bytes — five of those PCM frames — and backpressure helps keep one stage from overwhelming the next. Opus encoder complexity can be adjusted through playback.audio.opusComplexity (default: 6).
The production audio path is built from JavaScript/TypeScript, native modules and WebAssembly. FFmpeg is not part of the production runtime.
The filter engine includes implementations for:
Equalizer · Timescale · Tremolo · Vibrato · Rotation · Karaoke · Distortion · Channel Mix · Low-pass · High-pass · Chorus · Compressor · Echo · Phaser · Spatial · Reverb · Flanger · Phonograph · Tesseract
The filter registry and the default configuration are not the same list. Some implementations are registered in the engine without appearing as default-enabled configuration options, so check the current configuration and API information before assuming a filter is exposed in a fresh installation.
NodeLink also contains components for layered mixing, ducking, fades and transitions. Features such as Audio Mixer, Voice Receive and SponsorBlock have their own configuration and client-side requirements; they are not automatically available to every client.
The source modules cover music services, video platforms, live streams, local files, HTTP URLs, podcasts and text-to-speech providers.
Some sources provide a playable stream directly. Others resolve a track's metadata and delegate audio retrieval to another source. Finding a track in a provider's catalog does not necessarily mean that provider is delivering the audio bytes.
The default configuration enables 49 of the 52 source modules in the repository. Providers may still require credentials, cookies, tokens, external services or extra configuration. Third-party changes can also affect availability. The Configuration Guide lists the provider-specific options.
Playback is the main job, but it is not the only thing NodeLink can do. A few parts of the server work alongside the player and make it possible to build richer audio experiences.
NodeLink can retrieve synchronized or plain lyrics through /v4/loadlyrics, request chapter information through /v4/loadchapters, and expose music-meaning information through /v4/meaning. Clients that implement the lyrics subscription flow can receive timed lyric updates over WebSocket instead of repeatedly polling for the next line.
The Audio Mixer lets a client add and control extra audio layers in a player's output. That can be used for things like background audio or overlapping sounds without treating every layer as a separate Discord player. The playback components also include ducking, fades and transitions; availability depends on the relevant configuration and client support.
For supported YouTube streams, NodeLink can expose live-chat messages. The Voice Receive endpoint provides a way to receive incoming voice frames from a Discord voice connection. The client still needs to implement the receive-side WebSocket flow; opening the endpoint alone does not make an application process or record the audio.
The /v4/trackstream and /v4/loadstream endpoints expose separate track and raw-stream paths. SponsorBlock can retrieve and skip configured segment categories for supported YouTube tracks. For debugging the server itself, the profiler endpoints and web interface provide runtime inspection tools.
These capabilities belong to NodeLink's extended API. Some need to be enabled in config.ts, and clients must implement the corresponding request, event or WebSocket behavior before they can use them.
NodeLink exposes Prometheus metrics at GET /v4/metrics. The prom-client package is already included as a production dependency; there is no separate installation step.
Metrics availability is controlled by configuration, and the endpoint uses the configured authorization. The registry includes metrics related to API requests, sources, players, workers, memory, CPU and audio-frame counters.
The Troubleshooting guide is a good place to start when something doesn't behave as expected.
NodeLink uses a small set of focused packages for voice transport, decoding, resampling, stream handling and server utilities. Several are maintained by the people and projects around NodeLink; the rest are upstream libraries we build on.
@performanc/pwsl-server— PerformanC's WebSocket server layer. Maintained by PerformanC.@performanc/voice— Discord voice connections and voice packet transport. Maintained by PerformanC.@ecliptia/faad2-wasm— AAC decoding through the FAAD2 WebAssembly build. Published under the Ecliptia scope.@ecliptia/seekable-stream— seekable HTTP streams and byte-range handling. Ecliptia package; source maintained in the 1Lucas1apk repository.@toddynnn/symphonia-decoder— audio decoding for formats handled by the Symphonia-based module. Published under the@toddynnnnpm scope.@toddynnn/voice-opus— Opus encoding and decoding used by the audio path. Published under the@toddynnnnpm scope.@alexanderolsen/libsamplerate-js— sample-rate conversion. Upstream project: aolsenjazz/libsamplerate-js.fastest-validator— configuration and data validation. Upstream project: icebob/fastest-validator.mp4box— MP4 / ISOBMFF parsing and sample extraction. Upstream project: GPAC/MP4Box.js.prom-client— Prometheus metrics collection and exposition. Upstream project: siimon/prom-client.proxy-agent— proxy-agent support for HTTP requests. Upstream project: TooTallNate/proxy-agents.
All packages above are regular runtime dependencies in package.json; prom-client does not need to be installed separately. Development tools such as TypeScript, Biome, Husky and the Node.js/Bun type definitions are listed under devDependencies.
The README is the starting point. The documentation goes deeper when you need the full list of options or request details.
- Configuration — server settings, sources, filters, workers, security and metrics.
- API Reference — REST endpoints and request details.
- Community Plugins — extend NodeLink with community-maintained plugins.
- NodeLink vs Lavalink — where the APIs and features differ.
- Minimum Client Requirements — what a client needs for official NodeLink compatibility.
- Troubleshooting — common problems and things to check.
Want to run the project from source or help improve it? These are the scripts defined in package.json:
npm run start # Start from source
npm run build # Compile TypeScript
npm run type-check # Check types without emitting build outputThe project requires Node.js >=22.22.2. Source-specific test tooling is available under scripts/; check the script and provider setup before running live source checks.
Found something broken? Have an idea? Want to improve a source, add a filter, work on documentation, or just understand how a part of NodeLink works?
Issues and pull requests are welcome. Start with the issue tracker or reach out through the community servers below.
A huge thank you to @RainyXeon for allowing us to incorporate NicoVideo streaming logic from LunaStream.
NodeLink is open-source software released under the GNU General Public License v3.0. See LICENSE for the full text.
Made with love by Brazilian developers. 🇧🇷
Curiosity brought us here. There's room for you too.
Interested in joining the team? Get in touch through the PerformanC Discord or the Ecliptia Discord.
GIFs by Pudgy Penguins, embedded from Tenor. Click a GIF to visit its source.
