UNPKG

sip.js

Version:

A SIP library for JavaScript

121 lines (120 loc) 6.56 kB
import { SessionDescriptionHandler } from "./session-description-handler.js"; /** * Start a conference. * @param conferenceSessions - The sessions to conference. * * @beta */ export function startLocalConference(conferenceSessions) { if (conferenceSessions.length < 2) { throw new Error("Start local conference requires at leaast 2 sessions."); } // Return all possible pairs of elements in an array. const pairs = (arr) => arr.map((v, i) => arr.slice(i + 1).map((w) => [v, w])).reduce((acc, curVal) => acc.concat(curVal), []); // For each pair of sessions making up the conference, join their media together. // A session desciprion handler manages the media, streams and tracks for a session. pairs(conferenceSessions.map((session) => session.sessionDescriptionHandler)).forEach(([sdh0, sdh1]) => { if (!(sdh0 instanceof WebAudioSessionDescriptionHandler && sdh1 instanceof WebAudioSessionDescriptionHandler)) { throw new Error("Session description handler not instance of SessionManagerSessionDescriptionHandler"); } sdh0.joinWith(sdh1); }); } /** * A WebAudioSessionDescriptionHandler uses the Web Audio API to enable local conferencing of audio streams. * @remarks * This handler only works for one track of audio per peer connection. While the session description handler * being extended supports both audio and video, attempting to utilize video with this handler is not defined. * * @beta */ export class WebAudioSessionDescriptionHandler extends SessionDescriptionHandler { constructor(logger, mediaStreamFactory, sessionDescriptionHandlerConfiguration) { super(logger, mediaStreamFactory, sessionDescriptionHandlerConfiguration); if (!WebAudioSessionDescriptionHandler.audioContext) { WebAudioSessionDescriptionHandler.audioContext = new AudioContext(); } } /** * Helper function to enable/disable media tracks. * @param enable - If true enable tracks. */ enableSenderTracks(enable) { // This session decription handler is not using the original outbound (local) media stream source // and has instead inserted a Web Audio proxy media stream to allow conferencing and mixing of stream. // So here, we only want to mute the original source and not the proxy as it may be mixing other // sources into the outbound stream and we do not want to enable/disable those. We only want to // enable/disable the original stream source so that it's media gets muted/unmuted going to the proxy. const stream = this.localMediaStreamReal; if (stream === undefined) { throw new Error("Stream undefined."); } stream.getAudioTracks().forEach((track) => { track.enabled = enable; }); } /** * Returns a WebRTC MediaStream proxying the provided audio media stream. * This allows additional Web Audio media stream source nodes to be connected * to the destination node assoicated with the returned stream so we can mix * aditional audio sorces into the local media stream (ie for 3-way conferencing). * @param stream - The MediaStream to proxy. */ initLocalMediaStream(stream) { if (!WebAudioSessionDescriptionHandler.audioContext) { throw new Error("SessionManagerSessionDescriptionHandler.audioContext undefined."); } this.localMediaStreamReal = stream; this.localMediaStreamSourceNode = WebAudioSessionDescriptionHandler.audioContext.createMediaStreamSource(stream); this.localMediaStreamDestinationNode = WebAudioSessionDescriptionHandler.audioContext.createMediaStreamDestination(); this.localMediaStreamSourceNode.connect(this.localMediaStreamDestinationNode); return this.localMediaStreamDestinationNode.stream; } /** * Join (conference) media streams with another party. * @param peer - The session description handler of the peer to join with. */ joinWith(peer) { if (!WebAudioSessionDescriptionHandler.audioContext) { throw new Error("SessionManagerSessionDescriptionHandler.audioContext undefined."); } // Mix our inbound (remote) stream into the peer's outbound (local) streams. const ourNewInboundStreamSource = WebAudioSessionDescriptionHandler.audioContext.createMediaStreamSource(this.remoteMediaStream); const peerOutboundStreamDestination = peer.localMediaStreamDestinationNode; if (peerOutboundStreamDestination === undefined) { throw new Error("Peer outbound (local) stream local media stream destination is undefined."); } ourNewInboundStreamSource.connect(peerOutboundStreamDestination); // Mix the peer's inbound (remote) streams into our outbound (local) stream. const peerNewInboundStreamSource = WebAudioSessionDescriptionHandler.audioContext.createMediaStreamSource(peer.remoteMediaStream); const ourOutboundStreamDestination = this.localMediaStreamDestinationNode; if (ourOutboundStreamDestination === undefined) { throw new Error("Our outbound (local) stream local media stream destination is undefined."); } peerNewInboundStreamSource.connect(ourOutboundStreamDestination); } /** * Sets the original local media stream. * @param stream - Media stream containing tracks to be utilized. * @remarks * Only the first audio and video tracks of the provided MediaStream are utilized. * Adds tracks if audio and/or video tracks are not already present, otherwise replaces tracks. */ setRealLocalMediaStream(stream) { if (!WebAudioSessionDescriptionHandler.audioContext) { throw new Error("SessionManagerSessionDescriptionHandler.audioContext undefined."); } if (!this.localMediaStreamReal) { this.initLocalMediaStream(stream); return; } if (!this.localMediaStreamDestinationNode || !this.localMediaStreamSourceNode || !this.localMediaStreamReal) { throw new Error("Local media stream undefined."); } this.localMediaStreamReal = stream; this.localMediaStreamSourceNode.disconnect(this.localMediaStreamDestinationNode); this.localMediaStreamSourceNode = WebAudioSessionDescriptionHandler.audioContext.createMediaStreamSource(stream); this.localMediaStreamSourceNode.connect(this.localMediaStreamDestinationNode); } }