NodeLink Support
First-class support for NodeLink (PerformanC's Lavalink alternative) — auto-detection, built-in lyrics/chapters, gapless playback, fading, audio mixer, and voice receive.
Detection & node type
A node's server family is detected from /v4/info (isNodelink: true) on the first ready, or set explicitly with type (default "auto"). The legacy isNodeLink: true flag still works.
import { YuKumo, createLavalinkNode, createNodeLinkNode } from "yukumo";
const kumo = new YuKumo({
nodes: [
createLavalinkNode({ host: "ll", port: 2333, password: "x" }),
createNodeLinkNode({ host: "nl", port: 2333, password: "x" }),
// or inline: { host, port, password, type: "auto" | "lavalink" | "nodelink" }
],
send: (guildId, payload) => client.guilds.cache.get(guildId)?.shard.send(payload),
});
// After a node is ready:
node.type; // "lavalink" | "nodelink"
node.version; // server version string
node.sourceManagers; // ["youtube", "soundcloud", "bandcamp", ...]
node.isNodeLink; // true on NodeLink
player.isOnNodeLink; // true when the player is on a NodeLink nodeBehavioral differences YuKumo handles for you:
- Session resuming works on both — NodeLink v3+ supports it, so YuKumo enables the same resume handshake there as on Lavalink (restart recovery).
- NodeLink's
SponsorBlockSegmentsLoadedEvent/SponsorBlockSegmentSkippedEventmap to the existingsegmentsLoaded/segmentSkippedevents. yukumo.getLyrics()/player.getLyrics()transparently route to NodeLink's built-in/v4/loadlyrics— no plugin needed.
Capabilities & mixed pools
Lavalink and NodeLink nodes can share one pool. Each node exposes what it can do, so NodeLink-only features never accidentally run on a plain Lavalink node.
node.supports("lyrics"); // true on NodeLink
node.supports("voiceReceive"); // true on NodeLink
node.supports("extraFilters"); // true when the node has extra DSP filters enabled
node.supportsFilter("echo"); // check a single filter
node.capabilities; // { type, version, features, filters, sourceManagers }Feature requests are routed to a capable node automatically, and you can pick one yourself:
// Routed to a lyrics-capable node in a mixed pool
const lyrics = await kumo.getLyrics(encodedTrack);
// Pick a node for a specific capability
const node = kumo.nodes.pick(guildId, { feature: "voiceReceive" });If no connected node supports a requested feature, YuKumo throws a typed YukumoUnsupportedFeatureError (code: "UNSUPPORTED_FEATURE", with feature, nodeId, nodeType) instead of failing silently. node.assertSupports(feature) runs the same check on demand.
Cross-family failover
When a player moves between a NodeLink node and a Lavalink node, filters the target can't run (the NodeLink extras) are dropped automatically with a debug log — audio keeps playing rather than erroring.
Extra DSP filters
NodeLink adds ten filters beyond the Lavalink standard set, applied through the same API:
await player.setEcho({ delay: 200, feedback: 0.4 });
await player.setReverb({ roomSize: 0.8, damping: 0.5, width: 1 });
await player.setChorus({ depth: 0.5, rate: 1.5 });
// also: setCompressor, setPhaser, setHighPass, setFlanger, setSpatial, setPhonograph, setTesseractThey're gated by capability — calling one on a Lavalink node (or a NodeLink node with that filter disabled) is dropped rather than silently sent.
Built-in lyrics, chapters, meaning
await player.getNodeLinkLyrics("en"); // /v4/loadlyrics — YouTube captions, Musixmatch, LRCLib, Genius
await player.getChapters(); // /v4/loadchapters — YouTube chapter markers
await player.getTrackMeaning(); // /v4/meaning — Wikipedia / Letras.mus.br background infoREST-level (per node): rest.loadLyrics(encoded, lang?), rest.loadChapters(encoded), rest.getMeaning(encoded), rest.getConnectionMetrics().
Gapless playback & fading
// Preload the next track on the node — zero-gap transition
await player.setGaplessNext(nextTrack);
await player.setGaplessNext(null); // clear
// Fade curves: linear | exponential | logarithmic | s-curve
await player.setFading({
trackStart: { duration: 2000, curve: "s-curve" },
trackEnd: { duration: 3000, curve: "exponential" },
});Audio mixer
Overlay TTS or sound effects on top of playback:
const layer = await player.addMixLayer({ track: { encoded: ttsTrack.encoded }, volume: 80 });
await player.getMixLayers();
await player.updateMixLayer(mixId, { volume: 50 });
await player.removeMixLayer(mixId);
yukumo.on("mixStarted", (guildId, mix) => { ... });
yukumo.on("mixEnded", (guildId, mix) => { ... });Voice receive
Capture what users say in the voice channel via NodeLink's /connection/data WebSocket:
const receiver = player.createVoiceReceiver(); // or node.createVoiceReceiver(guildId)
await receiver.connect();
receiver.on("startSpeaking", ({ userId, guildId }) => {
console.log(`${userId} started speaking`);
});
receiver.on("endSpeaking", ({ userId, data, type }) => {
// data = base64 audio, type = "opus" | "pcm"
const audio = Buffer.from(data, "base64");
});
receiver.destroy(); // when doneAll NodeLink-only player APIs throw PlayerError("... requires a NodeLink node") when called against a regular Lavalink node, so mixed pools fail loudly instead of silently.
updatePlayer extensions
rest.updatePlayer() accepts NodeLink extensions directly: nextTrack, fading, and track.audioTrackId (alternate audio streams listed in pluginInfo.audioTracks).
Session Resuming
Survive bot restarts with zero audio downtime — Lavalink session resuming, full player snapshots, and automatic restore.
Autoplay & Utilities
Source-aware autoplay for every music source, plus the 1.7 DX utilities and the 1.8 cross-wrapper DX — offline track building, requester, state getters, repeat aliases, and queue slicing.