Skip to content

Commit

Permalink
fix: add new bridge api
Browse files Browse the repository at this point in the history
  • Loading branch information
twlite committed Jul 20, 2024
1 parent 5d2c517 commit 630dc0f
Show file tree
Hide file tree
Showing 15 changed files with 315 additions and 154 deletions.
8 changes: 4 additions & 4 deletions packages/discord-player/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "discord-player",
"version": "6.6.10",
"version": "6.7.0",
"description": "Complete framework to facilitate music commands using discord.js",
"main": "dist/index.js",
"types": "dist/index.d.ts",
Expand Down Expand Up @@ -64,10 +64,10 @@
"devDependencies": {
"@discord-player/tsconfig": "workspace:^",
"@types/ip": "^1.1.0",
"@types/node": "^18.6.3",
"@types/node": "^20.14.11",
"@types/ws": "^8.5.3",
"discord-api-types": "^0.37.0",
"discord.js": "^14.1.2",
"discord.js": "^14.15.3",
"opusscript": "^0.0.8",
"tsup": "^7.2.0",
"typescript": "^5.2.2",
Expand All @@ -78,4 +78,4 @@
"readmeFile": "./README.md",
"tsconfig": "./tsconfig.json"
}
}
}
9 changes: 6 additions & 3 deletions packages/discord-player/src/Player.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export class Player extends PlayerEventsEmitter<PlayerEvents> {
#lastLatency = -1;
#voiceStateUpdateListener = this.handleVoiceState.bind(this);
#lagMonitorTimeout!: NodeJS.Timeout;
#lagMonitorInterval!: NodeJS.Timer;
#lagMonitorInterval!: NodeJS.Timeout;
#onVoiceStateUpdate: VoiceStateHandler = defaultVoiceStateHandler;
#hooksCtx: Context<HooksCtx> | null = null;
/**
Expand Down Expand Up @@ -144,7 +144,9 @@ export class Player extends PlayerEventsEmitter<PlayerEvents> {
}
} as PlayerInitOptions;

this.client.setMaxListeners(this.client.getMaxListeners() + 1);
// @ts-ignore private method
this.client.incrementMaxListeners();

this.client.on(Events.VoiceStateUpdate, this.#voiceStateUpdateListener);

if (typeof this.options.lagMonitor === 'number' && this.options.lagMonitor > 0) {
Expand Down Expand Up @@ -297,7 +299,8 @@ export class Player extends PlayerEventsEmitter<PlayerEvents> {
public async destroy() {
this.nodes.cache.forEach((node) => node.delete());
this.client.off(Events.VoiceStateUpdate, this.#voiceStateUpdateListener);
this.client.setMaxListeners(this.client.getMaxListeners() - 1);
// @ts-ignore private method
this.client.decrementMaxListeners();
this.removeAllListeners();
this.events.removeAllListeners();
await this.extractors.unregisterAll();
Expand Down
6 changes: 6 additions & 0 deletions packages/discord-player/src/VoiceInterface/VoiceUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,12 @@ class VoiceUtils {
group?: string;
}
) {
const existingConnection = this.getConnection(channel.guild.id, options?.group);

if (existingConnection?.joinConfig.channelId === channel?.id && existingConnection.state.status !== VoiceConnectionStatus.Destroyed) {
return existingConnection;
}

const conn = joinVoiceChannel({
guildId: channel.guild.id,
channelId: channel.id,
Expand Down
10 changes: 10 additions & 0 deletions packages/discord-player/src/extractors/BaseExtractor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,16 @@ export class BaseExtractor<T extends object = object> {
public get supportsDemux() {
return !!this.context.player.options.skipFFmpeg;
}

/**
* Handle stream extraction for another extractor
* @param track The track to bridge
* @param sourceExtractor The source extractor
*/
public async bridge(track: Track, sourceExtractor: BaseExtractor | null): Promise<ExtractorStreamable | null> {
void sourceExtractor;
return null;
}
}

export type NextFunction = (error?: Error | null, stream?: Readable) => void;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { Player } from '../Player';
import { Collection } from '@discord-player/utils';
import { BaseExtractor } from './BaseExtractor';
import { BaseExtractor, ExtractorStreamable } from './BaseExtractor';
import { Util } from '../utils/Util';
import { PlayerEventsEmitter } from '../utils/PlayerEventsEmitter';
import { TypeUtil } from '../utils/TypeUtil';
import { Track } from '../fabric';

// prettier-ignore
const knownExtractorKeys = [
Expand Down Expand Up @@ -221,6 +222,32 @@ export class ExtractorExecutionContext extends PlayerEventsEmitter<ExtractorExec
} as ExtractorExecutionResult<false>;
}

