Kakao i Connect Live::Connect Live SDK::Android SDK

페이지 이동경로

SDK 개발 프로세스

Android 환경에서 Kakao i Connect Live SDK를 사용하여 일대일 통화(Live Call), 방송(Live Cast), 그룹 통화(Live Conference) 기능을 구현하는 프로세스는 다음과 같습니다.

Android SDK 개발 프로세스 그림Android SDK 개발 프로세스

SDK 개발 프로세스
구분 세부 작업 필수 여부 설명
사전 작업 서비스 고유 정보 발급 필수 서비스 고유 정보인 Service ID와 Secret Key 값 발급
프로젝트 생성 필수 새로운 프로젝트를 생성하고, API 및 호환성 레벨을 설정
SDK 스크립트 추가 필수 GradleManifest 설정을 수행
Android SDK 개발 레이아웃 등록 필수 자신의 카메라 화면과 상대방의 화면을 표시할 View(뷰) 등록
서비스 개발 필수 일대일 통화, 방송, 그룹 통화 서비스 별 개발 프로세스
안내
해당 문서는 Android 버전의 Kakao i Connect Live SDK 2.9.13 버전을 기준으로 작성되었습니다.

개발 요구 사항

Android 환경에서 Kakao i Connect Live SDK 사용 시 필요한 최소 요구 사항은 다음과 같습니다.

개발 최소 요구 사항
구분 항목 최소 버전
운영 체제 Android 5.0 이상
SDK minSdkVersion 21 이상
언어 Java 1.8 이상
언어 Kotlin 1.5.21 이상

사전 작업

Kakao i Connect Live SDK를 사용하여 개발을 수행하기 전에 다음 작업이 꼭 진행되어야 합니다.

서비스 고유 정보 발급받기

Kakao i Connect Live SDK를 사용하기 위해서는 서비스 고유 ID인 Service ID와 Secret Key 값을 반드시 발급받아야 합니다. Service ID와 Secret Key는 프로젝트 생성 시에 자동 생성되며, 이들은 서비스(앱) 별로 구분되어 사용량 측정 및 과금의 기준이 됩니다. 하나 이상의 애플리케이션을 만들 경우에는 앱의 개수만큼 Service ID와 Secret Key를 각각 발급받아야 합니다

  1. Kakao i Connect live 홈페이지에서 [Console] 버튼을 클릭하여 회원가입을 진행합니다.

    • 입력한 이메일 주소로 임시 비밀번호가 발송되며, 로그인 후 비밀번호 변경이 필요합니다.
  2. [새 프로젝트 추가] 버튼을 클릭하여 새로운 프로젝트를 생성합니다.

  3. 해당 프로젝트에서 사용할 서비스 고유 정보인 Service ID와 Secret Key를 확인합니다. 서비스 고유 정보

    구분 설명
    Service ID 프로젝트 생성 시 자동으로 생성되는 ID
    - 서비스 구분에 활용되는 유일값
    Secret Key API 호출 시에 인증을 위한 필수 값
    - 보안상의 이유로 *로 표기되며, [보기] 아이콘을 클릭하여 확인 가능
    주의
    Service ID와 Secret Key 정보가 외부로 유출될 경우에는 해킹 등 악용의 소지가 발생할 수 있으므로 GitHub과 같은 코드 저장소에 노출되지 않도록 주의하시기 바랍니다.

프로젝트 생성하기

Android Studio에서 새로운 프로젝트를 생성하고, API 및 호환성 레벨을 설정합니다.

  1. Android Studio를 최신 버전으로 업데이트하고, New Project 메뉴에서 신규 프로젝트를 생성합니다.

  2. Minimum SDK 레벨을 21 이상으로 설정합니다.

  3. Open Module Settings에서 Source CompatibilityTarget Compatibility를 1.8 이상으로 설정합니다.

SDK 스크립트 추가하기

Android Studio에서 Kakao i Connect Live SDK를 사용하기 위해, GradleManifest를 설정합니다.

Gradle 설정

  1. build.gradle(Project) 파일에서 allprojects-repositories 섹션의 mavenCentral() 아래에 리포지토리 URL을 추가합니다.

    코드예제build.gradle

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

  2. SDK 최신 버전을 컴파일하기 위해 build.gradle (module: app) 파일의 dependencies {} 섹션에 다음을 추가합니다.

    코드예제dependencies 섹션

    dependencies {
        /* SDK 설치 */
        implementation 'com.kakaoenterprise:kakao-i-connect-live:2.9.13'
    }
    

Manifest 설정

Android 최신 버전은 사용자가 처음 앱(애플리케이션) 사용 시 허용할 권한을 묻습니다. 따라서 서비스에 필요한 사용자 권한을 획득하기 위해 Android Manifest를 업데이트합니다.

  1. Project/…/AndroidManifest.xml 파일을 엽니다.

  2. 다음을 참고하여 필수 퍼미션을 업데이트합니다.

    Kakao i Agent SDK for Android 구성 요소
    퍼미션 필수 여부 설명
    android.permission.CAMERA 필수 카메라 사용을 위한 권한
    android.permission.CHANGE_NETWORK_STATE 필수 네트워크 상태 변경을 위한 권한
    android.permission.MODIFY_AUDIO_SETTINGS 필수 오디오 설정을 위한 권한
    android.permission.RECORD_AUDIO 필수 마이크 사용을 위한 권한
    android.permission.INTERNET 필수 인터넷 사용을 위한 권한
    android.permission.WRITE_EXTERNAL_STORAGE 필수 녹화 영상 저장을 위한 권한
    android.permission.ACCESS_NETWORK_STATE 필수 네트워크 상태 확인을 위한 권한
    android.permission.BLUETOOTH 필수 블루투스 사용을 위한 권한

    코드예제Manifest Permission 설정

    <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 SDK 개발

