@axsnull/audio-sync-engine
v1.1.0
Published
Production-grade client-side audio synchronization engine for distributed playback systems
Maintainers
Readme
@axsnull/audio-sync-engine
Production-grade client-side audio synchronization engine for distributed playback systems.
Overview
@axsnull/audio-sync-engine is a production-grade audio synchronization library designed for distributed playback systems. It provides server-authoritative audio playback with precise time synchronization, drift detection and correction, and multi-device coordination.
Key Features
- Server-Authoritative: Client is a renderer only, never the source of truth
- Precise Clock Sync: WebSocket ping/pong with latency compensation
- Web Audio API: High-precision audio clock
- Drift Detection: Automatic detection of audio/sync drift
- 3-Level Drift Correction: Ignore (50ms) → Soft (50-300ms) → Hard (>300ms)
- Periodic Sync Loop: Automatic correction every 1-2 seconds
- Synchronized Start: Delayed start support for multi-device coordination
- Buffer Management: 3-10s buffer with rebuffer avoidance
- Deterministic: Server State → Sync Engine → Audio Output
Installation
npm install @axsnull/audio-sync-engineQuick Start
import { AudioSyncEngine } from '@axsnull/audio-sync-engine';
// Initialize the engine
const syncEngine = new AudioSyncEngine(
async (timestamp) => {
// Send ping to server and get pong with server timestamp
const response = await fetch('/api/ping', {
method: 'POST',
body: JSON.stringify({ clientTimestamp: timestamp })
});
const data = await response.json();
return data.serverTimestamp;
},
{
syncIntervalMs: 1500,
bufferMinSeconds: 3,
bufferMaxSeconds: 10,
bufferTargetSeconds: 5
}
);
// Initialize with audio element
const audio = new Audio();
await syncEngine.initialize(audio, async (trackId) => {
const response = await fetch(`/api/tracks/${trackId}`);
return await response.json();
});
// Handle server player updates
socket.on('player:update', (state) => {
syncEngine.handlePlayerUpdate(state);
});
// Schedule synchronized start
await syncEngine.scheduleSynchronizedStart(serverStartAt);Architecture
Core Components
ClockSync
WebSocket ping/pong clock synchronization with latency compensation:
sync()- Perform clock syncsyncedNow()- Get synchronized time (Date.now() + offset)- Formula:
latency = (t4 - t1) / 2,offset = t3 - (t1 + latency)
AudioClock
Web Audio API high-precision clock:
start()- Start AudioContextgetCurrentTime()-audioStartTime + audioContext.currentTime
DriftDetector
Calculate audio/sync drift:
calculateDrift()-actualPosition - expectedPositionexpectedPosition = server.positionMs + (syncedNow - server.updatedAt)- Thresholds: 50ms (ignore), 300ms (soft correction)
DriftCorrector
3-level drift correction:
- Ignore: drift < 50ms
- Soft: 50-300ms - adjust playbackRate (0.98/1.02)
- Hard: > 300ms - seek to expectedPosition
PlayerStateSync
Handle server player updates:
handlePlayerUpdate()- Sync with server state- Load new tracks on trackId change
- Apply play/pause + position sync
PeriodicSync
Automatic periodic synchronization:
- Configurable interval (default 1.5s)
- Automatic drift correction every 1-2s
SynchronizedStart
Delayed start for multi-device coordination:
scheduleStart()- Calculate delay and setTimeout- Support for server-provided startAt timestamp
BufferManager
Audio buffer management:
- Min buffer: 3s
- Max buffer: 10s
- Target buffer: 5s
- Avoid rebuffer during sync
API Reference
AudioSyncEngine
Main class that integrates all components.
constructor(
sendPing: (timestamp: number) => Promise<number>,
config?: AudioSyncEngineConfig
)Methods
initialize(audio: HTMLAudioElement, loadTrack: (trackId: string) => Promise<TrackMetadata>)- Initialize enginehandlePlayerUpdate(state: PlayerState)- Handle server player updateperformPeriodicSync()- Perform periodic drift correctionscheduleSynchronizedStart(serverStartAt: number)- Schedule delayed startsyncTime()- Synchronize time with servergetSyncedNow()- Get synchronized timegetClockOffset()- Get clock offsetgetClockLatency()- Get clock latencystop()- Stop all processes
AudioSyncEngineConfig
{
syncIntervalMs?: number; // Default: 1500
bufferMinSeconds?: number; // Default: 3
bufferMaxSeconds?: number; // Default: 10
bufferTargetSeconds?: number; // Default: 5
}PlayerState
{
userId: string;
activeDeviceId: string | null;
trackId: string | null;
isPlaying: boolean;
positionMs: number;
updatedAt: number;
lastServerSyncAt: number;
queue?: string[];
currentIndex?: number;
}TrackMetadata
{
id: string;
streamUrl: string;
duration: number;
source: 's3' | 'cdn' | 'proxy';
}Usage Examples
Basic Setup
import { AudioSyncEngine } from '@axsnull/audio-sync-engine';
const syncEngine = new AudioSyncEngine(
async (timestamp) => {
const response = await fetch('/api/ping', {
method: 'POST',
body: JSON.stringify({ clientTimestamp: timestamp })
});
const data = await response.json();
return data.serverTimestamp;
}
);
const audio = new Audio();
await syncEngine.initialize(audio, async (trackId) => {
const response = await fetch(`/api/tracks/${trackId}`);
return await response.json();
});Handling Player Updates
socket.on('player:update', (state) => {
syncEngine.handlePlayerUpdate(state);
});Manual Time Sync
await syncEngine.syncTime();
const syncedNow = syncEngine.getSyncedNow();
console.log('Synced time:', syncedNow);
console.log('Clock offset:', syncEngine.getClockOffset());
console.log('Clock latency:', syncEngine.getClockLatency());Synchronized Start
const schedule = await syncEngine.scheduleSynchronizedStart(serverStartAt);
console.log(`Starting in ${schedule.delay}ms`);Cleanup
syncEngine.stop();Best Practices
- Never Initiate Playback Locally: Always sync with server state
- Use Synced Time: Always use
syncedNow()instead ofDate.now()for calculations - Handle Network Latency: Clock sync accounts for latency automatically
- Buffer Management: The engine manages buffers automatically
- Graceful Degradation: System continues to work even with occasional sync failures
Requirements
- Node.js >= 16.0.0
- Modern browser with Web Audio API support
- WebSocket connection to server
License
MIT
Contributing
Contributions are welcome! Please open an issue or submit a pull request.
Support
For issues and questions, please open an issue on GitHub. #� �-�a�x�s�n�u�l�l�-�a�u�d�i�o�-�s�y�n�c�-�e�n�g�i�n�e� � �
