feat: automatically turn down volume when people talks

This commit is contained in:
Yitong Xiao 2024-09-15 17:53:05 -04:00
parent 534d8fafaa
commit aae98255b1
7 changed files with 90 additions and 4 deletions

View file

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

View file

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

View file

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