Flutter视频通话插件twilio_programmable_video的使用

发布于 1周前 作者 phonegap100 来自 Flutter

Flutter视频通话插件twilio_programmable_video的使用

插件简介

twilio_programmable_video 是一个Flutter插件,它允许你使用Twilio Programmable Video构建实时视频通话应用(WebRTC)。该插件由社区维护,并非由Twilio官方支持。当前版本仍在开发中,因此不建议用于生产环境。

示例代码

以下是一个完整的示例demo,展示了如何使用twilio_programmable_video进行视频通话:

项目结构

lib/
├── main.dart
└── room/
    └── selection_page.dart

main.dart

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
import 'package:twilio_programmable_video_example/room/selection_page.dart';
import 'package:twilio_programmable_video_example/shared/services/backend_service.dart';

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  SystemChrome.setPreferredOrientations([
    DeviceOrientation.landscapeRight,
    DeviceOrientation.landscapeLeft,
    DeviceOrientation.portraitUp,
    DeviceOrientation.portraitDown,
  ]);
  runApp(TwilioProgrammableVideoExample());
}

class TwilioProgrammableVideoExample extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return FutureBuilder(
      future: Firebase.initializeApp(),
      builder: (context, snapshot) {
        if (snapshot.connectionState == ConnectionState.done) {
          return Provider<BackendService>(
            create: (_) => FirebaseFunctionsService.instance,
            child: MaterialApp(
              title: 'Twilio Programmable Video',
              theme: ThemeData(
                primarySwatch: Colors.blue,
                appBarTheme: AppBarTheme(
                  color: Colors.blue,
                  titleTextStyle: TextStyle(
                    fontSize: 20,
                    fontWeight: FontWeight.w500,
                    color: Colors.white,
                  ),
                ),
              ),
              home: SelectionPage(),
            ),
          );
        } else {
          return Center(child: CircularProgressIndicator());
        }
      },
    );
  }
}

selection_page.dart

import 'package:flutter/material.dart';
import 'package:twilio_programmable_video/twilio_programmable_video.dart';
import 'package:provider/provider.dart';

class SelectionPage extends StatefulWidget {
  @override
  _SelectionPageState createState() => _SelectionPageState();
}

class _SelectionPageState extends State<SelectionPage> {
  Room? _room;
  final Completer<Room> _completer = Completer<Room>();
  TextEditingController _accessTokenController = TextEditingController();
  TextEditingController _roomNameController = TextEditingController();

  void _onConnected(Room room) {
    print('Connected to ${room.name}');
    _completer.complete(_room);
  }

  void _onConnectFailure(RoomConnectFailureEvent event) {
    print('Failed to connect to room ${event.room.name} with exception: ${event.exception}');
    _completer.completeError(event.exception);
  }

  Future<void> _connectToRoom() async {
    var accessToken = _accessTokenController.text;
    var roomName = _roomNameController.text;

    var cameraSources = await CameraSource.getSources();
    var cameraCapturer = CameraCapturer(
      cameraSources.firstWhere((source) => source.isFrontFacing),
    );

    var connectOptions = ConnectOptions(
      accessToken,
      roomName: roomName,
      audioTracks: [LocalAudioTrack(true)],
      videoTracks: [LocalVideoTrack(true, cameraCapturer)],
    );

    _room = await TwilioProgrammableVideo.connect(connectOptions);
    _room?.onConnected.listen(_onConnected);
    _room?.onConnectFailure.listen(_onConnectFailure);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Twilio Programmable Video')),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          children: [
            TextField(
              controller: _accessTokenController,
              decoration: InputDecoration(labelText: 'Access Token'),
            ),
            TextField(
              controller: _roomNameController,
              decoration: InputDecoration(labelText: 'Room Name'),
            ),
            SizedBox(height: 20),
            ElevatedButton(
              onPressed: _connectToRoom,
              child: Text('Join Room'),
            ),
          ],
        ),
      ),
    );
  }
}

后端服务配置

为了生成访问令牌,你需要设置一个后端服务。这里我们使用Firebase Functions作为示例:

backend_service.dart