/**
* Request bridge for a track
* @param track The track to request bridge for
* @param sourceExtractor The source extractor of the track
*/
public async requestBridge(track: Track, sourceExtractor: BaseExtractor | null = track.extractor) {
return this.run<ExtractorStreamable>(async (ext) => {
if (sourceExtractor && ext.identifier === sourceExtractor.identifier) return false;
const result = await ext.bridge(track, sourceExtractor);
if (!result) return false;
return result;
});
}

/**
* Request bridge from the specified extractor
* @param track The track to request bridge for
* @param sourceExtractor The source extractor of the track
* @param targetExtractor The target extractor to bridge to
*/
public async requestBridgeFrom(track: Track, sourceExtractor: BaseExtractor | null, targetExtractor: ExtractorResolvable) {
const target = this.resolve(targetExtractor);
if (!target) return null;
return target.bridge(track, sourceExtractor);
}

/**
* Check if extractor is disabled
*/
Expand Down
4 changes: 2 additions & 2 deletions packages/discord-player/src/queue/GuildQueuePlayerNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -603,8 +603,8 @@ export class GuildQueuePlayerNode<Meta = unknown> {
const opusStream = $fmt === StreamType.Opus ?
stream :
$fmt === StreamType.OggOpus ?
stream.pipe(new prism.opus.OggDemuxer()) :
stream.pipe(new prism.opus.WebmDemuxer());
stream.pipe(new prism.opus.OggDemuxer() as any) :

Check warning on line 606 in packages/discord-player/src/queue/GuildQueuePlayerNode.ts

View workflow job for this annotation

GitHub Actions / ESLint

Unexpected any. Specify a different type
stream.pipe(new prism.opus.WebmDemuxer() as any);

Check warning on line 607 in packages/discord-player/src/queue/GuildQueuePlayerNode.ts

View workflow job for this annotation

GitHub Actions / ESLint

Unexpected any. Specify a different type

if (shouldPCM) {
// if we have any filters enabled, we need to decode the opus stream to pcm
Expand Down
4 changes: 2 additions & 2 deletions packages/discord-player/src/queue/SyncedLyricsProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export type LyricsAt = { timestamp: number; line: string };
const timestampPattern = /\[(\d{2}):(\d{2})\.(\d{2})\]/;

export class SyncedLyricsProvider {
#loop: NodeJS.Timer | null = null;
#loop: NodeJS.Timeout | null = null;
#callback: LyricsCallback | null = null;
#onUnsubscribe: Unsubscribe | null = null;

Expand Down Expand Up @@ -116,7 +116,7 @@ export class SyncedLyricsProvider {
const hasLoop = this.#loop !== null;

if (hasLoop) {
clearInterval(this.#loop as NodeJS.Timer);
clearInterval(this.#loop!);
this.#loop = null;
}

Expand Down
5 changes: 4 additions & 1 deletion packages/discord-player/src/queue/VoiceReceiverNode.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { UserResolvable } from 'discord.js';
import { PassThrough, type Readable } from 'stream';
import { PassThrough, type Readable } from 'node:stream';
import { EndBehaviorType } from 'discord-voip';
import * as prism from 'prism-media';
import { StreamDispatcher } from '../VoiceInterface/StreamDispatcher';
Expand Down Expand Up @@ -81,15 +81,18 @@ export class VoiceReceiverNode {
setImmediate(async () => {
if (options.mode === 'pcm') {
const pcm = receiveStream.pipe(
// @ts-ignore
// eslint-disable-next-line @typescript-eslint/no-explicit-any
new (prism.opus || (<any>prism).default.opus).Decoder({
channels: 2,
frameSize: 960,
rate: 48000
})
);
// @ts-ignore
return pcm.pipe(passThrough);
} else {
// @ts-ignore
return receiveStream.pipe(passThrough);
}
}).unref();
Expand Down
6 changes: 3 additions & 3 deletions packages/extractor/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@discord-player/extractor",
"version": "4.4.7",
"version": "4.5.0",
"description": "Extractors for discord-player",
"keywords": [
"discord-player",
Expand Down Expand Up @@ -36,7 +36,7 @@
"devDependencies": {
"@discord-player/tsconfig": "workspace:^",
"@distube/ytdl-core": "^4.13.0",
"@types/node": "^18.11.18",
"@types/node": "^20.14.11",
"discord-player": "workspace:^",
"mediaplex": "^0.0.7",
"play-dl": "^1.9.6",
Expand All @@ -62,4 +62,4 @@
"readmeFile": "./README.md",
"tsconfig": "./tsconfig.json"
}
}
}
30 changes: 20 additions & 10 deletions packages/extractor/src/extractors/AppleMusicExtractor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ export class AppleMusicExtractor extends BridgedExtractor<AppleMusicExtractorIni
source: info,
bridge: this.options.bridgeProvider ? (await this.options.bridgeProvider.resolve(this, track)).data : await pullYTMetadata(this, track)
};
},
}
});

track.extractor = this;
Expand All @@ -251,17 +251,27 @@ export class AppleMusicExtractor extends BridgedExtractor<AppleMusicExtractorIni
return stream;
}

const provider = this.bridgeProvider;
if (!provider) throw new Error(`Could not find bridge provider for '${this.constructor.name}'`);
// legacy
if (!('requestBridge' in this.context)) {
const provider = this.bridgeProvider;
if (!provider) throw new Error(`Could not find bridge provider for '${this.constructor.name}'`);

const data = await provider.resolve(this, info);
if (!data) throw new Error('Failed to bridge this track');

info.setMetadata({
...(info.metadata || {}),
bridge: data.data
});

return await provider.stream(data);
}

const data = await provider.resolve(this, info);
if (!data) throw new Error('Failed to bridge this track');
// new api
const result = await this.context.requestBridge(info, this);

info.setMetadata({
...(info.metadata || {}),
bridge: data.data
});
if (!result?.result) throw new Error('Could not bridge this track');

return await provider.stream(data);
return result.result;
}
}
15 changes: 15 additions & 0 deletions packages/extractor/src/extractors/SoundCloudExtractor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
BaseExtractor,
ExtractorInfo,
ExtractorSearchContext,
ExtractorStreamable,
type GuildQueueHistory,
Playlist,
QueryType,
Expand Down Expand Up @@ -222,4 +223,18 @@ export class SoundCloudExtractor extends BaseExtractor<SoundCloudExtractorInit>

return url;
}

public async bridge(track: Track, sourceExtractor: BaseExtractor | null): Promise<ExtractorStreamable | null> {
if (sourceExtractor?.identifier === this.identifier) {
return this.stream(track);
}

const info = await this.handle(track.url, {
requestedBy: track.requestedBy
});

if (!info.tracks.length) return null;

return this.stream(info.tracks[0]);
}
}
28 changes: 19 additions & 9 deletions packages/extractor/src/extractors/SpotifyExtractor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -356,18 +356,28 @@ export class SpotifyExtractor extends BridgedExtractor<SpotifyExtractorInit> {
return stream;
}

const provider = this.bridgeProvider;
if (!provider) throw new Error(`Could not find bridge provider for '${this.constructor.name}'`);
// legacy api
if (!('requestBridge' in this.context)) {
const provider = this.bridgeProvider;
if (!provider) throw new Error(`Could not find bridge provider for '${this.constructor.name}'`);

const data = await provider.resolve(this, info);
if (!data) throw new Error('Failed to bridge this track');
const data = await provider.resolve(this, info);
if (!data) throw new Error('Failed to bridge this track');

info.setMetadata({
...(info.metadata || {}),
bridge: data.data
});
info.setMetadata({
...(info.metadata || {}),
bridge: data.data
});

return await provider.stream(data);
}

// new api
const result = await this.context.requestBridge(info, this);

if (!result?.result) throw new Error('Could not bridge this track');

return await provider.stream(data);
return result.result;
}

public parse(q: string) {
Expand Down
18 changes: 17 additions & 1 deletion packages/extractor/src/extractors/YoutubeExtractor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,9 @@ export class YoutubeExtractor extends BaseExtractor<YoutubeExtractorInit> {
this._ytLibName = name;
}

process.emitWarning("YoutubeExtractor uses scraping-based streaming libraries which is known to be unstable at times.\nAn alternative is to use discord-player-youtubei https://npm.im/discord-player-youtubei\nView this GitHub issue for more information: https://github.com/Androz2091/discord-player/issues/1922")
process.emitWarning(
'YoutubeExtractor uses scraping-based streaming libraries which is known to be unstable at times.\nAn alternative is to use discord-player-youtubei https://npm.im/discord-player-youtubei\nView this GitHub issue for more information: https://github.com/Androz2091/discord-player/issues/1922'
);

YoutubeExtractor.instance = this;
}
Expand Down Expand Up @@ -261,6 +263,20 @@ export class YoutubeExtractor extends BaseExtractor<YoutubeExtractorInit> {
return this._stream(url, this, this.supportsDemux);
}

public async bridge(track: Track, sourceExtractor: BaseExtractor | null): Promise<ExtractorStreamable | null> {
if (sourceExtractor?.identifier === this.identifier) {
return this.stream(track);
}

const info = await this.handle(track.url, {
requestedBy: track.requestedBy
});

if (!info.tracks.length) return null;

return this.stream(info.tracks[0]);
}

public static validateURL(link: string) {
try {
YoutubeExtractor.parseURL(link);
Expand Down
6 changes: 2 additions & 4 deletions packages/extractor/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
{
"extends": "@discord-player/tsconfig/base.json",
"include": [
"src/**/*"
, "../discord-player/src/utils/IPRotator.ts" ]
}
"include": ["src/**/*"]
}
Loading

0 comments on commit 630dc0f

Please sign in to comment.