Flutter实时音视频通信插件pano_rtc的使用

Flutter实时音视频通信插件pano_rtc的使用

pub package

本插件是Pano SDK的封装。Pano提供了多种实时交互功能,以满足各种业务需求。通过集成Pano SDK,您可以轻松地在您的应用中实现语音通话、视频通话、互动白板、互动直播和云录制等功能。

使用方法

要使用此插件,请在您的pubspec.yaml文件中添加pano_rtc作为依赖项。

dependencies:
  pano_rtc: ^x.x.x

入门指南

查看示例目录,了解如何使用pano_rtc构建多人高清音频和视频通话应用。

设备权限

Pano SDK需要cameramicrophone权限才能开始视频通话。

Android

打开AndroidManifest.xml文件,并添加所需的设备权限:

<manifest>
    ...
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.RECORD_AUDIO" />
    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
    <uses-permission android:name="android.permission.BLUETOOTH" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-feature android:name="android.hardware.camera" />
    <uses-feature android:name="android.hardware.camera.autofocus" />
    ...
</manifest>

iOS

打开info.plist文件并添加以下内容:

  • Privacy - Microphone Usage Description,并在Value列中添加说明。
  • Privacy - Camera Usage Description,并在Value列中添加说明。

如果启用后台模式,您的应用程序可以在切换到后台时仍运行语音通话。在Xcode中选择应用目标,点击Capabilities标签,启用Background Modes,并勾选Voice over IP

错误处理

iOS视频无法显示(Android正常)

我们的SDK使用了PlatformView,您需要在info.plist中将io.flutter.embedded_views_preview设置为YES

API文档

示例代码

以下是使用pano_rtc的完整示例代码:

import 'UserManager.dart';
import 'ChannelViewController.dart';
import 'package:flutter/material.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';

import 'ChannelInfo.dart';

void main() {
  WidgetsFlutterBinding.ensureInitialized();

  runApp(MaterialApp(
    home: MyApp(),
    builder: EasyLoading.init(),
  ));
  configLoading();
}

void configLoading() {
  EasyLoading.instance
    ..indicatorType = EasyLoadingIndicatorType.ring
    ..loadingStyle = EasyLoadingStyle.custom
    ..indicatorSize = 45.0
    ..radius = 10.0
    ..progressColor = Color(0xff3BDCE4)
    ..backgroundColor = Colors.transparent
    ..indicatorColor = Color(0xff3BDCE4)
    ..textColor = Colors.transparent
    ..userInteractions = false;
}

class MyApp extends StatefulWidget {
  [@override](/user/override)
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  final TextEditingController txtChannelID = TextEditingController();
  final TextEditingController txtUsername = TextEditingController();
  final TextEditingController txtUserId = TextEditingController();

  [@override](/user/override)
  void initState() {
    super.initState();

    txtChannelID.text = ChannelInfo.channelId;
    txtChannelID.addListener(() {
      ChannelInfo.setChannelId(txtChannelID.text);
    });

    txtUsername.text = ChannelInfo.userName;
    txtUsername.addListener(() {
      ChannelInfo.setUserName(txtUsername.text);
    });

    txtUserId.text = ChannelInfo.userId;
    txtUserId.addListener(() {
      ChannelInfo.setUserId(txtUserId.text);
    });
  }

