Flutter Developer Guide - Video Conference

Omnitalk·2023년 5월 22일
1

안녕하세요. 오늘은 옴니톡 SDK를 이용하여 영상 회의실을 만드는 방법에 대해서 설명하려합니다.

이 문서는 영상 컨퍼런스 기능 개발에 대한 레퍼런스를 제공하며, 4인이 참여하는 예제를 제공합니다. 옴니톡의 영상 컨퍼런스 최대 참여 인원은 서버의 물리적 인스턴스에 따라 Scale In/Out이 가능한 구조이며, 클라이언트가 동시에 시청 가능한 영상 최대 개수는 32개로 제한됩니다. 이는 클라이언트의 접속 환경(cpu, memory, network)을 고려한 최대 수치이며, 일반적인 경우 10개 내외로 동시 시청 범위를 제한할 것을 권장합니다.

https://github.com/omnistory-labs/flutter.library

전체 소스는 해당 링크에서 확인 가능 합니다.

서비스 개발 순서

영상 컨퍼런스를 실행하기 위한 폴더 구조는 다음과 같습니다.

예제 | main.dart

예제는 Andriod MaterialApp으로 작성되었습니다. 앱 진입을 위한 홈 화면을 연결합니다.

import 'package:flutter/material.dart';
import 'package:sample/screen/home_screen.dart';
 
void main( ) {
	runApp( const MaterialApp(
		home: HomeScreen( ),
	));
}

예제 | home_screen.dart

화면 구성을 위한 위젯입니다.

import 'package:flutter/material.dart';
import 'package:sample/screen/video_conference.dart';
 
class HomeScreen extends StatelessWidget {
const HomeScreen({ super.key });

@override
Widget build(BuildContext context) {
	return Scaffold(
		backgroundColor: Colors.orange[100]!,
		body: SafeArea(
		  child: Padding(
			padding: const EdgeInsets.all(8.0),
			child: const [
			//Text('this will be omnitalk logo'),
			Expanded(child: Logo( )),
			Expanded(child: _Image( )),
			Expanded(child: EntryButton( )),
			],
		   ),
		  ),
		 ),
		);
	   }
}

예제 | video_conference.dart
예제는 FutureBuilder 위젯과 GridView 위젯을 이용한 간단한 영상 컨퍼런스 예시 코드입니다.

1. 필수 패키지 import

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_webrtc/flutter_webrtc.dart';
import 'package:omnitalk_sdk/omnitalk_sdk.dart';
import 'package:sample/screen/home_screen.dart';

2. 필수 변수 선언 및 초기화

옴니톡 객체를 선언해 sdk 사용 준비를 합니다. 세션의 시작과 종료 및 각종 이벤트는 모두 session id로 관리합니다. 이를 담아둘 변수를 선언합니다. user id, room subject 등은 optional입니다. 예제는 참여 가능한 룸을 조회해 들어가거나 따로 방을 생성할 수 있는 시나리오로 구성되어 있습니다. 이를 위해 selectedRoomId를 선언합니다. roomId는 참여하고자는 룸의 id 입니다. 참여가능한 룸을 조회하거나 룸에 들어와 방송을 개시한 참여자의 리스트도 roomList, partiList에 담아둡니다.

Omnitalk omnitalk;
String sessionId = ' ';
String  _roomSubject = ' ';
String?  selectedRoomId = ' ';
var roomId;
List<dynamic>_roomList = [];
ListpartiList = [];

비디오 영상을 담을 renderer 객체를 선언해둡니다. 예제는 로컬용 하나, 리모트용 3개를 준비했습니다.
예제는 GridView 위젯을 사용해 영상을 띄울 것이므로 원하는 사용자수만큼 renderer를 만들면 됩니다. 원하는 사용자수만큼 renderer를 만들면 됩니다. renderer들을 담아 관리할 리스트와 비디오 디스플레이 on/off를 제어하기 위한 count 변수와 boolean 플래그도 선언해둡니다.

RTCVideoRenderer localVideo = RTCVideoRenderer ( );
RTCVideoRenderer remoteVideo1 = RTCVideoRenderer ( );
RTCVideoRenderer remoteVideo2 = RTCVideoRenderer ( );
RTCVideoRenderer remoteVideo3 = RTCVideoRenderer ( );
List<RTCVideoRenderer> renderers = [ ];
int count = 1;
List<bool>  flags = [false, false, false];
새로 룸 생성시 textfield에서 디바운싱 처리를 위한 변수 및 controller와 상태에 따른 버튼 비활성화를 위한 버튼 플래그를 선언합니다.

새로 룸 생성시 textfield에서 디바운싱 처리를 위한 변수 및 controller와 상태에 따른 버튼 비활성화를 위한 버튼 플래그를 선언합니다.

var _futureRoomList;
Timer? _debounce;
final TextEditingController _inputController = TextEditingController ( );
final FocusNode focusnode = FocusNode ( );
bool isDropdonwSelected = false;
bool isBroadcastingStarted = false;

3. Omnitalk 인스턴스화

옴니톡의 service id와 service key를 인수로 전달해 옴니톡 객체를 생성합니다.

