Android Developer

Android Zoom Video SDK : 3. Essential Guides (2)

졸려질려 2022. 8. 10. 18:10
반응형

 지난 글에서는 Zoom SDK 를 초기화하고, Video SDK 의 Session 을 생성하는 방법까지 정리했다. 이번 글에서는 생성된 Session 에서 Video Rendering 을 추가해보고, 그 외에 기능들에 대해 정리한다.


1. Render a User's Video

 Zoom SDK 를 사용하는 목적은 대부분 화상 회의 기능을 구현하기 위해서이다. 그리고 화상 회의 기능의 핵심은 회의실 생성, 그리고 "회의 참여자들의 모습을 보여주는 기능" 이다. Video SDK 는 당연하게도 회의 참여자의 모습을 보여주는 기능을 제공한다. Video Rendering 을 구현하기 위해서는 다음의 작업 과정들이 필요하다.

  1. Session 에 참여한 사용자들의 ZoomVideoSDKUser 객체 획득
  2. 각 사용자로부터 Video Canvas 획득
  3. 각 사용자의 Video 를 렌더링해주는 ZoomVideoSDKVideoView 생성
  4. View 에서 사용자의 Video 렌더링을 제거하는 기능 추가

1-1) ZoomVideoSDKUser 객체 획득

 ZoomVideoSDKUser 객체는 Session 에 성공적으로 참여하게 되면 획득할 수 있다. 바로 "onUserJoin()" 콜백 함수로부터 받을 수 있기 때문이다.

// MainActivity.kt
class MainActivity : AppCompatActivity(), ZoomVideoSDKDelegate {
    ...
    override fun onUserJoin(
        userHelper: ZoomVideoSDKUserHelper,
        userList: MutableList<ZoomVideoSDKUser>
    ) {
        Log.d(TAG, "onUserJoin: ${userList.size}")
        userList.forEach {
            Log.d(TAG, "onUserJoin: ${it.userID}")
            Log.d(TAG, "onUserJoin: ${it.userName}")
            Log.d(TAG, "onUserJoin: ${it.isHost}")
            Log.d(TAG, "onUserJoin: ${it.isManager}")
        }
    }
    
    ...
}

 ZoomVideoSDKUser 객체로부터 단순히 받을 수 있는 정보는 위 로그 내용과 같다. Video SDK 에서 설정해준 UserID 값, Session 에 참여할 때 정했던 UserName 값, Host 인지 Manager 인지 확인할 수 있는 Boolean 값이다. 지금은 테스트용으로 1명만이 Session 에 참여 중이지만, 이후에 여러명이 Session 에 들어올 경우 UserName 이나 UserID 가 필요하게 될 것이다.

1-2) Video Canvas -> ZoomVideoSDKVideoView 생성

 ZoomVideoSDKUser 객체를 얻었다면, 위에 언급한 정보 외에 다른 정보를 얻을 수 있다. 바로 VideoCanvas 객체이다. 

...
    override fun onUserJoin(
        userHelper: ZoomVideoSDKUserHelper,
        userList: MutableList<ZoomVideoSDKUser>
    ) {
        userList.forEach { user ->
            Log.d(TAG, "onUserJoin: ${user.userName}")
            Log.d(TAG, "onUserJoin: ${user.isHost}")

            val canvas = user.videoCanvas
        }
    }
...

 "getVideoCanvas()" 를 통해 VideoCanvas 객체를 얻었다면, ZoomVideoSDKVideoView 에 전달해주면 된다. 우선, MainActivity 의 XML 에서 ZoomVideoSDKVideoView 위젯을 추가해주도록 한다.

// activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <us.zoom.sdk.ZoomVideoSDKVideoView
        android:id="@+id/zoom_video_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

 이제 MainActivity.kt 로 돌아와서, XML 에서 추가한 ZoomVideoSDKVideoView 에 대한 인스턴스를 생성하도록 한다. 그리고 VideoCanvas 객체의 "subscribe" 메소드의 인자값으로 넘겨준다.

// MainActivity.kt
...
    override fun onUserJoin(
        userHelper: ZoomVideoSDKUserHelper,
        userList: MutableList<ZoomVideoSDKUser>
    ) {
        userList.forEach { user ->
            Log.d(TAG, "onUserJoin: ${user.userName}")
            Log.d(TAG, "onUserJoin: ${user.isHost}")

            val videoView = findViewById<ZoomVideoSDKVideoView>(R.id.zoom_video_view)
            val canvas = user.videoCanvas

            canvas.subscribe(videoView, ZoomVideoSDKVideoAspect.ZoomVideoSDKVideoAspect_Original)
        }
    }
