UNPKG

@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
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); }; }