Home Reference Source

src/Remon.js

/*
eslint new-cap: ['error', {
 'capIsNewExceptions': [
   'PeerConnection',
   'SignalingConnection',
   'EventManager',
  ]
}]
*/

import 'webrtc-adapter';
import 'platform';
import Config from './Configure';
import Context from './Context';
import Media from './Media';
import EventManager from './EventManager';
import signalingStates from './SignalingStates';
import bindPeerConnectionEvents from './PeerConnectionHandler';
import SignalingConnection from './SignalingConnection';
import bindSignalingConnectionEvents from './SignalingConnectionHandler';
import RemonRecorder from './RemonRecorder';
import util from './Util';
import l from './Logger';
/**
* RemoteMonster API를 사용하기 위한 가장 중요한 클래스. Remon클래스를 통해 서버와 연결하고 명령을 보내고 Close한다. 서버로부터 메시지를 받는 것은 주로 RemonObserver를 통해 수행한다
*/
class Remon{
  /**
  * config와 listener를 입력받아서 Remon server에 연결 및 인증 절차를 밟는다.
  * 활용 예: var v = new Remon({config: rtcConfig, listener: rtcListener});
  */
  constructor({config, listener}){
    this.version = "0.2.5";

    this.context = new Context();
    this.context.eventManager = EventManager();
    this.config = config;
    util.validateConfig(this.context, this.config);
    this.media = new Media(this.context);
    this.context.mediaManager = this.media;
    this.uri = Config.appServer.url+"/init";
    this.key = this.config.credential.key;
    this.serviceId = this.config.credential.serviceId;
    this.context.key = this.key;
    this.context.serviceId = this.serviceId;
    this.context.state = 'INIT';
    this.logLevel = (this.config.dev && this.config.dev.logLevel)? this.config.dev.logLevel : "INFO";
    l.init(this.logLevel);
    if (listener){
      Object.keys(listener).forEach((type) => {
        const listenerItem = listener[type];
        this.context.eventManager.addEventListener({ type, listenerItem });
      });
    }
    if (!this.config.media)this.config.media={audio:true, video:true, record:false};
    if (this.config.media.record)this.context.useRecord = this.config.media.record;
    if (this.config.media.video.codec) this.context.videoCodec = this.config.media.video.codec;
    if (this.config.media.audio.codec) this.context.audioCodec = this.config.media.audio.codec;
    if (this.config.media.video===false) this.context.useVideo = false;
    if (this.config.media.audio===false) this.context.useAudio = false;
    if (this.config.credential.resturl) Config.appServer.url = this.config.credential.resturl;
    if (this.config.credential.wsurl) Config.signalingServer.url = this.config.credential.wsurl;

    const messageBody = {
      credential: {key:this.key,serviceId:this.serviceId},
      env: {
        os: platform.os.family,
        osVersion: platform.os.version||"0",
        device: platform.name,
        deviceVersion: platform.version||"0",
        networkType: Navigator.connection,
        sdkVersion: this.version,
      }
    };
    if (this.config.media.sendonly===true) messageBody.sendonly = "true";
    if (this.config.media.recvonly===true) messageBody.recvonly = "true";
    const message = { method: 'POST', body: JSON.stringify(messageBody) };
    l.v('INIT MSG ->:', message);
    fetch(this.uri, message).then((response) =>{
      response.json().then((responseJson) => {
        l.d('-> Message:', responseJson);
        Object.keys(responseJson).forEach((responseJsonKey) => {
          switch(responseJsonKey){
            case 'iceServers': {
              Config.rtc.iceServers = responseJson[responseJsonKey];
              break;
            }
            case 'credential': {break;}
            case 'token': {
              this.context.token = responseJson[responseJsonKey];
              break;
            }
            case 'env':{break;}
            case 'sendonly':{break;}
            case 'recvonly':{break;}
            default: { l.e('Init: Unknown property:' + responseJsonKey);}
          }
        });

        l.gEnd();
        this.context.signalingConnection = SignalingConnection({
          url : Config.signalingServer.url,
          context: this.context,
        });
        this.context.peerConnection = new RTCPeerConnection(Config.rtc);
        //Config.rtc.iceServers = null;
        this.context.hasAddTrack = (this.context.peerConnection.addTrack !== undefined);
        bindSignalingConnectionEvents({
          context:this.context,
          media:this.media,
        });
        bindPeerConnectionEvents({
          context:this.context,
          media:this.media,
        });
        if (this.context.useVideo){
          if (typeof this.config.view.local !==undefined)
            Config.rtc.localVideo = document.querySelector(`${this.config.view.local}`);
          this.context.remoteVideo = document.querySelector(`${this.config.view.remote}`);
          this.media.createLocalStream(this.context, this.config.media);
        }
        if (this.config.media.recvonly){
          this.context.remoteVideo = document.querySelector(`${this.config.view.remote}`);
        }
      }).catch((error) =>{
        if (this.context.eventManager.hasEventListener('onError')) { this.context.eventManager.dispatchEvent('onError', 'WebSocketFailedError'); }
        l.e('Init: failed:', error);
      });
    }).catch((error) => {
      if (this.context.eventManager.hasEventListener('onError')) { this.context.eventManager.dispatchEvent('onError', 'WebSocketFailedError'); }
      l.e('Init: failed', error);
    });
  }

