vroom-web-sdk-beta
Version:
VROOM SDK (beta) by True Virtual World
276 lines (218 loc) • 6.92 kB
text/typescript
import { get } from 'lodash'
import VroomPlugin from './vroom.plugin'
import VroomSession from '../session/vroom.session'
import { keepAlivePeriod, VROOM_COMMAND_STATUS } from '../constants'
import { JoinResponseType } from '../types/message'
import VroomParticipant from '../types/publisher'
class PublisherPlugin extends VroomPlugin {
keepAliveTimeoutID: any
userId: number | undefined
privateId: number | undefined
roomId: number | undefined
onPublisherJoined: Function = () => {}
onPublisherSJoined: Function = () => {}
onPublisherMuteAudio: Function = () => {}
onPublisherMuteVideo: Function = () => {}
onLeave: Function = () => {}
isCreateOffer: boolean = false
stream!: MediaStream
constructor(session: VroomSession, id: number) {
super(session, id)
this.setKeepAliveTimeout()
}
public async joinRoom(
stream: MediaStream,
roomId: number,
display: string,
muteAudio = false,
muteVideo = false,
): Promise<JoinResponseType> {
try {
this.roomId = roomId
this.stream = stream
let joinResponse: any = await this.sendMessage({
request: 'join',
room: roomId,
display,
ptype: 'publisher',
mute_audio: muteAudio,
mute_video: muteVideo,
})
if (joinResponse.janus === 'event' && get(joinResponse, 'plugindata.data', false)) {
const data = joinResponse.plugindata.data
if (data.videoroom === 'joined') {
this.userId = data.id
this.privateId = data.private_id
// add localStream to peerConnection
stream.getTracks().forEach((track: MediaStreamTrack) => {
this.pc.addTrack(track)
})
await this.createOffer(100000, !muteAudio, !muteVideo)
}
const mapPublisher = data.publishers.map((d: any) => new VroomParticipant(d))
return {
status: data.videoroom,
success: true,
publisher: mapPublisher || [],
attendees: data.attendees || []
} as JoinResponseType
}
return {
status: 'errors',
success: false,
publisher: [],
attendees: [],
} as JoinResponseType
} catch (e) {
return {
status: e,
success: false,
publisher: [],
attendees: [],
} as JoinResponseType
}
}
async onMessage(message: any) {
const { janus } = message
switch (janus) {
case VROOM_COMMAND_STATUS.TRICKLE: {
if (this.isRemoteDescriptionSet && message.candidate.sdpMid) {
if (!message?.candidate?.completed) {
await this.pc.addIceCandidate(
new RTCIceCandidate({
candidate: message.candidate.candidate,
sdpMid: message.candidate.sdpMid,
sdpMLineIndex: message.candidate.sdpMLineIndex,
}),
)
}
}
if (!message?.candidate.completed) {
this.cachedCandidates.push(
new RTCIceCandidate({
candidate: message.candidate.candidate,
sdpMid: message.candidate.sdpMid,
sdpMLineIndex: message.candidate.sdpMLineIndex,
}),
)
}
return
}
case VROOM_COMMAND_STATUS.EVENT: {
if (get(message, 'plugindata.data.videoroom') === VROOM_COMMAND_STATUS.EVENT) {
const data = get(message, 'plugindata.data')
if (get(data, 'publishers', false)) {
const publisher = get(data, 'publishers')
publisher
.map((data: any) => new VroomParticipant(data))
.forEach((p: VroomParticipant) => this.onPublisherJoined(p))
} else if (get(data, 'leaving', false)) {
const leavingPublisherID = get(data, 'leaving')
this.onLeave({ id: leavingPublisherID })
}
}
if (get(message, 'plugindata.data.videoroom') === VROOM_COMMAND_STATUS.S_JOIN) {
this.onPublisherSJoined(get(message, 'plugindata.data'))
}
return
}
case VROOM_COMMAND_STATUS.MUTE_AUDIO: {
const data = get(message, 'plugindata.data')
this.onPublisherMuteAudio(data)
return
}
}
if (message['jsep']) {
const answerRes = await this.createAnswer(message['jsep'])
await this.sendMessage(
{
request: 'start',
room: this.roomId,
},
answerRes,
)
}
}
public isAudioMuted() {
return !this.stream.getAudioTracks()[0].enabled
}
public muteAudio() {
this.stream.getAudioTracks()[0].enabled = false
}
public unmuteAudio() {
this.stream.getAudioTracks()[0].enabled = true
}
public isVideoMuted() {
return !this.stream.getVideoTracks()[0].enabled
}
public muteVideo() {
this.stream.getVideoTracks()[0].enabled = false
}
public unmuteVideo() {
this.stream.getVideoTracks()[0].enabled = true
}
private keepAlive() {
if (this.connected) {
this.setKeepAliveTimeout()
this
.send({
janus: 'keepalive',
session_id: this.sessionId,
})
.catch(console.error)
}
}
private setKeepAliveTimeout() {
if (this.connected) {
this.keepAliveTimeoutID = setTimeout(() => this.keepAlive(), keepAlivePeriod)
}
}
private async createOffer(bitrate: number, audio: boolean, video: boolean) {
const offerObj = {
offerToReceiveAudio: true,
offerToReceiveVideo: true,
}
const _offer = await this.pc.createOffer(offerObj)
await this.pc.setLocalDescription(_offer)
const configObj = {
request: 'configure',
audio,
video,
bitrate,
}
const offerResponse: any = await this.sendMessage(configObj, _offer)
await this.pc.setRemoteDescription(
new RTCSessionDescription({
sdp: offerResponse.jsep.sdp,
type: offerResponse.jsep.type,
}),
)
this.isRemoteDescriptionSet = true
this.cachedCandidates.forEach((candidate: RTCIceCandidate) => {
this.pc.addIceCandidate(candidate)
})
this.cachedCandidates = [] as RTCIceCandidate[]
this.isCreateOffer = true
}
private async createAnswer(jsep: any) {
await this.pc.setRemoteDescription(
new RTCSessionDescription({
sdp: jsep.sdp,
type: jsep.type,
}),
)
this.isRemoteDescriptionSet = true
// TODO async could not use in loop
this.cachedCandidates.forEach(async (candidate: RTCIceCandidate) => {
await this.pc.addIceCandidate(candidate)
})
this.cachedCandidates = [] as RTCIceCandidate[]
const answerRes = await this.pc.createAnswer({
offerToReceiveAudio: true,
offerToReceiveVideo: true,
})
await this.pc.setLocalDescription(answerRes)
return answerRes
}
}
export default PublisherPlugin