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

Flutter Web应用如何通过USB访问外接摄像头?

Flutter Web 访问USB外接摄像头解决方案

核心逻辑

Flutter Web的camera_web仅支持系统默认摄像头,USB外接设备需直接调用浏览器原生MediaDevices API实现访问——这是Web平台处理外接摄像头的标准方式,我们可以通过Flutter的dart:html库直接对接该API。


步骤1:枚举所有可用摄像头(含USB外接)

通过浏览器API列出所有视频输入设备,筛选出目标USB摄像头:

import 'dart:html' as html;

Future<List<html.MediaDeviceInfo>> getAvailableCameras() async {
  final allDevices = await html.window.navigator.mediaDevices.enumerateDevices();
  // 筛选视频输入设备(摄像头)
  return allDevices.where((device) => device.kind == 'videoinput').toList();
}

返回的MediaDeviceInfo包含设备名称(label)和唯一标识(deviceId),用deviceId即可指定要访问的USB摄像头。


步骤2:打开USB摄像头并显示预览

通过getUserMedia指定目标设备ID获取媒体流,再用HtmlElementView在Flutter中渲染预览:

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

class UsbCameraPreview extends StatefulWidget {
  final String deviceId;

  const UsbCameraPreview({super.key, required this.deviceId});

  @override
  State<UsbCameraPreview> createState() => _UsbCameraPreviewState();
}

class _UsbCameraPreviewState extends State<UsbCameraPreview> {
  html.VideoElement? _videoElement;
  String? _platformViewId;

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

  Future<void> _initializeCamera() async {
    final mediaConstraints = {
      'video': {
        'deviceId': {'exact': widget.deviceId},
        'width': 1280,
        'height': 720,
      }
    };

    try {
      final mediaStream = await html.window.navigator.mediaDevices.getUserMedia(mediaConstraints);
      _videoElement = html.VideoElement()
        ..srcObject = mediaStream
        ..autoplay = true
        ..playsInline = true;

      // 注册Web视图用于Flutter渲染
      _platformViewId = 'usb-camera-${widget.deviceId}';
      ui.platformViewRegistry.registerViewFactory(
        _platformViewId!,
        (int id) => _videoElement!,
      );

      setState(() {});
    } catch (e) {
      print('摄像头初始化失败:$e');
    }
  }

  @override
  void dispose() {
    // 释放媒体资源
    _videoElement?.srcObject?.getTracks().forEach((track) => track.stop());
    _videoElement?.remove();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    if (_platformViewId == null) {
      return const Center(child: CircularProgressIndicator());
    }
    return HtmlElementView(viewType: _platformViewId!);
  }
}

使用时传入目标USB摄像头的deviceId即可加载预览。


步骤3:拍摄照片

通过Canvas截取视频帧,转换成图片Blob或Base64格式:

// 在UsbCameraPreview的State类中添加方法
Future<html.Blob?> capturePhoto() async {
  if (_videoElement == null) return null;

  final canvas = html.CanvasElement(
    width: _videoElement!.videoWidth,
    height: _videoElement!.videoHeight,
  );
  final canvasContext = canvas.context2D;
  canvasContext.drawImage(_videoElement!, 0, 0);

  return await canvas.toBlob('image/png');
}

// 转换为Flutter可用的ImageProvider
Future<ImageProvider> getCapturedImage() async {
  final photoBlob = await capturePhoto();
  if (photoBlob == null) throw Exception('拍摄失败');

  final fileReader = html.FileReader();
  fileReader.readAsDataUrl(photoBlob);
  await fileReader.onLoad.first;
  return NetworkImage(fileReader.result as String);
}

关于usb_device包的说明

usb_device主要用于Flutter移动平台(Android/iOS)直接操作USB底层设备,Web平台中浏览器已封装了USB媒体设备的访问逻辑,无需直接操作USB协议,因此该包在Web环境下不适用。


注意事项

  • 需在HTTPS环境运行(本地开发用flutter run -d chrome可正常使用,Chrome对localhost的HTTP权限限制有所放宽)
  • 首次访问需用户授权摄像头权限
  • 不同浏览器对媒体设备的兼容细节略有差异,建议优先在Chrome中测试

内容的提问来源于stack exchange,提问作者Abdallah Reda

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 00:56:12