Flutter音视频通话插件linphone_flutter_plugin的使用

Flutter音视频通话插件linphone_flutter_plugin的使用

Linphone Flutter Plugin 是一个Flutter插件,允许你将Linphone SDK的原生Android通话功能集成到你的Flutter应用中。该插件专为使用UDP协议的VoIP通话设计,适用于需要实时语音通信的应用场景。

特性

  • 原生Android集成:利用Linphone SDK实现无缝VoIP通话。
  • UDP协议支持:确保快速高效的通信,并且具有极低的延迟。
  • 呼叫处理:直接从Flutter应用中发起、接收和管理VoIP通话。
  • 呼叫记录:检索通话历史记录以进行跟踪和分析。
  • 静音、扬声器和呼叫转移:通过高级功能控制通话状态。

安装

要使用此插件,在你的pubspec.yaml文件中添加linphone_flutter_plugin作为依赖项:

$ dart pub add linphone_flutter_plugin

使用

首先,导入插件:

import 'package:linphone_flutter_plugin/linphoneflutterplugin.dart';

Android权限

AndroidManifest.xml中添加以下权限:

<manifest ...>
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.RECORD_AUDIO" />
    <uses-permission android:name="android.permission.CALL_PHONE" />
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.SYSTEM_CAMERA" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_CAMERA" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_PHONE_CALL" />
    <uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
</manifest>

初始化插件

在可以发起或接收呼叫之前,需要初始化Linphone插件并请求必要的权限:

final _linphoneSdkPlugin = LinphoneFlutterPlugin();

Future<void> initLinphone() async {
  await _linphoneSdkPlugin.requestPermissions();
}

登录

使用SIP凭证登录Linphone:

Future<void> login({
  required String username,
  required String password,
  required String domain,
}) async {
  await _linphoneSdkPlugin.login(userName: username, domain: domain, password: password);
}

发起呼叫

通过指定接收者的号码发起呼叫:

Future<void> call(String number) async {
  await _linphoneSdkPlugin.call(number: number);
}

接收呼叫

监听并处理传入的呼叫:

_linphoneSdkPlugin.addCallStateListener().listen((CallState state) {
  if (state == CallState.IncomingReceived) {
    // 处理传入呼叫
  }
});

挂断

结束正在进行的通话:

Future<void> hangUp() async {
  await _linphoneSdkPlugin.hangUp();
}

示例

查看示例目录中的完整示例,了解如何使用此插件。

import 'package:flutter/material.dart';
import 'package:linphone_flutter_plugin/linphoneflutterplugin.dart';
import 'package:linphone_flutter_plugin/CallLog.dart';
import 'package:linphone_flutter_plugin/call_state.dart';
import 'dart:async';
import 'package:linphone_flutter_plugin/login_state.dart';

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

class MyApp extends StatefulWidget {
  const MyApp({Key? key}) : super(key: key);

  [@override](/user/override)
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  final _linphoneSdkPlugin = LinphoneFlutterPlugin();

  late TextEditingController _userController;
  late TextEditingController _passController;
  late TextEditingController _domainController;
  final _textEditingController = TextEditingController();

  [@override](/user/override)
  void initState() {
    super.initState();
    _userController = TextEditingController();
    _passController = TextEditingController();
    _domainController = TextEditingController();
    requestPermissions();
  }

  Future<void> requestPermissions() async {
    try {
      await _linphoneSdkPlugin.requestPermissions();
    } catch (e) {
      print("Error on request permission. ${e.toString()}");
    }
  }

  Future<void> login({
    required String username,
    required String pass,
    required String domain,
  }) async {
    try {
      await _linphoneSdkPlugin.login(
          userName: username, domain: domain, password: pass);
    } catch (e) {
      print("Error on login. ${e.toString()}");
    }
  }

  Future<void> call() async {
    if (_textEditingController.text.isNotEmpty) {
      String number = _textEditingController.text;
      try {
        await _linphoneSdkPlugin.call(number: number);
      } catch (e) {
        print("Error on call. ${e.toString()}");
      }
    }
  }

