This commit is contained in:
yumoqing 2024-08-20 14:04:50 +08:00
parent 9aed6c656b
commit db03059228
7 changed files with 154 additions and 52 deletions

BIN
bricks/.DS_Store vendored

Binary file not shown.

View File

@ -58,12 +58,12 @@ bricks.Image = class extends bricks.JsWidget {
bricks.Icon = class extends bricks.Image { bricks.Icon = class extends bricks.Image {
constructor(opts){ constructor(opts){
var rate = opts.rate || 1;
var siz = bricks.app.charsize * rate + 'px';
opts.width = siz;
opts.height = siz;
super(opts); super(opts);
this.rate = rate; }
options_parse(){
this.rate = this.rate || 1;
var siz = bricks.app.charsize * rate + 'px';
this.set_url(this.url)
this.cwidth = this.opts.cwidth || 1; this.cwidth = this.opts.cwidth || 1;
this.cheight = this.opts.cheight || 1; this.cheight = this.opts.cheight || 1;
this.dynsize = this.opts.dynsize || true; this.dynsize = this.opts.dynsize || true;
@ -71,6 +71,34 @@ bricks.Icon = class extends bricks.Image {
} }
} }
bricks.StatedIcon = class extends bricks.Icon {
/*
states:[{state:aaa,url:} ,,]
state:aaa,
*/
constructor(opts){
super(opts);
}
options_parse(){
if (! this.states){
return;
}
if (! this.state){
this.state = this.states[0].state;
}
this.set_state(this.state);
}
set_state(state){
this.state = state;
this.states.forEach(s => {
if(s.state == this.state){
this.url = s.url
super().options_parse();
}
});
}
}
bricks.BlankIcon = class extends bricks.JsWidget { bricks.BlankIcon = class extends bricks.JsWidget {
constructor(opts){ constructor(opts){
super(opts); super(opts);
@ -83,5 +111,6 @@ bricks.BlankIcon = class extends bricks.JsWidget {
} }
bricks.Factory.register('Image', bricks.Image); bricks.Factory.register('Image', bricks.Image);
bricks.Factory.register('StatedIcon', bricks.StatedIcon);
bricks.Factory.register('Icon', bricks.Icon); bricks.Factory.register('Icon', bricks.Icon);
bricks.Factory.register('BlankIcon', bricks.BlankIcon); bricks.Factory.register('BlankIcon', bricks.BlankIcon);

BIN
bricks/imgs/free_phone.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
bricks/imgs/lizhaoxing.jpeg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

BIN
bricks/imgs/using_phone.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -26,6 +26,7 @@ bricks.Menu = class extends bricks.VBox {
console.log('menu_clicked(): item=', item, item.opts, item.url); console.log('menu_clicked(): item=', item, item.opts, item.url);
if (!item.url){ if (!item.url){
console.log('itme.url is null'); console.log('itme.url is null');
this.bind('command', item.opts);
return; return;
} }
var t = bricks.getWidgetById(this.target); var t = bricks.getWidgetById(this.target);

View File

@ -20,33 +20,97 @@ bricks.RTCClient = class extends bricks.VBox {
{ {
"signaling_url": "signaling_url":
"ice_url": "ice_url":
"rtcid": "info":{
"rtcname" "id":
"name":
}
} }
*/ */
constructor(opts){ constructor(opts){
super(opts); super(opts);
this.build_phone();
this.videobox = new bricks.ResponsableBox({});
this.localVideo = new bricks.VideoBox({}); this.localVideo = new bricks.VideoBox({});
this.remoteVideo = new bricks.VideoBox({}); this.remoteVideo = new bricks.VideoBox({});
this.videobox.add_widget(this.localVideo);
this.videobox.add_widget(this.remoteVideo);
this.add_widget(this.videobox);
this.peerConnection; this.peerConnection;
this.socket = new WebSocket(this.signaling_url); this.socket = new WebSocket(this.signaling_url);
this.socket.onopen = this.register_myself.bind(this); this.socket.onopen = this.signaling_login.bind(this);
this.socket.onclose = null; this.socket.onclose = null;
this.socket.onerror = null; this.socket.onerror = null;
this.socket.onmessage = this.signaling_message_handle.bind(this); this.socket.onmessage = this.signaling_message_handle.bind(this);
} }
this.signaling_register(){ build_phone(){
var d = { opts = {
type:'register', states:[
data:{ {
rtcid:this.rtcid, state:'free',
rtcname:this.rtcname 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 = [];
this.onlineList.forEach( p => {
var m = {
name:p.id,
label:p.name
} }
items.push(m);
});
var menu = new bricks.Menu({
width:'300px',
height:'400px'
items:items
});
menu.set_css('popup');
Bricks.Body.add_widget(menu);
menu.bind('command', this.call_peer.bind(this));
this.menu = menu;
}
call_peer(event):
bricks.Body.remove(this.menu);
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
} }
this.socket.send(JSON.stringify(d)); this.socket.send(JSON.stringify(d));
} }
// 获取本地媒体流 // 获取本地媒体流
async function getLocalStream() { async getLocalStream() {
try { try {
const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true }); const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
this.localVideo.set_stream(stream); this.localVideo.set_stream(stream);
@ -55,12 +119,12 @@ bricks.RTCClient = class extends bricks.VBox {
console.error('获取本地媒体流失败:', error); console.error('获取本地媒体流失败:', error);
} }
} }
async call_request(peer_id){ async call_request(peer_info){
var d = { var d = {
type:'callrequest', type:'callRequest',
rtcid:peer_id to:peer_info
} }
this.peer_id = peer_id; this.peer_info = peer_info;
this.role = 'requester'; this.role = 'requester';
this.socket.send(JSON.stringify(d)); this.socket.send(JSON.stringify(d));
} }
@ -70,35 +134,35 @@ bricks.RTCClient = class extends bricks.VBox {
this.socket.send(JSON.stringify({ this.socket.send(JSON.stringify({
type:'offer', type:'offer',
offer:this.peerConnection.localDescription, offer:this.peerConnection.localDescription,
to:this.peer_id to:this.peer_info
})); }));
} }
send_candidate(event){ send_candidate(event){
if (event.candidate) { if (event.candidate) {
this.socket.send(JSON.stringify({ this.socket.send(JSON.stringify({
type: 'iceCandidate', type: 'iceCandidate',
to:this.peer_id, to:this.peer_info,
candidate: event.candidate candidate: event.candidate
})); }));
} }
} }
async need_conform_call(peer_id){ async need_conform_call(peer_info){
var w = new Conform({title:'请确认', var w = new Conform({title:'请确认',
message:'来自' + peer_id + '的连接请求'); message:'来自' + peer_info.name + '的连接请求');
w.bind('conformed', this.call_accepted.bind(this, peer_id)); w.bind('conformed', this.call_accepted.bind(this, peer_info));
w.bind('cancelled', this.call_hungup.bind(this.peer_id)); w.bind('cancelled', this.call_rejected.bind(this.peer_info));
w.open(); w.open();
} }
call_accepted(peer_id){ call_accepted(peer_info){
if (this.peer_id){ if (this.peer_info){
this.call_close(); this.call_close();
} }
this.peer_id = peer_id this.peer_info = peer_info
this.role = 'responser' this.role = 'responser'
async this.createPeerConnection(); async this.createPeerConnection();
this.socket.send(JSON.stringify({ this.socket.send(JSON.stringify({
type: 'callAccepted', type: 'callAccepted',
to: peer_id to: peer_info
})); }));
} }
async call_close(active){ async call_close(active){
@ -108,21 +172,24 @@ bricks.RTCClient = class extends bricks.VBox {
if (! active){ if (! active){
this.socket.send(JSON.stringify({ this.socket.send(JSON.stringify({
type:'disconnect', type:'disconnect',
to:this.peer_id to:this.peer_info
}); });
} }
this.peer_id = null; this.peer_info = null;
this.localVideo.get_stream().getTracks().forEach(track => track.stop()); this.localVideo.get_stream().getTracks().forEach(track => track.stop());
} }
call_hangup(peer_id){ call_rejected(peer_info){
this.socket.send(JSON.stringify({ this.socket.send(JSON.stringify({
type: 'callRejected', type: 'callRejected',
to: peer_id to: peer_info
})); }));
} }
async signaling_message_handle(event){ async signaling_message_handle(event){
var d = event.data; var d = event.data;
switch (d.type){ switch (d.type){
case 'onlineList':
this.onlineList = d.onlineList;
break;
case 'callRequest': case 'callRequest':
this.need_conform_call(d.from); this.need_conform_call(d.from);
break; break;
@ -133,25 +200,25 @@ bricks.RTCClient = class extends bricks.VBox {
this.socket.send(JSON.stringify({ this.socket.send(JSON.stringify({
type:'answer', type:'answer',
answer:answer, answer:answer,
to:this.peer_id to:this.peer_info
}); });
case 'answer': case 'answer':
if (d.from == self.peer_id){ if (d.from == self.peer_info){
var desc = new RTCSessionDescription(d.answer)); var desc = new RTCSessionDescription(d.answer));
this.peerConnection.setRemoteDescription(desc); this.peerConnection.setRemoteDescription(desc);
} }
break; break;
case 'iceCandidate': case 'iceCandidate':
if (d.from == self.peer_id){ if (d.from == self.peer_info){
var candidate = new RTCIceCandidate(d.candidate); var candidate = new RTCIceCandidate(d.candidate);
this.peerConnection.addIceCandidate(candidate); this.peerConnection.addIceCandidate(candidate);
} }
break; break;
case 'callAccepted': case 'callAccepted':
await this.createPeerConnection(this.peer_id); await this.createPeerConnection(this.peer_info);
break; break;
case 'callRejected': case 'callRejected':
this.peer_id = null; this.peer_info = null;
break; break;
case 'disconnect': case 'disconnect':
this.call_close(true); this.call_close(true);
@ -160,28 +227,33 @@ bricks.RTCClient = class extends bricks.VBox {
} }
// 创建 PeerConnection // 创建 PeerConnection
async createPeerConnection(to_id) { async createPeerConnection(to_info) {
this.phone.set_state('using');
if (this.role == 'requester'){ if (this.role == 'requester'){
await this.send_offer(); await this.send_offer();
} }
const configuration = { const configuration = {
// iceServers: [{ urls: 'stun:stun.l.google.com:19302' }]
iceServers: [ iceServers: [
{ urls: this.ice_url }, { urls: this.ice_url },
{ urls: 'stun:stunserver2024.stunprotocol.org:3478' } { urls: 'stun:stunserver2024.stunprotocol.org:3478' }
] ]
}; };
this.peerConnection = new RTCPeerConnection(configuration); this.peerConnection = new RTCPeerConnection(configuration);
// 处理本地流添加到 PeerConnection
this.localVideo
.get_stream()
.getTracks()
.forEach(track => {
this.peerConnection.addTrack(track,
this.localVideo.stream));
}
// 处理本地流添加到 PeerConnection // 处理 ICE 候选
this.localVideo.stream.getTracks().forEach(track => this.peerConnection.addTrack(track, this.localVideo.stream)); this.peerConnection.onicecandidate = this.send_candidate.bind(this, to_info);
// 处理 ICE 候选 // 处理远程流添加
this.peerConnection.onicecandidate = this.send_candidate.bind(this, to_id); this.peerConnection.ontrack = event => {
this.remoteVideo.set_stream(event.streams[0]);
// 处理远程流添加 };
this.peerConnection.ontrack = event => {
this.remoteVideo.set_stream(event.streams[0]);
};
} }
} }