Home Reference Source

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;