  Future<void> forward() async {
    try {
      await _linphoneSdkPlugin.callTransfer(destination: "1000");
    } catch (e) {
      print("Error on call transfer. ${e.toString()}");
    }
  }

  Future<void> hangUp() async {
    try {
      await _linphoneSdkPlugin.hangUp();
    } catch (e) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text("Hang up failed: ${e.toString()}")),
      );
    }
  }

  Future<void> toggleSpeaker() async {
    try {
      await _linphoneSdkPlugin.toggleSpeaker();
    } catch (e) {
      print("Error on toggle speaker. ${e.toString()}");
    }
  }

  Future<void> toggleMute() async {
    try {
      bool isMuted = await _linphoneSdkPlugin.toggleMute();
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text(isMuted ? "Muted" : "Unmuted")),
      );
    } catch (e) {
      print("Error on toggle mute. ${e.toString()}");
    }
  }

  Future<void> answer() async {
    try {
      await _linphoneSdkPlugin.answercall();
    } catch (e) {
      print("Error on answer call. ${e.toString()}");
    }
  }

  Future<void> reject() async {
    try {
      await _linphoneSdkPlugin.rejectCall();
    } catch (e) {
      print("Error on reject call. ${e.toString()}");
    }
  }

  Future<void> callLogs() async {
    try {
      CallLogs callLogs = await _linphoneSdkPlugin.callLogs();
      print("---------call logs length: ${callLogs.callHistory.length}");
    } catch (e) {
      print("Error on call logs. ${e.toString()}");
    }
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Linphone Flutter Plugin Example'),
        ),
        body: ListView(
          padding: const EdgeInsets.all(20),
          children: [
            TextFormField(
              controller: _userController,
              decoration: const InputDecoration(
                icon: Icon(Icons.person),
                hintText: "输入用户名",
                labelText: "用户名",
              ),
            ),
            TextFormField(
              controller: _passController,
              obscureText: true,
              decoration: const InputDecoration(
                icon: Icon(Icons.lock),
                hintText: "输入密码",
                labelText: "密码",
              ),
            ),
            TextFormField(
              controller: _domainController,
              decoration: const InputDecoration(
                icon: Icon(Icons.domain),
                hintText: "输入域",
                labelText: "域",
              ),
            ),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                login(
                  username: _userController.text,
                  pass: _passController.text,
                  domain: _domainController.text,
                );
              },
              child: const Text("登录"),
            ),
            const SizedBox(height: 20),
            StreamBuilder<LoginState>(
              stream: _linphoneSdkPlugin.addLoginListener(),
              builder: (context, snapshot) {
                LoginState status = snapshot.data ?? LoginState.none;
                return Text("登录状态: ${status.name}");
              },
            ),
            const SizedBox(height: 20),
            StreamBuilder<CallState>(
              stream: _linphoneSdkPlugin.addCallStateListener(),
              builder: (context, snapshot) {
                CallState? status = snapshot.data;
                if (status == CallState.IncomingReceived) {
                  return AlertDialog(
                    title: const Text('来电'),
                    content: const Text('您有一通来电。'),
                    actions: [
                      TextButton(
                        onPressed: () async {
                          await reject();
                          if (mounted) Navigator.of(context).pop();
                        },
                        child: const Text('拒绝'),
                      ),
                      TextButton(
                        onPressed: () async {
                          await answer();
                          if (mounted) Navigator.of(context).pop();
                        },
                        child: const Text('接听'),
                      ),
                    ],
                  );
                }
                return Column(
                  children: [
                    Text("通话状态: ${status?.name}"),
                    if (status == CallState.outgoingInit || status == CallState.outgoingProgress)
                      ElevatedButton(
                          onPressed: hangUp, child: const Text("挂断")),
                  ],
                );
              },
            ),
            const SizedBox(height: 20),
            TextFormField(
              controller: _textEditingController,
              keyboardType: TextInputType.phone,
              decoration: const InputDecoration(
                icon: Icon(Icons.phone),
                hintText: "输入号码",
                labelText: "号码",
              ),
            ),
            const SizedBox(height: 20),
            ElevatedButton(onPressed: call, child: const Text("拨打")),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                answer();
              },
              child: const Text("接听"),
            ),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                reject();
              },
              child: const Text("拒绝"),
            ),
            ElevatedButton(
              onPressed: () {
                hangUp();
              },
              child: const Text("挂断"),
            ),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                toggleSpeaker();
              },
              child: const Text("扬声器"),
            ),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                toggleMute();
              },
              child: const Text("静音"),
            ),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                forward();
              },
              child: const Text("转接"),
            ),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                callLogs();
              },
              child: const Text("通话记录"),
            ),
          ],
        ),
      ),
    );
  }

  [@override](/user/override)
  void dispose() {
    _linphoneSdkPlugin.removeLoginListener();
    _linphoneSdkPlugin.removeCallListener();
    _userController.dispose();
    _passController.dispose();
    _domainController.dispose();
    _textEditingController.dispose();
    super.dispose();
  }
}

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

