/* * @Description: 音视频通话集成 */ import TRTC from "trtc-js-sdk"; import { isUndefined } from "@/utils"; export default { data() { return { client: null, localStream: null, remoteStreamList: [], isJoining: false, isJoined: false, isPublishing: false, isPublished: false, isMutedVideo: false, isMutedAudio: false, isPlayingLocalStream: false, }; }, methods: { // 初始化客户端 async initClient() { this.client = TRTC.createClient({ mode: "rtc", sdkAppId: Number(process.env.VUE_APP_SDK_TRTC_ID), userId: this.userID?.toString(), userSig: this.userSig, }); // https://web.sdk.qcloud.com/trtc/webrtc/doc/zh-cn/TRTC.Logger.html TRTC.Logger.setLogLevel(process.env.NODE_ENV === "production" ? TRTC.Logger.LogLevel.NONE : TRTC.Logger.LogLevel.DEBUG); // this.addSuccessLog(`Client [${this.userID}] created.`); this.addSuccessLog(`客户端已创建`); this.handleClientEvents(); }, async initLocalStream({ audio = true, video = true }) { this.localStream = TRTC.createStream({ audio, video, userId: this.userId, cameraId: this.cameraId, microphoneId: this.microphoneId, // mirror: true, }); try { await this.localStream.initialize(); // this.addSuccessLog(`LocalStream [${this.userId}] initialized.`); this.addSuccessLog(`本地音视频流已初始化`); } catch (error) { this.localStream = null; switch (error.name) { case "NotFoundError": this.addFailedLog(`未找到摄像头或麦克风,请检查您的摄像头或麦克风`); break; case "NotReadableError": // 提示用户:暂时无法访问摄像头/麦克风,请确保当前没有其他应用请求访问摄像头/麦克风,并重试。 this.addFailedLog(`暂时无法访问摄像头/麦克风,请确保当前没有其他应用请求访问摄像头/麦克风,并重试`); break; case "RtcError": // DEVICE_NOT_FOUND console.log("error", error); if (error.getCode() === 4099) { // 当前设备没有麦克风或没有摄像头,但尝试采集麦克风、摄像头。 // 处理建议:引导用户检查设备的摄像头及麦克风是否正常,业务侧应在进房前的进行设备检测。 // 未检测到摄像头,请检查您的摄像头 this.addFailedLog(`未检测到摄像头或麦克风,请检查您的摄像头或麦克风`); } break; case "NotAllowedError": this.addFailedLog( `检查是否拒绝了当前的浏览器实例的访问音频、视频、屏幕分享请求。不授权摄像头/麦克风访问将无法进行音视频通话。` ); break; default: console.error(error); break; } throw error; } }, playLocalStream() { this.localStream .play("localStream", { objectFit: "fill", }) .then(() => { this.isPlayingLocalStream = true; // this.addSuccessLog(`LocalStream [${this.userId}] playing.`); this.addSuccessLog(`正在播放本地音视频流`); }) .catch((error) => { this.addFailedLog(`LocalStream [${this.userId}] failed to play. Error: ${error.message}`); }); }, destroyLocalStream() { this.localStream && this.localStream.stop(); this.localStream && this.localStream.close(); this.localStream = null; this.isPlayingLocalStream = false; }, playRemoteStream(remoteStream, element) { if (remoteStream.getType() === "main" && remoteStream.getUserId().indexOf("share") >= 0) { remoteStream.play(element, { objectFit: "fill" }).catch(); } else { remoteStream.play(element, { objectFit: "fill" }).catch(); } }, resumeStream(stream) { stream.resume(); }, async join() { if (this.isJoining || this.isJoined) { return; } this.isJoining = true; !this.client && (await this.initClient()); try { await this.client.join({ roomId: this.roomId }); this.isJoining = false; this.isJoined = true; // this.addSuccessLog(`Join room [${this.roomId}] success.`); this.addSuccessLog(`成功加入房间 [${this.roomId}] `); // this.reportSuccessEvent("joinRoom"); this.startGetAudioLevel(); } catch (error) { this.isJoining = false; console.error("join room failed", error); this.addFailedLog(`Join room ${this.roomId} failed, please check your params. Error: ${error.message}`); // this.reportFailedEvent("joinRoom", error); throw error; } }, async publish() { if (!this.isJoined || this.isPublishing || this.isPublished) { return; } this.isPublishing = true; try { await this.client.publish(this.localStream); this.isPublishing = false; this.isPublished = true; // this.addSuccessLog("LocalStream is published successfully."); this.addSuccessLog("本地音视频流发布成功"); // this.reportSuccessEvent("publish"); } catch (error) { this.isPublishing = false; console.error("publish localStream failed", error); this.addFailedLog(`LocalStream is failed to publish. Error: ${error.message}`); // this.reportFailedEvent("publish"); throw error; } }, async unPublish() { if (!this.isPublished || this.isUnPublishing) { return; } this.isUnPublishing = true; try { await this.client.unpublish(this.localStream); this.isUnPublishing = false; this.isPublished = false; // this.addSuccessLog("localStream unpublish successfully."); this.addSuccessLog("本地音视频流取消发布成功"); // this.reportSuccessEvent("unpublish"); } catch (error) { this.isUnPublishing = false; console.error("unpublish localStream failed", error); this.addFailedLog(`LocalStream is failed to unpublish. Error: ${error.message}`); // this.reportFailedEvent("unpublish", error); throw error; } }, async subscribe(remoteStream, config = { audio: true, video: true }) { try { await this.client.subscribe(remoteStream, { audio: isUndefined(config.audio) ? true : config.audio, video: isUndefined(config.video) ? true : config.video, }); // this.userLog(remoteStream.getUserId(), "订阅成功"); } catch (error) { console.error(`subscribe ${remoteStream.getUserId()} with audio: ${config.audio} video: ${config.video} error`, error); // this.userLog(remoteStream.getUserId(), "订阅失败", true); } }, async unSubscribe(remoteStream) { try { await this.client.unsubscribe(remoteStream); // this.userLog(remoteStream.getUserId(), "取消订阅成功"); } catch (error) { console.error(`unsubscribe ${remoteStream.getUserId()} error`, error); // this.userLog(remoteStream.getUserId(), "取消订阅失败", true); } }, async leave() { if (!this.isJoined || this.isLeaving) { return; } this.isLeaving = true; this.stopGetAudioLevel(); this.isPublished && (await this.unPublish()); this.localStream && this.destroyLocalStream(); try { await this.client.leave(); this.isLeaving = false; this.isJoined = false; // this.addSuccessLog("Leave room success."); this.addSuccessLog("呼叫已结束"); // this.reportSuccessEvent("leaveRoom"); } catch (error) { this.isLeaving = false; console.error("leave room error", error); this.addFailedLog(`Leave room failed. Error: ${error.message}`); // this.reportFailedEvent("leaveRoom", error); throw error; } }, muteVideo() { if (this.localStream) { this.localStream.muteVideo(); this.isMutedVideo = true; // this.addSuccessLog("LocalStream muted video."); this.addSuccessLog("已禁用视频轨道"); } }, muteAudio() { if (this.localStream) { this.localStream.muteAudio(); this.isMutedAudio = true; // this.addSuccessLog("LocalStream muted audio."); this.addSuccessLog("已禁用音频轨道"); } }, unmuteVideo() { if (this.localStream) { this.localStream.unmuteVideo(); this.isMutedVideo = false; // this.addSuccessLog("LocalStream unmuted video."); this.addSuccessLog("已启用视频轨道"); } }, unmuteAudio() { if (this.localStream) { this.localStream.unmuteAudio(); this.isMutedAudio = false; // this.addSuccessLog("LocalStream unmuted audio."); this.addSuccessLog("已启用音频轨道"); } }, switchDevice(type, deviceId) { try { if (this.localStream) { this.localStream.switchDevice(type, deviceId); this.addSuccessLog(`Switch ${type} device success.`); } } catch (error) { console.error("switchDevice failed", error); this.addFailedLog(`Switch ${type} device failed.`); } }, startGetAudioLevel() { // 文档:https://web.sdk.qcloud.com/trtc/webrtc/doc/zh-cn/module-ClientEvent.html#.AUDIO_VOLUME this.client.on("audio-volume", (event) => { event.result.forEach(({ userId, audioVolume }) => { if (audioVolume > 2) { console.log(`user: ${userId} is speaking, audioVolume: ${audioVolume}`); this.memberSpeaking(userId); } }); }); this.client.enableAudioVolumeEvaluation(200); }, stopGetAudioLevel() { this.client && this.client.enableAudioVolumeEvaluation(-1); }, handleClientEvents() { this.client.on("error", (error) => { console.error(error); alert(error); }); this.client.on("client-banned", async (event) => { console.warn(`client has been banned for ${event.reason}`); this.isPublished = false; this.localStream = null; await this.leave(); }); // fired when a remote peer is joining the room this.client.on("peer-join", (event) => { // 结束拨号计时 this.endDialingTimeCounting(); const { userId } = event; console.log(`peer-join ${userId}`, event); // this.handlePeerJoin(userId); const list = []; this.memberList.forEach((member) => { if (member.tencentUserId === userId) { member.state = "CONNECTED"; } list.push(member); }); this.$emit("update:member-list", list); // this.addSuccessLog(`${userId}已进入房间`); this.userLog(userId, "已进入房间"); }); // fired when a remote peer is leaving the room this.client.on("peer-leave", (event) => { const { userId } = event; console.log(`peer-leave ${userId}`, event); // this.handlePeerLeave(userId); const list = []; this.memberList.forEach((member) => { if (member.tencentUserId === userId) { member.state = "DISCONNECTED"; member.hangup = true; } list.push(member); }); this.$emit("update:member-list", list); // this.addSuccessLog(`${userId}已离开房间`); this.userLog(userId, "已离开房间"); }); // fired when a remote stream is added this.client.on("stream-added", (event) => { const { stream: remoteStream } = event; const remoteUserId = remoteStream.getUserId(); if (remoteUserId === `share_${this.userId}`) { // don't need screen shared by us this.unSubscribe(remoteStream); } else { console.log(`remote stream added: [${remoteUserId}] type: ${remoteStream.getType()}`); // subscribe to this remote stream remoteStream.on("connection-state-changed", (event) => { /** * Stream 连接状态变更事件 * * 'DISCONNECTED':连接断开 * 'CONNECTING':正在连接中 * 'CONNECTED':已连接 * 'RECONNECTING':自动重连中 * 不同状态变更的含义: * * DISCONNECTED -> CONNECTING: 正在尝试建立连接,调用推流或者订阅接口但尚未成功时触发。 * CONNECTING -> CONNECTED: 连接建立成功,推流成功或订阅成功触发。 * CONNECTED -> DISCONNECTED: 连接中断,当网络异常导致连接断开时触发。 * DISCONNECTED -> RECONNECTING: 正在重连,当连接异常断开时,SDK 尝试自动重连时触发。 * RECONNECTING -> CONNECTED: SDK 自动重连成功。 * RECONNECTING -> DISCONNECTED: SDK 自动重连失败。 */ console.log(`[${remoteUserId}] connection-state-changed prevState: ${event.prevState}, state: ${event.state}`); // const list = [] // this.memberList.forEach(member => { // if (member.tencentUserId === remoteUserId) { // member.state = event.state; // } // list.push(member); // }) // this.$emit("update:member-list",list) }); this.subscribe(remoteStream); // this.addSuccessLog(`RemoteStream added: [${remoteUserId}].`); // this.addSuccessLog(`[${remoteUserId}]已加入`); this.userLog(remoteUserId, "已加入"); } }); // fired when a remote stream has been subscribed this.client.on("stream-subscribed", (event) => { const { stream: remoteStream } = event; const remoteUserId = remoteStream.getUserId(); console.log("stream-subscribed userId: ", remoteUserId); // this.addSuccessLog(`RemoteStream subscribed: [${remoteUserId}].`); // this.addSuccessLog(`[${remoteUserId}]已加入`); this.userLog(remoteUserId, "已加入"); this.remoteStreamList.push(remoteStream); this.$nextTick(() => { this.playRemoteStream(remoteStream, remoteUserId); }); }); // fired when the remote stream is removed, e.g. the remote user called Client.unpublish() this.client.on("stream-removed", (event) => { const { stream: remoteStream } = event; remoteStream.stop(); // 解除所有事件绑定 remoteStream.off("*"); const index = this.remoteStreamList.indexOf(remoteStream); if (index >= 0) { this.remoteStreamList.splice(index, 1); } console.log(`stream-removed userId: ${remoteStream.getUserId()} type: ${remoteStream.getType()}`); }); this.client.on("stream-updated", (event) => { const { stream: remoteStream } = event; console.log( `type: ${remoteStream.getType()} stream-updated hasAudio: ${remoteStream.hasAudio()} hasVideo: ${remoteStream.hasVideo()}` ); this.addSuccessLog( `RemoteStream updated: [${remoteStream.getUserId()}] audio:${remoteStream.hasAudio()}, video:${remoteStream.hasVideo()}.` ); }); this.client.on("mute-audio", (event) => { const { userId } = event; console.log(`${userId} mute audio`); this.userLog(event.userId, "麦克风已关闭"); // [xxx]麦克风/摄像头已关闭 09:45 }); this.client.on("unmute-audio", (event) => { const { userId } = event; console.log(`${userId} unmute audio`); this.userLog(event.userId, "麦克风已开启"); }); this.client.on("mute-video", (event) => { const { userId } = event; console.log(`${userId} mute video`); this.userLog(event.userId, "摄像头已关闭"); }); this.client.on("unmute-video", (event) => { const { userId } = event; console.log(`${userId} unmute video`); this.userLog(event.userId, "摄像头已开启"); }); this.client.on("connection-state-changed", (event) => { console.log(`RtcClient state changed to ${event.state} from ${event.prevState}`); }); this.client.on("network-quality", (event) => { const { uplinkNetworkQuality, downlinkNetworkQuality } = event; console.log( `network-quality uplinkNetworkQuality: ${uplinkNetworkQuality}, downlinkNetworkQuality: ${downlinkNetworkQuality}` ); }); }, }, };