This commit is contained in:
yumoqing 2024-10-08 18:39:44 +08:00
parent 8b39dfd3e5
commit b28d506522
2 changed files with 267 additions and 25 deletions

View File

@ -47,6 +47,18 @@ dispatch_event:
params:
*/
bricks.uuid = window.crypto.randomUUID;
bricks.deviceid = function(appname){
var deviceid = appname + 'deviceid';
var id = localStorage.getItem(deviceid);
if (!id){
id = bricks.uuid();
localStorage.setItem(deviceid, id);
}
return id;
}
bricks.str2data = function(s, d){
/* fmt like
'my name is ${name}, ${age:type}'
@ -427,6 +439,7 @@ bricks.App = class extends bricks.Layout {
constructor(opts){
/*
opts = {
appname:
login_url:
"charsize:
"language":
@ -444,6 +457,7 @@ bricks.App = class extends bricks.Layout {
super(opts);
bricks.app = this;
bricks.Body = this;
this.deviceid = bricks.deviceid(opts.appname || 'appname');
this.login_url = opts.login_url;
this.charsize = this.opts.charsize || 20;
this.keyevent_blocked = false;

View File

@ -1,16 +1,33 @@
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.Signaling = class {
/*
{
signaling_url:
info:
multiple:false
connect_opts:
}
*/
constructor(opts){
this.signaling_url = opts.signaling_url;
this.info = opts.info;
this.connect_opts = opts.connect_opts;
this.peers = [];
this.sessions = {};
this.handlers = {};
@ -37,12 +54,21 @@ bricks.Signaling = class {
async signaling_recvdata(event){
var datapkg = JSON.parse(event.data);
var data = datapkg.data;
var name = data.sessionid
var handler = this.handlers.get(name)
if (data.session) {
var sessionid = data.session.sessionid;
var sessiontype = data.session.sessiontype;
var handler = this.handlers.get(sessionid)
if (!handler){
handler = this.recvdata_handler.bind(this)
var k = this.sessionhandlers.get(sessiontype);
if (!k){
throw 'recvdata_handler() exception(' + sessiontype + ')';
}
var handler = new k(this, data.session, this.connect_opts);
}
await handler(data);
} else {
await this.recvdata_handler(data);
}
}
add_handler(key, handler){
this.handlers[key] = handler;
@ -62,23 +88,25 @@ bricks.Signaling = class {
}
});
}
if (data.type == 'new_session'){
}
}
new_session(sessiontype, opts){
new_session(sessiontype, peer){
var k = this.sessionhandlers.get(sessiontype);
if (!k){
throw 'new_session() exception(' + sessiontype + ')';
}
var sessionid = bricks.uuid();
var session = {
sessiontype:sessiontype,
sessionid:sessionid
}
var d = {
type:'new_session',
session: {
sessiontype:sessiontype,
sessionid:bricks.uuid()
}
session: session
};
this.send_data(d);
var h = new k(this, sessiontype, opts);
opts = this.connect_options.copy();
opts.peer_info = peer;
var h = new k(this, session, this.connect_opts);
this.add_handler(sessionid, h.recvdata_handler);
return h
}
@ -105,21 +133,221 @@ bricks.Signaling = class {
}
}
bricks.RTCSession = class extends bricks.VBox {
bricks.RTCP2PConnect = class {
/*
opts:{
ice_servers:
peer_info:
auto_callaccept: true or false
media_options: { video:trur or false, audio:true or false }
data_connect: true or false
}
*/
}
bricks.VideoBox = class extends bricks.JsWidget {
create(){
this.dom_element = this._create('video');
this.set_attribute('autoplay', true);
this.set_attribute('muted', true);
constructor(signaling, session, opts){
this.signaling = signaling;
this.session = session;
this.requester = false;
this.opts = opts;
this.peers = {};
this.signal_handlers = {};
this.localStream = null;
this.localVideo = null;
this.add_handler('sessioncreated', this.h_sessioncreated.bind(this));
this.add_handler('callrequest', this.h_callrequest.bind(this));
this.add_handler('callaccepted', this.h_callaccepted.bind(this));
this.add_handler('offer', this.h_offer.bind(this));
this.add_handler('answer', this.h_answer.bind(this));
this.add_handler('icecandidate', this.h_icecandidate.bind(this));
this.add_handler('sessionquit', this.h_sessionquit.bind(this));
}
add_handler(type, f):
this.signal_handlers[type] = f;
}
async p2pconnect(peer){
await this.getLocalStream();
await this.createPeerConnection(peer);
}
async h_sessioncreated(data){
if (this.opts.peer_info){
var d = {
type:'callrequest',
msgto:this.opts.peer_info
}
this.signaling_send(d);
this.requester = true;
}
}
async h_callrequest(data){
if (this.opts.auto_callaccept){
var d = {
type:'callaccepted',
msgto:data.msgfrom
};
this.signaling_send(d);
await this.p2pconnect(data.msgfrom);
return;
}
this.dispatch('callrequest', data.msgfrom);
}
async h_callaccepted(data){
await this.p2pconnect(data.msgfrom);
await this.send_offer(data.msgfrom);
}
async h_offer(data){
var pc = this.peers[data.msgfrom.id].pc;
var offer = new RTCSessionDescription(data.offer);
await pc.setRemoteDescription(offer);
var answer = await pc.createAnswer();
await pc.setLocalDescription(answer);
this.signal_send({
type:'answer',
answer:pc.localDescription,
msgto:data.msgfrom
});
}
async h_answer(data){
var desc = new RTCSessionDescription(data.answer);
var pc = this.peers[data.msgfrom.id].pc;
await pc.setRemoteDescription(desc);
}
async h_icecandidate(data){
var candidate = new RTCIceCandidate(data.candidate);
var pc = this.peers[data.msgfrom.id].pc;
await pc.addIceCandidate(candidate);
}
async h_sessionquit(data){
var pc = self.peers[data.msgfrom.id].pc;
pc.close();
}
async send_offer(peer){
pc = this.peers[peer.id].pc;
var offer = await pc.createOffer();
await pc.setLocalDescription(offer);
var d = {
type:'offer',
offer:pc.localDescription,
msgto:peer
};
this.signal_send(d);
}
send_candidate(peer, event){
if (event.candidate) {
var candidate = event.candidate;
this.signaling_send({
type: 'icecandidate',
msgto:peer,
candidate: candidate
});
}
}
signaling_send(d){
d.session = this.session;
this.signaling.send_data(d);
}
async recvdata_handler(data){
var f = this.get_handler(data.type);
if (f) {
await f(data);
}
}
async ice_statechange(peer, event){
var pc = this.peers[peer.id].pc;
console.log(`oniceconnectionstatechange, pc.iceConnectionState is ${pc.iceConnectionState}.`);
}
async connection_statechange(peer, event){
var pc = this.peers[peer.id].pc;
if (pc.connectionState == 'disconnected'){
var w = new bricks.Error({
title:'Error',
message:'lose connection',
timeout:4
});
w.open();
return;
}
if (pc.connectionState == 'connected'){
if (this.opts.data_connect){
var dc = pc.createDataChannel();
if (this.requester and this.opts.data_connect){
await this.createDataChannel(peer);
}
}
console.log(`${peer.id} connect losed. ${pc.connectionState}`);
}
}
async createDataChannel(peer){
var pc = this.peers[peer.id].pc;
dc = pc.createDataChannel('data');
this.peers[peer.id].dc = dc;
dc.onmessage = this.datachannel_message.bind(this, peer);
dc.onopen = this.datachannel_open(this, peer);
dc.onclose = this.datachannl_close(this, peer);
}
async def datachannel_message(peer, msg){
var dc = this.peers[peer.id].dc;
if (this.on_dc_messaage){
await this.on_dc_message(dc, msg);
}
}
async def datachannel_open(peer){
var dc = this.peers[peer.id].dc
if (this.on_dc_open){
await this.on_dc_open(dc);
}
}
async def datachannel_close(peer){
var dc = this.peers[peer.id].dc
if (this.on_dc_close){
await this.on_dc_close(dc);
}
}
async createPeerConnection(peer){
const configuration = {
iceServers:this.opts.ice_servers
}
var pc = RTCPeerConnection(configuration);
this.localStream.getTracks()
.forEach(track => {
pc.addTrack(track, this.localStream);
});
this.peers[peer.id] = peer;
this.peers[peer.id][pc] = pc;
var remoteVideo = new VideoBox();
this.peers[peer.id][video] = remoteVideo;
pc.onicecandidate = this.send_candidate.bind(this, peer);
pc.oniceconnectionstatechange = this.ice_statechange.bind(this, peer);
pc.onconnectionstatechange = this.connection_statechange.bind(this, peer);
pc.ondatachanel = event => {
var dc = event.channel;
this.peers[peer.id].dc = dc;
dc.onmessage = this.data_message_recv.bind(this, peer);
dc.onopen = this.datachannel_opened(this, peer);
dc.onclose = this.datachannl_closed(this, peer);
}
pc.ontrack = event => {
remoteVideo.set_stream(event.streams[0]);
}
}
async getLocalStream() {
if (this.opts.media_options){
try {
var mediaOptions = this.opts.media_options;
this.local_stream = await navigator.mediaDevices.getUserMedia(mediaOptions);
this.localVideo = new VideoBox();
this.localVideo.set_stream(stream);
} catch (error) {
console.error('获取本地媒体流失败:', error);
}
get_stream(){
return this.stream;
}
set_stream(stream){
this.stream = stream
this.dom_element.srcObject = this.stream;
}
}