import 'package:firebase_functions/firebase_functions.dart';

class FirebaseFunctionsService {
  static final FirebaseFunctionsService _instance = FirebaseFunctionsService._internal();
  factory FirebaseFunctionsService.instance() => _instance;
  FirebaseFunctionsService._internal();

  final functions = FirebaseFunctions.instance;

  Future<String> getAccessToken(String identity) async {
    final HttpsCallable callable = functions.httpsCallable('getAccessToken');
    final result = await callable.call({'identity': identity});
    return result.data as String;
  }
}

Firebase Cloud Function (Node.js)

在你的Firebase项目中添加一个Cloud Function来生成访问令牌:

index.js

const functions = require('firebase-functions');
const twilio = require('twilio');

const accountSid = 'your_account_sid';
const apiKey = 'your_api_key';
const apiSecret = 'your_api_secret';

const client = new twilio(accountSid, apiKey, {apiSecret});

exports.getAccessToken = functions.https.onCall(async (data, context) => {
  const identity = data.identity;
  const token = client.tokens.create({identity});
  return token.toJwt();
});

配置说明

Android

  1. 权限:在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"/>
    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
    
  2. Proguard:在android/app/proguard-rules.pro中添加以下内容:
    -keep class tvi.webrtc.** { *; }
    -keep class com.twilio.video.** { *; }
    -keep class com.twilio.common.** { *; }
    -keepattributes InnerClasses
    

iOS

  1. 权限:在ios/Runner/Info.plist中添加以下权限:
    <key>NSCameraUsageDescription</key>
    <string>Your message to user when the camera is accessed for the first time</string>
    <key>NSMicrophoneUsageDescription</key>
    <string>Your message to user when the microphone is accessed for the first time</string>
    <key>io.flutter.embedded_views_preview</key>
    <true/>
    
  2. Podfile:在ios/Podfile中添加以下内容:
    post_install do |installer|
      installer.pods_project.targets.each do |target|
        flutter_additional_ios_build_settings(target)
        target.build_configurations.each do |config|
          config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [
            '$(inherited)',
            'PERMISSION_CAMERA=1',
            'PERMISSION_MICROPHONE=1',
          ]
        end
      end
    end
    

Web

web/index.html中添加以下脚本:

<script src="//media.twiliocdn.com/sdk/js/video/releases/2.14.0/twilio-video.min.js"></script>

连接到房间

使用TwilioProgrammableVideo.connect()方法连接到房间。连接成功后,你可以发送和接收音频和视频流。

Room _room;
final Completer<Room> _completer = Completer<Room>();

Future<Room> connectToRoom() async {
  var cameraSources = await CameraSource.getSources();
  var cameraCapturer = CameraCapturer(
    cameraSources.firstWhere((source) => source.isFrontFacing),
  );

  var connectOptions = ConnectOptions(
    accessToken,
    roomName: roomName,
    audioTracks: [LocalAudioTrack(true)],
    videoTracks: [LocalVideoTrack(true, cameraCapturer)],
  );

  _room = await TwilioProgrammableVideo.connect(connectOptions);
  _room.onConnected.listen(_onConnected);
  _room.onConnectFailure.listen(_onConnectFailure);
  return _completer.future;
}

参与者管理

你可以通过监听事件来处理参与者连接、断开连接等操作。

_room.onParticipantConnected((RoomParticipantConnectedEvent event) {
  print('Participant ${event.remoteParticipant.identity} has joined the room');
});

_room.onParticipantDisconnected((RoomParticipantDisconnectedEvent event) {
  print('Participant ${event.remoteParticipant.identity} has left the room');
});

断开连接

你可以通过调用room.disconnect()方法断开与房间的连接。

await room.disconnect();

总结

以上是一个完整的示例demo,展示了如何使用twilio_programmable_video插件进行视频通话。请根据实际需求调整代码,并确保按照文档中的配置步骤进行设置。如果你有任何问题或需要进一步的帮助,请加入Discord社区或参考FAQ


更多关于Flutter视频通话插件twilio_programmable_video的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter视频通话插件twilio_programmable_video的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,以下是一个关于如何在Flutter项目中使用twilio_programmable_video插件来实现视频通话的示例代码。这个示例将展示如何初始化Twilio视频通话客户端,创建房间,并连接到房间进行视频通话。

