From d6bc54cb137c34d7cd1f586c5a2f092a5141c67d Mon Sep 17 00:00:00 2001 From: konenet <kone_net@163.com> Date: Sun, 12 Dec 2021 11:16:10 +0800 Subject: [PATCH] feat: optimize the video and audio call --- README.md | 6 ++ src/chat/Panel.jsx | 31 ++++----- .../panel/right/component/ChatAudioOline.jsx | 55 ++++++++-------- .../panel/right/component/ChatVideoOline.jsx | 50 +++++++++------ src/chat/panel/right/index.jsx | 63 ++++--------------- 5 files changed, 91 insertions(+), 114 deletions(-) diff --git a/README.md b/README.md index 40eb29c..c3e4855 100644 --- a/README.md +++ b/README.md @@ -70,3 +70,9 @@ https://github.com/kone-net/go-chat-web * 屏幕共享  +## 分支说明 +one-file分支: +该分支是所有逻辑都在一个文件实现,包括语音,文字,图片,视频消息,视频通话,语音电话,屏幕共享。 +main分支: +是将各个部分进行拆分。将Panel拆分成,左、中、右。又将右边的发送文件,图片,文件拆分成更小的组件。 + diff --git a/src/chat/Panel.jsx b/src/chat/Panel.jsx index b77b838..a179aa7 100755 --- a/src/chat/Panel.jsx +++ b/src/chat/Panel.jsx @@ -80,7 +80,6 @@ class Panel extends React.Component { height: 0, width: 0 }, - rtcType: 'answer', } } @@ -191,11 +190,10 @@ class Panel extends React.Component { * @param {候选人信息} e */ peer.onicecandidate = (e) => { - console.log(this.state.rtcType + '_ice', e) if (e.candidate) { // rtcType参数默认是对端值为answer,如果是发起端,会将值设置为offer let candidate = { - type: this.state.rtcType + '_ice', + type: 'answer_ice', iceCandidate: e.candidate } let message = { @@ -212,10 +210,9 @@ class Panel extends React.Component { * @param {包含语音视频流} e */ peer.ontrack = (e) => { - console.log(e) if (e && e.streams) { if (this.state.onlineType === 1) { - let remoteVideo = document.getElementById("remoteVideo"); + let remoteVideo = document.getElementById("remoteVideoReceiver"); remoteVideo.srcObject = e.streams[0]; } else { let remoteAudio = document.getElementById("audioPhone"); @@ -231,10 +228,10 @@ class Panel extends React.Component { */ dealWebRtcMessage = (messagePB) => { const { type, sdp, iceCandidate } = JSON.parse(messagePB.content); - console.log(type) + if (type === "answer") { - const offerSdp = new RTCSessionDescription({ type, sdp }); - this.props.peer.localPeer.setRemoteDescription(offerSdp) + const answerSdp = new RTCSessionDescription({ type, sdp }); + this.props.peer.localPeer.setRemoteDescription(answerSdp) } else if (type === "answer_ice") { this.props.peer.localPeer.addIceCandidate(iceCandidate) } else if (type === "offer_ice") { @@ -247,7 +244,7 @@ class Panel extends React.Component { let video = false; if (messagePB.contentType === Constant.VIDEO_ONLINE) { - preview = document.getElementById("preview1"); + preview = document.getElementById("localVideoReceiver"); video = true this.setState({ onlineType: 1, @@ -368,9 +365,9 @@ class Panel extends React.Component { isRecord: false }) - let preview1 = document.getElementById("preview1"); - if (preview1 && preview1.srcObject && preview1.srcObject.getTracks()) { - preview1.srcObject.getTracks().forEach((track) => track.stop()); + let localVideoReceiver = document.getElementById("localVideoReceiver"); + if (localVideoReceiver && localVideoReceiver.srcObject && localVideoReceiver.srcObject.getTracks()) { + localVideoReceiver.srcObject.getTracks().forEach((track) => track.stop()); } let preview = document.getElementById("preview"); @@ -419,7 +416,11 @@ class Panel extends React.Component { </Col> <Col offset={1} span={16}> - <Right history={this.props.history} /> + <Right + history={this.props.history} + sendMessage={this.sendMessage} + checkMediaPermisssion={this.checkMediaPermisssion} + /> </Col> </Row> @@ -434,8 +435,8 @@ class Panel extends React.Component { /> </Tooltip> <br /> - <video id="preview1" width="700px" height="auto" autoPlay muted controls /> - <video id="remoteVideo" width="700px" height="auto" autoPlay muted controls /> + <video id="localVideoReceiver" width="700px" height="auto" autoPlay muted controls /> + <video id="remoteVideoReceiver" width="700px" height="auto" autoPlay muted controls /> <img id="receiver" width={this.state.currentScreen.width} height="auto" alt="" /> <canvas id="canvas" width={this.state.currentScreen.width} height={this.state.currentScreen.height} /> diff --git a/src/chat/panel/right/component/ChatAudioOline.jsx b/src/chat/panel/right/component/ChatAudioOline.jsx index b150883..a546eed 100644 --- a/src/chat/panel/right/component/ChatAudioOline.jsx +++ b/src/chat/panel/right/component/ChatAudioOline.jsx @@ -19,7 +19,7 @@ class ChatAudioOline extends React.Component { constructor(props) { super(props) this.state = { - + mediaPanelDrawerVisible: false, } } @@ -40,25 +40,19 @@ class ChatAudioOline extends React.Component { } this.webrtcConnection(); - console.log(this.props.peer) - this.setState({ - onlineType: 2, - rtcType: 'offer' - }) - navigator.mediaDevices .getUserMedia({ audio: true, video: false, }).then((stream) => { stream.getTracks().forEach(track => { - this.props.peer.localPeer.addTrack(track, stream); + localPeer.addTrack(track, stream); }); // 一定注意:需要将该动作,放在这里面,即流获取成功后,再进行offer创建。不然不能获取到流,从而不能播放视频。 - this.props.peer.localPeer.createOffer() + localPeer.createOffer() .then(offer => { - this.props.peer.localPeer.setLocalDescription(offer); + localPeer.setLocalDescription(offer); let data = { contentType: Constant.AUDIO_ONLINE, // 消息内容类型 content: JSON.stringify(offer), @@ -77,14 +71,12 @@ class ChatAudioOline extends React.Component { * webrtc 绑定事件 */ webrtcConnection = () => { - /** * 对等方收到ice信息后,通过调用 addIceCandidate 将接收的候选者信息传递给浏览器的ICE代理。 * @param {候选人信息} e */ - localPeer.onicecandidate = (e) => { - console.log(e) + localPeer.onicecandidate = (e) => { if (e.candidate) { // rtcType参数默认是对端值为answer,如果是发起端,会将值设置为offer let candidate = { @@ -104,29 +96,30 @@ class ChatAudioOline extends React.Component { * 当连接成功后,从里面获取语音视频流 * @param {包含语音视频流} e */ - localPeer.ontrack = (e) => { + localPeer.ontrack = (e) => { if (e && e.streams) { - if (this.state.onlineType === 1) { - let remoteVideo = document.getElementById("remoteVideo"); - remoteVideo.srcObject = e.streams[0]; - } else { - let remoteAudio = document.getElementById("audioPhone"); - remoteAudio.srcObject = e.streams[0]; - } + let remoteAudio = document.getElementById("remoteAudioPhone"); + remoteAudio.srcObject = e.streams[0]; } }; } /** - * 停止视频电话,屏幕共享 + * 停止语音电话 */ - stopVideoOnline = () => { - let audioPhone = document.getElementById("audioPhone"); + stopAudioOnline = () => { + let audioPhone = document.getElementById("remoteAudioPhone"); if (audioPhone && audioPhone.srcObject && audioPhone.srcObject.getTracks()) { audioPhone.srcObject.getTracks().forEach((track) => track.stop()); } } + mediaPanelDrawerOnClose = () => { + this.setState({ + mediaPanelDrawerVisible: false + }) + } + render() { const { chooseUser } = this.props; return ( @@ -141,18 +134,24 @@ class ChatAudioOline extends React.Component { /> </Tooltip> - <Drawer width='820px' forceRender={true} title="媒体面板" placement="right" onClose={this.mediaPanelDrawerOnClose} visible={this.state.mediaPanelDrawerVisible}> + <Drawer width='420px' + forceRender={true} + title="媒体面板" + placement="right" + onClose={this.mediaPanelDrawerOnClose} + visible={this.state.mediaPanelDrawerVisible} + > <Tooltip title="结束视频语音"> <Button shape="circle" - onClick={this.stopVideoOnline} + onClick={this.stopAudioOnline} style={{ marginRight: 10, float: 'right' }} icon={<PoweroffOutlined style={{ color: 'red' }} />} /> </Tooltip> <br /> - - <audio id="audioPhone" autoPlay controls /> + + <audio id="remoteAudioPhone" autoPlay controls /> </Drawer> </> ); diff --git a/src/chat/panel/right/component/ChatVideoOline.jsx b/src/chat/panel/right/component/ChatVideoOline.jsx index 17fcf40..dad9a28 100644 --- a/src/chat/panel/right/component/ChatVideoOline.jsx +++ b/src/chat/panel/right/component/ChatVideoOline.jsx @@ -19,17 +19,25 @@ class ChatVideoOline extends React.Component { constructor(props) { super(props) this.state = { - + mediaPanelDrawerVisible: false, } } componentDidMount() { + // const configuration = { + // iceServers: [{ + // "url": "stun:23.21.150.121" + // }, { + // "url": "stun:stun.l.google.com:19302" + // }] + // }; localPeer = new RTCPeerConnection(); let peer = { ...this.props.peer, localPeer: localPeer } this.props.setPeer(peer); + this.webrtcConnection(); } /** * 开启视频电话 @@ -38,28 +46,22 @@ class ChatVideoOline extends React.Component { if (!this.props.checkMediaPermisssion()) { return; } - this.webrtcConnection(); - let preview = document.getElementById("preview"); - this.setState({ - onlineType: 1, - rtcType: 'offer' - }) + let preview = document.getElementById("localPreviewSender"); navigator.mediaDevices .getUserMedia({ audio: true, video: true, }).then((stream) => { - console.log(stream) preview.srcObject = stream; stream.getTracks().forEach(track => { - this.props.peer.localPeer.addTrack(track, stream); + localPeer.addTrack(track, stream); }); // 一定注意:需要将该动作,放在这里面,即流获取成功后,再进行offer创建。不然不能获取到流,从而不能播放视频。 - this.props.peer.localPeer.createOffer() + localPeer.createOffer() .then(offer => { - this.props.peer.localPeer.setLocalDescription(offer); + localPeer.setLocalDescription(offer); let data = { contentType: Constant.VIDEO_ONLINE, content: JSON.stringify(offer), @@ -79,13 +81,11 @@ class ChatVideoOline extends React.Component { */ webrtcConnection = () => { - /** * 对等方收到ice信息后,通过调用 addIceCandidate 将接收的候选者信息传递给浏览器的ICE代理。 * @param {候选人信息} e */ localPeer.onicecandidate = (e) => { - console.log(e) if (e.candidate) { // rtcType参数默认是对端值为answer,如果是发起端,会将值设置为offer let candidate = { @@ -107,7 +107,7 @@ class ChatVideoOline extends React.Component { */ localPeer.ontrack = (e) => { if (e && e.streams) { - let remoteVideo = document.getElementById("remoteVideo"); + let remoteVideo = document.getElementById("remoteVideoSender"); remoteVideo.srcObject = e.streams[0]; } }; @@ -117,17 +117,23 @@ class ChatVideoOline extends React.Component { * 停止视频电话,屏幕共享 */ stopVideoOnline = () => { - let preview = document.getElementById("preview"); + let preview = document.getElementById("localPreviewSender"); if (preview && preview.srcObject && preview.srcObject.getTracks()) { preview.srcObject.getTracks().forEach((track) => track.stop()); } - let remoteVideo = document.getElementById("remoteVideo"); + let remoteVideo = document.getElementById("remoteVideoSender"); if (remoteVideo && remoteVideo.srcObject && remoteVideo.srcObject.getTracks()) { remoteVideo.srcObject.getTracks().forEach((track) => track.stop()); } } + mediaPanelDrawerOnClose = () => { + this.setState({ + mediaPanelDrawerVisible: false + }) + } + render() { const { chooseUser } = this.props; return ( @@ -141,7 +147,13 @@ class ChatVideoOline extends React.Component { /> </Tooltip> - <Drawer width='820px' forceRender={true} title="媒体面板" placement="right" onClose={this.mediaPanelDrawerOnClose} visible={this.state.mediaPanelDrawerVisible}> + <Drawer width='820px' + forceRender={true} + title="媒体面板" + placement="right" + onClose={this.mediaPanelDrawerOnClose} + visible={this.state.mediaPanelDrawerVisible} + > <Tooltip title="结束视频语音"> <Button shape="circle" @@ -151,8 +163,8 @@ class ChatVideoOline extends React.Component { /> </Tooltip> <br /> - <video id="preview" width="700px" height="auto" autoPlay muted controls /> - <video id="remoteVideo" width="700px" height="auto" autoPlay muted controls /> + <video id="localPreviewSender" width="700px" height="auto" autoPlay muted controls /> + <video id="remoteVideoSender" width="700px" height="auto" autoPlay muted controls /> </Drawer> </> ); diff --git a/src/chat/panel/right/index.jsx b/src/chat/panel/right/index.jsx index 65d5238..3d575e7 100644 --- a/src/chat/panel/right/index.jsx +++ b/src/chat/panel/right/index.jsx @@ -1,7 +1,6 @@ import React from 'react'; import { - message, Tag, Tooltip, Button @@ -22,7 +21,6 @@ import ChatVideoOline from './component/ChatVideoOline'; import ChatEdit from './component/ChatEdit'; import moment from 'moment'; -import protobuf from '../../proto/proto'; import { connect } from 'react-redux'; import { actions } from '../../redux/module/panel'; @@ -62,45 +60,6 @@ class RightIndex extends React.Component { this.appendMessage(<img src={base64String} alt="" width="150px" />); } - /** - * 发送消息 - * @param {消息内容} messageData - */ - sendMessage = (messageData) => { - let data = { - ...messageData, - messageType: this.props.chooseUser.messageType, // 消息类型,1.单聊 2.群聊 - fromUsername: localStorage.username, - from: localStorage.uuid, - to: this.props.chooseUser.toUser, - } - let message = protobuf.lookup("protocol.Message") - const messagePB = message.create(data) - - let socket = this.props.socket; - if (null == socket) { - message.error("socket未连接"); - return; - } - socket.send(message.encode(messagePB).finish()) - } - - /** - * 检查媒体权限是否开启 - * @returns 媒体权限是否开启 - */ - checkMediaPermisssion = () => { - navigator.getUserMedia = navigator.getUserMedia || - navigator.webkitGetUserMedia || - navigator.mozGetUserMedia || - navigator.msGetUserMedia; //获取媒体对象(这里指摄像头) - if (!navigator || !navigator.mediaDevices) { - message.error("获取摄像头权限失败!") - return false; - } - return true; - } - showMediaPanel = () => { let media = { ...this.props.media, @@ -119,37 +78,37 @@ class RightIndex extends React.Component { history={this.props.history} appendMessage={this.appendMessage} appendImgToPanel={this.appendImgToPanel} - sendMessage={this.sendMessage} + sendMessage={this.props.sendMessage} /> <ChatAudio history={this.props.history} appendMessage={this.appendMessage} - sendMessage={this.sendMessage} + sendMessage={this.props.sendMessage} /> <ChatVideo history={this.props.history} appendMessage={this.appendMessage} - sendMessage={this.sendMessage} - checkMediaPermisssion={this.checkMediaPermisssion} + sendMessage={this.props.sendMessage} + checkMediaPermisssion={this.props.checkMediaPermisssion} /> <ChatShareScreen history={this.props.history} - sendMessage={this.sendMessage} - checkMediaPermisssion={this.checkMediaPermisssion} + sendMessage={this.props.sendMessage} + checkMediaPermisssion={this.props.checkMediaPermisssion} /> <ChatAudioOline history={this.props.history} - sendMessage={this.sendMessage} - checkMediaPermisssion={this.checkMediaPermisssion} + sendMessage={this.props.sendMessage} + checkMediaPermisssion={this.props.checkMediaPermisssion} /> <ChatVideoOline history={this.props.history} - sendMessage={this.sendMessage} - checkMediaPermisssion={this.checkMediaPermisssion} + sendMessage={this.props.sendMessage} + checkMediaPermisssion={this.props.checkMediaPermisssion} /> <Tooltip title="显示视频面板"> @@ -169,7 +128,7 @@ class RightIndex extends React.Component { history={this.props.history} appendMessage={this.appendMessage} appendImgToPanel={this.appendImgToPanel} - sendMessage={this.sendMessage} + sendMessage={this.props.sendMessage} /> </>