Flutter

[Flutter] agora 사용해서 face time 따라해보기

yuraming 2024. 6. 6. 16:56

 

오늘은 간단하게 aogra를 사용하여 페이스타임과 유사한 영상통화 앱을 구현 포스팅입니다.

 

프로젝트를 생성 후,

pubspec.yaml에 dependencies 플러그인 추가 후 pubget 클릭 

agora_rtc_engine: ^6.3.0
agora_uikit: ^1.3.8
permission_handler: ^11.3.0

 

 

agora_rtc_engine : https://pub.dev/packages/agora_rtc_engine

 → 아고라 사용을 위한 플러그인

agora_uikit : https://pub.dev/packages/agora_uikit

→ 아고라 자체 UI 사용 

permission_handler : https://pub.dev/packages/permission_handler

→ 권한을 다루기 위해 필요

 

 


 

 

android/app/src/main/AndroidManifest.xml 경로에 agora_rtc_engine 권한 코드 추가

<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<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_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

 

 

lib/screen/home_screen.dart 파일에 기본 홈 UI 생성

import 'dart:math';

import 'package:facetime/screen/cam_screen.dart';
import 'package:flutter/material.dart';

class HomeScreen extends StatefulWidget {
  const HomeScreen({super.key});

  @override
  State<HomeScreen> createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Color(0xFF141414),
      body: SafeArea(
        child: Padding(
          padding: const EdgeInsets.symmetric(
            vertical: 80.0,
          ),
          child: Center(
            child: Column(
              children: [
                _Text(),
                Expanded(child: SizedBox()),
                _Button(),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

class _Text extends StatelessWidget {
  const _Text({super.key});

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text(
          'Hello',
          style: TextStyle(
            color: Colors.white,
            fontSize: 32.0,
          ),
        ),
        SizedBox(height: 8.0,),
        Text(
          'iPhone',
          style: TextStyle(
            color: Colors.white,
            fontSize: 16.0,
          ),
        ),
      ],
    );
  }
}

class _Button extends StatelessWidget {
  const _Button({super.key});

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      width: 66,
      height: 66,
      child: ElevatedButton(
        onPressed: () {
          Navigator.of(context).push(
            MaterialPageRoute(builder: (_) => CamScreen(), ),
          );
        },
        child: Center(
          child: Icon(
            Icons.call,
            size: 30.0,
          ),
        ),
        style: ElevatedButton.styleFrom(
          backgroundColor: Color(0xFF76D674),
          foregroundColor: Colors.white,
        ),
      ),
    );
  }
}

 

 

첫 화면의 통화 연결 버튼을 클릭하면 권한에 동의를 구하는 모달이 팝업된다. 

 


 

lib/screen/cam_screen.dart 파일에 영상 통화 코드 작성

import 'package:agora_rtc_engine/agora_rtc_engine.dart';
import 'package:agora_uikit/agora_uikit.dart';
import 'package:flutter/material.dart';
import 'package:video_call/const/keys.dart';

class CamScreen extends StatefulWidget {
  const CamScreen({super.key});

  @override
  State<CamScreen> createState() => _CamScreenState();
}

class _CamScreenState extends State<CamScreen> {
  RtcEngine? engine;
  int? uid = 0; // 0 인경우 아고라에서 랜덤하게 uid를 지정해줌
  int? remoteUid;

  Future<void> init() async {
    final resp = await [Permission.camera, Permission.microphone].request();

    final cameraPermission = resp[Permission.camera];
    final microphonePermission = resp[Permission.microphone];

    if (cameraPermission != PermissionStatus.granted ||
        microphonePermission != PermissionStatus.granted) {
      throw '카메라 또는 마이크 권한이 없습니다.';
    }

    if (engine == null) {
      engine = createAgoraRtcEngine();

      await engine!.initialize(
        RtcEngineContext(
          appId: appId,
        ),
      );

      engine!.registerEventHandler(
        RtcEngineEventHandler(
          onJoinChannelSuccess: (RtcConnection connection, int elapsed){
            // 내가 채널에 들어갔을 떄 연결정보와 시간
          },
            onLeaveChannel: (RtcConnection connection, RtcStats stauts){
            // 내가 채널에서 나갔을때 작동
            },
          onUserJoined: (RtcConnection connection,
          int remoteUid, int elapsed){
            print('---user joined---');
            setState(() {
              this.remoteUid = remoteUid;
            });

          },
          onUserOffline: (
              RtcConnection connection, //연결정보
              int remoteUid, //상대방이 오프라인 시
              UserOfflineReasonType reason,
          ){
            setState(() {
              this.remoteUid = null;
            });
          }
        )
      );


      await engine!.enableVideo(); //엔진 시작
      await engine!.startPreview(); //미리보기 시작

      ChannelMediaOptions options = ChannelMediaOptions();

      await engine!.joinChannel(
        token: token,
        channelId: channelName,
        uid: uid!,
        options: options,
      );
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(
          'LIVE',
        ),
      ),
      body: Expanded(
        child: FutureBuilder<void>(
          future: init(),
          builder: (context, snapshot) {
            if(snapshot.connectionState == ConnectionState.waiting){
              return Center(
                child: CircularProgressIndicator(),
              );
            }
            if(snapshot.hasError){
              child: Text(snapshot.error.toString());
            }
            return Stack(
              children: [
                Container(
                  child:renderMainView()
                ),
                Container(
                  width: 120.0,
                  height: 160.0,
                  child: AgoraVideoView(
                    controller: VideoViewController(
                      rtcEngine: engine!,
                      canvas: VideoCanvas(
                        uid: uid,
                      ),
                    ),
                  ),
                ),
                Positioned(
                  bottom: 16.0,
                  left: 20.0,
                  right: 20.0,
                  child: ElevatedButton(
                    onPressed: () {
                      // 엔진 종료
                      engine!.leaveChannel();
                      engine!.release();
                      Navigator.of(context).pop();
                    },
                    child: Text('나가기'),
                  ),
                ),
              ],
            );
          }
        ),
      ),
    );
  }

  renderMainView() {
    if (remoteUid == null) {
      return Center(
        child: CircularProgressIndicator(),
      );
    }

    return AgoraVideoView(
      controller: VideoViewController.remote(
        rtcEngine: engine!,
        canvas: VideoCanvas(
          uid: remoteUid,
        ),
        connection: RtcConnection(
          channelId: channelName,
        ),
      ),
    );
  }
}

 

agora basic video call 사이트로 이동 후,

agora 에서 생성한 APP ID, Token, Channel Name 입력해주면

현재 작업 중인 기기의 마이크와 카메라를 사용하여 영상 연결이 가능하다. 

좌측 상단에는 나의 영상, 그리고 메인 부분에는 상대방의 영상이 보여진다. stack으로 위젯을 쌓아 구현하였다.

https://webdemo.agora.io/basicVideoCall/index.html

 

위 이미지 처럼 영상통화가 연결이 정상적으로 되는 것을 확인 할 수 있다.

하단의 종료 버튼을 클릭하면 다시 첫화면으로 pop되며 agora 엔진을 종료한다.

간단하게 플러터로 페이스타임과 비슷한 영상통화 앱을 구현했고, 추후 더 디벨롭할 예정이다~