diff --git a/src/commands/fseek.ts b/src/commands/fseek.ts new file mode 100644 index 0000000..2fae1e6 --- /dev/null +++ b/src/commands/fseek.ts @@ -0,0 +1,33 @@ +import {Message, TextChannel} from 'discord.js'; +import {TYPES} from '../types'; +import {inject, injectable} from 'inversify'; +import PlayerManager from '../managers/player'; +import LoadingMessage from '../utils/loading-message'; +import Command from '.'; + +@injectable() +export default class implements Command { + public name = 'fseek'; + public description = 'forward seek position in currently playing song'; + private readonly playerManager: PlayerManager; + + constructor(@inject(TYPES.Managers.Player) playerManager: PlayerManager) { + this.playerManager = playerManager; + } + + public async execute(msg: Message, args: string []): Promise { + const seekTime = parseInt(args[0], 10); + + const loading = new LoadingMessage(msg.channel as TextChannel, 'hold on a sec'); + + await loading.start(); + + try { + await this.playerManager.get(msg.guild!.id).forwardSeek(seekTime); + + await loading.stop('seeked'); + } catch (_) { + await loading.stop('error somewhere'); + } + } +} diff --git a/src/inversify.config.ts b/src/inversify.config.ts index 4095859..9b91743 100644 --- a/src/inversify.config.ts +++ b/src/inversify.config.ts @@ -23,6 +23,7 @@ import QueueManager from './managers/queue'; import Command from './commands'; import Clear from './commands/clear'; import Config from './commands/config'; +import ForwardSeek from './commands/fseek'; import Play from './commands/play'; import QueueCommad from './commands/queue'; import Seek from './commands/seek'; @@ -41,6 +42,7 @@ container.bind(TYPES.Managers.Queue).to(QueueManager).inSingletonS // Commands container.bind(TYPES.Command).to(Clear).inSingletonScope(); container.bind(TYPES.Command).to(Config).inSingletonScope(); +container.bind(TYPES.Command).to(ForwardSeek).inSingletonScope(); container.bind(TYPES.Command).to(Play).inSingletonScope(); container.bind(TYPES.Command).to(QueueCommad).inSingletonScope(); container.bind(TYPES.Command).to(Seek).inSingletonScope(); diff --git a/src/services/player.ts b/src/services/player.ts index dfdf4b0..3ce68ae 100644 --- a/src/services/player.ts +++ b/src/services/player.ts @@ -21,6 +21,9 @@ export default class { private voiceConnection: VoiceConnection | null = null; private dispatcher: StreamDispatcher | null = null; + private lastStreamTime = 0; + private positionInSeconds = 0; + constructor(queue: Queue, cacheDir: string) { this.queue = queue; this.cacheDir = cacheDir; @@ -52,6 +55,16 @@ export default class { await this.waitForCache(currentSong.url); this.attachListeners(this.voiceConnection.play(this.getCachedPath(currentSong.url), {seek: positionSeconds})); + + this.positionInSeconds = positionSeconds; + } + + async forwardSeek(positionSeconds: number): Promise { + return this.seek(this.positionInSeconds + positionSeconds); + } + + getPosition(): number { + return this.positionInSeconds; } async play(): Promise { @@ -224,6 +237,10 @@ export default class { private attachListeners(stream: StreamDispatcher): void { stream.on('speaking', async isSpeaking => { + // Update position + this.positionInSeconds += (stream.streamTime - this.lastStreamTime) / 1000; + this.lastStreamTime = stream.streamTime; + // Automatically advance queued song at end if (!isSpeaking && this.status === STATUS.PLAYING) { if (this.queue.get().length > 0) {