search
LoginSignup
10

posted at

updated at

FlutterでWebRTCをやってみる with Twilio

はじめに

以前、Qiita | FlutterでWebRTCをやってみる with AgoraSDK という記事を書いたのですが、その時点で Twilio の Flutter plugin はありませんでした。
最近、Twilio の Flutter plugin が Publish されていたので実際に触ってみることにしました。

Twilio

image.png

Twilio 社が開発した Twilio は コミュニケーションに特化した API を提供しています。
提供する機能は以下のように幅広く、様々なシチュエーションで利用可能です。

スクリーンショット 2020-12-10 1.18.03.png

この中でも、ビデオ通話を利用したアプリを Flutter で実装してみます。

実装

実際にサンプルアプリを実装していきます.

Twilio に登録

Twilio を利用するには認証やその準備などがあるので、利用ハードルは少し高いです。
ちょっと試すだけなら全然大丈夫ですが、長く利用する予定がある場合は以下の記事のような手順を踏む必要があります。

TwilioでBundles(本人認証)の設定を行う(個人編)

Token 用の function 作成

各種設定は以下の記事を参考にすると良いです。

Qiita | Twilio WebRTCハンズオン 2020版

Flutter Project の作成

flutter create -t app --org com.example.twilioSample --project-name flutter_twilio_sample -i swift -a kotlin ./

Platform Settings

Platform 毎に権限周りや固有の設定をしていきます.

iOS

ios/Runner/info.plist にカメラとマイクの権限を追加

info.plist
<key>NSCameraUsageDescription</key>
<string>Use camera</string>
<key>NSMicrophoneUsageDescription</key>
<string>Use mic</string>
<key>UIBackgroundModes</key>
<array>
  <string>audio</string>
</array>

WebView を利用するので以下も追加

info.plist
<key>io.flutter.embedded_views_preview</key>
<true/>

Android

android/app/src/main/AndroidManifest.xml に以下の権限を追加します.

AndroidManifest.xml
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.BLUETOOTH"/>

android/app/src/proguard-rules.pro を作成し、以下を書いておきます
(難読化によるアプリクラッシュを防ぐ).

proguard-rules.pro
-keep class tvi.webrtc.** { *; }
-keep class com.twilio.video.** { *; }
-keep class com.twilio.common.** { *; }
-keepattributes InnerClasses

platform 固有の設定は以上です.

twilio_programmable_video を用いた実装

Plugin の追加

以下の Plugin を追加してください。

Dart pub | twilio_programmable_video

Twilio 部分の実装

状態管理は flutter_riverpod + state_notifier を利用してみます。

State

webrtc_state.dart
part 'webrtc_state.freezed.dart';

@freezed
abstract class WebRtcState with _$WebRtcState {
  const factory WebRtcState({
    @Default(VideoInfo()) VideoInfo localVideoInfo,
    @Default(VideoInfo()) VideoInfo remoteVideoInfo,
  }) = _WebRtcState;
}

Notifier

webrtc_controller.dart
class WebRtcController extends StateNotifier<WebRtcState> {
  WebRtcController() : super(const WebRtcState()) {
    _initializeWebRtc();
  }

  CameraCapturer _cameraCapturer;
  Room _room;
  StreamSubscription _onConnectedSubscription;
  StreamSubscription _onParticipantConnectedSubscription;
  StreamSubscription _onParticipantDisconnectedSubscription;
  final _remoteParticipantSubscriptions = <StreamSubscription>[];

  Future<void> _initializeWebRtc() async {
    // Token の取得
    final response = await http.get(Constants.twilioTokenEndpoint);
    final data = json.decode(response.body) as Map<String, dynamic>;

    // Mode の設定
    await TwilioProgrammableVideo.debug(dart: true, native: true);

    // 権限のリクエスト
    await TwilioProgrammableVideo.requestPermissionForCameraAndMicrophone();

    // Video Source の取得
    _cameraCapturer = CameraCapturer(CameraSource.FRONT_CAMERA);

    // 接続情報の定義
    final connectOptions = ConnectOptions(
      data['token'] as String,
      roomName: 'VideoRoom',
      preferredAudioCodecs: [OpusCodec()],
      audioTracks: [LocalAudioTrack(true)],
      dataTracks: [LocalDataTrack()],
      videoTracks: [LocalVideoTrack(true, _cameraCapturer)],
      enableNetworkQuality: true,
      networkQualityConfiguration: NetworkQualityConfiguration(
        remote: NetworkQualityVerbosity.NETWORK_QUALITY_VERBOSITY_MINIMAL,
      ),
      enableDominantSpeaker: true,
    );

    // Room に接続
    _room = await TwilioProgrammableVideo.connect(connectOptions);

    // イベントリスナーをセット
    if (_room != null) {
      _onConnectedSubscription = _room.onConnected.listen(_onConnected);
      _onParticipantConnectedSubscription =
          _room.onParticipantConnected.listen(_onParticipantConnected);
      _onParticipantDisconnectedSubscription =
          _room.onParticipantDisconnected.listen(_onParticipantDisconnected);
    }
  }
}

Provider

state_providers.dart
final webRtcController = StateNotifierProvider.autoDispose(
  (_) => WebRtcController(),
);

UI

webrtc_room_page.dart
class WebRtcRoomPage extends ConsumerWidget {
  const WebRtcRoomPage({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context, ScopedReader watch) {
    final controller = watch(webRtcController);
    final state = watch(webRtcController.state);

    return Scaffold(
      appBar: null,
      body: Column(
        mainAxisAlignment: MainAxisAlignment.start,
        children: [
          _videoWidget(context, state.localVideoInfo.widget),
          _videoWidget(context, state.remoteVideoInfo.widget),
        ],
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () async {
          await controller.endCall();
          Navigator.of(context)?.pop();
        },
        backgroundColor: Colors.red,
        child: const Icon(
          Icons.call_end,
          color: Colors.white,
        ),
      ),
    );
  }

  Widget _videoWidget(BuildContext context, Widget widget) {
    final height = MediaQuery.of(context).size.height;
    final width = MediaQuery.of(context).size.width;

    if (widget == null) {
      return Placeholder(
        fallbackWidth: width,
        fallbackHeight: height / 2.0,
      );
    }

    return SizedBox(
      width: width,
      height: height / 2.0,
      child: widget,
    );
  }
}

デモ

IMB_fmPZBj.GIF

特に難しいところもなく Flutter から Twilio を利用することができました。

終わりに

今回作成したサンプルアプリは GitHub に公開しているのでご自由にお使いください。

yukitaka13-1110 / flutter-twilio-programmable-video-sample

コロナウイルスの影響もあってリモートワークやその他遠隔コミュニケーションでビデオ通話ができるツールの需要が高まってきているのを感じているので、その裏側を作るのも面白そうだなと思いました。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
10