A production-ready Push-to-Talk application with Opus codec, dynamic rooms, audio replay, and real-time multi-user support.
Local Development:
npm install
npm start
# Visit http://localhost:3000Remote Server Deployment:
npm install
export PORT=3000
export HOST=0.0.0.0 # Listen on all interfaces
npm start
# Server now accessible from external domainsDevelopment with hot reload:
npm run devJoin a specific room:
http://localhost:3000?room=meeting
http://localhost:3000?room=team1
Important: For remote access (e.g., zellous.247420.xyz), ensure:
- Server is running with
HOST=0.0.0.0(automatic by default) - Firewall allows inbound traffic on the PORT (default: 3000)
- For domains, set up reverse proxy (nginx) or use port 80/443
- See CLAUDE.md for nginx configuration example
- Push-to-Talk: Hold button to record and transmit
- Opus Codec: 24kbps bitrate, 93.75% bandwidth reduction
- Dynamic Rooms: URL-based rooms with complete isolation
- Audio Replay: Replay last 50 messages with audio
- Live Playback: Stream audio in real-time from other speakers
- Auto Pause/Resume: Playback automatically pauses when you talk
- Active Speakers: Real-time display of who's speaking
- Message History: Timestamped communication log with replay buttons
- Multi-user: Supports simultaneous speakers per room
- Volume Control: Master volume slider (0-100%)
- Hot Reload: Nodemon for development
- Responsive: Desktop and mobile friendly
Zellous uses a modular, scalable design:
Modules:
config- Constantsstate- Centralized state object (18 properties including roomId)audio- Opus encoding/decoding/playback/replaymessage- Message routing (7 message types)network- WebSocket communication with room injectionaudioIO- Microphone/recordingptt- Push-to-talk logicui_*- UI rendering and events
Handler pattern:
handlers = {
join_room: (client, msg) => { /* ... */ },
audio_start: (client) => { /* ... */ },
audio_chunk: (client, msg) => { /* ... */ },
audio_end: (client) => { /* ... */ },
set_username: (client, msg) => { /* ... */ }
}Room-filtered broadcast efficiently distributes messages only to clients in the same room.
Semantic HTML with inline CSS. No external dependencies.
Single centralized state object contains:
- Audio context, microphone stream
- Opus encoder/decoders per user
- Audio buffers and sources per user
- Active speakers set
- Message history array (50 messages)
- Audio history for replay
- WebSocket connection
- User ID, room ID, speaking state
Benefits:
- Predictable data flow
- Easy debugging
- Simple reset capability
- Clear dependencies
{ type: 'join_room', roomId: 'lobby' } // Join a room
{ type: 'audio_start' } // User started talking
{ type: 'audio_chunk', data: [...] } // Audio data (Opus)
{ type: 'audio_end' } // User stopped talking
{ type: 'set_username', username: 'name' } // Update username{ type: 'room_joined', roomId, currentUsers } // Room join confirmed
{ type: 'speaker_joined', user, userId } // User started talking
{ type: 'speaker_left', userId, user } // User stopped talking
{ type: 'audio_data', userId, data } // Audio chunk
{ type: 'user_joined', user, userId } // User connected
{ type: 'user_left', userId } // User disconnected
{ type: 'connection_established', clientId } // Connection confirmed- 48kHz sample rate for quality
- Opus codec at 24kbps bitrate
- WebCodecs API for native encoding/decoding
- 93.75% bandwidth reduction (384kbps → 24kbps)
- 4096 sample chunks for low latency
- 100ms playback intervals for smooth audio
- Per-user AudioDecoder for concurrent playback
- Audio replay for last 50 messages
Server (server.js):
handlers.my_message = (client, msg) => {
broadcast({ type: 'my_response', data: msg.data });
}Client (app.js):
message.handlers.my_response = (msg) => {
// Handle response
}- Add element to HTML
- Add reference to
uiobject - Create render function in
ui_render - Bind events in
ui_events.setup()
Add methods to audio module and call from playback loop.
Dynamic rooms work via URL query parameters:
http://localhost:3000?room=team1
http://localhost:3000?room=meeting
Complete isolation between rooms with zero configuration required.
Access all internals via window.zellousDebug:
window.zellousDebug.state // Full application state
window.zellousDebug.audio // Audio functions
window.zellousDebug.message // Message handlers
window.zellousDebug.network // Network functions
window.zellousDebug.ptt // PTT controls
window.zellousDebug.config // Configuration- Minimal dependencies: Express, WebSocket only
- Event-driven: No polling
- Audio buffering: Prevents stuttering
- Opus codec: 93.75% bandwidth savings
- Map-based storage: O(1) client lookups
- Room filtering: O(1) per-client comparison
Full Support (WebCodecs API):
- Chrome/Chromium 94+ (Opus encoder/decoder)
- Edge 94+ (Opus encoder/decoder)
- Opera 80+ (Opus encoder/decoder)
Partial Support:
- Safari Technology Preview (AudioDecoder only)
Requirements:
- Web Audio API
- WebCodecs API (AudioEncoder, AudioDecoder)
- MediaDevices API
Already Implemented:
- ✅ Multiple rooms/channels (URL-based, zero config)
- ✅ Hot reload for development
- ✅ Audio replay functionality
Ready for:
- User authentication (add to message handlers)
- Encryption (wrap network.send)
- Database integration (replace broadcast)
- Clustering (use Redis, socket.io)
- Recording (add to audio.play callback)
See CLAUDE.md for detailed technical documentation.
server.js - Express + WebSocket server (93 lines)
app.js - Frontend application (373 lines)
index.html - UI markup and styles (68 lines)
package.json - Dependencies (express, ws, nodemon)
nodemon.json - Hot reload configuration (5 lines)
README.md - Quick start and features
CLAUDE.md - Technical reference
CHANGELOG.md - Version history
MIT