Files
taoyao/taoyao-server/src/main/resources/static/javascript/taoyao.js
2022-11-19 23:41:55 +08:00

673 lines
16 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/** 桃夭WebRTC终端核心功能 */
/** 兼容 */
const PeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection;
/** 默认音频配置 */
const defaultAudioConfig = {
// 音量0~1
volume: 0.5,
// 设备
// deviceId : '',
// 采样率8000|16000|32000|48000
sampleRate: 48000,
// 采样数16
sampleSize: 16,
// 延迟大小单位毫秒500毫秒以内较好
latency: 0.4,
// 声道数量1|2
channelCount : 1,
// 是否开启自动增益true|false
autoGainControl: false,
// 是否开启降噪功能true|false
noiseSuppression: true,
// 是否开启回音消除true|false
echoCancellation: true,
// 消除回音方式system|browser
echoCancellationType: 'system'
};
/** 默认视频配置 */
const defaultVideoConfig = {
// 宽度
width: 1280,
// 高度
height: 720,
// 设备
// deviceId: '',
// 帧率
frameRate: 24,
// 裁切
// resizeMode: '',
// 选摄像头user|left|right|environment
facingMode: 'environment'
}
/** 信令配置 */
const signalConfig = {
/** 当前终端SN */
sn: 'taoyao',
/** 当前版本 */
version: '1.0.0',
// 信令授权
username: 'taoyao',
password: 'taoyao'
};
/** 信令协议 */
const signalProtocol = {
/** 直播信令 */
live: {
},
/** 媒体信令 */
media: {
},
/** 终端信令 */
client: {
/** 注册 */
register: 2000,
/** 下发配置 */
config: 2004,
/** 心跳 */
heartbeat: 2005,
},
/** 会议信令 */
meeting: {
},
/** 平台信令 */
platform: {
},
/** 当前索引 */
index: 100000,
/** 最小索引 */
minIndex: 100000,
/** 最大索引 */
maxIndex: 999999,
/** 生成索引 */
buildId: function() {
if(this.index++ >= this.maxIndex) {
this.index = this.minIndex;
}
return Date.now() + '' + this.index;
},
/** 生成信令消息 */
buildProtocol: function(sn, pid, body, id) {
let message = {
header: {
v: signalConfig.version,
id: id || this.buildId(),
sn: sn,
pid: pid,
},
'body': body
};
return message;
}
};
/** 信令通道 */
const signalChannel = {
/** 桃夭 */
taoyao: null,
/** 通道 */
channel: null,
/** 地址 */
address: null,
/** 回调 */
callback: null,
/** 回调事件 */
callbackMapping: new Map(),
/** 心跳时间 */
heartbeatTime: 30 * 1000,
/** 心跳定时器 */
heartbeatTimer: null,
/** 重连定时器 */
reconnectTimer: null,
/** 防止重复重连 */
lockReconnect: false,
/** 当前重连时间 */
connectionTimeout: 5 * 1000,
/** 最小重连时间 */
minReconnectionDelay: 5 * 1000,
/** 最大重连时间 */
maxReconnectionDelay: 60 * 1000,
/** 重连失败时间增长倍数 */
reconnectionDelayGrowFactor: 2,
/** 心跳 */
heartbeat: function() {
let self = this;
if(self.heartbeatTimer) {
clearTimeout(self.heartbeatTimer);
}
self.heartbeatTimer = setTimeout(function() {
if (self.channel && self.channel.readyState == WebSocket.OPEN) {
self.push(signalProtocol.buildProtocol(
signalConfig.sn,
signalProtocol.client.heartbeat,
{
signal: 100,
battery: 100
}
));
self.heartbeat();
} else {
console.warn('发送心跳失败', self.channel);
}
}, self.heartbeatTime);
},
/** 连接 */
connect: function(address, callback, reconnection = true) {
let self = this;
self.address = address;
self.callback = callback;
return new Promise((resolve, reject) => {
console.debug('连接信令通道', address);
self.channel = new WebSocket(address);
self.channel.onopen = function(e) {
console.debug('打开信令通道', e);
// 注册终端
self.push(signalProtocol.buildProtocol(
signalConfig.sn,
signalProtocol.client.register,
{
ip: null,
mac: null,
signal: 100,
battery: 100,
username: signalConfig.username,
password: signalConfig.password
}
));
// 重置时间
self.connectionTimeout = self.minReconnectionDelay
// 开始心跳
self.heartbeat();
// 成功回调
resolve(e);
};
self.channel.onclose = function(e) {
console.error('信令通道关闭', self.channel, e);
if(reconnection) {
self.reconnect();
}
reject(e);
};
self.channel.onerror = function(e) {
console.error('信令通道异常', self.channel, e);
if(reconnection) {
self.reconnect();
}
reject(e);
};
self.channel.onmessage = function(e) {
console.debug('信令通道消息', e.data);
let done = false;
let data = JSON.parse(e.data);
// 注册回调
if(self.callback) {
done = self.callback(data);
}
// 默认回调
if(!done) {
self.defaultCallback(data);
}
// 请求回调
if(self.callbackMapping.has(data.header.id)) {
self.callbackMapping.get(data.header.id)();
self.callbackMapping.delete(data.header.id);
}
};
});
},
/** 重连 */
reconnect: function() {
let self = this;
if (self.lockReconnect) {
return;
}
self.lockReconnect = true;
// 关闭旧的通道
if(self.channel && self.channel.readyState == WebSocket.OPEN) {
self.channel.close();
self.channel = null;
}
if(self.reconnectTimer) {
clearTimeout(self.reconnectTimer);
}
// 打开定时重连
self.reconnectTimer = setTimeout(function() {
console.info('信令通道重连', self.address, new Date());
self.connect(self.address, self.callback, true);
self.lockReconnect = false;
}, self.connectionTimeout);
if (self.connectionTimeout >= self.maxReconnectionDelay) {
self.connectionTimeout = self.maxReconnectionDelay;
} else {
self.connectionTimeout = self.connectionTimeout * self.reconnectionDelayGrowFactor
}
},
/** 发送消息 */
push: function(data, callback) {
// 注册回调
if(data && callback) {
this.callbackMapping.set(data.header.id, callback);
}
// 发送消息
if(data && data.header) {
this.channel.send(JSON.stringify(data));
} else {
this.channel.send(data);
}
},
/** 关闭通道 */
close: function() {
clearTimeout(this.heartbeatTimer);
},
/** 默认回调 */
defaultCallback: function(data) {
console.debug('没有适配信令消息默认处理', data);
switch(data.header.pid) {
case signalProtocol.client.register:
console.debug('终端注册成功');
break;
case signalProtocol.client.config:
if(this.taoyao) {
this.taoyao
.configMedia(data.body.media)
.configWebrtc(data.body.webrtc);
}
break;
case signalProtocol.client.heartbeat:
console.debug('心跳');
break;
}
}
};
/** 终端 */
function TaoyaoClient(
sn
) {
/** 终端标识 */
this.sn = sn;
/** 视频对象 */
this.video = null;
/** 媒体状态 */
this.audioStatus = true;
this.videoStatus = true;
this.recordStatus = false;
/** 媒体信息 */
this.audioStreamId = null;
this.videoStreamId = null;
/** 播放视频 */
this.play = async function() {
await this.video.play();
return this;
};
/** 重新加载 */
this.load = function() {
this.video.load();
return this;
}
/** 暂停视频 */
this.pause = function() {
this.video.pause();
return this;
};
/** 关闭视频 */
this.close = function() {
this.video.close();
return this;
};
/** 设置视频对象 */
this.buildVideo = async function(videoId, stream) {
if(!this.video) {
this.video = document.getElementById(videoId);
}
await this.buildStream(stream);
return this;
};
/** 设置媒体流 */
this.buildStream = async function(stream) {
if(stream) {
if ('srcObject' in this.video) {
this.video.srcObject = stream;
} else {
this.video.src = URL.createObjectURL(stream);;
}
}
await this.play();
return this;
};
/** 设置音频流 */
this.buildAudioStream = function() {
};
/** 设置视频流 */
this.buildVideoStream = function() {
};
}
/** 桃夭 */
function Taoyao(
webSocket,
iceServer,
audioConfig,
videoConfig
) {
/** WebSocket地址 */
this.webSocket = webSocket;
/** IceServer地址 */
this.iceServer = iceServer;
/** 设备状态 */
this.audioEnabled = true;
this.videoEnabled = true;
/** 媒体配置 */
this.audioConfig = audioConfig || defaultAudioConfig;
this.videoConfig = videoConfig || defaultVideoConfig;
/** 发送信令 */
this.push = null;
/** 本地终端 */
this.localClient = null;
/** 远程终端 */
this.remoteClient = [];
/** 信令通道 */
this.signalChannel = null;
/** 检查设备 */
this.checkDevice = function() {
let self = this;
if(navigator.mediaDevices && 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;
});
}
return this;
};
/** 媒体配置 */
this.configMedia = function(audio = {}, video = {}) {
this.audioConfig = {...this.audioConfig, ...audio};
this.videoCofnig = {...this.videoCofnig, ...video};
console.debug('终端媒体配置', this.audioConfig, this.videoConfig);
return this;
};
/** WebRTC配置 */
this.configWebrtc = function(config = {}) {
this.webSocket = config.signalAddress;
this.iceServer = config.stun;
console.debug('WebRTC配置', this.webSocket, this.iceServer);
return this;
};
/** 打开信令通道 */
this.buildChannel = function(callback) {
signalChannel.taoyao = this;
this.signalChannel = signalChannel;
this.signalChannel.connect(this.webSocket, callback);
// 不能直接this.push = this.signalChannel.push这样导致this对象错误
this.push = function(data, callback) {
this.signalChannel.push(data, callback)
};
return this;
};
/** 打开本地媒体 */
this.buildLocalMedia = function() {
console.debug('打开终端媒体', this.audioConfig, this.videoConfig);
let self = this;
return new Promise((resolve, reject) => {
if(navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
navigator.mediaDevices.getUserMedia({
audio: self.audioConfig,
video: self.videoConfig
})
.then(resolve)
.catch(reject);
} else if(navigator.getUserMedia) {
navigator.getUserMedia({
audio: self.audioConfig,
video: self.videoConfig
}, resolve, reject);
} else {
reject('打开本地媒体失败');
}
});
};
/** 设置本地终端 */
this.buildLocalClient = async function(localVideoId, stream) {
this.localClient = new TaoyaoClient(signalConfig.sn);
await this.localClient.buildVideo(localVideoId, stream);
};
/** 关闭:关闭媒体 */
this.close = function() {
// TODO释放资源
};
/** 关机:关闭媒体、关闭信令 */
this.shutdown = function() {
this.close();
}
/** 媒体 */
/** 视频 */
};
/*
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();
}
*/