1 回复

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


当然,下面是一个关于如何使用linphone_flutter_plugin进行音视频通话的基本代码示例。这个插件允许Flutter应用集成Linphone的音视频通话功能。需要注意的是,实际应用中还需要处理更多的细节,比如错误处理、UI美化、权限管理等。

1. 添加依赖

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

dependencies:
  flutter:
    sdk: flutter
  linphone_flutter_plugin: ^最新版本号 # 请替换为最新的版本号

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

2. 配置Linphone

在应用启动时,你需要初始化Linphone并配置它。这通常在main.dartMyApp类中完成。

import 'package:flutter/material.dart';
import 'package:linphone_flutter_plugin/linphone_flutter_plugin.dart';

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

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  Linphone linphone = Linphone();

  @override
  void initState() {
    super.initState();
    initLinphone();
  }

  Future<void> initLinphone() async {
    // 配置Linphone
    await linphone.createCore();
    await linphone.configure({
      'nat_policy': 'ice',
      'video_enabled': true,
      // 其他配置...
    });

    // 设置监听器
    linphone.addListener((event) {
      // 处理Linphone事件,如来电、通话结束等
      print('Linphone event: $event');
    });

    // 启动Linphone
    await linphone.start();
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Linphone Flutter Plugin Demo'),
        ),
        body: Center(
          child: ElevatedButton(
            onPressed: () {
              // 发起通话的示例代码将在后面给出
            },
            child: Text('发起通话'),
          ),
        ),
      ),
    );
  }
}

3. 发起通话

要发起通话,你需要知道对方的SIP地址。下面是一个简单的发起通话的示例:

void startCall(String sipAddress) async {
  LinphoneCall call = await linphone.call(sipAddress);
  call.addListener((callEvent) {
    // 处理通话事件,如接听、挂断等
    print('Call event: $callEvent');
  });
}

你可以在按钮点击事件中调用这个函数:

ElevatedButton(
  onPressed: () {
    startCall('sip:username@domain'); // 替换为对方的SIP地址
  },
  child: Text('发起通话'),
),

4. 接听/挂断通话

接听和挂断通话可以通过监听LinphoneCall对象的事件来处理。例如,当来电时,你可以显示一个UI来让用户选择接听或挂断。

linphone.addListener((event) {
  if (event is LinphoneCallIncoming) {
    LinphoneCallIncoming incomingCall = event;
    // 显示UI让用户选择接听或挂断
    incomingCall.accept().then((call) {
      // 通话已接听
    }).catchError((error) {
      // 处理错误
    });
    // 或者直接挂断
    // incomingCall.decline();
  } else if (event is LinphoneCallEnded) {
    // 通话已结束
  }
  // 处理其他事件...
});

注意事项

  • 在实际应用中,你需要处理更多的错误和异常情况。
  • 确保你的应用有适当的权限,比如麦克风和摄像头的访问权限。
  • UI设计需要根据实际需求进行调整。
  • Linphone的配置项非常多,可以根据需要进行更详细的配置。

这个示例提供了一个基本的框架,展示了如何使用linphone_flutter_plugin进行音视频通话。实际应用中,你可能需要根据具体需求进行更多的定制和开发。

回到顶部