Flutter VoIP通信插件flutter_ios_voip_kit的使用
Flutter VoIP通信插件flutter_ios_voip_kit的使用
简介
flutter_ios_voip_kit
是一个用于在 Flutter iOS 应用中处理 VoIP 通知的插件。它利用了 CallKit 和 PushKit 来实现一对一视频通话。本文档将详细介绍如何安装和使用该插件。
动机
我们需要在 iOS 13 及以上版本中使用 CallKit 来处理传入的 VoIP 通知。查看 WWDC2019 视频以获取更多信息。因此,将 CallKit 和 PushKit 结合使用的需求越来越多。然而,网络上关于使用 CallKit 和 PushKit 的 VoIP 通知样本仍然很少(特别是针对 Flutter)。我决定创建一个包含最小必要功能的 Flutter 插件。你可以使用这个插件,但实际目的是帮助你创建适合你服务的 VoIPKit。
要求
- 仅支持 iOS,不支持 Android。
- 支持 iOS 10 及以上版本。
- 仅支持一对一通话,不支持群组通话。
- 需要一个服务器来通过 APNs 推送 VoIP 通知。
- 实际进行视频或通话时,需要链接到类似 WebRTC 的服务(例如:Agora, SkyWay, Amazon Kinesis Video Streams)。
使用方法
1. 安装
在你的 pubspec.yaml
文件中添加 flutter_ios_voip_kit
作为依赖项:
dependencies:
flutter_ios_voip_kit: ^latest_version
2. 在 Xcode 中设置能力
- 选择 Background Modes > Voice over IP 和 Remote notifications。
- 选择 Push Notifications。
- 在选择了能力后,编辑
ios/Runner/Info.plist
文件:
<key>UIBackgroundModes</key>
<array>
<string>remote-notification</string>
<string>voip</string>
</array>
3. 编辑 Info.plist
编辑 ios/Runner/Info.plist
文件,添加以下内容:
<key>FIVKIconName</key>
<string>AppIcon-VoIPKit</string>
<key>FIVKLocalizedName</key>
<string>VoIP-Kit</string>
<key>FIVKSupportVideo</key>
<true/>
<key>FIVKSkipRecallScreen</key>
<true/>
4. 为 CallKit 添加新的图像集
将图标(.png 或 .pdf)添加到 ios/Runner/Assets.xcassets/AppIcon-VoIPKit
,以便在锁定的 iPhone 上显示通话时使用。
5. 创建 VoIP 服务证书
- 访问 Apple Developer 并创建一个新的 VoIP 服务证书(.cer)。查看更多信息。
- 使用 KeyChainAccess 从 .cer 创建 .p12,并使用 openssl 创建 .pem:
openssl pkcs12 -in voip_services.p12 -out voip_services.pem -nodes -clcerts
6. 从服务器请求 VoIP 通知(APNs)
- 查看 Apple 文档。
- 发送通知请求到 APNs
- 添加数据(payload)如下:
{
"aps": {
"alert": {
"uuid": "<Version 4 UUID (e.g.: https://www.uuidgenerator.net/version4)>",
"incoming_caller_id": "<your service user id>",
"incoming_caller_name": "<your service user name>",
}
}
}
- 使用 curl 测试 VoIP 通知:
curl -v \
-d '{"aps":{"alert":{"uuid":"982cf533-7b1b-4cf6-a6e0-004aab68c503","incoming_caller_id":"0123456789","incoming_caller_name":"Tester"}}}' \
-H "apns-push-type: voip" \
-H "apns-expiration: 0" \
-H "apns-priority: 0" \
-H "apns-topic: <your app’s bundle ID>.voip" \
--http2 \
--cert ./voip_services.pem \
https://api.sandbox.push.apple.com/3/device/<VoIP device Token for your iPhone>
尝试示例应用
你可以尝试示例应用,而无需服务器。这里有一个使用 SkyWay 确认 flutter_ios_voip_kit
运行的示例。
示例代码
以下是一个简单的示例代码,展示了如何使用 flutter_ios_voip_kit
:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_ios_voip_kit/flutter_ios_voip_kit.dart';
import 'incoming_call_page.dart';
import 'outgoing_call_page.dart';
enum ExampleAction { RequestAuthorization, GetSettings }
extension on ExampleAction {
String get title {
switch (this) {
case ExampleAction.RequestAuthorization:
return 'Authorize Notifications';
case ExampleAction.GetSettings:
return 'Check Settings';
default:
return 'Unknown';
}
}
}
void main() {
WidgetsFlutterBinding.ensureInitialized();
runZonedGuarded(() {
FlutterIOSVoIPKit.instance.onDidUpdatePushToken = (token) {
print('🎈 example: onDidUpdatePushToken token = $token');
};
runApp(MaterialApp(
routes: <String, WidgetBuilder>{
OutgoingCallPage.routeName: (_) => OutgoingCallPage(),
IncomingCallPage.routeName: (_) => IncomingCallPage(),
},
home: SelectCallRoll(),
));
}, (object, stackTrace) {});
}
class SelectCallRoll extends StatefulWidget {
@override
_SelectCallRollState createState() => _SelectCallRollState();
}
class _SelectCallRollState extends State<SelectCallRoll> {
void _performExampleAction(ExampleAction action) async {
switch (action) {
case ExampleAction.RequestAuthorization:
final granted = await FlutterIOSVoIPKit.instance.requestAuthLocalNotification();
print('🎈 example: requestAuthLocalNotification granted = $granted');
break;
case ExampleAction.GetSettings:
final settings = await FlutterIOSVoIPKit.instance.getLocalNotificationsSettings();
print('🎈 example: getLocalNotificationsSettings settings: \n$settings');
showDialog(
context: context,
builder: (ctx) {
return AlertDialog(
title: Text('Settings'),
content: Text('$settings'),
actions: [
TextButton(
onPressed: () => Navigator.of(ctx).pop(),
child: Text('Ok'),
)
],
);
});
break;
default:
break;
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Select call roll'),
actions: [
PopupMenuButton<ExampleAction>(
icon: Icon(Icons.more_vert),
onSelected: (action) => _performExampleAction(action),
itemBuilder: (BuildContext context) {
return ExampleAction.values.map((ExampleAction choice) {
return PopupMenuItem<ExampleAction>(
value: choice,
child: Text(choice.title),
);
}).toList();
},
),
],
),
body: SafeArea(
child: Center(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 18),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
const Text(
'📱 To try out the example app, you need two iPhones with iOS 10 or later.',
textAlign: TextAlign.center,
style: TextStyle(
height: 1.5,
fontWeight: FontWeight.bold,
fontSize: 18,
),
),
_button(isCaller: true),
_button(isCaller: false),
],
),
),
),
),
);
}
Widget _button({
@required bool isCaller,
}) {
return SizedBox(
width: 140,
height: 140,
child: RawMaterialButton(
padding: EdgeInsets.zero,
elevation: 8.0,
shape: CircleBorder(),
fillColor: Colors.blue,
onPressed: () {
Navigator.pushNamed(
context,
isCaller ? OutgoingCallPage.routeName : IncomingCallPage.routeName,
);
},
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Icon(
isCaller ? Icons.call : Icons.ring_volume,
size: 32,
),
Text(
isCaller ? '🤙 Caller' : '🔔 Callee',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 18,
color: Colors.white,
),
),
],
),
),
);
}
}
常见问题
CallKit 是否有呼叫和拨出呼叫屏幕?
- 不。CallKit 仅支持来电屏幕。你需要自己制作呼叫和拨出呼叫屏幕。
我可以使用远程推送设备令牌而不是 VoIP 设备令牌吗?
- 不可以。由于 VoIP 令牌和推送令牌的规格不同,需要在数据库中分别管理它们。
在 iOS 13 上无法获取 VoIP 令牌
- 请卸载应用,重启终端并重新安装应用。稍后即可获取。
无法接收 VoIP 通知
- 请检查以下项目:
- VoIP 设备令牌是否正确?
- 是否设置了带有
.voip
的应用包标识符作为 apns-topic? - 是否设置了
voip
作为 apns-push-type? - APNs 终端(开发或生产)是否正确?
- 对于 iOS 13,如果 CallKit 呼叫多次失败,可能无法接收 VoIP 通知。请卸载应用,重启终端并重新安装应用。
在锁定屏幕上没有显示来电图标
- 图标图像应为边长为 40 点的正方形。颜色被忽略,请使用不同的 alpha 值设计。
- 如果使用 PDF 创建,请勾选
Preserve Vector Data
以调整大小,并将Single Scale
更改为Scales
。
参考资料
更多关于Flutter VoIP通信插件flutter_ios_voip_kit的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter VoIP通信插件flutter_ios_voip_kit的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
当然,下面是一个关于如何使用 flutter_ios_voip_kit
插件进行 VoIP 通信的示例代码。这个插件主要用于在 Flutter 应用中实现 iOS 平台上的 VoIP 功能。请注意,这只是一个基本的示例,实际项目中可能需要根据具体需求进行调整。
前提条件
- 确保你的 Flutter 环境已经配置好。
- 在
pubspec.yaml
文件中添加flutter_ios_voip_kit
依赖:
dependencies:
flutter:
sdk: flutter
flutter_ios_voip_kit: ^最新版本号
- 运行
flutter pub get
命令来安装依赖。
配置 iOS 项目
- 打开
ios/Runner/Info.plist
文件,添加必要的权限配置,比如麦克风权限和通知权限。
<key>NSMicrophoneUsageDescription</key>
<string>App需要访问麦克风以进行VoIP通话</string>
<key>UIBackgroundModes</key>
<array>
<string>voip</string>
</array>
<key>UIApplicationSupportsMultipleScenes</key>
<false/>
- 确保在 Xcode 中启用了 Background Modes 中的 VoIP 选项。
代码示例
1. 初始化 VoIPKit
在你的 Flutter 应用中,首先进行初始化:
import 'package:flutter/material.dart';
import 'package:flutter_ios_voip_kit/flutter_ios_voip_kit.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('VoIP Demo'),
),
body: VoIPDemoPage(),
),
);
}
}
class VoIPDemoPage extends StatefulWidget {
@override
_VoIPDemoPageState createState() => _VoIPDemoPageState();
}
class _VoIPDemoPageState extends State<VoIPDemoPage> {
VoIPKit? _voipKit;
@override
void initState() {
super.initState();
initVoIPKit();
}
Future<void> initVoIPKit() async {
_voipKit = VoIPKit();
// 注册VoIP通知
_voipKit!.registerVoIPNotifications()!
.then((_) => print('VoIP notifications registered'))
.catchError((error) => print('Error registering VoIP notifications: $error'));
// 监听来电
_voipKit!.onIncomingCall!.listen((call) {
print('Incoming call: ${call.uuid}');
// 在这里处理来电逻辑,比如显示来电界面
});
// 监听挂断
_voipKit!.onCallEnded!.listen((call) {
print('Call ended: ${call.uuid}');
// 在这里处理挂断逻辑
});
}
@override
Widget build(BuildContext context) {
return Center(
child: ElevatedButton(
onPressed: () {
// 发起VoIP呼叫的示例(需要后端支持)
// _voipKit!.startCall(to: 'callee_sip_address');
},
child: Text('发起VoIP呼叫'),
),
);
}
}
2. 处理来电通知
在 iOS 上,VoIP 通知需要特别处理。你需要配置一个 PushKit 服务来处理这些通知。由于 Flutter 本身不直接处理原生代码,这部分通常需要在原生 iOS 代码中进行。不过,flutter_ios_voip_kit
插件已经为你封装好了大部分逻辑,你只需要确保在 AppDelegate 中进行必要的配置。
打开 ios/Runner/AppDelegate.swift
文件,确保有以下代码:
import UIKit
import Flutter
import PushKit
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
// 配置 PushKit
let voipRegistry = PKPushRegistry(queue: .main)
voipRegistry.delegate = self
voipRegistry.desiredPushTypes = [PKPushType.voIP]
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
// 扩展 AppDelegate 以遵守 PKPushRegistryDelegate 协议
extension AppDelegate: PKPushRegistryDelegate {
func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, forType type: PKPushType) {
if type == .voIP {
// 处理VoIP通知
let uuidString = payload.dictionaryPayload["uuid"] as? String ?? UUID().uuidString
let voipKitPlugin = FlutterVoIPKitPlugin.sharedInstance()
voipKitPlugin.reportIncomingCall(with: uuidString, payload: payload.dictionaryPayload)
}
}
}
注意事项
- 后端支持:VoIP 功能通常需要后端服务器的支持,用于处理 SIP 信令和媒体流。
- 通知处理:确保你的应用能够正确处理和处理 VoIP 通知,特别是在后台运行时。
- 测试环境:在实际部署之前,在测试环境中充分测试 VoIP 功能,以确保其稳定性和可靠性。
这个示例提供了一个基本的框架,你可以根据具体需求进行扩展和修改。希望这对你有帮助!