# RemoteMonster

환영합니다.

RemoteMonster는 방송플랫폼과 통화플랫폼을 서비스로 제공하고 있습니다. RemoteMonster의 서비스는 초저지연(Ultra Low Latency) 콘텐츠 전송 기술을 기반으로 하고 있습니다. RemoteMonster를 이용하면 지연 없이, 실시간 서비스를 만들 수 있습니다. RemoteMonster가 동영상과 음성이 최고의 품질로 전송되도록 네트워크를 책임집니다. RemoteMonster의 서비스는, 앱 개발이 편리하도록 SDK(Web, Android, iOS용), 개발가이드, 샘플 코드, 개발자커뮤니티 등을 포함하고 있으며, 서비스의 통제와 모니터링이 편리하도록 웹 콘솔을 포함하고 있습니다.

RemoteMonster의 SDK를 이용하면 방송 앱, 통화 앱을 빨리 만들 수 있습니다. RemoteMonster가 추구하는 방송은, 초저지연으로만 가능한 진짜 인터랙티브 라이브 스트리밍, 대화형 실시간 방송입니다. RemoteMonster가 추구하는 통화는, 앱에 들어간 통화, 앱에서 이탈하지 않는 매끄럽고 새로운 사용자 경험입니다. 또한, RemoteMonnnster가 추구하는 경험은, 방송과 통화를 통합하여, 원격의 출연자 간에 실시간 상호작용이 일어나고, 이를 실시간으로 시청하고, 시청자와 출연자가 실시간 상호작용 하는 새로운 사용자 경험입니다.

RemoteMonster의 강력한 초저지연 콘텐츠 전송 기술과 플랫폼을 지금 바로 사용하십시오.

## 통화

### [5분 안에 시작하기](/videocallplatform/videocall-quickstart)

빨리 통화를 체험해볼 수 있습니다.

### [튜토리얼](/videocallplatform/tutorial-simplevideocall)

단순 통화 앱을 만들어 봅니다.

### [SDK](/drafts/drafts/inside-remoncall-sdk)

SDK의 내부에 대해 자세히 알아봅니다.

### [API 레퍼런스](/videocallplatform/api)

API에 대해 자세히 알아봅니다.

### [통화 앱 예제](/videocallplatform/videocall-app-examples)

각종 기능 구현 방법을 예제로 알아봅니다.

## 방송

### [5분 안에 시작하기](/livestreamingplatform/livestreaming-quickstart)

빨리 방송과 시청을 체험해볼 수 있습니다.

### [튜토리얼](/livestreamingplatform/tutorial-simplelivestreaming-viewer)

단순 방송/시청 앱을 만들어 봅니다.

### [SDK](broken://pages/-LtK1DqQVpW1II4_VyAy)

SDK의 내부에 대해 자세히 알아봅니다.

### [API 레퍼런스](/livestreamingplatform/api)

API에 대해 자세히 알아봅니다.

### [방송 앱 예제](/livestreamingplatform/livestreaming-app-examples)

각종 기능 구현 방법을 예제로 알아봅니다.


# 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/-Lt8T_4SDxLKfff6PDnP)

### **샘플 앱으로 통화 실시**

아래 GitHub 웹 사이트를 방문합니다.

{% embed url="<https://github.com/RemoteMonster/remon-devguide-quickstart>" %}

아래와 같은 모습이 보입니다. simplevideocall-tutorial.html 파일을 웹에 게시할 겁니다. 본 가이드는 GitHub Pages를 이용합니다. https가 지원되는 환경이라면 다른 곳에 파일을 게시해도 좋습니다.

![](/files/-Lt8Nl6vBOXJFk3Gs36c)

우측 상단의 Fork 버튼을 클릭합니다. 내 저장소가 생겼습니다. 내 저장소의 주소는 아래와 같습니다.

```
https://github.com/내 아이디/remon-devguide-quickstart
```

저장소의  HTML 파일들을 웹에서 볼 수 있도록 설정합니다. "Settings" 탭으로 이동합니다. 페이지 아래에 "GitHub Pages" 섹션으로 이동하여, Source를 master branch로 선택합니다.&#x20;

![](/files/-Lt8OIvkAyrpwpTugZEJ)

![](/files/-Lt8OVx77qPSvDyi48ky)

HTML 파일을 아래 주소에서 확인할 수 있습니다. 아래와 같은 화면이 표시됩니다.

```
https://내 아이디.github.io/remon-devguide-quickstart/simplevideocall-tutorial.html
```

![](/files/-Lt8Qc3H_B-XAs8sv2kg)

접속이 잘 되는 것을 확인했으면, 앱에 Service ID와 Secret Key를 입력합니다. 내 저장소로 이동하여 simplevideocall-tutorial.html 파일을 클릭합니다. 아래와 같은 화면이 표시됩니다. 오른쪽 편집 아이콘(연필 모양)을 클릭합니다.

![](/files/-Lt8QkRkzBmwY2kkqe8C)

콘솔에서 확인한 Service ID와 Secret Key(Service Key)를 52번 행에 입력합니다. "Commit changes" 버튼을 눌러 저장합니다.

![](/files/-Lt8R050EcY-qU0HUM7z)

서로 다른 디바이스(예, 노트북과 스마트폰)에서 웹 브라우저를 열고 아래 주소를 각각의 창에서 방문합니다.

```
https://내 아이디.github.io/remon-devguide-quickstart/simplevideocall-tutorial.html
```

두 창에서 "시작" 버튼을 클릭합니다. Camera와 Microphone 사용을 **반드시** 허락(Allow)합니다.

![](/files/-Lt8Wf3Bnwk4HbkuE5hK)

잘 안 되신다면 아래 3가지를 먼저 확인해보시기 바랍니다.\
\- https 가 아니고 http로 접속하지 않았나?\
\- 카메라, 마이크 접근 권한을 허용하지 않았나?\
\- 내 저장소의 주소가 아니라 리모트몬스터 저장소의 주소에 접속하지 않았나?

### 웹 콘솔에서 이 통화에 대한 정보 조회

콘솔 창에서, "통화플랫폼 > 채널 및 세션" 메뉴로 이동합니다. 방금 만든 통화에 대한 정보를 조회할 수 있습니다.

![](/files/-Lt8ZUNCNmLbZUxMLR7H)

**축하합니다. 통화플랫폼이 정상적을 동작하는 것을 확인하였습니다.**

튜토리얼의 새 프로젝트 설정, 단순 통화 앱 만들기 등을 참고하여, 통화 앱을 원하는 대로 만들어보십시오.


# 튜토리얼 : 단순 통화 앱 만들기

### 공통

[키 발급](/videocallplatform/tutorial-simplevideocall/create-key)

### Web용

[새 프로젝트 설정(Web)](/videocallplatform/tutorial-simplevideocall/new-project-web)

[단순 통화 앱 만들기(Web)](/videocallplatform/tutorial-simplevideocall/simplevideocall-code-web)

### Android용

[새 프로젝트 설정(Android)](/videocallplatform/tutorial-simplevideocall/new-project-android)

[단순 통화 앱 만들기(Android)](/drafts/drafts/simplevideocall-code-android)

### iOS용

[새 프로젝트 설정(iOS)](/videocallplatform/tutorial-simplevideocall/new-project-ios)

[단순 통화 앱 만들기(iOS)](/drafts/drafts/simplevideocall-code-ios)


# 키 발급

## 개요

RemoteMonster를 이용하려면 Service ID와 Key를 반드시 발급 받아야 합니다. Service ID는 리모트몬스터 사용자가 만드는 앱에 부여됩니다. Key는 사용자가 만든 앱의 리모트몬스터 플랫폼 이용 권한을 확인하는데 사용됩니다. 한 사용자가 여러 앱을 만들 경우 Service ID와 Key 또한 여러 개가 필요합니다. ServiceID는 사용량과 과금액을 구분하는 데에도 사용됩니다.

## 리모트몬스터 웹 콘솔

리모트몬스터 웹 콘솔을 이용하면 앱에서 통화가 잘 이루어지고 있는지 확인할 수 있습니다.\
새로운 통화 앱을 만들 때, 이 앱을 위한 새로운 Service ID와 Key를 생성할 수 있습니다.

웹 콘솔은 아래 주소에서 이용할 수 있습니다.

<https://console.remotemonster.com/>

### 프로젝트(서비스) 생성

웹 콘솔에 로그인하면 아래와 같은 화면이 표시됩니다. "새 프로젝트 추가" 카드를 클릭하면 새 프로젝트(서비스)를 생성할 수 있습니다. 각각의 프로젝트(서비스)는 고유한 Service ID와 Secret Key를 갖고 있습니다.

![](/files/-LrrA--cZzXWWCdKr9gW)

프로젝트명은 각각의 프로젝트(서비스)를 사람이 구분할 수 있도록 하기위해 입력합니다. 프로젝트 생성 시 Service ID가 자동으로 생성됩니다. Service ID는 각각의 프로젝트(서비스)를 앱이 구분할 수 있도록 합니다.

![](/files/-LrrAGt323pQVPnphVhJ)

프로젝트(서비스)를 생성하지 않아도 기본 프로젝트가 이미 만들어져있습니다. 기본 프로젝트의 이름을 바꾸어 사용하면 아주 조금 빨리 시작할 수 있습니다.

![](/files/-LrrAML3c8BOxZVjldds)

새로 만든 프로젝트(서비스) 카드를 클릭하면 프로젝트의 상세 정보와 Service ID, Secret Key를 확인 할 수 있습니다.

![](/files/-LrrAR4-SYy-h2g6vCCQ)

Secret Key를 확인하려면 우측의 눈 아이콘을 클릭하십시오.

![(그림 속의 Service ID와 Secret Key는 유효하지 않습니다.)](/files/-LrrAWGannABST80VTt4)

## Service ID와 Secret Key 사용

프로젝트(서비스) 마다 사용량이 구분됩니다. 새로운 통화 앱을 만들 때 새로운 프로젝트(서비스)를 생성하길 권장합니다.

앱 소스코드에 Service ID와 Key를 Config의 적절한 위치에 삽입합니다. 자세한 내용은 다음 가이드를 참고합니다.

[새 프로젝트 설정(Web)](/videocallplatform/tutorial-simplevideocall/new-project-web)

[새 프로젝트 설정(Android)](/videocallplatform/tutorial-simplevideocall/new-project-android)

