feat: 支持单个rtc连接信息查看

feat: 支持远程连接语言,设备,网络,ice状态,ice类型展示
feat: 支持多端画布自适应比例
feat: 调整优化文本绘制
feat: 调整优化缓冲区满的日志
fix: 修复控制台版本打印
fix: 修复滚动条样式
fix: 修复页面多余节点
fix: 修复某些中英文引用
fix: 修复缓冲区满的阈值
This commit is contained in:
https://blog.iamtsm.cn
2023-07-16 12:40:44 +08:00
parent c18e87068e
commit 8a6ac7c26e
18 changed files with 841 additions and 330 deletions

2
PAY.md
View File

@@ -16,7 +16,7 @@
- **价格:** 按功能大小,紧急程度,耗时,定制内容是否允许开源,等情况收费
## 收费模式
- **按小时计费:** 根据实际工作时间计算费用目前个人定制功能2小时起每小时200~300,企业定制另谈
- **按小时计费:** 根据实际工作时间计算费用目前个人定制功能2小时起每小时200~300
- **阶段性付费:** 确认开发前预付1/3开发完成付1/3交付上线1/3
## 付款方式

View File

@@ -1,5 +1,5 @@
{
"version": "10.2.5",
"version": "10.2.6",
"ws": {
"port": 8444,
"host": "ws://127.0.0.1:8444"

View File

@@ -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;
}

View File

@@ -7,8 +7,8 @@
<meta name="renderer" content="webkit">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=0">
<script src="static/layui/layui.js" v="layui" e="layui"></script>
<link rel="stylesheet" type="text/css" href="static/layui/css/layui.css" />
<script src="/static/layui/layui.js" v="layui" e="layui"></script>
<link rel="stylesheet" type="text/css" href="/static/layui/css/layui.css" />
<link href="/image/44826979.png" rel="shortcut icon" type="image/x-icon">
</head>

View File

@@ -77,7 +77,7 @@
<use xlink:href="#icon-rtc-file-five-g"></use>
</svg>
<svg class="icon" aria-hidden="true" v-show="network === 'wifi'"
style="width: 20px;height: 20px;margin-right: 10px;">
style="scale: 1.3;width: 20px;height: 20px;margin-right: 10px;">
<use xlink:href="#icon-rtc-file-WIFI"></use>
</svg>
</a>
@@ -90,14 +90,14 @@
<b style="transition: color 0.8s;margin-right: 5px;" id="screenShareTimes" v-show="isScreenShare">
{{lang.sharing}}: {{screenShareTimes < 10 ? '0' + screenShareTimes :
screenShareTimes}}{{lang.second}} </b>
<b style="transition: color 0.8s;margin-right: 5px;" id="videoShareTimes" v-show="isVideoShare">
{{lang.videoing}}: {{videoShareTimes < 10 ? '0' + videoShareTimes :
videoShareTimes}}{{lang.second}} </b>
<b style="transition: color 0.8s;margin-right: 5px;" id="liveShareTimes"
v-show="isLiveShare">
{{lang.living}}: {{liveShareTimes < 10 ? '0' + liveShareTimes :
liveShareTimes}}{{lang.second}} </b>
<b style="transition: color 0.8s;margin-right: 5px;" id="screenTimes"></b>
<b style="transition: color 0.8s;margin-right: 5px;" id="videoShareTimes" v-show="isVideoShare">
{{lang.videoing}}: {{videoShareTimes < 10 ? '0' + videoShareTimes :
videoShareTimes}}{{lang.second}} </b>
<b style="transition: color 0.8s;margin-right: 5px;" id="liveShareTimes"
v-show="isLiveShare && owner">
{{lang.living}}: {{liveShareTimes < 10 ? '0' + liveShareTimes :
liveShareTimes}}{{lang.second}} </b>
<b style="transition: color 0.8s;margin-right: 5px;" id="screenTimes"></b>
</a>
</div>
@@ -258,7 +258,6 @@
<b>{{lang.pickup_code}}</b>
</div>
</div>
<div class="layui-col-xs3 swiper-slide" @click="openRemoteDraw"
:class="switchData.openRemoteDraw ? '':'tl-rtc-file-tool-disabled'">
<div class="tl-rtc-file-tool tl-rtc-file-tool-mobile" v-if="clientWidth < 450">
@@ -316,10 +315,12 @@
</svg>
<div class="tl-rtc-file-user-body-left">
<b class="tl-rtc-file-user-body-left-nick">
<b v-show="owner" style="color: #7375e9;">【{{lang.owner}}】- </b>
{{nickName}} - {{lang.self}}
<b v-show="owner" style="color: #7375e9;">【{{lang.owner}}】-</b>
{{lang.self}}- {{nickName}}
</b>
<b class="tl-rtc-file-user-body-left-id">
{{socketId}}
</b>
<b class="tl-rtc-file-user-body-left-id"> {{socketId}} </b>
</div>
</div>
<div class="tl-rtc-file-user-body-right">
@@ -346,7 +347,7 @@
<!-- 远端连接 -->
<div class="layui-row" style="position: relative;">
<div class="layui-col-xs12 tl-rtc-file-user-list" v-for="remote in remoteMap">
<div class="layui-col-xs12 tl-rtc-file-user-list" v-for="remote in remoteMap" @click="showRemoteUser(remote)">
<div class="tl-rtc-file-user">
<div class="tl-rtc-file-user-body">
<svg class="icon" aria-hidden="true" style="width: 32px;height: 32px;">
@@ -355,13 +356,22 @@
<div class="tl-rtc-file-user-body-left">
<b class="tl-rtc-file-user-body-left-nick">
<b v-show="remote.owner" style="color: #7375e9;">【{{lang.owner}}】- </b>
{{remote.nickName}}
{{remote.nickName}} - {{remote.id}}
</b>
<b class="tl-rtc-file-user-body-left-id">
<svg class="icon" aria-hidden="true" style="width: 12px;height: 12px;">
<use xlink:href="#icon-rtc-file-kongzhuangzhongduan"></use>
</svg> : {{remote.langMode}} - {{remote.ua}} - {{remote.network}} -
<svg class="icon" aria-hidden="true" style="width: 12px;height: 12px;">
<use xlink:href="#icon-rtc-file-yunfuwuqi"></use>
</svg> :
<b v-if="iceOk(remote.iceConnectionState)" style="font-weight: bold;color: #69d31e;">{{remote.iceConnectionState}} - {{remote.p2pMode}}</b>
<b v-else style="font-weight: bold;color: red;">{{remote.iceConnectionState}} - {{remote.p2pMode}}</b>
</b>
<b class="tl-rtc-file-user-body-left-id">{{remote.langMode}} - {{remote.id}} </b>
</div>
</div>
<div class="tl-rtc-file-user-body-right">
<i :title="lang.send_text" @click="startChatRoomSingle(remote)"
<i :title="lang.send_text" @click="startChatRoomSingle(event, remote)"
class="layui-icon layui-icon-reply-fill"></i>
<span v-show="remote.receiveChatRoomSingleList.length > 0" style="right: -7px;"
class="layui-badge tl-rtc-file-msg-dot layui-anim layui-anim-rotate layui-anim-loop">
@@ -424,7 +434,7 @@
</div>
<div class="layui-row tl-rtc-file-send-list-all"
:style="{height: sendFileRecoderHeight+'px',overflowY: (sendFileRecoderList.length > 1 ? 'scroll' : 'none') }">
:style="{height: sendFileRecoderHeight+'px',overflowY: (sendFileRecoderList.length > 1 ? 'auto' : 'none') }">
<div class="layui-col-xs12 tl-rtc-file-send-list send-file-item"
v-for="file, index in sendFileRecoderList" :id="'send-file-item'+index">
<div class="tl-rtc-file-send" :style="{'--progress': file.progress + '%'}">
@@ -615,7 +625,7 @@
style="cursor: pointer; right: 10px;position: absolute;"></i>
</div>
<div class="layui-row tl-rtc-file-send-list-all"
:style="{height: chooseFileHeight+'px',overflowY: (chooseFileList.length > 1 ? 'scroll' : 'none') }">
:style="{height: chooseFileHeight+'px',overflowY: (chooseFileList.length > 1 ? 'auto' : 'none') }">
<div class="layui-col-xs12 tl-rtc-file-send-list" v-for="file, index in chooseFileList">
<div class="tl-rtc-file-send" :style="{'--progress': file.progress + '%'}">
<div class="tl-rtc-file-send-body">
@@ -691,7 +701,7 @@
style="cursor: pointer; right: 10px;position: absolute;"></i>
</div>
<div class="layui-row tl-rtc-file-send-list-all"
:style="{height: sendFileRecoderHistoryHeight+'px',overflowY: (sendFileRecoderHistoryList.length > 1 ? 'scroll' : 'none') }">
:style="{height: sendFileRecoderHistoryHeight+'px',overflowY: (sendFileRecoderHistoryList.length > 1 ? 'auto' : 'none') }">
<div class="layui-col-xs12 tl-rtc-file-send-list" v-for="file, index in sendFileRecoderHistoryList">
<div class="tl-rtc-file-send" :style="{'--progress': file.progress + '%'}">
<div class="tl-rtc-file-send-body">
@@ -771,7 +781,7 @@
style="cursor: pointer; right: 10px;position: absolute;"></i>
</div>
<div class="layui-row tl-rtc-file-send-list-all"
:style="{height: receiveFileHeight+'px',overflowY: (receiveFileRecoderList.length > 1 ? 'scroll' : 'none')}">
:style="{height: receiveFileHeight+'px',overflowY: (receiveFileRecoderList.length > 1 ? 'auto' : 'none')}">
<div class="layui-col-xs12 tl-rtc-file-send-list" v-for="file in receiveFileRecoderList">
<div class="tl-rtc-file-send" :style="{'--progress': file.progress + '%'}">
<div class="tl-rtc-file-send-body">
@@ -867,7 +877,7 @@
</div>
<div class="layui-row tl-rtc-file-send-list-all"
:style="{height: codeFileHeight+'px',overflowY: (receiveCodeFileList.length > 1 ? 'scroll' : 'none') }">
:style="{height: codeFileHeight+'px',overflowY: (receiveCodeFileList.length > 1 ? 'auto' : 'none') }">
<div class="layui-col-xs12 tl-rtc-file-send-list" v-for="file, index in receiveCodeFileList">
<div class="tl-rtc-file-send">
<div class="tl-rtc-file-send-body">
@@ -955,7 +965,7 @@
</div>
</div>
<div class="layui-card">
<div :style="{height: logsHeight+'px',overflowY: 'scroll'}">
<div :style="{height: logsHeight+'px',overflowY: 'auto'}">
<div class="layui-card-body tl-rtc-file-mask-log" v-for="log in filterLogs">
<div class="tl-rtc-file-mask-log-time">
<div>{{log.time}}</div>
@@ -964,7 +974,7 @@
</svg>
</div>
<div v-show="log.msg.length >= 500" class="tl-rtc-file-mask-log-msg"
style="max-height: 300px; overflow-y: scroll;">
style="max-height: 300px; overflow-y: auto;">
<b v-show="log.type === '【{{lang.sys_log}}】: '" style="color: #b54343;">{{log.type}}</b>
<b v-show="log.type === '【{{lang.op_log}}】: '"
style="color: cadetblue;">{{log.type}}</b>
@@ -993,7 +1003,7 @@
</div>
</div>
<div class="layui-card">
<div :style="{height: logsHeight+'px',overflowY: 'scroll'}">
<div :style="{height: logsHeight+'px',overflowY: 'auto'}">
<div class="layui-card-body">
<div class="tl-rtc-file-mask-media-container " id="mediaVideoRoomList"> </div>
</div>
@@ -1013,7 +1023,7 @@
</div>
</div>
<div class="layui-card">
<div :style="{height: logsHeight+'px',overflowY: 'scroll'}">
<div :style="{height: logsHeight+'px',overflowY: 'auto'}">
<div class="layui-card-body">
<div class="tl-rtc-file-mask-media-container " id="mediaScreenRoomList"> </div>
</div>
@@ -1033,7 +1043,7 @@
</div>
</div>
<div class="layui-card">
<div :style="{height: logsHeight+'px',overflowY: 'scroll'}">
<div :style="{height: logsHeight+'px',overflowY: 'auto'}">
<div class="layui-card-body">
<div class="tl-rtc-file-mask-media-container " id="mediaLiveRoomList"> </div>
</div>
@@ -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;

View File

@@ -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;

View File

@@ -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,
}});
}
})
}

