mirror of
https://github.com/BluemediaDev/muse.git
synced 2025-06-27 09:12:43 +02:00
feat: automatically turn down volume when people talks
This commit is contained in:
parent
534d8fafaa
commit
aae98255b1
7 changed files with 90 additions and 4 deletions
|
@ -18,3 +18,4 @@ SPOTIFY_CLIENT_SECRET=
|
||||||
# BOT_ACTIVITY_TYPE=
|
# BOT_ACTIVITY_TYPE=
|
||||||
# BOT_ACTIVITY_URL=
|
# BOT_ACTIVITY_URL=
|
||||||
# BOT_ACTIVITY=
|
# BOT_ACTIVITY=
|
||||||
|
# TURN_DOWN_VOLUME_WHEN_PEOPLE_SPEAK=
|
|
@ -5,6 +5,12 @@ 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]
|
||||||
|
## [2.9.5] - 2024-09-15
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- An optional `TURN_DOWN_VOLUME_WHEN_PEOPLE_SPEAK` config to automatically turn
|
||||||
|
down the volume when people are speaking in the channel
|
||||||
|
- An optional `TURN_DOWN_VOLUME_WHEN_PEOPLE_SPEAK_TARGET` config to set the target volume when people are speaking in the channel
|
||||||
|
|
||||||
## [2.9.4] - 2024-08-28
|
## [2.9.4] - 2024-08-28
|
||||||
|
|
||||||
|
|
|
@ -141,3 +141,10 @@ 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 let the bot automatically turn down the volume when people are speaking
|
||||||
|
in the channel though environment variable:
|
||||||
|
- `TURN_DOWN_VOLUME_WHEN_PEOPLE_SPEAK=true`
|
||||||
|
- `TURN_DOWN_VOLUME_WHEN_PEOPLE_SPEAK_TARGET=70` (optional, default is 70%)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "muse",
|
"name": "muse",
|
||||||
"version": "2.9.4",
|
"version": "2.9.5",
|
||||||
"description": "🎧 a self-hosted Discord music bot that doesn't suck ",
|
"description": "🎧 a self-hosted Discord music bot that doesn't suck ",
|
||||||
"repository": "git@github.com:museofficial/muse.git",
|
"repository": "git@github.com:museofficial/muse.git",
|
||||||
"author": "Max Isom <hi@maxisom.me>",
|
"author": "Max Isom <hi@maxisom.me>",
|
||||||
|
|
|
@ -2,22 +2,25 @@ import {inject, injectable} from 'inversify';
|
||||||
import {TYPES} from '../types.js';
|
import {TYPES} from '../types.js';
|
||||||
import Player from '../services/player.js';
|
import Player from '../services/player.js';
|
||||||
import FileCacheProvider from '../services/file-cache.js';
|
import FileCacheProvider from '../services/file-cache.js';
|
||||||
|
import Config from '../services/config.js';
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export default class {
|
export default class {
|
||||||
private readonly guildPlayers: Map<string, Player>;
|
private readonly guildPlayers: Map<string, Player>;
|
||||||
private readonly fileCache: FileCacheProvider;
|
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.guildPlayers = new Map();
|
||||||
this.fileCache = fileCache;
|
this.fileCache = fileCache;
|
||||||
|
this.config = config;
|
||||||
}
|
}
|
||||||
|
|
||||||
get(guildId: string): Player {
|
get(guildId: string): Player {
|
||||||
let player = this.guildPlayers.get(guildId);
|
let player = this.guildPlayers.get(guildId);
|
||||||
|
|
||||||
if (!player) {
|
if (!player) {
|
||||||
player = new Player(this.fileCache, guildId);
|
player = new Player(this.fileCache, guildId, this.config);
|
||||||
|
|
||||||
this.guildPlayers.set(guildId, player);
|
this.guildPlayers.set(guildId, player);
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,8 @@ const CONFIG_MAP = {
|
||||||
YOUTUBE_API_KEY: process.env.YOUTUBE_API_KEY,
|
YOUTUBE_API_KEY: process.env.YOUTUBE_API_KEY,
|
||||||
SPOTIFY_CLIENT_ID: process.env.SPOTIFY_CLIENT_ID,
|
SPOTIFY_CLIENT_ID: process.env.SPOTIFY_CLIENT_ID,
|
||||||
SPOTIFY_CLIENT_SECRET: process.env.SPOTIFY_CLIENT_SECRET,
|
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',
|
REGISTER_COMMANDS_ON_BOT: process.env.REGISTER_COMMANDS_ON_BOT === 'true',
|
||||||
DATA_DIR,
|
DATA_DIR,
|
||||||
CACHE_DIR: path.join(DATA_DIR, 'cache'),
|
CACHE_DIR: path.join(DATA_DIR, 'cache'),
|
||||||
|
@ -43,6 +45,8 @@ export default class Config {
|
||||||
readonly DATA_DIR!: string;
|
readonly DATA_DIR!: string;
|
||||||
readonly CACHE_DIR!: string;
|
readonly CACHE_DIR!: string;
|
||||||
readonly CACHE_LIMIT_IN_BYTES!: number;
|
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_STATUS!: PresenceStatusData;
|
||||||
readonly BOT_ACTIVITY_TYPE!: Exclude<ActivityType, ActivityType.Custom>;
|
readonly BOT_ACTIVITY_TYPE!: Exclude<ActivityType, ActivityType.Custom>;
|
||||||
readonly BOT_ACTIVITY_URL!: string;
|
readonly BOT_ACTIVITY_URL!: string;
|
||||||
|
|
|
@ -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 Config from './config.js';
|
||||||
|
|
||||||
export enum MediaSource {
|
export enum MediaSource {
|
||||||
Youtube,
|
Youtube,
|
||||||
|
@ -82,9 +83,13 @@ export default class {
|
||||||
private readonly fileCache: FileCacheProvider;
|
private readonly fileCache: FileCacheProvider;
|
||||||
private disconnectTimer: NodeJS.Timeout | null = null;
|
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.fileCache = fileCache;
|
||||||
this.guildId = guildId;
|
this.guildId = guildId;
|
||||||
|
this.config = config;
|
||||||
}
|
}
|
||||||
|
|
||||||
async connect(channel: VoiceChannel): Promise<void> {
|
async connect(channel: VoiceChannel): Promise<void> {
|
||||||
|
@ -96,6 +101,7 @@ 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,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -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();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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) {
|
canGoForward(skip: number) {
|
||||||
return (this.queuePosition + skip - 1) < this.queue.length;
|
return (this.queuePosition + skip - 1) < this.queue.length;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue