Autoplay System
Ryanlink includes a built-in Autoplay class that automatically finds and queues similar tracks when the queue empties. It uses a multi-source fetch chain with weighted scoring to pick the best match.
Enabling Autoplay
Section titled “Enabling Autoplay”Autoplay is triggered via the onEmptyQueue.autoPlayFunction option. Set player.autoplay = true to activate it per-player:
import { RyanlinkManager, Autoplay } from 'ryanlink';
const manager = new RyanlinkManager({ playerOptions: { onEmptyQueue: { autoPlayFunction: async (player, lastTrack) => { if (!player.autoplay) return; await Autoplay.defaultAutoplay(player, lastTrack); }, destroyAfterMs: 20000, // leave VC after 20s if queue empty and autoplay off }, },});
// Enable per-playerplayer.autoplay = true;
// Disableplayer.autoplay = false;AutoplayConfig Options
Section titled “AutoplayConfig Options”Fine-tune the selection algorithm via playerOptions.autoplayConfig:
| Option | Type | Default | Description |
|---|---|---|---|
enabled | boolean | true | Whether the built-in Autoplay class is active |
defaultSource | string | 'ytsearch' | Fallback search source |
limit | number | 1 | Tracks to add per autoplay trigger |
minDuration | number | 20000 | Minimum track duration (ms) |
maxDuration | number | 900000 | Maximum track duration (ms) |
durationTolerance | number | 90000 | Max delta (ms) between last track and candidate for full duration score |
historyLimit | number | 20 | Circular buffer size — tracks in this buffer are never re-queued |
prefetchThreshold | number | 1 | Trigger autoplay early when queue drops to this many tracks |
excludeKeywords | string[] | See below | Keyword blacklist applied to track titles |
fetchRelatedTracks | function | undefined | Custom async function to fetch candidates: (player, lastTrack) => Promise<Track[]> |
Default excluded keywords: nightcore, bass boosted, 8d audio, slowed, reverb, bass boost, pitch shift, speed up, sped up.
const manager = new RyanlinkManager({ playerOptions: { autoplayConfig: { limit: 1, minDuration: 20000, maxDuration: 900000, durationTolerance: 90000, historyLimit: 20, prefetchThreshold: 1, excludeKeywords: ['nightcore', 'slowed', 'reverb', '8d'], }, },});How Selection Works
Section titled “How Selection Works”1. Multi-Source Fetch Chain
Section titled “1. Multi-Source Fetch Chain”The engine tries sources in order until enough candidates are found:
- YouTube —
ytrec:{identifier}recommendation query (if source is YouTube and track is not a stream) - Spotify —
sprec:seed_artists={artistId}&seed_tracks={trackId}(if Spotify IDs are available) - Same-artist search —
{source}search:{artist}on the native source - Title+artist search —
{source}search:{title} {artist}on the native source - Final fallback —
ytsearch:{artist}search
2. Weighted Scoring
Section titled “2. Weighted Scoring”Each candidate is scored (higher = better):
| Factor | Points |
|---|---|
Duration within durationTolerance | 0–40 |
| Same artist (exact match) | +50 |
| Partial artist match | +25 |
| Random jitter | 0–10 |
Candidates are sorted by score descending. The top limit tracks are selected.
3. Filtering
Section titled “3. Filtering”Before scoring, candidates are filtered out if:
- Their identifier is in the history buffer
- Their ISRC is in the history buffer
- Their normalized title is in the history buffer
- Their title contains an excluded keyword
- Their normalized title matches the last track’s normalized title
- Their duration is outside
[minDuration, maxDuration]
4. Circular History Buffer
Section titled “4. Circular History Buffer”Track identifiers are stored in a per-player buffer (autoplay_history_buf). Any track in this buffer is hard-excluded from selection. The buffer is capped at historyLimit entries and synced with player.recentHistory.
5. Pre-fetch
Section titled “5. Pre-fetch”When queue.tracks.length <= prefetchThreshold, autoplay triggers early — the next track is resolved and queued before the current one ends, eliminating the silence gap.
Custom Fetch Function
Section titled “Custom Fetch Function”Override the entire candidate-fetching logic:
const manager = new RyanlinkManager({ playerOptions: { autoplayConfig: { fetchRelatedTracks: async (player, lastTrack) => { // Return an array of Track objects const result = await player.search( { query: `${lastTrack.info.author} radio`, source: 'ytsearch' }, 'Autoplay' ); return result.tracks; }, }, },});Keyword Exclusion
Section titled “Keyword Exclusion”Keywords are matched case-insensitively against the candidate’s title. The last track’s own keywords are not used to filter candidates.
autoplayConfig: { excludeKeywords: ['nightcore', 'bass boosted', '8d audio', 'slowed', 'reverb'],}Runtime Toggle
Section titled “Runtime Toggle”player.autoplay = true; // enableplayer.autoplay = false; // disableThe autoplay flag is also settable via createPlayer:
const player = manager.createPlayer({ guildId: '...', voiceChannelId: '...', autoplay: true,});