Skip to content

Audio transmission: audio bridge and media streams#

Beta version

The functionality is in beta mode. Features are subject to change and may also work unstably. There may be additional charges for functionality in the future.
You can request access to the functionality via GREEN-API support

Call signaling (dialing, accepting, rejecting, ending) goes through the client methods, while audio goes separately, over WebRTC. The connection does not start audio by itself: this is done by the calls.startAudioBridge() method. Further in the text, we call this the audio bridge.

Call order#

The audio bridge belongs to a call, so the call must already exist on the server:

  1. wait for dial() (outgoing) or accept() (incoming) to complete;
  2. call calls.startAudioBridge().

If you start the bridge earlier, the server rejects the audio offer with the no active call error.

await client.dial('79991234567');
await calls.startAudioBridge();

A bridge failure does not cancel the call itself: it remains on the server, and you can call startAudioBridge() again. Audio is restored the same way after a page reload: if calls.state.state is out-call or on-call, and calls.hasAudioBridge is false, start the bridge without dialing the number again.

if (['out-call', 'on-call'].includes(calls.state.state) && !calls.hasAudioBridge) {
  await calls.startAudioBridge();
}

When the call ends, the library tears down the bridge by itself — you do not need to call stopAudioBridge() in the end-call handler. The calls.close() method stops the bridge and closes the connection when calls are no longer needed.

Media stream events#

When the bridge is started, the connection reports two streams:

  • local-stream-ready — the stream of your microphone; it arrives first and is useful for the level indicator and for muting the microphone;
  • remote-stream-ready — the stream of the other party; you need to start playing it.
const audioElement = document.createElement('audio');

calls.addEventListener('local-stream-ready', (event) => {
  console.log('Microphone is ready.', event.detail);
});

calls.addEventListener('remote-stream-ready', (event) => {
  audioElement.srcObject = event.detail;
  audioElement.play();

  console.log('The other party stream is ready and playing.');
});

calls.addEventListener('end-call', () => {
  audioElement.srcObject = null;
  audioElement.pause();
});
If the call is silent

Both events arrive when the bridge is started, that is, before the application has time to show the call screen. A component that only subscribes to them when it appears will hear nothing — the events have already passed. Store the last received stream and pass it to the audio element when the element appears. This is how it is done in the React example, see React client.

Muting the microphone#

The library has no separate method for muting the microphone, and none is needed. The track from the local-stream-ready event is the same track that is transmitted to the other party, so disabling it is exactly what makes the other party stop hearing you:

let localStream = null;

calls.addEventListener('local-stream-ready', (event) => {
  localStream = event.detail;
});

function setMicrophoneEnabled(enabled) {
  localStream.getAudioTracks().forEach((track) => { track.enabled = enabled; });
}

Two things that are easy to miss:

  • Reconnecting replaces the microphone. If the connection is lost while the bridge is up, the library tears down the bridge, waits for the connection and starts the bridge again — with a new microphone request and a new track. The call does not end, and there is no end-call event. The new track arrives enabled, so the mute has to be transferred to it, otherwise the microphone silently starts working while the button in the interface keeps showing that it is muted.
  • A stopped track is not a microphone. Tracks stopped when the bridge is torn down remain in the stream, so simply counting them is not enough. Before showing that the microphone is muted, check that the track is live: track.readyState === 'live'.