Flutter视频通信插件livekit_client_custom的使用

Flutter视频通信插件livekit_client_custom的使用

LiveKit Flutter SDK

官方Flutter SDK用于LiveKit。可以轻松地为您的Flutter应用添加实时音视频功能。

文档

更多文档和指南可在 https://docs.livekit.io 查看。

当前支持的功能

功能 订阅/发布 多流传输 背景音频 屏幕共享
Web 🟢 🟢 🟢 🟢
iOS 🟢 🟢 🟢 🟢
Android 🟢 🟢 🟢 🟢
Mac 🟢 🟢 🟢 🟢
Windows 🟢 🟢 🟢 🟢

🟢 = 可用
🟡 = 即将推出(进行中)
🔴 = 当前不可用(未来可能)

示例应用

我们在example/文件夹中构建了一个多人会议应用。您可以从任何支持LiveKit客户端的应用程序加入同一个房间。

安装

pubspec.yaml 文件中包含此包:

dependencies:
  livekit_client_custom: <version>

iOS

在您的 Info.plist 文件中声明相机和麦克风的使用需求。

<key>NSCameraUsageDescription</key>
<string>$(PRODUCT_NAME) 使用您的摄像头</string>
<key>NSMicrophoneUsageDescription</key>
<string>$(PRODUCT_NAME) 使用您的麦克风</string>

如果启用后台模式,您的应用程序可以在切换到后台时运行语音通话。在Xcode中选择应用程序目标,点击“能力”标签,启用后台模式,并选中“音频、AirPlay和画中画”。

您的 Info.plist 文件应包含以下条目:

<key>UIBackgroundModes</key>
<array>
  <string>audio</string>
</array>

注意事项

由于xcode 14不再支持32位构建,我们的最新版本基于libwebrtc m104+,iOS框架不再支持32位构建。强烈建议升级到Flutter 3.3.0+。如果您使用的是Flutter 3.0.0或更低版本,则由于缺少i386和arm 32位框架,您的Flutter应用可能无法正确编译(参见issue 132issue 172)。

您可以通过修改 {projects_dir}/ios/Podfile 来解决此问题:

post_install do |installer|
  installer.pods_project.targets.each do |target|
    flutter_additional_ios_build_settings(target)

    target.build_configurations.each do |config|
      config.build_settings['ONLY_ACTIVE_ARCH'] = 'YES' # &lt;= 这一行
    end
  end
end

对于iOS,最低支持的部署目标为 12.1。您需要在Podfile中添加以下内容:

platform :ios, '12.1'

更新部署目标后,可能需要删除 Podfile.lock 并重新运行 pod install

Android

我们需要声明一组权限,这些权限需要在您的 AppManifest.xml 中声明。这些权限是由我们依赖的Flutter WebRTC所需的。

<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.your.package">
  <uses-feature android:name="android.hardware.camera" />
  <uses-feature android:name="android.hardware.camera.autofocus" />
  <uses-permission android:name="android.permission.CAMERA" />
  <uses-permission android:name="android.permission.RECORD_AUDIO" />
  <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
  <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
  <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
  <uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" />
  <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" />
  ...
</manifest>

桌面支持

为了启用Flutter桌面开发,请遵循 此处 的说明。

在M1 Mac上,您还需要安装x86_64版本的FFI:

sudo arch -x86_64 gem install ffi

在Windows上,需要安装 VS 2019(参见Flutter文档中的链接将下载VS 2022)。

使用

连接到房间,发布视频和音频

final roomOptions = RoomOptions(
  adaptiveStream: true,
  dynacast: true,
  // ... 你的房间选项
);

final room = await LiveKitClient.connect(url, token, roomOptions: roomOptions);
try {
  // 在iOS模拟器中运行时视频可能会失败
  await room.localParticipant.setCameraEnabled(true);
} catch (error) {
  print('无法发布视频,错误: $error');
}

await room.localParticipant.setMicrophoneEnabled(true);

屏幕共享

屏幕共享在所有平台上都受支持。您可以使用以下方法启用它:

room.localParticipant.setScreenShareEnabled(true);
Android

在Android上,您需要在 AndroidManifest.xml 中定义一个前台服务。

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
  <application>
    ...
    <service
        android:name="de.julianassmann.flutter_background.IsolateHolderService"
        android:enabled="true"
        android:exported="false"
        android:foregroundServiceType="mediaProjection" />
  </application>
</manifest>
iOS

在iOS上,您需要一个广播扩展来捕获其他应用程序的屏幕内容。请参阅 设置指南 获取详细说明。

桌面(Windows/MacOS)

在桌面上,您可以使用 ScreenSelectDialog 选择要共享的窗口或屏幕。

