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;