ios12promax上人脸识别卡死?

前端使用getUserMedia video标签录制人脸视频,有个用户是 iphone12promax ios18_4,在相机初始化后【成像】的界面就卡死了,用户做动作也没反应。但是我们的代码是在正常运行的(依据是我们前端的倒计时在正常执行),有大佬知道这是什么原因么?
备注:
1、他们是第三方银行APP内访问的。
2、我这边用浏览器没复现出来
3、他们公司其他人有正常访问的记录

阅读 641
1 个回答

Reactjs

import { useEffect, useRef, useState, useCallback } from "react";

interface Options {
  width?: number;
  height?: number;
  frameRate?: number;
  freezeTimeout?: number;
}

export function useSafeCamera(options: Options = {}) {
  const videoRef = useRef<HTMLVideoElement | null>(null);
  const streamRef = useRef<MediaStream | null>(null);
  const rafRef = useRef<number>();
  const lastTimeRef = useRef<number>(-1);
  const lastChangedAtRef = useRef<number>(Date.now());
  const isRestartingRef = useRef(false);

  const [isReady, setIsReady] = useState(false);
  const [error, setError] = useState<string | null>(null);

  const {
    width = 640,
    height = 480,
    frameRate = 24,
    freezeTimeout = 2000,
  } = options;

  const stopCamera = useCallback(() => {
    if (rafRef.current) {
      cancelAnimationFrame(rafRef.current);
      rafRef.current = undefined;
    }
    if (streamRef.current) {
      streamRef.current.getTracks().forEach((t) => t.stop());
      streamRef.current = null;
    }
  }, []);

  const startFreezeDetection = useCallback(() => {
    if (rafRef.current) cancelAnimationFrame(rafRef.current);

    const video = videoRef.current;
    if (!video) return;

    const check = () => {
      const current = video.currentTime;
      const now = Date.now();

      if (current !== lastTimeRef.current) {
        lastTimeRef.current = current;
        lastChangedAtRef.current = now;
      } else if (
        lastTimeRef.current !== -1 &&
        now - lastChangedAtRef.current > freezeTimeout
      ) {
        console.warn("Freeze detected, restarting...");
        stopCamera();
        startCameraInner();
        return;
      }

      rafRef.current = requestAnimationFrame(check);
    };

    rafRef.current = requestAnimationFrame(check);
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [freezeTimeout, stopCamera]);

  const startCameraInner = useCallback(async () => {
    if (isRestartingRef.current) return;
    isRestartingRef.current = true;

    try {
      const stream = await navigator.mediaDevices.getUserMedia({
        video: {
          width: { ideal: width },
          height: { ideal: height },
          frameRate: { ideal: frameRate },
          facingMode: "user",
        },
        audio: false,
      });

      streamRef.current = stream;

      const video = videoRef.current;
      if (!video) return;

      video.srcObject = stream;
      video.setAttribute("autoplay", "");
      video.setAttribute("muted", "");
      video.setAttribute("playsinline", "");

      await video.play().catch(() => {});

      lastTimeRef.current = -1;
      lastChangedAtRef.current = Date.now();

      setIsReady(true);
      setError(null);

      startFreezeDetection();
    } catch (err: any) {
      console.error(err);
      setError(err.message || "camera error");
    } finally {
      isRestartingRef.current = false;
    }
  }, [width, height, frameRate, startFreezeDetection]);

  useEffect(() => {
    const activate = () => {
      videoRef.current?.play().catch(() => {});
    };
    document.body.addEventListener("touchstart", activate, { once: true });
    return () => document.body.removeEventListener("touchstart", activate);
  }, []);

  useEffect(() => {
    startCameraInner();
    return () => stopCamera();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  return {
    videoRef,
    isReady,
    error,
    restartCamera: startCameraInner,
  };
}

调用

import React from "react";
import { useSafeCamera } from "./useSafeCamera";

export default function FacePage() {
  const { videoRef, isReady, error, restartCamera } = useSafeCamera({
    width: 640,
    height: 480,
    frameRate: 24,
    freezeTimeout: 2000,
  });

  return (
    <div style={{ position: "relative" }}>
      {!isReady && (
        <p style={{ textAlign: "center" }}>加载相机中...</p>
      )}
      {error && (
        <div>
          <p style={{ color: "red" }}>错误: {error}</p>
          <button onClick={restartCamera}>重试</button>
        </div>
      )}
      <video
        ref={videoRef}
        muted
        playsInline
        style={{ width: "100%", background: "#000" }}
      />
    </div>
  );
}