Flutter语音通话插件twilio_voice_flutter的使用

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

Flutter语音通话插件twilio_voice_flutter的使用

描述

twilio_voice_flutter 包简化了与 Twilio 的 Programmable Voice SDK 的集成,使您可以在 Flutter 应用中实现 VoIP 通话。它支持 iOS 和 Android 平台,提供了易于使用的 API 来管理通话。该插件非常适合客户服务、通讯或任何需要实时语音功能的应用程序,利用 Twilio 可靠的基础架构来提供高质量的 VoIP 功能。

Twilio Voice Banner

开始使用

添加依赖

pubspec.yaml 文件中添加依赖项并运行 Pub get:

dependencies:
  twilio_voice_flutter: ^0.0.4

然后在您的 Dart 文件中导入包:

import 'package:twilio_voice_flutter/twilio_voice_flutter.dart';

重要提示

⚠️ 此插件需要 Firebase 设置才能进行 Twilio 语音通话!它使用 FCM 令牌处理 Android 上的通话 📲 和 VoIP 通知处理 iOS 上的通话 📞。确保两个平台都正确配置以确保通话功能顺畅。 ⚠️

平台设置

Android

要将 Twilio Voice 插件集成到您的 Android 项目中,请执行以下步骤:

  1. 打开项目的 AndroidManifest.xml 文件。
  2. <application> 标签内添加以下服务声明:
<service
    android:name="com.twilio.voice.flutter.fcm.VoiceFirebaseMessagingService"
    android:exported="false"
    android:stopWithTask="false">
    <intent-filter> 
        <action android:name="com.google.firebase.MESSAGING_EVENT" />
    </intent-filter> 
</service>

iOS

要配置您的 iOS 项目以支持 VoIP 通话,请按照以下步骤操作:

  1. 在 Xcode 中打开您的项目。
  2. 从 Project Navigator 中选择您的项目。
  3. 转到 Signing & Capabilities 标签页。
  4. 启用以下后台模式:
    • Audio, AirPlay, and Picture in Picture:允许应用程序在后台继续播放音频。
    • Voice over IP:允许应用程序在后台接收传入的 VoIP 通话。
  5. 确保 Info.plist 文件包含所需的键:
<key>UIBackgroundModes</key>
<array>
    <string>audio</string>
    <string>voip</string>
</array>

Twilio 语音通话访问令牌设置

此插件支持 Twilio 语音通话,适用于 Android 和 iOS。要生成访问令牌并启用 Twilio 通话,请参考官方 Twilio 存储库中的说明:

这些存储库提供了详细的指导,帮助您生成访问令牌并通过 Twilio 在 Android 和 iOS 平台上进行语音通话。⚡ 确保遵循文档中的步骤,成功将 Twilio 语音功能集成到您的应用程序中。

设置 Twilio 令牌

setTwilioToken 函数用于使用提供的访问令牌注册用户的标识信息到 Twilio Voice。此函数处理 Android 设备上的 Firebase Cloud Messaging (FCM) 令牌的检索,并注册用户到 Twilio Voice。

static Future<bool> setTwilioToken(String identity, String accessToken) async {
  try {
    String accessToken = await _getAccessToken(identity);
    if (Platform.isAndroid) {
      String? fcmToken = await FirebaseMessaging.instance.getToken() ?? "";
      await TwilioVoiceFlutter.register(
          identity: identity, accessToken: accessToken, fcmToken: fcmToken);
    } else {
      await TwilioVoiceFlutter.register(
          identity: identity, accessToken: accessToken, fcmToken: "");
    }
    return true;
  } catch (_) {
    return false;
  }
}

功能概述

VoIP 通话管理

  • 发起通话:使用 makeCall 方法轻松开始 VoIP 通话,能够传递自定义通话数据。
  • 接收传入通话:通过推送通知处理传入通话邀请,并使用内置 CallKit 支持在 iOS 上接听通话。
  • 通话状态通知:通过 Flutter MethodChannel 回调保持通话状态(如响铃、连接和断开)的更新。

通话控制

  • 静音/取消静音:使用 toggleMute 方法切换正在进行的通话静音状态。使用 isMuted 方法检查当前是否静音。
  • 扬声器控制:使用 toggleSpeaker 在设备内置扬声器和听筒之间切换。使用 isSpeaker 验证当前音频路由。

iOS CallKit 集成

  • CallKit 支持:在 iOS 上使用原生 CallKit 功能管理传入和传出 VoIP 通话,确保熟悉用户体验。
  • 传入通话处理:向 CallKit 报告传入通话,使系统显示原生通话 UI 并正确处理中断。
  • 后台 VoIP 支持:通过启用必要的后台模式并相应配置 Info.plist,确保应用程序能够在后台接收 VoIP 通话。