사전 작업을 완료 후, 레이아웃 등록을 등록하고 본격적으로 SDK 개발을 시작할 수 있습니다.

레이아웃 등록하기

방송과 통화 모두 음성 전용이 아니라면 자신의 카메라 화면과 상대방의 화면을 표시해야 합니다.
SDK에서는 이러한 화면을 담당하는 SurfaceViewRenderer가 존재하며, 이를 layout.xml 파일에 등록하고 해당 View를 SDK에 지정해야 합니다.

코드예제SurfaceViewRenderer

<RelativeLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_alignParentBottom="false"
    android:layout_weight="2"
    android:background="@android:color/darker_gray">

    <!--상대방의 카메라 영상을 출력-->
    <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>

서비스 개발하기

서비스 개발 프로세스는 일대일 통화, 방송, 그룹 통화 서비스에 따라 구분됩니다.

일대일 통화

RemonCall 클래스를 사용하여 일대일 통화 기능을 쉽고 빠르게 구현할 수 있습니다. 일대일 통화 기능은 항상 두 명만 참여할 수 있으며, 일대일 통화방의 이름은 해당 Channel ID로 생성됩니다.

Android 일대일 통화 서비스 개발 프로세스 그림Android 일대일 통화 서비스 개발 프로세스

일대일 통화 개발 프로세스
프로세스 구분 행위자 설명
View 등록 필수 All 통화 중 자신과 상대방을 확인할 수 있는 View(뷰)를 등록
SDK 초기화 및 서비스 고유 정보 설정 필수 All SDK 초기화 수행
- 콘솔에서 발급받은 서비스 고유 정보(Service ID와 Service Key) 설정
Callback 구현 선택 All SDK 초기화 또는 통화 시작/종료와 같은 이벤트 상태 확인을 위해 Callback 구현
통화 걸기 구현 필수 Caller connect() 메서드를 이용하여 Caller의 통화 걸기 기능 구현
채널 목록 조회 구현 선택 Callee 통화방에 입장하기 위해 채널 목록 조회
- Caller와 Callee가 Channel ID를 공유
통화 받기 구현 필수 Callee connect() 메서드를 이용하여 Callee의 통화 받기 기능 구현
통화 종료 구현 필수 All close() 메서드를 이용하여 통화 종료 기능 구현
  1. 일대일 통화 중 자신의 화면을 볼 수 있는 Local View와 상대방의 화면을 볼 수 있는 Remote View를 등록합니다.

    a. Local View에 자신의 화면을 담습니다.

    코드예제일대일 통화 Local View 등록

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

    b. Remote View에 상대방의 화면을 담습니다.

    코드예제일대일 통화 Remote View 등록

    <!-- Remote 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>
    

  2. 콘솔에서 발급받은 서비스 고유 정보(Service ID와 Service Key)와 View 관련 설정 및 SDK 초기화를 진행합니다.

    코드예제일대일 통화 서비스 고유 정보 설정

    remonCall = RemonCall.builder()
        .serviceId("{SERVICE_ID}") // 콘솔에서 획득한 Service ID
        .key("{SERVICE_KEY}") // 콘솔에서 획득한 Service Key
        .context(CallActivity.this)
        .localView(surfRendererLocal)
        .remoteView(surfRendererRemote)
        .build();
    
    remonCall = RemonCall.builder()
        .serviceId("{SERVICE_ID}") // 콘솔에서 획득한 Service ID
        .key("{SERVICE_KEY}") // 콘솔에서 획득한 Service Key
        .context(this@CallActivity)
        .localView(surfRendererLocal)
        .remoteView(surfRendererRemote)
        .build()
    

    안내
    서비스 고유 정보를 발급받는 자세한 설명은 서비스 고유 정보 발급받기를 참고하시기 바랍니다.
  3. SDK 초기화 또는 통화 시작/종료와 같은 이벤트가 발생했거나 특정 시점에 도달했을 때, 시스템에서 호출할 수 있는 Callback 메서드를 구현합니다.

    • Android 2.4.13 버전부터 Callback은 모두 UI Thread에서 호출됩니다.

    코드예제일대일 통화 Callback

    remonCall = RemonCall.builder().build();
    
    remonCall.onInit(() -> {
    ... // UI 처리 등 SDK 초기화 시 처리해야 할 작업
    });
    
    remonCall.onConnect((channelId) -> {
    ... // 해당 Channel ID로 생성된 채널이 없다면 다른 사용자가 해당 Channel ID로 연결을 시도 할 때까지 대기 상태가 됨
    });
    
    remonCall.onComplete(() -> {
    ... // Caller와 Callee가 정상적으로 연결되어 통화 시작
    });
    
    remonCall.onClose((closeType) -> {
     ... // 종료
    });
    
    remonCall = RemonCall.builder().build()
    
    remonCall.onInit {
    ... // UI 처리 등 SDK 초기화 시 처리하여야 할 작업
    }
    
    remonCall.onConnect { channelId ->
    ... // 해당 Channel ID로 생성된 채널이 없다면 다른 사용자가 해당 Channel ID로 연결을 시도할 때까지 대기 상태가 됨
    }
    
    remonCall.onComplete {
    ... // Caller와 Callee가 정상적으로 연결되어 통화 시작
    }
    
    remonCall.onClose { closeType ->
    ... // 종료
    }
    

    안내
    Callback 메서드에 대한 자세한 설명은 Callback 문서를 참고하시기 바랍니다.
  4. connect() 메서드에 Channel ID를 전달하여 통화 걸기를 시도하는 기능을 구현합니다. 해당 Channel ID의 채널이 존재하지 않을 경우에만 해당 Channel ID의 이름으로 신규 채널이 생성됩니다.

    • 필요 시, onConnect() Callback 메서드를 구현하여 통화 연결 완료에 대한 이벤트를 추적할 수 있습니다.
    • 채널이 생성되면 Caller는 해당 채널에 연결을 기다리는 대기 상태가 되며, Callee가 해당 Channel ID로 연결을 시도하면 일대일 통화가 시작됩니다.
    • Remon 웹 샘플 클라이언트에서 Android 앱과 통화 테스트를 할 수 있습니다.
    Channel ID 정의 및 생성 규칙
    구분 설명
    Channel ID 채널을 식별하는 수단
    - Caller가 채널에 연결한 시각부터 Caller 또는 Callee 중 어느 한 쪽이 연결을 해제한 시각까지 유효
    - 개발자가 직접 지정해서 사용 가능하며, 미지정 시에는 임의로 생성됨
    - 과금은 Caller와 Callee가 실제 연결되어 통화가 이루어진 시간을 기준으로 책정
    생성 규칙
    - 영문 기준 최소 4자 ~ 최대 1,024자 까지 허용
    - 특수문자(:, ;, *, $, ' 등)와 한글 사용 금지

    코드예제일대일 통화 걸기

    caller = RemonCall.builder()
            .serviceId("{SERVICE_ID}")
            .key("{SERVICE_KEY}")
            .context(CallActivity.this)
            .localView(surfRendererLocal)
            .remoteView(surfRendererRemote)
            .build();
    
    caller.onConnect((channelId) -> {
        myChannelId = channelId;    // Callee는 통화 연결을 위해 Caller로 부터 Channel ID 정보를 받아야 함 
    });
    
    caller.onComplete(() -> {
    ... // Caller와 Callee가 정상적으로 연결되어 통화 시작
    });
    
    caller.connect("{CHANNEL_ID}"); // Caller가 지정한 채널 이름이며, Callee는 이 정보를 사용하여 통화방에 입장 
    
    caller = RemonCall.builder()
        .serviceId("{SERVICE_ID}") 
        .key("{SERVICE_KEY}") 
        .context(this@CallActivity)
        .localView(surfRendererLocal)
        .remoteView(surfRendererRemote)
        .build()
    
    caller.onConnect { channelId ->
        myChannelId = channelId   // Callee는 통화 연결을 위해 Caller로 부터 Channel ID 정보를 받아야 함 
    }
    
    caller.onComplete {
    ... // Caller와 Callee가 정상적으로 연결되어 통화 시작
    }
    
    caller.connect("{CHANNEL_NAME}")  // Caller가 지정한 채널 이름이며, Callee는 이 정보를 사용하여 통화방에 입장 
    

  5. 랜덤 채팅 등과 같은 서비스에서 전체 채널 목록 정보를 조회할 수 있도록 채널 목록 조회 기능을 구현합니다.

    코드예제일대일 통화 목록 조회

    remonCall = RemonCall.builder().build();
    remonCall.onFetch(calls -> {
        for (Room call : calls) {
            if (call.getStatus().equals("WAIT")) { //  대기중인 통화 목록
                myChannelId = call.getId();
            }
        }
    });
    
    remonCall.fetchCalls();
    
    remonCall = RemonCall.builder().build()
    remonCall.onFetch { calls ->
        for (call in calls) {
            if (call.status == "WAIT") {  //  대기중인 통화 목록
                myChannelId = call.id
            }
        }
    }
    
    remonCall.fetchCalls()
    

    call.getStatus() Elements
    파라미터 타입 필수 여부 설명
    Method 선택 통화 목록을 조회
    COMPLETED String 선택 통화 연결이 완료된 경우
    - Caller와 Callee가 모두 입장하여 통화가 성사된 경우
    WAIT String 선택 통화 연결이 대기중인 경우
    - Caller는 입장하여 Callee의 입장을 기다리는 경우
  6. connect() 메서드에 일대일 통화를 원하는 Channel ID를 입력하여 Caller만 접속한 상태의 통화 받기 기능을 구현합니다.

    • 필요 시, onComplete() Callback 메서드를 구현하여 일대일 통화가 정상적으로 연결되었는지 추적할 수 있습니다.

    코드예제일대일 통화 받기

    callee = RemonCall.builder()
            .serviceId("{SERVICE_ID}")
            .key("{SERVICE_KEY}")
            .context(CallActivity.this)
            .localView(surfRendererLocal)
            .remoteView(surfRendererRemote)
            .build();
    
    callee.onComplete(() -> {
    ... // Caller와 Callee가 정상적으로 연결되어 통화 시작
    });
    
    callee.connect("{CHANNEL_ID}");
    
    callee = RemonCall.builder()
        .serviceId("{SERVICE_ID}")
        .key("{SERVICE_KEY}")
        .context(this@CallActivity)
        .localView(surfRendererLocal)
        .remoteView(surfRendererRemote)
        .build()
    
    callee.onComplete {
    ... // Caller와 Callee가 정상적으로 연결되어 통화 시작
    }
    
    callee.connect("{Channel_ID}")
    

  7. close() 메서드를 사용하여 통화 종료 기능을 구현합니다. close() 메서드 호출 시 모든 통신 자원과 미디어 스트림 자원이 해제됩니다.

    • 필요 시, onClose() Callback 메서드를 구현하여 통화가 정상적으로 종료되었는지 추적할 수 있습니다.

    코드예제일대일 통화 종료

    remonCall = RemonCall.builder().build();
    ...
    remonCall.close();
    
    remonCall = RemonCall.builder().build()
    ...
    remonCall.close()
    

방송

RemonCast 클래스를 사용하여 1:N 방송 기능을 쉽고 빠르게 구현할 수 있습니다.

Android 방송 서비스 개발 프로세스 그림Android 방송 서비스 개발 프로세스

방송 서비스 개발 프로세스
프로세스 구분 행위자 설명
View 등록 필수 All 통화 중 자신과 상대방을 확인할 수 있는 View(뷰)를 등록
SDK 초기화 및 서비스 고유 정보 설정 필수 All SDK 초기화 수행
- 콘솔에서 발급받은 서비스 고유 정보(Service ID와 Service Key) 설정
Callback 구현 선택 All SDK 초기화 또는 통화 시작/종료와 같은 이벤트 상태 확인을 위해 Callback 구현
방송 생성 및 송출 구현 필수 Caster create() 메서드를 이용하여 Caster가 방송을 생성하고 송출
방송 목록 조회 구현 선택 Viewer fetchCasts() 메서드를 이용하여 생성된 방송 목록을 조회
방송 시청 구현 필수 Viewer join() 메서드를 이용하여 Viewer가 방송 시청
방송 종료 구현 필수 All close() 메서드를 이용하여 방송 송출 및 시청을 종료
  1. 방송 중 자신의 화면을 볼 수 있는 Local View와 상대방의 화면을 볼 수 있는 Remote View를 등록합니다.

    a. Local View에 자신의 화면을 담습니다.

    코드예제방송 Local View 등록

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

    b. Remote View에 상대방의 화면을 담습니다.

    코드예제방송 Remote View 등록

    <!-- Remote 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>
    

  2. 콘솔에서 발급받은 서비스 고유 정보(Service ID와 Service Key)와 View 관련 설정 및 SDK 초기화를 진행합니다.

    • Caster와 Viewer로 구분하여 작업을 수행합니다.

    코드예제Caster 서비스 고유 정보 설정

    caster = RemonCast.builder()
            .serviceId("{SERVICE_ID}")
            .key("{SERVICE_KEY}")
            .context(CasterActivity.this)
            .localView(surfRendererLocal)
            .build();
    
    caster = RemonCast.builder()
        .serviceId("{SERVICE_ID}")
        .key("{SERVICE_KEY}")
        .context(this@CasterActivity)
        .localView(surfRendererLocal)
        .build()
    

    코드예제Viewer 서비스 고유 정보 설정

    viewer = RemonCast.builder()
            .serviceId("{SERVICE_ID}")
            .key("{SERVICE_KEY}")
            .context(ViewerActivity.this)
            .remoteView(surfRendererRemote)
            .build();
    
    viewer = RemonCast.builder()
        .serviceId("{SERVICE_ID}")
        .key("{SERVICE_KEY}")
        .context(this@ViewerActivity)
        .remoteView(surfRendererRemote)
        .build()
    

    안내
    서비스 고유 정보를 발급받는 자세한 설명은 서비스 고유 정보 발급받기를 참고하시기 바랍니다.
  3. SDK 초기화 또는 방송 시작/종료와 같은 이벤트가 발생했거나 특정 시점에 도달했을 때, 시스템에서 호출할 수 있는 Callback 메서드를 구현합니다.

    • Android 2.4.13 버전부터 Callback은 모두 UI Thread에서 호출됩니다.

    코드예제방송 Callback

    remonCast = RemonCast.builder().build();
    
    ... // UI 처리 등 Remon 클래스가 초기화 되었을 때 처리하여야 할 작업
    remonCast.onInit(() -> {
    });
    
    // 방송 생성
    remonCast.onCreate((channelId) -> {
    });
    
    // 방송 참여
    remonCast.onJoin(() -> {
    });
    
    // Caster와 Viewer가 정상적으로 연결되어 방송 시작
    remonCast.onComplete(() -> {
    });
    
    // 종료
    remonCast.onClose(() -> {
    });
    
    remonCast = RemonCast.builder().build()
    
    ... // UI 처리등 Remon 클래스가 초기화 되었을 때 처리하여야 할 작업
    remonCast.onInit {
    }
    
    // 방송 생성
    remonCast.onCreate { channelId ->
    }
    
    // 방송 참여
    remonCast.onJoin {
    }
    
    // Caster와 Viewer가 정상적으로 연결되어 방송 시작
    remonCast.onComplete {
    }
    
    // 종료
    remonCast.onClose {
    }
    

    안내
    Callback 메서드에 대한 자세한 설명은 Callback 문서를 참고하시기 바랍니다.
  4. RemonCast 클래스의 create() 메서드에 Channel ID를 전달하여 지정된 채널방에서 방송을 생성하고 송출하는 기능을 구현합니다. 채널방은 해당 Channel ID 명칭으로 생성됩니다.

    • Channel ID는 채널을 식별하는 고유한 값으로 생성 규칙을 참고하여 개발자가 직접 지정할 수 있으며, 만약 지정하지 않는다면 임의로 생성됩니다.
    • Channel ID는 Caster가 명시적으로 채널을 생성하는 시각부터 채널을 종료하는 시각까지 유효합니다. Caster가 영상을 송출하지 않고 채널을 생성만 해도 유효합니다. 유효한 채널이 생성되면 Viewer는 해당 채널에 입장할 수 있습니다.
    Channel ID 정의 및 생성 규칙
    구분 설명
    Channel ID 채널을 식별하는 수단
    - Caller가 채널에 연결한 시각부터 Caller 또는 Callee 중 어느 한 쪽이 연결을 해제한 시각까지 유효
    - 개발자가 직접 지정해서 사용 가능하며, 미지정 시에는 임의로 생성됨
    - 과금은 Caller와 Callee가 실제 연결되어 통화가 이루어진 시간을 기준으로 책정
    생성 규칙
    - 영문 기준 최소 4자 ~ 최대 1,024자 까지 허용
    - 특수문자(:, ;, *, $, ' 등)와 한글 사용 금지

    코드예제방송 생성

    caster = RemonCast.builder()
            .serviceId("{SERVICE_ID}")
            .key("{SERVICE_KEY}")
            .context(CastActivity.this)
            .localView(surfRendererlocal) // Local View(자신의 화면)을 보여주는 렌더러 
            .build();
    
    caster.create("{CHANNEL_ID}"); // CHANNEL ID는 선택 입력값, 생성 규칙에 따라 생성 필요(미 지정 시 자동 생성됨) 
    
    caster = RemonCast.builder()
        .serviceId("{SERVICE_ID}")
        .key("{SERVICE_KEY}")
        .context(this@CastActivity)
        .localView(surfRendererlocal)    // Local View(자신의 화면)을 보여주는 렌더러 
        .build()
    
    caster.create("{CHANNEL_ID}") // CHANNEL_ID는 선택 입력값으로, 생성 규칙에 따라 생성할 수 있으며 미 지정 시 자동 생성됨
    

  5. Viewer가 전체 방송 목록 정보를 조회할 수 있도록 fetchCasts() 메서드를 사용하여 방송 목록 조회 기능을 구현합니다.

    • 방송이 만들어지면 고유한 Channel ID의 식별값을 가진 채널 생성이 생성되며, Viewer는 해당 Channel ID를 통해 방송에 접근할 수 있습니다.

    코드예제전체 방송 목록 조회

    remonCast = RemonCast.builder().build();
    
    remonCast.onFetch((casts) -> {
        ...
    });
    
    remonCast.fetchCasts();
    
    remonCast = RemonCast.builder().build()
    
    remonCast.onFetch { casts ->
        ...
    }
    
    remonCast.fetchCasts()
    

  6. RemonCast의 joinRoom() 메서드를 호출하여 Viewer가 방송을 시청하는 기능을 구현합니다.

    • 필요 시, onJoin() Callback 메서드를 구현하여 Viewer가 방송에 연결되어 미디어를 시청할 수 있는 상태를 추적할 수 있습니다.
    • Viewer는 주로 Channel ID 정보를 사용하여 방송을 선택하게 됩니다.

    코드예제방송 시청(Viewer)

    viewer = RemonCast.builder()
            .serviceId("{SERVICE_ID}")
            .key("{SERVICE_KEY}")
            .context(ViewerActivity.this)
            .remoteView(surfRendererRemote)   // Remote View(상대의 화면)을 보여주는 렌더러 
            .build();
    
    viewer.onJoin(() -> {
        ...
    });
    
    viewer.join("{CHANNEL_ID}");    // Caster로 부터 받은 Channel ID
    
    viewer = RemonCast.builder()
        .serviceId("{SERVICE_ID}")
        .key("{SERVICE_KEY}")
        .context(this@ViewerActivity)
        .remoteView(surfRendererRemote)   // Remote View(상대의 화면)을 보여주는 렌더러 
        .build()
    
    viewer.onJoin {
        ...
    }
    
    viewer.join("{CHANNEL_ID}") // Caster로 부터 받은 Channel ID
    

  7. close() 메서드를 사용하여 방송 송출 또는 시청을 종료하는 기능을 구현합니다. close() 메서드 호출 시, 모든 통신 자원과 미디어 스트림 자원이 해제됩니다.

    코드예제방송 종료

    remonCast = RemonCast.builder().build();
    ...
    remonCast.close();
    
    remonCast = RemonCast.builder().build()
    ...
    remonCast.close()
    

그룹 통화

RemonConference 클래스를 사용하여 다수의 참여자가 통화에 참여하는 그룹 통화 서비스를 쉽고 빠르게 구현할 수 있습니다. 참여자는 애플리케이션을 이용하는 ‘나’와 나를 제외한 ‘참여자’로 구분됩니다. 한 회기의 그룹 통화는 RemonConference 클래스의 인스턴스로 대표되며, 통화 연결과 참여자들의 입장/퇴장 알림 등 대부분의 이벤트를 RemonConference 객체에게 위임합니다. 그룹 통화 서비스의 개발 방법은 다음과 같습니다.

Andriod 그룹 통화 서비스 개발 프로세스 그림Andriod 그룹 통화 서비스 개발 프로세스

그룹 통화 서비스 개발 프로세스
프로세스 구분 행위자 설명
레이아웃 구성 필수 All 나와 참여자들의 영상을 배치할 레이아웃을 구성
레이아웃 초기화 필수 All 레이아웃을 바인딩하고, 각 뷰를 배열에 담아 인덱스로 접근할 수 있도록 설정
영상 송출 구현 필수 All RemonConference 클래스를 생성하고, 나의 영상을 송출하기 위한 설정
Callback 메서드 구현 필수 All 그룹 통화 생성 시 필요한 Callback 메서드 구현
그룹 통화 종료 구현 필수 All remonConference.leave() 메서드를 사용하여 그룹 통화 종료 기능 구현
  1. 그룹 통화 화면을 나의 영상(한 개)과 그룹 참여자의 영상(여러 개)으로 레이아웃을 구성합니다. 레이아웃 구성 시, 영상을 표시할 View(뷰)를 만들고 인덱스를 지정하여 참여자의 영상을 원하는 위치에 표시합니다.

    코드예제그룹 통화 레이아웃 구성

    <layout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto">
    
        <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 View(나의 영상) -->
                <RelativeLayout
                    android:id="@+id/layout0"
                    android:layout_width="0dp"
                    android:layout_height="0dp"
                    android:layout_margin="10dp"
                    android:background="@drawable/view_shape"
                    app:layout_constraintBottom_toBottomOf="parent"
                    app:layout_constraintDimensionRatio="H,1:1.33"
                    app:layout_constraintEnd_toEndOf="parent"
                    app:layout_constraintStart_toStartOf="parent"
                    app:layout_constraintTop_toTopOf="parent">
    
                    <org.webrtc.SurfaceViewRenderer
                        android:id="@+id/surfRendererLocal"
                        android:layout_width="match_parent"
                        android:layout_height="match_parent"
                        android:visibility="visible" />
                </RelativeLayout>
    
    
                <!--참여자(Remote View) 영상 1-->
                <FrameLayout
                    android:id="@+id/layout1"
                    android:layout_width="80dp"
                    android:layout_height="0dp"
                    android:layout_margin="18dp"
                    app:layout_constraintBottom_toBottomOf="parent"
                    app:layout_constraintDimensionRatio="H,1:1.33"
                    app:layout_constraintEnd_toEndOf="parent"
                    app:layout_constraintTop_toTopOf="parent"
                    app:layout_constraintVertical_bias="0.1">
    
                    <org.webrtc.SurfaceViewRenderer
                        android:id="@+id/surfRendererRemote1"
                        android:layout_width="match_parent"
                        android:layout_height="match_parent"
                        android:visibility="invisible" />
                </FrameLayout>
    
               <!--참여자(Remote View) 영상 2-->
                <FrameLayout
                    android:id="@+id/layout2"
                    android:layout_width="80dp"
                    android:layout_height="0dp"
                    android:layout_margin="18dp"
                    app:layout_constraintBottom_toBottomOf="parent"
                    app:layout_constraintDimensionRatio="H,1:1.33"
                    app:layout_constraintEnd_toEndOf="parent"
                    app:layout_constraintTop_toTopOf="parent"
                    app:layout_constraintVertical_bias="0.3">
    
                    <org.webrtc.SurfaceViewRenderer
                        android:id="@+id/surfRendererRemote2"
                        android:layout_width="match_parent"
                        android:layout_height="match_parent"
                        android:visibility="invisible" />
                </FrameLayout>
    
                <!--참여자(Remote View) 영상 3-->
                <FrameLayout
                    android:id="@+id/layout3"
                    android:layout_width="80dp"
                    android:layout_height="0dp"
                    android:layout_margin="18dp"
                    app:layout_constraintBottom_toBottomOf="parent"
                    app:layout_constraintDimensionRatio="H,1:1.33"
                    app:layout_constraintEnd_toEndOf="parent"
                    app:layout_constraintTop_toTopOf="parent"
                    app:layout_constraintVertical_bias="0.5">
    
                    <org.webrtc.SurfaceViewRenderer
                        android:id="@+id/surfRendererRemote3"
                        android:layout_width="match_parent"
                        android:layout_height="match_parent"
                        android:visibility="invisible" />
                </FrameLayout>
    
                <!--참여자(Remote View) 영상 4-->
                <FrameLayout
                    android:id="@+id/layout4"
                    android:layout_width="80dp"
                    android:layout_height="0dp"
                    android:layout_margin="18dp"
                    app:layout_constraintBottom_toBottomOf="parent"
                    app:layout_constraintDimensionRatio="H,1:1.33"
                    app:layout_constraintEnd_toEndOf="parent"
                    app:layout_constraintTop_toTopOf="parent"
                    app:layout_constraintVertical_bias="0.7">
    
                    <org.webrtc.SurfaceViewRenderer
                        android:id="@+id/surfRendererRemote4"
                        android:layout_width="match_parent"
                        android:layout_height="match_parent"
                        android:visibility="invisible" />
                </FrameLayout>
    
                <!--참여자(Remote View) 영상 5-->
                <FrameLayout
                    android:id="@+id/layout5"
                    android:layout_width="80dp"
                    android:layout_height="0dp"
                    android:layout_margin="18dp"
    
                    app:layout_constraintBottom_toBottomOf="parent"
                    app:layout_constraintDimensionRatio="H,1:1.33"
                    app:layout_constraintEnd_toEndOf="parent"
                    app:layout_constraintTop_toTopOf="parent"
                    app:layout_constraintVertical_bias="0.9">
    
                    <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>
    

  2. 레이아웃을 바인딩하고, 각 View를 배열에 담아 인덱스로 접근할 수 있도록 레이아웃을 초기화합니다.

    코드예제그룹 통화 레이아웃 초기화

    binding = DataBindingUtil.setContentView(this, R.layout.activity_name);
    
    SurfaceViewRenderer[] surfaceRendererArray = {
            binding.surfRendererLocal,
            binding.surfRendererRemote1,
            binding.surfRendererRemote2,
            binding.surfRendererRemote3,
            binding.surfRendererRemote4,
            binding.surfRendererRemote5
    };
    
    ... // 비어있는 View를 처리하기 위한 배열로, 각 서비스에 따라 구현 필요
    boolean[] availableView = new boolean[surfaceRendererArray.length];
    Arrays.fill(availableView, false);
    
    binding = DataBindingUtil.setContentView(this, R.layout.activity_name)
    
    val surfaceRendererArray = arrayOf(
        binding.surfRendererLocal,
        binding.surfRendererRemote1,
        binding.surfRendererRemote2,
        binding.surfRendererRemote3,
        binding.surfRendererRemote4,
        binding.surfRendererRemote5
    )
    
    ... // 비어있는 View를 처리하기 위한 배열로, 각 서비스에 따라 구현 필요
    val availableView = Array(surfaceRendererArray.size) { false }
    

  3. RemonConference 클래스를 생성하고, 자신의 영상을 송출하기 위한 설정을 합니다.

    코드예제RemonConference 클래스 생성

    RemonConference remonConference = new RemonConference();
    
    Config config = new Config();
    config.serviceId = "{SERVICE_ID}";
    config.key = "{SECRET_KEY}";
    
    remonConference.create("{ROOM_NAME}", config, participant -> {
    		// 자신의 View를 초기화
    		participant.setLocalView(surfaceRendererArray.get(0));
    
    	  // View 설정
        availableView[0] = true; // boolean : 할당된 미디어가 있는지 여부
    }).close(() -> {
    	 // 클라이언트의 사용자(참여자)가 연결된 채널이 종료되면 호출됨
         // 송출이 중단되면 그룹 통화에서 연결이 끊어진 것이며, 다른 사용자와의 연결도 모두 끊어짐
    }).error(e -> {
    	// 클라이언트의 사용자(참여자)가 연결된 채널에서 오류 발생 시 호출됨
        // 오류로 연결이 종료되면 error -> close 순으로 호출됨
    });
    
    var remonConference = RemonConference()
    
    var config = Config()
    config.context = this
    config.serviceId = "{SERVICE_ID}"
    config.key = "{SECRET_KEY}"
    
    remonConference.create( "{Room_Name}", config) { 
        participant ->
        
        // 자신의 View를 초기화
        participant.localView = surfaceRendererArray[0]
        
        // View 설정
        availableView[0] = true // boolean : 할당된 미디어가 있는지 여부
    }.close {
        // 클라이언트의 사용자(참여자)가 연결된 채널이 종료되면 호출됨
        // 송출이 중단되면 그룹 통화에서 연결이 끊어진 것이며, 다른 사용자와의 연결도 모두 끊어짐
    }.error { 
        error:RemonException ->
        // 클라이언트의 사용자(참여자)가 연결된 채널에서 오류 발생 시 호출됨
        // 오류로 연결이 종료되면 error -> close 순으로 호출됨
    }
    

    RemonConference 클래스 생성
    파라미터 타입 필수 여부 설명
    Method 필수 그룹 통화를 생성하는 메서드
    Room_Name String 필수 그룹 통화방의 ID
    config Config 필수 설정 값
    callback OnEventCallback 필수 그룹 통화와 관련된 이벤트 Callback
  4. 그룹 통화가 생성되면 송출이 시작되고, 각 Callback이 호출됩니다. Callback은 create() 메서드 호출 후 on(“이벤트”){} 형태로 등록할 수 있으며, 새 참여자가 그룹 통화에 입장하면 연결된 on 메서드 Callback이 호출됩니다. on 메서드 Callback은 참여자의 RemonParticipant 객체를 제공하므로, 해당 정보를 사용하여 설정을 진행해야 합니다.

    코드예제그룹 통화 Callback

    remonConference.create("{ROOM_NAME)", config, () -> {
        ...
    }).on("onRoomCreated", (participant) -> {
    
    
        // 방에 입장 이후에 호출, 자신의 미디어 송출 시작
        // TODO: 실제 사용자 정보는 각 서비스에서 관리하므로, 서비스에서 채널과 실제 사용자 매핑 작업 진행
    
        // tag 객체에 holder 패턴 형태로 객체를 지정하여 사용
        // 예제에서는 View 설정을 위해 단순히 View의 index를 저장함
        participant.tag = 0;
    
    }).on("onUserJoined", (participant) -> {
    
        Log.d(TAG, "Joined new user");
        // 그룹 통화에 새로운 참여자가 입장했을 때 호출됨
        // 다른 사용자가 입장한 경우 초기화를 위해 호출됨
        // TODO: 실제 사용자 매핑 : it.id 값으로 연결된 실제 사용자와 매핑
    
    
        // 뷰(View) 설정
        int index = getAvailableView();
        if (index > 0) {
            participant.config.localView = null;
            participant.config.remoteView = surfaceRendererArray[index];
            participant.tag = index;
        }
    
        // 해당 참여자가 연결이 완되었을 때 처리할 작업이 있는 경우
        participant.on("onComplete", (participant2) -> {
            // updateView()
        });
    }).on("onUserStreamConnected", (participant) -> {
    
        // 새로운 참여자의 영상을 성공적으로 수신하기 시작하면 호출
    
    }).on("onUserLeft", (participant) -> {
        // 참여자가 그룹 통화에서 퇴장하거나 연결이 종료된 경우 호출됨
        // id와 tag를 참조하여 어떤 사용자가 퇴장했는지 확인 후 퇴장 처리를 진행
        int index = (Integer) participant.tag;
        availableView[index] = false;
    }).close(() -> {
        // 그룹 통화 종료 시 호출됨
    }).error((RemonException error) -> {
        // 오류 발생시 호출됨
    });
    
    ...
    
    // 비어있는 View는 아래처럼 얻어올 수 있음
    // 서비스에 해당하는 부분이므로 각 서비스 UI에 맞게 구성 필요
    private int getAvailableView() {
        for (int i = 0; i < availableView.length; i++) {
            if (!availableView[i]) {
                availableView[i] = true;
                return i;
            }
        }
    
        return -1;
    }
    
    remonConference.create("{ROOM_NAME)", config) {
        ...
    }.on("onRoomCreated") { participant ->
    
        // 방에 입장 이후에 호출, 자신의 미디어 송출 시작
        // TODO: 실제 사용자 정보는 각 서비스에서 관리하므로, 서비스에서 채널과 실제 사용자 매핑 작업 진행
    
        // tag 객체에 holder 패턴 형태로 객체를 지정하여 사용
        // 예제에서는 View 설정을 위해 단순히 View의 index를 저장함
        participant.tag = 0
    
    }.on("onUserJoined") { participant ->
    
        Log.d(TAG, "Joined new user")
        // 그룹 통화에 새로운 참여자가 입장했을 때 호출됨
        // 다른 사용자가 입장한 경우 초기화를 위해 호출됨
        // TODO: 실제 사용자 매핑 : it.id 값으로 연결된 실제 사용자와 매핑
    
    
        // 뷰(View) 설정
        val index = getAvailableView()
        if (index > 0) {
            participant.config.localView = null
            participant.config.remoteView = surfaceRendererArray[index]
            participant.tag = index
        }
    
        // 해당 참여자가 연결이 완되었을 때 처리할 작업이 있는 경우
        participant.on("onComplete") { participant ->
            // updateView()
        }
    }.on("onUserStreamConnected") { participant ->
        // 새로운 참여자의 영상을 성공적으로 수신하기 시작하면 호출
    
    }.on("onUserLeft") { participant ->
        // 참여자가 그룹 통화에서 퇴장하거나 연결이 종료된 경우 호출됨
        // id와 tag를 참조하여 어떤 사용자가 퇴장했는지 확인 후 퇴장 처리를 진행
        val index = participant.tag as Int
        availableView[index] = false
    }.close {
        // 그룹 통화 종료 시 호출됨
    }.error { error: RemonException ->
        // 오류 발생시 호출됨
    }
    
    ...
    
    // 비어있는 View는 아래처럼 얻어올 수 있음
    // 서비스에 해당하는 부분이므로 각 서비스 UI에 맞게 구성 필요
    private fun getAvailableView(): Int {
        for( i in availableView.indices) {
            if(!availableView[i]) {
                availableView[i] = true
                return i
            }
        }
        return -1
    }
    

    안내
    Callback 메서드에 대한 자세한 설명은 Callback 문서를 참고하시기 바랍니다.
    RemonParticipant 란?
    나와 참여자들과의 연결은 RemonConference 클래스 내부의 RemonParticipant 객체를 통해 이루어집니다. RemonParticipant 객체는 RemonClient를 상속받으며, 기능은 RemonCall(일대일 통화)과 RemonCast(방송)와 동일합니다.
    각 이벤트마다 RemonParticipant 객체가 전달되므로, 해당 객체를 통해 연결을 제어할 수 있습니다. 나를 포함한 모든 참여자의 RemonParticipant 객체를 RemonConference 객체에서 얻어올 수 있습니다.

    코드예제클라이언트의 사용자(참여자) 정보 획득

    RemonParticipant me = remonConference.me;
    
    var me = remonConference.me
    

    주의
    RemonParticipant 객체는 RemonClient를 상속받은 객체입니다. onCreate, onClose, onError Callback 메서드는 on으로 재정의되어 RemonConference 클래스에서 관리되므로 해당 Callback을 변경하지 마시기 바랍니다.
  5. 그룹 통화 종료하기 기능을 구현합니다. 그룹 통화에서 퇴장하면 나와 그룹 통화의 연결이 종료되고, 나와 참여자들 간 연결도 종료됩니다.

    코드예제그룹 통화 연결 종료

    remonConference.leave();
    
    remonConference.leave()
    

관련 문서

Android SDK Kakao i Connect Live iOS SDK Kakao i Connect Live Web SDK Kakao i Connect Live

이 문서가 만족스러운 이유를 알려주세요.
이 문서에 아쉬운 점을 알려주세요.
평가해주셔서 감사합니다.

더 자세한 의견은 documentation@kakaoenterprise.com 으로 제보해주세요.