diff --git a/src/assets/icon/icon-loginout.png b/src/assets/icon/icon-loginout.png new file mode 100644 index 0000000..3d2e498 Binary files /dev/null and b/src/assets/icon/icon-loginout.png differ diff --git a/src/assets/icon/icon-pencil.png b/src/assets/icon/icon-pencil.png new file mode 100644 index 0000000..322ab81 Binary files /dev/null and b/src/assets/icon/icon-pencil.png differ diff --git a/src/assets/web(1).rar b/src/assets/web(1).rar new file mode 100644 index 0000000..918843f Binary files /dev/null and b/src/assets/web(1).rar differ diff --git a/src/assets/web(1)/web/css/client.css b/src/assets/web(1)/web/css/client.css new file mode 100644 index 0000000..479ae01 --- /dev/null +++ b/src/assets/web(1)/web/css/client.css @@ -0,0 +1,70 @@ +* { + margin: 0; + padding: 0; +} + +html, body { + height: 100%; +} + +button { + padding: 1px 6px; +} + +video { + background: #222; + margin: 0 0 0 0; + width: 320px; + height: 240px; +} + +.container { + display: flex; + height: 100%; + flex-direction: column; +} + +.top { + margin: 0.5em; +} + +.middle { + flex: 1; + overflow: auto; + margin: 0 0.5em; +} + +.log { + overflow: scroll; + width: 30em; + height: 100%; + background-color: #eeeeee; + float: left; +} + +.preview { + width: auto; + height: 100%; + float: left; +} + +.local { + margin: 0.5em 0.5em; + float: left; +} + +.remote { + margin: 0.5em 0.5em 0.5em 0; + float: left; +} + +.sdp { + width: 320px; + height: 200px; + margin: 0; + resize: none; +} + +.bottom { + margin: 0.5em; +} \ No newline at end of file diff --git a/src/assets/web(1)/web/js/client.js b/src/assets/web(1)/web/js/client.js new file mode 100644 index 0000000..a947148 --- /dev/null +++ b/src/assets/web(1)/web/js/client.js @@ -0,0 +1,503 @@ +'use strict' + +//本地视频预览窗口 +var localVideo = document.querySelector('video#localvideo'); + +//远端视频预览窗口 +var remoteVideo = document.querySelector('video#remotevideo'); + +//查看Offer文本窗口 +var offer = document.querySelector('textarea#offer'); + +//查看Answer文本窗口 +var answer = document.querySelector('textarea#answer'); + +var pcConfig = { + 'iceServers': [{ + //TURN服务器地址 + 'urls': 'turn:43.143.227.135:3478', + //TURN服务器用户名 + 'username': "ghb", + //TURN服务器密码 + 'credential': "moonshine" + }], + //默认使用relay方式传输数据 + "iceTransportPolicy": "relay", + "iceCandidatePoolSize": "0" +}; + +//websocket连接 +var conn; + +//会话描述 +var session; + +//本地视频流 +var localStream = null; + +//远端视频流 +var remoteStream = null; + +//PeerConnection +var pc = null; + +//offer描述 +var offerdesc = null; + +/** + * 功能: 判断此浏览器是在PC端,还是移动端。 + * 返回值: false, 说明当前操作系统是移动端 + * true, 说明当前的操作系统是PC端。 + */ + +function IsPC() { + var userAgentInfo = navigator.userAgent; + var Agents = ["Android", "iPhone", "SymbianOS", "Windows Phone", "iPad", "iPod"]; + var flag = true; + for (var v = 0; v < Agents.length; v++) { + if (userAgentInfo.indexOf(Agents[v]) > 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 {