推送通知支持

  • VoIP 推送通知:通过 Firebase Cloud Messaging (FCM) 处理 Android 上的 VoIP 推送通知和 Apple Push Notification service (APNs) 处理 iOS 上的 VoIP 推送通知。
  • 推送凭证管理:管理接收 VoIP 推送通知所需的设备和访问令牌,确保可靠通信。

DTMF(双音多频)信令

  • 发送 DTMF 数字:使用 sendDigits 方法在活动通话期间发送 DTMF 音调(例如,与自动电话系统交互)。

联系人管理

  • 自定义联系人数据:存储和检索自定义联系人数据(如显示名称和照片 URL),以增强通话体验,提供个性化信息。

持久化数据存储

  • 令牌和联系人数据持久化:使用 iOS 上的 UserDefaults 安全地存储和检索访问令牌和联系人数据,以维护跨应用会话的状态。

带有错误管理的通话处理

  • 优雅的错误处理:全面的错误处理确保有效管理和报告问题,如令牌过期或失败的通话连接。

函数概览

  • registerTwilio():使用访问令牌和设备令牌为 VoIP 推送通知注册设备。
  • unregisterTwilio():注销设备的 Twilio VoIP 推送通知,移除访问令牌和设备令牌。
  • makeCall(String to):发起一个语音通话到指定的接收者。如果存在活动通话,则返回错误。
  • toggleMute():切换正在进行的通话静音状态。通知 Flutter 端更改。
  • isMuted():返回正在进行的通话当前静音状态。
  • toggleSpeaker():切换正在进行的通话扬声器模式。通知 Flutter 端更改。
  • isSpeaker() -> Bool:检查当前是否处于扬声器模式。
  • hangUp():结束当前通话。如果没有活动通话,则清除通话相关数据。
  • activeCall():返回存在的活动通话详情。
  • sendDigits(String digits):在活动通话期间发送 DTMF 数字。

示例代码

下面是一个完整的示例代码,展示了如何在 Flutter 应用中使用 twilio_voice_flutter 插件:

import 'dart:async';

import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:twilio_voice_flutter/model/event.dart';
import 'package:twilio_voice_flutter/model/status.dart';
import 'package:twilio_voice_flutter/twilio_voice_flutter.dart';
import 'package:twilio_voice_flutter_example/firebase_options.dart';
import 'package:twilio_voice_flutter_example/twilio_voice_services.dart';

GlobalKey<NavigatorState> appKey = GlobalKey<NavigatorState>();

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
  TwilioVoiceFlutter.init();
  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      navigatorKey: appKey,
      title: 'Twilio Voice Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(),
    );
  }
}

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

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  bool _isSpeaker = false;
  bool _isMuted = false;
  bool _isCalling = false;
  String _callStatus = "";

  StreamSubscription<TwilioVoiceFlutterEvent>? callEventsListener;

  final TextEditingController identifyController = TextEditingController();

  void setCallEventsListener() {
    callEventsListener?.cancel();
    callEventsListener = null;
    callEventsListener = TwilioVoiceServices.callEventsListener.listen((event) {
      if (event.status == TwilioVoiceFlutterStatus.ringing ||
          event.status == TwilioVoiceFlutterStatus.connected) {
        _callStatus = "Ringing...";
      } else if (event.status == TwilioVoiceFlutterStatus.connecting) {
        _callStatus = "Connecting...";
      } else if (event.status == TwilioVoiceFlutterStatus.reconnected) {
        _callStatus = "Reconnected...";
      } else if (event.status == TwilioVoiceFlutterStatus.disconnected) {
        endCall();
      }
      setState(() {});
    });
  }

  @override
  void initState() {
    TwilioVoiceServices.initialize();
    setCallEventsListener();
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        centerTitle: true,
        title: const Text("Twilio Voice Call Example"),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          children: [
            TextField(
              controller: identifyController,
              decoration: InputDecoration(
                  hintText: "Enter call identifier", enabled: !_isCalling),
            ),
            const Spacer(),
            Text(
              _callStatus,
              style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w600),
            ),
            const Spacer(),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                IconButton.filled(
                  onPressed: () {
                    toggleMuted();
                  },
                  icon: _isMuted
                      ? const Icon(Icons.mic, size: 30,)
                      : const Icon(Icons.mic_off_rounded, size: 30,),
                ),
                const SizedBox(width: 15,),
                Theme(
                  data: ThemeData(
                      iconButtonTheme: IconButtonThemeData(
                          style: ButtonStyle(
                              backgroundColor: MaterialStatePropertyAll<Color>(
                                  _isCalling ? Colors.red : Colors.green)))),
                  child: IconButton.filled(
                    color: Colors.white,
                    onPressed: () {
                      if (!_isCalling) {
                        makeCall(identifyController.text);
                      } else {
                        endCall();
                      }
                    },
                    icon: _isCalling
                        ? const Icon(Icons.call_end, size: 30,)
                        : const Icon(Icons.call, size: 30,),
                  ),
                ),
                const SizedBox(width: 15,),
                IconButton.filled(
                    onPressed: () {
                      toggleSpeaker();
                    },
                    icon: _isSpeaker
                        ? const Icon(CupertinoIcons.speaker_fill, size: 30,)
                        : const Icon(CupertinoIcons.speaker_slash_fill, size: 30,)),
              ],
            )
          ],
        ),
      ),
    );
  }

  void endCall() async {
    await TwilioVoiceServices.hangUp();
    setState(() {
      _isCalling = false;
      _callStatus = "";
    });
  }

  void makeCall(String identify) async {
    setState(() {
      _isCalling = true;
    });
    final status = await TwilioVoiceServices.makeCall(to: identify);
    if (!status) {
      setState(() {
        _isCalling = false;
      });
    }
  }

  toggleSpeaker() async {
    _isSpeaker = await TwilioVoiceServices.toggleSpeaker() ?? _isSpeaker;
    setState(() {});
  }

  toggleMuted() async {
    _isMuted = await TwilioVoiceServices.toggleMute() ?? _isMuted;
    setState(() {});
  }
}

