@panaudia/client
v2.0.0
Published
Panaudia client for spatial audio streaming — MOQ and WebRTC transports
Maintainers
Readme
@panaudia/client
Unified TypeScript client for Panaudia spatial audio. Supports MOQ (Media over QUIC) and WebRTC transports behind a common API.
Installation
npm install @panaudia/clientQuick Start
Production (with gateway)
import {PanaudiaClient, resolveServer} from '@panaudia/client';
// Resolve server URL + transport via gateway
const server = await resolveServer(ticket); // { serverUrl, transport }
// Create and connect (audio starts automatically)
const client = new PanaudiaClient({...server, ticket});
await client.connect();
// Set spatial pose (Panaudia coordinates: position 0-1, rotation in degrees)
client.setPose({
position: {x: 0.5, y: 0.5, z: 0.5},
rotation: {yaw: 90, pitch: 0, roll: 0}
});
// Listen for other entities
client.on('entityState', (state) => {
console.log(`${state.uuid} at (${state.position.x}, ${state.position.y}, ${state.position.z})`);
});
// Disconnect
await client.disconnect();With coordinate conversion (Three.js example)
import { PanaudiaClient } from '@panaudia/client';
import { threejsToPanaudia, panaudiaToThreejs } from '@panaudia/client';
const client = new PanaudiaClient({ serverUrl, ticket });
await client.connect();
// Convert Three.js pose to Panaudia and send
const pose = threejsToPanaudia(
{ x: 0, y: 1.6, z: -5 },
{ x: 0, y: Math.PI / 2, z: 0 },
);
client.setPose(pose);
// Convert incoming entity state back to Three.js
client.on('entityState', (state) => {
const threejs = panaudiaToThreejs(state.position, state.rotation);
mesh.position.set(threejs.position.x, threejs.position.y, threejs.position.z);
});With world bounds normalization
// Positions are automatically normalized from [-100, 100] to [0, 1] for the server,
// and denormalized back to [-100, 100] in entityState events.
const client = new PanaudiaClient({
serverUrl,
ticket,
worldBounds: { min: -100, max: 100 },
});Microphone Selection
Bluetooth microphones can force stereo audio to collapse to mono (the
HFP/SCO profile replaces A2DP). The client never blocks on this —
connect() always proceeds — but it warns when it can see a Bluetooth
mic is (or is likely to be) in use, without ever opening a device to
check (opening one is itself what triggers the HFP switch):
const client = new PanaudiaClient({ serverUrl, ticket });
client.on('warning', (w) => {
if (w.code === 'BLUETOOTH_MIC' || w.code === 'BLUETOOTH_MIC_DEFAULT') {
// BLUETOOTH_MIC — the explicitly chosen microphoneId is Bluetooth
// BLUETOOTH_MIC_DEFAULT — the system default mic looks Bluetooth
showWarningBanner(w.message);
}
});
await client.connect();An actual stereo→mono collapse is detected after connecting — see
getStereoDiagnostics() and probeOutputDeviceSampleRate().
To steer users to a better device, show a mic picker before connecting:
// List all mics with type classification
const mics = await PanaudiaClient.listMicrophones();
// Or get the recommended non-Bluetooth mic for pre-selection
const recommended = await PanaudiaClient.getRecommendedMicrophone();Microphone Capture Options
By default the client captures raw mic audio — no echo cancellation, no
noise suppression, no auto gain control. This preserves the signal for
spatial mixing. You can opt in to the browser's processing by passing
an audio config:
const client = new PanaudiaClient({
serverUrl,
ticket,
audio: {
echoCancellation: true, // Default: false
noiseSuppression: true, // Default: false
autoGainControl: true, // Default: false
},
});These flags apply to both MOQ and WebRTC transports. Sample rate is fixed at 48 kHz.
Local Development (no gateway)
const client = new PanaudiaClient({
serverUrl: 'quic://dev.panaudia.com:4433/moq',
ticket: devJwt,
});
await client.connect();Coordinate Conversion
The library includes conversion functions for 7 web 3D frameworks. Each pair converts between that framework's native coordinate system and Panaudia's internal coordinates.
| Framework | To Panaudia | From Panaudia | Position Convention | Rotation Convention |
|-----------|-------------|---------------|--------------------|--------------------|
| Three.js | threejsToPanaudia() | panaudiaToThreejs() | RH, Y-up, -Z fwd | XYZ Euler, radians |
| Babylon.js | babylonToPanaudia() | panaudiaToBabylon() | LH, Y-up, +Z fwd | YXZ Euler, radians |
| A-Frame | aframeToPanaudia() | panaudiaToAframe() | RH, Y-up, -Z fwd | YXZ Euler, degrees |
| PlayCanvas | playcanvasToPanaudia() | panaudiaToPlaycanvas() | RH, Y-up, -Z fwd | XYZ Euler, degrees |
| Unity | unityToPanaudia() | panaudiaToUnity() | LH, Y-up, +Z fwd | ZXY Euler, degrees |
| Unreal | unrealToPanaudia() | panaudiaToUnreal() | LH, Z-up, +X fwd | FRotator (P/Y/R), degrees |
| PixiJS | pixiToPanaudia() | panaudiaToPixi() | 2D, Y-down | Scalar rotation, radians |
All rotations use quaternion intermediary math to correctly handle handedness and axis permutation differences between frameworks.
Transport Selection
The client supports two transports:
| Transport | Protocol | Browser Support | |-----------|----------|-----------------------| | MOQ (default) | WebTransport + QUIC | Chrome, Edge, Firefox | | WebRTC (fallback) | WebSocket signaling + RTCPeerConnection | All browsers |
Transport selection happens in resolveServer() — it auto-detects by
default (MOQ if WebTransport is supported, else WebRTC), asks the gateway
for a server URL for that transport, and returns both. Pass the result
straight to the constructor so the client uses the transport the URL was
resolved for:
// Auto-detect (default)
const server = await resolveServer(ticket); // { serverUrl, transport }
const client = new PanaudiaClient({ ...server, ticket });
// Force WebRTC
const server = await resolveServer(ticket, { transport: 'webrtc' });
const client = new PanaudiaClient({ ...server, ticket });
// Hardcoded dev URL (no gateway) — say which transport the URL is for,
// or omit it to auto-detect
const client = new PanaudiaClient({
serverUrl: 'quic://dev.panaudia.com:4433/moq',
ticket,
transport: 'moq',
});API Reference
See docs/api-reference.md for the full API reference, types, sub-path exports, direct MOQ access, and migration guide.
Browser Support
| Browser | Transport | Notes | |---------|-----------|-------| | Chrome 114+ | MOQ (WebTransport) | Default | | Edge 114+ | MOQ (WebTransport) | Default | | Firefox 114+ | MOQ (WebTransport) | Default | | Safari 18+ | WebRTC | Auto-fallback (no WebTransport yet) |
Development
npm install
npm run build # TypeScript + Vite build
npm test # Unit tests (vitest)
npm run typecheck # Type checking only
npm run test:integration # Playwright integration tests