diff --git a/bricks/.DS_Store b/bricks/.DS_Store index f5a50a7..3e0821f 100755 Binary files a/bricks/.DS_Store and b/bricks/.DS_Store differ diff --git a/bricks/image.js b/bricks/image.js index b947cff..8885d8c 100755 --- a/bricks/image.js +++ b/bricks/image.js @@ -58,12 +58,12 @@ bricks.Image = class extends bricks.JsWidget { bricks.Icon = class extends bricks.Image { constructor(opts){ - var rate = opts.rate || 1; - var siz = bricks.app.charsize * rate + 'px'; - opts.width = siz; - opts.height = siz; 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.cheight = this.opts.cheight || 1; 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 { constructor(opts){ super(opts); @@ -83,5 +111,6 @@ bricks.BlankIcon = class extends bricks.JsWidget { } bricks.Factory.register('Image', bricks.Image); +bricks.Factory.register('StatedIcon', bricks.StatedIcon); bricks.Factory.register('Icon', bricks.Icon); bricks.Factory.register('BlankIcon', bricks.BlankIcon); diff --git a/bricks/imgs/free_phone.png b/bricks/imgs/free_phone.png new file mode 100644 index 0000000..1ea56e6 Binary files /dev/null and b/bricks/imgs/free_phone.png differ diff --git a/bricks/imgs/lizhaoxing.jpeg b/bricks/imgs/lizhaoxing.jpeg new file mode 100644 index 0000000..478dc3d Binary files /dev/null and b/bricks/imgs/lizhaoxing.jpeg differ diff --git a/bricks/imgs/using_phone.png b/bricks/imgs/using_phone.png new file mode 100644 index 0000000..80b5203 Binary files /dev/null and b/bricks/imgs/using_phone.png differ diff --git a/bricks/menu.js b/bricks/menu.js index 1f74b3b..fcd135f 100755 --- a/bricks/menu.js +++ b/bricks/menu.js @@ -26,6 +26,7 @@ bricks.Menu = class extends bricks.VBox { console.log('menu_clicked(): item=', item, item.opts, item.url); if (!item.url){ console.log('itme.url is null'); + this.bind('command', item.opts); return; } var t = bricks.getWidgetById(this.target); diff --git a/bricks/rtc.js b/bricks/rtc.js index 3cbce6f..deb4c82 100644 --- a/bricks/rtc.js +++ b/bricks/rtc.js @@ -20,33 +20,97 @@ bricks.RTCClient = class extends bricks.VBox { { "signaling_url": "ice_url": - "rtcid": - "rtcname" + "info":{ + "id": + "name": + } } */ constructor(opts){ super(opts); + this.build_phone(); + this.videobox = new bricks.ResponsableBox({}); this.localVideo = 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.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.onerror = null; this.socket.onmessage = this.signaling_message_handle.bind(this); } - this.signaling_register(){ - var d = { - type:'register', - data:{ - rtcid:this.rtcid, - rtcname:this.rtcname + build_phone(){ + opts = { + 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 = []; + 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)); } // 获取本地媒体流 - async function getLocalStream() { + async getLocalStream() { try { const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true }); this.localVideo.set_stream(stream); @@ -55,12 +119,12 @@ bricks.RTCClient = class extends bricks.VBox { console.error('获取本地媒体流失败:', error); } } - async call_request(peer_id){ + async call_request(peer_info){ var d = { - type:'callrequest', - rtcid:peer_id + type:'callRequest', + to:peer_info } - this.peer_id = peer_id; + this.peer_info = peer_info; this.role = 'requester'; this.socket.send(JSON.stringify(d)); } @@ -70,35 +134,35 @@ bricks.RTCClient = class extends bricks.VBox { this.socket.send(JSON.stringify({ type:'offer', offer:this.peerConnection.localDescription, - to:this.peer_id + to:this.peer_info })); } send_candidate(event){ if (event.candidate) { this.socket.send(JSON.stringify({ type: 'iceCandidate', - to:this.peer_id, + to:this.peer_info, candidate: event.candidate })); } } - async need_conform_call(peer_id){ + async need_conform_call(peer_info){ var w = new Conform({title:'请确认', - message:'来自' + peer_id + '的连接请求'); - w.bind('conformed', this.call_accepted.bind(this, peer_id)); - w.bind('cancelled', this.call_hungup.bind(this.peer_id)); + message:'来自' + peer_info.name + '的连接请求'); + w.bind('conformed', this.call_accepted.bind(this, peer_info)); + w.bind('cancelled', this.call_rejected.bind(this.peer_info)); w.open(); } - call_accepted(peer_id){ - if (this.peer_id){ + call_accepted(peer_info){ + if (this.peer_info){ this.call_close(); } - this.peer_id = peer_id + this.peer_info = peer_info this.role = 'responser' async this.createPeerConnection(); this.socket.send(JSON.stringify({ type: 'callAccepted', - to: peer_id + to: peer_info })); } async call_close(active){ @@ -108,21 +172,24 @@ bricks.RTCClient = class extends bricks.VBox { if (! active){ this.socket.send(JSON.stringify({ 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()); } - call_hangup(peer_id){ + call_rejected(peer_info){ this.socket.send(JSON.stringify({ type: 'callRejected', - to: peer_id + to: peer_info })); } async signaling_message_handle(event){ var d = event.data; switch (d.type){ + case 'onlineList': + this.onlineList = d.onlineList; + break; case 'callRequest': this.need_conform_call(d.from); break; @@ -133,25 +200,25 @@ bricks.RTCClient = class extends bricks.VBox { this.socket.send(JSON.stringify({ type:'answer', answer:answer, - to:this.peer_id + to:this.peer_info }); case 'answer': - if (d.from == self.peer_id){ + if (d.from == self.peer_info){ var desc = new RTCSessionDescription(d.answer)); this.peerConnection.setRemoteDescription(desc); } break; case 'iceCandidate': - if (d.from == self.peer_id){ + if (d.from == self.peer_info){ var candidate = new RTCIceCandidate(d.candidate); this.peerConnection.addIceCandidate(candidate); } break; case 'callAccepted': - await this.createPeerConnection(this.peer_id); + await this.createPeerConnection(this.peer_info); break; case 'callRejected': - this.peer_id = null; + this.peer_info = null; break; case 'disconnect': this.call_close(true); @@ -160,28 +227,33 @@ bricks.RTCClient = class extends bricks.VBox { } // 创建 PeerConnection - async createPeerConnection(to_id) { + async createPeerConnection(to_info) { + this.phone.set_state('using'); if (this.role == 'requester'){ await this.send_offer(); } - const configuration = { - // iceServers: [{ urls: 'stun:stun.l.google.com:19302' }] + const configuration = { iceServers: [ { urls: this.ice_url }, { 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 - this.localVideo.stream.getTracks().forEach(track => this.peerConnection.addTrack(track, this.localVideo.stream)); + // 处理 ICE 候选 + 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]); + }; } }