Browse Source

feat: video call need answer first

feat_video_fine
konenet 4 years ago
parent
commit
dca6f8c436
  1. 4
      public/index.html
  2. 142
      src/chat/Panel.jsx
  3. 13
      src/chat/common/constant/Constant.jsx
  4. 28
      src/chat/panel/center/component/UserList.jsx
  5. 4
      src/chat/panel/center/index.jsx
  6. 5
      src/chat/panel/left/component/SwitchChat.jsx
  7. 4
      src/chat/panel/left/index.jsx
  8. 2
      src/chat/panel/right/component/ChatDetails.jsx
  9. 79
      src/chat/panel/right/component/ChatVideoOline.jsx
  10. 8
      src/chat/panel/right/index.jsx
  11. 2
      src/chat/redux/module/panel.jsx

4
public/index.html

@ -7,7 +7,7 @@
<meta name="theme-color" content="#000000" /> <meta name="theme-color" content="#000000" />
<meta <meta
name="description" name="description"
content="Web site created using create-react-app" content="使用Go基于WebSocket开发的web聊天应用。单聊,群聊。文字,图片,语音,视频消息,屏幕共享,剪切板图片,基于WebRTC的P2P语音,视频聊天。"
/> />
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" /> <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!-- <!--
@ -24,7 +24,7 @@
work correctly both with client-side routing and a non-root public URL. work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`. Learn how to configure a non-root public URL by running `npm run build`.
--> -->
<title>React App</title> <title>go-chat</title>
</head> </head>
<body> <body>
<noscript>You need to enable JavaScript to run this app.</noscript> <noscript>You need to enable JavaScript to run this app.</noscript>

142
src/chat/Panel.jsx

@ -6,6 +6,7 @@ import {
message, message,
Drawer, Drawer,
Tooltip, Tooltip,
Modal
} from 'antd'; } from 'antd';
import { import {
PoweroffOutlined, PoweroffOutlined,
@ -80,6 +81,9 @@ class Panel extends React.Component {
height: 0, height: 0,
width: 0 width: 0
}, },
videoCallModal: false,
callName: '',
fromUserUuid: '',
} }
} }
@ -112,7 +116,20 @@ class Panel extends React.Component {
reader.readAsArrayBuffer(message.data); reader.readAsArrayBuffer(message.data);
reader.onload = ((event) => { reader.onload = ((event) => {
let messagePB = messageProto.decode(new Uint8Array(event.target.result)) let messagePB = messageProto.decode(new Uint8Array(event.target.result))
if (this.props.chooseUser.toUser !== messagePB.from || messagePB.type === "heatbeat") { console.log(messagePB)
if (messagePB.type === "heatbeat") {
return;
}
// webrtc
if (messagePB.type === Constant.MESSAGE_TRANS_TYPE) {
this.dealWebRtcMessage(messagePB);
return;
}
//
if (this.props.chooseUser.toUser !== messagePB.from) {
this.showUnreadMessageDot(messagePB.from);
return; return;
} }
@ -142,11 +159,11 @@ class Panel extends React.Component {
return; return;
} }
// webrtc // // webrtc
if (messagePB.type === Constant.MESSAGE_TRANS_TYPE) { // if (messagePB.type === Constant.MESSAGE_TRANS_TYPE) {
this.dealWebRtcMessage(messagePB); // this.dealWebRtcMessage(messagePB);
return; // return;
} // }
let avatar = this.props.chooseUser.avatar let avatar = this.props.chooseUser.avatar
if (messagePB.messageType === 2) { if (messagePB.messageType === 2) {
@ -227,6 +244,10 @@ class Panel extends React.Component {
* @param {消息内容}} messagePB * @param {消息内容}} messagePB
*/ */
dealWebRtcMessage = (messagePB) => { dealWebRtcMessage = (messagePB) => {
if (messagePB.contentType >= Constant.DIAL_MEDIA_START && messagePB.contentType <= Constant.DIAL_MEDIA_END) {
this.dealMediaCall(messagePB);
return;
}
const { type, sdp, iceCandidate } = JSON.parse(messagePB.content); const { type, sdp, iceCandidate } = JSON.parse(messagePB.content);
if (type === "answer") { if (type === "answer") {
@ -324,12 +345,16 @@ class Panel extends React.Component {
* @param {消息内容} messageData * @param {消息内容} messageData
*/ */
sendMessage = (messageData) => { sendMessage = (messageData) => {
let toUser = messageData.toUser;
if (null == toUser) {
toUser = this.props.chooseUser.toUser;
}
let data = { let data = {
...messageData, ...messageData,
messageType: this.props.chooseUser.messageType, // 1. 2. messageType: this.props.chooseUser.messageType, // 1. 2.
fromUsername: localStorage.username, fromUsername: localStorage.username,
from: localStorage.uuid, from: localStorage.uuid,
to: this.props.chooseUser.toUser, to: toUser,
} }
let message = protobuf.lookup("protocol.Message") let message = protobuf.lookup("protocol.Message")
const messagePB = message.create(data) const messagePB = message.create(data)
@ -402,20 +427,102 @@ class Panel extends React.Component {
this.props.setMedia(media) this.props.setMedia(media)
} }
/**
* 如果接收到的消息不是正在聊天的消息显示未读提醒
* @param {发送给对应人员的uuid} toUuid
*/
showUnreadMessageDot = (toUuid) => {
let userList = this.props.userList;
for (var index in userList) {
if (userList[index].uuid === toUuid) {
userList[index].hasUnreadMessage = true;
this.props.setUserList(userList);
break;
}
}
}
/**
* 接听电话后发送接听确认消息显示媒体面板
*/
handleOk = () => {
this.setState({
videoCallModal: false,
})
let data = {
contentType: Constant.ACCEPT_VIDEO_ONLINE,
type: Constant.MESSAGE_TRANS_TYPE,
toUser: this.state.fromUserUuid,
}
this.sendMessage(data);
let media = {
...this.props.media,
showMediaPanel: true,
}
this.props.setMedia(media)
}
handleCancel = () => {
let data = {
contentType: Constant.REJECT_VIDEO_ONLINE,
type: Constant.MESSAGE_TRANS_TYPE,
}
this.sendMessage(data);
this.setState({
videoCallModal: false,
})
}
dealMediaCall = (message) => {
if (message.contentType === Constant.DIAL_AUDIO_ONLINE || message.contentType === Constant.DIAL_VIDEO_ONLINE) {
this.setState({
videoCallModal: true,
callName: message.fromUsername,
fromUserUuid: message.from,
})
return;
}
if (message.contentType === Constant.CANCELL_AUDIO_ONLINE || message.contentType === Constant.CANCELL_VIDEO_ONLINE) {
this.setState({
videoCallModal: false,
})
return;
}
if (message.contentType === Constant.REJECT_AUDIO_ONLINE || message.contentType === Constant.REJECT_VIDEO_ONLINE) {
let media = {
...this.props.media,
mediaReject: true,
}
this.props.setMedia(media);
return;
}
if (message.contentType === Constant.ACCEPT_VIDEO_ONLINE || message.contentType === Constant.ACCEPT_AUDIO_ONLINE) {
let media = {
...this.props.media,
mediaConnected: true,
}
this.props.setMedia(media);
}
}
render() { render() {
return ( return (
<> <>
<Row style={{ paddingTop: 20, paddingBottom: 40 }}> <Row style={{ paddingTop: 35, borderBottom: '1px solid #f0f0f0', borderTop: '1px solid #f0f0f0' }}>
<Col span={2} style={{ borderRight: '1px solid #f0f0f0', textAlign: 'center' }}> <Col span={2} style={{ borderRight: '1px solid #f0f0f0', textAlign: 'center', borderTop: '1px solid #f0f0f0' }}>
<Left history={this.props.history} /> <Left history={this.props.history} />
</Col> </Col>
<Col span={4} style={{ borderRight: '1px solid #f0f0f0' }}> <Col span={4} style={{ borderRight: '1px solid #f0f0f0', borderTop: '1px solid #f0f0f0' }}>
<Center /> <Center />
</Col> </Col>
<Col offset={1} span={16}> <Col offset={1} span={16} style={{ borderTop: '1px solid #f0f0f0' }}>
<Right <Right
history={this.props.history} history={this.props.history}
sendMessage={this.sendMessage} sendMessage={this.sendMessage}
@ -442,6 +549,17 @@ class Panel extends React.Component {
<canvas id="canvas" width={this.state.currentScreen.width} height={this.state.currentScreen.height} /> <canvas id="canvas" width={this.state.currentScreen.width} height={this.state.currentScreen.height} />
<audio id="audioPhone" autoPlay controls /> <audio id="audioPhone" autoPlay controls />
</Drawer> </Drawer>
<Modal
title="视频电话"
visible={this.state.videoCallModal}
onOk={this.handleOk}
onCancel={this.handleCancel}
okText="接听"
cancelText="挂断"
>
<p>{this.state.callName}来电</p>
</Modal>
</> </>
); );
} }
@ -454,11 +572,13 @@ function mapStateToProps(state) {
messageList: state.panelReducer.messageList, messageList: state.panelReducer.messageList,
chooseUser: state.panelReducer.chooseUser, chooseUser: state.panelReducer.chooseUser,
peer: state.panelReducer.peer, peer: state.panelReducer.peer,
userList: state.panelReducer.userList,
} }
} }
function mapDispatchToProps(dispatch) { function mapDispatchToProps(dispatch) {
return { return {
setUserList: (data) => dispatch(actions.setUserList(data)),
setMessageList: (data) => dispatch(actions.setMessageList(data)), setMessageList: (data) => dispatch(actions.setMessageList(data)),
setSocket: (data) => dispatch(actions.setSocket(data)), setSocket: (data) => dispatch(actions.setSocket(data)),
setMedia: (data) => dispatch(actions.setMedia(data)), setMedia: (data) => dispatch(actions.setMedia(data)),

13
src/chat/common/constant/Constant.jsx

@ -1,5 +1,18 @@
export const AUDIO_ONLINE = 6; // export const AUDIO_ONLINE = 6; //
export const VIDEO_ONLINE = 7; // export const VIDEO_ONLINE = 7; //
export const DIAL_MEDIA_START = 10; //
export const DIAL_AUDIO_ONLINE = 11; //
export const ACCEPT_AUDIO_ONLINE = 12; //
export const CANCELL_AUDIO_ONLINE = 13; //
export const REJECT_AUDIO_ONLINE = 14; //
export const DIAL_VIDEO_ONLINE = 15; //
export const ACCEPT_VIDEO_ONLINE = 16; //
export const CANCELL_VIDEO_ONLINE = 17; //
export const REJECT_VIDEO_ONLINE = 18; //
export const DIAL_MEDIA_END = 20; //
export const MESSAGE_TRANS_TYPE = "webrtc"; // heatbeat,线webrtc export const MESSAGE_TRANS_TYPE = "webrtc"; // heatbeat,线webrtc

28
src/chat/panel/center/component/UserList.jsx

@ -9,6 +9,7 @@ import {
} from '@ant-design/icons'; } from '@ant-design/icons';
import moment from 'moment'; import moment from 'moment';
import InfiniteScroll from 'react-infinite-scroll-component';
import { connect } from 'react-redux' import { connect } from 'react-redux'
import { actions } from '../../../redux/module/panel' import { actions } from '../../../redux/module/panel'
import * as Params from '../../../common/param/Params' import * as Params from '../../../common/param/Params'
@ -38,6 +39,7 @@ class UserList extends React.Component {
avatar: value.avatar avatar: value.avatar
} }
this.fetchMessages(chooseUser); this.fetchMessages(chooseUser);
this.removeUnreadMessageDot(value.uuid);
} }
/** /**
@ -100,11 +102,33 @@ class UserList extends React.Component {
return content; return content;
} }
/**
* 查看消息后去掉未读提醒
* @param {发送给对应人员的uuid} toUuid
*/
removeUnreadMessageDot = (toUuid) => {
let userList = this.props.userList;
for (var index in userList) {
if (userList[index].uuid === toUuid) {
userList[index].hasUnreadMessage = false;
this.props.setUserList(userList);
break;
}
}
}
render() { render() {
return ( return (
<> <>
<div id="userList" style={{
height: document.body.scrollHeight - 125,
overflow: 'auto',
}}>
<InfiniteScroll
dataLength={this.props.userList.length}
scrollableTarget="userList"
>
<List <List
itemLayout="horizontal" itemLayout="horizontal"
dataSource={this.props.userList} dataSource={this.props.userList}
@ -113,13 +137,15 @@ class UserList extends React.Component {
<List.Item.Meta <List.Item.Meta
style={{ paddingLeft: 30 }} style={{ paddingLeft: 30 }}
onClick={() => this.chooseUser(item)} onClick={() => this.chooseUser(item)}
avatar={<Badge dot={true}><Avatar src={item.avatar} /></Badge>} avatar={<Badge dot={item.hasUnreadMessage}><Avatar src={item.avatar} /></Badge>}
title={item.username} title={item.username}
description="" description=""
/> />
</List.Item> </List.Item>
)} )}
/> />
</InfiniteScroll>
</div>
</> </>
); );
} }

4
src/chat/panel/center/index.jsx

@ -7,10 +7,10 @@ export default class CenterIndex extends React.Component {
render() { render() {
return ( return (
<> <div style={{ marginTop: 10 }}>
<UserSearch /> <UserSearch />
<UserList /> <UserList />
</> </div>
); );
} }
} }

5
src/chat/panel/left/component/SwitchChat.jsx

@ -38,6 +38,7 @@ class SwitchChat extends React.Component {
let data = [] let data = []
for (var index in users) { for (var index in users) {
let d = { let d = {
hasUnreadMessage: false,
username: users[index].username, username: users[index].username,
uuid: users[index].uuid, uuid: users[index].uuid,
messageType: 1, messageType: 1,
@ -80,7 +81,7 @@ class SwitchChat extends React.Component {
render() { render() {
const { menuType } = this.state const { menuType } = this.state
return ( return (
<> <div style={{marginTop: 25}}>
<p > <p >
<Button <Button
icon={<UserOutlined />} icon={<UserOutlined />}
@ -102,7 +103,7 @@ class SwitchChat extends React.Component {
> >
</Button> </Button>
</p> </p>
</> </div>
); );
} }
} }

4
src/chat/panel/left/index.jsx

@ -7,10 +7,10 @@ export default class LeftIndex extends React.Component {
render() { render() {
return ( return (
<> <div style={{ marginTop: 10 }}>
<UserInfo history={this.props.history} /> <UserInfo history={this.props.history} />
<SwitchChat /> <SwitchChat />
</> </div>
); );
} }
} }

2
src/chat/panel/right/component/ChatDetails.jsx

@ -104,7 +104,7 @@ class ChatDetails extends React.Component {
<div <div
id="scrollableDiv" id="scrollableDiv"
style={{ style={{
height: 450, height: document.body.scrollHeight / 3 * 1.4,
overflow: 'auto', overflow: 'auto',
padding: '0 16px', padding: '0 16px',
border: '0px solid rgba(140, 140, 140, 0.35)', border: '0px solid rgba(140, 140, 140, 0.35)',

79
src/chat/panel/right/component/ChatVideoOline.jsx

@ -2,7 +2,8 @@ import React from 'react';
import { import {
Tooltip, Tooltip,
Button, Button,
Drawer Drawer,
Modal
} from 'antd'; } from 'antd';
import { import {
@ -20,6 +21,7 @@ class ChatVideoOline extends React.Component {
super(props) super(props)
this.state = { this.state = {
mediaPanelDrawerVisible: false, mediaPanelDrawerVisible: false,
videoCallModal: false,
} }
} }
@ -39,6 +41,7 @@ class ChatVideoOline extends React.Component {
this.props.setPeer(peer); this.props.setPeer(peer);
this.webrtcConnection(); this.webrtcConnection();
} }
videoIntervalObj = null;
/** /**
* 开启视频电话 * 开启视频电话
*/ */
@ -46,6 +49,52 @@ class ChatVideoOline extends React.Component {
if (!this.props.checkMediaPermisssion()) { if (!this.props.checkMediaPermisssion()) {
return; return;
} }
let media = {
...this.props.media,
mediaConnected: false,
}
this.props.setMedia(media);
this.setState({
videoCallModal: true,
})
let data = {
contentType: Constant.DIAL_VIDEO_ONLINE,
type: Constant.MESSAGE_TRANS_TYPE,
}
this.props.sendMessage(data);
this.videoIntervalObj = setInterval(() => {
console.log("video call")
//
if (this.props.media && this.props.media.mediaConnected) {
this.setMediaState();
this.sendVideoData();
return;
}
//
if (this.props.media && this.props.media.mediaReject) {
this.setMediaState();
return;
}
this.props.sendMessage(data);
}, 3000)
}
setMediaState = () => {
this.videoIntervalObj && clearInterval(this.videoIntervalObj);
this.setState({
videoCallModal: false,
})
let media = {
...this.props.media,
mediaConnected: false,
mediaReject: false,
}
this.props.setMedia(media)
}
sendVideoData = () => {
let preview = document.getElementById("localPreviewSender"); let preview = document.getElementById("localPreviewSender");
navigator.mediaDevices navigator.mediaDevices
@ -134,6 +183,22 @@ class ChatVideoOline extends React.Component {
}) })
} }
handleOk = () => {
}
handleCancel = () => {
this.setState({
videoCallModal: false,
})
let data = {
contentType: Constant.CANCELL_VIDEO_ONLINE,
type: Constant.MESSAGE_TRANS_TYPE,
}
this.props.sendMessage(data);
this.videoIntervalObj && clearInterval(this.videoIntervalObj);
}
render() { render() {
const { chooseUser } = this.props; const { chooseUser } = this.props;
return ( return (
@ -166,6 +231,17 @@ class ChatVideoOline extends React.Component {
<video id="localPreviewSender" 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 /> <video id="remoteVideoSender" width="700px" height="auto" autoPlay muted controls />
</Drawer> </Drawer>
<Modal
title="视频电话"
visible={this.state.videoCallModal}
onOk={this.handleOk}
onCancel={this.handleCancel}
okText="确认"
cancelText="取消"
>
<p>呼叫中...</p>
</Modal>
</> </>
); );
} }
@ -177,6 +253,7 @@ function mapStateToProps(state) {
chooseUser: state.panelReducer.chooseUser, chooseUser: state.panelReducer.chooseUser,
socket: state.panelReducer.socket, socket: state.panelReducer.socket,
peer: state.panelReducer.peer, peer: state.panelReducer.peer,
media: state.panelReducer.media,
} }
} }

8
src/chat/panel/right/index.jsx

@ -71,7 +71,11 @@ class RightIndex extends React.Component {
render() { render() {
return ( return (
<> <div style={{
height: document.body.scrollHeight - 80,
overflow: 'hidden',
}}
>
<ChatDetails history={this.props.history} appendMessage={this.appendMessage} /> <ChatDetails history={this.props.history} appendMessage={this.appendMessage} />
<br /> <br />
<ChatFile <ChatFile
@ -131,7 +135,7 @@ class RightIndex extends React.Component {
sendMessage={this.props.sendMessage} sendMessage={this.props.sendMessage}
/> />
</> </div>
); );
} }
} }

2
src/chat/redux/module/panel.jsx

@ -11,6 +11,8 @@ const initialState = {
media: { media: {
isRecord: false, isRecord: false,
showMediaPanel: false, showMediaPanel: false,
mediaConnected: false,
mediaReject: false,
}, },
peer: { peer: {
localPeer: null, // WebRTC peer localPeer: null, // WebRTC peer

Loading…
Cancel
Save