@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.
238 lines (220 loc) • 8.52 kB
text/typescript
import { Platform } from 'react-native';
import type { AudioDevice, EnterParams, GroupCallMethods, RoomListener, RoomProperties } from '../types';
import { ControllableModuleType, RouteChangeReason } from '../types';
import { Logger } from '../utils/logger';
import type NativeBinder from './NativeBinder';
import { CallsEvent, GroupCallEventType } from './NativeBinder';
import { LocalParticipant, Participant } from './Participant';
import { SendbirdError } from './SendbirdError';
export interface InternalEvents<T> {
pool: Partial<T>[];
emit: (event: keyof T, ...args: unknown[]) => void;
add: (listener: Partial<T>) => () => void;
}
export class Room implements RoomProperties, GroupCallMethods {
/** @internal **/
private static pool: Record<string, Room> = {};
/** @internal **/
public static poolRelease() {
Room.pool = {};
}
/** @internal **/
public static get(binder: NativeBinder, props: RoomProperties) {
if (!Room.pool[props.roomId]) Room.pool[props.roomId] = new Room(binder, props);
const room = Room.pool[props.roomId];
return room._updateInternal(props);
}
constructor(binder: NativeBinder, props: RoomProperties) {
this._binder = binder;
this._props = props;
this._localParticipant = null;
this._participants = [];
this._remoteParticipants = [];
}
private _binder: NativeBinder;
private _props: RoomProperties;
private _localParticipant: LocalParticipant | null;
private _participants: Participant[];
private _remoteParticipants: Participant[];
private _internalEvents = {
pool: [] as Partial<RoomListener>[],
emit: (event: keyof RoomListener, ...args: unknown[]) => {
// @ts-ignore
this._internalEvents.pool.forEach((listener) => listener[event]?.(...args));
},
add: (listener: Partial<RoomListener>) => {
this._internalEvents.pool.push(listener);
return () => {
const index = this._internalEvents.pool.indexOf(listener);
if (index > -1) delete this._internalEvents.pool[index];
};
},
};
private _updateInternal(props: RoomProperties) {
this._localParticipant = LocalParticipant.get(
props.localParticipant,
this._binder,
this._internalEvents,
this.roomId,
);
this._participants = props.participants.map((participant) => Participant.get(participant) as Participant);
this._remoteParticipants = props.remoteParticipants.map(
(remoteParticipant) => Participant.get(remoteParticipant) as Participant,
);
this._props = props;
return this;
}
public get roomId() {
return this._props.roomId;
}
public get state() {
return this._props.state;
}
public get type() {
return this._props.type;
}
public get customItems() {
return this._props.customItems;
}
public get participants() {
return this._participants;
}
public get localParticipant() {
return this._localParticipant;
}
public get remoteParticipants() {
return this._remoteParticipants;
}
public get android_availableAudioDevices() {
return this._props.android_availableAudioDevices;
}
public get android_currentAudioDevice() {
return this._props.android_currentAudioDevice;
}
public get createdAt() {
return this._props.createdAt;
}
public get createdBy() {
return this._props.createdBy;
}
/**
* Add GroupCall Room listener.
* supports multiple listeners.
*
* @since 1.0.0
*/
public addListener = (listener: Partial<RoomListener>) => {
Logger.info('[GroupCall]', 'addListener', this.roomId);
const unsubscribes: Array<() => void> = [];
const disposeInternal = this._internalEvents.add(listener);
const disposeNative = this._binder.addListener(CallsEvent.GROUP_CALL, ({ type, data, additionalData }) => {
if (data.roomId !== this.roomId) return;
Logger.info('[GroupCall]', 'receive events ', type, data.roomId, additionalData);
this._updateInternal(data);
switch (type) {
case GroupCallEventType.ON_DELETED: {
listener.onDeleted?.();
break;
}
case GroupCallEventType.ON_ERROR: {
const error = new SendbirdError(additionalData?.errorMessage, additionalData?.errorCode);
const participant = Participant.get(additionalData?.participant);
listener.onError?.(error, participant);
break;
}
case GroupCallEventType.ON_LOCAL_PARTICIPANT_DISCONNECTED: {
listener.onLocalParticipantDisconnected?.(Participant.get(additionalData?.participant) as Participant);
break;
}
case GroupCallEventType.ON_LOCAL_PARTICIPANT_RECONNECTED: {
listener.onLocalParticipantReconnected?.(Participant.get(additionalData?.participant) as Participant);
break;
}
case GroupCallEventType.ON_REMOTE_PARTICIPANT_ENTERED: {
listener.onRemoteParticipantEntered?.(Participant.get(additionalData?.participant) as Participant);
break;
}
case GroupCallEventType.ON_REMOTE_PARTICIPANT_EXITED: {
listener.onRemoteParticipantExited?.(Participant.get(additionalData?.participant) as Participant);
break;
}
case GroupCallEventType.ON_REMOTE_PARTICIPANT_STREAM_STARTED: {
listener.onRemoteParticipantStreamStarted?.(Participant.get(additionalData?.participant) as Participant);
break;
}
case GroupCallEventType.ON_AUDIO_DEVICE_CHANGED: {
if (Platform.OS === 'android') {
listener.onAudioDeviceChanged?.({
platform: 'android',
data: {
currentAudioDevice: additionalData?.currentAudioDevice ?? null,
availableAudioDevices: additionalData?.availableAudioDevices ?? [],
},
});
}
if (Platform.OS === 'ios') {
listener.onAudioDeviceChanged?.({
platform: 'ios',
data: {
reason: additionalData?.reason ?? RouteChangeReason.unknown,
currentRoute: additionalData?.currentRoute ?? { inputs: [], outputs: [] },
previousRoute: additionalData?.previousRoute ?? { inputs: [], outputs: [] },
},
});
}
break;
}
case GroupCallEventType.ON_REMOTE_VIDEO_SETTINGS_CHANGED: {
listener.onRemoteVideoSettingsChanged?.(Participant.get(additionalData?.participant) as Participant);
break;
}
case GroupCallEventType.ON_REMOTE_AUDIO_SETTINGS_CHANGED: {
listener.onRemoteAudioSettingsChanged?.(Participant.get(additionalData?.participant) as Participant);
break;
}
case GroupCallEventType.ON_CUSTOM_ITEMS_UPDATED: {
listener.onCustomItemsUpdated?.(additionalData?.updatedKeys ?? []);
break;
}
case GroupCallEventType.ON_CUSTOM_ITEMS_DELETED: {
listener.onCustomItemsDeleted?.(additionalData?.deletedKeys ?? []);
break;
}
}
});
unsubscribes.push(disposeNative, disposeInternal);
return () => unsubscribes.forEach((fn) => fn());
};
/**
* Enter the room
* Will trigger {@link RoomListener.onRemoteParticipantEntered} method of remote participants after successfully entering the room.
* If a remote participant entered the room, the local user will be notified via the same method.
*
* @since 1.0.0
*/
public enter = (options: EnterParams = { audioEnabled: true, videoEnabled: true }) => {
return this._binder.nativeModule.enter(this.roomId, options);
};
/**
* Exit from the room
* Will trigger {@link RoomListener.onRemoteParticipantExited} method of remote participants after successfully exiting the room.
* If a remote participant exited the room, the local user will be notified via the same method.
*
* @since 1.0.0
*/
public exit() {
this._binder.nativeModule.exit(this.roomId);
}
/**
* Selects audio device
* Changes current audio device asynchronously.
* Will trigger {@link RoomListener.onAudioDeviceChanged} method of the local participant after successfully changing the 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.GROUP_CALL, this.roomId, device);
};
}