Files
ermnvldmr.com/services/docs/content/en/quetza/guides/voice-features.md
T
deytenit b3638f4d63 fix: cyrillic fonts, dev certs, page head, and ru-text typography (#67)
🤖 PR generated for branch `fix/cyrillic-fonts-dev-certs-and-page-head`.

### Changes Summary
- **Fonts**: Add Cyrillic glyph subsets for Lato, EB Garamond, and Fira
Code.
- **Dev**: Support remote Root CA and encrypted dev CA key in
`.env.dev`.
- **UI & Kits**: Add size variant to `Breadcrumbs` component and enlarge
`PageHead` breadcrumbs with tuned collapse scroll threshold.
- **ESLint**: Add `no-hanging-particles` i18n & markdown typography rule
with auto-fixer.
- **Content & i18n**: Apply `ru-text` editorial and typography polish
(em dashes, number ranges, «ё» restoration, non-breaking spaces) across
`services/www` and `services/docs`.
2026-10-25 15:21:09 +03:00

1.8 KiB

title, weight
title weight
Interactive Voice Features 3

Goal: Implement sophisticated voice interactions that respect guild boundaries and manage ephemeral resources.

1. Resource Scoping Strategy

Voice features MUST be scoped to the Guild.

  • Pattern: The Controller maintains a Map<GuildID, Player>.
  • Behavior:
    • Join: Create a Player for Guild A if none exists.
    • Leave: Destroy the Player and release connections.
    • Isolation: Commands in Guild B never affect Guild A.

2. Command Orchestration

Voice features require a suite of commands coordinated via the Controller.

Command Action Controller Logic
/join Connect to channel createPlayer(guildId, channelId)
/play Add track to queue getPlayer(guildId).enqueue(track)
/stop Disconnect destroyPlayer(guildId)

3. State Persistence

Audio sessions are ephemeral.

  • Transient State: Active audio stream (Lost on disconnect).
  • Durable State: Queue/Playlist (Persisted in Controller until restart).

Tip

Resilience: Separate the Queue logic from the Voice Connection logic. This allows the bot to reconnect without losing the user's playlist.

4. Implementation Sketch

// lib/MusicController.ts
class MusicController {
  private players = new Map<string, AudioPlayer>();

  getPlayer(guildId: string): AudioPlayer {
    if (!this.players.has(guildId)) {
      this.players.set(guildId, new AudioPlayer());
    }
    return this.players.get(guildId);
  }

  destroyPlayer(guildId: string) {
    this.players.get(guildId)?.disconnect();
    this.players.delete(guildId);
  }
}

See also: