[Flutter] Agora API로 그룹 채팅 & 보이스콜 구현하기

dosilv·2022년 1월 4일
1

다음 프로젝트에 채팅과 보이스콜 기능이 필요할 것 같은데, 플러터로도 가능할까? 싶어서 찾아보니 Agora라는 업체에서 해당 서비스를 제공하고 있었다. 문서화도 비교적 잘 되어 있고 적용이 어렵지는 않았지만, 정보가(특히 in Korean...) 없어도 너무 없어서 살짝 서글펐음...!

🍩 Agora signup

우선 아고라 사이트 계정을 만든다.

실제 서비스에 사용하게 되면 요금을 내야겠지만, 테스트 과정은 크레딧카드 등록도 없이 무료로 가능했음!

그다음 Console > Project Management > Create를 눌러 프로젝트를 생성한다.

이름과 사용 목적을 입력하고, 인증 메커니즘은 Testing mode로 체크해서 submit한다.

처음에는 Recommended라고 되어 있길래 앱 ID + 토큰 둘 다 필요한 첫번째 방법으로 진행했는데, 보이스콜 구현에는 문제 없었지만 채팅 기능 구현 과정에서 토큰을 null로 설정하면 오류가 나서... 스택오버플로우의 도움을 받아 테스팅 모드로 다시 진행했다.

그리고 생성한 프로젝트의 app ID 복사해 두기!



🍩 dependencies 추가

  agora_rtc_engine: ^4.0.7
  permission_handler: ^6.0.0
  agora_rtm: ^1.0.1

2022.01.02 기준 최신 버전들
간략히 설명을 하자면 agora_rtc_engine보이스콜 구현을 위한 엔진을 제공해 주고, agora_rtm은 채팅(real-time-messaging)을 위한 패키지당. permission_handler는 마이크 등의 접근 권한을 쉽게 얻기 위해서 사용한다.

나는 상태관리를 위해서 getX도 추가해 줌!



🍩 그 외 세팅 (마이크, 카메라 접근 권한 허용)

사실 음성통화 기능만 구현할 거라 카메라는 필요 없지만.. 일단 하라는 대로 다 해줬다.

Android

AndroidManifest.xml

<manifest>
    ...
    <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.RECORD_AUDIO" />
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

    <!-- The Agora SDK requires Bluetooth permissions in case users are using Bluetooth devices.-->
    <uses-permission android:name="android.permission.BLUETOOTH" />
    ...
</manifest>

IOS

Info.plist에서
Privacy - Microphone Usage Description
Privacy - Camera Usage Description

+) 안드로이드 추가 세팅

이건 아고라 문서에서 해주면 좋다고 나와있길래... 근데 주소를 까먹어버림ㅎ.ㅎ

/android/build.gradle

allprojects {
    repositories {
        ...
        maven { url 'https://www.jitpack.io' }
    }
}

🍩 코드

우선 서버와 통신하는 부분을 따로 빼기 위해서 AgoraApi라는 클래스를 정의했다.

class AgoraApi {
  String appId = 'e9731f1d2a5448279c91d49eadca075b';
  String channelName = 'new channel';
  
  late RtcEngine engine;
  late AgoraRtmClient client;

  Future<void> initAgora() async {
    engine = await RtcEngine.create(appId);
    client = await AgoraRtmClient.createInstance(appId);
  }
}

아까 아고라에서 복사한 app ID를 정의해주고, 채널 이름도 임의로 정한다.
그리고 앱 아이디를 이용해서 RtcEngineAgoraRtmClient를 생성해 준다. 전자는 보이스콜, 후자는 채팅 기능을 위한 인스턴스임!

voice call 기능 구현

그리고 보이스콜을 위한 GetxController 부분에서 생성자를 만들 때 AgoraApi를 상속받아 내부 인자들을 사용한다.

  AgoraApi agoraApi;
  CallController(this.agoraApi);

그리고 ui 변경을 위해 보이스콜 중일 때/아닐 때를 구분하는 bool타입을 정의해 놓았다.

  bool onCall = false;

보이스콜 구현에 필요한 세팅들을 모아 초기화 메서드를 만들었다. (얘를 ui 코드에서 get.find 직후에 호출해 줌)

  void initCall() async {
    await agoraApi.initAgora();
    await [Permission.microphone].request();
    await agoraApi.engine.enableAudio();
    await agoraApi.engine.setEnableSpeakerphone(true);
  }

AgoraApi클래스에서 생성한 initAgora로 engine을 먼저 생성해주고, permission을 얻어온다.
그 다음엔 차례로 오디오와 스피커폰을 켜는 코드인데, 첨에 이걸 안하고 실행하니까 제대로 목소리를 주고 받을 수 없었다. 터미널엔 계속 Access denied finding property "net.dns1", Access denied finding property "net.dns2"... 이런 ㅇㅔ러가 뜨길래 api 통신 자체에 문제가 있는 줄 알았는데, 스택오버플로우(https://stackoverflow.com/questions/67474736/agora-io-flutter-access-denied-finding-property-net-dns1) 참고 결과 저게 떠도 기능엔 문제가 없다는 댓글을 보고, 다른 빠트린 게 있는지 열심히 구글링해 보다가... 오디오랑 스피커폰 설정을 켜니까 해결됨!


통화에 참여하고 나가는 메서드도 각각 만들어주었다. (사실 그냥 아고라에서 제공하는 join/leave메서드에 ui 변경을 위해 onCall 상태를 바꾸는 코드만 추가한 것..~! update는 상태변경을 알리는 getX의 메서드)

  void joinCallChannel() async {
    final channelName = agoraApi.channelName;
    await agoraApi.engine.joinChannel(null, channelName, null, 0);

    onCall = true;
    update();
  }

  void leaveCallChannel() async {
    await agoraApi.engine.leaveChannel();

    onCall = false;
    update();
  }

messaging 기능

class ChatController extends GetxController {
  AgoraApi agoraApi;
  ChatController(this.agoraApi);

  final List<Message> messages = [];

  final nameCtrl = TextEditingController();
  final msgCtrl = TextEditingController();

  final scrollCtrl = ScrollController();

  AgoraRtmChannel? _channel;

  Future<void> _log(bool mine, String userName, String text) async {
    messages.insert(0, Message(mine, userName, text));
    update();
  }

  void joinChatChannel() async {
    final _client = agoraApi.client;
    final channelName = agoraApi.channelName;

    await _client.login(null, nameCtrl.text);

    //chat join
    _channel = await _client.createChannel(channelName);

    if (_channel != null) {
      await _channel!.join();

      _channel!.onMemberJoined = (AgoraRtmMember member) {
        _log(false, member.userId, "ENTER");
      };

      _channel!.onMemberLeft = (AgoraRtmMember member) {
        _log(false, member.userId, "EXIT");
      };

      _channel!.onMessageReceived = (AgoraRtmMessage msg, AgoraRtmMember mem) {
        _log(false, mem.userId, msg.text);
      };
    }
  }

  void sendMessage() async {
    if (msgCtrl.text != '') {
      _channel!.sendMessage(AgoraRtmMessage.fromText(msgCtrl.text));
      await _log(true, nameCtrl.text, msgCtrl.text);

      msgCtrl.text = '';
    }
  }

  void leaveChatChannel() async {
    final _client = agoraApi.client;

    _channel!.leave();
    _client.logout();

    messages.clear();
    msgCtrl.text = '';
    nameCtrl.text = '';
  }
}

기타 참고 문서들

credential 생성하는 법
https://docs.agora.io/en/Agora%20Platform/faq/restful_authentication

RTM 사용

https://stackoverflow.com/questions/61691407/agora-is-there-anyway-i-can-know-how-many-users-currently-joined-in-channel

액티브 스피커 표시
https://www.agora.io/en/blog/highlighting-the-active-speaker-using-the-agora-flutter-sdk/

채팅방 멤버
https://stackoverflow.com/questions/55648205/how-to-retrieve-channel-users-list-with-agora-io-unity-sdk

API reference
https://docs.agora.io/en/Video/API%20Reference/flutter/index.html

https://github.com/AgoraIO/Agora-Flutter-SDK/issues/497

profile
DevelOpErUN 성장일기🌈

0개의 댓글