...

 이제 내 화면이 보일 것으로 예상된다.

 예상과 다르게, 검은 화면만 나오고 있다. 권한 문제인가 싶어 권한 체크까지 해봤으나, 문제를 일으킬 만한 이유가 없었다. 해결책을 찾기 위해 Zoom Video SDK 의 Sample 앱을 켜보았다. Sample 을 켰더니 잘되었고, 심지어 위에 Test 앱에 화면이 뜨기 시작했다. 그런데 둘 다 Emulator 로 실행을 해서, Emulator 의 전면 카메라 화면이 똑같이 비춰졌다. 그러던 중, Emulator 와 실제 기기 사이에 Test 앱을 켜보았더니, Emulator 에는 실제 기기의 전면 카메라 화면이 나타났고, 실제 기기에는 Emulator 의 전면 카메라 화면이 출력됐다. 여기서, 현재 Test 앱의 상태를 알 수 있었다. 즉, Session 에 2명이 참가했을 때, 상대방의 화면을 서로 보여주는 것이었다. 그래서, Emulator 하나만 실행했을 때, 검은 화면만 출력되었던 것이다.

 Emulator 2개로 Test 앱을 실행시키면, 위와 같이 서로 화면이 출력되고 있는 것을 볼 수 있다. 한 명만 Session 에 있을 때는 Video Rendering 이 안되는 것인지 정확한 이유는 알 수 없었다. 이미 위 현상 때문에 시간을 꽤 지체해서 보류해두도록 한다.

 다시 본론으로 돌아와서, VideoCanvas 객체의 "subscribe" 메소드를 호출 할때, Aspect 속성을 지정하는 파라미터가 있다. 위 코드에서는 Original 로 지정했는데, 그 외에 다른 것들에 대해 정리한다.

Type Description
ZoomVideoSDKVideoAspect_Original 아무런 조정 없이 비디오 출력
ZoomVideoSDKVideoAspect_Full_Filled Canvas 를 채우도록 비디오 출력
ZoomVideoSDKVideoAspect_LetterBox 4:3 화면에서 16:9 비율의 비디오를 출력한다면, 위-아래에 검은 바(Bar)가 추가된다.
16:9 화면에서 4:3 비율의 비디오를 출력한다면, 좌-우에 검은 바(Bar)가 추가된다.
ZoomVideoSDKVideoAspect_PanAndScan 4:3 화면에서 16:9 비율의 비디오를 출력한다면, 좌-우를 늘린다.
16:9 화면에서 4:3 비율의 비디오를 출력한다면, 위-아래를 늘린다.

1-3) Video Rendering 제거

 VideoView 로부터 화면 출력을 없애고 싶을 때가 있을 것이다. 그럴 때는 "unsubscribe" 메소드를 사용하면 된다.

videoCanvas.unsubscribe(videoView)

2. Manage User Information

 Session 에 참가한 사용자들은 ZoomVideoSDKUser 객체로 나타나진다. 객체 안에는 Session 에 참가한 사용자의 다양한 정보가 담겨져 있고, 대표적으로 사용자의 ID 를 가지고 있다.

2-1) User Information

 Session 이 유지되는 동안, 그곳에 참여하고 있는 사용자가 가지고 있는 정보는 다음과 같다.

  • User
  • Display Name
  • Video Status
  • Audio Status
  • Share Status
  • Video Statistic Information
  • Share Statistic Information
  • Custom Identity (set on JWT payload)

즉, 위에 나열된 정보들은 ZoomVideoSDKUser 객체에서 얻을 수 있는 것들이다.

Get All Users

 먼저, Session 에 참여한 모든 유저 목록을 얻는 방법은 "getRemoteUsers()" 메소드를 호출하는 것이다. 해당 메소드는 ZoomVideoSDKSession 객체에서 호출할 수 있다.

...
    private fun getAllUsers() {
        val session = videoSDK.session
        val userList = session.remoteUsers

        Log.d(TAG, "getAllUsers: $userList")
    }
...

 위처럼 코드를 작성하면, Session 에 참여 중인 사용자 목록을 List<ZoomVideoSDKUser> 타입으로 받을 수 있다. "videoSDK" 는 VideoSDK 를 초기화 할 때 얻는 VideoSDK 객체를 전역 변수화 시킨 것이다. ZoomVideoSDKUser 에서 제공하는 메소드는 어떤 것이 있는지 JavaDocs 링크를 첨부한다.

2-2) User Roles and Actions

 Session 에 참가한 사용자는 3가지의 역할로 나눌 수 있다.

  1. Host : Session 의 호스트로써, Session 을 생성하거나 시작한 첫번째 참여자를 의미한다.
  2. Manager : Host 에 의해 부여될 수 있는 역할로, Session 내 참여자들을 관리할 수 있다.
  3. Participant : Session 에 참여한 일반적인 사용자를 의미한다.

 이 중에서 Participant 는 자신의 정보와 다른 사람들의 정보를 볼 수 있는 권한만 가지고 있다. 그 외에 Host 와 Manager 는 다양한 권한을 가질 수 있다.

Permission of Host

  1. Session 내 사용자들의 정보를 볼 수 있음
  2. Session 내 사용자의 표시 이름을 수정할 수 있음
  3. 사용자의 Audio 를 Mute/Unmute 할 수 있음
  4. Session 의 Host 역할을 다른 사람에게 양도 할 수 있음
  5. Session 의 Manager 역할을 부여 및 회수 할 수 있음
  6. Session 에서 사용자를 강퇴시킬 수 있음
  7. Live Stream 을 시작할 수 있음
  8. 다른 사용자가 화면 공유하는 것을 막을 수 있음
  9. Session 을 끝낼 수 있음

