import { copyToClipBoard, getRandomString } from 'billd-utils'; import { NButton, useDialog } from 'naive-ui'; import { computed, h, onUnmounted, ref, watch } from 'vue'; import { useRoute } from 'vue-router'; import { fetchVerifyPkKey } from '@/api/liveRoom'; import { fetchRtcV1Publish } from '@/api/srs'; import { SRS_CB_URL_PARAMS, THEME_COLOR, WEBSOCKET_URL } from '@/constant'; import { DanmuMsgTypeEnum, ILiveUser, WsMessageMsgIsFileEnum, } from '@/interface'; import { WebRTCClass } from '@/network/webRTC'; import { WebSocketClass, prettierReceiveWsMsg } from '@/network/webSocket'; import router, { routerName } from '@/router'; import { useAppStore } from '@/store/app'; import { useNetworkStore } from '@/store/network'; import { useUserStore } from '@/store/user'; import { LiveRoomTypeEnum } from '@/types/ILiveRoom'; import { IUser } from '@/types/IUser'; import { IDanmu, WSGetRoomAllUserType, WSLivePkKeyType, WsAnswerType, WsCandidateType, WsConnectStatusEnum, WsDisableSpeakingType, WsGetLiveUserType, WsHeartbeatType, WsJoinType, WsLeavedType, WsMessageType, WsMsgTypeEnum, WsOfferType, WsOtherJoinType, WsRoomLivingType, WsStartLiveType, WsUpdateJoinInfoType, } from '@/types/websocket'; import { createVideo } from '@/utils'; import { useRTCParams } from './use-rtcParams'; import { useTip } from './use-tip'; export const useWebsocket = () => { const route = useRoute(); const appStore = useAppStore(); const userStore = useUserStore(); const networkStore = useNetworkStore(); const dialog = useDialog(); const { maxBitrate, maxFramerate, resolutionRatio } = useRTCParams(); const connectStatus = ref(); const loopHeartbeatTimer = ref(); const loopGetLiveUserTimer = ref(); const liveUserList = ref([]); const roomId = ref(''); const isPull = ref(false); const roomLiving = ref(false); const isAnchor = ref(false); const isSRS = ref(false); const anchorInfo = ref(); const anchorSocketId = ref(''); const canvasVideoStream = ref(); const lastCoverImg = ref(''); const currentMaxBitrate = ref(maxBitrate.value[3].value); const currentMaxFramerate = ref(maxFramerate.value[2].value); const currentResolutionRatio = ref(resolutionRatio.value[3].value); const timerObj = ref({}); const damuList = ref([]); onUnmounted(() => { clearInterval(loopHeartbeatTimer.value); clearInterval(loopGetLiveUserTimer.value); }); watch( () => appStore.pkStream, (newval) => { if (newval && isAnchor.value) { console.log('转推到srs', newval); srsWebRtc.sendOffer({ isPk: true, sender: mySocketId.value, }); } } ); watch( [() => userStore.userInfo?.id, () => connectStatus.value], ([userInfo, status]) => { if (userInfo && status === WsConnectStatusEnum.connect) { const ws = networkStore.wsMap.get(roomId.value); if (!ws) return; ws.send({ requestId: getRandomString(8), msgType: WsMsgTypeEnum.updateJoinInfo, data: { live_room_id: Number(roomId.value), }, }); } }, { immediate: true } ); const mySocketId = computed(() => { return networkStore.wsMap.get(roomId.value)?.socketIo?.id || '-1'; }); function handleHeartbeat(socketId: string) { loopHeartbeatTimer.value = setInterval(() => { const ws = networkStore.wsMap.get(roomId.value); if (!ws) return; ws.send({ requestId: getRandomString(8), msgType: WsMsgTypeEnum.heartbeat, data: { socket_id: socketId, live_room_id: Number(roomId.value), }, }); }, 1000 * 5); } function handleSendGetLiveUser(liveRoomId: number) { loopGetLiveUserTimer.value = setInterval(() => { const ws = networkStore.wsMap.get(roomId.value); if (!ws) return; ws.send({ requestId: getRandomString(8), msgType: WsMsgTypeEnum.getLiveUser, data: { live_room_id: liveRoomId, }, }); }, 1000 * 5); } function handleStartLive({ coverImg, name, type, msrDelay, msrMaxDelay, }: { coverImg?: string; name?: string; type: LiveRoomTypeEnum; videoEl?: HTMLVideoElement; msrDelay: number; msrMaxDelay: number; }) { networkStore.wsMap.get(roomId.value)?.send({ requestId: getRandomString(8), msgType: WsMsgTypeEnum.startLive, data: { cover_img: coverImg!, name: name!, type, msrDelay, msrMaxDelay, }, }); // if (type === LiveRoomTypeEnum.user_msr) { // return; // } // if ([LiveRoomTypeEnum.user_srs, LiveRoomTypeEnum.user_obs].includes(type)) { // isSRS.value = true; // srsWebRtc.sendOffer({ // isPk: false, // sender: mySocketId.value, // }); // } else { // isSRS.value = false; // } } function sendJoin() { const instance = networkStore.wsMap.get(roomId.value); if (!instance) return; instance.send({ requestId: getRandomString(8), msgType: WsMsgTypeEnum.join, data: { socket_id: mySocketId.value, live_room_id: Number(roomId.value), user_info: userStore.userInfo, }, }); } async function handleUserMedia({ video, audio }) { try { const event = await navigator.mediaDevices.getUserMedia({ video, audio, }); return event; } catch (error) { console.log(error); } } const nativeWebRtc = { newWebrtc: ({ isAnchor, sender, receiver, videoEl, }: { isAnchor: boolean; sender: string; receiver: string; videoEl: HTMLVideoElement; }) => { return new WebRTCClass({ maxBitrate: currentMaxBitrate.value, maxFramerate: currentMaxFramerate.value, resolutionRatio: currentResolutionRatio.value, isSRS: false, roomId: roomId.value, isAnchor, videoEl, sender, receiver, localStream: canvasVideoStream.value, }); }, /** * 原生webrtc视频通话 * 视频发起方是房主,房主发offer给用户 */ sendOffer: async ({ sender, receiver, }: { sender: string; receiver: string; }) => { console.log('开始nativeWebRtc的sendOffer', { sender, receiver }); try { const ws = networkStore.wsMap.get(roomId.value); if (!ws) return; if (networkStore.rtcMap.get(receiver)) { return; } const rtc = nativeWebRtc.newWebrtc({ isAnchor: true, sender, receiver, videoEl: createVideo({ appendChild: true, }), }); canvasVideoStream.value?.getTracks().forEach((track) => { if (rtc && canvasVideoStream.value) { console.log( 'nativeWebRtc的canvasVideoStream插入track', track.kind, track ); rtc.peerConnection?.addTrack(track, canvasVideoStream.value); } }); const offerSdp = await rtc.createOffer(); if (!offerSdp) { console.error('nativeWebRtc的offerSdp为空'); return; } await rtc.setLocalDescription(offerSdp!); networkStore.wsMap.get(roomId.value)?.send({ requestId: getRandomString(8), msgType: WsMsgTypeEnum.nativeWebRtcOffer, data: { live_room: appStore.liveRoomInfo!, live_room_id: Number(roomId.value), sender, receiver, sdp: offerSdp, }, }); } catch (error) { console.error('nativeWebRtc的sendOffer错误'); } }, /** * 原生webrtc视频通话 * 用户收到房主的offer,用户回复房主answer */ sendAnswer: async ({ isPk, sdp, sender, receiver, }: { isPk: boolean; sdp: RTCSessionDescriptionInit; sender: string; receiver: string; }) => { console.log('开始nativeWebRtc的sendAnswer', { sender, receiver, sdp }); try { const ws = networkStore.wsMap.get(roomId.value); if (!ws) return; const rtc = networkStore.rtcMap.get(receiver); if (rtc) { await rtc.setRemoteDescription(sdp); if (isPk) { if (!isAnchor.value) { const stream = await handleUserMedia({ video: true, audio: true, }); if (rtc?.peerConnection) { rtc.peerConnection.onnegotiationneeded = (event) => { console.log('onnegotiationneeded', event); }; stream?.getTracks().forEach((track) => { rtc.peerConnection?.addTrack(track, stream); }); } } } const answerSdp = await rtc.createAnswer(); if (!answerSdp) { console.error('nativeWebRtc的answerSdp为空'); return; } await rtc.setLocalDescription(answerSdp); networkStore.wsMap.get(roomId.value)?.send({ requestId: getRandomString(8), msgType: WsMsgTypeEnum.nativeWebRtcAnswer, data: { live_room_id: Number(roomId.value), sender, receiver, sdp: answerSdp, }, }); } else { console.error('rtc不存在'); } } catch (error) { console.error('nativeWebRtc的sendAnswer错误'); } }, }; const srsWebRtc = { newWebrtc: ({ roomId, sender, videoEl, }: { roomId: string; sender: string; videoEl: HTMLVideoElement; }) => { return new WebRTCClass({ isAnchor: true, maxBitrate: currentMaxBitrate.value, maxFramerate: currentMaxFramerate.value, resolutionRatio: currentResolutionRatio.value, roomId, videoEl, isSRS: true, sender, receiver: 'srs', localStream: canvasVideoStream.value, }); }, /** * srs的webrtc推流视频通话 * 视频发起方是房主,房主发offer给srs */ sendOffer: async ({ isPk, sender }: { isPk: boolean; sender: string }) => { console.log('开始srsWebRtc的sendOffer', { sender }); try { const ws = networkStore.wsMap.get(roomId.value); if (!ws) return; const rtc = srsWebRtc.newWebrtc({ roomId: `${isPk ? `${roomId.value}___pk` : `${roomId.value}`}`, sender, videoEl: createVideo({ appendChild: true }), }); canvasVideoStream.value?.getTracks().forEach((track) => { if (rtc && canvasVideoStream.value) { console.log( 'srsWebRtc的canvasVideoStream插入track', track.kind, track ); rtc.peerConnection?.addTrack(track, canvasVideoStream.value); } }); const offerSdp = await rtc.createOffer(); if (!offerSdp) { console.error('srsWebRtc的offerSdp为空'); return; } await rtc.setLocalDescription(offerSdp!); const myLiveRoom = userStore.userInfo!.live_rooms![0]; const answerRes = await fetchRtcV1Publish({ api: `/rtc/v1/publish/`, clientip: null, sdp: offerSdp!.sdp!, streamurl: `${myLiveRoom.rtmp_url!}?${ SRS_CB_URL_PARAMS.publishKey }=${myLiveRoom.key!}&${SRS_CB_URL_PARAMS.publishType}=${ isPk ? LiveRoomTypeEnum.user_pk : LiveRoomTypeEnum.user_srs }`, tid: getRandomString(10), }); if (answerRes.data.code !== 0) { console.error('/rtc/v1/publish/拿不到sdp'); window.$message.error('/rtc/v1/publish/拿不到sdp'); return; } await rtc.setRemoteDescription( new RTCSessionDescription({ type: 'answer', sdp: answerRes.data.sdp }) ); } catch (error) { console.error('srsWebRtc的sendOffer错误'); } }, }; function initReceive() { const ws = networkStore.wsMap.get(roomId.value); if (!ws?.socketIo) return; // websocket连接成功 ws.socketIo.on(WsConnectStatusEnum.connect, () => { prettierReceiveWsMsg(WsConnectStatusEnum.connect, ws.socketIo); handleHeartbeat(ws.socketIo!.id); if (!ws) return; connectStatus.value = WsConnectStatusEnum.connect; ws.status = WsConnectStatusEnum.connect; ws.update(); sendJoin(); }); // websocket连接断开 ws.socketIo.on(WsConnectStatusEnum.disconnect, (err) => { prettierReceiveWsMsg(WsConnectStatusEnum.disconnect, ws); console.log('websocket连接断开', err); if (!ws) return; ws.status = WsConnectStatusEnum.disconnect; ws.update(); }); // 收到livePkKey ws.socketIo.on(WsMsgTypeEnum.livePkKey, (data: WSLivePkKeyType['data']) => { console.log('收到livePkKey', data); const url = router.resolve({ name: routerName.pull, params: { roomId: data.live_room_id }, query: { pkKey: data.key, }, }); const pkurl = `${window.location.origin}${url.href}`; useTip({ title: '邀请主播加入PK', width: '360px', hiddenCancel: true, content: h('div', [ h('div', { style: { marginBottom: '5px' } }, `${pkurl}`), h( NButton, { size: 'small', type: 'primary', color: THEME_COLOR, onClick: () => { copyToClipBoard(pkurl); window.$message.success('复制成功!'); }, }, () => '复制链接' // 用箭头函数返回性能更好。 ), h('div', { style: { marginTop: '5px' } }, '注意,有效期:5分钟'), ]), }).catch(() => {}); }); // 收到srsOffer ws.socketIo.on( WsMsgTypeEnum.srsOffer, async (data: WsOfferType['data']) => { console.log('收到srsOffer', data); if (data.receiver === mySocketId.value) { console.warn('是发给我的srsOffer'); const videoEl = createVideo({ appendChild: true }); const rtc = new WebRTCClass({ isAnchor: true, maxBitrate: currentMaxBitrate.value, maxFramerate: currentMaxFramerate.value, resolutionRatio: currentResolutionRatio.value, roomId: roomId.value, videoEl, isSRS: true, sender: data.sender, receiver: data.receiver, }); isSRS.value = true; await rtc.setRemoteDescription(data.sdp); const answerSdp = await rtc.createAnswer(); if (answerSdp) { await rtc.setLocalDescription(answerSdp); ws.send({ requestId: getRandomString(8), msgType: WsMsgTypeEnum.srsAnswer, data: { live_room_id: Number(roomId.value), sdp: answerSdp, receiver: data.sender, sender: mySocketId.value, }, }); } else { console.error('srsOffer的answerSdp为空'); } } else { console.error('不是发给我的srsOffer'); } } ); // 收到srsAnswer ws.socketIo.on(WsMsgTypeEnum.srsAnswer, (data: WsAnswerType['data']) => { console.log('收到srsAnswer', data); if (data.receiver === mySocketId.value) { console.warn('是发给我的srsAnswer'); const rtc = networkStore.getRtcMap(data.sender); rtc?.setRemoteDescription(data.sdp); } else { console.error('不是发给我的srsAnswer'); } }); // 收到srsCandidate ws.socketIo.on( WsMsgTypeEnum.srsCandidate, (data: WsCandidateType['data']) => { console.log('收到srsCandidate', data); if (data.receiver === mySocketId.value) { console.warn('是发给我的srsCandidate'); const rtc = networkStore.getRtcMap(data.sender); rtc?.addIceCandidate(data.candidate); } else { console.error('不是发给我的srsCandidate'); } } ); // 收到nativeWebRtcOffer ws.socketIo.on( WsMsgTypeEnum.nativeWebRtcOffer, async (data: WsOfferType['data']) => { console.log('收到nativeWebRtcOffer', data); if (data.live_room.type === LiveRoomTypeEnum.user_pk) { if (!isAnchor.value) { const res = await fetchVerifyPkKey({ liveRoomId: Number(roomId.value), key: route.query.pkKey, }); if (res.code === 200 && res.data === true) { dialog.warning({ title: '提示', content: '是否加入PK', positiveText: '确认', onPositiveClick() { async function main() { if (data.receiver === mySocketId.value) { console.warn('是发给我的nativeWebRtcOffer'); await nativeWebRtc.newWebrtc({ isAnchor: true, // 因为这里是收到offer,而offer是房主发的,所以此时的data.data.sender是房主;data.data.receiver是接收者; // 但是这里的nativeWebRtc的sender,得是自己,不能是data.data.sender,不要混淆 sender: mySocketId.value, receiver: data.sender, videoEl: createVideo({ appendChild: true, }), }); nativeWebRtc.sendAnswer({ isPk: true, sender: mySocketId.value, // data.data.receiver是接收者;我们现在new pc,发送者是自己,接收者肯定是房主,不能是data.data.receiver,因为data.data.receiver是自己 receiver: data.sender, sdp: data.sdp, }); } else { console.error('不是发给我的nativeWebRtcOffer'); } } return main(); }, }); } else { window.$message.error('验证pkKey错误!'); } } else { if (data.receiver === mySocketId.value) { console.warn('是发给我的nativeWebRtcOffer'); await nativeWebRtc.newWebrtc({ isAnchor: true, // 因为这里是收到offer,而offer是房主发的,所以此时的data.data.sender是房主;data.data.receiver是接收者; // 但是这里的nativeWebRtc的sender,得是自己,不能是data.data.sender,不要混淆 sender: mySocketId.value, receiver: data.sender, videoEl: createVideo({ appendChild: true, }), }); nativeWebRtc.sendAnswer({ isPk: true, sender: mySocketId.value, // data.data.receiver是接收者;我们现在new pc,发送者是自己,接收者肯定是房主,不能是data.data.receiver,因为data.data.receiver是自己 receiver: data.sender, sdp: data.sdp, }); } else { console.error('不是发给我的nativeWebRtcOffer'); } } } else { if (data.receiver === mySocketId.value) { console.warn('是发给我的nativeWebRtcOffer'); await nativeWebRtc.newWebrtc({ isAnchor: true, // 因为这里是收到offer,而offer是房主发的,所以此时的data.data.sender是房主;data.data.receiver是接收者; // 但是这里的nativeWebRtc的sender,得是自己,不能是data.data.sender,不要混淆 sender: mySocketId.value, receiver: data.sender, videoEl: createVideo({ appendChild: true, }), }); await nativeWebRtc.sendAnswer({ isPk: false, sender: mySocketId.value, // data.data.receiver是接收者;我们现在new pc,发送者是自己,接收者肯定是房主,不能是data.data.receiver,因为data.data.receiver是自己 receiver: data.sender, sdp: data.sdp, }); } else { console.error('不是发给我的nativeWebRtcOffer'); } } return '1'; } ); // 收到nativeWebRtcAnswer ws.socketIo.on( WsMsgTypeEnum.nativeWebRtcAnswer, async (data: WsAnswerType['data']) => { console.log('收到nativeWebRtcAnswer', data); if (data.receiver === mySocketId.value) { console.warn('是发给我的nativeWebRtcAnswer'); const rtc = networkStore.getRtcMap(data.sender); if (rtc) { await rtc.setRemoteDescription(data.sdp); } } else { console.error('不是发给我的nativeWebRtcAnswer'); } } ); // 收到nativeWebRtcCandidate ws.socketIo.on( WsMsgTypeEnum.nativeWebRtcCandidate, (data: WsCandidateType['data']) => { console.log('收到nativeWebRtcCandidate', data); if (data.receiver === mySocketId.value) { console.warn('是发给我的nativeWebRtcCandidate'); const rtc = networkStore.getRtcMap(data.sender); rtc?.addIceCandidate(data.candidate); } else { console.error('不是发给我的nativeWebRtcCandidate'); } } ); // 主播正在直播 ws.socketIo.on( WsMsgTypeEnum.roomLiving, (data: WsRoomLivingType['data']) => { prettierReceiveWsMsg(WsMsgTypeEnum.roomLiving, data); roomLiving.value = true; if (data.anchor_socket_id) { anchorSocketId.value = data.anchor_socket_id; } isSRS.value = true; if ( data.live_room.type && [LiveRoomTypeEnum.user_wertc, LiveRoomTypeEnum.user_pk].includes( data.live_room.type ) ) { isSRS.value = false; } } ); // 主播不在直播 ws.socketIo.on(WsMsgTypeEnum.roomNoLive, (data) => { prettierReceiveWsMsg(WsMsgTypeEnum.roomNoLive, data); roomLiving.value = false; }); // 当前所有在线用户 ws.socketIo.on( WsMsgTypeEnum.liveUser, (data: WSGetRoomAllUserType['data']) => { prettierReceiveWsMsg(WsMsgTypeEnum.liveUser, data); liveUserList.value = data.liveUser; } ); // 收到用户发送消息 ws.socketIo.on(WsMsgTypeEnum.message, (data: WsMessageType) => { prettierReceiveWsMsg(WsMsgTypeEnum.message, data); damuList.value.push({ user_agent: data.data.user_agent, live_room_id: data.data.live_room_id, request_id: data.request_id, socket_id: data.socket_id, msgType: data.data.msgType, msg: data.data.msg, userInfo: data.user_info, msgIsFile: data.data.msgIsFile, send_msg_time: data.data.send_msg_time, }); }); // 收到disableSpeaking ws.socketIo.on( WsMsgTypeEnum.disableSpeaking, (data: WsDisableSpeakingType['data']) => { prettierReceiveWsMsg(WsMsgTypeEnum.disableSpeaking, data); // if (data.is_disable_speaking) { // window.$message.error('你已被禁言!'); // appStore.disableSpeaking.set(data.live_room_id, { // exp: data.disable_expired_at, // label: formatDownTime({ // startTime: +new Date(), // endTime: data.disable_expired_at, // }), // }); // clearTimeout(timerObj.value[data.live_room_id]); // timerObj.value[data.live_room_id] = setInterval(() => { // if ( // data.disable_expired_at && // +new Date() > data.disable_expired_at // ) { // clearTimeout(timerObj.value[data.live_room_id]); // } // appStore.disableSpeaking.set(data.live_room_id, { // exp: data.disable_expired_at!, // label: formatDownTime({ // startTime: +new Date(), // endTime: data.disable_expired_at!, // }), // }); // }, 1000); // damuList.value = damuList.value.filter( // (v) => v.request_id !== data.request_id // ); // } if (data.user_id !== userStore.userInfo?.id && data.disable_ok) { window.$message.success('禁言成功!'); } if ( data.user_id !== userStore.userInfo?.id && data.restore_disable_ok ) { window.$message.success('解除禁言成功!'); } if ( data.user_id === userStore.userInfo?.id && data.restore_disable_ok ) { window.$message.success('禁言接触了!'); clearTimeout(timerObj.value[data.live_room_id]); appStore.disableSpeaking.delete(data.live_room_id); } } ); // 用户加入房间完成 ws.socketIo.on(WsMsgTypeEnum.joined, (data: WsJoinType['data']) => { prettierReceiveWsMsg(WsMsgTypeEnum.joined, data); appStore.setLiveRoomInfo(data.live_room); anchorInfo.value = data.anchor_info; }); // 其他用户加入房间 ws.socketIo.on(WsMsgTypeEnum.otherJoin, (data: WsOtherJoinType['data']) => { prettierReceiveWsMsg(WsMsgTypeEnum.otherJoin, data); const requestId = getRandomString(8); const danmu: IDanmu = { live_room_id: data.live_room.id!, request_id: requestId, msgType: DanmuMsgTypeEnum.otherJoin, socket_id: data.join_socket_id, userInfo: data.join_user_info, msgIsFile: WsMessageMsgIsFileEnum.no, msg: '', send_msg_time: +new Date(), }; damuList.value.push(danmu); if (data.live_room.type === LiveRoomTypeEnum.user_wertc) { isSRS.value = false; nativeWebRtc.sendOffer({ sender: mySocketId.value, receiver: data.join_socket_id, }); } else { data.socket_list.forEach((item) => { if (item !== mySocketId.value) { if ( [ LiveRoomTypeEnum.user_wertc, LiveRoomTypeEnum.user_wertc_meeting, LiveRoomTypeEnum.user_pk, ].includes(data.live_room.type!) ) { isSRS.value = false; nativeWebRtc.sendOffer({ sender: mySocketId.value, receiver: item, }); } } }); } }); // 用户离开房间 ws.socketIo.on(WsMsgTypeEnum.leave, (data) => { prettierReceiveWsMsg(WsMsgTypeEnum.leave, data); }); // 用户离开房间完成 ws.socketIo.on(WsMsgTypeEnum.leaved, (data: WsLeavedType['data']) => { prettierReceiveWsMsg(WsMsgTypeEnum.leaved, data); if (anchorSocketId.value === data.socket_id) { roomLiving.value = false; } networkStore.removeRtc(`${roomId.value}`); damuList.value.push({ live_room_id: Number(roomId.value), socket_id: data.socket_id, msgType: DanmuMsgTypeEnum.userLeaved, msgIsFile: WsMessageMsgIsFileEnum.no, userInfo: data.user_info, msg: '', send_msg_time: +new Date(), }); }); } function initSrsWs(data: { isAnchor: boolean; roomId: string; currentResolutionRatio?: number; currentMaxFramerate?: number; currentMaxBitrate?: number; }) { roomId.value = data.roomId; isAnchor.value = data.isAnchor; if (data.currentMaxBitrate) { currentMaxBitrate.value = data.currentMaxBitrate; } if (data.currentMaxFramerate) { currentMaxFramerate.value = data.currentMaxFramerate; } if (data.currentResolutionRatio) { currentResolutionRatio.value = data.currentResolutionRatio; } new WebSocketClass({ roomId: roomId.value, url: WEBSOCKET_URL, isAnchor: data.isAnchor, }); initReceive(); } return { isPull, initSrsWs, handleStartLive, handleSendGetLiveUser, mySocketId, canvasVideoStream, lastCoverImg, roomLiving, anchorInfo, liveUserList, damuList, currentMaxFramerate, currentMaxBitrate, currentResolutionRatio, }; };