Guides

Tracks & Resolution

Work with resolved and unresolved tracks, and narrow them with type guards.

A Track is playable: it carries the base64 encoded string Lavalink needs. An UnresolvedTrack only carries search hints — a title, maybe an author, a URI or an ISRC — and turns into a Track when the player resolves it against a node.

Queue either kind: resolution happens when the track is about to play.

Type guards

TrackResolution groups the guards. The first two narrow the library's own classes; the rest recognise plain objects coming from Lavalink or from storage.

track-type-guards.ts
import {  } from 'hoshimi';
import type { ,  } from 'hoshimi';

declare const :  | ;

if (.()) {
  .(., ..);
}

if (.()) {
  .(..);
}
GuardTrue for
isResolveda Track instance
isUnresolvedan UnresolvedTrack instance
isLavalinkResolveda plain Lavalink track object with encoded and a requester
isLavalinkUnresolveda plain object with info.title and no resolve
isStoredTracka TrackJSON read back from a storage adapter

Queueing a mix

queue-mixed-tracks.ts
import type { , ,  } from 'hoshimi';

declare const : ;
declare const : < | >;

await ..();

if (!.()) {
  await .();
}

Building an unresolved track

Useful when importing a playlist from somewhere that is not Lavalink: keep the metadata now and pay for the search later, only for the tracks that actually get played.

unresolved-track.ts
import {  } from 'hoshimi';
import type {  } from 'hoshimi';

declare const : ;

const  = .(
  {
    : {
      : 'Harder Better Faster Stronger',
      : 'Daft Punk',
      : 'GBDUW0000059',
    },
  },
  { : '123', : 'demo-user' },
);

await ..();

How resolution picks a track

UnresolvedTrack.resolve() tries, in order:

  1. encoded — decoded directly through the node, no search.
  2. info.uri — searched as a URL, first result wins.
  3. A query built from title and author, then narrowed by author name, then by duration (±1.5s), then by ISRC.

It throws a ResolveError when nothing matches, so a bad import surfaces as a failed play() instead of a silent skip.

Key Concepts

  • Requester travels with the track: requester and userData survive resolution, so whatever you attached at search time is still there when the track plays.
  • queue.utils.build() normalises any of the five shapes above into a Track; the player calls it for you.
  • Storage round-trip: tracks saved through a queue adapter come back as TrackJSON, which isStoredTrack recognises and Structures.Track rehydrates.