TeamsCall
Duplex provider that joins a Microsoft Teams meeting via Azure Communication Services and bridges its audio to the pipeline.
Defined in: src/providers/io/teams/TeamsCall.ts:413
Duplex provider that joins a Microsoft Teams meeting via Azure Communication Services and bridges its audio to the pipeline.
Remarks
TeamsCall covers both the 'input' and 'output' pipeline roles:
- Input — the meeting’s mixed remote audio (
call.remoteAudioStreams[0], resolved to aMediaStreamviagetMediaStream()) is processed through anAudioContext(AudioWorklet preferred,ScriptProcessorNodefallback), resampled to 16 kHz mono, and emitted as linear16 AudioChunks. - Output — TTS audio is decoded into
AudioBuffers and scheduled sequentially into anAudioContext.createMediaStreamDestination()node whoseMediaStreamis sent into the call as an ACSLocalAudioStream(passed viaaudioOptions.localAudioStreamsat join time). The queue/buffering/merge/stop semantics mirror BrowserAudioOutput (i.e. the SDK’sAudioPlayer): chunks are gated behind a 200 ms minimum buffer, drained and merged to play gaplessly, and a generation counter makesstop()cancel in-flight scheduling immediately.
Duplex method semantics. Both roles share one class, so the shared lifecycle methods are defined as follows (this matches how CompositeVoice drives duplex providers):
| Method | Semantics |
|---|---|
start() | Input: begin emitting captured meeting audio |
stop() | Output: barge-in — halt playback into the meeting and clear the buffer. Capture is not deactivated (the pipeline calls output.stop() mid-conversation when the user interrupts; killing capture would deafen the agent). Use hangUp() or dispose() to leave the call and stop capture. |
pause()/resume() | Input: gate emission (used by turn-taking to mute capture during TTS playback). Playback into the meeting is unaffected — pausing it would silence the agent’s own speech. |
isActive() | Input: started and not paused |
isPlaying() | Output: audio currently being played into the meeting |
Supported TTS formats (via configure()): linear16 at any sample rate (resampled by the browser), mulaw/alaw (decoded with the SDK’s G.711 codecs), and mp3 (decoded with decodeAudioData). Containerless opus is rejected with instructions to reconfigure the TTS provider.
Data-flow diagram:
Teams meeting ──remoteAudioStreams[0]──> AudioContext ──PCM 16k──> onAudio() ──> [STT]
^
└──LocalAudioStream(dest.stream)── AudioContext <──AudioBuffer── enqueue() <── [TTS]
Example
import { CompositeVoice, TeamsCall, DeepgramSTT, AnthropicLLM, DeepgramTTS } from 'composite-voice';
const teams = new TeamsCall({
token: acsUserAccessToken,
displayName: 'Voice Agent',
meetingLink: 'https://teams.microsoft.com/l/meetup-join/19%3ameeting_...',
});
teams.onCallStateChanged((state) => {
if (state === 'InLobby') console.log('Waiting for lobby admission...');
if (state === 'Connected') console.log('Joined the meeting');
});
const voice = new CompositeVoice({
providers: [
teams, // fills both 'input' and 'output'
new DeepgramSTT({ apiKey: '...' }),
new AnthropicLLM({ model: 'claude-sonnet-4-20250514', apiKey: '...' }),
new DeepgramTTS({ apiKey: '...', encoding: 'linear16', sampleRate: 24000 }),
],
});
await voice.initialize(); // joins the meeting
await voice.startListening();
See
- TeamsCallConfig for configuration options
- AudioInputProvider and AudioOutputProvider for the contracts
- BrowserAudioOutput for the mirrored playback semantics
Implements
Constructors
Constructor
new TeamsCall(config): TeamsCall;
Defined in: src/providers/io/teams/TeamsCall.ts:555
Creates a new TeamsCall instance.
Parameters
| Parameter | Type | Description |
|---|---|---|
config | TeamsCallConfig | Provider configuration. meetingLink and one of token/tokenCredential are required (validated in initialize()). |
Returns
TeamsCall
Remarks
The constructor only captures configuration — no network activity or SDK loading occurs until initialize(), which imports the peer dependencies, creates the CallAgent, and joins the meeting.
Example
const teams = new TeamsCall({
token: '<ACS user access token>',
meetingLink: 'https://teams.microsoft.com/l/meetup-join/19%3ameeting_...',
});
Properties
| Property | Modifier | Type | Default value | Description | Defined in |
|---|---|---|---|---|---|
roles | readonly | readonly ProviderRole[] | undefined | Pipeline roles covered by this provider. Remarks TeamsCall is a duplex provider filling both the 'input' slot (meeting audio in) and the 'output' slot (TTS audio into the meeting). It requires separate STT, LLM, and TTS providers. | src/providers/io/teams/TeamsCall.ts:431 |
type | readonly | ProviderType | 'websocket' | Communication type for this provider. Remarks 'websocket' because the ACS Calling SDK maintains a persistent signaling and media connection for the lifetime of the call. | src/providers/io/teams/TeamsCall.ts:421 |
Methods
configure()
configure(metadata): void;
Defined in: src/providers/io/teams/TeamsCall.ts:952
Configure the output with audio format metadata from the TTS provider.
Parameters
| Parameter | Type | Description |
|---|---|---|
metadata | AudioMetadata | Format description for the incoming TTS audio. |
Returns
void
Remarks
Accepted encodings:
linear16at any sample rate — decoded manually into anAudioBufferat the source rate; the browser resamples on playback.mulaw/alaw— decoded with the SDK’s G.711 codecs.mp3— decoded withAudioContext.decodeAudioData.
Containerless opus cannot be decoded by the Web Audio API and is rejected — reconfigure your TTS provider, e.g. new DeepgramTTS({ encoding: 'linear16', sampleRate: 24000 }).
Throws
Error when the encoding cannot be played into the meeting.
See
Implementation of
dispose()
dispose(): Promise<void>;
Defined in: src/providers/io/teams/TeamsCall.ts:716
Dispose of the provider: hang up, release the call agent, and free all audio resources.
Returns
Promise<void>
Remarks
Safe to call multiple times. Performs hangUp() (which stops capture and playback), then disposes the CallAgent, disposes the token credential only if this provider created it, closes both AudioContexts, and clears all callback references. The instance may be re-initialized afterwards.
Implementation of
enqueue()
enqueue(chunk): void;
Defined in: src/providers/io/teams/TeamsCall.ts:984
Enqueue a TTS audio chunk for playback into the meeting.
Parameters
| Parameter | Type | Description |
|---|---|---|
chunk | AudioChunk | Audio data in the format declared via configure(). |
Returns
void
Remarks
Chunks are buffered and played sequentially into the outgoing MediaStream. Mirroring BrowserAudioOutput, playback starts once a 200 ms minimum buffer accumulates (or after a short wait), and all queued chunks are merged before scheduling to avoid gaps between chunk boundaries.
See
Implementation of
flush()
flush(): Promise<void>;
Defined in: src/providers/io/teams/TeamsCall.ts:1002
Wait for all enqueued audio to finish playing into the meeting.
Returns
Promise<void>
Remarks
Polls (every 50 ms, up to 30 s) until the queue is drained and playback returns to idle — the same completion semantics as BrowserAudioOutput. Resolves immediately when playback was halted by a barge-in stop().
See
Implementation of
getCallState()
getCallState(): TeamsCallState | null;
Defined in: src/providers/io/teams/TeamsCall.ts:1093
Get the current ACS call state.
Returns
TeamsCallState | null
The current TeamsCallState, or null when no call is active (before initialize() or after hangUp()/dispose()).
getMetadata()
getMetadata(): AudioMetadata;
Defined in: src/providers/io/teams/TeamsCall.ts:920
Get the audio format metadata for the captured meeting audio.
Returns
Metadata describing 16 kHz mono 16-bit linear PCM.
Remarks
The capture graph resamples the browser’s native rate (typically 44.1/48 kHz) down to 16 kHz mono linear16, which the pipeline uses to auto-configure the STT provider.
Implementation of
AudioInputProvider.getMetadata
hangUp()
hangUp(): Promise<void>;
Defined in: src/providers/io/teams/TeamsCall.ts:1109
Leave the Teams meeting: hang up the call and stop capture/playback.
Returns
Promise<void>
Remarks
Stops meeting-audio emission, tears down the capture graph, halts playback (firing onPlaybackEnd if audio was pending), removes call event listeners, and calls call.hangUp(). The provider stays initialized — the CallAgent, credential, and playback AudioContext are only released by dispose().
Safe to call when no call is active (no-op).
initialize()
initialize(): Promise<void>;
Defined in: src/providers/io/teams/TeamsCall.ts:597
Initialize the provider: load the ACS SDK, create the call agent, and join the Teams meeting.
Returns
Promise<void>
Remarks
Performs, in order:
- Validates configuration (
meetingLink,token/tokenCredential) and the browser environment (AudioContextmust exist). - Dynamically imports
@azure/communication-callingand@azure/communication-commonviaimportPeerDep. - Resolves the token (calling the factory if one was supplied) and constructs an
AzureCommunicationTokenCredentialunless a credential was provided. - Creates the
CallAgentwith the configureddisplayName. - Builds the outgoing audio graph (
AudioContext+createMediaStreamDestination()) and wraps its stream in an ACSLocalAudioStream. - Joins the meeting:
callAgent.join({ meetingLink }, { audioOptions: { localAudioStreams: [las], muted: false } }). - Subscribes to
stateChangedandremoteAudioStreamsUpdated, and starts the documented poll fallback for remote audio.
Joining does not mean audio flows yet — the call may sit in the Teams lobby ('InLobby') until admitted. If already initialized, this is a no-op.
Throws
ProviderInitializationError when configuration is invalid, a peer dependency is missing, the environment lacks Web Audio support, or createCallAgent fails.
Throws
ProviderConnectionError when joining the meeting fails.
Implementation of
AudioOutputProvider.initialize
isActive()
isActive(): boolean;
Defined in: src/providers/io/teams/TeamsCall.ts:891
Check whether the input side is actively emitting audio.
Returns
boolean
true when started and not paused.
Implementation of
isPlaying()
isPlaying(): boolean;
Defined in: src/providers/io/teams/TeamsCall.ts:1024
Check whether audio is currently being played into the meeting.
Returns
boolean
true when the playback state is 'playing'.
See
Implementation of
isReady()
isReady(): boolean;
Defined in: src/providers/io/teams/TeamsCall.ts:773
Check whether the provider has been initialized.
Returns
boolean
true when initialize has completed and dispose has not yet been called.
Implementation of
onAudio()
onAudio(callback): void;
Defined in: src/providers/io/teams/TeamsCall.ts:906
Register a callback to receive captured meeting audio chunks.
Parameters
| Parameter | Type | Description |
|---|---|---|
callback | (chunk) => void | Function invoked with each AudioChunk while the provider is active. |
Returns
void
Remarks
Only one callback can be registered at a time; subsequent calls replace the previous callback. Chunks are linear16 PCM at 16 kHz mono (see getMetadata()).
Implementation of
onCallStateChanged()
onCallStateChanged(callback): void;
Defined in: src/providers/io/teams/TeamsCall.ts:1083
Register a callback for ACS call state changes.
Parameters
| Parameter | Type | Description |
|---|---|---|
callback | (state) => void | Function invoked with the new call state. |
Returns
void
Remarks
Invoked with the new TeamsCallState whenever the SDK fires stateChanged. Watch for 'InLobby' (waiting for a participant to admit the agent — remote audio does not flow yet) and 'Disconnected' (the call ended; capture and playback are torn down automatically). Only one callback can be registered at a time.
Example
teams.onCallStateChanged((state) => {
if (state === 'InLobby') showLobbyBanner();
});
onPlaybackEnd()
onPlaybackEnd(callback): void;
Defined in: src/providers/io/teams/TeamsCall.ts:1047
Register a callback invoked when all queued audio has been played.
Parameters
| Parameter | Type | Description |
|---|---|---|
callback | () => void | Function called when the queue fully drains or playback is stopped. |
Returns
void
See
AudioOutputProvider.onPlaybackEnd
Implementation of
AudioOutputProvider.onPlaybackEnd
onPlaybackError()
onPlaybackError(callback): void;
Defined in: src/providers/io/teams/TeamsCall.ts:1058
Register a callback invoked when a playback error occurs.
Parameters
| Parameter | Type | Description |
|---|---|---|
callback | (error) => void | Function called with the error. |
Returns
void
See
AudioOutputProvider.onPlaybackError
Implementation of
AudioOutputProvider.onPlaybackError
onPlaybackStart()
onPlaybackStart(callback): void;
Defined in: src/providers/io/teams/TeamsCall.ts:1035
Register a callback invoked when playback into the meeting begins.
Parameters
| Parameter | Type | Description |
|---|---|---|
callback | () => void | Function called when playback starts after being idle. |
Returns
void
See
AudioOutputProvider.onPlaybackStart
Implementation of
AudioOutputProvider.onPlaybackStart
pause()
pause(): void;
Defined in: src/providers/io/teams/TeamsCall.ts:867
Temporarily pause meeting-audio emission without leaving the call.
Returns
void
Remarks
Gates the input side only — the turn-taking system uses this to mute capture while the agent speaks. Playback into the meeting is intentionally unaffected: suspending the shared audio path here would silence the agent’s own TTS audio mid-sentence.
Implementation of
resume()
resume(): void;
Defined in: src/providers/io/teams/TeamsCall.ts:879
Resume meeting-audio emission after a pause.
Returns
void
See
Implementation of
start()
start(): void;
Defined in: src/providers/io/teams/TeamsCall.ts:787
Start emitting captured meeting audio to the registered callback.
Returns
void
Remarks
Chunks only flow once the remote audio stream has been acquired, which requires the call to be 'Connected' (i.e. admitted past any lobby). Audio captured before start() is silently dropped.
Implementation of
stop()
stop(): void;
Defined in: src/providers/io/teams/TeamsCall.ts:814
Stop the provider — barge-in while the agent is speaking, capture halt otherwise.
Returns
void
Remarks
As a duplex provider, the same stop() is invoked by CompositeVoice for two different reasons, so the provider dispatches on playback state:
- Barge-in (
output.stop()) — while audio is queued or playing into the meeting, playback is stopped in the BrowserAudioOutput manner: the in-flightAudioBufferSourceNodeis stopped, the queue is cleared, andonPlaybackEndfires. Meeting-audio capture deliberately survives — barge-in depends on the very speech the agent is hearing, so cutting capture here would permanently deafen the pipeline. - Stop listening (
input.stop()) — with no playback in flight, meeting-audio emission is halted instead (restart with start()).
hangUp() and dispose() stop both sides unconditionally.
Implementation of
stopCapture()
stopCapture(): void;
Defined in: src/providers/io/teams/TeamsCall.ts:852
Stop emitting meeting audio, leaving playback alone.
Returns
void
Remarks
The pipeline calls this when it means “stop listening”. Restart with start().
Implementation of
AudioInputProvider.stopCapture
stopPlayback()
stopPlayback(): void;
Defined in: src/providers/io/teams/TeamsCall.ts:840
Stop playback, leaving meeting-audio capture running.
Returns
void
Remarks
The pipeline calls this for barge-in, so the provider never has to infer which side was meant. Capture stays open deliberately: barge-in fires because someone started talking, and that speech has to reach STT.
Safe when nothing is playing — barge-in is also raised while the agent is still thinking, and there is simply no audio to drop yet.