Files
tl-rtc-file/svr/res/js/index.js
https://blog.iamtsm.cn 8044e369fe feat: 环境部署相关完善
feat: 调整turn服务帐号默认为有效帐号模式
feat: 文档更新
2023-08-05 01:50:49 +08:00

4153 lines
198 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// --------------------------- //
// -- index.js -- //
// -- version : 1.0.0 -- //
// -- date : 2023-06-22 -- //
// --------------------------- //
// index.js
var file = null;
// 是否禁用中继
let useTurn = (window.localStorage.getItem("tl-rtc-file-use-relay") || "") === 'true';
axios.get("/api/comm/initData?turn="+useTurn, {}).then((initData) => {
let resData = initData.data;
file = new Vue({
el: '#tl-rtc-file-app',
data: function () {
let socket = null;
if (io) {
socket = io(resData.wsHost,{
transports : ['polling', 'websocket']
});
}
return {
langMode : "zh", // 默认中文
lang : {}, // 语言包
logo : resData.logo, // 打印logo
version : resData.version,// 项目当前版本
socket: socket, // socket
config: resData.rtcConfig, // rtc配置
options: resData.options, // rtc配置
showReceiveFile: false, // 展示底部接收文件列表
showSendFile: false, // 展示底部发送文件列表
showChooseFile: false, // 展示底部已选文件列表
showSendFileHistory: false, // 展示底部发送文件历史记录列表
showReceiveTxt: false, // 展示底部接收文字列表
showCodeFile: false, // 展示底部取件码文件列表
showLogs: false, // 展示运行日志
showMedia: false, // 展示音视频/屏幕共享/直播
isScreenShare: false, //是否在屏幕共享中
screenShareTimes: 0, //当前屏幕共享时间
isVideoShare: false, //是否在音视频中
videoShareTimes: 0, //当前音视频时间
isLiveShare: false, //是否在直播中
liveShareTimes: 0, //当前直播时间
isPasswordRoom: false, //是否在密码房中
isAiAnswering: false, //是否ai正在回答中
switchDataGet: false, // 是否已经拿到配置开关数据
openaiSendContext: false, // ai对话是否发送上下文
allSended: false,//当前文件是否全部发送给房间内所有用户
isSending: false, //是否正在发送文件中
owner : false, //本人是否是房主
isJoined: false, // 是否加入房间
openRoomInput : false, //是否打开房间号输入框
isSendFileToSingleSocket : false, //是否是单独发送文件给某个socket
isMouseDrag : false, //是否正在拖拽鼠标
isSendAllWaiting : false, //一键发送文件时有1秒时间间隔这个记录当前是否是一键发送文件等待中
isShareJoin : false, //是否是分享加入房间
isCameraEnabled : true, //音视频场景下自己的摄像头是否开启
isAudioEnabled : true, //音视频场景下自己的麦克风是否开启
isLoudspeakerEnabled : true, //音视频场景下自己的扬声器是否开启
facingMode : "user", //音视频场景下自己的摄像头前后置 ,user, environment
videoDeviceId : "", //音视频场景下自己的摄像头设备id
audioDeviceId : "", //音视频场景下自己的麦克风设备id
loudspeakerDeviceId : "", //音视频场景下自己的扬声器设备id
sendFileMaskHeightNum: 150, // 用于控制发送文件列表面板展示
chooseFileMaskHeightNum: 150, // 用于控制选择文件列表面板展示
sendFileHistoryMaskHeightNum: 150, // 用于控制发送文件记录列表面板展示
receiveFileMaskHeightNum: 150,// 用于控制接收文件列表面板展示
codeFileMaskHeightNum : 150, // 用于控制暂存文件展示
logMaskHeightNum: -150, // 用于控制日志栏展示
mediaVideoMaskHeightNum: -150, // 用于控制音视频展示
mediaScreenMaskHeightNum: -150, // 用于控制屏幕共享展示
mediaLiveMaskHeightNum: -150, // 用于控制直播展示
logsHeight: 0, // 日志栏目展示高度
sendFileRecoderHeight : 0, // 发送文件展示列表高度
chooseFileHeight: 0, // 已选文件展示列表高度
sendFileRecoderHistoryHeight : 0, // 发送文件历史记录展示列表高度
receiveFileHeight : 0, // 接收文件展示列表高度
codeFileHeight : 0, // 暂存文件展示列表高度
allManCount: 0, // 当前在线人数
txtEditId: 0, // 文字模式输入框id
nickName: "", //本人名称
socketId: 0, //本人的id
roomId: "10086", //房间号
roomType : "file", //房间类型
liveShareMode : "video", //直播类型 video, screen
codeId: "", //取件码
recoderId: 0, //记录id
rtcConns: {}, //远程连接
remoteMap: {}, //远程连接map
switchData: {}, //配置开关数据
chatRoomSingleSocketId : "", //私聊对方的socketId
chunkSize: 16 * 1024, // 一块16kb 最大应该可以设置到64kb
currentSendAllSize: 0, // 统计发送文件总大小 (流量统计)
uploadCodeFileProgress: 0, // 上传暂存文件的进度
previewFileMaxSize : 1024 * 1024 * 5, // 5M以内允许预览
uploadCodeFileMaxSize : 1024 * 1024 * 10, // 10M以内允许暂存
currentChooseFileRecoder : null, //当前进行发送的文件记录
currentChooseFile: null, //当前发送中的文件
chooseFileList: [], //选择的文件列表
sendFileRecoderList: [], //发送文件的列表
sendFileRecoderHistoryList: [], //发送过文件的列表记录
receiveFileRecoderList: [], //接收文件的列表
receiveChatRoomList: [], //接收的文字列表
receiveCodeFileList: [], //取件码文件列表
receiveChatCommList: [], //公共聊天频道内容
receiveAiChatList: [], //ai对话内容
logs: [], //记录日志
popUpList: [], //消息数据
preMouseMove : {}, //上一次鼠标移动的事件
ips: [], // 记录ip列表检测是否支持p2p
popUpMsgDom : [], // 消息弹窗dom
videoDeviceList : [], //摄像头设备列表
audioDeviceList : [], //麦克风设备列表
loudspeakerDeviceList : [], //扬声器设备列表
token: "", //登录token
manageIframeId: 0, //实现自适应
useTurn: useTurn, //是否使用中继服务器
aiAnsweringTxtIntervalId: 0, //实现等待动画
aiAnsweringTxt: "思考中...", //ai思考中的文字
logsFilter: "", //日志过滤参数
changeLiveShareMediaTrackAndStreamTimeLimit : 9, //切换直播媒体流的时间限制, 允许9s内切换一次
changeLiveShareMediaTrackAndStreamTime : 0, //可以下一次切换直播媒体流的时间
changeLiveShareMediaTrackAndStreamIntervalId : 0, //切换直播媒体流的时间间隔id
clientWidth : document.body.clientWidth, //页面宽度
}
},
computed: {
canSendFile: function () {
return this.isJoined && this.chooseFileList.length > 0;
},
hasManInRoom: function () {
return Object.keys(this.remoteMap).length > 0;
},
canSendChatingRoom : function(){
return this.isJoined && Object.keys(this.remoteMap).length > 0;
},
isMobile: function () {
return this.clientWidth <= 500 && navigator.userAgent.match(
/(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone)/i
);
},
network: function () {
return window.tlrtcfile.getNetWorkState()
},
filterLogs: function () {
return this.logs.filter(item => {
if(item.msg){
return item.msg.indexOf(this.logsFilter) > -1
|| item.time.indexOf(this.logsFilter) > -1
|| item.type.indexOf(this.logsFilter) > -1
}
})
},
toolSlidesPerViewCount: function(){
return (this.clientWidth < 300) ? 3 : (this.clientWidth < 380) ? 4 : (this.clientWidth < 450) ? 5
: (this.clientWidth < 570) ? 3 : (this.clientWidth < 710) ? 4 : (this.clientWidth < 890) ? 5
: (this.clientWidth < 1000) ? 6 : 7;
},
isMediaing : function(){
return this.isVideoShare || this.isScreenShare || this.isLiveShare
},
videoConstraints : function(){
return {
audio: true,
video: {
facingMode: this.facingMode,
deviceId: this.videoDeviceId,
width: {
ideal : 200, max : 200, min : 100
},
height: {
ideal : 150, max : 150, min : 150
},
frameRate: {
ideal: 100, max: 100
},
}
}
},
},
watch: {
isAiAnswering: function (newV, oldV) {
if (newV) {
this.aiAnsweringTxtIntervalId = setInterval(() => {
if (this.aiAnsweringTxt === this.lang.ai_thinking + '....') {
this.aiAnsweringTxt = this.lang.ai_thinking
} else {
this.aiAnsweringTxt += '.'
}
this.openaiChatTpl();
}, 500);
} else {
clearInterval(this.aiAnsweringTxtIntervalId)
}
},
remoteMap: {
handler: function (newV, oldV) { },
deep: true,
immediate: true
},
receiveFileRecoderList: {
handler: function (newV, oldV) { },
deep: true,
immediate: true
},
receiveChatRoomList: {
handler: function (newV, oldV) { },
deep: true,
immediate: true
},
sendFileRecoderList: {
handler: function (newV, oldV) { },
deep: true,
immediate: true
},
receiveChatCommList: {
handler: function (newV, oldV) { },
deep: true,
immediate: true
},
chooseFileList: {
handler: function (newV, oldV) { },
deep: true,
immediate: true
}
},
methods: {
changeLiveShareMediaTrackAndStream : async function(){
//切换媒体流之前,先将已关闭的媒体流打开之后再进行切换
if(this.liveShareMode === 'live'){
//屏幕共享流只需要打开video通道即可
await this.changeShareStream('live', 'video', 'open')
}else if(this.liveShareMode === 'video'){
//视频流需要打开video和audio通道
await this.changeShareStream('live', 'video', 'open')
await this.changeShareStream('live', 'audio', 'open')
}
let that = this;
this.liveShareMode = this.liveShareMode === 'video' ? 'screen' : 'video';
window.Bus.$emit("changeLiveShareMediaTrackAndStream", {
type : this.liveShareMode,
kind : 'video',
constraints : this.videoConstraints,
rtcConns : this.rtcConns,
callback : (success) => {
if(!success){ //没有成功,切换回来
that.liveShareMode = that.liveShareMode === 'video' ? 'screen' : 'video';
}
}
});
if(this.changeLiveShareMediaTrackAndStreamTime === 0){
this.changeLiveShareMediaTrackAndStreamTime = this.changeLiveShareMediaTrackAndStreamTimeLimit - 1;
//10s内只允许切换一次
this.changeLiveShareMediaTrackAndStreamIntervalId = setInterval(() => {
if(this.changeLiveShareMediaTrackAndStreamTime === 0){
clearInterval(this.changeLiveShareMediaTrackAndStreamIntervalId)
return
}
this.changeLiveShareMediaTrackAndStreamTime -= 1;
}, 1000);
}
},
changeShareStream : async function(type, kind, value){
let stream = null;
if(type === 'video'){
stream = await new Promise((resolve, reject) => {
window.Bus.$emit("getVideoShareTrackAndStream", (track, stream) => {
resolve(stream)
})
});
}else if(type === 'screen'){
stream = await new Promise((resolve, reject) => {
stream = window.Bus.$emit("getScreenShareTrackAndStream", (track, stream) => {
resolve(stream)
});
});
}else if(type === 'live'){
stream = await new Promise((resolve, reject) => {
stream = window.Bus.$emit("getLiveShareTrackAndStream", (track, stream) => {
resolve(stream)
});
});
}
if(kind === 'video'){
//指定开关
if(value && ['open','close'].includes(value)){
stream.getVideoTracks()[0].enabled = value === 'open'
this.isCameraEnabled = value === 'open'
}else{
//根据当前状态取反
stream.getVideoTracks()[0].enabled = !stream.getVideoTracks()[0].enabled
this.isCameraEnabled = !this.isCameraEnabled
}
}
if(kind === 'audio'){
//指定开关
if(value && ['open','close'].includes(value)){
stream.getAudioTracks()[0].enabled = value === 'open'
this.isAudioEnabled = value === 'open'
}else{
//根据当前状态取反
stream.getAudioTracks()[0].enabled = !stream.getAudioTracks()[0].enabled
this.isAudioEnabled = !this.isAudioEnabled
}
}
this.socket.emit('message', {
emitType: "openCamera",
room: this.roomId,
from : this.socketId,
type : type,
kind : kind,
isCameraEnabled : this.isCameraEnabled,
isAudioEnabled : this.isAudioEnabled
});
},
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-V${this.version} ____ \n____ FORK ME ON GITHUB ____ \n____ https://github.com/tl-open-source/tl-rtc-file ____`, this.logo)
},
changeLanguage: function () {
let that = this;
window.dropdown.render({
elem: '#language',
data: [{
title: 'Chinese',
id: 'zh'
},{
type: '-'
},{
title: 'English',
id: 'en'
}],
className: 'language-mode',
click: function (obj) {
window.location.href = window.tlrtcfile.addUrlHashParams({
lang: obj.id
});
window.location.reload()
}
});
},
openDisclaimer: function(){
layer.open({
type: 2,
title : this.lang.website_agreement_statement,
area: ['100%','100%'],
shade: 0.5,
shadeClose : true,
content: 'disclaimer.html',
success: function(){
document.querySelector(".layui-layer-title").style.borderTopRightRadius = "8px";
document.querySelector(".layui-layer-title").style.borderTopLeftRadius = "8px";
document.querySelector(".layui-layer").style.borderRadius = "8px";
}
})
},
// 分享取件码
getCodeFileCode : function(file){
let that = this;
layer.closeAll(function () {
layer.open({
type: 1,
closeBtn: 0,
fixed: true,
maxmin: false,
shadeClose: true,
area: ['350px', '380px'],
title: that.lang.share_pickup_code + "("+that.lang.expires_one_day+")",
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";
if(window.tlrtcfile.getQrCode){
tlrtcfile.getQrCode("tl-rtc-file-code-share-image", window.location.href + "#c="+file.codeId)
}
},
content: `
<div style="margin-top: 20px; text-align: center; margin-bottom: 25px;">
<div id="tl-rtc-file-code-share"> ${file.codeId} </div>
</div>
<div id="tl-rtc-file-code-share-image">
`
})
})
this.addUserLogs(this.lang.open_share_pickup_code)
},
// 暂存取件码文件
prepareCodeFile: function (recoder) {
let index = recoder.index;
let id = recoder.id;
let filterFile = this.chooseFileList.filter(item=>{
return item.index === index;
});
if(filterFile.length === 0){
this.addUserLogs(this.lang.file_not_exist);
return
}
let file = filterFile[0]
if (file.size > this.uploadCodeFileMaxSize) {
layer.msg(`${this.lang.max_saved} ${this.uploadCodeFileMaxSize / 1024 / 1024} ${this.lang.mb_file}`);
return
}
//更新当前文件相关的所有记录的暂存状态
this.updateSendFileRecoderUpload(index, {
upload : "uploading"
})
this.socket.emit('prepareCodeFile', {
index: file.index,
name: file.name,
type: file.type,
size: file.size,
room: this.roomId,
from: this.socketId,
nickName : this.nickName,
sendFileRecoderId : id
});
},
// 获取取件码文件
getCodeFile: function () {
let that = this;
if (!this.switchData.openGetCodeFile) {
layer.msg(this.lang.feature_close)
this.addUserLogs(this.lang.feature_close)
return
}
layer.prompt({
formType: 0,
title: this.lang.please_enter_code,
value: this.codeId,
}, function (value, index, elem) {
if(value.length < 30 || tlrtcfile.containSymbol(value) || tlrtcfile.containChinese(value)){
layer.msg(that.lang.please_enter_right_code)
return
}
that.codeId = value;
that.socket.emit('getCodeFile', {
room: that.roomId,
from: that.socketId,
code: that.codeId,
});
layer.close(index);
that.addUserLogs(that.lang.get_pickup_file + "," + value);
});
},
//点击搜索暂存文件面板
clickCodeFile: function () {
this.showCodeFile = !this.showCodeFile;
if (this.showCodeFile) {
this.codeFileMaskHeightNum = 20;
this.addUserLogs(this.lang.expand_temporary);
} else {
this.codeFileMaskHeightNum = 150;
this.addUserLogs(this.lang.collapse_temporary);
}
},
// 单独发送文件给用户
sendFileToSingle: function(recoder){
layer.msg(`${this.lang.send_to_user_separately} ${recoder.id}`)
this.isSendFileToSingleSocket = true;
this.initSendFile(recoder);
},
// 私聊弹窗
startChatRoomSingle: function(event, remote){
event.stopPropagation();
this.chatRoomSingleSocketId = remote.id;
let that = this;
let options = {
type: 1,
fixed: false,
maxmin: false,
shadeClose : true,
area: ['600px', '600px'],
title: `${this.lang.private_chat}${remote.nickName}】-【${remote.id}`,
success: function (layero, index) {
if (window.layedit) {
that.txtEditId = layedit.build('chating_room_single_value', {
tool: ['strong', 'italic', 'underline', 'del', '|', 'left', 'center', 'right'],
height: 120
});
}
that.chatingRoomSingleTpl();
if(window.tlrtcfile.chatKeydown){
let textareaIframe = document.getElementsByTagName("iframe");
if(textareaIframe && textareaIframe.length === 1){
tlrtcfile.chatKeydown(
document.getElementsByTagName("iframe")[0].contentDocument.body,
sendChatingRoomSingle
)
}
}
},
cancel: function (index, layero) {
this.chatRoomSingleSocketId = "";
},
content: `
<div class="layui-col-sm12" style="padding: 15px;">
<div id="chating_room_single_tpl_view" style="padding: 5px;"> </div>
<script id="chating_room_single_tpl" type="text/html">
{{# layui.each(d, function(index, info){ }}
{{# if(info.socketId !== '${this.socketId}') { }}
<div style="margin-bottom: 30px;display: inline-flex;width:100%;">
<a > <img style="width: 32px; height: 32px;" src="/image/44826979.png" alt="img"> </a>
<div style="margin-left: 15px; margin-top: -5px;width:100%;">
<div style="word-break: break-all;">
<small>${this.lang.user}: <b>{{info.nickName}}</b></small> -
<small>id: <b>{{info.socketId}}</b></small> -
<small>${this.lang.time}: <b>{{info.timeAgo}}</b></small>
</div>
<div style="margin-top: 5px;word-break: break-all;width: 90%;">
<b style="font-weight: bold; font-size: large;">{{- info.content }}</b>
</div>
</div>
</div>
{{# }else { }}
<div style="margin-bottom: 30px;display: inline-flex;width:100%;">
<div style="margin-right: 15px; margin-top: -5px;width:100%;text-align: right;">
<div style="word-break: break-all;">
<small>${this.lang.self}: {{info.nickName}} </small> -
<small>${this.lang.time}: <b>{{info.timeAgo}}</b></small>
</div>
<div style="margin-top: 5px;word-break: break-all;width: 90%; margin-left: 10%;">
<b style="font-weight: bold; font-size: large;">{{- info.content }}</b>
</div>
</div>
<a > <img style="width: 32px; height: 32px;" src="/image/44826979.png" alt="img"> </a>
</div>
{{# } }}
{{# }); }}
</script>
</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>
<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>
`
}
if (this.isMobile) {
delete options.area
}
layer.closeAll(function () {
let index = layer.open(options)
if (that.isMobile) {
layer.full(index)
}
})
this.addUserLogs(this.lang.open_private_chat)
},
// 私聊渲染
chatingRoomSingleTpl: function () {
let tpl_html = document.getElementById("chating_room_single_tpl");
let tpl_view_html = document.getElementById("chating_room_single_tpl_view");
if (tpl_html && tpl_view_html) {
//私聊数据放在连接对象中
let remoteRtc = this.getRemoteInfo(this.chatRoomSingleSocketId);
let receiveChatRoomSingleList = [];
if(remoteRtc && remoteRtc.receiveChatRoomSingleList){
receiveChatRoomSingleList = remoteRtc.receiveChatRoomSingleList;
}
this.tpl(tpl_html, receiveChatRoomSingleList, tpl_view_html)
let chatDom = document.querySelector("#chating_room_single_tpl_view")
let chatDomHeight = chatDom.clientHeight
let height = 0;
if (this.isMobile) {
height = document.documentElement.clientHeight - 335;
} else {
height = 300
}
if (chatDomHeight > height) {
chatDom.style.height = height + "px"
chatDom.style.overflowY = "scroll"
} else {
chatDom.style.overflowY = "none"
}
if(window.tlrtcfile.scrollToBottom){
window.tlrtcfile.scrollToBottom(chatDom, 1000, 100)
}
}
},
// 私聊发言
sendChatingRoomSingle: function () {
if (!this.isJoined) {
layer.msg(this.lang.please_join_then_send)
this.addUserLogs(this.lang.please_join_then_send);
return
}
if (!this.hasManInRoom) {
layer.msg(this.lang.room_least_two_can_send_content)
this.addUserLogs(this.lang.room_least_two_can_send_content);
return
}
let realContent = layedit.getContent(this.txtEditId)
if (realContent.length <= 0) {
layer.msg(this.lang.please_enter_content)
this.addUserLogs(this.lang.please_enter_content);
return
}
if (realContent.length > 10000) {
layer.msg(this.lang.content_max_10000)
this.addUserLogs(this.lang.content_max_10000);
return
}
this.socket.emit('chatingRoom', {
content: tlrtcfile.escapeStr(realContent),
room: this.roomId,
from: this.socketId,
to : this.chatRoomSingleSocketId,
nickName : this.nickName,
recoderId: this.recoderId
});
//私聊数据放在连接对象中
let remoteRtc = this.getRemoteInfo(this.chatRoomSingleSocketId);
if(remoteRtc){
let now = new Date().toLocaleString();
let receiveChatRoomSingleList = remoteRtc.receiveChatRoomSingleList || [];
receiveChatRoomSingleList.push({
socketId: this.socketId,
content: realContent,
nickName : this.nickName,
to : this.chatRoomSingleSocketId,
time: now,
timeAgo : window.util ? util.timeAgo(now) : now
})
this.setRemoteInfo(this.chatRoomSingleSocketId, {
receiveChatRoomSingleList : receiveChatRoomSingleList
});
}
this.chatingRoomSingleTpl();
layer.msg(this.lang.text_send_done)
this.addUserLogs(this.lang.text_send_done);
layedit.setContent(this.txtEditId, "", false)
},
// 右上角弹窗
startPopUpMsg : async function() {
let that = this;
let data = this.popUpList.shift();
let lengthLevel = {//渐进式弹悬浮时间
2 : 1800, // 队列只有两个弹窗排队时, 弹窗悬停时间1800ms
5 : 1600,
8 : 1300,
10 : 900,
20 : 700
};
//轮训是否有弹窗排队中
if(!data){
await new Promise(resolve=>{
setTimeout(async ()=>{
await this.startPopUpMsg()
resolve()
}, 1000);
})
return
}
let levelTime = 1800;
for(let len in lengthLevel){
if(len > this.popUpList.length){
levelTime = lengthLevel[len]
break;
}
}
let msgDom = document.createElement('div');
msgDom.setAttribute("class","tl-rtc-file-notification")
msgDom.style.opacity = 0;
msgDom.innerHTML = `
<div class="tl-rtc-file-notification-close"><i class="layui-icon layui-icon-close "></i></div>
<div class="tl-rtc-file-notification-icon"><i class="layui-icon layui-icon-chat"></i></div>
<div class="tl-rtc-file-notification-content">
<div class="tl-rtc-file-notification-title"> ${data.title} </div>
<div class="tl-rtc-file-notification-content-msg"> ${data.message} </div>
</div>
`;
let msgDomContainer = document.getElementById('notificationContainer');
msgDomContainer.style.right = "-320px";
msgDomContainer.prepend(msgDom);
setTimeout(() => {
msgDomContainer.style.right = "10px";
msgDom.style.opacity = 1;
setTimeout(() => {
msgDomContainer.style.right = "-320px";
msgDom.style.opacity = 0;
setTimeout(() => {
msgDomContainer.removeChild(msgDom);
that.startPopUpMsg();
}, 450);
}, levelTime);
}, 450);
},
// 预览发送文件
previewSendFile: async function (index) {
let filterFile = this.chooseFileList.filter(item=>{
return item.index === index;
});
if(filterFile.length === 0){
this.addUserLogs(this.lang.preview_file + "【", file.name, "】"+ this.lang.failed_find_file);
return
}
await this.previewFile(filterFile[0])
},
// 预览接收文件
previewReceiveFile: async function (index) {
let filterFile = this.receiveFileRecoderList.filter(item=>{
return item.index === index;
});
if(filterFile.length === 0){
this.addUserLogs(this.lang.preview_file + "【", file.name, "】"+ this.lang.failed_find_file);
return
}
let fileRecorde = filterFile[0];
if (fileRecorde.size > this.previewFileMaxSize) {
layer.msg(`${this.lang.max_previewed} ${this.previewFileMaxSize / 1024 / 1024} ${this.lang.mb_file}`);
return
}
let file = await new Promise((resolve, reject) => {
let req = new XMLHttpRequest();
req.open("GET", fileRecorde.href);
req.setRequestHeader('Accept', 'image/png');
req.responseType = "blob";
req.onload = () => {
resolve( new File([req.response], fileRecorde.name, { type: fileRecorde.type }) );
};
req.onerror = reject
req.send();
});
await this.previewFile(file);
},
// 预览文件
previewFile : async function(file){
try{
let that = this;
let isText = this.typeInArr([
'text','json', 'lua', 'html', 'css', 'js', 'java', 'cpp', 'javascript',
'sql', 'php', 'py', 'go', 'conf', 'log', 'md', 'scss', 'xml',
'rb', 'sh' , 'ts','jsx', 'less', 'htm', 'xhtml', 'tsx',
], file.type, file.name);
let isPdf = this.typeInArr([
'pdf', 'pdx', 'pdn', 'fdf', 'pdp'
], file.type, file.name);
let isImage = this.typeInArr([
'image', 'png', 'jpg', 'jpeg', 'gif', 'webp'
], file.type, file.name);
let isDoc = this.typeInArr([
'wordprogressingml.document','application/msword'
], file.type, file.name);
let isVideo = this.typeInArr([
'video','mp4'
], file.type, file.name);
if(isPdf){
await window.tlrtcfile.previewPdfFile({
file : file,
max : this.previewFileMaxSize,
callback : function(msg){
if(window.layer){
layer.msg(msg)
}
that.addUserLogs(msg)
}
})
} else if (isImage) {
window.tlrtcfile.previewImageFile({
file : file,
max : this.previewFileMaxSize,
callback : function(msg){
if(window.layer){
layer.msg(msg)
}
that.addUserLogs(msg)
}
})
} else if (isVideo) {
window.tlrtcfile.previewVideoFile({
file : file,
max : this.previewFileMaxSize,
callback : function(msg){
if(window.layer){
layer.msg(msg)
}
that.addUserLogs(msg)
}
})
} else if (isText) {
window.tlrtcfile.previewCodeFile({
file : file,
max : this.previewFileMaxSize,
callback : function(msg){
if(window.layer){
layer.msg(msg)
}
that.addUserLogs(msg)
}
})
} else{
layer.msg(this.lang.preview_not_supported);
}
}catch(e){
layer.msg(this.lang.preview_not_supported);
}
},
// 删除待发送文件
deleteSendFile: function(index){
let that = this;
let sendFileRecorder = this.sendFileRecoderList[index];
if(sendFileRecorder){
let filename = sendFileRecorder.name;
let fileindex = sendFileRecorder.index;
let fileId = sendFileRecorder.id;
let dom = document.querySelector("#send-file-item"+index)
dom.classList.add("tl-rtc-file-fade-leave-active");
setTimeout(() => { // 因为动画效果,所以延迟执行
// 清除对应的记录
that.sendFileRecoderList = that.sendFileRecoderList.filter(item=>{
return fileindex !== item.index && filename !== item.filename
})
// 移除文件
that.chooseFileList = that.chooseFileList.filter(item=>{
return item.index !== fileindex;
})
layer.msg(`${that.lang.send_cancel}${filename}`);
}, 600);
}
},
// 设置昵称
setNickName: function(){
if(window.tlrtcfile.genNickName){
this.nickName = window.tlrtcfile.genNickName();
}
},
// 打开公告
clickNotice: function(){
let noticeMsgList = this.switchData.noticeMsgList || [{
msg : this.lang.no_notice
}]
let content = "";
noticeMsgList.forEach(item=>{
content += `<div> ${item.msg} </div>`;
})
layer.open({
title: this.lang.notice,
content: content,
btn : this.lang.confirm,
shadeClose : true
});
},
// 打开ai窗口
openaiChat: function () {
if (!this.switchData.openAiChat) {
layer.msg(this.lang.feature_close)
this.addUserLogs(this.lang.feature_close)
return
}
if (!this.isJoined) {
layer.msg(this.lang.please_join_then_chat_with_ai)
this.addUserLogs(this.lang.please_join_then_chat_with_ai)
return
}
let that = this;
let options = {
type: 1,
fixed: false, //不固定
maxmin: false,
shadeClose: true,
area: ['600px', '600px'],
title: this.lang.ai_chat,
success: function (layero, index) {
document.querySelector(".layui-layer-title").style.borderTopRightRadius = "15px"
document.querySelector(".layui-layer-title").style.borderTopLeftRadius = "15px"
document.querySelector(".layui-layer").style.borderRadius = "15px"
that.openaiChatTpl();
if(window.tlrtcfile.chatKeydown){
tlrtcfile.chatKeydown(document.getElementById("openaiChat_value"), sendOpenaiChat)
}
},
content: `
<div class="layui-col-sm12" style="padding: 15px;">
<div class="layui-card" id="openaiChat_tpl_view" style="padding: 5px;"> </div>
<script id="openaiChat_tpl" type="text/html">
{{# if(d.openaiSendContext) { }}
<div style="font-weight: bold;text-align: center; color: #000000; font-size: 12px;margin-top: -5px; margin-bottom: 20px;">
${this.lang.open_ai_switch}
</div>
{{# }else{ }}
<div style="text-align: center; color: #000000; font-size: 12px;margin-top: -5px; margin-bottom: 20px;">
${this.lang.try_open_ai_switch}
</div>
{{# } }}
<div style="text-align: center; color: #000000; font-size: 12px;margin-top: -5px; margin-bottom: 20px;">
-------- ${this.lang.room} ${this.roomId} - ${this.lang.ai_chat_record} --------
</div>
{{# layui.each(d.list, function(index, info){ }}
{{# if(info.type === 'openai') { }}
<div style="margin-bottom: 30px;display: inline-flex;width:100%;">
<a > <img style="width: 32px; height: 32px;" src="/image/44826979.png" alt="img"> </a>
<div style="margin-left: 15px; margin-top: -5px;width:100%;">
<div style="word-break: break-all;"> <small> AI: </small> - <small>${this.lang.time}: <b>{{info.timeAgo}}</b></small> </div>
<div style="margin-top: 5px;word-break: break-all;width: 90%;"> <b style="font-weight: bold; font-size: large;"> {{- info.content}} </b></div>
</div>
</div>
{{# }else { }}
<div style="margin-bottom: 30px;display: inline-flex;text-align: right;float: right;width:100%;">
<div style="margin-right: 15px; margin-top: -5px;width:100%;">
<div style="word-break: break-all;"> <small>${this.lang.self}: <b>{{info.socketId}}</b> </small> <small>${this.lang.time}: <b>{{info.timeAgo}}</b></small> </div>
<div style="margin-top: 5px;word-break: break-all;width: 90%; margin-left: 10%;"> <b style="font-weight: bold; font-size: large;"> {{- info.content}} </b></div>
</div>
<a > <img style="width: 32px; height: 32px;" src="/image/44826979.png" alt="img"> </a>
</div>
{{# } }}
{{# }); }}
{{# if(d.isAiAnswering) { }}
<div style="margin-bottom: 30px;display: inline-flex;width:100%;">
<a > <img style="width: 32px; height: 32px;" src="/image/44826979.png" alt="img"> </a>
<div style="margin-left: 15px; margin-top: -5px;width:100%;">
<div style="word-break: break-all;">
<small> AI: </small> -
<small>${this.lang.time}: <b>{{d.time}}</b></small>
</div>
<div style="margin-top: 5px;word-break: break-all;width: 90%;"> <b style="font-weight: bold; font-size: large;"> {{d.aiAnsweringTxt}} </b></div>
</div>
</div>
{{# } }}
</script>
</div>
<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>
<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>
`
}
if (this.isMobile) {
delete options.area
}
layer.closeAll(function () {
let index = layer.open(options)
if (that.isMobile) {
layer.full(index)
}
})
this.addUserLogs(this.lang.open_ai_chat)
},
// ai窗口渲染
openaiChatTpl: function (callback) {
let tpl_html = document.getElementById("openaiChat_tpl");
let tpl_view_html = document.getElementById("openaiChat_tpl_view");
if (tpl_html && tpl_view_html) {
this.tpl(tpl_html, {
list: this.receiveAiChatList,
isAiAnswering: this.isAiAnswering,
aiAnsweringTxt: this.aiAnsweringTxt,
time: window.util ? util.timeAgo(new Date().toDateString) : new Date().toDateString,
openaiSendContext: this.openaiSendContext
}, tpl_view_html, callback)
let chatDom = document.querySelector("#openaiChat_tpl_view")
let chatDomHeight = chatDom.clientHeight
let height = 0;
if (this.isMobile) {
height = document.documentElement.clientHeight - 235;
} else {
height = 350
}
if (chatDomHeight > height) {
chatDom.style.height = height + "px"
chatDom.style.overflowY = "scroll"
} else {
chatDom.style.overflowY = "none"
}
if(window.tlrtcfile.scrollToBottom){
window.tlrtcfile.scrollToBottom(chatDom, 1000, 100)
}
}
},
// 发送ai问题
sendOpenaiChat: function () {
if (this.isAiAnswering) {
layer.msg(this.lang.ai_answering)
this.addUserLogs(this.lang.ai_answering)
return
}
let value = document.querySelector("#openaiChat_value").value;
if (value === '' || value === undefined) {
layer.msg(this.lang.please_fill_content)
this.addUserLogs(this.lang.please_fill_content)
return
}
if (value.length > 1000) {
layer.msg(this.lang.content_max_1000)
this.addUserLogs(this.lang.content_max_1000)
return
}
value = window.util.escape(value);
this.receiveAiChatList.push({
room: this.roomId,
socketId: this.socketId,
content: value
})
// 发送上下文
let contextContent = "";
if (this.openaiSendContext) {
let isShortContentChatList = true;
this.receiveAiChatList.forEach(item => {
if (item.content.length > 100) {
isShortContentChatList = false;
}
})
let isShortChatList = this.receiveAiChatList.length < 6;
if (isShortChatList) { // 对话次数不多
if (isShortContentChatList) { // 对话内容精简
this.receiveAiChatList.forEach(item => {
contextContent += item.content + "\n";
})
} else { //对话内容复杂
this.receiveAiChatList.forEach(item => {
contextContent += item.content + "\n";
})
}
} else { // 对话次数较多
if (isShortContentChatList) { // 对话内容精简
this.receiveAiChatList.slice(this.receiveAiChatList.length - 6).forEach(item => {
contextContent += item.content + "\n";
})
} else { // 对话内容复杂
this.receiveAiChatList.slice(this.receiveAiChatList.length - 4).forEach(item => {
contextContent += item.content + "\n";
})
}
}
contextContent = contextContent.substring(0, 5000);
}
this.socket.emit('openai', {
room: this.roomId,
socketId: this.socketId,
content: contextContent,
value: value
});
this.isAiAnswering = true;
this.openaiChatTpl()
this.addUserLogs(this.lang.i_said_to_ai + value);
document.querySelector("#openaiChat_value").value = ''
},
// 创建/加入密码房间
startPassword: function () {
if (!this.switchData.openPasswordRoom) {
layer.msg(this.lang.feature_close)
this.addUserLogs(this.lang.feature_close)
return
}
if (!this.isPasswordRoom) {
if (this.isJoined) {
layer.msg(this.lang.please_exit_then_join_password_room)
this.addUserLogs(this.lang.please_exit_then_join_password_room)
return
}
let that = this;
layer.prompt({
formType: 0,
title: this.lang.please_enter_password
}, function (value, index, elem) {
that.roomId = value;
layer.close(index);
that.isPasswordRoom = !that.isPasswordRoom;
layer.prompt({
formType: 1,
title: that.lang.please_enter_password
}, function (value, index, elem) {
that.createPasswordRoom(value);
layer.close(index);
that.addUserLogs(that.lang.enter_password_room + that.roomId + `,${that.lang.password}:` + value);
});
});
}
},
// 打开设置
setting: function () {
let that = this;
let options = {
type: 1,
fixed: false,
maxmin: false,
shadeClose: true,
area: ['300px', '350px'],
title: this.lang.setting,
success: function (layero, index) {
document.querySelector(".layui-layer-title").style.borderTopRightRadius = "15px"
document.querySelector(".layui-layer-title").style.borderTopLeftRadius = "15px"
document.querySelector(".layui-layer").style.borderRadius = "15px"
document.querySelector(".layui-layer-content").style.borderRadius = "15px"
window.form.render()
},
content: `
<div class="setting-main">
<div class="setting-main-body">
<ul class="layui-row layui-col-space10">
<li class="layui-col-xs4">
<a title="${this.lang.blog}" href="https://blog.iamtsm.cn" target="_blank">
<svg class="icon" aria-hidden="true" style="width:42px;height:50px;" id="blog">
<use xlink:href="#icon-rtc-file-zhu"></use>
</svg>
<cite>${this.lang.blog}</cite>
</a>
</li>
<li class="layui-col-xs4">
<a title="github" href="https://github.com/iamtsm" target="_blank">
<svg class="icon" aria-hidden="true" style="width:42px;height:50px;">
<use xlink:href="#icon-rtc-file-github"></use>
</svg>
<cite>github</cite>
</a>
</li>
<li class="layui-col-xs4" >
<a title="${this.lang.webrtc_check}" onclick="webrtcCheck()">
<svg class="icon" aria-hidden="true" style="width:42px;height:50px;" id="rtcCheck">
<use xlink:href="#icon-rtc-file-gongju"></use>
</svg>
<cite>${this.lang.webrtc_check}</cite>
</a>
</li>
<li class="layui-col-xs4" >
<a title="${this.lang.p2p_check}" onclick="p2pCheck()">
<svg class="icon" aria-hidden="true" style="width:42px;height:50px;" id="p2pCheck">
<use xlink:href="#icon-rtc-file-PP-"></use>
</svg>
<cite>${this.lang.p2p_check}</cite>
</a>
</li>
<li class="layui-col-xs4" style="${this.switchData.openTurnServer ? '' : 'display:none;'}">
<a title="${this.lang.relay_setting}" onclick="relaySetting()">
<svg class="icon" aria-hidden="true" style="width:42px;height:50px;">
<use xlink:href="#icon-rtc-file-yunfuwuqi"></use>
</svg>
<cite>${this.lang.relay_setting}</cite>
</a>
</li>
<li class="layui-col-xs4" style="${this.switchData.openAiChat ? '' : 'display:none;'}">
<a title="${this.lang.ai_setting}" onclick="sendOpenaiChatWithContext()">
<svg class="icon" aria-hidden="true" style="width:42px;height:50px;" id="aiContext">
<use xlink:href="#icon-rtc-file-AIzhineng"></use>
</svg>
<cite>${this.lang.ai_setting}</cite>
</a>
</li>
<li class="layui-col-xs4" style="${this.switchData.openSendBug ? '' : 'display:none;'}">
<a title="${this.lang.feedback}" onclick="sendBugs()" >
<svg class="icon" aria-hidden="true" style="width:42px;height:50px;" id="sendBugs">
<use xlink:href="#icon-rtc-file-yonghufankuibeifen"></use>
</svg>
<cite>${this.lang.feedback}</cite>
</a>
</li>
</ul>
</div>
</div>
`
}
layer.closeAll(function () {
layer.open(options)
})
this.addUserLogs(this.lang.open_setting)
},
// 打开中继设置面板
relaySetting: function () {
let options = {
type: 1,
fixed: false,
maxmin: false,
shadeClose: true,
area: ['300px', '350px'],
title: this.lang.relay_setting,
success: function (layero, index) {
let active = null;
document.querySelector(".layui-layer-title").style.borderTopRightRadius = "15px"
document.querySelector(".layui-layer-title").style.borderTopLeftRadius = "15px"
document.querySelector(".layui-layer").style.borderRadius = "15px"
document.querySelector(".layui-layer-content").style.borderRadius = "15px"
},
content: `
<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;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-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>
</div>
</div>
`
}
layer.closeAll(function () {
layer.open(options)
})
this.addUserLogs(this.lang.open_relay_setting)
},
// 创建/加入音视频房间
startVideoShare: function () {
if (!this.switchData.openVideoShare) {
layer.msg(this.lang.feature_close)
this.addUserLogs(this.lang.feature_close)
return
}
if (this.isScreenShare) {
layer.msg(this.lang.in_sharing_screen)
this.addUserLogs(this.lang.in_sharing_screen)
return
}
if (this.isLiveShare) {
layer.msg(this.lang.in_living)
this.addUserLogs(this.lang.in_living)
return
}
if (this.isVideoShare) {
window.Bus.$emit("stopVideoShare")
this.isVideoShare = !this.isVideoShare;
this.addUserLogs(this.lang.end_video_call);
return
}
if (this.isJoined) {
layer.msg(this.lang.please_exit_then_join_video)
this.addUserLogs(this.lang.please_exit_then_join_video)
return
}
let that = this;
if(that.isShareJoin){ //分享进入
that.createMediaRoom("video");
that.socket.emit('message', {
emitType: "startVideoShare",
room: that.roomId,
to : that.socketId
});
that.clickMediaVideo();
that.isVideoShare = !that.isVideoShare;
that.addUserLogs(that.lang.start_video_call);
}else{
layer.prompt({
formType: 0,
title: that.lang.please_enter_video_call_room_num
}, function (value, index, elem) {
that.roomId = value;
that.createMediaRoom("video");
layer.close(index)
that.socket.emit('message', {
emitType: "startVideoShare",
room: that.roomId,
to : that.socketId
});
that.clickMediaVideo();
that.isVideoShare = !that.isVideoShare;
that.addUserLogs(that.lang.start_video_call);
});
}
},
// 创建/加入屏幕共享房间
startScreenShare: function () {
if (!this.switchData.openScreenShare) {
layer.msg(this.lang.feature_close)
this.addUserLogs(this.lang.feature_close)
return
}
if (this.isVideoShare) {
layer.msg(this.lang.in_videoing)
this.addUserLogs(this.lang.in_videoing)
return
}
if (this.isLiveShare) {
layer.msg(this.lang.in_living)
this.addUserLogs(this.lang.in_living)
return
}
if (this.isScreenShare) {
window.Bus.$emit("stopScreenShare")
this.isScreenShare = !this.isScreenShare;
this.addUserLogs(this.lang.end_screen_sharing);
return
}
if (this.isJoined) {
layer.msg(this.lang.please_exit_then_join_screen)
this.addUserLogs(this.lang.please_exit_then_join_screen)
return
}
let that = this;
if(that.isShareJoin){ //分享进入
that.createMediaRoom("screen");
that.socket.emit('message', {
emitType: "startScreenShare",
room: that.roomId,
to : that.socketId
});
that.clickMediaScreen();
that.isScreenShare = !that.isScreenShare;
that.addUserLogs(that.lang.start_screen_sharing);
}else{
layer.prompt({
formType: 0,
title: this.lang.please_enter_screen_sharing_room_num,
}, function (value, index, elem) {
that.roomId = value;
that.createMediaRoom("screen");
layer.close(index)
that.socket.emit('message', {
emitType: "startScreenShare",
room: that.roomId,
to : that.socketId
});
that.clickMediaScreen();
that.isScreenShare = !that.isScreenShare;
that.addUserLogs(that.lang.start_screen_sharing);
});
}
},
// 创建/加入直播房间
startLiveShare: function () {
if (!this.switchData.openLiveShare) {
layer.msg(this.lang.feature_close)
this.addUserLogs(this.lang.feature_close)
return
}
if (this.isVideoShare) {
layer.msg(this.lang.in_videoing)
this.addUserLogs(this.lang.in_videoing)
return
}
if (this.isScreenShare) {
layer.msg(this.lang.in_sharing_screen)
this.addUserLogs(this.lang.in_sharing_screen)
return
}
if (this.isLiveShare) {
window.Bus.$emit("stopLiveShare")
this.isLiveShare = !this.isLiveShare;
this.addUserLogs(this.lang.end_live);
return
}
if (this.isJoined) {
layer.msg(this.lang.please_exit_then_join_live)
this.addUserLogs(this.lang.please_exit_then_join_live)
return
}
let that = this;
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: 0,
title: this.lang.please_enter_live_room_num,
btn : ['视频直播', '屏幕直播', '取消'],
yes: function (index, layero) {
const value = $('#layui-layer'+index + " .layui-layer-input").val();
if(value === ''){
return
}
that.roomId = value;
that.liveShareMode = "video";
that.createMediaRoom("live");
layer.close(index)
that.socket.emit('message', {
emitType: "startLiveShare",
room: that.roomId,
to : that.socketId,
liveShareMode : that.liveShareMode
});
that.clickMediaLive();
that.isLiveShare = !that.isLiveShare;
that.addUserLogs(that.lang.start_live);
return false
},
btn2: function (index, layero) {
const value = $('#layui-layer'+index + " .layui-layer-input").val();
if(value === ''){
return false
}
that.roomId = value;
that.liveShareMode = "screen";
that.createMediaRoom("live");
layer.close(index)
that.socket.emit('message', {
emitType: "startLiveShare",
room: that.roomId,
to : that.socketId,
liveShareMode : that.liveShareMode
});
that.clickMediaLive();
that.isLiveShare = !that.isLiveShare;
that.addUserLogs(that.lang.start_live);
return false
}
});
}
},
// 打开画笔
openRemoteDraw : function(){
let that = this;
if (!this.switchData.openRemoteDraw) {
layer.msg(this.lang.feature_close)
this.addUserLogs(this.lang.feature_close)
return
}
if (!this.isJoined) {
layer.msg(this.lang.please_join_then_draw)
this.addUserLogs(this.lang.please_join_then_draw)
return
}
// 触发draw.js中的方法
window.Bus.$emit("openDraw", {
openCallback: () => {
that.socket.emit('message', {
emitType: "startRemoteDraw",
room: that.roomId,
to: that.socketId
});
},
closeCallback: (drawCount) => {
that.socket.emit('message', {
emitType: "stopRemoteDraw",
room: that.roomId,
to: that.socketId,
drawCount : drawCount
});
},
localDrawCallback : (data) => {
Object.entries(that.remoteMap).forEach(([id, remote]) => {
if(remote && remote.sendDataChannel){
const sendDataChannel = remote.sendDataChannel;
if (!sendDataChannel || sendDataChannel.readyState !== 'open') {
that.addSysLogs("sendDataChannel error in draw")
return;
}
sendDataChannel.send(JSON.stringify(data));
}
});
}
})
},
// 开始本地录制
openLocalScreen: function () {
let that = this;
if (!this.switchData.openScreen) {
layer.msg(this.lang.feature_close)
this.addUserLogs(this.lang.feature_close)
return
}
if (this.isMobile) {
layer.msg(this.lang.mobile_not_support_recording)
this.addUserLogs(this.lang.mobile_not_support_recording)
return
}
// 触发screen.js中的方法
window.Bus.$emit("openLocalScreen", {
openCallback : () => {
that.socket.emit('message', {
emitType: "startScreen",
room: that.roomId,
to : that.socketId
});
that.addUserLogs(that.lang.start_local_screen_recording);
},
closeCallback : (res) => {
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',
type: "webm/mp4",
size: res.size,
progress: 100,
done: true,
start: 0,
cost: res.times
})
that.socket.emit('message', {
emitType: "stopScreen",
to : that.socketId,
room: that.roomId,
size: res.size,
cost: res.times
});
that.addUserLogs(that.lang.end_local_screen_recording);
}
});
},
// 打开公共聊天室
openChatingComm: function () {
if (!this.switchData.openCommRoom) {
layer.msg(this.lang.feature_close)
this.addUserLogs(this.lang.feature_close)
return
}
let that = this;
let options = {
type: 1,
fixed: false, //不固定
maxmin: false,
shadeClose : true,
area: ['600px', '600px'],
title: this.lang.public_chat_channel,
success: function (layero, index) {
let lIndex = layer.load(1);
setTimeout(() => {
layer.close(lIndex)
that.chatingCommTpl();
}, 300);
if(window.tlrtcfile.chatKeydown){
tlrtcfile.chatKeydown(document.getElementById("chating_comm_value"), sendChatingComm)
}
},
content: `
<div class="layui-col-sm12" style="padding: 15px;">
<div class="layui-card" id="chating_comm_tpl_view" style="padding: 5px;overflow-x: hidden;"> </div>
<script id="chating_comm_tpl" type="text/html">
<div style="text-align: center; color: #000000; font-size: 12px;margin-top: -5px; margin-bottom: 20px;"> -------- ${this.lang.only_show}${this.switchData.chatingCommCount || 10}${this.lang.history_msg} -------- </div>
{{# layui.each(d, function(index, info){ }}
<div style="margin-bottom: 30px;display: inline-flex;">
<a > <img style="width: 32px; height: 32px;" src="/image/44826979.png" alt="img"> </a>
<div style="margin-left: 15px; margin-top: -5px;">
<div style="word-break: break-all;"> <small>${this.lang.room}: <b>{{info.room}}</b></small> - <small>${this.lang.user}: <b>{{info.socketId}}</b></small> - <small>${this.lang.time}: <b>{{info.timeAgo}}</b></small> </div>
<div style="margin-top: 5px;word-break: break-all;">说: <b style="font-weight: bold; font-size: large;"> {{info.msg}} </b></div>
</div>
</div>
{{# }); }}
</script>
</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>
<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>
`
}
if (this.isMobile) {
delete options.area
}
layer.closeAll(function () {
let index = layer.open(options)
if (that.isMobile) {
layer.full(index)
}
})
this.addUserLogs(this.lang.open_public_chat_panel)
},
// 公共聊天室渲染数据
chatingCommTpl: function () {
let that = this;
let tpl_html = document.getElementById("chating_comm_tpl");
let tpl_view_html = document.getElementById("chating_comm_tpl_view");
if (tpl_html && tpl_view_html) {
this.tpl(tpl_html, this.receiveChatCommList, tpl_view_html)
let chatDom = document.querySelector("#chating_comm_tpl_view")
let chatDomHeight = chatDom.clientHeight
let height = 0;
if (this.isMobile) {
height = document.documentElement.clientHeight - 235;
} else {
height = 350
}
if (chatDomHeight > height) {
chatDom.style.height = height + "px"
chatDom.style.overflowY = "scroll"
} else {
chatDom.style.overflowY = "none"
}
if(window.tlrtcfile.scrollToBottom){
window.tlrtcfile.scrollToBottom(chatDom, 1000, 100)
}
}
},
// laytpl渲染
tpl: function (tpl_html, data, tpl_view_html, callback) {
if (window.laytpl) {
laytpl(tpl_html.innerHTML).render(data, (html) => {
tpl_view_html.innerHTML = html;
if (callback) {
callback()
}
});
}
},
// 发送公共聊天室消息
sendChatingComm: function () {
if (!this.isJoined) {
layer.msg(this.lang.please_join_then_send)
this.addUserLogs(this.lang.please_join_then_send)
return
}
let content = document.querySelector("#chating_comm_value").value;
if (content === '' || content === undefined) {
layer.msg(this.lang.please_fill_content)
this.addUserLogs(this.lang.please_fill_content)
return
}
if (content.length > 1000) {
layer.msg(this.lang.content_max_1000)
this.addUserLogs(this.lang.content_max_1000)
return
}
this.socket.emit('chatingComm', {
msg: tlrtcfile.escapeStr(content),
room: this.roomId,
socketId: this.socketId,
});
this.addUserLogs(this.lang.public_channel_send_done);
document.querySelector("#chating_comm_value").value = ''
},
// 房间内群聊弹窗
openChatingRoom: function () {
let that = this;
if (window.layer) {
let options = {
type: 1,
fixed: false, //不固定
maxmin: false,
shadeClose : true,
area: ['600px', '600px'],
title: `${this.roomId}` + this.lang.chat_channel,
success: function (layero, index) {
if (window.layer && window.layui && window.layedit) {
that.txtEditId = layedit.build('chating_room_value', {
tool: ['strong', 'italic', 'underline', 'del', '|', 'left', 'center', 'right'],
height: 120
});
}
that.chatingRoomTpl();
if(window.tlrtcfile.chatKeydown){
let textareaIframe = document.getElementsByTagName("iframe");
if(textareaIframe && textareaIframe.length === 1){
tlrtcfile.chatKeydown(
document.getElementsByTagName("iframe")[0].contentDocument.body,
sendChatingRoom
)
}
}
},
content: `
<div class="layui-col-sm12" style="padding: 15px;">
<div id="chating_room_tpl_view" style="padding: 5px;"> </div>
<script id="chating_room_tpl" type="text/html">
{{# layui.each(d, function(index, info){ }}
{{# if(info.socketId !== '${this.socketId}') { }}
<div style="margin-bottom: 30px;display: inline-flex;width:100%;">
<a > <img style="width: 32px; height: 32px;" src="/image/44826979.png" alt="img"> </a>
<div style="margin-left: 15px; margin-top: -5px;width:100%;">
<div style="word-break: break-all;">
<small>${this.lang.user}: <b>{{info.nickName}}</b></small> -
<small>id: <b>{{info.socketId}}</b></small> -
<small>${this.lang.time}: <b>{{info.timeAgo}}</b></small>
</div>
<div style="margin-top: 5px;word-break: break-all;width: 90%;">
<b style="font-weight: bold; font-size: large;"> {{- info.content}} </b>
</div>
</div>
</div>
{{# }else { }}
<div style="margin-bottom: 30px;display: inline-flex;width:100%;">
<div style="margin-right: 15px; margin-top: -5px;width:100%;text-align: right;">
<div style="word-break: break-all;">
<small>${this.lang.self}: {{info.nickName}} </small> -
<small>${this.lang.time}: <b>{{info.timeAgo}}</b></small>
</div>
<div style="margin-top: 5px;word-break: break-all;width: 90%; margin-left: 10%;">
<b style="font-weight: bold; font-size: large;"> {{- info.content}} </b>
</div>
</div>
<a > <img style="width: 32px; height: 32px;" src="/image/44826979.png" alt="img"> </a>
</div>
{{# } }}
{{# }); }}
</script>
</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>
<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>
`
}
if (this.isMobile) {
delete options.area
}
layer.closeAll(function () {
let index = layer.open(options)
if (that.isMobile) {
layer.full(index)
}
})
}
this.addUserLogs(this.lang.open_room_chat_panel)
},
// 房间内群聊渲染
chatingRoomTpl: function () {
let tpl_html = document.getElementById("chating_room_tpl");
let tpl_view_html = document.getElementById("chating_room_tpl_view");
if (tpl_html && tpl_view_html) {
this.tpl(tpl_html, this.receiveChatRoomList, tpl_view_html)
let chatDom = document.querySelector("#chating_room_tpl_view")
let chatDomHeight = chatDom.clientHeight
let height = 0;
if (this.isMobile) {
height = document.documentElement.clientHeight - 335;
} else {
height = 300
}
if (chatDomHeight > height) {
chatDom.style.height = height + "px"
chatDom.style.overflowY = "scroll"
} else {
chatDom.style.overflowY = "none"
}
if(window.tlrtcfile.scrollToBottom){
window.tlrtcfile.scrollToBottom(chatDom, 1000, 100)
}
}
},
// 房间内群聊发言
sendChatingRoom: function () {
if (!this.isJoined) {
layer.msg(this.lang.please_join_then_send)
this.addUserLogs(this.lang.please_join_then_send);
return
}
if (!this.hasManInRoom) {
layer.msg(this.lang.room_least_two_can_send_content)
this.addUserLogs(this.lang.room_least_two_can_send_content);
return
}
let realContent = layedit.getContent(this.txtEditId)
if (realContent.length <= 0) {
layer.msg(this.lang.please_enter_content)
this.addUserLogs(this.lang.please_enter_content);
return
}
if (realContent.length > 10000) {
layer.msg(this.lang.content_max_10000)
this.addUserLogs(this.lang.content_max_10000);
return
}
this.socket.emit('chatingRoom', {
content: tlrtcfile.escapeStr(realContent),
room: this.roomId,
from: this.socketId,
nickName : this.nickName,
recoderId: this.recoderId
});
let now = new Date().toLocaleString();
this.receiveChatRoomList.push({
socketId: this.socketId,
content: realContent,
nickName : this.nickName,
time: now,
timeAgo : window.util ? util.timeAgo(now) : now
});
this.chatingRoomTpl();
layer.msg(this.lang.text_send_done)
this.addUserLogs(this.lang.text_send_done);
layedit.setContent(this.txtEditId, "", false)
},
// 中继信息提示
useTurnMsg: function () {
layer.msg(this.lang.relay_on)
this.addUserLogs(this.lang.relay_on)
},
// 当前网络状态
networkMsg: function () {
layer.msg(this.lang.current_network + (this.network !== 'wifi' ? this.lang.mobile_data : this.network))
this.addUserLogs(this.lang.current_network + (this.network !== 'wifi' ? this.lang.mobile_data : this.network))
},
// 添加弹窗
addPopup: function (msg) {
this.popUpList.push({
title : msg.title,
message : msg.msg
})
},
// 记录系统日志
addSysLogs: function (msg) {
this.addLogs(msg, "【"+this.lang.sys_log+"】: ")
},
// 记录用户操作日志
addUserLogs: function (msg) {
this.addLogs(msg, "【"+this.lang.op_log+"】: ")
},
// 记录日志
addLogs: function (msg, type) {
if (this.logs.length > 1000) {
this.logs.shift();
}
this.logs.unshift({
type: type,
msg: msg,
time: new Date().toLocaleString()
})
},
// 清空日志
cleanLogs: function () {
this.logs = []
this.addSysLogs(this.lang.clear_log)
},
// 发送建议反馈
sendBugs: function () {
let that = this;
$("#sendBugs").removeClass("layui-anim-rotate")
setTimeout(() => {
$("#sendBugs").addClass("layui-anim-rotate")
}, 50)
setTimeout(() => {
layer.prompt({
formType: 2,
title: that.lang.please_describe_your_feedback,
}, function (value, index, elem) {
that.socket.emit('message', {
emitType: "sendBugs",
msg: value,
room: that.roomId,
to: that.socketId
});
layer.msg(that.lang.send_bug_info_ok)
layer.close(index);
that.addUserLogs(that.lang.send_bug_info_ok + ", " + value);
});
}, 500);
},
// 随机刷新房间号
refleshRoom: function () {
if (!this.isJoined) {
this.roomId = parseInt(Math.random() * 100000);
this.addPopup({
title : this.lang.refresh_room,
msg : this.lang.you_refresh_room + this.roomId
});
this.addUserLogs(this.lang.you_refresh_room + this.roomId);
}
},
// 复制分享房间url
shareUrl: function () {
let that = this;
layer.closeAll(function () {
layer.open({
type: 1,
closeBtn: 0,
fixed: true,
maxmin: false,
shadeClose: true,
area: ['350px', '380px'],
title: that.lang.share_join_room,
success: function (layero, index) {
let content = window.tlrtcfile.addUrlHashParams({
r : that.roomId,
t : that.roomType
});
document.querySelector(".layui-layer-title").style.borderTopRightRadius = "8px";
document.querySelector(".layui-layer-title").style.borderTopLeftRadius = "8px";
document.querySelector(".layui-layer").style.borderRadius = "8px";
if(window.tlrtcfile.getQrCode){
tlrtcfile.getQrCode("tl-rtc-file-room-share-image", content)
}
document.querySelector("#shareUrl").setAttribute("data-clipboard-text", content);
let clipboard = new ClipboardJS('#shareUrl');
clipboard.on('success', function (e) {
e.clearSelection();
setTimeout(() => {
layer.msg(that.lang.copy_room_link)
}, 500);
});
that.addUserLogs(that.lang.copy_room_link);
},
content: `
<div style="margin-top: 20px; text-align: center; margin-bottom: 25px;">
<div id="tl-rtc-file-room-share"> ${that.lang.share_join_room_done}
<i class="layui-icon layui-icon-ok-circle" style="margin-top: 3px; position: absolute; margin-left: 10px; color: #96e596; font-weight: 300;"></i>
</div>
</div>
<div id="tl-rtc-file-room-share-image">
`
})
})
this.addUserLogs(this.lang.open_share_join_room)
},
// 获取分享的取件码文件
handlerGetCodeFile: function () {
let that = this;
let hash = window.location.hash || "";
if (hash && hash.includes("#")) {
let codeIdArgs = hash.split("c=");
if (codeIdArgs && codeIdArgs.length > 1) {
this.codeId = (codeIdArgs[1] + "").replace(/\s*/g, "").substring(0, 40);
layer.confirm(this.lang.is_pickup_code, (index) => {
window.location.hash = "";
layer.close(index)
that.getCodeFile();
}, (index) => {
that.codeId = "";
window.location.hash = "";
layer.close(index)
})
this.addPopup({
title : this.lang.share_pickup_code_file,
msg : this.lang.get_pickup_file + this.codeId
});
this.addUserLogs(this.lang.get_pickup_file + this.codeId);
}
}
},
// 分享进入房间
handlerJoinShareRoom: function () {
let that = this;
let hash = window.location.hash || "";
if (hash && hash.includes("#") && hash.includes("r=")) {
//房间号
let roomIdArgs = tlrtcfile.getRequestHashArgs("r")
if (!roomIdArgs) {
return
}
//房间类型
let typeArgs = tlrtcfile.getRequestHashArgs("t")
this.roomId = (roomIdArgs + "").replace(/\s*/g, "").substring(0, 15);
if (window.layer) {
layer.confirm(this.lang.join_room + this.roomId, (index) => {
window.location.hash = "";
layer.close(index)
that.openRoomInput = true;
that.isShareJoin = true;
if(typeArgs && ['screen','live','video'].includes(typeArgs)){
if(typeArgs === 'screen'){
that.startScreenShare();
}else if(typeArgs === 'live'){
that.startLiveShare();
}else if(typeArgs === 'video'){
that.startVideoShare();
}
}else{
that.createFileRoom();
}
}, (index) => {
that.roomId = "";
window.location.hash = "";
layer.close(index)
})
}
this.addPopup({
title : this.lang.share_join_room,
msg : this.lang.you_join_room + this.roomId
});
this.addUserLogs(this.lang.you_join_room + this.roomId);
}
},
// 赞助面板
coffee: function () {
let options = {
type: 1,
fixed: false,
maxmin: false,
shadeClose: true,
area: ['300px', '350px'],
title: this.lang.donate,
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";
},
content: `<img style=" width: 100%; height: 100%;border-bottom-left-radius: 8px; border-bottom-right-radius: 8px;" src="/image/coffee.jpeg" alt="img"> `
}
layer.closeAll(function () {
layer.open(options)
})
this.addUserLogs(this.lang.open_donate)
},
//点击下载文件面板
clickReceiveFile: function () {
if(this.receiveFileRecoderList.length === 0){
layer.msg(this.lang.no_received_file)
return
}
this.showReceiveFile = !this.showReceiveFile;
if (this.showReceiveFile) {
this.addUserLogs(this.lang.expand_receive_file);
this.receiveFileMaskHeightNum = 20;
} else {
this.receiveFileMaskHeightNum = 150;
this.addUserLogs(this.lang.collapse_receive_file);
}
},
//点击已选文件面板
clickChooseFile: function () {
if(!this.hasManInRoom && !this.showChooseFile){
layer.msg(this.lang.room_least_two_can_send_content)
return
}
this.showChooseFile = !this.showChooseFile;
if (this.showChooseFile) {
this.chooseFileMaskHeightNum = 20;
this.addUserLogs(this.lang.expand_selected_file);
} else {
this.chooseFileMaskHeightNum = 150;
this.addUserLogs(this.lang.collapse_selected_file);
}
},
//点击待发送文件面板
clickSendFile: function () {
if(!this.hasManInRoom && !this.showSendFile){
layer.msg(this.lang.room_least_two_can_send_content)
return
}
this.showSendFile = !this.showSendFile;
if (this.showSendFile) {
this.sendFileMaskHeightNum = 20;
this.addUserLogs(this.lang.expand_wait_send_file);
} else {
this.sendFileMaskHeightNum = 150;
this.addUserLogs(this.lang.collapse_wait_send_file);
}
},
//点击发送文件历史记录面板
clickSendFileHistory: function () {
if(this.sendFileRecoderHistoryList.length === 0){
layer.msg(this.lang.no_send_file)
return
}
this.showSendFileHistory = !this.showSendFileHistory;
if (this.showSendFileHistory) {
this.sendFileHistoryMaskHeightNum = 20;
this.addUserLogs(this.lang.expand_send_file_record);
} else {
this.sendFileHistoryMaskHeightNum = 150;
this.addUserLogs(this.lang.collapse_send_file_record);
}
},
//点击查看日志面板
clickLogs: function (e) {
this.showLogs = !this.showLogs;
this.touchResize();
if (this.showLogs) {
this.addUserLogs(this.lang.expand_log);
this.logMaskHeightNum = 0;
} else {
this.addUserLogs(this.lang.collapse_log);
this.logMaskHeightNum = -150;
}
},
//点击打开音视频面板
clickMediaVideo: function () {
this.showMedia = !this.showMedia;
this.touchResize();
if (this.showMedia) {
this.addUserLogs(this.lang.expand_video);
this.mediaVideoMaskHeightNum = 0;
if(this.clientWidth < 500){
document.getElementById("iamtsm").style.marginLeft = '0';
}else{
document.getElementById("iamtsm").style.marginLeft = "50%";
}
} else {
this.addUserLogs(this.lang.collapse_video);
this.mediaVideoMaskHeightNum = -150;
document.getElementById("iamtsm").style.marginLeft = "0";
}
},
//点击打开屏幕共享面板
clickMediaScreen: function () {
this.showMedia = !this.showMedia;
this.touchResize();
if (this.showMedia) {
this.addUserLogs(this.lang.expand_screen_sharing);
this.mediaScreenMaskHeightNum = 0;
if(this.clientWidth < 500){
document.getElementById("iamtsm").style.marginLeft = "0";
}else{
document.getElementById("iamtsm").style.marginLeft = "50%";
}
} else {
this.addUserLogs(this.lang.collapse_screen_sharing);
this.mediaScreenMaskHeightNum = -150;
document.getElementById("iamtsm").style.marginLeft = "0";
}
},
//点击打开直播面板
clickMediaLive: function () {
this.showMedia = !this.showMedia;
this.touchResize();
if (this.showMedia) {
this.addUserLogs(this.lang.expand_live);
if(this.clientWidth < 500){
document.getElementById("iamtsm").style.marginLeft = "0";
}else{
document.getElementById("iamtsm").style.marginLeft = "50%";
}
this.mediaLiveMaskHeightNum = 0;
} else {
this.addUserLogs(this.lang.collapse_live);
this.mediaLiveMaskHeightNum = -150;
document.getElementById("iamtsm").style.marginLeft = "0";
}
},
typeInArr: function(arr, type, name = ""){
if(type === ''){
let fileTail = name.split(".").pop()
return arr.filter(item=>{
return fileTail.toLowerCase().includes(item) && name.endsWith("."+fileTail);
}).length > 0;
}else{
return arr.filter(item=>{
return type.toLowerCase().includes(item);
}).length > 0;
}
},
//文件大小
getFileSizeStr: function (size) {
let sizeStr = (size / 1048576).toString();
let head = sizeStr.split(".")[0];
let tail = "";
if (sizeStr.split(".")[1]) {
tail = sizeStr.split(".")[1].substring(0, 3);
}
return head + '.' + tail + "M";
},
//创建文件发送房间
createFileRoom: function () {
this.openRoomInput = !this.openRoomInput;
if(this.openRoomInput){
return
}
this.roomId = this.roomId.toString().replace(/\s*/g, "")
if (this.roomId === null || this.roomId === undefined || this.roomId === '') {
layer.msg(this.lang.please_enter_room_num)
this.addUserLogs(this.lang.please_enter_room_num);
return;
}
if (!this.switchData.allowChinese && window.tlrtcfile.containChinese(this.roomId)) {
layer.msg(this.lang.room_num_no_zh)
this.addUserLogs(this.lang.room_num_no_zh);
return;
}
if (!this.switchData.allowNumber && window.tlrtcfile.containNumber(this.roomId)) {
layer.msg(this.lang.room_num_no_number)
this.addUserLogs(this.lang.room_num_no_number);
return;
}
if (!this.switchData.allowSymbol && window.tlrtcfile.containSymbol(this.roomId)) {
layer.msg(this.lang.room_num_no_special_symbols)
this.addUserLogs(this.lang.room_num_no_special_symbols);
return;
}
if (this.chooseFileList.length > 0) {
layer.msg(this.lang.please_join_then_choose_file)
this.addUserLogs(this.lang.please_join_then_choose_file);
return;
}
if (this.roomId) {
if (this.roomId.toString().length > 15) {
layer.msg(this.lang.room_num_too_long)
this.addUserLogs(this.lang.room_num_too_long);
return;
}
this.setNickName();
this.socket.emit('createAndJoin', {
room: this.roomId,
type : 'file',
nickName : this.nickName,
langMode : this.langMode,
ua: this.isMobile ? 'mobile' : 'pc',
network : this.network
});
this.isJoined = true;
this.addPopup({
title : this.lang.file_room,
msg : this.lang.you_enter_file_room + this.roomId
});
this.addUserLogs( this.lang.you_enter_file_room + this.roomId);
}
},
//创建流媒体房间
createMediaRoom: function (type) {
this.roomId = this.roomId.toString().replace(/\s*/g, "")
if (this.roomId === null || this.roomId === undefined || this.roomId === '') {
layer.msg(this.lang.please_enter_room_num)
this.addUserLogs(this.lang.please_enter_room_num);
return;
}
if (this.roomId) {
if (this.roomId.toString().length > 15) {
layer.msg(this.lang.room_num_too_long)
this.addUserLogs(this.lang.room_num_too_long);
return;
}
this.setNickName();
this.socket.emit('createAndJoin', {
room: this.roomId,
type: type,
nickName : this.nickName,
langMode : this.langMode,
ua: this.isMobile ? 'mobile' : 'pc',
network : this.network
});
this.isJoined = true;
this.roomType = type;
this.addPopup({
title : this.lang.stream_room,
msg : this.lang.you_enter_stream_room + this.roomId
});
this.addUserLogs(this.lang.you_enter_stream_room + this.roomId);
}
},
//创建密码房间
createPasswordRoom: function (password) {
this.roomId = this.roomId.toString().replace(/\s*/g, "")
if (this.roomId === null || this.roomId === undefined || this.roomId === '') {
layer.msg(this.lang.please_enter_room_num)
this.addUserLogs(this.lang.please_enter_room_num);
return;
}
if (this.roomId) {
if (this.roomId.toString().length > 15) {
layer.msg(this.lang.room_num_too_long)
this.addUserLogs(this.lang.room_num_too_long);
return;
}
if (password.toString().length > 15) {
layer.msg(this.lang.password_too_long)
this.addUserLogs(this.lang.password_too_long);
return;
}
this.setNickName();
this.socket.emit('createAndJoin', {
room: this.roomId,
type : 'password',
password: password,
nickName : this.nickName,
langMode : this.langMode,
ua: this.isMobile ? 'mobile' : 'pc',
network : this.network
});
this.isJoined = true;
this.addPopup({
title : this.lang.password_room,
msg : this.lang.you_enter_password_room + this.roomId
});
this.addUserLogs(this.lang.you_enter_password_room + this.roomId);
}
},
//退出房间
exitRoom: function () {
if (this.roomId) {
this.socket.emit('exit', {
from: this.socketId,
room: this.roomId,
recoderId: this.recoderId
});
}
for (let i in this.rtcConns) {
let rtcConnect = this.rtcConns[i];
rtcConnect.close();
rtcConnect = null;
}
window.location.reload();
},
//创立链接
createRtcConnect: function (id) {
if (id === undefined) {
return;
}
let that = this;
let rtcConnect = new RTCPeerConnection(this.config);
//ice
rtcConnect.onicecandidate = (e) => {
that.iceCandidate(rtcConnect, id, e)
};
rtcConnect.oniceconnectionstatechange = (e) => {
that.addSysLogs("iceConnectionState: " + rtcConnect.iceConnectionState);
that.setRemoteInfo(id, {
iceConnectionState : rtcConnect.iceConnectionState
})
}
//媒体流通道
rtcConnect.ontrack = (e) => {
that.mediaTrackHandler(e, id)
};
//文件发送数据通道
let sendFileDataChannel = rtcConnect.createDataChannel('sendFileDataChannel');
sendFileDataChannel.binaryType = 'arraybuffer';
sendFileDataChannel.addEventListener('open', (event) => {
if (sendFileDataChannel.readyState === 'open') {
that.addSysLogs(that.lang.establish_connection)
}
});
sendFileDataChannel.addEventListener('close', (event) => {
if (sendFileDataChannel.readyState === 'close') {
that.addSysLogs(that.lang.connection_closed)
}
});
sendFileDataChannel.addEventListener('error', (error) => {
console.error(error.error)
that.addSysLogs(that.lang.connection_disconnected + ",file:e=" + error)
that.removeStream(null, id, null)
});
//自定义数据发送通道
let sendDataChannel = rtcConnect.createDataChannel('sendDataChannel');
sendDataChannel.binaryType = 'arraybuffer';
sendDataChannel.addEventListener('open', (event) => {
if (sendDataChannel.readyState === 'open') {
that.addSysLogs(that.lang.establish_connection)
}
});
sendDataChannel.addEventListener('close', (event) => {
if (sendDataChannel.readyState === 'close') {
that.addSysLogs(that.lang.connection_closed)
}
});
sendDataChannel.addEventListener('error', (error) => {
console.error(error.error)
that.addSysLogs(that.lang.connection_disconnected + ",cus:e=" + error)
that.removeStream(null, id, null)
});
rtcConnect.addEventListener('datachannel', (event) => {
that.initReceiveDataChannel(event, id);
});
rtcConnect.onremovestream = (e) => {
that.removeStream(rtcConnect, id, e)
};
//保存peer连接
this.rtcConns[id] = rtcConnect;
if (!this.remoteMap[id]) {
Vue.set(this.remoteMap, id, {
id: id,
receiveChatRoomSingleList : [],
p2pMode : '识别中...',
sendFileDataChannel: sendFileDataChannel,
sendDataChannel : sendDataChannel
})
}
return rtcConnect;
},
//获取本地与远程连接
getOrCreateRtcConnect: function (id) {
// 获取rtc缓存连接
let rtcConnect = this.rtcConns[id];
// 不存在,创建一个
if (typeof (rtcConnect) == 'undefined') {
rtcConnect = this.createRtcConnect(id);
}
return rtcConnect;
},
//远程媒体流处理
mediaTrackHandler: function(event, id){
let that = this;
if(event.track.kind === 'audio'){
return;
}
let video = null;
if(this.roomType === 'video'){
$(`#mediaVideoRoomList`).append(`
<div class="tl-rtc-file-mask-media-video">
<video id="otherMediaVideoShare${id}" preload="auto" autoplay="autoplay" x-webkit-airplay="true" playsinline ="true" webkit-playsinline ="true" x5-video-player-type="h5" x5-video-player-fullscreen="true" x5-video-orientation="portraint" ></video>
<svg id="otherMediaVideoShareVideoSvg${id}" class="icon" aria-hidden="true" style="width: 100%;height: 100%;display:none;">
<use xlink:href="#icon-rtc-file-shexiangtou_guanbi"></use>
</svg>
<svg id="otherMediaVideoShareAudioOpenSvg${id}" class="tl-rtc-file-mask-media-video-other-audio" aria-hidden="true">
<use xlink:href="#icon-rtc-file-maikefeng-XDY"></use>
</svg>
<svg id="otherMediaVideoShareAudioCloseSvg${id}" class="tl-rtc-file-mask-media-video-other-audio" aria-hidden="true" style="display:none;">
<use xlink:href="#icon-rtc-file-guanbimaikefeng"></use>
</svg>
</div>
`);
video = document.querySelector(`#otherMediaVideoShare${id}`);
} else if(this.roomType === 'screen'){
$(`#mediaScreenRoomList`).append(`
<div class="tl-rtc-file-mask-media-video">
<video id="otherMediaScreenShare${id}" playsinline ="true" webkit-playsinline ="true" x5-video-player-type="h5" x5-video-player-fullscreen="true" x5-video-orientation="portraint" ></video>
<svg id="otherMediaScreenShareVideoSvg${id}" class="icon" aria-hidden="true" style="width: 100%;height: 100%;display:none;">
<use xlink:href="#icon-rtc-file-guanbipingmu"></use>
</svg>
</div>
`);
video = document.querySelector(`#otherMediaScreenShare${id}`);
} else if(this.roomType === 'live'){
if(this.liveShareMode === 'video'){
$(`#mediaLiveRoomList`).append(`
<div class="tl-rtc-file-mask-media-video">
<video id="otherMediaLiveShare${id}" playsinline ="true" webkit-playsinline ="true" x5-video-player-type="h5" x5-video-player-fullscreen="true" x5-video-orientation="portraint" ></video>
<svg id="otherMediaLiveShareVideoSvg${id}" class="icon" aria-hidden="true" style="width: 100%;height: 100%;display:none;">
<use xlink:href="#icon-rtc-file-shexiangtou_guanbi"></use>
</svg>
<svg id="otherMediaLiveShareAudioOpenSvg${id}" class="tl-rtc-file-mask-media-video-other-audio" aria-hidden="true">
<use xlink:href="#icon-rtc-file-maikefeng-XDY"></use>
</svg>
<svg id="otherMediaLiveShareAudioCloseSvg${id}" class="tl-rtc-file-mask-media-video-other-audio" aria-hidden="true" style="display:none;">
<use xlink:href="#icon-rtc-file-guanbimaikefeng"></use>
</svg>
</div>
`);
}else if(this.liveShareMode === 'screen'){
$(`#mediaLiveRoomList`).append(`
<div class="tl-rtc-file-mask-media-video">
<video id="otherMediaLiveShare${id}" playsinline ="true" webkit-playsinline ="true" x5-video-player-type="h5" x5-video-player-fullscreen="true" x5-video-orientation="portraint"></video>
<svg id="otherMediaLiveShareVideoSvg${id}" class="icon" aria-hidden="true" style="width: 100%;height: 100%;display:none;">
<use xlink:href="#icon-rtc-file-shexiangtou_guanbi"></use>
</svg>
<svg id="otherMediaLiveShareAudioOpenSvg${id}" class="tl-rtc-file-mask-media-video-other-audio" aria-hidden="true">
<use xlink:href="#icon-rtc-file-maikefeng-XDY"></use>
</svg>
<svg id="otherMediaLiveShareAudioCloseSvg${id}" class="tl-rtc-file-mask-media-video-other-audio" aria-hidden="true" style="display:none;">
<use xlink:href="#icon-rtc-file-guanbimaikefeng"></use>
</svg>
</div>
`);
}
video = document.querySelector(`#otherMediaLiveShare${id}`);
}
if(video){
video.addEventListener('loadedmetadata', function() {
video.play();
that.addSysLogs("loadedmetadata")
// ios 微信浏览器兼容问题
document.addEventListener('WeixinJSBridgeReady', function () {
that.addSysLogs("loadedmetadata WeixinJSBridgeReady")
video.play();
}, false);
});
document.addEventListener('WeixinJSBridgeReady', function () {
that.addSysLogs("WeixinJSBridgeReady")
video.play();
}, false);
video.srcObject = event.streams[0]
video.play();
}
},
// 初始发送
// pickRecoder : 指定发送记录进行发送
initSendFile: function (pickRecoder) {
if(!this.hasManInRoom){
layer.msg(this.lang.room_least_two_can_send_content)
return
}
//选中一个记录进行发送
this.chooseSendFileRecoder(pickRecoder);
},
// 选一个未发送的文件进行发送,如有下一个,切换下一个文件
// pickRecoder : 指定发送记录进行发送
chooseSendFileRecoder: async function (pickRecoder) {
let chooseFile = null;
let chooseFileRecoder = null;
if(this.isSendFileToSingleSocket){
chooseFileRecoder = pickRecoder;
}else{
this.addSysLogs(this.lang.select_wait_send_record)
for (let i = 0; i < this.sendFileRecoderList.length; i++) {
let recoder = this.sendFileRecoderList[i]
if (!recoder.done) {
chooseFileRecoder = recoder;
break;
}
}
// 如果没有,说明全部发完
if(!chooseFileRecoder){
this.chooseFileList = []
this.sendFileRecoderList = []
this.addPopup({
title : this.lang.send_file,
msg : this.lang.file_send_done
});
this.addSysLogs(this.lang.file_send_done)
this.isSending = false;
this.allSended = true;
return
}
}
// 还有没有发送的记录根据file index 找到文件
let filterFile = this.chooseFileList.filter(item=>{
return item.index === chooseFileRecoder.index;
});
if(filterFile.length === 0){
this.addUserLogs(this.lang.failed_find_file);
layer.msg(this.lang.failed_find_file);
return
}
chooseFile = filterFile[0];
if(chooseFile == null){
this.addUserLogs(this.lang.failed_find_file);
layer.msg(this.lang.failed_find_file);
return
}
this.currentChooseFile = chooseFile;
this.currentChooseFileRecoder = chooseFileRecoder;
this.socket.emit('message', {
emitType: "sendFileInfo",
index: this.currentChooseFile.index,
name: this.currentChooseFile.name,
type: this.currentChooseFile.type,
size: this.currentChooseFile.size,
room: this.roomId,
from: this.socketId,
nickName : this.nickName,
to: this.currentChooseFileRecoder.id,
recoderId: this.recoderId
});
this.isSending = true;
let remote = this.remoteMap[this.currentChooseFileRecoder.id]
let fileReader = remote[this.currentChooseFile.index + "reader"];
fileReader.addEventListener('loadend', this.sendFileToRemoteByLoop);
fileReader.addEventListener('error', error => {
that.addSysLogs(this.lang.read_file_error + " : " + error);
});
fileReader.addEventListener('abort', event => {
that.addSysLogs(this.lang.read_file_interrupt + " : " + event);
});
this.readSlice(0);
},
//一次发送一个文件给一个用户
sendFileToRemoteByLoop: function (event) {
let that = this;
if (!this.currentChooseFileRecoder) {
return
}
let remote = this.remoteMap[this.currentChooseFileRecoder.id];
let fileOffset = remote[this.currentChooseFile.index + "offset"]
let sendFileDataChannel = remote.sendFileDataChannel;
if (!sendFileDataChannel || sendFileDataChannel.readyState !== 'open') {
this.addSysLogs(this.lang.file_send_channel_not_establish)
return;
}
let sendFileInfoAck = remote[this.currentChooseFile.index + "ack"]
// 还不能进行发送,等一下
if (!sendFileInfoAck) {
this.addSysLogs(this.lang.wait_ack)
setTimeout(() => {
that.sendFileToRemoteByLoop(event)
}, 500);
return
}
this.setRemoteInfo(this.currentChooseFileRecoder.id, {
[this.currentChooseFile.index + "status"]: 'sending'
})
// 开始发送通知
if (fileOffset === 0) {
this.addPopup({
title : this.lang.send_file,
msg : this.lang.sending_to + this.currentChooseFileRecoder.id.substr(0, 4) + ",0%。"
});
this.addSysLogs(this.lang.sending_to + this.currentChooseFileRecoder.id.substr(0, 4) + ",0%。")
this.updateSendFileRecoderProgress(this.currentChooseFileRecoder.id, {
start: Date.now()
})
}
//缓冲区暂定 256kb
sendFileDataChannel.bufferedAmountLowThreshold = 16 * 1024 * 16;
//局域网一般不会走缓冲区所以bufferedAmount一般为0公网部分情况受限于带宽bufferedAmount可能会逐渐堆积从而进行排队
if (sendFileDataChannel.bufferedAmount > sendFileDataChannel.bufferedAmountLowThreshold) {
this.addSysLogs(
that.lang.file_send_channel_buffer_full + ",bufferedAmount=" +
sendFileDataChannel.bufferedAmount + ",bufferedAmountLowThreshold=" +
sendFileDataChannel.bufferedAmountLowThreshold
)
sendFileDataChannel.onbufferedamountlow = () => {
that.addSysLogs(
that.lang.file_send_channel_buffer_recover + ",bufferedAmount=" +
sendFileDataChannel.bufferedAmount
)
sendFileDataChannel.onbufferedamountlow = null;
that.sendFileToRemoteByLoop(event);
}
return;
}
// 发送数据
sendFileDataChannel.send(event.target.result);
fileOffset += event.target.result.byteLength;
remote[this.currentChooseFile.index + "offset"] = fileOffset
this.currentSendAllSize += event.target.result.byteLength;
//更新发送进度
this.updateSendFileRecoderProgress(this.currentChooseFileRecoder.id, {
progress: ((fileOffset / this.currentChooseFile.size) * 100).toFixed(3) || 0
})
//发送完一份重置相关数据
if (fileOffset === this.currentChooseFile.size) {
this.addPopup({
title : this.lang.send_file,
msg : this.lang.sending_to + this.currentChooseFileRecoder.id.substr(0, 4) + ",100%。"
});
this.addSysLogs(this.lang.sending_to + this.currentChooseFileRecoder.id.substr(0, 4) + ",100%。")
this.socket.emit('message', {
emitType: "sendDone",
room: this.roomId,
from: this.socketId,
size: this.currentChooseFile.size,
name: this.currentChooseFile.name,
type: this.currentChooseFile.type,
to: this.currentChooseFileRecoder.id
});
//更新发送进度
this.updateSendFileRecoderProgress(this.currentChooseFileRecoder.id, {
progress: 100,
done: true
})
this.setRemoteInfo(this.currentChooseFileRecoder.id, {
[this.currentChooseFile.index + "status"]: 'done'
})
this.isSending = false;
//发完一条记录,继续下一条
this.currentChooseFile = null;
this.currentChooseFileRecoder = null;
//如果是单独发送给某个用户,发完直接退出发送逻辑
if(this.isSendFileToSingleSocket){
this.isSendFileToSingleSocket = false;
setTimeout(() => {
let allDone = this.sendFileRecoderList.filter(item => {
return item.done;
}).length === this.sendFileRecoderList.length;
// 全部发完
if(allDone){
this.chooseFileList = []
this.sendFileRecoderList = []
this.addPopup({
title : this.lang.send_file,
msg : this.lang.file_send_done
});
this.addSysLogs(this.lang.file_send_done)
this.allSended = true;
return
}
}, 1000);
}else{
this.isSendAllWaiting = true;
setTimeout(() => {
//如果不是点击单独发送,继续下一个记录, 缓冲一秒钟
this.initSendFile()
this.isSendAllWaiting = false;
}, 1000);
}
return
}
// 继续下一个分片
if (fileOffset < this.currentChooseFile.size) {
this.readSlice(fileOffset + this.chunkSize)
}
},
//文件分片 -- 发送
readSlice: function (offset) {
if (this.currentChooseFileRecoder) {
let remote = this.remoteMap[this.currentChooseFileRecoder.id]
let fileOffset = remote[this.currentChooseFile.index + "offset"]
let fileReader = remote[this.currentChooseFile.index + "reader"]
let slice = this.currentChooseFile.slice(fileOffset, offset + this.chunkSize);
fileReader.readAsArrayBuffer(slice);
}
},
//初始化接收数据事件
initReceiveDataChannel: function (event, id) {
if (!id || !event) {
return;
}
let currentRtc = this.getRemoteInfo(id);
if (!currentRtc) {
return
}
let receiveChannel = event.channel;
//文件接收
if(receiveChannel.label === 'sendFileDataChannel'){
receiveChannel.binaryType = 'arraybuffer';
receiveChannel.onmessage = (evt) => {
this.receiveFileData(evt, id);
};
receiveChannel.onopen = () => {
const readyState = receiveChannel.readyState;
this.addSysLogs(this.lang.file_receive_channel_ready + readyState)
};
receiveChannel.onclose = () => {
const readyState = receiveChannel.readyState;
this.addSysLogs(this.lang.file_receive_channel_closed + readyState)
};
this.setRemoteInfo(id, { receiveFileDataChannel: receiveChannel });
}
//自定义数据接收
if(receiveChannel.label === 'sendDataChannel'){
receiveChannel.binaryType = 'arraybuffer';
receiveChannel.onmessage = (evt) => {
//接收自定义数据 , 暂时用做远程画笔数据接收
if (!evt || !id) {
return;
}
let data = JSON.parse(evt.data) || {};
window.Bus.$emit("openRemoteDraw", data)
}
receiveChannel.onopen = () => {
const readyState = receiveChannel.readyState;
this.addSysLogs(this.lang.custom_data_receive_channel_ready + readyState)
};
receiveChannel.onclose = () => {
const readyState = receiveChannel.readyState;
this.addSysLogs(this.lang.custom_data_receive_channel_closed + readyState)
};
this.setRemoteInfo(id, { receiveDataChannel: receiveChannel });
}
},
//接收文件
receiveFileData: function (event, id) {
if (!event || !id) {
return;
}
let currentRtc = this.getRemoteInfo(id);
let receiveFiles = currentRtc.receiveFiles;
let name = receiveFiles.name;
let size = receiveFiles.size;
let type = receiveFiles.type;
//获取数据存下本地
let receiveBuffer = currentRtc.receiveBuffer || new Array();
let receivedSize = currentRtc.receivedSize || 0;
receiveBuffer.push(event.data);
receivedSize += event.data.byteLength;
this.setRemoteInfo(id, { receiveBuffer: receiveBuffer, receivedSize: receivedSize })
//更新接收进度
this.updateReceiveProgress(id, {
progress: ((receivedSize / size) * 100).toFixed(3) || 0
});
if (receivedSize === size) {
this.addSysLogs(name + this.lang.receive_done);
this.addPopup({
title : this.lang.file_receive,
msg : "[ " + name + " ]" + this.lang.receive_done
});
//更新接收进度
this.updateReceiveProgress(id, {
style: 'color: #ff5722;text-decoration: underline;',
progress: 100,
href: URL.createObjectURL(new Blob(receiveBuffer), { type: type }),
done: true
});
//清除接收的数据缓存
this.setRemoteInfo(id, { receiveBuffer: new Array(), receivedSize: 0 })
}
},
//关闭连接
closeDataChannels: function () {
for (let remote in this.remoteMap) {
let id = remote.id;
if(!id) continue;
let sendFileDataChannel = remote.sendFileDataChannel;
if(sendFileDataChannel){
sendFileDataChannel.close();
}
let sendDataChannel = remote.sendDataChannel;
if(sendDataChannel){
sendDataChannel.close();
}
let receiveFileDataChannel = remote.receiveFileDataChannel;
if(receiveFileDataChannel){
receiveFileDataChannel.close();
}
let receiveDataChannel = remote.receiveDataChannel;
if(receiveDataChannel){
receiveDataChannel.close();
}
}
},
//设置rtc缓存远程连接数据
setRemoteInfo(id, data) {
if (!id || !data) {
return;
}
let oldData = this.remoteMap[id];
if (oldData) {
Object.assign(oldData, data);
Vue.set(this.remoteMap, id, oldData);
}
},
//更新接收进度
updateReceiveProgress: function (id, data) {
for (let i = 0; i < this.receiveFileRecoderList.length; i++) {
let item = this.receiveFileRecoderList[i];
if (item.id === id && !item.done) {
if (item.start === 0) {
item.start = Date.now();
}
data.cost = ((Date.now() - item.start) / 1000).toFixed(3)
Object.assign(this.receiveFileRecoderList[i], data);
}
}
this.$forceUpdate();
},
//更新文件发送进度
updateSendFileRecoderProgress: function (id, data) {
for (let i = 0; i < this.sendFileRecoderList.length; i++) {
let item = this.sendFileRecoderList[i];
if (item.id === id && item.index === this.currentChooseFile.index && !item.done) {
data.cost = ((Date.now() - item.start) / 1000).toFixed(3);
Object.assign(this.sendFileRecoderList[i], data);
if(data.done){ // 发送完毕,统计到历史记录
this.sendFileRecoderHistoryList.push(this.sendFileRecoderList[i])
}
}
}
this.$forceUpdate();
},
//更新文件暂存状态
updateSendFileRecoderUpload: function (index, data) {
for (let i = 0; i < this.sendFileRecoderList.length; i++) {
let item = this.sendFileRecoderList[i];
if (item.index === index) {
Object.assign(this.sendFileRecoderList[i], data);
}
}
this.$forceUpdate();
},
//获取rtc缓存远程连接数据
getRemoteInfo(id) {
if (!id) {
return;
}
return this.remoteMap[id];
},
//移除rtc连接
removeStream: function (rtcConnect, id, event) {
this.getOrCreateRtcConnect(id).close;
const remoteInfo = this.remoteMap[id] || {};
const removeIsOwner = remoteInfo.owner;
delete this.rtcConns[id];
delete this.remoteMap[id];
//断开连接的时候,剔除此用户的发送记录
this.sendFileRecoderList = this.sendFileRecoderList.filter(item => {
return item.id !== id;
})
if(['live','video','screen'].includes(this.roomType)){
//主播异常关闭直播,观众页面强制刷新
if(this.roomType === 'live' && removeIsOwner){
window.location.reload()
}
//多人音视频/多人屏幕共享有人异常退出移除对应的video标签
if(this.roomType === 'video' || this.roomType === 'screen'){
$(`#otherMediaVideoShare${id}`).parent().remove();
}
}
},
// ice
iceCandidate: function (rtcConnect, id, event) {
if (event.candidate != null) {
let message = {
from: this.socketId,
to: id,
room: this.roomId,
sdpMid: event.candidate.sdpMid,
sdpMLineIndex: event.candidate.sdpMLineIndex,
sdp: event.candidate.candidate
};
this.socket.emit('candidate', message);
let ipRegex = /([0-9]{1,3}(\.[0-9]{1,3}){3})/;
let match = ipRegex.exec(event.candidate.candidate);
let ipAddress = match && Array.isArray(match) && match.length > 0 ? match[1] : "unknow";
if (ipAddress !== 'unknow') {
this.ips.push(ipAddress);
}
this.addSysLogs("IP: " + ipAddress);
}
},
// offer
offerSuccess: function (rtcConnect, id, offer) {
rtcConnect.setLocalDescription(offer).then(r => { })
let message = {
from: this.socketId,
to: id,
room: this.roomId,
sdp: offer.sdp
};
this.socket.emit('offer', message);
},
// offer
offerFailed: function (rtcConnect, id, error) {
this.addSysLogs(this.lang.offer_failed + error);
},
// answer
answerSuccess: function (rtcConnect, id, offer) {
rtcConnect.setLocalDescription(offer).then(r => { });
let message = {
from: this.socketId,
to: id,
room: this.roomId,
sdp: offer.sdp
};
this.socket.emit('answer', message);
},
// answer
answerFailed: function (rtcConnect, id, error) {
this.addSysLogs(this.lang.answer_failed + error);
},
//ice
addIceCandidateSuccess: function (res) {
this.addSysLogs(this.lang.add_ice_candidate_success);
},
//ice
addIceCandidateFailed: function (err) {
this.addSysLogs(this.lang.add_ice_candidate_failed + err);
},
socketListener: function () {
let that = this;
this.socket.on('connect_error', error => {
console.error('connect_error', error);
if(error){
layer.msg("socket服务连接失败请检查socket服务是否正常启动 " + error.message);
}
});
// created作用是让自己去和其他人建立rtc连接
// 1. 对于screen, video房间来说是双方都需要传输各自的媒体流
// 2. 对于live房间来说只有房主需要获取媒体流
this.socket.on('created', async function (data) {
that.addSysLogs(that.lang.receive_create_room_event + JSON.stringify(data));
that.socketId = data.id;
that.roomId = data.room;
that.recoderId = data.recoderId;
that.owner = data.owner;
//第一个人进入时获取媒体流数据此时无需addTrack因为房间只有一个连接addTrack没意义
//因为一个连接无需进行offer收集offer信息收集是从第二个连接开始。
if(data.peers.length === 0){
if(data.type === 'screen'){
window.Bus.$emit("startScreenShare");
}
if(data.type === 'video'){
window.Bus.$emit("startVideoShare", that.videoConstraints);
}
if(data.type === 'live'){
window.Bus.$emit("startLiveShare", {
liveShareMode : that.liveShareMode
});
}
}
for (let i = 0; i < data.peers.length; i++) {
let otherSocketId = data.peers[i].id;
let otherRtcConnect = that.getOrCreateRtcConnect(otherSocketId);
// 处理完连接后,更新下昵称
that.setRemoteInfo(otherSocketId, {
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 => {
if(data.type === 'screen'){
window.Bus.$emit("startScreenShare",(track, stream) => {
//其他人将数据流添加到通道中, 此时需要addTrack因为后面会有offer收集然后进行answer ...等后续操作
otherRtcConnect.addTrack(track, stream);
resolve()
});
}else if(data.type === 'video'){
window.Bus.$emit("startVideoShare", that.videoConstraints, (track, stream) => {
//其他人将数据流添加到通道中, 此时需要addTrack因为后面会有offer收集然后进行answer ...等后续操作
otherRtcConnect.addTrack(track, stream);
resolve()
});
}else{
resolve();
}
}).then(()=>{
otherRtcConnect.createOffer(that.options).then(offer => {
that.offerSuccess(otherRtcConnect, otherSocketId, offer);
}, error => {
that.offerFailed(otherRtcConnect, otherSocketId, error);
});
});
}
});
// join的作用是通知其他人我加入进来了
this.socket.on('joined', function (data) {
that.addSysLogs(that.lang.receive_join_room_event + JSON.stringify(data));
that.recoderId = data.recoderId;
let rtcConnect = that.getOrCreateRtcConnect(data.id);
// 处理完连接后,更新下昵称
that.setRemoteInfo(data.id, {
nickName : data.nickName,
owner : data.owner,
langMode : data.langMode,
ua : data.ua,
network : data.network,
joinTime : data.joinTime,
userAgent : data.userAgent,
ip : data.ip,
})
//这部分逻辑主要是将比当前加入连接更早加入的连接媒体流添加到track中
//以便于当前加入的连接可以收到之前的连接的媒体流数据
if (data.type === 'screen') {
//比如多人屏幕共享后面加入的连接已经在created事件中addTrack了
//所以这个地方主要是通知当前连接之前的那一些连接进行addTrack以便于当前连接能收到
window.Bus.$emit("getScreenShareTrackAndStream", (track, stream) => {
rtcConnect.addTrack(track, stream);
});
}
if (data.type === 'video') {
//比如多人音视频后面加入的连接已经在created事件中addTrack了
//所以这个地方主要是通知当前连接之前的那一些连接进行addTrack以便于当前连接能收到
window.Bus.$emit("getVideoShareTrackAndStream", (track, stream) => {
rtcConnect.addTrack(track, stream);
});
}
if (data.type === 'live') {
//比如直播,后面加入的都是观众,所以每个观众加入的时候,都会通知一下所有人可以添加媒体流到通道了(这里就是只有房主有媒体流数据)
window.Bus.$emit("getLiveShareTrackAndStream", (track, stream) => {
rtcConnect.addTrack(track, stream);
});
}
that.addPopup({
title : that.lang.join_room,
msg : data.nickName + that.lang.join_room
});
});
this.socket.on('offer', function (data) {
that.addSysLogs(that.lang.receive_offer_event + JSON.stringify(data));
let rtcConnect = that.getOrCreateRtcConnect(data.from);
let rtcDescription = { type: 'offer', sdp: data.sdp };
rtcConnect.setRemoteDescription(new RTCSessionDescription(rtcDescription)).then(r => { });
rtcConnect.createAnswer(that.options).then((offer) => {
that.answerSuccess(rtcConnect, data.from, offer)
}).catch((error) => {
that.answerFailed(rtcConnect, data.from, error)
});
});
this.socket.on('answer', function (data) {
that.addSysLogs(that.lang.receive_answer_event + JSON.stringify(data));
let rtcConnect = that.getOrCreateRtcConnect(data.from);
let rtcDescription = { type: 'answer', sdp: data.sdp };
rtcConnect.setRemoteDescription(new RTCSessionDescription(rtcDescription)).then(r => { });
});
this.socket.on('candidate', function (data) {
that.addSysLogs(that.lang.receive_candidate_event + JSON.stringify(data));
let rtcConnect = that.getOrCreateRtcConnect(data.from);
let rtcIceCandidate = new RTCIceCandidate({
candidate: data.sdp,
sdpMid: data.sdpMid,
sdpMLineIndex: data.sdpMLineIndex
});
rtcConnect.addIceCandidate(rtcIceCandidate).then(res => {
that.addIceCandidateSuccess(res);
}).catch(error => {
that.addIceCandidateFailed(error);
});
});
this.socket.on('exit', function (data) {
var rtcConnect = that.rtcConns[data.from];
if (typeof (rtcConnect) == 'undefined') {
return;
} else {
that.addPopup({
title : that.lang.exit_room,
msg : data.from + that.lang.exit_room
});
that.addSysLogs(that.lang.exit_room + JSON.stringify(data));
that.getOrCreateRtcConnect(data.from).close;
delete that.rtcConns[data.from];
Vue.delete(that.remoteMap, data.from);
}
that.touchResize();
})
//选中文件时发送给接收方
this.socket.on('sendFileInfo', function (data) {
let fromId = data.from;
that.setRemoteInfo(fromId, { receiveFiles: data });
that.addPopup({
title : that.lang.send_file,
msg : data.from + that.lang.selected_file + "[ " + data.name + " ], "+that.lang.will_send
});
that.addSysLogs(data.from + that.lang.selected_file + "[ " + data.name + " ], "+that.lang.will_send);
that.receiveFileRecoderList.push({
id: fromId,
nickName : data.nickName,
index: data.index,
href: "",
name: data.name,
type: data.type,
size: data.size,
progress: 0,
done: false,
start: 0,
cost: 0,
upload : 'wait'
})
that.socket.emit("message", {
emitType : "sendFileInfoAck",
room: that.roomId,
from: that.socketId, // from代表自己发出去的回执
to: fromId // 谁发过来的sendFileInfo事件就回执给谁
})
});
//接收放已经收到待发送文件信息,代表可以进行发送了,
//没有ack的话由于发送文件信息(websocket)和发送文件流(webrtc)是分开的
//webrtc和websocket之间互存在一个时差导致接收的时候报错
this.socket.on('sendFileInfoAck', function (data) {
let to = data.to;
let fromId = data.from;
if (to === that.socketId) { // 是自己发出去的文件ack回执
that.addSysLogs(that.lang.receive_ack + fromId)
that.setRemoteInfo(fromId, {
[that.currentChooseFile.index + "ack"]: true
})
}
})
//获取取件码文件
this.socket.on('getCodeFile', function (data) {
if(!data.download){
layer.msg(that.lang.no_code_file)
return
}
that.receiveCodeFileList = [data];
that.clickCodeFile();
})
//暂存成功通知
this.socket.on('addCodeFile', function (data) {
layer.msg(that.lang.save_ok);
})
//收到暂存链接
this.socket.on('prepareCodeFile', async function (data) {
let index = data.index;
that.addSysLogs(that.lang.receive_temporary_link);
let filterFile = that.chooseFileList.filter(item=>{
return item.index === index;
});
if(filterFile.length === 0){
layer.msg(that.lang.file_not_exist);
that.addUserLogs(file_not_exist);
return
}
if(!data.uploadLink){
layer.msg(that.lang.save_fail);
that.addSysLogs(that.lang.temporary_link_empty + file.name);
return
}
const file = filterFile[0]
let formData = new FormData()
formData.append('file', file)
formData.append('replace', data.replace)
formData.append('parent_dir', data.parent_dir)
try{
axios.defaults.withCredentials = true;
let res = await axios.post(data.uploadLink, formData, {
Headers : { "Content-Type" : "multipart/form-data;" },
})
let result = res.data;
if(!result || result.length === 0){
//更新当前文件相关的所有记录的暂存状态为失败
that.updateSendFileRecoderUpload(index, {
codeId : "",
upload : 'fail'
})
layer.msg(that.lang.save_fail);
that.addSysLogs(that.lang.save_fail + file.name);
return
}
let ossFileId = result[0].id;
let ossFileName = result[0].name;
that.socket.emit('addCodeFile', {
ossFileId : ossFileId,
ossFileName : ossFileName,
index: file.index,
name: file.name,
type: file.type,
size: file.size,
room: that.roomId,
from: that.socketId,
nickName : that.nickName,
to : that.socketId
});
//更新当前文件相关的所有记录的暂存状态
that.updateSendFileRecoderUpload(index, {
codeId : ossFileId,
upload : 'done'
})
}catch(e){
that.updateSendFileRecoderUpload(index, {
codeId : "",
upload : 'fail'
})
layer.msg(that.lang.save_fail);
that.addSysLogs(that.lang.save_fail + file.name);
return
}
})
//发送文字内容
this.socket.on('chatingRoom', function (data) {
let fromId = data.from;
that.addPopup({
title : that.lang.send_text,
msg : data.from + that.lang.send_text + "[ " + data.content.substr(0, 10) + " ]"
});
that.addSysLogs(data.from + that.lang.send_text + "[ " + data.content.substr(0, 10) + " ]");
try {
data.content = tlrtcfile.unescapeStr(data.content)
} catch (e) {
that.addSysLogs(that.lang.text_decode_failed + data.content);
}
let now = new Date().toLocaleString();
//私聊
if(data.to && data.to !== ''){
//私聊数据放在连接对象中
let remoteRtc = that.getRemoteInfo(fromId);
if(remoteRtc){
let receiveChatRoomSingleList = remoteRtc.receiveChatRoomSingleList || [];
receiveChatRoomSingleList.push({
socketId: fromId,
to : data.to,
content: data.content,
nickName : data.nickName,
time: now,
timeAgo : window.util ? util.timeAgo(now) : now
})
that.setRemoteInfo(fromId, {
receiveChatRoomSingleList : receiveChatRoomSingleList
});
}
that.chatingRoomSingleTpl();
}else{
//群聊
that.receiveChatRoomList.push({
socketId: fromId,
content: data.content,
nickName : data.nickName,
time: now,
timeAgo : window.util ? util.timeAgo(now) : now
})
that.chatingRoomTpl();
}
});
//在线数量
this.socket.on('count', function (data) {
that.allManCount = data.mc;
that.addSysLogs(that.lang.current_number + ":" + data.mc + that.lang.online_number)
});
//提示
this.socket.on('tips', function (data) {
if (window.layer) {
layer.msg(data.msg)
if (data.reload) {
setTimeout(() => {
window.location.reload()
}, 1300);
}
}
});
//关闭共享
this.socket.on('stopScreenShare', function (data) {
if (data.id === that.socketId) {
that.clickMediaScreen();
} else {
$(`#otherMediaScreenShare${data.id}`).parent().remove();
}
});
//关闭共享
this.socket.on('openCamera', function (data) {
that.setRemoteInfo(data.from, {
isCameraEnabled : data.isCameraEnabled,
isAudioEnabled : data.isAudioEnabled
})
if(data.type === 'video'){
if(data.kind === 'video'){
document.querySelector(`#otherMediaVideoShare${data.from}`).style.display = data.isCameraEnabled ? 'block' : 'none';
document.querySelector(`#otherMediaVideoShareVideoSvg${data.from}`).style.display = data.isCameraEnabled ? 'none' : 'block';
}else if(data.kind === 'audio'){
document.querySelector(`#otherMediaVideoShareAudioOpenSvg${data.from}`).style.display = data.isAudioEnabled ? 'block' : 'none';
document.querySelector(`#otherMediaVideoShareAudioCloseSvg${data.from}`).style.display = data.isAudioEnabled ? 'none' : 'block';
}
}else if(data.type === 'screen'){
if(data.kind === 'video'){
document.querySelector(`#otherMediaScreenShare${data.from}`).style.display = data.isCameraEnabled ? 'block' : 'none';
document.querySelector(`#otherMediaScreenShareVideoSvg${data.from}`).style.display = data.isCameraEnabled ? 'none' : 'block';
}else if(data.kind === 'audio'){
document.querySelector(`#otherMediaScreenShareAudioOpenSvg${data.from}`).style.display = data.isAudioEnabled ? 'block' : 'none';
document.querySelector(`#otherMediaScreenShareAudioCloseSvg${data.from}`).style.display = data.isAudioEnabled ? 'none' : 'block';
}
}else if(data.type === 'live'){
if(data.kind === 'video'){
document.querySelector(`#otherMediaLiveShare${data.from}`).style.display = data.isCameraEnabled ? 'block' : 'none';
document.querySelector(`#otherMediaLiveShareVideoSvg${data.from}`).style.display = data.isCameraEnabled ? 'none' : 'block';
}else if(data.kind === 'audio'){
document.querySelector(`#otherMediaLiveShareAudioOpenSvg${data.from}`).style.display = data.isAudioEnabled ? 'block' : 'none';
document.querySelector(`#otherMediaLiveShareAudioCloseSvg${data.from}`).style.display = data.isAudioEnabled ? 'none' : 'block';
}
}
});
//关闭音视频
this.socket.on('stopVideoShare', function (data) {
if (data.id === that.socketId) {
that.clickMediaVideo();
} else {
$(`#otherMediaVideoShare${data.id}`).parent().remove();
}
});
//退出直播
this.socket.on('stopLiveShare', function (data) {
//如果是主动房主退出,所有观众都退出
if(data.owner){
window.location.reload();
return
}
if (data.id === that.socketId) {
that.clickMediaLive();
}
});
//ai对话
this.socket.on('openaiAnswer', function (data) {
that.isAiAnswering = false
that.receiveAiChatList.push(data)
that.addSysLogs("AI : " + data.content)
that.addPopup({
title : that.lang.ai_reply,
msg : that.lang.ai_reply_you
});
that.receiveAiChatList.forEach(item => {
item.timeAgo = window.util ? util.timeAgo(item.time) : item.time;
})
that.openaiChatTpl()
});
//开关数据
this.socket.on('commData', function (data) {
that.switchData = data.switchData
that.switchDataGet = true;
if(data.switchData.noticeMsgList){
let alert = window.localStorage.getItem("tl-rtc-file-alert-notice")
if(!alert || (Date.now() - parseInt(alert)) / 1000 > (24 * 60 * 60) ){
setTimeout(() => {
that.clickNotice()
window.localStorage.setItem("tl-rtc-file-alert-notice", Date.now())
}, 1000);
}
}
if(data.chatingCommData){
data.chatingCommData.forEach(elem => {
try {
elem.msg = tlrtcfile.unescapeStr(elem.msg)
} catch (e) {
that.addSysLogs(that.lang.text_decode_failed + elem.msg);
}
that.receiveChatCommList.push(elem)
})
that.receiveChatCommList.forEach(item => {
item.timeAgo = window.util ? util.timeAgo(item.time) : item.time;
})
}
});
//公共聊天频道
this.socket.on('chatingComm', function (data) {
that.addSysLogs(data.room + ":" + data.socketId + that.lang.send_text + ": [ " + data.msg + " ]");
try {
data.msg = tlrtcfile.unescapeStr(data.msg)
} catch (e) {
that.addSysLogs(that.lang.text_decode_failed + data.msg);
}
that.receiveChatCommList.push(data);
if (that.receiveChatCommList.length > 10) {
that.receiveChatCommList.shift();
}
that.receiveChatCommList.forEach(item => {
item.timeAgo = window.util ? util.timeAgo(item.time) : item.time;
})
that.chatingCommTpl()
that.addPopup({
title : that.lang.chat_comm,
msg : that.lang.public_chat_channel_someone_interact
});
});
this.socket.on('manageCheck', function (data) {
layer.prompt({
formType: 1,
title: that.lang.please_enter,
}, function (value, index, elem) {
that.socket.emit('manageConfirm', {
room: that.roomId,
value: value
});
layer.close(index)
});
});
this.socket.on('manage', function (data) {
if (data.socketId !== that.socketId) {
layer.msg(that.lang.illegal_event)
return
}
layer.closeAll();
that.token = data.token;
layer.load(2, {
time: 1000,
shade: [0.8, '#000000'],
success: function (layero) {
layer.setTop(layero); //重点2
}
})
setTimeout(() => {
that.manageIframeId = layer.tab({
area: ['100%', '100%'],
shade: [0.8, '#393D49'],
closeBtn : 0,
tab: [{
title: data.content[0].title,
content: data.content[0].html
}, {
title: data.content[1].title,
content: data.content[1].html
}, {
title: data.content[2].title,
content: data.content[2].html
}],
cancel: function (index, layero) {
that.manageIframeId = 0;
},
})
layer.full(that.manageIframeId)
}, 500);
});
},
// 检测浏览器是支持webrtc
webrtcCheck: function () {
let that = this;
if (window.tlrtcfile) {
$("#rtcCheck").removeClass("layui-anim-rotate")
setTimeout(() => {
$("#rtcCheck").addClass("layui-anim-rotate")
let rtcCheck = tlrtcfile.supposeWebrtc();
layer.msg(`${that.lang.your_browser}${rtcCheck ? that.lang.support : that.lang.not_support}webrtc`)
that.addUserLogs(`${that.lang.your_browser}${rtcCheck ? that.lang.support : that.lang.not_support}webrtc`)
}, 50)
}
},
// 打开p2p检测面板
p2pCheck: function () {
let that = this;
$("#p2pCheck").removeClass("layui-anim-rotate")
setTimeout(() => {
$("#p2pCheck").addClass("layui-anim-rotate")
let msg = "<p style='font-size:16px;margin-bottom:10px'>"+that.lang.p2p_check_principle+": </p> "
msg += "<p style='margin-bottom:5px'> "+that.lang.p2p_check_principle_detail+" <b> "+that.lang.p2p_check_principle_detail_2+" </b></p>"
msg += "<p style='font-size:16px;margin-bottom:10px;margin-top: 10px;'>"+that.lang.p2p_check_principle_detail_3+": </p> "
msg += "<p style='margin-bottom:5px'> "+that.lang.p2p_check_principle_detail_4+"<b> "+that.lang.p2p_check_principle_detail_5+" </b>"+that.lang.p2p_check_principle_detail_6+"</p>"
msg += "<p style='margin-bottom:5px'> "+that.lang.p2p_check_principle_detail_7+" </p>"
msg += "<p style='margin-bottom:5px'> "+that.lang.p2p_check_principle_detail_8+" chrome://flags/ , "+that.lang.p2p_check_principle_detail_9+" </p>"
msg += "<p style='margin-bottom:5px'> "+that.lang.p2p_check_principle_detail_10+" </p>"
layer.confirm(msg, (index) => {
layer.closeAll(() => {
that.clickLogs()
})
}, (index) => {
layer.close(index)
})
that.addUserLogs(`${that.lang.your_ip_list} : ${JSON.stringify(this.ips)}`)
}, 50)
},
// 自动监听窗口变化更新css
reCaculateWindowSize: function () {
this.clientWidth = document.body.clientWidth;
if (window.fileTxtToolSwiper) {
window.fileTxtToolSwiper.params.slidesPerView = this.clientWidth < 600 ? 1 : 2;
}
if (window.toolSwiper) {
window.toolSwiper.params.slidesPerView = this.toolSlidesPerViewCount;
}
// logs height
this.logsHeight = document.documentElement.clientHeight - 130;
this.sendFileRecoderHeight = document.querySelector("#send-file-list").clientHeight - 190;
this.chooseFileHeight = document.querySelector("#send-file-list-choose").clientHeight - 40;
this.sendFileRecoderHistoryHeight = document.querySelector("#send-file-list-history").clientHeight - 40;
this.receiveFileHeight = document.querySelector("#receive-file-list").clientHeight - 40;
this.codeFileHeight = document.querySelector("#code-file-list").clientHeight - 40;
//manage frame resize
if (window.layer && this.manageIframeId !== 0) {
layer.full(this.manageIframeId)
}
},
// 自动监听窗口变化更新css
touchResize: function (e) {
if(e){ //onresize
this.reCaculateWindowSize();
return
}
//主动触发
const myEvent = new Event('resize');
window.dispatchEvent(myEvent);
this.reCaculateWindowSize();
},
// 加载js调试器
loadVConsoleJs: function () {
let that = this;
if (window.location.hash && window.location.hash.includes("debug")) {
window.tlrtcfile.loadJS('/static/js/vconsole.min.js', function () {
window.tlrtcfile.loadJS('/static/js/vconsole.js', function () {
that.addSysLogs("load vconsole success")
});
});
}
},
// 定义事件到window上
windowOnBusEvent: function () {
window.Bus.$on("changeScreenShareState", (res) => {
if(!res){//状态失败,收起面板
this.clickMediaScreen();
}
this.isScreenShare = res
})
window.Bus.$on("changeScreenShareTimes", (res) => {
if (res === 0) {
this.socket.emit('message', {
emitType: "stopScreenShare",
id: this.socketId,
room: this.roomId,
cost: this.screenShareTimes,
});
}
this.screenShareTimes = res
})
window.Bus.$on("changeVideoShareState", (res) => {
if(!res){//状态失败,收起面板
this.clickMediaVideo();
}
this.isVideoShare = res
})
window.Bus.$on("changeVideoShareTimes", (res) => {
if (res === 0) {
this.socket.emit('message', {
emitType: "stopVideoShare",
id: this.socketId,
room: this.roomId,
cost: this.videoShareTimes,
});
}
this.videoShareTimes = res
})
window.Bus.$on("changeLiveShareState", (res) => {
if(!res){//状态失败,收起面板
this.clickMediaLive();
}
this.isLiveShare = res
})
window.Bus.$on("changeLiveShareTimes", (res) => {
if (res === 0) {
this.socket.emit('message', {
emitType: "stopLiveShare",
id: this.socketId,
room: this.roomId,
cost: this.liveShareTimes,
owner : this.owner,
});
}
this.liveShareTimes = res
})
window.Bus.$on("sendChatingComm", (res) => {
this.sendChatingComm()
})
window.Bus.$on("sendChatingRoom", (res) => {
this.sendChatingRoom()
})
window.Bus.$on("sendChatingRoomSingle", (res) => {
this.sendChatingRoomSingle()
})
window.Bus.$on("sendOpenaiChat", (res) => {
this.sendOpenaiChat()
})
window.Bus.$on("sendOpenaiChatWithContext", () => {
this.openaiSendContext = !this.openaiSendContext;
layer.msg(`${this.lang.ai_switch}${this.openaiSendContext ? this.lang.on : this.lang.off}`)
this.addUserLogs(`${this.lang.ai_switch}${this.openaiSendContext ? this.lang.on : this.lang.off}`)
$("#aiContext").removeClass("layui-anim-rotate")
setTimeout(() => {
$("#aiContext").addClass("layui-anim-rotate")
}, 50)
})
window.Bus.$on("manageChange", (data) => {
this.socket.emit('manageChange', {
id: data.id,
room: this.roomId,
token: this.token,
content: data.content,
});
})
window.Bus.$on("manageReload", (data) => {
this.socket.emit('manageReload', {
id: data.id,
room: this.roomId,
token: this.token,
content: data.time,
});
})
window.Bus.$on("webrtcCheck", (res) => {
this.webrtcCheck()
})
window.Bus.$on("p2pCheck", (res) => {
this.p2pCheck()
})
window.Bus.$on("sendBugs", (res) => {
this.sendBugs()
})
window.Bus.$on("relaySetting", (res) => {
this.relaySetting()
})
window.Bus.$on("addSysLogs", (res) => {
this.addSysLogs(res)
})
},
// 初始化选择文件面板
renderChooseFileComp: function () {
let that = this;
if (window.upload) {
upload.render({
elem: '#chooseFileList',
accept: 'file',
auto: false,
drag: true,
multiple: true,
choose: async function (obj) {
that.allSended = false;
//清空上次选择的文件和记录
that.chooseFileList = [];
that.sendFileRecoderList = [];
//这是改动layui源码补充的方法 : 清空文件列表
obj.clearAllFile();
//重新生成文件记录
let files = obj.pushFile();
for(let index in files){
let file = files[index];
//是否存在选择的文件
let hasChooseFile = that.chooseFileList.filter((item) => {
return item.name === file.name && item.size === file.size &&
item.fileLastModified === file.lastModified && item.type === file.type;
}).length > 0;
//如果文件已经存在,就不再添加了
if(hasChooseFile){
that.addUserLogs(`${that.lang.selected_file_exist} : ${file.name}, ${that.lang.size} : ${that.getFileSizeStr(file.size)}, ${that.lang.type} : ${file.type}`);
continue
}
that.chooseFileList.push(
Object.assign(file, {
fileLastModified : file.lastModified,
index : index,
offset : 0
})
)
//根据房间内的用户,生成文件发送记录
for (let remoteId in that.remoteMap) {
let hasFileRecoder = that.sendFileRecoderList.filter(recoder => {
return file.name === recoder.name && file.size === recoder.size &&
file.type === recoder.type && recoder.id === remoteId;
}).length > 0;
//如果已经存在记录了,就不再添加了
if (hasFileRecoder) {
that.addUserLogs(`${that.lang.send_file_record_exist} : ${file.name} : ${that.remoteMap[remoteId].nickName}`);
continue
}
that.setRemoteInfo(remoteId, {
[index + "offset"]: 0,
[index + "status"]: 'wait',
[index + "file"]: file,
[index + "reader"]: new FileReader()
})
that.sendFileRecoderList.unshift({
index: index,
id: remoteId,
nickName : that.remoteMap[remoteId].nickName,
name: file.name,
size: file.size,
type: file.type,
progress: 0,
done: false,
start: 0,
cost: 0,
upload : 'wait'
});
that.addUserLogs(`${that.lang.generate_send_file_record} : ${file.name}, ${that.lang.size} : ${that.getFileSizeStr(file.size)}, ${that.lang.type} : ${file.type}, : ${that.remoteMap[remoteId].nickName}`);
}
}
}
});
}
},
// swiper个数样式更新
initSwiper: function(){
let clientWidth = document.body.clientWidth;
//发文件,收文件功能
let fileTxtToolSwiper = new Swiper('.tl-rtc-file-send-file-txt-tool', {
direction: 'horizontal',
loop: false,
slidesPerView: clientWidth < 600 ? 1 : 2,
observer: true
})
window.fileTxtToolSwiper = fileTxtToolSwiper;
//工具功能
let toolSwiper = new Swiper('.tl-rtc-file-tool-list', {
direction: 'horizontal',
loop: false,
slidesPerView: this.toolSlidesPerViewCount,
observer: true,
scrollbar: {
el : '.swiper-scrollbar',
hide: true,
}
})
window.toolSwiper = toolSwiper;
},
},
mounted: function () {
let langArgs = tlrtcfile.getRequestHashArgs("lang")
if (langArgs && ['zh','en'].includes(langArgs)) {
this.langMode = langArgs;
}
this.lang = window.local_lang[this.langMode];
this.addSysLogs(this.lang.init_language_done);
this.addSysLogs(this.lang.print_logo);
this.consoleLogo();
this.addSysLogs(this.lang.refresh_random_room_num_init);
this.refleshRoom()
this.addSysLogs(this.lang.refresh_random_room_num_init_done);
this.addSysLogs(this.lang.slider_init);
this.initSwiper();
this.addSysLogs(this.lang.slider_init_done);
this.addSysLogs(this.lang.socket_init);
this.socketListener();
this.addSysLogs(this.lang.socket_init_done);
this.addSysLogs(this.lang.basic_data_get);
this.socket.emit('getCommData', {});
this.addSysLogs(this.lang.basic_data_get_done);
this.addSysLogs(this.lang.window_event_init);
window.onresize = this.touchResize;
setInterval(() => {
this.touchResize()
}, 1000);
this.addSysLogs(this.lang.window_event_init_done);
this.addSysLogs(this.lang.webrtc_check_init);
setInterval(async () => {
await this.updateRemoteRtcState()
}, 5000);
this.addSysLogs(this.lang.webrtc_check_init_done);
this.addSysLogs(this.lang.message_box_init);
this.startPopUpMsg()
this.addSysLogs(this.lang.message_box_init_done);
this.addSysLogs(this.lang.share_init);
this.handlerJoinShareRoom();
this.handlerGetCodeFile();
this.addSysLogs(this.lang.share_init_done);
this.addSysLogs(this.lang.common_event_init);
this.windowOnBusEvent();
this.addSysLogs(this.lang.common_event_init_done);
setTimeout(() => {
this.addSysLogs(this.lang.file_select_init);
this.renderChooseFileComp();
this.addSysLogs(this.lang.file_select_init_done);
this.addSysLogs(this.lang.language_select_init);
this.changeLanguage()
this.addSysLogs(this.lang.language_select_init_done);
window.Bus.$emit("getVideoShareDeviceList", (v, a, l) => {
this.videoDeviceList = v;
this.audioDeviceList = a;
this.loudspeakerDeviceList = l;
});
}, 2000);
this.addSysLogs(this.lang.debug_init);
this.loadVConsoleJs();
this.addSysLogs(this.lang.debug_init_done);
this.addSysLogs(this.lang.current_relay_status + (this.useTurn ? this.lang.on : this.lang.off))
}
});
window.manageReload = function (data) {
window.Bus.$emit("manageReload", data)
}
window.manageChange = function (data) {
window.Bus.$emit("manageChange", data)
}
window.sendChatingComm = function () {
window.Bus.$emit("sendChatingComm", {})
}
window.sendChatingRoom = function () {
window.Bus.$emit("sendChatingRoom", {})
}
window.sendChatingRoomSingle = function () {
window.Bus.$emit("sendChatingRoomSingle", {})
}
window.sendOpenaiChat = function () {
window.Bus.$emit("sendOpenaiChat", {})
}
window.webrtcCheck = function () {
window.Bus.$emit("webrtcCheck", {})
}
window.p2pCheck = function () {
window.Bus.$emit("p2pCheck", {})
}
window.sendBugs = function () {
window.Bus.$emit("sendBugs", {})
}
window.sendOpenaiChatWithContext = function () {
window.Bus.$emit("sendOpenaiChatWithContext", {})
}
window.relaySetting = function () {
window.layer.closeAll(() => {
window.Bus.$emit("relaySetting", {})
});
}
window.useTurn = function () {
if ((window.localStorage.getItem("tl-rtc-file-use-relay") || "") === 'true') {
window.localStorage.setItem("tl-rtc-file-use-relay", false)
} else {
window.localStorage.setItem("tl-rtc-file-use-relay", true)
}
window.location.reload()
}
})