bricks/bricks/rtc.js

369 lines
9.4 KiB
JavaScript
Raw Normal View History

2024-08-19 15:50:51 +08:00
var bricks = window.bricks || {}
bricks.VideoBox = class extends bricks.JsWidget {
create(){
this.dom_element = this._create('video');
this.set_attribute('autoplay', true);
this.set_attribute('muted', true);
}
get_stream(){
return this.stream;
}
set_stream(stream){
this.stream = stream
this.dom_element.srcObject = this.stream;
}
}
bricks.RTCClient = class extends bricks.VBox {
/*
{
"signaling_url":
"ice_url":
2024-08-21 17:43:12 +08:00
"auto_call_accept":false
2024-08-20 14:04:50 +08:00
"info":{
"id":
"name":
}
2024-08-19 15:50:51 +08:00
}
*/
constructor(opts){
2024-08-21 13:44:45 +08:00
opts.width = '100%';
2024-08-19 15:50:51 +08:00
super(opts);
2024-08-21 13:44:45 +08:00
this.build_head();
this.videobox = new bricks.ResponsableBox({css:'filler'});
2024-08-23 17:29:16 +08:00
this.videobox.set_style('position', 'relative');
2024-08-23 17:57:44 +08:00
this.localVideo = new bricks.VideoBox({});
this.remoteVideo = new bricks.VideoBox({});
2024-08-20 14:04:50 +08:00
this.videobox.add_widget(this.localVideo);
this.videobox.add_widget(this.remoteVideo);
this.add_widget(this.videobox);
2024-08-23 17:51:34 +08:00
this.videosize_switch();
2024-08-21 11:36:18 +08:00
this.pc;
2024-08-20 16:58:09 +08:00
this.onlineList= [];
2024-08-22 17:49:56 +08:00
this.init_websocket();
2024-08-22 17:46:05 +08:00
}
2024-08-23 17:29:16 +08:00
videosize_switch(){
2024-08-23 17:51:34 +08:00
if(! this.local_small){
2024-08-23 17:29:16 +08:00
this.local_small = true;
2024-08-23 17:51:34 +08:00
this.localVideo.set_css('smallvideo');
this.remoteVideo.set_css('bigvideo');
} else {
this.local_small = false;
this.localVideo.set_css('bigvideo');
this.remoteVideo.set_css('smallvideo');
2024-08-23 17:29:16 +08:00
}
}
2024-08-22 17:46:05 +08:00
init_websocket(){
2024-08-19 15:50:51 +08:00
this.socket = new WebSocket(this.signaling_url);
2024-08-20 17:41:55 +08:00
this.socket.onmessage = this.signaling_message_handle.bind(this);
2024-08-20 14:04:50 +08:00
this.socket.onopen = this.signaling_login.bind(this);
2024-08-19 15:50:51 +08:00
this.socket.onclose = null;
this.socket.onerror = null;
}
2024-08-21 13:44:45 +08:00
build_head(){
var box = new bricks.HBox({height:'50px'});
this.add_widget(box);
this.build_phone(box);
this.peer_w = new bricks.Text({text:''});
box.add_widget(this.peer_w);
}
build_phone(box){
2024-08-20 14:44:47 +08:00
var opts = {
2024-08-20 14:04:50 +08:00
states:[
{
state:'free',
url:bricks_resource('imgs/free_phone.png')
},{
state:'using',
2024-08-21 13:58:32 +08:00
url:bricks_resource('imgs/using_phone.png')
2024-08-20 14:04:50 +08:00
}
],
state:'free',
rate:2
}
this.phone = new bricks.StatedIcon(opts);
this.phone.bind('click', this.phone_action.bind(this));
2024-08-21 13:44:45 +08:00
box.add_widget(this.phone);
2024-08-20 14:04:50 +08:00
}
phone_action(){
if (this.phone.state == 'free'){
this.choose_peer();
} else {
this.call_close();
}
}
choose_peer(){
var items = [];
2024-08-20 17:36:30 +08:00
if (this.onlineList.length < 1){
var w = new bricks.Error({title:'Error',
timeout:4,
message:'no peer logined'
});
w.open();
2024-08-20 17:41:55 +08:00
return;
2024-08-20 17:36:30 +08:00
}
2024-08-20 14:04:50 +08:00
this.onlineList.forEach( p => {
var m = {
name:p.id,
label:p.name
2024-08-19 15:50:51 +08:00
}
2024-08-20 14:04:50 +08:00
items.push(m);
});
var menu = new bricks.Menu({
width:'300px',
2024-08-20 14:23:36 +08:00
height:'400px',
2024-08-20 14:04:50 +08:00
items:items
});
menu.set_css('popup');
2024-08-20 18:02:17 +08:00
bricks.Body.add_widget(menu);
2024-08-20 14:04:50 +08:00
menu.bind('command', this.call_peer.bind(this));
this.menu = menu;
}
2024-08-21 10:26:42 +08:00
async call_peer(event){
2024-08-20 18:41:39 +08:00
console.log('event params=', event.params);
2024-08-20 18:43:06 +08:00
bricks.Body.remove_widget(this.menu);
2024-08-20 14:04:50 +08:00
var peer_info = {
id:event.params.name,
name:event.params.label
}
this.call_request(peer_info);
}
signaling_login(){
var d = {
type:'login',
info:this.info
}
this.socket.send(JSON.stringify(d));
}
signaling_logout(){
var d= {
type:'logout',
info:this.info
2024-08-19 15:50:51 +08:00
}
this.socket.send(JSON.stringify(d));
}
// 获取本地媒体流
2024-08-20 14:04:50 +08:00
async getLocalStream() {
2024-08-19 15:50:51 +08:00
try {
const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
this.localVideo.set_stream(stream);
return stream;
} catch (error) {
console.error('获取本地媒体流失败:', error);
}
}
2024-08-20 14:04:50 +08:00
async call_request(peer_info){
2024-08-19 15:50:51 +08:00
var d = {
2024-08-20 14:04:50 +08:00
type:'callRequest',
to:peer_info
2024-08-19 15:50:51 +08:00
}
2024-08-20 14:04:50 +08:00
this.peer_info = peer_info;
2024-08-19 15:50:51 +08:00
this.role = 'requester';
this.socket.send(JSON.stringify(d));
}
async send_offer(){
2024-08-21 11:36:18 +08:00
var offer = await this.pc.createOffer();
2024-08-21 13:44:45 +08:00
await this.pc.setLocalDescription(offer);
2024-08-23 11:53:23 +08:00
console.log('########send_offer =', offer, this.pc.localDescription);
2024-08-19 15:50:51 +08:00
this.socket.send(JSON.stringify({
type:'offer',
2024-08-21 11:36:18 +08:00
offer:this.pc.localDescription,
2024-08-20 14:04:50 +08:00
to:this.peer_info
2024-08-19 15:50:51 +08:00
}));
}
2024-08-21 14:29:16 +08:00
send_candidate(peer_info, event){
2024-08-19 15:50:51 +08:00
if (event.candidate) {
2024-08-21 16:23:50 +08:00
var candidate = event.candidate;
2024-08-21 16:20:45 +08:00
console.log('send_candidate(), candidate=', candidate);
2024-08-19 15:50:51 +08:00
this.socket.send(JSON.stringify({
type: 'iceCandidate',
2024-08-21 14:29:16 +08:00
to:peer_info,
2024-08-21 16:20:45 +08:00
candidate: candidate
2024-08-19 15:50:51 +08:00
}));
}
}
2024-08-20 14:04:50 +08:00
async need_conform_call(peer_info){
2024-08-21 10:17:14 +08:00
var w = new bricks.Conform({title:'请确认',
2024-08-20 14:26:42 +08:00
message:'来自' + peer_info.name + '的连接请求'});
2024-08-20 14:04:50 +08:00
w.bind('conformed', this.call_accepted.bind(this, peer_info));
w.bind('cancelled', this.call_rejected.bind(this.peer_info));
2024-08-19 15:50:51 +08:00
w.open();
}
2024-08-20 14:32:15 +08:00
async call_accepted(peer_info){
2024-08-20 14:04:50 +08:00
if (this.peer_info){
2024-08-19 15:50:51 +08:00
this.call_close();
}
2024-08-20 14:04:50 +08:00
this.peer_info = peer_info
2024-08-19 15:50:51 +08:00
this.role = 'responser'
2024-08-21 13:49:41 +08:00
await this.createPeerConnection(this.peer_info);
2024-08-19 15:50:51 +08:00
this.socket.send(JSON.stringify({
type: 'callAccepted',
2024-08-20 14:04:50 +08:00
to: peer_info
2024-08-19 15:50:51 +08:00
}));
}
async call_close(active){
2024-08-23 11:53:23 +08:00
this.role = null;
2024-08-21 11:36:18 +08:00
this.pc.onicecandidate = null;
this.pc.ontrack = null;
this.pc.close();
2024-08-19 15:50:51 +08:00
if (! active){
this.socket.send(JSON.stringify({
type:'disconnect',
2024-08-20 14:04:50 +08:00
to:this.peer_info
2024-08-20 14:33:44 +08:00
}));
2024-08-19 15:50:51 +08:00
}
2024-08-20 14:04:50 +08:00
this.peer_info = null;
2024-08-21 14:58:19 +08:00
if (this.pc.getLocalStreams().length > 0) {
for (let stream of this.pc.getLocalStreams()) {
if (stream.getTracks) {
for (let track of stream.getTracks()) {
track.stop();
}
}
}
}
2024-08-19 15:50:51 +08:00
this.localVideo.get_stream().getTracks().forEach(track => track.stop());
2024-08-21 15:02:34 +08:00
this.phone.set_state('free');
this.peer_w.set_text('');
2024-08-19 15:50:51 +08:00
}
2024-08-20 14:04:50 +08:00
call_rejected(peer_info){
2024-08-19 15:50:51 +08:00
this.socket.send(JSON.stringify({
type: 'callRejected',
2024-08-20 14:04:50 +08:00
to: peer_info
2024-08-19 15:50:51 +08:00
}));
}
async signaling_message_handle(event){
2024-08-20 17:49:48 +08:00
var datapkg = JSON.parse(event.data);
2024-08-20 19:23:01 +08:00
var d = datapkg.data;
2024-08-21 15:50:30 +08:00
console.log('data received from server', 'd=', d);
2024-08-19 15:50:51 +08:00
switch (d.type){
2024-08-20 14:04:50 +08:00
case 'onlineList':
this.onlineList = d.onlineList;
2024-08-20 17:56:50 +08:00
console.log('onlineList branch exe');
2024-08-20 14:04:50 +08:00
break;
2024-08-19 15:50:51 +08:00
case 'callRequest':
2024-08-21 17:46:36 +08:00
if (this.auto_call_accept){
2024-08-21 17:43:12 +08:00
this.call_accepted(d.from);
return
}
2024-08-19 15:50:51 +08:00
this.need_conform_call(d.from);
2024-08-20 17:56:50 +08:00
console.log('callRequest branch exe');
2024-08-19 15:50:51 +08:00
break;
case 'offer':
var offer = new RTCSessionDescription(d.offer);
2024-08-21 13:44:45 +08:00
await this.pc.setRemoteDescription(offer);
2024-08-21 11:36:18 +08:00
var answer = await this.pc.createAnswer();
2024-08-21 13:44:45 +08:00
await this.pc.setLocalDescription(answer);
2024-08-23 11:53:23 +08:00
console.log('###### send_answer=', answer, 'desc=', this.pc.localDescription);
2024-08-19 15:50:51 +08:00
this.socket.send(JSON.stringify({
type:'answer',
2024-08-21 11:36:18 +08:00
answer:this.pc.localDescription,
2024-08-20 14:04:50 +08:00
to:this.peer_info
2024-08-20 14:35:19 +08:00
}));
2024-08-20 17:56:50 +08:00
console.log('offer branch exe');
2024-08-23 11:53:23 +08:00
if (this.role == 'responser'){
this.send_offer();
}
2024-08-20 17:56:50 +08:00
break;
2024-08-19 15:50:51 +08:00
case 'answer':
2024-08-23 11:53:23 +08:00
console.log('#### receive_answer: d.from=', d.from, 'peer_info=', this.peer_info);
2024-08-21 14:07:22 +08:00
if (d.from.id == this.peer_info.id){
2024-08-20 17:56:50 +08:00
var desc = new RTCSessionDescription(d.answer);
2024-08-21 13:44:45 +08:00
await this.pc.setRemoteDescription(desc);
console.log('answer branch exe');
2024-08-19 15:50:51 +08:00
}
break;
case 'iceCandidate':
2024-08-21 14:07:22 +08:00
if (d.from.id == this.peer_info.id){
2024-08-21 16:20:45 +08:00
var candidate = new RTCIceCandidate(d.candidate);
await this.pc.addIceCandidate(candidate);
2024-08-21 11:36:18 +08:00
console.log('iceCandidate branch exe');
2024-08-19 15:50:51 +08:00
}
break;
case 'callAccepted':
2024-08-20 14:04:50 +08:00
await this.createPeerConnection(this.peer_info);
2024-08-20 17:56:50 +08:00
console.log('callAccepted branch exe');
2024-08-19 15:50:51 +08:00
break;
case 'callRejected':
2024-08-20 14:04:50 +08:00
this.peer_info = null;
2024-08-20 17:56:50 +08:00
console.log('callRejected branch exe');
2024-08-19 15:50:51 +08:00
break;
case 'disconnect':
this.call_close(true);
2024-08-20 17:56:50 +08:00
console.log('disconnect branch exe');
break;
default:
console.log(d.type, 'no branch defined');
2024-08-19 15:50:51 +08:00
break;
}
2024-08-22 17:56:49 +08:00
if (this.pc){
console.log('signal_status=', this.pc.signalingState,
2024-08-22 17:54:28 +08:00
'iceconnectstate=', this.pc.iceConnectionState,
2024-08-22 17:46:05 +08:00
'connectstate=', this.pc.connectionState,
2024-08-22 17:55:25 +08:00
'cantrickleIceCandidate=', this.pc.canTrickleIceCandidates,
2024-08-22 17:54:28 +08:00
'peerConnectionState=', this.pc.peerConnectionState,
'icegatheringstate=', this.pc.iceGatheringState);
2024-08-22 17:56:49 +08:00
}
2024-08-19 15:50:51 +08:00
}
// 创建 PeerConnection
2024-08-20 14:04:50 +08:00
async createPeerConnection(to_info) {
this.phone.set_state('using');
2024-08-21 13:44:45 +08:00
this.peer_w.set_text(to_info.name);
2024-08-21 14:36:07 +08:00
await this.getLocalStream();
2024-08-20 14:04:50 +08:00
const configuration = {
2024-08-21 17:34:18 +08:00
iceServers: this.ice_servers,
2024-08-20 14:04:50 +08:00
};
2024-08-21 16:07:26 +08:00
console.log('configuration=', configuration);
2024-08-21 11:36:18 +08:00
this.pc = new RTCPeerConnection(configuration);
2024-08-20 14:04:50 +08:00
// 处理本地流添加到 PeerConnection
this.localVideo
.get_stream()
.getTracks()
.forEach(track => {
2024-08-21 11:36:18 +08:00
this.pc.addTrack(track, this.localVideo.stream);
2024-08-20 14:38:31 +08:00
});
2024-08-21 11:36:18 +08:00
if (! this.pc){
console.log('pc is null');
2024-08-21 10:39:54 +08:00
return;
}
2024-08-20 14:04:50 +08:00
// 处理 ICE 候选
2024-08-21 11:36:18 +08:00
this.pc.onicecandidate = this.send_candidate.bind(this, to_info);
2024-08-22 14:18:34 +08:00
this.pc.oniceconnectionstatechange = this.ice_statechange.bind(this);
this.pc.onicegatheringstatechange = this.ice_g_statechange.bind(this);
2024-08-22 17:46:05 +08:00
this.pc.onconnectionstatechange = this.connection_statechange.bind(this);
2024-08-19 15:50:51 +08:00
2024-08-20 14:04:50 +08:00
// 处理远程流添加
2024-08-21 11:36:18 +08:00
this.pc.ontrack = event => {
2024-08-20 14:04:50 +08:00
this.remoteVideo.set_stream(event.streams[0]);
};
2024-08-23 11:53:23 +08:00
if (this.role == 'requester'){
await this.send_offer();
}
2024-08-21 17:34:18 +08:00
2024-08-21 14:24:24 +08:00
console.log('createPeerConnection() finished');
2024-08-19 15:50:51 +08:00
}
2024-08-22 14:38:18 +08:00
async ice_statechange(event){
2024-08-22 17:46:05 +08:00
console.log(`oniceconnectionstatechange, pc.iceConnectionState is ${this.pc.iceConnectionState}.`);
2024-08-22 14:18:34 +08:00
}
2024-08-22 17:46:05 +08:00
async connection_statechange(event){
console.log(`onconnectionstatechange, pc.connectionState is ${this.pc.connectionState}.`);
2024-08-23 17:29:16 +08:00
if (ths.pc.connectionState == 'disconnected'){
var w = new bricks.Error({
title:'Error',
message:'lose connection',
timeout:4
});
w.open();
this.call_close();
}
2024-08-22 17:46:05 +08:00
}
2024-08-22 14:18:34 +08:00
ice_g_statechange(event){
2024-08-22 14:27:11 +08:00
console.log(`onicegatheringstatechange, pc.iceGatheringState is ${this.pc.iceGatheringState}.`);
2024-08-22 14:18:34 +08:00
}
2024-08-19 15:50:51 +08:00
}
2024-08-20 14:43:28 +08:00
bricks.Factory.register('RTCClient', bricks.RTCClient);