aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/commands/pause.ts29
-rw-r--r--src/commands/play.ts23
-rw-r--r--src/inversify.config.ts2
-rw-r--r--src/services/player.ts95
4 files changed, 123 insertions, 26 deletions
diff --git a/src/commands/pause.ts b/src/commands/pause.ts
new file mode 100644
index 0000000..54855e0
--- /dev/null
+++ b/src/commands/pause.ts
@@ -0,0 +1,29 @@
+import {Message} from 'discord.js';
+import {TYPES} from '../types';
+import {inject, injectable} from 'inversify';
+import PlayerManager from '../managers/player';
+import {STATUS} from '../services/player';
+import Command from '.';
+
+@injectable()
+export default class implements Command {
+ public name = 'pause';
+ public description = 'pause currently playing song';
+ private readonly playerManager: PlayerManager;
+
+ constructor(@inject(TYPES.Managers.Player) playerManager: PlayerManager) {
+ this.playerManager = playerManager;
+ }
+
+ public async execute(msg: Message, _: string []): Promise<void> {
+ const player = this.playerManager.get(msg.guild!.id);
+
+ if (player.status !== STATUS.PLAYING) {
+ await msg.channel.send('error: not currently playing');
+ return;
+ }
+
+ player.pause();
+ await msg.channel.send('paused');
+ }
+}
diff --git a/src/commands/play.ts b/src/commands/play.ts
index 03992d7..2d91d19 100644
--- a/src/commands/play.ts
+++ b/src/commands/play.ts
@@ -36,6 +36,29 @@ export default class implements Command {
}
public async execute(msg: Message, args: string []): Promise<void> {
+ const queue = this.queueManager.get(msg.guild!.id);
+
+ if (args.length === 0) {
+ if (this.playerManager.get(msg.guild!.id).status === STATUS.PLAYING) {
+ await msg.channel.send('error: already playing, give me a song name');
+ return;
+ }
+
+ // Must be resuming play
+ if (queue.get().length === 0) {
+ await msg.channel.send('error: nothing to play');
+ return;
+ }
+
+ const channel = getMostPopularVoiceChannel(msg.guild!);
+
+ await this.playerManager.get(msg.guild!.id).connect(channel);
+ await this.playerManager.get(msg.guild!.id).play();
+
+ await msg.channel.send('play resuming');
+ return;
+ }
+
const newSongs: QueuedSong[] = [];
const res = new LoadingMessage(msg.channel as TextChannel, 'hold on a sec');
diff --git a/src/inversify.config.ts b/src/inversify.config.ts
index 759a27ac..afdd5b8 100644
--- a/src/inversify.config.ts
+++ b/src/inversify.config.ts
@@ -24,6 +24,7 @@ import Command from './commands';
import Clear from './commands/clear';
import Config from './commands/config';
import ForwardSeek from './commands/fseek';
+import Pause from './commands/pause';
import Play from './commands/play';
import QueueCommad from './commands/queue';
import Seek from './commands/seek';
@@ -45,6 +46,7 @@ container.bind<QueueManager>(TYPES.Managers.Queue).to(QueueManager).inSingletonS
container.bind<Command>(TYPES.Command).to(Clear).inSingletonScope();
container.bind<Command>(TYPES.Command).to(Config).inSingletonScope();
container.bind<Command>(TYPES.Command).to(ForwardSeek).inSingletonScope();
+container.bind<Command>(TYPES.Command).to(Pause).inSingletonScope();
container.bind<Command>(TYPES.Command).to(Play).inSingletonScope();
container.bind<Command>(TYPES.Command).to(QueueCommad).inSingletonScope();
container.bind<Command>(TYPES.Command).to(Seek).inSingletonScope();
diff --git a/src/services/player.ts b/src/services/player.ts
index 3ce68ae..be2cdc0 100644
--- a/src/services/player.ts
+++ b/src/services/player.ts
@@ -20,8 +20,8 @@ export default class {
private readonly cacheDir: string;
private voiceConnection: VoiceConnection | null = null;
private dispatcher: StreamDispatcher | null = null;
+ private playPositionInterval: NodeJS.Timeout | undefined;
- private lastStreamTime = 0;
private positionInSeconds = 0;
constructor(queue: Queue, cacheDir: string) {
@@ -37,6 +37,10 @@ export default class {
disconnect(): void {
if (this.voiceConnection) {
+ if (this.status === STATUS.PLAYING) {
+ this.pause();
+ }
+
this.voiceConnection.disconnect();
}
}
@@ -54,9 +58,12 @@ export default class {
await this.waitForCache(currentSong.url);
- this.attachListeners(this.voiceConnection.play(this.getCachedPath(currentSong.url), {seek: positionSeconds}));
+ this.dispatcher = this.voiceConnection.play(this.getCachedPath(currentSong.url), {seek: positionSeconds});
+
+ this.attachListeners();
+ this.startTrackingPosition(positionSeconds);
- this.positionInSeconds = positionSeconds;
+ this.status = STATUS.PLAYING;
}
async forwardSeek(positionSeconds: number): Promise<void> {
@@ -73,9 +80,14 @@ export default class {
}
// Resume from paused state
- if (this.status === STATUS.PAUSED && this.dispatcher) {
- this.dispatcher.resume();
- this.status = STATUS.PLAYING;
+ if (this.status === STATUS.PAUSED) {
+ if (this.dispatcher) {
+ this.dispatcher.resume();
+ this.status = STATUS.PLAYING;
+ } else {
+ await this.seek(this.getPosition());
+ }
+
return;
}
@@ -85,27 +97,32 @@ export default class {
throw new Error('Queue empty.');
}
- let dispatcher: StreamDispatcher;
-
if (await this.isCached(currentSong.url)) {
- dispatcher = this.voiceConnection.play(this.getCachedPath(currentSong.url));
+ this.dispatcher = this.voiceConnection.play(this.getCachedPath(currentSong.url));
} else {
const stream = await this.getStream(currentSong.url);
- dispatcher = this.voiceConnection.play(stream, {type: 'webm/opus'});
+ this.dispatcher = this.voiceConnection.play(stream, {type: 'webm/opus'});
}
- this.attachListeners(dispatcher);
+ this.attachListeners();
this.status = STATUS.PLAYING;
- this.dispatcher = dispatcher;
+
+ this.startTrackingPosition();
}
pause(): void {
- if (!this.dispatcher || this.status !== STATUS.PLAYING) {
+ if (this.status !== STATUS.PLAYING) {
throw new Error('Not currently playing.');
}
- this.dispatcher.pause();
+ this.status = STATUS.PAUSED;
+
+ if (this.dispatcher) {
+ this.dispatcher.pause();
+ }
+
+ this.stopTrackingPosition();
}
private getCurrentSong(): QueuedSong|null {
@@ -235,12 +252,45 @@ export default class {
return capacitor.createReadStream();
}
- private attachListeners(stream: StreamDispatcher): void {
- stream.on('speaking', async isSpeaking => {
- // Update position
- this.positionInSeconds += (stream.streamTime - this.lastStreamTime) / 1000;
- this.lastStreamTime = stream.streamTime;
+ private startTrackingPosition(initalPosition?: number): void {
+ if (initalPosition) {
+ this.positionInSeconds = initalPosition;
+ }
+ if (this.playPositionInterval) {
+ clearInterval(this.playPositionInterval);
+ }
+
+ this.playPositionInterval = setInterval(() => {
+ this.positionInSeconds++;
+ }, 1000);
+ }
+
+ private stopTrackingPosition(): void {
+ if (this.playPositionInterval) {
+ clearInterval(this.playPositionInterval);
+ }
+ }
+
+ private attachListeners(): void {
+ if (!this.voiceConnection) {
+ return;
+ }
+
+ this.voiceConnection.on('disconnect', () => {
+ // Automatically pause
+ if (this.status === STATUS.PLAYING) {
+ this.pause();
+ }
+
+ this.dispatcher = null;
+ });
+
+ if (!this.dispatcher) {
+ return;
+ }
+
+ this.dispatcher.on('speaking', async isSpeaking => {
// Automatically advance queued song at end
if (!isSpeaking && this.status === STATUS.PLAYING) {
if (this.queue.get().length > 0) {
@@ -249,12 +299,5 @@ export default class {
}
}
});
-
- stream.on('close', () => {
- // Remove dispatcher from guild player
- this.dispatcher = null;
-
- // TODO: set voiceConnection null as well?
- });
}
}