src/SignalingConnectionHandler.js
import Context from './Context';
import l from './Logger';
import util from './Util';
import signalingStates from './SignalingStates'
function bindSignalingConnectionEvents({
context,
media,
}) {
function handleDescription(description) {
l.g('Signaling: Handle local description: Set local description to peerconnection and send to signaling server.');
l.v('Local Description:', description);
if (!context.videoCodec){
l.v("Signaling: video codec: H264");
description.sdp = replaceCodec(description.sdp, /m=video(:?.*)?/, "H264");
}else{
l.v("Signaling: video codec: "+ context.videoCodec);
description.sdp = replaceCodec(description.sdp, /m=video(:?.*)?/, context.videoCodec);
}
const message = context.signalingConnection.createMessage({ command: 'sdp', body: JSON.stringify(description) });
context.peerConnection.setLocalDescription(description)
.then(() => {
l.v('Local Description Setted:', context.peerConnection.localDescription);
l.v('Message ->:', message);
context.signalingConnection.send(JSON.stringify(message));
})
.catch((error) => {
l.e('PeerConnection: set Local description failed', error);
if (context.eventManager.hasEventListener('onError')) {
var e1 = {
code:'ConnectChannelFailedError',
body:'PeerConnection: set Local description failed',
};
context.eventManager.dispatchEvent('onError', e1);
}
});
l.gEnd();
}
const signalingEvents = {
onCreate(message) {
l.g('Signaling: On create channel');
context.isCaller = false;
if (context.eventManager.hasEventListener('onStateChange')) { context.eventManager.dispatchEvent('onStateChange', 'WAIT'); }
if (!message.channel) {
if (context.eventManager.hasEventListener('onError')) {
var e1 = {
code:'ConnectChannelFailedError',
body:'Can not make a channel',
};
context.eventManager.dispatchEvent('onError', e1);
}
}
context.channel = message.channel;
l.i('Channel id:', context.channel.id);
l.i('Channel type: ', context.channel.type);
// if (!peerConnection.localDescription) {
if (context.channel.type!=="VIEWER")
media.bindLocalStreamToPeerConnection(context.peerConnection);
// }
if (context.eventManager.hasEventListener('onCreateChannel')) {
context.eventManager.dispatchEvent('onCreateChannel', context.channel.id);
}
if (context.channel.type ==='BROADCAST'){
createPresenterOffer();
}else if (context.channel.type ==='VIEWER'){
createViewerOffer();
}
l.gEnd();
},
onConnect(message) {
l.g('Signaling: On connect channel');
context.isCaller = true;
context.channel = message.channel;
l.i('Channel id:', context.channel.id);
l.i('Channel type: ', context.channel.type);
l.d('isCaller: true');
if (context.eventManager.hasEventListener('onStateChange')) {
context.eventManager.dispatchEvent('onStateChange', 'CONNECT');
}
if (!message.channel) {
if (context.eventManager.hasEventListener('onError')) {
var e1 = {
code:'ConnectChannelFailedError',
body:'Can not make a channel',
};
context.eventManager.dispatchEvent('onError', e1);
}
return;
}
// if (!peerConnection.localDescription) {
media.bindLocalStreamToPeerConnection(context.peerConnection);
// }
if (context.eventManager.hasEventListener('onConnectChannel')) {
context.eventManager.dispatchEvent('onConnectChannel', context.channel.id);
}
l.i('Create offer');
const offerOptions = {
offerToReceiveAudio: 1,
offerToReceiveVideo: 1
};
context.peerConnection.createOffer(offerOptions)
.then(handleDescription)
.catch((error) => {
l.e('PeerConnection: Create offer failed:', error);
if (context.eventManager.hasEventListener('onError')) {
var e1 = {
code:'ICEFailedError',
body:'PeerConnection: Create offer failed',
};
context.eventManager.dispatchEvent('onError', e1);
return;
}
});
l.gEnd();
},
onSdp(message) {
l.g('Signaling: On sdp');
const description = new RTCSessionDescription(JSON.parse(message.body));
l.i('-> Remote Description:', description);
context.peerConnection.setRemoteDescription(description)
.then(() => {
l.i('Remote Description Setted');
})
.catch((error) => {
l.e('PeerConnection: Remote description set failed:', error);
if (context.eventManager.hasEventListener('onError')) {
var e1 = {
code:'ICEFailedError',
body:'PeerConnection: Remote description set failed',
};
context.eventManager.dispatchEvent('onError', e1);
return;
}
});
l.i('Am I a caller?:', context.isCaller);
if (!context.isCaller && context.channel.type !=="BROADCAST" && context.channel.type !=="VIEWER") {
l.i('Create answer');
context.peerConnection.createAnswer()
.then(handleDescription)
.catch((error) => {
l.e('PeerConnection: Create Answer failed:', error);
if (context.eventManager.hasEventListener('onError')) {
var e1 = {
code:'ICEFailedError',
body:'PeerConnection: Create Answer failed',
};
context.eventManager.dispatchEvent('onError', e1);
return;
}
});
}
l.gEnd();
},
onDisconnectChannel(message){
l.i('Signaling: onDisconnectChannel');
context.health.stop();
context.peerConnection.close();
context.signalingConnection.close();
if (context.eventManager.hasEventListener('onDisconnectChannel')) { context.eventManager.dispatchEvent('onDisconnectChannel'); }
},
onStateChange(message) {
l.g('Signaling: On state change');
if (!signalingStates.includes(message.body)) {
l(l.e('Unknown signaling state:'+message.body));
} else {
context.state = message.body;
if (context.eventManager.hasEventListener('onStateChange')) {
context.eventManager.dispatchEvent('onStateChange', message.body);
}
switch (message.body) {
case 'INIT': { l.i('>STATE:INIT');break; }
case 'WAIT': { l.i('>STATE:WAIT');break; }
case 'CONNECT': { l.i('>STATE:CONNECT');break; }
case 'COMPLETE': {
l.i('>STATE:COMPLETE');
if (context.eventManager.hasEventListener('onStateChange')) { context.eventManager.dispatchEvent('onStateChange', 'COMPLETE'); }
if (context.eventManager.hasEventListener('onComplete')) { context.eventManager.dispatchEvent('onComplete'); }
break;
}
case 'CLOSE': {
l.i('>STATE:CLOSE');
context.health.stop();
context.peerConnection.close();
context.signalingConnection.close();
if (context.eventManager.hasEventListener('onStateChange')) { context.eventManager.dispatchEvent('onStateChange', 'CLOSE'); }
//if (context.eventManager.hasEventListener('onDisconnectChannel')) { context.eventManager.dispatchEvent('onDisconnectChannel'); }
break;
}
case 'FAIL': { l.i('>STATE:FAIL');break; }
default: { break; }
}
}
l.gEnd();
},
onIce(message) {
l.g('Signaling: On ice');
const candidate = new RTCIceCandidate(JSON.parse(message.body));
l.v('Candidate:', JSON.stringify(candidate));
context.peerConnection.addIceCandidate(candidate)
.then(() => { l.v('Add ICE candidate success'); })
.catch((error) => {
l.e('Peer Connection: Add ICE candidate failed', error);
if (context.eventManager.hasEventListener('onError')) {
var e1 = {
code:'ICEFailedError',
body:'Peer Connection: Add ICE candidate failed',
};
context.eventManager.dispatchEvent('onError', e1);
}
});
l.gEnd();
},
onMessage(message) {
l.v('Signaling: On message: ' + message.body);
if (context.eventManager.hasEventListener('onMessage')) {
context.eventManager.dispatchEvent('onMessage', message.body);
}
},
onSearch(message){
l.v('Signaling: On search: '+ message.body);
if (context.eventManager.hasEventListener('onSearch')) {
context.eventManager.dispatchEvent('onSearch', message.body);
}
},
onError(message) {
l.e('Signaling error -> Message:', message);
if (context.eventManager.hasEventListener('onError')) { context.eventManager.dispatchEvent('onError', message); }
},
};
function createViewerOffer(){
l.i("createViewerOffer is called");
// const offerOptions = {
// offerToReceiveAudio: 1,
// offerToReceiveVideo: 1
// };
const offerOptions= {
offerToReceiveAudio: 1,
offerToReceiveVideo: 1
};
l.i("with option:");
l.i( offerOptions);
context.peerConnection.createOffer(offerOptions)
.then(handleDescription)
.catch((error) => {
l.e('PeerConnection: Create offer failed:', error);
if (context.eventManager.hasEventListener('onError')) {
var e1 = {
code:'ICEFailedError',
body:'PeerConnection: Create offer failed',
};
context.eventManager.dispatchEvent('onError', e1);
return;
}
});
}
function createPresenterOffer(){
const offerOptions = {
offerToReceiveAudio: false,
offerToReceiveVideo: false
};
context.peerConnection.createOffer(offerOptions)
.then(handleDescription)
.catch((error) => {
l.e('PeerConnection: Create offer failed:', error);
if (context.eventManager.hasEventListener('onError')) {
var e1 = {
code:'ICEFailedError',
body:'PeerConnection: Create offer failed',
};
context.eventManager.dispatchEvent('onError', e1);
return;
}
});
}
function handleDescription(description) {
l.g('Signaling: Handle local description: Set local description to peerconnection and send to signaling server.');
l.i('Local Description:', description);
if (!context.videoCodec){
l.v("Signaling: video codec: H264");
description.sdp = replaceCodec(description.sdp, /m=video(:?.*)?/, "H264");
}else{
l.v("Signaling: video codec: "+ context.videoCodec);
description.sdp = replaceCodec(description.sdp, /m=video(:?.*)?/, context.videoCodec);
}
const message = context.signalingConnection.createMessage({ command: 'sdp', body: JSON.stringify(description) });
message.channel.type=context.channel.type;
context.peerConnection.setLocalDescription(description)
.then(() => {
l.v('Local Description Setted:', context.peerConnection.localDescription);
l.i('Message ->:', message);
context.signalingConnection.send(JSON.stringify(message));
})
.catch((error) => {
l.e('PeerConnection: set Local description failed', error);
if (context.eventManager.hasEventListener('onError')) {
var e1 = {
code:'ConnectChannelFailedError',
body:'PeerConnection: set Local description failed',
};
context.eventManager.dispatchEvent('onError', e1);
}
});
l.gEnd();
}
function handleMessageEvent(event) {
l.d('Signaling: Got command from server');
const message = JSON.parse(event.data);
const type = message.command;
l.v(`-> Message: ${message.command}:`, message);
signalingEvents[type](message);
}
function replaceCodec(sdp, mLineReg, preferCodec){
var mLine,
newMLine = [],
sdpCodec,
mLineSplit,
reg = new RegExp("a=rtpmap:(\\d+) " + preferCodec + "/\\d+");
mLine = sdp.match(mLineReg);
if(!mLine){
return sdp;
}
sdpCodec = sdp.match(reg);
if(!sdpCodec){
return sdp;
}
mLine = mLine[0];
sdpCodec = sdpCodec[1];
mLineSplit = mLine.split(" ");
newMLine.push(mLineSplit[0]);
newMLine.push(mLineSplit[1]);
newMLine.push(mLineSplit[2]);
newMLine.push(sdpCodec);
for(var i=3; i<mLineSplit.length; i++){
if(mLineSplit[i] !== sdpCodec){
newMLine.push(mLineSplit[i]);
}
}
return sdp.replace(mLine, newMLine.join(" "));
}
context.signalingConnection.onMessage(handleMessageEvent);
}
export default bindSignalingConnectionEvents;