Base class for creating custom extractors. Extractors handle searching, validating, and streaming audio from various sources.
Constructor
new BaseExtractor<T extends object = object>(
context: ExtractorExecutionContext,
options?: T
)
context
ExtractorExecutionContext
required
The execution context that instantiated this extractor
Initialization options for this extractor
Static Properties
identifier
string
default:"com.discord-player.extractor"
Unique identifier for this extractor. Should be overridden in subclasses
Properties
context
ExtractorExecutionContext
The execution context managing this extractor
Configuration options for this extractor
Priority of this extractor. Higher values execute first (range: any number)
List of query protocols this extractor supports (e.g., ['spotify:', 'https://spotify.com'])
Instance identifier matching the static identifier property
Whether this extractor supports demuxable streams (opus/ogg/webm formats)
Function to create search query for bridging. Defaults to: ${track.title} by ${track.author} official audio
Methods
activate
Called when the extractor is registered and activated.
async activate(): Promise<void>
Example:
class MyExtractor extends BaseExtractor {
async activate() {
// Initialize API client, set up event listeners, etc.
console.log('MyExtractor activated');
}
}
deactivate
Called when the extractor is unregistered or deactivated.
async deactivate(): Promise<void>
Example:
class MyExtractor extends BaseExtractor {
async deactivate() {
// Clean up resources, close connections, etc.
console.log('MyExtractor deactivated');
}
}
Reconfigures the extractor with new options.
async reconfigure(options: T): Promise<void>
New options to apply to the extractor
Example:
await myExtractor.reconfigure({
apiKey: 'new-api-key',
timeout: 5000
});
validate
Validate if this extractor can handle the given query.
async validate(query: string, type?: SearchQueryType | null): Promise<boolean>
The search query to validate
The type of query being validated
true if this extractor can handle the query, false otherwise
Example:
class SpotifyExtractor extends BaseExtractor {
async validate(query: string) {
return query.includes('spotify.com') || query.startsWith('spotify:');
}
}
handle
Handle a search query and return results.
async handle(
query: string,
context: ExtractorSearchContext
): Promise<ExtractorInfo>
The search query to handle
context
ExtractorSearchContext
required
Search context containing:
type: Query type
requestedBy: User who requested the search
requestOptions: HTTP request options
protocol: Protocol being used
Object containing playlist and tracks array
Example:
class MyExtractor extends BaseExtractor {
async handle(query: string, context: ExtractorSearchContext) {
const results = await this.searchAPI(query);
const tracks = results.map(r => new Track(this.context.player, {
title: r.title,
author: r.artist,
url: r.url,
duration: r.duration,
requestedBy: context.requestedBy
}));
return this.createResponse(null, tracks);
}
}
stream
Stream audio for the given track.
async stream(info: Track): Promise<ExtractorStreamable>
return
Promise<ExtractorStreamable>
Can be:
Readable stream
- String URL to audio
- Object with
{ stream: Readable, $fmt: string } where $fmt is the format
Example:
class MyExtractor extends BaseExtractor {
async stream(track: Track) {
// Return URL
return 'https://example.com/audio.mp3';
// Or return stream
const stream = await getAudioStream(track.url);
return stream;
// Or return stream with format
return {
stream: await getOpusStream(track.url),
$fmt: 'opus'
};
}
}
Get related tracks for autoplay.
async getRelatedTracks(
track: Track,
history: GuildQueueHistory
): Promise<ExtractorInfo>
The source track to find related tracks for
history
GuildQueueHistory
required
Queue history to avoid repeating tracks
Object containing related tracks
Example:
class MyExtractor extends BaseExtractor {
async getRelatedTracks(track: Track, history: GuildQueueHistory) {
const related = await this.api.getRelated(track.url);
const tracks = related
.filter(t => !history.tracks.some(h => h.url === t.url))
.map(t => new Track(this.context.player, t));
return this.createResponse(null, tracks);
}
}
bridge
Handle stream extraction for another extractor when the source extractor cannot stream.
async bridge(
track: Track,
sourceExtractor: BaseExtractor | null
): Promise<ExtractorStreamable | null>
The extractor that originally found the track
return
Promise<ExtractorStreamable | null>
Stream/URL if bridging is supported, null otherwise
Example:
class YouTubeExtractor extends BaseExtractor {
async bridge(track: Track, sourceExtractor: BaseExtractor | null) {
// Search YouTube for the track
const query = this.createBridgeQuery(track);
const results = await this.handle(query, {});
if (results.tracks.length === 0) return null;
// Stream the first result
return this.stream(results.tracks[0]);
}
}
handlePostStream
Middleware to process streams before passing to the player.
handlePostStream(stream: Readable, next: NextFunction): void
The incoming audio stream
Callback function: (error?: Error | null, stream?: Readable) => void
Example:
class MyExtractor extends BaseExtractor {
handlePostStream(stream: Readable, next: NextFunction) {
// Transform the stream
const transformed = stream.pipe(someTransform());
// Pass to next middleware
next(null, transformed);
// Or handle errors
transformed.on('error', (err) => next(err));
}
}
createResponse
Create standardized extractor response.
createResponse(
playlist?: Playlist | null,
tracks?: Track[]
): ExtractorInfo
Playlist object if results are from a playlist
Array of tracks. Defaults to playlist tracks if not provided
Standardized response object with playlist and tracks properties
Example:
// Single track
return this.createResponse(null, [track]);
// Multiple tracks without playlist
return this.createResponse(null, tracks);
// Playlist with tracks
return this.createResponse(playlist, tracks);
emit
Dispatch an event to the player.
emit<K extends keyof PlayerEvents>(
event: K,
...args: Parameters<PlayerEvents[K]>
): boolean
event
keyof PlayerEvents
required
The event name to emit
Example:
this.emit('error', queue, error);
this.emit('debug', 'Fetching audio stream...');
debug
Write a debug message.
debug(message: string): void
Example:
this.debug('Searching for tracks...');
this.debug(`Found ${results.length} results`);
Types
Valid return types for the stream() method.
type ExtractorStreamable =
| Readable
| string
| { $fmt: string; stream: Readable };
Standard extractor response format.
interface ExtractorInfo {
playlist: Playlist | null;
tracks: Track[];
}
Context provided to the handle() method.
interface ExtractorSearchContext {
type?: SearchQueryType | null;
requestedBy?: User | null;
requestOptions?: RequestOptions;
protocol?: string | null;
}
NextFunction
Callback for stream middleware.
type NextFunction = (error?: Error | null, stream?: Readable) => void;
Complete Example
import { BaseExtractor, ExtractorInfo, ExtractorSearchContext } from 'discord-player';
import { Track } from 'discord-player';
interface MyExtractorOptions {
apiKey: string;
timeout?: number;
}
class MyExtractor extends BaseExtractor<MyExtractorOptions> {
static identifier = 'com.example.my-extractor';
public priority = 10; // Higher priority
public protocols = ['myservice:', 'https://myservice.com'];
async activate() {
this.debug('MyExtractor activated with API key');
// Initialize API client
}
async deactivate() {
this.debug('MyExtractor deactivated');
// Clean up resources
}
async validate(query: string) {
return query.includes('myservice.com') || query.startsWith('myservice:');
}
async handle(query: string, context: ExtractorSearchContext): Promise<ExtractorInfo> {
this.debug(`Searching for: ${query}`);
// Search your service
const results = await this.searchAPI(query);
// Convert to Track objects
const tracks = results.map(result => new Track(this.context.player, {
title: result.title,
author: result.artist,
url: result.url,
duration: result.duration,
thumbnail: result.thumbnail,
requestedBy: context.requestedBy
}));
return this.createResponse(null, tracks);
}
async stream(track: Track) {
this.debug(`Streaming: ${track.title}`);
// Get stream URL from your service
const streamUrl = await this.getStreamUrl(track.url);
return streamUrl; // Or return a Readable stream
}
async getRelatedTracks(track: Track, history: GuildQueueHistory): Promise<ExtractorInfo> {
const related = await this.api.getRelated(track.url);
const tracks = related
.filter(t => !history.tracks.some(h => h.url === t.url))
.slice(0, 5)
.map(t => new Track(this.context.player, t));
return this.createResponse(null, tracks);
}
private async searchAPI(query: string) {
// Your API implementation
return [];
}
private async getStreamUrl(url: string) {
// Your streaming implementation
return 'https://example.com/stream.mp3';
}
}
// Register the extractor
await player.extractors.register(MyExtractor, {
apiKey: 'your-api-key',
timeout: 5000
});