update discordeno-audio-plugin, add playlist ability
This commit is contained in:
parent
c7c049a097
commit
50e3c4ed21
|
@ -1 +1,4 @@
|
|||
/configs.ts
|
||||
/configs.ts
|
||||
/errors.txt
|
||||
/unused_extras.ts
|
||||
/test
|
|
@ -46,6 +46,10 @@ export async function parseCommand(bot: Bot, interaction: Interaction) {
|
|||
await skip(bot, interaction);
|
||||
break;
|
||||
}
|
||||
case "stop": {
|
||||
await pause(bot, interaction);
|
||||
break;
|
||||
}
|
||||
case "unloop": {
|
||||
await unloop(bot, interaction);
|
||||
break;
|
||||
|
|
|
@ -7,9 +7,17 @@ import {
|
|||
type CreateSlashApplicationCommand
|
||||
} from "../deps.ts";
|
||||
|
||||
import { ensureVoiceConnection, formatCallbackData, waitingForResponse } from "../utils.ts";
|
||||
import { YouTube } from "../discordeno-audio-plugin/deps.ts";
|
||||
|
||||
function addedToQueueResponse(interaction: Interaction, title: string) {
|
||||
import { ensureVoiceConnection, formatCallbackData, isPlaylist, waitingForResponse } from "../utils.ts";
|
||||
|
||||
async function addedPlaylistResponse(interaction: Interaction, url: string) {
|
||||
const playlist = await YouTube.getPlaylist(url);
|
||||
return formatCallbackData(`${interaction.user.username} added ${playlist.videoCount} videos from [**${playlist.title}**](${interaction!.data!.options![0].value}) to the queue.`,
|
||||
"Added to queue");
|
||||
}
|
||||
|
||||
function addedSongResponse(interaction: Interaction, title: string) {
|
||||
return formatCallbackData(`${interaction.user.username} added [**${title}**](${interaction!.data!.options![0].value}) to the queue.`, "Added to queue");
|
||||
}
|
||||
|
||||
|
@ -74,7 +82,12 @@ export async function play(bot: Bot, interaction: Interaction) {
|
|||
|
||||
const result = await player.pushQuery(interaction.guildId, interaction.user.username, href);
|
||||
if(result && result[0] && parsed_url.href.indexOf("youtube.com") !== -1 || parsed_url.href.indexOf("youtu.be") !== -1 && result[0].title) {
|
||||
await editOriginalInteractionResponse(bot, interaction.token, addedToQueueResponse(interaction, result[0].title));
|
||||
if(isPlaylist(parsed_url.href))
|
||||
{
|
||||
await editOriginalInteractionResponse(bot, interaction.token, await addedPlaylistResponse(interaction, parsed_url.href));
|
||||
} else {
|
||||
await editOriginalInteractionResponse(bot, interaction.token, addedSongResponse(interaction, result[0].title));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// restart the player if there's no url
|
||||
|
|
|
@ -2,5 +2,5 @@ export * from "https://deno.land/x/discordeno@18.0.1/mod.ts";
|
|||
export * from "https://deno.land/x/discordeno@18.0.1/plugins/cache/mod.ts";
|
||||
export * as opus from "https://unpkg.com/@evan/wasm@0.0.95/target/opus/deno.js";
|
||||
export * from "https://unpkg.com/@evan/wasm@0.0.95/target/nacl/deno.js";
|
||||
export { ytDownload } from "https://deno.land/x/yt_download@1.7/mod.ts";
|
||||
export { getVideoInfo, ytDownload } from "https://deno.land/x/yt_download@1.7/mod.ts";
|
||||
export { default as YouTube } from "https://deno.land/x/youtube_sr@v4.1.17/mod.ts";
|
|
@ -1,7 +1,23 @@
|
|||
import { YouTube } from "../../deps.ts";
|
||||
import { getYoutubeSources } from "./youtube.ts";
|
||||
|
||||
import { isPlaylist } from "../../../utils.ts";
|
||||
|
||||
export type LoadSource = typeof loadLocalOrYoutube;
|
||||
|
||||
export function loadLocalOrYoutube(query: string, guildId: bigint, added_by?: string) {
|
||||
return getYoutubeSources(guildId, String(added_by), query);
|
||||
export async function loadLocalOrYoutube(query: string, guildId: bigint, added_by?: string) {
|
||||
const queries = [];
|
||||
|
||||
if(isPlaylist(query))
|
||||
{
|
||||
const playlist = await YouTube.getPlaylist(query);
|
||||
for(const video of playlist.videos) {
|
||||
const videoId = video.id ? video.id : "";
|
||||
queries.push(videoId);
|
||||
}
|
||||
} else {
|
||||
queries.push(query);
|
||||
}
|
||||
|
||||
return getYoutubeSources(guildId, String(added_by), queries);
|
||||
}
|
||||
|
|
|
@ -1,22 +1,63 @@
|
|||
import { YouTube, ytDownload } from "../../deps.ts";
|
||||
import { getVideoInfo, YouTube, ytDownload } from "../../deps.ts";
|
||||
import { bufferIter, retry } from "../../utils/mod.ts";
|
||||
import { demux } from "../demux/mod.ts";
|
||||
import { createAudioSource, empty } from "./audio-source.ts";
|
||||
|
||||
import { errorMessageCallback, parseYoutubeId } from "../../../utils.ts";
|
||||
import { errorMessageCallback, isPlaylist } from "../../../utils.ts";
|
||||
|
||||
export async function getYoutubeSources(guildId: bigint, added_by?: string, ...queries: string[]) {
|
||||
export async function getYoutubeSources(guildId: bigint, added_by?: string, queries: string[]) {
|
||||
const sources = queries.map((query) => getYoutubeSource(query, guildId, added_by));
|
||||
const awaitedSources = await Promise.all(sources);
|
||||
|
||||
return awaitedSources
|
||||
.filter((source) => source !== undefined)
|
||||
.map((source) => source!);
|
||||
}
|
||||
|
||||
export async function getYoutubeSource(query: string, guildId: bigint, added_by?: string) {
|
||||
/*export async function getYoutubeSource(query: string, guildId: bigint, added_by?: string) {
|
||||
if(isPlaylist(query)) {
|
||||
const playlist = await YouTube.getPlaylist(query);
|
||||
const count = playlist.videoCount;
|
||||
const sources = [];
|
||||
|
||||
for(const video of playlist.videos) {
|
||||
const videoId = video.id ? video.id : "";
|
||||
sources.push(getVideo(videoId, guildId, added_by));
|
||||
}
|
||||
|
||||
return sources;
|
||||
}
|
||||
|
||||
return await getVideo(query, guildId, added_by);
|
||||
}*/
|
||||
|
||||
async function getYoutubeSource(query: string, guildId: bigint, added_by?: string) {
|
||||
try {
|
||||
query = parseYoutubeId(query);
|
||||
const results = await YouTube.search(query, { limit: 1, type: "video" });
|
||||
const result = await getVideoInfo(query);
|
||||
if(result.videoDetails.videoId) {
|
||||
const id = result.videoDetails.videoId;
|
||||
const title = result.videoDetails.title;
|
||||
|
||||
return createAudioSource(title, async () => {
|
||||
const stream = await retry(
|
||||
async () =>
|
||||
await ytDownload(id, {
|
||||
mimeType: `audio/webm; codecs="opus"`,
|
||||
})
|
||||
);
|
||||
if (stream === undefined) {
|
||||
errorMessageCallback(guildId, `There was an error trying to play **${title}**:\n
|
||||
The stream couldn't be found`);
|
||||
console.log(`Failed to play ${title}\n Returning empty stream`);
|
||||
return empty();
|
||||
}
|
||||
return bufferIter(demux(stream));
|
||||
}, guildId, added_by);
|
||||
}
|
||||
//const result = await ytDownload(query, { mimeType: `audio/webm; codecs="opus"`, });
|
||||
//console.log(result);
|
||||
|
||||
/*const results = await YouTube.search(query, { limit: 1, type: "video" });
|
||||
if (results.length > 0) {
|
||||
const { id, title } = results[0];
|
||||
return createAudioSource(title!, async () => {
|
||||
|
@ -28,13 +69,13 @@ export async function getYoutubeSource(query: string, guildId: bigint, added_by?
|
|||
);
|
||||
if (stream === undefined) {
|
||||
errorMessageCallback(guildId, `There was an error trying to play **${title}**:\n
|
||||
something broke in getYoutubeSource`);
|
||||
The stream couldn't be found`);
|
||||
console.log(`Failed to play ${title}\n Returning empty stream`);
|
||||
return empty();
|
||||
}
|
||||
return bufferIter(demux(stream));
|
||||
}, guildId, added_by);
|
||||
}
|
||||
}*/
|
||||
} catch(err) {
|
||||
console.error(err);
|
||||
return undefined;
|
||||
|
|
|
@ -7,17 +7,17 @@ import { sendAudioPacket } from "./udp/packet.ts";
|
|||
export type BotData = {
|
||||
bot: Bot;
|
||||
guildData: Map<bigint, ConnectionData>;
|
||||
udpSource: EventSource<[UdpArgs]>;
|
||||
udpSource: EventSource<UdpArgs>;
|
||||
bufferSize: number;
|
||||
loadSource: (query: string, guild_id: bigint, added_by?: string) => AudioSource[] | Promise<AudioSource[]>;
|
||||
};
|
||||
|
||||
export type ConnectionData = {
|
||||
player: QueuePlayer;
|
||||
audio: EventSource<[Uint8Array]>;
|
||||
audio: EventSource<Uint8Array>;
|
||||
guildId: bigint;
|
||||
udpSocket: Deno.DatagramConn;
|
||||
udpStream: () => AsyncIterableIterator<Uint8Array>;
|
||||
udpRaw: EventSource<Uint8Array>;
|
||||
ssrcToUser: Map<number, bigint>;
|
||||
usersToSsrc: Map<bigint, number>;
|
||||
context: {
|
||||
|
@ -56,7 +56,7 @@ function randomNBit(n: number) {
|
|||
|
||||
export function createBotData(
|
||||
bot: Bot,
|
||||
udpSource: EventSource<[UdpArgs]>,
|
||||
udpSource: EventSource<UdpArgs>,
|
||||
loadSource: LoadSource,
|
||||
bufferSize = 10
|
||||
) {
|
||||
|
@ -97,12 +97,12 @@ export function getConnectionData(botId: bigint, guildId: bigint) {
|
|||
}
|
||||
}
|
||||
udpSocket = udpSocket as Deno.DatagramConn;
|
||||
const udpReceive = new EventSource<[Uint8Array]>();
|
||||
const udpRaw = new EventSource<Uint8Array>();
|
||||
data = {
|
||||
player: undefined as unknown as QueuePlayer,
|
||||
guildId,
|
||||
udpSocket,
|
||||
udpStream: () => udpReceive.iter().map(([packet]) => packet),
|
||||
udpRaw,
|
||||
context: {
|
||||
ssrc: 1,
|
||||
ready: false,
|
||||
|
@ -113,7 +113,7 @@ export function getConnectionData(botId: bigint, guildId: bigint) {
|
|||
reconnect: 0,
|
||||
},
|
||||
connectInfo: {},
|
||||
audio: new EventSource<[Uint8Array]>(),
|
||||
audio: new EventSource<Uint8Array>(),
|
||||
ssrcToUser: new Map<number, bigint>(),
|
||||
usersToSsrc: new Map<bigint, number>(),
|
||||
stopHeart: () => {},
|
||||
|
@ -125,7 +125,7 @@ export function getConnectionData(botId: bigint, guildId: bigint) {
|
|||
guildId,
|
||||
udpSocket,
|
||||
botData.udpSource,
|
||||
udpReceive
|
||||
udpRaw
|
||||
);
|
||||
connectAudioIterable(data);
|
||||
}
|
||||
|
@ -133,7 +133,7 @@ export function getConnectionData(botId: bigint, guildId: bigint) {
|
|||
}
|
||||
|
||||
async function connectAudioIterable(conn: ConnectionData) {
|
||||
for await (const [chunk] of conn.audio.iter()) {
|
||||
for await (const chunk of conn.audio.iter()) {
|
||||
await sendAudioPacket(conn, chunk);
|
||||
}
|
||||
}
|
||||
|
@ -155,8 +155,8 @@ async function connectSocketToSource(
|
|||
bot: Bot,
|
||||
guildId: bigint,
|
||||
socket: Deno.DatagramConn,
|
||||
source: EventSource<[UdpArgs]>,
|
||||
localSource: EventSource<[Uint8Array]>
|
||||
source: EventSource<UdpArgs>,
|
||||
localSource: EventSource<Uint8Array>
|
||||
) {
|
||||
for await (const [data, _address] of socket) {
|
||||
source.trigger({ bot, guildId, data });
|
||||
|
|
|
@ -26,15 +26,11 @@ type ReceivedAudio = {
|
|||
};
|
||||
|
||||
export function createOnAudio(
|
||||
source: EventSource<
|
||||
[
|
||||
{
|
||||
bot: Bot;
|
||||
guildId: bigint;
|
||||
data: Uint8Array;
|
||||
}
|
||||
]
|
||||
>
|
||||
source: EventSource<{
|
||||
bot: Bot;
|
||||
guildId: bigint;
|
||||
data: Uint8Array;
|
||||
}>
|
||||
) {
|
||||
return (
|
||||
guild: bigint | bigint[],
|
||||
|
@ -44,7 +40,6 @@ export function createOnAudio(
|
|||
const users = asArray(user);
|
||||
return source
|
||||
.iter()
|
||||
.map(([udp]) => udp)
|
||||
.filter(({ data }) => {
|
||||
return data[1] === 120;
|
||||
})
|
||||
|
|
|
@ -33,7 +33,7 @@ export function enableAudioPlugin<T extends Bot>(
|
|||
}
|
||||
|
||||
function createAudioHelpers(bot: Bot, loadSource: LoadSource) {
|
||||
const udpSource = new EventSource<[UdpArgs]>();
|
||||
const udpSource = new EventSource<UdpArgs>();
|
||||
createBotData(bot, udpSource, loadSource);
|
||||
const resetPlayer = (guildId: bigint) => {
|
||||
const conn = getConnectionData(bot.id, guildId);
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
import { BasicSource } from "../../utils/event-source.ts";
|
||||
|
||||
export type AllEventTypes = RawEventTypes | QueueEventTypes;
|
||||
export type RawEventTypes = "next" | "done";
|
||||
export type QueueEventTypes = "loop";
|
||||
|
||||
export type PlayerListener<T, J extends AllEventTypes> = (
|
||||
data: EventData<T>[J]
|
||||
) => void;
|
||||
|
||||
type PlayerEvent<T, J extends AllEventTypes> = {
|
||||
type: J;
|
||||
data: EventData<T>[J];
|
||||
};
|
||||
type EventData<T> = {
|
||||
next: T;
|
||||
done: T;
|
||||
loop: T;
|
||||
};
|
||||
|
||||
export class PlayerEventSource<T, K extends AllEventTypes> {
|
||||
#source = new BasicSource<PlayerEvent<T, K>>();
|
||||
|
||||
on<J extends K>(event: J, listener: PlayerListener<T, J>) {
|
||||
return this.#source.listen((value) => {
|
||||
if (value.type === event) {
|
||||
listener((value as PlayerEvent<T, J>).data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
trigger<J extends K>(event: J, data: EventData<T>[J]) {
|
||||
this.#source.trigger({ type: event, data });
|
||||
}
|
||||
}
|
|
@ -1,50 +1,64 @@
|
|||
import { Queue } from "../../utils/mod.ts";
|
||||
import { AudioSource, LoadSource } from "../audio-source/mod.ts";
|
||||
import { ConnectionData } from "../connection-data.ts";
|
||||
import { PlayerEventSource, AllEventTypes, PlayerListener } from "./events.ts";
|
||||
import { RawPlayer } from "./raw-player.ts";
|
||||
import { Player } from "./types.ts";
|
||||
|
||||
import { nowPlayingCallback } from "../../../commands/np.ts";
|
||||
|
||||
export class QueuePlayer extends Queue<AudioSource> implements Player {
|
||||
playing = false;
|
||||
export class QueuePlayer
|
||||
extends Queue<AudioSource>
|
||||
implements Player<AudioSource>
|
||||
{
|
||||
playing = true;
|
||||
looping = false;
|
||||
playingSince?: number;
|
||||
nowPlaying?: AudioSource;
|
||||
#rawPlayer: RawPlayer;
|
||||
#loadSource: LoadSource;
|
||||
#events = new PlayerEventSource<AudioSource, AllEventTypes>();
|
||||
|
||||
constructor(conn: ConnectionData, loadSource: LoadSource) {
|
||||
super();
|
||||
this.#loadSource = loadSource;
|
||||
this.#rawPlayer = new RawPlayer(conn);
|
||||
this.#startQueue();
|
||||
this.playing = true;
|
||||
super.waiting = true;
|
||||
this.playNext();
|
||||
this.#rawPlayer.on("done", async () => {
|
||||
const current = this.current();
|
||||
if (current) {
|
||||
this.#events.trigger("done", current);
|
||||
}
|
||||
await this.playNext();
|
||||
if (!this.playing) {
|
||||
this.pause();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async #setSong(song: AudioSource) {
|
||||
async playNext() {
|
||||
let song;
|
||||
const current = this.current();
|
||||
if (this.looping && current !== undefined) {
|
||||
song = current;
|
||||
this.#events.trigger("loop", song);
|
||||
} else {
|
||||
song = await super.next();
|
||||
this.#events.trigger("next", song);
|
||||
}
|
||||
this.playingSince = Date.now();
|
||||
this.nowPlaying = song;
|
||||
this.#rawPlayer.setAudio(await song.data());
|
||||
await nowPlayingCallback(this.#rawPlayer.conn);
|
||||
await this.#rawPlayer.onDone();
|
||||
if (this.looping) {
|
||||
this.#setSong(song);
|
||||
} else {
|
||||
this.triggerNext();
|
||||
}
|
||||
}
|
||||
|
||||
async #startQueue() {
|
||||
for await (const [song] of this.stream()) {
|
||||
await this.#setSong(song);
|
||||
}
|
||||
clear() {
|
||||
return super.clear();
|
||||
}
|
||||
|
||||
play() {
|
||||
this.#rawPlayer.play();
|
||||
this.playing = true;
|
||||
this.#rawPlayer.play();
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
|
@ -54,12 +68,12 @@ export class QueuePlayer extends Queue<AudioSource> implements Player {
|
|||
}
|
||||
|
||||
stop() {
|
||||
this.playingSince = undefined;
|
||||
this.#rawPlayer.clear();
|
||||
this.skip();
|
||||
this.pause();
|
||||
}
|
||||
|
||||
skip() {
|
||||
this.looping = false;
|
||||
this.#rawPlayer.clear();
|
||||
}
|
||||
|
||||
|
@ -71,8 +85,23 @@ export class QueuePlayer extends Queue<AudioSource> implements Player {
|
|||
this.#rawPlayer.interrupt(undefined);
|
||||
}
|
||||
|
||||
interrupt(audio: AsyncIterableIterator<Uint8Array>) {
|
||||
this.#rawPlayer.interrupt(audio);
|
||||
/**
|
||||
* Listen to events:
|
||||
*
|
||||
* `next`: New sound started playing
|
||||
*
|
||||
* `done`: Last sound is done playing
|
||||
*
|
||||
* `loop`: New loop iteration was started
|
||||
* @param event Event to listen to
|
||||
* @param listener Triggered on event
|
||||
* @returns Function that disconnects the listener
|
||||
*/
|
||||
on<J extends AllEventTypes>(
|
||||
event: J,
|
||||
listener: PlayerListener<AudioSource, J>
|
||||
) {
|
||||
return this.#events.on(event, listener);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,33 +1,23 @@
|
|||
import { EventSource } from "../../utils/mod.ts";
|
||||
import { ConnectionData } from "../connection-data.ts";
|
||||
import { FRAME_DURATION } from "../sample-consts.ts";
|
||||
import { Player } from "./types.ts";
|
||||
import { setDriftlessInterval, clearDriftless } from "npm:driftless";
|
||||
import { PlayerEventSource, RawEventTypes, PlayerListener } from "./events.ts";
|
||||
|
||||
export class RawPlayer implements Player {
|
||||
import { errorMessageCallback } from "../../../utils.ts";
|
||||
|
||||
export class RawPlayer implements Player<AsyncIterableIterator<Uint8Array>> {
|
||||
#audio?: AsyncIterableIterator<Uint8Array>;
|
||||
#interrupt?: AsyncIterableIterator<Uint8Array>;
|
||||
playing = false;
|
||||
conn: ConnectionData;
|
||||
#doneSource = new EventSource<[]>();
|
||||
#nextSource = new EventSource<[]>();
|
||||
#onNext = () => this.#nextSource.iter().nextValue();
|
||||
#events = new PlayerEventSource<
|
||||
AsyncIterableIterator<Uint8Array>,
|
||||
RawEventTypes
|
||||
>();
|
||||
|
||||
constructor(conn: ConnectionData) {
|
||||
this.conn = conn;
|
||||
this.play();
|
||||
}
|
||||
|
||||
setAudio(audio: AsyncIterableIterator<Uint8Array>) {
|
||||
this.#audio = audio;
|
||||
this.#nextSource.trigger();
|
||||
}
|
||||
|
||||
interrupt(audio?: AsyncIterableIterator<Uint8Array>) {
|
||||
this.#interrupt = audio;
|
||||
if (this.#audio === undefined) {
|
||||
this.#nextSource.trigger();
|
||||
}
|
||||
}
|
||||
|
||||
play() {
|
||||
|
@ -40,28 +30,16 @@ export class RawPlayer implements Player {
|
|||
const inter = setDriftlessInterval(async () => {
|
||||
if (this.playing === false) {
|
||||
clearDriftless(inter);
|
||||
}
|
||||
if (this.#interrupt) {
|
||||
const { done, value } = await this.#interrupt.next();
|
||||
if (done) {
|
||||
this.#interrupt = undefined;
|
||||
} else {
|
||||
this.conn.audio.trigger(value);
|
||||
return;
|
||||
}
|
||||
}
|
||||
const nextAudioIter = await this.#audio?.next();
|
||||
if (nextAudioIter === undefined || nextAudioIter.done) {
|
||||
this.#audio = undefined;
|
||||
this.#doneSource.trigger();
|
||||
this.playing = false;
|
||||
await this.#onNext();
|
||||
this.play();
|
||||
return;
|
||||
}
|
||||
this.conn.audio.trigger(nextAudioIter.value);
|
||||
const frame = await this.#getFrame();
|
||||
if (frame === undefined) {
|
||||
return;
|
||||
}
|
||||
this.conn.audio.trigger(frame);
|
||||
}, FRAME_DURATION);
|
||||
} catch(err) {
|
||||
errorMessageCallback(this.conn.guildId, `The player broke for some reason: ${err}`);
|
||||
console.log("error while playing!!");
|
||||
console.error(err);
|
||||
}
|
||||
|
@ -72,16 +50,61 @@ export class RawPlayer implements Player {
|
|||
}
|
||||
|
||||
stop() {
|
||||
this.pause();
|
||||
this.clear();
|
||||
this.pause();
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.#audio = undefined;
|
||||
if (this.#audio) {
|
||||
this.#events.trigger("done", this.#audio);
|
||||
}
|
||||
this.#interrupt = undefined;
|
||||
}
|
||||
|
||||
async onDone() {
|
||||
await this.#doneSource.iter().nextValue();
|
||||
setAudio(audio: AsyncIterableIterator<Uint8Array>) {
|
||||
this.#audio = audio;
|
||||
this.#events.trigger("next", audio);
|
||||
this.play();
|
||||
}
|
||||
|
||||
interrupt(audio?: AsyncIterableIterator<Uint8Array>) {
|
||||
this.#interrupt = audio;
|
||||
if (!this.playing) {
|
||||
this.play();
|
||||
}
|
||||
}
|
||||
|
||||
on<J extends RawEventTypes>(
|
||||
event: J,
|
||||
listener: PlayerListener<AsyncIterableIterator<Uint8Array>, J>
|
||||
) {
|
||||
return this.#events.on(event, listener);
|
||||
}
|
||||
|
||||
async #getFrame() {
|
||||
const interrupt = await this.#getNextFrame(this.#interrupt);
|
||||
if (interrupt !== undefined) {
|
||||
return interrupt;
|
||||
}
|
||||
const audio = await this.#getNextFrame(this.#audio);
|
||||
if (audio === undefined) {
|
||||
this.#handleAudioStopped();
|
||||
}
|
||||
return audio;
|
||||
}
|
||||
|
||||
async #getNextFrame(source: AsyncIterableIterator<Uint8Array> | undefined) {
|
||||
const nextResult = await source?.next();
|
||||
return nextResult !== undefined && !nextResult?.done
|
||||
? nextResult.value
|
||||
: undefined;
|
||||
}
|
||||
|
||||
#handleAudioStopped() {
|
||||
if (this.#audio !== undefined) {
|
||||
this.#events.trigger("done", this.#audio);
|
||||
}
|
||||
this.#audio = undefined;
|
||||
this.playing = false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,13 @@
|
|||
export type Player = {
|
||||
import { PlayerListener, RawEventTypes } from "./events.ts";
|
||||
|
||||
export type Player<T> = {
|
||||
playing: boolean;
|
||||
play(): void;
|
||||
pause(): void;
|
||||
stop(): void;
|
||||
clear(): void;
|
||||
interrupt(audio: AsyncIterableIterator<Uint8Array>): void;
|
||||
on<J extends RawEventTypes>(
|
||||
event: J,
|
||||
listener: PlayerListener<T, J>
|
||||
): () => void;
|
||||
};
|
||||
|
|
|
@ -12,7 +12,7 @@ export async function discoverIP(
|
|||
port,
|
||||
transport: "udp",
|
||||
});
|
||||
const { value } = await conn.udpStream().next();
|
||||
const value = await conn.udpRaw.next();
|
||||
const decoder = new TextDecoder();
|
||||
const localIp = decoder.decode(value.slice(8, value.indexOf(0, 8)));
|
||||
const localPort = new DataView(value.buffer).getUint16(72, false);
|
||||
|
|
|
@ -1,28 +1,17 @@
|
|||
import { IterSource, fromCallback } from "./iterator/mod.ts";
|
||||
import { Arr } from "./types.ts";
|
||||
|
||||
type Listener<T extends Arr> = (...arg: T) => void;
|
||||
type Listener<T> = (arg: T) => void;
|
||||
|
||||
export class EventSource<T extends Arr> {
|
||||
export class BasicSource<T> {
|
||||
listeners: Listener<T>[] = [];
|
||||
iter: IterSource<T>["iterator"];
|
||||
disconnect: IterSource<T>["disconnect"];
|
||||
|
||||
constructor() {
|
||||
const { iterator, disconnect } = fromCallback<T>((listener) =>
|
||||
this.addListener(listener)
|
||||
);
|
||||
this.iter = iterator;
|
||||
this.disconnect = disconnect;
|
||||
}
|
||||
|
||||
trigger(...arg: T) {
|
||||
trigger(value: T) {
|
||||
for (const listener of this.listeners) {
|
||||
listener(...arg);
|
||||
listener(value);
|
||||
}
|
||||
}
|
||||
|
||||
addListener(listener: Listener<T>) {
|
||||
listen(listener: Listener<T>) {
|
||||
this.listeners.push(listener);
|
||||
return () => {
|
||||
this.removeListener(listener);
|
||||
|
@ -33,4 +22,31 @@ export class EventSource<T extends Arr> {
|
|||
const index = this.listeners.indexOf(listener);
|
||||
this.listeners.splice(index, 1);
|
||||
}
|
||||
|
||||
listenOnce(listener: Listener<T>) {
|
||||
const disconnect = this.listen((value) => {
|
||||
disconnect();
|
||||
listener(value);
|
||||
});
|
||||
}
|
||||
|
||||
next() {
|
||||
return new Promise<T>((resolve) => {
|
||||
this.listenOnce(resolve);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class EventSource<T> extends BasicSource<T> {
|
||||
iter: IterSource<T>["iterator"];
|
||||
disconnect: IterSource<T>["disconnect"];
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
const { iterator, disconnect } = fromCallback<T>((listener) =>
|
||||
this.listen(listener)
|
||||
);
|
||||
this.iter = iterator;
|
||||
this.disconnect = disconnect;
|
||||
}
|
||||
}
|
|
@ -1,22 +1,21 @@
|
|||
import { Arr } from "../types.ts";
|
||||
import { pushIter, addIterUtils } from "./util/mod.ts";
|
||||
|
||||
type Listener<T> = { push: (arg: T) => void; done: () => void };
|
||||
export type IterSource<T extends Arr> = ReturnType<typeof fromCallback<T>>;
|
||||
export type IterSource<T> = ReturnType<typeof fromCallback<T>>;
|
||||
|
||||
export function fromCallback<T extends Arr>(
|
||||
source: (listener: (...values: T) => void) => void,
|
||||
export function fromCallback<T>(
|
||||
source: (listener: (value: T) => void) => void,
|
||||
disconnect?: () => void
|
||||
) {
|
||||
let isDone = false;
|
||||
let listeners: Listener<T>[] = [];
|
||||
|
||||
function trigger(...values: T) {
|
||||
function trigger(value: T) {
|
||||
if (isDone) {
|
||||
return;
|
||||
}
|
||||
for (const listener of listeners) {
|
||||
listener.push(values);
|
||||
listener.push(value);
|
||||
}
|
||||
}
|
||||
source(trigger);
|
||||
|
@ -53,4 +52,4 @@ export function fromCallback<T extends Arr>(
|
|||
},
|
||||
disconnect: done,
|
||||
};
|
||||
}
|
||||
}
|
|
@ -1,15 +1,15 @@
|
|||
import { assertEquals } from "https://deno.land/std@0.104.0/testing/asserts.ts";
|
||||
import { arrayMove, arrayShuffle } from "./array.ts";
|
||||
import { EventSource } from "./event-source.ts";
|
||||
|
||||
export class Queue<T> {
|
||||
#current: T | undefined;
|
||||
#queue: T[] = [];
|
||||
#source = new EventSource<[T]>();
|
||||
waiting = false;
|
||||
#waiting: ((value: T) => void)[] = [];
|
||||
|
||||
clear() {
|
||||
const cleared = this.#queue;
|
||||
this.#queue = [];
|
||||
return cleared;
|
||||
}
|
||||
|
||||
current() {
|
||||
|
@ -22,10 +22,15 @@ export class Queue<T> {
|
|||
|
||||
push(...values: T[]) {
|
||||
this.#queue.push(...values);
|
||||
if (this.waiting) {
|
||||
this.triggerNext();
|
||||
this.waiting = false;
|
||||
for (const waiting of this.#waiting) {
|
||||
const value = this.#queue.shift();
|
||||
this.#current = value;
|
||||
if (value == undefined) {
|
||||
break;
|
||||
}
|
||||
waiting(value);
|
||||
}
|
||||
this.#waiting = [];
|
||||
}
|
||||
|
||||
unshift(...values: T[]) {
|
||||
|
@ -52,18 +57,15 @@ export class Queue<T> {
|
|||
return false;
|
||||
}
|
||||
|
||||
triggerNext() {
|
||||
const value = this.#queue.shift();
|
||||
async next() {
|
||||
let value = this.#queue.shift();
|
||||
this.#current = value;
|
||||
if (value === undefined) {
|
||||
this.waiting = true;
|
||||
} else {
|
||||
this.#source.trigger(value);
|
||||
value = await new Promise<T>((resolve) => {
|
||||
this.#waiting.push(resolve);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
stream() {
|
||||
return this.#source.iter();
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -71,18 +73,15 @@ Deno.test({
|
|||
name: "Test",
|
||||
fn: async () => {
|
||||
const queue = new Queue<string>();
|
||||
const promise0 = queue.next();
|
||||
queue.push("Hello");
|
||||
queue.push("World!");
|
||||
const messages = queue.stream();
|
||||
queue.triggerNext();
|
||||
queue.triggerNext();
|
||||
queue.triggerNext();
|
||||
queue.triggerNext();
|
||||
queue.triggerNext();
|
||||
assertEquals("Hello", await messages.nextValue());
|
||||
assertEquals("World!", await messages.nextValue());
|
||||
assertEquals(undefined, await messages.nextValue());
|
||||
assertEquals(undefined, await messages.nextValue());
|
||||
assertEquals(undefined, await messages.nextValue());
|
||||
assertEquals("Hello", await promise0);
|
||||
assertEquals("World!", await queue.next());
|
||||
const promise1 = queue.next();
|
||||
const promise2 = queue.next();
|
||||
queue.push("Multiple", "Words!");
|
||||
assertEquals("Multiple", await promise1);
|
||||
assertEquals("Words!", await promise2);
|
||||
},
|
||||
});
|
||||
});
|
|
@ -1 +0,0 @@
|
|||
export type Arr = readonly unknown[];
|
11
main.ts
11
main.ts
|
@ -7,20 +7,18 @@ import {
|
|||
upsertGlobalApplicationCommands
|
||||
} from "./deps.ts";
|
||||
import { parseCommand } from "./commands.ts";
|
||||
import { cyan, yellow } from "https://deno.land/std@0.161.0/fmt/colors.ts";
|
||||
import { cyan } from "https://deno.land/std@0.161.0/fmt/colors.ts";
|
||||
import { helpCommand } from "./commands/help.ts";
|
||||
import { leaveCommand } from "./commands/leave.ts";
|
||||
import { loopCommand } from "./commands/loop.ts";
|
||||
import { npCommand } from "./commands/np.ts";
|
||||
import { pauseCommand } from "./commands/pause.ts";
|
||||
import { pauseCommand, stopCommand } from "./commands/pause.ts";
|
||||
import { playCommand } from "./commands/play.ts";
|
||||
import { skipCommand } from "./commands/skip.ts";
|
||||
import { unloopCommand } from "./commands/unloop.ts";
|
||||
|
||||
import { enableAudioPlugin } from "./discordeno-audio-plugin/mod.ts";
|
||||
|
||||
let sessionId = "";
|
||||
|
||||
const baseBot = createBot({
|
||||
token: configs.discord_token,
|
||||
intents: Intents.Guilds | Intents.GuildMessages | Intents.GuildVoiceStates,
|
||||
|
@ -28,10 +26,9 @@ const baseBot = createBot({
|
|||
|
||||
export const bot = enableAudioPlugin(baseBot);
|
||||
|
||||
bot.events.ready = async function (bot, payload) {
|
||||
bot.events.ready = async function () {
|
||||
//await registerCommands(bot);
|
||||
console.log(`${cyan("permanent waves")} is ready to go`);
|
||||
sessionId = payload.sessionId;
|
||||
}
|
||||
|
||||
// Another way to do events
|
||||
|
@ -42,7 +39,7 @@ bot.events.interactionCreate = async function (bot, interaction) {
|
|||
await startBot(bot);
|
||||
|
||||
async function registerCommands(bot: Bot) {
|
||||
console.log(await upsertGlobalApplicationCommands(bot, [helpCommand, leaveCommand, loopCommand, npCommand, pauseCommand, playCommand, skipCommand, unloopCommand]));
|
||||
console.log(await upsertGlobalApplicationCommands(bot, [helpCommand, leaveCommand, loopCommand, npCommand, pauseCommand, playCommand, skipCommand, stopCommand, unloopCommand]));
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
import { assertEquals } from "https://deno.land/std@0.177.0/testing/asserts.ts";
|
||||
|
||||
import { parseYoutubeId } from "../utils.ts";
|
||||
|
||||
Deno.test("parseYoutubeId", () => {
|
||||
const url = parseYoutubeId("https://www.youtube.com/watch?v=ylAF0WNvLx0");
|
||||
|
||||
assertEquals(url, "ylAF0WNvLx0");
|
||||
});
|
8
utils.ts
8
utils.ts
|
@ -95,6 +95,14 @@ export async function errorMessageCallback(guildId: bigint, message: string) {
|
|||
await sendMessage(bot, channel.id, errorMessage(message));
|
||||
}
|
||||
|
||||
export function isPlaylist(query: string) {
|
||||
if(query.includes("playlist")){
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export function parseYoutubeId(url: string) {
|
||||
return url.substring(url.indexOf("?")+3);
|
||||
}
|
Loading…
Reference in New Issue