Microphone & audio
Device selection, mute, autoplay recovery, and reconnect behavior.
The SDK manages the microphone and the audio element for you. It auto-creates the <audio> element
that plays the persona's voice and appends it to the page — you don't manage playback yourself.
The page must be served over HTTPS (or localhost): browsers withhold microphone access
entirely on a plain http:// origin, which the SDK reports as NO_MIC_AVAILABLE (see
Requirements).
Permission is requested before the call starts
connect() asks the browser for the microphone before it redeems the session token. Denying
that first prompt costs nothing: the token is not spent, no call is created, and nothing is
billed — so a rep who blocks the mic cannot leave a charged session that never starts.
That guarantee covers the pre-connect prompt only. The same codes can also be raised after
redemption — a mic unplugged in the moment between the permission check and the call going live —
and from the code alone you cannot tell which happened. So retry the token once, and mint a fresh
one only if that retry says the token is gone. Create a new session for every attempt; a session
is finished once you call disconnect() or its call has ended:
import { AthosRoleplay, AthosRoleplayError } from '@useathos/sdk';
const MIC_CODES = ['MIC_PERMISSION_DENIED', 'NO_MIC_AVAILABLE', 'MIC_DEVICE_DISCONNECTED'];
// Called from your "start call" button, and again from a "try again" button.
async function start(token: string) {
const session = AthosRoleplay.create({ token, drillKey: 'ma-full-sale' });
session.on('ready', showCallUI);
try {
await session.connect();
return session;
} catch (err) {
// A caught value is `unknown` under strict TypeScript — narrow before reading `.code`.
if (err instanceof AthosRoleplayError) {
if (MIC_CODES.includes(err.code)) {
// Don't loop: getUserMedia won't behave differently until the rep acts.
showMicHelp({ retryWith: token });
} else if (err.code === 'TOKEN_ALREADY_USED') {
showMicHelp({ retryWith: await mintToken() }); // the failure came later
}
}
throw err;
}
}A browser leaves the permission prompt pending for as long as the user ignores it — it neither
resolves nor rejects. connect() stays unsettled and the session stays in connecting until they
answer or dismiss it. Show your own "waiting for microphone access" state on connecting; don't
wait on ready to decide the call has started.
Cancelling while the prompt is open
If the rep changes their mind, call session.disconnect(). Nothing further starts: the token is not
redeemed, no call is created, the microphone is released, and the session goes silent — no
ready, no error, not even for a prompt the rep denies afterwards.
Treat your own disconnect() call as the end of the attempt. Do not wait on the pending
connect() promise to tell you the cancel finished — it cannot settle until the browser prompt is
answered, which may be never:
cancelButton.onclick = () => {
session.disconnect();
clearCallUI(); // now, not in a .then() on connect()
};That session object is spent either way — start the next attempt with a new one.
Selecting a microphone
const mics = await session.listMicrophones(); // [{ deviceId, label }]
await session.setMicrophone(mics[0].deviceId); // switch mid-call, no droplistMicrophones() may prompt the user for microphone permission the first time. setMicrophone()
switches the active input without dropping the call — wire it to a settings dropdown.
Mute
await session.mute();
await session.unmute();Autoplay recovery
Browsers block audio autoplay until the user interacts with the page. If playback is blocked, the
SDK emits an error with code AUDIO_PLAYBACK_BLOCKED. Recover by calling resumeAudio() from
within a user-gesture handler (a click) — browsers only unblock audio then.
session.on('error', ({ code }) => {
if (code === 'AUDIO_PLAYBACK_BLOCKED') showResumeButton();
});
// in your button's click handler:
resumeButton.onclick = () => session.resumeAudio();Reconnect behavior
On a transient network drop the SDK reconnects automatically:
drop ──▶ reconnecting ──▶ reconnected (recovered)
└──▶ error: NETWORK_LOST (after 30s, session ends)- It emits
reconnecting, thenreconnectedon recovery. - If recovery does not succeed within 30 seconds, it emits an
errorwith codeNETWORK_LOSTand ends the session.
Show a non-blocking banner on reconnecting and clear it on reconnected; treat NETWORK_LOST as
a terminal error and offer to restart.
Microphone errors
| Code | Meaning |
|---|---|
MIC_PERMISSION_DENIED | The user denied microphone permission. Prompt them to allow it. |
MIC_DEVICE_DISCONNECTED | The active mic was unplugged, or is held by another app. |
NO_MIC_AVAILABLE | No microphone is available, or the page is not on HTTPS. |
Raised from connect(), the token is usually unspent — always so for a permission the rep never
granted, which is the common case. Raised mid-call, the session stays live and billable: the SDK
only emits the error, so call setMicrophone() to switch devices or disconnect() to end the
call yourself. Only NETWORK_LOST ends a session on its own.
See the full list in Error codes.