[+] mesh
This commit is contained in:
@@ -84,7 +84,7 @@ taoyao:
|
||||
- stun:stun4.l.google.com:19302
|
||||
- stun:stun.stunprotocol.org:3478
|
||||
# 信令服务配置
|
||||
host: localhost
|
||||
host: 192.168.1.100
|
||||
port: ${server.port:8888}
|
||||
schema: wss
|
||||
websocket: /websocket.signal
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/** 桃夭WebRTC终端核心功能 */
|
||||
/** 兼容 */
|
||||
const RTCIceCandidate = window.RTCIceCandidate || window.mozRTCIceCandidate || window.webkitRTCIceCandidate;
|
||||
const RTCPeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection;
|
||||
const RTCSessionDescription = window.RTCSessionDescription || window.mozRTCSessionDescription || window.webkitRTCSessionDescription;
|
||||
/** 默认音频配置 */
|
||||
@@ -70,6 +71,12 @@ const signalProtocol = {
|
||||
},
|
||||
/** 媒体信令 */
|
||||
media: {
|
||||
/** 发布 */
|
||||
publish: 5000,
|
||||
/** 订阅 */
|
||||
subscribe: 5002,
|
||||
/** 候选 */
|
||||
candidate: 5004
|
||||
},
|
||||
/** 终端信令 */
|
||||
client: {
|
||||
@@ -108,12 +115,12 @@ const signalProtocol = {
|
||||
return Date.now() + '' + this.index;
|
||||
},
|
||||
/** 生成信令消息 */
|
||||
buildProtocol: function(sn, pid, body, id) {
|
||||
buildProtocol: function(pid, body, id) {
|
||||
let message = {
|
||||
header: {
|
||||
v: signalConfig.version,
|
||||
id: id || this.buildId(),
|
||||
sn: sn,
|
||||
sn: signalConfig.sn,
|
||||
pid: pid,
|
||||
},
|
||||
'body': body
|
||||
@@ -156,9 +163,8 @@ const signalChannel = {
|
||||
clearTimeout(self.heartbeatTimer);
|
||||
}
|
||||
self.heartbeatTimer = setTimeout(function() {
|
||||
if (self.channel && self.channel.readyState == WebSocket.OPEN) {
|
||||
if (self.channel && self.channel.readyState === WebSocket.OPEN) {
|
||||
self.push(signalProtocol.buildProtocol(
|
||||
signalConfig.sn,
|
||||
signalProtocol.client.heartbeat,
|
||||
{
|
||||
signal: 100,
|
||||
@@ -183,7 +189,6 @@ const signalChannel = {
|
||||
console.debug('打开信令通道', e);
|
||||
// 注册终端
|
||||
self.push(signalProtocol.buildProtocol(
|
||||
signalConfig.sn,
|
||||
signalProtocol.client.register,
|
||||
{
|
||||
ip: null,
|
||||
@@ -252,7 +257,7 @@ const signalChannel = {
|
||||
}
|
||||
self.lockReconnect = true;
|
||||
// 关闭旧的通道
|
||||
if(self.channel && self.channel.readyState == WebSocket.OPEN) {
|
||||
if(self.channel && self.channel.readyState === WebSocket.OPEN) {
|
||||
self.channel.close();
|
||||
self.channel = null;
|
||||
}
|
||||
@@ -292,6 +297,15 @@ const signalChannel = {
|
||||
defaultCallback: function(data) {
|
||||
console.debug('没有适配信令消息默认处理', data);
|
||||
switch(data.header.pid) {
|
||||
case signalProtocol.media.publish:
|
||||
this.defaultMediaPublish(data);
|
||||
break;
|
||||
case signalProtocol.media.subscribe:
|
||||
this.defaultMediaSubscribe(data);
|
||||
break;
|
||||
case signalProtocol.media.candidate:
|
||||
this.defaultMediaCandidate(data);
|
||||
break;
|
||||
case signalProtocol.client.register:
|
||||
break;
|
||||
case signalProtocol.client.config:
|
||||
@@ -305,6 +319,7 @@ const signalChannel = {
|
||||
case signalProtocol.meeting.create:
|
||||
break;
|
||||
case signalProtocol.meeting.enter:
|
||||
this.defaultMeetingEnter(data);
|
||||
break;
|
||||
case signalProtocol.platform.error:
|
||||
console.error('信令发生错误', data);
|
||||
@@ -322,8 +337,43 @@ const signalChannel = {
|
||||
location.reload();
|
||||
},
|
||||
/** 默认媒体回调 */
|
||||
defaultMediaPublish: function(data) {
|
||||
this.taoyao.localMediaChannel.setRemoteDescription(new RTCSessionDescription(data.body));
|
||||
},
|
||||
defaultMediaSubscribe: function(data) {
|
||||
|
||||
let self = this;
|
||||
const from = data.body.from;
|
||||
let remote = this.taoyao.remoteClientFilter(from);
|
||||
if(!remote) {
|
||||
remote = new TaoyaoClient(from);
|
||||
this.taoyao.remoteClient.push(remote);
|
||||
}
|
||||
self.taoyao.remoteMediaChannel.setRemoteDescription(new RTCSessionDescription(data.body));
|
||||
self.taoyao.remoteMediaChannel.createAnswer().then(description => {
|
||||
console.debug('Local Create Answer', description);
|
||||
self.taoyao.remoteMediaChannel.setLocalDescription(description);
|
||||
self.push(signalProtocol.buildProtocol(
|
||||
signalProtocol.media.publish,
|
||||
{
|
||||
to: from,
|
||||
sdp: description.sdp,
|
||||
type: description.type
|
||||
}
|
||||
));
|
||||
});
|
||||
},
|
||||
defaultMediaCandidate: function(data) {
|
||||
if(
|
||||
!data.body.candidate ||
|
||||
!data.body.candidate.candidate ||
|
||||
!data.body.candidate.sdpMid ||
|
||||
!data.body.candidate.sdpMLineIndex
|
||||
) {
|
||||
console.warn('候选缺失要素', data);
|
||||
return;
|
||||
}
|
||||
let candidate = new RTCIceCandidate(data.body.candidate);
|
||||
this.taoyao.remoteMediaChannel.addIceCandidate(candidate);
|
||||
},
|
||||
/** 会议默认回调 */
|
||||
defaultMeetingEnter: function(data) {
|
||||
@@ -347,6 +397,9 @@ function TaoyaoClient(
|
||||
this.audioStatus = false;
|
||||
this.videoStatus = false;
|
||||
this.recordStatus = false;
|
||||
/** 重置 */
|
||||
this.reset = function() {
|
||||
}
|
||||
/** 播放视频 */
|
||||
this.play = async function() {
|
||||
await this.video.play();
|
||||
@@ -368,29 +421,47 @@ function TaoyaoClient(
|
||||
return this;
|
||||
};
|
||||
/** 设置视频对象 */
|
||||
this.buildVideo = async function(videoId, stream) {
|
||||
this.buildVideo = async function(videoId, stream, track) {
|
||||
if(!this.video) {
|
||||
this.video = document.getElementById(videoId);
|
||||
}
|
||||
await this.buildStream(stream);
|
||||
await this.buildStream(stream, track);
|
||||
return this;
|
||||
};
|
||||
/** 设置媒体流 */
|
||||
this.buildStream = async function(stream) {
|
||||
this.buildStream = async function(stream, track) {
|
||||
if(stream) {
|
||||
this.stream = stream;
|
||||
this.video.srcObject = stream;
|
||||
let audioTrack = stream.getAudioTracks();
|
||||
let videoTrack = stream.getVideoTracks();
|
||||
if(audioTrack && audioTrack.length) {
|
||||
this.audioTrack = audioTrack;
|
||||
this.audioStatus = true;
|
||||
if(track) {
|
||||
if(!this.stream) {
|
||||
this.stream = stream;
|
||||
this.video.srcObject = this.stream;
|
||||
}
|
||||
// TODO:删除旧的
|
||||
this.stream.addTrack(track);
|
||||
if(track.kind === 'audio') {
|
||||
this.audioTrack = track;
|
||||
this.audioStatus = true;
|
||||
} else if(track.kind === 'video') {
|
||||
this.videoTrack = track;
|
||||
this.videoStatus = true;
|
||||
}
|
||||
await this.video.load();
|
||||
} else {
|
||||
this.stream = stream;
|
||||
this.video.srcObject = stream;
|
||||
let audioTrack = stream.getAudioTracks();
|
||||
let videoTrack = stream.getVideoTracks();
|
||||
if(audioTrack && audioTrack.length) {
|
||||
this.audioTrack = audioTrack;
|
||||
this.audioStatus = true;
|
||||
}
|
||||
if(videoTrack && videoTrack.length) {
|
||||
this.videoTrack = videoTrack;
|
||||
this.videoStatus = true;
|
||||
}
|
||||
await this.video.load();
|
||||
}
|
||||
if(videoTrack && videoTrack.length) {
|
||||
this.videoTrack = videoTrack;
|
||||
this.videoStatus = true;
|
||||
}
|
||||
console.debug('设置媒体流', this.stream, this.audioTrack, this.videoTrack);
|
||||
console.debug('设置媒体流', this.video, this.stream, this.audioTrack, this.videoTrack);
|
||||
await this.play();
|
||||
}
|
||||
return this;
|
||||
@@ -430,51 +501,6 @@ function Taoyao(
|
||||
this.remoteMediaChannel = null;
|
||||
/** 信令通道 */
|
||||
this.signalChannel = null;
|
||||
/** 检查设备 */
|
||||
this.checkDevice = function() {
|
||||
let self = this;
|
||||
if(
|
||||
navigator.mediaDevices &&
|
||||
navigator.mediaDevices.getUserMedia &&
|
||||
navigator.mediaDevices.enumerateDevices
|
||||
) {
|
||||
navigator.mediaDevices.enumerateDevices()
|
||||
.then(list => {
|
||||
let audioDevice = false;
|
||||
let videoDevice = false;
|
||||
list.forEach(v => {
|
||||
console.debug('终端媒体设备', v.kind, v.label);
|
||||
switch(v.kind) {
|
||||
case 'audioinput':
|
||||
audioDevice = true;
|
||||
break;
|
||||
case 'videoinput':
|
||||
videoDevice = true;
|
||||
break;
|
||||
default:
|
||||
console.debug('没有适配设备', v.kind, v.label);
|
||||
break;
|
||||
}
|
||||
});
|
||||
if(!audioDevice) {
|
||||
console.warn('终端没有音频输入设备');
|
||||
self.audioEnabled = false;
|
||||
}
|
||||
if(!videoDevice) {
|
||||
console.warn('终端没有视频输入设备');
|
||||
self.videoEnabled = false;
|
||||
}
|
||||
})
|
||||
.catch(e => {
|
||||
console.error('检查终端设备异常', e);
|
||||
self.videoEnabled = false;
|
||||
self.videoEnabled = false;
|
||||
});
|
||||
} else {
|
||||
throw new Error('不支持的终端设备');
|
||||
}
|
||||
return this;
|
||||
};
|
||||
/** 媒体配置 */
|
||||
this.configMedia = function(audio = {}, video = {}) {
|
||||
this.audioConfig = {...this.audioConfig, ...audio};
|
||||
@@ -502,21 +528,60 @@ function Taoyao(
|
||||
};
|
||||
/** 打开本地媒体 */
|
||||
this.buildLocalMedia = function() {
|
||||
console.debug('打开终端媒体', this.audioConfig, this.videoConfig);
|
||||
let self = this;
|
||||
this.checkDevice();
|
||||
return new Promise((resolve, reject) => {
|
||||
navigator.mediaDevices.getUserMedia({
|
||||
audio: self.audioConfig,
|
||||
video: self.videoConfig
|
||||
})
|
||||
.then(resolve)
|
||||
.catch(reject);
|
||||
// 兼容旧版
|
||||
// navigator.getUserMedia({
|
||||
// audio: self.audioConfig,
|
||||
// video: self.videoConfig
|
||||
// }, resolve, reject);
|
||||
if(
|
||||
navigator.mediaDevices &&
|
||||
navigator.mediaDevices.getUserMedia &&
|
||||
navigator.mediaDevices.enumerateDevices
|
||||
) {
|
||||
navigator.mediaDevices.enumerateDevices()
|
||||
.then(list => {
|
||||
let audioEnabled = false;
|
||||
let videoEnabled = false;
|
||||
list.forEach(v => {
|
||||
console.debug('终端媒体设备', v, v.kind, v.label);
|
||||
switch(v.kind) {
|
||||
case 'audioinput':
|
||||
audioEnabled = true;
|
||||
break;
|
||||
case 'videoinput':
|
||||
videoEnabled = true;
|
||||
break;
|
||||
default:
|
||||
console.debug('没有适配设备', v.kind, v.label);
|
||||
break;
|
||||
}
|
||||
});
|
||||
if(!audioEnabled) {
|
||||
console.warn('终端没有音频输入设备');
|
||||
self.audioEnabled = false;
|
||||
}
|
||||
if(!videoEnabled) {
|
||||
console.warn('终端没有视频输入设备');
|
||||
self.videoEnabled = false;
|
||||
}
|
||||
console.debug('打开终端媒体', self.audioEnabled, self.videoEnabled, self.audioConfig, self.videoConfig);
|
||||
navigator.mediaDevices.getUserMedia({
|
||||
audio: self.audioEnabled ? self.audioConfig : false,
|
||||
video: self.videoEnabled ? self.videoConfig : false
|
||||
})
|
||||
.then(resolve)
|
||||
.catch(reject);
|
||||
// 兼容旧版
|
||||
// navigator.getUserMedia({
|
||||
// audio: self.audioConfig,
|
||||
// video: self.videoConfig
|
||||
// }, resolve, reject);
|
||||
})
|
||||
.catch(e => {
|
||||
console.error('检查终端设备异常', e);
|
||||
self.videoEnabled = false;
|
||||
self.videoEnabled = false;
|
||||
});
|
||||
} else {
|
||||
throw new Error('不支持的终端设备');
|
||||
}
|
||||
});
|
||||
};
|
||||
/** 远程终端过滤 */
|
||||
@@ -537,6 +602,7 @@ function Taoyao(
|
||||
};
|
||||
/** 打开媒体通道 */
|
||||
this.buildMediaChannel = async function(localVideoId, stream) {
|
||||
let self = this;
|
||||
// 本地视频
|
||||
this.localClient = new TaoyaoClient(signalConfig.sn);
|
||||
await this.localClient.buildVideo(localVideoId, stream);
|
||||
@@ -548,61 +614,92 @@ function Taoyao(
|
||||
if(this.localClient.videoTrack) {
|
||||
this.localClient.videoTrack.forEach(v => this.localMediaChannel.addTrack(v, this.localClient.stream));
|
||||
}
|
||||
this.localMediaChannel.ontrack = this.localMediaChannelTrack;
|
||||
this.localMediaChannel.ondatachannel = this.localMediaChannelDataChannel;
|
||||
this.localMediaChannel.onicecandidate = this.localMediaChannelIceCandidate;
|
||||
this.localMediaChannel.ontrack = function(e) {
|
||||
console.debug('Local Media Track', e);
|
||||
};
|
||||
this.localMediaChannel.ondatachannel = function(channel) {
|
||||
channel.onopen = function() {
|
||||
console.debug('Local DataChannel Open');
|
||||
}
|
||||
channel.onmessage = function(data) {
|
||||
console.debug('Local DataChannel Message', data);
|
||||
}
|
||||
channel.onclose = function() {
|
||||
console.debug('Local DataChannel Close');
|
||||
}
|
||||
channel.onerror = function(e) {
|
||||
console.debug('Local DataChannel Error', e);
|
||||
}
|
||||
};
|
||||
this.localMediaChannel.onicecandidate = function(e) {
|
||||
let sns = self.remoteClient.filter(v => v.video === null).map(v => v.sn);
|
||||
console.debug('Local ICE Candidate', sns, e);
|
||||
self.push(signalProtocol.buildProtocol(
|
||||
signalProtocol.media.candidate,
|
||||
{
|
||||
sns: sns,
|
||||
candidate: e.candidate
|
||||
}
|
||||
));
|
||||
};
|
||||
// 远程通道
|
||||
this.remoteMediaChannel = new RTCPeerConnection(defaultRPCConfig);
|
||||
this.remoteMediaChannel.ontrack = this.remoteMediaChannelTrack;
|
||||
this.remoteMediaChannel.ondatachannel = this.remoteMediaChannelDataChannel;
|
||||
this.remoteMediaChannel.onicecandidate = this.remoteMediaChannelIceCandidate;
|
||||
this.remoteMediaChannel.ontrack = function(e) {
|
||||
console.debug('Remote Media Track', e);
|
||||
// TODO:匹配
|
||||
let remote = self.remoteClient[0];
|
||||
remote.buildVideo(remote.sn, e.streams[0], e.track);
|
||||
};
|
||||
this.remoteMediaChannel.ondatachannel = function(channel) {
|
||||
channel.onopen = function() {
|
||||
console.debug('Remote DataChannel Open');
|
||||
}
|
||||
channel.onmessage = function(data) {
|
||||
console.debug('Remote DataChannel Message', data);
|
||||
}
|
||||
channel.onclose = function() {
|
||||
console.debug('Remote DataChannel Close');
|
||||
}
|
||||
channel.onerror = function(e) {
|
||||
console.debug('Remote DataChannel Error', e);
|
||||
}
|
||||
};
|
||||
this.remoteMediaChannel.onicecandidate = function(e) {
|
||||
let sns = self.remoteClient.filter(v => v.video === null).map(v => v.sn);
|
||||
console.debug('Remote ICE Candidate', sns, e);
|
||||
self.push(signalProtocol.buildProtocol(
|
||||
signalProtocol.media.candidate,
|
||||
{
|
||||
sns: sns,
|
||||
candidate: e.candidate
|
||||
}
|
||||
));
|
||||
};
|
||||
console.debug('打开媒体通道', this.localMediaChannel, this.remoteMediaChannel);
|
||||
return this;
|
||||
};
|
||||
/** 本地 */
|
||||
this.localMediaChannelTrack = function() {
|
||||
};
|
||||
this.localMediaChannelDataChannel = function(channel) {
|
||||
channel.onopen = function() {
|
||||
console.debug('DataChannel Open');
|
||||
}
|
||||
channel.onmessage = function(data) {
|
||||
console.debug('DataChannel Message', data);
|
||||
}
|
||||
channel.onclose = function() {
|
||||
console.debug('DataChannel Close');
|
||||
}
|
||||
channel.onerror = function(e) {
|
||||
console.debug('DataChannel Error', e);
|
||||
}
|
||||
};
|
||||
this.localMediaChannelIceCandidate = function() {
|
||||
};
|
||||
/** 远程 */
|
||||
this.localMediaChannelTrack = function() {
|
||||
};
|
||||
this.localMediaChannelDataChannel = function(channel) {
|
||||
channel.onopen = function() {
|
||||
console.debug('DataChannel Open');
|
||||
}
|
||||
channel.onmessage = function(data) {
|
||||
console.debug('DataChannel Message', data);
|
||||
}
|
||||
channel.onclose = function() {
|
||||
console.debug('DataChannel Close');
|
||||
}
|
||||
channel.onerror = function(e) {
|
||||
console.debug('DataChannel Error', e);
|
||||
}
|
||||
};
|
||||
this.localMediaChannelIceCandidate = function() {
|
||||
};
|
||||
/** 媒体信令 */
|
||||
this.mediaSubscribe = function(sn, callback) {
|
||||
let self = this;
|
||||
let remote = self.remoteClientFilter(sn);
|
||||
if(remote) {
|
||||
remote.reset();
|
||||
} else {
|
||||
remote = new TaoyaoClient(sn);
|
||||
this.remoteClient.push(remote);
|
||||
}
|
||||
if(self.webrtc.model === 'MESH') {
|
||||
self.localMediaChannel.createOffer().then(description => {
|
||||
console.debug('Local Create Offer', description);
|
||||
self.localMediaChannel.setLocalDescription(description);
|
||||
self.push(signalProtocol.buildProtocol(
|
||||
signalProtocol.media.subscribe,
|
||||
{
|
||||
to: sn,
|
||||
sdp: description.sdp,
|
||||
type: description.type
|
||||
}
|
||||
), callback);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -610,14 +707,12 @@ function Taoyao(
|
||||
this.meetingCreate = function(callback) {
|
||||
let self = this;
|
||||
self.push(signalProtocol.buildProtocol(
|
||||
signalConfig.sn,
|
||||
signalProtocol.meeting.create,
|
||||
signalProtocol.meeting.create
|
||||
), callback);
|
||||
}
|
||||
this.meetingEnter = function(id, callback) {
|
||||
let self = this;
|
||||
self.push(signalProtocol.buildProtocol(
|
||||
signalConfig.sn,
|
||||
signalProtocol.meeting.enter,
|
||||
{
|
||||
id: id
|
||||
@@ -625,206 +720,3 @@ function Taoyao(
|
||||
), callback);
|
||||
};
|
||||
};
|
||||
/*
|
||||
var peer;
|
||||
var socket; // WebSocket
|
||||
var supportStream = false; // 是否支持使用数据流
|
||||
var localVideo; // 本地视频
|
||||
var localVideoStream; // 本地视频流
|
||||
var remoteVideo; // 远程视频
|
||||
var remoteVideoStream; // 远程视频流
|
||||
var initiator = false; // 是否已经有人在等待
|
||||
var started = false; // 是否开始
|
||||
var channelReady = false; // 是否打开WebSocket通道
|
||||
// 初始
|
||||
function initialize() {
|
||||
console.log("初始聊天");
|
||||
// 获取视频
|
||||
localVideo = document.getElementById("localVideo");
|
||||
remoteVideo = document.getElementById("remoteVideo");
|
||||
supportStream = "srcObject" in localVideo;
|
||||
// 显示状态
|
||||
if (initiator) {
|
||||
setNotice("开始连接");
|
||||
} else {
|
||||
setNotice("加入聊天:https://www.acgist.com/demo/video/?oid=FFB85D84AC56DAF88B7E22AFFA7533D3");
|
||||
}
|
||||
// 打开WebSocket
|
||||
openChannel();
|
||||
// 创建终端媒体
|
||||
buildUserMedia();
|
||||
}
|
||||
function openChannel() {
|
||||
console.log("打开WebSocket");
|
||||
socket = new WebSocket("wss://www.acgist.com/video.ws/FFB85D84AC56DAF88B7E22AFFA7533D3");
|
||||
socket.onopen = channelOpened;
|
||||
socket.onmessage = channelMessage;
|
||||
socket.onclose = channelClosed;
|
||||
socket.onerror = channelError;
|
||||
}
|
||||
function channelOpened() {
|
||||
console.log("打开WebSocket成功");
|
||||
channelReady = true;
|
||||
}
|
||||
function channelMessage(message) {
|
||||
console.log("收到消息:" + message.data);
|
||||
var msg = JSON.parse(message.data);
|
||||
if (msg.type === "offer") { // 处理Offer消息
|
||||
if (!initiator && !started) {
|
||||
connectPeer();
|
||||
}
|
||||
peer.setRemoteDescription(new RTCSessionDescription(msg));
|
||||
peer.createAnswer().then(buildLocalDescription);
|
||||
} else if (msg.type === "answer" && started) { // 处理Answer消息
|
||||
peer.setRemoteDescription(new RTCSessionDescription(msg));
|
||||
} else if (msg.type === "candidate" && started) {
|
||||
var candidate = new RTCIceCandidate({
|
||||
sdpMLineIndex : msg.label,
|
||||
candidate : msg.candidate
|
||||
});
|
||||
peer.addIceCandidate(candidate);
|
||||
} else if (msg.type === "bye" && started) {
|
||||
onRemoteClose();
|
||||
setNotice("对方已断开!");
|
||||
} else if(msg.type === "nowaiting") {
|
||||
onRemoteClose();
|
||||
setNotice("对方已离开!");
|
||||
}
|
||||
}
|
||||
function channelClosed() {
|
||||
console.log("关闭WebSocket");
|
||||
openChannel(); // 重新打开WebSocket
|
||||
}
|
||||
function channelError(event) {
|
||||
console.log("WebSocket异常:" + event);
|
||||
}
|
||||
function buildUserMedia() {
|
||||
console.log("获取终端媒体");
|
||||
if(navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
|
||||
navigator.mediaDevices.getUserMedia({
|
||||
"audio" : true,
|
||||
"video" : true
|
||||
})
|
||||
.then(onUserMediaSuccess)
|
||||
.catch(onUserMediaError);
|
||||
} else {
|
||||
navigator.getUserMedia({
|
||||
"audio" : true,
|
||||
"video" : true
|
||||
}, onUserMediaSuccess, onUserMediaError);
|
||||
}
|
||||
}
|
||||
function onUserMediaSuccess(stream) {
|
||||
localVideoStream = stream;
|
||||
if (supportStream) {
|
||||
localVideo.srcObject = localVideoStream;
|
||||
} else {
|
||||
localVideo.src = URL.createObjectURL(localVideoStream);
|
||||
}
|
||||
if (initiator) {
|
||||
connectPeer();
|
||||
}
|
||||
}
|
||||
function onUserMediaError(error) {
|
||||
alert("请打开摄像头!");
|
||||
}
|
||||
function connectPeer() {
|
||||
if (!started && localVideoStream && channelReady) {
|
||||
console.log("开始连接Peer");
|
||||
started = true;
|
||||
buildPeerConnection();
|
||||
peer.addStream(localVideoStream);
|
||||
if (initiator) {
|
||||
peer.createOffer().then(buildLocalDescription);
|
||||
}
|
||||
}
|
||||
}
|
||||
function buildPeerConnection() {
|
||||
//var server = {"iceServers" : [{"url" : "stun:stun.l.google.com:19302"}]};
|
||||
var server = {"iceServers" : [{"url" : "stun:stun1.l.google.com:19302"}]};
|
||||
peer = new PeerConnection(server);
|
||||
peer.onicecandidate = peerIceCandidate;
|
||||
peer.onconnecting = peerConnecting;
|
||||
peer.onopen = peerOpened;
|
||||
peer.onaddstream = peerAddStream;
|
||||
peer.onremovestream = peerRemoveStream;
|
||||
}
|
||||
function peerIceCandidate(event) {
|
||||
if (event.candidate) {
|
||||
sendMessage({
|
||||
type : "candidate",
|
||||
id : event.candidate.sdpMid,
|
||||
label : event.candidate.sdpMLineIndex,
|
||||
candidate : event.candidate.candidate
|
||||
});
|
||||
} else {
|
||||
console.log("不支持的candidate");
|
||||
}
|
||||
}
|
||||
function peerConnecting(message) {
|
||||
console.log("Peer连接");
|
||||
}
|
||||
function peerOpened(message) {
|
||||
console.log("Peer打开");
|
||||
}
|
||||
function peerAddStream(event) {
|
||||
console.log("远程视频添加");
|
||||
remoteVideoStream = event.stream;
|
||||
if(supportStream) {
|
||||
remoteVideo.srcObject = remoteVideoStream;
|
||||
} else {
|
||||
remoteVideo.src = URL.createObjectURL(remoteVideoStream);
|
||||
}
|
||||
setNotice("连接成功");
|
||||
waitForRemoteVideo();
|
||||
}
|
||||
function peerRemoveStream(event) {
|
||||
console.log("远程视频移除");
|
||||
}
|
||||
function buildLocalDescription(description) {
|
||||
peer.setLocalDescription(description);
|
||||
sendMessage(description);
|
||||
}
|
||||
function sendMessage(message) {
|
||||
var msgJson = JSON.stringify(message);
|
||||
socket.send(msgJson);
|
||||
console.log("发送信息:" + msgJson);
|
||||
}
|
||||
function setNotice(msg) {
|
||||
document.getElementById("footer").innerHTML = msg;
|
||||
}
|
||||
function onRemoteClose() {
|
||||
started = false;
|
||||
initiator = false;
|
||||
if(supportStream) {
|
||||
remoteVideo.srcObject = null;
|
||||
} else {
|
||||
remoteVideo.src = null;
|
||||
}
|
||||
peer.close();
|
||||
}
|
||||
function waitForRemoteVideo() {
|
||||
if (remoteVideo.currentTime > 0) { // 判断远程视频长度
|
||||
setNotice("连接成功!");
|
||||
} else {
|
||||
setTimeout(waitForRemoteVideo, 100);
|
||||
}
|
||||
}
|
||||
window.onbeforeunload = function() {
|
||||
sendMessage({type : "bye"});
|
||||
if(peer) {
|
||||
peer.close();
|
||||
}
|
||||
socket.close();
|
||||
}
|
||||
if(!WebSocket) {
|
||||
alert("你的浏览器不支持WebSocket!");
|
||||
} else if(!PeerConnection) {
|
||||
alert("你的浏览器不支持RTCPeerConnection!");
|
||||
} else {
|
||||
setTimeout(initialize, 100); // 加载完成调用初始化方法
|
||||
}
|
||||
window.onbeforeunload = function() {
|
||||
socket.close();
|
||||
}
|
||||
*/
|
||||
@@ -28,14 +28,14 @@
|
||||
<a class="record icon-radio-checked" title="录制视频" @click="recordSelf"></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="meeting" v-for="client in this.clients" :key="client.sn">
|
||||
<div class="meeting" v-for="client in this.remoteClient" :key="client.sn">
|
||||
<div class="video">
|
||||
<video v-bind:id="client.sn"></video>
|
||||
</div>
|
||||
<div class="handler">
|
||||
<a class="audio" title="音频状态" v-bind:class="client.audio?'icon-volume-medium':'icon-volume-mute2'" @click="audio(client.sn)"></a>
|
||||
<a class="video" title="视频状态" v-bind:class="client.video?'icon-play2':'icon-stop'" @click="video(client.sn)"></a>
|
||||
<a class="record icon-radio-checked" title="录制视频" v-bind:class="client.record?'active':''" @click="record(client.sn)"></a>
|
||||
<a class="audio" title="音频状态" v-bind:class="client.audioStatus?'icon-volume-medium':'icon-volume-mute2'" @click="audio(client.sn)"></a>
|
||||
<a class="video" title="视频状态" v-bind:class="client.videoStatus?'icon-play2':'icon-stop'" @click="video(client.sn)"></a>
|
||||
<a class="record icon-radio-checked" title="录制视频" v-bind:class="client.recordStatus?'active':''" @click="record(client.sn)"></a>
|
||||
<a class="expel icon-cancel-circle" title="踢出会议" @click="expel(client.sn)"></a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -45,30 +45,36 @@
|
||||
const vue = new Vue({
|
||||
el: "#app",
|
||||
data: {
|
||||
clients: [
|
||||
{sn:"1", audio: true, video: true, record: false},
|
||||
{sn:"2", audio: true, video: true, record: false},
|
||||
{sn:"3", audio: true, video: true, record: false}
|
||||
],
|
||||
taoyao: null,
|
||||
remoteClient: [],
|
||||
meetingId: null
|
||||
},
|
||||
mounted() {
|
||||
if(signalConfig.sn) {
|
||||
// TODO:修改sn
|
||||
// 随机终端标识
|
||||
if(signalConfig.sn === 'taoyao') {
|
||||
let sn = localStorage.getItem('taoyao.sn');
|
||||
if(sn) {
|
||||
signalConfig.sn = sn;
|
||||
}
|
||||
console.debug('终端标识', sn);
|
||||
}
|
||||
let self = this;
|
||||
this.taoyao = new Taoyao("wss://localhost:8888/websocket.signal");
|
||||
this.taoyao = new Taoyao("wss://192.168.1.100:8888/websocket.signal");
|
||||
this.remoteClient = this.taoyao.remoteClient;
|
||||
// 打开信令通道
|
||||
this.taoyao
|
||||
.buildChannel(self.callback)
|
||||
.then(e => console.debug('连接成功'));
|
||||
.then(e => console.debug('信令通道连接成功'));
|
||||
// 打开媒体通道
|
||||
this.taoyao.buildLocalMedia()
|
||||
.then(stream => {
|
||||
self.taoyao.buildMediaChannel('local', stream);
|
||||
})
|
||||
.catch(e => console.error('打开终端媒体失败', e));
|
||||
.catch(e => {
|
||||
console.error('打开终端媒体失败', e);
|
||||
// 方便相同电脑测试
|
||||
self.taoyao.buildMediaChannel('local', null);
|
||||
});
|
||||
},
|
||||
beforeDestroy() {
|
||||
},
|
||||
@@ -86,7 +92,6 @@
|
||||
create: function(event) {
|
||||
let self = this;
|
||||
this.taoyao.meetingCreate(data => {
|
||||
console.log(data)
|
||||
self.taoyao.meetingEnter(data.body.id);
|
||||
return true;
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user