Internet-Based Synchronized Audio Playback — Beat-level sync across multiple devices without WebSockets.
Live Demo — Try it now!
- Overview
- Features
- Tech Stack
- Architecture
- Getting Started
- Project Structure
- How It Works
- API Reference
- Deployment
- Troubleshooting
ChronoBeat-IO enables synchronized music playback between multiple devices without WebSockets, by anchoring audio playback to a global server time instead of relying on real-time message delivery.
The goal is perceptual beat-level sync (like an add-on speaker), not sample-perfect hardware sync. All connected devices play the same track at the same global timestamp, creating seamless multi-device listening experiences.
Instead of synchronizing play / pause events, all devices synchronize time.
Every device plays the same track at the same global timestamp.
[+] Multi-Device Synchronization — Sync music playback across unlimited devices in real-time
[+] WebSocket-Free — Uses stateless REST HTTP APIs for maximum compatibility
[+] NTP-Style Time Sync — Client clocks auto-calibrate for accuracy
[+] Drift Correction — Automatic periodic correction for clock drift
[+] Room-Based Architecture — Create multiple independent listening rooms
[+] Leader/Follower Model — One user controls playback, others follow seamlessly
[+] Audio Upload & Streaming — Upload MP3/WAV files or stream from URLs
[+] User Management — Track connected users with avatars and IDs
[+] Volume Control & Muting — Individual device volume control
[+] Real-Time User List — See who's connected in your room
[+] Progressive Web App — Works on desktop and mobile
[+] Dark Mode UI — Beautiful Material Design interface
- React 19.2 — UI framework
- Vite 7.2 — Fast build tool
- Material-UI 7.3 — Component library
- React Router 7.10 — Routing
- Tailwind CSS 4.1 — Utility-first styling
- Node.js + Express 5.2 — REST API server
- Multer 2.0 — File upload handling
- CORS 2.8 — Cross-origin requests
- Render — Deployment platform (see RENDER_DEPLOYMENT.md)
- HTTP/REST — Stateless API design
Client clocks are not trusted directly. Each device estimates its offset from server time using an NTP-style exchange:
offset = serverTime - clientLocalTime
This offset is stored locally and applied to all playback calculations.
When a leader starts playback, the system creates a playback anchor:
{
"trackUrl": "song.mp3",
"songPositionMs": 4534,
"serverTimeMs": 1723456789267,
"isPlaying": true
}This anchor is the single source of truth for all connected devices.
When a device receives the playback anchor later than the leader:
nowServerTime = Date.now() + offset
elapsed = nowServerTime - anchor.serverTimeMs
startPositionSec = (anchor.songPositionMs + elapsed) / 1000Playback starts from the calculated position:
audio.currentTime = startPositionSec
audio.play()This ensures beat alignment, even if playback begins mid-track.
Clock drift and decoding differences are corrected periodically.
Every 2–3 seconds:
expected = (Date.now() + offset - serverStartTime) / 1000
drift = audio.currentTime - expected
if (Math.abs(drift) > 0.12) {
audio.currentTime = expected
}Small corrections keep devices perceptually synchronized over long sessions.
- Node.js 16+ and npm
- Modern web browser with Web Audio API support
- Clone the repository
git clone https://github.com/yourusername/ChronoBeat-IO.git
cd ChronoBeat-IO- Install dependencies
# Frontend dependencies
npm install
# Backend dependencies
cd server
npm install
cd ..- Start development servers
Open two terminals:
Terminal 1 - Frontend (Vite)
npm run devFrontend runs on http://localhost:5173
Terminal 2 - Backend (Express)
cd server
npm startBackend runs on http://localhost:3000
- Open in browser
Navigate to
http://localhost:5173and start creating rooms!
The application is deployed and accessible at:
- Frontend: https://chronobeat-frontend.onrender.com
- Backend: https://chronobeat-backend.onrender.com (or your configured URL)
You can start using it immediately without local setup!
# Frontend build
npm run build
# Outputs to dist/ directoryChronoBeat-IO/
├── src/
│ ├── components/
│ │ ├── AudioSelector.jsx # Audio file/URL selection UI
│ │ └── UserList.jsx # Connected users display
│ ├── hooks/
│ │ ├── useTimeSync.js # NTP-style time synchronization
│ │ ├── useAudioPreloader.js # Audio file preloading
│ │ ├── useSyncedAudio.js # Core playback sync logic
│ │ ├── useRoom.js # Room state management
│ │ ├── useScheduledAudio.js # Scheduled playback
│ │ └── useWebRTC.js # WebRTC signaling (future)
│ ├── pages/ # Route pages
│ ├── App.jsx # Main React component
│ ├── main.jsx # Entry point
│ └── assets/ # Static assets
├── server/
│ ├── index.js # Express REST API server
│ └── package.json # Server dependencies
├── public/ # Static files
├── index.html # HTML entry point
├── vite.config.js # Vite configuration
├── eslint.config.js # Linting rules
├── package.json # Frontend dependencies
└── README.md # This file
- Leader creates a room (gets a room ID)
- Followers join using the room ID
- Server tracks all connected users
- Upload an MP3/WAV file, or
- Provide a URL to stream audio
- Audio is stored on the server and accessible to all users in the room
- Each device pings the
/api/timeendpoint - Calculates clock offset from server
- Stores offset for playback calculations
- Leader clicks play → sends playback anchor to server
- Server broadcasts anchor state to all devices
- Each device calculates playback position based on:
- Server timestamp
- Audio position at anchor time
- Their clock offset
- All devices start playing at the same calculated position
- Every 2-3 seconds, devices check audio drift
- If drift > 120ms, resync to calculated position
- Keeps all devices in perceptual sync
- Users ping server with heartbeat every 10 seconds
- Server removes inactive users after 30 seconds
- If leader leaves, next user becomes new leader
GET /api/timeReturns current server timestamp in milliseconds.
Response:
{ "time": 1723456789267 }Join Room
POST /api/rooms/:roomId/join
Content-Type: application/json
{
"userId": "user123",
"username": "Alice",
"isLeader": false
}Leave Room
POST /api/rooms/:roomId/leave
Content-Type: application/json
{ "userId": "user123" }Get Room State
GET /api/rooms/:roomId/statePing (Heartbeat)
POST /api/rooms/:roomId/ping
Content-Type: application/json
{ "userId": "user123" }Upload Audio
POST /api/rooms/:roomId/audio/upload
Content-Type: multipart/form-data
[binary audio file]Set Current Audio
POST /api/rooms/:roomId/audio/current
Content-Type: application/json
{
"url": "https://example.com/song.mp3",
"filename": "song.mp3"
}Get Current Audio
GET /api/rooms/:roomId/audio/currentStart Playback
POST /api/rooms/:roomId/playback/start
Content-Type: application/json
{
"userId": "user123",
"audioOffset": 0,
"speed": 1.0
}Pause Playback
POST /api/rooms/:roomId/playback/pause
Content-Type: application/json
{ "userId": "user123" }Seek Position
POST /api/rooms/:roomId/playback/seek
Content-Type: application/json
{
"userId": "user123",
"position": 45000
}Frontend: https://chronobeat-frontend.onrender.com
See RENDER_DEPLOYMENT.md for complete setup instructions.
Quick Summary:
- Deploy backend Express server to Render Web Service
- Set
PUBLIC_URLandALLOWED_ORIGINSenvironment variables - Deploy frontend to Render Static Site with build command
npm run build - Configure frontend to point to backend URL
Backend (.env file):
PORT=3000
NODE_ENV=production
PUBLIC_URL=https://your-backend.onrender.com
ALLOWED_ORIGINS=https://your-frontend.onrender.com,http://localhost:5173
Frontend (.env file):
VITE_API_URL=https://your-backend.onrender.com
- Solution: Check network latency. Large latency (>500ms) can affect sync precision.
- Solution: Restart the room to reinitialize time sync.
- Solution: Ensure all devices have system time set correctly.
- Solution: Check browser console for network errors.
- Solution: Ensure
ALLOWED_ORIGINSin backend includes your frontend domain. - Solution: Check if devices are behind strict firewalls.
- Solution: Verify file format is MP3 or WAV.
- Solution: Check file size (max 50MB).
- Solution: Ensure CORS headers are correctly configured.
- Solution: Verify backend is running and accessible.
- Solution: Check browser network tab for failed requests.
- Solution: Ensure
PUBLIC_URLenvironment variable matches your deployment URL.
- Sync Precision: Typically ±100-200ms perceptual sync
- Max Users Per Room: Tested with 50+ users per room
- Audio Quality: Limited by client's audio playback decoder
- Latency Tolerance: Works best with <500ms network latency
- Server Memory: ~1-2MB per active room
- WebRTC audio streaming for lower latency
- Playlist management
- User authentication & persistent profiles
- Advanced audio visualization
- Voice chat integration
- Mobile app (React Native)
- Offline mode with sync on reconnect
- Fork the repository
- Create a feature branch (
git checkout -b feature/AmazingFeature) - Commit changes (
git commit -m 'Add AmazingFeature') - Push to branch (
git push origin feature/AmazingFeature) - Open a Pull Request
This project is licensed under the ISC License - see the LICENSE file for details.
- Email: support@chronobeat-io.com
- Bug Reports: Open an issue on GitHub
- Discussions: GitHub Discussions tab
This system uses stateless HTTP APIs:
| Action | Method |
|---|---|
| Create room | POST |
| Play | POST |
| Pause | POST |
| Seek | POST |
| Sync state | GET (polling) |
Clients poll the playback state every 2–5 seconds.
| Network Conditions | Sync Accuracy |
|---|---|
| Good broadband | ±50–100 ms |
| Mobile / average | ±150 ms |
| Cross-region | ±200 ms |
This level of sync is sufficient for music listening and shared playback.
Even with timestamp anchoring, sample-perfect sync cannot be achieved due to:
- Different audio decoding latencies
- Browser audio buffering
- Hardware clock differences
- Mobile power management
This limitation applies to all internet-based playback systems.
- Works over the public internet
- No WebSockets required
- Compatible with serverless platforms (Vercel)
- Scales easily
- Mobile browser friendly
- Shared music listening
- Add-on speaker behavior across devices
- Watch / listen together apps
- Lightweight real-time audio sync
Timestamp-anchored playback is the best achievable method for synchronized audio over the internet.
By synchronizing time instead of events, devices can play the same song in perceptual sync, even when joining late or experiencing network latency.
This approach mirrors the techniques used by major streaming platforms and is production-proven.