Flutter音视频通信插件nertc的使用

nertc

构建状态 pub package

Flutter 插件用于网易 RTC SDK。


Flutter音视频通信插件nertc使用方法

pubspec.yaml 文件中添加 nertc 作为依赖项。

iOS

ios/Runner/Info.plist 中添加两行配置:

  • 一行键为 Privacy - Camera Usage Description 并附带描述。
  • 另一行键为 Privacy - Microphone Usage Description 并附带描述。

或者以文本格式添加以下内容:

<key>NSCameraUsageDescription</key>
<string>可以使用相机吗?</string>
<key>NSMicrophoneUsageDescription</key>
<string>可以使用麦克风吗?</string>

Android

android/app/build.gradle 文件中将最低 Android SDK 版本设置为 21 或更高。

minSdkVersion 21

示例代码

以下是一个完整的示例代码,展示了如何使用 nertc 插件进行音视频通信。

示例代码:main.dart

import 'dart:io';
import 'dart:math';
import 'package:flutter/material.dart';
import 'settings.dart'; // 假设有一个 settings.dart 文件
import 'package:permission_handler/permission_handler.dart';
import 'about.dart';
import 'call.dart';

void main() => runApp(RtcApp());

class RtcApp extends StatelessWidget {
  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'FLTNERTC',
      home: MainPage(),
    );
  }
}

class MainPage extends StatefulWidget {
  [@override](/user/override)
  _MainPageState createState() {
    return _MainPageState();
  }
}

// 定义菜单选项和确认动作
enum OptionsMenu { SETTINGS, ABOUT }
enum ConfirmAction { CANCEL, ACCEPT }

class _MainPageState extends State<MainPage> {
  FocusNode _channelFocusNode = FocusNode();
  FocusNode _uidFocusNode = FocusNode();
  final _channelController = TextEditingController();
  bool _channelValidateError = false;
  final _uidController = TextEditingController();
  bool _uidValidateError = false;

  [@override](/user/override)
  void initState() {
    super.initState();
    _uidController.text = Random().nextInt(1 << 32).toString();
    Settings.getInstance(); // 初始化设置
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('FLTNERTC'),
        actions: [
          PopupMenuButton(
            onSelected: (OptionsMenu result) {
              _onOptionsItemSelected(result);
            },
            itemBuilder: (BuildContext context) =>
                <PopupMenuEntry<OptionsMenu>>[
              const PopupMenuItem<OptionsMenu>(
                value: OptionsMenu.SETTINGS,
                child: Text('Settings'),
              ),
              const PopupMenuItem<OptionsMenu>(
                value: OptionsMenu.ABOUT,
                child: Text('About'),
              ),
            ],
          ),
        ],
      ),
      body: Container(
        margin: const EdgeInsets.all(15.0),
        child: Column(
          mainAxisSize: MainAxisSize.max,
          children: [
            TextField(
              focusNode: _uidFocusNode,
              controller: _uidController,
              onChanged: (value) {
                if (_uidValidateError) {
                  setState(() {
                    _uidValidateError = value.isEmpty;
                  });
                }
              },
              decoration: InputDecoration(
                  hintText: 'UID',
                  errorText: _uidValidateError ? 'Required' : null),
            ),
            Padding(
              padding: EdgeInsets.symmetric(vertical: 5),
              child: TextField(
                controller: _channelController,
                onChanged: (value) {
                  if (_channelValidateError) {
                    setState(() {
                      _channelValidateError = value.isEmpty;
                    });
                  }
                },
                autofocus: true,
                focusNode: _channelFocusNode,
                decoration: InputDecoration(
                    hintText: 'Channel Name',
                    errorText: _channelValidateError ? 'Required' : null),
              ),
            ),
            SizedBox(height: 20),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                ButtonTheme(
                  minWidth: 200.0,
                  height: 55.0,
                  child: RaisedButton(
                    shape: RoundedRectangleBorder(
                        borderRadius: BorderRadius.circular(5.0)),
                    onPressed: () {
                      _channelFocusNode.unfocus();
                      _uidFocusNode.unfocus();
                      _startRTC(context);
                    },
                    child: const Text(
                      '开始会话',
                      style: TextStyle(fontWeight: FontWeight.bold),
                    ),
                  ),
                )
              ],
            ),
          ],
        ),
      ),
    );
  }

  void _onOptionsItemSelected(OptionsMenu item) {
    switch (item) {
      case OptionsMenu.SETTINGS:
        Navigator.push(
            context, MaterialPageRoute(builder: (context) => SettingsPage()));
        break;
      case OptionsMenu.ABOUT:
        Navigator.push(
            context, MaterialPageRoute(builder: (context) => AboutPage()));
        break;
    }
  }

  Future<void> _startRTC(BuildContext context) async {
    _channelValidateError = _channelController.text.isEmpty;
    _uidValidateError = _uidController.text.isEmpty;

    setState(() {});

    if (_channelValidateError || _uidValidateError) return;

    // 检查权限
    final permissions = [Permission.camera, Permission.microphone];
    if (Platform.isAndroid) {
      permissions.add(Permission.storage);
    }
    List<Permission> missed = [];
    for (var permission in permissions) {
      PermissionStatus status = await permission.status;
      if (status != PermissionStatus.granted) {
        missed.add(permission);
      }
    }

    bool allGranted = missed.isEmpty;
    if (!allGranted) {
      List<Permission> showRationale = [];
      for (var permission in missed) {
        bool isShown = await permission.shouldShowRequestRationale;
        if (isShown) {
          showRationale.add(permission);
        }
      }

      if (showRationale.isNotEmpty) {
        ConfirmAction action = await showDialog<ConfirmAction>(
            context: context,
            builder: (BuildContext context) {
              return AlertDialog(
                content: const Text('你需要允许一些权限'),
                actions: [
                  FlatButton(
                    child: const Text('取消'),
                    onPressed: () {
                      Navigator.of(context).pop(ConfirmAction.CANCEL);
                    },
                  ),
                  FlatButton(
                    child: const Text('同意'),
                    onPressed: () {
                      Navigator.of(context).pop(ConfirmAction.ACCEPT);
                    },
                  )
                ],
              );
            });
        if (action == ConfirmAction.ACCEPT) {
          Map<Permission, PermissionStatus> allStatus = await missed.request();
          allGranted = true;
          for (var status in allStatus.values) {
            if (status != PermissionStatus.granted) {
              allGranted = false;
            }
          }
        }
      } else {
        Map<Permission, PermissionStatus> allStatus = await missed.request();
        allGranted = true;
        for (var status in allStatus.values) {
          if (status != PermissionStatus.granted) {
            allGranted = false;
          }
        }
      }
    }

    if (!allGranted) {
      openAppSettings();
    } else {
      Navigator.push(
          context,
          MaterialPageRoute(
              builder: (context) => CallPage(
                    cid: _channelController.text,
                    uid: int.parse(_uidController.text),
                  )));
    }
  }
}

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