  [@override](/user/override)
  void dispose() {
    super.dispose();
    txtChannelID.removeListener(() {});
    txtUsername.removeListener(() {});
    txtUserId.removeListener(() {});
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      body: SingleChildScrollView(
        child: Container(
          padding: EdgeInsets.all(15),
          child: Column(
            children: [
              SizedBox(
                height: 80,
              ),
              Text(
                'Pano Room',
                style: TextStyle(
                  color: Colors.black,
                  fontSize: 25,
                ),
                textAlign: TextAlign.center,
              ),
              SizedBox(
                height: 50,
              ),
              TextField(
                maxLines: 1,
                controller: txtChannelID,
                autocorrect: false,
                decoration: InputDecoration(
                    contentPadding: EdgeInsets.all(8.0),
                    prefixIcon: Padding(
                      padding: EdgeInsets.only(
                        left: 15,
                        right: 15,
                      ),
                    ),
                    enabledBorder: OutlineInputBorder(
                      borderSide: BorderSide(color: Color(0xffE7E7EF)),
                      borderRadius: BorderRadius.circular(5),
                    ),
                    border: OutlineInputBorder(
                      borderSide: BorderSide(color: Color(0xffE7E7EF)),
                      borderRadius: BorderRadius.circular(5),
                    ),
                    hintText: '输入频道ID',
                    labelText: '频道ID'),
                style: TextStyle(
                  fontSize: 15,
                  color: const Color(0xff000000),
                ),
              ),
              SizedBox(
                height: 8,
              ),
              TextField(
                maxLines: 1,
                controller: txtUserId,
                autocorrect: false,
                decoration: InputDecoration(
                    contentPadding: EdgeInsets.all(8.0),
                    prefixIcon: Padding(
                      padding: EdgeInsets.only(
                        left: 15,
                        right: 15,
                      ),
                    ),
                    enabledBorder: OutlineInputBorder(
                      borderSide: BorderSide(color: Color(0xffE7E7EF)),
                      borderRadius: BorderRadius.circular(5),
                    ),
                    border: OutlineInputBorder(
                      borderSide: BorderSide(color: Color(0xffE7E7EF)),
                      borderRadius: BorderRadius.circular(5),
                    ),
                    hintText: '输入用户ID',
                    labelText: '用户ID'),
                style: TextStyle(
                  fontSize: 15,
                  color: const Color(0xff000000),
                ),
              ),
              SizedBox(
                height: 8,
              ),
              TextField(
                maxLines: 1,
                controller: txtUsername,
                autocorrect: false,
                decoration: InputDecoration(
                    contentPadding: EdgeInsets.all(8.0),
                    prefixIcon: Padding(
                      padding: EdgeInsets.only(
                        left: 15,
                        right: 15,
                      ),
                    ),
                    enabledBorder: OutlineInputBorder(
                      borderSide: BorderSide(color: Color(0xffE7E7EF)),
                      borderRadius: BorderRadius.circular(5),
                    ),
                    border: OutlineInputBorder(
                      borderSide: BorderSide(color: Color(0xffE7E7EF)),
                      borderRadius: BorderRadius.circular(5),
                    ),
                    hintText: '输入用户名',
                    labelText: '用户名'),
                style: TextStyle(
                  fontSize: 15,
                  color: const Color(0xff000000),
                ),
              ),
              ElevatedButton(
                onPressed: () async {
                  if (txtChannelID.text == '') {
                    return;
                  }
                  if (txtUserId.text == '') {
                    return;
                  }
                  if (txtUsername.text == '') {
                    return;
                  }
                  UserManager.shared().removeAllUser();
                  Navigator.push(
                      context,
                      MaterialPageRoute(
                          builder: (context) => ChannelViewController()));
                },
                child: Text('加入'),
              ),
              SizedBox(
                height: 50,
              ),
            ],
          ),
        ),
      ),
    );
  }
}

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

1 回复

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


当然,以下是一个关于如何在Flutter项目中使用pano_rtc插件来实现实时音视频通信的示例代码。这个示例包括初始化PanoRTC、加入房间、发布和订阅音视频流等基本功能。

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

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

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

接下来,在你的Flutter项目中,你可以按照以下步骤实现实时音视频通信:

1. 初始化PanoRTC

在你的主页面(如main.dart)中,首先导入必要的包并初始化PanoRTC:

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

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'PanoRTC Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: PanoRTCDemo(),
    );
  }
}

class PanoRTCDemo extends StatefulWidget {
  @override
  _PanoRTCDemoState createState() => _PanoRTCDemoState();
}

class _PanoRTCDemoState extends State<PanoRTCDemo> {
  PanoRTCClient? _panoRTCClient;

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

