aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorYitong Xiao <[email protected]>2024-09-15 17:53:05 -0400
committerYitong Xiao <[email protected]>2024-09-15 17:53:05 -0400
commitaae98255b19e83ce78aaea389627dbae504910f8 (patch)
tree8eeca95adaeb9134b6d3f3d5b6bd3bc9afbbe090 /src
parent534d8fafaa7f09f7ba940d044b08e6c48f800c7a (diff)
downloadmuse-aae98255b19e83ce78aaea389627dbae504910f8.tar.xz
muse-aae98255b19e83ce78aaea389627dbae504910f8.zip
feat: automatically turn down volume when people talks
Diffstat (limited to 'src')
-rw-r--r--src/managers/player.ts7
-rw-r--r--src/services/config.ts4
-rw-r--r--src/services/player.ts67
3 files changed, 75 insertions, 3 deletions
diff --git a/src/managers/player.ts b/src/managers/player.ts
index 420cf48..577d9f3 100644
--- a/src/managers/player.ts
+++ b/src/managers/player.ts
@@ -2,22 +2,25 @@ import {inject, injectable} from 'inversify';
import {TYPES} from '../types.js';
import Player from '../services/player.js';
import FileCacheProvider from '../services/file-cache.js';
+import Config from '../services/config.js';
@injectable()
export default class {
private readonly guildPlayers: Map<string, Player>;
private readonly fileCache: FileCacheProvider;
+ private readonly config: Config;
- constructor(@inject(TYPES.FileCache) fileCache: FileCacheProvider) {
+ constructor(@inject(TYPES.FileCache) fileCache: FileCacheProvider, @inject(TYPES.Config) config: Config) {
this.guildPlayers = new Map();
this.fileCache = fileCache;
+ this.config = config;
}
get(guildId: string): Player {
let player = this.guildPlayers.get(guildId);
if (!player) {
- player = new Player(this.fileCache, guildId);
+ player = new Player(this.fileCache, guildId, this.config);
this.guildPlayers.set(guildId, player);
}
diff --git a/src/services/config.ts b/src/services/config.ts
index b6b9aea..ef2a7c9 100644
--- a/src/services/config.ts
+++ b/src/services/config.ts
@@ -14,6 +14,8 @@ const CONFIG_MAP = {
YOUTUBE_API_KEY: process.env.YOUTUBE_API_KEY,
SPOTIFY_CLIENT_ID: process.env.SPOTIFY_CLIENT_ID,
SPOTIFY_CLIENT_SECRET: process.env.SPOTIFY_CLIENT_SECRET,
+ TURN_DOWN_VOLUME_WHEN_PEOPLE_SPEAK: process.env.TURN_DOWN_VOLUME_WHEN_PEOPLE_SPEAK === 'true',
+ TURN_DOWN_VOLUME_WHEN_PEOPLE_SPEAK_TARGET: process.env.TURN_DOWN_VOLUME_WHEN_PEOPLE_SPEAK_TARGET ?? 20,
REGISTER_COMMANDS_ON_BOT: process.env.REGISTER_COMMANDS_ON_BOT === 'true',
DATA_DIR,
CACHE_DIR: path.join(DATA_DIR, 'cache'),
@@ -43,6 +45,8 @@ export default class Config {
readonly DATA_DIR!: string;
readonly CACHE_DIR!: string;
readonly CACHE_LIMIT_IN_BYTES!: number;
+ readonly TURN_DOWN_VOLUME_WHEN_PEOPLE_SPEAK!: boolean;
+ readonly TURN_DOWN_VOLUME_WHEN_PEOPLE_SPEAK_TARGET!: number;
readonly BOT_STATUS!: PresenceStatusData;
readonly BOT_ACTIVITY_TYPE!: Exclude<ActivityType, ActivityType.Custom>;
readonly BOT_ACTIVITY_URL!: string;
diff --git a/src/services/player.ts b/src/services/player.ts
index 5e284a6..00081ca 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 Config from './config.js';
export enum MediaSource {
Youtube,
@@ -82,9 +83,13 @@ export default class {
private readonly fileCache: FileCacheProvider;
private disconnectTimer: NodeJS.Timeout | null = null;
- constructor(fileCache: FileCacheProvider, guildId: string) {
+ private readonly channelToSpeakingUsers: Map<string, Set<string>> = new Map();
+ private readonly config: Config;
+
+ constructor(fileCache: FileCacheProvider, guildId: string, config: Config) {
this.fileCache = fileCache;
this.guildId = guildId;
+ this.config = config;
}
async connect(channel: VoiceChannel): Promise<void> {
@@ -96,6 +101,7 @@ export default class {
this.voiceConnection = joinVoiceChannel({
channelId: channel.id,
guildId: channel.guild.id,
+ selfDeaf: false,
adapterCreator: channel.guild.voiceAdapterCreator as DiscordGatewayAdapterCreator,
});
@@ -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();
+ }
});
}
@@ -302,6 +311,62 @@ export default class {
}
}
+ registerVoiceActivityListener(): void {
+ if (!this.config.TURN_DOWN_VOLUME_WHEN_PEOPLE_SPEAK || !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();
+ });
+
+ 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();
+ });
+ }
+
+ suppressVoiceWhenPeopleAreSpeaking(): void {
+ if (!this.currentChannel) {
+ return;
+ }
+
+ const speakingUsers = this.channelToSpeakingUsers.get(this.currentChannel.id);
+ if (speakingUsers && speakingUsers.size > 0) {
+ this.setVolume(this.config.TURN_DOWN_VOLUME_WHEN_PEOPLE_SPEAK_TARGET);
+ } else {
+ this.setVolume(this.defaultVolume);
+ }
+ }
+
canGoForward(skip: number) {
return (this.queuePosition + skip - 1) < this.queue.length;
}