1 回复

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


nertc 是网易云信提供的一个音视频通信 SDK,支持在 Flutter 应用中实现实时音视频通信。使用 nertc 插件,你可以轻松地在 Flutter 应用中集成音视频通话功能。以下是如何在 Flutter 项目中使用 nertc 插件的基本步骤。

1. 添加依赖

首先,你需要在 pubspec.yaml 文件中添加 nertc 插件的依赖。

dependencies:
  flutter:
    sdk: flutter
  nertc: ^版本号 # 请使用最新版本

然后运行 flutter pub get 来获取依赖。

2. 初始化 SDK

在使用 nertc 之前,你需要初始化 SDK。通常,你可以在应用的 main 函数中进行初始化。

import 'package:nertc/nertc.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  
  // 初始化 NERTC SDK
  await NERtcEngine.instance.init(
    appKey: '你的AppKey',
    logDirPath: '日志目录路径', // 可选
  );
  
  runApp(MyApp());
}

3. 加入房间

在初始化 SDK 之后,你可以调用 joinChannel 方法加入音视频房间。

void joinRoom() async {
  try {
    await NERtcEngine.instance.joinChannel(
      token: '你的Token', // 如果需要鉴权
      channelName: '房间名称',
      uid: '用户ID', // 可选,如果不传,SDK 会自动生成
    );
  } catch (e) {
    print('加入房间失败: $e');
  }
}

4. 设置本地视频视图

在加入房间后,你可以设置本地视频视图,以便显示本地摄像头的画面。

void setupLocalVideo() async {
  await NERtcEngine.instance.setupLocalVideo(
    NERtcVideoView(
      key: 'local_video',
      width: 200,
      height: 200,
    ),
  );
}

5. 设置远程视频视图

当有远程用户加入房间时,你可以设置远程视频视图,以便显示远程用户的画面。

void setupRemoteVideo(int uid) async {
  await NERtcEngine.instance.setupRemoteVideo(
    NERtcVideoView(
      key: 'remote_video_$uid',
      width: 200,
      height: 200,
    ),
    uid: uid,
  );
}

