@signalwire/js
Version:
205 lines (186 loc) • 6.3 kB
text/typescript
import {
BaseClient,
ClientEvents,
ClientContract,
actions,
Chat as ChatNamespace,
PubSub as PubSubNamespace,
} from '@signalwire/core'
import type { CustomSaga } from '@signalwire/core'
import { ConnectionOptions } from '@signalwire/webrtc'
import { makeAudioElementSaga } from './features/mediaElements/mediaElementsSagas'
import { VideoManager, createVideoManagerObject } from './cantina'
import type { Client as ChatClient } from './chat/Client'
import type { Client as PubSubClient } from './pubSub/Client'
import type { RoomSession } from './video/RoomSession'
import { buildVideoElement } from './buildVideoElement'
import {
createVideoRoomSessionObject,
VideoRoomSessionConnection,
} from './video/VideoRoomSession'
export interface Client<RoomSessionType = RoomSession>
extends ClientContract<Client<RoomSessionType>, ClientEvents> {
rooms: ClientAPI['rooms']
chat: ClientAPI['chat']
pubSub: ClientAPI['pubSub']
}
export interface MakeRoomOptions extends ConnectionOptions {
/** HTML element in which to display the video stream */
rootElement?: HTMLElement
/** Whether to apply the local-overlay on top of your video. Default: `true`. */
applyLocalVideoOverlay?: boolean
/** Whether to apply an overlay on top of each member. Default: `true`. */
applyMemberOverlay?: boolean
/** Whether to mirror the local video overlay. Default: `false`. */
mirrorLocalVideoOverlay?: boolean
/** Whether to stop the camera when the member is muted. Default: `true`. */
stopCameraWhileMuted?: boolean
/** Whether to stop the microphone when the member is muted. Default: `true`. */
stopMicrophoneWhileMuted?: boolean
/** Local media stream to override the local video and audio stream tracks */
localStream?: MediaStream
}
export class ClientAPI extends BaseClient<ClientEvents> {
private _videoManager: VideoManager
private _chat: ChatClient
private _pubSub: PubSubClient
get rooms() {
return {
makeRoomObject: (makeRoomOptions: MakeRoomOptions) => {
const {
rootElement,
applyLocalVideoOverlay = true,
applyMemberOverlay = true,
mirrorLocalVideoOverlay = true,
stopCameraWhileMuted = true,
stopMicrophoneWhileMuted = true,
...options
} = makeRoomOptions
// TODO: This might not be needed here. We can initiate these sagas in the BaseRoomSession constructor.
const customSagas: Array<CustomSaga<VideoRoomSessionConnection>> = []
/**
* By default the SDK will attach the audio to
* an Audio element (regardless of "rootElement")
*/
customSagas.push(
makeAudioElementSaga({
speakerId: options.speakerId,
})
)
const room = createVideoRoomSessionObject({
...options,
store: this.store,
customSagas,
})
/**
* If the user provides a `rootElement` we'll
* automatically handle the Video element for them
*/
if (rootElement) {
try {
buildVideoElement({
applyLocalVideoOverlay,
applyMemberOverlay,
mirrorLocalVideoOverlay,
room,
rootElement,
})
} catch (error) {
this.logger.error('Unable to build the video element automatically')
}
}
/**
* If the user joins with `join_video_muted: true` or
* `join_audio_muted: true` we'll stop the streams
* right away.
*/
room.on('room.subscribed', (params) => {
const member = params.room_session.members?.find(
(m) => m.id === room.memberId
)
if (member?.audio_muted) {
try {
room.stopOutboundAudio()
} catch (error) {
this.logger.error('Error handling audio_muted', error)
}
}
if (member?.video_muted) {
try {
room.stopOutboundVideo()
} catch (error) {
this.logger.error('Error handling video_muted', error)
}
}
})
/**
* Stop and Restore outbound audio on audio_muted event
*/
if (stopMicrophoneWhileMuted) {
room.on('member.updated.audio_muted', ({ member }) => {
try {
if (member.id === room.memberId && 'audio_muted' in member) {
member.audio_muted
? room.stopOutboundAudio()
: room.restoreOutboundAudio()
}
} catch (error) {
this.logger.error('Error handling audio_muted', error)
}
})
}
/**
* Stop and Restore outbound video on video_muted event
*/
if (stopCameraWhileMuted) {
room.on('member.updated.video_muted', ({ member }) => {
try {
if (member.id === room.memberId && 'video_muted' in member) {
member.video_muted
? room.stopOutboundVideo()
: room.restoreOutboundVideo()
}
} catch (error) {
this.logger.error('Error handling video_muted', error)
}
})
}
return room
},
}
}
get chat() {
if (!this._chat) {
this._chat = ChatNamespace.createBaseChatObject<ChatClient>({
store: this.store,
})
}
return this._chat
}
get pubSub() {
if (!this._pubSub) {
this._pubSub = PubSubNamespace.createBasePubSubObject<PubSubClient>({
store: this.store,
})
}
return this._pubSub
}
/** @internal */
get videoManager() {
if (!this._videoManager) {
this._videoManager = createVideoManagerObject(this.options)
}
return this._videoManager
}
/**
* Reauthenticate with the SignalWire network using a new token
* For now it returns void since with an invalid token the server
* will close the connection right away so we can hook on the session
* events in case. Need to improve it.
*
* @internal
*/
reauthenticate(token: string) {
this.store.dispatch(actions.reauthAction({ token }))
}
}