首先,确保你已经在pubspec.yaml文件中添加了twilio_programmable_video依赖:

dependencies:
  flutter:
    sdk: flutter
  twilio_programmable_video: ^0.6.3  # 请注意版本号,使用最新版本

然后,运行flutter pub get来安装依赖。

接下来,你需要配置Twilio的Access Token。这通常是通过你的服务器生成的,并且包含用于身份验证和媒体连接的所有必要信息。为了简化示例,这里假设你已经有一个有效的Access Token。

主代码示例

  1. 初始化Twilio Video客户端
import 'package:flutter/material.dart';
import 'package:twilio_programmable_video/twilio_programmable_video.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: VideoCallScreen(),
    );
  }
}

class VideoCallScreen extends StatefulWidget {
  @override
  _VideoCallScreenState createState() => _VideoCallScreenState();
}

class _VideoCallScreenState extends State<VideoCallScreen> {
  late TwilioVideoLocalVideoRenderer localRenderer;
  late TwilioVideoRemoteVideoRenderer remoteRenderer;
  late TwilioVideo? videoClient;
  late Room? room;

  @override
  void initState() {
    super.initState();
    initRenderers();
    connectToRoom();
  }

  void initRenderers() {
    localRenderer = TwilioVideoLocalVideoRenderer();
    remoteRenderer = TwilioVideoRemoteVideoRenderer();

    localRenderer.init(
      remoteView: RemoteView(
        uid: 0,  // Use 0 for local participant
        view: VideoView(localRenderer),
      ),
    ).then((_) {
      setState(() {});
    });

    remoteRenderer.init(
      remoteViews: [
        RemoteView(
          uid: 1,  // Placeholder for remote participant
          view: VideoView(remoteRenderer),
        ),
      ],
    ).then((_) {
      setState(() {});
    });
  }

  Future<void> connectToRoom() async {
    // 假设你已经有一个有效的Access Token
    const accessToken = "YOUR_ACCESS_TOKEN_HERE";
    videoClient = await TwilioVideo.create(accessToken);

    room = await videoClient!.connect(
      RoomOptions(
        roomName: "your_room_name",
        videoOptions: VideoOptions(
          localRenderer: localRenderer,
          remoteRenderer: remoteRenderer,
        ),
      ),
    );

    room!.on('participantConnected', (participant) {
      print("Participant connected: ${participant.identity}");
      remoteRenderer.addRemoteView(
        RemoteView(
          uid: participant.sid!,
          view: VideoView(remoteRenderer),
        ),
      );
    });

    room!.on('participantDisconnected', (participant) {
      print("Participant disconnected: ${participant.identity}");
      remoteRenderer.removeRemoteView(participant.sid!);
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Twilio Video Call'),
      ),
      body: Column(
        children: [
          Expanded(
            child: localRenderer.viewContainer,
          ),
          Expanded(
            child: remoteRenderer.viewContainer,
          ),
        ],
      ),
    );
  }

  @override
  void dispose() {
    room?.disconnect();
    videoClient?.dispose();
    localRenderer.dispose();
    remoteRenderer.dispose();
    super.dispose();
  }
}

注意事项

  1. Access Token:上面的代码示例中,YOUR_ACCESS_TOKEN_HERE需要替换为你从Twilio服务器获取的有效的Access Token。在实际应用中,你通常需要通过你的后端服务器生成并分发这个Token。

  2. 错误处理:示例代码没有包含错误处理逻辑。在实际应用中,你应该添加适当的错误处理来确保应用的健壮性。

  3. UI布局:示例代码使用了简单的Column布局来展示本地和远程视频。你可能需要根据实际需求调整UI布局。

  4. 权限:确保你的应用具有相机和麦克风权限。

  5. 依赖版本:请确保使用twilio_programmable_video的最新版本,并查阅其官方文档以获取最新的API和最佳实践。

通过上述代码,你应该能够在Flutter应用中实现基本的Twilio视频通话功能。

回到顶部