はじめに
以前、Qiita | FlutterでWebRTCをやってみる with AgoraSDK という記事を書いたのですが、その時点で Twilio の Flutter plugin はありませんでした。
最近、Twilio の Flutter plugin が Publish されていたので実際に触ってみることにしました。
Twilio
Twilio 社が開発した Twilio は コミュニケーションに特化した API を提供しています。
提供する機能は以下のように幅広く、様々なシチュエーションで利用可能です。
この中でも、ビデオ通話を利用したアプリを 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
にカメラとマイクの権限を追加
<key>NSCameraUsageDescription</key>
<string>Use camera</string>
<key>NSMicrophoneUsageDescription</key>
<string>Use mic</string>
<key>UIBackgroundModes</key>
<array>
<string>audio</string>
</array>
WebView
を利用するので以下も追加
<key>io.flutter.embedded_views_preview</key>
<true/>
Android
android/app/src/main/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
を作成し、以下を書いておきます
(難読化によるアプリクラッシュを防ぐ).
-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
part 'webrtc_state.freezed.dart';
@freezed
abstract class WebRtcState with _$WebRtcState {
const factory WebRtcState({
@Default(VideoInfo()) VideoInfo localVideoInfo,
@Default(VideoInfo()) VideoInfo remoteVideoInfo,
}) = _WebRtcState;
}
Notifier
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
final webRtcController = StateNotifierProvider.autoDispose(
(_) => WebRtcController(),
);
UI
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,
);
}
}
デモ
特に難しいところもなく Flutter
から Twilio
を利用することができました。
終わりに
今回作成したサンプルアプリは GitHub に公開しているのでご自由にお使いください。
yukitaka13-1110 / flutter-twilio-programmable-video-sample
コロナウイルスの影響もあってリモートワークやその他遠隔コミュニケーションでビデオ通話ができるツールの需要が高まってきているのを感じているので、その裏側を作るのも面白そうだなと思いました。