NodeLink Support
NodeLink is a Node.js-based Lavalink alternative. Ryanlink has full NodeLink support via the NodeLinkNode class, which extends RyanlinkNode with all NodeLink-exclusive endpoints.
Set nodeType: 'NodeLink' in your node configuration:
const manager = new RyanlinkManager({ nodes: [{ id: 'nodelink-main', host: 'localhost', port: 3000, authorization: 'youshallnotpass', nodeType: 'NodeLink', }], // ...});Detecting NodeLink
Section titled “Detecting NodeLink”import { NodeLinkNode } from 'ryanlink';
if (player.node.isNodeLink()) { const nodeLinkNode = player.node as NodeLinkNode;}Audio Mixer NodeLink only
Section titled “Audio Mixer ”Overlay TTS, SFX, or background music on top of the main track.
const node = player.node as NodeLinkNode;
// Add a mix layer (volume 0–100)const layer = await node.addMixerLayer(player, ttsTrack, 90);// layer.id, layer.track, layer.volume
// List active layersconst layers = await node.listMixerLayers(player);// layers.mixes[] — { id, track, volume, position, startTime }
// Update volumeawait node.updateMixerLayerVolume(player, layer.id, 50);
// Removeawait node.removeMixerLayer(player, layer.id);Fading NodeLink only
Section titled “Fading ”Smooth volume transitions for track changes, seeking, and pausing.
await node.setFading(player, { enabled: true, trackStart: { duration: 1500, curve: 's-curve' }, trackEnd: { duration: 1000, curve: 'linear' }, seek: { duration: 500, curve: 'linear' }, stop: { duration: 800, curve: 'exponential' }, ducking: { volume: 0.3, duration: 300, curve: 'exponential' },});Supported curves: linear, exponential, logarithmic, s-curve.
Gapless Preloading NodeLink only
Section titled “Gapless Preloading ”Pre-load the next track while the current one is still playing.
// Pre-load next track from queueawait node.setNextTrackGapLess(player);
// Or specify a track explicitlyawait node.setNextTrackGapLess(player, specificTrack);
// Remove preloaded trackawait node.removeNextTrackGapLess(player);Lyrics NodeLink only
Section titled “Lyrics ”loadLyrics
Section titled “loadLyrics”const lyrics = await node.loadLyrics(track, 'en');// Returns NodeLinkLyrics | NodeLinkNoLyrics// lyrics.data.synced — boolean// lyrics.data.lines[] — { text, time, duration }// lyrics.data.source — provider nameloadChapters
Section titled “loadChapters”const chapters = await node.loadChapters(track);// Returns NodeLinkChapter[] | { loadType: 'empty' }// chapter.title, chapter.startTime, chapter.endTime, chapter.durationnodeLinkLyrics (player session endpoint)
Section titled “nodeLinkLyrics (player session endpoint)”const lyrics = await node.nodeLinkLyrics(player, track, 'en');// Uses player.queue.current if track is not providedSubscribe to real-time lyrics
Section titled “Subscribe to real-time lyrics”await node.subscribeLyricsNodeLink(player);await node.subscribeLyricsNodeLink(player, true); // skipTrackSourceawait node.unsubscribeLyricsNodeLink(player);getChapters (player session endpoint)
Section titled “getChapters (player session endpoint)”const chapters = await node.getChapters(player, track);// Uses player.queue.current if track is not providedtimedLyrics (lyrics.kt / java-timed-lyrics)
Section titled “timedLyrics (lyrics.kt / java-timed-lyrics)”const result = await node.timedLyrics.getCurrent(player.guildId);const result2 = await node.timedLyrics.getByVideoId('dQw4w9WgXcQ');const results = await node.timedLyrics.search('never gonna give you up');const genius = await node.timedLyrics.search('never gonna give you up', 'genius');Meaning NodeLink only
Section titled “Meaning ”Wikipedia/Letras background info for a track or artist.
const meaning = await node.getMeaning(track, 'en');// meaning.data.title, .description, .paragraphs[], .url, .provider, .typeDirect Streaming NodeLink only
Section titled “Direct Streaming ”getDirectStream
Section titled “getDirectStream”const stream = await node.getDirectStream(track);// stream.url — direct audio URL// stream.formats[] — { itag, mimeType, qualityLabel, bitrate }
// With specific itagconst stream2 = await node.getDirectStream(track, 251);loadDirectStream (GET)
Section titled “loadDirectStream (GET)”const pcmStream = await node.loadDirectStream(track, 100, 30000, { equalizer: [] });// Returns ReadableStreamloadDirectStreamPost (POST)
Section titled “loadDirectStreamPost (POST)”const pcmStream2 = await node.loadDirectStreamPost(track, 100, 30000, { equalizer: [] });// Returns ReadableStreamTrack Encoding NodeLink only
Section titled “Track Encoding ”// Encode a single track object to base64const encoded = await node.encodeTrack({ identifier: 'dQw4w9WgXcQ', title: '...', /* ... */ });// encoded.encoded — base64 string
// Encode multiple tracksconst encodedList = await node.encodeTracks([track1Info, track2Info]);// encodedList[] — { encoded: string | null, error?: string }YouTube Config NodeLink only
Section titled “YouTube Config ”// Update OAuth/visitorData live (no restart needed)await node.updateYoutubeConfig('1//0gN...refreshToken', 'Cgt...visitorData');
// Get current configconst config = await node.getYoutubeConfig();// config.refreshToken, .visitorData, .isConfigured, .isValid
// Validate token with Googleconst validated = await node.getYoutubeConfig(true);
// Exchange refresh token for access tokenconst oauth = await node.getYoutubeOAUTH(refreshToken);// oauth.access_token, .expires_in, .scope, .token_type
// POST variantconst oauth2 = await node.updateYoutubeOAUTH(refreshToken);Cluster Workers NodeLink only
Section titled “Cluster Workers ”// Get all worker metricsconst workers = await node.getWorkers();// workers.workers[] — { id, clusterId, pid, status, players, uptime, memory }// workers.total — number
// Terminate/restart a workerawait node.patchWorker('ADMIN_CODE', { id: 1 });await node.patchWorker('ADMIN_CODE', { pid: 24560 });await node.patchWorker('ADMIN_CODE', { clusterId: 0 });Multi-Audio Track (Dubs) NodeLink only
Section titled “Multi-Audio Track (Dubs) ”When loading a track, pluginInfo.audioTracks contains available audio streams:
const result = await manager.search({ query: 'https://youtube.com/...' }, user);const track = result.tracks[0];
// Check available audio tracksconsole.log(track.pluginInfo.audioTracks);// [{ id: 'en', name: 'English (Original)', isDefault: true, isAutoDubbed: false }, ...]
// Select a specific audio track languageawait node.changeAudioTrackLanguage(player, 'pt-br');NodeLink Stats NodeLink only
Section titled “NodeLink Stats ”// Detailed stats with per-source and per-endpoint trackingconst stats = await node.getDetailedStats();// stats.players, .playingPlayers, .uptime, .memory, .cpu, .frameStats, .detailedStats
// NodeLink infoconst info = await node.getNodeLinkInfo();// info.version, .buildTime, .git, .sourceManagers, .filters, .plugins
// Connection metricsconst metrics = await node.getConnectionMetrics();// metrics.status, .metrics.speed, .metrics.downloadedBytesNodeLink-Exclusive Filters NodeLink only
Section titled “NodeLink-Exclusive Filters ”const node = player.node as NodeLinkNode;
await node.specificFilters.echo(player, { delay: 0.5, feedback: 0.3, mix: 0.5 });await node.specificFilters.chorus(player, { rate: 1.0, depth: 0.5, delay: 0.02, mix: 0.5, feedback: 0.3 });await node.specificFilters.compressor(player, { threshold: -20, ratio: 4, attack: 5, release: 50, gain: 0 });await node.specificFilters.highPass(player, { smoothing: 20 });await node.specificFilters.phaser(player, { stages: 4, rate: 0.5, depth: 0.5, feedback: 0.3, mix: 0.5, minFrequency: 300, maxFrequency: 3000 });await node.specificFilters.spatial(player, { depth: 0.5, rate: 0.5 });
// Disable a filterawait node.specificFilters.echo(player, {}, true);
// Reset all NodeLink filtersawait node.specificFilters.resetNodeLinkFilters(player);