UNPKG

react-native-voximplant

Version:

VoxImplant Mobile SDK for embedding voice and video communication into React Native apps.

518 lines (468 loc) 17.3 kB
/* * Copyright (c) 2011-2022, Zingaya, Inc. All rights reserved. */ 'use strict'; import { NativeModules, NativeEventEmitter, } from 'react-native'; import CallEvents from './CallEvents'; import CallManager from './CallManager'; import Endpoint from './Endpoint'; import VideoStream from './VideoStream'; import {VideoCodec} from "../Enums"; import QualitySubscriber from './QualitySubscriber'; const CallModule = NativeModules.RNVICallModule; const EventEmitter = new NativeEventEmitter(CallModule); /** * @memberOf Voximplant * @class Call * @classdesc Class that may be used for call operations like answer, reject, hang up abd mid-call operations like hold, start/stop video and others. */ export default class Call { /** * @member {string} callId - The call id * @memberOf Voximplant.Call */ callId; /** * @member {VideoStream[]} localVideoStreams - Local video streams * @memberOf Voximplant.Call */ localVideoStreams; /** * @memberOf {string} callKitUUID - The CallKit UUID that may be used to match an incoming call with a push notification received before * Always nil for outgoing calls on Call instance creation. * For outgoing calls it is recommended to set CXStartCallAction.callUUID value to this property on * handling CXStartCallAction * @memberOf Voximplant.Call */ callKitUUID; /** * Instance of a class that may be used to subscribe to call quality issues events. */ qualityIssues; /** * @ignore */ constructor(callId) { this.callId = callId; this.callKitUUID = null; this.listeners = {}; this.localVideoStreams = []; this._addEventListeners(); CallModule.internalSetup(this.callId); CallManager.getInstance().addCall(this); this.qualityIssues = new QualitySubscriber(this.callId); } /** * Register a handler for the specified call event. * One event can have more than one handler. * Use the {@link Voximplant.Call#off} method to delete a handler. * @param {Voximplant.CallEvents} event * @param {function} handler - Handler function. A single parameter is passed - object with event information * @memberOf Voximplant.Call */ on(event, handler) { if (!handler || !(handler instanceof Function)) { console.warn(`Call: on: handler is not a Function`); return; } if (Object.values(CallEvents).indexOf(event) === -1) { console.warn(`Call: on: CallEvents does not contain ${event} event`); return; } if (!this.listeners[event]) { this.listeners[event] = new Set(); } this.listeners[event].add(handler); } /** * Remove a handler for the specified call event. * @param {Voximplant.CallEvents} event * @param {function} handler - Handler function. If not specified, all handlers for the event will be removed. * @memberOf Voximplant.Call */ off(event, handler) { if (!this.listeners[event]) { return; } if (Object.values(CallEvents).indexOf(event) === -1) { console.warn(`Call: off: CallEvents does not contain ${event} event`); return; } if (handler && handler instanceof Function) { this.listeners[event].delete(handler); } else { this.listeners[event] = new Set(); } } /** * Answer the incoming call. * @param {Voximplant.CallSettings} [callSettings] - Optional set of call settings. * @memberOf Voximplant.Call */ answer(callSettings) { if (!callSettings) { callSettings = {}; } if (callSettings.preferredVideoCodec === undefined) { callSettings.preferredVideoCodec = VideoCodec.AUTO; } if (callSettings.video === undefined) { callSettings.video = {}; callSettings.video.sendVideo = false; callSettings.video.receiveVideo = true; } if (callSettings.customData === undefined) { callSettings.customData = null; } if (callSettings.extraHeaders === undefined) { callSettings.extraHeaders = {}; } if (callSettings.enableSimulcast === undefined) { callSettings.enableSimulcast = false; } CallModule.answer(this.callId, callSettings); } /** * Reject incoming call on all devices, where this user logged in. * @param {object} [headers] - Optional custom parameters (SIP headers) that should be sent after rejecting incoming call. Parameter names must start with "X-" to be processed by application * @memberOf Voximplant.Call */ decline(headers) { CallModule.decline(this.callId, headers); } /** * Reject incoming call on the part of Web SDK. * If a call is initiated from the PSTN, the network will receive "reject" command. * In case of a call from another Web SDK client, it will receive the CallEvents.Failed event with the 603 code. * @param {object} [headers] - Optional custom parameters (SIP headers) that should be sent after rejecting incoming call. Parameter names must start with "X-" to be processed by application * @memberOf Voximplant.Call */ reject(headers) { CallModule.reject(this.callId, headers); } /** * Enables or disables audio transfer from microphone into the call. * @param {boolean} enable - True if audio should be sent, false otherwise * @memberOf Voximplant.Call */ sendAudio(enable) { CallModule.sendAudio(this.callId, enable); } /** * Send tone (DTMF). It triggers the {@link https://voximplant.com/docs/references/appengine/CallEvents.html#CallEvents_ToneReceived CallEvents.ToneReceived} event in the Voximplant cloud. * @param {string} key - Send tone according to pressed key: 0-9 , * , # * @memberOf Voximplant.Call */ sendTone(key) { CallModule.sendDTMF(this.callId, key); } /** * Start/stop sending video from a call. * In case of a remote participant uses a React Native SDK client, it will receive either * the {@link EndpointEvents#RemoteVideoStreamAdded} or {@link EndpointEvents#RemoteVideoStreamRemoved} event accordingly. * @param {boolean} enable - True if video should be sent, false otherwise * @returns {Promise<void|EventHandlers.CallOperationFailed>} * @memberOf Voximplant.Call */ sendVideo(enable) { return CallModule.sendVideo(this.callId, enable); } /** * Hold or unhold the call * @param {boolean} enable - True if the call should be put on hold, false for unhold * @returns {Promise<void|EventHandlers.CallOperationFailed>} * @memberOf Voximplant.Call */ hold(enable) { return CallModule.hold(this.callId, enable); } /** * Start receive video if video receive was disabled before. Stop receiving video during the call is not supported. * @returns {Promise<void|EventHandlers.CallOperationFailed>} * @memberOf Voximplant.Call */ receiveVideo() { return CallModule.receiveVideo(this.callId); } /** * Hangup the call * @param {object} [headers] - Optional custom parameters (SIP headers) that should be sent after disconnecting/cancelling call. Parameter names must start with "X-" to be processed by application * @memberOf Voximplant.Call */ hangup(headers) { CallModule.hangup(this.callId, headers); } /** * Send text message. It is a special case of the {@link Voximplant.Call#sendInfo} method as it allows to send messages only of "text/plain" type. * You can get this message via the Voxengine {@link https://voximplant.com/docs/references/websdk/voximplant/callevents#messagereceived CallEvents.MessageReceived} event in our cloud. * You can get this message in Web SDK on other side via the {@link CallEvents#MessageReceived} event; see the similar * events for the {@link https://voximplant.com/docs/references/websdk Web}, * {@link https://voximplant.com/docs/references/iossdk iOS} and {@link https://voximplant.com/docs/references/androidsdk Android} SDKs. * @param {string} message - Message text * @memberOf Voximplant.Call */ sendMessage(message) { CallModule.sendMessage(this.callId, message); } /** * Send Info (SIP INFO) message inside the call. * You can get this message via the Voxengine {@link https://voximplant.com/docs/references/websdk/voximplant/callevents#inforeceived CallEvents.InfoReceived} * event in the Voximplant cloud. * You can get this message in Web SDK on other side via the {@link CallEvents.InfoReceived} event; see the similar * events for the {@link https://voximplant.com/docs/references/websdk Web}, * {@link https://voximplant.com/docs/references/iossdk iOS} and {@link https://voximplant.com/docs/references/androidsdk Android} SDKs. * @param {string} mimeType - MIME type of the message, for example "text/plain", "multipart/mixed" etc. * @param {string} body - Message content * @param {object} [extraHeaders] - Optional custom parameters (SIP headers) that should be sent after rejecting incoming call. Parameter names must start with "X-" to be processed by application * @memberOf Voximplant.Call */ sendInfo(mimeType, body, extraHeaders) { CallModule.sendInfo(this.callId, mimeType, body, extraHeaders); } /** * Get all current Endpoints in the call. * @returns {Voximplant.Endpoint[]} * @memberOf Voximplant.Call */ getEndpoints() { let endpoints = CallManager.getInstance().getCallEndpoints(this.callId); if (endpoints) { return [...endpoints]; } return []; } /** * Get the call duration in seconds * @returns {Promise<number|CallError>} * @memberOf Voximplant.Call */ getDuration() { return CallModule.getCallDuration(this.callId); } /** * Returns current status for all quality issues. * @returns {Promise<Object.<QualityEvents, QualityIssueLevel>|CallError>} * @memberOf Voximplant.Call */ currentQualityIssues() { return CallModule.currentQualityIssues(this.callId); } /** * @private */ _emit(event, ...args) { const handlers = this.listeners[event]; if (handlers) { for (const handler of handlers) { console.log(`Call: emit event ${event}`); handler(...args); } } else { console.log(`Call: emit: no handlers for event: ${event}`); } } //Call events /** * @private */ _VICallConnectedCallback = (event) => { if (event.callId === this.callId) { this._replaceCallIdWithCallInEvent(event); this._emit(CallEvents.Connected, event); } }; /** * @private */ _VICallDisconnectedCallback = (event) => { if (event.callId === this.callId) { this._removeEventListeners(); CallManager.getInstance().removeCall(this); this._replaceCallIdWithCallInEvent(event); this.qualityIssues._removeEventListeners(); this._emit(CallEvents.Disconnected, event); } }; /** * @private */ _VICallEndpointAddedCallback = (event) => { if (event.callId === this.callId) { this._replaceCallIdWithCallInEvent(event); let endpoint = CallManager.getInstance().getEndpointById(event.endpointId); if (!endpoint) { endpoint = new Endpoint(event.endpointId, event.displayName, event.sipUri, event.endpointName); CallManager.getInstance().addEndpoint(this.callId, endpoint); } event.endpoint = endpoint; this._emit(CallEvents.EndpointAdded, event); } }; /** * @private */ _VICallFailedCallback = (event) => { if (event.callId === this.callId) { this._removeEventListeners(); this._replaceCallIdWithCallInEvent(event); CallManager.getInstance().removeCall(this); this.qualityIssues._removeEventListeners(); this._emit(CallEvents.Failed, event); } }; /** * @private */ _VICallICETimeoutCallback = (event) => { if (event.callId === this.callId) { this._replaceCallIdWithCallInEvent(event); this._emit(CallEvents.ICETimeout, event); } }; /** * @private */ _VICallICECompletedCallback = (event) => { if (event.callId === this.callId) { this._replaceCallIdWithCallInEvent(event); this._emit(CallEvents.ICECompleted, event); } }; /** * @private */ _VICallInfoReceivedCallback = (event) => { if (event.callId === this.callId) { this._replaceCallIdWithCallInEvent(event); this._emit(CallEvents.InfoReceived, event); } }; /** * @private */ _VICallMessageReceivedCallback = (event) => { if (event.callId === this.callId) { this._replaceCallIdWithCallInEvent(event); this._emit(CallEvents.MessageReceived, event); } }; /** * @private */ _VICallProgressToneStartCallback = (event) => { if (event.callId === this.callId) { this._replaceCallIdWithCallInEvent(event); this._emit(CallEvents.ProgressToneStart, event); } }; /** * @private */ _VICallProgressToneStopCallback = (event) => { if (event.callId === this.callId) { this._replaceCallIdWithCallInEvent(event); this._emit(CallEvents.ProgressToneStop, event); } }; /** * @private */ _VICallLocalVideoStreamAddedCallback = (event) => { if (event.callId === this.callId) { this._replaceCallIdWithCallInEvent(event); let videoStream = new VideoStream(event.videoStreamId, true, event.videoStreamType); CallManager.getInstance().addVideoStream(this.callId, videoStream); this.localVideoStreams.push(videoStream); delete event.videoStreamId; delete event.videoStreamType; event.videoStream = videoStream; this._emit(CallEvents.LocalVideoStreamAdded, event); } }; /** * @private */ _VICallLocalVideoStreamRemovedCallback = (event) => { if (event.callId === this.callId) { this._replaceCallIdWithCallInEvent(event); let videoStream = CallManager.getInstance().getVideoStreamById(event.videoStreamId); CallManager.getInstance().removeVideoStream(this.callId, videoStream); delete event.videoStreamId; event.videoStream = videoStream; let videoStreamPos; this.localVideoStreams.forEach(function (item, index) { if (item.id === videoStream.id) { videoStreamPos = index; } }); this.localVideoStreams.splice(videoStreamPos, 1); this._emit(CallEvents.LocalVideoStreamRemoved, event); } }; /** * @private */ _VICallReconnectingCallback = (event) => { if (event.callId === this.callId) { this._replaceCallIdWithCallInEvent(event); this._emit(CallEvents.CallReconnecting, event); } } /** * @private */ _VICallReconnectedCallback = (event) => { if (event.callId === this.callId) { this._replaceCallIdWithCallInEvent(event); this._emit(CallEvents.CallReconnected, event); } } /** * @private */ _replaceCallIdWithCallInEvent(event) { delete event.callId; event.call = this; } /** * @private */ _events = [ 'VICallConnected', 'VICallDisconnected', 'VICallEndpointAdded', 'VICallFailed', 'VICallICECompleted', 'VICallICETimeout', 'VICallInfoReceived', 'VICallMessageReceived', 'VICallProgressToneStart', 'VICallProgressToneStop', 'VICallLocalVideoStreamAdded', 'VICallLocalVideoStreamRemoved', 'VICallReconnecting', 'VICallReconnected' ]; /** * @private */ _addEventListeners() { this._events.forEach((item) => { this[`_${item}Subscriber`] = EventEmitter.addListener(item, this[`_${item}Callback`]); }); } /** * @private */ _removeEventListeners() { this._events.forEach((item) => { if(this[`_${item}Subscriber`]) { this[`_${item}Subscriber`].remove(); delete this[`_${item}Subscriber`]; } }); } }