Skip to content

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 a MediaStream via getMediaStream()) is processed through an AudioContext (AudioWorklet preferred, ScriptProcessorNode fallback), resampled to 16 kHz mono, and emitted as linear16 AudioChunks.
  • Output — TTS audio is decoded into AudioBuffers and scheduled sequentially into an AudioContext.createMediaStreamDestination() node whose MediaStream is sent into the call as an ACS LocalAudioStream (passed via audioOptions.localAudioStreams at join time). The queue/buffering/merge/stop semantics mirror BrowserAudioOutput (i.e. the SDK’s AudioPlayer): chunks are gated behind a 200 ms minimum buffer, drained and merged to play gaplessly, and a generation counter makes stop() 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):

MethodSemantics
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

Implements

Constructors

Constructor

new TeamsCall(config): TeamsCall;

Defined in: src/providers/io/teams/TeamsCall.ts:555

Creates a new TeamsCall instance.

Parameters

ParameterTypeDescription
configTeamsCallConfigProvider 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

PropertyModifierTypeDefault valueDescriptionDefined in
rolesreadonlyreadonly ProviderRole[]undefinedPipeline 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
typereadonlyProviderType'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

ParameterTypeDescription
metadataAudioMetadataFormat description for the incoming TTS audio.

Returns

void

Remarks

Accepted encodings:

  • linear16 at any sample rate — decoded manually into an AudioBuffer at the source rate; the browser resamples on playback.
  • mulaw / alaw — decoded with the SDK’s G.711 codecs.
  • mp3 — decoded with AudioContext.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

AudioOutputProvider.configure

Implementation of

AudioOutputProvider.configure


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

AudioOutputProvider.dispose


enqueue()

enqueue(chunk): void;

Defined in: src/providers/io/teams/TeamsCall.ts:984

Enqueue a TTS audio chunk for playback into the meeting.

Parameters

ParameterTypeDescription
chunkAudioChunkAudio 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

AudioOutputProvider.enqueue

Implementation of

AudioOutputProvider.enqueue


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

AudioOutputProvider.flush

Implementation of

AudioOutputProvider.flush


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

AudioMetadata

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:

  1. Validates configuration (meetingLink, token/tokenCredential) and the browser environment (AudioContext must exist).
  2. Dynamically imports @azure/communication-calling and @azure/communication-common via importPeerDep.
  3. Resolves the token (calling the factory if one was supplied) and constructs an AzureCommunicationTokenCredential unless a credential was provided.
  4. Creates the CallAgent with the configured displayName.
  5. Builds the outgoing audio graph (AudioContext + createMediaStreamDestination()) and wraps its stream in an ACS LocalAudioStream.
  6. Joins the meeting: callAgent.join({ meetingLink }, { audioOptions: { localAudioStreams: [las], muted: false } }).
  7. Subscribes to stateChanged and remoteAudioStreamsUpdated, 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

AudioInputProvider.isActive


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

AudioOutputProvider.isPlaying

Implementation of

AudioOutputProvider.isPlaying


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

AudioOutputProvider.isReady


onAudio()

onAudio(callback): void;

Defined in: src/providers/io/teams/TeamsCall.ts:906

Register a callback to receive captured meeting audio chunks.

Parameters

ParameterTypeDescription
callback(chunk) => voidFunction 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

AudioInputProvider.onAudio


onCallStateChanged()

onCallStateChanged(callback): void;

Defined in: src/providers/io/teams/TeamsCall.ts:1083

Register a callback for ACS call state changes.

Parameters

ParameterTypeDescription
callback(state) => voidFunction 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

ParameterTypeDescription
callback() => voidFunction 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

ParameterTypeDescription
callback(error) => voidFunction 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

ParameterTypeDescription
callback() => voidFunction 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

AudioOutputProvider.pause


resume()

resume(): void;

Defined in: src/providers/io/teams/TeamsCall.ts:879

Resume meeting-audio emission after a pause.

Returns

void

See

pause

Implementation of

AudioOutputProvider.resume


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

AudioInputProvider.start


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-flight AudioBufferSourceNode is stopped, the queue is cleared, and onPlaybackEnd fires. 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

AudioOutputProvider.stop


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.

Implementation of

AudioOutputProvider.stopPlayback

© 2026 CompositeVoice. All rights reserved.

Font size
Contrast
Motion
Transparency