Skip to content

Similarity Engine

Port for “find tracks similar to this one.” It powers radio mode and “more like this” playlist generation in your own recommendation flows. (The built-in SmartShuffleGenerator does not use it — it shuffles by genre/decade tags only.)

No default ships, this is the one music adapter you wire yourself. Similarity is domain knowledge the player cannot guess: it depends on your catalogue, your metadata, and whatever recommendation service you already run.

TypeScript
import type { ISimilarityEngine } from '@nomercy-entertainment/nomercy-music-player/adapters/similarity-engine';

Interface

TypeScript
interface ISimilarityEngine<T extends BasePlaylistItem = BasePlaylistItem> {
readonly id: string;
findSimilar(seed: T, opts?: SimilarityQueryOptions): Promise<T[]>;
}

interface SimilarityQueryOptions {
limit?: number;
excludeIds?: ReadonlyArray<string | number>;
minScore?: number;
}

id

Human-readable identifier. Used in logging and debug tooling.

findSimilar(seed, opts)

Resolve a list of items similar to seed, ordered by descending similarity score (most similar first). Returns an empty array when no results are available.

OptionTypeDescription
limitnumberMaximum number of results. Default is implementation-defined.
excludeIdsReadonlyArray<string | number>Item ids to skip, typically the ones already in the queue.
minScorenumberMinimum similarity score in [0, 1]. Implementation-defined scale.

Common implementations

There is no single right answer, so the port stays open. Typical backings:

  • Server-driven — the NoMercy media server recommendation endpoint
  • Audio-feature-based — BPM, key, and energy similarity
  • Tag-based — genre, decade, and mood overlap
  • ML embedding — vector proximity in an embedding space
  • External service — Last.fm similar tracks or Spotify recommendations

Custom implementation: server-driven

TypeScript
import type {
ISimilarityEngine,
SimilarityQueryOptions,
} from '@nomercy-entertainment/nomercy-music-player/adapters/similarity-engine';
import type { MusicPlaylistItem } from '@nomercy-entertainment/nomercy-music-player';

class ServerSimilarityEngine implements ISimilarityEngine<MusicPlaylistItem> {
readonly id = 'server-recommendations';

async findSimilar(
seed: MusicPlaylistItem,
opts?: SimilarityQueryOptions,
): Promise<MusicPlaylistItem[]> {
const params = new URLSearchParams({ seed: String(seed.id) });
if (opts?.limit) params.set('limit', String(opts.limit));
if (opts?.minScore) params.set('minScore', String(opts.minScore));

const response = await fetch(`https://api.example.com/similar?${params}`);
const tracks: MusicPlaylistItem[] = await response.json();

const exclude = new Set(opts?.excludeIds ?? []);
return tracks.filter((track) => !exclude.has(track.id));
}
}

See also