From cd9f8b8404f85b30a121368b136b73e55d4b2a8a Mon Sep 17 00:00:00 2001 From: congzc Date: Thu, 23 Mar 2023 17:43:54 +0800 Subject: [PATCH] a --- src/assets/icon/icon-loginout.png | Bin 0 -> 1777 bytes src/assets/icon/icon-pencil.png | Bin 0 -> 952 bytes src/assets/web(1).rar | Bin 0 -> 6692 bytes src/assets/web(1)/web/css/client.css | 70 ++++ src/assets/web(1)/web/js/client.js | 503 +++++++++++++++++++++++++++ src/assets/web(1)/web/user.html | 196 +++++++++++ src/components/common/head.vue | 16 +- src/router/index.js | 11 +- src/views/chat/chat.vue | 61 ++++ src/views/model/chooseModel.vue | 40 +-- src/views/model/editModel.vue | 142 ++++++-- src/views/myPage/myPage.vue | 30 +- 12 files changed, 997 insertions(+), 72 deletions(-) create mode 100644 src/assets/icon/icon-loginout.png create mode 100644 src/assets/icon/icon-pencil.png create mode 100644 src/assets/web(1).rar create mode 100644 src/assets/web(1)/web/css/client.css create mode 100644 src/assets/web(1)/web/js/client.js create mode 100644 src/assets/web(1)/web/user.html create mode 100644 src/views/chat/chat.vue diff --git a/src/assets/icon/icon-loginout.png b/src/assets/icon/icon-loginout.png new file mode 100644 index 0000000000000000000000000000000000000000..3d2e498a3590d1603bc98c3a617d3445c98c748b GIT binary patch literal 1777 zcmVPx*t4TybRA@u(S!rloRTMtwHpN(-SW*;Mj9M*{TKn$3lXO~@x}g-6)&&(YZiTA2 zAzBr;y0l7*LS3LB)w-ZyTP>C%iquqVi_JSXW0J;hiuyyTD>lY-CiC8r8{Z2r$xLS6 zWQJ1c{WZ)x+xO0L&p8j_AU}AJ+YeO*ptWAi%y%>M24l<(0N_{pg;foJQfdM-_aLIM zMkAthUAO(90gy_i>a*GGAECiQh*@oIZCefj81@FL7D9(SP-3jb_CP9?I(+Nat(jsK zr5q1Tn>Ou;j*gDr5^WzKivwVsQfe^(+z$Xh`M!T%dwcuOg_OksFnRLi!|LklHX>pQ z0N(BE>$|p(y@r{u4D>NG zUu2BgI9z{T07|J30AOA~ydM##yRN&lRO2fE0F+Wk0YEnZ93SZYmWZYfzkfymkW!ur z09|1h$;^w4F*Yt^opN&^szfPu4FJ3!{>RK$8Ds3r9XuldD5YKlfcch!ng8nT?LB$t z&YeB6tpC5Nl$`@nT~f+4BAy-Cv&9%Qs~~{cvuD@z^z`fnfO<=Wh)Z18jV4pcI4TI> z21I-(%mn~wAflf_Lxy-jYke^@e;PjYeZR52z1_xP>{BTKb}#~f-y#L&J|bG47l2ag zX#jXUc({v*ro^^9V5k%TD^4l(Appz`!f35A=ITNo_=uS=4+!5j#zb$$SXk`Mn#pAT z35$W}c}_Z=w&ghXQA#}w0M7-C-xJYkg*>ne08S48EHuU}i>)ofptWAa%y$961|quI z)`fE9X|3nj#Xy(`eSLkk-QC^Ufjo^0R7(900F#3MZYH8P%TZWq9>;OC5TY%Vo5^I3 z?ds~X#byKmi)ws8IFE?dRoa+xZb8J?gB-}@a=A%yw*=Jys+8IR0O6%NBr=A0Ael^_lMtr zAdtN`kw}~v9clBm(>DRYs)#Wc5Ygw=Fs_(P8xfx8?MB4Ig0Nm@j9D1v%LiaQGw(vg zhJdg$kx0l=2KizJ#eFWNT!V;L1$=z~a2gT)GAaQ3P;1@7%+WUD86t{q3gX%lo8EDp z>x2;RMPh%cG3KG+f(mXWlu~wp9w?*C>tI2=xDu%jKr# ztB3^wIF56&5P~AjT^}OOab35y44X=3X>M*lJ(tUEigX110ATabF4#t#Q8#l^%1aP& zeWby&AX;44T~o3}F$lHRO}_81N5o?yZ1)n;ONGP~-Hl2q?Z>&dB3lakp>FxUefu8S zvuBT8vsdDvQtBQ6cpd<1BXPgj81vX@3W@@-%r>DAaaCB(g1Fo*gm_@Ac_F2oA%u7_ z(t{0%x9deBdbZfxDG7iTqLexZ0Blt_F)Eaqzd^)Tvf1p~&d$!iiYc_ugGKIT0C2mF z?_oUl%jN>tbq6cZm^}~*Xl!h(udS_p8362#bU1iB41Em%n-OsbBL3+6evc5s_dGAb z%qJk?6#Ef_nP(y5k;4sR=1qur8xeh1%8RfxC3_$$FzC;h0D$#MCC(0JezKeasw@DZ z#nY!xpIK8=Ge20u4)*A!AY$eWB7TgBZ@8{&D_vOw|Cj)ThBh=b)YjM6&lEzO$INyO zXg3?jGV_tPdNH#dRewiB+t+OOeg8{4VG+^arGTwi4vbYkZ+00000NkvXXu0mjf7^gTi literal 0 HcmV?d00001 diff --git a/src/assets/icon/icon-pencil.png b/src/assets/icon/icon-pencil.png new file mode 100644 index 0000000000000000000000000000000000000000..322ab81d2959743bd9abe9da0046d87ae9a0e0c2 GIT binary patch literal 952 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 0) { + flag = false; + break; + } + } + return flag; +} + +/** + * 功能: 判断是Android端还是iOS端。 + * 返回值: true,说明是Android端 + * false,说明是iOS端。 + */ +function IsAndroid() { + var u = navigator.userAgent, app = navigator, appVersion; + var isAndroid = u.indexOf('Android') > -1 || u.indexOf('Linux') > -1; + var isIOS = !!u.match(/\(i[^;]+;( U;) ? CPU.+Mac OS X/); + if (isAndroid) { + //这个是Android系统 + return true; + } + if (isIOS) { + //这个是iOS系统 + return false; + } +} + +/** + * 功能: 向对端发消息 + * 返回值: 无 + */ +function sendMessage(roomid, data) { + console.log('send message to other end', roomid, data); + if (!socket) { + console.log('socket is null'); + } + socket.emit('message', roomid, data); +} + +/** + *功能: 与信令服务器建立socket.io连接;并根据信令更新状态机。 + *返回值: 无 + */ +function conn() { + //连接信令服务器 + socket = io.connect(); + //joined'消息处理函数 + socket.on('joined', (roomid, id) => { + console.log('receive joined message!', roomid, id); + //状态机变更为joined' + state = 'joined'; + /** + * 如果是Mesh方案,第一个人不该在这里创建 + * peerConnection,而是要等到所有端都收到一个'otherjoin'消息时再创建 + */ + //创建PeerConnection 并绑定音视频轨 + createPeerConnection(); + bindTracks(); + //设置button状态 + btnConn.disabled = true; + btnLeave.disabled = false; + console.log('receive joined message, state = ', state); + }); + + //otherjoin消息处理函数 + socket.on('other_join', (roomid) => { + console.log('receive joined message:', roomid, state); + //如果是多人,每加入一个人都要创建一个新的 PeerConnection + if (state === 'joined_unbind') { + createPeerConnection(); + bindTracks(); + } + //状态机变更为 joined_conn + state = 'joined_conn'; + //开始“呼叫”对方 + call1(); + console.log('receive other_join message, state = ', state); + }); + + //full消息处理函数 + socket.on('full', (roomid, id) => { + console.log('receive full message', roomid, id); + //关闭gocket.io连接 + socket.disconnect(); + //挂断“呼叫” + hangup(); + //关闭本地媒体 + closeLocalMedia(); + //状态机变更为 leaved + state = 'leaved'; + console.log('receive full message, state = ', state); + alert('the room is full!'); + }); + + //leaved消息处理函数 + socket.on('left', (roomid, id) => { + console.log('receive leaved message', roomid, id); + //状态机变更为leaved + state = 'leaved' + //关闭socket.io连接 + socket.disconnect(); + console.log('receive leaved message, state = ', state); + //改变button状态 + btnConn.disabled = false; + btnLeave.disabled = true; + }); + + //bye消息处理函数 + socket.on('bye', (room, id) => { + console.log('receive bye message', roomid, id); + /** + * 当是Mesh方案时,应该带上当前房间的用户数, + * 如果当前房间用户数不小于 2,则不用修改状态, + * 并且关闭的应该是对应用户的PeerConnection。 + * 在客户端应该维护一张PeerConnection表,它是 + * key:value的格式,key=userid,value=peerconnection + */ + //状态机变更为 joined_unbind + state = 'joined_unbind'; + //挂断“呼叫” + hangup(); + offer.value = ''; + answer.value = ''; + console.log('receive bye message, state=', state); + }); + + //socket.io连接断开处理函数 + socket.on('disconnect', (socket) => { + console.log('receive disconnect message!', roomid); + if (!(state === 'leaved')) { + //挂断“呼叫” + hangup(); + //关闭本地媒体 + closeLocalMedia(); + } + //状态机变更为 leaved + state = 'leaved'; + }); + + //收到对端消息处理函数 + socket.on('message', (roomid, data) => { + console.log('receive message!', roomid, data); + if( data === null || data === undefined) { + console.error('the message is invalid!'); + return; + } + //如果收到的SDP是offer + if (data.hasOwnProperty('type') && data.type === 'offer') { + offer.value = data.sdp; + //进行媒体协商 + pc.setRemoteDescription(new RTCSessionDescription(data)); + //创建answer + pc.createAnswer() + .then(getAnswer) + .catch(handleAnswerError); + //如果收到的SDP是answer + } else if (data.hasOwnProperty('type') && data.type == 'answer') { + answer.value = data.sdp; + //进行媒体协商 + pc.setRemoteDescription(new RTCSessionDescription(data)); + //如果收到的是Candidate消息 + } else if (data.hasOwnProperty('type') && data.type === 'candidate') { + var candidate = new RTCIceCandidate({ + sdpMLineIndex: data.label, + candidate: data.candidate + }); + //将远端Candidate消息添加到PeerConnection中 + pc.addIceCandidate(candidate); + } else { + console.log('the message is invalid!', data); + } + }); + + //从url中获取roomid + roomid = getQueryVariable('room'); + + //发送'join'消息 + socket.emit('join', roomid); + + return true; +} + +/** + * 功能: 打开音视频设备成功时的回调函数 + * 返回值: 永远为true + */ +function getMediaStream(stream) { + //将从设备上获取到的音视频track添加到localStream中 + if (localStream) { + stream.getAudioTracks().forEach((track) => { + localStream.addTrack(track); + stream.removeTrack(track); + }); + } else { + localStream = stream; + } + //本地视频标签与本地流绑定 + localVideo.srcObject = localStream; + + //创建PeerConnection 并绑定音视频轨 + createPeerConnection(); + bindTracks(); +} + +/** + * 功能: 错误处理函数 + * + * 返回值: 无 + */ +function handleError(err) { + console.error('Failed to get Media Stream!', err); +} + +/** + *功能: 打开音视频设备 + *返回值: 无 + */ +function startLocalMedia() { + if (!navigator.mediaDevices || + !navigator.mediaDevices.getUserMedia) { + console.error('the getUserMedia is not supported!'); + return; + } else { + var constraints; + constraints = { + video: true, + audio: { + echoCancellation: true, + noiseSuppression: true, + autoGainControl: true + } + }; + + navigator.mediaDevices.getUserMedia(constraints) + .then(getMediaStream) + .catch(handleError); + } +} + +/** + * 功能: 获得远端媒体流 + * 返回值: 无 + */ +function getRemoteStream(e) { + //存放远端视频流 + remoteStream = e.streams[0]; + //远端视频标签与远端视频流绑定 + remoteVideo.srcObject = e.streams[0]; +} + +/** + * 功能: 处理Offer错误 + * 返回值: 无 + */ +function handleOfferError(err) { + console.error('Failed to create offer:', err); +} + +/** +*功能:处理Answer错误 +*返回值:无 +*/ +function handleAnswerError(err) { + console.error('Failed to create answer;', err); +} + +/** +*功能:获取Answer SDP 描述符的回调函数 +* +*返回值:无 +*/ +function getAnswer(desc) { + //设置Answer + pc.setLocalDescription(desc); + //将Answer显示出来 + answer.value = desc.sdp; + //将AnswerSDP发送给对端 + let req = new Object; + req.cmd = 'CMD_WEBRTC'; + req.data = desc; + let str = JSON.stringify(req); + console.log(str); + conn.send(str); +} + +/** +*功能:获取Offer SDP 描述符的回调函数 +*返回值:无 +*/ +function getOffer(desc) { + //设置Offer + pc.setLocalDescription(desc); + //将Offer显示出来 + offer.value = desc.sdp; + offerdesc = desc; + + console.log(offerdesc) + + //将 OfferSDP发送给对端 + let req = new Object; + req.cmd = 'CMD_WEBRTC'; + req.data = desc; + let str = JSON.stringify(req); + console.log(str); + conn.send(str); +} + +/** +* 功能: 创建PeerConnection对象 +* 返回值: 无 +*/ +function createPeerConnection() { + /* + * 如果是多人的话,在这里要创建一个新的连接 + * 新创建好的要放到一个映射表中 + */ + //key=userid, value=peerconnection + console.log('create RTCPeerConnection!'); + if (!pc) { + //创建PeerConnection对象 + pc = new RTCPeerConnection(pcConfig); + //当收集到Candidate后 + pc.onicecandidate = (event) => { + if (event.candidate) { + console.log("candidate" + JSON.stringify(event.candidate.toJSON())); + //将Candidate发送给对端 + let req = new Object; + req.cmd = 'CMD_WEBRTC'; + req.data = { + type: 'candidate', + label: event.candidate.sdpMLineIndex, + id: event.candidate.sdpMid, + candidate: event.candidate.candidate + }; + let str = JSON.stringify(req); + console.log(str); + conn.send(str); + } else { + console.log('this is the end candidate'); + } + } + /** + * 当PeerConnection对象收到远端音视频流时 + * 触发ontrack事件,并回调getRemoteStream函数 + */ + pc.ontrack = getRemoteStream; + } else { + console.log('the pc have be created!'); + } + return; +} + +/** +* 功能: 将音视频track绑定到PeerConnection对象中 +* 返回值: 无 +*/ +function bindTracks() { + console.log('bind tracks into RTCPeerConnection!'); + if (pc === null && localStream === undefined) { + console.error('pc is null or undefined!'); + return; + } + + if (localStream === null && localStream === undefined) { + console.error('localstream is null or undefined!'); + return; + } + + //将本地音视频流中所有的track添加到PeerConnection对象中 + localStream.getTracks().forEach((track)=>{ + pc.addTrack(track, localStream); + }); +} + +/** + * 功能: 开启“呼叫” + * 返回值: 无 + */ +function videoCall() { + var offerOptions = { + offerToReceiveAudio: 1, + offerToReceiveVideo: 1 + }; + /** + * 创建Offer, + * 如果成功,则回调getoffer()方法 + * 如果失败,则回调handleofferError()方法 + */ + pc.createOffer(offerOptions) + .then(getOffer) + .catch(handleOfferError); +} + +/** + * 功能: 挂断“呼叫” + * 返回值: 无 + */ +function hangup() { + if (!pc) { + return; + } + + //发送“挂断”消息 + let r = new Object; + r.cmd = "CMD_HANGUP"; + let str = JSON.stringify(r); + conn.send(str); + + /* + offerdesc = null; + + //将PeerConnection连接关掉 + pc.close(); + pc = null; + */ +} + +/** + * 功能:关闭本地媒体 + * 返回值:无 + */ +function closeLocalMedia() { + if (!(localStream === null || localStream === undefined)) { + //遍历每个track,并将其关闭 + localStream.getTracks().forEach((track) => { + track.stop(); + }); + } + localStream = null; +} + +/** +*功能:离开房间 +*返回值:无 +*/ +function leave() { + //向信令服务器发送leave消息 + socket.emit('leave', roomid); + //挂断“呼叫” + hangup(); + //关闭本地媒体 + closeLocalMedia(); + offer.value = ''; + answer.value = ''; +} + diff --git a/src/assets/web(1)/web/user.html b/src/assets/web(1)/web/user.html new file mode 100644 index 0000000..da517a9 --- /dev/null +++ b/src/assets/web(1)/web/user.html @@ -0,0 +1,196 @@ + + + + + 用户页面 + + + + + +
+
+ + + + + +
+
+
+
+
+

Local:

+ +

Offer SDP:

+ +
+
+

Remote:

+ +

Answer SDP:

+ +
+
+
+
+ + +
+
+ + + + + + + \ No newline at end of file diff --git a/src/components/common/head.vue b/src/components/common/head.vue index e3bead7..861da08 100644 --- a/src/components/common/head.vue +++ b/src/components/common/head.vue @@ -5,15 +5,20 @@
用户名
- - --> + +
@@ -100,14 +105,15 @@ export default { font-weight: 600; font-size: 13px; margin-right: 15px; + margin-top: 3px; } .userModel { position: relative; top: 3px; margin-right: 10px; .userImg { - width: 30px; - height: 30px; + width: 24px; + height: 24px; margin-right: 2px; } } diff --git a/src/router/index.js b/src/router/index.js index 1c90211..69ff670 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -44,19 +44,24 @@ const routes = [ name: 'chooseModel', component: () => import("../views/model/chooseModel"), }, - // 模板预览 + // 模板预览new { path: '/previewModel', name: 'previewModel', component: () => import("../views/model/previewModel"), }, - // 编辑模板 + // 编辑模板new { path: '/editModel', name: 'editModel', component: () => import("../views/model/editModel"), }, - + // 聊天首页new + { + path: '/chat', + name: 'chat', + component: () => import("@/views/chat/chat"), + }, // 录入文案 { path: '/getPaper', diff --git a/src/views/chat/chat.vue b/src/views/chat/chat.vue new file mode 100644 index 0000000..3e91e5d --- /dev/null +++ b/src/views/chat/chat.vue @@ -0,0 +1,61 @@ + + + + + diff --git a/src/views/model/chooseModel.vue b/src/views/model/chooseModel.vue index b760002..e8a6f91 100644 --- a/src/views/model/chooseModel.vue +++ b/src/views/model/chooseModel.vue @@ -3,13 +3,14 @@
- - + + -
选择模板
-
+ + +
@@ -67,15 +68,7 @@ export default { type: 0, currentId: "", modeList: [], // 模板列表 - tabList: [ - { id: 1, name: '全部' }, - { id: 2, name: '男性' }, - { id: 3, name: '女性' }, - { id: 4, name: '原P' }, - { id: 5, name: '武装直升机' }, - { id: 6, name: '粥P' }, - { id: 7, name: '二刺猿' }, - ] + tabList: ['全部','男性','女性','原P','武装直升机','粥P','二刺猿'] }; }, computed: { @@ -86,7 +79,11 @@ export default { methods: { ...mapActions(["setCharacters"]), - + //点击tab栏选择模板列表 + chooseType1(name, title) { + this.type = this.tabList.findIndex(item => item==title) + this.loadModeList(this.type); + }, globalWatcher(e) { console.log(e, "e"); }, @@ -99,10 +96,10 @@ export default { this.phase = num; }, - chooseType(num) { - this.type = num; - this.loadModeList(num); - }, + // chooseType(num) { + // this.type = num; + // this.loadModeList(num); + // }, selectModel(id) { if (this.currentId != id) { @@ -235,9 +232,9 @@ export default { } .typeOutSide { position: sticky; - top: 60px; + top: 0px; z-index: 99; - + .type { display: flex; justify-content: space-between; @@ -265,6 +262,7 @@ export default { } .choose { + padding-top: 20px; margin-bottom: 40px; .modeList { diff --git a/src/views/model/editModel.vue b/src/views/model/editModel.vue index 860aa66..6df2f46 100644 --- a/src/views/model/editModel.vue +++ b/src/views/model/editModel.vue @@ -7,15 +7,14 @@
-
- 中午吃啥? -
+ +
添加文案
- 已录入65字   + 已录入{{textareaContent.length}}字   视频时长: 00:16
@@ -23,7 +22,11 @@
- + + +
@@ -54,7 +57,7 @@
- +
@@ -101,7 +104,7 @@
更改声音
-
+
@@ -124,7 +127,7 @@
更换背景
-
+
@@ -143,7 +146,18 @@
-
+
+
+
+
+
+ +
+ × +
+ + +
@@ -153,7 +167,7 @@
更换素材
-
+
@@ -167,12 +181,23 @@
图片
-
+
-
+
+
+
+
+
+ +
+ × +
+ + +
@@ -201,19 +226,19 @@ export default { imgUrl:null, modelId: null, inputTitle: '', - textareaContent: '', + textareaContent: '', // 输入框内容 bottomTabActive: 2,// 底部导航高亮 showSubtitle: true, // 是否展示字幕 showSynthesisDialog: false,//合成进度条窗口显示隐藏 - showTabSheet: true, //tab栏弹出框显隐 + showTabSheet: false, //tab栏弹出框显隐 soundTabList: ['男生', '女生', '御姐', '萝莉'], // 声音tab栏标题 - soundListAction:0,//声音tab栏标题高亮 + soundListAction:-1,//声音tab栏标题高亮 soundItemList: ['普通话', '英语', '日语', '日语', '日语', '日语', '日语'],// 声音展示列表 - soundItemListAction:0,// 声音展示列表高亮 - bjAction:0,// 背景:素材库和上传高亮 - bjImgAction:0,// 选中背景图高亮 - scAction:0,// 素材:素材库和上传高亮 - scImgAction:0,// 选中素材图高亮 + soundItemListAction:-1,// 声音展示列表高亮 + bjAction:-1,// 背景:素材库和上传高亮 + bjImgAction:-1,// 选中背景图高亮 + scAction:-1,// 素材:素材库和上传高亮 + scImgAction:-1,// 选中素材图高亮 }; }, computed: { @@ -252,6 +277,7 @@ export default {