_VideoConferenceDemoState( ): omnitalk = Omnitalk("service_id","service_key")  { }

4. onmessage 콜백 작성

옴니톡 시그널링 서버에서 보내는 이벤트 메시지를 수신하고 이에 따라 구성할 로직 작성을 위해 onmessage 콜백을 작성합니다. 비디오 컨퍼런스에서 타인의 방송 구독을 위해 반드시 작성해야하는 이벤트는 BROADCASTING_EVENT 입니다.

omnitalk.onmessage = ( event ) async {
    
    switch (event["cmd"])  {
        case "SESSION_EVENT":
             print('Session started ${event["session"]}');
            break;
        case "BROADCASTING_EVENT":
             await omnitalk.subscribe(
            publish_idx: event["publish_idx"],
            remoteRenderer: remoteVideos[count]);
          setState(() {
             count++;
            });
            break;
        case "ONAIR_EVENT":
             print(event["track_type"]);
            break;
        case "LEAVE_EVENT":
             print('Session closed ${event["session"]}');
            break;
    }
};

5. 세션 시작

sdk에서 사용할 api는 장치 접근 허용 요청을 위한 omnitalk.getPermission()과 세션을 시작하는 omnitalk.createSession()입니다. 예제는 FutureBuilder 위젯에서 사용할 future 프로퍼티로 세션의 시작 핸들러를 사용했습니다. 그리고 initState()로 관리합니다.

_onCreateSession( ) async  {
  await omnitalk.getPermission( );
  var session = await omnitalk.createSession( );
  sessionId = session["session"];
  return await omnitalk.roomList( );
}

@override
void initState( ) {
  super.initState( );
   _futureRoomList = _onCreateSession( );
}

6. 룸 생성 및 참여

예제는 룸 리스트를 구해 기존 개설된 룸에 들어가거나 새로 룸을 만들 수 있도록 구성되어 있습니다. 이를 위해 룸 참여를 위해 두 가지 핸들러를 작성했습니다.

//드랍다운 리스트에서 선택시
_onJoinRoom( ) async  {
  await omnitalk.joinRoom(room_id: selectedRoomId);
}
//새로 룸 생성시
_onCreateJoinRoom( ) async  {
  var  roomObj = await omnitalk.createRoom(subject:     
  _roomSubject);
  roomId = roomObj?["room_id"];
  await omnitalk.joinRoom(room_id: roomId);
  isDropdonwSelected = true;
}
_onCreateNewRoom( ) async  {
  setState(( ) {
    isDropdonwSelected = true;
    _roomSubject = _inputController.text;
   });
 await _onCreateJoinRoom( );
  focusnode.unfocus(disposition:    
  UnfocusDisposition.previouslyFocusedChild);
}

7. 방송 개시 및 구독

룸에 들어가 자신의 방송을 개시하고 타인의 방송을 구독할 수 있습니다.

_onPubSub( ) async  {
  await omnitalk.publish(localRenderer: localVideo);
  var partiResult = await omnitalk.partiList(roomId);
     for (var parti in partiResult)  {
         int pubIdx = parti["publish_idx"];
         partiList.add(pubIdx);
     }

     for (int i = 0;  i< partiList.length; i++)  {
         int pubIdx = partiList[i];
         partiList.add(pubIdx);
         await omnitalk.subscribe(
         publish_idx: pubIdx, remoteRenderer: 
         renderers[i + 1]);
         count++;
      }
    setState(( ) {
        renderers;
   });
}

8. 방송영상 송출을 위한 item builder

GridView에서 사용할 item들을 만들어주는 함수입니다.

_buildVideoItems(int count) {
  List<Widget> items = [];
  for ( int i = 0;  i< count; i++)  {
    items.add(RTCVideoView(
      renderers[i],
      objectFit: RTCVideoViewObjectFit.RTCVideoViewObjectFitCover,
     mirror: i == 0 ? true : false,
   ));
  }
 return items;
}

9. 방송 종료

방송 종료를 위해 omnitalk.leave( ) api를 사용하면 인수로 전달한 세션이 끊어집니다. 더불어 예제에서는 textfield의 컨트롤러 및 localRenderer도 종료하고 홈 화면으로 돌아가도록 구성하였습니다.

_onLeave( ) async  {
  Navigator.of(context).push(
    MaterialPageRoute(
     builder: (_) => const HomeScreen(),
     ),
   );

  await omnitalk.leave(sessionId);
  _inputController.dispose( );
  await localVideo.dispose( );
}

omnitalk은 개발자들을 위해 1시간 무료로 사용가능한 test key를 제공합니다.

https://omnitalk.io/demo/video에서 테스트키를 발급 받을 수 있으며,

발급 받은 키를 통해서 다양한 데모를 체험해 볼 수 있습니다.

궁금하신 점이 있다면 언제든 댓글로 질문해주세요^^ 감사합니다.

profile
옴니톡의 기술 블로그입니다.

1개의 댓글

comment-user-thumbnail
2023년 5월 24일

플러터로 WebRTC 비대면 통신 서비스를 개발하려는 스타트업입니다.
관련하여 기술 지원도 가능한지 궁금합니다.

답글 달기