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

iOS端Telegram Web App摄像头画面不显示求助(浏览器端正常)

问题概述

使用React + face-api.js开发的实时人脸戴帽应用,通过摄像头采集画面实现功能:

  • iOS Safari/Chrome等浏览器中摄像头画面正常显示
  • Android端(含Telegram WebApp)及PC端全场景运行无异常
  • iOS Telegram WebApp中:已授予摄像头权限(系统绿灯亮起),但摄像头画面完全不显示;用Eruda排查发现控制台无报错,video标签存在于DOM结构中;已排除react-webcam组件问题,推测与Telegram iOS端使用的WKWebView兼容性有关。
针对性解决方案建议

1. 替换react-webcam为原生video元素,手动控制MediaStream初始化

第三方摄像头组件的封装逻辑在WKWebView中可能存在兼容性黑盒,直接调用原生Media API更可靠:

import React, { useEffect, useRef, useState } from 'react';
import {detectAllFaces} from 'face-api.js';
import * as faceapi from 'face-api.js';
import imgPath from './images/overlay-cap.png';
import './App.css';

const App = () => {
  const videoRef = useRef(null);
  const canvasRef = useRef(null);
  const hatImageRef = useRef(null);
  const [cameraActive, setCameraActive] = useState(false);

  useEffect(() => {
    const app = window.Telegram.WebApp;
    app.expand();
    app.ready();

    // 手动初始化摄像头
    const initCamera = async () => {
      try {
        const stream = await navigator.mediaDevices.getUserMedia({
          video: { facingMode: 'user', width: { ideal: 1280 }, height: { ideal: 720 } }
        });
        if (videoRef.current) {
          videoRef.current.srcObject = stream;
          videoRef.current.onloadedmetadata = () => {
            videoRef.current.play();
            setCameraActive(true);
            startFaceDetection();
          };
        }
      } catch (err) {
        console.error('摄像头启动失败:', err);
      }
    };

    // 人脸检测逻辑(依赖已就绪的视频流)
    const startFaceDetection = async () => {
      const MODEL_URL = process.env.PUBLIC_URL + '/models';
      await faceapi.loadTinyFaceDetectorModel(MODEL_URL);
      await faceapi.loadFaceLandmarkModel(MODEL_URL);
      
      const videoEl = videoRef.current;
      const canvas = canvasRef.current;
      const context = canvas.getContext('2d');

      const detectLoop = setInterval(async () => {
        if (!cameraActive) return;
        const detections = await faceapi.detectAllFaces(videoEl, new faceapi.TinyFaceDetectorOptions()).withFaceLandmarks();
        context.clearRect(0, 0, canvas.width, canvas.height);

        detections.forEach((detection) => {
          const hatImage = hatImageRef.current;
          hatImage.style.display = 'none';

          const landmarks = detection.landmarks;
          const leftEyeBrow = landmarks.getLeftEyeBrow();
          const rightEyeBrow = landmarks.getRightEyeBrow();
          const leftPoint = leftEyeBrow[0];
          const rightPoint = rightEyeBrow.at(-1);
          const width = (rightPoint.x - leftPoint.x) * 2;

          canvas.width = width;
          canvas.height = 91;
          canvas.style.width = `${width}px`;
          canvas.style.left = `${leftPoint.x - width * 0.1 - 10}px`;
          canvas.style.top = `${leftEyeBrow[0].y - width * 0.55}px`;

          context.drawImage(hatImage, 0, 0, canvas.width, 91);
        });
      }, 300);

      return () => clearInterval(detectLoop);
    };

    // 绑定Telegram WebApp的就绪回调(确保环境初始化完成)
    app.ready(() => {
      initCamera();
    });

  }, []);

  return (
    <div className='App'>
      {/* 原生video替代react-webcam,必须加playsInline */}
      <video
        ref={videoRef}
        className='App__video'
        playsInline
        autoPlay
        muted
        style={{
          position: 'absolute',
          left: 0,
          top: 0,
          zIndex: 1,
          width: '100%',
          height: '100%',
          objectFit: 'cover'
        }}
      />
      {/* 其他元素保持不变 */}
      <canvas
        ref={canvasRef}
        style={{
          position: 'absolute',
          zIndex: 100,
          aspectRatio: '171 / 91'
        }}
      />
      <img
        ref={hatImageRef}
        src={imgPath}
        className='cap'
        alt="Hat"
        style={{ display: 'none' }}
      />
    </div>
  );
};

export default App;

关键注意点:

  • 必须给video标签添加playsInline属性:WKWebView默认强制video全屏播放,该属性是内联显示的核心前提
  • 配合autoPlay和muted:规避iOS的自动播放限制(摄像头视频无音频,但该组合能提升兼容性)
  • 视频流就绪后再启动人脸检测:通过onloadedmetadata回调确保视频可播放后再执行检测逻辑

2. 强制在用户交互上下文内触发摄像头权限请求

WKWebView对媒体权限的请求有严格限制,必须在用户主动点击/触摸操作的回调中调用getUserMedia,否则可能出现权限已授予但流无法正常播放的情况。可以添加启动按钮:

// 在组件内添加状态和点击事件
const [showStartBtn, setShowStartBtn] = useState(true);

const handleStartCamera = async () => {
  setShowStartBtn(false);
  // 这里调用initCamera逻辑
};

// 在JSX中添加按钮
{showStartBtn && (
  <button 
    onClick={handleStartCamera}
    style={{
      position: 'relative',
      zIndex: 200,
      padding: '16px 32px',
      fontSize: '18px',
      backgroundColor: '#2196F3',
      color: 'white',
      border: 'none',
      borderRadius: '8px'
    }}
  >
    开启摄像头
  </button>
)}

3. 调整Telegram WebApp初始化顺序

确保Telegram WebApp完全初始化后再执行摄像头和模型加载逻辑,避免环境未就绪导致的隐性问题:

useEffect(() => {
  const app = window.Telegram.WebApp;
  // 先调用ready,再执行后续逻辑
  app.ready(() => {
    app.expand();
    // 这里执行摄像头初始化和模型加载
    initCamera();
  });
}, []);

4. 排查WKWebView的媒体权限状态

手动验证摄像头权限的实际状态,排除权限授予异常:

const checkCameraPermission = async () => {
  const permissionStatus = await navigator.permissions.query({ name: 'camera' });
  console.log('摄像头权限状态:', permissionStatus.state);
  // 如果状态是denied,引导用户去系统设置开启
  if (permissionStatus.state === 'denied') {
    alert('请在系统设置中开启摄像头权限');
  }
};
总结

iOS Telegram WebApp的WKWebView在媒体处理上有特殊的安全和行为限制,核心解决思路是:

  • 放弃第三方摄像头组件的封装,直接控制原生MediaStream流程
  • 确保媒体请求和播放触发在用户交互上下文内
  • 正确配置video元素的WKWebView专属属性(如playsInline)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 14:07:10