Browse Source

feat: video call need answer first

feat_video_fine
konenet 3 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. 56
      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 @@ @@ -7,7 +7,7 @@
<meta name="theme-color" content="#000000" />
<meta
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" />
<!--
@ -24,7 +24,7 @@ @@ -24,7 +24,7 @@
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`.
-->
<title>React App</title>
<title>go-chat</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>

142
src/chat/Panel.jsx

@ -6,6 +6,7 @@ import { @@ -6,6 +6,7 @@ import {
message,
Drawer,
Tooltip,
Modal
} from 'antd';
import {
PoweroffOutlined,
@ -80,6 +81,9 @@ class Panel extends React.Component { @@ -80,6 +81,9 @@ class Panel extends React.Component {
height: 0,
width: 0
},
videoCallModal: false,
callName: '',
fromUserUuid: '',
}
}
@ -112,7 +116,20 @@ class Panel extends React.Component { @@ -112,7 +116,20 @@ class Panel extends React.Component {
reader.readAsArrayBuffer(message.data);
reader.onload = ((event) => {
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;
}
@ -142,11 +159,11 @@ class Panel extends React.Component { @@ -142,11 +159,11 @@ class Panel extends React.Component {
return;
}
// webrtc
if (messagePB.type === Constant.MESSAGE_TRANS_TYPE) {
this.dealWebRtcMessage(messagePB);
return;
}
// // webrtc
// if (messagePB.type === Constant.MESSAGE_TRANS_TYPE) {
// this.dealWebRtcMessage(messagePB);
// return;
// }
let avatar = this.props.chooseUser.avatar
if (messagePB.messageType === 2) {
@ -227,6 +244,10 @@ class Panel extends React.Component { @@ -227,6 +244,10 @@ class Panel extends React.Component {
* @param {消息内容}} 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);
if (type === "answer") {
@ -324,12 +345,16 @@ class Panel extends React.Component { @@ -324,12 +345,16 @@ class Panel extends React.Component {
* @param {消息内容} messageData
*/
sendMessage = (messageData) => {
let toUser = messageData.toUser;
if (null == toUser) {
toUser = this.props.chooseUser.toUser;
}
let data = {
...messageData,
messageType: this.props.chooseUser.messageType, // 1. 2.
fromUsername: localStorage.username,
from: localStorage.uuid,
to: this.props.chooseUser.toUser,
to: toUser,
}
let message = protobuf.lookup("protocol.Message")
const messagePB = message.create(data)
@ -402,20 +427,102 @@ class Panel extends React.Component { @@ -402,20 +427,102 @@ class Panel extends React.Component {
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() {
return (
<>
<Row style={{ paddingTop: 20, paddingBottom: 40 }}>
<Col span={2} style={{ borderRight: '1px solid #f0f0f0', textAlign: 'center' }}>
<Row style={{ paddingTop: 35, borderBottom: '1px solid #f0f0f0', borderTop: '1px solid #f0f0f0' }}>
<Col span={2} style={{ borderRight: '1px solid #f0f0f0', textAlign: 'center', borderTop: '1px solid #f0f0f0' }}>
<Left history={this.props.history} />
</Col>
<Col span={4} style={{ borderRight: '1px solid #f0f0f0' }}>
<Col span={4} style={{ borderRight: '1px solid #f0f0f0', borderTop: '1px solid #f0f0f0' }}>
<Center />
</Col>
<Col offset={1} span={16}>
<Col offset={1} span={16} style={{ borderTop: '1px solid #f0f0f0' }}>
<Right
history={this.props.history}
sendMessage={this.sendMessage}
@ -442,6 +549,17 @@ class Panel extends React.Component { @@ -442,6 +549,17 @@ class Panel extends React.Component {
<canvas id="canvas" width={this.state.currentScreen.width} height={this.state.currentScreen.height} />
<audio id="audioPhone" autoPlay controls />
</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) { @@ -454,11 +572,13 @@ function mapStateToProps(state) {
messageList: state.panelReducer.messageList,
chooseUser: state.panelReducer.chooseUser,
peer: state.panelReducer.peer,
userList: state.panelReducer.userList,
}
}
function mapDispatchToProps(dispatch) {
return {
setUserList: (data) => dispatch(actions.setUserList(data)),
setMessageList: (data) => dispatch(actions.setMessageList(data)),
setSocket: (data) => dispatch(actions.setSocket(data)),
setMedia: (data) => dispatch(actions.setMedia(data)),

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

@ -1,5 +1,18 @@ @@ -1,5 +1,18 @@
export const AUDIO_ONLINE = 6; //
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

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

@ -9,6 +9,7 @@ import { @@ -9,6 +9,7 @@ import {
} from '@ant-design/icons';
import moment from 'moment';
import InfiniteScroll from 'react-infinite-scroll-component';
import { connect } from 'react-redux'
import { actions } from '../../../redux/module/panel'
import * as Params from '../../../common/param/Params'
@ -38,6 +39,7 @@ class UserList extends React.Component { @@ -38,6 +39,7 @@ class UserList extends React.Component {
avatar: value.avatar
}
this.fetchMessages(chooseUser);
this.removeUnreadMessageDot(value.uuid);
}
/**
@ -100,26 +102,50 @@ class UserList extends React.Component { @@ -100,26 +102,50 @@ class UserList extends React.Component {
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() {
return (
<>
<List
itemLayout="horizontal"
dataSource={this.props.userList}
renderItem={item => (
<List.Item>
<List.Item.Meta
style={{ paddingLeft: 30 }}
onClick={() => this.chooseUser(item)}
avatar={<Badge dot={true}><Avatar src={item.avatar} /></Badge>}
title={item.username}
description=""
/>
</List.Item>
)}
/>
<div id="userList" style={{
height: document.body.scrollHeight - 125,
overflow: 'auto',
}}>
<InfiniteScroll
dataLength={this.props.userList.length}
scrollableTarget="userList"
>
<List
itemLayout="horizontal"
dataSource={this.props.userList}
renderItem={item => (
<List.Item>
<List.Item.Meta
style={{ paddingLeft: 30 }}
onClick={() => this.chooseUser(item)}
avatar={<Badge dot={item.hasUnreadMessage}><Avatar src={item.avatar} /></Badge>}
title={item.username}
description=""
/>
</List.Item>
)}
/>
</InfiniteScroll>
</div>
</>
);
}

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

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

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

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

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

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

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

@ -104,7 +104,7 @@ class ChatDetails extends React.Component { @@ -104,7 +104,7 @@ class ChatDetails extends React.Component {
<div
id="scrollableDiv"
style={{
height: 450,
height: document.body.scrollHeight / 3 * 1.4,
overflow: 'auto',
padding: '0 16px',
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'; @@ -2,7 +2,8 @@ import React from 'react';
import {
Tooltip,
Button,
Drawer
Drawer,
Modal
} from 'antd';
import {
@ -20,6 +21,7 @@ class ChatVideoOline extends React.Component { @@ -20,6 +21,7 @@ class ChatVideoOline extends React.Component {
super(props)
this.state = {
mediaPanelDrawerVisible: false,
videoCallModal: false,
}
}
@ -39,6 +41,7 @@ class ChatVideoOline extends React.Component { @@ -39,6 +41,7 @@ class ChatVideoOline extends React.Component {
this.props.setPeer(peer);
this.webrtcConnection();
}
videoIntervalObj = null;
/**
* 开启视频电话
*/
@ -46,6 +49,52 @@ class ChatVideoOline extends React.Component { @@ -46,6 +49,52 @@ class ChatVideoOline extends React.Component {
if (!this.props.checkMediaPermisssion()) {
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");
navigator.mediaDevices
@ -134,6 +183,22 @@ class ChatVideoOline extends React.Component { @@ -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() {
const { chooseUser } = this.props;
return (
@ -166,6 +231,17 @@ class ChatVideoOline extends React.Component { @@ -166,6 +231,17 @@ class ChatVideoOline extends React.Component {
<video id="localPreviewSender" width="700px" height="auto" autoPlay muted controls />
<video id="remoteVideoSender" width="700px" height="auto" autoPlay muted controls />
</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) { @@ -177,6 +253,7 @@ function mapStateToProps(state) {
chooseUser: state.panelReducer.chooseUser,
socket: state.panelReducer.socket,
peer: state.panelReducer.peer,
media: state.panelReducer.media,
}
}

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

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

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

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

Loading…
Cancel
Save