Merge pull request #1136 from museofficial/feature/lower-volume-when-talking

Feature/lower volume when talking
This commit is contained in:
Stefano 2024-11-04 13:51:29 +01:00 committed by GitHub
commit b2565a3a92
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 162 additions and 10 deletions

View file

@ -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). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased] ## [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 ## [2.9.5] - 2024-10-29
- Dependency update - Dependency update

View file

@ -143,3 +143,12 @@ In the default state, Muse has the status "Online" and the text "Listening to Mu
### Bot-wide commands ### 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`. 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)

View file

@ -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;

View file

@ -32,6 +32,8 @@ model Setting {
autoAnnounceNextSong Boolean @default(false) autoAnnounceNextSong Boolean @default(false)
defaultVolume Int @default(100) defaultVolume Int @default(100)
defaultQueuePageSize Int @default(10) defaultQueuePageSize Int @default(10)
turnDownVolumeWhenPeopleSpeak Boolean @default(false)
turnDownVolumeWhenPeopleSpeakTarget Int @default(20)
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
} }

View file

@ -40,6 +40,22 @@ export default class implements Command {
.setName('value') .setName('value')
.setDescription('whether bot responses to queue additions are only displayed to the requester') .setDescription('whether bot responses to queue additions are only displayed to the requester')
.setRequired(true))) .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 .addSubcommand(subcommand => subcommand
.setName('set-auto-announce-next-song') .setName('set-auto-announce-next-song')
.setDescription('set whether to announce the next song in the queue automatically') .setDescription('set whether to announce the next song in the queue automatically')
@ -197,6 +213,40 @@ export default class implements Command {
break; 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': { case 'get': {
const embed = new EmbedBuilder().setTitle('Config'); 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', 'Add to queue reponses show for requester only': config.autoAnnounceNextSong ? 'yes' : 'no',
'Default Volume': config.defaultVolume, 'Default Volume': config.defaultVolume,
'Default queue page size': config.defaultQueuePageSize, 'Default queue page size': config.defaultQueuePageSize,
'Reduce volume when people speak': config.turnDownVolumeWhenPeopleSpeak ? 'yes' : 'no',
}; };
let description = ''; let description = '';

View file

@ -20,6 +20,7 @@ import FileCacheProvider from './file-cache.js';
import debug from '../utils/debug.js'; import debug from '../utils/debug.js';
import {getGuildSettings} from '../utils/get-guild-settings.js'; import {getGuildSettings} from '../utils/get-guild-settings.js';
import {buildPlayingMessageEmbed} from '../utils/build-embed.js'; import {buildPlayingMessageEmbed} from '../utils/build-embed.js';
import {Setting} from '@prisma/client';
export enum MediaSource { export enum MediaSource {
Youtube, Youtube,
@ -82,6 +83,8 @@ export default class {
private readonly fileCache: FileCacheProvider; private readonly fileCache: FileCacheProvider;
private disconnectTimer: NodeJS.Timeout | null = null; private disconnectTimer: NodeJS.Timeout | null = null;
private readonly channelToSpeakingUsers: Map<string, Set<string>> = new Map();
constructor(fileCache: FileCacheProvider, guildId: string) { constructor(fileCache: FileCacheProvider, guildId: string) {
this.fileCache = fileCache; this.fileCache = fileCache;
this.guildId = guildId; this.guildId = guildId;
@ -96,9 +99,12 @@ export default class {
this.voiceConnection = joinVoiceChannel({ this.voiceConnection = joinVoiceChannel({
channelId: channel.id, channelId: channel.id,
guildId: channel.guild.id, guildId: channel.guild.id,
selfDeaf: false,
adapterCreator: channel.guild.voiceAdapterCreator as DiscordGatewayAdapterCreator, adapterCreator: channel.guild.voiceAdapterCreator as DiscordGatewayAdapterCreator,
}); });
const guildSettings = await getGuildSettings(this.guildId);
// Workaround to disable keepAlive // Workaround to disable keepAlive
this.voiceConnection.on('stateChange', (oldState, newState) => { 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 */ /* 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 */ /* 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; 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) { canGoForward(skip: number) {
return (this.queuePosition + skip - 1) < this.queue.length; return (this.queuePosition + skip - 1) < this.queue.length;
} }