try {
  final source = await showDialog<DesktopCapturerSource>(
    context: context,
    builder: (context) => ScreenSelectDialog(),
  );
  if (source == null) {
    print('取消屏幕共享');
    return;
  }
  print('DesktopCapturerSource: ${source.id}');
  var track = await LocalVideoTrack.createScreenShareTrack(
    ScreenShareCaptureOptions(
      sourceId: source.id,
      maxFrameRate: 15.0,
    ),
  );
  await room.localParticipant.publishVideoTrack(track);
} catch (e) {
  print('无法发布屏幕共享: $e');
}

高级轨道操作

setCameraEnabledsetMicrophoneEnabled 辅助函数是轨道API的包装器。

您也可以手动创建和发布轨道:

var localVideo = await LocalVideoTrack.createCameraTrack();
await room.localParticipant.publishVideoTrack(localVideo);

渲染视频

每个轨道都可以通过提供的 VideoTrackRenderer 小部件单独渲染。

VideoTrack? track;

@override
Widget build(BuildContext context) {
  if (track != null) {
    return VideoTrackRenderer(track);
  } else {
    return Container(
      color: Colors.grey,
    );
  }
}

音频处理

只要订阅了音频轨道,音频就会自动播放。

处理变化

LiveKit客户端使得构建响应状态变化的声明式UI变得简单。它以两种方式通知更改:

  • ChangeNotifier - 通用更改通知。这在构建反应式UI且只关心可能影响渲染的更改时非常有用。
  • EventsListener<Event> - 监听特定事件的模式(请参阅 events.dart)。

以下示例展示了如何使用两者来响应房间事件。

class RoomWidget extends StatefulWidget {
  final Room room;

  RoomWidget(this.room);

  @override
  State<StatefulWidget> createState() {
    return _RoomState();
  }
}

class _RoomState extends State<RoomWidget> {
  late final EventsListener<RoomEvent> _listener = widget.room.createListener();

  @override
  void initState() {
    super.initState();
    // 用于通用更改更新
    widget.room.addListener(_onChange);

    // 用于特定事件
    _listener
      ..on<RoomDisconnectedEvent>((_) {
        // 处理断开连接
      })
      ..on<ParticipantConnectedEvent>((e) {
        print("参与者已加入: ${e.participant.identity}");
      });
  }

  @override
  void dispose() {
    // 确保释放监听器以停止接收进一步更新
    _listener.dispose();
    widget.room.removeListener(_onChange);
    super.dispose();
  }

  void _onChange() {
    // 执行计算并调用setState
    // setState将触发构建
    setState(() {
      // 您的更新在这里
    });
  }

  @override
  Widget build(BuildContext context) {
    // 您的构建函数
  }
}

类似地,当渲染参与者时,您也可以这样做。响应变化使得处理发布的/未发布的轨道或在UI中重新排序参与者成为可能。

class VideoView extends StatefulWidget {
  final Participant participant;

  VideoView(this.participant);

  @override
  State<StatefulWidget> createState() {
    return _VideoViewState();
  }
}

class _VideoViewState extends State<VideoView> {
  TrackPublication? videoPub;

  @override
  void initState() {
    super.initState();
    widget.participant.addListener(this._onParticipantChanged);
    // 触发初始更改
    _onParticipantChanged();
  }

  @override
  void dispose() {
    widget.participant.removeListener(this._onParticipantChanged);
    super.dispose();
  }

  @override
  void didUpdateWidget(covariant VideoView oldWidget) {
    oldWidget.participant.removeListener(_onParticipantChanged);
    widget.participant.addListener(_onParticipantChanged);
    _onParticipantChanged();
    super.didUpdateWidget(oldWidget);
  }

  void _onParticipantChanged() {
    var subscribedVideos = widget.participant.videoTracks.values.where((pub) {
      return pub.kind == TrackType.VIDEO && !pub.isScreenShare && pub.subscribed;
    });

    setState(() {
      if (subscribedVideos.length > 0) {
        var videoPub = subscribedVideos.first;
        // 当静音时显示占位符
        if (!videoPub.muted) {
          this.videoPub = videoPub;
          return;
        }
      }
      this.videoPub = null;
    });
  }

  @override
  Widget build(BuildContext context) {
    var videoPub = this.videoPub;
    if (videoPub != null) {
      return VideoTrackRenderer(videoPub.track as VideoTrack);
    } else {
      return Container(
        color: Colors.grey,
      );
    }
  }
}

静音/取消静音本地轨道

LocalTrackPublication 上,您可以控制轨道是否静音,通过设置其 muted 属性。更改静音状态将生成 onTrackMutedonTrackUnmuted 委托调用给本地参与者。其他参与者也会收到状态更改的通知。

// 静音轨道
trackPub.muted = true;

// 取消静音轨道
trackPub.muted = false;

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

1 回复

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


livekit_client_custom 是一个用于 Flutter 的插件,用于实现视频通信功能。它基于 LiveKit 的 SDK,允许开发者在 Flutter 应用中集成实时音视频通信功能。以下是使用 livekit_client_custom 插件的基本步骤:

1. 添加依赖

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

dependencies:
  flutter:
    sdk: flutter
  livekit_client_custom: ^1.0.0  # 请根据最新版本号进行替换

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

2. 初始化 LiveKit 客户端

在你的 Flutter 应用中,首先需要初始化 LiveKit 客户端。通常,你会在应用的入口处进行初始化。

import 'package:livekit_client_custom/livekit_client_custom.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  
  // 初始化 LiveKit 客户端
  await LiveKitClient.initialize();

  runApp(MyApp());
}

3. 连接到房间

要加入一个房间并进行音视频通信,你需要连接到 LiveKit 服务器。通常,你需要提供一个 WebSocket URL 和一个访问令牌(Token)。

import 'package:livekit_client_custom/livekit_client_custom.dart';

class VideoCallScreen extends StatefulWidget {
  @override
  _VideoCallScreenState createState() => _VideoCallScreenState();
}

class _VideoCallScreenState extends State<VideoCallScreen> {
  Room? _room;

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

  Future<void> _connectToRoom() async {
    try {
      // 连接到房间
      _room = await LiveKitClient.connect(
        'wss://your-livekit-server-url',
        'your-access-token',
      );

      // 监听房间事件
      _room?.on(RoomEvent.trackSubscribed, _onTrackSubscribed);
      _room?.on(RoomEvent.trackUnsubscribed, _onTrackUnsubscribed);

      // 开始发布音视频
      await _room?.localParticipant?.enableCameraAndMicrophone();
    } catch (e) {
      print('Failed to connect to room: $e');
    }
  }

  void _onTrackSubscribed(Track track, RemoteParticipant participant) {
    // 处理订阅的轨道
    setState(() {});
  }

  void _onTrackUnsubscribed(Track track, RemoteParticipant participant) {
    // 处理取消订阅的轨道
    setState(() {});
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Video Call'),
      ),
      body: _buildVideoView(),
    );
  }

  Widget _buildVideoView() {
    if (_room == null) {
      return Center(child: Text('Connecting...'));
    }

    // 显示本地和远程视频
    return ListView(
      children: [
        if (_room?.localParticipant?.cameraTrack != null)
          VideoTrackWidget(_room!.localParticipant!.cameraTrack!),
        for (var participant in _room!.remoteParticipants.values)
          if (participant.cameraTrack != null)
            VideoTrackWidget(participant.cameraTrack!),
      ],
    );
  }
}

4. 显示视频轨道

在上面的代码中,VideoTrackWidget 是一个自定义的 Widget,用于显示视频轨道。你可以使用 VideoRenderer 来渲染视频。

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

class VideoTrackWidget extends StatelessWidget {
  final VideoTrack track;

  VideoTrackWidget(this.track);

  @override
  Widget build(BuildContext context) {
    return AspectRatio(
      aspectRatio: 16 / 9,
      child: VideoRenderer(track),
    );
  }
}

5. 处理房间事件

你可以监听房间的各种事件,例如参与者加入、离开、轨道订阅等,以便在 UI 上做出相应的更新。

void _listenToRoomEvents() {
  _room?.on(RoomEvent.participantConnected, (participant) {
    // 处理参与者连接事件
    setState(() {});
  });

  _room?.on(RoomEvent.participantDisconnected, (participant) {
    // 处理参与者断开连接事件
    setState(() {});
  });
}

6. 断开连接

当用户离开房间时,记得断开连接并清理资源。

@override
void dispose() {
  _room?.disconnect();
  super.dispose();
}

7. 发布和订阅音视频

你可以通过 localParticipant 来发布音视频轨道,并通过 remoteParticipants 来订阅其他参与者的音视频轨道。

// 发布音视频
await _room?.localParticipant?.enableCameraAndMicrophone();

// 订阅远程音视频
for (var participant in _room!.remoteParticipants.values) {
  participant.subscribeTracks();
}

8. 处理权限

确保在应用中请求摄像头和麦克风的权限。

import 'package:permission_handler/permission_handler.dart';

Future<void> _requestPermissions() async {
  await [Permission.camera, Permission.microphone].request();
}

9. 错误处理

在处理音视频通信时,可能会遇到各种错误,例如网络问题、权限问题等。确保在代码中适当地处理这些错误。

try {
  await _connectToRoom();
} catch (e) {
  print('Failed to connect to room: $e');
}

10. 自定义 UI

你可以根据需要自定义 UI,例如添加按钮来控制摄像头、麦克风、切换摄像头等。

IconButton(
  icon: Icon(_room?.localParticipant?.isCameraEnabled ?? false ? Icons.videocam : Icons.videocam_off),
  onPressed: () {
    _room?.localParticipant?.setCameraEnabled(!_room!.localParticipant!.isCameraEnabled);
    setState(() {});
  },
),
回到顶部