Permission of Manager

  1. Session 에서 사용자를 강퇴시킬 수 있음
  2. Session 내 사용자의 표시 이름을 수정할 수 있음
  3. 사용자의 Audio 를 Mute/Unmute 할 수 있음
  4. 다른 사용자의 화면 공유를 막을 수 있음

2-3) Change Display Name

 Session 의 Host 이거나 Manager 라면, 다른 Participant 의 표시 이름(Display Name)을 수정할 수 있다.

userHelper.changeName("MyName", user)

2-4) Transfer Host Privilege to Another User

 Session 의 Host 는 다른 Participant 에게 Host 권한을 넘겨줄 수 있다.

userHelper.makeHost(user)

2-5) Assign(Revoke) Manager Privilege to Another User

 Host 는 다른 Participant 에게 Manager 권한을 부여할 수 있다. 그와 동시에 Manager 권한을 회수할 수 있다.

// Assign Manager
userHelper.makeManager(user)

// Revoke Manager
userHelper.revokeManager(user)

2-6) Remove User From a Session

 Host 와 Manager 는 Participant 를 강퇴시킬 수 있다.

userHelper.removeUser(user)

3. Manage Audio and Video Options

 Video SDK 에서 제공하는 메소드들을 통해 Audio 와 Video 를 설정할 수 있다. 하지만, 그 전에 Audio 와 Video 와 관련된 권한을 획득해야한다. Android 기준으로 CAMERARECORD_AUDIO 권한이다.

 SDK 의 기능을 통해 Session 내 사용자들은 자신의 Video 와 Audio 를 제어할 수 있다. 특히, Session 의 Host 는 자신 뿐만 아니라, 다른 사람들의 Video 와 Audio 를 관리할 수 있다. 보통 Video 와 Audio 를 제어할 수 있는 기능이라 하면, Video ON/OFF 또는 Audio Mute/Unmute 를 생각할 것이다.

3-1) Mute or Unmute a User's Audio

 ZoomVideoSDKAudioHelper 는 사용자의 Audio 를 제어할 수 있는 함수를 가지고 있다. Audio 제어 기능을 구현하기 전에 사용자의 오디오가 연결되어 있는지 알아야한다. 오디오가 연결된 상태인지 아는 방법은 사용자의 Audio Type 을 확인하는 것이다.

val audioStatus = user.audioStatus
val audioType = audioStatus.audioType
val audioHelper = ZoomVideoSDK.getInstance().audioHelper

 위 코드는 사용자의 Audio Type 과 ZoomVideoSDKAudioHelper 객체를 얻는 코드이다. 이제 AudioType 을 비교해서 오디오가 연결된 상태가 아닐 때, 오디오가 켜지도록 한다.

if (audioType == ZoomVideoSDKAudioStatus.ZoomVideoSDKAudioType.ZoomVideoSDKAudioType_None) {
    audioHelper.startAudio()
} else {
    // The User is already connected to audio
}

 오디오가 연결되었다면, Mute/Unmute 를 할 수 있다. Mute/Unmute 시키고 싶은 사용자의 ZoomVideoSDKUser 객체를 "muteAudio()", "unMuteAudio()" 메소드의 파라미터로 전달한다.

val audioHelper = ZoomVideoSDK.getInstance().audioHelper

// Mute User
audioHelper.muteAudio(user)

// Unmute User
audioHelper.unMuteAudio(user)

 마지막으로, Audio 연결을 끊는 메소드는 "stopAudio()" 이다.

if (audioType == ZoomVideoSDKAudioStatus.ZoomVideoSDKAudioType.ZoomVideoSDKAudioType_VOIP) {
    audioHelper.stopAudio()
}

3-2) Display or Hide a User's Video

 Audio 와 마찬가지로, Video 제어를 하기 전에 Video 를 송출하는 상태인지 알아야한다.

val isVideoOn = user.videoStatus.isOn

 만약 사용자의 Video 가 송출되고 있는 상태가 아니라면, VideoHelper 의 "startVideo()" 메소드를 통해 Video 를 연결한다.

val videoHelper = ZoomVideoSDK.getInstance().videoHelper

// Start Video
videoHelper.startVideo()

// Stop Video
videoHelper.stopVideo()

 이번 글에서는 Video Renderring 을 비롯하여, Session 내 사용자들의 정보 얻는 방법, Audio/Video 제어 방법에 대해 알아보았다. 초반에 Video Renderring 부분은 혼자서 간단하게 실습이 가능해서, 실제로 써보고 글로 정리했다. 하지만, 이후에 배워본 것은 추가로 개발을 해줘야할 것이 있어서 실습하지 않고 정리했다.

 다음 글에서는 Video 를 송출하는 RecyclerView 와 Item, Video/Audio 제어 기능들을 구현하여 정리해보고자 한다. 그리고 그 과정에서 겪게되는 어려움들도 메모겸 정리를 할 것이다.

반응형