import mqtt from "mqtt"; import { setServerInfo, PublishVideo, initLibrary, SubscibeVideo } from "./utils/sdk"; class MQTT { /** * @constructor * @param options {{password: *, roomId: *, username: *}} * @param options.username {string} 登录用户名,登录接口返回的loginUser中的name * @param options.password {string} 登录用户密码,登录接口返回的loginUser中的password * @param options.mqttId {string} mqtt登录id,调度台的唯一id * @param options.roomId {string} 房间id */ constructor(options = {}) { const { username, password, mqttId, roomId } = options; // 视频服务器地址 this.server = process.env.VUE_APP_GWSD_WS_SERVER; // 信令控制 this.brokerUrl = process.env.VUE_APP_GWSD_CMD_SERVER; // 登录用户名,登录接口返回的loginUser中的name this.username = username; // 登录用户密码,登录接口返回的loginUser中的password this.password = password; // mqtt登录id,调度台的唯一id this.mqttId = mqttId; this.roomId = roomId; // 远程视频流 this.remoteVideos = {}; // 事件绑定 this._events = {}; // mqtt客户端 this.client = null; this.pushVideoObj = null; // 必填参数校验 optionsValidator(this, ["server", "brokerUrl", "username", "password", "mqttId"]); // 初始化 this.connect(this); } // 监听事件 on(event, listener) { if (typeof listener !== "function") { throw new TypeError("Listener must be a function"); } if (!this._events[event]) { this._events[event] = []; } this._events[event].push(listener); } // 触发事件 emit(event, ...args) { if (this._events[event]) { this._events[event].forEach((listener) => { listener.apply(this, args); }); } } // 连接服务器 connect() { // 初始化库 initLibrary((ok) => { // console.log("init video library succeed.", ok); setServerInfo(this.server); //配置服务器地址 setTimeout(() => { this.emit("init", ok); }, 0); }); // Step 1 信令服务器初始化 this.client = mqtt.connect(this.brokerUrl, { path: "/mqtt", username: this.username, password: this.password, }); // Step 2 订阅主题,id为调度台的唯一id this.client.on("connect", () => { // 连接成功订阅主题 this.client.subscribe(`target/pc/qos0/msg/${this.mqttId}`, (err) => { // console.log(err, "订阅上下线成功"); if (!err) { this.emit("subscribe"); } }); this.client.subscribe(`target/media/signal/${this.mqttId}`, (err) => { // console.log(err, "订阅语音视频"); if (!err) { this.emit("subscribeMedia"); } }); this.client.subscribe(`target/media/signal/${this.roomId}`, (err) => { console.log("err", "测试订阅成功", err); }); this.emit("connect"); }); // 监听终端信令,监听终端发过来的指令 this.client.on("message", (topic, message) => { let response = JSON.parse(message.toString()); // console.log("mqtt on message ===", response, topic); this.emit("message", { response, topic }); }); // 设置错误处理函数 this.client.on("error", (error) => { console.log(error); this.emit("error", error); }); } } function optionsValidator(target, requiredFields) { let invalidFields = []; requiredFields.forEach((field) => { if (target[field] === null || target[field === undefined] || target[field] === "") { invalidFields.push(field); } }); if (invalidFields.length > 0) { throw new Error(`参数${invalidFields.join(", ")}缺失!`); } } /** * 信令下发 * @param options {{toId: string, action: number, name: string, audioonly: boolean}} * @param options.toId 目标ID * @param options.action 0:发起呼叫 | 1:接听呼叫 | 2:挂断呼叫 * @param options.name 会话名称 * @param options.audioonly 是否仅音频通话 */ MQTT.prototype.publish = function (options = {}) { console.log(options); optionsValidator(options, ["toId", "action"]); let cmd = { cmd: "meeting", to: options.toId, //目标ID from: this.roomId, //本人ID name: options.name || "调度台", sessionId: "", fromUType: 1, timestamp: new Date().getTime(), data: { roomId: this.roomId, roomDesc: "", videoRatio: "1280*720", action: options.action, audioonly: options.audioonly || false, //仅音频通话 reason: "", }, }; console.log(JSON.stringify(cmd)); this.client.publish(`target/media/signal/${options.toId}`, JSON.stringify(cmd)); }; // 邀请参会人 MQTT.prototype.invite = function (options = {}) { this.publish({ ...options, action: 0 }); }; // 踢掉单个参会人 MQTT.prototype.kickOut = function (options) { this.publish({ ...options, action: 2 }); if (this.remoteVideos[options.toId]) { this.remoteVideos[options.toId].closeRemoteVideo(); delete this.remoteVideos[options.toId]; // const videoElem = document.getElementById(`remote-video-${options.toId}`); // if (videoElem) videoElem.remove(); } }; // 远程邀请连麦 MQTT.prototype.toSendMic = function (options) { this.publish({ ...options, action: 5 }); }; // 远程取消连麦 MQTT.prototype.toCancelMic = function (options) { this.publish({ ...options, action: 4 }); }; // 1 调度台发起 MQTT.prototype.publishLocalVideo = function (options = {}) { this.pushVideoObj = new PublishVideo(); this.pushVideoObj.init({ success: () => { this.emit("localVideoPush"); this.openLocalVideo(options); }, error: (err) => { this.emit("localVideoPushError", err); console.log("init publish video fail:" + err); }, }); }; MQTT.prototype.openLocalVideo = function (options = {}) { const { videoName, isAudio, isVideo } = options; this.pushVideoObj.openLocalVideo({ videoName: videoName || "调度台", // html视频控件 videoWnd: document.getElementById("local-video"), // !! 在view层写video标签,id一定要为【local-video】 !! roomId: this.roomId, isAudio, // 是否上传音频 isVideo, // 是否上传视频 success: (userId, mediaId) => { this.emit("localVideoOpen", { userId, mediaId }); }, videoClose: () => { console.log("videoClose", this.pushVideoObj); this.pushVideoObj = null; this.emit("localVideoClose"); }, error: (err) => { this.emit("localVideoError", err); console.log("open local video fail: " + err); }, }); }; MQTT.prototype.deleteRemote = function (userId) { if (this.remoteVideos[userId]) { delete this.remoteVideos[userId]; } }; // 关闭所有视频 MQTT.prototype.closeAllVideos = function () { console.log("close all videos", this.remoteVideos); Object.keys(this.remoteVideos).forEach((userId) => { if (this.remoteVideos[userId]) { console.log("remote subscribe video", this.remoteVideos[userId]); this.kickOut({ toId: userId }); // this.remoteVideos[userId].closeRemoteVideo(); delete this.remoteVideos[userId]; // const videoElem = document.getElementById(`remote-video-${userId}`); // if (videoElem) videoElem.remove(); } }); }; // 挂断 MQTT.prototype.leave = function () { try{ this.closeAllVideos(); // 销毁整个会话 this.pushVideoObj.closeLocalVideo(this.roomId); }catch(e){ console.log("销毁整个会话异常",e); } }; /** * 3 终端接收上面调度台发的mqtt后,给返回消息( * "data":{ * "mediaId":4065489132, * "roomId":4147994882254753, * "server":"ws://47.106.165.35:8188", * "userId":8706460590024001 * }为目标终端返回的数据) 给调度台,调度执行拉取音频操作即可进行双工会话 */ /** * 拉取远端视频 * @param userId 远端用户id */ MQTT.prototype.pullRemoteVideo = function (userId) { if (this.remoteVideos[userId]) { console.warn(`用户 ${userId} 的视频已存在`); return; } setServerInfo(this.server); //配置服务器地址 const pullCall = new SubscibeVideo(); pullCall.init({ success: () => { this.emit("pullRemoteSuccess"); pullCall.openRemoteVideo({ videoWnd: document.getElementById(`remote-video-${userId}`), // videoWnd: document.getElementById(`remote-${userId}`), roomId: this.roomId, userId, isAudio: true, isVideo: true, success: () => { console.log(`远程用户 ${userId} 视频打开成功!`); this.remoteVideos[userId] = pullCall; this.emit("remoteVideoOpen"); }, videoClose: () => { delete this.remoteVideos[userId]; // videoElem.remove(); this.emit("remoteVideoClose"); }, error: (err) => { this.emit("remoteVideoError", err); throw new Error(err); }, }); }, error: (err) => { this.emit("pullRemoteError", err); console.error("init pull video fail:" + err); }, }); }; export default MQTT;