aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStefano <[email protected]>2024-11-04 13:33:00 +0100
committerGitHub <[email protected]>2024-11-04 13:33:00 +0100
commitd4e11241c5dacf5d11fa2925849d08ca78e76622 (patch)
tree7f79cba387d76b5aea1cd5602a8b4aa72667e37c
parente82bc977ff36d946f7563925a37518fef5bc4317 (diff)
parent4ec8a6d091e72764b202be7ff5558a952b5a2ee0 (diff)
downloadmuse-d4e11241c5dacf5d11fa2925849d08ca78e76622.tar.xz
muse-d4e11241c5dacf5d11fa2925849d08ca78e76622.zip
Merge pull request #1096 from xytxxx/auto-turn-voice-down-when-people-speaks
feat: automatically turn volume down when people talks
-rw-r--r--CHANGELOG.md3
-rw-r--r--README.md9
-rw-r--r--migrations/20241031084730_add_turn_down_volume_when_people_speak/migration.sql21
-rw-r--r--schema.prisma22
-rw-r--r--src/commands/config.ts51
-rw-r--r--src/services/player.ts66
6 files changed, 162 insertions, 10 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 054a6f9..f6d8b5f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
+- New `/config set-reduce-vol-when-voice` command to automatically turn down the volume when people are speaking in the channel
+- New `/config set-reduce-vol-when-voice-target` command to set the target volume percentage (0-100) when people are speaking in the channel
+
## [2.9.5] - 2024-10-29
- Dependency update
diff --git a/README.md b/README.md
index b21bb52..31b75e5 100644
--- a/README.md
+++ b/README.md
@@ -143,3 +143,12 @@ In the default state, Muse has the status "Online" and the text "Listening to Mu
### Bot-wide commands
If you have Muse running in a lot of guilds (10+) you may want to switch to registering commands bot-wide rather than for each guild. (The downside to this is that command updates can take up to an hour to propagate.) To do this, set the environment variable `REGISTER_COMMANDS_ON_BOT` to `true`.
+
+### Automatically turn down volume when people speak
+
+You can configure the bot to automatically turn down the volume when people are speaking in the channel using the following commands:
+
+- `/config set-reduce-vol-when-voice true` - Enable automatic volume reduction
+- `/config set-reduce-vol-when-voice false` - Disable automatic volume reduction
+- `/config set-reduce-vol-when-voice-target <volume>` - Set the target volume percentage when people speak (0-100, default is 70)
+
diff --git a/migrations/20241031084730_add_turn_down_volume_when_people_speak/migration.sql b/migrations/20241031084730_add_turn_down_volume_when_people_speak/migration.sql
new file mode 100644
index 0000000..07be5d9
--- /dev/null
+++ b/migrations/20241031084730_add_turn_down_volume_when_people_speak/migration.sql
@@ -0,0 +1,21 @@
+-- RedefineTables
+PRAGMA foreign_keys=OFF;
+CREATE TABLE "new_Setting" (
+ "guildId" TEXT NOT NULL PRIMARY KEY,
+ "playlistLimit" INTEGER NOT NULL DEFAULT 50,
+ "secondsToWaitAfterQueueEmpties" INTEGER NOT NULL DEFAULT 30,
+ "leaveIfNoListeners" BOOLEAN NOT NULL DEFAULT true,
+ "queueAddResponseEphemeral" BOOLEAN NOT NULL DEFAULT false,
+ "autoAnnounceNextSong" BOOLEAN NOT NULL DEFAULT false,
+ "defaultVolume" INTEGER NOT NULL DEFAULT 100,
+ "defaultQueuePageSize" INTEGER NOT NULL DEFAULT 10,
+ "turnDownVolumeWhenPeopleSpeak" BOOLEAN NOT NULL DEFAULT false,
+ "turnDownVolumeWhenPeopleSpeakTarget" INTEGER NOT NULL DEFAULT 20,
+ "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "updatedAt" DATETIME NOT NULL
+);
+INSERT INTO "new_Setting" ("autoAnnounceNextSong", "createdAt", "defaultQueuePageSize", "defaultVolume", "guildId", "leaveIfNoListeners", "playlistLimit", "queueAddResponseEphemeral", "secondsToWaitAfterQueueEmpties", "updatedAt") SELECT "autoAnnounceNextSong", "createdAt", "defaultQueuePageSize", "defaultVolume", "guildId", "leaveIfNoListeners", "playlistLimit", "queueAddResponseEphemeral", "secondsToWaitAfterQueueEmpties", "updatedAt" FROM "Setting";
+DROP TABLE "Setting";
+ALTER TABLE "new_Setting" RENAME TO "Setting";
+PRAGMA foreign_key_check;
+PRAGMA foreign_keys=ON;
diff --git a/schema.prisma b/schema.prisma
index 65c3aee..4723196 100644
--- a/schema.prisma
+++ b/schema.prisma
@@ -24,16 +24,18 @@ model KeyValueCache {
}
model Setting {
- guildId String @id
- playlistLimit Int @default(50)
- secondsToWaitAfterQueueEmpties Int @default(30)
- leaveIfNoListeners Boolean @default(true)
- queueAddResponseEphemeral Boolean @default(false)
- autoAnnounceNextSong Boolean @default(false)
- defaultVolume Int @default(100)
- defaultQueuePageSize Int @default(10)
- createdAt DateTime @default(now())
- updatedAt DateTime @updatedAt
+ guildId String @id
+ playlistLimit Int @default(50)
+ secondsToWaitAfterQueueEmpties Int @default(30)
+ leaveIfNoListeners Boolean @default(true)
+ queueAddResponseEphemeral Boolean @default(false)
+ autoAnnounceNextSong Boolean @default(false)
+ defaultVolume Int @default(100)
+ defaultQueuePageSize Int @default(10)
+ turnDownVolumeWhenPeopleSpeak Boolean @default(false)
+ turnDownVolumeWhenPeopleSpeakTarget Int @default(20)
+ createdAt DateTime @default(now())
+ updatedAt DateTime @updatedAt
}
model FavoriteQuery {
diff --git a/src/commands/config.ts b/src/commands/config.ts
index 91b2578..01d9fe9 100644
--- a/src/commands/config.ts
+++ b/src/commands/config.ts
@@ -41,6 +41,22 @@ export default class implements Command {
.setDescription('whether bot responses to queue additions are only displayed to the requester')
.setRequired(true)))
.addSubcommand(subcommand => subcommand
+ .setName('set-reduce-vol-when-voice')
+ .setDescription('set whether to turn down the volume when people speak')
+ .addBooleanOption(option => option
+ .setName('value')
+ .setDescription('whether to turn down the volume when people speak')
+ .setRequired(true)))
+ .addSubcommand(subcommand => subcommand
+ .setName('set-reduce-vol-when-voice-target')
+ .setDescription('set the target volume when people speak')
+ .addIntegerOption(option => option
+ .setName('volume')
+ .setDescription('volume percentage (0 is muted, 100 is max & default)')
+ .setMinValue(0)
+ .setMaxValue(100)
+ .setRequired(true)))
+ .addSubcommand(subcommand => subcommand
.setName('set-auto-announce-next-song')
.setDescription('set whether to announce the next song in the queue automatically')
.addBooleanOption(option => option
@@ -197,6 +213,40 @@ export default class implements Command {
break;
}
+ case 'set-reduce-vol-when-voice': {
+ const value = interaction.options.getBoolean('value')!;
+
+ await prisma.setting.update({
+ where: {
+ guildId: interaction.guild!.id,
+ },
+ data: {
+ turnDownVolumeWhenPeopleSpeak: value,
+ },
+ });
+
+ await interaction.reply('👍 turn down volume setting updated');
+
+ break;
+ }
+
+ case 'set-reduce-vol-when-voice-target': {
+ const value = interaction.options.getInteger('volume')!;
+
+ await prisma.setting.update({
+ where: {
+ guildId: interaction.guild!.id,
+ },
+ data: {
+ turnDownVolumeWhenPeopleSpeakTarget: value,
+ },
+ });
+
+ await interaction.reply('👍 turn down volume target setting updated');
+
+ break;
+ }
+
case 'get': {
const embed = new EmbedBuilder().setTitle('Config');
@@ -212,6 +262,7 @@ export default class implements Command {
'Add to queue reponses show for requester only': config.autoAnnounceNextSong ? 'yes' : 'no',
'Default Volume': config.defaultVolume,
'Default queue page size': config.defaultQueuePageSize,
+ 'Reduce volume when people speak': config.turnDownVolumeWhenPeopleSpeak ? 'yes' : 'no',
};
let description = '';
diff --git a/src/services/player.ts b/src/services/player.ts
index 5e284a6..b833022 100644
--- a/src/services/player.ts
+++ b/src/services/player.ts
@@ -20,6 +20,7 @@ import FileCacheProvider from './file-cache.js';
import debug from '../utils/debug.js';
import {getGuildSettings} from '../utils/get-guild-settings.js';
import {buildPlayingMessageEmbed} from '../utils/build-embed.js';
+import {Setting} from '@prisma/client';
export enum MediaSource {
Youtube,
@@ -82,6 +83,8 @@ export default class {
private readonly fileCache: FileCacheProvider;
private disconnectTimer: NodeJS.Timeout | null = null;
+ private readonly channelToSpeakingUsers: Map<string, Set<string>> = new Map();
+
constructor(fileCache: FileCacheProvider, guildId: string) {
this.fileCache = fileCache;
this.guildId = guildId;
@@ -96,9 +99,12 @@ export default class {
this.voiceConnection = joinVoiceChannel({
channelId: channel.id,
guildId: channel.guild.id,
+ selfDeaf: false,
adapterCreator: channel.guild.voiceAdapterCreator as DiscordGatewayAdapterCreator,
});
+ const guildSettings = await getGuildSettings(this.guildId);
+
// Workaround to disable keepAlive
this.voiceConnection.on('stateChange', (oldState, newState) => {
/* eslint-disable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call */
@@ -115,6 +121,9 @@ export default class {
/* eslint-enable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call */
this.currentChannel = channel;
+ if (newState.status === VoiceConnectionStatus.Ready) {
+ this.registerVoiceActivityListener(guildSettings);
+ }
});
}
@@ -302,6 +311,63 @@ export default class {
}
}
+ registerVoiceActivityListener(guildSettings: Setting) {
+ const {turnDownVolumeWhenPeopleSpeak, turnDownVolumeWhenPeopleSpeakTarget} = guildSettings;
+ if (!turnDownVolumeWhenPeopleSpeak || !this.voiceConnection) {
+ return;
+ }
+
+ this.voiceConnection.receiver.speaking.on('start', (userId: string) => {
+ if (!this.currentChannel) {
+ return;
+ }
+
+ const member = this.currentChannel.members.get(userId);
+ const channelId = this.currentChannel?.id;
+
+ if (member) {
+ if (!this.channelToSpeakingUsers.has(channelId)) {
+ this.channelToSpeakingUsers.set(channelId, new Set());
+ }
+
+ this.channelToSpeakingUsers.get(channelId)?.add(member.id);
+ }
+
+ this.suppressVoiceWhenPeopleAreSpeaking(turnDownVolumeWhenPeopleSpeakTarget);
+ });
+
+ this.voiceConnection.receiver.speaking.on('end', (userId: string) => {
+ if (!this.currentChannel) {
+ return;
+ }
+
+ const member = this.currentChannel.members.get(userId);
+ const channelId = this.currentChannel.id;
+ if (member) {
+ if (!this.channelToSpeakingUsers.has(channelId)) {
+ this.channelToSpeakingUsers.set(channelId, new Set());
+ }
+
+ this.channelToSpeakingUsers.get(channelId)?.delete(member.id);
+ }
+
+ this.suppressVoiceWhenPeopleAreSpeaking(turnDownVolumeWhenPeopleSpeakTarget);
+ });
+ }
+
+ suppressVoiceWhenPeopleAreSpeaking(turnDownVolumeWhenPeopleSpeakTarget: number): void {
+ if (!this.currentChannel) {
+ return;
+ }
+
+ const speakingUsers = this.channelToSpeakingUsers.get(this.currentChannel.id);
+ if (speakingUsers && speakingUsers.size > 0) {
+ this.setVolume(turnDownVolumeWhenPeopleSpeakTarget);
+ } else {
+ this.setVolume(this.defaultVolume);
+ }
+ }
+
canGoForward(skip: number) {
return (this.queuePosition + skip - 1) < this.queue.length;
}