Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: automatically turn volume down when people talks #1096

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@ SPOTIFY_CLIENT_SECRET=
# BOT_ACTIVITY_TYPE=
# BOT_ACTIVITY_URL=
# BOT_ACTIVITY=
# TURN_DOWN_VOLUME_WHEN_PEOPLE_SPEAK=
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).

## [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

Expand Down
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,3 +141,10 @@ 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 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%)
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "muse",
"version": "2.9.4",
"version": "2.9.5",
"description": "🎧 a self-hosted Discord music bot that doesn't suck ",
"repository": "[email protected]:museofficial/muse.git",
"author": "Max Isom <[email protected]>",
Expand Down
7 changes: 5 additions & 2 deletions src/managers/player.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
4 changes: 4 additions & 0 deletions src/services/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
Expand Down Expand Up @@ -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;
Expand Down
67 changes: 66 additions & 1 deletion src/services/player.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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> {
Expand All @@ -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,
});

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

Expand Down Expand Up @@ -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;
}
Expand Down
Loading