View File

@@ -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 = ` <div> description_zh: <b>${statItem.description_zh}</b> </div> `;
for(let key in statItem){
if(key === 'description_zh'){
continue
}
rtcStatDomVal += ` <div> ${key}: <b>${statItem[key]}</b> </div> `;
}
rtcStatDomList += ` <div> ${rtcStatDomVal} </div> `;
})
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: `
<div class="remote_user_info layui-carousel" id="tl-rtc-file-rtcinfo">
<div carousel-item>
<div>
<div> ${that.lang.userid}: <b>${remote.id}</b> </div>
<div> ${that.lang.nickname}: <b>${remote.nickName}</b> </div>
<div> ${that.lang.room_channel}: <b>${that.roomId}</b> </div>
<div> ${that.lang.website_language}: <b>${remote.langMode}</b> </div>
<div> ${that.lang.network_status}: <b>${remote.network}</b> </div>
<div> ${that.lang.join_time}: <b>${remote.joinTime}</b> </div>
<div> ${that.lang.public_ip}: <b>${remote.ip}</b> </div>
<div> ${that.lang.webrtc_ice_state}: <b>${remote.iceConnectionState}</b> </div>
<div> ${that.lang.device_classification}: <b>${remote.ua}</b> </div>
<div> ${that.lang.terminal_equipment}: <b>${remote.userAgent}</b> </div>
</div>
${rtcStatDomList}
</div>
</div>
`
})
})
},
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) => {
</div>
<div class="chating_input_body">
<textarea maxlength="50000" id="chating_room_single_value" class="layui-textarea" placeholder="${this.lang.communication_rational} ~"></textarea>
<span class="chating_send_body chating_send_body_span">shift+enter ${this.lang.enter_send} </span>
<span class="chating_send_body chating_send_body_span">shift+enter | ${this.lang.enter_send} </span>
<button onclick="sendChatingRoomSingle()" type="button" class="layui-btn layui-btn-normal layui-btn-sm chating_send_body chating_send_body_button">${this.lang.send_chat}</button>
</div>
`
@@ -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) => {
<div class="chating_input_body">
<textarea maxlength="50000" id="openaiChat_value" class="layui-textarea" placeholder="${this.lang.communication_rational} ~"></textarea>
<span class="chating_send_body chating_send_body_span">shift+enter ${this.lang.enter_send} </span>
<span class="chating_send_body chating_send_body_span">shift+enter | ${this.lang.enter_send} </span>
<button onclick="sendOpenaiChat()" type="button" class="layui-btn layui-btn-normal layui-btn-sm chating_send_body chating_send_body_button">${this.lang.send_chat}</button>
</div>
`
@@ -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) => {
<div class="setting-main">
<div class="setting-main-body">
<div class="relayDoc" style="padding: 15px; position: absolute; width: 100%; height: 100%;">
<p style="text-align: center; font-weight: bold; position: relative; top: 2px; display: block; font-size: 17px;"> ${this.lang.relay_server_current} ${useTurn ? this.lang.on : this.lang.off} </p>
<p style="font-weight: bold; position: relative; top: 15px; display: block; font-size: 14px;"> ${this.lang.relay_server_current_detail} </p>
<div style="position: relative; margin-top: 140px;">
<p style="text-align: center; font-weight: bold; position: relative; top: 2px; display: block; font-size: 17px;"> ${this.lang.relay_server_current} '${useTurn ? this.lang.on : this.lang.off}' </p>
<p style="font-weight: bold; position: relative; top: 15px; display: block; font-size: 14px;height: 80%;"> ${this.lang.relay_server_current_detail} </p>
<div>
<div style="text-align: center;">
<button onclick="useTurn()" type="button" class="layui-btn layui-btn-sm layui-btn-normal" style="margin-right: 45px;"> ${this.lang.on} </button>
<button onclick="useTurn()" type="button" class="layui-btn layui-btn-sm layui-btn-danger"> ${this.lang.off} </button>
<button onclick="useTurn()" type="button" class="layui-btn layui-btn-sm layui-btn-primary layui-border-blue" style="margin-right: 45px;"> ${this.lang.on} </button>
<button onclick="useTurn()" type="button" class="layui-btn layui-btn-sm layui-btn-primary layui-border-red"> ${this.lang.off} </button>
</div>
</div>
</div>
@@ -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) => {
</div>
<div class="chating_input_body">
<textarea maxlength="50000" id="chating_comm_value" class="layui-textarea" placeholder="${this.lang.communication_rational} ~"></textarea>
<span class="chating_send_body chating_send_body_span">shift+enter ${this.lang.enter_send} </span>
<span class="chating_send_body chating_send_body_span">shift+enter | ${this.lang.enter_send} </span>
<button onclick="sendChatingComm()" type="button" class="layui-btn layui-btn-normal layui-btn-sm chating_send_body chating_send_body_button">${this.lang.send_chat}</button>
</div>
`
@@ -1617,7 +1709,7 @@ axios.get("/api/comm/initData?turn="+useTurn, {}).then((initData) => {
</div>
<div class="chating_input_body">
<textarea maxlength="50000" id="chating_room_value" class="layui-textarea" placeholder="${this.lang.communication_rational} ~"></textarea>
<span class="chating_send_body chating_send_body_span">shift+enter ${this.lang.enter_send} </span>
<span class="chating_send_body chating_send_body_span">shift+enter | ${this.lang.enter_send} </span>
<button onclick="sendChatingRoom()" type="button" class="layui-btn layui-btn-normal layui-btn-sm chating_send_body chating_send_body_button">${this.lang.send_chat}</button>
</div>
`
@@ -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);

View File

@@ -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": "单独发送",

View File

@@ -63,7 +63,7 @@
<h2>收费模式</h2>
<ul>
<li><strong>按小时计费:</strong>根据实际工作时间计算费用目前个人定制功能2小时起每小时200~300,企业定制另谈</li>
<li><strong>按小时计费:</strong>根据实际工作时间计算费用目前个人定制功能2小时起每小时200~300</li>
<li><strong>阶段性付费:</strong>确认开发前预付1/3开发完成付1/3交付上线1/3</li>
</ul>

View File

@@ -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
});
}

View File

@@ -54,6 +54,30 @@
<div class="content unicode" style="display: block;">
<ul class="icon_lists dib-box">
<li class="dib">
<span class="icon iconfont">&#xe61d;</span>
<div class="name">数据汇总</div>
<div class="code-name">&amp;#xe61d;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe6bb;</span>
<div class="name">控桩终端</div>
<div class="code-name">&amp;#xe6bb;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe6e8;</span>
<div class="name">浏览器</div>
<div class="code-name">&amp;#xe6e8;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xec57;</span>
<div class="name">连接流</div>
<div class="code-name">&amp;#xec57;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe619;</span>
<div class="name">三角形</div>
@@ -534,9 +558,9 @@
<pre><code class="language-css"
>@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');
}
</code></pre>
<h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3>
@@ -562,6 +586,42 @@
<div class="content font-class">
<ul class="icon_lists dib-box">
<li class="dib">
<span class="icon iconfont icon-rtc-file-daohang-shujufenxi"></span>
<div class="name">
数据汇总
</div>
<div class="code-name">.icon-rtc-file-daohang-shujufenxi
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-rtc-file-kongzhuangzhongduan"></span>
<div class="name">
控桩终端
</div>
<div class="code-name">.icon-rtc-file-kongzhuangzhongduan
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-rtc-file-liulanqi"></span>
<div class="name">
浏览器
</div>
<div class="code-name">.icon-rtc-file-liulanqi
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-rtc-file-lianjieliu"></span>
<div class="name">
连接流
</div>
<div class="code-name">.icon-rtc-file-lianjieliu
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-rtc-file-sanjiaoxing"></span>
<div class="name">
@@ -1282,6 +1342,38 @@
<div class="content symbol">
<ul class="icon_lists dib-box">
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-rtc-file-daohang-shujufenxi"></use>
</svg>
<div class="name">数据汇总</div>
<div class="code-name">#icon-rtc-file-daohang-shujufenxi</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-rtc-file-kongzhuangzhongduan"></use>
</svg>
<div class="name">控桩终端</div>
<div class="code-name">#icon-rtc-file-kongzhuangzhongduan</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-rtc-file-liulanqi"></use>
</svg>
<div class="name">浏览器</div>
<div class="code-name">#icon-rtc-file-liulanqi</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-rtc-file-lianjieliu"></use>
</svg>
<div class="name">连接流</div>
<div class="code-name">#icon-rtc-file-lianjieliu</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-rtc-file-sanjiaoxing"></use>

View File

@@ -1,8 +1,8 @@
@font-face {
font-family: "iconfont"; /* Project id 4147343 */
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 {
@@ -13,6 +13,22 @@
-moz-osx-font-smoothing: grayscale;
}
.icon-rtc-file-daohang-shujufenxi:before {
content: "\e61d";
}
.icon-rtc-file-kongzhuangzhongduan:before {
content: "\e6bb";
}
.icon-rtc-file-liulanqi:before {
content: "\e6e8";
}
.icon-rtc-file-lianjieliu:before {
content: "\ec57";
}
.icon-rtc-file-sanjiaoxing:before {
content: "\e619";
}

File diff suppressed because one or more lines are too long

View File

@@ -5,6 +5,34 @@
"css_prefix_text": "icon-rtc-file-",
"description": "",
"glyphs": [
{
"icon_id": "3494344",
"name": "数据汇总",
"font_class": "daohang-shujufenxi",
"unicode": "e61d",
"unicode_decimal": 58909
},
{
"icon_id": "25071396",
"name": "控桩终端",
"font_class": "kongzhuangzhongduan",
"unicode": "e6bb",
"unicode_decimal": 59067
},
{
"icon_id": "25853325",
"name": "浏览器",
"font_class": "liulanqi",
"unicode": "e6e8",
"unicode_decimal": 59112
},
{
"icon_id": "5961312",
"name": "连接流",
"font_class": "lianjieliu",
"unicode": "ec57",
"unicode_decimal": 60503
},
{
"icon_id": "3101162",
"name": "三角形",