Add channel and permission management, sounds, themes and the public server list
- Channels: create, edit, delete, link and move (drag and drop for channels and people), right-click on free space in the channel list - Permission editor with inherited rules, allow/deny per permission, groups and members; menus grey out actions the server does not allow - Notification sounds for connection, channel, chat, mute and push-to-talk events; custom files and TeamSpeak 3 style sound packs (settings.ini), stored in IndexedDB - Color schemes: Dark, Light, Midnight, Frost and Windows 95 - Public server list with live UDP ping, fetched through Chromium's network stack - Kick, ban and connection-lost card with Reconnect - Right-click menus for text fields, links, images and message authors - Formatting toolbar in the message box, shared with the description editor - Resizable channel list, menu headers, registration from the self menu only - Fix: client-side pacing now mirrors Murmur's whole-second leaky bucket exactly, so quick actions are no longer dropped silently Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -80,6 +80,11 @@ class VoiceEngine {
|
||||
private client: MumbleClient | null = null;
|
||||
private sessionGains = new Map<number, number>();
|
||||
private unsubscribe: (() => void)[] = [];
|
||||
// Push-to-talk press/release, for the notification sounds
|
||||
onPtt: ((down: boolean) => void) | null = null;
|
||||
// Speaking (or pressing push to talk) while muted, for the reminder sound
|
||||
onTalkingWhileMuted: (() => void) | null = null;
|
||||
private lastMutedReminder = 0;
|
||||
|
||||
private frameCounter = 0; // Mumble sequence, in 10 ms frames
|
||||
private sampleTime = 0; // encoder timestamps, microseconds
|
||||
@@ -301,6 +306,12 @@ class VoiceEngine {
|
||||
if (db > s.vadThreshold) this.lastVoiceAt = now;
|
||||
wanted = now - this.lastVoiceAt < s.vadHoldMs;
|
||||
}
|
||||
// Speaking into a muted microphone: remind, at most every 8 seconds
|
||||
const self = this.client?.self;
|
||||
if (wanted && s.mode === 'vad' && self && (self.selfMute || self.mute) && !self.selfDeaf && now - this.lastMutedReminder > 8000) {
|
||||
this.lastMutedReminder = now;
|
||||
this.onTalkingWhileMuted?.();
|
||||
}
|
||||
wanted &&= this.canTalk() && !!this.encoder && this.encoder.state === 'configured';
|
||||
|
||||
if (wanted && !this.open) { this.open = true; this.transmitting = true; }
|
||||
@@ -353,7 +364,13 @@ class VoiceEngine {
|
||||
// Typing in a text field does not trigger push to talk
|
||||
const t = e.target as HTMLElement | null;
|
||||
if (down && t && (t.tagName === 'INPUT' || t.tagName === 'TEXTAREA' || t.isContentEditable)) return;
|
||||
if (this.pttDown === down) return;
|
||||
this.pttDown = down;
|
||||
if (this.settings.mode === 'ptt' && this.client) {
|
||||
const self = this.client.self;
|
||||
if (down && self && (self.selfMute || self.mute)) this.onTalkingWhileMuted?.();
|
||||
else this.onPtt?.(down);
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Receiving ──────────────────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user