From 866b3f39a14e86697884bfca0bf3133cb5abfb42 Mon Sep 17 00:00:00 2001 From: Ammar Ansari Date: Tue, 5 Sep 2023 17:23:58 +0200 Subject: [PATCH 01/14] Hand raise API for the video SDK --- packages/core/src/rooms/methods.ts | 52 +++++++++++++++++++ packages/core/src/types/videoMember.ts | 14 ++++- packages/core/src/types/videoRoomSession.ts | 38 ++++++++++++++ packages/core/src/utils/interfaces.ts | 3 ++ packages/js/src/BaseRoomSession.ts | 2 + .../realtime-api/src/video/RoomSession.ts | 2 + .../src/video/RoomSessionMember.ts | 1 + 7 files changed, 110 insertions(+), 2 deletions(-) diff --git a/packages/core/src/rooms/methods.ts b/packages/core/src/rooms/methods.ts index c65e0a82b..7e603d95d 100644 --- a/packages/core/src/rooms/methods.ts +++ b/packages/core/src/rooms/methods.ts @@ -471,6 +471,18 @@ export const startStream: RoomMethodDescriptor = { }, } +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 @@ -492,6 +504,9 @@ export type StartStream = ReturnType export type Lock = ReturnType export type Unlock = ReturnType +export type SetPrioritizeHandraise = ReturnType< + typeof setPrioritizeHandraise.value +> // End Room Methods /** @@ -715,6 +730,42 @@ export const deleteMemberMeta = createRoomMemberMethod( } ) +export interface SetRaisedHandRoomParams { + memberId: string + 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 @@ -740,4 +791,5 @@ export type UpdateMemberMeta = ReturnType export type DeleteMemberMeta = ReturnType export type PromoteMember = ReturnType export type DemoteMember = ReturnType +export type SetRaisedHand = ReturnType // End Room Member Methods diff --git a/packages/core/src/types/videoMember.ts b/packages/core/src/types/videoMember.ts index a33e6ac3c..fe57375a2 100644 --- a/packages/core/src/types/videoMember.ts +++ b/packages/core/src/types/videoMember.ts @@ -76,7 +76,7 @@ export type MemberListUpdated = 'memberList.updated' /** * See {@link MEMBER_UPDATED_EVENTS} for the full list of events. */ -export type MemberUpdatedEventNames = typeof MEMBER_UPDATED_EVENTS[number] +export type MemberUpdatedEventNames = (typeof MEMBER_UPDATED_EVENTS)[number] export type MemberTalkingStarted = 'member.talking.started' export type MemberTalkingEnded = 'member.talking.ended' /** @@ -109,7 +109,7 @@ export type VideoMemberEventNames = | MemberListUpdated export type InternalMemberUpdatedEventNames = - typeof INTERNAL_MEMBER_UPDATED_EVENTS[number] + (typeof INTERNAL_MEMBER_UPDATED_EVENTS)[number] /** * List of internal events @@ -277,6 +277,16 @@ export interface VideoMemberContract extends VideoMemberUpdatableProps { * ``` */ remove(): Rooms.RemoveMember + + /** + * Removes this member from the room. + * + * @example + * ```typescript + * await member.remove() + * ``` + */ + setRaisedHand(params?: Rooms.SetRaisedHandMemberParams): Rooms.SetRaisedHand } /** diff --git a/packages/core/src/types/videoRoomSession.ts b/packages/core/src/types/videoRoomSession.ts index e1bc8b054..ec8d7216b 100644 --- a/packages/core/src/types/videoRoomSession.ts +++ b/packages/core/src/types/videoRoomSession.ts @@ -732,6 +732,44 @@ export interface VideoRoomSessionContract { startStream(params: Rooms.StartStreamParams): Promise lock(): Rooms.Lock unlock(): Rooms.Unlock + /** + * Set the priority of members hand raise + * @param param a boolean flag to enable/disable the hand raise prioritization + * + * @permissions + * - `video.prioritize_handraise`: to set the teh hand raise priority + * + * You need to specify the permissions when [creating the Video Room + * Token](https://developer.signalwire.com/apis/reference/create_room_token) + * on the server side. + * + * @example + * ```typescript + * await room.setPrioritizeHandraise(true) + * ``` + */ + setPrioritizeHandraise(params: boolean): Rooms.SetPrioritizeHandraise + /** + * Raise or lower the hand of a specific participant in the room. + * @param params + * @param params.memberId id of the member to remove + * @param params.raised boolean flag to raise or lower the hand + * + * @permissions + * - `video.member.raisehand`: to raise a hand + * - `video.member.lowerhand`: to lower a hand + * + * You need to specify the permissions when [creating the Video Room + * Token](https://developer.signalwire.com/apis/reference/create_room_token) + * on the server side. + * + * @example + * ```typescript + * const id = 'de550c0c-3fac-4efd-b06f-b5b8614b8966' // you can get this from getMembers() + * await room.setHandRaised({ memberId: id, raised: false }) + * ``` + */ + setRaisedHand(params: Rooms.SetRaisedHandRoomParams): Rooms.SetRaisedHand } /** diff --git a/packages/core/src/utils/interfaces.ts b/packages/core/src/utils/interfaces.ts index db160ccfc..2ad0d4600 100644 --- a/packages/core/src/utils/interfaces.ts +++ b/packages/core/src/utils/interfaces.ts @@ -338,6 +338,9 @@ export type RoomMethod = | 'video.stream.stop' | 'video.lock' | 'video.unlock' + | 'video.member.raisehand' + | 'video.member.lowerhand' + | 'video.prioritize_handraise' export interface WebSocketClient { addEventListener: WebSocket['addEventListener'] diff --git a/packages/js/src/BaseRoomSession.ts b/packages/js/src/BaseRoomSession.ts index e72fea3a5..e02b2ad6a 100644 --- a/packages/js/src/BaseRoomSession.ts +++ b/packages/js/src/BaseRoomSession.ts @@ -492,6 +492,8 @@ export const RoomSessionAPI = extendComponent< startStream: Rooms.startStream, lock: Rooms.lock, unlock: Rooms.unlock, + setRaisedHand: Rooms.setRaisedHand, + setPrioritizeHandraise: Rooms.setPrioritizeHandraise, }) type RoomSessionObjectEventsHandlerMapping = RoomSessionObjectEvents & diff --git a/packages/realtime-api/src/video/RoomSession.ts b/packages/realtime-api/src/video/RoomSession.ts index e7cbfcf0f..682a0c948 100644 --- a/packages/realtime-api/src/video/RoomSession.ts +++ b/packages/realtime-api/src/video/RoomSession.ts @@ -278,6 +278,8 @@ export const RoomSessionAPI = extendComponent< startStream: Rooms.startStream, lock: Rooms.lock, unlock: Rooms.unlock, + setRaisedHand: Rooms.setRaisedHand, + setPrioritizeHandraise: Rooms.setPrioritizeHandraise, }) export const createRoomSessionObject = ( diff --git a/packages/realtime-api/src/video/RoomSessionMember.ts b/packages/realtime-api/src/video/RoomSessionMember.ts index ecfe463c7..4b985e8ba 100644 --- a/packages/realtime-api/src/video/RoomSessionMember.ts +++ b/packages/realtime-api/src/video/RoomSessionMember.ts @@ -158,6 +158,7 @@ const RoomSessionMemberAPI = extendComponent< setSpeakerVolume: Rooms.setOutputVolumeMember, setOutputVolume: Rooms.setOutputVolumeMember, setInputSensitivity: Rooms.setInputSensitivityMember, + setRaisedHand: Rooms.setRaisedHand, }) export const createRoomSessionMemberObject = ( From ca35fac81ef2631af3fd2b913e89e4ff476aaeda Mon Sep 17 00:00:00 2001 From: Ammar Ansari Date: Fri, 8 Sep 2023 17:04:57 +0200 Subject: [PATCH 02/14] include getters in the room session --- packages/core/src/rooms/methods.ts | 16 ++++------------ packages/core/src/types/videoMember.ts | 4 +++- packages/core/src/types/videoRoomSession.ts | 5 ++++- packages/realtime-api/src/video/RoomSession.ts | 4 ++++ .../realtime-api/src/video/RoomSessionMember.ts | 4 ++++ 5 files changed, 19 insertions(+), 14 deletions(-) diff --git a/packages/core/src/rooms/methods.ts b/packages/core/src/rooms/methods.ts index 7e603d95d..599fdb609 100644 --- a/packages/core/src/rooms/methods.ts +++ b/packages/core/src/rooms/methods.ts @@ -730,22 +730,14 @@ export const deleteMemberMeta = createRoomMemberMethod( } ) -export interface SetRaisedHandRoomParams { - memberId: string - raised?: boolean -} - -export interface SetRaisedHandMemberParams { +export interface SetRaisedHandParams { + memberId?: string raised?: boolean } -export const setRaisedHand: RoomMethodDescriptor< - void, - SetRaisedHandRoomParams | SetRaisedHandMemberParams -> = { +export const setRaisedHand: RoomMethodDescriptor = { value: function (value) { - const { raised = true, memberId = this.memberId } = - (value as SetRaisedHandRoomParams) || {} + const { raised = true, memberId = this.memberId } = value || {} if (!memberId) { throw new TypeError('Invalid or missing "memberId" argument') diff --git a/packages/core/src/types/videoMember.ts b/packages/core/src/types/videoMember.ts index fe57375a2..6eacf1e68 100644 --- a/packages/core/src/types/videoMember.ts +++ b/packages/core/src/types/videoMember.ts @@ -148,6 +148,8 @@ export interface VideoMemberContract extends VideoMemberUpdatableProps { currentPosition?: VideoPosition /** Metadata associated to this member. */ meta?: Record + /** Indicate if the member hand is raised or not */ + handraised: Boolean /** * Mutes the outbound audio for this member (e.g., the one coming from a @@ -286,7 +288,7 @@ export interface VideoMemberContract extends VideoMemberUpdatableProps { * await member.remove() * ``` */ - setRaisedHand(params?: Rooms.SetRaisedHandMemberParams): Rooms.SetRaisedHand + setRaisedHand(params?: Rooms.SetRaisedHandParams): Rooms.SetRaisedHand } /** diff --git a/packages/core/src/types/videoRoomSession.ts b/packages/core/src/types/videoRoomSession.ts index ec8d7216b..2d1d324ce 100644 --- a/packages/core/src/types/videoRoomSession.ts +++ b/packages/core/src/types/videoRoomSession.ts @@ -98,6 +98,8 @@ export interface VideoRoomSessionContract { streaming: boolean /** List of active streams in the room session. */ streams?: Rooms.RoomSessionStream[] + /** Prioritize the hand raise for the layout */ + prioritizeHandraise: Boolean /** * Puts the microphone on mute. The other participants will not hear audio @@ -769,7 +771,7 @@ export interface VideoRoomSessionContract { * await room.setHandRaised({ memberId: id, raised: false }) * ``` */ - setRaisedHand(params: Rooms.SetRaisedHandRoomParams): Rooms.SetRaisedHand + setRaisedHand(params?: Rooms.SetRaisedHandParams): Rooms.SetRaisedHand } /** @@ -815,6 +817,7 @@ type InternalVideoRoomEntity = { recordings?: any[] playbacks?: any[] streams?: any[] + prioritize_handraise: boolean } /** diff --git a/packages/realtime-api/src/video/RoomSession.ts b/packages/realtime-api/src/video/RoomSession.ts index 682a0c948..dbdb6b1ad 100644 --- a/packages/realtime-api/src/video/RoomSession.ts +++ b/packages/realtime-api/src/video/RoomSession.ts @@ -113,6 +113,10 @@ export class RoomSessionConsumer extends BaseConsumer { return this._payload.room_session.event_channel } + get prioritizeHandraise() { + return this._payload.room_session.prioritize_handraise + } + /** @internal */ protected override getSubscriptions() { const eventNamesWithPrefix = this.eventNames().map( diff --git a/packages/realtime-api/src/video/RoomSessionMember.ts b/packages/realtime-api/src/video/RoomSessionMember.ts index 4b985e8ba..1ab00777e 100644 --- a/packages/realtime-api/src/video/RoomSessionMember.ts +++ b/packages/realtime-api/src/video/RoomSessionMember.ts @@ -119,6 +119,10 @@ class RoomSessionMemberComponent extends BaseComponent<{}> { return this._payload.member.talking } + get handraised() { + return this._payload.member.handraised + } + /** @internal */ protected setPayload(payload: RoomSessionMemberEventParams) { // Reshape the payload since the `video.member.talking` event does not return all the parameters of a member From 377fc251e6f6c4a66e77f19087e9950a0b186b22 Mon Sep 17 00:00:00 2001 From: Ammar Ansari Date: Fri, 8 Sep 2023 17:41:33 +0200 Subject: [PATCH 03/14] include unit tests --- packages/core/src/rooms/methods.test.ts | 70 +++++++ packages/core/src/rooms/methods.ts | 16 +- packages/core/src/types/videoMember.ts | 2 +- packages/core/src/types/videoRoomSession.ts | 2 +- .../src/video/RoomSessionMember.test.ts | 182 ++++++++++-------- 5 files changed, 188 insertions(+), 84 deletions(-) diff --git a/packages/core/src/rooms/methods.test.ts b/packages/core/src/rooms/methods.test.ts index ca6335986..911b3409b 100644 --- a/packages/core/src/rooms/methods.test.ts +++ b/packages/core/src/rooms/methods.test.ts @@ -240,4 +240,74 @@ describe('Room Custom Methods', () => { ) }) }) + + describe('setRaisedHand', () => { + it.each([ + { + input: { + memberId: 'c22d7124-5a01-49fe-8da0-46bec8e75f12', + }, + method: 'video.member.raisehand', + }, + { + input: { + memberId: 'c22d7124-5a01-49fe-8da0-46bec8e75f12', + raised: true, + }, + method: 'video.member.raisehand', + }, + { + input: { + memberId: 'c22d7124-5a01-49fe-8da0-46bec8e75f12', + raised: false, + }, + method: 'video.member.lowerhand', + }, + { + input: {}, + method: 'video.member.raisehand', + }, + ])('should execute with proper params', async ({ input, method }) => { + ;(instance.execute as jest.Mock).mockResolvedValueOnce({}) + instance.roomSessionId = 'mocked' + instance.memberId = 'c22d7124-5a01-49fe-8da0-46bec8e75f12' + + await instance.setRaisedHand(input) + + expect(instance.execute).toHaveBeenCalledTimes(1) + expect(instance.execute).toHaveBeenCalledWith( + { + method, + params: { + room_session_id: 'mocked', + member_id: 'c22d7124-5a01-49fe-8da0-46bec8e75f12', + }, + }, + { + transformResolve: expect.anything(), + } + ) + }) + }) + + describe('setPrioritizeHandraise', () => { + it.each([true, false])( + 'should execute with proper params', + async (enable) => { + ;(instance.execute as jest.Mock).mockResolvedValueOnce({}) + instance.roomSessionId = 'mocked' + + await instance.setPrioritizeHandraise(enable) + + expect(instance.execute).toHaveBeenCalledTimes(1) + expect(instance.execute).toHaveBeenCalledWith({ + method: 'video.prioritize_handraise', + params: { + room_session_id: 'mocked', + enable, + }, + }) + } + ) + }) }) diff --git a/packages/core/src/rooms/methods.ts b/packages/core/src/rooms/methods.ts index 599fdb609..7e603d95d 100644 --- a/packages/core/src/rooms/methods.ts +++ b/packages/core/src/rooms/methods.ts @@ -730,14 +730,22 @@ export const deleteMemberMeta = createRoomMemberMethod( } ) -export interface SetRaisedHandParams { - memberId?: string +export interface SetRaisedHandRoomParams { + memberId: string + raised?: boolean +} + +export interface SetRaisedHandMemberParams { raised?: boolean } -export const setRaisedHand: RoomMethodDescriptor = { +export const setRaisedHand: RoomMethodDescriptor< + void, + SetRaisedHandRoomParams | SetRaisedHandMemberParams +> = { value: function (value) { - const { raised = true, memberId = this.memberId } = value || {} + const { raised = true, memberId = this.memberId } = + (value as SetRaisedHandRoomParams) || {} if (!memberId) { throw new TypeError('Invalid or missing "memberId" argument') diff --git a/packages/core/src/types/videoMember.ts b/packages/core/src/types/videoMember.ts index 6eacf1e68..e08abf5b9 100644 --- a/packages/core/src/types/videoMember.ts +++ b/packages/core/src/types/videoMember.ts @@ -288,7 +288,7 @@ export interface VideoMemberContract extends VideoMemberUpdatableProps { * await member.remove() * ``` */ - setRaisedHand(params?: Rooms.SetRaisedHandParams): Rooms.SetRaisedHand + setRaisedHand(params?: Rooms.SetRaisedHandMemberParams): Rooms.SetRaisedHand } /** diff --git a/packages/core/src/types/videoRoomSession.ts b/packages/core/src/types/videoRoomSession.ts index 2d1d324ce..5e124cd8f 100644 --- a/packages/core/src/types/videoRoomSession.ts +++ b/packages/core/src/types/videoRoomSession.ts @@ -771,7 +771,7 @@ export interface VideoRoomSessionContract { * await room.setHandRaised({ memberId: id, raised: false }) * ``` */ - setRaisedHand(params?: Rooms.SetRaisedHandParams): Rooms.SetRaisedHand + setRaisedHand(params?: Rooms.SetRaisedHandRoomParams): Rooms.SetRaisedHand } /** diff --git a/packages/realtime-api/src/video/RoomSessionMember.test.ts b/packages/realtime-api/src/video/RoomSessionMember.test.ts index 008b6c520..10c52b779 100644 --- a/packages/realtime-api/src/video/RoomSessionMember.test.ts +++ b/packages/realtime-api/src/video/RoomSessionMember.test.ts @@ -26,7 +26,6 @@ describe('Member Object', () => { return new Promise(async (resolve) => { const roomSession = createRoomSessionObject({ store, - // @ts-expect-error emitter, payload: { // @ts-expect-error @@ -83,82 +82,109 @@ describe('Member Object', () => { member_id: memberId, }, }) - // await member.audioUnmute() - // expectExecute({ - // method: 'video.member.audio_unmute', - // params: { - // room_session_id: roomSessionId, - // member_id: memberId, - // }, - // }) - // await member.videoMute() - // expectExecute({ - // method: 'video.member.video_mute', - // params: { - // room_session_id: roomSessionId, - // member_id: memberId, - // }, - // }) - // await member.videoUnmute() - // expectExecute({ - // method: 'video.member.video_unmute', - // params: { - // room_session_id: roomSessionId, - // member_id: memberId, - // }, - // }) - // await member.setDeaf(true) - // expectExecute({ - // method: 'video.member.deaf', - // params: { - // room_session_id: roomSessionId, - // member_id: memberId, - // }, - // }) - // await member.setDeaf(false) - // expectExecute({ - // method: 'video.member.undeaf', - // params: { - // room_session_id: roomSessionId, - // member_id: memberId, - // }, - // }) - // await member.setInputVolume({ volume: 10 }) - // expectExecute({ - // method: 'video.member.set_input_volume', - // params: { - // room_session_id: roomSessionId, - // member_id: memberId, - // volume: 10, - // }, - // }) - // await member.setOutputVolume({ volume: 10 }) - // expectExecute({ - // method: 'video.member.set_output_volume', - // params: { - // room_session_id: roomSessionId, - // member_id: memberId, - // volume: 10, - // }, - // }) - // await member.setInputSensitivity({ value: 10 }) - // expectExecute({ - // method: 'video.member.set_input_sensitivity', - // params: { - // room_session_id: roomSessionId, - // member_id: memberId, - // value: 10, - // }, - // }) - - // await member.remove() - // // @ts-expect-error - // expect(member.execute).toHaveBeenLastCalledWith({ - // method: 'video.member.remove', - // params: { - // room_session_id: member.roomSessionId, - // member_id: member.id, - // }, - // }) + await member.audioUnmute() + expectExecute({ + method: 'video.member.audio_unmute', + params: { + room_session_id: roomSessionId, + member_id: memberId, + }, + }) + await member.videoMute() + expectExecute({ + method: 'video.member.video_mute', + params: { + room_session_id: roomSessionId, + member_id: memberId, + }, + }) + await member.videoUnmute() + expectExecute({ + method: 'video.member.video_unmute', + params: { + room_session_id: roomSessionId, + member_id: memberId, + }, + }) + await member.setDeaf(true) + expectExecute({ + method: 'video.member.deaf', + params: { + room_session_id: roomSessionId, + member_id: memberId, + }, + }) + await member.setDeaf(false) + expectExecute({ + method: 'video.member.undeaf', + params: { + room_session_id: roomSessionId, + member_id: memberId, + }, + }) + await member.setInputVolume({ volume: 10 }) + expectExecute({ + method: 'video.member.set_input_volume', + params: { + room_session_id: roomSessionId, + member_id: memberId, + volume: 10, + }, + }) + await member.setOutputVolume({ volume: 10 }) + expectExecute({ + method: 'video.member.set_output_volume', + params: { + room_session_id: roomSessionId, + member_id: memberId, + volume: 10, + }, + }) + await member.setInputSensitivity({ value: 10 }) + expectExecute({ + method: 'video.member.set_input_sensitivity', + params: { + room_session_id: roomSessionId, + member_id: memberId, + value: 10, + }, + }) + await member.remove() + // @ts-expect-error + expect(member.execute).toHaveBeenLastCalledWith({ + method: 'video.member.remove', + params: { + room_session_id: member.roomSessionId, + member_id: member.id, + }, + }) + await member.setRaisedHand() + // @ts-expect-error + expect(member.execute).toHaveBeenLastCalledWith( + { + method: 'video.member.raisehand', + params: { + room_session_id: member.roomSessionId, + member_id: member.id, + }, + }, + { + transformResolve: expect.anything(), + } + ) + await member.setRaisedHand({ raised: false }) + // @ts-expect-error + expect(member.execute).toHaveBeenLastCalledWith( + { + method: 'video.member.lowerhand', + params: { + room_session_id: member.roomSessionId, + member_id: member.id, + }, + }, + { + transformResolve: expect.anything(), + } + ) }) }) From aa1a697c39b7a7dd321adc9cd33f6bb590e59fad Mon Sep 17 00:00:00 2001 From: Ammar Ansari Date: Fri, 8 Sep 2023 18:57:58 +0200 Subject: [PATCH 04/14] playground ui update for the hand raise api --- internal/playground-js/src/heroku/index.html | 19 +++++++++++++++++++ internal/playground-js/src/heroku/index.js | 10 ++++++++++ 2 files changed, 29 insertions(+) diff --git a/internal/playground-js/src/heroku/index.html b/internal/playground-js/src/heroku/index.html index 005405ad7..2b70cdb96 100644 --- a/internal/playground-js/src/heroku/index.html +++ b/internal/playground-js/src/heroku/index.html @@ -207,6 +207,25 @@
Controls
+
+ + +
+
Controls