[새 프로젝트 설정(iOS)](https://github.com/RemoteMonster/remon-devguide-ko-2019/tree/eb0c6572bc39b0b2570a633d77a6749d62fef12a/videocallplatform/tutorial-simplevideocall/new-project-ios.md)


# 새 프로젝트 설정(Web)

## 준비사항 <a href="#undefined" id="undefined"></a>

* Project ID (Service ID) [키 발급](/videocallplatform/tutorial-simplevideocall/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 파일에 위 예시를 참고하여 삽입하십시오.&#x20;

```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파일의 예시는 다음과 같습니다.&#x20;

{% 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 클래스 알아보기](/drafts/drafts/inside-remoncall-sdk/remoncall)

프로젝트 설정을 완료했습니다. 이어서 기능을 구현에 대해 알아봅니다.

[단순 통화 앱 만들기(Web)](/videocallplatform/tutorial-simplevideocall/simplevideocall-code-web)


# 새 프로젝트 설정(Android)

## 준비사항

* Project ID (Service ID) [키 발급](/videocallplatform/tutorial-simplevideocall/create-key)
* 안드로이드 개발 환경, minSdkVersion 18 이상
* Java 1.8 이상

## 프로젝트 생성 및 설정

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

API Level 18이상으로 설정 합니다.

![](/files/-Ls0LXoJyTPFoqM-7KBq)

### Compatibility 설정

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

![](/files/-Ls0LXoLnUyPV97FqwwU)

### Module Gradle 설정

build.gradle(Module:app) 의 dependencies에 아래와 같이 추가합니다.

```groovy
dependencies {
    /* RemoteMonster SDK */
    api 'com.remotemonster:sdk:2.4.14'
}
```

그 외에 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"/>
```

프로젝트 설정을 완료했습니다. 이어서 기능을 구현에 대해 알아봅니다.

[단순 통화 앱 만들기(Android)](/drafts/drafts/simplevideocall-code-android)


# 새 프로젝트 설정(iOS)

## 준비 사항

* Project ID (Service ID) [키 발급](/videocallplatform/tutorial-simplevideocall/create-key)
* Xcode 개발 환경
* iOS 10.0 이상

## 새 프로젝트 생성 및 설정

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

프로젝트 생성 후 `Build Settings`에서 `Enable Bitcode`를 `No`로 선택합니다.

![Bitcode](/files/-LsGD6-_4vcyQdtK88NI)

Objective-C 환경이라면 ALWAYS\_EMBED\_SWIFT\_STANDARD\_LIBRARIES를 Yes로 선택합니다.

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

또한 Info.plist에서 다음 항목을 required로 선택합니다.

* Privacy: Bluetooth, Microphone, Camera

![Settings](/files/-LsGD6-dWy-6MLgYBTzD)

## 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 %}

Podfile 수정 후 터미널에서 `pod install` 를 실행합니다. 필요 시 `pod update`를 실행합니다.

```bash
$ pod install
```

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

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

## 수동으로 import하여 SDK 설치

아래의 링크를 통해 iOS SDK의 최신 버전을 다운로드 받아 풀어 놓습니다.

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

RemoteMonster iOS SDK 폴더 안에 다음 3개의 폴더가 있습니다. 이 3개의 폴더를 프로젝트 트리창에 놓습니다.

```
RemoteMonster.framework
SwiftProtobuf.framework
WebRTC.framework
```

![Framework](/files/-LsGD6-fPfhc82Nr5nld)

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

![Copy Frameworks](/files/-LsGD6-h3WKa818wbEZY)

## Service Id, Service Key 입력

* 스토리보드 파일을 열어 `RemonCall`을 추가합니다.
  * Xcode의 네비게이터 영역에서 스토리보드 파일을 찾아 엽니다. Object 컴포넌트를 삽입 후 Class를 RemonCall으로, Module은 RemoteMonster로 수정합니다.

![](/files/-LsGbb9A2jNkr1RAbP6T)

* Xcode의 유틸리티 영역에서 `Service Id`와 `Service Key`를 입력합니다.

![](/files/-LsGD6-jVrUp8bQvLxQl)

프로젝트 설정을 완료했습니다. 이어서 기능을 구현에 대해 알아봅니다.

[단순 통화 앱 만들기(iOS)](/drafts/drafts/simplevideocall-code-ios)


# 단순 통화 앱 만들기(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를 표시할 영역을 추가합니다. 시작 버튼을 눌렀을 때 영상이 보이지 않으면 video 요소의 autoplay 속성을 확인하십시오.

아래와 같은 모습을 만들겁니다. 안 예쁘다고요? 문서 뒷부분에서 예쁘게 해보겠습니다.

![](/files/-LsLpLJRN6j0wx5jgm62)

{% 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></video>
      나    
      <video id="localVideo" autoplay></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 %}

## 채널에 들어가기, 채널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></video> 
      나 
      <video id="localVideo" autoplay></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></video>
          </div>
          <div id="me" class="col-12 col-sm-6 col-md-6">
            <h2>나</h2>
            <video id="localVideo" class="localVideo" autoplay></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/-Lt3lf4WABKgcTPY4ijt)

## 그 외 주요 메소드들 <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)


# API 레퍼런스

* Web
  * <https://remotemonster.github.io/web-sdk/docs/Remon.html>
* Android
  * <https://remotemonster.github.io/android-sdk/index.html?com/remotemonster/sdk/RemonCall.html>
* iOS
  * <https://remotemonster.github.io/ios-sdk/Classes/RemonCall.html>


# 통화 앱 예제

## Web

### Simple Call (웹)

단순한 영상통화&#x20;

전체 코드 보기 : <https://github.com/RemoteMonster/web-sdk/blob/master/examples/full/simpleCall.html>

### Voice Call (웹)

단순한 음성통화

<https://github.com/RemoteMonster/web-sdk/blob/master/examples/full/voiceCall.html>

### Call List (웹)

활성 통화채널 목록 조회

<https://github.com/RemoteMonster/web-sdk/blob/master/examples/full/castList.html>

### Video Config (웹)

비디오 설정

<https://github.com/RemoteMonster/web-sdk/blob/master/examples/full/videoConfig.html>

### Screen Share (데스크탑 웹)

화면 공유

<https://github.com/RemoteMonster/web-sdk/blob/master/examples/full/screenShare.html>

### Camera Switch (웹)

카메라가 여러개 있을 경우 카메라 선택&#x20;

<https://github.com/RemoteMonster/web-sdk/blob/master/examples/full/cameraSwitch.html>

### Send message (웹)

<https://github.com/RemoteMonster/web-sdk/blob/master/examples/full/sendMessage.html>

### VolumeBar (웹)

볼륨 표시

<https://github.com/RemoteMonster/web-sdk/blob/master/examples/full/voiceCallWithVolumeBar.html>

## Android

### SimpleCall (Android)

<https://github.com/RemoteMonster/android-sdk/blob/master/examples/SimpleCall/app/src/main/java/com/remotemonster/example/simplecall/MainActivity.kt>

### Full (Android)

<https://github.com/RemoteMonster/android-sdk/tree/master/examples/full/app/src/main/java/com/remotemonster/sdktest/sample>

## iOS

### SimpleCall (iOS)

주요 코드

```swift
class SimpleCallViewController: UIViewController {
    // 생략
    
    // 1:1 통화를 위한 채널에 접속합니다.
    @IBAction func connectChannel(_ sender: Any) {
        self.view.endEditing(true)
        
        // 입력된 채널명(채널ID)
        let channelID = self.chField.text
        
        if channelID != nil && channelID!.count > 0 {
            
            // config는 별도 설정한 뒤에 전달해도 되며, nil 인 경우 remonCall 내부 프로퍼티 값을 사용하게 됩니다.
            // 내부 프로퍼티는 IB에서 설정할 수 있습니다.
            remonCall.connect(channelID!, customConfig)
            self.chLabel.text = channelID
        } else {
            
            // 랜덤번호를 채널명으로 사용하는 예
            let randChannelID:String! = String(arc4random_uniform(9999))
            remonCall.connect(randChannelID, customConfig)
            self.chLabel.text = randChannelID
        }
    }
    // 생략
}    
```

전체 코드

<https://github.com/RemoteMonster/ios-sdk/blob/master/examples/BaseExamples/SimpleRemon/SimpleCallViewController.swift>


# 그룹통화 만들기

그룹통화를 만드는 데에는 2가지 방식이 있습니다.

### 참여자간 통화를 여러개 만드는 방식

mesh architecture 라고도 합니다.

6명의 참여자가 있을 때, 한 참여자가 나머지 다섯 참여자에게 통화를 5개씩 연결하는 방식입니다. \
6명의 참여자가 있는 그룹통화는 총 15개의 통화 연결이 필요합니다.\
6명의 화면을 모두 표시하려면 각각의 영상의 해상도를 줄여서 표시합니다.

서버에 의존하는 부분이 상대적으로 적습니다.

여러 개의 통화 연결을 관리하는 부분이 부담될 수 있습니다.

### 참여자별로 방송을 만드는 방식

MCU(Multipoint Conferencing Unit), SFU(Selective Forwarding Unit) 과 관련이 있습니다.

7명의 참여자가 있을 때, 7개의 방송이 만들어집니다.\
한 참여자는 1개의 업로드 연결만 만들면 됩니다. 다운로드 연결은 MCU, SFU에 따라 1개 또는 여러개로 다양합니다.

연결을 관리하는 부분이 상대적으로 적습니다.

서버에 의존하는 부분이 큽니다.

### 예시

(준비 중)


# 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/-Lt8T_4SDxLKfff6PDnP)

## **샘플 앱으로 방송/시청 실시**

아래 GitHub 웹 사이트를 방문합니다.

{% embed url="<https://github.com/RemoteMonster/remon-devguide-quickstartlivestreaming/>" %}

아래와 같은 모습이 보입니다. simplelivestreaming.html, simplewatch.html 파일을 웹에 게시할 겁니다. 본 가이드는 GitHub Pages를 이용합니다. https가 지원되는 환경이라면 다른 곳에 파일을 게시해도 좋습니다.

![](/files/-Lt8Nl6vBOXJFk3Gs36c)

우측 상단의 Fork 버튼을 클릭합니다. 내 저장소가 생겼습니다. 내 저장소의 주소는 아래와 같습니다.

```
https://github.com/내 아이디/remon-devguide-quickstartlivestreaming
```

저장소의 HTML 파일들을 웹에서 볼 수 있도록 설정합니다. "Settings" 탭으로 이동합니다. 페이지 아래에 "GitHub Pages" 섹션으로 이동하여, Source를 master branch로 선택합니다.

![](/files/-Lt8OIvkAyrpwpTugZEJ)

![](/files/-Lt8OVx77qPSvDyi48ky)

HTML 파일을 아래 주소에서 확인할 수 있습니다. 아래와 같은 화면이 표시됩니다.

```
https://내 아이디.github.io/remon-devguide-quickstartlivestreaming/simplelivestreaming.html
```

```
https://내 아이디.github.io/remon-devguide-quickstartlivestreaming/simplewatch.html
```

![](/files/-Lt9MFqWvCLRF5FOQ_yG)

접속이 잘 되는 것을 확인했으면, 앱에 Service ID와 Secret Key를 입력합니다. 내 저장소로 이동하여 simplelivestreaming.html, simplewatch.html 파일을 클릭합니다. 아래와 같은 화면이 표시됩니다. 오른쪽 편집 아이콘(연필 모양)을 클릭합니.

![](/files/-Lt8QkRkzBmwY2kkqe8C)

콘솔에서 확인한 Service ID와 Secret Key(Service Key)를 52번 행에 입력합니다. "Commit changes" 버튼을 눌러 저장합니다.

![](/files/-Lt8R050EcY-qU0HUM7z)

웹 브라우저 창을 2개 엽니다. 아래 주소를 각각의 창에서 방문합니다.

```
https://내 아이디.github.io/remon-devguide-quickstartlivestreaming/simplelivestreaming.html
```

```
https://내 아이디.github.io/remon-devguide-quickstartlivestreaming/simplewatch.html
```

두 창에서 "시작" 버튼을 클릭합니다. Camera와 Microphone 사용을 **반드시** 허락(Allow)합니다.

## 웹 콘솔에서 이 방송에 대한 정보 조회

콘솔 창에서, "방송플랫폼 > 채널 및 세션" 메뉴로 이동합니다. 방금 만든 방송에 대한 정보를 조회할 수 있습니다.

![](/files/-Lt9MmIeUBrUVHGpE8YE)

**축하합니다. 방송플랫폼이 정상적을 동작하는 것을 확인하였습니다.**

튜토리얼의 새 프로젝트 설정, 단순 시청 앱 만들기 등을 참고하여, 방송/시청 앱을 원하는 대로 만들어보십시오.


# 튜토리얼 : 단순 방송/시청 앱 만들기

### 공통

[키 발급](/livestreamingplatform/tutorial-simplelivestreaming-viewer/create-key)

### Web용

[새 프로젝트 설정(Web)](/livestreamingplatform/tutorial-simplelivestreaming-viewer/new-project-web)

[단순 시청 앱 만들기(Web)](/livestreamingplatform/tutorial-simplelivestreaming-viewer/simplelivestreaming-code-web)

### Android용

[새 프로젝트 설정(Android)](/livestreamingplatform/tutorial-simplelivestreaming-viewer/new-project-android)

[단순 시청 앱 만들기(Android)](/drafts/drafts/simplelivestreaming-code-android)

### iOS용

[새 프로젝트 설정(iOS)](/livestreamingplatform/tutorial-simplelivestreaming-viewer/new-project-ios)

[단순 시청 앱 만들기(iOS)](/drafts/drafts/simplelivestreaming-code-ios)


# 키 발급

## 개요

RemoteMonster를 이용하려면 Service ID와 Key를 반드시 발급 받아야 합니다. Service ID는 리모트몬스터 사용자가 만드는 앱에 부여됩니다. Key는 사용자가 만든 앱의 리모트몬스터 플랫폼 이용 권한을 확인하는데 사용됩니다. 한 사용자가 여러 앱을 만들 경우 Service ID와 Key 또한 여러 개가 필요합니다. ServiceID는 사용량과 과금액을 구분하는 데에도 사용됩니다.

## 리모트몬스터 웹 콘솔

리모트몬스터 웹 콘솔을 이용하면 앱에서 통화가 잘 이루어지고 있는지 확인할 수 있습니다.\
새로운 통화 앱을 만들 때, 이 앱을 위한 새로운 Service ID와 Key를 생성할 수 있습니다.

웹 콘솔은 아래 주소에서 이용할 수 있습니다.

<https://console.remotemonster.com/>

### 프로젝트(서비스) 생성

웹 콘솔에 로그인하면 아래와 같은 화면이 표시됩니다. "새 프로젝트 추가" 카드를 클릭하면 새 프로젝트(서비스)를 생성할 수 있습니다. 각각의 프로젝트(서비스)는 고유한 Service ID와 Secret Key를 갖고 있습니다.

![](/files/-LrrA--cZzXWWCdKr9gW)

프로젝트명은 각각의 프로젝트(서비스)를 사람이 구분할 수 있도록 하기위해 입력합니다. 프로젝트 생성 시 Service ID가 자동으로 생성됩니다. Service ID는 각각의 프로젝트(서비스)를 앱이 구분할 수 있도록 합니다.

![](/files/-LrrAGt323pQVPnphVhJ)

프로젝트(서비스)를 생성하지 않아도 기본 프로젝트가 이미 만들어져있습니다. 기본 프로젝트의 이름을 바꾸어 사용하면 아주 조금 빨리 시작할 수 있습니다.

![](/files/-LrrAML3c8BOxZVjldds)

새로 만든 프로젝트(서비스) 카드를 클릭하면 프로젝트의 상세 정보와 Service ID, Secret Key를 확인 할 수 있습니다.

![](/files/-LrrAR4-SYy-h2g6vCCQ)

Secret Key를 확인하려면 우측의 눈 아이콘을 클릭하십시오.

![(그림 속의 Service ID와 Secret Key는 유효하지 않습니다.)](/files/-LrrAWGannABST80VTt4)

## Service ID와 Secret Key 사용

프로젝트(서비스) 마다 사용량이 구분됩니다. 새로운 통화 앱을 만들 때 새로운 프로젝트(서비스)를 생성하길 권장합니다.

앱 소스코드에 Service ID와 Key를 Config의 적절한 위치에 삽입합니다. 자세한 내용은 다음 가이드를 참고합니다.

[새 프로젝트 설정(Web)](/livestreamingplatform/tutorial-simplelivestreaming-viewer/new-project-web)

[새 프로젝트 설정(Android)](/livestreamingplatform/tutorial-simplelivestreaming-viewer/new-project-android)

[새 프로젝트 설정(iOS)](https://github.com/RemoteMonster/remon-devguide-ko-2019/tree/eb0c6572bc39b0b2570a633d77a6749d62fef12a/videocallplatform/tutorial-simplevideocall/new-project-ios.md)


# 새 프로젝트 설정(Web)

## 준비사항 <a href="#undefined" id="undefined"></a>

* Project ID (Service ID) [키 발급](/livestreamingplatform/tutorial-simplelivestreaming-viewer/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)


# 새 프로젝트 설정(Android)

## 준비사항

* Project ID (Service ID) [키 발급](/livestreamingplatform/tutorial-simplelivestreaming-viewer/create-key)
* 안드로이드 개발 환경, minSdkVersion 18 이상
* Java 1.8 이상

## 프로젝트 생성 및 설정

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

API Level 18이상으로 설정 합니다.

![](/files/-Ls0LXoJyTPFoqM-7KBq)

### Compatibility 설정

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

![](/files/-Ls0LXoLnUyPV97FqwwU)

### Module Gradle 설정

build.gradle(Module:app) 의 dependencies에 아래와 같이 추가합니다.

```groovy
dependencies {
    /* RemoteMonster SDK */
    api 'com.remotemonster:sdk:2.4.14'
}
```

그 외에 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"/>
```

프로젝트 설정을 완료했습니다. 이어서 기능을 구현에 대해 알아봅니다.

[단순 통화 앱 만들기(Android)](https://github.com/RemoteMonster/remon-devguide-ko-2019/tree/f32c3e15637cf2b089461da0c2b5c5b8b154cbe3/livestreamingplatform/tutorial-simplelivestreaming-viewer/simplevideocall-code-android.md)


# 새 프로젝트 설정(iOS)

## 준비 사항

* Project ID (Service ID) [키 발급](/livestreamingplatform/tutorial-simplelivestreaming-viewer/create-key)
* Xcode 개발 환경
* iOS 10.0 이상

## 새 프로젝트 생성 및 설정

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

프로젝트 생성 후 `Build Settings`에서 `Enable Bitcode`를 `No`로 선택합니다.

![Bitcode](/files/-LsGD6-_4vcyQdtK88NI)

Objective-C 환경이라면 ALWAYS\_EMBED\_SWIFT\_STANDARD\_LIBRARIES를 Yes로 선택합니다.

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

또한 Info.plist에서 다음 항목을 required로 선택합니다.

* Privacy: Bluetooth, Microphone, Camera

![Settings](/files/-LsGD6-dWy-6MLgYBTzD)

## 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 %}

Podfile 수정 후 터미널에서 `pod install` 를 실행합니다. 필요 시 `pod update`를 실행합니다.

```bash
$ pod install
```

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

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

## 수동으로 import하여 SDK 설치

아래의 링크를 통해 iOS SDK의 최신 버전을 다운로드 받아 풀어 놓습니다.

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

RemoteMonster iOS SDK 폴더 안에 다음 3개의 폴더가 있습니다. 이 3개의 폴더를 프로젝트 트리창에 놓습니다.

```
RemoteMonster.framework
SwiftProtobuf.framework
WebRTC.framework
```

![Framework](/files/-LsGD6-fPfhc82Nr5nld)

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

![Copy Frameworks](/files/-LsGD6-h3WKa818wbEZY)

## Service Id, Service Key 입력

* 스토리보드 파일을 열어 `RemonCall`을 추가합니다.
  * Xcode의 네비게이터 영역에서 스토리보드 파일을 찾아 엽니다. Object 컴포넌트를 삽입 후 Class를 RemonCall으로, Module은 RemoteMonster로 수정합니다.

![](/files/-LsGbb9A2jNkr1RAbP6T)

* Xcode의 유틸리티 영역에서 `Service Id`와 `Service Key`를 입력합니다.

![](/files/-LsGD6-jVrUp8bQvLxQl)

프로젝트 설정을 완료했습니다. 이어서 기능을 구현에 대해 알아봅니다.

[단순 통화 앱 만들기(iOS)](https://github.com/RemoteMonster/remon-devguide-ko-2019/tree/f32c3e15637cf2b089461da0c2b5c5b8b154cbe3/livestreamingplatform/tutorial-simplelivestreaming-viewer/simplevideocall-code-ios.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 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'
    }
  };
  </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 %}

![(시청 앱의 실제 외양은 다를 수 있습니다.)](/files/-LtOPOEx04CPm40MdSHW)

## 그 외 주요 메소드들 <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)


# API 레퍼런스

* Web
  * <https://remotemonster.github.io/web-sdk/docs/Remon.html>
* Android
  * <https://remotemonster.github.io/android-sdk/index.html?com/remotemonster/sdk/RemonCast.html>
* iOS
  * <https://remotemonster.github.io/ios-sdk/Classes/RemonCast.html>


# 방송 앱 예제

리모트몬스터의 샘플 웹 사이트를 확인해주십시오.

<https://sample.remotemonster.com/>


# 그룹통화 만들기(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" ) { particpant:RemonParticipant ->
}.on( "onUserLeft" ) { 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와 동일
    // updateView()

}.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") { participant 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를 이용하여 통화 또는 방송 구현 경험이 있으면 이해에 도움이 됩니다. 통화 또는 방송 튜토리얼을 학습한 후에 그룹통화를 학습하기를 권장합니다.&#x20;

## &#x20;그룹통화 샘플 앱

* <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>


# FAQ

## Android, iOS용 샘플 앱 빌드가 안 됩니다.

빌드 환경의 편차가 커서, 환경에 따라 빌드가 안 되는 경우가 간혹 있습니다. [리모트몬스터 커뮤니티](https://community.remotemonster.com/)에 오류 메시지와 함께 질문을 남겨주십시오.&#x20;

## 방송/통화 시작 전에 카메라를 선택하고 싶습니다.

채널에 접속하기 전에 카메라를 선택하면 됩니다. 아래 예제를 참고하시기 바랍니다.

### 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\(cs\)RemonClient\(im\)switchCameraWithIsMirror: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>


# 초안


# FAQ1

### Android, iOS용 샘플 앱 빌드가 안 됩니다.


# 그룹통화 개념


# 채널ID 규칙

채널ID(Channel ID)는 개발자가 지정할 수 있습니다. 채널 생성 시 채널ID를 지정하지 않으면 임의로 생성됩니다. 채널ID는 아래 사항을 주의해주십시오.

* 영문자, 숫자, 하이픈(-), 언더스코어(\_)만 사용\
  \- 특수문자( : ; \* $ \` 등)를 사용하지 않음\
  \- 한글을 사용하지 않음
* 최소 4자 최대 1024자

채널ID는 각각의 방송 또는 통화에 대해 고유하고 추측이 어렵게 만들어야합니다.\
동일한 채널ID로 여러 번의 방송 또는 통화를 만드는 것은 권장하지 않습니다.

방송채널의 ID는 방송자가 명시적으로 채널을 생성하는 시각부터 방송자가 채널을 종료하는 시각까지 유효합니다. 방송자가 영상을 송출하지 않아도 채널을 생성하기만해도 유효합니다. 채널이 유효하면, 시청자가 이 채널에 들어갈 수 있습니다.

통화채널의 ID는 통화 송신자가 채널에 연결한 시각부터 송신자 또는 수신자 둘 중 어느 한 쪽이 연결을 해제한 시각까지 유효합니다. 과금은 양측이 연결되어 실제 통화가 이루어진 시간을 대상으로 합니다.


# 빨리 방송 시작하기


# 단순 시청 앱 만들기(Android) (초안)

## 단순 시청 앱 만들기(Android)

### 레이아웃 작업

방송과 통화 모두 음성전용이 아니라면 화면에 카메라 화면이나 상대편의 화면을 표시해야 합니다. 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'라는 이름의 방송을 시청합니다.

**방송 목록 검색**

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

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

remonCast.join(myChannelId);
```

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

### 그 외 주요 메소드들

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

* close() : RemonCast 객체가 소멸됩니다. 방송을 종료할 때 사용합니다. close 후에 새 방송을 시작하려면 RemonCast 객체를 새롭게 생성해야 합니다.
* showLocalVideo(): 방송 시작 전에 자신의 카메라 화면을 미리 보고 싶을때 호출합니다.
* pauseLocalVideo(true): 방송 중 자신의 카메라 화면을 중지하고 싶을 때 호출합니다.
* switchCamera(): 카메라가 여러 개일 때 카메라를 순차적으로 스위칭해서 보여줍니다.
* fetchCasts: 방송채널 조회를 위한 메소드입니다.
  * fetchCalls: 통화채널 조회를 위한 메소드입니다. 통화에서만 사용하는 메소드입니다.

### Callback에 대하여

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

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

## 단순 통화 앱 만들기(Android)

### 레이아웃 작업

방송과 통화 모두 음성전용이 아니라면 화면에 카메라 화면이나 상대편의 화면을 표시해야 합니다. 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를 구동시켜볼 준비가 완료되었습니다.

### 통화

`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)
```

### 그 외 주요 메소드들

RemonCall의 주요 인스턴스 메소드는 다음과 같습니다.

* close() : RemonCall 객체가 소멸됩니다. 통화를 종료할 때 사용합니다. close 후에 새 통화를 시작하려면 RemonCall 객체를 새롭게 생성 해야 합니다.
* showLocalVideo(): 통화 시작 전에 자신의 카메라 화면을 미리 보고 싶을 때 호출합니다.
* pauseLocalVideo(true): 통화 중 자신의 카메라 화면을 중지하고 싶을 때 호출합니다.
* switchCamera(): 카메라가 여러 개일 때 카메라를 순차적으로 스위칭해서 보여줍니다.
* fetchCalls() : 통화채널을 조회할 때 호출합니다.

### Callback에 대하여

* onInit(): RemonCall 객체 생성이 정상적으로 처리됐을 때 호출됩니다. token이 인자로 전달됩니다.
* onConnect(): 통화채널이 만들어졌을 때 호출됩니다. 생성된 채널ID가 인자로 전달됩니다.&#x20;
* onComplete() : 통화 상대방과 연결이 됐을 때 호출됩니다.&#x20;
* onClose(): 통화가 종료되었을 때 호출됩니다. CloseType이 인자로 전달됩니다.&#x20;
* onError(): 에러가 발생했을 때 호출됩니다. 오류 메시지가 인자로 전달됩니다.&#x20;
* onStat(): RemoteMonster가 통화품질 데이터를 5초마다 자동으로 생성합니다. 통화품질 데이터가 생성될 때 호출됩니다. 통화품질 데이터가 인자로 전달됩니다.

자세한 내용은 다음 문서를 참고합니다.

[Callback과 Observer](https://github.com/RemoteMonster/remon-devguide-ko-2019/tree/b0fd0a1cb7ea2076ffc914d105de5b01b945a9b5/livestreamingplatform/inside-remoncall-sdk/callback-observer.md)

단순 통화 앱을 만들며 RemoteMonster를 이용해 기능을 구현하는 방법을 알아보았습니다. Remon SDK의 내부를 알면 다양한 옵션을 선택할 수 있고, 원하는 것을 쉽게 구현할 수 있습니다.

[Remon SDK의 내부](https://github.com/RemoteMonster/remon-devguide-ko-2019/tree/b0fd0a1cb7ea2076ffc914d105de5b01b945a9b5/livestreamingplatform/inside-remoncall-sdk/README.md)


# 단순 시청 앱 만들기(iOS) (초안)

## 단순 시청 앱 만들기(iOS)

### 방송

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

#### 방송 송출

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

#### 방송 시청

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

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

## 단순 통화 앱 만들기(iOS)

영상통화 기능을 구현합니다.

### 통신

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

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

### 레이아웃 구성 : 영상통화 화면

* 스토리보드에서 Scene을 선택하고 내 영상이 표시될 View와 상대방의 영상이 표시될 View를 배치합니다. `RemonCall`의 `remoteView`와 `localView`에 바인딩합니다.

![](/files/-LsGD6-lIHKhmS1uy4XW)

* `RemonCall` 을 사용하는 `ViewContoller`에 RemoteMonster SDK를 임포트 하고, `RemonCall`객체를 아웃렛 변수에 바인딩 합니다.

![](/files/-LsGD6-nZt7ri36HJC_G)

### 그 외 주요 메소드들

RemonCall의 주요 인스턴스 메소드는 다음과 같습니다.

* close() : RemonCall 객체가 소멸됩니다. 통화를 종료할 때 사용합니다. close 후에 새 통화를 시작하려면 RemonCall 객체를 새롭게 생성 해야 합니다.
* showLocalVideo(): 통화 시작 전에 자신의 카메라 화면을 미리 보고 싶을 때 호출합니다.
* pauseLocalVideo(true): 통화 중 자신의 카메라 화면을 중지하고 싶을 때 호출합니다.
* switchCamera(): 카메라가 여러 개일 때 카메라를 순차적으로 스위칭해서 보여줍니다.
* fetchCalls() : 통화채널을 조회할 때 호출합니다.&#x20;

### Callback에 대하여

* onInit(): RemonCall 객체 생성이 정상적으로 처리됐을 때 호출됩니다. token이 인자로 전달됩니다.
* onConnect(): 통화채널이 만들어졌을 때 호출됩니다. 생성된 채널ID가 인자로 전달됩니다.
* onComplete() : 통화 상대방과 연결이 됐을 때 호출됩니다.
* onClose(): 통화가 종료되었을 때 호출됩니다. CloseType이 인자로 전달됩니다.
* onError(): 에러가 발생했을 때 호출됩니다. 오류 메시지가 인자로 전달됩니다.&#x20;
* onStat(): RemoteMonster가 통화품질 데이터를 5초마다 자동으로 생성합니다. 통화품질 데이터가 생성될 때 호출됩니다. 통화품질 데이터가 인자로 전달됩니다.

자세한 내용은 다음 문서를 참고합니다.

[Callback과 Observer](https://github.com/RemoteMonster/remon-devguide-ko-2019/tree/b0fd0a1cb7ea2076ffc914d105de5b01b945a9b5/livestreamingplatform/inside-remoncall-sdk/callback-observer.md)

단순 통화 앱을 만들며 RemoteMonster를 이용해 기능을 구현하는 방법을 알아보았습니다. Remon SDK의 내부를 알면 다양한 옵션을 선택할 수 있고, 원하는 것을 쉽게 구현할 수 있습니다.

[Remon SDK의 내부](https://github.com/RemoteMonster/remon-devguide-ko-2019/tree/b0fd0a1cb7ea2076ffc914d105de5b01b945a9b5/livestreamingplatform/inside-remoncall-sdk/README.md)


# 단순 통화 앱 만들기(iOS) (초안)

영상통화 기능을 구현합니다.

## 통신

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

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

## 레이아웃 구성 : 영상통화 화면

* 스토리보드에서 Scene을 선택하고 내 영상이 표시될 View와 상대방의 영상이 표시될 View를 배치합니다. `RemonCall`의 `remoteView`와 `localView`에 바인딩합니다.

![](/files/-LsGD6-lIHKhmS1uy4XW)

* `RemonCall` 을 사용하는 `ViewContoller`에 RemoteMonster SDK를 임포트 하고, `RemonCall`객체를 아웃렛 변수에 바인딩 합니다.

![](/files/-LsGD6-nZt7ri36HJC_G)

## 그 외 주요 메소드들

RemonCall의 주요 인스턴스 메소드는 다음과 같습니다.

* close() : RemonCall 객체가 소멸됩니다. 통화를 종료할 때 사용합니다. close 후에 새 통화를 시작하려면 RemonCall 객체를 새롭게 생성 해야 합니다.
* showLocalVideo(): 통화 시작 전에 자신의 카메라 화면을 미리 보고 싶을 때 호출합니다.
* pauseLocalVideo(true): 통화 중 자신의 카메라 화면을 중지하고 싶을 때 호출합니다.
* switchCamera(): 카메라가 여러 개일 때 카메라를 순차적으로 스위칭해서 보여줍니다.
* fetchCalls() : 통화채널을 조회할 때 호출합니다.&#x20;

## Callback에 대하여

* onInit(): RemonCall 객체 생성이 정상적으로 처리됐을 때 호출됩니다. token이 인자로 전달됩니다.
* onConnect(): 통화채널이 만들어졌을 때 호출됩니다. 생성된 채널ID가 인자로 전달됩니다.
* onComplete() : 통화 상대방과 연결이 됐을 때 호출됩니다.
* onClose(): 통화가 종료되었을 때 호출됩니다. CloseType이 인자로 전달됩니다.
* onError(): 에러가 발생했을 때 호출됩니다. 오류 메시지가 인자로 전달됩니다.&#x20;
* onStat(): RemoteMonster가 통화품질 데이터를 5초마다 자동으로 생성합니다. 통화품질 데이터가 생성될 때 호출됩니다. 통화품질 데이터가 인자로 전달됩니다.

자세한 내용은 다음 문서를 참고합니다.

[Callback과 Observer](/drafts/drafts/inside-remoncall-sdk/callback-observer)

단순 통화 앱을 만들며 RemoteMonster를 이용해 기능을 구현하는 방법을 알아보았습니다. Remon SDK의 내부를 알면 다양한 옵션을 선택할 수 있고, 원하는 것을 쉽게 구현할 수 있습니다.

[Remon SDK의 내부](/drafts/drafts/inside-remoncall-sdk)


# 단순 통화 앱 만들기(Android) (초안)

## 레이아웃 작업

방송과 통화 모두 음성전용이 아니라면 화면에 카메라 화면이나 상대편의 화면을 표시해야 합니다. 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를 구동시켜볼 준비가 완료되었습니다.

## 통화

`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)
```

## 그 외 주요 메소드들

RemonCall의 주요 인스턴스 메소드는 다음과 같습니다.

* close() : RemonCall 객체가 소멸됩니다. 통화를 종료할 때 사용합니다. close 후에 새 통화를 시작하려면 RemonCall 객체를 새롭게 생성 해야 합니다.
* showLocalVideo(): 통화 시작 전에 자신의 카메라 화면을 미리 보고 싶을 때 호출합니다.
* pauseLocalVideo(true): 통화 중 자신의 카메라 화면을 중지하고 싶을 때 호출합니다.
* switchCamera(): 카메라가 여러 개일 때 카메라를 순차적으로 스위칭해서 보여줍니다.
* fetchCalls() : 통화채널을 조회할 때 호출합니다.

## Callback에 대하여

* onInit(): RemonCall 객체 생성이 정상적으로 처리됐을 때 호출됩니다. token이 인자로 전달됩니다.
* onConnect(): 통화채널이 만들어졌을 때 호출됩니다. 생성된 채널ID가 인자로 전달됩니다.&#x20;
* onComplete() : 통화 상대방과 연결이 됐을 때 호출됩니다.&#x20;
* onClose(): 통화가 종료되었을 때 호출됩니다. CloseType이 인자로 전달됩니다.&#x20;
* onError(): 에러가 발생했을 때 호출됩니다. 오류 메시지가 인자로 전달됩니다.&#x20;
* onStat(): RemoteMonster가 통화품질 데이터를 5초마다 자동으로 생성합니다. 통화품질 데이터가 생성될 때 호출됩니다. 통화품질 데이터가 인자로 전달됩니다.

자세한 내용은 다음 문서를 참고합니다.

[Callback과 Observer](/drafts/drafts/inside-remoncall-sdk/callback-observer)

단순 통화 앱을 만들며 RemoteMonster를 이용해 기능을 구현하는 방법을 알아보았습니다. Remon SDK의 내부를 알면 다양한 옵션을 선택할 수 있고, 원하는 것을 쉽게 구현할 수 있습니다.

[Remon SDK의 내부](/drafts/drafts/inside-remoncall-sdk)


# 통화 SDK (초안)

SDK의 내부에 대해 자세히 알아봅니다.


# RemonCall

통화 기능을 구현하려면 RemonCall 클래스를 이용합니다. RemonCall 클래스의 connect() 메소드를 이용하여 채널 생성 및 접속이 가능합니다.

전체적인 구성과 흐름은 아래를 참고하십시오.

## View 등록

통화중 스스로의 모습을 보거나 상대방의 모습을 보기위한 뷰가 필요합니다. 자기 자신의 모습은 Local View, 상대방의 모습은 Remote View로 등록을 합니다.

{% tabs %}
{% tab %}

{% endtab %}
{% endtabs %}

```javascript
<!-- local view -->
<video id="localVideo" autoplay muted></video>
<!-- remote view -->
<video id="remoteVideo" autoplay></video>
```

```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>
```

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

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

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

## 통화채널 생성

`connect()` 함수에 전달한 `channelId` 값에 해당하는 채널이 존재하지 않으면 채널이 생성되고, 다른 사용자가 해당 채널에 연결 하기를 대기 하는 상태가 됩니다. 이때 해당 `channelId`로 다른 사용자가 연결을 시도 하면 연결이 완료 되고, 통화이 시작 됩니다.

{% tabs %}
{% 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="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을 제공 합니다.

* 안드로이드 2.4.13, iOS 2.6.9 버전부터 콜백은 모두 UI Thread 에서 호출됩니다.

{% tabs %}
{% 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 %}

더 많은 내용은 아래를 참조 하세요.

## Channel <a href="#channels" id="channels"></a>

랜덤채팅등과 같은 서비스에서는 전체 채널 목록을 필요로 하게 됩니다. 이를 위한 전체 채널 목록을 제공합니다.

{% tabs %}
{% 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/-LrdFLLVFvMv0MqYXGxD" %}
[Channel](/drafts/drafts/inside-remoncall-sdk/channel)
{% endcontent-ref %}

## 종료 <a href="#undefined-4" id="undefined-4"></a>

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

{% tabs %}
{% 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/-LrdFI\_1su5Sef8BYUhw" %}
[Config](/drafts/drafts/inside-remoncall-sdk/config)
{% endcontent-ref %}


# Config

통화는 RemonCall 객체를 통해 이루어집니다. RemonCall 객체에 설정 정보를 지정할 수 있습니다. 설정 정보는 Config 객체로 생성하여, RemoCall 객체 생성 시 인자로 전달합니다. Config 객체를 통화 view가 아닌 곳에서 생성하여 통화 view로 전달할 수 있어 편리합니다.

RemonCall 객체 생성 시 Config를 전달하는 방법 외에도, RemonCall의 connnect() 메소드 호출 시 Config를 인자로 전달하는 방법도 있습니다. 이 방법을 이용하면 RemonCall 객체 생성 시 사용한 Config을 사용하지 않고 connect() 메소드에게 전달된 Config를 사용합니다.

플랫폼 별로 클래스명이 조금 다릅니다. Web, Android용은 Config, iOS용은 RemonConfig 클래스를 참고하십시오.

* <https://remotemonster.github.io/web-sdk/docs/module-Config.html>
* <https://remotemonster.github.io/android-sdk/index.html?com/remotemonster/sdk/Config.html>
* <https://remotemonster.github.io/ios-sdk/Classes/RemonConfig.html>

## Basics

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

### 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

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

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

### 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`가 제대로 동작하지 않습니다.

{% 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);
```

{% endtab %}

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

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

{% 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 %}

### Music / Voice Mode

영상, 음성모드일때 음성의 작동방식을 설정 할 수 있습니다. Voice 모드는 노이즈를 억제하고 음성을 명확하게 전달하는데 초점이 맞추어져 있으며 통화에 적합합니다. Music모드는 모든 소리를 왜곡 없이 명확하게 전달하는데 중점이 있습니다. 기본값은 Voice 입니다.

{% tabs %}
{% tab title="Web" %}
N/A
{% 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는 동적으로 해당기능을 제공하지 않으며 다음과 같이 설정하여야 합니다.

![](https://github.com/RemoteMonster/remon-devguide-ko-2019/tree/0317e4ceb2c6bc098862158bda529753fb8e59b4/videocallplatform/.gitbook/assets/remonsettings%20\(1\).png)

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

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

![](https://github.com/RemoteMonster/remon-devguide-ko-2019/tree/0317e4ceb2c6bc098862158bda529753fb8e59b4/videocallplatform/.gitbook/assets/remonsettings%20\(1\).png)

`RemonSettings.plist` 파일을 프로젝트에 추가하고, AudioType 값을 원하는 모드로 변경해 주세요.
{% 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 %}


# 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은 아래를 참고하세요.

## 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 %}


# Video/Audio 옵션

## Overview

최적의 미디어 품질을 얻기위한 안내입니다.

기본적인 설정과 플렛폼별 안내는 아래를 참고하세요.

{% content-ref url="/pages/-LrdFI\_1su5Sef8BYUhw" %}
[Config](/drafts/drafts/inside-remoncall-sdk/config)
{% endcontent-ref %}

## Bitrate

영상의 품질과 네트워크 환경에 영향을 받습니다. maxBitrate 로 조절이 가능합니다. 지나치게 낮은 maxBitrate는 인코더에 추가적인 부하로인해 품질을 저하할 수 있습니다.

{% content-ref url="/pages/-LrdFI\_1su5Sef8BYUhw" %}
[Config](/drafts/drafts/inside-remoncall-sdk/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

여러 품질의 영상을 제공하고 수신자가 품질을 선택할 수 있도록 합니다.

## 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/>" %}


# Callback과 Observer

## Overview

`RemonCast`, `RemonCall`의 간단한 코드 만으로 통신 및 방송이 가능 합니다. 사용자의 필요에 따라 UI처리 및 추가 작업이 필요한 경우가 발생 합니다. 아래의 다양한 Callback을 통해 보다 세부적인 개발이 가능합니다.

안드로이드 2.4.13, iOS 2.6.9 버전부터 콜백은 모두 UI Thread 에서 호출됩니다. 이전 버전의 경우 UI 요소의 변경 시 UI Thread 와 관련한 처리를 추가해야 합니다.

방송과 통신은 각각에 적합한 이벤트와 흐름을 가지고 있습니다. 이를 알아두면 Callback를 활용하는데 도움이 됩니다. 이에 대한 내용은 아래를 참고하세요.

{% content-ref url="/pages/-LrdFLLVFvMv0MqYXGxD" %}
[Channel](/drafts/drafts/inside-remoncall-sdk/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 코드에 대해 자세히 알고 싶으시면 아래 내용을 참고하시기 바랍니다.

### 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 %}

{% endtab %}
{% endtabs %}

N/A

N/A

```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
}
```

아래의 링크를 통해 구체적인 응용 예시를 확인할 수 있습니다.

```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;
    }];
```

아래의 링크를 통해 구체적인 응용 예시를 확인할 수 있습니다.

### onStat(report)

통신 / 방송 상태를 알수있는 `report`를 받습니다. `report`는 사용자가 `remon` 생성시 설정한 `statInterval`간격 마다 들어오게 됩니다. 네트워크 상황등에 따른 미디어 품질을 나타냄으로 사용자에게 로딩 UI 처리등 안내를 하는데 유용합니다.

이때, 들어오는 값은 영상 및 음성 통화 중에 현재 통화의 품질이 어떠한지를 통합하여 1에서 5까지의 단계로 확인할 수 있습니다.

사용자는 간혹 자신 혹은 상대방의 네트워크 문제로 인하여 통화 품질이 안좋거나 끊어진 상황에서도 서비스의 문제라고 생각하고 불만을 제기할 수 있습니다. 때문에 사용자의 문제가 네트워크의 문제임을 사전에 알려주거나 다양한 UI 처리가 가능합니다.

현재 이 통화 품질 정보는 5초에 한번씩 받을 수 있습니다.

| 단계 | 품질        | 비고 |
| -- | --------- | -- |
| 1  | 매우 좋음     |    |
| 2  | 좋음        |    |
| 3  | 나쁨        |    |
| 4  | 매우 나쁨     |    |
| 5  | 방송, 통화 불능 |    |

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

```swift
let remonCall = RemonCall()
remoCall.onRemonStatReport{ (stat) in 
    let rating:RatingValue = stat.getRttRating()
    let level = rating.levle
}
self.showRemoteVideoStat = true //stat 정보가 영상 위에 오버레이 됩니다.
```

{% endtab %}

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

```objectivec
[self.remonCast onRemonStatReportWithBlock:^(RemonStatReport * _Nonnull stat) {
    RatingValue *rating = [stat getRttRating];
    // Do something
}];
```

{% endtab %}
{% endtabs %}

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


# 방송 SDK (초안)

SDK의 내부에 대해 자세히 알아봅니다.


# RemonCast

방송 및 시청 기능을 구현하려면 RemonCast 클래스를 이용합니다. RemonCast 클래스의 create() 메소드로 방송자가 방송채널을 생성하고 join() 메소드로 시청자가 방송채널에 참여합니다.

## View 등록

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

{% tabs %}
{% tab %}

{% endtab %}
{% endtabs %}

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

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

```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>
```

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

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

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

## 방송생성

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

{% tabs %}
{% 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="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을 제공 합니다.

* 안드로이드 2.4.13, iOS 2.6.9 버전부터 콜백은 모두 UI Thread 에서 호출됩니다.

{% tabs %}
{% 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 %}

더 많은 내용은 아래를 참조 하세요.​

## Channel 목록 조회

방송을 만들면 채널이 생성되고 고유한 `channelId`가 생성 됩니다. 이 `channelId`를 통해 시청자가 생성된 방송에 접근가능합니다. 이때 방송중인 전체 채널 목록을 아래와 같이 조회 가능합니다.

{% tabs %}
{% 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/-LtKHEKqOm7lHsIT-CO9" %}
[Broken mention](broken://pages/-LtKHEKqOm7lHsIT-CO9)
{% endcontent-ref %}

## 종료

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

{% tabs %}
{% 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/-LtK1DqS8rHq0J8ywdeJ" %}
[Config](/drafts/drafts/inside-remoncast-sdk/config)
{% endcontent-ref %}


# Config

방송은 RemonCast 객체를 통해 이루어집니다. RemonCast 객체에 설정 정보를 지정할 수 있습니다. 설정 정보는 Config 객체로 생성하여, RemoCast 객체 생성 시 인자로 전달합니다. Config 객체를 방송/시청 view가 아닌 곳에서 생성하여 방송/시청 view로 전달할 수 있어 편리합니다.

RemonCast 객체 생성 시 Config를 전달하는 방법 외에도, RemonCast의 connnect() 메소드 호출 시 Config를 인자로 전달하는 방법도 있습니다. 이 방법을 이용하면 RemonCast 객체 생성 시 사용한 Config을 사용하지 않고 connect() 메소드에게 전달된 Config를 사용합니다.

플랫폼 별로 클래스명이 조금 다릅니다. Web, Android용은 Config, iOS용은 RemonConfig 클래스를 참고하십시오.

* <https://remotemonster.github.io/web-sdk/docs/module-Config.html>
* <https://remotemonster.github.io/android-sdk/index.html?com/remotemonster/sdk/Config.html>
* <https://remotemonster.github.io/ios-sdk/Classes/RemonConfig.html>

## Basics

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

### 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

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

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

### 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`가 제대로 동작하지 않습니다.

{% 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);
```

{% endtab %}

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

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

{% 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 %}

### Music / Voice Mode

영상, 음성모드일때 음성의 작동방식을 설정 할 수 있습니다. Voice 모드는 노이즈를 억제하고 음성을 명확하게 전달하는데 초점이 맞추어져 있으며 통화에 적합합니다. Music모드는 모든 소리를 왜곡 없이 명확하게 전달하는데 중점이 있습니다. 기본값은 Voice 입니다.

{% tabs %}
{% tab title="Web" %}
N/A
{% 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는 동적으로 해당기능을 제공하지 않으며 다음과 같이 설정하여야 합니다.

![](https://github.com/RemoteMonster/remon-devguide-ko-2019/tree/0317e4ceb2c6bc098862158bda529753fb8e59b4/livestreamingplatform/.gitbook/assets/remonsettings%20\(1\).png)

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

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

![](https://github.com/RemoteMonster/remon-devguide-ko-2019/tree/0317e4ceb2c6bc098862158bda529753fb8e59b4/livestreamingplatform/.gitbook/assets/remonsettings%20\(1\).png)

`RemonSettings.plist` 파일을 프로젝트에 추가하고, AudioType 값을 원하는 모드로 변경해 주세요.
{% 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 %}


# 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은 아래를 참고하세요.

## 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 %}


# Video/Audio 옵션

## Overview

최적의 미디어 품질을 얻기위한 안내입니다.

기본적인 설정과 플렛폼별 안내는 아래를 참고하세요.

{% content-ref url="/pages/-LtK1DqS8rHq0J8ywdeJ" %}
[Config](/drafts/drafts/inside-remoncast-sdk/config)
{% endcontent-ref %}

## Bitrate

영상의 품질과 네트워크 환경에 영향을 받습니다. maxBitrate 로 조절이 가능합니다. 지나치게 낮은 maxBitrate는 인코더에 추가적인 부하로인해 품질을 저하할 수 있습니다.

{% content-ref url="/pages/-LtK1DqS8rHq0J8ywdeJ" %}
[Config](/drafts/drafts/inside-remoncast-sdk/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

여러 품질의 영상을 제공하고 수신자가 품질을 선택할 수 있도록 합니다.

## 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/>" %}


# Callback과 Observer

## Overview

`RemonCast`, `RemonCall`의 간단한 코드 만으로 통신 및 방송이 가능 합니다. 사용자의 필요에 따라 UI처리 및 추가 작업이 필요한 경우가 발생 합니다. 아래의 다양한 Callback을 통해 보다 세부적인 개발이 가능합니다.

안드로이드 2.4.13, iOS 2.6.9 버전부터 콜백은 모두 UI Thread 에서 호출됩니다. 이전 버전의 경우 UI 요소의 변경 시 UI Thread 와 관련한 처리를 추가해야 합니다.

방송과 통신은 각각에 적합한 이벤트와 흐름을 가지고 있습니다. 이를 알아두면 Callback를 활용하는데 도움이 됩니다. 이에 대한 내용은 아래를 참고하세요.

{% content-ref url="/pages/-LvFo2okv7jc1iQkE0bF" %}
[Broken mention](broken://pages/-LvFo2okv7jc1iQkE0bF)
{% 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 코드에 대해 자세히 알고 싶으시면 아래 내용을 참고하시기 바랍니다.

### 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 %}

{% endtab %}
{% endtabs %}

N/A

N/A

```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
}
```

아래의 링크를 통해 구체적인 응용 예시를 확인할 수 있습니다.

```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;
    }];
```

아래의 링크를 통해 구체적인 응용 예시를 확인할 수 있습니다.

### onStat(report)

통신 / 방송 상태를 알수있는 `report`를 받습니다. `report`는 사용자가 `remon` 생성시 설정한 `statInterval`간격 마다 들어오게 됩니다. 네트워크 상황등에 따른 미디어 품질을 나타냄으로 사용자에게 로딩 UI 처리등 안내를 하는데 유용합니다.

이때, 들어오는 값은 영상 및 음성 통화 중에 현재 통화의 품질이 어떠한지를 통합하여 1에서 5까지의 단계로 확인할 수 있습니다.

사용자는 간혹 자신 혹은 상대방의 네트워크 문제로 인하여 통화 품질이 안좋거나 끊어진 상황에서도 서비스의 문제라고 생각하고 불만을 제기할 수 있습니다. 때문에 사용자의 문제가 네트워크의 문제임을 사전에 알려주거나 다양한 UI 처리가 가능합니다.

현재 이 통화 품질 정보는 5초에 한번씩 받을 수 있습니다.

| 단계 | 품질        | 비고 |
| -- | --------- | -- |
| 1  | 매우 좋음     |    |
| 2  | 좋음        |    |
| 3  | 나쁨        |    |
| 4  | 매우 나쁨     |    |
| 5  | 방송, 통화 불능 |    |

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

```swift
let remonCall = RemonCall()
remoCall.onRemonStatReport{ (stat) in 
    let rating:RatingValue = stat.getRttRating()
    let level = rating.levle
}
self.showRemoteVideoStat = true //stat 정보가 영상 위에 오버레이 됩니다.
```

{% endtab %}

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

```objectivec
[self.remonCast onRemonStatReportWithBlock:^(RemonStatReport * _Nonnull stat) {
    RatingValue *rating = [stat getRttRating];
    // Do something
}];
```

{% endtab %}
{% endtabs %}

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


# 인증


# 인증


# 개념

## 채널

리모트몬스터를 이용하는 앱은 채널(channel)을 생성하게 됩니다. 방송을 위한 채널은 "방송채널", 통화를 위한 채널은 "통화채널"으로 부릅니다. 통화 채널 중 그룹통화를 위한 채널은 "그룹통화채널"으로 구분하여 부릅니다.\
채널은 무언가 흘러가는 **공간**이며, water channel에 물이 흐르듯, 리모트몬스터의 채널에는 비디오(오디오 포함)가 흐릅니다.

![현실의 channel : 물이 흘러가는 공간](/files/-Lv9mDU8b-1tZTbDA01k)

## 세션

리모트몬스터가 제공하는 하나의 채널 안에는, 비디오(오디오 포함) 흐름을 일정 **시간** 이용하는 세션(session)이 존재합니다. 현실에서 세션이 좌석에 앉아 있는 행위와 그 앉아 있는 시간을 의미하는 것과 마찬가지입니다.

방송채널 안에는 "방송세션"과 "시청세션"이 존재합니다. 리모트몬스터의 방송채널은 방송세션과 함께 시작하기 때문에 방송세션이 없는 방송채널은 있을 수 없습니다. 시청 없이 종료된 방송채널은 시청세션이 없을 수 있습니다.&#x20;

통화채널 안에는 "통화세션"이 있습니다. 1:1 통화에서 통화세션은 1개 또는 2개입니다. 1:1 통화에서 채널은 발신자의 통화세션과 함께 시작합니다. 한 채널에 수신자의 통화세션이 시작되면 더 이상 채널에 참여할 수 없습니다.

그룹통화채널 안에도 통화세션이 있습니다. 1:1 통화와 구분하여 "그룹통화세션"이라고 부릅니다.

![현실의 session : 앉아 있는 행위와 그 앉아 있는 시간](/files/-Lv9xXJ1VtUQizTDl9Lq)

## 채널의 위상(topology)

### 방송채널

방송자 ===== (리모트몬스터) ==== 시청자1\
&#x20;                                              └==== 시청자2\
&#x20;                                              └==== 시청자3

### 1:1 통화채널

송신자 ==== (리모트몬스터) ==== 수신자

### 그룹통화채널

참여자1 ====┐                     ┌==== 참여자2\
참여자3 ====  리몬 컨퍼런스  ==== 참여자4\
참여자5 ====┘                     └==== 참여자6

<br>


# 개념


# 통화와 방송

리모트몬스터는 통화플랫폼과 방송플랫폼을 제공하고 있습니다.

통화플랫폼으로는 ... 를 할 수 있습니다.

방송플랫폼으로는 ... 를 할 수 있습니다.

... 는 통화플랫폼과 방송플랫폼을 함께 사용하여 만들 수 있습니다.

통화플랫폼 바로가기 ->

방송플랫폼 바로가기 ->


# 통화 개요

* `RemonCall`은 통화(1:1)를 제공합니다.
* 방송에 대해 궁금하시면 `RemonCast`를 확인해주십시오.


# 방송 개요


