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

Flutter WebRTC摄像头仅在锁屏解锁后工作问题求助

解决flutter_webrtc摄像头显示问题

一、模拟器无法显示摄像头的原因及解决

Android模拟器本身无物理摄像头,默认未加载虚拟摄像头,可按以下方式处理:

  • 打开模拟器设置(右上角三点按钮)→ Virtual Sensors → 找到Camera选项,选择模拟摄像头(如webcam0或虚拟视频文件),保存后重启模拟器。
  • 优先使用Android真机测试,模拟器的摄像头模拟存在兼容性局限。

二、Android真机锁屏解锁后才显示的问题修复

你的代码存在异步流程未同步、未触发UI更新、权限缺失等问题,以下是针对性修复:

1. 同步异步流程并触发UI刷新

initState中直接调用异步函数会导致渲染器未初始化完成就设置媒体流,且设置后未通知UI更新。修改初始化逻辑:

@override
void initState() {
  super.initState();
  _initCamera(); // 封装为异步初始化方法
}

Future<void> _initCamera() async {
  await initRenderers(); // 等待渲染器初始化完成
  final stream = await _getUserMedia();
  setState(() { // 触发UI重新构建
    _localVideoRenderer.srcObject = stream;
  });
}

同时清理_getUserMedia中重复的流设置代码,并明确摄像头方向:

Future<MediaStream> _getUserMedia() async {
  final Map<String, dynamic> mediaConstraints = {
    'audio': true,
    'video': {
      'facingMode': 'user', // 指定前置摄像头,提升兼容性
    },
  };

  return await navigator.mediaDevices.getUserMedia(mediaConstraints);
}

2. 配置并动态申请权限

Android 6.0+需动态申请摄像头和麦克风权限:

  1. 在android/app/src/main/AndroidManifest.xml添加权限声明:
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />
  1. 添加permission_handler插件(在pubspec.yaml中添加permission_handler: ^10.2.0),修改_initCamera先申请权限:
import 'package:permission_handler/permission_handler.dart';

Future<void> _initCamera() async {
  final cameraStatus = await Permission.camera.request();
  final micStatus = await Permission.microphone.request();
  if (cameraStatus.isDenied || micStatus.isDenied) {
    ScaffoldMessenger.of(context).showSnackBar(
      const SnackBar(content: Text('需要摄像头和麦克风权限才能使用')),
    );
    return;
  }

  await initRenderers();
  final stream = await _getUserMedia();
  setState(() {
    _localVideoRenderer.srcObject = stream;
  });
}

3. 添加渲染器初始化异常捕获

避免初始化失败导致后续流程卡住:

Future<void> initRenderers() async {
  try {
    await _localVideoRenderer.initialize();
  } catch (e) {
    print('渲染器初始化失败: $e');
  }
}

三、完整修改后的代码

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

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

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final _localVideoRenderer = RTCVideoRenderer();
  MediaStream? _localStream;

  Future<void> initRenderers() async {
    try {
      await _localVideoRenderer.initialize();
    } catch (e) {
      print('渲染器初始化失败: $e');
    }
  }

  Future<MediaStream> _getUserMedia() async {
    final Map<String, dynamic> mediaConstraints = {
      'audio': true,
      'video': {
        'facingMode': 'user',
      },
    };

    return await navigator.mediaDevices.getUserMedia(mediaConstraints);
  }

  Future<void> _initCamera() async {
    final cameraStatus = await Permission.camera.request();
    final micStatus = await Permission.microphone.request();
    if (cameraStatus.isDenied || micStatus.isDenied) {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text('需要摄像头和麦克风权限才能正常使用')),
      );
      return;
    }

    await initRenderers();
    final stream = await _getUserMedia();
    setState(() {
      _localStream = stream;
      _localVideoRenderer.srcObject = stream;
    });
  }

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

  @override
  void dispose() async {
    _localStream?.dispose();
    await _localVideoRenderer.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Stack(
        children: [
          Positioned.fill(
            child: RTCVideoView(_localVideoRenderer),
          ),
        ],
      ),
    );
  }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 23:16:01