|
|
|
|
@ -7,7 +7,7 @@ import {
@@ -7,7 +7,7 @@ import {
|
|
|
|
|
Drawer, |
|
|
|
|
Tag, |
|
|
|
|
Popover, |
|
|
|
|
Tooltip |
|
|
|
|
Tooltip, |
|
|
|
|
} from 'antd'; |
|
|
|
|
import { |
|
|
|
|
UserOutlined, |
|
|
|
|
@ -21,17 +21,22 @@ import {
@@ -21,17 +21,22 @@ import {
|
|
|
|
|
PhoneOutlined, |
|
|
|
|
VideoCameraOutlined, |
|
|
|
|
UngroupOutlined, |
|
|
|
|
DesktopOutlined |
|
|
|
|
DesktopOutlined, |
|
|
|
|
FileOutlined |
|
|
|
|
} from '@ant-design/icons'; |
|
|
|
|
import InfiniteScroll from 'react-infinite-scroll-component'; |
|
|
|
|
import moment from 'moment'; |
|
|
|
|
import { axiosGet, axiosPostBody } from './util/Request'; |
|
|
|
|
import * as Params from './common/param/Params' |
|
|
|
|
import UserInfo from './component/UserInfo' |
|
|
|
|
|
|
|
|
|
import protobuf from './proto/proto' |
|
|
|
|
import Recorder from 'js-audio-recorder'; |
|
|
|
|
import { connect } from 'react-redux' |
|
|
|
|
import { actions } from './redux/module/userInfo' |
|
|
|
|
|
|
|
|
|
var socket = null |
|
|
|
|
var socket = null; |
|
|
|
|
var peer = null; |
|
|
|
|
|
|
|
|
|
const { TextArea } = Input; |
|
|
|
|
|
|
|
|
|
@ -105,7 +110,6 @@ var heartCheck = {
@@ -105,7 +110,6 @@ var heartCheck = {
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Panel extends React.Component { |
|
|
|
|
constructor(props) { |
|
|
|
|
super(props) |
|
|
|
|
@ -137,12 +141,13 @@ class Panel extends React.Component {
@@ -137,12 +141,13 @@ class Panel extends React.Component {
|
|
|
|
|
}, |
|
|
|
|
share: { |
|
|
|
|
height: 540, |
|
|
|
|
width: 960 |
|
|
|
|
width: 750 |
|
|
|
|
}, |
|
|
|
|
currentScreen: { |
|
|
|
|
height: 0, |
|
|
|
|
width: 0 |
|
|
|
|
} |
|
|
|
|
}, |
|
|
|
|
rtcType: 'answer', |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@ -194,9 +199,25 @@ class Panel extends React.Component {
@@ -194,9 +199,25 @@ class Panel extends React.Component {
|
|
|
|
|
const messagePB = message.create(data) |
|
|
|
|
socket.send(message.encode(messagePB).finish()) |
|
|
|
|
|
|
|
|
|
this.appendImgToPanel(imgData) |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
}, false) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* 本地上传后,将图片追加到聊天框 |
|
|
|
|
* @param {Arraybuffer类型图片}} imgData |
|
|
|
|
*/ |
|
|
|
|
appendImgToPanel(imgData) { |
|
|
|
|
// 将ArrayBuffer转换为base64进行展示 |
|
|
|
|
const str = String.fromCharCode(...new Uint8Array(imgData)); |
|
|
|
|
let base64String = `data:image/jpeg;base64,${window.btoa(str)}`; |
|
|
|
|
var binary = ''; |
|
|
|
|
var bytes = new Uint8Array(imgData); |
|
|
|
|
var len = bytes.byteLength; |
|
|
|
|
for (var i = 0; i < len; i++) { |
|
|
|
|
binary += String.fromCharCode(bytes[i]); |
|
|
|
|
} |
|
|
|
|
let base64String = `data:image/jpeg;base64,${window.btoa(binary)}`; |
|
|
|
|
|
|
|
|
|
this.setState({ |
|
|
|
|
comments: [ |
|
|
|
|
@ -208,10 +229,9 @@ class Panel extends React.Component {
@@ -208,10 +229,9 @@ class Panel extends React.Component {
|
|
|
|
|
datetime: moment().fromNow(), |
|
|
|
|
}, |
|
|
|
|
], |
|
|
|
|
}, () => { |
|
|
|
|
setTimeout(this.scrollToBottom(), 3000) |
|
|
|
|
}) |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
}, false) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
@ -220,8 +240,13 @@ class Panel extends React.Component {
@@ -220,8 +240,13 @@ class Panel extends React.Component {
|
|
|
|
|
fetchUserDetails = () => { |
|
|
|
|
axiosGet(Params.USER_URL + this.state.fromUser) |
|
|
|
|
.then(response => { |
|
|
|
|
let user = { |
|
|
|
|
...response.data, |
|
|
|
|
avatar: Params.HOST + "/file/" + response.data.avatar |
|
|
|
|
} |
|
|
|
|
this.props.setUser(user) |
|
|
|
|
this.setState({ |
|
|
|
|
user: response.data, |
|
|
|
|
user: user, |
|
|
|
|
}) |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
@ -230,39 +255,15 @@ class Panel extends React.Component {
@@ -230,39 +255,15 @@ class Panel extends React.Component {
|
|
|
|
|
* websocket连接 |
|
|
|
|
*/ |
|
|
|
|
connection = () => { |
|
|
|
|
console.log("to connect...") |
|
|
|
|
peer = new RTCPeerConnection(); |
|
|
|
|
var image = document.getElementById('receiver'); |
|
|
|
|
|
|
|
|
|
let arr = [] |
|
|
|
|
var video = document.getElementById('preview1') |
|
|
|
|
let i = 0 |
|
|
|
|
|
|
|
|
|
// let flag = false |
|
|
|
|
// let sourceBuffer |
|
|
|
|
// let mediaSource = new MediaSource() |
|
|
|
|
// var video = document.getElementById('preview1') |
|
|
|
|
// video.src = URL.createObjectURL(mediaSource) |
|
|
|
|
// mediaSource.addEventListener('sourceopen', sourceOpen); |
|
|
|
|
|
|
|
|
|
// function sourceOpen(e) { |
|
|
|
|
// console.log("sourceOpen", "mediaSource ready state: ", mediaSource.readyState) |
|
|
|
|
// // var mime = 'video/mp4; codecs="avc1.42E01E, mp4a.40.2"' |
|
|
|
|
// var mime = 'video/webm; codecs="opus, vp9"'; |
|
|
|
|
// // 新建一个 sourceBuffer |
|
|
|
|
// sourceBuffer = mediaSource.addSourceBuffer(mime); |
|
|
|
|
|
|
|
|
|
// sourceBuffer.addEventListener('updateend', function (_) { |
|
|
|
|
// console.log(mediaSource.readyState); // ended |
|
|
|
|
// // sourceBuffer.appendBuffer(arr) |
|
|
|
|
// }); |
|
|
|
|
// } |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
console.log("to connection") |
|
|
|
|
socket = new WebSocket("ws://" + Params.IP_PORT + "/socket.io?user=" + this.props.match.params.user) |
|
|
|
|
|
|
|
|
|
socket.onopen = () => { |
|
|
|
|
heartCheck.start() |
|
|
|
|
console.log("connected") |
|
|
|
|
this.webrtcConnection() |
|
|
|
|
} |
|
|
|
|
socket.onmessage = (message) => { |
|
|
|
|
heartCheck.start() |
|
|
|
|
@ -277,6 +278,7 @@ class Panel extends React.Component {
@@ -277,6 +278,7 @@ class Panel extends React.Component {
|
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 视频图像 |
|
|
|
|
if (messagePB.contentType === 8) { |
|
|
|
|
let currentScreen = { |
|
|
|
|
width: this.state.video.width, |
|
|
|
|
@ -289,6 +291,7 @@ class Panel extends React.Component {
@@ -289,6 +291,7 @@ class Panel extends React.Component {
|
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 屏幕共享 |
|
|
|
|
if (messagePB.contentType === 9) { |
|
|
|
|
let currentScreen = { |
|
|
|
|
width: this.state.share.width, |
|
|
|
|
@ -301,28 +304,9 @@ class Panel extends React.Component {
@@ -301,28 +304,9 @@ class Panel extends React.Component {
|
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 接受语音电话或者视频电话 |
|
|
|
|
if (messagePB.contentType === 6 || messagePB.contentType === 7) { |
|
|
|
|
i++ |
|
|
|
|
console.log(i) |
|
|
|
|
// arr.push(messagePB.file) |
|
|
|
|
|
|
|
|
|
if (i % 5 === 0) { |
|
|
|
|
// let fileReader = new FileReader(); |
|
|
|
|
let recordedBlob = new Blob(arr, { type: "video/webm" }); //video/x-matroska;codecs=avc1,opus video/webm |
|
|
|
|
// fileReader.readAsDataURL(recordedBlob) |
|
|
|
|
// fileReader.onload = (e) => { |
|
|
|
|
// console.log(e.target.result) |
|
|
|
|
// video.src = e.target.result; |
|
|
|
|
// this.setState({ |
|
|
|
|
// url: e.target.result |
|
|
|
|
// }) |
|
|
|
|
// } |
|
|
|
|
console.log(recordedBlob) |
|
|
|
|
video.src = URL.createObjectURL(recordedBlob); |
|
|
|
|
} |
|
|
|
|
// sourceBuffer.appendBuffer(messagePB.file.buffer) |
|
|
|
|
|
|
|
|
|
// 接受语音电话或者视频电话 webrtc |
|
|
|
|
if (messagePB.type === "webrtc") { |
|
|
|
|
this.dealWebRtcMessage(messagePB); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@ -331,18 +315,20 @@ class Panel extends React.Component {
@@ -331,18 +315,20 @@ class Panel extends React.Component {
|
|
|
|
|
avatar = messagePB.avatar |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 文件内容,录制的视频,语音内容 |
|
|
|
|
let content = this.getContentByType(messagePB.contentType, messagePB.url, messagePB.content) |
|
|
|
|
this.setState({ |
|
|
|
|
comments: [ |
|
|
|
|
...this.state.comments, |
|
|
|
|
{ |
|
|
|
|
author: messagePB.fromUsername, |
|
|
|
|
avatar: avatar, |
|
|
|
|
content: <p>{(messagePB.contentType === 2 || messagePB.contentType === 3) ? <img src={Params.HOST + "/file/" + messagePB.url} alt="" height="350px" /> : messagePB.content}</p>, |
|
|
|
|
content: <p>{content}</p>, |
|
|
|
|
datetime: moment().fromNow(), |
|
|
|
|
}, |
|
|
|
|
], |
|
|
|
|
}, () => { |
|
|
|
|
this.scrollToBottom() |
|
|
|
|
setTimeout(this.scrollToBottom(), 3000) |
|
|
|
|
}) |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
@ -360,6 +346,98 @@ class Panel extends React.Component {
@@ -360,6 +346,98 @@ class Panel extends React.Component {
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* webrtc 绑定事件 |
|
|
|
|
*/ |
|
|
|
|
webrtcConnection = () => { |
|
|
|
|
/** |
|
|
|
|
* 对等方收到ice信息后,通过调用 addIceCandidate 将接收的候选者信息传递给浏览器的ICE代理。 |
|
|
|
|
* @param {候选人信息} e |
|
|
|
|
*/ |
|
|
|
|
peer.onicecandidate = (e) => { |
|
|
|
|
if (e.candidate) { |
|
|
|
|
// rtcType参数默认是对端值为answer,如果是发起端,会将值设置为offer |
|
|
|
|
let candidate = { |
|
|
|
|
type: this.state.rtcType + '_ice', |
|
|
|
|
iceCandidate: e.candidate |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
let data = { |
|
|
|
|
fromUsername: localStorage.username, |
|
|
|
|
from: this.state.fromUser, |
|
|
|
|
to: this.state.toUser, |
|
|
|
|
messageType: this.state.messageType, |
|
|
|
|
content: JSON.stringify(candidate), |
|
|
|
|
type: "webrtc", |
|
|
|
|
} |
|
|
|
|
let message = protobuf.lookup("protocol.Message") |
|
|
|
|
const messagePB = message.create(data) |
|
|
|
|
socket.send(message.encode(messagePB).finish()) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* 当连接成功后,从里面获取语音视频流 |
|
|
|
|
* @param {包含语音视频流} e |
|
|
|
|
*/ |
|
|
|
|
peer.ontrack = (e) => { |
|
|
|
|
if (e && e.streams) { |
|
|
|
|
let remoteVideo = document.getElementById("remoteVideo"); |
|
|
|
|
remoteVideo.srcObject = e.streams[0]; |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* 处理webrtc消息,包括获取请求方的offer,回应answer等 |
|
|
|
|
* @param {消息内容}} messagePB |
|
|
|
|
*/ |
|
|
|
|
dealWebRtcMessage = (messagePB) => { |
|
|
|
|
const { type, sdp, iceCandidate } = JSON.parse(messagePB.content); |
|
|
|
|
|
|
|
|
|
if (type === "answer") { |
|
|
|
|
const offerSdp = new RTCSessionDescription({ type, sdp }); |
|
|
|
|
peer.setRemoteDescription(offerSdp) |
|
|
|
|
} else if (type === "answer_ice") { |
|
|
|
|
peer.addIceCandidate(iceCandidate) |
|
|
|
|
} else if (type === "offer_ice") { |
|
|
|
|
peer.addIceCandidate(iceCandidate) |
|
|
|
|
} else if (type === "offer") { |
|
|
|
|
let preview = document.getElementById("preview1"); |
|
|
|
|
navigator.mediaDevices |
|
|
|
|
.getUserMedia({ |
|
|
|
|
audio: true, |
|
|
|
|
video: true, |
|
|
|
|
}).then((stream) => { |
|
|
|
|
preview.srcObject = stream; |
|
|
|
|
stream.getTracks().forEach(track => { |
|
|
|
|
peer.addTrack(track, stream); |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
// 一定注意:需要将该动作,放在这里面,即流获取成功后,再进行answer创建。不然不能获取到流,从而不能播放视频。 |
|
|
|
|
const offerSdp = new RTCSessionDescription({ type, sdp }); |
|
|
|
|
peer.setRemoteDescription(offerSdp) |
|
|
|
|
.then(() => { |
|
|
|
|
peer.createAnswer().then(answer => { |
|
|
|
|
peer.setLocalDescription(answer) |
|
|
|
|
let data = { |
|
|
|
|
fromUsername: localStorage.username, |
|
|
|
|
from: this.state.fromUser, |
|
|
|
|
to: this.state.toUser, |
|
|
|
|
messageType: this.state.messageType, |
|
|
|
|
content: JSON.stringify(answer), |
|
|
|
|
type: "webrtc", |
|
|
|
|
} |
|
|
|
|
let message = protobuf.lookup("protocol.Message") |
|
|
|
|
const messagePB = message.create(data) |
|
|
|
|
socket.send(message.encode(messagePB).finish()) |
|
|
|
|
}) |
|
|
|
|
}); |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* 断开连接后重新连接 |
|
|
|
|
*/ |
|
|
|
|
@ -397,7 +475,7 @@ class Panel extends React.Component {
@@ -397,7 +475,7 @@ class Panel extends React.Component {
|
|
|
|
|
username: users[index].username, |
|
|
|
|
uuid: users[index].uuid, |
|
|
|
|
messageType: 1, |
|
|
|
|
avatar: users[index].avatar, |
|
|
|
|
avatar: Params.HOST + "/file/" + users[index].avatar, |
|
|
|
|
} |
|
|
|
|
data.push(d) |
|
|
|
|
} |
|
|
|
|
@ -534,16 +612,22 @@ class Panel extends React.Component {
@@ -534,16 +612,22 @@ class Panel extends React.Component {
|
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
axiosGet(Params.USER_URL + value) |
|
|
|
|
let data = { |
|
|
|
|
name: value |
|
|
|
|
} |
|
|
|
|
axiosGet(Params.USER_NAME_URL, data) |
|
|
|
|
.then(response => { |
|
|
|
|
console.log(response) |
|
|
|
|
if (response.data.username === 0) { |
|
|
|
|
message.error("无此用户") |
|
|
|
|
let data = response.data |
|
|
|
|
if (data.user.username === "" && data.group.name === "") { |
|
|
|
|
message.error("未查找到群或者用户") |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
let queryUser = { |
|
|
|
|
username: response.data.username, |
|
|
|
|
nickname: response.data.nickname, |
|
|
|
|
username: data.user.username, |
|
|
|
|
nickname: data.user.nickname, |
|
|
|
|
|
|
|
|
|
groupUuid: data.group.uuid, |
|
|
|
|
groupName: data.group.name, |
|
|
|
|
} |
|
|
|
|
this.setState({ |
|
|
|
|
hasUser: true, |
|
|
|
|
@ -573,6 +657,18 @@ class Panel extends React.Component {
@@ -573,6 +657,18 @@ class Panel extends React.Component {
|
|
|
|
|
}); |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
joinGroup = () => { |
|
|
|
|
// /group/join/:userUid/:groupUuid |
|
|
|
|
axiosPostBody(Params.GROUP_JOIN_URL + this.state.fromUser + "/" + this.state.queryUser.groupUuid) |
|
|
|
|
.then(_response => { |
|
|
|
|
message.success("添加成功") |
|
|
|
|
this.fetchUserList() |
|
|
|
|
this.setState({ |
|
|
|
|
hasUser: false |
|
|
|
|
}); |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
handleCancel = () => { |
|
|
|
|
this.setState({ |
|
|
|
|
hasUser: false |
|
|
|
|
@ -601,21 +697,11 @@ class Panel extends React.Component {
@@ -601,21 +697,11 @@ class Panel extends React.Component {
|
|
|
|
|
} |
|
|
|
|
for (var i = 0; i < data.length; i++) { |
|
|
|
|
let contentType = data[i].contentType |
|
|
|
|
let content = data[i].content; |
|
|
|
|
|
|
|
|
|
if (contentType === 2) { |
|
|
|
|
|
|
|
|
|
} else if (contentType === 3) { |
|
|
|
|
content = <img src={Params.HOST + "/file/" + data[i].url} alt="" width="150px" /> |
|
|
|
|
} else if (contentType === 4) { |
|
|
|
|
content = <audio src={Params.HOST + "/file/" + data[i].url} controls autoPlay={false} preload="auto" /> |
|
|
|
|
} else if (contentType === 5) { |
|
|
|
|
content = <video src={Params.HOST + "/file/" + data[i].url} controls autoPlay={false} preload="auto" width='200px' /> |
|
|
|
|
} |
|
|
|
|
let content = this.getContentByType(contentType, data[i].url, data[i].content) |
|
|
|
|
|
|
|
|
|
let comment = { |
|
|
|
|
author: data[i].fromUsername, |
|
|
|
|
avatar: data[i].avatar, |
|
|
|
|
avatar: Params.HOST + "/file/" + data[i].avatar, |
|
|
|
|
content: <p>{content}</p>, |
|
|
|
|
datetime: moment(data[i].createAt).fromNow(), |
|
|
|
|
} |
|
|
|
|
@ -626,17 +712,40 @@ class Panel extends React.Component {
@@ -626,17 +712,40 @@ class Panel extends React.Component {
|
|
|
|
|
comments: comments |
|
|
|
|
}, () => { |
|
|
|
|
this.scrollToBottom() |
|
|
|
|
setTimeout(this.scrollToBottom(), 5000) |
|
|
|
|
}) |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* 根据文件类型渲染对应的标签,比如视频,图片等。 |
|
|
|
|
* @param {文件类型} type |
|
|
|
|
* @param {文件地址} url |
|
|
|
|
* @returns |
|
|
|
|
*/ |
|
|
|
|
getContentByType = (type, url, content) => { |
|
|
|
|
if (type === 2) { |
|
|
|
|
content = <FileOutlined style={{ fontSize: 38 }} /> |
|
|
|
|
} else if (type === 3) { |
|
|
|
|
content = <img src={Params.HOST + "/file/" + url} alt="" width="150px" /> |
|
|
|
|
} else if (type === 4) { |
|
|
|
|
content = <audio src={Params.HOST + "/file/" + url} controls autoPlay={false} preload="auto" /> |
|
|
|
|
} else if (type === 5) { |
|
|
|
|
content = <video src={Params.HOST + "/file/" + url} controls autoPlay={false} preload="auto" width='200px' /> |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return content; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* 获取群聊信息,群成员列表 |
|
|
|
|
*/ |
|
|
|
|
chatDetails = () => { |
|
|
|
|
axiosGet(Params.GROUP_USER_URL + this.state.toUser) |
|
|
|
|
.then(response => { |
|
|
|
|
console.log(response) |
|
|
|
|
if (null == response.data) { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
this.setState({ |
|
|
|
|
drawerVisible: true, |
|
|
|
|
groupUsers: response.data |
|
|
|
|
@ -660,6 +769,17 @@ class Panel extends React.Component {
@@ -660,6 +769,17 @@ class Panel extends React.Component {
|
|
|
|
|
if (!files || !files[0]) { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
let fileName = files[0].name |
|
|
|
|
if (null == fileName) { |
|
|
|
|
message.error("文件无名称") |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
let index = fileName.lastIndexOf('.'); |
|
|
|
|
let fileSuffix = null; |
|
|
|
|
if (index >= 0) { |
|
|
|
|
fileSuffix = fileName.substring(index + 1); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let reader = new FileReader() |
|
|
|
|
reader.onload = ((event) => { |
|
|
|
|
@ -676,15 +796,38 @@ class Panel extends React.Component {
@@ -676,15 +796,38 @@ class Panel extends React.Component {
|
|
|
|
|
messageType: this.state.messageType, |
|
|
|
|
content: this.state.value, |
|
|
|
|
contentType: 3, |
|
|
|
|
fileSuffix: fileSuffix, |
|
|
|
|
file: u8 |
|
|
|
|
} |
|
|
|
|
const messagePB = message.create(data) |
|
|
|
|
|
|
|
|
|
socket.send(message.encode(messagePB).finish()) |
|
|
|
|
|
|
|
|
|
if (["jpeg", "jpg", "png", "gif", "tif", "bmp", "dwg"].indexOf(fileSuffix) !== -1) { |
|
|
|
|
this.appendImgToPanel(file) |
|
|
|
|
} else { |
|
|
|
|
this.appendFile() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
}) |
|
|
|
|
reader.readAsArrayBuffer(files[0]) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
appendFile = () => { |
|
|
|
|
this.setState({ |
|
|
|
|
comments: [ |
|
|
|
|
...this.state.comments, |
|
|
|
|
{ |
|
|
|
|
author: localStorage.username, |
|
|
|
|
avatar: this.state.user.avatar, |
|
|
|
|
content: <p><FileOutlined style={{ fontSize: 38 }} /></p>, |
|
|
|
|
datetime: moment().fromNow(), |
|
|
|
|
}, |
|
|
|
|
], |
|
|
|
|
}, () => { |
|
|
|
|
this.scrollToBottom() |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* 开始录制音频 |
|
|
|
|
*/ |
|
|
|
|
@ -854,7 +997,8 @@ class Panel extends React.Component {
@@ -854,7 +997,8 @@ class Panel extends React.Component {
|
|
|
|
|
|
|
|
|
|
let preview = document.getElementById("preview1"); |
|
|
|
|
this.setState({ |
|
|
|
|
isRecord: true |
|
|
|
|
isRecord: true, |
|
|
|
|
rtcType: 'offer' |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
navigator.mediaDevices |
|
|
|
|
@ -863,65 +1007,35 @@ class Panel extends React.Component {
@@ -863,65 +1007,35 @@ class Panel extends React.Component {
|
|
|
|
|
video: true, |
|
|
|
|
}).then((stream) => { |
|
|
|
|
preview.srcObject = stream; |
|
|
|
|
|
|
|
|
|
//传输视频流 |
|
|
|
|
// this.recorder = new MediaRecorder(stream); |
|
|
|
|
// this.recorder.ondataavailable = (event) => { |
|
|
|
|
// let data = event.data; |
|
|
|
|
// let reader = new FileReader() |
|
|
|
|
// reader.readAsArrayBuffer(data) |
|
|
|
|
|
|
|
|
|
// reader.onload = ((e) => { |
|
|
|
|
// let fileData = e.target.result |
|
|
|
|
|
|
|
|
|
// // 上传文件必须将ArrayBuffer转换为Uint8Array |
|
|
|
|
// let data = { |
|
|
|
|
// fromUsername: localStorage.username, |
|
|
|
|
// from: this.state.fromUser, |
|
|
|
|
// to: this.state.toUser, |
|
|
|
|
// messageType: this.state.messageType, |
|
|
|
|
// content: this.state.value, |
|
|
|
|
// contentType: 6, |
|
|
|
|
// file: new Uint8Array(fileData) |
|
|
|
|
// } |
|
|
|
|
// let message = protobuf.lookup("protocol.Message") |
|
|
|
|
// const messagePB = message.create(data) |
|
|
|
|
// socket.send(message.encode(messagePB).finish()) |
|
|
|
|
// }) |
|
|
|
|
// }; |
|
|
|
|
// this.recorder.start(1000); |
|
|
|
|
stream.getTracks().forEach(track => { |
|
|
|
|
peer.addTrack(track, stream); |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var canvas = document.getElementById("canvas"); |
|
|
|
|
var ctx = canvas.getContext('2d'); |
|
|
|
|
this.interval = window.setInterval(() => { |
|
|
|
|
let width = this.state.video.width |
|
|
|
|
let height = this.state.video.height |
|
|
|
|
let currentScreen = { |
|
|
|
|
width: width, |
|
|
|
|
height: height |
|
|
|
|
} |
|
|
|
|
this.setState({ |
|
|
|
|
currentScreen: currentScreen |
|
|
|
|
}) |
|
|
|
|
ctx.drawImage(preview, 0, 0, width, height); |
|
|
|
|
// 一定注意:需要将该动作,放在这里面,即流获取成功后,再进行offer创建。不然不能获取到流,从而不能播放视频。 |
|
|
|
|
peer.createOffer() |
|
|
|
|
.then(offer => { |
|
|
|
|
peer.setLocalDescription(offer); |
|
|
|
|
let data = { |
|
|
|
|
fromUsername: localStorage.username, |
|
|
|
|
from: this.state.fromUser, |
|
|
|
|
to: this.state.toUser, |
|
|
|
|
messageType: this.state.messageType, |
|
|
|
|
content: canvas.toDataURL("image/jpeg", 0.5), |
|
|
|
|
contentType: 8, |
|
|
|
|
content: JSON.stringify(offer), |
|
|
|
|
type: "webrtc", |
|
|
|
|
} |
|
|
|
|
let message = protobuf.lookup("protocol.Message") |
|
|
|
|
const messagePB = message.create(data) |
|
|
|
|
socket.send(message.encode(messagePB).finish()) |
|
|
|
|
}, 60); |
|
|
|
|
}); |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
this.setState({ |
|
|
|
|
mediaPanelDrawerVisible: true |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* 停止视频电话 |
|
|
|
|
* 停止视频电话,屏幕共享 |
|
|
|
|
*/ |
|
|
|
|
stopVideoOnline = () => { |
|
|
|
|
this.setState({ |
|
|
|
|
@ -1024,7 +1138,6 @@ class Panel extends React.Component {
@@ -1024,7 +1138,6 @@ class Panel extends React.Component {
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
render() { |
|
|
|
|
|
|
|
|
|
const { comments, submitting, value, toUser } = this.state; |
|
|
|
|
|
|
|
|
|
return ( |
|
|
|
|
@ -1032,9 +1145,7 @@ class Panel extends React.Component {
@@ -1032,9 +1145,7 @@ class Panel extends React.Component {
|
|
|
|
|
<Row style={{ paddingTop: 20, paddingBottom: 40 }}> |
|
|
|
|
<Col span={2} style={{ borderRight: '1px solid #f0f0f0', textAlign: 'center' }}> |
|
|
|
|
<p style={{ marginTop: 15 }}> |
|
|
|
|
<Tooltip title={this.state.user.username}> |
|
|
|
|
<Avatar src={this.state.user.avatar} alt={this.state.user.username} /> |
|
|
|
|
</Tooltip> |
|
|
|
|
<UserInfo history={this.props.history} /> |
|
|
|
|
</p> |
|
|
|
|
<p > |
|
|
|
|
<Button icon={<UserOutlined />} size="large" type='link' disabled={this.state.menuType === 1} onClick={this.fetchUserList}> |
|
|
|
|
@ -1133,7 +1244,9 @@ class Panel extends React.Component {
@@ -1133,7 +1244,9 @@ class Panel extends React.Component {
|
|
|
|
|
shape="circle" |
|
|
|
|
onClick={this.startVideoOnline} |
|
|
|
|
style={{ marginRight: 10 }} |
|
|
|
|
icon={<PhoneOutlined />} disabled={toUser === ''} |
|
|
|
|
icon={<PhoneOutlined />} |
|
|
|
|
// disabled={toUser === ''} |
|
|
|
|
disabled |
|
|
|
|
/> |
|
|
|
|
</Tooltip> |
|
|
|
|
<Tooltip title="视频聊天"> |
|
|
|
|
@ -1189,9 +1302,14 @@ class Panel extends React.Component {
@@ -1189,9 +1302,14 @@ class Panel extends React.Component {
|
|
|
|
|
</Col> |
|
|
|
|
</Row> |
|
|
|
|
|
|
|
|
|
<Modal title="用户信息" visible={this.state.hasUser} onOk={this.handleOk} onCancel={this.handleCancel} okText="添加用户"> |
|
|
|
|
<Modal title="用户信息" visible={this.state.hasUser} onCancel={this.handleCancel} okText="添加用户" footer={null}> |
|
|
|
|
<p>用户名:{this.state.queryUser.username}</p> |
|
|
|
|
<p>昵称:{this.state.queryUser.nickname}</p> |
|
|
|
|
<Button type='primary' onClick={this.handleOk} disabled={this.state.queryUser.username == null || this.state.queryUser.username === ''}>添加用户</Button> |
|
|
|
|
<br /><br /><hr /><br /><br /> |
|
|
|
|
|
|
|
|
|
<p>群信息:{this.state.queryUser.groupName}</p> |
|
|
|
|
<Button type='primary' onClick={this.joinGroup} disabled={this.state.queryUser.groupUuid == null || this.state.queryUser.groupUuid === ''}>添加群</Button> |
|
|
|
|
</Modal> |
|
|
|
|
|
|
|
|
|
<Drawer title="成员列表" placement="right" onClose={this.drawerOnClose} visible={this.state.drawerVisible}> |
|
|
|
|
@ -1202,7 +1320,7 @@ class Panel extends React.Component {
@@ -1202,7 +1320,7 @@ class Panel extends React.Component {
|
|
|
|
|
<List.Item> |
|
|
|
|
<List.Item.Meta |
|
|
|
|
style={{ paddingLeft: 30 }} |
|
|
|
|
avatar={<Avatar src={item.avatar} />} |
|
|
|
|
avatar={<Avatar src={Params.HOST + "/file/" + item.avatar} />} |
|
|
|
|
title={item.username} |
|
|
|
|
description="" |
|
|
|
|
/> |
|
|
|
|
@ -1211,9 +1329,20 @@ class Panel extends React.Component {
@@ -1211,9 +1329,20 @@ class Panel extends React.Component {
|
|
|
|
|
/> |
|
|
|
|
</Drawer> |
|
|
|
|
|
|
|
|
|
<Drawer width='1000px' forceRender={true} title="媒体面板" placement="right" onClose={this.mediaPanelDrawerOnClose} visible={this.state.mediaPanelDrawerVisible}> |
|
|
|
|
<video id="preview1" width="540px" height="auto" autoPlay muted controls /> |
|
|
|
|
<img id="receiver" width={this.state.currentScreen.width} height={this.state.currentScreen.height} alt="" /> |
|
|
|
|
<Drawer width='820px' forceRender={true} title="媒体面板" placement="right" onClose={this.mediaPanelDrawerOnClose} visible={this.state.mediaPanelDrawerVisible}> |
|
|
|
|
<Tooltip title="结束视频语音"> |
|
|
|
|
<Button |
|
|
|
|
shape="circle" |
|
|
|
|
onClick={this.stopVideoOnline} |
|
|
|
|
style={{ marginRight: 10, float: 'right' }} |
|
|
|
|
icon={<PoweroffOutlined style={{ color: 'red' }} />} |
|
|
|
|
/> |
|
|
|
|
</Tooltip> |
|
|
|
|
<br /> |
|
|
|
|
<video id="preview1" width="700px" height="auto" autoPlay muted controls /> |
|
|
|
|
<video id="remoteVideo" 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} /> |
|
|
|
|
</Drawer> |
|
|
|
|
</> |
|
|
|
|
@ -1221,4 +1350,18 @@ class Panel extends React.Component {
@@ -1221,4 +1350,18 @@ class Panel extends React.Component {
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
export default Panel; |
|
|
|
|
function mapStateToProps(state) { |
|
|
|
|
return { |
|
|
|
|
user: state.userInfoReducer.user, |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function mapDispatchToProps(dispatch) { |
|
|
|
|
return { |
|
|
|
|
setUser: (data) => dispatch(actions.setUser(data)), |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
Panel = connect(mapStateToProps, mapDispatchToProps)(Panel) |
|
|
|
|
|
|
|
|
|
export default Panel |