YuKumo

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 node

Behavioral 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 / SponsorBlockSegmentSkippedEvent map to the existing segmentsLoaded / segmentSkipped events.
  • 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, setTesseract

They'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 info

REST-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 done

All 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).

On this page