aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMax Isom <[email protected]>2022-03-13 18:30:36 -0400
committerGitHub <[email protected]>2022-03-13 17:30:36 -0500
commit6c00727a4a22e28c45711b301c7bf4dbaff90117 (patch)
treec23696f95ce0ef9a4f31586af96021329dedf292
parent03d5cfffd1a9cd8a9977de0d24cce1be0cc73210 (diff)
downloadmuse-6c00727a4a22e28c45711b301c7bf4dbaff90117.tar.xz
muse-6c00727a4a22e28c45711b301c7bf4dbaff90117.zip
Parse duration strings for /fseek and /seek (#565)
-rw-r--r--CHANGELOG.md1
-rw-r--r--package.json6
-rw-r--r--src/commands/fseek.ts15
-rw-r--r--src/commands/seek.ts5
-rw-r--r--src/services/player.ts16
-rw-r--r--src/utils/duration-string-to-seconds.ts21
-rw-r--r--yarn.lock7
7 files changed, 58 insertions, 13 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5bc2ec8..97f2d71 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Changed
- Now uses [esmo](https://github.com/antfu/esno) so we don't have to build
+- `/seek` and `/fseek` can now be given duration strings. For example, `1m` and `2m 15s` work. If the input consists only of numbers, Muse will treat it as the number of seconds to advance (backwards-compatible behavior).
## [1.5.0] - 2022-03-12
### Changed
diff --git a/package.json b/package.json
index c0ea806..ce04e8f 100644
--- a/package.json
+++ b/package.json
@@ -11,7 +11,9 @@
"engines": {
"node": ">=16.0.0"
},
- "files": ["src"],
+ "files": [
+ "src"
+ ],
"scripts": {
"lint": "eslint \"src/**/*.{ts,tsx}\"",
"lint:fix": "eslint \"src/**/*.{ts,tsx}\" --fix",
@@ -31,6 +33,7 @@
"@types/debug": "^4.1.5",
"@types/fluent-ffmpeg": "^2.1.17",
"@types/fs-capacitor": "^2.0.0",
+ "@types/ms": "0.7.31",
"@types/node": "^17.0.0",
"@types/node-emoji": "^1.8.1",
"@types/spotify-web-api-node": "^5.0.2",
@@ -100,6 +103,7 @@
"p-event": "^5.0.1",
"p-limit": "^4.0.0",
"p-queue": "^7.2.0",
+ "parse-duration": "1.0.2",
"read-pkg": "7.1.0",
"reflect-metadata": "^0.1.13",
"spotify-uri": "^2.2.0",
diff --git a/src/commands/fseek.ts b/src/commands/fseek.ts
index 985a7c4..3e34438 100644
--- a/src/commands/fseek.ts
+++ b/src/commands/fseek.ts
@@ -5,15 +5,16 @@ import {inject, injectable} from 'inversify';
import PlayerManager from '../managers/player.js';
import Command from '.';
import {prettyTime} from '../utils/time.js';
+import durationStringToSeconds from '../utils/duration-string-to-seconds.js';
@injectable()
export default class implements Command {
public readonly slashCommand = new SlashCommandBuilder()
.setName('fseek')
.setDescription('seek forward in the current song')
- .addNumberOption(option => option
- .setName('seconds')
- .setDescription('the number of seconds to skip forward')
+ .addStringOption(option => option
+ .setName('time')
+ .setDescription('an interval expression or number of seconds (1m, 30s, 100)')
.setRequired(true));
public requiresVC = true;
@@ -37,12 +38,14 @@ export default class implements Command {
throw new Error('can\'t seek in a livestream');
}
- const seekTime = interaction.options.getNumber('seconds');
+ const seekValue = interaction.options.getString('value');
- if (!seekTime) {
- throw new Error('missing number of seconds to seek');
+ if (!seekValue) {
+ throw new Error('missing seek value');
}
+ const seekTime = durationStringToSeconds(seekValue);
+
if (seekTime + player.getPosition() > currentSong.length) {
throw new Error('can\'t seek past the end of the song');
}
diff --git a/src/commands/seek.ts b/src/commands/seek.ts
index 07f6590..00e9c2e 100644
--- a/src/commands/seek.ts
+++ b/src/commands/seek.ts
@@ -5,6 +5,7 @@ import PlayerManager from '../managers/player.js';
import Command from '.';
import {parseTime, prettyTime} from '../utils/time.js';
import {SlashCommandBuilder} from '@discordjs/builders';
+import durationStringToSeconds from '../utils/duration-string-to-seconds.js';
@injectable()
export default class implements Command {
@@ -13,7 +14,7 @@ export default class implements Command {
.setDescription('seek to a position from beginning of song')
.addStringOption(option =>
option.setName('time')
- .setDescription('time to seek')
+ .setDescription('an interval expression or number of seconds (1m, 30s, 100)')
.setRequired(true),
);
@@ -45,7 +46,7 @@ export default class implements Command {
if (time.includes(':')) {
seekTime = parseTime(time);
} else {
- seekTime = parseInt(time, 10);
+ seekTime = durationStringToSeconds(time);
}
if (seekTime > currentSong.length) {
diff --git a/src/services/player.ts b/src/services/player.ts
index 6e99364..b3ed34d 100644
--- a/src/services/player.ts
+++ b/src/services/player.ts
@@ -5,7 +5,17 @@ import ytdl from 'ytdl-core';
import {WriteStream} from 'fs-capacitor';
import ffmpeg from 'fluent-ffmpeg';
import shuffle from 'array-shuffle';
-import {AudioPlayer, AudioPlayerStatus, createAudioPlayer, createAudioResource, joinVoiceChannel, StreamType, VoiceConnection, VoiceConnectionStatus} from '@discordjs/voice';
+import {
+ AudioPlayer,
+ AudioPlayerState,
+ AudioPlayerStatus,
+ createAudioPlayer,
+ createAudioResource,
+ joinVoiceChannel,
+ StreamType,
+ VoiceConnection,
+ VoiceConnectionStatus,
+} from '@discordjs/voice';
import FileCacheProvider from './file-cache.js';
import debug from '../utils/debug.js';
import {prisma} from '../utils/db.js';
@@ -493,7 +503,7 @@ export default class {
}
if (this.audioPlayer.listeners('stateChange').length === 0) {
- this.audioPlayer.on('stateChange', this.onAudioPlayerStateChange.bind(this));
+ this.audioPlayer.on(AudioPlayerStatus.Idle, this.onAudioPlayerIdle.bind(this));
}
}
@@ -501,7 +511,7 @@ export default class {
this.disconnect();
}
- private async onAudioPlayerStateChange(_oldState: {status: AudioPlayerStatus}, newState: {status: AudioPlayerStatus}): Promise<void> {
+ private async onAudioPlayerIdle(_oldState: AudioPlayerState, newState: AudioPlayerState): Promise<void> {
// Automatically advance queued song at end
if (newState.status === AudioPlayerStatus.Idle && this.status === STATUS.PLAYING) {
await this.forward(1);
diff --git a/src/utils/duration-string-to-seconds.ts b/src/utils/duration-string-to-seconds.ts
new file mode 100644
index 0000000..588ba5b
--- /dev/null
+++ b/src/utils/duration-string-to-seconds.ts
@@ -0,0 +1,21 @@
+import parse from 'parse-duration';
+
+/**
+ * Parse duration strings to seconds.
+ * @param str any common duration format, like 1m or 1hr 30s. If the input is a number it's assumed to be in seconds.
+ * @returns seconds
+ */
+const durationStringToSeconds = (str: string) => {
+ let seconds;
+ const isInputSeconds = Boolean(/\d+$/.exec(str));
+
+ if (isInputSeconds) {
+ seconds = Number.parseInt(str, 10);
+ } else {
+ seconds = parse(str) / 1000;
+ }
+
+ return seconds;
+};
+
+export default durationStringToSeconds;
diff --git a/yarn.lock b/yarn.lock
index 3f03253..3090dc2 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -384,7 +384,7 @@
resolved "https://registry.yarnpkg.com/@types/libsodium-wrappers/-/libsodium-wrappers-0.7.9.tgz#89c3ad2156d5143e64bce86cfeb0045a983aeccc"
integrity sha512-LisgKLlYQk19baQwjkBZZXdJL0KbeTpdEnrAfz5hQACbklCY0gVFnsKUyjfNWF1UQsCSjw93Sj5jSbiO8RPfdw==
-"@types/ms@*":
+"@types/ms@*", "@types/[email protected]":
version "0.7.31"
resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.31.tgz#31b7ca6407128a3d2bbc27fe2d21b345397f6197"
integrity sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==
@@ -2855,6 +2855,11 @@ parent-module@^1.0.0:
dependencies:
callsites "^3.0.0"
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/parse-duration/-/parse-duration-1.0.2.tgz#b9aa7d3a1363cc7e8845bea8fd3baf8a11df5805"
+ integrity sha512-Dg27N6mfok+ow1a2rj/nRjtCfaKrHUZV2SJpEn/s8GaVUSlf4GGRCRP1c13Hj+wfPKVMrFDqLMLITkYKgKxyyg==
+
[email protected], parse-json@^5.0.0, parse-json@^5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd"