You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter LiveKit客户端:如何发布Canvas视频流替代摄像头?

如何在Flutter LiveKit客户端中发布Canvas画面替代摄像头画面?

问题描述

我正在使用Flutter版LiveKit客户端开发一款支持绘图功能的视频通话应用,当前通过Canvas实现空白页面上的绘图操作。希望在LiveKit视频通话中发布Canvas画面来替代摄像头画面,React中可以通过captureStream方法实现,但在Dart中找不到对应方法,特此求助。

React中的实现示例:

// Create 30fps MediaStream
mediaStreamRef.current = canvasRef.current.captureStream(30);

// Publish the canvas video
const publishedTrack = mediaStreamRef.current.getVideoTracks()[0]
localParticipant.publishTrack(publishedTrack, {
  name: "video_from_canvas",
  source: Track.Source.Camera
})

解决方案

Flutter中没有直接的captureStream方法,但可以通过定期捕获Canvas截图并创建自定义LiveKit视频轨道的方式实现需求,具体步骤如下:

1. 用RepaintBoundary包裹Canvas组件

首先需要用RepaintBoundary包裹你的绘图Canvas,以便后续捕获其渲染内容:

import 'package:flutter/material.dart';
import 'dart:ui' as ui;

final GlobalKey _repaintBoundaryKey = GlobalKey();

// 你的绘图Widget结构
Widget build(BuildContext context) {
  return RepaintBoundary(
    key: _repaintBoundaryKey,
    child: CustomPaint(
      painter: YourCanvasPainter(), // 自定义的绘图Painter
      size: Size.infinite,
    ),
  );
}

2. 实现Canvas截图与帧推送逻辑

通过RepaintBoundary的GlobalKey捕获截图,再将截图转换为LiveKit可识别的VideoFrame,定期推送给自定义视频捕获器:

import 'package:livekit_client/livekit_client.dart';
import 'dart:async';
import 'dart:typed_data';

// 捕获Canvas截图
Future<Uint8List> _captureCanvas() async {
  RenderRepaintBoundary boundary = _repaintBoundaryKey.currentContext!.findRenderObject() as RenderRepaintBoundary;
  ui.Image image = await boundary.toImage(pixelRatio: 1.0);
  ByteData? byteData = await image.toByteData(format: ui.ImageByteFormat.png);
  return byteData!.buffer.asUint8List();
}

// 创建自定义视频轨道
Future<LocalVideoTrack> createCanvasVideoTrack() async {
  final capturer = CustomVideoCapturer();
  
  // 以30fps频率捕获并推送帧(每33ms一次)
  Timer.periodic(const Duration(milliseconds: 33), (timer) async {
    if (!capturer.isCapturing) {
      timer.cancel();
      return;
    }
    
    Uint8List imageBytes = await _captureCanvas();
    ui.Image image = await ui.decodeImageFromList(imageBytes);
    VideoFrame frame = VideoFrame.fromImage(image);
    capturer.onFrame(frame);
  });
  
  await capturer.startCapture();
  return LocalVideoTrack(capturer, source: TrackSource.camera);
}

3. 发布自定义轨道替代摄像头画面

停止已发布的摄像头轨道(如果存在),然后发布Canvas对应的自定义视频轨道:

Future<void> publishCanvasTrack(Room room) async {
  final localParticipant = room.localParticipant;
  if (localParticipant == null) return;
  
  // 停止并取消发布原摄像头轨道
  for (final trackPublication in localParticipant.videoTracks) {
    if (trackPublication.source == TrackSource.camera) {
      await localParticipant.unpublishTrack(trackPublication.track);
      trackPublication.track.stop();
    }
  }
  
  // 创建并发布Canvas轨道
  LocalVideoTrack canvasTrack = await createCanvasVideoTrack();
  await localParticipant.publishTrack(
    canvasTrack,
    publishOptions: const PublishOptions(name: "video_from_canvas"),
  );
}

注意事项

  • 性能优化:频繁截图可能导致性能损耗,可根据实际需求调整帧率(比如降低到15fps),或仅在用户绘图操作后触发捕获。
  • 格式兼容:确保转换后的VideoFrame格式与LiveKit服务端兼容,VideoFrame.fromImage已处理大部分格式适配。
  • 权限处理:由于使用自定义轨道,无需申请摄像头权限,但需确保LiveKit客户端初始化配置正确。

内容的提问来源于stack exchange,提问作者Marouan Mekri

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 20:05:13