bricks/bricks/rtc.js

285 lines
6.8 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-20 14:04:50 +08:00
"info":{
"id":
"name":
}
2024-08-19 15:50:51 +08:00
}
*/
constructor(opts){
super(opts);
2024-08-20 14:04:50 +08:00
this.build_phone();
this.videobox = new bricks.ResponsableBox({});
2024-08-19 15:50:51 +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-19 15:50:51 +08:00
this.peerConnection;
2024-08-20 16:58:09 +08:00
this.onlineList= [];
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-20 14:04:50 +08:00
build_phone(){
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',
url:bricks_resource('img/using_phone.png')
}
],
state:'free',
rate:2
}
this.phone = new bricks.StatedIcon(opts);
this.add_widget(this.phone);
this.phone.bind('click', this.phone_action.bind(this));
}
phone_action(){
if (this.phone.state == 'free'){
this.choose_peer();
} else {
this.call_close();
this.phone.set_state('free');
}
}
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-20 14:24:58 +08:00
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(){
var offer = await this.peerConnection.createOffer();
this.peerConnection.setLocalDescription(offer);
this.socket.send(JSON.stringify({
type:'offer',
offer:this.peerConnection.localDescription,
2024-08-20 14:04:50 +08:00
to:this.peer_info
2024-08-19 15:50:51 +08:00
}));
}
send_candidate(event){
if (event.candidate) {
this.socket.send(JSON.stringify({
type: 'iceCandidate',
2024-08-20 14:04:50 +08:00
to:this.peer_info,
2024-08-19 15:50:51 +08:00
candidate: event.candidate
}));
}
}
2024-08-20 14:04:50 +08:00
async need_conform_call(peer_info){
2024-08-19 15:50:51 +08:00
var w = new 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-20 14:31:11 +08:00
await this.createPeerConnection();
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){
this.peerConnection.onicecandidate = null;
this.peerConnection.ontrack = null;
this.peerConnection.close();
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-19 15:50:51 +08:00
this.localVideo.get_stream().getTracks().forEach(track => track.stop());
}
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-20 17:59:41 +08:00
console.log('data received from server', datapkg, 'd=', d, event);
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':
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);
var answer = await this.peerConnection.setRemoteDescription(offer);
this.peerConnection.setLocalDescription(answer);
this.socket.send(JSON.stringify({
type:'answer',
answer:answer,
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');
break;
2024-08-19 15:50:51 +08:00
case 'answer':
2024-08-20 14:04:50 +08:00
if (d.from == self.peer_info){
2024-08-20 17:56:50 +08:00
var desc = new RTCSessionDescription(d.answer);
this.peerConnection.setRemoteDescription(desc);
2024-08-19 15:50:51 +08:00
}
2024-08-20 17:56:50 +08:00
console.log('answer branch exe');
2024-08-19 15:50:51 +08:00
break;
case 'iceCandidate':
2024-08-20 14:04:50 +08:00
if (d.from == self.peer_info){
2024-08-20 17:56:50 +08:00
var candidate = new RTCIceCandidate(d.candidate);
this.peerConnection.addIceCandidate(candidate);
2024-08-19 15:50:51 +08:00
}
2024-08-20 17:56:50 +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;
}
}
// 创建 PeerConnection
2024-08-20 14:04:50 +08:00
async createPeerConnection(to_info) {
this.phone.set_state('using');
2024-08-19 15:50:51 +08:00
if (this.role == 'requester'){
await this.send_offer();
}
2024-08-20 14:04:50 +08:00
const configuration = {
2024-08-19 15:50:51 +08:00
iceServers: [
{ urls: this.ice_url },
{ urls: 'stun:stunserver2024.stunprotocol.org:3478' }
]
2024-08-20 14:04:50 +08:00
};
this.peerConnection = new RTCPeerConnection(configuration);
// 处理本地流添加到 PeerConnection
this.localVideo
.get_stream()
.getTracks()
.forEach(track => {
2024-08-20 14:38:31 +08:00
this.peerConnection.addTrack(track, this.localVideo.stream);
});
2024-08-19 15:50:51 +08:00
2024-08-20 14:04:50 +08:00
// 处理 ICE 候选
this.peerConnection.onicecandidate = this.send_candidate.bind(this, to_info);
2024-08-19 15:50:51 +08:00
2024-08-20 14:04:50 +08:00
// 处理远程流添加
this.peerConnection.ontrack = event => {
this.remoteVideo.set_stream(event.streams[0]);
};
2024-08-19 15:50:51 +08:00
}
}
2024-08-20 14:43:28 +08:00
bricks.Factory.register('RTCClient', bricks.RTCClient);