  void initPanoRTC() {
    _panoRTCClient = PanoRTCClient();
    _panoRTCClient?.setLogLevel(PanoLogLevel.DEBUG); // 设置日志级别
    _panoRTCClient?.init({
      'appKey': '你的AppKey', // 替换为你的实际AppKey
      'server': '你的服务器地址', // 替换为你的实际服务器地址
    });
  }

  // 其他方法...
}

2. 加入房间

接下来,你可以添加一个按钮来加入房间:

class _PanoRTCDemoState extends State<PanoRTCDemo> {
  // ... 其他代码

  void joinRoom(String roomName, String userName) {
    _panoRTCClient?.joinChannel({
      'channelName': roomName,
      'uid': userName,
      'token': '', // 如果需要Token认证,则填写Token
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('PanoRTC Demo'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text('请输入房间名和用户名:'),
            TextField(
              decoration: InputDecoration(labelText: '房间名'),
              onSubmitted: (roomName) {
                TextField userNameField = TextField(
                  decoration: InputDecoration(labelText: '用户名'),
                  onSubmitted: (userName) {
                    joinRoom(roomName, userName);
                  },
                );
                showDialog(
                  context: context,
                  builder: (BuildContext context) {
                    return AlertDialog(
                      title: Text('输入用户名'),
                      content: userNameField,
                      actions: <Widget>[
                        TextButton(
                          onPressed: () {
                            Navigator.of(context).pop();
                          },
                          child: Text('取消'),
                        ),
                        TextButton(
                          onPressed: () {
                            userNameField.controller?.text.isNotEmpty
                                ? joinRoom(roomName, userNameField.controller?.text ?? '')
                                : ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('用户名不能为空')));
                            Navigator.of(context).pop();
                          },
                          child: Text('加入'),
                        ),
                      ],
                    );
                  },
                );
              },
            ),
          ],
        ),
      ),
    );
  }
}

3. 发布和订阅音视频流

在加入房间后,你需要发布和订阅音视频流。这通常涉及到设置视频视图来显示远程用户的视频流,并启动本地摄像头和麦克风。

class _PanoRTCDemoState extends State<PanoRTCDemo> {
  // ... 其他代码

  late PanoRTCVideoView _remoteVideoView;

  @override
  Widget build(BuildContext context) {
    _remoteVideoView = PanoRTCVideoView();

    _panoRTCClient?.on('user-joined', (event) {
      // 用户加入房间时的事件处理
      print('User joined: ${event['uid']}');
    });

    _panoRTCClient?.on('stream-published', (event) {
      // 本地流发布成功时的事件处理
      print('Local stream published');
    });

    _panoRTCClient?.on('stream-subscribed', (event) {
      // 远程流订阅成功时的事件处理
      int uid = event['uid'];
      _panoRTCClient?.setupRemoteVideo(uid, _remoteVideoView.textureId);
      setState(() {}); // 刷新UI以显示视频
    });

    return Scaffold(
      // ... 其他代码
      body: Stack(
        children: <Widget>[
          // 本地视频视图(如果需要显示本地视频)
          // PanoRTCVideoView() 可以添加到这里并设置相应的textureId
          // _localVideoView,
          
          // 远程视频视图
          Positioned(
            top: 50,
            left: 50,
            width: 300,
            height: 400,
            child: _remoteVideoView,
          ),
        ],
      ),
    );
  }

  @override
  void dispose() {
    _panoRTCClient?.leaveChannel();
    _panoRTCClient?.dispose();
    super.dispose();
  }
}

注意:

  1. 远程视频视图使用PanoRTCVideoView,它是一个Flutter的TextureWidget,用于显示视频流。
  2. 你需要确保在AndroidManifest.xmlInfo.plist中配置了必要的权限,如摄像头和麦克风权限。
  3. 完整的实现还需要处理更多的细节,如错误处理、用户界面的优化等。

这个示例提供了一个基本的框架,你可以根据需要进行扩展和修改。希望这能帮助你开始使用pano_rtc插件进行Flutter实时音视频通信。

回到顶部