# RemoteMonster

안녕하세요! 리모트몬스터 입니다.

## **환영합니다.**

리모트몬스터는 방송 통신을 응용하는 서비스를 쉽고 빠르게 개발할 수 있도록 다양한 기능을 제공하고 있습니다.


# Introduction

리모트 몬스터를 개발하기 위해 알아야할 기본적인 것들을 소개합니다.

## 개발 순서

RemoteMonster API의 SDK를 이용하여 개발하는 순서는 크게 다음과 같습니다. ![개발 흐름](/files/-LCmAZo9GBCPIg72Eed2)

### 서비스 등록 및 인증키 획득

* 리모트몬스터 홈페이지에서 여러분의 서비스 개발을 위한 등록을 마칩니다. 리모트몬스터는 메일을 통해 인증키를 발급합니다.
* 인증키는 2개로 구성됩니다. 테스트키는 개발용이며 운영용은 운영시 사용하게 됩니다. 키가 분리된 이유는 과금과 관리를 위해서입니다. 테스트키는 과금내역에 들어가지 않습니다. 테스트키는 안정성과 품질을 보장하지는 않습니다.

### 개발 환경 설정 및 SDK 다운로드

* 리모트몬스터 API의 SDK는 모두 별도의 SDK 저장소로부터 자동으로 다운로드받을 수 있습니다. 안드로이드는 메이븐 레파지토리 방식을 따르므로 Gradle 빌드 파일에서 손쉽게 설정만으로 최신 버전을 다운로드 받을 수 있고 Javascript SDK는 NPM 레파지토리를 통해서 제공하므로 npm 명령을 통해 다운로드할 수 있습니다.
* iOS는 xcode, 안드로이드는 Android Studio, Javascript는 NPM환경을 기본적으로 제공합니다.
* <https://github.com/RemoteMonster> 에서 자신에게 맞는 예제를 다운로드받아서 개발하면 빠르게 개발이 가능합니다.

### 개발

* 개발에 필요한 예제와 문서와 함께 어려운 점은 [리모트몬스터 게시판](http://community.remotemonster.com/)을 활용합니다. 2-3시간 안에 질문에 대한 답을 얻을 수 있습니다.

### 운영

* 서비스 개발이 끝나면 상용 버전의 서비스 시작 전 리모트몬스터에 통보합니다. 리모트몬스터는 보다 나은 품질과 안정성을 제공하기 위한 서버세팅과 관리자 화면을 제공합니다.
* 테스트용키가 아니라 운영 키를 사용하여 보안과 품질을 높이세요.

### 관리

* 리모트몬스터는 상용 서비스에 한하여 별도의 관리자 화면을 제공하여 트래픽 정보와 통화 품질에 대한 정보를 실시간으로 제공합니다.
* 이를 통해 어떤 고객에게 주로 문제가 발생하는지, 어떻게 개선하면 좋을지 점검할 수 있습니다.

## 핵심 클래스

리모트몬스터 API의 SDK를 사용하는 것은 매우 간단합니다. 크게 3가지의 클래스 사용법만 익히면 됩니다.

![RemoteMonster API SDK overview](/files/-LCmAZsoiLrRN0QL8m6k)

### Remon class

* Remon에서 모든 방송, 통신관련 조작을 수행합니다. 방송, 통신상태를 초기화하고 방을 개설하고 방에 접속하고 종료하는 일련의 모든 과정에서 이 Remon 클래스를 사용합니다.
* 주요 기능
  * Init: 객체를 초기화하는 과정입니다. 이를 통해 RemoteMonster API 서버와 연결됩니다. 연결되는 순간 RemoteMonster 서버는 이 Remon 객체에게 1회용 토큰을 제공합니다.
  * Connect: 통신에 사용하는 기능입니다. 방에 접속하거나 방을 만드는 명령입니다. 주어진 이름의 방이 없을 경우 방을 만들고 이미 방이 있을 경우 방에 접속합니다. 물론 사전에 방 이름이 있어야 하겠지만 만약 방 이름이 없을 경우 RemoteMonster는 고유한 방 이름을 생성해서 반환합니다.
  * CreateRoom, JoinRoom: 방송에 사용하는 기능입니다. 방송용 방을 만들거나 시청하는 명령입니다. 방을 만들 때에는 방의 닉네임을 전달할 수 있고 Observer등의 콜백을 통해 onCreate에서 방의 실제 유일한 id값을 받게 되며 이 id값을 이용하여 JoinRoom 메소드를 통해 시청자는 해당 방에 접근할 수 있습니다.
  * Search: 주어진 인자값에 해당하는 방이름이 있는지 검색하여 알려줍니다. 인자값이 없으면 모든 방 정보를 알려줍니다.
  * sendMessage: 상대편에게 문자형태의 메시지를 보낼 수 있습니다. 이 기능을 통해 다양한 작업을 수행할 수 있습니다. 주로 통신 기능에서만 사용이 가능합니다.
  * Close: 방을 나오거나 방을 없애버리고 초기화합니다.
* Remon 클래스를 생성하기 위해서는 2개의 미리 준비된 정보가 필요합니다. 그것이 바로 위의 그림에서 보듯 Config와 Observer입니다.

### Config Class

* Remon 객체를 초기화하기 전에 필요한 사전 설정작업은 모두 이 Config를 통해서 이루어집니다. 다음과 같이 구성되어 있습니다.
* Credential: 인증과 관련된 정보를 처리합니다.
  * key: 리모트몬스터로부터 받은 인증키를 입력합니다. 리모트몬스터는 2개의 인증키를 발급합니다. 테스트용키는 개발중에 사용하시면 되며, 상용키는 별도로 리모트몬스터와 협의 후 사용하시면 됩니다.
  * serviceId: 여러분이 누구인지를 처음에 가입했을 때 입력하신 unique한 id를 입력하면 리모트몬스터는 이 정보를 바탕으로 인증을 수행합니다.
* Media: 영상과 음성에 대한 여러가지 설정을 할 수 있습니다.
  * 영상 사용여부: 음성 통화만을 원한다면 영상은 false로 하고 사용하면 아까운 통신요금과 Bandwidth를 줄일 수 있겠죠.
  * 코덱 선택: 음성과 영상 모두에 대해 코덱을 선택할 수 있습니다. 영상의 경우 리모트몬스터는 기본적으로 H.264를 사용하고 있으며 음성은 OPUS를 사용합니다.
  * FrameRate와 해상도: 영상의 경우 해상도와 FrameRate을 수정하여 원하는 품질을 적용할 수 있습니다.
  * 녹음여부: 리모트몬스터는 녹음기능을 기본 제공합니다. 증거가 필요한 서비스의 경우 녹음기능을 켜두면 안심입니다.
  * 기타: 그 외에도 다양한 카메라, 마이크, 스피커에 대한 처리가 가능합니다.
* 기타 개발관련 설정
  * 로그: 로그 수준을 조절하여 개발시에 유용하게 사용할 수 있습니다.

### Observer Class

* Remon 클래스가 명령을 내리는 용도라면 Observer(Javascript SDK에서는 Listener, iOS에서는 Delegetor)는 Callback 메시지를 수신하기 위한 클래스입니다. Observer로부터 수신되는 이벤트를 이용하여 보다 세밀한 통신효과와 능동적인 서비스를 구현할 수 있습니다. 다음과 같은 메소드가 호출됩니다.
* onStateChange: 최초 Remon객체를 만들고 방을 만들며 접속하고 접속에 성공하고 방송, 통신을 마칠 때까지의 모든 상태 변화에 대해 처리하는 메소드입니다. RemonState enum객체를 통해 어떤 상태로 변경되었는지를 알려줍니다. RemonState의 상태는 다음과 같습니다.
  * INIT(시작), WAIT(방 생성), CONNECT(방 접속), COMPLETE(통신 연결완료), FAIL(실패), CLOSE(종료)
* onError: 통신 시도 중 장애 발생시 호출됩니다.
* onAddLocalStream: 자기 자신의 카메라의 영상이 혹은 음성 스트림을 획득하였을 경우 호출됩니다.
* onAddRemoteStream: 상대방의 영상이나 음성 스트림을 획득하였을 경우 호출됩니다. 연결이 되었다는 뜻이죠.
* onInit: Remon객체가 무사히(?) 생성되었을 경우 불려집니다. 이 onInit메소드가 불려지고 바로 Remon.connectchannel 메소드를 수행할 수 있습니다.
* onSearch: Remon.search 메소드를 호출하면 Observer의 onSearch메소드가 결과를 반환합니다.
* onMessage: Remon.sendMessage 메소드를 호출하면 Observer의 onMessage메소드가 결과를 반환합니다.


# Spec

제품의 주요 규격과 기능을 소개합니다.

## 리모트 몬스터 API

* 리모트몬스터의 모든 방송 통신기술은 API형태로 제공됩니다. 이제 복잡한 Back-end개발과 방송 통신기술 개발의 부담에서 해방되어 누구나 쉽게 Skype나 Hangout, 스냅챗, 아프리카TV와 같은 방송 통신 서비스를 구현할 수 있습니다. 리모트몬스터가 구축하는 글로벌한 통신 API 백엔드와 SDK를 통해 방송 통신회사에 준하는 자원을 즉시 확보하세요.
* 리모트몬스터 API는 다음과 같은 형태로 구성되어 있습니다.

  ![RemoteMonster API overview](/files/-LCmAZvtoBBXmHeXpxhX)

### 주요기능

* 핵심 기능
  * 모바일 플랫폼 지원: 이미 운영중인 서비스 혹은 개발할 서비스에 SDK를 추가하고 몇가지 소스코드를 추가하여 통신 기능을 적용합니다.
  * 실시간 통화 지원: 영상, 음성 통신 뿐 아니라 방송이나 일반전화 대상 송수신 등의 기능을 제공합니다
  * 방송 서비스 지원: 아프리카TV나 트위치와 같은 실시간 방송 서비스 기능을 서비스에 적용할 수 있습니다.
  * 글로벌 클라우드 환경 지원: 리모트몬스터의 기능은 글로벌 클라우드에 의해 운영되어 세계 어디에서나 빠른 통신품질을 보장합니다.
  * 모니터링 기능: 모든 통화 트랜잭션의 상태를 실시간으로 또는 통계적으로 모니터링할 수 있습니다.
  * 녹화 기능: 필요하다면 통화 내용을 녹화하여 지속적으로 보관하는 것도 가능합니다. 앱 내에서 녹화하는 기능과 서비스 서버에서 녹화하는 기능등을 제공합니다.

### 플랫폼 지원

* 최신의 플랫폼 지원: WebRTC 엔진인 Chrominum 6개월 이내의 최신 버전을 제공하여 호환성과 성능 및 품질 최고를 경험.
* 최신의 표준 지원: WebRTC 1.0을 가장 빠르게 지원(WebRTC 1.0 CR 기준)
* 가장 다양한 플랫폼 및 최신의 언어 지원: Android, iOS(Objective C, Swift 3.0), Chrome/Firefox/Edge/Whale ( promise 방식, ES6 준수), 국내환경에서 가장 많은 플렛폼 지원

### 품질

* Global 통신 인프라 지원: 국내 뿐 아니라 Global한 통신에 대한 레퍼런스 및 경험 확보
* 속도: 국내에서 가장 빠른 Setup time. 평균 1초 미만의 Setup time 보장(국내, Browser 기준)
* 품질: 모바일 기기의 성능에 따른 다양한 성능 옵션 선택 가능. 네트워크 상황에 따른 지능적인 동적 품질 적용 엔진. 다양한 코덱 선택 가능(영상: VP8, VP9, H.264, 음성: G.711, Opus(Stereo), ISAC(Mono)

### 개발 용이성

* WebRTC 체계를 만드는데 1달 가까이 걸리는 시간을 절약하고 바로 시작
* Simple한 개발 방식: 3 line(JS), 10 line(Android)
* Config → Connect 2단계로 개발하는 가장 쉬운 ᅟWebRTC 코드

### 세부 기능표

| 분류     | 기능              | 설명                                                            |
| ------ | --------------- | ------------------------------------------------------------- |
| 지원 플랫폼 | Chrome Browser  | Desktop, Android 모두 지원                                        |
|        | Firefox Browser | Desktop, Android 모두 지원                                        |
|        | Naver Whale     | 지원                                                            |
|        | MS Edge Browser | 음성 통신 지원                                                      |
|        | Android         | 최소 18(젤리빈), 권장 21(롤리팝) 이상. ARM기반 단말만 지원                       |
|        | iOS             | 지원(iPad, iPhone), bitcode 미지원                                 |
| 기능     | 1:1 음성 통화       | 지원                                                            |
|        | 1:1 영상 통화       | 지원                                                            |
|        | M:M 영상/음성 통화    | 최소 4:4에서 최대 8:8까지 지원. 기기 성능과 품질 정의에 따라 다름                     |
|        | 1:N 방송          | 지원                                                            |
|        | 채팅              | 1:1만 Text 전송 지원                                               |
|        | TURN서버          | 지원. 글로벌 크라우드 기반 제공. Pricing에 따라 차별화                           |
|        | 실시간 품질 확인       | 현재 통화 상태를 API로 실시간 제공 및 분석결과 제공                               |
|        | 블루투스 헤드셋        | 지원                                                            |
|        | 스피커모드           | 지원(안드로이드). 근접센서에 의해 자동 변환 제공                                  |
|        | 자동 재연결          | 지원 예정. 예기치못한 통화종료시 자동 재연결 기능                                  |
|        | 녹음,녹화 기능        | 지원. RemoteMonster 서버에서 파일 다운로드 지원. Pricing에 따라 차별화            |
| 음성 품질  | 지원 코덱           | G.711, Opus, ISAC                                             |
|        | 최소 네트워크 환경      | 최소 50kbps 이상                                                  |
| 영상 품질  | 지원 코덱           | H.264(기본), VP8, VP9                                           |
|        | 최소 네트워크 환경      | 최소 300kbps 이상                                                 |
|        | 최소 PC 사양        | 듀얼코어 이상의 CPU 벤치마크 사이트 PassMark Software에서 제시한 점수 1000점 이상의 PC |
| 기타 품질  | Latency         | 최소 0.2초 - 최대 1초. 네트워크와 환경에 따라 다를 수 있음                         |
|        | Setup time      | 웹: 1초 미만, 모바일: 1.5초 미만. 네트워크와 기타 환경에 따라 다를 수 있음               |
|        | 지원 국가           | 전세계(전화 통화 제외)                                                 |

### 활용 고객

* [튜터링](http://tutoring.kr/): 모바일 영어회화 어플
* [설레Go](https://play.google.com/store/apps/details?id=net.seole.seolego.user): 대리운전 앱
* [화상운세](https://play.google.com/store/apps/details?id=kr.co.xitech.www.videounse): 모바일 화상기반 역술인 채팅 서비스
* [심리상담](https://play.google.com/store/apps/details?id=com.humart.trost2): 트로스트 - 심리상담, 고민상담 서비스
* [설레는 아침](https://play.google.com/store/apps/details?id=kr.co.marshmallowstudio.seol_a): 소셜 모닝콜 서비스

## 적용 가능 서비스

* 영어회화, 공부방, 교육 서비스
* 뷰티 서비스
* 운세 서비스
* 상담 서비스
* IoT 등 기기연동 서비스
* O2O, 옴니채널 서비스


# Browser

브라우저로 간단한 방송 앱을 개발합니다.

## 준비사항

* node.js
* WebServer (Apache server, python SimpleHttpServer, nginx, harp ...)

## 가장 쉬운 방송앱 개발

* 먼저 웹서버가 서비스할 디렉토리 하나를 선택하거나 만듭니다.
* 해당 디렉토리에서 `npm install @remotemonster/sdk` 실행합니다.
* index.html 파일을 생성하고 코드를 작성합니다.

```markup
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">

  <style>
    video#localVideo { width:auto; height: 80%; background-color: black; }
    button#connectChannelButton { position:absolute; overflow:visible; left:50%; top:10px; }
    html,body { height:100%; }
  </style>
  <title>Remon JS Simple Test</title>
  </head>
  <body>
    <video id="localVideo" autoplay controls class="video"></video>
    <input type="text" class="btn" id="chidText"></input>
    <button id="connectChannelButton" class="btn btn-sm" onclick="start();">Create</button>
  </body>
  <script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
  <script src="https://remotemonster.com/sdk/remon.min.js"></script>
  <script>
  let isConnected = false;
  const chidTextEl = document.querySelector('#chidText');
  const config = { credential: {
      key: '1234567890', serviceId: 'SERVICEID1'
    },
    view: {
      local: '#localVideo'
    },
    media: {
      audio:true,
      video:true,
      sendonly: true
    }
  };
  const listener = {
    onCreateChannel(channelId) {
      console.log(`EVENT FIRED: onCreateChannel: ${channelId}`); 
      chidTextEl.value=channelId;
    }
  }
  const remon = new Remon({ config:config, listener:listener });

  function start() {
    if (isConnected === false){
      isConnected = true;
      document.getElementById("connectChannelButton").innerHTML = "Close";
      remon.createRoom('testroom');
    }else{
      isConnected = false;
      document.getElementById("connectChannelButton").innerHTML = "Create";
      remon.disconnect();
    }
  }
  </script>

</html>
```

* 위의 페이지는 방송을 만드는, 방송하는 쪽 페이지입니다. 통신쪽 소스코드와 다른 점은 localVideo만 태그 정의가 되어있다는 점이고 config에 sendonly 필드가 추가되어 있다는 점입니다.
* 이제 이 디렉토리를 웹서버를 통해서 접근할 차례입니다. 예를 들어 harp server를 사용했다고 합시다.
* harp server 라고 실행하면 포트 9000번을 통해 접근할 수 있죠.
* <http://localhost:9000> 으로 파이어폭스나 크롬 브라우저를 통해 접근해봅시다. 'Create'를 클릭하면?
* 잘 못느낄 수도 있지만 이제 방송이 성공했습니다. 이제 뒤 이어 시청자 앱을 만들어봅시다.
* 같은 디렉토리에 다음과 갈은 페이지를 하나 더 만들어봅시다.

```markup
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">

  <style>
    video#localVideo { width:auto; height: 80%; background-color: black; }
    button#connectChannelButton { position:absolute; overflow:visible; left:50%; top:10px; }
    html,body { height:100%; }
  </style>
  <title>Remon JS Simple Cast Viewer</title>
  </head>
  <body>
    <video id="remoteVideo" autoplay controls class="video"></video>
    <input type="text" class="btn" id="chidText"></input>
    <button id="connectChannelButton" class="btn btn-sm" onclick="start();">Create</button>
  </body>
  <script src="https://remotemonster.com/sdk/remon.min.js"></script>
  <script>
  let isConnected = false;
  const chidTextEl = document.querySelector('#chidText');
  const config = { credential: {
      key: '1234567890', serviceId: 'SERVICEID1'
    },
    view: {
      remote: '#remoteVideo'
    },
    media: {
      audio:true,
      video:true,
      recvonly: true
    }
  };
  const listener = {
    onStateChange(state) {
      if (state === 'INIT'){
        remon.search('');
      }
    },
    onSearch(result){
      var resultObj = JSON.parse(result);
      chidTextEl.value = resultObj[0].id;
    }
  }
  const remon = new Remon({ config:config, listener:listener });

  function start() {
    if (isConnected === false){
      isConnected = true;
      document.getElementById("connectChannelButton").innerHTML = "Close";
      remon.joinRoom(chidTextEl.value);
    }else{
      isConnected = false;
      document.getElementById("connectChannelButton").innerHTML = "Create";
      remon.disconnect();
    }
  }
  </script>

</html>
```

* 위의 페이지는 방송을 시청하는 쪽 페이지입니다. 통신쪽 소스코드와 다른 점은 remoteVideo만 태그 정의가 되어있다는 점이고 config에 recvonly 필드가 추가되어 있다는 점입니다.
* 한가지 특이한 점은 remon.search() 메소드를 통해 현재 방송중인 방 정보를 질의한다는 점입니다. 현재는 RemoteMonster의 공용 id인 SERVICEID1으로 접근하고 있지만 별도로 id를 발급받으면 별도의 id로 독립적인 서비스 안에서의 방 목록을 받을 수 있을 것입니다.
* Listener의 onSearch 메소드를 통해서 search메소드의 결과를 받을 수 있습니다. 이 정보를 통해서 해당 방에 접근할 수 있겠죠.
* 방을 시청하는 메소드는 remon.joinRoom입니다. 이를 통해서 removeView는 시청 내용을 출력합니다.

## Callback 이벤트 처리기 사용하기

Remon은 수많은 일들을 내부적으로 Remote Monster의 서버와 작업하게 됩니다. 때로는 네트워크 상황이 안좋아서 연결이 안될 수도 있고 특별한 이벤트는 귀기울여 수신해야할 필요도 있습니다. 때문에 Remon은 콜백 이벤트 처리기, Listener를 제공하여 이를 통해 다양한 정보를 개발자가 얻을 수 있도록 하고 있습니다.

```javascript
const listener = {
  onInit(token) { ... },
  onCreateChannel(channelId) { ... },
  onConnectChannel(channelId) { ... },
  onComplete() { ... },
  onAddLocalStream(stream) { ... },
  onAddRemoteStream(stream) { ... }, // 상대 영상이 들어왔을 때 처음 발생합니다
  onStateChange(state) { ... },
  onDisconnectChannel(who) { ... }, // 방을 시청중일 때 방이 종료되면 발생합니다
  onError(error) { ... }, // 에러 정보를 반환합니다.
  onStat(result) { ... }, // 방의 품질을 매 5초마다 반환합니다
  onSearch(result) { ... }, // 검색 결과를 반환합니다
};
```


# Android

Android로 간단한 통신 앱을 개발합니다.

## 준비 사항

* 안드로이드 스마트폰 2개
* Android Studio 개발 환경
* 롤리팝 이상의 안드로이드 OS

## 세상에서 가장 쉬운 안드로이드 방송앱 개발

* 안드로이드에서 Remote Monster를 적용하려면 크게 다음과 같은 순서로 개발이 진행됩니다.
  1. Remote Monster Android SDK download center URL을 프로젝트 build.gradle 등록
  2. 모듈 build.gradle에 SDK 다운로드 설정
  3. AndroidMenifest.xml에 권한등의 설정
  4. Config file 생성
  5. Remon 객체 생성
  6. 방송자의 경우 Remon.createRoom, 시청자의 경우 Remon.joinRoom 실행
  7. Callback 메소드 처리하기
* 이제 하나씩 따라해봅시다. 예상 소요시간은 약 30분 입니다.

### 프로젝트 build.gradle에서 repository URL 설정

* 다음과 같이 Remote Monster가 제공하는 안드로이드 SDK repository URL을 등록합니다.
* 이 URL을 통해서 이전 버전의 Remote Monster SDK를 다운로드 받을 수도 있습니다.

```groovy
allprojects {
  repositories {
    jcenter()
    maven {
      url 'https://demo.remotemonster.com/artifactory/libs-release-local'
    }
  }
}
```

* Java SDK는 8버전 이상이 필요합니다.

  **❗❗❗만약 빌드시** `failed to resolve: com.remon:remondroid:x.x.x`**와 같은 에러가 나타난다면** [**여기**](http://community.remotemonster.com/t/topic/34/6?u=seunggi)**를 참고하세요 ❗❗❗**

  **결론적으로 오래된 JDK가 새로운 루트 인증서를 인식 못하는 문제로** [**최신 JDK**](http://www.oracle.com/technetwork/java/javase/downloads/index.html)**로 혹은 JDK 8u101 이상으로 업그레이드하면 됩니다**

### module build.gradle을 수정하기

* 모듈 build.gradle dependencies 항목 마지막 라인에 다음과 같이 한 줄을 추가합니다.

```groovy
compile(group: 'com.remon', name: 'remondroid', version: '1.0.1')
```

* 이제 안드로이드 스튜디오를 동기화하면 자동으로 Remote Monster의 Android SDK인 remondroid를 다운로드 받게 됩니다.

### `AndroidManifest.xml`에 권한 추가

* 아래와 같이 Remondroid가 동작하기 위해 필요한 권한등을 추가합니다.

```markup
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera2" />
<uses-feature android:name="android.hardware.camera.autofocus" />
<uses-feature android:name="android.hardware.camera.flash" />
<uses-feature android:glEsVersion="0x00020000" android:required="true" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-permission android:name="android.permission.BROADCAST_STICKY"/>
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
```

### local과 remote view를 layout에 추가

* 통화와 다르게 방송은 방송자와 시청자의 UX가 분리되는 경우가 많습니다. 때문에 별도의 Activity로 구현할 경우가 많겠죠. 아래 예제에서는 영상을 보여주는 SurfaceViewRenderer를 정의하는 xml파일입니다. PercentFrameLayout은 동적으로 다양한 비율로 화면 크기와 위치를 조절하는 레이아웃이며 실제 영상을 보여주는 뷰는 SurfaceViewRenderer입니다.

```markup
<com.remon.remondroid.PercentFrameLayout android:id="@+id/remote_video_layout"
 android:layout_width="match_parent"
 android:layout_height="match_parent">
 <org.webrtc.SurfaceViewRenderer
 android:id="@+id/remote_video_view"
 android:layout_width="wrap_content"
 android:layout_height="wrap_content" />
</com.remon.remondroid.PercentFrameLayout>
```

### Remon의 Config 객체 생성

* 이제 코딩의 시간입니다. 먼저 사전에 환경 설정을 할 필요가 있습니다. View가 시작될 때 다음과 같이 Config객체를 생성합니다. Key와 ServiceID는 넣지 않아도 됩니다. 자동으로 테스트용 key와 serviceId로 설정됩니다. 나중에 본격적으로 Remote Monster를 사용하고 싶다면 회원가입을 하고 키를 발급받아서 Config에 입력하면 됩니다.
* Config 객체를 통해 다양한 환경 설정을 할 수 있습니다. 여기서는 일단 앞서 설정한 Local, Remote View를 설정하였습니다.

```java
Config config = new com.remon.remondroid.Config();
//config.setKey("1234567890");
//config.setServiceId("SERVICEID1");
config.setLocalView((SurfaceViewRenderer) findViewById(R.id.local_video_view));
config.setRemoteView((SurfaceViewRenderer) findViewById(R.id.remote_video_view));
```

### Remon 객체 생성

* 이제 Remon 객체를 생성합니다. 주로 View 시작시에 먼저 객체를 생성할 것을 권합니다.
* Remon객체 생성시 앞서 설정하였던 Config객체를 인자로 넣어주고 기타 Observer는 그냥 다음에 설명하겠습니다.
* Observer는 Callback 클래스인데 굳이 넣고 싶지 않다면 RemonObserver를 new 해도 됩니다.

```java
// create channel
//remon = new Remon(MainActivity.this, config, new MyObserver());
remon = new Remon(MainActivity.this, config, new RemonObserver());
// 혹은
remon = new Remon(MainActivity.this, config);
remon.init(new RemonObserver());
```

### 방 만들기 혹은 시청하기

* 이제 방을 만들거나 이미 만들어진 방을 시청할 차례입니다. createRoom(name)은 방을 만드는 명령입니다. 또한 joinRoom(channelId)는 이미 만들어진 방에 접속하는 방법입니다. createRoom의 인자는 방의 닉네임입니다. id가 아닙니다. 방을 만들고 나면 Observer의 onCreate()메소드를 통해서 방의 실제 유일한 id인 채널 id를 발급받게 됩니다. 이 채널 id를 가지고 joinRoom때 쓰게 됩니다.

```java
// 방을 만들기
remon.createRoom(“myroom”);
// 방을 시청하기
remon.joinRoom("CH12322213213213123");
```

### onDestroy 처리

* 방송과 시청이 끝났을 경우 꼭 remon객체를 close해주어야 합니다. close를 통해서 모든 방송자원과 미디어 스트림 자원이 해제됩니다.

```java
remon.close();
```

### 권한에 대한 고객 확인창 처리

* 안드로이드 최신 버전의 경우 앱의 권한에 대해 처음 앱 사용시 사용자에게 직접 묻게 됩니다. 이를 위한 처리도 필요하겠죠.
* 안드로이드 개발자인 당신이 가장 선호하는 방식으로 이것을 처리하면 됩니다. 처리해야할 권한은 다음과 같습니다.

```java
public static final String[] MANDATORY_PERMISSIONS = {
  "android.permission.INTERNET",
  "android.permission.CAMERA",
  "android.permission.RECORD_AUDIO",
  "android.permission.MODIFY_AUDIO_SETTINGS",
  "android.permission.ACCESS_NETWORK_STATE",
  "android.permission.CHANGE_WIFI_STATE",
  "android.permission.ACCESS_WIFI_STATE",
  "android.permission.READ_PHONE_STATE",
  "android.permission.BLUETOOTH",
  "android.permission.BLUETOOTH_ADMIN",
  "android.permission.WRITE_EXTERNAL_STORAGE"
};
```

### RemonObserver 클래스 생성

* Remote Monster를 통해 오고가는 모든 방송과정의 이벤트를 수신할 필요가 있습니다. 이를 위해 RemonObserver에서 상속받은 별도의 Callback 클래스를 만들어봅시다.

```java
public class MyObserver extends RemonObserver {
  @Override
  public void onError(Throwable t) {
    super.onError(t);
  }
}
```

* RemonObserver를 통해 처리하면 좋은 메소드는 다음과 같습니다.
  * onStateChange: 최초 Remon객체를 만들고 방을 만들며 접속하고 접속에 성공하고 통신을 마칠 때까지의 모든 상태 변화에 대해 처리하는 메소드입니다. RemonState enum객체를 통해 어떤 상태로 변경되었는지를 알려줍니다. RemonState의 상태는 다음과 같습니다.
    * INIT(시작), WAIT(방 생성), CONNECT(방 접속), COMPLETE(방송 연결완료), FAIL(실패), CLOSE(종료)
  * onError: 통신 시도 중 장애 발생시 호출됩니다.
  * onAddLocalStream: 자기 자신의 카메라의 영상이 혹은 음성 스트림을 획득하였을 경우 호출됩니다.
  * onAddRemoteStream: 상대방의 영상이나 음성 스트림을 획득하였을 경우 호출됩니다. 연결이 되었다는 뜻이죠.
  * onSearch: Remon.search메소드를 통해 검색 결과를 받을 때 사용합니다


# iOS

## 준비 사항

* Xcode 개발 환경
* iOS 9.2 이상

## 프로젝트 생성 및 설정

* Xcode에서 Swift기반의 프로젝트를 하나 생성합니다.
* 프로젝트 생성 후 Build Settings에서 bitcode에 대하여 No를 설정해야 합니다. (Remon SDK 0.1.6 부터는 bitcode를 지원함)

![bitcode](/files/-LAlY6SV2kKLXP4QpHf8)

* 또한 Info.plist에서 다음 항목에 대해 추가 혹은 변경을 해주셔야 합니다.
  * Privacy: Bluetooth, Microphone, Camera

![settings](/files/-LAlY6TgO3P-QI0AZpuK)

## Cocoapods을 이용한 SDK 설치

SDK 설치를 원하는 프로젝트의 Podfile에 `pod 'Remon-iOS-SDK', '~> 2.0`을 추가 하거나

```
target 'MyApp' do
  pod 'Remon-iOS-SDK', '~> 2.0'
end
```

을 추가 합니다. 그리고 터미널에서 *pod install* 를 실행 합니다. 만약 *pod install* 이 동작하지 않는 다면 *pod update*를 실행 합니다.

## 직접 RemoteMonster iOS SDK import하기 (Without Cocoapods)

* 다운로드받은 RemoteMonster iOS SDK를 압축을 풀면 2개의 Framework이 존재합니다. 각각의 Framework을 Finder에서 끌어다 프로젝트 트리창에 놓습니다. 그러면 RemoteMonster iOS SDK를 프레임워크로 인식하게 됩니다.

![framework](/files/-LAlY6WK9lIxJ6dc9gwn)

## Remon 설정 및 레이아웃 구성

Remon은 RemonIBController를 이용하여 InterfaceBuilder를 이용한 설정이 가능 합니다.

* 스토리보드에 RemonIBController의 하위객체인 RemonCall 또는 RemonCast를 추가합니다.
  * RemonCall를 1:1 통신을 지원 하며 RemonCast는 1:N 방송을 지원 합니다.
  * InterfaceBuilder에서 Utilities 뷰를 이용하여 Remon을 설정 합니다.
  * Service ID와 Service Key를 필수 설정값입니다. 발급 받으신 ID와 Key를 입력 합니다.

![](/files/-LChk7L2J9ppJvCU5IPw)

* 스토리보드에서 원하는 Scene에서 원하는 위치에 Veiw를 배치하고 RemonIBController의 remoteView와 localView에 바인딩 하여 줍니다.

![](/files/-LChnrnzniscflk9Y_6B)

* Remon를 사용하는 ViewContoller에 remonios를 임포트 하고, RemonIBController객체를 아웃렛 변수에 바인딩 합니다.

![](/files/-LChnn6k5syWuRD-MTIN)

## 개발

* Remon 설정이 완료 되었다면 실제 개발은 쉽습니다. ViewContoller에서 방송 또는 통신을 시도 하세요!

```
remonCall.connetChannel("channelID")
```

#### InterfaceBuilder를 이용하지 않아도 Remon을 이용할 수 있습니다.

```
let caster = RemonCast()
caster.serviceId = "YourServiceID"
caster.serviceKey = "YourServiceKey"
caster.broadcast = true
caster.localView = localView
caster.createRoom()
```

* RemonIBController에 값을 직접 설정 않고, connectChannel(), createRoom(), joinRoom() 함수에 RemonConfig를 전달 할 수도 있습니다. 이 경우에는  RemonIBContoller 인스턴스에 설정된 값이 무시되고 전달된 config 정보를 이용합니다.

```
let viewer = RemonCast()
viewer.remoteView = remoteView
let config = RemonConfig()
config.serviceId = "YourServiceID"
config.key = "YourServiceKey"
config.channelType = "VIEWER"
caster.joinRoom(config)
```


# Browser

브라우저로 간단한 통신 앱을 개발합니다.

## 준비사항

* node.js
* WebServer (Apache server, python SimpleHttpServer, nginx, harp ...)

## 가장 쉬운 통화앱 개발

* 먼저 웹서버가 서비스할 디렉토리 하나를 선택하거나 만듭니다.
* 해당 디렉토리에서 `npm install @remotemonster/sdk` 실행합니다.
* index.html 파일을 생성하고 코드를 작성합니다.

```markup
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">

  <style>
    video#remoteVideo { width:auto; height: 80%; background-color: black; }
    button#connectChannelButton { position:absolute; overflow:visible; left:50%; top:10px; }
    html,body { height:100%; }
  </style>

  <script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
  <script src="https://remotemonster.com/sdk/remon.min.js"></script>
  <script>
  let isConnected = false;
  const config = { credential: {
      key: '1234567890', serviceId: 'SERVICEID1'
    },
    view: {
      remote: '#remoteVideo'
    },
  };
  const remon = new Remon({ config:config });

  function start() {
    if (isConnected === false){
      isConnected = true;
      document.getElementById("connectChannelButton").innerHTML = "Close";
      remon.connectChannel("simpleRemon");
    }else{
      isConnected = false;
      document.getElementById("connectChannelButton").innerHTML = "Connect";
      remon.disconnect();
    }
  }
  </script>
  <title>Remon JS Simple Test</title>
</head>
<body>
  <video id="remoteVideo" autoplay controls class="video"></video>
  <button id="connectChannelButton" class="btn btn-sm" onclick="start();">Connect</button>
</body>
</html>
```

* 이제 이 디렉토리를 웹서버를 통해서 접근할 차례입니다. 예를 들어 harp server를 사용했다고 합시다.
* harp server 라고 실행하면 포트 9000번을 통해 접근할 수 있죠.
* <http://localhost:9000> 으로 파이어폭스나 크롬 브라우저를 통해 접근해봅시다. 2개의 탭으로 접근하거나 아예 다른 창으로 각각 'Connect'를 클릭하면?
* 첫번째 Remon 앱을 만든 것을 축하합니다.

## 소스를 살펴보기

### Config

먼저 설정 객체를 만듭니다.

```javascript
const config = { credential: {
    key: '1234567890', serviceId: 'SERVICEID1'
  },
  view: {
    remote: '#remoteVideo'
  },
};
```

* key값은 Remote Monster에 회원가입하여 받게 되는 비밀번호입니다. serviceId값은 Remote Monster에 회원가입시 입력한 자신의 서비스 id입니다. 잘 모른다면 `credential` 객체를 설정 않해도 무방합니다.
* view 항목에 보면 video 태그의 id를 설정합니다. 원격에서 상대방의 영상이 수신되면 그 영상을 출력할 video 태그의 id입니다.

만들어둔 설정을 인자로 하여 Remon 객체를 생성 합니다.

* `const remon = new Remon({ config:config });`

### Connect

이제 방에 들어갈 시간입니다. connectChannel 메소드는 입력값에 해당하는 방으로 들어가는 명령을 수행합니다.

* `remon.connectChannel("simpleRemon");`

혹은 방에서 나옵니다.

* `remon.disconnect()`
* 방에 들어가는 명령이 있다면 나오는 명령이 있겠죠. disconnect는 바로 들어갔던 방에서 나오는 명령입니다
* 이제 모든 것이 끝났습니다. Remote Monster의 Javascript API는 이것만으로도 통신의 모든 것을 완벽히 수행합니다. 물론 더 자세한 조작은 필요하겠죠?

## LocalVideo 생성

앞서 예제는 상대편 video만 있어서 연결하기 전에는 나의 얼굴을 확인할 수 없었습니다. 이제 나의 video tag를 삽입하고 그 tag의 id를 RemoteMonster에게 알려줍시다.

`html body`에 다음과 같이 `local Video`를 추가합니다.

* `<video id=\"localVideo\" autoplay controls class=\"video\">`

그리고 config를 다음과 같이 수정합니다.

```javascript
const config = {
  credential: {
    key: '1234567890', serviceId: 'SERVICEID1'
  },
  view: {
    remote: '#remoteVideo', local:'#localVideo'
  },
};
```

## Callback 이벤트 처리기 사용하기

Remon은 수많은 일들을 내부적으로 Remote Monster의 서버와 작업하게 됩니다. 때로는 네트워크 상황이 안좋아서 연결이 안될 수도 있고 특별한 이벤트는 귀기울여 수신해야할 필요도 있습니다. 때문에 Remon은 콜백 이벤트 처리기, Listener를 제공하여 이를 통해 다양한 정보를 개발자가 얻을 수 있도록 하고 있습니다.

```javascript
const listener = {
  onInit(token) { ... },
  onCreateChannel(channelId) { ... },
  onConnectChannel(channelId) { ... },
  onComplete() { ... },
  onAddLocalStream(stream) { ... },
  onAddRemoteStream(stream) { ... },
  onStateChange(state) { ... },
  onDisconnectChannel() { ... },
  onMessage(message) { ... },
  onError(error) { ... },
  onStat(result) { ... },
  onSearch(result) { ... },
};
```

위에서 만든 설정과 리스너를 인자로 하여 다시 시작해봅시다.

```javascript
remon.init({ config:config, listener:listener });
...
remon.connectChannel("simpleRemon");
```


# Android

Android로 간단한 방송 앱을 개발합니다.

## 준비 사항

* 안드로이드 스마트폰 2개
* Android Studio 개발 환경
* 롤리팝 이상의 안드로이드 OS

## 세상에서 가장 쉬운 안드로이드 통화앱 개발

* 안드로이드에서 Remote Monster를 적용하려면 크게 다음과 같은 순서로 개발이 진행됩니다.
  1. Remote Monster Android SDK download center URL을 프로젝트 build.gradle 등록
  2. 모듈 build.gradle에 SDK 다운로드 설정
  3. AndroidMenifest.xml에 권한등의 설정
  4. 영상 통화인 경우 layout file에 영상전용 View 등록
  5. Config file 생성
  6. Remon 객체 생성
  7. Remon.connect 실행
  8. Callback 메소드 처리하기
* 이제 하나씩 따라해봅시다. 예상 소요시간은 약 30분 입니다.

### 프로젝트 build.gradle에서 repository URL 설정

* 다음과 같이 Remote Monster가 제공하는 안드로이드 SDK repository URL을 등록합니다.
* 이 URL을 통해서 이전 버전의 Remote Monster SDK를 다운로드 받을 수도 있습니다.

```groovy
allprojects {
  repositories {
    jcenter()
    maven {
      url 'https://demo.remotemonster.com/artifactory/libs-release-local'
    }
  }
}
```

**❗❗❗만약 빌드시** `failed to resolve: com.remon:remondroid:x.x.x`**와 같은 에러가 나타난다면** [**여기**](http://community.remotemonster.com/t/topic/34/6?u=seunggi)**를 참고하세요 ❗❗❗** **결론적으로 오래된 JDK가 새로운 루트 인증서를 인식 못하는 문제로** [**최신 JDK**](http://www.oracle.com/technetwork/java/javase/downloads/index.html)**로 혹은 JDK 8u101 이상으로 업그레이드하면 됩니다**

### module build.gradle을 수정하기

* 모듈 build.gradle dependencies 항목 마지막 라인에 다음과 같이 한 줄을 추가합니다.

```groovy
compile(group: 'com.remon', name: 'remondroid', version: '0.2.27')
```

* 이제 안드로이드 스튜디오를 동기화하면 자동으로 Remote Monster의 Android SDK인 remondroid를 다운로드 받게 됩니다.

### `AndroidManifest.xml`에 권한 추가

* 아래와 같이 Remondroid가 동작하기 위해 필요한 권한등을 추가합니다.

```markup
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera2" />
<uses-feature android:name="android.hardware.camera.autofocus" />
<uses-feature android:name="android.hardware.camera.flash" />
<uses-feature android:glEsVersion="0x00020000" android:required="true" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-permission android:name="android.permission.BROADCAST_STICKY"/>
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
```

### local과 remote view를 layout에 추가

* 만약 영상통화를 한다면 보통 자신의 화면과 원격의 화면을 동시에 보여주고 싶을 것입니다. 때문에 미리 자신이 원하는 View에 다음과 같이 로컬과 원격의 뷰를 추가합니다. PercentFrameLayout은 동적으로 다양한 비율로 화면 크기와 위치를 조절하는 레이아웃이며 실제 영상을 보여주는 뷰는 SurfaceViewRenderer입니다.

```markup
<com.remon.remondroid.PercentFrameLayout android:id="@+id/remote_video_layout"
 android:layout_width="match_parent"
 android:layout_height="match_parent">
 <org.webrtc.SurfaceViewRenderer
 android:id="@+id/remote_video_view"
 android:layout_width="wrap_content"
 android:layout_height="wrap_content" />
</com.remon.remondroid.PercentFrameLayout>
<com.remon.remondroid.PercentFrameLayout android:id="@+id/local_video_layout"
 android:layout_width="match_parent"
 android:layout_height="match_parent">
 <org.webrtc.SurfaceViewRenderer
 android:id="@+id/local_video_view"
 android:layout_width="wrap_content"
 android:layout_height="wrap_content" />
</com.remon.remondroid.PercentFrameLayout>
```

### Remon의 Config 객체 생성

* 이제 코딩의 시간입니다. 먼저 사전에 환경 설정을 할 필요가 있습니다. View가 시작될 때 다음과 같이 Config객체를 생성합니다. Key와 ServiceID는 넣지 않아도 됩니다. 자동으로 테스트용 key와 serviceId로 설정됩니다. 나중에 본격적으로 Remote Monster를 사용하고 싶다면 회원가입을 하고 키를 발급받아서 Config에 입력하면 됩니다.
* Config 객체를 통해 다양한 환경 설정을 할 수 있습니다. 여기서는 일단 앞서 설정한 Local, Remote View를 설정하였습니다.
* 필요에 따라서는 영상통화가 아닌 음성통화만 사용하게 할 수도 있고 코덱을 바꾸거나 해상도를 바꿀 수도 있습니다.

```java
Config config = new com.remon.remondroid.Config();
//config.setKey("1234567890");
//config.setServiceId("SERVICEID1");
config.setLocalView((SurfaceViewRenderer) findViewById(R.id.local_video_view));
config.setRemoteView((SurfaceViewRenderer) findViewById(R.id.remote_video_view));
```

### Remon 객체 생성

* 이제 Remon 객체를 생성합니다. 주로 View 시작시에 먼저 객체를 생성할 것을 권합니다.
* Remon객체 생성시 앞서 설정하였던 Config객체를 인자로 넣어주고 기타 Observer는 그냥 다음에 설명하겠습니다.
* Observer는 Callback 클래스인데 굳이 넣고 싶지 않다면 RemonObserver를 new 해도 됩니다.

```java
// create channel
//remon = new Remon(MainActivity.this, config, new MyObserver());
remon = new Remon(MainActivity.this, config, new RemonObserver());
```

### Connect

* 이제 방을 만들거나 이미 만들어진 방에 들어갈 차례입니다. connect에는 크게 두가지 방식이 있습니다. 방이름을 입력하지 않고 connect명령을 할 경우 RemoteMonster는 임의의 방이름을 생성해서 반환값으로 방이름을 반환합니다. 다음에 그 방으로 입장하고 싶은 이는 그 반환된 값으로 connect할 때 사용하여 connect하면 됩니다. 그렇지 않고 직접 방 이름을 넣어서 방을 생성하거나 방을 접속할 수도 있습니다.

```java
// connect Channel with channel name
remon.connectChannel(“myroom”);
```

### onDestroy 처리

* 모든 통신이 끝났을 경우 꼭 remon객체를 close해주어야 합니다. close를 통해서 모든 통신자원과 미디어 스트림 자원이 해제됩니다.

```java
remon.close();
```

### 권한에 대한 고객 확인창 처리

* 안드로이드 최신 버전의 경우 앱의 권한에 대해 처음 앱 사용시 사용자에게 직접 묻게 됩니다. 이를 위한 처리도 필요하겠죠.
* 안드로이드 개발자인 당신이 가장 선호하는 방식으로 이것을 처리하면 됩니다. 처리해야할 권한은 다음과 같습니다.

```java
public static final String[] MANDATORY_PERMISSIONS = {
  "android.permission.INTERNET",
  "android.permission.CAMERA",
  "android.permission.RECORD_AUDIO",
  "android.permission.MODIFY_AUDIO_SETTINGS",
  "android.permission.ACCESS_NETWORK_STATE",
  "android.permission.CHANGE_WIFI_STATE",
  "android.permission.ACCESS_WIFI_STATE",
  "android.permission.READ_PHONE_STATE",
  "android.permission.BLUETOOTH",
  "android.permission.BLUETOOTH_ADMIN",
  "android.permission.WRITE_EXTERNAL_STORAGE"
};
```

### RemonObserver 클래스 생성

* Remote Monster를 통해 오고가는 모든 통신과정의 이벤트를 수신할 필요가 있습니다. 이를 위해 RemonObserver에서 상속받은 별도의 Callback 클래스를 만들어봅시다.

```java
public class MyObserver extends RemonObserver {
  @Override
  public void onError(Throwable t) {
    super.onError(t);
  }
}
```

* RemonObserver를 통해 처리하면 좋은 메소드는 다음과 같습니다.
  * onStateChange: 최초 Remon객체를 만들고 방을 만들며 접속하고 접속에 성공하고 통신을 마칠 때까지의 모든 상태 변화에 대해 처리하는 메소드입니다. RemonState enum객체를 통해 어떤 상태로 변경되었는지를 알려줍니다. RemonState의 상태는 다음과 같습니다.
    * INIT(시작), WAIT(방 생성), CONNECT(방 접속), COMPLETE(통신 연결완료), FAIL(실패), CLOSE(종료)
  * onError: 통신 시도 중 장애 발생시 호출됩니다.
  * onAddLocalStream: 자기 자신의 카메라의 영상이 혹은 음성 스트림을 획득하였을 경우 호출됩니다.
  * onAddRemoteStream: 상대방의 영상이나 음성 스트림을 획득하였을 경우 호출됩니다. 연결이 되었다는 뜻이죠.


# iOS

## 준비 사항

* Xcode 개발 환경
* iOS 9.2 이상

## 프로젝트 생성 및 설정

* Xcode에서 Swift기반의 프로젝트를 하나 생성합니다.
* 프로젝트 생성 후 Build Settings에서 bitcode에 대하여 No를 설정해야 합니다. (Remon SDK 0.1.6 부터는 bitcode를 지원함)

![bitcode](/files/-LCmAW1qFrttzZZxfXuP)

* 또한 Info.plist에서 다음 항목에 대해 추가 혹은 변경을 해주셔야 합니다.
  * Privacy: Bluetooth, Microphone, Camera

![settings](/files/-LCmAW3iIm6HarX1pq1P)

## Cocoapods을 이용한 SDK 설치

SDK 설치를 원하는 프로젝트의 Podfile에 `pod 'Remon-iOS-SDK', '~> 2.0`을 추가 하거나

```
target 'MyApp' do
  pod 'Remon-iOS-SDK', '~> 2.0'
end
```

을 추가 합니다. 그리고 터미널에서 *pod install* 를 실행 합니다. 만약 *pod install* 이 동작하지 않는 다면 *pod update*를 실행 합니다.

## 직접 RemoteMonster iOS SDK import하기 (Without Cocoapods)

* 다운로드받은 RemoteMonster iOS SDK를 압축을 풀면 2개의 Framework이 존재합니다. 각각의 Framework을 Finder에서 끌어다 프로젝트 트리창에 놓습니다. 그러면 RemoteMonster iOS SDK를 프레임워크로 인식하게 됩니다.

![framework](/files/-LCmAW8m8lqaitbaUamL)

## Remon 설정 및 레이아웃 구성

Remon은 RemonIBController를 이용하여 InterfaceBuilder를 이용한 설정이 가능 합니다.

* 스토리보드에 RemonIBController의 하위객체인 RemonCall 또는 RemonCast를 추가합니다.
  * RemonCall를 1:1 통신을 지원 하며 RemonCast는 1:N 방송을 지원 합니다.
  * InterfaceBuilder에서 Utilities 뷰를 이용하여 Remon을 설정 합니다.
  * Service ID와 Service Key를 필수 설정값입니다. 발급 받으신 ID와 Key를 입력 합니다.

![](/files/-LCmAWAwjUQDTDvJDx1y)

* 스토리보드에서 원하는 Scene에서 원하는 위치에 Veiw를 배치하고 RemonIBController의 remoteView와 localView에 바인딩 하여 줍니다.

![](/files/-LCmAWCZdQADOpw_nMDy)

* Remon를 사용하는 ViewContoller에 remonios를 임포트 하고, RemonIBController객체를 아웃렛 변수에 바인딩 합니다.

![](/files/-LCmAWEgrYA9vOTyLbKV)

## 개발

* Remon 설정이 완료 되었다면 실제 개발은 쉽습니다. ViewContoller에서 방송 또는 통신을 시도 하세요!

```
remonCall.connetChannel("channelID")
```

### InterfaceBuilder를 이용하지 않아도 Remon을 이용할 수 있습니다.

```
let caster = RemonCast()
caster.serviceId = "YourServiceID"
caster.serviceKey = "YourServiceKey"
caster.broadcast = true
caster.localView = localView
caster.createRoom()
```

* RemonIBController에 값을 직접 설정 않고, connectChannel(), createRoom(), joinRoom() 함수에 RemonConfig를 전달 할 수도 있습니다. 이 경우에는  RemonIBContoller 인스턴스에 설정된 값이 무시되고 전달된 config 정보를 이용합니다.

```
let viewer = RemonCast()
viewer.remoteView = remoteView
let config = RemonConfig()
config.serviceId = "YourServiceID"
config.key = "YourServiceKey"
config.channelType = "VIEWER"
caster.joinRoom(config)
```


# Overview

* Remon클래스는 가장 핵심이 되는 클래스로서 다음과 같은 메소드를 제공합니다.

  ```javascript
  // 생성자
  class Remon({config:config, listener:listener})
  ```
* Config 정보, 콜백메소드 처리를 위한 Listener를 입력값으로 넣으면 원격의 리모트몬스터 서버와 연결이 됩니다.
* 연결이 완료되면 listener의 onInit메소드와 onStateChange메소드가 호출됩니다.
* 만약 객체가 생성후 바로 상대편과 연결을 하고 싶다면 onInit메소드 때 Remon의 connectChannel 메소드를 호출하면 됩니다.

  ```javascript
  // 방에 통화 연결
  Remon.connectChannel(String channelId)
  ```
* 방에 접속하거나 방을 만드는 명령입니다. 주어진 이름의 방이 없을 경우 방을 만들고 이미 방이 있을 경우 방에 접속합니다. 물론 사전에 방 이름이 있어야 하겠지만 만약 방 이름이 없을 경우 RemoteMonster는 고유한 방 이름을 생성해서 listener의 onCreateChannel메소드를 통해 방이름을 반환하게 됩니다.
* 메소드는 하나이지만 방이름이 이미 존재하는지 여부에 따라 없으면 listener의 onCreateChannel메소드, 있으면 onConnectChannel 메소드가 호출된다고 보면 되며, onConnectChannel메소드 호출시 상대편과 연결이 완료되었음을 의미합니다.
* 상대편과 통화 연결에 완전히 성공하는 경우 Observer의 onStateChange의 STATE.COMPLETE 상태가 입력값으로 들어옵니다. 이 때 필요한 서비스를 진행하면 됩니다.

  ```javascript
  Remon.createRoom(roomname); // 방송용 방 만들기
  Remon.joinRoom(room id); //방송용 방 시청하기
  ```
* 방송용 방을 만들거나 시청하는 명령입니다.

```javascript
// 방을 검색
Remon.search(String channelId)
```

* 주어진 인자값에 해당하는 방이름이 있는지 검색하여 알려줍니다. 인자값이 없으면 모든 방 정보를 알려줍니다.
* 검색의 결과는 Observer의 onSearch를 통해서 받을 수 있습니다.

  ```javascript
  pauseLocalVideo(bool)
  pauseRemoteVideo(bool)
  muteLocalAudio(bool)
  muteRemoteAudio(bool)
  ```
* 로컬 영상/음성을 잠시 pause하거나 다시 실행시키는 기능입니다.

```javascript
Remon.close();
```

* Remon을 사용한 이후에는 반드시 close를 해주어야 합니다. 통신에 문제가 생기면 알아서 close가 되기도 하지만 남아있는 자원이 만에 하나 있다면 기기 성능에 아무래도 영향을 미칠 수 있습니다.
* 재연결이 필요한 경우에도 close를 하고 다시 Remon객체를 생성하는 것을 권합니다.
* 명시적으로 close를 할 경우 상대 peer에게 onDisconnectChannel 이벤트가 발생합니다. 또한 명시적이지 않고 브라우저를 그냥 닫았다면  상대편에게는 onStateChange 이벤트가 발생하면서 STATE.CLOSE 혹은 STATE.FAIL의 이벤트가 발생합니다. 만약 네트워크가 안좋아졌거나 끊어지면 상대편에게 onError의 type=ICEFailedError이 발생합니다.

```javascript
Remon.sendMessage(userMessage)
```

* 연결이 된 상태에서 상대에게 메시지를 전달할 수 있습니다.
* 메시지를 받는 것은 listener의 onMessage를 통해서 받을 수 있습니다. 주로 통신용으로만 사용합니다


# Config


# Examples


# Debug inside


# Overview

안드로이드에서 개발개요를 설명합니다.

## Remon Class

* Remon클래스는 가장 핵심이 되는 클래스로서 다음과 같은 메소드를 제공합니다.

  ```java
  // 생성자
  public Remon(Context ctx, Config config, RemonObserver observer)
  ```
* 안드로이드 컨텍스트와 리모트몬스터 Config, 콜백메소드 처리를 위한 Observer를 입력값으로 넣으면 원격의 리모트몬스터 서버와 연결이 됩니다.
* 연결이 완료되면 Observer의 onInit메소드와 onStateChange메소드가 호출됩니다.
* 만약 객체가 생성후 바로 상대편과 연결을 하고 싶다면 onInit메소드 때 Remon의 connectChannel 메소드를 호출하면 됩니다.

  ```java
  // 방에 통화 연결
  public void connectChannel(String channelId)
  ```
* 통신용으로 쓰는 기능입니다. 방에 접속하거나 방을 만드는 명령입니다. 주어진 이름의 방이 없을 경우 방을 만들고 이미 방이 있을 경우 방에 접속합니다. 물론 사전에 방 이름이 있어야 하겠지만 만약 방 이름이 없을 경우 RemoteMonster는 고유한 방 이름을 생성해서 Observer의 onCreateChannel메소드를 통해 방이름을 반환하게 됩니다.
* 메소드는 하나이지만 방이름이 이미 존재하는지 여부에 따라 없으면 Observer의 onCreateChannel메소드, 있으면 onConnectChannel 메소드가 호출된다고 보면 되며, onConnectChannel메소드 호출시 상대편과 연결이 완료되었음을 의미합니다.
* 상대편과 통화 연결에 완전히 성공하는 경우 Observer의 onStateChange의 STATE.COMPLETE 상태가 입력값으로 들어옵니다. 이 때 필요한 서비스를 진행하면 됩니다.

  ```java
  // 방송용 방을 개설
  public void createRoom(String name)
  // 방 시청하기
  public void joinRoom(String channelId)
  ```

```java
// 방을 검색
public void searchChannels(String channelId)
```

* 주어진 인자값에 해당하는 방이름이 있는지 검색하여 알려줍니다. 인자값이 없으면 모든 방 정보를 알려줍니다.
* 검색의 결과는 Observer의 onSearchChannels를 통해서 받을 수 있습니다.

  ```java
  public void sendMessage(String msg);
  ```
* 통신 중에 상대방에게 문자 형태의 메시지를 전달합니다. 이 기능을 활용하여 채팅에 활용하거나 내부 로직에 사용할 수 있습니다.
* Observer의 onMessage 메소드를 통해서 상대편의 메시지를 수신할 수 있습니다.

  ```java
  public void setVideoEnabled(boolean enable)
  public void setLocalVideoEnabled(boolean enable)
  public void setRemoteVideoEnabled(boolean enable)
  public void setAudioEnabled(boolean enable)
  public void setMicMute(boolean mute);
  ```
* 로컬/리모트 영상, 음성을 잠시 pause하거나 다시 실행시키는 기능입니다. 아울러 자신의 기기의 마이크를 잠시 꺼두는 기능도 제공합니다. setVideoEnabled는 전체 영상을 끄고 킬 수 있습니다.

  ```java
  public void switchCamera();
  ```
* 로컬 카메라가 전면과 후면이 있을 경우 이것을 토글시켜서 변경하는 것이 가능합니다.

  ```java
  public void showLocalVideo();
  ```
* Remon 객체 생성시 Local video를 활성화하고 싶다면 showLocalvideo메소드를 호출합니다. connectChannel하기 전에 미리 자신의 카메라 화면을 볼 수 있습니다.

  ```java
  public void close();
  ```
* Remon을 사용한 이후에는 반드시 close를 해주어야 합니다. 통신에 문제가 생기면 알아서 close가 되기도 하지만 남아있는 자원이 만에 하나 있다면 기기 성능에 아무래도 영향을 미칠 수 있습니다.
* 재연결이 필요한 경우에도 close를 하고 다시 Remon객체를 생성하는 것을 권합니다.
* 명시적으로 close를 할 경우 상대 peer에게 onDisconnect 이벤트가 발생합니다. 또한 명시적이지 않고 브라우저를 그냥 닫았다면  상대편에게는 onStateChange 이벤트가 발생하면서 STATE.CLOSE 혹은 STATE.FAIL의 이벤트가 발생합니다. 만약 네트워크가 안좋아졌거나 끊어지면 상대편에게 onError의 type=ICEFailedError이 발생합니다.

## RemonFactory: 멀티채팅 구현시

* 여러개의 Remon객체를 생성하여 다중 채널, 멀티 채팅을 구현하고자 한다면 Remonfactory를 사용하여 Remon객체를 생성하기 바랍니다.
* RemonFactory는 여러 Remon객체의 자원을 관리하므로 직접 Remon객체를 여러개 만드는 것보다 수월하게 Remon객체의 수명주기를 관리할 수 있습니다.
* 마찬가지로 개별 Remon객체의 close는 RemonFactory.close 메소드를 활용해야 합니다.


# Config

안드로이드에서 설정방법을 소개합니다.

## Config

* RemoteMonster는 객체 생성 전에 config값을 선행적으로 받습니다. 가장 단순하게는

  ```java
  Config config = new com.remon.remondroid.Config();
  config.setLocalView((SurfaceViewRenderer) findViewById(R.id.local_video_view));
  config.setRemoteView((SurfaceViewRenderer) findViewById(R.id.remote_video_view));
  ```
* 와 같이 영상을 출력할 video view를 설정하는 것만으로 RemoteMonster는 잘 알아듣고 config처리를 합니다. 하지만 실제 서비스를 위해서 RemoteMonster는 더 많은 설정값을 요구합니다. 이를테면 서비스 인증을 위한 키값을 요구합니다.

  ```java
  Config config = new com.remon.remondroid.Config();
  config.setServiceId("simpleapp");
  config.setKey("blahblah");
  ```
* 위와 같이 config는 view 항목뿐 아니라 인증값을 요구합니다. 따라서 되도록이면 홈페이지에서 별도의 인증키를 발급받으시는 것을 추천합니다. 이를 통해 더 나은 서비스를 제공받을 수 있습니다.
* key는 리모트몬스터로 부터 발급받는 비밀키입니다. serviceId는 여러분이 리모트몬스터에 서비스 가입을 할 때 입력하는 값입니다. 즉 당신의 id값이라고 보면 됩니다.
* 이제 이 config에 더하여 음성과 영상에 대한 보다 다양한 옵션을 살펴봅시다.

  ```java
  config.setVideoCall(true);
  ```
* 영상통신이 필요없고 음성통신만 필요한 경우에는 videoCall항목을 false로 합니다.

  ```java
  config.setVideoWidth(640);
  config.setVideoHeight(480);
  ```
* width와 height는 상대편에게 보낼 영상의 해상도를 결정하는 것입니다. 최대 640 480의 해상도로 보낼 것을 설정하였지만 이것이 꼭 지켜지는 것은 아닙니다. WebRTC는 기본적으로 네트워크나 단말의 상태에 따라 해상도와 framerate등을 유연하게 변화시키면서 품질을 유지하고 있기 때문입니다. HD급부터 QQVGA(160x120)급까지 다양한 해상도를 적용할 수 있습니다.

  ```java
  config.setVideoCodec("VP8");
  ```
* 영상통신시의 코덱을 결정합니다. H.264와 VP8, VP9등의 영상 코덱을 지원하고 있습니다. RemoteMonster는 H.264를 기본 코덱으로 사용하고 있으며 변경이 필요하다면 이 설정으로 변경할 수 있습니다.

  ```java
  config.setVideoFps(15);
  ```
* frameRate항목은 1초에 몇번의 frame으로 인코딩할 지를 결정합니다. 일반적인 영상통화에서는 15정도면 적당하지만 더 촘촘한 framerate를 원한다면 높여줄 수 있겠죠.

  ```java
  config.setSpeakerPhone("auto"|"true"|"false")
  ```
* Remon은 자동으로 사용자의 접근성을 감지하여 이어링모드로 할지 스피커폰모드로 할지 동적으로 변화합니다. 만약 이 기능을 해제하고 싶다면 false, 계속 유지하고 싶다면 true로 설정하세요.

```java
config.setLogLevel(Log.DEBUG);
```

* 마지막으로 로그레벨 설정입니다. ERROR, WARN, INFO, DEBUG, VERBOSE를 설정할 수 있으며 뒤로 갈 수록 더 자세한 로그를 확인할 수 있습니다.


# View, Layout

뷰와 레이아웃에 대해 소개합니다.

## Introduction

* 리모트몬스터 안드로이드 SDK는 레이아웃과 관련하여 2개의 클래스를 제공하고 있습니다. 가장 핵심이 되는, 영상을 출력하는 View인 org.webrtc.SurfaceViewRender와 이 SurfaceViewRender를 효율적으로 RelativeLayout에서 배치하는데 도움을 주는 PercentFrameLayout으로 이루어져 있습니다. 이 중에서도 SurfaceViewRender가 가장 핵심이므로 먼저 살펴보겠습니다.

## SurfaceViewRender

* android layout 파일에서 다음과 같이 layout에 배치함으로써 SurfaceViewRender를 사용할 수 있습니다.

  ```markup
  <RelativeLayout
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:background="@android:color/darker_gray"
  android:layout_alignParentBottom="false"
  android:layout_weight="2">
    <org.webrtc.SurfaceViewRenderer
      android:id="@+id/remote_video_view"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content" />
    <org.webrtc.SurfaceViewRenderer
      android:id="@+id/local_video_view"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content" />
  </RelativeLayout>
  ```
* 그리고 특정 Activity에서 다음과 같이 이 View의 객체를 얻어옵니다.

  ```java
  SurfaceViewRender localRender =
    (SurfaceViewRenderer) findViewById(R.id.local_video_view);
  SurfaceViewRender remoteRender =
    (SurfaceViewRenderer) findViewById(R.id.remote_video_view);
  ```
* 이제 이 뷰를 사용할 기본 준비가 완료되었습니다.
* Remon 클래스의 객체를 생성하고 생성할 때 Config 객체에 이 두 쌍의 뷰를 설정하면 통신이 시작됨과 함께 이 뷰에 카메라나 원격의 영상 스트림이 출력됩니다.
* 몇가지 이 SurfaceViewRender의 메소드를 살펴보겠습니다.

  ```java
  localRender.setZOrderMediaOverlay(true/false);
  ```
* 뷰간의 ZOrder를 설정할 수 있습니다. 즉 이 뷰를 다른 뷰보다 위 위치하도록 설정합니다. 겹치는 뷰가 있을 경우 다른 뷰는 false로 하고 해당 뷰만 true로 설정해야 합니다.

  ```java
  localRender.setMirror(false);
  ```
* 해당 뷰의 영상을 거울효과를 주어서 좌우가 바뀌는 방식으로 보여줄 수 있습니다.

  ```java
  localRender.setScalingType(RendererCommon.ScalingType);
  ```
* layout에 어떻게 채울지를 결정합니다.

## PercentFrameLayout

* 리모트몬스터 안드로이드 SDK에서는 영상관련 View의 배치를 쉽게 하기 위해 PercentFrameLayout을 제공합니다. PercentFrameLayout을 이용하면 영상 관련 View를 RelativeLayout안에서 자유롭게 배치하고 동적으로 움직일 수 있습니다.
* 다음 화면처럼 여러 SurfaceViewRender를 구성하는 것도 가능합니다.

  ![4자채팅](https://github.com/RemoteMonster/documents/tree/a2e3f2912c47e8315385e30ed5cf50ab3600cc90/src/android/.gitbook/assets/4peoplelayout.jpg)
* PercentFrameLayout은 layout내의 뷰들을 퍼센트 방식으로 배치합니다. 즉,

  ```java
  layout.setPosition(0,0,100,100);
  ```
* 은 해당 레이아웃을 relativeLayout상에서 가로세로 100%채워서 배치하겠다는 것이고

  ```java
  layout.setPosition(0,50,50,50);
  ```
* 은 해당 레이아웃을 relativeLayout을 4분면으로 나눠서 좌측 하단에 50%만큼 자리를 차지하겠다는 것입니다.


# 통화품질 실시간 확인

* 영상 및 음성 통화 중에 현재 통화의 품질이 어떠한지를 1에서 5까지의 단계로 항상 확인할 수 있습니다.
* 사용자는 간혹 자신 혹은 상대방의 네트워크 문제로 인하여 통화 품질이 안좋거나 끊어진 상황에서도 서비스회사의 문제라고 생각하고 불만을 제기할 수 있습니다. 때문에 사용자의 문제가 네트워크의 문제임을 사전에 알려줄 수 있습니다.
* 현재 이 통화 품질 정보는 5초에 한번씩 받을 수 있습니다.

  **Javascript에서 통화품질 정보를 얻기**
* Remon 객체를 생성할 때 입력 인자로 넣는 listener의 메소드 중 onStat() 을 구현하여 품질 정보를 받을 수 있습니다.

  ```javascript
  // listener 구현
  const rtcListener = {
  onInit(token) {
    l(`EVENT FIRED : onInit: ${token}`);
  },
  onCreateChannel(channelId) {
    l(`EVENT FIRED : onCreateChannel: ${channelId}`);
    appTitleElement.innerHTML = roomName+" - "+ "Waiting";
  },
  onStat(result){
    const stat = `State: l.cand: ${result.localCandidate} /r.cand: ${result.remoteCandidate} /l.res: ${result.localFrameWidth} x ${result.localFrameHeight} /r.res: ${result.remoteFrameWidth} ${result.remoteFrameHeight} /l.rate: ${result.localFrameRate} /r.rate: ${result.remoteFrameRate} / Health: ${result.rating}`;
    console.log(stat);
  }
  };
  ```
* 위의 result에서 받을 수 있는 여러 정보 중 result.rating 이 바로 네트워크 상황에 따른 통합적인 통화 품질 정보입니다.
* 1:매우 좋음, 2: 좋음 3: 나쁨 4: 매우 나쁨 5: 통화불능 으로 나누어집니다.

  **Android에서 통화품질 정보를 얻기**
* RemonObserver를 상속받은 클래스에서 onStatReport 메소드를 오버라이드하여 정보를 얻습니다.

  ```java
    @Override
    public void onStatReport(RemonStatReport report) {
        Logger.i(TAG, "report: " + report.getHealthRating());
        String stat = "health:" + report.getHealthRating().getLevel() + "\n";
    }
  ```
* RemonStatReport 객체의 getHealthRating메소드의 getLevel 메소드를 통해 1에서 5까지의 숫자를 얻을 수 있습니다.
* 1에서 5까지의 정의는 위의 자바스크립트에서의 정의와 동일합니다.


# ErrorCode

에러코드에 대해 설명합니다.

## Fail과 Error의 차이

* RemoteMonster API는 크게 두가지의 예외상황을 전달합니다. Fail과 Error입니다.
  * Fail은 주로 통신 중에 발생합니다. 통신 연결이 안되었거나 통신이 끊어졌거나 끊어진 것은 아니지만 상태가 불안정할 때 fail이 발생합니다. onStateChange 콜백 메소드를 통해서 fail관련 이벤트를 받을 수 있습니다.
  * Error는 Fail을 포함한 더 넓은 영역에서의 예외상황을 말합니다. onError 콜백 메소드를 통해서 Error를 받게 됩니다.

## onError의 Error

* InvalidParameterError
  * new Remon시 parameter가 잘못될 경우: config.key, serviceId, local/remoteView, 혹은 config나 callback자체가 없는 경우이거나 너무 길이가 큰 경우
  * connectChannel시에 잘못된 값(길이가 1이하이거나 필요이상으로 너무 큰 경우 100이상)
* UnsupportedPlatformError
  * browser가 지원하지 않는 경우
  * version이 지원하지 않는 경우
* InitFailedError
  * restful 반환 자체가 에러가 난 경우 즉 500error
  * signal서버가 죽어있는 경우 --> webserver는 살아있으므로 webserver가 엉뚱한 페이지를 전달함
  * webserver가 죽어있는 경우 --> 400error가 나오므로 ...
  * ws,restful host가 문제가 있는 경우
  * ws open중에 에러가 난 경우
* WebSocketError: websocket 통신중에 발생한 에러
  * send하다가 난 에러
  * receive하다가 난 일반적 ws error
* ConnectChannelFailedError
  * create/connect의 반환에 channel정보가 없는 경우
  * channel이 expired되거나 channel이 없는데 connect하는 경우는 알아서 서버가 onCreateChannel로 변화시켜버림
* BusyChannelError
  * 채널이 이미 사용중인 경우
* UserMediaDeviceError
  * media 특히 camera를 못가져온 경우(video를 on했음에도 불구하고))
  * videoCapture를 못가져온 경우
* ICE(Failed)Error
  * peerConnection create 안되면...
  * sdp가 이미 있는데 또 자기것이 생성된 경우.
  * ice,sdp format등의 문제로 파싱이 안되거나 add가 안되는 경우


# WebRTC 주요 링크 모음

## 기초

* [WebRTC 시작하기](https://coiiee.com/blog.php?idx=2): Coiiee 대표 정유철님께서 작성하신 알기쉬운 WebRTC 자료
* [HTML5Rocks의 WebRTC가이드](https://www.html5rocks.com/ko/tutorials/webrtc/basics/): WebRTC개발을 처음 시작할 때 개념을 잡기 좋습니다.

## 주요 블로그 및 커뮤니티

* [RemoteMonster Blog](http://blog.remotemonster.com): 리모트몬스터가 운영하는 WebRTC 블로그
* [WebRTC 한국 커뮤니티](https://www.facebook.com/groups/rtc.korea/): 한국 최초 최고의 WebRTC 관련 커뮤니티

## 최신 트렌드 및 시장조사

* [WebRTC의 현재와 미래](https://blog.remotemonster.com/webrtc-현재와-미래-8858579a4264): 2016년 WebRTC의 트렌드를 정리한 문서
* [WebRTC 시장동향 리포트](https://blog.remotemonster.com/webrtc-시장동향-리포트-f880a45a3bce): 국내외 WebRTC 시장 동향 리포트

## 기본이 되는 사이트

* [WebRTC org](https://webrtc.org/): WebRTC 프로젝트를 대표하는 사이트입니다. 여기에서 native 엔진 관련정보를 얻을 수 있습니다.
* [WebRTC 표준](https://www.w3.org/TR/webrtc/): WebRTC 표준을 이끌고 있는 W3C의 페이지입니다.
* [IETF rtcweb WG](https://tools.ietf.org/wg/rtcweb/): WebRTC의 통신 프로토콜이나 코덱등의 표준을 이끌고 있는 IETF의 페이지입니다.

## 해외 사이트

* [WebRTC github](https://github.com/webrtc): WebRTC 소스코드 저장소
* [CodeLab](https://bitbucket.org/webrtc/codelab): 따라하기 식으로 WebRTC 코딩을 교육받을 수 있는 사이트
* [모질라 WebRTC API doc](https://developer.mozilla.org/ko/docs/Web/API/WebRTC_API): 모질라에서 제공하는 WebRTC API 문서. 가장 정확하고 최신의 표준에 입각한 API를 확인할 수 있음.
* [Bloggeek.me](https://bloggeek.me/): WebRTC에서 가장 유명한 블로거 Tsahi의 블로그
* [WebRTCHacks](https://webrtchacks.com/): WebRTC계의 가장 유명한 해커 Chad Hart가 운영하는 사이트. 그 외 Victor나 Philipp등도 같이 운영
* [WebRTC Weekly](https://webrtcweekly.com/): WebRTC의 최신 트렌드를 주단위로 받아볼 수 있음

## 주요 라이브러리 및 개발 정보

* [adapter.js](https://github.com/webrtc/adapter): 표준화가 덜된 관계로 브라우저마다 API조금씩 다른 WebRTC. 그래서 adapter.js를 사용해야 그 호환성을 지킬 수 있습니다.

최근 업데이트: 2017년 3월 17일 by Jinho Choi


# RemoteMonster

## **환영합니다.**

RemoteMonster는 방송 통신을 응용하는 서비스를 쉽고 빠르게 개발할 수 있도록 다양한 기능을 제공하고 있습니다.

이 기능을 개발 하기 위해서는 RemoteMonster 서버에서 제공되는 API와 RemoteMonster 서버를 활용하기 위한 클라이언트 SDK를 사용하게 됩니다. 개발시에는 대부분 SDK를 통해 개발을 진행하게 됩니다. SDK의 구성과 흐름을 전체적으로 살펴보고 싶으면 아래를 참고하세요.

## 개발절차

### 웹 콘솔 가입 및 인증 키 획득

RemoteMonster 웹 콘솔 가입을 합니다. 웹 콘솔에서 프로젝트(서비스) 기본 정보 화면에 Service ID와 Secret Key가 표시됩니다.

유료 이용 계약을 하기 전에는, 통화와 방송이 3분간 지속되고 자동으로 끊어집니다.

{% embed url="<https://remotemonster.com>" %}
RemoteMonster Homepage
{% endembed %}

자세한 내용은 아래를 참고하세요.

{% content-ref url="/pages/-LGUX\_VzVLLV66GO3DnE" %}
[Service Key](/common/service-key)
{% endcontent-ref %}

### SDK 및 개발 환경 설정

Browser, Android, iOS 모두 각각에 맞는 패키지 매니저를 통해 매우 간단하게 준비 가능합니다.

아래를 통해 각 플랫폼별 세부적인 내용을 확인해 보세요.

{% content-ref url="/pages/-LGUX\_VegX1nF20YlowL" %}
[Web - Getting Started](/web/web-getting-started)
{% endcontent-ref %}

{% content-ref url="/pages/-LGUX\_VmZyrTChz29QXB" %}
[Android - Getting Started](/android/android-getting-started)
{% endcontent-ref %}

{% content-ref url="/pages/-LGUX\_VtIk9TFRWqQTsY" %}
[iOS - Getting Started](/ios/ios-getting-started)
{% endcontent-ref %}

### 개발

RemoteMonster는 쉬운 개발을 위해 커뮤니티와 다양한 예제 코드를 제공하고 있습니다.

아래의 RemoteMonster Github에서 플랫폼별 예제와 데모 그리고 레퍼런스 문서를 직접 확인해 보세요.

{% embed url="<https://github.com/remotemonster>" %}
RemoteMonster Github Repository
{% endembed %}

혹시 어려운 점이 생기면 RemoteMonster의 커뮤니티를 둘러보세요. 아래의 커뮤니티에 질문을 올리면 쉽고 빠르게 답변을 얻을 수 있습니다.

{% embed url="<https://community.remotemonster.com>" %}
RemoteMonster Community
{% endembed %}

### 운영

서비스 개발이 끝나면 상용 버전의 서비스 시작 전 RemoteMonster에 통보합니다. RemoteMonster는 보다 나은 품질과 안정성을 제공하기 위한 서버세팅과 관리자 화면을 제공합니다.

테스트용키가 아니라 운영 키를 사용하여 보안과 품질을 높이세요.

{% content-ref url="/pages/-LGUX\_VzVLLV66GO3DnE" %}
[Service Key](/common/service-key)
{% endcontent-ref %}

### 관리

RemoteMonster는 상용 서비스에 한하여 별도의 관리자 화면을 제공하여 트래픽 정보와 통화 품질에 대한 정보를 실시간으로 제공합니다.

이를 통해 어떤 고객에게 주로 문제가 발생하는지, 어떻게 개선하면 좋을지 점검할 수 있습니다.

## Advanced

최적의 네트워크 환경, 송출 스튜디오 환경, 코덱/비트레이트등 미디어 설정에 대해서는 아래를 찾아보세요.

{% content-ref url="/pages/-LRQKY\_sVXVaNRh\_4zBi" %}
[Network Environment](/common/network-environment)
{% endcontent-ref %}

{% content-ref url="/pages/-LT5GWhEmvQtjtCf588U" %}
[Studio Environment](/common/studio-environment)
{% endcontent-ref %}

{% content-ref url="/pages/-LGUX\_W4zkJRbNnqyOLb" %}
[Media](/common/media)
{% endcontent-ref %}

미디어를 녹화하거나, 동시에 여러 품질의 미디어를 송출하여 선택적으로 품질을 선택하는 방법을 알아보세요.

{% content-ref url="/pages/-LRQY\_hgb3oEPjWMz4Za" %}
[Simulcast](/common/livecast-simulcast)
{% endcontent-ref %}

{% content-ref url="/pages/-LRKoeIOesQqWgW9X0PY" %}
[Record - beta](/common/record)
{% endcontent-ref %}

에러상황의 종류와 처리에 대해 확인하세요.

{% content-ref url="/pages/-LGUX\_W5VllExeWY9OIA" %}
[Error](/common/error)
{% endcontent-ref %}


# Flow

## Overview

RemoteMonster를 사용하는 전반에 있어서 보편적인 흐름이 있습니다. 각각 아래를 참고하세요.

## Livecast

방송시에는 간단하게 방을 생성하여 방송을 송출하고, 방에 접속하여 방송을 수신하는 경우로 나뉩니다. 아래와 같은 흐름을 가지고 있습니다.

* Caster: 방송을 송출하는 행위자
* Viewer: 방송을 시청하는 행위자

|                 | 초기화                 | 채널 생성      | 채널 접속               | 해지                     |
| --------------- | ------------------- | ---------- | ------------------- | ---------------------- |
| Caster Event    | ready RemoteMonster | `create()` | -                   | `close()`, disconnect  |
| Caster Callback | `onInit`            | `onCreate` | -                   | `onClose`              |
| Viewer Event    | ready RemoteMonster | -          | `join('channelId')` | `cloase()`, disconnect |
| Viewer Callback | `onInit`            | -          | `onJoin`            | `onClose`              |

## Communication

통신에서는 통화를 요청하고 이를 수신하는 행위로 구분됩니다. 통화를 요청하는 측은 채널을 생성하고 상대방을 기다리는 상태가 되며, 이때 얻은 chid를 통해 상대방이 같은 채널에 접속하게 되고 상호간 통화가 시작됩니다.

* Caller : 통신을 요청하는 행위자
* Callee : 통신을 응답하는 행위자

|                 | 초기화                 | 채널 생성       | 채널 접속                  | 통화시작                     | 해지                    |
| --------------- | ------------------- | ----------- | ---------------------- | ------------------------ | --------------------- |
| Caller Event    | ready RemoteMonster | `connect()` | Wait callee            | Caller, Callee Connected | `close()`, disconnect |
| Caller Callback | `onInit`            | `onConnect` | -                      | `onComplete`             | `onClose`             |
| Callee Event    | ready RemoteMonster | -           | `connect('channelId')` | Caller, Callee Connected | `close()`, disconnect |
| Callee Callback | `onInit`            | -           | `onConnect`            | `onComplete`             | `onClose`             |


# Structure

## Overview

RemoteMonster의 SDK를 사용하는 것은 매우 간단합니다. 크게 3가지의 클래스 사용법만 익히면 됩니다.

![](/files/-LGUXbA0SqO3jq2DdiN4)

## Remon class

`Remon`에서 모든 방송, 통신관련 조작을 수행합니다. 방송, 통신상태를 초기화하고 방을 개설하고 방에 접속하고 종료하는 일련의 모든 과정에서 이 `Remon` 클래스를 사용합니다. 또한, 메세지 보내기와 같은 추가적인 기능을 수행합니다.

`Remon` 클래스를 생성하기 위해서는 2개의 미리 준비된 정보가 필요합니다. 다음에 설명될 `Config`와 `Observer`입니다.

### Init

객체를 초기화하는 과정입니다. 이를 통해 RemoteMonster API 서버와 연결됩니다. 연결되는 순간 RemoteMonster 서버는 이 `Remon` 객체에게 1회용 토큰을 제공합니다.

### Create, Join

방송에 사용하는 기능입니다. 방송을 만들거나 시청하는 명령입니다. 방송을 만들 때에는 `Observer` 콜백을 통해 `onCreate`에서 방송의 실제 유일한 id값을 받게 되며 이 id값을 이용하여 `Join` 메소드를 통해 시청자는 해당 방송에 접근할 수 있습니다.

### Connect

통신에 사용하는 기능입니다. 채널에 접속하거나 채널을 만드는 명령입니다. 주어진 이름의채널이 없을 경우 채널을 만들고 이미 채널이 있을 경우 채널에 접속합니다. 만약 채널 이름이 없을 경우 RemoteMonster는 고유한 채널 이름을 생성해서 반환합니다.

### Close

채널을 나오거나 채널을 없애버리고 초기화합니다.

### 사용법

{% content-ref url="/pages/-LGUX\_W01FLXFdahB7Rx" %}
[Livecast](/common/livecast)
{% endcontent-ref %}

{% content-ref url="/pages/-LGUX\_W1RyXdYhFarqs1" %}
[Communication](/common/communication)
{% endcontent-ref %}

## Config Class

`Remon` 객체를 초기화하기 전에 필요한 사전 설정작업은 모두 이 `Config`를 통해서 이루어집니다. Service Id, Key와 같은 인증정보와 미디어, 네트워크와 관련된 다양한 작업을 처리 합니다. 보다 세부적인 내용은 아래를 참고하세요.

{% content-ref url="/pages/-LGUX\_W-t9BCj3vjDn1A" %}
[Config/RemonConfig](/common/config)
{% endcontent-ref %}

## Observer Class

`Remon` 클래스가 명령을 내리는 용도라면 `Observer`는 Callback 메시지를 수신하기 위한 클래스입니다. `Observer`로부터 수신되는 이벤트를 이용하여 보다 세밀한 통신효과와 능동적인 서비스를 구현할 수 있습니다. 자세한 내용은 아래를 참고하세요.

{% content-ref url="/pages/-LGUX\_W2IMDRX7sIYMoc" %}
[Callbacks](/common/callbacks)
{% endcontent-ref %}


# Examples

## 예제

RemoteMonster Github 저장소의 다양한 예제를 살펴보고 확인합니다.

### Web SDK & Examples

{% embed url="<https://github.com/remotemonster/web-sdk/>" %}

### Android SDK & Examples

{% embed url="<https://github.com/remotemonster/android-sdk>" %}

### iOS SDK & Examples

{% embed url="<https://github.com/remotemonster/ios-sdk>" %}


# Features

## Overview

RemoteMonster의 방송 통신기술은 API형태로 제공됩니다. 복잡한 Back-end, Infra, 방송, 통신기술 개발이 필요없이 쉽게 Skype나 Hangout, Snapchat, 아프리카TV와 같은 방송 통신 서비스를 구현할 수 있습니다. RemoteMonster가 구축하는 글로벌한 통신 API와 SDK를 통해 즉시 서비스를 시작하세요.

![](/files/-LGUXaTcOD_ks6VudEDj)

## Highlights

* 모바일 플랫폼 지원
  * 이미 운영중인 서비스 혹은 개발할 서비스에 SDK를 추가하고 간략한 개발을 통해 바로 방송 통신 기능 적용
* 실시간 통화 지원
  * 영상, 음성 통신, 방송 중 통화 등의 기능을 제공
* 방송 서비스 지원
  * 아프리카TV나 Twitch와 같은 실시간 방송 서비스 기능을 서비스에 적용
* 글로벌 클라우드 환경 지원
  * RemoteMonster의 기능은 글로벌 클라우드에 의해 운영되어 세계 어디에서나 빠른 통신품질을 보장
* 모니터링 기능
  * 트랜잭션의 상태를 실시간으로 또는 통계적으로 모니터링
* 녹화 기능
  * 클라이언트, 서버에서 녹화하는 기능을 제공

### 플랫폼 지원

* WebRTC Edge Engine
  * WebRTC 엔진인 Chrominum 최신 버전을 제공하여 높은 호환성과 성능, 품질 제공
* Latest Standard
  * WebRTC 1.0 Based API
* 다양한 플랫폼 및 최신의 언어 지원
  * Android
  * iOS(Objective C, Swift)
  * Chrome/Firefox/Edge/Safari/Whale (Promise API, ES6)

### 품질

* Global Infra
  * 국내 뿐 아니라 Global 방송, 통신에 대한 레퍼런스 및 경험 확보
* 속도
  * 국내에서 가장 빠른 평균 1초 미만의 Setup time
  * 방송 중 가장 빠른 평균 1초 미만의 Latency time
* 품질
  * 모바일 기기의 성능에 따른 다양한 성능 옵션 선택 가능
  * 네트워크 상황에 따른 지능적인 동적 품질 적용 엔진
  * 다양한 코덱 선택 가능
    * 영상: VP8, VP9, H.264
    * 음성: G.711, Opus(Stereo), ISAC(Mono)

### 개발 용이성

* 모든 인프라를 제공하여 WebRTC 체계를 만드는데 1달 가까이 걸리는 시간을 절약하고 바로 시작
* Config → Connect 2단계로 개발하는 가장 쉬운  WebRTC 코드

## Customers

* 튜터링: 모바일 영어회화 어플
* 설레Go: 대리운전 앱
* 화상운세: 모바일 화상기반 역술인 채팅 서비스
* 심리상담: 트로스트 - 심리상담, 고민상담 서비스
* 설레는 아침: 소셜 모닝콜 서비스
* 와글 라이브: 모바일 생방송 서비스
* 와글 퀴즈: 모바일 인터렉티브 퀴즈 생방송 서비스

## Use cases

* 영어회화, 공부방, 교육 서비스
* 뷰티 서비스
* 운세 서비스
* 상담 서비스
* IoT 등 기기연동 서비스
* O2O, 옴니채널 서비스


# Spec

## Features

| 항목          | 내용                                                 |
| ----------- | -------------------------------------------------- |
| 1:1 음성 통화   | 지원                                                 |
| 1:1 영상 통화   | 지원                                                 |
| 그룹통화(영상/음성) | 최대 10명 지원                                          |
| 1:N 방송      | 지원                                                 |
| 채팅          | 지원                                                 |
| TURN서버      | 지원. 글로벌 크라우드 기반 제공. Pricing에 따라 차별화                |
| 실시간 품질 확인   | 현재 통화 상태를 API로 실시간 제공 및 분석결과 제공                    |
| 블루투스 헤드셋    | 지원                                                 |
| 스피커모드       | API 제공                                             |
| 카메라 변경      | 초기 카메라 설정, 변경 및 이벤트 수신                             |
| 자동 재연결      | 예기치못한 통화종료시 자동 재연결 기능                              |
| 녹음, 녹화 기능   | 지원. RemoteMonster 서버에서 파일 다운로드 지원. Pricing에 따라 차별화 |

## Environments

| 항목            | 내용                                 |
| ------------- | ---------------------------------- |
| Web - Chrome  | Desktop, Android 모두 지원             |
| Web - Firefox | Desktop, Android 모두 지원             |
| Web - Whale   | 지원                                 |
| Web - Edge    | 음성 통신 지원                           |
| Web - Safari  | 지원 (11이상)                          |
| Android       | 최소 21(롤리팝) 이상. ARM기반 단말만 지원        |
| iOS           | iOS 11 이상, 64bit Only, bitcode 미지원 |

## Media and Networks

| 구분    | 항목               | 내용                                                    |
| ----- | ---------------- | ----------------------------------------------------- |
| Video | Codec            | H.264(기본), VP8, VP9                                   |
|       | Networks         | 최소 300kbps 이상                                         |
|       | Processing Power | 듀얼코어 이상. CPU 벤치마크 PassMark Software 제시점수 1000점 이상의 PC |
| Audio | Codec            | G.711, Opus, ISAC                                     |
|       | Networks         | 최소 50kbps 이상                                          |
| Etc   | Latency          | 최소 0.2초 - 최대 1초. 네트워크와 환경에 따라 다를 수 있음                 |
|       | Setup time       | 웹: 1초 미만, 모바일: 1.5초 미만. 네트워크와 기타 환경에 따라 다를 수 있음       |
|       | Region           | 전세계                                                   |


# Web - Overview

## Overview

웹을 위한 SDK 입니다. 브라우저에서 작동합니다.

## Resources

### Downloads, Change Log, Examples

{% embed url="<https://github.com/remotemonster/web-sdk/>" %}

RemoteMonster Web SDK에 대한 모든 정보를 얻을 수 있습니다. 다양한 예제를 코드로 검토하세요.

### Reference Document

{% embed url="<https://remotemonster.github.io/web-sdk/docs/>" %}

SDK의 기능을 세부적으로 안내합니다.


# Web - Getting Started

## 준비사항

* 웹 브라우저, 프론트엔드 개발 환경
* WebRTC를 지원하는 모던 브라우저

## 프로젝트 생성 및 설정

RemoteMonster의 SDK는 브라우저 환경에서 작동합니다. 간단하게 일반적인 웹 프론트엔드 개발 준비를 하면 됩니다.

```bash
npm init
npm i http-server
touch index.html
npx http-server
# Open browser "localhost:8081"
```

## SDK 설치 - npm <a href="#undefined-1" id="undefined-1"></a>

npm을 통해 간단하게 설치가 가능합니다.

```
npm i @remotemonster/sdk
```

```javascript
import Remon from '@remotemonster/sdk'
```

{% code title="index.html" %}

```markup
<script src="node_modules/@remotemonster/sdk/remon.min.js"></script>
```

{% endcode %}

## SDK 설치 - Static Import <a href="#undefined-1" id="undefined-1"></a>

[jsDelivr CDN](https://www.jsdelivr.com/package/npm/@remotemonster/sdk)을 사용 할 수 있습니다. 아래와 같이 Latest 또는 Specific version을 HTML 파일에 삽입하세요.

{% code title="index.html" %}

```markup
<!-- Latest -->
<script src="https://cdn.jsdelivr.net/npm/@remotemonster/sdk/remon.min.js"></script>

<!-- Specific version -->
<script src="https://cdn.jsdelivr.net/npm/@remotemonster/sdk@2.0.8/remon.min.js"></script>
```

{% endcode %}

## 개발

이제 모든 준비가 끝났습니다. 아래를 통해 세부적인 개발 방법을 확인하세요.

### Service Key

SDK를 통해 RemoteMonster 방송, 통신 인프라에 접근하려면, Service Id와 Key가 필요합니다. 간단한 테스트와 데모를 위해서라면 이 과정을 넘어가도 좋습니다. 실제 서비스를 개발하고 운영하기 위해서는 아래를 참고하여 Service Id, Key를 발급 받아 적용하도록 합니다.

{% content-ref url="/pages/-LGUX\_VzVLLV66GO3DnE" %}
[Service Key](/common/service-key)
{% endcontent-ref %}

### 방송

`Remon`을 통해 방송 기능을 쉽고 빠르게 만들 수 있습니다.

#### 방송 송출

```javascript
<video id="localVideo" autoplay muted></video>
<script>
const config = {
  view: {
    local: '#localVideo'
  }
}

const caster = new Remon({ config })
caster.createCast()
</script>
```

#### 방송 시청

```javascript
<video id="remoteVideo" autoplay></video>
<script>
const config = {
  view: {
    remote: '#remoteVideo'
  }
}

const viewer = new Remon({ config })
viewer.joinCast('CHANNEL_ID')
</script>
```

혹은 좀더 자세한 내용은 아래를 참고하세요.

{% content-ref url="/pages/-LGUX\_W01FLXFdahB7Rx" %}
[Livecast](/common/livecast)
{% endcontent-ref %}

### 통신

`Remon`을 통해 통신 기능을 쉽고 빠르게 만들 수 있습니다.

```javascript
<video id="localVideo" autoplay muted></video>
<video id="remoteVideo" autoplay></video>
<script>
const config = {
  view: {
    local: '#localVideo',
    remote: '#remoteVideo'
  }
}

const remonCall = new Remon({ config })
remonCall.connectCall('CHANNEL_ID')
</script>
```

혹은 좀더 자세한 내용은 아래를 참고하세요.

{% content-ref url="/pages/-LGUX\_W1RyXdYhFarqs1" %}
[Communication](/common/communication)
{% endcontent-ref %}


# Web - Media

## Overview

영상, 음성 미디어의 표출과 사용에 대해 안내합니다.

공통적인 부분은 아래를 참고하세요.

{% content-ref url="/pages/-LGUX\_W4zkJRbNnqyOLb" %}
[Media](/common/media)
{% endcontent-ref %}

## View

### Basic

```markup
<video id="remoteVideo" autoplay controls></video>
<video id="localVideo" autoplay controls muted></video>
```

Controls 속성을 추가할 경우 영상에서 제어 컨트롤을 추가할 수 있습니다.

Local Video의 경우 보통 `muted` 속성을 추가하여 자기 음성이 다시 자기에게 들리는 하울링 현상을 없애야 합니다.

### Advanced

```javascript
// 자신의 영상을 mute하기
pauseLocalVideo(bool)
// 원격의 영상을 mute하기
pauseRemoteVideo(bool)
// 자신의 음성을 mute하기
muteLocalAudio(bool)
// 원격의 영상을 mute하기
muteRemoteAudio(bool)
```


# Web - Debug Inside

## WebRTC Internals

webrtc-internals는 WebRTC서비스에서 발생하는 이슈르 해결해야할 때 굉장히 좋은 도구입니다. 이 도구에 익숙하지 않다면 크롬브라우저에서 WebRTC세션을 연결하고 다른 탭을 더 열어서 주소창에서 chrome://webrtc-internals/ 를 입력하면 webrtc-internals를 실행할 수 있습니다.

webrtc-internals는 현재 연결되어서 통신하고 있는 여러 stat정보를 덩치 큰 JSON덩어리로 저장할 수 있고 이것을 이용하여 다음과 같이 살펴볼 수도 있습니다.

아래를 참고하여 작성되었습니다.

{% embed url="<https://testrtc.com/webrtc-internals-documentation/>" %}

## getUserMedia Requests

getUserMedia를 통해 전달된 constraints값들이 기록되어 있습니다. 세부적인 내용은 없으며 간단한 내용을 확인가능합니다.

## RTCPeerConnection

실질적으로 내부 정보를 확인해 볼 수 있는 가장 중요한 곳 입니다.

### Overview

![From testRTC, Copyright 2018 testRTC](/files/-LGUXanqJ_dzGXrSe44u)

1. RTCPeerConnection이 어떻게 설정되어 있는지, 어떤 STUN, TURN서버가 사용되고 그 옵션은 어떻게 설정되어 있는지 알려줍니다.
2. 왼쪽편은 PeerConnection 객체가 호출된 trace입니다. 즉, PeerConnection객체의 메소드가 호출된 순서대로 나열되어 있고 그 인자값(예: createOffer)등과 callback 이벤트 이를테면 onicecandidate 등도 전부 기록합니다. 이것은 정말 강력하기 때문에 ICE 실패가 어디서 왜 일어났는지를 확인하거나 TURN서버를 어디에 설치해야하는 지 결정하는데에도 많은 영감을 줄 수 있습니다.
3. Stats Tables: getStats() 메소드로부터 받은 통계정보를 보여줍니다.
4. getStats() 의 값들을 그래프로 보여줍니다. webrtc-internals의 통계값들은 사실 크롬브라우저의 내부의 포맷이어서 현재의 표준과는 조금 다릅니다. 하지만 크게 다르지는 않고 점점 이 통계치들은 표준과 유사하게 맞춰지고 있는 상황입니다.

### Get stats to codes

통계값을 webrtc-internals가 아닌 코드나 콘솔을 통해 확인하고 싶다면 다음과 같이 실행하면 됩니다.

```javascript
RTCPeerConnection.getStats(function(stats) { console.log(stats.result()); )};
```

RTCStatsReport 객체의 배열값이고 아래에서와 같이 매우 많은 key와 value쌍으로 이루어져 있습니다.

```javascript
RTCPeerConnection.getStats(function(stats) {
 var report = stats.result()[0];
 report.names().forEach(function(name) {
     console.log(name, report.stat(name));
 });
)}
```

이들 Report객체를 읽는 방법 중 중요한 원칙 중 하나는 바로 끝에 Id로 끝나는 key 이름은 보통 다른 report의 id 속성을 가리킨다는 것입니다. 때문에 거의 모든 report 객체들은 서로간에 연결되어 있는 구조입니다. 또한 대부분의 값들은 string인것도 명심하세요.

RTCStatsReport의 가장 중요한 속성은 report의 type입니다. 여기 그것들 중 중요한 것들을 소개합니다.

* googTrack
* googLibjingleSession
* googCertificate
* googComponent
* googCandidatePair
* localCandidate
* remoteCandidate
* ssrc
* VideoBWE

이들 report들을 하나씩 살펴봅시다.

### googCertificate report

googCertificate report는 local에서 사용하고 certificate 자체 용도로도 사용하는 DTLS certificate 정보를 담고 있습니다. 이것에 대한 자세한 내용은 [RTCCertificateStats dictionary](https://w3c.github.io/webrtc-stats/#certificatestats-dict) 에서 확인할 수 있습니다.

### googComponent report

certificate 통계와 connection간의 접착제 역할을 수행합니다. 즉, 현재 active한 candidate 쌍들에 대한 링크를 가지고 있습니다.

### googCandidatePair report

ICE Candidate 쌍을 다룹니다. 이 report를 통해 아래와 같은 정보를 얻을 수 있습니다.

* 송수신된 패킷과 bytes의 전체 수(bytesSent, bytesReceived, packetsSent; packetsReceived는 알수없는 이유로 missing). 이것들은 RTP헤더를 포함한 raw형태의 UDP 혹은 TCP bytes가 기본값입니다.
* 현재 active한 connection인지 여부를 googActiveConnection항목을 통해 알 수 있습니다. 대부분의 시간동안 active candidate 쌍의 통계에 대해서만 관심이 있을 겁니다. 이 정보에 대한 자세한 내용은 [여기](https://w3c.github.io/webrtc-stats/#transportstats-dict) 를 통해 확인하세요.
* 송수신된 STUN 요청 및 수신의 수(requestsSent, responsesReceived, requestsReceived, responsesSent). 즉, ICE 과정 중 사용된 송수신 STUN요청의 count정보입니다.
* googRtt를 통해 마지막 STUN 요청의 round trip time을 알 수 있습니다. ssrc report의 googRtt와는 다른 겁니다.
* localCandidateId와 remoteCandidateId를 통해 localCandidate와 remoteCandidate 객체의 id를 알 수 있습니다.
* googTransportType을 통해 전송 type을 알 수 있습니다. 대부분 udp이나 TURN서버 사용시 TURN over TCP가 사용될 수 있습니다. [ICE-TCP](https://webrtcglossary.com/ice-tcp/)가 사용되면 tcp로 설정됩니다.

### **localCandidate, remoteCandidate report**

ip 주소, 포트번호, candidate의 종류등을 확인할 수 있습니다.

### Ssrc report

가장 중요합니다. peerconnection을 통해서 송수신되는 음성 혹은 영상 트랙 하나를 담당하고 있습니다. 표준에서는 이를 [MediaStreamTrackStats](https://w3c.github.io/webrtc-stats/#mststats-dict)과 [RTPStreamStats](https://w3c.github.io/webrtc-stats/#streamstats-dict)로 구분하여 정의하고 있습니다. 이 report는 그것이 다루고 있는 것이 음성인지 영상인지 혹은 송신인지 수신인지에 영향을 받고 있습니다. 일단 공통적인 값들부터 살펴봅시다.

* mediaType: 음성인지 영상인지를 알려줍니다.
* ssrc: 송신하는 것인지 수신하는 것인지 등의 고유값을 나타냅니다.
* googTrackId: 이 통계가 대상으로 하는 트랙의 id를 나타냅니다. 이는 SDP의 local 혹은 remote media 스트림 트랙부분에서 역시 찾을 수 있습니다. 원래 원칙상 끝에 Id가 붙는 것들은 다른 report를 가리켜야하지만 이것만은 예외입니다.
* googRtt: rount-trip time인데 이것은 RTCP에서 측정된 값입니다.
* transportId: 이 RTP 스트림을 전송하는데 사용한 컴포넌트를 가리킵니다. 만약 Bundle을 사용중이라면 음성과 영상 스트림 모두 동일한 값으로 지정되어 있을 것입니다.
* googCodecName: codec의 이름입니다. opus, VP8, VP9, H264 등등. codec의 구현체를 codecImplementationName stat을 통해 확인할 수도 있습니다.
* bytesSent, bytesReceived, packetsSent, packetsReceived (해당 ssrc가 송신인지 수신인지에 따라 차이): bitrates 계산값입니다. 이는 누적값이기 때문에 이전에 측정한 getStats로부터 걸린 시간과 함께 적절한 계산을 해주어야 합니다. 표준에 나와있는 [샘플코드](http://w3c.github.io/webrtc-pc/#example)는 비교적 훌륭합니다만 가끔 크롬은 이 값을 리셋하기 때문에 때때로 마이너스값이 나올 수 있음을 명심하세요.
* packetsLost: 잃어버린 패킷수. 송신자입장에선 RTCP를 통해, 수신자입장에서는 로컬기반으로 수집합니다. 통화 상태를 나타내는 가장 중요한 값 중 하나입니다.

### Voice

* 음성 트랙을 위해 audioInputLevel과 audioOutputLevel(표준에서는 이를 [audioLevel](https://w3c.github.io/webrtc-stats/#dom-rtcmediastreamtrackstats-audiolevel)이라 칭합니다)값이 있습니다. 음성 신호가 마이크에서 오는지 스피커를 통해서 오는지 등을 알려줍니다. 이는 크롬의 [음성 처리 버그](https://bugs.chromium.org/p/webrtc/issues/detail?id=4799)를 감지하는 데 사용됩니다.
* googJitterReceived와 googJitterBufferReceived: [수신된 Jitter의 양](https://webrtcglossary.com/jitter/)에 대한 정보 그리고 [jitter buffer state](https://webrtcglossary.com/jitter-buffer/)에 대한 정보를 제공합니다.

### Video

* googNacksSent: [NACK](https://webrtcglossary.com/nack/)에 대한 정보
* googPLIsSent: [PLI](https://webrtcglossary.com/pli/)에 대한 정보
* googFIRsSent: [FIR](https://webrtcglossary.com/fir/)에 대한 정보
* 위 정보들은 패킷손실(packetloss)가 영상 품질에 미치는 영향을 이해하는 데 도움을 줍니다.
* googFrameWidthInput, googFrameHeightInput, googFrameRateInput: 입력된 frame size와 frame rate등을 알려주며
* googFrameWidthSent, googFrameHeightSent, googFrameRateSent: 실제 네트워크를 통해 보내어진 수치입니다.
* googFrameWidthReceived, googFrameHeightReceived: 수신한 frame size이고
* googFrameRateReceived, googFrameRateDecoded, googFrameRateOutput: 수신한 framerate 정보입니다.
* 영상을 인코딩하는 입장에서는 이들 값들의 차이를 확인하고 왜 영상의 resolution이 낮아졌는지에 대한 정보를 확인할 수 있습니다. 일반적으로 충분하지 않은 CPU 혹은 bandwidth가 원인인 경우가 많습니다.
* 낮아진 framerate에 대한 비교는 googFrameRateInput와 googFrameRateSent값을 비교하여 알 수 있는데, 이에 더하여 낮아진 해상도의 원인이 CPU때문인지(googCpuLimitedResolution값이 true 인 경우) 혹은 불충분한 bandwidth때문인지(googBandwidthLimitedResolution값이 true)들을 여러 정보들을 통하여 확인할 수 있습니다. 이들중 어떤 조건이 변경이 되면 googAdaptionChanges의 counter가 증가합니다.
* 패킷손실(packet loss)을 인위적으로 발생시켜 보았습니다. 응답쪽에서 크롬은 googFrameWidthSent와 googFrameWidthInput값이 달라지는 t=184에서 해상도를 낮추려 합니다. t=186일 때 input framerate이 30에서 0에 가깝게 변하는 것을 알 수 있습니다.

### VideoBWE report

bandwidth를 예측합니다. 다음과 같은 정보를 가지고 있습니다.

* googAvailableReceiveBandwidth: 수신중인 영상 데이터를 위해 가용한 bandwidth 입니다.
* googAvailableSendBandwidth: 송신중인 영상데이터를 위해 가용한 bandwidth 입니다.
* googTargetEncBitrate: 영상 인코더가 목표로 하는 bitrate입니다. 가용한 bandwidth를 최대한 사용하려 합니다.
* googActualEncBitrate: 영상 인코더의 실제 수행하는 초기 bitrate입니다. 목표 bitrate와 대응해야 합니다.
* googTransmitBitrate: 실제 전송하는 bitrate입니다. googActualEncBitrate와 차이가 심하다면 아마 [forward error correction](https://webrtcglossary.com/fec/) 때문일 것입니다.
* googRetransmitBitrate: RTX가 사용된다면 재전송의 bitrate을 측정을 허용합니다. 패킷손실을 측정하는 데 유용합니다.
* googBucketDelay: 큰 프레임과 관련된 구글의 "leaky bucket" 전략의 측정입니다. 크게 중요하지 않습니다.


# Android - Overview

## Overview

![](/files/-LGUXbbEcmMuyVycKGGm)

`Remon Class`는 Remon SDK 에서 가장 핵심이 되는 클래스 입니다. `Remon Config` 와 `Remon Observer`를 통해서 직접 방송과 통신을 만들 수 있습니다. Remon 클래스로 가장 기본적인 RemoteMonster의 방송/통화 서비스를 개발할 수 있지만 직접 Remon 클래스를 쓰지 않고 방송과 통화에 각각 적합하게 개발할 수 있는 RemonClient의 하위 클래스인 `RemonCall` 과 `RemonCast` 를 제공 합니다. 이를 통해 방송, 통신 기능을 구현할 수 있습니다.

## Resources

### Downloads, Change Log, Examples

{% embed url="<https://github.com/remotemonster/android-sdk/>" %}

RemoteMonster Android SDK에 대한 모든 정보를 얻을 수 있습니다. SDK를 얻을 수 있고 API 문서와 샘플등을 다운로드할 수 있습니다.

### Reference Document

{% embed url="<https://remotemonster.github.io/android-sdk>" %}

RemoteMonster Android SDK의 API 문서입니다.


# Android - Getting Started

## 준비사항

* 안드로이드 개발 환경
* minSdkVersion 21이상
* java 1.8 이상

## 프로젝트 생성 및 설정

### 프로젝트 생성 및 API 레벨 설정

Minimum SDK Level 21이상으로 설정 합니다.

![](/files/-MZR6giOVbOcclgtY5oD)

### Compatibility 설정

Open Module Settings에서 Source Compatibility, Target Compatibility를 1.8 이상으로 설정해줍니다.

![](/files/-LGUXc9pD7uuyT94WRVy)

###

### Gradle 설정

{% code title="Project:build.gradle" %}

```java
allprojects {
		repositories {
			...
			maven { url 'https://icl.jfrog.io/artifactory/kakaoenterprise' }
		}
	}
```

{% endcode %}

{% code title="Module:build.gradle" %}

```java
dependencies {
    /* kakao i Connect Live SDK */
    implementation 'com.kakaoenterprise:kakao-i-connect-live:2.9.11'
}
```

{% endcode %}

그 외에 multiDex 를 설정합니다.

```
android {
    defaultConfig {
        ...
        multiDexEnabled true
    }
}
```

### Permission 설정

안드로이드 최신 버전의 경우 앱의 권한에 대해 처음 앱 사용시 사용자에게 직접 묻게 됩니다. 서비스에서 추가적으로 필요한 권한을 설정합니다. SDK 에서 디폴트로 필요로하는 권한은 다음과 같습니다.

```markup
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.BLUETOOTH"/>
```

## 개발

이제 모든 준비가 끝났습니다. 아래를 통해 세부적인 개발 방법을 확인하세요.

### Service Key

SDK를 통해 RemoteMonster 방송, 통신 인프라에 접근하려면, Service Id와 Key가 필요합니다. 간단한 테스트와 데모를 위해서라면 이 과정을 넘어가도 좋습니다. 실제 서비스를 개발하고 운영하기 위해서는 아래를 참고하여 Service Id, Key를 발급 받아 적용하도록 합니다.

{% content-ref url="/pages/-LGUX\_VzVLLV66GO3DnE" %}
[Service Key](/common/service-key)
{% endcontent-ref %}

### 레이아웃 작업

방송과 통화 모두 음성전용이 아니라면 화면에 카메라 화면이나 상대편의 화면을 표시해야 합니다. Remon SDK에서는 이러한 화면을 담당하는 View가 별도로 있습니다. `SurfaceViewRenderer` 입니다. SurfaceViewRenderer를 layout.xml에 등록하고 해당 view를 Remon SDK에 지정해줘야 합니다.

즉, 다음과 같이 layout.xml에 Remon의 SurfaceViewRenderer를 등록합니다.

```markup
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/darker_gray"
android:layout_alignParentBottom="false"
android:layout_weight="2">
  <org.webrtc.SurfaceViewRenderer
    android:id="@+id/remote_video_view"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />
  <org.webrtc.SurfaceViewRenderer
    android:id="@+id/local_video_view"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />
</RelativeLayout>
```

위 예에서는 local 즉 자기 자신의 카메라영상을 출력하는 view와 remote 즉 상대방의 영상을 출력하는 view를 각각 등록하였습니다. 이제 필요한 작업이 모두 완료되었습니다. 위의 layout에 방송을 송출하거나 시청 혹은 통화를 시도하기 위한 버튼을 추가하면 기본적인 Remon SDK를 구동시켜볼 준비가 완료되었습니다.

### 방송

`RemonCast`로 방송 기능을 쉽고 빠르게 만들 수 있습니다.

#### 방송 송출

```java
caster = RemonCast.builder()
    .context(CastActivity.this)
    .localView(surfRendererlocal)        // 자신 Video Renderer
    .serviceId("SERVICEID1")    // RemoteMonster 사이트에서 등록했던 당신의 id를 입력하세요.
    .key("1234567890")    // RemoteMonster로부터 받은 당신의 key를 입력하세요.
    .build();
button.setOnClickListener{
    caster.create("CHANNEL_ID"); // 방송의 방 id와 함께 방송을 송출
}
```

버튼을 클릭하면 'CHANNEL\_ID'라는 이름의 방송 room이 만들어져서 당신은 방송을 즉시 할 수 있습니다. 당신의 방송을 시청하고 싶다면 다음 예제를 이용하여 개발을 하시거나 [웹용 Remon 샘플페이지](https://sample.remotemonster.com/castList.html)에서 당신의 방송을 시청해보세요.

#### 방송 시청

```java
viewer = RemonCast.builder()
    .context(ViewerActivity.this)
    .remoteView(surfRendererRemote)        // 방송자의 Video Renderer
    .serviceId("SERVICEID1")    // RemoteMonster 사이트에서 등록했던 당신의 id를 입력하세요.
    .key("1234567890")    // RemoteMonster로부터 받은 당신의 key를 입력하세요.
    .build();
button.setOnClickListener{
    viewer.join("CHANNEL_ID");              // 들어가고자 하는 channel
}
```

'CHANNEL\_ID'라는 이름의 방송을 시청합니다.&#x20;

#### 방송 목록 검색

시청의 경우 먼저 방송 목록을 가져와야 합니다. 때문에 다음과 같은 RemonCast의 메소드를 호출하여 방송 목록을 받아와야 합니다.

```
remonCast = RemonCast.builder().context(ListActivity.this).build();
remonCast.fetchCasts();
remonCast.onFetch(casts -> {
    for (Channel cast : casts) {
        myChannelId = cast.getId;
    }
});

remonCast.join(myChannelId);
```

방송 서비스 개발을 위한 좀 더 자세한 내용은 아래를 참고하세요.

{% content-ref url="/pages/-LGUX\_W01FLXFdahB7Rx" %}
[Livecast](/common/livecast)
{% endcontent-ref %}

### 통화

`RemonCall`로 통화 기능을 쉽고 빠르게 만들 수 있습니다.

```java
remonCall = RemonCall.builder()
    .context(CallActivity.this)        
    .localView(surfRendererLocal)        //나의 Video Renderer
    .remoteView(surfRendererRemote)      //상대방 video Renderer
    .serviceId("SERVICEID1")    // RemoteMonster 사이트에서 등록했던 당신의 id를 입력하세요.
    .key("1234567890")    // RemoteMonster로부터 받은 당신의 key를 입력하세요.
    .build();
button.setOnClickListener{
    remonCall.connect("CHANNEL_ID")
}
```

버튼을 클릭하면 'CHANNEL\_ID'라는 이름의 통화용 방이 만들어집니다. [웹용 Remon 샘플 페이지](https://sample.remotemonster.com/callList.html)에서 당신이 만든 안드로이드 앱과 서로 통화를 해보세요.

#### 통화 목록 검색

만약 A라는 통화용 채널을 만들었는데 상대방은 어떻게 A라는 채널이 있는지 확인할 수 있을까요? 검색을 해야합니다. 다음과 같이 RemonCall의 메소드를 이용하여 채널을 검색하고 해당 채널에 들어갈 수 있습니다. 통화는 항상 2명만 참여할 수 있습니다.

```java
remonCall = RemonCall.builder().build();
remonCall.fetchCalls();
remonCall.onFetch(calls -> {
    for (Channel call : calls) {
        if (call.getStatus.equals("WAIT")) {   // Only WAIT channels
            myChannelId = call.getId;
        }
    }
});

remonCall.connect(myChannelId)
```

통화에 대하여 좀 더 자세한 내용은 아래를 참고하세요.

{% content-ref url="/pages/-LGUX\_W1RyXdYhFarqs1" %}
[Communication](/common/communication)
{% endcontent-ref %}

### 그 외 주요 메소드들

remonCall이나 remonCast로 수행할 수 있는 메소드는 크게 다음과 같습니다.

* close() : 방송이나 통화를 종료할 때 사용합니다. 모든 자원이 사라집니다. close 후에 다시 RemonCall이나 RemonCast를 이용하여 방송/통화를 재개하고 싶다면 객체를 새롭게 생성하셔야 합니다.
* showLocalVideo(): 방송이나 통화 시작 전에 자신의 카메라 화면을 미리 보고 싶을때 호출합니다.
* pauseLocalVideo(true): 방송이나 통화 중 자신의 카메라 화면을 중지하고 싶을 때 호출합니다.
* switchCamera(): 모바일 플랫폼에서 가지고 있는 카메라를 순차적으로 스위칭해서 보여줍니다.
* fetchCalls, fetchCasts: 채널 검색을 위한 명령입니다.

### Callback에 대하여

다양한 이벤트를 받아서 처리할 수 있습니다. 이벤트 콜백은 방송, 통신에 따라 콜백의 종류가 다릅니다.&#x20;

* onInit(): 방송(RemonCast), 통화(RemonCall) 객체를 생성하면 인증절차등을 거쳐서 객체 생성이 마무리됩니다. 이렇게 잘 마무리되면 onInit메소드가 호출됩니다. 보통 onInit의 인자로 인증의 결과인 token값이 같이 전송됩니다.&#x20;
* onConnect(): 통화(RemonCall)를 위한 채널이 만들어졌을 때 발생합니다. 연결된 채널명(채널ID)가 전달되며, 동일한 채널에 접속한 사용자간 Peer 연결이 진행됩니다.
* onComplete : 통화(RemonCall) 를 위해 다른 사용자와의 Peer 연결이 완료된 이후 발생합니다.
* onCreate(): 방송(RemonCast) 을 온전히 송출하게 될 때 발생합니다. 인자값으로 방송룸의 ID가 반환됩니다.
* onJoin(): 방송(RemonCast) 시청이 온전히 이루어질 때 발생합니다.
* onClose(): 방송(RemonCast)과 통화(RemonCall)가 종료되었을 때 발생합니다. 인자값으로 CloseType이 전달됩니다.
* onError(): 에러가 발생하면 모두 이 onError로 오류 메시지가 전달됩니다.&#x20;
* onStat(): 현재 방송, 통화 품질을 3초마다 주기로 알려줍니다.

콜백에 대해 세부 내용 알고싶으시면 아래 내용을 참고하세요.

{% content-ref url="/pages/-LGUX\_W2IMDRX7sIYMoc" %}
[Callbacks](/common/callbacks)
{% endcontent-ref %}


# Android - Media

## Overview

영상, 음성 미디어의 표출과 사용에 대해 안내합니다.

공통적인 부분은 아래를 참고하세요.

{% content-ref url="/pages/-LGUX\_W4zkJRbNnqyOLb" %}
[Media](/common/media)
{% endcontent-ref %}

## View

레이아웃과 관련하여 2개의 클래스를 제공하고 있습니다. 가장 핵심이 되는, 영상을 출력하는 View인 `SurfaceViewRender` 와 SurfaceViewRender 를 효율적으로 RelativeLayout에서 배치하는데 도움을 주는 `PercentFrameLayout`으로 이루어져 있습니다. 이 중에서도 `SurfaceViewRender`가 가장 핵심이므로 먼저 살펴보겠습니다.

### SurfaceViewRender

#### Basic

Android layout 파일에서 다음과 같이 layout에 배치함으로써 `SurfaceViewRender`를 사용할 수 있습니다.

```markup
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/darker_gray"
android:layout_alignParentBottom="false"
android:layout_weight="2">
  <org.webrtc.SurfaceViewRenderer
    android:id="@+id/remote_video_view"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />
  <org.webrtc.SurfaceViewRenderer
    android:id="@+id/local_video_view"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />
</RelativeLayout>
```

그리고 특정 Activity에서 다음과 같이 이 View의 객체를 얻어옵니다.

```java
SurfaceViewRender localRender =
  (SurfaceViewRenderer) findViewById(R.id.local_video_view);
SurfaceViewRender remoteRender =
  (SurfaceViewRenderer) findViewById(R.id.remote_video_view);
```

이제 이 뷰를 사용할 기본 준비가 완료되었습니다. `Remon` 클래스의 객체를 생성하고 생성할 때 `Config`객체에 이 두 쌍의 뷰를 설정하면 통신이 시작됨과 함께 이 뷰에 카메라나 원격의 영상 스트림이 출력됩니다.

#### Advanced

몇가지 이 `SurfaceViewRender`의 메소드를 살펴보겠습니다.

뷰간의 Z-Order를 설정할 수 있습니다. 즉 이 뷰를 다른 뷰보다 위 위치하도록 설정합니다. 겹치는 뷰가 있을 경우 다른 뷰는 false로 하고 해당 뷰만 true로 설정해야 합니다.

```java
localRender.setZOrderMediaOverlay(true/false);
```

해당 뷰의 영상을 거울효과를 주어서 좌우가 바뀌는 방식으로 보여줄 수 있습니다.

```java
localRender.setMirror(false);
```

layout에 어떻게 채울지를 결정합니다.

```java
localRender.setScalingType(RendererCommon.ScalingType);
```

### PercentFrameLayout

안드로이드의 ConstraintLayout 과 같이 제공하려는 서비스에 맞게 레이아웃을 구성 합니다. 리모트몬스터 안드로이드 SDK에서는 영상관련 View의 배치를 쉽게 하기 위해 `PercentFrameLayout`을 제공합니다. `PercentFrameLayout`을 이용하면 영상 관련 View를 RelativeLayout안에서 자유롭게 배치하고 동적으로 움직일 수 있습니다. `PercentFrameLayout`은 layout내의 뷰들을 퍼센트 방식으로 배치합니다.&#x20;

해당 레이아웃을 relativeLayout상에서 가로세로 100%채워서 배치 합니다.

```java
layout.setPosition(0,0,100,100);
```

해당 레이아웃을 relativeLayout을 4분면으로 나눠서 좌측 하단에 50%를 차지합니다.

```java
layout.setPosition(0,50,50,50);
```

### Fill Policy

Video를 보여주는 `SurfaceViewRenderer`의 `ScalingType`을 통해 비디오소스가 보이는 방식을 지정 할 수 있습니다.&#x20;

![Video Source](/files/-LRpQ6mil1z-A2G94K0P)

#### Fit

뷰의 크기에 맞게 비디오 프레임의 크기가 조정됩니다. 가로 세로 비율을 유지합니다. 검은 색 테두리가 표시 될 수 있습니다.

```java
surfaceView.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FIT);
```

![](/files/-LRpSVPznPyL6dlpW31N)

#### Fill

비디오 프레임이 뷰의 크기를 채우기 위해 크기가 조정됩니다.종횡비 유지. 비디오 프레임의 일부는 Clipping 됩니다.

```java
surfaceView.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FILL);
```

![](/files/-LRpSXnfMBSdSVUjRQNs)

#### Balanced

FIT와 FILL 간의 절충입니. 비디오 프레임은 다음과 같이 채울 것입니다. 적어도 가로 세로 비율을 유지하면서 뷰를 가능하게 합니다.

```java
surfaceView.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_BALNANCED);
```

![](/files/-LRpSZ9C_USscfmJfF0t)

## Audio

RemonCall의 setSpeakerphone(boolean)을 이용하여,  스피커폰을 사용할지 Earpiece 모드를 사용할 지 설정 할 수 있습니다.&#x20;

{% code title="CallActivity.java" %}

```java
btnSpeakerPhoneOnOff.setOnClickListener(view -> {
            if (remonCall != null) {
                isSpeakerPhone = isSpeakerPhone ? (isSpeakerPhone = false) : (isSpeakerPhone = true);
                remonCall.setSpeakerphoneOn(isSpeakerPhone);
            }
        });
```

{% endcode %}

## Background Policy

안드로이드는 특별한 설정이 없으면 백그라운드시 모든 미디어가 송출 및 수신이 됩니다. 만약 수신등에서 백그라운드로 진입시 음소거가 필요하다면, 시스템 이벤트등을 통해 별도 처리하면 됩니다.

| 상황       | 미디어 | 내용               |
| -------- | --- | ---------------- |
| 송출 백그라운드 | 영상  | 수신측에서 영상/음성 정상 수 |
| 송출 백그라운드 | 음성  | 수신측에서 음성 정상 수신   |
| 수신 백그라운  | 영상  | 음성을 들을 수 있음      |
| 수신 백그라운드 | 음성  | 음성을 들을 수 있음      |


# Android - ProGuard

ProGuard 설정시 RemonSDK의 Setting이 필요합니다.  SDK에서 쓰이는 옵션과 종류에대해 정리합니다.

## gradle.build

{% code title="gradle.build" %}

```
defaultConfig {
    ...
    consumerProguardFiles 'consumer-rules.pro'
}

buildTypes {
    release {
        minifyEnabled false
        proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
    }

    debug {
        minifyEnabled false
        shrinkResources false
        proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
    }
}
```

{% endcode %}

## consumer-rules.pro

{% code title="proguard-rules.pro" %}

```
-keep class com.remotemonster.sdk.* { *;}
-keep class com.remotemonster.sdk.data.** { *;}
-keep class com.remotemonster.sdk.util.Logger{ *;}
-keep enum com.remotemonster.sdk.data.ChannelStatus { *;}
-keep enum com.remotemonster.sdk.data.ChannelType { *;}

# webrtc
-keep class org.webrtc.** { *; }
-keep interface org.webrtc.** { *; }

# netty 4.0
-dontwarn io.netty.**
-keep class io.netty.** { *;}
-keep interface io.netty.** { *;}

# Slf4j for android
-keep class org.slf4j.** { *;}
-keep interface org.slf4j.** { *; }

# Jzlib
-keep class com.jcraft.jzlib.** { *; }
-keep interface com.jcraft.jzlib.** { *; }

#################################################################################################
### https://github.com/google/gson/blob/master/examples/android-proguard-example/proguard.cfg ###
#################################################################################################
##---------------Begin: proguard configuration for Gson  ----------
# Gson uses generic type information stored in a class file when working with fields. Proguard
# removes such information by default, so configure it to keep all of it.
-keepattributes Signature

# For using GSON @Expose annotation
-keepattributes *Annotation*

# Gson specific classes
-dontwarn sun.misc.**
#-keep class com.google.gson.stream.** { *; }


# Prevent proguard from stripping interface information from TypeAdapter, TypeAdapterFactory,
# JsonSerializer, JsonDeserializer instances (so they can be used in @JsonAdapter)
-keep class * extends com.google.gson.TypeAdapter
-keep class * implements com.google.gson.TypeAdapterFactory
-keep class * implements com.google.gson.JsonSerializer
-keep class * implements com.google.gson.JsonDeserializer

# Prevent R8 from leaving Data object members always null
-keepclassmembers,allowobfuscation class * {
  @com.google.gson.annotations.SerializedName <fields>;
}

##---------------End: proguard configuration for Gson  ----------

#######################################################################
### https://github.com/FasterXML/jackson-docs/wiki/JacksonOnAndroid ###
#######################################################################
# Proguard configuration for Jackson 2.x
-dontwarn com.fasterxml.jackson.databind.**
-keepclassmembers class * {
     @com.fasterxml.jackson.annotation.* *;
}


########################################################################################################
### https://github.com/square/okio/blob/master/okio/src/jvmMain/resources/META-INF/proguard/okio.pro ###
########################################################################################################
# Animal Sniffer compileOnly dependency to ensure APIs are compatible with older versions of Java.
-dontwarn org.codehaus.mojo.animal_sniffer.*


############################################################################################################
### https://github.com/square/okhttp/blob/master/okhttp/src/main/resources/META-INF/proguard/okhttp3.pro ###
############################################################################################################
# JSR 305 annotations are for embedding nullability information.
-dontwarn javax.annotation.**

# A resource is loaded with a relative path so the package of this class must be preserved.
-keepnames class okhttp3.internal.publicsuffix.PublicSuffixDatabase

# Animal Sniffer compileOnly dependency to ensure APIs are compatible with older versions of Java.
-dontwarn org.codehaus.mojo.animal_sniffer.*

# OkHttp platform used only on JVM and when Conscrypt dependency is available.
-dontwarn okhttp3.internal.platform.ConscryptPlatform


##################################################################################################################
### https://github.com/square/retrofit/blob/master/retrofit/src/main/resources/META-INF/proguard/retrofit2.pro ###
##################################################################################################################
# Retrofit does reflection on generic parameters. InnerClasses is required to use Signature and
# EnclosingMethod is required to use InnerClasses.
-keepattributes Signature, InnerClasses, EnclosingMethod

# Retrofit does reflection on method and parameter annotations.
-keepattributes RuntimeVisibleAnnotations, RuntimeVisibleParameterAnnotations

# Retain service method parameters when optimizing.
-keepclassmembers,allowshrinking,allowobfuscation interface * {
    @retrofit2.http.* <methods>;
}

# Ignore annotation used for build tooling.
-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement

# Ignore JSR 305 annotations for embedding nullability information.
-dontwarn javax.annotation.**

# Guarded by a NoClassDefFoundError try/catch and only used when on the classpath.
-dontwarn kotlin.Unit

# Top-level functions that can only be used by Kotlin.
-dontwarn retrofit2.KotlinExtensions
-dontwarn retrofit2.KotlinExtensions$*

# With R8 full mode, it sees no subtypes of Retrofit interfaces since they are created with a Proxy
# and replaces all potential values with null. Explicitly keeping the interfaces prevents this.
-if interface * { @retrofit2.http.* <methods>; }
-keep,allowobfuscation interface <1>
```

{% endcode %}

## proguard-rules.pro

{% code title="proguard-rules.pro" %}

```
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /home/calmglow/dev/Android/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
#   http://developer.android.com/guide/developing/tools/proguard.html

# Add any project specific keep options here:

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
#   public *;
#}
-keepattributes SourceFile,LineNumberTable, InnerClasses, Exceptions, Signature
-keepattributes *Annotation*
-dontoptimize
-dontwarn org.**
-dontwarn retrofit2.**

-keep class com.remotemonster.sdk.* { *;}
-keep class com.remotemonster.sdk.data.** { *;}
-keep class com.remotemonster.sdk.util.Logger{ *;}
-keep class com.remotemonster.sdk.core.RemonExternalCapturer{ *;}
-keep class com.remotemonster.sdk.data.Channel{ *;}
-keep class com.remotemonster.sdk.data.InitMessage{ *;}
-keep enum com.remotemonster.sdk.data.ChannelStatus{ *;}
-keep enum com.remotemonster.sdk.data.ChannelType{ *;}
-keep class org.** { *;}
-keep interface org.** { *;}

-keepclasseswithmembers class * {
    @retrofit2.http.* <methods>;
}
-keep class retrofit2.** { *; }
```

{% endcode %}


# iOS - Overview

## Overview

![](/files/-LGUXbcjQfXdwtumRvnH)

`Remon`를 클래스는 RemoteMonster SDK 에서 가장 핵심이 되는 클래스 입니다. `Remon` 클래스를 사용하고, `RemonDelegate`를 직접 구현 하여 `Remon`이 제공하는 통신 기능과 방송 기능을 이용 할 수도 있지만 이는 복잡하고, 따분한 작업이 될 것입니다. 그래서 SDK 사용자가 좀 더 쉽고 빠르게 `Remon`를 이용 할 수 있도록 복잡 하고, 반복적인 기본 작업을 포함 하고 있는 `RemonController` 클래스와 Interface Builder지원을 위한 `RemonIBController` 클래스를 제공 합니다. `RemonController` 클래스를 이용한면 복잡한 `RemonDelegate`의 메소드들을 구현 할 필요 없이 필요한 부분만 추가 적으로 구현 하면 됩니다.

## Resources

### Downloads, Change Log, Examples

{% embed url="<https://github.com/remotemonster/ios-sdk>" %}

RemoteMonster iOS SDK에 대한 모든 정보를 얻을 수 있습니다. 다양한 예제를 코드로 검토하세요.

### Reference Document

{% embed url="<https://remotemonster.github.io/ios-sdk/>" %}

SDK의 기능을 세부적으로 안내합니다.


# iOS - Getting Started

## 준비 사항

* Xcode 개발 환경
* iOS 10.0 이상

## 프로젝트 생성 및 설정

Xcode에서 Swift기반의 프로젝트를 생성합니다.

프로젝트 생성 후 `Build Settings`에서 `Enable Bitcode`에 대하여 `No`를 설정해야 합니다.

![Bitcode](/files/-Ld6qIpXDLHXx0v_pnou)

Objc 환경이라면 ALWAYS\_EMBED\_SWIFT\_STANDARD\_LIBRARIES 값을 Yes로 설정해야 합니다.

![ALWAYS\_EMBED\_SWIFT\_STANDARD\_LIBRARIES](/files/-Ld6qIpZwQdtYfKIb5QZ)

또한 Info.plist에서 다음 항목에 대해 추가 혹은 변경을 해주셔야 합니다.

* Privacy: Bluetooth, Microphone, Camera

![Settings](/files/-Ld6qIpa2MsE7fAnkhP_)

## SDK 설치 - Cocoapods

SDK 설치를 원하는 프로젝트의 `Podfile`에 `pod 'RemoteMonster', '~> 2.6.11'`을 추가 합니다

{% code title="Podfile" %}

```
target 'MyApp' do
  # Comment the next line if you're not using Swift and don't want to use dynamic frameworks
  use_frameworks!
  pod 'RemoteMonster', '~> 2.6.11'
end
```

{% endcode %}

. 그리고 터미널에서 `pod install` 를 실행 합니다. 만약 `pod install` 이 동작하지 않는 다면 `pod update`를 실행 합니다.

```bash
$ pod install
```

업데이트가 정상적으로 이루어지지 않는 경우 cocoapods의 로컬 저장소를 먼저 업데이트 후 사용해 보시기 바랍니다.

```
$ pod repo update
$ pod update
```

## SDK 설치 - 직접 import

우선 아래의 링크를 통해 iOS SDK의 마지막 버전을 다운로드 받습니다.

{% embed url="<https://github.com/remotemonster/ios-sdk>" %}

다운로드받은 RemoteMonster iOS SDK를 폴더에는 3개의 관련 Framework이 존재합니다. 각각의 Framework을 Finder에서 끌어다 프로젝트 트리창에 놓습니다. 그러면 RemoteMonster SDK를 프레임워크로 인식하게 됩니다.

![Framework](/files/-Ld6qIpcxmw2szyGL8X3)

Build Phases에 copy file 항목을 추가 하고, 위 단계에서 추가한 Frameworks를 복사 대상으로 추가 하여 줍니다.

![Copy Frameworks](/files/-Ld6qIpe6LoMSXMQrKW5)

## Remon 설정 및 레이아웃 구성

### 인터페이스빌

RemonCast, RemonCall 객체는 `RemonClient(sdk 2.6.9 이하 버전은 RemonIBController)` 를 상속받은 객체이며,  RemonClient는 InterfaceBuilder를 이용한 설정이 가능하도록 구성되어 있습니다.&#x20;

* 스토리보드에 `RemonClient`의 하위객체인 `RemonCall` 또는 `RemonCast`를 추가합니다.
  * `RemonCall`를 1:1 통신을 지원 하며 `RemonCast`는 1:N 방송을 지원 합니다.
  * xcode의 한계상 직접 RemonCall이나 RemonCast를 삽입이 불가능합니다. 따라서 RemonCall이나 RemonCast 객체를 Designer에 삽입할 수 없으므로 Library에서 Object 컴포넌트로 먼저 삽입후 해당 Object의 class를 아래 그림과 같이 직접 수정해 주세요. module은 RemoteMonster로 설정하시면 됩니다. ![](/files/-Ld6qIpg6NsTGdPdgbe4)
  * InterfaceBuilder에서 `Utilities` 뷰를 이용하여 `RemonCall`을 설정 합니다.
* `ServiceID`와 `Service Key`를 설정합니다.
  * 만약 간단하게 테스트를 하기 원한다면 아무것도 입력 안해도 됩니다.
  * 실제 서비스를 고려한다면 아래를 참고하여 내가 사용할 키를 발급받으세요.

{% content-ref url="/pages/-LGUX\_VzVLLV66GO3DnE" %}
[Service Key](/common/service-key)
{% endcontent-ref %}

![](/files/-Ld7-kQzup4CCfkLEa-N)

* 스토리보드에서 원하는 Scene에서 원하는 위치에 `Veiw`를 배치하고 `RemonClient(sdk 2.6.9 이하는 RemonIBController)`의 `remoteView`와 `localView`에 바인딩 하여 줍니다.

![](/files/-Ld6qIpkRvRZSXzSOxAo)

* `Remon`를 사용하는 `ViewContoller`에 RemoteMonster SDK를 임포트 하고, `RemonClient(sdk 2.6.9 이하는 RemonIBController)`객체를 아웃렛 변수에 바인딩 합니다.

![](/files/-Ld6qIpmFyfHeCfoOpIj)

### 코드에서 직접 구현

## 개발

이제 모든 준비가 끝났습니다. 아래를 통해 세부적인 개발 방법을 확인하세요.

### 방송

`RemonCast`로 방송 기능을 쉽고 빠르게 만들 수 있습니다.

#### 방송 송출

```swift
let caster = RemonCast()
caster.create()
```

#### 방송 시청

```swift
let viewer = RemonCast()
viewer.join("CHANNEL_ID")
```

혹은 좀더 자세한 내용은 아래를 참고하세요.

{% content-ref url="/pages/-LGUX\_W01FLXFdahB7Rx" %}
[Livecast](/common/livecast)
{% endcontent-ref %}

### 통신

`RemonCall`로 통신 기능을 쉽고 빠르게 만들 수 있습니다.

```swift
let remonCall = RemonCall()
remonCall.connect("CHANNEL_ID")            // Communication
```

혹은 좀더 자세한 내용은 아래를 참고하세요.

{% content-ref url="/pages/-LGUX\_W1RyXdYhFarqs1" %}
[Communication](/common/communication)
{% endcontent-ref %}

### 그 외 주요 메소드들

remonCall이나 remonCast로 수행할 수 있는 메소드는 크게 다음과 같습니다.

* close() : 방송이나 통화를 종료할 때 사용합니다. 모든 자원이 사라집니다. close 후에 다시 RemonCall이나 RemonCast를 이용하여 방송/통화를 재개하고 싶다면 객체를 새롭게 생성하셔야 합니다.
* showLocalVideo(): 방송이나 통화 시작 전에 자신의 카메라 화면을 미리 보고 싶을때 호출합니다.
* pauseLocalVideo(true): 방송이나 통화 중 자신의 카메라 화면을 중지하고 싶을 때 호출합니다.
* switchCamera(): 모바일 플랫폼에서 가지고 있는 카메라를 순차적으로 스위칭해서 보여줍니다.
* fetchCalls, fetchCasts: 채널 검색을 위한 명령입니다.

### Callback에 대하여

다양한 이벤트를 받아서 처리할 수 있습니다.

* onInit(): 방송(RemonCast), 통화(RemonCall) 객체를 생성하면 인증절차등을 거쳐서 객체 생성이 마무리됩니다. 이렇게 잘 마무리되면 onInit메소드가 호출됩니다. 보통 onInit의 인자로 인증의 결과인 token값이 같이 전송됩니다.
* onConnect(): 통화(RemonCall)를 위한 채널이 만들어졌을 때 발생합니다. 연결된 채널명(채널ID)가 전달되며, 동일한 채널에 접속한 사용자간 Peer 연결이 진행됩니다.
* onComplete : 통화(RemonCall) 를 위해 다른 사용자와의 Peer 연결이 완료된 이후 발생합니다.
* onCreate(): 방송(RemonCast) 을 온전히 송출하게 될 때 발생합니다. 인자값으로 방송룸의 ID가 반환됩니다.
* onJoin(): 방송(RemonCast) 시청이 온전히 이루어질 때 발생합니다.
* onClose(): 방송(RemonCast)과 통화(RemonCall)가 종료되었을 때 발생합니다. 인자값으로 CloseType이 전달됩니다.
* onError(): 에러가 발생하면 모두 이 onError로 오류 메시지가 전달됩니다.&#x20;
* onStat(): 현재 방송, 통화 품질을 3초마다 주기로 알려줍니다.

{% content-ref url="/pages/-LGUX\_W2IMDRX7sIYMoc" %}
[Callbacks](/common/callbacks)
{% endcontent-ref %}


# iOS - Media

## Overview

영상, 음성 미디어의 표출과 사용에 대해 안내합니다.&#x20;

공통적인 부분은 아래를 참고하세요.

{% content-ref url="/pages/-LGUX\_W4zkJRbNnqyOLb" %}
[Media](/common/media)
{% endcontent-ref %}

## View

### Basic

Interface Builder를 통해 StoryBoard를 이용하여 환경설정과 개발을 할 수 있으며, 제공된 SDK를 통해 개별 코드로 View를 구성할 수 있습니다.

Interface Builder를 통한 기본적인 사용은 아래를 참고하세요.

{% content-ref url="/pages/-LGUX\_VtIk9TFRWqQTsY" %}
[iOS - Getting Started](/ios/ios-getting-started)
{% endcontent-ref %}

### Advanced

Interface Builder를 사용하지 않는 다면 아래 코드를 참조 하세요.

{% tabs %}
{% tab title="iOS - Swift" %}

```swift
let remonCall = RemonCall()
remonCall.remoteView = myRemoteView
remonCall.localView = myLocalView
```

{% endtab %}

{% tab title="iOS - ObjC" %}

```
RemonCall *remonCall = [[RemonCall alloc] init];
remonCall.remoteView = myRemoteView;
remonCall.localView = myLocalView;
```

{% endtab %}
{% endtabs %}

### Size

영상 크기값을 얻기 원하신다면 `onRemoteVideoSizeChanged` 함수와 `onLocalVideoSizeChanged` 함수를 구현하여 줍니다.

```swift
let remonCall = RemonCall()
remonCall.onRemoteVideoSizeChanged {(view, size) in 
    let raito = size.height / size.width
    let oldSize = view.frame.size
    let newFrame = 
    CGRect(x: 0.0, y: 0.0, width: oldSize.width, height: oldSize.width * raito)
    view.frame = newFram
}
```

{% hint style="info" %}
`RemonCall/RemonCast 에서는 사이즈에 대한 정보만 제공하고, 실제 뷰의 비율을 조정하지않습니다. 특정 비율로 영상을 보여주기 위해서는 LayoutConstraint 등을 사용해 원하는 뷰 크로 조정해야 합니다.`
{% endhint %}

###

### Use External Capturer

2.4.43 버전 부터 기본 RTCCameraCapturer가 아닌 외부 Capturer를 이용 할 수 있도록 지원합니다. 이 기능을 사용하기 위해서는 RemonController의 useExternalCapturer  값을 true로 설정 하고, RemonController의 localExternalCaptureDelegator에게 외부 Capturer로 부터 얻어온 Frame를 넘겨주시면 됩니다.

{% tabs %}
{% tab title="iOS - Swift" %}

```swift
remonCast.useExternalCapturer = true

remonCast.onCreate { (chid) in
    self.startCapter()
}

func startCapture() {
    YourExCapturer.captureBlock { (pixelBuffer, comTime) in
        if let rtcCaptureDelegate = 
            remonCall.localExternalCaptureDelegator {
                rtcCaptureDelegate.didCaptureFrame(
                    pixelBuffer: pixelBuffer,
                    timeStamp: cmTime, 
                    videoRetation: ._0)
        }
    }
} 
```

{% endtab %}

{% tab title="iOS - ObjC" %}

```objectivec
remonCast.useExternalCapturer = YES;

[remonCast onCreateWithBlock:^(NSString * _Nullable chId) {
    [self startCapture];    
}];

- (void)startCapture {
    [yourExCapturer captureBlock: ^(CVPixelBuffer *pixelBuffer, CMTime *cmTime) {
       [remonCast.localExternalCaptureDelegator 
           didCaptureFrameWithPixelBuffer:pixelBuffer 
           timeStamp:cmTime 
           videoRetation:RemonVideoRotation_0]; 
    }];
}
```

{% endtab %}
{% endtabs %}

## Audio

### Session Category, Mode

iOS에서 `Audio Session Category, Mode` 설정에 따라 스피커출력, 음소거 스위치의 작동, 이어폰 연결 작동, 블루투스 등이 상이 하게 작동할 수 있습니다. 서비스에 따라 `AVAudioSession.Category.playback, AVAudioSession.Category.playAndRecord` 등을 사용할 수 있습니다.  WebRTC 의 기본값은 playAndRecord 이며, 대부분의 방송, 통신에 대해서는 기본값을 권장합니다. 다만 통화, 방송 송출, 방송 시청 등 서비스에 맞춰 필요에 따라 AVAudioSession.Category 를 변경해 사용하실 수 있습니다.

소리 출력 디바이스는 기본적으로 ear piece 로 나오게 되며 스피커로 나오게 하려면 아래와 같은 적용이 필요합니다. RemonCall, RemonCast 생성전에 호출하면 해당 설정이 유지되므로, viewDidLoad() 메쏘드에서 원하는 카테고리와 모드를 설정해 줍니다.

구버전과 최신버전간 사용 api 가 다르므로 아래 코드를 참고하시기 바랍니다.

{% tabs %}
{% tab title="iOS - Swift" %}

```swift
override func viewDidLoad() {
	super.viewDidLoad()
	
	// sdk 2.6.0 이하버전
	do {
        // 오디오세션 카테고리 설정
        if #available(iOS 10.0, *) {
            try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playback, mode: AVAudioSession.Mode.default)
        }else {
            AVAudioSession.sharedInstance().perform(NSSelectorFromString("setCategory:error:"), with: AVAudioSession.Category.playback)
        }
        try AVAudioSession.sharedInstance().setActive(true, options: [])
        try AVAudioSession.sharedInstance().overrideOutputAudioPort(.speaker)
    } catch {
        print(error)
    }
	
	
	// sdk 2.6.10 이상
	// AVAudioSession.Mode.voiceChat : 수화기 사용
	// AVAudioSession.Mode.videoChat : 스피커 사용
	RemonClient.setAudioSessionConfiguration(
                category: AVAudioSession.Category.playAndRecord,
                mode: AVAudioSession.Mode.videoChat,
                options: [] );
}
```

{% endtab %}

{% tab title="iOS - ObjC" %}

```objectivec
// Objective-C의 경우 아직 업데이트되지 않았습니다.
//  이 코드는 soloAmbient 카테고리를 사용하고, speaker로 음성을 출력 합니다.
- (void)viewDidLoad {
    [super viewDidLoad];
	@try {
	    NSError *error = nil;
	    [AVAudioSession.sharedInstance setCategory:AVAudioSessionCategorySoloAmbient error:&error];
	    [AVAudioSession.sharedInstance setActive:YES error:&error];
	    [AVAudioSession.sharedInstance overrideOutputAudioPort:AVAudioSessionPortOverrideSpeaker error:&error];
	} @catch (NSException *exception) {
	    NSLog(@"error is %@",[exception description]);
	}
}
```

{% endtab %}
{% endtabs %}

Audio Session에 대한 모드와 일반적인 사용은 아래 링크를 확인하세요.

{% embed url="<https://developer.apple.com/library/archive/documentation/Audio/Conceptual/AudioSessionProgrammingGuide/AudioSessionCategoriesandModes/AudioSessionCategoriesandModes.html>" %}

### Volume Ratio

`RemonCast` 또는 `RemonCall`를 `music mode`로 이용할 경우 `outputVolume`이 크게 느껴질 수 있습니다. 이럴 경우 `volumeRatio` 값을 조정 하여 `AudioSession outputVolume` 과의 출력 비율 조정 할 수 있습니다. 예를 들어 `outputVolume`이 1.0 (최대값) 일때 `volumeRatio`를 0.8로 설정 한다면 `RemonCast` 또는 `RemonCall`은 `outputVolume`의 80%의 크기로 출력 되어 집니다.

{% tabs %}
{% tab title="iOS - Swift" %}

```swift
self.remonCall.volumeRatio = 0.8
```

{% endtab %}

{% tab title="iOS - ObjC" %}

```objectivec
[self.remonCall setVolumeRatio:0.8]
```

{% endtab %}
{% endtabs %}

### Mixing Sound

WebRTC가 동작을 시작 하면  WebRTC가 `AVAudioSession`을 점유 합니다. 그로 인하여 `AVAudioSession`을 이용한 효과음 재생에 문제가 발생 합니다. 이 문제를 해결 하기 위하여 `OpenAL`과 같은 저수준의 효과음 재생 라이브러리 사용을 권장 드리며 `OpenAL`을 좀 더 쉽게 사용할 수 있게 만들어진 `ObjectAL`과 같은 라이브러리를 사용 할 수 있습니다.

```objectivec
#import "ObjectAL.h"
#define SHOOT_SOUND @"shoot.caf"

[[OALSimpleAudio sharedInstance] playEffect:SHOOT_SOUND];
```

{% embed url="<http://kstenerud.github.io/ObjectAL-for-iPhone/documentation/index.html>" %}

## Background Policy

백그라운드에서 SDK 연결을 지속적으로 필요하면 아래의 옵션을 Project > Targets > Capabilities > Background Modes 에서 설정하면 됩니다. 백그라운드 설정을 안할 경우 앱이 백그라운드로 진입시 RemoteMonster와의 연결이 종료되어 방송 및 통화가 종료됩니다.&#x20;

![](/files/-LGUXcogOamcOFL2apdr)

{% embed url="<https://developer.apple.com/library/archive/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/BackgroundExecution/BackgroundExecution.html>" %}

아래는 지속적으로 연결이 되어있을때 작동이며 위 옵션을 키지 않으면 백그라운드시 모든 경우에 영상, 음성의 송출, 수신이 중단됩니다. 단  아래의 동작은 `Audio Session Category`가 `AVAudioSessionCategoryPlayback` 일 경우의 동작입니다.

| 상황       | 미디어 | 내용                                       |
| -------- | --- | ---------------------------------------- |
| 송출 백그라운  | 영상  | 수신측 정지화면(마지막 프레임, FPS 0)이나 음성은 들림        |
| 송출 백그라운드 | 음성  | 수신측 음성들림                                 |
| 수신 백그라운드 | 영상  | 음성은 들을 수 있으며 개발을 통해 백그라운드시 음성을 키거나 끌 수있음 |
| 수신 백그라운드 | 음성  | 음성은 들을 수 있으며 개발을 통해 백그라운드시 음성을 키거나 끌 수있음 |


# Webhook API - Livecast - beta

기능개발시 필요에 따라 리모트몬스터로부터 특정 정보를 받을 필요가 종종 생깁니다. 리모트몬스터는 이를 Webhook 형태로 제공하고 있습니다. 호출 받고자하는 Webhook 주소는 아래를 참고하여 설정합니다.

{% content-ref url="/pages/-LSroGK9ChQm81zu59TM" %}
[Broken mention](broken://pages/-LSroGK9ChQm81zu59TM)
{% endcontent-ref %}

## &#x20;record done

<mark style="color:green;">`POST`</mark> `https://YOUR_DOMAIN.COM/SLUG/`&#x20;

&#x20;녹화가 완료되면 아래와 같이 JSON 몸체와 함께 POST를 호출 합니다.&#x20;

#### Request Body

| Name      | Type   | Description        |
| --------- | ------ | ------------------ |
| id        | string | 녹화가 진행된 Channel Id |
| url       | string | 녹화된 파일의 URL        |
| duration  | string | 녹화된 시간             |
| filesize  | string | 녹화된 파일의 크기(Byte)   |
| thumbnail | string | 녹화된 파일의 썸네일 URL    |

{% tabs %}
{% tab title="200 " %}

```
```

{% endtab %}
{% endtabs %}

자세한 내용은 아래를 확인하세요.

{% content-ref url="/pages/-LRKoeIOesQqWgW9X0PY" %}
[Record - beta](/common/record)
{% endcontent-ref %}

## &#x20;channel create, close

<mark style="color:blue;">`GET`</mark> `https://YOUR_DOMAIN.COM/SLUG/`&#x20;

&#x20;방송이 종료되면 아래와 같이 해당하는 값을 Query로 호출합니다.&#x20;

#### Query Parameters

| Name   | Type   | Description       |
| ------ | ------ | ----------------- |
| action | string | close \|\| create |
| chid   | string | Channel Id        |

{% tabs %}
{% tab title="200 " %}

```
```

{% endtab %}
{% endtabs %}


# Managing Channels

리모트몬스터는 채널 관리를 위한 서버용 API를 제공합니다. 이 API는 채널 검색, 채널 강제 종료 기능을 제공합니다. \
서버용 API는 앱용(클라이언트용) API와 달리 비디오/오디오를 전송할 수 없습니다.&#x20;

## ※ 주의 사항

{% hint style="info" %}
타인의 Service ID, Secret Key를 무단으로 이용하면 처벌을 받을 수 있습니다. \
(정보통신망 이용 촉진 및 정보보호에 관한 법률, 통신비밀보호법, 형법 등에 의거)
{% endhint %}

{% hint style="info" %}
타인의 Service ID, Secret Key를 무단으로 이용하면 업무 방해에 대한 피해를 보상해야합니다.\
(민법 등에 의거)
{% endhint %}

## Service ID, Secret Key 확인

[리모트몬스터 웹 콘솔](https://console.remotemonster.com/)의 Project Information 메뉴에서 Service ID, Secret Key를 확인할 수 있습니다.\
Secret Key는 \*\*\*\*\*\*\*\*\*\*\*\*\*\* 이 아닙니다. 우측 눈 아이콘을 누르면 표시됩니다.

![웹 콘솔의 프로젝트 기본정보 화면](/files/-LyTCM08fj9j729siE2z)

## 채널 검색

## <https://consoleapi.remotemonster.com/v1/search>

<mark style="color:green;">`POST`</mark> `https://signal.remotemonster.com/lambda/broadcast-channel-list`&#x20;

기간과 채널 이름 조건에 따라 채널을 검색합니다.\
\
새로 생성된 채널이 검색되려면 다소 시간이 걸릴 수 있습니다. 새로 생성된 채널을 확인하려면 앱으로부터 채널 정보를 받는 것이 좋습니다. 앱은 SDK의 onCreate, onComplete 콜백 함수를 이용하여 채널 ID를 확인할 수 있습니다.

#### Headers

| Name         | Type   | Description      |
| ------------ | ------ | ---------------- |
| Content-type | string | application/json |

#### Request Body

| Name      | Type    | Description            |
| --------- | ------- | ---------------------- |
| serviceId | string  | Service ID             |
| secret    | string  | Secret Key             |
| keyword   | string  | 채널 ID가 포함할 문자열         |
| starttime | integer | 조회할 기간, Unix time, 초단위 |
| endtime   | integer | 조회할 기간, Unix time, 초단위 |

{% tabs %}
{% tab title="200 peerCount 는 누적 세션수입니다. " %}

```
{
  "startTime": 1577862148,
  "currentTime": 1580454148,
  "totalCount": 1,
  "items": [
    {
      "chid": "20200110",
      "createTime": 1580281515,
      "endTime": 1580281577,
      "peerCount": 2
    }
  ]
}
```

{% endtab %}
{% endtabs %}

### 예시

{% tabs %}
{% tab title="curl" %}

```bash
curl -X POST https://consoleapi.remotemonster.com/v1/search \
-H 'Content-type: application/json' \
-d '
{
  "serviceId": "my-service-id",
  "secret":"my-secret",
  "keyword" : "",
  "starttime": 1581315000,
  "endtime": 1581315900
}
'
```

{% endtab %}
{% endtabs %}

## 방송 중인 채널 조회

## <https://signal.remotemonster.com/lambda/broadcast-channel-list>

<mark style="color:green;">`POST`</mark> `https://signal.remotemonster.com/lambda/broadcast-channel-list`&#x20;

방송 중인 채널을 조회합니다. 종료된 방송 채널은 조회되지 않습니다.

#### Headers

| Name         | Type   | Description      |
| ------------ | ------ | ---------------- |
| Content-type | string | application/json |

#### Request Body

| Name      | Type   | Description |
| --------- | ------ | ----------- |
| serviceId | string | Service ID  |
| secret    | string | Secret Key  |

{% tabs %}
{% tab title="200 id 는 채널 ID 입니다.
numOfWatchers 는 조회 시각의 시청세션수입니다, 누적 시청세션수가 아닙니다." %}

```
[
  {
    "id": "1581324488053_Dc5aQ",
    "serviceId": "my-service-id",
    "createTime": "2020-02-10T08:48:09.828Z",
    "numOfWatchers": 0
  },
  {
    "id": "1581324445005_s1c9g",
    "serviceId": "my-service-id",
    "createTime": "2020-02-10T08:47:27.011Z",
    "numOfWatchers": 0
  }
]

```

{% endtab %}
{% endtabs %}

### 예시

{% tabs %}
{% tab title="curl" %}

```bash
curl -X POST 'https://signal.remotemonster.com/lambda/broadcast-channel-list' \
-H 'Content-type: application/json' \
-d '{"serviceId":"my-service-id","secret":"my-secret-key"}'
```

{% endtab %}
{% endtabs %}

## 채널 강제 종료

## <https://signal.remotemonster.com/lambda/channel-force-termination>

<mark style="color:green;">`POST`</mark> `https://signal.remotemonster.com/lambda/channel-force-termination`&#x20;

채널을 강제로 종료합니다.&#x20;

#### Headers

| Name         | Type   | Description      |
| ------------ | ------ | ---------------- |
| Content-type | string | application/json |

#### Request Body

| Name      | Type   | Description |
| --------- | ------ | ----------- |
| serviceId | string | Service ID  |
| secret    | string | Secret Key  |
| channelId | string | 종료할 채널 ID   |

{% tabs %}
{% tab title="200 " %}

```
{
  "send": true
}
```

{% endtab %}
{% endtabs %}

### 예시

{% tabs %}
{% tab title="curl" %}

```bash
curl -X POST https://signal.remotemonster.com/lambda/channel-force-termination \
-H 'Content-type: application/json' \
-d '{"serviceId":"my-service-id", "secret":"my-secret-key", "channelId":"the-channel-id"}'
```

{% endtab %}
{% endtabs %}


# Service Key

## Overview

RemoteMonster를 이용하기 위해서는 Service ID와 Secret Key를 발급 받아야 합니다. Service ID와 Secret Key는 프로젝트(서비스) 생성 시 자동으로 생성됩니다. 리모트몬스터는 Service ID와 Secret Key를 서비스(앱)를 구분 하는데 사용됩니다. 동시에 사용량 측정 및 과금의 기준이 됩니다. 고객님이 여러 개의 앱을 만들 경우 앱 수만큼 Service ID를 발급받아 사용하면 됩니다.

## 프로젝트 생성

아래 웹 사이트의 콘솔 메뉴를 클릭하고, 회원 가입을 완료해주십시오. 프로젝트(서비스) 상세 정보를 조회하면 Service ID와 Secret Key를 확인할 수 있습니다.

{% embed url="<https://remotemonster.com>" %}
RemoteMonster Homepage
{% endembed %}

타 사용자의 Service ID와 Secret Key를 함부로 이용해서는 안 됩니다.

### Service ID

내 서비스에 사용되는 ID입니다. 다른 서비스와 구분됩니다.

### Production Key

내 서비스 ID를 인증하는 키 입니다. 이 키가 노출이 안되도록 주의해주세요.

### Test Key

필요에 따라 단순 테스트 용도로 사용 가능한 키입니다. 개발이나 테스트시 사용하세요.

## 사용

실제 ID 와 Key를 사용하는 방법은 아래 각 플랫폼의 환경설정을 참고하세요.

{% content-ref url="/pages/-LGUX\_VegX1nF20YlowL" %}
[Web - Getting Started](/web/web-getting-started)
{% endcontent-ref %}

{% content-ref url="/pages/-LGUX\_VmZyrTChz29QXB" %}
[Android - Getting Started](/android/android-getting-started)
{% endcontent-ref %}

{% content-ref url="/pages/-LGUX\_VtIk9TFRWqQTsY" %}
[iOS - Getting Started](/ios/ios-getting-started)
{% endcontent-ref %}


# Config/RemonConfig

## Overview

RemoteMonster는 RemonCall, RemonCast객체에 직접 설정 정보를 지정할 수 있습니다. 이러한 설정 정보들을 별도의 객체에 생성해 두고, RemonCall, RemonCast 객체 생성 후 해당 설정 값을 사용하고자 하는 경우 혹은 다른 화면에서 설정값을 지정한 뒤 방송,통신 화면으로 전달하는 경우에 `config`값을 사용할 수 있습니다.

RemonCall, RemonCast 의 connect() 메쏘드 호출 시 config 정보를 함께 전달하게 되면, RemonCall, RemonCast 내부의 설정이 아닌 config 의 설정을 사용해 연결이 이루어집니다.

Android,Web 환경은 Config, iOS의 경우 RemonConfig 객체를 사용합니다.

## Basics

가장 기본적으로 화면이 보일 View와 Service Id, Key를 지정하는 것이 필요합니다.&#x20;

### View

영상이 표출될 View를 지정하는 설정으로 뷰가 지정되지 않으면 영상이 보이지 않습니다. (iOS의 경우 View는 RemonConfig 에서 제공하지 않으므로, RemonCall, RemonCast 객체에 직접 지정해야 합니다.)

{% tabs %}
{% tab title="Web" %}

```markup
<video id="remoteVideo" autoplay controls></video>
<video id="localVideo" autoplay controls muted></video>
<script>
  const config = {
    view: {
      remote: '#remoteVideo', local: '#localVideo'
    }
  }
</script>
```

{% endtab %}

{% tab title="Android - Java" %}

```java
Config config = new com.remotemonster.sdk.Config();
config.setLocalView((SurfaceViewRenderer) findViewById(R.id.local_video_view));
config.setRemoteView((SurfaceViewRenderer) findViewById(R.id.remote_video_view));
```

{% endtab %}

{% tab title="Android - Kotlin" %}

```kotlin
Config config = com.remotemonster.sdk.Config()
config.localView = ((SurfaceViewRenderer) findViewById(R.id.local_video_view))
config.remoteView = ((SurfaceViewRenderer) findViewById(R.id.remote_video_view))
```

{% endtab %}

{% tab title="iOS - Swift" %}

```swift
let myRemoteView:UIView! = UIView()
let myLocalView:UIView! = UIView()
let remonCall = RemonCall()
remonCall.remoteView = myRemoteView
remonCall.localView = myLocalView
```

{% endtab %}

{% tab title="iOS - ObjC" %}

```objectivec
UIView *myRemoteView = [UIView new];
UIView *myLocalView = [UIView new];
RemonCall *remonCall = [[RemonCall alloc] init];
remonCall.remoteView = myRemoteView
remonCall.localView = myLocalView
```

{% endtab %}
{% endtabs %}

### Service Id, Key

Service Id, Key를 지정 하는 단계로 필수 입니다.

{% tabs %}
{% tab title="Web" %}

```javascript
const config = {
  credential: {
    serviceId: 'myServiceId', key: 'myKey'
  }
}
```

{% endtab %}

{% tab title="Android - Java" %}

```java
Config config = new com.remotemonster.sdk.Config();
config.setServiceId("myServiceId");
config.setKey("myKey");
```

{% endtab %}

{% tab title="Android - Kotlin" %}

```kotlin
var config = com.remotemoster.sdk.Config()
config.serviceId = "serviceId"
config.key = "serviceKey"
```

{% endtab %}

{% tab title="iOS - Swift" %}

```swift
let remonCall = RemonCall()
remonCall.serviceId = "myServiceId"
remonCall.serviceKey = "myServiceKey"
```

{% endtab %}

{% tab title="iOS - ObjC" %}

```objectivec
RemonCall *remonCall = [[RemonCall alloc] init];
remonCall.serviceId = @"myServiceId"
remonCall.serviceKey = @"myServiceKey"
```

{% endtab %}
{% endtabs %}

## Meta

사용자의 UID, 프로퍼티 등 서비스에 필요한 meta 데이터를 설정 할 수 있습니다. 안드로이드의 경우 HashMap 객체를 전달할 수 있으며, iOS 는 String 데이터만 지원합니다.

{% tabs %}
{% tab title="Web" %}
N/A
{% endtab %}

{% tab title="Android - Java" %}

```java
HashMap<String, Object> meta = new HashMap<>();
meta.put("uid", "lucas1234");

Config config = new com.remotemonster.sdk.Config();
config.setMeta( meta );
```

{% endtab %}

{% tab title="Android - Kotlin" %}

```kotlin
var meta = hashMapOf<String, Object>()
meta.put("uid", "myUid")

var config = com.remotemoster.sdk.Config()
config.meta = meta
```

{% endtab %}

{% tab title="iOS - Swift" %}

```swift
let config:RemonConfig = RemonConfig()
config.userMeta = "string"
```

{% endtab %}

{% tab title="iOS - ObjC" %}

```
RemonConfig *config = [RemonConfig new];
config.userMeta = @"string";
```

{% endtab %}
{% endtabs %}

## Media

음성과 영상에 대한 보다 다양한 옵션이 제공됩니다.

각 플렛폼별 추가적인 설정은 아래를 참고하세요.

{% content-ref url="/pages/-LGUX\_VfR-uQd-5BzMiv" %}
[Web - Media](/web/web-view)
{% endcontent-ref %}

{% content-ref url="/pages/-LGUX\_Vnqg7GkW4vXRPF" %}
[Android - Media](/android/android-media)
{% endcontent-ref %}

{% content-ref url="/pages/-LGUX\_VucmwTGQ6RClCr" %}
[iOS - Media](/ios/ios-media)
{% endcontent-ref %}

### Select Video, Audio

비디오를 끄면 음성방송/통화 서비스를 만들 수 있습니다.

{% tabs %}
{% tab title="Web" %}

```javascript
// Audio Only
const config = {
  media: {
    audio: true,
    video: false
  }
}

// Audio, Video
const config = {
  media: {
    audio: true,
    video: true
  }
}
```

{% endtab %}

{% tab title="Android - Java" %}

```java
// Audio Only
config.setVideoCall(false);

// Audio, Video
config.setVideoCall(true);
```

{% endtab %}

{% tab title="Android - Kotlin" %}

```kotlin
// Audio Only
config.videoCall = false

// Audio, Video
config.videoCall = true
```

{% endtab %}

{% tab title="iOS - Swift" %}

```swift
// Audio Only
remonCall.onlyAudio = true

// Audio, Video
remonCall.onlyAudio = false             //default fasle
```

{% endtab %}

{% tab title="iOS - ObjC" %}

```objectivec
// Audio Only
remonCall.onlyAudio = YES;

// Audio, Video
remonCall.onlyAudio = NO;             //default fasle
```

{% endtab %}
{% endtabs %}

### Video Options

`width`와 `height`는 상대편에게 보낼 영상의 해상도를 결정하는 것입니다. 최대 640, 480의 해상도로 보낼 것을 설정하였지만 이것이 꼭 지켜지는 것은 아닙니다. WebRTC는 기본적으로 네트워크나 단말의 상태에 따라 해상도와 `framerate`등을 유연하게 변화시키면서 품질을 유지하고 있기 때문입니다. 해상도가 실제와 차이가 날 수는 있지만 ratio는 최대한 맞추기 위해 노력합니다.

Browser는 H.264와 VP8, VP9등의 영상 코덱을 지원하고 있습니다. RemoteMonster는 H.264를 기본 코덱으로 사용하고 있으며 변경이 필요하다면 이 설정으로 변경할 수 있습니다.

`frameRate`항목은 1초에 몇번의 프레임으로 인코딩할 지를 결정합니다. 일반적인 영상통화에서는 15정도도 적당합니다.

`facingMode`는 기기의 앞과 뒤에 카메라가 있을 때 어떤 쪽의 카메라를 사용할 것인지를 결정하는 것입니다. 현재 모바일 버전의 크롬 브라우저에서는 `facingMode`가 제대로 동작하지 않습니다.

`startVideoBitrate`는 해당 비디오의 대여폭을 설정 합니다. 무조건 높게 설정한다고 좋은 품질을 나타내진 않습니다. 앱의 전체적인 네트워크 트래픽을 고려하여 적절한 값을 사용해야 합니다.&#x20;

{% tabs %}
{% tab title="Web" %}

```javascript
const config = {
  media: {
    video: {
      width: {max: '640', min: '640'},
      height: {max: '480', min: '480'},
      codec: 'H264',                 // 'VP9', 'VP8', 'H264'
      frameRate: {max:15, min:15},
      facingMode: 'user',             // 'user', 'environment'
      maxBandwidth: '3000'
    }
  }
}
```

{% endtab %}

{% tab title="Android - Java" %}

```java
config.setVideoWidth(640);
config.setVideoHeight(480);
config.setVideoCodec("VP8");  // 'VP9', 'VP8', 'H264'
config.setVideoFps(15);
config.setStartVideoBitrate(700);
```

{% endtab %}

{% tab title="Android - Kotlin" %}

```kotlin
config.videoWidth = 640
config.videoHeight = 480
config.videoCodec = "VP8"  // 'VP9', 'VP8', 'H264'
config.videoFps = 15
config.startVideoBitrate = 700
```

{% endtab %}

{% tab title="iOS - Swift" %}

```swift
let remonCall = RemonCall()

remonCall.videoWidth = 640
remonCall.videoHeight = 480
remonCall.videoFps = 24
remonCall.videoCodec = "H264"
remonCall.useFrontCamera = true       // default true, 만약 false 라면 후면 카메라를 사용합니다.

// 로컬 비디오 전송 준비가 완료 되면 자동으로 로컬 비디오 캡쳐를 시작 합니다.
// 만약 이 값을 false로 설정 한다면 onComplete() 호출 이후에 startLocalVideoCapture()를 호출 하여야 합니다.
remonCall.autoCaptureStart = true     // default true
```

{% endtab %}

{% tab title="iOS - ObjC" %}

```objectivec
RemonCall *remonCall = [[RemonCall alloc] init];
​
remonCall.videoWidth = 640;
remonCall.videoHeight = 480;
remonCall.videoFps = 24;
remonCall.videoCodec = @"H264";
remonCall.useFrontCamera = YES;       // this is true by default, if this is false, use the rear camera.
// Start local video capture automatically when it is ready to transmit a local video.
// If you set this value to false, you must call startLocalVideoCapture() after the onComplete() call.
remonCall.autoCaptureStart = YES;     // default true
```

{% endtab %}
{% endtabs %}

### 오디오 타입 : Voice, Music 타입

오디오 관련 작동방식을 설정 할 수 있습니다. Voice 타입은 주변 소음을 제거하고 음성을 전달하는데 초점이 맞추어져 있으며 통화에 적합합니다. Music 타입은 모든 소리를 가공 없이 전달하는데 중점이 있습니다.

{% tabs %}
{% tab title="Web" %}

```javascript
let config = {
    media: {},
    rtc: {audioType: "music"}
};
```

기본 오디오 타입은 **VOICE** 입니다.
{% endtab %}

{% tab title="Android - Java" %}

```java
config.setAudioType( AudioType.VOICE );
```

�Builder에서 AudioType은 `AudioType.MUSIC`, `AudioType.VOICE` 두가지가 있습니다.

`RemonCall`의 default AudioType은 **VOICE**이며, `RemonCast`의 default는 **MUSIC**입니다.
{% endtab %}

{% tab title="Android - Kotlin" %}

```kotlin
config.audioType = AudioType.VOICE
```

Builder에서 AudioType은 `AudioType.MUSIC`, `AudioType.VOICE` 두가지가 있습니다.

`RemonCall`의 default AudioType은 **VOICE**이며, `RemonCast`의 default는 **MUSIC**입니다.
{% endtab %}

{% tab title="iOS - Swift" %}
iOS는 동적으로 해당기능을 제공하지 않으며 다음과 같이 설정하여야 합니다.

![](/files/-LGUXcoe6Ox4DNgV5Kwc)

`RemonSettings.plist` 파일을 프로젝트에 추가하고, AudioType 값을 원하는 모드로 변경해 주세요.
{% endtab %}

{% tab title="iOS - ObjC" %}
iOS는 동적으로 해당기능을 제공하지 않으며 다음과 같이 설정하여야 합니다.

![](/files/-LGUXcoe6Ox4DNgV5Kwc)

`RemonSettings.plist` 파일을 프로젝트에 추가하고, AudioType 값을 원하는 모드로 변경해 주세요.
{% endtab %}
{% endtabs %}

### 오디오 추가 설정

오디오 설정에는 다양한 세부항목이 있습니다. 아래와 같은 세부항목을 설정할 수 있습니다. 방송/통화 환경이나 목적에 따라 적합한 설정이 다를 수 있습니다. 설정 항목 들을 적절히 조합하여 이용하시기 바랍니다.

{% tabs %}
{% tab title="Web" %}

```javascript
let config = {
  media: {
    audio: {
      channelCount: 2,
      maxBandwidth: 128,
      autoGainControl: false,
      echoCancellation: false,
      noiseSuppression: false,
    }
  },
  rtc: {
    audioType: "music"
  }
};
```

{% endtab %}

{% tab title="Android - Java" %}

```java
config.setAudioCodec("OPUS");  //ISAC
config.setAudioStartBitrate(48);
```

{% endtab %}

{% tab title="Android - Kotlin" %}

```kotlin
config.audioCodec = "OPUS"  //ISAC
config.audioStartBitrate = 48
```

{% endtab %}
{% endtabs %}

## Debug

`SILENT`, `ERROR`, `WARN`, `INFO`, `DEBUG`, `VERBOSE`를 설정할 수 있으며 뒤로 갈 수록 더 자세한 로그를 확인할 수 있습니다.

{% tabs %}
{% tab title="Web" %}

```javascript
const config = {
  dev:{
    logLevel: 'INFO'
  }
}
```

{% endtab %}

{% tab title="Android - Java" %}

```java
config.setLogLevel(Log.DEBUG);
```

{% endtab %}

{% tab title="Android - Kotlin" %}

```kotlin
config.setLogLevel = Log.DEBUG
```

{% endtab %}

{% tab title="iOS - Swift" %}

```swift
let remonCast = RemonCast()
remonCast.debugMode = true
```

{% endtab %}

{% tab title="iOS - ObjC" %}

```objectivec
RemonCall *remonCall = [[RemonCall alloc] init];
remonCall.debugMode = YES;
```

{% endtab %}
{% endtabs %}


# Livecast

## 기본 설정

방송을 하기 전에 각 플렛폼 별 프로젝트 설정을 진행 합니다.

## 개발

`RemonCast` 클래스는 방송 생성 및 시청을 위한 기능을 제공합니다. `RemonCast` 클래스의 `create()` 함수와 `join()` 함수를 이용하여 방송 기능을 이용 할 수 있습니다.

전체적인 구성과 흐름은 아래를 참고하세요.

{% content-ref url="/pages/-LGUX\_VXNBELDoLRtDdi" %}
[Flow](/overview/flow)
{% endcontent-ref %}

{% content-ref url="/pages/-LGUX\_VY8BHCX9QlkNNM" %}
[Structure](/overview/structure)
{% endcontent-ref %}

### View 등록

방송 송출자가 스스로의 모습을 확인하거나, 시청자가 방송을 보기 위해서 실제 비디오가 그려지는 View를 정하고 연결해야 됩니다. 방송 송출자에게는 스스로가 보이도록 Local View를 등록 하고, 시청자에게는 송출자가 보이도록 Remote View를 등록합니다.

{% tabs %}
{% tab title="Web" %}

```markup
<!-- Caster : local view -->
<video id="localVideo" autoplay muted></video>
```

```markup
<!-- Viewer : remote view -->
<video id="remoteVideo" autoplay></video>
```

{% endtab %}

{% tab title="Android" %}

```markup
<!-- Caster : local view -->
<com.remotemonster.sdk.PercentFrameLayout
    android:id="@+id/perFrameLocal"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <org.webrtc.SurfaceViewRenderer
        android:id="@+id/surfRendererLocal"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</com.remotemonster.sdk.PercentFrameLayout>
```

```markup
<!-- Viewer : remote view -->
<com.remotemonster.sdk.PercentFrameLayout
    android:id="@+id/perFrameRemote"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <org.webrtc.SurfaceViewRenderer
        android:id="@+id/surfRendererRemote"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</com.remotemonster.sdk.PercentFrameLayout>
```

ConstraintLayout 과 같이 안드로이드에서 제공하는 레이아웃으로 구성할 수 있습니다.

```markup
<androidx.constraintlayout.widget.ConstraintLayout
    android:id="@+id/constraintLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <!-- Remote -->
    <RelativeLayout
        android:id="@+id/layoutRemote"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_margin="10dp"
        app:layout_constraintDimensionRatio="H,1:1.33"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        >
        <org.webrtc.SurfaceViewRenderer
            android:id="@+id/surfRendererRemote"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    </RelativeLayout>
    
    <!-- Local -->
    <RelativeLayout
        android:id="@+id/layoutLocal"
        android:layout_width="80dp"
        android:layout_height="0dp"
        android:layout_margin="18dp"
        app:layout_constraintDimensionRatio="H,1:1.33"
        app:layout_constraintVertical_bias="0.1"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        >

        <org.webrtc.SurfaceViewRenderer
            android:id="@+id/surfRendererLocal"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            />
        <ImageView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:src="@drawable/remon_identity"
            android:scaleType="fitCenter"
            android:visibility="visible"
            />
    </RelativeLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
```

{% endtab %}

{% tab title="iOS - Swift" %}
Interface Builder를 통해 지정 하게 되며 iOS - Getting Start에 따라 환경설정을 했다면 이미 View등록이 완료된 상태 입니다. 혹, 아직 완료가 안된 상태라면 아래를 참고하세요.

{% content-ref url="/pages/-LGUX\_VtIk9TFRWqQTsY" %}
[iOS - Getting Started](/ios/ios-getting-started)
{% endcontent-ref %}
{% endtab %}

{% tab title="iOS - ObjC" %}
Interface Builder를 통해 지정 하게 되며 iOS - Getting Start에 따라 환경설정을 했다면 이미 View등록이 완료된 상태 입니다. 혹, 아직 완료가 안된 상태라면 아래를 참고하세요.

{% content-ref url="/pages/-LGUX\_VtIk9TFRWqQTsY" %}
[iOS - Getting Started](/ios/ios-getting-started)
{% endcontent-ref %}
{% endtab %}
{% endtabs %}

보다 더 자세한 내용은 아래를 참고하세요.

{% content-ref url="/pages/-LGUX\_VfR-uQd-5BzMiv" %}
[Web - Media](/web/web-view)
{% endcontent-ref %}

{% content-ref url="/pages/-LGUX\_Vnqg7GkW4vXRPF" %}
[Android - Media](/android/android-media)
{% endcontent-ref %}

{% content-ref url="/pages/-LGUX\_VucmwTGQ6RClCr" %}
[iOS - Media](/ios/ios-media)
{% endcontent-ref %}

### 방송생성

`RemonCast`의 `create()` 함수를 이용하여 방송 만들 수 있습니다. `create()` 함수가 호출 되면 `Remon`의 미디어 서버에다른 사용자들이 접속 할 수 있는 방송이 채널로써 만들어 지게 됩니다. 이때 채널이 만들어 지면서 `channelId`를 반환하게 되고, 이를 통해 시청자가 접근할 수 있습니다.

{% tabs %}
{% tab title="Web" %}

```javascript
// <video id="localVideo" autoplay muted></video>
let myChannelId

const config = {
  credential: {
    serviceId: 'MY_SERVICE_ID',
    key: 'MY_SERVICE_KEY'
  },
  view: {
    local: '#localVideo'
  },
  media: {
    sendonly: true
  }
}

const listener = {
  onCreate(channelId) {
    myChannelId = channelId
  }
}
​
const caster = new Remon({ listener, config })
caster.createCast()
```

{% endtab %}

{% tab title="Android - Java" %}

```java
caster = RemonCast.builder()
    .serviceId("MY_SERVICE_ID")
    .key("MY_SERVICE_KEY")
    .context(CastActivity.this)
    .localView(surfRendererlocal)        // local Video Renderer
    .build();

caster.onCreate((channelId) -> {
    myChannelId = channelId;
});

caster.create();
```

{% endtab %}

{% tab title="Android - Kotlin" %}

```kotlin
caster = RemonCast.builder()
    .serviceId("MY_SERVICE_ID")
    .key("MY_SERVICE_KEY")
    .context(CastActivity.this)
    .localView(surfRendererlocal)        // local Video Renderer
    .build()

caster.onCreate { channelId -> 
    myChannelId = channelId;
}

caster.create()
```

{% endtab %}

{% tab title="iOS - Swift" %}

```swift
remonCast.create("MY_CHANNEL_ID")
```

혹은 아래와 같이 Interface Builder 없이 작성 가능합니다.

```swift
let caster = RemonCast()
caster.serviceId = "MY_SERVICE_ID"
caster.serviceKey = "MY_SERVICE_KEY"
caster.localView = localView

remonCast.onCreate { (channelId) in
    let myChannelId = caster.channelId
}

caster.create("MY_CHANNEL_ID")
```

{% endtab %}

{% tab title="iOS - ObjC" %}

```objectivec
[remonCast create:@"MY_CHANNEL_ID"];
```

Or you can create it without *Interface Builder* as follows.

```objectivec
RemonCast *caster = [[RemonCast alloc]init];
caster.serviceId = @"MY_SERVICE_ID";
caster.serviceKey = @"MY_SERVICE_KEY";
caster.localView = localView;

[self.remonCast onCreateWithBlock:^(NSString * _Nullable chId) {
    [self.channelIdLabel setText:chId];
}];

[caster create:@"MY_CHANNEL_ID"];
```

{% endtab %}
{% endtabs %}

### 방송시청

`RemonCast`의 `joinRoom(channelId)` 함수를 이용하면 방송에 참여 할 수 있습니다. 이때 원하는 `channelId`를 알려줘야 하는데 보통 아래의 Channel을 참고하여 전체 목록을 통해 사용자가 선택하는 방식이 많이 사용됩니다.

{% tabs %}
{% tab title="Web" %}

```javascript
// <video id="remoteVideo" autoplay></video>
let myChannelId

const config = {
  credential: {
    serviceId: 'MY_SERVICE_ID',
    key: 'MY_SERVICE_KEY'
  },
  view: {
    local: '#remoteVideo'
  },
  media: {
    recvonly: true
  }
}

const listener = {
  onJoin() {
    // Do something
  }
}
​
const viewer = new Remon({ listener, config })
viewer.joinCast('MY_CHANNEL_ID')                  // myChnnelId from caster
```

{% endtab %}

{% tab title="Android - Java" %}

```java
viewer = RemonCast.builder()
    .serviceId("MY_SERVICE_ID")
    .key("MY_SERVICE_KEY")
    .context(ViewerActivity.this)
    .remoteView(surfRendererRemote)        // remote video renderer
    .build();
​
viewer.onJoin(() -> {});

viewer.join("MY_CHANNEL_ID");                     // myChid from caster
```

{% endtab %}

{% tab title="Android - Kotlin" %}

```kotlin
viewer = RemonCast.builder()
    .serviceId("MY_SERVICE_ID")
    .key("MY_SERVICE_KEY")
    .context(ViewerActivity.this)
    .remoteView(surfRendererRemote)        // remote video renderer
    .build()
​
viewer.onJoin{
}

viewer.join("MY_CHANNEL_ID")                     // myChid from caster
```

{% endtab %}

{% tab title="iOS - Swift" %}

```swift
remonCast.join("MY_CHANNEL_ID")
```

혹은 아래와 같이 Interface Builder 없이 작성 가능합니다.

```swift
let viewer = RemonCast()
viewer.serviceId = "MY_SERVICE_ID"
viewer.key = "MY_SERVICE_KEY"
viewer.remoteView = remoteView

viewer.onJoin {
    // Do something
}

viewer.join("MY_CHANNEL_ID")
```

{% endtab %}

{% tab title="iOS - ObjC" %}

```javascript
[remonCast join:@"MY_CHANNEL_ID"]
```

Or you can create it without *Interface Builder* as follows.

```swift
RemonCast *caster = [[RemonCast alloc]init];
caster.serviceId = @"MY_SERVICE_ID";
caster.serviceKey = @"MY_SERVICE_KEY";
caster.localView = localView;

[self.remonCast onJoinWithBlock:^() {

}];

[caster join:@"MY_CHANNEL_ID"];
```

{% endtab %}
{% endtabs %}

### Callbacks <a href="#observer" id="observer"></a>

개발중 다양한 상태 추적을 돕기 위한 Callback을 제공 합니다.&#x20;

* 안드로이드 2.4.13, iOS 2.6.9 버전부터 콜백은 모두 UI Thread 에서 호출됩니다.

{% tabs %}
{% tab title="Web" %}

```javascript
const listener = {
  onInit(token) {
    // UI 처리등 remon이 초기화 되었을 때 처리하여야 할 작업
  },
​  
  onConnect(channelId) {
    // 통화 생성 후 대기 혹은 응답
  },
​
  onComplete() {
    // Caller, Callee간 통화 시작
  },
​  
  onClose() {
    // 종료
  }
}
```

{% endtab %}

{% tab title="Android - Java" %}

```java
remonCast = RemonCast.builder().build();

// UI 처리등 remon이 초기화 되었을 때 처리하여야 할 작업
remonCast.onInit(() -> {
});
​
// 방송 생성
remonCast.onCreate((channelId) -> {
});
​
// 방송 참
remonCast.onJoin ( () -> {
});

// Caller, Callee간 통화 시작
remonCast.onComplete(() -> {
});
​
// 종료
remonCast.onClose(() -> {
});
```

{% endtab %}

{% tab title="Android - Kotlin" %}

```kotlin
remonCast = RemonCast.builder().build()

// UI 처리등 remon이 초기화 되었을 때 처리하여야 할 작업
remonCast.onInit {
}
​
// 방송 생성
remonCast.onCreate { channelId -> {
}
​
// 방송 참
remonCast.onJoin {
}

// Caller, Callee간 통화 시작
remonCast.onComplete {
}
​
// 종료
remonCast.onClose {
}
```

{% endtab %}

{% tab title="iOS - Swift" %}

```swift
let remonCast = RemonCast()

remonCast.onInit { [weak self] in
    // UI 처리등 remon이 초기화 되었을 때 처리하여야 할 작업
}
​
remonCast.onCreate { [weak self](channelId) in
    // 해당 'chid'로 미리 생성된 채널이 없다면 다른 사용자가 해당 'chid'로 연결을 시도 할때 까지 대기 상태가 됩니다. 
}
​
remonCast.onJoin { [weak self] in
}

remonCast.onComplete { [weak self] in
    // Caller, Callee간 통화 시작
}
​
remonCast.onClose { [weak self](closeType) in
    // 종료
}
```

{% endtab %}

{% tab title="iOS - ObjC" %}

```objectivec
RemonCast *remonCast = [[RemonCast alloc] init];

[remonCast onInitWithBlock:^{
    // Things to do when remon is initialized, such as UI processing, etc.
}];

[remonCast onConnectWithBlock:^(NSString * _Nullable chId) {
    // Make a call then wait the callee
}];

[remonCast onJoinWithBlock:^{
}];

[remonCast onCompleteWithBlock:^{
    // Start between Caller and Callee
}];

[remonCast onCloseWithBlock:^{
    // End calling
}];
```

{% endtab %}
{% endtabs %}

더 많은 내용은 아래를 참조 하세요.​

{% content-ref url="/pages/-LGUX\_W2IMDRX7sIYMoc" %}
[Callbacks](/common/callbacks)
{% endcontent-ref %}

### Channel 목록 조회

방송을 만들면 채널이 생성되고 고유한 `channelId`가 생성 됩니다. 이 `channelId`를 통해 시청자가 생성된 방송에 접근가능합니다. 이때 방송중인 전체 채널 목록을 아래와 같이 조회 가능합니다.

{% tabs %}
{% tab title="Web" %}

```javascript
const remonCast = new Remon()
const casts = await remonCast.fetchCasts()
```

{% endtab %}

{% tab title="Android - Java" %}

```java
remonCast = RemonCast.builder().build();

remonCast.onFetch((casts) -> {
    // Do something
});

remonCast.featchCasts();
```

{% endtab %}

{% tab title="Android - Kotlin" %}

```kotlin
remonCast = RemonCast.builder().build()

remonCast.onFetch { casts ->
    // Do something
}

remonCast.featchCasts()

```

{% endtab %}

{% tab title="iOS - Swift" %}

```swift
let remonCast = RemonCast()

remonCast.fetchCasts { (error, results) in
    // Do something
}
```

{% endtab %}

{% tab title="iOS - ObjC" %}

```objectivec
RemonCast *remonCast = [[RemonCast alloc]init];
 [remonCast fetchCastsWithIsTest:YES
                   complete:^(NSArray<RemonSearchResult *> * _Nullable chs) {
                        // Do something
                    }];
```

{% endtab %}
{% endtabs %}

더 자세한 내용은 아래를 참고하세요.

{% content-ref url="/pages/-LGUX\_W3PoO-2aW2JGKd" %}
[Channel](/common/channel)
{% endcontent-ref %}

### 종료

방송의 송출, 시청이 끝났을 경우 꼭 `RemonCast`객체를 `close()`해주어야 합니다. close를 통해서 모든 방송 자원과 미디어 스트림 자원이 해제됩니다.

{% tabs %}
{% tab title="Web" %}

```javascript
const remonCast = new Remon()
remonCast.close()
```

{% endtab %}

{% tab title="Android - Java" %}

```java
remonCast = RemonCast.builder().build();
remonCast.close();
```

{% endtab %}

{% tab title="Android - Kotlin" %}

```kotlin
remonCast = RemonCast.builder().build()
remonCast.close()
```

{% endtab %}

{% tab title="iOS - Swift" %}

```swift
let remonCast = RemonCast()
remonCast.closeRemon()
```

{% endtab %}

{% tab title="iOS - ObjC" %}

```objectivec
RemonCast *remonCast = [[RemonCast alloc]init];
[remonCast closeRemon];
```

{% endtab %}
{% endtabs %}

### 기타

아래를 통해 보다 자세한 설정, 실 서비스를 위한 프렉티스등 다양한 내용을 확인해 보세요.

{% content-ref url="/pages/-LGUX\_W-t9BCj3vjDn1A" %}
[Config/RemonConfig](/common/config)
{% endcontent-ref %}

{% content-ref url="/pages/-LRQKY\_sVXVaNRh\_4zBi" %}
[Network Environment](/common/network-environment)
{% endcontent-ref %}


# Communication

## 기본 설정 <a href="#undefined" id="undefined"></a>

통신을 하기 전에 프로젝트 설정을 진행 합니다.​

## 개발 <a href="#undefined-1" id="undefined-1"></a>

통신을 기능은 이용하기 위해서는 `RemonCall` 클래스를 이용합니다. `RemonCall`클래스의 `connect()` 함수를 이용하여 채널 생성 및 접속이 가능합니다.

전체적인 구성과 흐름은 아래를 참고하세요.​​

{% content-ref url="/pages/-LGUX\_VXNBELDoLRtDdi" %}
[Flow](/overview/flow)
{% endcontent-ref %}

{% content-ref url="/pages/-LGUX\_VY8BHCX9QlkNNM" %}
[Structure](/overview/structure)
{% endcontent-ref %}

### View 등록

통화중 스스로의 모습을 보거나 상대방의 모습을 보기위한 뷰가 필요합니다. 자기 자신의 모습은 Local View, 상대방의 모습은 Remote View로 등록을 합니다.

{% tabs %}
{% tab title="Web" %}

```javascript
<!-- local view -->
<video id="localVideo" autoplay muted></video>
<!-- remote view -->
<video id="remoteVideo" autoplay></video>
```

{% endtab %}

{% tab title="Android" %}

```markup
<!-- local view -->
<com.remotemonster.sdk.PercentFrameLayout
    android:id="@+id/perFrameLocal"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <org.webrtc.SurfaceViewRenderer
        android:id="@+id/surfRendererLocal"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</com.remotemonster.sdk.PercentFrameLayout>
```

```markup
<!-- remote view -->
<com.remotemonster.sdk.PercentFrameLayout
    android:id="@+id/perFrameRemote"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <org.webrtc.SurfaceViewRenderer
        android:id="@+id/surfRendererRemote"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</com.remotemonster.sdk.PercentFrameLayout>
```

{% endtab %}

{% tab title="iOS - Swift" %}
Interface Builder를 통해 지정 하게 되며 iOS - Getting Start에 따라 환경설정을 했다면 이미 View등록이 완료된 상태 입니다. 혹, 아직 완료가 안된 상태라면 아래를 참고하세요.

{% content-ref url="/pages/-LGUX\_VtIk9TFRWqQTsY" %}
[iOS - Getting Started](/ios/ios-getting-started)
{% endcontent-ref %}
{% endtab %}

{% tab title="iOS - ObjC" %}
Interface Builder를 통해 지정 하게 되며 iOS - Getting Start에 따라 환경설정을 했다면 이미 View등록이 완료된 상태 입니다. 혹, 아직 완료가 안된 상태라면 아래를 참고하세요.

{% content-ref url="/pages/-LGUX\_VtIk9TFRWqQTsY" %}
[iOS - Getting Started](/ios/ios-getting-started)
{% endcontent-ref %}
{% endtab %}
{% endtabs %}

보다 더 자세한 내용은 아래를 참고하세요.

{% content-ref url="/pages/-LGUX\_VfR-uQd-5BzMiv" %}
[Web - Media](/web/web-view)
{% endcontent-ref %}

{% content-ref url="/pages/-LGUX\_Vnqg7GkW4vXRPF" %}
[Android - Media](/android/android-media)
{% endcontent-ref %}

{% content-ref url="/pages/-LGUX\_VucmwTGQ6RClCr" %}
[iOS - Media](/ios/ios-media)
{% endcontent-ref %}

### 통화 걸기

`connect()` 함수에 전달한 `channelId` 값에 해당하는 채널이 존재하지 않으면 채널이 생성되고, 다른 사용자가 해당 채널에 연결 하기를 대기 하는 상태가 됩니다. 이때 해당 `channelId`로 다른 사용자가 연결을 시도 하면 연결이 완료 되고, 통신이 시작 됩니다.

{% tabs %}
{% tab title="Web" %}

```javascript
// <video id="localVideo" autoplay muted></video>
// <video id="remoteVideo" autoplay></video>
let myChid
​
const config = {
  credential: {
    serviceId: 'MY_SERVICE_ID',
    key: 'MY_SERVICE_KEY'
  },
  view: {
    local: '#localVideo',
    remote: '#remoteVideo'
  }
}
​
const listener = {
  onConnect(channelId) {
    myChannelId = channelId
  },
  onComplete() {
    // Do something
  }
}
​
const caller = new Remon({ listener, config })
caller.connectCall()
```

{% endtab %}

{% tab title="Android - Java" %}

```java
caller = RemonCall.builder()
    .serviceId("MY_SERVICE_ID")
    .key("MY_SERVICE_KEY")
    .context(CallActivity.this)
    .localView(surfRendererLocal)
    .remoteView(surfRendererRemote)
    .build();
​
caller.onConnect((channelId) -> {
    myChannelId = channelId  // Callee need chid from Caller for connect
});
​
caller.onComplete(() -> {
    // Caller-Callee connect each other. Do something
});

caller.connect("CHANNEL_NAME");
```

{% endtab %}

{% tab title="Android - Kotlin" %}

```kotlin
caller = RemonCall.builder()
    .serviceId("MY_SERVICE_ID")
    .key("MY_SERVICE_KEY")
    .context(CallActivity.this)
    .localView(surfRendererLocal)
    .remoteView(surfRendererRemote)
    .build()
​
caller.onConnect { channelId -> 
    myChannelId = channelId  // Callee need chid from Caller for connect
}
​
caller.onComplete {
    // Caller-Callee connect each other. Do something
}

caller.connect("CHANNEL_NAME")
```

{% endtab %}

{% tab title="iOS - Swift" %}

```swift
let caller = RemonCall()

caller.onConnect { [weak self](channelId) in
    let myChannelId = channelId          // Callee need channelId from Caller for connect
}

caller.connect("MY_CHANNEL_ID")
```

{% endtab %}

{% tab title="iOS - ObjC" %}

```objectivec
RemonCall *caller = [[RemonCall alloc] init];
​
[caller onConnectWithBlock:^(NSString * _Nullable channelId) {
// Callee need channelId from Caller for connect
    [self setMyChannelId:channelId];
}];
​
[caller connect:chId :@"MY_CHANNEL_ID"];
```

{% endtab %}
{% endtabs %}

### 통화 받기 <a href="#undefined-3" id="undefined-3"></a>

`connect()` 함수에 접속을 원하는 `channelId`값을 넣습니다. 대기상태에 있던 사용자와 연결을 진행하고, 정상 연결이 완료되면 onComplete 콜백이 호출됩니다.

{% tabs %}
{% tab title="Web" %}

```javascript
// <video id="localVideo" autoplay muted></video>
// <video id="remoteVideo" autoplay></video>
const config = {
  credential: {
    serviceId: 'MY_SERVICE_ID',
    key: 'MY_SERVICE_KEY'
  },
  view: {
    local: '#localVideo',
    remote: '#remoteVideo'
  }
}
​
const listener = {
  onComplete() {
    // Do something
  }
}
​
const callee = new Remon({ listener, config })
callee.connectCall('MY_CHANNEL_ID')
```

{% endtab %}

{% tab title="Android - Java" %}

```java
callee = RemonCall.builder()
    .serviceId("MY_SERVICE_ID")
    .key("MY_SERVICE_KEY")
    .context(CallActivity.this)
    .localView(surfRendererLocal)
    .remoteView(surfRendererRemote)
    .build();

callee.onComplete(() -> {
    // Caller-Callee connect each other. Do something
});

callee.connect("MY_CHANNEL_ID");
```

{% endtab %}

{% tab title="Android - Kotlin" %}

```kotlin
callee = RemonCall.builder()
    .serviceId("MY_SERVICE_ID")
    .key("MY_SERVICE_KEY")
    .context(CallActivity.this)
    .localView(surfRendererLocal)
    .remoteView(surfRendererRemote)
    .build()

callee.onComplete {
    // Caller-Callee connect each other. Do something
}

callee.connect("MY_CHANNEL_ID")
```

{% endtab %}

{% tab title="iOS - Swift" %}

```swift
let callee = RemonCall()

callee.onComplete {
    // Caller-Callee connect each other. Do something
}

callee.connect("MY_CHANNEL_ID")
```

{% endtab %}

{% tab title="iOS - ObjC" %}

```objectivec
RemonCall *callee = [[RemonCall alloc] init];
​
[callee onCompleteWithBlock:^{
    // Caller-Callee connect each other. Do something
}];
​
[callee connect:chId :@"MY_CHANNEL_ID"];
```

{% endtab %}
{% endtabs %}

### Callbacks <a href="#observer" id="observer"></a>

개발중 다양한 상태 추적을 돕기 위한 Callback을 제공 합니다.&#x20;

* 안드로이드 2.4.13, iOS 2.6.9 버전부터 콜백은 모두 UI Thread 에서 호출됩니다.

{% tabs %}
{% tab title="Web" %}

```javascript
const listener = {
  onInit(token) {
    // UI 처리등 remon이 초기화 되었을 때 처리하여야 할 작업
  },
​  
  onConnect(channelId) {
    // 통화 생성 후 대기 혹은 응답
  },
​
  onComplete() {
    // Caller, Callee간 통화 시작
  },
​  
  onClose() {
    // 종료
  }
}
```

{% endtab %}

{% tab title="Android - Java" %}

```java
remonCall = RemonCall.builder().build();

remonCall.onInit(() -> {
    // UI 처리등 remon이 초기화 되었을 때 처리하여야 할 작업
});
​
remonCall.onConnect((channelId) -> {
    // 통화 생성 후 대기 혹은 응답
});
​
remonCall.onComplete(() -> {
    // Caller, Callee간 통화 시작
});
​
remonCall.onClose(() -> {
    // 종료
});
```

{% endtab %}

{% tab title="Android - Kotlin" %}

```kotlin
remonCall = RemonCall.builder().build()

remonCall.onInit {
    // UI 처리등 remon이 초기화 되었을 때 처리하여야 할 작업
}
​
remonCall.onConnect { channelId ->
    // 통화 생성 후 대기 혹은 응답
}
​
remonCall.onComplete {
    // Caller, Callee간 통화 시작
}
​
remonCall.onClose {
    // 종료
}
```

{% endtab %}

{% tab title="iOS - Swift" %}

```swift
let remonCall = RemonCall()

remonCall.onInit { [weak self](token) in
    // UI 처리등 remon이 초기화 되었을 때 처리하여야 할 작업
}
​
remonCall.onConnect { [weak self](channelId) in
    // 해당 'chid'로 미리 생성된 채널이 없다면 다른 사용자가 해당 'chid'로 연결을 시도 할때 까지 대기 상태가 됩니다. 
}
​
remonCall.onComplete { [weak self] in
    // Caller, Callee간 통화 시작
}
​
remonCast.onClose { [weak self](closeType) in
    // 종료
}
```

{% endtab %}

{% tab title="iOS - ObjC" %}

```objectivec
RemonCall *remonCall = [[RemonCall alloc] init];

[remonCall onInitWithBlock:^{
    // Things to do when remon is initialized, such as UI processing, etc.
}];

[remonCallter onConnectWithBlock:^(NSString * _Nullable chId) {
    // Make a call then wait the callee
}];

[remonCall onCompleteWithBlock:^{
    // Start between Caller and Callee
}];

[remonCall onCloseWithBlock:^{
    // End calling
}];
```

{% endtab %}
{% endtabs %}

더 많은 내용은 아래를 참조 하세요.​

{% content-ref url="/pages/-LGUX\_W2IMDRX7sIYMoc" %}
[Callbacks](/common/callbacks)
{% endcontent-ref %}

### Channel <a href="#channels" id="channels"></a>

랜덤채팅등과 같은 서비스에서는 전체 채널 목록을 필요로 하게 됩니다. 이를 위한 전체 채널 목록을 제공합니다.

{% tabs %}
{% tab title="Web" %}

```javascript
const remonCall = new Remon()
const calls = await remonCall.fetchCalls()
```

{% endtab %}

{% tab title="Android - Java" %}

```java
remonCall = RemonCall.builder().build();

remonCall.fetchCalls();
remonCall.onFetch( calls -> {
    // Do something
});
```

{% endtab %}

{% tab title="Android - Kotlin" %}

```kotlin
remonCall = RemonCall.builder().build()

remonCall.fetchCalls()
remonCall.onFetch { calls -> 
    // Do something
}
```

{% endtab %}

{% tab title="iOS - Swift" %}

```swift
let remonCall = RemonCall()

remonCall.fetchCalls { (error, results) in
    // Do something
}
```

{% endtab %}

{% tab title="iOS - ObjC" %}

```objectivec
RemonCall *remonCall = [[RemonCall alloc]init];
[remonCall fetchCastsWithIsTest:YES
              complete:^(NSArray<RemonSearchResult *> * _Nullable chs) {
                            // Do something
}];
```

{% endtab %}
{% endtabs %}

채널에 대한 더 자세한 내용은 아래를 참고하세요.​

{% content-ref url="/pages/-LGUX\_W3PoO-2aW2JGKd" %}
[Channel](/common/channel)
{% endcontent-ref %}

### 종료 <a href="#undefined-4" id="undefined-4"></a>

모든 통신이 끝났을 경우 꼭 RemonCast객체를 `close()`해주어야 합니다. close를 통해서 모든 통신자원과 미디어 스트림 자원이 해제됩니다.

{% tabs %}
{% tab title="Web" %}

```javascript
const remonCall = new Remon()
remonCall.close()
```

{% endtab %}

{% tab title="Android - Java" %}

```java
remonCall = RemonCall.builder().build();
remonCall.close();
```

{% endtab %}

{% tab title="Android - Kotlin" %}

```kotlin
remonCall = RemonCall.builder().build()
remonCall.close()
```

{% endtab %}

{% tab title="iOS - Swift" %}

```swift
let remonCall = RemonCall()
remonCall.closeRemon()
```

{% endtab %}

{% tab title="iOS - ObjC" %}

```objectivec
RemonCall *remonCall = [[RemonCall alloc]init];
[remonCall closeRemon];
```

{% endtab %}
{% endtabs %}

### 기타 <a href="#undefined-5" id="undefined-5"></a>

아래를 통해 보다 자세한 설정,  실 서비스를 위한 프렉티스등 다양한 내용을 확인해 보세요.

{% content-ref url="/pages/-LGUX\_W-t9BCj3vjDn1A" %}
[Config/RemonConfig](/common/config)
{% endcontent-ref %}

{% content-ref url="/pages/-LRQKY\_sVXVaNRh\_4zBi" %}
[Network Environment](/common/network-environment)
{% endcontent-ref %}


# Callbacks

## Overview

`RemonCast`, `RemonCall`의 간단한 코드 만으로 통신 및 방송이 가능 합니다. 사용자의 필요에 따라 UI처리 및 추가 작업이 필요한 경우가 발생 합니다. 아래의 다양한 Callback을 통해 보다 세부적인 개발이 가능합니다.

안드로이드 2.4.13, iOS 2.6.9 버전부터 콜백은 모두 UI Thread 에서 호출됩니다. 이전 버전의 경우 UI 요소의 변경 시 UI Thread 와 관련한 처리를 추가해야 합니다.

방송과 통신은 각각에 적합한 이벤트와 흐름을 가지고 있습니다. 이를 알아두면 Callback를 활용하는데 도움이 됩니다. 이에 대한 내용은 아래를 참고하세요.

{% content-ref url="/pages/-LGUX\_VXNBELDoLRtDdi" %}
[Flow](/overview/flow)
{% endcontent-ref %}

{% content-ref url="/pages/-LGUX\_W3PoO-2aW2JGKd" %}
[Channel](/common/channel)
{% endcontent-ref %}

## Basics

### onInit(token) / onInit()

`onInit`은 SDK가 인터넷을 통해 RemoteMonster 서버에 정상적으로 접속하여 RemoteMonster의 방송, 통신 인프라를 사용할 준비가 완료된 상태를 의미합니다. 이때 인증 정보인 `token`을 돌려 받습니다. 대다수의 경우 사용할 일이 없으며 디버깅에 활용하게 됩니다.\
(Android 의 경우 token 을 전달하지 않습니다.)

{% tabs %}
{% tab title="Web" %}

```javascript
const listener = {
  onInit(token) {
    // Do something
  }
}
```

{% endtab %}

{% tab title="Android - Java" %}

```java
remonCast.onInit(() -> {
    // Do something
});
```

{% endtab %}

{% tab title="Android - Kotlin" %}

```kotlin
remonCast.onInit {

}
```

{% endtab %}

{% tab title="iOS - Swift" %}

```swift
remonCast.onInit { (token) in
  // Do something
}
```

{% endtab %}

{% tab title="iOS - ObjC" %}

```objectivec
[remonCast onInitWithBlock:^{
    // Do something
}];
```

{% endtab %}
{% endtabs %}

### onCreate(channelId) - livecast

방송에서 송출자만 사용합니다. 송출자가 `create()`을 통해 방송을 정상적으로 생성하여 송출이 될때입니다.

`onCreate`는 인자로 `channelId`를 넘겨줍니다. 이것은 이 방의 고유한 구분자로 시청자들이 이 `channelId`를 통해 접속하여 방송을 보게 됩니다.

{% tabs %}
{% tab title="Web" %}

```javascript
const listener = {
  onCreate(channelId) {
    // Do something
  }
}

const cast = new Remon({ listener })
cast.createCast()                          // Server generate chid
```

{% endtab %}

{% tab title="Android - Java" %}

```java
remonCast.onCreate((channelId) -> {
    // Do something
});

remonCast.create("ChannelName");             // Server generate channelId
```

{% endtab %}

{% tab title="Android - Kotlin" %}

```kotlin
remonCast.onCreate {(channelId)->

}

remonCast.create("ChannelName")
```

{% endtab %}

{% tab title="iOS - Swift" %}

```swift
remonCast.onCreate { (channelId) in
  // Do something
}

remonCast.create()               // Server generate chid
```

{% endtab %}

{% tab title="iOS - ObjC" %}

```objectivec
[remonCast onCreateWithBlock:^(NSString * _Nullable chId) {
    // Do something
}];
```

{% endtab %}
{% endtabs %}

### onJoin() - livecast

방송에서 시청자만 사용됩니다. 시청자가 `join()`을 통해 연결이 완료 된후 미디어 시청이 가능해 졌을 때 호출 됩니다.

{% tabs %}
{% tab title="Web" %}

```javascript
const listener = {
  onJoin(channelId) {
    // Do something
  }
}

const cast = new Remon({ listener })
cast.joinCast('MY_CHANNEL_ID')                    // 'chid' is mandatory
```

{% endtab %}

{% tab title="Android - Java" %}

```java
remonCast.onJoin(() ->
    // Do something
});

remonCast.join('MY_CHANNEL_ID');             // channelId is mandatory
```

{% endtab %}

{% tab title="Android -Kotlin" %}

```kotlin
remonCast.onJoin {
    // Do something
}

remonCast.join('MY_CHANNEL_ID');   
```

{% endtab %}

{% tab title="iOS - Swift" %}

```swift
remonCast.onJoin {
  // Do something
}

remonCast.join('MY_CHANNEL_ID')            // 'chid' is mandatory
```

{% endtab %}

{% tab title="iOS - ObjC" %}

```javascript
const listener = {
  onJoin(channelId) {
    // Do something
  }
}

const cast = new Remon({ listener })
cast.joinCast('MY_CHANNEL_ID')                    // 'channelId' is mandatory
```

{% endtab %}
{% endtabs %}

### onConnect(channelId) - communication

통신에서만 사용됩니다. 실질적으로 채널을 만들어 통화를 요청하는 Caller이거나 만들어진 채널에 접속하여 요청에 응답하는 Callee일때의 동작을 달리 하는 경우가 많으며 개발자가 Caller, Callee여부에 대한 상태를 관리해야 합니다.

Caller는 `connect()`을 통해 채널을 새로 만들고 상대방이 입장하기를 기다립니다.

Callee는 `connect()`을 통해 이미 만들어진 채널에 접속하게 됩니다. 이때 만들어진 채널의 `channelId`를 필수로 필요하게 됩니다. 정상적으로 완료되면 `onConnect`가 생기나, Callee라면 곧바로 발생하는 `onComplete`를 사용하는것을 권장합니다.

{% tabs %}
{% tab title="Web" %}

```javascript
const listener = {
  onConnect(channelId) {
    if (isCaller) {
      // Do something
    }
  }
}

const call = new Remon({ listener })
call.connectCall()
// Or
call.connectCall('MY_CHANNEL_ID')
```

{% endtab %}

{% tab title="Android - Java" %}

```java
// 콜백
remonCall.onConnect((channelId) -> {
    // Do something
});

// 연결
remonCall.connect("MY_CHANNEL_ID");
```

{% endtab %}

{% tab title="Android - Kotlin" %}

```kotlin
// 콜백
remonCall.onConnect{ channelId ->
    // Do something
}

// 연결
remonCall.connect("MY_CHANNEL_ID");
```

{% endtab %}

{% tab title="iOS - Swift" %}

```swift
remonCall.onConnect { (channelId) in
     // Do something
}

remonCast.connect("MY_CHANNEL_ID")
```

{% endtab %}

{% tab title="iOS - ObjC" %}

```objectivec
[remonCall onConnectWithBlock:^(NSString * _Nullable chId) {
    // Do something
}];

[remonCall connect:@"MY_CHANNEL_ID" :nil];
```

{% endtab %}
{% endtabs %}

### onComplete() - communication

통신에만 사용됩니다. 상호간 연결이 완료 된후 미디어 전송이 가능해 졌을 때 호출 됩니다.

{% tabs %}
{% tab title="Web" %}

```javascript
const listener = {
  onComplete() {
    // Do something
  }
}
```

{% endtab %}

{% tab title="Android - Java" %}

```java
remonCall.onComplete(() -> {
    // Do something
});
```

{% endtab %}

{% tab title="Android - Kotlin" %}

```kotlin
remonCall.onComplete {
    // Do something
}
```

{% endtab %}

{% tab title="iOS - Swift" %}

```swift
remonCall.onComplte {
    // Do something
}
```

{% endtab %}

{% tab title="iOS - ObjC" %}

```objectivec
[remonCall onCompleteWithBlock:^{
    // Do something
```

{% endtab %}
{% endtabs %}

### onClose()

사용자가 명시적으로 `close()` 함수를 호출 하거나 상대방이 `close()`함수를 호출 했을때 또는 네트워크 이상 등으로 더이상 연결을 유지 하기 어려울 때 등 연결이 종료 되면 호출 되며, `Remon`에서 사용했던 자원들 해제가 완료된 상태입니다.

{% tabs %}
{% tab title="Web" %}

```javascript
const listener = {
  onClose() {
    // Do something
  }
}
```

{% endtab %}

{% tab title="Android - Java" %}

```java
remonCast.onClose((closeType) -> {
    // CloseType.MINE : 자신이 close() 를 호출해 연결을 끊은 경우
    // CloseType.OTHER : 상대방이 close() 를 호출해 연결을 끊은 경우
    // CloseType.OTHER_UNEXPECTED : 상대방이 끊어져서 연결이 종료된 경우
    // CloseType.UNKNOWN : 이유를 알 수 없이 연결이 종료된 경우
});
```

{% endtab %}

{% tab title="Android - Kotlin" %}

```kotlin
remonCast.onClose { closeType:CloseType ->
    // CloseType.MINE : 자신이 close() 를 호출해 연결을 끊은 경우
    // CloseType.OTHER : 상대방이 close() 를 호출해 연결을 끊은 경우
    // CloseType.OTHER_UNEXPECTED : 상대방이 끊어져서 연결이 종료된 경우
    // CloseType.UNKNOWN : 이유를 알 수 없이 연결이 종료된 경우
}
```

{% endtab %}

{% tab title="iOS - Swift" %}

```swift
remonCast.onClose {
    // Do something
}

remonCast.close()
```

{% endtab %}

{% tab title="iOS - ObjC" %}

```objectivec
[remonCast onCloseWithBlock:^{
    // Do something
}];
```

{% endtab %}
{% endtabs %}

### onError(error)

`Remon`이 동작 중에 에러가 발생 할때 호출 됩니다.

{% tabs %}
{% tab title="Web" %}

```javascript
const listener = {
  onError(error) {
    // Do something
  }
}
```

{% endtab %}

{% tab title="Android - Java" %}

```java
remonCast.onError((error) -> {
    // Do something
});
```

{% endtab %}

{% tab title="Android -Kotlin" %}

```kotlin
remonCall.onError { error: RemonException ->
    when( error.code ) {
    RemonErrorCode.unknown->
    RemonErrorCode.initError->
    RemonErrorCode.wsError->
    RemonErrorCode.connectError->
    RemonErrorCode.iceError->
    RemonErrorCode.mediaError->
    RemonErrorCode.invalidParameterError->
    RemonErrorCode.networkChange->
    }
}
```

{% endtab %}

{% tab title="iOS - Swift" %}

```swift
remonCast.onError { (error) in
    // RemonError
    switch error {
    case .ConnectChannelFailed(_)
    break;
    }
    
}
```

{% endtab %}

{% tab title="iOS - ObjC" %}
N/A
{% endtab %}
{% endtabs %}

Error 코드에 대해 자세히 알고 싶으시면 아래 내용을 참고하시기 바랍니다.

{% content-ref url="/pages/-LGUX\_W5VllExeWY9OIA" %}
[Error](/common/error)
{% endcontent-ref %}

### onRetry(completed) - Beta

`Remon` 이 동작 중에 네트워크 환경의 변경이 감지되면 재연결을 시도 합니다. 이 때 재연결 상태를 알려주는 `onRetry()` 함수가 호출 되며 재연결이 시도가 시작 될 때는 `completed` 값을 `false`로 호출 되고, 재연결이 완료 되면 `completed` 값을 `true`로 호출 됩니다. 만약 재연결 시도중 재연결이 실패 하거나 에러가 발생 한다면 `onRetry()`가 아닌 `onError()` 또는 `onClose()`가 호출 될 수도 있습니다.

{% tabs %}
{% tab title="Web" %}
N/A
{% endtab %}

{% tab title="Android" %}
N/A
{% endtab %}

{% tab title="iOS - Swift" %}

```swift
self.remonCast.onRetry { (completed) in
    if completed {
        // 재연결이 완료 되었습니다.
        // 재연결 시도 중 실패가 발생 한다면 호출 되지 않을 수 있습니다.
    } else {
        // 재연결을 시도 합니다. 재연결이 시도가 시작 되면 항상 호출 됩니다.
    }
}
```

{% endtab %}

{% tab title="iOS - ObjC" %}

```objectivec
[self.remonCall onRetryWithBlock:^(BOOL completed) {
    if (completed) {
        // 재연결이 완료 되었습니다.
        // 재연결 시도 중 실패가 발생 한다면 호출 되지 않을 수 있습니다.
    } else {
        // 재연결을 시도 합니다. 재연결이 시도가 시작 되면 항상 호출 됩니다.
    }        
}];
```

{% endtab %}
{% endtabs %}

## Advanced

### onRemoteVideoSizeChanged(view, size)/onLocalVideoSizeChanged(view, size)

영상의 사이즈는 네트워크 상태에 따라 시시각각 변화 하며, 영상의 비율은 영상장치에 따라 다릅니다. 영상 송출자가 고정된 사이즈와 비율 보장해 주지 않는 환경이라면 `onRemoteVideoSizeChanged`와 `onLocalVideoSizeChanged` 함수를 구현 하여 변화 하는 영상크기에 반응 하도록 구현합니다.

{% tabs %}
{% tab title="Web" %}
N/A
{% endtab %}

{% tab title="Android" %}
N/A
{% endtab %}

{% tab title="iOS - Swift" %}

```swift
let remonCall = RemonCall()
remonCall.onRemoteVideoSizeChanged {(view, size) in 
    let raito = size.height / size.width
    let oldSize = view.frame.size
    let newFrame = 
    CGRect(x: 0.0, y: 0.0, width: oldSize.width, height: oldSize.width * raito)
    view.frame = newFrame
}
```

아래의 링크를 통해 구체적인 응용 예시를 확인할 수 있습니다.

{% content-ref url="/pages/-LGUX\_VucmwTGQ6RClCr" %}
[iOS - Media](/ios/ios-media)
{% endcontent-ref %}
{% endtab %}

{% tab title="iOS - ObjC" %}

```objectivec
[self.remonCall onLocalVideoSizeChangedWithBlock:^(UIView * _Nullable view, CGSize size) {
        CGFloat raito = size.height / size.width;
        CGRect oldFrame = view.frame;
        CGSize oldSize = oldFrame.size;
        CGRect newFrame = CGRectMake(0, 0, oldSize.width, oldSize.width * raito);
        view.frame = newFrame;
    }];
```

아래의 링크를 통해 구체적인 응용 예시를 확인할 수 있습니다.

{% content-ref url="/pages/-LGUX\_VucmwTGQ6RClCr" %}
[iOS - Media](/ios/ios-media)
{% endcontent-ref %}
{% endtab %}
{% endtabs %}

###

### onStat(report)

통신 / 방송 상태를 알수있는 `report`를 받습니다. `report`는 사용자가 `remon` 생성시 설정한 `statInterval`간격 마다 들어오게 됩니다. 네트워크 상황등에 따른 미디어 품질을 나타냄으로 사용자에게 로딩 UI 처리등 안내를 하는데 유용합니다.

이때, 들어오는 값은 영상 및 음성 통화 중에 현재 통화의 품질이 어떠한지를 통합하여 1에서 5까지의 단계로 확인할 수 있습니다.

사용자는 간혹 자신 혹은 상대방의 네트워크 문제로 인하여 통화 품질이 안좋거나 끊어진 상황에서도 서비스의 문제라고 생각하고 불만을 제기할 수 있습니다. 때문에 사용자의 문제가 네트워크의 문제임을 사전에 알려주거나 다양한 UI 처리가 가능합니다.

현재 이 통화 품질 정보는 5초에 한번씩 받을 수 있습니다.

| 단계 | 품질        | 비고 |
| -- | --------- | -- |
| 1  | 매우 좋음     |    |
| 2  | 좋음        |    |
| 3  | 나쁨        |    |
| 4  | 매우 나쁨     |    |
| 5  | 방송, 통화 불능 |    |

{% tabs %}
{% tab title="Web" %}

```javascript
const listener = {
  onStat(result){
    const stat = `State: l.cand: ${result.localCandidate} /r.cand: ${result.remoteCandidate} /l.res: ${result.localFrameWidth} x ${result.localFrameHeight} /r.res: ${result.remoteFrameWidth} ${result.remoteFrameHeight} /l.rate: ${result.localFrameRate} /r.rate: ${result.remoteFrameRate} / Health: ${result.rating}`
    console.log(stat)
  }
}
```

`Remon` 객체를 생성할 때 입력 인자로 넣는 listener의 메소드 중 `onStat()` 을 구현하여 품질 정보를 받을 수 있습니다. 위의 `result`에서 받을 수 있는 여러 정보 중 `result.rating` 이 바로 네트워크 상황에 따른 통합적인 통화 품질 정보입니다.
{% endtab %}

{% tab title="Android - Java" %}

```java
  @Override
  public void onStat(RemonStatReport report) {
      Logger.i(TAG, "report: " + report.getHealthRating());
      String stat = "health:" + report.getHealthRating().getLevel() + "\n";
  }
```

report에는 방송/통신의 상태를 알 수있는 여러가지 값들이 있습니다. `report.getHealthRating().getLevel()`을 통해 품질을 상태를 알 수도 있고, `report.getRemoteFrameRate()` / `report.getLocalFrameRate()`를 통해 해당 연결의 fps를 확인 할 수 있습니다.
{% endtab %}

{% tab title="Android - Kotlin" %}

```kotlin
remonCall.onStat { report:RemonStatReport ->
      Logger.i(TAG, "report: " + report.getHealthRating())
      val stat:String = "health:" + report.getHealthRating().getLevel() + "\n"
}
```

report에는 방송/통신의 상태를 알 수있는 여러가지 값들이 있습니다. `report.getHealthRating().getLevel()`을 통해 품질을 상태를 알 수도 있고, `report.getRemoteFrameRate()` / `report.getLocalFrameRate()`를 통해 해당 연결의 fps를 확인 할 수 있습니다.
{% endtab %}

{% tab title="iOS - Swift" %}

```swift
let remonCall = RemonCall()
remoCall.onRemonStatReport{ (stat) in 
    let rating:RatingValue = stat.getRttRating()
    let level = rating.levle
}
```

{% endtab %}

{% tab title="iOS - ObjC" %}

```objectivec
[self.remonCast onRemonStatReportWithBlock:^(RemonStatReport * _Nonnull stat) {
    RatingValue *rating = [stat getRttRating];
    // Do something
}];
```

{% endtab %}
{% endtabs %}

보다 더 자세한 내용은 아래를 확인하세요.


# Channel

## Overview

RemoteMonster에서는 방송, 통신중 접속자가 공유하는 자원을 채널이란 이름으로 제공 하고 있습니다. 이 채널은 처음 만들때 생성되어 각각의 고유한 Id를 제공하며, 이들의 목록을 가져오거나 검색하여 특정 채널에 접속 하게 됩니다. 추가적으로 채널에 Name을 별칭으로 지정하여 좀 더 편하게 사용할 수 있습니다.

|               | Class     | Id(unique) | Name        | Methods                        | Callbacks                 |
| ------------- | --------- | ---------- | ----------- | ------------------------------ | ------------------------- |
| Livecast      | remonCast | ChannelId  | ChannelName | `create`, `join`, `fetchCasts` | `onCreate`, `onJoin`      |
| Communication | remonCall | ChannelId  | ChannelName | `connect`, `fetchCalls`        | `onConnect`, `onComplete` |

전체적인 흐름과 여기에 대응하는 Callback은 아래를 참고하세요.

{% content-ref url="/pages/-LGUX\_VXNBELDoLRtDdi" %}
[Flow](/overview/flow)
{% endcontent-ref %}

{% content-ref url="/pages/-LGUX\_W2IMDRX7sIYMoc" %}
[Callbacks](/common/callbacks)
{% endcontent-ref %}

## Livecast

방송에서 방송 목록을 얻는 방법입니다. 일반적으로 방송 목록에서 진입할 방송을 찾는 UI에 자주 사용됩니다.

{% tabs %}
{% tab title="Web" %}

```javascript
const remonCast = new Remon()
const casts = await remonCast.fetchCasts()    // Return Promise
```

{% endtab %}

{% tab title="Android - Java" %}

```java
remonCast = RemonCast.builder()
        .context(ListActivity.this)
        .build();

remonCast.onFetch( List<Room> casts -> {
    for (Room cast : casts) {
        myChannelId = cast.getId;
    }
});

remonCast.fetchCasts();
```

{% endtab %}

{% tab title="Android - Kotlin" %}

```kotlin
remonCast = RemonCast.builder()
        .context(ListActivity.this)
        .build()
    
remonCast.onFetch { casts:List<Room> -> 
    for ( Room cast in casts) {
        myChannelId = cast.getId;
    }
}

remonCast.fetchCasts()
```

{% endtab %}

{% tab title="iOS - Swift" %}

```swift
remonCast.fetchCasts { (err, results) in
    if let err = err {
        // 검색 중에 발생한 에러는 remonCast.onError()를 호출 하지 않습니다.
        print(err.localizedDescription)
    } else if let results = results {
        for cast:RemonSearchResult in results {
            myChannelId = cast.id
        }
    }
}

remonCast.join(myChannelId)
```

{% endtab %}

{% tab title="iOS - ObjC" %}

```objectivec
RemonCast *remonCast = [[RemonCast alloc]init];
[remonCast fetchCastsWithIsTest:YES complete:^(NSArray<RemonSearchResult *> * _Nullable chs) {
     if (chs != nil) {
          for (RemonSearchResult *item in chs) {
               // Do Somethig                         
           }
     }                       
}];
```

{% endtab %}
{% endtabs %}

## Communication

통신에서 통화 목록을 얻는 방법입니다. 랜덤 채팅과 같은 상황에서 쓰이며 일반적으로는 잘 사용되지 않습니다.

{% tabs %}
{% tab title="Web" %}

```javascript
const remonCall = new Remon()
const calls = await remonCall
  .fetchCasts()                             // Return Promise
  .filter(item => item.status === "WAIT")
```

{% endtab %}

{% tab title="Android - Java" %}

```java
remonCall = RemonCall.builder()
        .build();

remonCall.onFetch( List<Room> calls -> {
    for ( Room call : calls) {
        if (call.getStatus.equals("WAIT")) {   // Only WAIT channels
            myChannelId = call.getId;
        }
    }
});

remonCall.fetchCalls();
```

{% endtab %}

{% tab title="Android - Kotlin" %}

```kotlin
remonCall = RemonCall.builder()
        .build();

remonCall.onFetch { calls:List<Room> ->
    for (Room call in calls) {
        if (call.status == "WAIT" ) {   // Only WAIT channels
            myChannelId = call.getId
        }
    }
}

remonCall.fetchCalls()
```

{% endtab %}

{% tab title="iOS - Swift" %}

```swift
let remonCall = RemonCall()
remonCall.fetchCalls { (err, results) in
    if let err = err {
        //검색 중에 발생한 에러는 remonCall.onError()를 호출 하지 않습니다.
        print(err.localizedDescription)
    } else if let results = results {
        for call:RemonSearchResult in results {
            if itme.status == "WAIT" {        // Only WAIT channels
                myChannelId = call.id
            }
        }
    }
}

remonCall.connect(myChannelId)
```

{% endtab %}

{% tab title="iOS - ObjC" %}

```objectivec
RemonCall *remonCall = [[RemonCall alloc]init];
[remonCall fetchCastsWithIsTest:YES complete:^(NSArray<RemonSearchResult *> * _Nullable chs) {
     if (chs != nil) {
          for (RemonSearchResult *item in chs) {
               if ([item.status isEqualToString@"WAIT"]) {
                    // Do Somethig
               }                  
           }          
     }                       
}];
```

{% endtab %}
{% endtabs %}


# Channel ID 규칙

채널ID(Channel ID)는 개발자가 지정할 수 있습니다. 채널 생성 시 채널ID를 지정하지 않으면 임의로 생성됩니다. 채널ID는 아래 사항을 주의해주십시오.

* 특수문자( : ; \* $ \` 등)를 사용하지 않음
* 한글을 사용하지 않음

  최소 4자 최대 1024자

채널ID는 각각의 방송 또는 통화에 대해 고유하고 추측이 어렵게 만들어야합니다. 동일한 채널ID로 여러 번의 방송 또는 통화를 만드는 것은 권장하지 않습니다.

방송채널의 ID는 방송자가 명시적으로 채널을 생성하는 시각부터 방송자가 채널을 종료하는 시각까지 유효합니다. 방송자가 영상을 송출하지 않아도 채널을 생성하기만해도 유효합니다. 채널이 유효하면, 시청자가 이 채널에 들어갈 수 있습니다.&#x20;

통화채널의 ID는 통화 송신자가 채널에 연결한 시각부터 송신자 또는 수신자 둘 중 어느 한 쪽이 연결을 해제한 시각까지 유효합니다. 과금은 양측이 연결되어 실제 통화가 이루어진 시간을 대상으로 합니다.


# Network Environment

## &#x20;Overview

영상, 음성등 미디어를 전송하는데 있어서 네트워크 환경은 절대적인 영향을 미칩니다. 리모트몬스터의 방송과 통신기능은 기본적으로 실시간에 준하는 빠른 전송을 하기위한 다양한 기술이 접목되어 있습니다. 다만, 이러한것과는 별개로 최상의 사용자 경험을 위해 몇가지 추가적인 작업을 하는것을 권장드립니다.

### WiFi 환경

MIMO기능등을 통해 동시에 여러 단말과 통신이 가능한 기능이 없고 한 순간에 한번의 단말과 통신이 되는 일반적인 보급형 모델의 경우 한 WiFi 라우터에 여러 단말이 접속해 있고, 이들이 동시에 미디어를 수신하게 된다면 어려움을 겪을 수 있습니다. 이러한 증상으로 단말에서는 검은화면(FPS 0)이 나타날수 있으며 아래의 FPS 감시 등을 통해 사용자에게 안내가 가능합니다. 까페와 같은 공공장소, 사무실등 보급형 무선 라우터 환경에서는 필수적으로 권장됩니다.&#x20;

{% embed url="<https://ko.wikipedia.org/wiki/MIMO>" %}

### 방송, 통화간 차이

통화는 peer-to-peer 를 기본으로 1:1로 연결이 진행됩니다. 이 상황에서는 특별한 변화가 없는 이상 각 peer가 서로의 네트워크 환경과 단말의 미디어 환경에 맞추어 서로간에 품질 최적화 등의 작업을 지능적으로 수행합니다.

다만 방송의 경우 송출자 및 시청자간 지능적인 품질 최적화의 일부 제한이 있을 수 있으며, 아래의 경우를 참고하여 대비하는것이 좋습니다. 대다수의 사용자는 네트워크 환경에 따른 미디어 품질의 높고 낮음을 인지 하지 못하고 단순히 응용 서비스의 작동이상으로 착각하는 경우가 많음으로 아래와 같은 항목을 적극 활용하는것이 유용합니다.

{% hint style="info" %}
통화기능을 사용한다면 필요시 이 장을 참고하여도 좋습니다. 방송기능을 사용한다면 아래의 내용을 살펴보고 처음부터 준비하는것을 권장합니다.
{% endhint %}

### FPS 감시 - 방송/통신 - 영상

방송/통신 영상 수신에 관련된 방법입니다. 3G 같이 지나치게 낮은 대역폭, 네트워크의 문제나 미디어가 최초에 연결될때 지연같은 이유로 영상이 안나오는 경우가 있습니다. 이때 SDK에서는 FPS가 0으로 나타나게되고 이 값을 감시하면서 로딩 인디케이터나 안내등을 제공하여 사용자 경험을 향상 할 수 있습니다.

e.g. FPS가 0일시 방송화면 전체를 가리는 로딩 인디케이터

{% tabs %}
{% tab title="Web" %}

```javascript
const listener = {
  onStat(result){
    const stat = `l.rate: ${result.localFrameRate} /r.rate: ${result.remoteFrameRate}`
    console.log(stat)
  }
}
```

{% endtab %}

{% tab title="Android" %}

```java
  @Override
  public void onStat(RemonStatReport report) {
      int fps = report.getRemoteFrameRate();
  }
```

`report.getRemoteFrameRate()` / `report.getLocalFrameRate()`를 통해 해당 연결의 fps를 확인 할 수 있습니다.

방송 SDK를 사용하는 경우, receive fps가 0이 됐을때 자동으로 로딩 인디케이터를 띄워주고 원활하게 방송이 나올경우 사라지게 됩니다.
{% endtab %}

{% tab title="iOS - Swift" %}

```swift
self.remonCast.onRemonStatReport { (report) in
    if report.remoteFrameRate == 0 {
        print("Remote frame rate is zero")
    } 
    if report.localFrameRate == 0 {
        print("Local frame rate is zero")
    }
}
```

{% endtab %}

{% tab title="iOS - ObjC" %}

```objectivec
[self.remonCast onRemonStatReportWithBlock:^(RemonStatReport * _Nonnull stat) {
    if ( [stat remoteFrameRate] == 0 ) {
        NSLog(@"Remote frame rate is zero");
    }
        
    if ( [stat localFrameRate] == 0 ) {
        NSLog(@"Local frame rate is zero");
    }
}];
```

{% endtab %}
{% endtabs %}

{% content-ref url="/pages/-LGUX\_W2IMDRX7sIYMoc" %}
[Callbacks](/common/callbacks)
{% endcontent-ref %}

### Media Health Report 감시 - 방송/통신 - 영상/음성

방송/통신의 영상/음성 수신에 관련된 방법입니다. SDK 내부에서 자체적인 품질값을 제공하고 있습니다. 특히 이 값이 최저 일때, 간단하게는 안내, 토스트등의 사용자 경험을 보호하는 UI를 제공하는것이 의미 있습니다. 다만, 이값이 꼭 미디어가 안보이는 상황을 의미하는 것은 아니기 때문에 간단한 안내가 적합합니다. 또는 일정 시간동안 이 값의 추이를 감시하여 별도의 처리를 하는 방법이 있을 수 있습니다. 실제 사용은 아래의 링크를 참고하세요.

e.g. 품질이 나쁠 때, 네트워크 불안정으로 인해 품질이 안 좋음을 안내하는 toast UI

{% content-ref url="/pages/-LGUX\_W2IMDRX7sIYMoc" %}
[Callbacks](/common/callbacks)
{% endcontent-ref %}

{% hint style="info" %}
Fps와 Quality Statistics Report는 어떻게 다른가요?

Fps는 사용자경험에 매우 직접적인 결과를 나타내는 값으로 연속적인 사용자 경험을 보장하기 위한 로딩 인디케이터를 띄우는 것이 유의미 합니다.

Quality Statistics Report는 다양한 값을 참조하고 경우에 따라서는 즉각적이지 않고 실제 품질을 후행하는 지표일 수 있음으로 사용자에게 앱의 이상이 아님을 안내 하는 용도로 적합합니다.
{% endhint %}

### Simulcast(beta) - 방송 - 영상

영상 방송의 송출과 수신에 관련된 기능입니다. 동시에 두개이상의 품질의 미디어를 제공해서 수신자측에서 네트워크 환경에 맞게 선택적으로 품질을 정할수 있습니다.

데스크탑 브라우저에서 방송송출시 simulcast기능을 사용해 두개의 고화질, 저화질의 두가지 화질을 동시에 지원가능합니다. 이 때, 단말은 처음은 고화질로 시청을 시도하고 만약 불안정 하다면 낮은 품질로 시청을 하게 됩니다. 다만 자동으로 고품질로 시도하는 기능은 제공하고 있지 않습니다.

자세한 내용은 아래 링크를 참고하세요.

{% content-ref url="/pages/-LRQY\_hgb3oEPjWMz4Za" %}
[Simulcast](/common/livecast-simulcast)
{% endcontent-ref %}

### WiFi - Data 통신(3G/LTE)간 전환 - 방송/통신 - 영상/음성

RemoteMonster의 SDK는 모바일 네트워크 연결에서 자동으로 WiFi-데이터 통신간 변경시 연속적인 연결과 미디어 송수신을 제공합니다. 단, 연결변경시에 변경되는 망의 품질등에 따라 접속이 실패 할 수 있으며, 이는 다양한 상황과 환경을 고려하여 대응해야 합니다.&#x20;

아래를 참고하여 변경실패에 대비할것을 권합니다.

{% tabs %}
{% tab title="Web" %}
N/A
{% endtab %}

{% tab title="Android" %}

```java
private ConnectivityManager manager;
private final ConnectivityManager.NetworkCallback networkCallback = new ConnectivityManager.NetworkCallback() {
    @Override
    public void onAvailable(Network network) {
        super.onAvailable(network);
        Log.i("ConnectivityManager", "Network Type : " + (manager.isActiveNetworkMetered() ? "LTE" : "WIFI"));
    }

    @Override
    public void onLost(Network network) {
        super.onLost(network);
    }
};

protected void onCreate(@Nullable Bundle savedInstanceState) {
    ...
    manager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
    manager.registerDefaultNetworkCallback(networkCallback);
}

protected void onDestroy() {
    ...
    manager.unregisterNetworkCallback(networkCallback);
}
```

[ConnectivityManager.NetworkCallback](https://developer.android.com/reference/android/net/ConnectivityManager.NetworkCallback.html#ConnectivityManager.NetworkCallback\(\))() 의 [onAvailable](https://developer.android.com/reference/android/net/ConnectivityManager.NetworkCallback.html#onAvailable\(android.net.Network\))([Network](https://developer.android.com/reference/android/net/Network.html) network)을 활용해 네트워크 변경시 재연결을 시키는 로직을 구현할 수도 있습니다.

{% endtab %}

{% tab title="iOS - Swift" %}
`onRetry` 콜백을 통해 제공 됩니다. 아래를 확인하세요.

{% content-ref url="/pages/-LGUX\_W2IMDRX7sIYMoc" %}
[Callbacks](/common/callbacks)
{% endcontent-ref %}
{% endtab %}

{% tab title="iOS - ObjC" %}
`onRetry` 콜백을 통해 제공 됩니다. 아래를 확인하세요.

{% content-ref url="/pages/-LGUX\_W2IMDRX7sIYMoc" %}
[Callbacks](/common/callbacks)
{% endcontent-ref %}
{% endtab %}
{% endtabs %}


# Studio Environment

방송을 송출시 스튜디오 구성에 대한 안내입니다. 일반적으로 모바일이나, 간단하게 카메라가 있는 노트북을 통해 방송 송출이 가능하지만, 경우에 따라서 1인 방송환경이나, 전문 스튜디오 환경에서 방송송출이 필요할 수 있습니다. 이런 환경에서 방송시 아래와 같은 구성을 할 수 있습니다.

방송을 기준으로 설명되어 있으며, 같은과정을 통해 통신환경에서도 사용할 수 있습니다.

## 1인 방송 환경

기본적인 노트북, PC에서 추가적인 카메라와 마이크 등을 통해 방송을 송출하고자 할 때 입니다.

노트북, PC에서 간단하게 웹캠을 추가하여 방송할 수 있습니다. 장치를 USB에 연결하고 브라우저에서 Web SDK Config를 통해 기기를 선택하면 됩니다. 보통 최적의 방송 품질을 위해 아래와 같은 4K지원의 웹캠을 사용합니다.

{% embed url="<https://www.logitech.com/en-us/product/4k-pro-webcam?crid=34>" %}

웹캠이 아니더라도 UVC(USB Video Class) 규격을 지원하는 모든 비디오 장치는 별도의 과정없이 곧바로 브라우저에서 해당 장비를 선택하여 방송을 송출하거나 통신에 사용이 가능합니다.

소지하고 있는 장비가 UVC를 지원하는지 확인하려면 Skype를 통해 지원 여부와 정상작동여부를 확인 가능합니다. 설정 > Audio / Video 에서 기기 선택이 가능하면 UVC를 지원하는 장치 입니다.&#x20;

{% embed url="<https://skype.com>" %}

UVC에 대한 자세한 내용은 아래를 참고하세요.

{% embed url="<https://en.wikipedia.org/wiki/USB_video_device_class>" %}

{% embed url="<https://docs.microsoft.com/ko-kr/windows-hardware/drivers/stream/usb-video-class-driver>" %}

## 스튜디오 환경

별도의 외부 카메라와 전문 장비가 갖추어진 스튜디오 환경 안내 입니다. 실질적으로는 UVC가 지원되지 않는 DSLR, Video Camera 사용에 관한 안내입니다. 아래의 문서에서 브라우저에서 장치선택, 미디어의 브라우저 입력에 관한 부분은 설정에 관련된 개발이 일부 필요할 수 있으며 해당내용은 이문서의 개발을 확인하세요.

### 구성

기본적으로 외부카메라와 외부카메라를 연결하는 케이블, 케이블을 통해 신호를 입력받는 캡쳐보드로 구성됩니다.

이 때, 케이블 혹은 입출력 규격에 따라 크게 HDMI와 SDI를 고려하여 구성을 할 수 있습니다. HDMI의 경우 대중적으로 많이 사용되는 규격으로 대다수의 카메라가 지원하며, SDI의 경우는 보다 전문적인 장비에 주로 탑제되어 사용됩니다.

{% embed url="<https://ko.wikipedia.org/wiki/%EA%B3%A0%EC%84%A0%EB%AA%85_%EB%A9%80%ED%8B%B0%EB%AF%B8%EB%94%94%EC%96%B4_%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4>" %}

{% embed url="<https://ko.wikipedia.org/wiki/%EC%8B%9C%EB%A6%AC%EC%96%BC_%EB%94%94%EC%A7%80%ED%84%B8_%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4>" %}

캡쳐보드, 캡쳐 인터페이스는 이러한 입력 신호를 수신할 수 있는 장비를 선택하면 됩니다. 리모트몬스터는 아래와 같은 장비를 테스트 하였으며 정상 작동을 확인하였습니다.

### Windows - Blackmagic DeckLink

{% embed url="<https://www.blackmagicdesign.com/kr/products/decklink>" %}

윈도우에서는 PCI-e인터페이스를 사용하는 캡쳐보드나 USB-C규격을 사용하는 외부 캡쳐 장비를 사용할 수 있습니다. 그중에서 동작을 확인한 덱링크 캡쳐보드를 통해 카메라 입력신호를 받는 방법을 안내합니다. 덱링크에서 카메라 입력시 브라우저에서는 보통 2개의 장비가 보이게 됩니다. 이 중 Decklink Video Capture를 선택하고 정확한 해상도와 FPS를 RemoteMonster Web SDK Config를 통해 입력해야 합니다.

정확한 입력이 되었음에도 불구하고 경우에 따라서 카메라의 설정이 브라우저가 지원하지 않는 범위 어서 화면이 정상적으로 표출 되지 않을 수 있습니다. 이를 해결하기 위해 아래의 단계를 거칩니다.

간단하게는 크롬 브라우저의 주소입력에서 chrome://media-internals/ 를 입력하여 현재 브라우저가 인식할 수 있는 장치와 해상도, FPS를 확인가능합니다.

{% embed url="<https://www.chromium.org/audio-video/media-internals>" %}

Blackmagic 장비의 경우 홈페이지에서 최신 Desktop Video와 Desktop Video SDK를 설치하고 보다 세부적인 정보를 아래와 같은 방법으로 알 수 있습니다.

{% embed url="<https://www.blackmagicdesign.com/kr/support/family/capture-and-playback>" %}

Desktop Video SDK를 받아 압축을 해제하고 다음 파일을 실행합니다.

```
Win/DirectShow/Samples/bin/DecklinkCapture.exe
```

![DecklinkCapture](/files/-LT5jperMcyFSj8kMVek)

프로그램의 Video Device and Format에서 Decklink Video Capture를 선택한후 아래에 나오는  해상도, FPS가 브라우저가 입력받을 수 있는 값입니다. 이 값중 원하는 값으로 카메라를 설정하고 Blackmagic Desktop Video Setup를 실행하여 카메라에서 설정된 값이 입력되고 있는지 확인합니다. 정상적으로 카메라 입력신호를 받고있다면 DecklinkCapture프로그램에서 해상도와 FPS를 설정하면 Preview화면에 비디오가 보일것 입니다.

그 후 브라우저와 RemoteMonster Web SDK의 config를 통해 정확한 해상도와 FPS를 입력하면 정상적으로 미디어가 브라우저를 통해 전송될 것 입니다. 이때 카메라를 사용하고 있는 DecklinkCapture를 종료시켜야 브라우저가 정상적으로 카메라 자원을 얻을 수 있습니다. 캡쳐보드 자원은 한번에 한 프로그램만 사용 가능합니다.

{% content-ref url="/pages/-LGUX\_W-t9BCj3vjDn1A" %}
[Config/RemonConfig](/common/config)
{% endcontent-ref %}

기타 장비로도 위와같이 윈도우의 Direct Show 구성을 확인하여 브라우저에서 미디어를 얻을 수 있으나, 일부 저렴한 장비에서는 720P, 30FPS 이상의 고품질 환경에서 화면 끊김등이나 카메라, 캡쳐 보드간 화면 끊김이 확인되고 있음을 참고하여 다른 캡쳐 장비를 선택 가능합니다.

### MacOS - Blackmagic UltraStudio

{% embed url="<https://www.blackmagicdesign.com/kr/products/ultrastudio>" %}

맥에서는 Thunderbolt 3입력을 지원하는 외부 캡쳐 장치를 사용하여 외부 전문 카메라를 입력받아 방송에 사용 할 수 있습니다. 그중에서 동작을 확인한 울트라 스튜디오를 통해 카메라 입력신호를 받는 방법을 안내합니다.  울트라스튜디오에서 카메라 입력시 브라우저에서는 매우 많은 의 장비가 보이게 됩니다. 이 중 실제로 입력받고 있는 정확한 해상도와 FPS의 장비를 선택한 후 RemoteMonster Web SDK Config를 통해 같은 값을 정확히 입력해야 합니다.

### 기타

위의 장비가 아닌 다른 장비의 경우 소프트웨어적인 방법으로 브라우저로 미디어를 전송할 수 있습니다. 일반적으로 Virtual WebCam이라는 형태로 아래의 프로그램을 통해 송출 가능합니다.

#### OBS + OBS VirtualCam Plugin - Windows

{% embed url="<https://obsproject.com/ko>" %}

{% embed url="<https://obsproject.com/forum/resources/obs-virtualcam.539/>" %}

윈도우에서만 가능하며 화상은 OBS와 플러그인을 통해 브라우저로 입력하고, 소리는 곧장 캡쳐보드에서 브라우저로 입력합니다. 이 구성은 장치의 환경에 따라서 소리와 화면간 동기가 안맞을 수 있으니 주의해서 사용해야 합니다.

#### Wirecast - Windows/MacOS

{% embed url="<https://www.telestream.net/wirecast/>" %}

윈도우와 맥에서 사용가능합니다. 와이어케스트의 가상카메라 기능을 통해 브라우저로 화면과 소리를 동시에 송출 할 수 있습니다. 아래를 참고하여 설정 가능합니다.

{% embed url="<http://telestream.force.com/kb/articles/Knowledge_Article/Wirecast-Virtual-Camera-into-Skype>" %}

## 개발

장비를 선택하거나 FPS, 해상도를 정확히 입력하는 작업이 필요합니다. 이 작업은 웹표준에서 지원하는 방식을 그대로 사용하며 아래는 간략하게 소개입니다.

{% embed url="<https://w3c.github.io/mediacapture-main/>" %}

{% embed url="<https://developer.mozilla.org/en-US/docs/Web/API/Media_Streams_API/Constraints>" %}

### 장치를 열거하고 장치 ID 확인하기

브라우저가 인식할 수 있는 장치를 열거하고 장치의 ID를 얻어올 수 있습니다. 이때 지금 페이지에서 브라우저가 사용자에게 권한을 얻어오면 사용자가 선택하기 쉽도록 라벨명도 얻어올 수 있습니다.

```javascript
async function getDevices() {
  try {
    const mediaStream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true })
    const userDevices = await navigator.mediaDevices.enumerateDevices()
    mediaStream.getTracks().forEach(track => track.stop()) // getUserMedia 때문에 자원을 반환해야함
    return userDevices
  } catch (err) {
    console.error(err)
  }
}

const devices = await getDevices()
```

{% embed url="<https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/enumerateDevices>" %}

{% embed url="<https://developer.mozilla.org/en-US/docs/Web/API/Navigator/permissions>" %}

### 해당장치가 제공되는 기능 확인하기

획득한 장치 ID를 갖고 해당장치의 기능을 미리 확인 가능합니다. 이때도 마찬가지로 권한이 있어야 합니다. 아래와 같이 ID와 장치 type을 통해 기능을 확인 가능합니다. 얻을 수 있는 정보는 아래의 링크를 확인하세요.

```javascript
async function getDeviceCapabilities(kind, deviceId) {
  const videoConstraints = {
    audio: false,
    video: {
      deviceId: deviceId ? { exact: deviceId } : undefined
    }
  }
  const audioConstraints = {
    audio: {
      deviceId: deviceId ? { exact: deviceId } : undefined
    },
    video: false
  }
  const constraints = !kind ? undefined :
    kind === 'audioinput' ? audioConstraints :
    kind === 'videoinput' ? videoConstraints :
    new Error('getDeviceCapabilities/unexpected_args', kind)

  try {
    if (constraints instanceof Error) return
    const mediaStream = await navigator.mediaDevices.getUserMedia(constraints)
    const capabilities = mediaStream.getTracks().map(track => track.getCapabilities())
    mediaStream.getTracks().forEach(track => track.stop())
    return capabilities
  } catch (err) {
    console.error('getDeviceCapabilities/getUserMedia', err)
  }
}

const capabilities = await Promise.all(devices.map(d => getCapabilities(d.kind, d.deviceId))) 
```

{% embed url="<https://w3c.github.io/mediacapture-main/#media-track-capabilities>" %}

### 특정 장치와 장치 설정값으로 방송만들기

Web SDK의 Config를 사용해 특정장치와 정확한 값을 입력하고 방송을 만들 수 있습니다. 세부적인 내용은 아래를 참고하세요.

```javascript
const config = {
  media: {
    video: {
      deviceId: { exact: __DEVICE_ID__ },
      width: { exact: __WIDTH__ },
      height: { exact: __HEIGHT__ },
      frameRate: { exact: __FRAME_RATE__ }
    }
  }
}

const remon = new Remon({ config })
```

{% content-ref url="/pages/-LGUX\_W-t9BCj3vjDn1A" %}
[Config/RemonConfig](/common/config)
{% endcontent-ref %}

### 만들어진 미디어에 대한 설정값 확인하기

만들어진 미디어에 대해 지정된 설정값이 반영되었는지를 확인할 수 있습니다.

```javascript
const settings = remon.cofig.rtc.localStream.getTracks().map(track => track.getSettings())
```


# Media

## Overview

최적의 미디어 품질을 얻기위한 안내입니다.&#x20;

기본적인 설정과 플렛폼별 안내는 아래를 참고하세요.

{% content-ref url="/pages/-LGUX\_W-t9BCj3vjDn1A" %}
[Config/RemonConfig](/common/config)
{% endcontent-ref %}

{% content-ref url="/pages/-LGUX\_VfR-uQd-5BzMiv" %}
[Web - Media](/web/web-view)
{% endcontent-ref %}

{% content-ref url="/pages/-LGUX\_Vnqg7GkW4vXRPF" %}
[Android - Media](/android/android-media)
{% endcontent-ref %}

{% content-ref url="/pages/-LGUX\_VucmwTGQ6RClCr" %}
[iOS - Media](/ios/ios-media)
{% endcontent-ref %}

## Bitrate

영상의 품질과 네트워크 환경에 영향을 받습니다. maxBitrate 로 조절이 가능합니다. 지나치게 낮은 maxBitrate는 인코더에 추가적인 부하로인해 품질을 저하할 수 있습니다.

{% content-ref url="/pages/-LGUX\_W-t9BCj3vjDn1A" %}
[Config/RemonConfig](/common/config)
{% endcontent-ref %}

코덱별로 적합한 bitrate가 다릅니다. 아래를 참고하세요

### 일반

{% embed url="<https://help.netflix.com/ko/node/306>" %}

{% embed url="<https://support.google.com/youtube/answer/2853702?hl=en>" %}

### VP9

{% embed url="<https://developers.google.com/media/vp9/settings/vod/>" %}

### h.264

{% embed url="<https://bitmovin.com/video-bitrate-streaming-hls-dash/>" %}

## Simulcast

여러 품질의 영상을 제공하고 수신자가 품질을 선택할 수 있도록 합니다.

{% content-ref url="/pages/-LRQY\_hgb3oEPjWMz4Za" %}
[Simulcast](/common/livecast-simulcast)
{% endcontent-ref %}

## Hardware Acceleration

단말에서 인코딩, 디코딩의 하드웨어 가속여부는 사용성과 영상표현의 한계에 많은 영향을 미칩니다. 사용하는 코덱이 하드웨어 가속을 받으면 인코딩, 디코딩시 보다 여유로운 사용자 경험을 제공하거나, 4K 60FPS와 같은 더 높은 품질을 제공할 수 있습니다.

코덱의 하드웨어 가속은 칩셋, 플렛폼, OS, OS Version, 해상도등 원하는 미디어의 품질, 인코딩/디코딩여부등 다양한 상황에 연관되어 있으며, 목표로 하는 단말에서 결과적으로는 직접 확인하는것이 좋습니다.

방송/통신에서 송출측의 경우 FPS, Bitrate를 변경할 시 인코더가 하드웨어 가속을 받는다면, 원하는 형태의 환경을 구성할 가능성이 더 높습니다. 예를 들어 Full HD입력신호를 1000kbps와같이 낮은 대역폭으로 제공한다던지, 60FPS의 입력신호를 모바일환경을 고려하여 24FPS로 낮추는 것과 같은 입력신호를 변형하는 인코딩 상황에서 하드웨어 가속은 큰 도움이 됩니다.

아래는 환경을 확인할수 있는 방법을 안내합니다.

### Web

#### Chrome

주소표시줄에 아래를 입력함으로써 내용을 살펴 볼 수 있습니다.

* `chrome://gpu` : 코덱에 대한 하드웨어 인코딩/디코딩 여부 확인
* `chrome://media-internals` : 지원가능한 해상도/FPS등 미디어 환경

#### Firefox

주소표시줄에 `about:support` 입력하여 코덱에 대한 하드웨어 인코딩/디코딩 여부를 확인 가능합니다.

### Android

아래의 `MediaCodecList` API를 통해 확인이 가능합니다. 이중 `OMX.google.` 로시작되는것은 소프트웨어 코덱으로 이를 제외한 값들이 하드웨어로 지원되는 코덱입니다.

{% embed url="<https://developer.android.com/reference/android/media/MediaCodecList>" %}

### iOS

아래의 VideoToolbox API를 통해 확인이 가능합니다. h264 코덱만 지원됩니다.

{% embed url="<https://developer.apple.com/documentation/videotoolbox>" %}

### References

#### Intel

{% embed url="<https://github.com/intel/media-driver>" %}

{% embed url="<https://en.wikipedia.org/wiki/Intel_Quick_Sync_Video>" %}

#### Nvidia

{% embed url="<https://developer.nvidia.com/video-encode-decode-gpu-support-matrix>" %}

#### AMD

{% embed url="<https://en.wikipedia.org/wiki/Video_Coding_Engine>" %}

{% embed url="<https://en.wikipedia.org/wiki/Video_Core_Next>" %}

#### Encoders

{% embed url="<https://trac.ffmpeg.org/wiki/HWAccelIntro>" %}

#### Browser

{% embed url="<https://caniuse.com/#feat=webm>" %}

#### Codecs

{% embed url="<https://en.wikipedia.org/wiki/VP9>" %}

#### Articles

{% embed url="<https://bloggeek.me/webrtc-h264-video-codec-hardware-support/>" %}


# Simulcast

Simulcast는 비디오를 다양한 화질로 송신하고, 네트워크 상황에 따라 적절한 화질을 선택하여 수신하는 기능입니다.

자세한 설명은 아래 WebRTC Glossary를 참고해주십시오.

{% embed url="<https://webrtcglossary.com/simulcast/>" %}

## 지원범위

방송과 그룹통화만 simulcast를 지원합니다. 1:1 통화는 simulcast를 지원하지 않습니다.\
Simulcast는 비디오만 관여하고, 오디오는 관여하지 않습니다.

| 플랫폼          | 지원                 |
| ------------ | ------------------ |
| Web - 송출     | O                  |
| Web - 수신     | O                  |
| Android - 송출 | O (SDK v2.6.3 이상)  |
| Android - 수신 | O                  |
| iOS - 송출     | O (SDK v2.6.15 이상) |
| iOS - 수신     | O                  |

아래와 같이 코덱을 지원합니다. 권장하는 코덱은 VP8 입니다.

| 코덱    | 지원여부 |
| ----- | ---- |
| VP8   | O    |
| VP9   | X    |
| AV1   | X    |
| H.264 | O    |

웹 브라우저는 최신 Chrome, Firefox를 지원합니다. WebRTC를 지원하는 여타 웹 브라우저에서 simulcast가 동작할 수 있습니다.

## 규격

송출 시 선택할 수 있는 해상도는 Capture resolution 컬럼의 값을 참고하십시오.

수신 시 선택되는 해상도는 송출 해상도와 layer 선택(HIGH, MEDIUM, LOW)에 따라 정해집니다. Layer 선택은 아래 "수신" 섹션에서 설명합니다.

| **Capture resolution** | LOW           | MEDIUM   | HIGH      |
| ---------------------- | ------------- | -------- | --------- |
| 1920x1080              | 320x180       | 640x360  | 1920x1080 |
| 1280x720               | 320x180       | 640x360  | 1280x720  |
| 960x540                | under 320x180 | 480x270  | 960x540   |
| 640x360                | under 320x180 | 640x360  | disabled  |
| 480x270                | under 320x180 | 480x270  | disabled  |
| 320x180                | 320x180       | disabled | disabled  |

####

Simulcast 내부에 대해 자세히 알고 싶으면, 아래 소스코드를 참고하시기 바랍니다.

```cpp
struct SimulcastFormat {
  int width;
  int height;
  // The maximum number of simulcast layers can be used for
  // resolutions at |widthxheigh|.
  size_t max_layers;
  // The maximum bitrate for encoding stream at |widthxheight|, when we are
  // not sending the next higher spatial stream.
  int max_bitrate_kbps;
  // The target bitrate for encoding stream at |widthxheight|, when this layer
  // is not the highest layer (i.e., when we are sending another higher spatial
  // stream).
  int target_bitrate_kbps;
  // The minimum bitrate needed for encoding stream at |widthxheight|.
  int min_bitrate_kbps;
};
// These tables describe from which resolution we can use how many
// simulcast layers at what bitrates (maximum, target, and minimum).
// Important!! Keep this table from high resolution to low resolution.
// clang-format off
const SimulcastFormat kSimulcastFormats[] = {
  {1920, 1080, 3, 5000, 4000, 800},
  {1280, 720, 3,  2500, 2500, 600},
  {960, 540, 3, 900, 900, 450},
  {640, 360, 2, 700, 500, 150},
  {480, 270, 2, 450, 350, 150},
  {320, 180, 1, 200, 150, 30},
  {0, 0, 1, 200, 150, 30}
};
```

#### Chrome의 simulcast 구현

{% embed url="<https://chromium.googlesource.com/external/webrtc/+/master/media/engine/simulcast.cc>" %}

#### Firefox의 simulcast 구현

{% embed url="<https://hg.mozilla.org/mozilla-central/file/default/media/webrtc/trunk/webrtc/media/engine/simulcast.cc>" %}

## 송출 (beta)

송출 시 아래와 같이 `simulcast: true`설정을 적용합니다.&#x20;

{% tabs %}
{% tab title="Web" %}

```javascript
const config = {
  rtc: {
    simulcast: true
  },
  media : {
    video : {
      width: 1280,
      height: 720
    }
  }
}

const remon = new Remon({ config })
```

{% endtab %}

{% tab title="Android" %}

```java
// sdk v2.6.3 부터 지원
// RemonCast
RemonCast.builder()
    .context( android_context )
    .videoCodec( "VP8" )
    .videoWidth( 1920 )
    .videoHeight( 1080 )
    .simulcast( true )
    .build();

// RemonConference
var remonConferece = RemonConference()
remonConference.create {
    it.context( android_context )
    .videoWidth( 1920 )
    .videoHeight( 1080 )
    .videoCodec( "VP8" )
    .simulcast( true )
}.then{
}.close{
}
```

{% endtab %}

{% tab title="iOS - Swift" %}

```swift
// sdk v2.6.15 부터 지원
// RemonCast
let remonCast = RemonCast()
remonCast.videoWidth = 1920
remonCast.videoHeight = 1080
remonCast.videoCodec = "VP8"
remonCast.simulcast = true

// RemonConference
var remonConference = RemonConference()
remonConference.create{ participant in
    participant.videoWidth = 1920
    participant.videoHeight = 1080
    participant.videoCodec = "VP8"
    participant.simulcast = true
}.then{ channel in
}.close{
}.error{ error in
}
    
```

{% endtab %}

{% tab title="iOS - ObjC" %}

```objectivec
// sdk v2.6.15 부터 지원
RemonCast *remonCast = [RemonCast new];
remonCast.videoWidth = 1920;
remonCast.videoHeight = 1080;
remonCast.videoCodec = "VP8";
remonCast.simulcast = true;
```

{% endtab %}
{% endtabs %}

Web SDK는 fps를 낮추어 정해진 대역폭에서 움직임을 떨어뜨리고 고화질의 이미지를 보여주거나 maxBandwidth를 낮추어 저화질의 이미지를 보여주는 등의 최적화를 시도할 수 있습니다. 이런 설정은 추가적인 인코더의 연산을 일으키므로 가급적 변경하지 않는 것이 좋습니다. 필요하다면 입력장치의 설정을 변경하는 것이 좋습니다. \
\
Android, iOS SDK는 fps, maxBandwidth 등 세부적인 조절은 불가능하며, 내부에 설정된 기준대로만 화질이 선택됩니다.&#x20;

Simulcast를 적용한 송출은 모바일 기기의 CPU, 배터리 사용량을 다소 높이는 점에 유의하십시오.

## 수신 (beta)

수신 시 아래와 같이 Simulcast layer를 선택합니다. 선택지는 HIGH, MEDIUM, LOW입니다.

{% tabs %}
{% tab title="Web" %}

```javascript
const remon = new Remon()

remon.setVideoQulity('HIGH')
remon.setVideoQulity('LOW')
```

{% endtab %}

{% tab title="Android" %}

```java
// RemonCast
remonCast.switchSimulcastLayer( "HIGH" ); // "HIGH", "MEDIUM", "LOW"

// RemonConference
var participant = remonConference.getClient(1) as RemonParticipant
participant.switchSimulcastLayer( "LOW" )
```

{% endtab %}

{% tab title="iOS - Swift" %}

```swift
// RemonCast
let remonCast = RemonCast()
remonCast.switchSimulcastLayer(bandwidth:.HIGH) // .HIGH || .MEDIUM || .LOW 

// RemonConference
let participant = remonConference.getClient(index: 1 )
participant?.switchSimulcastLayer(bandwidth:.LOW)
```

{% endtab %}

{% tab title="iOS - ObjC" %}

```objectivec
// 2.4.21 부터 지원
RemonCast *remonCast = [RemonCast new];

// .HIGH || .MEDIUM || .LOW
[remonCast switchSimulcastLayerWithBandwidth:objc_RemonBandwidth.HIGH];
```

{% endtab %}
{% endtabs %}

SDK는 fps, 비디오 특성 등을 참고하여 자동으로 낮은 화질로 변경합니다. 자동으로 높은 화질로 변경하지는 않습니다. 높은 화질로 변경은 앱의 특성에 따라 개발사가 구현하시길 권합니다.&#x20;


# Record - beta

## Overview

방송, 통신간 영상을 녹화, 녹음 하는 기능을 제공하고 있습니다.

### 지원범위

|                       | Web | Android | iOS | Server |
| --------------------- | --- | ------- | --- | ------ |
| Communication - Video | X   | X       | X   | X      |
| Communication - Audio | O   | O       | O   | X      |
| Livecast - Video      | X   | X       | X   | O      |
| Livecast - Audio      | X   | O       | O   | O      |

### Client (Beta) - 방송/통신

안드로이드, iOS의 경우 녹음기능이 제공됩니다. 화면 녹화는 플렛폼에서 제공하는 전용 API를 사용할 수 있습니다. 녹음은 크게 다음과 같은 단계로 진행됩니다.

* 녹음 형식 설정
* 원본 소리 저장 (dump)
* 인코딩 (unpack)

녹음 기능의 특성상 원본을 로컬 저장소에 저장하게 되며 일정부분 저장소 용량을 차지하게 됩니다. 모든 저장이 완료 되면, 이를 압축하는 인코딩 과정이 필요하며, 이때는 CPU자원을 소모하게됩니다. 기기에 따라 하드웨어 가속등을 받지 못할 수도 있으며 클라이언트 자원을 소모함으로 필요한 경우에만 사용될 것을 권장합니다.

{% tabs %}
{% tab title="Web" %}
WebSDK의 경우 현재 녹음만을 지원합니다. 다음과 같이 config에 record관련 속성을 추가합니다.

```
config = {credential, view,
    media: { audio:true, video:true,
        record: true,
        recordUrl: "https://blahblah.com/record/url"
        // recordUrl이 없을 경우 기본 리몬서버로 전송됨
        // recordUrl이 'local'인 경우 녹음만 하고 전송하지 않음.
    }
}
```

record 속성은 record를 enable하는 속성입니다. 만약 record만 true로 할경우 remotemonster의 별도의 서버에 녹음파일이 저장됩니다. recordUrl을 설정하면 당신이 원하는 서버로 녹음파일이 전달됩니다. SDK에서는 녹음 과정에 대하여 다음과 같은 이벤트를 제공합니다. 녹음파일은 webm 형식으로 전달됩니다.

```
remon.onRecordEvent(event) {
    switch(event.event){
        case "stop: // 녹음이 종료되었을 때 발생합니다.
            console.log(event.id, event.size, event.file);
            break;
        case "upload": // 녹음 후 recordUrl로 전송이 시작할 때 발생합니다.
            console.log(event.id, event.size);
            break;
        case "progress": // 전송 중 파일이 큰 경우 업로드 도중에 호출될 수 있습니다.
            console.log(event.id, event.size);
            break;
        case "uploaded":// 전송이 완료되었을 때 발생합니다.
            console.log(event.id, event.size);
            break;
        case "error": //전송중 에러가 발생한 경우 발생합니다.
            console.log(event.id, event.size, error);
            break;
        }
    }
}
```

{% endtab %}

{% tab title="Android" %}
Remon SDK는 녹음만 지원됩니다. 영상의 녹화는 안드로이드의 `Mediaprojection API`를 사용하시면 됩니다. 녹음기능은 다음과 같이 사용 하시면 됩니다.

```java
remonCall = RemonCall.builder()
        .context(CallActivity.this)
        .localView(surfRendererLocal)
        .remoteView(surfRendererRemote)
        .serviceId(remonApplication.getConfig().getServiceId())
        .key(remonApplication.getConfig().getKey())
        .saveInputAudioToFile(true)                // 녹음기능 사용
        .fileSizeLimitBytes(300000000)             // 생성할 녹음파일의 최대사이즈
        .aecDumpFilePath(Environment.getExternalStorageDirectory().getPath() + File.separator + "Download/aecdump") // 파일 저장위
        .build();
```

�Builder에서 `saveInputAudioToFile`, `fileSizeLimitBytes`, `aecDumpFilePath` 등의 값을 지정해주시면 됩니다.&#x20;

녹음 파일은 별도의 포맷으로 저장 되므로,  Unpack 작업이 필요합니다.&#x20;

```java
String source = Environment.getExternalStorageDirectory().getPath() + File.separator + "Download/aecdump";
String result = Environment.getExternalStorageDirectory().getPath() + File.separator + "Download/record.wav";
UnpackAecdump unpackAecdump = new UnpackAecdump();
try {
    unpackAecdump.run(RouterActivity.this, source , result, state -> {
        switch (state) {
            case UNPACK_DONE:
                break;
            case WRITE_DONE:
                runOnUiThread(() -> Toast.makeText(RouterActivity.this, "녹음파일 저장 완료.", Toast.LENGTH_SHORT).show());
                break;
            case ERROR:
                runOnUiThread(() -> Toast.makeText(RouterActivity.this, "잘못된 녹음 파일입니다.", Toast.LENGTH_SHORT).show());
                break;
        }
    });
} catch (Exception e) {
    e.printStackTrace();
}
```

�UnpackAecDump의 run()함수를 통해 Upack작업을 수행할 수 있으며, 인자는 다음과 같습니다 run(`context`, `dump파일위치`, `음성파일저장위치`, `OnRecordAudioStatusListener`)&#x20;

`OnRecordAudioStatusListener` 의 state로 진행과정을 확인할 수 있습니다. UNPACK\_DONE은 upack완료, WRITE\_DONE은 wav파일 생성 완료 입니다.
{% endtab %}

{% tab title="iOS - Swift" %}

```swift
remonCall.onComplete { () in            
    //덤프 기록 시작    
    self.remonCall.startDump(
        withFileName: "audio.aecdump", maxSizeInBytes: 100 * 1024 * 1024) //100mb
}

remonCall.onClose {
    //덤프 기록 중지
    self.remonCall.stopDump()
    
    // m4a 형식으로 변환을 원하는 경우 변환작업 시작
    // 이 과정은 많은 시간이 소요 됩니다.
    RemonClient.unpackAecDump( dumpName: "audio.aecdump", 
                               resultFileName: "unpack.m4a", 
                               progress: {
        (error, state) in
        if let error = error {
            // 에러가 발생 하였습니다. 프로그래스 종료.
        } else {
            // 디코딩 상태 
            /*
            case START = 0
            case WARNING
            case ERROR // 이 경우 error가 존재 합니다. 프로그래스 종료.
            case WROTE
            case COMPLTE  //프로그래스 종료.
            */
        }
    })
}
```

{% endtab %}

{% tab title="iOS - ObjC" %}

```objectivec
[self.remonCall onCompleteWithBlock:^{
    //덤프 기록 시작
    [self.remonCall startDumpWithFileName:
        @"audio.aecdump" maxSizeInBytes:100 * 1024];
}];

[self.remonCall onCloseWithBlock:^{
    //덤프 기록 중지
    [self.remonCall stopDump];
    
    // m4a 형식으로 변환을 원하는 경우 변환작업 시작
    // 이 과정은 많은 시간이 소요 됩니다.
    [RemonClient unpackAecDumpWithDumpName:
        @"audio.aecdump" resultFileName:@"unpack.m4a" progress:
        ^(NSError * _Nullable erro, enum REMON_AECUNPACK_STATE state) {
            // 디코딩 상태
        }
    ];
}];
```

{% endtab %}
{% endtabs %}

이외에도 각 플렛폼은 자체적으로 녹화가능한 기능을 제공하고 있습니다. 아래를 참고하여 클라이언트에서 독자적으로 녹화 기능을 제작할수 있습니다. 아래 서버녹화와 다르게 이기능을 활용하면 사용자가 보는 모든 화면을 녹화할 수 있습니다.

#### Web Media Element Recording API

{% embed url="<https://developer.mozilla.org/en-US/docs/Web/API/MediaStream_Recording_API/Recording_a_media_element>" %}

#### Android Screen Recording API

{% embed url="<https://developer.android.com/reference/android/media/projection/MediaProjection>" %}

#### iOS Screen Recording API

{% embed url="<https://developer.apple.com/documentation/replaykit>" %}

### Server (Beta) - 방송

방송기능 사용시 서버를 통해 영상 녹화 기능이 제공됩니다. 이를 통해 녹화를 하면, 최종 사용자가 보는 UI가 조합된 화면이 아닌, 송출하는 원본영상을 녹화 받을 수 있습니다. 방송이 진행되면 서버에서 녹화가 되며 방송이 종료되어 녹화가 완료된 순간, 사용자가 지정한 주소로 Webhook이 호출됩니다. 이 전문안의 규격을 갖고 사용자는 영상을 다운로드 받을 수 있습니다. 영상은 6시간 후 리모트몬스터 서버에서 삭제됩니다.

서버를 통한 영상녹화 기능은 현재 베타기능이며 향후 별도의 과금체계와 함께 공식지원될 예정입니다.

#### Setting Webhook URL

REST POST가 호출될 URL 주소를 Admin API를 통해 설정 합니다. 아래를 참고하여 설정을 합니다.

{% content-ref url="/pages/-LSroGK9ChQm81zu59TM" %}
[Broken mention](broken://pages/-LSroGK9ChQm81zu59TM)
{% endcontent-ref %}

리모트몬스터에서 해당 설정이 완료되면 지정된 주소로 POST가 호출됩니다. 자세한 내용은 아래를 참고하세요.

{% content-ref url="/pages/-LT1pFmvsAnDm2XZyTMU" %}
[Webhook API - Livecast - beta](/api/webhook-livecast)
{% endcontent-ref %}

thumbnail를 얻길 원하면 아래와 같은 외부 API 등을 검토해 보세요.

{% embed url="<https://cloud.google.com/video-intelligence/>" %}

{% embed url="<https://aws.amazon.com/ko/rekognition/>" %}

{% embed url="<https://azure.microsoft.com/ko-kr/services/media-services/video-indexer/>" %}

#### Downloading Media

제공받은 URL에 접근함으로써 녹화된 영상을 다운로드 받을 수있습니다. 영상은 정상적으로  Webhook이 호출 된 이후 6시간 후 삭제되며 그 이전에는 언제든지 다운로드가 가능합니다.

간단하게 아래와 같이 `curl`, `wget`으로 테스트가 가능하며 이 기능을 서비스의 서버에서 구현하여 사용할 수 있습니다.

```
curl -O https://s3.amazonaws.com/__SERVICEID__/__RAND__/__CHANNELID__.mp4
wget https://s3.amazonaws.com/__SERVICEID__/__RAND__/__CHANNELID__.mp4
```

#### Simple Webhook Server - by hooka

간단하게 서버를 만들어서 웹훅을 확인하고 녹화를 다운로드 할 수 있습니다. 아래와 같이 docker 컨테이너와 간단한 설정을 통해 다운로드 서버를 구축 가능합니다.

```bash
$ docker run -v ./webhooks.json:/src/webhooks.json -p 3000:3000 danistefanovic/hooka
```

{% code title="webhooks.json" %}

```javascript
[
    {
        "method": "POST",
        "path": "/MYSLUG",
        "command": "wget $URL;",
        "parseJson": [
            {
                "query": "payload.id",
                "variable": "ID"
            },
            {
                "query": "payload.url",
                "variable": "URL"
            }
        ]
    }
]
```

{% endcode %}

보다 자세한 사용법은 아래를 확인하세요.

{% embed url="<https://github.com/danistefanovic/hooka>" %}

#### Simple Webhook Server - by nodejs

혹은 아래와 같이 간단한 nodejs 서버 프로그래밍으로 웹훅을 수신 가능합니다.

```javascript
const express = require('express')
const bodyParser = require('body-parser')
const app = express()

app.use(bodyParser.urlencoded())
app.use(bodyParser.json())

app.post('/__MYSLUG__', (req, res) => {
    console.log(JSON.stringify(req.body))
    res.end()
})

const server = app.listen(3000, '127.0.0.1', () => {
    const host = server.address().address
    const port = server.address().port
    console.log("Example app listening at http://%s:%s", host, port)
})
```


# Error

## Overview

Error는 넓은 영역에서의 예외상황을 말합니다. `onError` 콜백 메소드를 통해서 Error를 받게 되며 아래와 같이 다양한 상황이 있습니다.\
&#x20;Android 의 경우 RemonException 객체에 RemonErrorCode 와 메시지가 전달되며, iOS 의 경우 RemonError 로 정의된 enum 타입으로 에러 코드가 전달됩니다.&#x20;

RemonErrorCode(Android) / RemonError (iOS)

#### invalidParameterError / InvalidParameterError

* `new Remon`시 인자가 잘못될 경우
  * `config`의 Key, Service Id, Local View, Remote View 혹은 `config`나 Callback자체가 없는 경우이거나 너무 길이가 큰 경우
* `connectChannel`시에 잘못된 값(길이가 1이하이거나 필요이상으로 너무 큰 경우 100이상)
* `UnsupportedPlatformError`
  * Browser가 지원하지 않는 경우
  * Version이 지원하지 않는 경우

#### initError / RestInitError

* RESTful API 반환시 에러가 난 경우
  * 500 Error
* 시그널링 서버가 죽어있는 경우
  * 웹 서버는 살아있으므로 웹 서버가 잘못된 페이지를 전달함
* 웹 서버가 죽어있는 경우
  * 400 Error
* Web Socket, RESTful 호스트가 문제가 있는 경우
* Web Socket 시작중에 에러가 난 경우

#### wsError / WebSocketError

* Websocket 통신 중 발생한 에러
* Send Error
* Receive Error

#### connectError / ConnectChannelFailed

* `create`/`connect`의 반환에 `channel`정보가 없는 경우
* `channel`이 만료되거나 `channel`이 없는데 `connect`하는 경우는 알아서 서버가 `onCreateChannel`로 변화시켜버림

#### mediaError / UserMediaDeviceError

* Media 특히 Camera를 못가져온 경우(Video를 On했음에도 불구하고))
* Video Capture를 못가져온 경우

#### iceError / IceFailed

* `peerConnection` 생성 안될때
* SDP가 이미 있는데 또 자기것이 생성된 경우
* ICE, SDP가 파싱이 안되거나 추가가 안되는 경우

#### networkChange

* 네트워크 변경에 의한 에러

#### unknown / Unknown

* 서버에서 전달한 에러 혹은 알수 없는 에

## 반드시 종료해야할 오류코드 <a href="#errorcode_must" id="errorcode_must"></a>

onError 콜백 함수 호출 시 인자로 오류코드와 설명이 주어집니다. 1:1통화와 방송시청 시 아래 오류코드를 받았을 경우는 접속이 원활하지 않아 접속을 종료해야하는 상황입니다. 아래 오류코드를 확인하게되면 close 메소드를 호출하여 접속을 종료해주십시오. \
(\* Android, iOS의 경우 onError 콜백 호출 후 자동으로 접속이 종료되고, onClose 콜백이 호출됩니다.)\
\
일부 오류코드는 자동으로 close 메소드가 호출됩니다. \
Web, Android, iOS 공통으로 사용되는 코드이며, 플랫폼에 따라 일부 차이가 있습니다.\
그룹통화 시 아래 오류코드는 의미가 다릅니다. 그룹통화를 이용하시는 경우 이메일로 문의 바랍니다.

4101 : Service ID, Secret Key가 없음\
4102 : (안드로이드) context 오류

4181, 4182 : 카메라 또는 마이크를 이용할 수 없음\
4161 : (안드로이드) 비디오 캡처 오류\
4162 : (안드로이드) 오디오 레코더 오류\
4163 : (안드로이드) 오디오 트랙 오류

4201 : 리모트몬스터와 인증이 올바르게 이루어지지 않음 (클라이언트의 이유)\
4203 : 서버 초기화 오류\
4204 : 리모트몬스터와 인증이 올바르게 이루어지지 않음 (서버의 이유)\
4205 : 서버 초기화시 잘못된 데이터 오류

4211 : 디바이스가 offline 임

4230 : 피어 생성 실패 \
4231 : 로컬피어 정보 획득 실패\
4232 : 로컬피어 정보 설정 실패\
4244 : 원격피어 정보 설정 실패\
4245 : 미디어,접속 협상 실패\
4246 : 피어 접속 연결 실패

4241 : 네트워크 환경이 매우 좋지 않아 리모트몬스터 서버로 접속이 이루어지지 않음 또는 끊어짐\
4341 : 네트워크 환경이 불안정함\
4342 : 네트워크 환경이 불안정함\
4343 : 네트워크 환경이 불안정함\
\
\
\
2000번대 오류는 서버에서 전달하는 오류입니다. \
2010 : (통화에서) 이미 해당 채널에서 통화가 진행되고 있음\
2370 : 계약하지 않은 서비스 아이디로서, 테스트 시간 3분을 초과하여 종료됨

## 종료 하지 않아도 되는 오류코드

onError 콜백 함수 호출 시 인자로 오류코드와 설명이 주어집니다. 아래 오류코드를 받았을 경우는 통화, 방송, 시청에 잠재적으로 영향을 줄 수 있는 환경의 변화을 알려줍니다. 접속을 종료하지 않아도 안전합니다.\
Web, Android, iOS 공통으로 사용되는 코드입니다.\
그룹통화 시 아래 오류코드는 의미가 다릅니다. 그룹통화를 이용하시는 경우 이메일로 문의 바랍니다.

4344 : 네트워크 환경의 변화

4345 : 네트워크 환경의 변화


# About WebRTC

## 기초

* [WebRTC 시작하기](https://coiiee.com/blog.php?idx=2): Coiiee 대표 정유철님께서 작성하신 알기쉬운 WebRTC 자료
* [HTML5Rocks의 WebRTC가이드](https://www.html5rocks.com/ko/tutorials/webrtc/basics/): WebRTC개발을 처음 시작할 때 개념을 잡기 좋습니다.

## 주요 블로그 및 커뮤니티

* [RemoteMonster Blog](http://blog.remotemonster.com): 리모트몬스터가 운영하는 WebRTC 블로그
* [WebRTC 한국 커뮤니티](https://www.facebook.com/groups/rtc.korea/): 한국에서 가장 활발한 WebRTC 커뮤니티

## 최신 트렌드 및 시장조사

* [WebRTC의 현재와 미래](https://blog.remotemonster.com/webrtc-현재와-미래-8858579a4264): 2016년 WebRTC의 트렌드를 정리한 문서
* [WebRTC 시장동향 리포트](https://blog.remotemonster.com/webrtc-시장동향-리포트-f880a45a3bce): 국내외 WebRTC 시장 동향 리포트

## 표준

* [WebRTC org](https://webrtc.org/): WebRTC 프로젝트를 대표하는 사이트입니다. 여기에서 native 엔진 관련정보를 얻을 수 있습니다.
* [WebRTC 표준](https://www.w3.org/TR/webrtc/): WebRTC 표준을 이끌고 있는 W3C의 페이지입니다.
* [IETF rtcweb WG](https://tools.ietf.org/wg/rtcweb/): WebRTC의 통신 프로토콜이나 코덱등의 표준을 이끌고 있는 IETF의 페이지입니다.

## 해외 사이트

* [WebRTC github](https://github.com/webrtc): WebRTC 소스코드 저장소
* [CodeLab](https://bitbucket.org/webrtc/codelab): WebRTC를 코드로 따라하며 배우기
* [모질라 WebRTC API doc](https://developer.mozilla.org/ko/docs/Web/API/WebRTC_API): 모질라에서 제공하는 WebRTC API 문서. 가장 정확하고 최신의 표준에 입각한 API를 확인할 수 있음.
* [Bloggeek.me](https://bloggeek.me/): WebRTC에서 가장 유명한 블로거 Tsahi의 블로그
* [WebRTCHacks](https://webrtchacks.com/): WebRTC계의 가장 유명한 해커 Chad Hart가 운영하는 사이트. 그 외 Victor나 Philipp등도 같이 운영
* [WebRTC Weekly](https://webrtcweekly.com/): WebRTC의 최신 트렌드를 주단위로 받아볼 수 있음

## 주요 라이브러리 및 개발 정보

* [adapter.js](https://github.com/webrtc/adapter): 표준화가 덜된 관계로 브라우저마다 API조금씩 다른 WebRTC. 그래서 adapter.js를 사용해야 그 호환성을 지킬 수 있습니다. 다만 최근 1.0이 지정되면서 필요성이 많이 감소하였고 더이상 유지 보수가 되지 않을 예정입니다.


# FAQ

## Android, iOS용 샘플 앱 빌드가 안 됩니다.

빌드 환경의 편차가 커서, 환경에 따라 빌드가 안 되는 경우가 간혹 있습니다. [리모트몬스터 커뮤니티](https://community.remotemonster.com/)에 오류 메시지와 함께 질문을 남겨주십시오.

## 방송/통화 시작 전에 카메라를 선택하고 싶습니다.

채널에 접속하기 전에 카메라를 선택하면 됩니다. 아래 예제를 참고하시기 바랍니다.

### Web

<https://sample.remotemonster.com/videoConfig.html>

<https://sample.remotemonster.com/videoConfigForCast.html>

### Android

RemonCall, RemonCast 객체를 생성할 때 아래와 같이 isFirstFrontFacing() 메소드를 이용하여 카메라를 선택합니다. 객체를 생성한 후에는 switchCamera() 메소드를 이용해야합니다.

```
RemonCall.Builder().isFirstFrontFacing(true);
RemonCast.Builder().isFirstFrontFacing(true);
```

### iOS

RemonCall, RemonCast 객체의 frontCamera 속성을 변경합니다.

```
remonCall.frontCamera = true
remonCast.frontCamera = true
```

## 방송/통화 중에 카메라를 전환하고 있습니다. 어떻게 하나요?

RemonCast, RemonCall의 인스턴스 메소드 중 switchCamera 가 있습니다. 이 메소드를 이용하면 카메라를 전환할 수 있습니다.\
아래 문서에서 swtichCamera 메소드를 확인해주십시오.

### [Web용 SDK](https://remotemonster.github.io/web-sdk/docs/Remon.html)

#### [iOS용 SDK](https://remotemonster.github.io/ios-sdk/Classes/RemonClient.html#/c:@CM@RemoteMonster@objc%28cs%29RemonClient%28im%29switchCameraWithIsMirror:isToggle:)

#### [Android용 SDK](https://remotemonster.github.io/android-sdk/index.html?com/remotemonster/sdk/Remon.html)

## 시청자 쪽에서 채널에 접속이 안 됩니다.

방송자 쪽에서 채널을 생성한 후에 시청자 쪽에서 채널에 접속할 수 있습니다. 시청자 쪽에서 먼저 채널에 접속하려고 시도했는지 확인 바랍니다.

위 문제가 아니라면 [리모트몬스터 커뮤니티](https://community.remotemonster.com/)에 오류 메시지와 함께 질문을 남겨주십시오.

## 통화 테스트 시 소리 울림이 심합니다. 왜 그런가요?

통화에 사용한 두 디바이스가 가까이에 있으면 울림이 발생합니다. 흔히 하울링이라고 합니다. 두 디바이스를 멀리 두고 테스트 해보시기 바랍니다.

통화 시 음성의 문제는 다양한 원인에 의해 발생할 수 있습니다. [리모트몬스터 커뮤니티](https://community.remotemonster.com/)에 상황을 설명해주시면 원인을 찾는데 도움이 됩니다.

## 웹에서, 오디오만 쓸 건데 video 태그를 써야하나요?

오디오만 쓸 때엔 video 태그 말고 audio 태그를 사용해주십시오. 아래 샘플 앱을 참고해주시기 바랍니다.

<https://sample.remotemonster.com/voiceCall.html>

<https://sample.remotemonster.com/voiceCast.html>


# 그룹통화 만들기(Android)

## 그룹통화란?

다수의 참여자가 통화에 참여하는 서비스를 위한 기능입니다. 참여자는 앱을 이용하는 나와 그 외 참여자로 구분할 수 있습니다. 아래에서는 나와 참여자로 줄여서 표시합니다. 한 회기의 그룹통화는 RemonConference 클래스의 인스턴스로 대표됩니다. 나는 통화 연결, 참여자들의 입장/퇴장 알림 등 대부분의 일을 RemonConference 객체에게 위임합니다.

## RemonConference

안드로이드 SDK 버전 v2.7.0 이상\
그룹통화를 위해 RemonConference 객체를 생성하고, 설정을 진행합니다.

RemonConference 클래스는 그룹통화를 위해 아래 메소드를 제공합니다.

```kotlin
create( String roomName, Config config, OnEventCallback callback);
leave()
```

RemonConference 클래스는 콜백으로 사용하기 위해 아래 메소드를 제공합니다. 이하 콜백용 메소드라고 합니다. 콜백용 메소드는 위에서 언급한 메소드의 콜백으로만 호출하며, 일반적인 메소드처럼 호출하지 않습니다.

```kotlin
// 룸의 콜백용 메소드
on( "onRoomCreate" ) { participant:RemonParticipant ->
}.on( "onUserJoined" ) { participant:RemonParticipant ->
}.on( "onUserStreamConnected" ) { participant:RemonParticipant ->
}.on( "onUserLeaved" ) { participant:RemonParticipant ->
}.close {
}.error { error:RemonException ->
}

// participant 콜백용 메소드
.on( "onComplete" ) { participant:RemonParticipant ->
}
```

## 레이아웃 작업

그룹통화 화면을 나의 영상 한 개와 그룹 참여자의 영상 여러 개로 구성합니다. 레이아웃에 영상을 표시할 view를 만들고 인덱스를 지정하여 참여자의 영상을 원하는 위치에 표시할 수 있도록 합니다.

```markup
<layout>
<RelativeLayout
    android:id="@+id/rootLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#000">


    <androidx.constraintlayout.widget.ConstraintLayout
        android:id="@+id/constraintLayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <!-- Local -->
        <RelativeLayout
            android:id="@+id/layout0"
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:layout_margin="10dp"
            android:background="@drawable/view_shape"
            app:layout_constraintDimensionRatio="H,1:1.33"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
           >

            <org.webrtc.SurfaceViewRenderer
                android:id="@+id/surfRendererLocal"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:visibility="visible"
                />

        </RelativeLayout>



        <!-- Remote 1 -->
        <FrameLayout
            android:id="@+id/layout1"
            android:layout_width="80dp"
            android:layout_height="0dp"
            android:layout_margin="18dp"
            app:layout_constraintDimensionRatio="H,1:1.33"
            app:layout_constraintVertical_bias="0.1"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            >


            <org.webrtc.SurfaceViewRenderer
                android:id="@+id/surfRendererRemote1"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:visibility="invisible"
                />
        </FrameLayout>


        <!-- Remote 2 -->
        <FrameLayout
            android:id="@+id/layout2"
            android:layout_width="80dp"
            android:layout_height="0dp"
            android:layout_margin="18dp"
            app:layout_constraintDimensionRatio="H,1:1.33"
            app:layout_constraintVertical_bias="0.3"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            >


            <org.webrtc.SurfaceViewRenderer
                android:id="@+id/surfRendererRemote2"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:visibility="invisible"
                />

        </FrameLayout>


        <!-- Remote 3 -->
        <FrameLayout
            android:id="@+id/layout3"
            android:layout_width="80dp"
            android:layout_height="0dp"
            android:layout_margin="18dp"
            app:layout_constraintDimensionRatio="H,1:1.33"
            app:layout_constraintVertical_bias="0.5"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            >


            <org.webrtc.SurfaceViewRenderer
                android:id="@+id/surfRendererRemote3"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:visibility="invisible"
                />

        </FrameLayout>


        <!-- Remote 4 -->
        <FrameLayout
            android:id="@+id/layout4"
            android:layout_width="80dp"
            android:layout_height="0dp"
            android:layout_margin="18dp"
            app:layout_constraintDimensionRatio="H,1:1.33"
            app:layout_constraintVertical_bias="0.7"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            >


            <org.webrtc.SurfaceViewRenderer
                android:id="@+id/surfRendererRemote4"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:visibility="invisible"
                />

        </FrameLayout>



        <!-- Remote 5 -->
        <FrameLayout
            android:id="@+id/layout5"
            android:layout_width="80dp"
            android:layout_height="0dp"
            android:layout_margin="18dp"

            app:layout_constraintDimensionRatio="H,1:1.33"
            app:layout_constraintVertical_bias="0.9"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            >


            <org.webrtc.SurfaceViewRenderer
                android:id="@+id/surfRendererRemote5"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:visibility="invisible"
                />

        </FrameLayout>


    </androidx.constraintlayout.widget.ConstraintLayout>
</RelativeLayout>
</layout>
```

## 레이아웃 초기화

레이아웃을 바인딩하고, 각 view를 배열에 담아 index 로 접근이 가능하도록 설정합니다.

```kotlin
var surfaceRendererArray:Array<SurfaceViewRenderer>

binding = DataBindingUtil.setContentView( this, R.layout.activity_name )
surfaceRendererArray = arrayOf(
    binding.surfRendererLocal,
    binding.surfRendererRemote1,
    binding.surfRendererRemote2,
    binding.surfRendererRemote3,
    binding.surfRendererRemote4,
    binding.surfRendererRemote5
)

// 비어있는 뷰를 처리하기 위한 배열입니다. 각 서비스에 따라 구
var availableView:Array<Boolean>
availableView = Array(mSurfaceViewArray.size) {false}
```

## RemonConference 객체 생성

RemonConference 객체를 생성하고, 나의 영상을 송출하기 위한 설정을 합니다.

```kotlin
private var remonConference = RemonConference()

var config = Config()
config.context = this
config.serviceId = "콘솔을 통해 발급 받은 Service Id"
config.key = "콘솔을 통해 발급 받은 Secret Key"

remonConference.create( "방이름", config) { 
    participant ->
    
    // 마스터 유저(송출자,나자신) 초기화
    participant.localView = surfaceRendererArray[0]
    
    // 뷰 설정
    availableView[0] = true
}.close {
    // 마스터 유저가 연결된 채널이 종료되면 호출됩니다.
    // 송출이 중단되면 그룹통화에서 끊어진 것이므로, 다른 유저와의 연결도 모두 끊어집니다.
}.error { 
    error:RemonException ->
    // 마스터 유저가 연결된 채널에서 에러 발생 시 호출됩니다.
    // 오류로 연결이 종료되면 error -> close 순으로 호출됩니다.
}
```

## 그룹통화 콜백

그룹통화가 생성되면 송출이 시작되고, 각 콜백이 호출됩니다. 콜백은 create() 호출후 on("이벤트"){} 형태로 등록할 수 있습니다.\
새 참여자가 그룹통화에 입장하면 연결된 on 메소드의 콜백이 호출됩니다. on 메소드 콜백에서 참여자의 RemonParticipant 객체가 제공되므로, 해당 정보를 사용해 설정을 진행합니다.&#x20;

```kotlin
remonConference.create( "방이름", config) {
    .
    .
}.on( "onRoomCreated" ) { 
    participant ->
    
    // 마스터 유저가 접속된 이후에 호출(실제 송출 시작)
    // TODO: 실제 유저 정보는 각 서비스에서 관리하므로, 서비스에서 채널과 실제 유저 매핑 작업 진행

    // tag 객체에 holder 패턴 형태로 객체를 지정해 사용할 수 있습니다.
    // 예제에서는 뷰설정을 위해 단순히 view의 index를 저장합니다.
    participant.tag = 0
    
}.on( "onUserJoined" ) { 
    participant ->
    
    Log.d( TAG, "Joined new user" )
    // 그룹통화에 새로운 잠여자가 입장했을 때 호출됩니다.
    // 다른 사용자가 입장한 경우 초기화를 위해 호출됨
    // TODO: 실제 유저 매핑 : it.id 값으로 연결된 실제 유저를 얻습니다.


    // 뷰 설정
    val index = getAvailableView()
    if( index > 0 ) {
        participant.config.localView = null
        participant.config.remoteView = mSurfaceViewArray[index]
        participant.tag = index
    }
    
    // 피어가 연결이 완되었을때 처리할 작업이 있는 경우
    participant.on( "onComplete" ) { participant ->
        // updateView()
    }
}.on( "onUserStreamConnected" ) { participant ->
    // 피어의 onComplete 콜백과 동일

}.on( "onUserLeft" ) { participant ->
    // 상대방이 그룹통화에서 퇴장한 경우 or 연결이 종료된 경우 호출됩니다.
    // id 와 tag 를 참조해 어떤 사용자가 퇴장했는지 확인후 퇴장 처리를 합니다.
    val index = participant.tag as Int
    availableView[index] = false
}


// 비어있는 뷰는 아래처럼 얻어올 수 있습니다.
// 서비스에 해당하는 부분이므로 각 서비스 UI에 맞게 구성합니다.
private fun getAvailableView(): Int {
    for( i in 0 until this.mAvailableView.size) {
        if(!mAvailableView[i]) {
            mAvailableView[i] = true
            return i
        }
    }
    return -1
}
```

## 그룹통화 종료

그룹통화에서 퇴장하면 나와 그룹통화의 연결이 종료됩니다. 나와 참여자들 간의 연결도 종료됩니다.

```kotlin
remonConference.leave()
```

## RemonParticipant

각 참여자들과의 연결은 RemonConference 내부의 RemonParticipant 객체를 통해 이루어집니다. RemonParticipant 객체는 RemonClient를 상속받은 객체이므로, 공통적인 기능은 RemonCall, RemonCast 와 동일합니다. 각 이벤트마다 RemonParticipant 객체가 전달되므로 각 연결은 해당 객체를 통해 제어할 수 있으며, 마스터 객체의 경우 RemonConference 객체에서 얻어올 수 있습니다.

```kotlin
// 마스터 유저 얻기
RemonParticipant participant = remonConference.me

```

{% hint style="warning" %}
RemonParticipant 객체는 RemonClient를 상속받은 객체입니다. onCreate, onClose, onError 콜백은 on으로 재정의되어 RemonConference에서 관리, 사용되고 있으므로, 해당 콜백을 변경하지 마시기 바랍니다.
{% endhint %}


# 그룹통화 만들기(iOS)

##

## 그룹통화란?

다수의 참여자가 통화에 참여하는 서비스를 위한 기능입니다. 참여자는 앱을 이용하는 나와 그 외 참여자로 구분할 수 있습니다. 아래에서는 나와 참여자로 줄여서 표시합니다. 한 회기의 그룹통화는 RemonConference 클래스의 인스턴스로 대표됩니다. 나는 통화 연결, 참여자들의 입장/퇴장 알림 등 대부분의 일을 RemonConference 객체에게 위임합니다.

## RemonConference

iOS SDK 2.7.0이상

그룹통화를 위해 RemonConference 객체를 생성하고, 설정을 진행합니다.\
RemonConference 클래스는 그룹통화를 위해 아래 메소드를 제공합니다.

```swift
create( roomName:String, config:RemonConfig, callback:OnConferenceEventCallback )
leave()
```

RemonConference 클래스는 콜백으로 사용하기 위해 아래 메소드를 제공합니다. 이하 콜백용 메소드라고 합니다. 콜백용 메소드는 위에서 언급한 메소드의 콜백으로만 호출하며, 일반적인 메소드처럼 호출하지 않습니다.

```kotlin
// create 메소드의 콜백용 메소드
.on( eventName:"onRoomCreate") { participant in
}.on( eventName:"onUserJoined") { participant in
}.on( eventName: "onUserStreamConnected" ) { particpant in
}.on( eventName:"onUserLeft") { participant in
}.close {
}.error { error in
}

// participant 의 콜백용 메소드 
.on(event:"onComplete") { participant in
}.on(event:"onClose") { participant in
}.on(evennt:"onError") { error in
}
```

## 레이아웃 작업

그룹통화 화면을 나의 영상 한 개와 그룹 참여자의 영상 여러 개로 구성합니다. 레이아웃에 영상을 표시할 view를 만들고 인덱스를 지정하여 참여자의 영상을 원하는 위치에 표시할 수 있도록 합니다.

![XCode 스토리보드](/files/-LvEE47YJUFqhwZVUaZ1)

## 레이아웃 초기화

레이아웃을 바인딩하고, 각 view를 배열에 담아 index 로 접근이 가능하도록 설정합니다.

```swift
@IBOutlet var viewArray: [UIView]!
var availableViews:[Bool]?
```

![뷰들을 배열에 추가](/files/-LvEMno1NAbzMkt7fF3o)

## RemonConference 객체 생성

RemonConference 객체를 생성하고, 나의 영상을 송출하기 위한 설정을 합니다.

```kotlin
// 뷰를 설정하기 위한 배열 : 서버스에 맞게 구
availableViews = [Bool](repeating: false, count: self.viewArray.count)

var remonConference = RemonConference()

// config
let config = RemonConfig()
config.serviceId = "콘솔을 통해 발급 받은 Service Id"
config.key = "콘솔을 통해 발급 받은 Secret Key"


remonConference.create( "방이름", config: config) { 
    participant in
    
    // 마스터유저가 전달됩니다. (iOS의 경우 Builder 를 제공하지 않습니다)
    // 객체 생성은 RemonConference에서 이루어지므로 전달된 객체에 설정만을 제공합니다.
    participant.localView( surfaceRendererArray[0] )
    
}.close {
    // 마스터 유저가 종료된 경우 호출됩니다.
    // 송출이 중단되면 그룹통화에서 끊어진 것이므로, 다른 유저와의 연결도 모두 끊어집니다.
}.error { 
    error in
    // 마스터 유저가 연결된 채널에서 에러 발생 시 호출됩니다.
}
```

## 그룹통화 콜백

create 메소드로 그룹통화에 입장한 뒤 on() 메쏘드로 콜백을 등록할 수 있습니다.모든 참여자가 퇴장하면 이 이름의 그룹통화는 소멸됩니다.\
새 참여자가 그룹통화에 입장하면 연결된 on 메소드의 콜백이 호출됩니다. on 메소드 콜백에서 RemonParticipant 객체가 제공되므로, 해당 정보를 사용해 설정을 진행합니다.&#x20;

```kotlin
remonConference.create( "방이름", config: config) { 
    participant in
}.on( "onRoomCreated" ) { 
    participant in
    
    // 마스터 유저가 접속된 이후에 호출(실제 송출 시작)
    // TODO: 실제 유저 정보는 각 서비스에서 관리하므로, 서비스에서 채널과 실제 유저 매핑 작업 진행
    // tag 객체에 holder 형태로 객체를 지정해 사용할 수 있습니다.
    // 예제에서는 뷰 할당을 위해 단순히 view의 index를 저장합니다.
    participant.tag = 0
    
    // 뷰 설정용
    availableViews?[0] = true
}.on( "onUserJoined" ) { 
    participant in
    
    Log.d( TAG, "Joined new user" )
    // 그룹통화에 새로운 참여자가 입장했을 때 호출됩니다.
    // 새로운 참여자의 RemonParticipant 객체가 전달됩니다.
    
    // 뷰 리스트에서 비어있는 뷰를 얻어와 설정합니다.
    if let index = self?.getAvailableView() {
        participant.localView = nil
        participant.remoteView = self?.viewArray[index]
        participant.tag = index
    }

}.on( "onUserStreamConnected" ) { 
    participant in
    // 참여자가 연결된 이후에 호출됩니다.
    
}.on( "onUserLeft" ) { 
    participant in
    
    // 다른 사용자가 퇴장한 경우
    // participant.id 와 participant.tag 를 참조해 어떤 사용자가 퇴장했는지 확인후 퇴장 처리를 합니다.
    if let index = participant.tag as? Int {
        self?.availableViews?[index] = false
    }
}


// 비어있는 뷰 검색 함수 : 각 서비스에 맞게 구성합니다.
func getAvailableView() ->Int {
    if let views = self.availableViews {
        for i in 0 ... views.count {
            if views[i] == false {
                self.availableViews?[i] = true
                return i
            }
        }
    }
        
    return 0
}
```

## 그룹통화 종료

그룹통화에서 퇴장하면 나와 그룹통화의 연결이 종료됩니다. 나와 참여자들 간의 연결도 종료됩니다.

```swift
remonConference.leave()
```

## RemonParticipant

각 참여자들과의 연결은 RemonConference 내부의 RemonParticipant 객체를 통해 이루어집니다. RemonParticipant 객체는 RemonClient를 상속받은 객체이므로, 공통적인 기능은 RemonCall, RemonCast 와 동일합니다. 각 이벤트마다 RemonParticipant 객체가 전달되므로 각 연결은 해당 객체를 통해 제어할 수 있으며, 마스터 객체의 경우 RemonConference 객체에서 얻어올 수 있습니다.

```kotlin
// 마스터 유저 얻기
var participant:RemonParticipant = remonConference.me
```

{% hint style="warning" %}
RemonParticipant 객체는 RemonClient를 상속받은 객체입니다. onCreate, onClose, onError 콜백은 on 로 재정의되어 RemonConference에서 관리, 사용되고 있으므로, 해당 콜백을 변경하지 마시기 바랍니다.
{% endhint %}


# 그룹통화 만들기(Web)

## 그룹통화란?

다수의 참여자가 통화에 참여하는 서비스를 위한 기능입니다. Remon SDK를 이용하여 통화 또는 방송 구현 경험이 있으면 이해에 도움이 됩니다. 통화 또는 방송 튜토리얼을 학습한 후에 그룹통화를 학습하기를 권장합니다.

## 그룹통화 샘플 앱

* <https://sample.remotemonster.com/conference.html>

## 가이드의 주요 내용

1. **그룹통화를 위한 방(room) 만들기**
2. **방의 참여자 목록 조회**
3. **각의 참여자와 연결 수립하기**
4. **방으로부터 참여자 입장(join), 퇴장(leave) 이벤트 수신**

## 그룹통화를 위한 방(room) 만들기

* 모든 시작은 Remon 클래스에서  시작합니다. Remon클래스의  객체를 생성하는  방법은 다른 페이지등을 통해 참고하세요.
* Remon객체를 생성하면 바로 방(ROOM)을 개설할 수 있습니다.

```
remon.createRoom("Room name");
```

* 위 코드는  'Room name'에 해당하는 방이름으로 그룹 통화방을 개설하는 코드입니다.
* 지정된 videoTag의 local  영상이  해당  방에  송출됩니다.  여기까지는  나의  영상을 Room에  송출하는 것까지 완료되는 것입니다.

## 방의 참여자 목록 조회

* 'room name' 방으로부터 참여자 목록을 조회합니다.&#x20;
* fetchRooms는 참여자 목록을 담은 배열을 리턴합니다. 배열의 각 항목은 id 속성을 갖고 있습니다.
* 참여자 목록은 앱 사용자 자신을 포함합니다. 나를 포함하여 총 3명이 방에 있으면, 3개의 항목이 조회됩니다.

```
var searchResult= await remon.fetchRooms("Room name");
searchResult.forEach( ({id}, i) => {
  // id에 대한  처리
};
```

* 앱 사용자 자신의 id를 확인하려면 onConnect 콜백을 이용합니다.

```
var listener= {
  onConnect(chid) {
    console.log(`My ID is ${chid} in the room`);
  },
}

// 또는, 연결이 진행된 후, getChannelId 메소드를 이용합니다.
// var myid = remon.getChannelId();


// 권장하는 방법은 아니지만, 연결이 진행된 후 Remon 객체에서도 확인할 수 있습니다.
// remon = new Remon({ config, listener });
// var myid = remon.context.channel.id;
```

* 이제 그룹 통화 방에 속해있는 모든 참여자의 채널id를 확보하였습니다.

## 각 참여자와 연결 수립하기

* 각 참여자와의 연결은 리모트몬스터의 방송을 이용합니다.
* 각 참여자의 id는 리모트몬스터 방송 채널ID입니다.
* 위에서 조회한 각 참여자의 채널ID에 대해 Remon 객체의 시청 메소드 joinCast를 호출합니다.
* 각 참여자별로 Remon 객체를 만들어야합니다. 하나의 Remon 객체를 이용하면 안 됩니다.

```
searchResult.forEach( ({id}, i) => {
  remon1.joinCast(id);
}
```

## 방으로부터 참여자 입장(join), 퇴장(leave) 이벤트 수신

* 그룹통화 중 수시로 참여자가 입장하고 퇴장할 수 있습니다. 참여자의 입장과 퇴장 이벤트 수신은 onRoomEvent 콜백을 이용합니다.

```
var listener= {
  onRoomEvent(evt){
    console.log('event type: '+ evt.event); // 'join' or 'leave'
    console.log('channel id: '+ evt.channel.id); // 퇴장한 참여자의 ID
  }
}
```

* 현재 지원하는 이벤트 종류는 join과 leave입니다. 이 이벤트를 통해 새롭게 들어온 채널과 종료된 채널을 실시간으로 알 수 있습니다.

자세한 이용 방법은 그룹통화 샘플 앱의 소스코드를 참고하시기 바랍니다.

<https://sample.remotemonster.com/conference.html>


# 통화 5 분 안에 시작하기

"5분 안에 시작하기"로 통화를 빨리 체험봅니다. 5분 안에 시작하기는 다음의 내용으로 이루어져있습니다.

1. **Service ID와 Secret Key 확인**
2. **샘플 앱으로 통화 실시**
3. **웹 콘솔에서 이 통화에 대한 정보 조회**

## 준비 사항

* 웹 콘솔 회원 가입 : <https://console.remotemonster.com/>
* GitHub 계정 또는 HTTPS가 지원되는 웹 호스팅&#x20;

## Service ID와 Secret Key 확인

리모트몬스터 콘솔을 방문하여 회원가입을 하고 "기본 프로젝트"를 선택합니다. Service ID와 Secret Key(Service Key)를 확인합니다. 이 Service ID와 Secret Key를 샘플 앱의 코드에 삽입합니다. Secret Key를 보려면 우측 **눈 아이콘**을 클릭합니다.

<https://console.remotemonster.com/>

주의 : Secret Key는 \*\*\*\*\*\*\*\*\*\*\*\*\*\* 이 아닙니다. 우측 눈 아이콘을 누르면 표시됩니다.

![](/files/-LyTCM08fj9j729siE2z)

## **샘플 앱으로 통화 실시**

아래 GitHub 웹 사이트를 방문합니다.

{% embed url="<https://github.com/RemoteMonster/remon-devguide-quickstart>" %}

아래와 같은 모습이 보입니다. simplevideocall-tutorial.html 파일을 웹에 게시할 겁니다. 본 가이드는 GitHub Pages를 이용합니다. https가 지원되는 환경이라면 다른 곳에 파일을 게시해도 좋습니다.

![](/files/-LyTCbHhfRLh16lLEZ3B)

우측 상단의 Fork 버튼을 클릭합니다. 내 저장소가 생겼습니다. 내 저장소의 주소는 아래와 같습니다.

```
https://github.com/내 아이디/remon-devguide-quickstart
```

저장소의 HTML 파일들을 웹에서 볼 수 있도록 설정합니다. "Settings" 탭으로 이동합니다. 페이지 아래에 "GitHub Pages" 섹션으로 이동하여, Source를 master branch로 선택합니다.

![](/files/-LyTCwL4fV7Q03KEamay)

![](/files/-LyTD5YB7GKPP_MEHypY)

HTML 파일을 아래 주소에서 확인할 수 있습니다. 아래와 같은 화면이 표시됩니다.

```
https://내 아이디.github.io/remon-devguide-quickstart/simplevideocall-tutorial.html
```

![](/files/-LyTDCRSI1AJ2usT1tVA)

접속이 잘 되는 것을 확인했으면, 앱에 Service ID와 Secret Key를 입력합니다. 내 저장소로 이동하여 simplevideocall-tutorial.html 파일을 클릭합니다. 아래와 같은 화면이 표시됩니다. 오른쪽 편집 아이콘(연필 모양)을 클릭합니다.

![](/files/-LyTDNkQ9UX5Qp1RgIuA)

콘솔에서 확인한 Service ID와 Secret Key(Service Key)를 52번 행에 입력합니다. "Commit changes" 버튼을 눌러 저장합니다.

![](/files/-LyTDXjwMPvXxzmEFUzc)

서로 다른 디바이스(예, 노트북과 스마트폰)에서 웹 브라우저를 열고 아래 주소를 각각의 창에서 방문합니다.

```
https://내 아이디.github.io/remon-devguide-quickstart/simplevideocall-tutorial.html
```

두 창에서 "시작" 버튼을 클릭합니다. Camera와 Microphone 사용을 **반드시** 허락(Allow)합니다.

![](/files/-LyTDdipbfg0p44Px2kh)

잘 안 되신다면 아래 3가지를 먼저 확인해보시기 바랍니다.

* https 가 아니고 http로 접속하지 않았나? &#x20;
* 카메라, 마이크 접근 권한을 허용하지 않았나? &#x20;
* 내 저장소의 주소가 아니라 리모트몬스터 저장소의 주소에 접속하지 않았나?

## 웹 콘솔에서 이 통화에 대한 정보 조회

콘솔 창에서, "통화플랫폼 > 채널 및 세션" 메뉴로 이동합니다. 방금 만든 통화에 대한 정보를 조회할 수 있습니다.

![](/files/-LyTDlKBjgKITuy4l8_K)

**축하합니다. 통화플랫폼이 정상적을 동작하는 것을 확인하였습니다.**

튜토리얼의 새 프로젝트 설정, 단순 통화 앱 만들기 등을 참고하여, 통화 앱을 원하는 대로 만들어보십시오.


# 통화 키 발급

## 개요

RemoteMonster를 이용하려면 Service ID와 Key를 반드시 발급 받아야 합니다. Service ID는 리모트몬스터 사용자가 만드는 앱에 부여됩니다. Key는 사용자가 만든 앱의 리모트몬스터 플랫폼 이용 권한을 확인하는데 사용됩니다. 한 사용자가 여러 앱을 만들 경우 Service ID와 Key 또한 여러 개가 필요합니다. ServiceID는 사용량과 과금액을 구분하는 데에도 사용됩니다.

## 리모트몬스터 웹 콘솔

리모트몬스터 웹 콘솔을 이용하면 앱에서 통화가 잘 이루어지고 있는지 확인할 수 있습니다.\
새로운 통화 앱을 만들 때, 이 앱을 위한 새로운 Service ID와 Key를 생성할 수 있습니다.

웹 콘솔은 아래 주소에서 이용할 수 있습니다.

<https://console.remotemonster.com/>

### 프로젝트(서비스) 생성

웹 콘솔에 로그인하면 아래와 같은 화면이 표시됩니다. "새 프로젝트 추가" 카드를 클릭하면 새 프로젝트(서비스)를 생성할 수 있습니다. 각각의 프로젝트(서비스)는 고유한 Service ID와 Secret Key를 갖고 있습니다.

![](/files/-LyTE8ICGun4h1tIeQry)

프로젝트명은 각각의 프로젝트(서비스)를 사람이 구분할 수 있도록 하기위해 입력합니다. 프로젝트 생성 시 Service ID가 자동으로 생성됩니다. Service ID는 각각의 프로젝트(서비스)를 앱이 구분할 수 있도록 합니다.

![](/files/-LyTEElv0lAuA81HySxm)

프로젝트(서비스)를 생성하지 않아도 기본 프로젝트가 이미 만들어져있습니다. 기본 프로젝트의 이름을 바꾸어 사용하면 아주 조금 빨리 시작할 수 있습니다.

![](/files/-LyTEN4B7LuhXaXuV3qD)

새로 만든 프로젝트(서비스) 카드를 클릭하면 프로젝트의 상세 정보와 Service ID, Secret Key를 확인 할 수 있습니다.

![](/files/-LyTEapRbZDFxksytNOd)

Secret Key를 확인하려면 우측의 눈 아이콘을 클릭하십시오.

![](/files/-LyTEpsZ6z64bE1NB_5F)

## Service ID와 Secret Key 사용

프로젝트(서비스) 마다 사용량이 구분됩니다. 새로운 통화 앱을 만들 때 새로운 프로젝트(서비스)를 생성하길 권장합니다.

앱 소스코드에 Service ID와 Key를 Config의 적절한 위치에 삽입합니다. 자세한 내용은 다음 가이드를 참고합니다.

[새 프로젝트 설정(Web)](/drafts-videocall/new-project-web)


# 새 통화 프로젝트 설정 - Web

## 준비사항 <a href="#undefined" id="undefined"></a>

* Project ID (Service ID) [키 발급](/drafts-videocall/create-key)
* 웹 브라우저, 프론트엔드 개발 환경
* WebRTC를 지원하는 브라우저

### 프로젝트 생성 및 설정

웹 브라우저에서 RemoteMonster를 이용할 때에는 JavaScript 라이브러리를 이용합니다. 일반적인 웹 프론트엔드 개발 준비를 하면 됩니다.

### CDN으로 라이브러리 추가 (권장)

[jsDelivr](https://www.jsdelivr.com/package/npm/@remotemonster/sdk) CDN에 있는 RemoteMonster SDK를 이용할 수 있습니다. 아래와 같이 Latest 또는 Specific version을 HTML 파일에 삽입하십시오.

adapter.js 최신 버전을 remon.js 앞에 삽입해주십시오. 웹 브라우저 호환성을 위해 권장합니다.

{% tabs %}
{% tab title="index.html" %}

```markup
<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>

<!-- Latest -->
<script src="https://cdn.jsdelivr.net/npm/@remotemonster/sdk/remon.min.js"></script>

<!-- 또는 -->

<!-- Specific version -->
<script src="https://cdn.jsdelivr.net/npm/@remotemonster/sdk@2.0.8/remon.min.js"></script>
```

{% endtab %}
{% endtabs %}

### NPM으로 라이브러리 다운로드

npm으로 최신 버전을 다운로드 할 수 있습니다. node\_modules/webrtc-adapter/out/adapter.js 파일과 node\_modules/@remotemonster/sdk/remon.min.js 파일을 HTML 파일에 위 예시를 참고하여 삽입하십시오.

```bash
npm install @remotemonster/sdk
npm install webrtc-adapter
```

### Service Id, Service Key 입력

remon.min.js 파일을 추가한 뒤에 다음과 Service Id와 Key를 입력합니다.

```markup
<script>
const config = {
  credential: {
    serviceId: 'myServiceId', 
    key: 'myKey'
  }
}

// config는 다음과 같이 새로운 Remon 객체를 생성할 때 인자로 사용합니다.
// const caller = new Remon({config: config});
</script>
```

설정이 완료된 HTML파일의 예시는 다음과 같습니다.

{% tabs %}
{% tab title="index.html" %}

```markup
<!DOCTYPE html>
<html lang="ko">

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>

<body>


  <script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/@remotemonster/sdk/remon.min.js"></script>
  <script>
    const config = {
      credential: {
        serviceId: '발급 받은 Service Id를 입력',
        key: '발급 받은 Service Key를 입력'
      }
    }
  </script>
</body>

</html>
```

{% endtab %}
{% endtabs %}

이 HTML 파일을 https가 지원되는 웹 서버에 업로드 하고 테스트합니다. 반드시 **https**에서 테스트하십시오.

RemoteMonster를 이용한 개발은 Remon 클래스에 대한 이해가 필요합니다.

[Remon 클래스 알아보기](https://github.com/RemoteMonster/documents/tree/86188abf462170321bc5ebe2a7f5421ffb9799fb/drafts/drafts/inside-remoncall-sdk/remoncall.md)

프로젝트 설정을 완료했습니다. 이어서 기능을 구현에 대해 알아봅니다.

[단순 통화 앱 만들기(Web)](/drafts-videocall/simplevideocall-code-web)


# 단순 통화 앱 만들기 - Web

## 목차

1. HTML 템플릿 받아서 풀어놓기&#x20;
2. JavaScript 라이브러리 파일 추가, Config 만들기&#x20;
3. 동영상이 표시될 요소(video tag) 추가
4. 채널에 들어가기, 채널ID 확인하기
5. 테스트 하기&#x20;
6. 아주 조금 예쁘게 하기, 통화 상태 표시하기

## 준비사항

* Service Id
* https 지원 웹 서버 또는 웹 호스팅
* [단순 통화 앱 HTML 템플릿](https://www.remotemonster.com/devguide-assets/remotemonster-simplevideocall-tutorial-web.zip)

## HTML 템플릿 받아서 풀어놓기

[단순 통화 앱 HTML 템플릿](https://www.remotemonster.com/devguide-assets/remotemonster-simplevideocall-tutorial-web.zip)을 다운로드하여 풀어 놓습니다. index.html, style.css, complete.html 3개의 파일을 확인하십시오. 내용이 비어있는 index.html 을 수정하며 단순 통화 앱을 만들어봅니다. 수정 완료된 내용은 complete.html 파일에서 확인할 수 있습니다. index.html 파일의 내용은 아래와 같습니다.

{% tabs %}
{% tab title="index.html" %}

```markup
<!DOCTYPE html>
<html lang="ko">
   <head>
      <meta charset="utf-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>리모트몬스터 단순 통화 앱</title>
      <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.3.1/css/bootstrap.min.css">
   </head>
   <body>
      <footer>
         <p>리모트몬스터 제공</p>
      </footer>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.slim.min.js"></script>  
      <script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.3.1/js/bootstrap.min.js"></script>
   </body>
</html>
```

{% endtab %}
{% endtabs %}

## RemoteMonster JavaScript 라이브러리 파일 추가, Config 만들기

리모트몬스터 라이브러리 remon.js 파일을 추가합니다. 웹 브라우저 호환성을 위해 adapter.js 파일도 추가합니다. config를 만듭니다.

{% tabs %}
{% tab title="index.html" %}

```markup
<!DOCTYPE html>
<html lang="ko">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>리모트몬스터 단순 통화 앱</title>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.3.1/css/bootstrap.min.css">
  </head>
  <body>
    <footer>
      <p>리모트몬스터 제공</p>
    </footer>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.slim.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.3.1/js/bootstrap.min.js"></script>
    <!-- adapter.js, remon.js 파일을 추가하고 config를 만듭니다. -->
    <script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/@remotemonster/sdk/remon.min.js"></script>
    <script>
      const config = {      
        credential: {        
          serviceId: '발급 받은 Service Id를 입력',        
          key: '발급 받은 Secret Key를 입력'      
        }    
      }
    </script>
  </body>
</html>
```

{% endtab %}
{% endtabs %}

## 동영상이 표시될 엘리먼트(video tag) 추가, 레이아웃에 맞추기

video 요소 2개를 추가합니다. 하나는 내 영상을 표시하고, 다른 하나는 상대방의 영상을 표시하기 위해서입니다. local video 요소에 autoplay 속성이 있으면 통화채널에 들어갔을 때 내 영상이 자동으로 표시됩니다. remote video 요소에 autoplay 속성이 있으면 통화채널에 상대방이 들어왔을 때 상대방 영상이 자동으로 표시됩니다. config에 remote와 local 항목으로 이 video 요소들의 ID를 입력합니다. button 요소 2개를 추가합니다. "시작" 버튼은 통화채널에 들어가고, "종료" 버튼은 통화채널에서 나가는 데 씁니다. 통화채널의 ID를 표시할 영역을 추가합니다.&#x20;

시작 버튼을 눌렀을 때 영상이 보이지 않으면 video 요소의 autoplay 속성을 확인하십시오. Safari에서 동영상이 표시되지 않으면 playsinline 속성을 추가합니다.

local video 요소에는 muted 속성을 추가합니다. muted 속성이 없으면 내 마이크로 들어간 소리가 내 스피커로 나와 하울링이 발생할 수 있습니다.

{% tabs %}
{% tab title="index.html" %}

```markup
<!DOCTYPE html>
<html lang="ko">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>리모트몬스터 단순 통화 앱</title>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.3.1/css/bootstrap.min.css">
  </head>
  <body>
    <main>
      상대방    
      <video id="remoteVideo" autoplay playsinline></video>
      나    
      <video id="localVideo" muted autoplay playsinline></video>
      <button id="startCall">시작</button>    
      <button id="stopCall">종료</button>    
      <div id="channelId"></div>
    </main>
    <footer>
      <p>리모트몬스터 제공</p>
    </footer>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.slim.min.js"></script>  
    <script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.3.1/js/bootstrap.min.js"></script>

    <!-- adapter.js, remon.js 파일을 추가하고 config를 만듭니다. -->  
    <script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>  
    <script src="https://cdn.jsdelivr.net/npm/@remotemonster/sdk/remon.min.js"></script>  
    <script>    
      const config = {      
        credential: {        
          serviceId: '발급 받은 Service Id를 입력',        
          key: '발급 받은 Secret Key를 입력'      
        },      
        view: {        
          remote: '#remoteVideo',        
          local: '#localVideo'      
        }    
      }  
    </script>
  </body>
</html>
```

{% endtab %}
{% endtabs %}

아래와 같은 모습을 만들겁니다. 안 예쁘다고요? 문서 뒷부분에서 예쁘게 해보겠습니다.

![](/files/-M3JzQBtGuPenSTvj53W)

## 채널에 들어가기, 채널ID 확인하기

채널ID를 명시하지 않으면 임의의 채널을 생성합니다. onCreate 콜백을 이용하여 채널ID를 확인합니다.

{% tabs %}
{% tab title="index.html" %}

```markup
<!DOCTYPE html>
<html lang="ko">

<head>
    <title>리모트몬스터 단순 통화 앱</title>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.3.1/css/bootstrap.min.css">
</head>

<body>
    <main> 
      상대방    
      <video id="remoteVideo" autoplay playsinline></video>
      나    
      <video id="localVideo" muted autoplay playsinline></video>
      <button id="startCall">시작</button> 
      <button id="stopCall">종료</button>
      <div id="channelId"></div>
    </main>
    <footer>
      <p>리모트몬스터 제공</p>
    </footer>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.slim.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.3.1/js/bootstrap.min.js"></script>

    <!-- "시작" 버튼을 클릭 했을 때 채널에 들어갑니다. connectCall 부분을 주목해주십시오.        채널에 들어가면 콜백을 이용해 채널ID를 화면에 표시합니다. onConnect 부분을 주목해주십시오. -->
    <script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/@remotemonster/sdk/remon.min.js"></script>
    <script>
      const config = {
        credential: {
          serviceId: '발급 받은 Service Id를 입력',
          key: '발급 받은 Secret Key를 입력'
        },
        view: {
          remote: '#remoteVideo',
          local: '#localVideo'
        }
      };

      const listener = {
        onConnect(chid) {
          $('#channelId').text(chid);
          console.log(`onConnect: ${chid}`);
        }
      };

      const remonCall = new Remon({
        config: config,
        listener: listener
      });

      $('#startCall').click(function() {
        remonCall.connectCall('my-first-channel');
      });
    </script>
</body>

</html>
```

{% endtab %}
{% endtabs %}

## 테스트 하기

위 index.html 파일을 웹에 게시합니다. 로컬에서 테스트 할 경우 http와 https 모두 가능합니다. 로컬이 아닐 경우 반드시 https 환경에서 테스트해야합니다.

웹 브라우저 창을 2개 엽니다. 두 창에서 각각 index.html 파일의 주소를 엽니다. 두 창에서 각각 "시작" 버튼을 클릭합니다. 두 개의 동영상이 표시됩니다.

정상적으로 두 동영상이 표시되지 않는다면, 웹 브라우저의 JavaScript 콘솔 창을 열어 오류 메시지를 확인합니다.

## 아주 조금 예쁘게 하기, 통화 상태 표시하기

video 요소의 크기를 조절하고 배치를 바꾸어 보기 좋게 합니다. 다운로드 한 파일 중 style.css 파일을 index.html 파일에 삽입합니다.

{% tabs %}
{% tab title="index.html" %}

```markup
<!DOCTYPE html>
<html lang="ko">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>리모트몬스터 단순 통화 앱</title>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.3.1/css/bootstrap.min.css">

    <!-- 다운로드한 파일 중 style.css 파일을 index.html 파일에 삽입합니다. -->  
    <link rel="stylesheet" href="style.css">
  </head>
  <body>
    <main>
      <div class="container">
        <div class="row">
          <div id="you" class="col-12 col-sm-6 col-md-6">
            <h2>상대방</h2>
            <video id="remoteVideo" class="remoteVideo" autoplay playsinline></video>
          </div>
          <div id="me" class="col-12 col-sm-6 col-md-6">
            <h2>나</h2>
            <video id="localVideo" class="localVideo" muted autoplay playsinline></video>
          </div>
        </div>
        <div class="row">
          <div id="controlLeft" class="col-6 col-sm-6 col-md-6">          
            <button id="startCall">시작</button>        
          </div>
          <div id="controlRight" class="col-6 col-sm-6 col-md-6">          
            <button id="stopCall">종료</button>        
          </div>
        </div>
        <div class="row">
          <div class="col-12 col-sm-12 col-md-12">
            <div id="channelId"></div>
            <div id="channelState"></div>
          </div>
        </div>
      </div>
    </main>
    <footer>
      <p>리모트몬스터 제공</p>
    </footer>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.slim.min.js"></script>  
    <script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.3.1/js/bootstrap.min.js"></script>  

    <script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>  
    <script src="https://cdn.jsdelivr.net/npm/@remotemonster/sdk/remon.min.js"></script>  
    <script>
    const config = {
        credential: {
            serviceId: '발급 받은 Service Id를 입력',
            key: '발급 받은 Secret Key를 입력'
        },
        view: {
            remote: '#remoteVideo',
            local: '#localVideo'
        }
    };

    const listener = {
        onConnect(chid) {
            $('#channelId').text(chid);
            $('#channelState').text("대기 중");
            console.log(`onConnect: ${chid}`);
        }, onComplete() {
            $('#channelState').text("통화 중");
            console.log(`onComplete`);
        }, onClose() {
            $('#channelState').text("통화 종료");
            console.log(`onClose`);
        }
    };

    const remonCall = new Remon({
        config: config,
        listener: listener
    });

    $('#startCall').click(function () {
        remonCall.connectCall('my-first-channel');
    }); 

    // "종료" 버튼을 클릭하면 통화채널에서 나갑니다.    
    $('#stopCall').click(function() {
      remonCall.close();    
    });  
    </script>
  </body>
</html>
```

{% endtab %}

{% tab title="style.css" %}

```css
.container {
  text-align: center;
}

#controlLeft {
  text-align: right;
}

#controlRight {
  text-align: left;
}

.remoteVideo {
  background: orange;
  width: 240px;
  height: 180px;
}

.localVideo {
  background: purple;
  width: 240px;
  height: 180px;
}

footer {
  margin-top: 16px;
  text-align: center;
}
```

{% endtab %}
{% endtabs %}

![](/files/-M3K-Kvwuf0GgffyS0wI)

## 그 외 주요 메소드들 <a href="#undefined-2" id="undefined-2"></a>

RemonCall의 주요 인스턴스 메소드는 다음과 같습니다.

* close() : RemonCall 객체가 소멸됩니다. 통화를 종료할 때 사용합니다. close 후에 새 통화를 시작하려면 RemonCall 객체를 새롭게 생성 해야 합니다.
* showLocalVideo(): 통화 시작 전에 자신의 카메라 화면을 미리 보고 싶을 때 호출합니다.
* pauseLocalVideo(true): 통화 중 자신의 카메라 화면을 중지하고 싶을 때 호출합니다.
* switchCamera(): 카메라가 여러 개일 때 카메라를 순차적으로 스위칭해서 보여줍니다.
* fetchCalls() : 통화채널을 조회할 때 호출합니다.

## Callback에 대하여 <a href="#callback" id="callback"></a>

* onInit(): RemonCall 객체 생성이 정상적으로 처리됐을 때 호출됩니다. token이 인자로 전달됩니다.
* onConnect(): 통화채널이 만들어졌을 때 호출됩니다. 생성된 채널ID가 인자로 전달됩니다.
* onComplete() : 통화 상대방과 연결이 됐을 때 호출됩니다.
* onClose(): 통화가 종료되었을 때 호출됩니다. CloseType이 인자로 전달됩니다.
* onError(): 에러가 발생했을 때 호출됩니다. 오류 메시지가 인자로 전달됩니다.
* onStat(): RemoteMonster가 통화품질 데이터를 5초마다 자동으로 생성합니다. 통화품질 데이터가 생성될 때 호출됩니다. 통화품질 데이터가 인자로 전달됩니다.

자세한 내용은 다음 문서를 참고합니다.

​[Callback과 Observer](https://app.gitbook.com/@remotemonster/s/remon-ko-20191001/~/drafts/-LsL6vPjeqajroIstiKC/primary/videocallplatform/inside-remoncall-sdk/callback-observer)​

단순 통화 앱을 만들며 RemoteMonster를 이용해 기능을 구현하는 방법을 알아보았습니다. Remon SDK의 내부를 알면 다양한 옵션을 선택할 수 있고, 원하는 것을 쉽게 구현할 수 있습니다.

​[Remon SDK의 내부](https://app.gitbook.com/@remotemonster/s/remon-ko-20191001/~/drafts/-LsL6vPjeqajroIstiKC/primary/videocallplatform/inside-remoncall-sdk)


# 방송 5 분 안에 시작하기

"5분 안에 시작하기"로 빨리 방송/통화를 체험해봅니다. 5분 안에 시작하기는 다음의 내용으로 이루어져있습니다.

1. **Service ID와 Secret Key 확인**
2. **샘플 앱으로 방송/시청 실시**
3. **웹 콘솔에서 이 방송에 대한 정보 조회**

## 준비 사항

* 웹 콘솔 회원 가입 : <https://console.remotemonster.com/>
* GitHub 계정 또는 HTTPS가 지원되는 웹 호스팅&#x20;

## Service ID와 Secret Key 확인

리모트몬스터 콘솔을 방문하여 회원가입을 하고 "기본 프로젝트"를 선택합니다. Service ID와 Secret Key(Service Key)를 확인합니다. 이 Service ID와 Secret Key를 샘플 앱의 코드에 삽입할 겁니다.

<https://console.remotemonster.com/>

주의 : Secret Key는 \*\*\*\*\*\*\*\*\*\*\*\*\*\* 이 아닙니다. 우측 눈 아이콘을 누르면 표시됩니다.

![](/files/-LyTCM08fj9j729siE2z)

## **샘플 앱으로 방송/시청 실시**

아래 GitHub 웹 사이트를 방문합니다.

{% embed url="<https://github.com/RemoteMonster/remon-devguide-quickstartlivestreaming/>" %}

아래와 같은 모습이 보입니다. simplelivestreaming.html, simplewatch.html 파일을 웹에 게시할 겁니다. 본 가이드는 GitHub Pages를 이용합니다. https가 지원되는 환경이라면 다른 곳에 파일을 게시해도 좋습니다.

![](/files/-LyTCbHhfRLh16lLEZ3B)

우측 상단의 Fork 버튼을 클릭합니다. 내 저장소가 생겼습니다. 내 저장소의 주소는 아래와 같습니다.

```
https://github.com/내 아이디/remon-devguide-quickstartlivestreaming
```

저장소의 HTML 파일들을 웹에서 볼 수 있도록 설정합니다. "Settings" 탭으로 이동합니다. 페이지 아래에 "GitHub Pages" 섹션으로 이동하여, Source를 master branch로 선택합니다.

![](/files/-LyTCwL4fV7Q03KEamay)

![](/files/-LyTD5YB7GKPP_MEHypY)

HTML 파일을 아래 주소에서 확인할 수 있습니다. 아래와 같은 화면이 표시됩니다.

```
https://내 아이디.github.io/remon-devguide-quickstartlivestreaming/simplelivestreaming.html
```

```
https://내 아이디.github.io/remon-devguide-quickstartlivestreaming/simplewatch.html
```

![](/files/-LyTj_y0cktYS_BTDn1i)

접속이 잘 되는 것을 확인했으면, 앱에 Service ID와 Secret Key를 입력합니다. 내 저장소로 이동하여 simplelivestreaming.html, simplewatch.html 파일을 클릭합니다. 아래와 같은 화면이 표시됩니다. 오른쪽 편집 아이콘(연필 모양)을 클릭합니.

![](/files/-LyTDNkQ9UX5Qp1RgIuA)

콘솔에서 확인한 Service ID와 Secret Key(Service Key)를 52번 행에 입력합니다. "Commit changes" 버튼을 눌러 저장합니다.

![](/files/-LyTDXjwMPvXxzmEFUzc)

웹 브라우저 창을 2개 엽니다. 아래 주소를 각각의 창에서 방문합니다.

```
https://내 아이디.github.io/remon-devguide-quickstartlivestreaming/simplelivestreaming.html
```

```
https://내 아이디.github.io/remon-devguide-quickstartlivestreaming/simplewatch.html
```

두 창에서 "시작" 버튼을 클릭합니다. Camera와 Microphone 사용을 **반드시** 허락(Allow)합니다.

## 웹 콘솔에서 이 방송에 대한 정보 조회

콘솔 창에서, "방송플랫폼 > 채널 및 세션" 메뉴로 이동합니다. 방금 만든 방송에 대한 정보를 조회할 수 있습니다.

![](/files/-LyTDlKBjgKITuy4l8_K)

**축하합니다. 방송플랫폼이 정상적을 동작하는 것을 확인하였습니다.**

튜토리얼의 새 프로젝트 설정, 단순 시청 앱 만들기 등을 참고하여, 방송/시청 앱을 원하는 대로 만들어보십시오.


# 방송 키 발급

## 개요

RemoteMonster를 이용하려면 Service ID와 Key를 반드시 발급 받아야 합니다. Service ID는 리모트몬스터 사용자가 만드는 앱에 부여됩니다. Key는 사용자가 만든 앱의 리모트몬스터 플랫폼 이용 권한을 확인하는데 사용됩니다. 한 사용자가 여러 앱을 만들 경우 Service ID와 Key 또한 여러 개가 필요합니다. ServiceID는 사용량과 과금액을 구분하는 데에도 사용됩니다.

## 리모트몬스터 웹 콘솔

리모트몬스터 웹 콘솔을 이용하면 앱에서 통화가 잘 이루어지고 있는지 확인할 수 있습니다.\
새로운 통화 앱을 만들 때, 이 앱을 위한 새로운 Service ID와 Key를 생성할 수 있습니다.

웹 콘솔은 아래 주소에서 이용할 수 있습니다.

<https://console.remotemonster.com/>

### 프로젝트(서비스) 생성

웹 콘솔에 로그인하면 아래와 같은 화면이 표시됩니다. "새 프로젝트 추가" 카드를 클릭하면 새 프로젝트(서비스)를 생성할 수 있습니다. 각각의 프로젝트(서비스)는 고유한 Service ID와 Secret Key를 갖고 있습니다.

![](/files/-LyTE8ICGun4h1tIeQry)

프로젝트명은 각각의 프로젝트(서비스)를 사람이 구분할 수 있도록 하기위해 입력합니다. 프로젝트 생성 시 Service ID가 자동으로 생성됩니다. Service ID는 각각의 프로젝트(서비스)를 앱이 구분할 수 있도록 합니다.

![](/files/-LyTEElv0lAuA81HySxm)

프로젝트(서비스)를 생성하지 않아도 기본 프로젝트가 이미 만들어져있습니다. 기본 프로젝트의 이름을 바꾸어 사용하면 아주 조금 빨리 시작할 수 있습니다.

![](/files/-LyTEN4B7LuhXaXuV3qD)

새로 만든 프로젝트(서비스) 카드를 클릭하면 프로젝트의 상세 정보와 Service ID, Secret Key를 확인 할 수 있습니다.

![](/files/-LyTCM08fj9j729siE2z)

Secret Key를 확인하려면 우측의 눈 아이콘을 클릭하십시오.

![](/files/-LyTEpsZ6z64bE1NB_5F)

## Service ID와 Secret Key 사용

프로젝트(서비스) 마다 사용량이 구분됩니다. 새로운 통화 앱을 만들 때 새로운 프로젝트(서비스)를 생성하길 권장합니다.

앱 소스코드에 Service ID와 Key를 Config의 적절한 위치에 삽입합니다. 자세한 내용은 다음 가이드를 참고합니다.

[새 프로젝트 설정(Web)](/drafts-livestreaming/new-project-web)


# 새 방송 프로젝트 설정 - Web

## 준비사항 <a href="#undefined" id="undefined"></a>

* Project ID (Service ID) [키 발급](/drafts-livestreaming/create-key)
* 웹 브라우저, 프론트엔드 개발 환경
* WebRTC를 지원하는 브라우저

### 프로젝트 생성 및 설정

웹 브라우저에서 RemoteMonster를 이용할 때에는 JavaScript 라이브러리를 이용합니다. 일반적인 웹 프론트엔드 개발 준비를 하면 됩니다.

### CDN으로 라이브러리 추가 (권장)

[jsDelivr](https://www.jsdelivr.com/package/npm/@remotemonster/sdk) CDN에 있는 RemoteMonster SDK를 이용할 수 있습니다. 아래와 같이 Latest 또는 Specific version을 HTML 파일에 삽입하십시오.

adapter.js 최신 버전을 remon.js 앞에 삽입해주십시오. 웹 브라우저 호환성을 위해 권장합니다.

{% code title="index.html" %}

```markup
<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>

<!-- Latest -->
<script src="https://cdn.jsdelivr.net/npm/@remotemonster/sdk/remon.min.js"></script>

<!-- 또는 -->

<!-- Specific version -->
<script src="https://cdn.jsdelivr.net/npm/@remotemonster/sdk@2.0.8/remon.min.js"></script>
```

{% endcode %}

### NPM으로 라이브러리 다운로드

npm으로 최신 버전을 다운로드 할 수 있습니다. node\_modules/webrtc-adapter/out/adapter.js 파일과 node\_modules/@remotemonster/sdk/remon.min.js 파일을 HTML 파일에 위 예시를 참고하여 삽입하십시오.

```bash
npm install @remotemonster/sdk
npm install webrtc-adapter
```

### Service Id, Service Key 입력

remon.min.js 파일을 추가한 뒤에 다음과 Service Id와 Key를 입력합니다.

```markup
<script>
const config = {
  credential: {
    serviceId: 'myServiceId', 
    key: 'myKey'
  }
}

// config는 다음과 같이 새로운 Remon 객체를 생성할 때 인자로 사용합니다.
// const caller = new Remon({config: config});
</script>
```

설정이 완료된 HTML파일의 예시는 다음과 같습니다.

{% code title="index.html" %}

```markup
<!DOCTYPE html>
<html lang="ko">

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>

<body>


  <script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/@remotemonster/sdk/remon.min.js"></script>
  <script>
    const config = {
      credential: {
        serviceId: '발급 받은 Service Id를 입력',
        key: '발급 받은 Service Key를 입력'
      }
    }
  </script>
</body>

</html>
```

{% endcode %}

이 HTML 파일을 https가 지원되는 웹 서버에 업로드 하고 테스트합니다. 반드시 **https**에서 테스트하십시오.

RemoteMonster를 이용한 개발은 Remon 클래스에 대한 이해가 필요합니다.

[Remon 클래스 알아보기](https://github.com/RemoteMonster/remon-devguide-ko-2019/tree/f32c3e15637cf2b089461da0c2b5c5b8b154cbe3/livestreamingplatform/inside-remoncall-sdk/remoncall.md)

프로젝트 설정을 완료했습니다. 이어서 기능을 구현에 대해 알아봅니다.

[단순 통화 앱 만들기(Web)](https://github.com/RemoteMonster/remon-devguide-ko-2019/tree/f32c3e15637cf2b089461da0c2b5c5b8b154cbe3/livestreamingplatform/tutorial-simplelivestreaming-viewer/simplevideocall-code-web.md)


# 단순 시청 앱 만들기 - Web

## 목차

1. HTML 템플릿 받아서 풀어놓기&#x20;
2. JavaScript 라이브러리 파일 추가, Config 만들기&#x20;
3. 동영상이 표시될 요소(video tag) 추가
4. 채널에 들어가기, 채널ID 확인하기
5. 테스트 하기&#x20;
6. 아주 조금 예쁘게 하기, 통화 상태 표시하기

## 준비사항

* Service Id
* https 지원 웹 서버 또는 웹 호스팅

## HTML 템플릿 받아서 풀어놓기

[단순 시청 앱 HTML 템플릿](https://www.remotemonster.com/devguide-assets/remotemonster-simplevideocall-tutorial-web.zip)을 다운로드하여 풀어 놓습니다. simplewatch.html, complete.html, simplelivestreaming.html 3개의 파일을 확인하십시오. 내용이 비어있는 simplewatch.html 을 수정하며 단순 시청 앱을 만들어봅니다. 수정 완료된 내용은 complete.html 파일에서 확인할 수 있습니다. simplewatch.html 파일의 내용은 아래와 같습니다.

{% tabs %}
{% tab title="simplewatch.html" %}

```markup
<!DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
  <title>리모트몬스터 단순 시청 앱</title>

  <script src="https://code.jquery.com/jquery-3.4.1.slim.min.js"></script>
</head>
<body>

</body>
</html>
```

{% endtab %}
{% endtabs %}

## RemoteMonster JavaScript 라이브러리 파일 추가, Config 만들기

리모트몬스터 라이브러리 remon.js 파일을 추가합니다. 웹 브라우저 호환성을 위해 adapter.js 파일도 추가합니다. config를 만듭니다.

{% tabs %}
{% tab title="simplewatch.html" %}

```markup
<!DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
  <title>리모트몬스터 단순 시청 앱</title>

  <script src="https://code.jquery.com/jquery-3.4.1.slim.min.js"></script>
</head>
<body>

  <script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/@remotemonster/sdk/remon.min.js"></script>
  <script>
  let remon;
  const config = {
    credential: {
      serviceId: '발급 받은 Service Id를 입력',
      key: '발급 받은 Secret Key를 입력'
    },
    view: {
      remote: '#remoteVideo'
    }
  };
  </script>
</body>
</html>
```

{% endtab %}
{% endtabs %}

## 동영상이 표시될 엘리먼트(video tag) 추가, 레이아웃에 맞추기

video 요소 1개를 추가합니다. 방송의 영상을 표시하기 위해서입니다. remote video 요소에 autoplay 속성이 있으면 방송채널에 들어갔을 때 동영상이 바로 표시됩니다. config에 remote 항목으로 이 video 요소의 ID를 입력합니다. button 요소 2개를 추가합니다. "시작" 버튼은 방송채널에 들어가고, "종료" 버튼은 방송채널에서 나가는 데 씁니다. 방송채널의 ID를 표시할 영역을 추가합니다. 시작 버튼을 눌렀을 때 영상이 보이지 않으면 video 요소의 autoplay 속성을 확인하십시오.

아래와 같은 모습을 만들겁니다. 안 예쁘다고요? 문서 뒷부분에서 예쁘게 해보겠습니다.

{% tabs %}
{% tab title="simplewatch.html" %}

```markup
<!DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
  <title>리모트몬스터 단순 시청 앱</title>

  <script src="https://code.jquery.com/jquery-3.4.1.slim.min.js"></script>
</head>
<body>
  <div id="wingleft"></div>
  <div id="mymain">
    <h2>시청</h2>
    <video id="remoteVideo" autoplay playsinline></video>
    <br>
    <button id="mystart" class="btn btn-main">Start</button>
    <button id="mystop" class="btn btn-main" disabled>Stop</button>
  </div>
  <div id="wingright"></div>

  <script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/@remotemonster/sdk/remon.min.js"></script>
  <script>
  let remon;
  const config = {
    credential: {
      serviceId: '발급 받은 Service Id를 입력',
      key: '발급 받은 Secret Key를 입력'
    },
    view: {
      remote: '#remoteVideo'
    }
  };
  </script>
</body>
</html>
```

{% endtab %}
{% endtabs %}

## 채널에 들어가기

방송을 테스트할 때 사용할 채널ID를 미리 정합니다. 튜토리얼은 채널ID를 "my-first-livestreaming"으로 합니다.

출시를 위한 앱을 만들 때에는 고유하고 추측하기 어려운 채널ID를 써야합니다.

{% tabs %}
{% tab title="simplewatch.html" %}

```markup
<!DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
  <title>리모트몬스터 단순 시청 앱</title>

  <script src="https://code.jquery.com/jquery-3.4.1.slim.min.js"></script>
</head>
<body>
  <div id="wingleft"></div>
  <div id="mymain">
    <h2>시청</h2>
    <video id="remoteVideo" autoplay muted></video>
    <br>
    <button id="mystart" class="btn btn-main">Start</button>
    <button id="mystop" class="btn btn-main" disabled>Stop</button>
  </div>
  <div id="wingright"></div>

  <script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/@remotemonster/sdk/remon.min.js"></script>
  <script>
  let remon;
  const config = {
    credential: {
      serviceId: '발급 받은 Service Id를 입력',
      key: '발급 받은 Secret Key를 입력'
    },
    view: {
      remote: '#remoteVideo'
    }
  };
  const listener = {
    onCreate(chid) { console.log(`EVENT FIRED: onCreate: ${chid}`); },
    onJoin(chid) { console.log(`EVENT FIRED: onJoin: ${chid}`); $('#mystart').prop( "disabled", true ); $('#mystop').prop( "disabled", false ); },
    onClose() { console.log('EVENT FIRED: onClose'); $('#mystart').prop( "disabled", false ); $('#mystop').prop( "disabled", true ); },
    onError(error) { console.log(`EVENT FIRED: onError: ${error}`); },
    onStat(result) { console.log(`EVENT FIRED: onStat: ${result}`); }
  };
  $('#mystop').click(function(){
    remon.close();
  });
  $('#mystart').click(function(){
    remon.joinCast("my-first-livestreaming");
  });
  </script>
</body>
</html>
```

{% endtab %}
{% endtabs %}

### 채널 생성하기

simplelivestreaming.html 파일을 열어 Service ID와 Secret Key를 수정합니다.

```javascript
  $('#mystart').click(function(){
    // createCast의 인자는 방송채널의 ID입니다. 실제 서비스에서는 동일한 방송채널의 ID가 아닌, 고유하고 예측이 어려운 ID를 사용해야합니다.
    remon.createCast("my-first-livestreaming");
  });
```

## 테스트 하기

simplewatch.html, simplelivestreaming.html 파일을 웹에 게시합니다. 로컬에서 테스트 할 경우 http와 https 모두 가능합니다. 로컬이 아닐 경우 반드시 https 환경에서 테스트해야합니다.

웹 브라우저 창을 2개 엽니다. 두 창에서 각각 simplewatch.html, simplelivestreaming.html 파일의 주소를 엽니다. simplelivestreaming.html 창에서 "시작" 버튼을 클릭합니다. 다음 simplewatch.html 창에서 "시작" 버튼을 누릅니다. 동영상이 표시됩니다.

정상적으로 동영상이 표시되지 않는다면, 웹 브라우저의 JavaScript 콘솔 창을 열어 오류 메시지를 확인합니다.

## 아주 조금 예쁘게 하기

video 요소의 크기를 조절하고 배치를 바꾸어 보기 좋게 합니다. head 요소 안에 style 을 추가 합니다.

{% tabs %}
{% tab title="simplewatch.html" %}

```markup
<!DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
  <title>RemoteMonster - Simple Example</title>

  <!-- 아주 조금 예쁘게 하기 -->   
  <style>
    #mymain { 
      margin-left: auto; margin-right: auto; width: 320px; 
    }
    #wingleft { 
    }
    #wingright { 
    }
    #remoteVideo { 
      width: 320px; height: 240px; background-color: black; 
    }
  </style>
  <script src="https://code.jquery.com/jquery-3.4.1.slim.min.js"></script>
</head>
<body>
  <div id="wingleft"></div>
  <div id="mymain">
    <h2>시청</h2>
    <video id="remoteVideo" autoplay muted></video>
    <br>
    <button id="mystart" class="btn btn-main">Start</button>
    <button id="mystop" class="btn btn-main" disabled>Stop</button>
  </div>
  <div id="wingright"></div>

  <script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/@remotemonster/sdk/remon.min.js"></script>
  <script>
  let isConnected = false;
  let remon;
  const config = {
    credential: {
      serviceId: '발급 받은 Service Id를 입력',
      key: '발급 받은 Secret Key를 입력'
    },
    view: {
      remote: '#remoteVideo'
    }
  };
  const listener = {
    onCreate(chid) { console.log(`EVENT FIRED: onCreate: ${chid}`); },
    onJoin(chid) { console.log(`EVENT FIRED: onJoin: ${chid}`); $('#mystart').prop( "disabled", true ); $('#mystop').prop( "disabled", false ); },
    onClose() { console.log('EVENT FIRED: onClose'); $('#mystart').prop( "disabled", false ); $('#mystop').prop( "disabled", true ); },
    onError(error) { console.log(`EVENT FIRED: onError: ${error}`); },
    onStat(result) { console.log(`EVENT FIRED: onStat: ${result}`); }
  };
  remon = new Remon({ config, listener });
  $('#mystop').click(function(){
    remon.close();
  });
  $('#mystart').click(function(){
    remon.joinCast("my-first-livestreaming");
  });
  </script>
</body>
</html>
```

{% endtab %}
{% endtabs %}

![(시청 앱의 실제 외양은 다를 수 있습니다.)](https://github.com/RemoteMonster/documents/tree/02c6158dc0e266b8b3ebb7cc7339759e1749eee8/.gitbook/assets/video.saramara.ai_sangyong_simplewatch-l.html-nexus-5.png)

## 그 외 주요 메소드들 <a href="#undefined-2" id="undefined-2"></a>

RemonCall의 주요 인스턴스 메소드는 다음과 같습니다.

* close() : RemonCall 객체가 소멸됩니다. 통화를 종료할 때 사용합니다. close 후에 새 통화를 시작하려면 RemonCall 객체를 새롭게 생성 해야 합니다.
* showLocalVideo(): 통화 시작 전에 자신의 카메라 화면을 미리 보고 싶을 때 호출합니다.
* pauseLocalVideo(true): 통화 중 자신의 카메라 화면을 중지하고 싶을 때 호출합니다.
* switchCamera(): 카메라가 여러 개일 때 카메라를 순차적으로 스위칭해서 보여줍니다.
* fetchCalls() : 통화채널을 조회할 때 호출합니다.

## Callback에 대하여 <a href="#callback" id="callback"></a>

* onInit(): RemonCall 객체 생성이 정상적으로 처리됐을 때 호출됩니다. token이 인자로 전달됩니다.
* onConnect(): 통화채널이 만들어졌을 때 호출됩니다. 생성된 채널ID가 인자로 전달됩니다.
* onComplete() : 통화 상대방과 연결이 됐을 때 호출됩니다.
* onClose(): 통화가 종료되었을 때 호출됩니다. CloseType이 인자로 전달됩니다.
* onError(): 에러가 발생했을 때 호출됩니다. 오류 메시지가 인자로 전달됩니다.
* onStat(): RemoteMonster가 통화품질 데이터를 5초마다 자동으로 생성합니다. 통화품질 데이터가 생성될 때 호출됩니다. 통화품질 데이터가 인자로 전달됩니다.

자세한 내용은 다음 문서를 참고합니다.

​[Callback과 Observer](https://app.gitbook.com/@remotemonster/s/remon-ko-20191001/~/drafts/-LsL6vPjeqajroIstiKC/primary/videocallplatform/inside-remoncall-sdk/callback-observer)​

단순 통화 앱을 만들며 RemoteMonster를 이용해 기능을 구현하는 방법을 알아보았습니다. Remon SDK의 내부를 알면 다양한 옵션을 선택할 수 있고, 원하는 것을 쉽게 구현할 수 있습니다.

​[Remon SDK의 내부](https://app.gitbook.com/@remotemonster/s/remon-ko-20191001/~/drafts/-LsL6vPjeqajroIstiKC/primary/videocallplatform/inside-remoncall-sdk)