  /**
  * 이미 개설된 채널(방)에 입장하거나 이미 개설된 방이 없으면 입력한 방 이름으로 방을 개설한다.
  * 사용예: remon.connectChannel("roomname1");
  */
  connectChannel(...args) {
    return this.context.signalingConnection.connectChannel(...args);
  }
  /**
  * 방송을 위한 방을 개설한다.
  */
  createChannel(args){
    if (this.config.media.sendonly === true){
      //this.context.peerConnection.createPresenterOffer();
      l.w("createBroadcastChannel is called");
      this.context.signalingConnection.createBroadcastChannel(args);
    }else if (this.config.media.recvonly === true){
      this.context.signalingConnection.createViewerChannel(args);
    }
  }
  /**
  * 현재 통신 상태정보를 가져온다.
  */
  getHealth(){
    return this.context.health.result;
  }
  /**
  * 현재 sdk 버전 정보를 가져온다
  */
  getVersion() {
    return this.version;
  }
  /**
  * 현재 접속한 채널의 id(방이름)를 가져온다
  */
  getChannelId() {
    return this.context.channel.id;
  }
  /**
  * 현재 연결을 끊는다.
  */
  disconnect(){
    this.close();
  }
  /**
  * 자신의 영상을 잠시 멈춘다.
  * @param (bool) bool true이면 멈추고 아니면 보인다
  */
  pauseLocalVideo(bool) {
    this.media.mediaStreamTrackSwitch(Config.rtc.localStream).type('Video').enabled(!!bool);
  }
  /**
  * 상대의 영상을 잠시 멈춘다.
  * @param (bool) bool true이면 멈추고 아니면 보인다
  */
  pauseRemoteVideo(bool) {
    this.media.mediaStreamTrackSwitch(this.context.remoteStream).type('Video').enabled(!!bool);
  }

  cameraSwitch() {
    this.media.setUserDevices(null, this.context.devices[1].deviceId);
  }
  muteLocalAudio(bool) {
    this.media.mediaStreamTrackSwitch(Config.rtc.localStream).type('Audio').enabled(!!bool);
  }

  muteRemoteAudio(bool) {
    this.media.mediaStreamTrackSwitch(this.context.remoteStream).type('Audio').enabled(!!bool);
  }
  /**
  * 특정 방 이름을 검색하여 그 목록을 가져온다.
  * @param (string) id 검색할 방 이름. 부분 검색 가능
  */
  search(id){
    l.i("search by" + id);
//    const message = this.context.signalingConnection.createMessage({ command: 'search', body: id});
    const message = {
      command:'search',
      token: this.context.token,
      serviceId: this.context.serviceId,
      body: id,
    };

    this.context.signalingConnection.send(JSON.stringify(message));
  }
  /**
  * 통신으로 서로 연결된 상태에서 상대방에게 특정 문자형식의 메시지를 보낼수 있다. 상대편은 RemonObserver의 onMessage에서 메시지를 수신함
  * @param (string) userMessage 상대방에게 보낼 메시지
  */
  sendMessage(userMessage) {
    l.g('Signaling: Send user message');
    const message = this.context.signalingConnection.createMessage({ command: 'message', body: JSON.stringify(userMessage) });
    l.d('Message ->:', message);
    this.context.signalingConnection.send(JSON.stringify(message));
    l.gEnd();
  }

  /**
  * Remon 객체가 가진 모든 자원을 해제함.
  */
  close() {
    l.i("Remon.close");
    if (this.context.useRecord && this.context.remoteRecorder){
      this.context.remoteRecorder.stop();
      this.context.remoteRecorder = null;
    }
    if (this.context.useRecord && this.context.localRecorder){
      this.context.localRecorder.stop();
      this.context.localRecorder = null;
      this.context.useRecord = false;
    }
    if (this.context.remoteVideo.srcObject) {
      this.context.remoteVideo.srcObject.getTracks().forEach(track => track.stop());
    }

    if (!Config.rtc.localVideo && Config.rtc.localVideo.srcObject) {
      Config.rtc.localVideo.srcObject.getTracks().forEach(track => track.stop());
    }
    if (this.context.remoteVideo)
      this.context.remoteVideo.srcObject = null;
    if (!this.context.signalingConnection) return;
    //this.context.localVideo.srcObject = null;
    // FIXME: Chrome, adapter does not support addTrack.
    if (!this.context.peerConnection) return;
    if (this.context.health)
      this.context.health.stop();
    if (this.context.hasAddTrack) {
      this.context.peerConnection.ontrack = null;
    } else {
      this.context.peerConnection.onaddstream = null;
    }
    this.context.peerConnection.onremovestream = null;
    this.context.peerConnection.onicecandidate = null;
    this.context.peerConnection.oniceconnectionstatechange = null;
    this.context.peerConnection.onsignalingstatechange = null;
    this.context.peerConnection.onicegatheringstatechange = null;
    this.context.peerConnection.onnegotiationneeded = null;
    this.context.peerConnection.close();
    this.context.peerConnection = null;
    this.context.signalingConnection.close();
  }
}

export default Remon;