import { type BaseRoomInterface, type RoomSessionRecording, type RoomSessionPlayback, type RoomSessionStream, createRoomSessionPlaybackObject, createRoomSessionRecordingObject, createRoomSessionStreamObject, } from '.' import type { VideoMemberEntity, MemberCommandParams, VideoPosition, } from '../types' import type { ExecuteExtendedOptions, RoomMethod, BaseRPCResult, MediaAllowed, VideoMeta, } from '../utils/interfaces' export type RoomMethodParams = Record interface RoomMethodPropertyDescriptor extends PropertyDescriptor { value: (params: ParamsType) => Promise } type RoomMethodDescriptor< OutputType = unknown, ParamsType = RoomMethodParams > = RoomMethodPropertyDescriptor & // TODO: Replace string with a tighter type ThisType> /** * Transform for returning `undefined` for `execute`s that were * successully resolved. If the `execute` failed for some reason, then * the promise will be rejected and this transform will never be * executed. */ const baseCodeTransform = () => {} const createRoomMethod = < InputType, OutputType = InputType, ParamsType extends RoomMethodParams = RoomMethodParams >( method: RoomMethod, options: ExecuteExtendedOptions = {} ): RoomMethodDescriptor => ({ value: function (params = {} as ParamsType): Promise { return this.execute( { method, params: { room_session_id: this.roomSessionId, ...params, }, }, options ) }, }) /** * Type the params for each room member method that uses the provided * memberId or fallback to the instance memberId. Additional params * can be passed as `value` or `volume`. */ export interface RoomMemberMethodParams extends Record { memberId?: string } const createRoomMemberMethod = < InputType, OutputType, ParamsType extends RoomMemberMethodParams = RoomMemberMethodParams >( method: RoomMethod, options: ExecuteExtendedOptions = {} ): RoomMethodDescriptor => ({ value: function ({ memberId, ...rest } = {} as ParamsType) { return this.execute( { method, params: { room_session_id: this.roomSessionId, member_id: memberId || this.memberId, ...rest, }, }, options ) }, }) /** * Room Methods */ export const getLayouts = createRoomMethod<{ layouts: string[] }>( 'video.list_available_layouts', { transformResolve: (payload) => ({ layouts: payload.layouts }), } ) export const getMembers = createRoomMethod<{ members: VideoMemberEntity[] }>( 'video.members.get', { transformResolve: (payload) => ({ members: payload.members }), } ) export interface SetLayoutParams { name: string positions?: Record } export const setLayout = createRoomMethod( 'video.set_layout', { transformResolve: baseCodeTransform, } ) export interface SetPositionsParams { positions: Record } export const setPositions = createRoomMethod( 'video.set_position', { transformResolve: baseCodeTransform, } ) export const hideVideoMuted = createRoomMethod( 'video.hide_video_muted', { transformResolve: baseCodeTransform, } ) export const showVideoMuted = createRoomMethod( 'video.show_video_muted', { transformResolve: baseCodeTransform, } ) export const lock = createRoomMethod('video.lock', { transformResolve: baseCodeTransform, }) export const unlock = createRoomMethod('video.unlock', { transformResolve: baseCodeTransform, }) export const setHideVideoMuted: RoomMethodDescriptor = { value: function (value) { return this.execute( { method: value ? 'video.hide_video_muted' : 'video.show_video_muted', params: { room_session_id: this.roomSessionId, }, }, { transformResolve: baseCodeTransform, } ) }, } export interface GetRecordingsOutput { recordings: RoomSessionRecording[] } export const getRecordings: RoomMethodDescriptor = { value: function () { return new Promise(async (resolve, reject) => { try { const { recordings } = await this.execute({ method: 'video.recording.list', params: { room_session_id: this.roomSessionId, }, }) const recordingInstances: RoomSessionRecording[] = [] recordings.forEach((recording: any) => { let recordingInstance = this.instanceMap.get( recording.id ) if (!recordingInstance) { recordingInstance = createRoomSessionRecordingObject({ store: this.store, payload: { room_id: this.roomId, room_session_id: this.roomSessionId, recording, }, }) } else { recordingInstance.setPayload({ room_id: this.roomId, room_session_id: this.roomSessionId, recording, }) } recordingInstances.push(recordingInstance) this.instanceMap.set( recordingInstance.id, recordingInstance ) }) resolve({ recordings: recordingInstances }) } catch (error) { reject(error) } }) }, } export const startRecording: RoomMethodDescriptor = { value: function () { return new Promise(async (resolve, reject) => { try { const { recording } = await this.execute({ method: 'video.recording.start', params: { room_session_id: this.roomSessionId, }, }) const recordingInstance = createRoomSessionRecordingObject({ store: this.store, payload: { room_id: this.roomId, room_session_id: this.roomSessionId, recording, }, }) this.instanceMap.set( recordingInstance.id, recordingInstance ) resolve(recordingInstance) } catch (error) { reject(error) } }) }, } export interface GetPlaybacksOutput { playbacks: RoomSessionPlayback[] } export const getPlaybacks: RoomMethodDescriptor = { value: function () { return new Promise(async (resolve, reject) => { try { const { playbacks } = await this.execute({ method: 'video.playback.list', params: { room_session_id: this.roomSessionId, }, }) const playbackInstances: RoomSessionPlayback[] = [] playbacks.forEach((playback: any) => { let playbackInstance = this.instanceMap.get( playback.id ) if (!playbackInstance) { playbackInstance = createRoomSessionPlaybackObject({ store: this.store, payload: { room_id: this.roomId, room_session_id: this.roomSessionId, playback, }, }) } else { playbackInstance.setPayload({ room_id: this.roomId, room_session_id: this.roomSessionId, playback, }) } playbackInstances.push(playbackInstance) this.instanceMap.set( playbackInstance.id, playbackInstance ) }) resolve({ playbacks: playbackInstances }) } catch (error) { reject(error) } }) }, } export type PlayParams = { url: string volume?: number positions?: Record layout?: string seekPosition?: number /** * @deprecated Use {@link seekPosition} instead. * `currentTimecode` will be removed in v4.0.0 */ currentTimecode?: number } export const play: RoomMethodDescriptor = { value: function ({ seekPosition, currentTimecode, ...params }) { return new Promise(async (resolve, reject) => { try { const seek_position = seekPosition || currentTimecode const { playback } = await this.execute({ method: 'video.playback.start', params: { room_session_id: this.roomSessionId, seek_position, ...params, }, }) const playbackInstance = createRoomSessionPlaybackObject({ store: this.store, payload: { room_id: this.roomId, room_session_id: this.roomSessionId, playback, }, }) this.instanceMap.set( playbackInstance.id, playbackInstance ) resolve(playbackInstance) } catch (error) { reject(error) } }) }, } const createRoomMetaMethod = ( method: RoomMethod ) => { return createRoomMethod(method, { transformResolve: baseCodeTransform, transformParams: (params) => { const { room_session_id, ...meta } = params return { room_session_id, meta } }, }) } interface GetMetaOutput { meta: VideoMeta } export const getMeta = createRoomMethod('video.get_meta', { transformResolve: ({ meta }) => ({ meta }), }) export interface SetMetaParams extends Record {} export const setMeta = createRoomMetaMethod('video.set_meta') export interface UpdateMetaParams extends Record {} export const updateMeta = createRoomMetaMethod('video.update_meta') export type DeleteMetaParams = string[] export const deleteMeta: RoomMethodDescriptor = { value: function (params) { return this.execute({ method: 'video.delete_meta', params: { room_session_id: this.roomSessionId, keys: params, }, }) }, } export interface GetStreamsOutput { streams: RoomSessionStream[] } export const getStreams: RoomMethodDescriptor = { value: function () { return new Promise(async (resolve, reject) => { try { const { streams } = await this.execute({ method: 'video.stream.list', params: { room_session_id: this.roomSessionId, }, }) const streamInstances: RoomSessionStream[] = [] streams.forEach((stream: any) => { let streamInstance = this.instanceMap.get( stream.id ) if (!streamInstance) { streamInstance = createRoomSessionStreamObject({ store: this.store, payload: { room_id: this.roomId, room_session_id: this.roomSessionId, stream, }, }) } else { streamInstance.setPayload({ room_id: this.roomId, room_session_id: this.roomSessionId, stream, }) } streamInstances.push(streamInstance) this.instanceMap.set( streamInstance.id, streamInstance ) }) resolve({ streams: streamInstances }) } catch (error) { reject(error) } }) }, } export interface StartStreamParams { url: string } export const startStream: RoomMethodDescriptor = { value: function (params) { return new Promise(async (resolve, reject) => { try { const { stream } = await this.execute({ method: 'video.stream.start', params: { room_session_id: this.roomSessionId, ...params, }, }) const streamInstance = createRoomSessionStreamObject({ store: this.store, payload: { room_id: this.roomId, room_session_id: this.roomSessionId, stream, }, }) this.instanceMap.set( streamInstance.id, streamInstance ) resolve({ stream: streamInstance }) } catch (error) { reject(error) } }) }, } export const setPrioritizeHandraise: RoomMethodDescriptor = { value: function (params) { return this.execute({ method: 'video.prioritize_handraise', params: { room_session_id: this.roomSessionId, enable: params, }, }) }, } export type GetLayouts = ReturnType export type GetMembers = ReturnType export type HideVideoMuted = ReturnType export type ShowVideoMuted = ReturnType export type SetHideVideoMuted = ReturnType export type GetRecordings = ReturnType export type StartRecording = ReturnType export type GetPlaybacks = ReturnType export type Play = ReturnType export type GetMeta = ReturnType export type SetMeta = ReturnType export type UpdateMeta = ReturnType export type DeleteMeta = ReturnType export type GetStreams = ReturnType export type StartStream = ReturnType export type Lock = ReturnType export type Unlock = ReturnType export type SetPrioritizeHandraise = ReturnType< typeof setPrioritizeHandraise.value > // End Room Methods /** * Room Member Methods */ export const audioMuteMember = createRoomMemberMethod( 'video.member.audio_mute', { transformResolve: baseCodeTransform, } ) export const audioUnmuteMember = createRoomMemberMethod( 'video.member.audio_unmute', { transformResolve: baseCodeTransform, } ) export const videoMuteMember = createRoomMemberMethod( 'video.member.video_mute', { transformResolve: baseCodeTransform, } ) export const videoUnmuteMember = createRoomMemberMethod( 'video.member.video_unmute', { transformResolve: baseCodeTransform, } ) export const deafMember = createRoomMemberMethod( 'video.member.deaf', { transformResolve: baseCodeTransform, } ) export const undeafMember = createRoomMemberMethod( 'video.member.undeaf', { transformResolve: baseCodeTransform, } ) // This is used on a RoomSessionMember instance where we have // `this.roomSessionId` and `this.memberId` export const setDeaf: RoomMethodDescriptor = { value: function (value) { return this.execute( { method: value ? 'video.member.deaf' : 'video.member.undeaf', params: { room_session_id: this.roomSessionId, member_id: this.memberId, }, }, { transformResolve: baseCodeTransform, } ) }, } export const setInputVolumeMember = createRoomMemberMethod( 'video.member.set_input_volume', { transformResolve: baseCodeTransform, } ) export const setOutputVolumeMember = createRoomMemberMethod< BaseRPCResult, void >('video.member.set_output_volume', { transformResolve: baseCodeTransform, }) export const setInputSensitivityMember = createRoomMemberMethod< BaseRPCResult, void >('video.member.set_input_sensitivity', { transformResolve: baseCodeTransform, }) interface PromoteDemoteMemberParams extends Required { mediaAllowed?: MediaAllowed } export interface PromoteMemberParams extends PromoteDemoteMemberParams { permissions?: string[] meta?: VideoMeta joinAudioMuted?: boolean joinVideoMuted?: boolean } export const promote: RoomMethodDescriptor = { value: function ({ memberId, mediaAllowed, joinAudioMuted, joinVideoMuted, ...rest }) { return this.execute( { method: 'video.member.promote', params: { room_session_id: this.roomSessionId, member_id: memberId, media_allowed: mediaAllowed, join_audio_muted: joinAudioMuted, join_video_muted: joinVideoMuted, ...rest, }, }, { transformResolve: baseCodeTransform as any, } ) }, } export interface DemoteMemberParams extends PromoteDemoteMemberParams {} export const demote: RoomMethodDescriptor = { value: function ({ memberId, mediaAllowed }) { return this.execute( { method: 'video.member.demote', params: { room_session_id: this.roomSessionId, member_id: memberId, media_allowed: mediaAllowed, }, }, { transformResolve: baseCodeTransform as any, } ) }, } export interface SetMemberPositionParams extends MemberCommandParams { position: VideoPosition } export const setMemberPosition = createRoomMemberMethod( 'video.member.set_position', { transformResolve: baseCodeTransform, } ) export const removeMember: RoomMethodDescriptor< void, Required > = { value: function ({ memberId, ...rest }) { if (!memberId) { throw new TypeError('Invalid or missing "memberId" argument') } return this.execute( { method: 'video.member.remove', params: { room_session_id: this.roomSessionId, member_id: memberId, ...rest, }, }, { transformResolve: baseCodeTransform, } ) }, } export const removeAllMembers: RoomMethodDescriptor = { value: function () { return this.execute( { method: 'video.member.remove', params: { room_session_id: this.roomSessionId, member_id: 'all', }, }, { transformResolve: baseCodeTransform, } ) }, } interface GetMemberMetaOutput { meta: VideoMeta } export const getMemberMeta = createRoomMemberMethod< BaseRPCResult & VideoMeta, GetMemberMetaOutput >('video.member.get_meta', { transformResolve: ({ meta }) => ({ meta }), }) export interface SetMemberMetaParams extends MemberCommandParams { meta: VideoMeta } export const setMemberMeta = createRoomMemberMethod( 'video.member.set_meta', { transformResolve: baseCodeTransform, } ) export interface UpdateMemberMetaParams extends MemberCommandParams { meta: VideoMeta } export const updateMemberMeta = createRoomMemberMethod( 'video.member.update_meta', { transformResolve: baseCodeTransform, } ) export interface DeleteMemberMetaParams extends MemberCommandParams { keys: string[] } export const deleteMemberMeta = createRoomMemberMethod( 'video.member.delete_meta', { transformResolve: baseCodeTransform, } ) export interface SetRaisedHandRoomParams extends MemberCommandParams { raised?: boolean } export interface SetRaisedHandMemberParams { raised?: boolean } export const setRaisedHand: RoomMethodDescriptor< void, SetRaisedHandRoomParams | SetRaisedHandMemberParams > = { value: function (value) { const { raised = true, memberId = this.memberId } = (value as SetRaisedHandRoomParams) || {} if (!memberId) { throw new TypeError('Invalid or missing "memberId" argument') } return this.execute( { method: raised ? 'video.member.raisehand' : 'video.member.lowerhand', params: { room_session_id: this.roomSessionId, member_id: memberId, }, }, { transformResolve: baseCodeTransform, } ) }, } export type AudioMuteMember = ReturnType export type AudioUnmuteMember = ReturnType export type VideoMuteMember = ReturnType export type VideoUnmuteMember = ReturnType export type DeafMember = ReturnType export type UndeafMember = ReturnType export type SetDeaf = ReturnType export type SetLayout = ReturnType export type SetPositions = ReturnType export type SetInputVolumeMember = ReturnType export type SetOutputVolumeMember = ReturnType< typeof setOutputVolumeMember.value > export type SetInputSensitivityMember = ReturnType< typeof setInputSensitivityMember.value > export type SetMemberPosition = ReturnType export type RemoveMember = ReturnType export type RemoveAllMembers = ReturnType export type GetMemberMeta = ReturnType export type SetMemberMeta = ReturnType export type UpdateMemberMeta = ReturnType export type DeleteMemberMeta = ReturnType export type PromoteMember = ReturnType export type DemoteMember = ReturnType export type SetRaisedHand = ReturnType // End Room Member Methods