这个示例展示了如何创建一个简单的界面,允许用户输入通话标识符并发起或结束通话,同时还可以切换静音和扬声器模式。


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

1 回复

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


当然,以下是一个关于如何在Flutter项目中使用twilio_voice_flutter插件实现语音通话功能的示例代码。请注意,为了成功运行以下代码,你需要先在Flutter项目中添加twilio_voice_flutter依赖,并确保已经在Twilio平台上注册并获取了必要的凭证(Account SID、Auth Token和Twilio Phone Number)。

1. 添加依赖

首先,在你的pubspec.yaml文件中添加twilio_voice_flutter依赖:

dependencies:
  flutter:
    sdk: flutter
  twilio_voice_flutter: ^0.6.8  # 请检查最新版本号并替换

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

2. 配置Twilio凭证

你需要在你的应用中配置Twilio的Account SID和Auth Token。通常,这些敏感信息不应该硬编码在应用中,而是应该通过安全的后端服务提供。但为了演示目的,这里我们直接在代码中设置(生产环境中应避免这种做法)。

3. 实现语音通话功能

下面是一个简单的Flutter应用示例,展示了如何使用twilio_voice_flutter插件来发起和接听语音通话。

import 'package:flutter/material.dart';
import 'package:twilio_voice_flutter/twilio_voice.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Twilio Voice Flutter Demo'),
        ),
        body: VoiceCallScreen(),
      ),
    );
  }
}

class VoiceCallScreen extends StatefulWidget {
  @override
  _VoiceCallScreenState createState() => _VoiceCallScreenState();
}

class _VoiceCallScreenState extends State<VoiceCallScreen> {
  TwilioVoice _twilioVoice = TwilioVoice();
  String _callStatus = 'Not in a call';

  @override
  void initState() {
    super.initState();
    // 初始化TwilioVoice,设置Account SID和Auth Token
    _twilioVoice.init(
      accountSid: 'your_account_sid_here', // 替换为你的Account SID
      authToken: 'your_auth_token_here',   // 替换为你的Auth Token
    );

    // 监听通话状态变化
    _twilioVoice.onCallDidStart = () {
      setState(() {
        _callStatus = 'Call Started';
      });
    };

    _twilioVoice.onCallDidConnect = () {
      setState(() {
        _callStatus = 'Call Connected';
      });
    };

    _twilioVoice.onCallDidDisconnect = () {
      setState(() {
        _callStatus = 'Call Disconnected';
      });
    };

    _twilioVoice.onCallDidFail = (error) {
      setState(() {
        _callStatus = 'Call Failed: $error';
      });
    };
  }

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Text(
            _callStatus,
            style: TextStyle(fontSize: 24),
          ),
          SizedBox(height: 20),
          ElevatedButton(
            onPressed: () async {
              // 发起通话,替换为你要拨打的Twilio Phone Number
              String toPhoneNumber = '+1234567890'; // 替换为实际号码
              try {
                await _twilioVoice.dial(toPhoneNumber);
              } catch (e) {
                print('Dial failed: $e');
              }
            },
            child: Text('Make Call'),
          ),
        ],
      ),
    );
  }

  @override
  void dispose() {
    // 清理资源
    _twilioVoice.dispose();
    super.dispose();
  }
}

注意事项

  1. 敏感信息处理:在生产环境中,避免在客户端代码中硬编码Account SID和Auth Token。应该通过安全的后端服务提供这些凭证。
  2. 权限处理:确保在Android和iOS项目中配置了必要的权限,比如麦克风权限。
  3. 错误处理:在实际应用中,应该添加更详细的错误处理和用户反馈机制。
  4. UI优化:示例中的UI非常简单,实际应用中你可能需要设计更复杂的UI来提供更好的用户体验。

这个示例展示了如何使用twilio_voice_flutter插件的基本功能。根据实际需求,你可能需要扩展和调整代码。

回到顶部