diff --git a/PAY.md b/PAY.md index 4444eee..0862dab 100644 --- a/PAY.md +++ b/PAY.md @@ -16,7 +16,7 @@ - **价格:** 按功能大小,紧急程度,耗时,定制内容是否允许开源,等情况收费 ## 收费模式 -- **按小时计费:** 根据实际工作时间计算费用,目前个人定制功能2小时起,每小时200~300,企业定制另谈 +- **按小时计费:** 根据实际工作时间计算费用,目前个人定制功能2小时起,每小时200~300 - **阶段性付费:** 确认开发前,预付1/3,开发完成付1/3,交付上线1/3 ## 付款方式 diff --git a/svr/conf/cfg.json b/svr/conf/cfg.json index 0067843..9070541 100644 --- a/svr/conf/cfg.json +++ b/svr/conf/cfg.json @@ -1,5 +1,5 @@ { - "version": "10.2.5", + "version": "10.2.6", "ws": { "port": 8444, "host": "ws://127.0.0.1:8444" diff --git a/svr/res/css/index.css b/svr/res/css/index.css index 20da8ad..0c0a28d 100644 --- a/svr/res/css/index.css +++ b/svr/res/css/index.css @@ -284,7 +284,7 @@ body { } .tl-rtc-file-user-body { - display: flex; + display: -webkit-box; padding-top: 10px; } @@ -1134,3 +1134,28 @@ body { border-radius: 5px; } + +.remote_user_info{ + text-align: left; + padding: 20px 20px 0 20px; +} + +.remote_user_info div{ + padding-bottom: 5px; + font-weight: 200; + word-break: break-all; +} + +.remote_user_info div b{ + color: #548726; + margin-left: 5px; +} + +.layui-carousel[lay-indicator=outside] .layui-carousel-ind{ + top: 10px; +} + +.layui-carousel>[carousel-item]{ + overflow-y: auto; + overflow-x: hidden; +} diff --git a/svr/res/home.html b/svr/res/home.html index d535e30..1727569 100644 --- a/svr/res/home.html +++ b/svr/res/home.html @@ -7,8 +7,8 @@ - - + + diff --git a/svr/res/index.html b/svr/res/index.html index 6fef5ae..adb33b8 100644 --- a/svr/res/index.html +++ b/svr/res/index.html @@ -77,7 +77,7 @@ @@ -90,14 +90,14 @@ {{lang.sharing}}: {{screenShareTimes < 10 ? '0' + screenShareTimes : screenShareTimes}}{{lang.second}} - - {{lang.videoing}}: {{videoShareTimes < 10 ? '0' + videoShareTimes : - videoShareTimes}}{{lang.second}} - - {{lang.living}}: {{liveShareTimes < 10 ? '0' + liveShareTimes : - liveShareTimes}}{{lang.second}} - + + {{lang.videoing}}: {{videoShareTimes < 10 ? '0' + videoShareTimes : + videoShareTimes}}{{lang.second}} + + {{lang.living}}: {{liveShareTimes < 10 ? '0' + liveShareTimes : + liveShareTimes}}{{lang.second}} + @@ -258,7 +258,6 @@ {{lang.pickup_code}} -
@@ -316,10 +315,12 @@
- 【{{lang.owner}}】- - {{nickName}} - {{lang.self}} + 【{{lang.owner}}】- + 【{{lang.self}}】- {{nickName}} + + + {{socketId}} - {{socketId}}
@@ -346,7 +347,7 @@
-
+
【{{lang.owner}}】- - {{remote.nickName}} + {{remote.nickName}} - {{remote.id}} + + + : {{remote.langMode}} - {{remote.ua}} - {{remote.network}} - + : + {{remote.iceConnectionState}} - {{remote.p2pMode}} + {{remote.iceConnectionState}} - {{remote.p2pMode}} - {{remote.langMode}} - {{remote.id}}
- @@ -424,7 +434,7 @@
+ :style="{height: sendFileRecoderHeight+'px',overflowY: (sendFileRecoderList.length > 1 ? 'auto' : 'none') }">
@@ -615,7 +625,7 @@ style="cursor: pointer; right: 10px;position: absolute;">
+ :style="{height: chooseFileHeight+'px',overflowY: (chooseFileList.length > 1 ? 'auto' : 'none') }">
@@ -691,7 +701,7 @@ style="cursor: pointer; right: 10px;position: absolute;">
+ :style="{height: sendFileRecoderHistoryHeight+'px',overflowY: (sendFileRecoderHistoryList.length > 1 ? 'auto' : 'none') }">
@@ -771,7 +781,7 @@ style="cursor: pointer; right: 10px;position: absolute;">
+ :style="{height: receiveFileHeight+'px',overflowY: (receiveFileRecoderList.length > 1 ? 'auto' : 'none')}">
@@ -867,7 +877,7 @@
+ :style="{height: codeFileHeight+'px',overflowY: (receiveCodeFileList.length > 1 ? 'auto' : 'none') }">
@@ -955,7 +965,7 @@
-
+
{{log.time}}
@@ -964,7 +974,7 @@
+ style="max-height: 300px; overflow-y: auto;"> {{log.type}} {{log.type}} @@ -993,7 +1003,7 @@
-
+
@@ -1013,7 +1023,7 @@
-
+
@@ -1033,7 +1043,7 @@
-
+
@@ -1057,10 +1067,11 @@ layui.use([ 'layedit', 'form', 'layer', 'laytpl', 'upload', 'dropdown', 'carousel', 'util', 'colorpicker', - 'slider', 'dropdown' + 'slider', 'dropdown', 'carousel' ], function () { window.layer = layui.layer; window.form = layui.form; + window.carousel = layui.carousel; window.$ = layui.$; window.layedit = layui.layedit; window.laytpl = layui.laytpl; diff --git a/svr/res/js/comm.js b/svr/res/js/comm.js index ceb4e6c..0a1b4d5 100644 --- a/svr/res/js/comm.js +++ b/svr/res/js/comm.js @@ -286,38 +286,8 @@ window.tlrtcfile = { return false; } }, + getWebrtcStats: async function (peerConnection) { - // 候选者对 - "candidate-pair" | - // 证书相关的统计信息 - "certificate" | - // 当前音视频编解码器的统计信息 - "codec" | - // CSRC相关的统计信息 - "csrc" | - // 数据通道的相关统计信息 - "data-channel" | - // 传入数据流的相关统计信息 - "inbound-rtp" | - // 本地候选连接的相关统计信息 - "local-candidate" | - // 媒体源的相关统计信息 - "media-source" | - // 传出数据流的相关统计信息 - "outbound-rtp" | - // 对等连接的相关统计信息 - "peer-connection" | - // 对等连接的相关统计信息 - "remote-candidate" | - // 远程传入数据流的相关统计信息 - "remote-inbound-rtp" | - // 远程传出数据流的相关统计信息 - "remote-outbound-rtp" | - // 媒体轨道的相关统计信息 - "track" | - // 传输协议的相关统计信息 - "transport"; - if (!peerConnection) { return "RTCPeerConnection is not available"; } @@ -325,23 +295,63 @@ window.tlrtcfile = { return "RTCStatsReport is not available"; } - let result = { } - - let stats = await peerConnection.getStats(null); - - stats.forEach((report) => { - if (!report.type) return; - let data = {} - Object.keys(report).forEach((statName) => { - data[statName] = report[statName] - }); - result[report.type] = { - kind : report.kind, - data : data + function getTypeDescription(type) { + switch (type) { + case 'candidate-pair': + return '候选者对'; + case 'certificate': + return '证书相关的统计信息'; + case 'codec': + return '当前音视频编解码器的统计信息'; + case 'csrc': + return 'CSRC相关的统计信息'; + case 'data-channel': + return '数据通道的相关统计信息'; + case 'inbound-rtp': + return '传入数据流的相关统计信息'; + case 'local-candidate': + return '本地候选连接的相关统计信息'; + case 'media-source': + return '媒体源的相关统计信息'; + case 'outbound-rtp': + return '传出数据流的相关统计信息'; + case 'peer-connection': + return '对等连接的相关统计信息'; + case 'remote-candidate': + return '远程候选连接的相关统计信息'; + case 'remote-inbound-rtp': + return '远程传入数据流的相关统计信息'; + case 'remote-outbound-rtp': + return '远程传出数据流的相关统计信息'; + case 'track': + return '媒体轨道的相关统计信息'; + case 'transport': + return '传输协议的相关统计信息'; + case 'media-playout': + return '音频播放的相关统计数据' + default: + return '未知类型'; } - }); + } - return result + function getRTCStats(peerConnection) { + const statsMap = new Map(); + return new Promise((resolve) => { + peerConnection.getStats().then((stats) => { + stats.forEach((report) => { + const { type } = report; + if (!statsMap.has(type)) { + statsMap.set(type, []); + } + statsMap.get(type).push({ report, description: getTypeDescription(type) }); + }); + + resolve(statsMap); + }); + }); + } + + return await getRTCStats(peerConnection); }, copyTxt: function (id, content) { let that = this; diff --git a/svr/res/js/draw.js b/svr/res/js/draw.js index 6edc064..eb0205f 100644 --- a/svr/res/js/draw.js +++ b/svr/res/js/draw.js @@ -17,6 +17,8 @@ const draw = new Vue({ drawHistoryList: [], // 绘制历史操作列表, 用于回退 drawRollbackPoint: 0, // 绘制回退点 lineWidth: 1, // 画笔线宽 + lineCap : "round", + lineJoin : "round", strokeStyle: "#000000", // 画笔颜色 //line: 线条, circle: 圆形, rectangle: 矩形, text: 文字, delete: 擦除 drawMode: "line", // 画笔模式 @@ -26,7 +28,7 @@ const draw = new Vue({ starFill : false, //填充星星 rhomboidFill : false, //填充平行四边形 hexagonFill : false, //填充六边形 - circleStarPoint: { x: 0, y: 0 }, //圆形开始点 + circleStartPoint: { x: 0, y: 0 }, //圆形开始点 triangleStartPoint : { x: 0, y: 0 }, //三角形开始点 starStartPoint : { x: 0, y: 0 }, //星星开始点 rhomboidStartPoint : { x: 0, y: 0 }, //平行四边形开始点 @@ -106,39 +108,66 @@ const draw = new Vue({ if (!this.isOpenDraw) { return } + const { drawMode, event } = options; const canvas = document.getElementById('tl-rtc-file-mouse-draw-canvas'); - options.canvas = canvas; - options.context = canvas.getContext('2d'); - options.fromRemote = true; - - const { drawMode } = options; + const context = canvas.getContext('2d'); + options.remote.canvas = canvas; + options.remote.context = context; //收到结束标识,保存当前画板到缓存数据中 - if(options.event === 'end'){ - this.endDrawHandler(options) + if(event === 'end'){ + this.endDrawHandler({canvas, context}) return } + let { + width : remoteWidth, height : remoteHeight, lineWidth, + curPoint, prePoint, starStartPoint, circleStartPoint, triangleStartPoint, rectangleStartPoint + } = options.remote; + + //计算双方画布比例,按比例进行坐标放大/缩小 + const ratioWidth = canvas.width / remoteWidth; + const ratioHeight = canvas.height / remoteHeight; + + //调整画笔 + options.remote.lineWidth = lineWidth * (ratioWidth + ratioHeight) / 2 + curPoint.x = curPoint.x * ratioWidth; + curPoint.y = curPoint.y * ratioHeight; + options.remote.curPoint = curPoint; + if (drawMode === 'line') { + prePoint.x = prePoint.x * ratioWidth; + prePoint.y = prePoint.y * ratioHeight; + options.remote.prePoint = prePoint; this.drawLine(options); } else if (drawMode === 'circle') { + circleStartPoint.x = circleStartPoint.x * ratioWidth; + circleStartPoint.y = circleStartPoint.y * ratioHeight; + options.remote.circleStartPoint = circleStartPoint; this.drawCircle(options); } else if (drawMode === 'rectangle') { + rectangleStartPoint.x = rectangleStartPoint.x * ratioWidth; + rectangleStartPoint.y = rectangleStartPoint.y * ratioHeight; + options.remote.rectangleStartPoint = rectangleStartPoint; this.drawRectangle(options); } else if (drawMode === 'text') { this.drawText(options); } else if(drawMode === 'triangle'){ + triangleStartPoint.x = triangleStartPoint.x * ratioWidth; + triangleStartPoint.y = triangleStartPoint.y * ratioHeight; + options.remote.triangleStartPoint = triangleStartPoint; this.drawTriangle(options); } else if(drawMode === 'star'){ + starStartPoint.x = starStartPoint.x * ratioWidth; + starStartPoint.y = starStartPoint.y * ratioHeight; + options.remote.starStartPoint = starStartPoint; this.drawStar(options); } else { console.log("收到远程未知的绘制模式") } }, // 打开/关闭本地画笔 - openDraw: function ({ - openCallback, closeCallback, localDrawCallback - }) { + openDraw: function ({ openCallback, closeCallback, localDrawCallback }) { let that = this; if (this.isOpenDraw) { @@ -466,126 +495,185 @@ const draw = new Vue({ that.drawing = false; }; }, + // 开始绘制 startDrawHandler: function ({ canvas, context, localDrawCallback }) { //公共参数 - let commOptions = { - event : "start", + let localCommOptions = { canvas, context, localDrawCallback, - drawMode: this.drawMode, + devicePixelRatio : this.devicePixelRatio, + width : canvas.width, + height: canvas.height, + lineCap: this.lineCap, + lineJoin: this.lineJoin, lineWidth: this.lineWidth, strokeStyle: this.strokeStyle, fillStyle: this.strokeStyle, - lineCap: "round", - lineJoin: "round" } - + if (this.drawMode === 'delete') { - this.drawDelete(Object.assign(commOptions, { - prePoint: this.prePoint, - curPoint : this.prePoint - })) + this.drawDelete({ + event : "start", + drawMode: this.drawMode, + local : Object.assign(localCommOptions, { + prePoint: this.prePoint, + curPoint : this.prePoint + }), + }) } else if (this.drawMode === 'rectangle') { //开始的时候固定好矩形的起点 this.rectangleStartPoint = this.prePoint; - this.drawRectangle(Object.assign(commOptions, { - rectangleStartPoint: this.rectangleStartPoint, - curPoint: this.prePoint, - rectangleFill : this.rectangleFill - })) + this.drawRectangle({ + event : "start", + drawMode: this.drawMode, + local : Object.assign(localCommOptions, { + rectangleStartPoint: this.rectangleStartPoint, + curPoint: this.prePoint, + rectangleFill : this.rectangleFill + }), + }) } else if (this.drawMode === 'circle') { //开始的时候固定好圆的起点 this.circleStartPoint = this.prePoint; - this.drawCircle(Object.assign(commOptions, { - circleStartPoint: this.circleStartPoint, - curPoint: this.prePoint, - circleFill : this.circleFill, - })) + this.drawCircle({ + event : "start", + drawMode: this.drawMode, + local : Object.assign(localCommOptions, { + circleStartPoint: this.circleStartPoint, + curPoint: this.prePoint, + circleFill : this.circleFill, + }), + }) } else if(this.drawMode === 'triangle'){ //开始的时候固定好三角形的起点 this.triangleStartPoint = this.prePoint; - this.drawTriangle(Object.assign(commOptions, { - triangleStartPoint: this.triangleStartPoint, - curPoint: this.prePoint, - triangleFill : this.triangleFill - })); + this.drawTriangle({ + event : "start", + drawMode: this.drawMode, + local : Object.assign(localCommOptions, { + triangleStartPoint: this.triangleStartPoint, + curPoint: this.prePoint, + triangleFill : this.triangleFill + }), + }) } else if(this.drawMode === 'star'){ //开始的时候固定好星星的起点 this.starStartPoint = this.prePoint; - this.drawStar(Object.assign(commOptions, { - starStartPoint: this.starStartPoint, - curPoint: this.prePoint, - starFill : this.starFill - })); + this.drawStar({ + event : "start", + drawMode: this.drawMode, + local : Object.assign(localCommOptions, { + starStartPoint: this.starStartPoint, + curPoint: this.prePoint, + starFill : this.starFill + }), + }) } else if (this.drawMode === 'text') { - this.drawText(Object.assign(commOptions, { - curPoint: this.prePoint, - })) - this.endDrawHandler(Object.assign(commOptions, { + this.drawText({ + event : "start", + drawMode: this.drawMode, + local : Object.assign(localCommOptions, { + curPoint: this.prePoint, + }), + }) + this.endDrawHandler(Object.assign(localCommOptions, { curPoint: this.prePoint, })) } else if(this.drawMode === 'line'){ - this.drawLine(Object.assign(commOptions, { - prePoint: this.prePoint, - curPoint: this.prePoint, - })); + this.drawLine({ + event : "start", + drawMode: this.drawMode, + local : Object.assign(localCommOptions, { + prePoint: this.prePoint, + curPoint: this.prePoint, + }), + }) } }, + // 绘制中 drawingHandler: function ({ canvas, curPoint, context, localDrawCallback }) { if (!this.drawing) { return } + //公共参数 - let commOptions = { - event : "move", + let localCommOptions = { canvas, context, localDrawCallback, - drawMode: this.drawMode, + devicePixelRatio : this.devicePixelRatio, + width : canvas.width, + height: canvas.height, + lineCap: this.lineCap, + lineJoin: this.lineJoin, lineWidth: this.lineWidth, strokeStyle: this.strokeStyle, fillStyle: this.strokeStyle, - lineCap: "round", - lineJoin: "round" } if (this.drawMode === 'delete') { - this.drawDelete(Object.assign(commOptions, { - prePoint: this.prePoint, - curPoint - })) + this.drawDelete({ + event : "move", + drawMode: this.drawMode, + local : Object.assign(localCommOptions, { + prePoint: this.prePoint, + curPoint + }), + }) } else if (this.drawMode === 'rectangle') { - this.drawRectangle(Object.assign(commOptions, { - rectangleStartPoint: this.rectangleStartPoint, - curPoint, - rectangleFill : this.rectangleFill - })); + this.drawRectangle({ + event : "move", + drawMode: this.drawMode, + local : Object.assign(localCommOptions, { + rectangleStartPoint: this.rectangleStartPoint, + curPoint, + rectangleFill : this.rectangleFill + }), + }) } else if (this.drawMode === 'circle') { - this.drawCircle(Object.assign(commOptions, { - circleStartPoint: this.circleStartPoint, - curPoint, - circleFill : this.circleFill, - })) + this.drawCircle({ + event : "move", + drawMode: this.drawMode, + local : Object.assign(localCommOptions, { + circleStartPoint: this.circleStartPoint, + curPoint, + circleFill : this.circleFill, + }), + }) } else if(this.drawMode === 'triangle'){ - this.drawTriangle(Object.assign(commOptions, { - triangleStartPoint: this.triangleStartPoint, - curPoint, - triangleFill : this.triangleFill - })); + this.drawTriangle({ + event : "move", + drawMode: this.drawMode, + local : Object.assign(localCommOptions, { + triangleStartPoint: this.triangleStartPoint, + curPoint, + triangleFill : this.triangleFill + }), + + }) } else if(this.drawMode === 'star'){ - this.drawStar(Object.assign(commOptions, { - starStartPoint: this.starStartPoint, - curPoint, - starFill : this.starFill - })); + this.drawStar({ + event : "move", + drawMode: this.drawMode, + local : Object.assign(localCommOptions, { + starStartPoint: this.starStartPoint, + curPoint, + starFill : this.starFill + }), + }) } else if(this.drawMode === 'line'){ - this.drawLine(Object.assign(commOptions, { - prePoint: this.prePoint, - curPoint, - })); + this.drawLine({ + event : "move", + drawMode: this.drawMode, + local : Object.assign(localCommOptions, { + prePoint: this.prePoint, + curPoint, + }), + }) } }, + // 结束绘制 endDrawHandler: function ({ canvas, curPoint, context, localDrawCallback }) { //图像记录,用于回滚撤销操作 if ( @@ -600,13 +688,12 @@ const draw = new Vue({ //结束的时候通知下远程,可以保存画布到缓存列表中 localDrawCallback && localDrawCallback({ event : "end", - drawMode : this.drawMode + drawMode : this.drawMode, + remote : {} }) }, - //下载画布图片 - drawDownload: function ( options ) { - const { canvas, context, localDrawCallback } = options; - + // 下载画布图片 + drawDownload: function ({ canvas, context, localDrawCallback }) { let image = canvas.toDataURL("image/png").replace("image/png", "image/octet-stream"); let link = document.createElement('a'); link.href = image; @@ -614,16 +701,13 @@ const draw = new Vue({ link.click(); }, // 画布重置 - drawReset: function (options) { - const { canvas, context, localDrawCallback } = options; - + drawReset: function ({ canvas, context, localDrawCallback }) { context.clearRect(0, 0, canvas.width, canvas.height); this.drawHistoryList = []; this.drawRollbackPoint = 0; }, // 回退回滚的绘制 - drawUndoRollback: function (options) { - const { canvas, context, localDrawCallback } = options; + drawUndoRollback: function ({ canvas, context, localDrawCallback }) { //最多前进到最后一条记录 if (this.drawRollbackPoint < this.drawHistoryList.length - 1) { this.drawRollbackPoint = this.drawRollbackPoint + 1; @@ -637,8 +721,7 @@ const draw = new Vue({ } }, // 画布回退 - drawRollback: async function (options) { - const { canvas, context, localDrawCallback } = options; + drawRollback: async function ({ canvas, context, localDrawCallback }) { //最多回退到原点 if (this.drawRollbackPoint > 0) { this.drawRollbackPoint = this.drawRollbackPoint - 1; @@ -651,9 +734,17 @@ const draw = new Vue({ } }, // 画笔擦除 - drawDelete: function (options) { - const { canvas, context, prePoint, curPoint, localDrawCallback } = options; - context.lineWidth = this.lineWidth; + drawDelete: function ({ event, drawMode, local : { + canvas, context, lineWidth, curPoint, prePoint, lineCap, + width, height, devicePixelRatio, + lineJoin, strokeStyle, fillStyle + }}) { + context.lineWidth = lineWidth; + context.lineCap = lineCap; + context.lineJoin = lineJoin; + context.strokeStyle = strokeStyle; + context.fillStyle = fillStyle; + //防止移动过快,canvas渲染存在间隔,导致线条断层,在这里补充绘制间隔的点 let x = prePoint.x; let y = prePoint.y; @@ -666,14 +757,17 @@ const draw = new Vue({ while (i < distance) { x += xUnit; y += yUnit; - context.clearRect(x - 20, y - 20, this.lineWidth, this.lineWidth); + context.clearRect(x - 20, y - 20, lineWidth, lineWidth); i++; } }, // 图片渲染处理 - drawImage: function (options) { + drawImage: function ({ event, drawMode, local : { + canvas, context, lineWidth, curPoint, prePoint, lineCap, + width, height, devicePixelRatio, + lineJoin, strokeStyle, fillStyle, localDrawCallback + }}) { let that = this; - const { canvas, context, localDrawCallback } = options; let input = document.createElement("input"); input.setAttribute("type", "file"); input.setAttribute("accept", "image/*"); @@ -695,11 +789,14 @@ const draw = new Vue({ } }, // 画笔渲染处理, 两点式绘制 - drawLine: function (options) { - const { - canvas, context, localDrawCallback, prePoint, curPoint, - lineWidth, strokeStyle, fillStyle, lineCap, lineJoin, fromRemote, - } = options; + drawLine: function ({ event, drawMode, fromRemote, local, remote}) { + if(fromRemote){ + local = remote; + } + let { + canvas, context, lineWidth, curPoint, prePoint, lineCap, + width, height, devicePixelRatio, lineJoin, strokeStyle, fillStyle, localDrawCallback + } = local; // 设置画笔样式 context.lineWidth = lineWidth; @@ -728,16 +825,23 @@ const draw = new Vue({ i++; } - if (!fromRemote) { //本地的绘制数据回调给远端 - localDrawCallback && localDrawCallback(options); + //如果是本地绘制,完成绘制后数据回调给远端 + if (!fromRemote) { + localDrawCallback && localDrawCallback({ event, drawMode, fromRemote : true, remote : { + lineWidth, curPoint, prePoint, lineCap, width, height, devicePixelRatio, + lineJoin, strokeStyle, fillStyle, + }}); } }, // 星星渲染处理 - drawStar: function (options) { - const { - canvas, context, localDrawCallback, starStartPoint, curPoint, - lineWidth, strokeStyle, fillStyle, lineCap, lineJoin, fromRemote, starFill - } = options; + drawStar: function ({ event, drawMode, fromRemote, local, remote}) { + if(fromRemote){ + local = remote; + } + let { + canvas, context, lineWidth, curPoint, starStartPoint, lineCap, starFill, + width, height, devicePixelRatio, lineJoin, strokeStyle, fillStyle, localDrawCallback + } = local; // 设置画笔样式 context.lineWidth = lineWidth; @@ -778,16 +882,23 @@ const draw = new Vue({ } } - if (!fromRemote) { //本地的绘制数据回调给远端 - localDrawCallback && localDrawCallback(options); + //如果是本地绘制,完成绘制后数据回调给远端 + if (!fromRemote) { + localDrawCallback && localDrawCallback({ event, drawMode, fromRemote : true, remote : { + lineWidth, curPoint, starStartPoint, lineCap, width, height, devicePixelRatio, + lineJoin, strokeStyle, fillStyle, starFill + }}); } }, // 三角形渲染处理 - drawTriangle: function (options) { - const { - canvas, context, localDrawCallback, triangleStartPoint, curPoint, - lineWidth, strokeStyle, fillStyle, lineCap, lineJoin, fromRemote, triangleFill - } = options; + drawTriangle: function ({ event, drawMode, fromRemote, local, remote }) { + if(fromRemote){ + local = remote; + } + let { + canvas, context, lineWidth, curPoint, triangleStartPoint, lineCap, triangleFill, + width, height, devicePixelRatio, lineJoin, strokeStyle, fillStyle, localDrawCallback + } = local; // 设置画笔样式 context.lineWidth = lineWidth; @@ -827,16 +938,23 @@ const draw = new Vue({ } } - if (!fromRemote) { //本地的绘制数据回调给远端 - localDrawCallback && localDrawCallback(options); + //如果是本地绘制,完成绘制后数据回调给远端 + if (!fromRemote) { + localDrawCallback && localDrawCallback({ event, drawMode, fromRemote : true, remote : { + lineWidth, curPoint, triangleStartPoint, lineCap, width, height, devicePixelRatio, + lineJoin, strokeStyle, fillStyle, triangleFill + }}); } }, // 圆形渲染处理 - drawCircle: function (options) { - const { - canvas, context, localDrawCallback, circleStartPoint, curPoint, - lineWidth, strokeStyle, fillStyle, lineCap, lineJoin, fromRemote, circleFill - } = options; + drawCircle: function ({ event, drawMode, fromRemote, local, remote }) { + if(fromRemote){ + local = remote; + } + let { + canvas, context, lineWidth, curPoint, circleStartPoint, lineCap, circleFill, + width, height, devicePixelRatio, lineJoin, strokeStyle, fillStyle, localDrawCallback + } = local; // 设置画笔样式 context.lineWidth = lineWidth; @@ -869,16 +987,23 @@ const draw = new Vue({ } } - if (!fromRemote) { //本地的绘制数据回调给远端 - localDrawCallback && localDrawCallback(options); + //如果是本地绘制,完成绘制后数据回调给远端 + if (!fromRemote) { + localDrawCallback && localDrawCallback({ event, drawMode, fromRemote : true, remote : { + lineWidth, curPoint, circleStartPoint, lineCap, width, height, devicePixelRatio, + lineJoin, strokeStyle, fillStyle, circleFill + }}); } }, // 矩形渲染处理 - drawRectangle: function (options) { - const { - canvas, context, localDrawCallback, rectangleStartPoint, curPoint, - lineWidth, strokeStyle, fillStyle, lineCap, lineJoin, fromRemote, rectangleFill - } = options; + drawRectangle: function ({ event, drawMode, fromRemote, local, remote }) { + if(fromRemote){ + local = remote; + } + let { + canvas, context, lineWidth, curPoint, rectangleStartPoint, lineCap, rectangleFill, + width, height, devicePixelRatio, lineJoin, strokeStyle, fillStyle, localDrawCallback + } = local; // 设置画笔样式 context.lineWidth = lineWidth; @@ -890,8 +1015,8 @@ const draw = new Vue({ // 计算矩形的位置和尺寸 const x = Math.min(rectangleStartPoint.x, curPoint.x); const y = Math.min(rectangleStartPoint.y, curPoint.y); - const width = Math.abs(curPoint.x - rectangleStartPoint.x); - const height = Math.abs(curPoint.y - rectangleStartPoint.y); + const rwidth = Math.abs(curPoint.x - rectangleStartPoint.x); + const rheight = Math.abs(curPoint.y - rectangleStartPoint.y); if (this.drawRollbackPoint >= 0) { const img = new Image(); @@ -903,7 +1028,7 @@ const draw = new Vue({ context.drawImage(img, 0, 0, canvas.width, canvas.height); //绘制新矩形 context.beginPath(); - context.rect(x, y, width, height); + context.rect(x, y, rwidth, rheight); if(rectangleFill){ context.fill(); } @@ -911,43 +1036,58 @@ const draw = new Vue({ } } - if (!fromRemote) { //本地的绘制数据回调给远端 - localDrawCallback && localDrawCallback(options); + //如果是本地绘制,完成绘制后数据回调给远端 + if (!fromRemote) { + localDrawCallback && localDrawCallback({ event, drawMode, fromRemote : true, remote : { + lineWidth, curPoint, rectangleStartPoint, lineCap, width, height, devicePixelRatio, + lineJoin, strokeStyle, fillStyle, rectangleFill + }}); } }, // 文字渲染处理 - drawText: function (options) { - let { - canvas, context, localDrawCallback, curPoint, text, lineWidth, strokeStyle, - fillStyle, lineCap, lineJoin, fromRemote, - } = options; + drawText: function ({ event, drawMode, fromRemote, local, remote }) { + if(fromRemote){ + local = remote; + } + let { + canvas, context, lineWidth, curPoint, text, lineCap, + width, height, devicePixelRatio, lineJoin, strokeStyle, fillStyle, localDrawCallback + } = local; curPoint = { - x : curPoint.x / window.devicePixelRatio, - y : curPoint.y / window.devicePixelRatio + x : curPoint.x / devicePixelRatio, + y : curPoint.y / devicePixelRatio } // 设置字体样式 context.strokeStyle = strokeStyle; - context.font = "28px orbitron"; + context.font = Math.min(lineWidth * 7, 28) + "px orbitron"; context.textBaseline = "middle"; context.lineCap = lineCap; context.lineJoin = lineJoin; - context.lineWidth = 3; + context.lineWidth = 1; const canvasWidth = parseInt(canvas.style.width); const canvasHeight = parseInt(canvas.style.height); //文字渲染处理 function drawTextHandler(content){ - const textWidth = context.measureText(content).width; - // 如果文字超出画布宽度,将文字绘制到画布最右边 - let fixPointX = canvasWidth - textWidth > curPoint.x ? curPoint.x : canvasWidth - textWidth; - fixPointX = fixPointX < 10 ? 10 : fixPointX; - // 如果文字超出画布高度,将文字绘制到画布最下边 - let fixPointY = canvasHeight - 20 > curPoint.y ? curPoint.y : canvasHeight - 20; - fixPointY = fixPointY < 10 ? 10 : fixPointY; - context.strokeText(content, fixPointX * window.devicePixelRatio, fixPointY * window.devicePixelRatio); + let words = content.split(""); + let subWords = ""; + let wordHeight = 40; + for(let i = 0; i < words.length; i++){ + let curSubWords = subWords + words[i]; + const curSubWordsWidth = context.measureText(curSubWords).width; + + if(curPoint.x + curSubWordsWidth > canvasWidth && i > 0){ + context.fillText(subWords, curPoint.x * devicePixelRatio, curPoint.y * devicePixelRatio); + subWords = words[i]; + curPoint.y += wordHeight; + }else{ + subWords = curSubWords; + } + } + context.fillText(subWords, curPoint.x * devicePixelRatio, curPoint.y * devicePixelRatio); } if(fromRemote){ @@ -983,7 +1123,7 @@ const draw = new Vue({ } else if (curPoint.y < 100) { textarea.style.bottom = (canvasHeight - 100) + 'px'; } else { - textarea.style.top = (curPoint.y + 70)+ 'px'; + textarea.style.top = (curPoint.y + 170)+ 'px'; } parentDom.appendChild(textarea); textarea.focus(); @@ -992,8 +1132,11 @@ const draw = new Vue({ if (textarea.value !== '') { drawTextHandler(textarea.value); document.getElementById("drawLine").click() - options.text = textarea.value; - localDrawCallback && localDrawCallback(options); + + localDrawCallback && localDrawCallback({ event, drawMode, fromRemote : true, remote : { + lineWidth, curPoint, lineCap, width, height, devicePixelRatio, text : textarea.value, + lineJoin, strokeStyle, fillStyle, + }}); } }) } diff --git a/svr/res/js/index.js b/svr/res/js/index.js index 00ca647..1474e1b 100644 --- a/svr/res/js/index.js +++ b/svr/res/js/index.js @@ -204,8 +204,102 @@ axios.get("/api/comm/initData?turn="+useTurn, {}).then((initData) => { } }, methods: { + updateRemoteRtcState : async function(){ + for(let id in this.remoteMap){ + let stat = await window.tlrtcfile.getWebrtcStats( + this.getOrCreateRtcConnect(id) + ); + let remoteCandidate = stat.get("remote-candidate") || []; + let p2pModes = remoteCandidate.map(item => { + if(['host','srflx','prflx'].includes(item.report.candidateType)){ + return "直连" + }else if(item.report.candidateType === 'relay'){ + return "中继" + }else{ + return "未知" + } + }) + this.setRemoteInfo(id, { + //p2p连接模式: host, srflx, prflx, relay + p2pMode : Array.from(new Set(p2pModes)).join(",") + }) + } + this.$forceUpdate() + }, + showRemoteUser : async function(remote){ + let stat = await window.tlrtcfile.getWebrtcStats(this.getOrCreateRtcConnect(remote.id)); + const rtcStatList = []; + stat.forEach((value, key)=>{ + rtcStatList.push( + ...value.map(item => { + return Object.assign(item.report, { + description_zh: item.description + }) + }) + ) + }) + let rtcStatDomList = ''; + rtcStatList.forEach(statItem => { + let rtcStatDomVal = `
description_zh: ${statItem.description_zh}
`; + for(let key in statItem){ + if(key === 'description_zh'){ + continue + } + rtcStatDomVal += `
${key}: ${statItem[key]}
`; + } + rtcStatDomList += `
${rtcStatDomVal}
`; + }) + + let that = this; + layer.closeAll(function () { + layer.open({ + type: 1, + closeBtn: 0, + fixed: true, + maxmin: false, + shadeClose: true, + area: ['350px', '380px'], + title: `rtc连接实时统计信息`, + success: function (layero, index) { + document.querySelector(".layui-layer-title").style.borderTopRightRadius = "8px"; + document.querySelector(".layui-layer-title").style.borderTopLeftRadius = "8px"; + document.querySelector(".layui-layer").style.borderRadius = "8px"; + document.querySelector(".layui-layer").style.background = "#f8f8f8"; + + carousel.render({ + elem: '#tl-rtc-file-rtcinfo', + width: '100%', + autoplay : false, + indicator: 'outside' + }); + }, + content: ` + + ` + }) + }) + }, + iceOk : function(state){ + return ['completed', 'connected', 'checking', 'new'].includes(state); + }, consoleLogo : function(){ - window.console.log(`%c____ TL-RTC-FILE-V10.1.5 ____ \n____ FORK ME IN GITHUB ____ \n____ https://github.com/tl-open-source/tl-rtc-file ____`, this.logo) + window.console.log(`%c____ TL-RTC-FILE-V${this.version} ____ \n____ FORK ME IN GITHUB ____ \n____ https://github.com/tl-open-source/tl-rtc-file ____`, this.logo) }, changeLanguage: function () { let that = this; @@ -291,9 +385,7 @@ axios.get("/api/comm/initData?turn="+useTurn, {}).then((initData) => { let file = filterFile[0] if (file.size > this.uploadCodeFileMaxSize) { - if(window.layer){ - layer.msg(`${this.lang.max_saved} ${this.uploadCodeFileMaxSize / 1024 / 1024} ${this.lang.mb_file}`); - } + layer.msg(`${this.lang.max_saved} ${this.uploadCodeFileMaxSize / 1024 / 1024} ${this.lang.mb_file}`); return } @@ -363,7 +455,8 @@ axios.get("/api/comm/initData?turn="+useTurn, {}).then((initData) => { this.initSendFile(recoder); }, // 私聊弹窗 - startChatRoomSingle: function(remote){ + startChatRoomSingle: function(event, remote){ + event.stopPropagation(); this.chatRoomSingleSocketId = remote.id; let that = this; let options = { @@ -433,7 +526,7 @@ axios.get("/api/comm/initData?turn="+useTurn, {}).then((initData) => {
- shift+enter ${this.lang.enter_send} + shift+enter | ${this.lang.enter_send}
` @@ -626,7 +719,7 @@ axios.get("/api/comm/initData?turn="+useTurn, {}).then((initData) => { let fileRecorde = filterFile[0]; if (fileRecorde.size > this.previewFileMaxSize) { - layer.msg(`${this.lang.max_previewed} ${this.previewFileMaxSize / 1024 / 1024} ${mb_file}`); + layer.msg(`${this.lang.max_previewed} ${this.previewFileMaxSize / 1024 / 1024} ${this.lang.mb_file}`); return } @@ -853,7 +946,7 @@ axios.get("/api/comm/initData?turn="+useTurn, {}).then((initData) => {
- shift+enter ${this.lang.enter_send} + shift+enter | ${this.lang.enter_send}
` @@ -1007,11 +1100,11 @@ axios.get("/api/comm/initData?turn="+useTurn, {}).then((initData) => { layer.prompt({ formType: 1, - title: this.lang.please_enter_password + title: that.lang.please_enter_password }, function (value, index, elem) { that.createPasswordRoom(value); layer.close(index); - that.addUserLogs(this.lang.enter_password_room + that.roomId + `,${this.lang.password}:` + value); + that.addUserLogs(that.lang.enter_password_room + that.roomId + `,${that.lang.password}:` + value); }); }); } @@ -1124,12 +1217,12 @@ axios.get("/api/comm/initData?turn="+useTurn, {}).then((initData) => {
-

${this.lang.relay_server_current} ${useTurn ? this.lang.on : this.lang.off}

-

${this.lang.relay_server_current_detail}

-
+

${this.lang.relay_server_current} '${useTurn ? this.lang.on : this.lang.off}'

+

${this.lang.relay_server_current_detail}

+
- - + +
@@ -1180,11 +1273,11 @@ axios.get("/api/comm/initData?turn="+useTurn, {}).then((initData) => { }); that.clickMediaVideo(); that.isVideoShare = !that.isVideoShare; - that.addUserLogs(this.lang.start_video_call); + that.addUserLogs(that.lang.start_video_call); }else{ layer.prompt({ formType: 1, - title: this.lang.please_enter_video_call_room_num + title: that.lang.please_enter_video_call_room_num }, function (value, index, elem) { that.roomId = value; that.createMediaRoom("video"); @@ -1197,7 +1290,7 @@ axios.get("/api/comm/initData?turn="+useTurn, {}).then((initData) => { }); that.clickMediaVideo(); that.isVideoShare = !that.isVideoShare; - that.addUserLogs(this.lang.start_video_call); + that.addUserLogs(that.lang.start_video_call); }); } }, @@ -1239,7 +1332,7 @@ axios.get("/api/comm/initData?turn="+useTurn, {}).then((initData) => { }); that.clickMediaScreen(); that.isScreenShare = !that.isScreenShare; - that.addUserLogs(this.lang.start_screen_sharing); + that.addUserLogs(that.lang.start_screen_sharing); }else{ layer.prompt({ formType: 1, @@ -1256,7 +1349,7 @@ axios.get("/api/comm/initData?turn="+useTurn, {}).then((initData) => { }); that.clickMediaScreen(); that.isScreenShare = !that.isScreenShare; - that.addUserLogs(this.lang.this.lang.start_screen_sharing); + that.addUserLogs(that.lang.start_screen_sharing); }); } }, @@ -1289,9 +1382,25 @@ axios.get("/api/comm/initData?turn="+useTurn, {}).then((initData) => { return } let that = this; - if (window.layer) { - if(that.isShareJoin){ //分享进入 + if(that.isShareJoin){ //分享进入 + that.createMediaRoom("live"); + that.socket.emit('message', { + emitType: "startLiveShare", + room: that.roomId, + to : that.socketId + }); + that.clickMediaLive(); + that.isLiveShare = !that.isLiveShare; + that.addUserLogs(that.lang.start_live); + }else{ + layer.prompt({ + formType: 1, + title: this.lang.please_enter_live_room_num, + }, function (value, index, elem) { + that.roomId = value; that.createMediaRoom("live"); + layer.close(index) + that.socket.emit('message', { emitType: "startLiveShare", room: that.roomId, @@ -1299,30 +1408,13 @@ axios.get("/api/comm/initData?turn="+useTurn, {}).then((initData) => { }); that.clickMediaLive(); that.isLiveShare = !that.isLiveShare; - that.addUserLogs(this.lang.start_live); - }else{ - layer.prompt({ - formType: 1, - title: this.lang.please_enter_live_room_num, - }, function (value, index, elem) { - that.roomId = value; - that.createMediaRoom("live"); - layer.close(index) - - that.socket.emit('message', { - emitType: "startLiveShare", - room: that.roomId, - to : that.socketId - }); - that.clickMediaLive(); - that.isLiveShare = !that.isLiveShare; - that.addUserLogs(this.lang.start_live); - }); - } + that.addUserLogs(that.lang.start_live); + }); } }, // 打开画笔 openRemoteDraw : function(){ + let that = this; if (!this.switchData.openRemoteDraw) { layer.msg(this.lang.feature_close) this.addUserLogs(this.lang.feature_close) @@ -1338,26 +1430,26 @@ axios.get("/api/comm/initData?turn="+useTurn, {}).then((initData) => { // 触发draw.js中的方法 window.Bus.$emit("openDraw", { openCallback: () => { - this.socket.emit('message', { + that.socket.emit('message', { emitType: "startRemoteDraw", - room: this.roomId, - to: this.socketId + room: that.roomId, + to: that.socketId }); }, closeCallback: (drawCount) => { - this.socket.emit('message', { + that.socket.emit('message', { emitType: "stopRemoteDraw", - room: this.roomId, - to: this.socketId, + room: that.roomId, + to: that.socketId, drawCount : drawCount }); }, localDrawCallback : (data) => { - Object.entries(this.remoteMap).forEach(([id, remote]) => { + Object.entries(that.remoteMap).forEach(([id, remote]) => { if(remote && remote.sendDataChannel){ const sendDataChannel = remote.sendDataChannel; if (!sendDataChannel || sendDataChannel.readyState !== 'open') { - this.addSysLogs("sendDataChannel error in draw") + that.addSysLogs("sendDataChannel error in draw") return; } sendDataChannel.send(JSON.stringify(data)); @@ -1387,15 +1479,15 @@ axios.get("/api/comm/initData?turn="+useTurn, {}).then((initData) => { openCallback : () => { that.socket.emit('message', { emitType: "startScreen", - room: this.roomId, - to : this.socketId + room: that.roomId, + to : that.socketId }); - that.addUserLogs(this.lang.start_local_screen_recording); + that.addUserLogs(that.lang.start_local_screen_recording); }, closeCallback : (res) => { - this.receiveFileRecoderList.push({ - id: this.lang.web_screen_recording, - nickName : this.nickName, + that.receiveFileRecoderList.push({ + id: that.lang.web_screen_recording, + nickName : that.nickName, href: res.src, style: 'color: #ff5722;text-decoration: underline;', name: 'screen-recording-' + res.donwId + '.mp4', @@ -1406,14 +1498,14 @@ axios.get("/api/comm/initData?turn="+useTurn, {}).then((initData) => { start: 0, cost: res.times }) - this.socket.emit('message', { + that.socket.emit('message', { emitType: "stopScreen", - to : this.socketId, - room: this.roomId, + to : that.socketId, + room: that.roomId, size: res.size, cost: res.times }); - this.addUserLogs(this.lang.end_local_screen_recording); + that.addUserLogs(that.lang.end_local_screen_recording); } }); }, @@ -1461,7 +1553,7 @@ axios.get("/api/comm/initData?turn="+useTurn, {}).then((initData) => {
- shift+enter ${this.lang.enter_send} + shift+enter | ${this.lang.enter_send}
` @@ -1617,7 +1709,7 @@ axios.get("/api/comm/initData?turn="+useTurn, {}).then((initData) => {
- shift+enter ${this.lang.enter_send} + shift+enter | ${this.lang.enter_send}
` @@ -2118,7 +2210,9 @@ axios.get("/api/comm/initData?turn="+useTurn, {}).then((initData) => { type : 'password', password : '', nickName : this.nickName, - langMode : this.langMode + langMode : this.langMode, + ua: this.isMobile ? 'mobile' : 'pc', + network : this.network }); this.isJoined = true; this.addPopup({ @@ -2147,7 +2241,9 @@ axios.get("/api/comm/initData?turn="+useTurn, {}).then((initData) => { room: this.roomId, type: type, nickName : this.nickName, - langMode : this.langMode + langMode : this.langMode, + ua: this.isMobile ? 'mobile' : 'pc', + network : this.network }); this.isJoined = true; this.roomType = type; @@ -2183,7 +2279,9 @@ axios.get("/api/comm/initData?turn="+useTurn, {}).then((initData) => { type : 'password', password: password, nickName : this.nickName, - langMode : this.langMode + langMode : this.langMode, + ua: this.isMobile ? 'mobile' : 'pc', + network : this.network }); this.isJoined = true; this.addPopup({ @@ -2225,12 +2323,19 @@ axios.get("/api/comm/initData?turn="+useTurn, {}).then((initData) => { rtcConnect.oniceconnectionstatechange = (e) => { that.addSysLogs("iceConnectionState: " + rtcConnect.iceConnectionState); + that.setRemoteInfo(id, { + iceConnectionState : rtcConnect.iceConnectionState + }) } //保存peer连接 this.rtcConns[id] = rtcConnect; if (!this.remoteMap[id]) { - Vue.set(this.remoteMap, id, { id: id, receiveChatRoomSingleList : [] }) + Vue.set(this.remoteMap, id, { + id: id, + receiveChatRoomSingleList : [], + p2pMode : '识别中...' + }) } //数据通道 @@ -2467,11 +2572,20 @@ axios.get("/api/comm/initData?turn="+useTurn, {}).then((initData) => { }) } - // 缓冲区満了 + //缓冲区暂定 256kb + sendFileDataChannel.bufferedAmountLowThreshold = 16 * 1024 * 16; + //局域网一般不会走缓冲区,所以bufferedAmount一般为0,公网部分情况受限于带宽,bufferedAmount可能会逐渐堆积,从而进行排队 if (sendFileDataChannel.bufferedAmount > sendFileDataChannel.bufferedAmountLowThreshold) { - this.addSysLogs(this.lang.file_send_channel_buffer_full) + this.addSysLogs( + that.lang.file_send_channel_buffer_full + ",bufferedAmount=" + + sendFileDataChannel.bufferedAmount + ",bufferedAmountLowThreshold=" + + sendFileDataChannel.bufferedAmountLowThreshold + ) sendFileDataChannel.onbufferedamountlow = () => { - this.addSysLogs(this.lang.file_send_channel_buffer_recover) + that.addSysLogs( + that.lang.file_send_channel_buffer_recover + ",bufferedAmount=" + + sendFileDataChannel.bufferedAmount + ) sendFileDataChannel.onbufferedamountlow = null; that.sendFileToRemoteByLoop(event); } @@ -2844,15 +2958,17 @@ axios.get("/api/comm/initData?turn="+useTurn, {}).then((initData) => { for (let i = 0; i < data.peers.length; i++) { let otherSocketId = data.peers[i].id; - let otherSocketIdNickName = data.peers[i].nickName; - let otherSocketIdLangMode = data.peers[i].langMode; - let otherSocketIdOwner = data.peers[i].owner; let rtcConnect = that.getOrCreateRtcConnect(otherSocketId); // 处理完连接后,更新下昵称 that.setRemoteInfo(otherSocketId, { - nickName : otherSocketIdNickName, - langMode : otherSocketIdLangMode, - owner : otherSocketIdOwner + nickName : data.peers[i].nickName, + langMode : data.peers[i].langMode, + owner : data.peers[i].owner, + ua : data.peers[i].ua, + joinTime : data.peers[i].joinTime, + userAgent : data.peers[i].userAgent, + ip : data.peers[i].ip, + network : data.peers[i].network, }) await new Promise(resolve => { @@ -2895,7 +3011,11 @@ axios.get("/api/comm/initData?turn="+useTurn, {}).then((initData) => { nickName : data.nickName, owner : data.owner, langMode : data.langMode, - owner : false + ua : data.ua, + network : data.network, + joinTime : data.joinTime, + userAgent : data.userAgent, + ip : data.ip, }) // 处理音视频逻辑 if (data.type === 'screen') { @@ -3656,10 +3776,14 @@ axios.get("/api/comm/initData?turn="+useTurn, {}).then((initData) => { this.addSysLogs(this.lang.basic_data_get_done); this.addSysLogs(this.lang.window_event_init); - window.onresize = this.touchResize; + window.onresize = this.touchResize; setInterval(() => { this.touchResize() }, 1000); + + setInterval(async () => { + await this.updateRemoteRtcState() + }, 5000); this.addSysLogs(this.lang.window_event_init_done); this.addSysLogs(this.lang.message_box_init); diff --git a/svr/res/js/language.js b/svr/res/js/language.js index 6e19fbd..9310c6c 100644 --- a/svr/res/js/language.js +++ b/svr/res/js/language.js @@ -314,8 +314,28 @@ const local_lang = { "you_refresh_room": "You refreshed the room number, the current room number is", "your_browser": "Your browser", "your_ip_list": "Your IP list is", + "nickname" : "Nickname", + "userid" : "Userd", + "room_channel" : "Room channel", + "join_time" : "Join time", + "website_language" : "Website language", + "terminal_equipment" : "Terminal equipment", + "device_classification" : "Device classification", + "network_status" : "Network status", + "public_ip" : "Public IP", + "webrtc_ice_state" : "webrtc state" }, "zh": { + "webrtc_ice_state" : "webrtc状态", + "nickname" : "用户昵称", + "userid" : "用户ID ", + "room_channel" : "房间频道", + "join_time" : "加入时间", + "website_language" : "网站语言", + "terminal_equipment" : "终端设备", + "device_classification" : "设备分类", + "network_status" : "网络状态", + "public_ip" : "传输地址", "add_ice_candidate_failed": "addIceCandidate失败", "add_ice_candidate_success": "addIceCandidateSuccess成功", "ai_answering": "AI正在回答您的问题,请稍后再问", @@ -445,9 +465,9 @@ const local_lang = { "not_support": "不支持", "note_website_for_learing": "注意:示例网站仅用于学习演示,请勿他用", "notice": "通知", - "off": "已关闭", + "off": "关闭", "offer_failed": "offer失败", - "on": "已开启", + "on": "开启", "online": "当前在线人数", "online_number": "人在线", "only_show" : "仅展示 ", @@ -553,7 +573,7 @@ const local_lang = { "select_wait_send_record": "选择待发送记录中", "selected_file": "已选择文件", "selected_file_exist": "选择的文件已经存在相同的文件,不再重复添加", - "self": "我自己", + "self": "自己", "send": "发送", "send_all": "一键发送", "send_alone": "单独发送", diff --git a/svr/res/pay.html b/svr/res/pay.html index e338466..e21e8da 100644 --- a/svr/res/pay.html +++ b/svr/res/pay.html @@ -63,7 +63,7 @@

收费模式

    -
  • 按小时计费:根据实际工作时间计算费用,目前个人定制功能2小时起,每小时200~300,企业定制另谈
  • +
  • 按小时计费:根据实际工作时间计算费用,目前个人定制功能2小时起,每小时200~300
  • 阶段性付费:确认开发前,预付1/3,开发完成付1/3,交付上线1/3
diff --git a/svr/src/socket/rtcCreateJoin/createJoin.js b/svr/src/socket/rtcCreateJoin/createJoin.js index 407b0ac..3d70176 100644 --- a/svr/src/socket/rtcCreateJoin/createJoin.js +++ b/svr/src/socket/rtcCreateJoin/createJoin.js @@ -19,7 +19,10 @@ const check = require("./../../utils/check/content"); async function userCreateAndJoin(io, socket, tables, dbClient, data){ let {handshake, userAgent, ip} = utils.getSocketClientInfo(socket); - let {room, type, nickName, password = '', langMode = 'zh'} = data; + let { + room, type = 'file', nickName = '', password = '', + langMode = 'zh', ua = '', network = '' + } = data; if (room && room.length > 15) { room = room.toString().substr(0, 14); @@ -28,14 +31,37 @@ async function userCreateAndJoin(io, socket, tables, dbClient, data){ if(nickName && nickName.length > 20){ nickName = nickName.substr(0, 20); } - //设置昵称 - io.sockets.connected[socket.id].nickName = nickName; - io.sockets.connected[socket.id].langMode = langMode; + + if(['zh', 'en'].indexOf(langMode) === -1){ + langMode = 'zh' + } + + if(['file', 'screen', 'video', 'password', 'live'].indexOf(type) === -1){ + type = 'file' + } + + if(['pc', 'mobile'].indexOf(ua) === -1){ + ua = 'pc'; + } + + if(['wifi', '4g', '3g', '2g', '5g'].indexOf(network) === -1){ + network = '2g'; + } if(password && password.length > 6){ password = password.toString().substr(0,6); } + //设置连接信息 + io.sockets.connected[socket.id].nickName = nickName; + io.sockets.connected[socket.id].langMode = langMode; + io.sockets.connected[socket.id].ua = ua; + io.sockets.connected[socket.id].network = network; + io.sockets.connected[socket.id].ip = ip; + const joinTime = utils.formateDateTime(new Date(), "yyyy-MM-dd hh:mm:ss") + io.sockets.connected[socket.id].joinTime = joinTime; + io.sockets.connected[socket.id].userAgent = userAgent; + let recoderId = await daoRoom.createJoinRoom({ uid: "1", uname: nickName, @@ -105,6 +131,11 @@ async function userCreateAndJoin(io, socket, tables, dbClient, data){ type: type, recoderId : recoderId, langMode : langMode, + ua : ua, + network : network, + joinTime : joinTime, + ip : ip, + userAgent : userAgent }); let peers = new Array(); @@ -114,11 +145,22 @@ async function userCreateAndJoin(io, socket, tables, dbClient, data){ let peerNickName = io.sockets.connected[otherSocketId].nickName let peerOwner = io.sockets.connected[otherSocketId].owner let peerLangMode = io.sockets.connected[otherSocketId].langMode + let peerUa = io.sockets.connected[otherSocketId].ua + let peerNetwork = io.sockets.connected[otherSocketId].network + let peerJoinTime = io.sockets.connected[otherSocketId].joinTime + let peerIp = io.sockets.connected[otherSocketId].ip + let peerUserAgent = io.sockets.connected[otherSocketId].userAgent + peers.push({ id: otherSocketId, nickName: peerNickName, owner : peerOwner, - langMode : peerLangMode + langMode : peerLangMode, + ua : peerUa, + network : peerNetwork, + joinTime : peerJoinTime, + ip : peerIp, + userAgent : peerUserAgent }); } diff --git a/svr/static/layui/font-ext/demo_index.html b/svr/static/layui/font-ext/demo_index.html index 918da38..3864f52 100644 --- a/svr/static/layui/font-ext/demo_index.html +++ b/svr/static/layui/font-ext/demo_index.html @@ -54,6 +54,30 @@
    +
  • + +
    数据汇总
    +
    &#xe61d;
    +
  • + +
  • + +
    控桩终端
    +
    &#xe6bb;
    +
  • + +
  • + +
    浏览器
    +
    &#xe6e8;
    +
  • + +
  • + +
    连接流
    +
    &#xec57;
    +
  • +
  • 三角形
    @@ -534,9 +558,9 @@
    @font-face {
       font-family: 'iconfont';
    -  src: url('iconfont.woff2?t=1688291642630') format('woff2'),
    -       url('iconfont.woff?t=1688291642630') format('woff'),
    -       url('iconfont.ttf?t=1688291642630') format('truetype');
    +  src: url('iconfont.woff2?t=1688799262496') format('woff2'),
    +       url('iconfont.woff?t=1688799262496') format('woff'),
    +       url('iconfont.ttf?t=1688799262496') format('truetype');
     }
     

    第二步:定义使用 iconfont 的样式

    @@ -562,6 +586,42 @@
      +
    • + +
      + 数据汇总 +
      +
      .icon-rtc-file-daohang-shujufenxi +
      +
    • + +
    • + +
      + 控桩终端 +
      +
      .icon-rtc-file-kongzhuangzhongduan +
      +
    • + +
    • + +
      + 浏览器 +
      +
      .icon-rtc-file-liulanqi +
      +
    • + +
    • + +
      + 连接流 +
      +
      .icon-rtc-file-lianjieliu +
      +
    • +
    • @@ -1282,6 +1342,38 @@
        +
      • + +
        数据汇总
        +
        #icon-rtc-file-daohang-shujufenxi
        +
      • + +
      • + +
        控桩终端
        +
        #icon-rtc-file-kongzhuangzhongduan
        +
      • + +
      • + +
        浏览器
        +
        #icon-rtc-file-liulanqi
        +
      • + +
      • + +
        连接流
        +
        #icon-rtc-file-lianjieliu
        +
      • +