6. 处理事件

你可以监听 nertc 的事件,例如用户加入、离开、音视频状态变化等。

void setupEventListeners() {
  NERtcEngine.instance.onUserJoined = (int uid) {
    print('用户 $uid 加入房间');
    setupRemoteVideo(uid);
  };

  NERtcEngine.instance.onUserLeft = (int uid) {
    print('用户 $uid 离开房间');
  };

  NERtcEngine.instance.onUserVideoStart = (int uid) {
    print('用户 $uid 开始视频');
  };

  NERtcEngine.instance.onUserVideoStop = (int uid) {
    print('用户 $uid 停止视频');
  };
}

7. 离开房间

当你想离开房间时,可以调用 leaveChannel 方法。

void leaveRoom() async {
  try {
    await NERtcEngine.instance.leaveChannel();
  } catch (e) {
    print('离开房间失败: $e');
  }
}

8. 释放资源

在应用退出时,记得释放 nertc 的资源。

void dispose() async {
  await NERtcEngine.instance.release();
}

9. 处理权限

在 Android 和 iOS 上,你需要确保应用有摄像头和麦克风的权限。你可以在 pubspec.yaml 中添加 permission_handler 插件来处理权限。

dependencies:
  permission_handler: ^版本号

然后在代码中请求权限:

import 'package:permission_handler/permission_handler.dart';

void requestPermissions() async {
  await [Permission.camera, Permission.microphone].request();
}

10. 完整示例

以下是一个简单的完整示例:

import 'package:flutter/material.dart';
import 'package:nertc/nertc.dart';
import 'package:permission_handler/permission_handler.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  
  // 初始化 NERTC SDK
  await NERtcEngine.instance.init(
    appKey: '你的AppKey',
    logDirPath: '日志目录路径', // 可选
  );
  
  runApp(MyApp());
}

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

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

class _VideoCallScreenState extends State<VideoCallScreen> {
  [@override](/user/override)
  void initState() {
    super.initState();
    requestPermissions();
    setupEventListeners();
    joinRoom();
    setupLocalVideo();
  }

  void requestPermissions() async {
    await [Permission.camera, Permission.microphone].request();
  }

  void joinRoom() async {
    try {
      await NERtcEngine.instance.joinChannel(
        token: '你的Token', // 如果需要鉴权
        channelName: '房间名称',
        uid: '用户ID', // 可选,如果不传,SDK 会自动生成
      );
    } catch (e) {
      print('加入房间失败: $e');
    }
  }

  void setupLocalVideo() async {
    await NERtcEngine.instance.setupLocalVideo(
      NERtcVideoView(
        key: 'local_video',
        width: 200,
        height: 200,
      ),
    );
  }

  void setupRemoteVideo(int uid) async {
    await NERtcEngine.instance.setupRemoteVideo(
      NERtcVideoView(
        key: 'remote_video_$uid',
        width: 200,
        height: 200,
      ),
      uid: uid,
    );
  }

  void setupEventListeners() {
    NERtcEngine.instance.onUserJoined = (int uid) {
      print('用户 $uid 加入房间');
      setupRemoteVideo(uid);
    };

    NERtcEngine.instance.onUserLeft = (int uid) {
      print('用户 $uid 离开房间');
    };

    NERtcEngine.instance.onUserVideoStart = (int uid) {
      print('用户 $uid 开始视频');
    };

    NERtcEngine.instance.onUserVideoStop = (int uid) {
      print('用户 $uid 停止视频');
    };
  }

  void leaveRoom() async {
    try {
      await NERtcEngine.instance.leaveChannel();
    } catch (e) {
      print('离开房间失败: $e');
    }
  }

  [@override](/user/override)
  void dispose() async {
    await NERtcEngine.instance.release();
    super.dispose();
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('视频通话'),
      ),
      body: Center(
        child: Column(
          children: [
            NERtcVideoView(
              key: 'local_video',
              width: 200,
              height: 200,
            ),
            Expanded(
              child: ListView.builder(
                itemCount: remoteUsers.length,
                itemBuilder: (context, index) {
                  return NERtcVideoView(
                    key: 'remote_video_${remoteUsers[index]}',
                    width: 200,
                    height: 200,
                  );
                },
              ),
            ),
            ElevatedButton(
              onPressed: leaveRoom,
              child: Text('离开房间'),
            ),
          ],
        ),
      ),
    );
  }
}
回到顶部