@sendbird/calls-react-native
Version:
Sendbird Calls SDK for React Native: Empower React Native apps with seamless audio, video, and group calling. Build interactive communication easily.
405 lines (375 loc) • 12.5 kB
text/typescript
import { Platform } from 'react-native';
import type {
AudioDevice,
CallOptions,
DirectCallListener,
DirectCallMethods,
DirectCallProperties,
VideoDevice,
} from '../types';
import { ControllableModuleType, RouteChangeReason } from '../types';
import { Logger } from '../utils/logger';
import type NativeBinder from './NativeBinder';
import { CallsEvent, DirectCallEventType } from './NativeBinder';
export class DirectCall implements DirectCallProperties, DirectCallMethods {
/** @internal **/
private static pool: Record<string, DirectCall> = {};
/** @internal **/
public static poolRelease() {
DirectCall.pool = {};
}
/** @internal **/
public static get(binder: NativeBinder, props: DirectCallProperties) {
if (!DirectCall.pool[props.callId]) DirectCall.pool[props.callId] = new DirectCall(binder, props);
const directCall = DirectCall.pool[props.callId];
return directCall._updateInternal(props);
}
constructor(binder: NativeBinder, props: DirectCallProperties) {
this._binder = binder;
this._props = props;
}
private _binder: NativeBinder;
private _props: DirectCallProperties;
private _internalEvents = {
pool: [] as Partial<DirectCallListener>[],
emit: (event: keyof DirectCallListener, ...args: unknown[]) => {
// @ts-ignore
this._internalEvents.pool.forEach((listener) => listener[event]?.(...args));
},
add: (listener: Partial<DirectCallListener>) => {
this._internalEvents.pool.push(listener);
return () => {
const index = this._internalEvents.pool.indexOf(listener);
if (index > -1) delete this._internalEvents.pool[index];
};
},
};
private _updateInternal(props: DirectCallProperties) {
this._props = props;
return this;
}
public get ios_callUUID() {
return this._props.ios_callUUID;
}
public get android_availableAudioDevices() {
return this._props.android_availableAudioDevices;
}
public get android_currentAudioDevice() {
return this._props.android_currentAudioDevice;
}
public get availableVideoDevices() {
return this._props.availableVideoDevices;
}
public get callId() {
return this._props.callId;
}
public get callLog() {
return this._props.callLog;
}
public get callee() {
return this._props.callee;
}
public get caller() {
return this._props.caller;
}
public get currentVideoDevice() {
return this._props.currentVideoDevice;
}
public get customItems() {
return this._props.customItems;
}
public get startedAt() {
return this._props.startedAt;
}
public get duration() {
if (this.startedAt > 0) {
return Math.max(0, Date.now() - this.startedAt);
} else if (this._props.duration > 0) {
return this._props.duration;
} else {
return 0;
}
}
public get endedBy() {
return this._props.endedBy;
}
public get isEnded() {
return this._props.isEnded;
}
public get isLocalAudioEnabled() {
return this._props.isLocalAudioEnabled;
}
public get isLocalScreenShareEnabled() {
return this._props.isLocalScreenShareEnabled;
}
public get isLocalVideoEnabled() {
return this._props.isLocalVideoEnabled;
}
public get isOnHold() {
return this._props.isOnHold;
}
public get isOngoing() {
return this._props.isOngoing;
}
public get isRemoteAudioEnabled() {
return this._props.isRemoteAudioEnabled;
}
public get isRemoteVideoEnabled() {
return this._props.isRemoteVideoEnabled;
}
public get isVideoCall() {
return this._props.isVideoCall;
}
public get localRecordingStatus() {
return this._props.localRecordingStatus;
}
public get localUser() {
return this._props.localUser;
}
public get myRole() {
return this._props.myRole;
}
public get remoteRecordingStatus() {
return this._props.remoteRecordingStatus;
}
public get remoteUser() {
return this._props.remoteUser;
}
public get endResult() {
return this._props.endResult;
}
/**
* Add DirectCall listener.
* supports multiple listeners.
*
* @since 1.0.0
*/
public addListener = (listener: Partial<DirectCallListener>) => {
Logger.info('[DirectCall]', 'addListener', this.callId);
const unsubscribes: Array<() => void> = [];
const disposeInternal = this._internalEvents.add(listener);
const disposeNative = this._binder.addListener(CallsEvent.DIRECT_CALL, ({ type, data, additionalData }) => {
if (data.callId !== this.callId) return;
this._updateInternal(data);
switch (type) {
case DirectCallEventType.ON_ESTABLISHED: {
listener.onEstablished?.(this);
break;
}
case DirectCallEventType.ON_CONNECTED: {
listener.onConnected?.(this);
break;
}
case DirectCallEventType.ON_RECONNECTING: {
listener.onReconnecting?.(this);
break;
}
case DirectCallEventType.ON_RECONNECTED: {
listener.onReconnected?.(this);
break;
}
case DirectCallEventType.ON_ENDED: {
listener.onEnded?.(this);
break;
}
case DirectCallEventType.ON_REMOTE_AUDIO_SETTINGS_CHANGED: {
listener.onRemoteAudioSettingsChanged?.(this);
break;
}
case DirectCallEventType.ON_REMOTE_VIDEO_SETTINGS_CHANGED: {
listener.onRemoteVideoSettingsChanged?.(this);
break;
}
case DirectCallEventType.ON_LOCAL_VIDEO_SETTINGS_CHANGED: {
listener.onLocalVideoSettingsChanged?.(this);
break;
}
case DirectCallEventType.ON_REMOTE_RECORDING_STATUS_CHANGED: {
listener.onRemoteRecordingStatusChanged?.(this);
break;
}
case DirectCallEventType.ON_CUSTOM_ITEMS_UPDATED: {
listener.onCustomItemsUpdated?.(this, additionalData?.updatedKeys ?? []);
break;
}
case DirectCallEventType.ON_CUSTOM_ITEMS_DELETED: {
listener.onCustomItemsDeleted?.(this, additionalData?.deletedKeys ?? []);
break;
}
case DirectCallEventType.ON_USER_HOLD_STATUS_CHANGED: {
listener.onUserHoldStatusChanged?.(
this,
additionalData?.isLocalUser ?? false,
additionalData?.isUserOnHold ?? false,
);
break;
}
case DirectCallEventType.ON_AUDIO_DEVICE_CHANGED: {
if (Platform.OS === 'android') {
listener.onAudioDeviceChanged?.(this, {
platform: 'android',
data: {
currentAudioDevice: additionalData?.currentAudioDevice ?? null,
availableAudioDevices: additionalData?.availableAudioDevices ?? [],
},
});
}
if (Platform.OS === 'ios') {
listener.onAudioDeviceChanged?.(this, {
platform: 'ios',
data: {
reason: additionalData?.reason ?? RouteChangeReason.unknown,
currentRoute: additionalData?.currentRoute ?? { inputs: [], outputs: [] },
previousRoute: additionalData?.previousRoute ?? { inputs: [], outputs: [] },
},
});
}
break;
}
}
});
unsubscribes.push(disposeNative, disposeInternal);
return () => unsubscribes.forEach((fn) => fn());
};
/**
* Accepts call.
*
* @since 1.0.0
*/
public accept = async (
options: CallOptions = { audioEnabled: true, frontCamera: true, videoEnabled: true },
holdActiveCall = false,
) => {
await this._binder.nativeModule.accept(this.callId, options, holdActiveCall);
};
/**
* Ends the call.
* {@link DirectCallListener.onEnded} will be called after successful ending.
* This listener will also be called when the remote user ends the call.
*
* @since 1.0.0
*/
public end = async () => {
await this._binder.nativeModule.end(this.callId);
};
/**
* Selects video device.
* Changes current video device asynchronously.
*
* @since 1.0.0
*/
public selectVideoDevice = async (device: VideoDevice) => {
await this._binder.nativeModule.selectVideoDevice(ControllableModuleType.DIRECT_CALL, this.callId, device);
};
/**
* Selects audio device.
*
* @platform Android
* @since 1.0.0
*/
public android_selectAudioDevice = async (device: AudioDevice) => {
if (Platform.OS !== 'android') return;
await this._binder.nativeModule.selectAudioDevice(ControllableModuleType.DIRECT_CALL, this.callId, device);
};
/**
* Connects the device camera and Sendbird Calls SDK to stream video.
*
* @platform Android
* @since 1.1.3
* */
public android_resumeVideoCapturer = () => {
if (Platform.OS !== 'android') return;
this._binder.nativeModule.resumeVideoCapturer(ControllableModuleType.DIRECT_CALL, this.callId);
};
/**
* Connects the device audio and Sendbird Calls SDK to stream audio.
*
* @platform Android
* @since 1.1.5
* */
public android_resumeAudioTrack = () => {
if (Platform.OS !== 'android') return;
this._binder.nativeModule.resumeAudioTrack(ControllableModuleType.DIRECT_CALL, this.callId);
};
/**
* Mutes the audio of local user.
* Will trigger {@link DirectCallListener.onRemoteAudioSettingsChanged} method of the remote user.
* If the remote user changes their audio settings, local user will be notified via same delegate method.
*
* @since 1.0.0
*/
public muteMicrophone = () => {
this._binder.nativeModule.muteMicrophone(ControllableModuleType.DIRECT_CALL, this.callId);
// NOTE: native doesn't have onLocalAudioSettingsChanged event
this._props.isLocalAudioEnabled = false;
this._internalEvents.emit('onPropertyUpdatedManually', this);
};
/**
* Unmutes the audio of local user.
* Will trigger {@link DirectCallListener.onRemoteAudioSettingsChanged} method of the remote user.
* If the remote user changes their audio settings, local user will be notified via same delegate method.
*
* @since 1.0.0
*/
public unmuteMicrophone = () => {
this._binder.nativeModule.unmuteMicrophone(ControllableModuleType.DIRECT_CALL, this.callId);
// NOTE: native doesn't have onLocalAudioSettingsChanged event
this._props.isLocalAudioEnabled = true;
this._internalEvents.emit('onPropertyUpdatedManually', this);
};
/**
* Starts local video.
* If the callee changes video settings,
* the caller is notified via the {@link DirectCallListener.onRemoteVideoSettingsChanged} listener.
*
* @since 1.0.0
*/
public startVideo = () => {
this._binder.nativeModule.startVideo(ControllableModuleType.DIRECT_CALL, this.callId);
this._props.isLocalVideoEnabled = true;
// NOTE: ios native doesn't have onLocalAudioSettingsChanged event
Platform.OS === 'ios' && this._internalEvents.emit('onLocalVideoSettingsChanged', this);
};
/**
* Stops local video.
* If the callee changes video settings,
* the caller is notified via the {@link DirectCallListener.onRemoteVideoSettingsChanged} listener.
*
* @since 1.0.0
*/
public stopVideo = () => {
this._binder.nativeModule.stopVideo(ControllableModuleType.DIRECT_CALL, this.callId);
this._props.isLocalVideoEnabled = false;
// NOTE: ios native doesn't have onLocalAudioSettingsChanged event
Platform.OS === 'ios' && this._internalEvents.emit('onLocalVideoSettingsChanged', this);
};
/**
* Toggles the selection between the front and the back camera.
*
* on Android, In case of more than two cameras, the next camera will be selected.
* If the last camera is already selected, the first one will be selected again.
*
* @since 1.0.0
*/
public switchCamera = async () => {
await this._binder.nativeModule.switchCamera(ControllableModuleType.DIRECT_CALL, this.callId);
};
/**
* Update local video view.
* @see DirectCallVideoView.videoViewId
*
* @since 1.0.0
*/
public updateLocalVideoView = (videoViewId: number) => {
this._binder.nativeModule.updateLocalVideoView(this.callId, videoViewId);
};
/**
* Update remote video view.
* @see DirectCallVideoView.videoViewId
*
* @since 1.0.0
*/
public updateRemoteVideoView = (videoViewId: number) => {
this._binder.nativeModule.updateRemoteVideoView(this.callId, videoViewId);
};
}