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

iPhone端React应用视频上传异常:视频被识别为image/jpeg

iPhone视频上传Bug排查与解决方案

我在React应用里碰到个棘手问题:iPhone用户上传视频时总是失败,但Windows桌面端、安卓设备和iPad都完全正常。排查后发现关键异常:在iPhone上选择明明是视频的文件时,文件名后缀会变成.jpeg,file.type显示为image/jpeg,只有文件大小和原视频一致。

原始代码参考

Input元素

<input
  hidden
  multiple
  accept="video/mp4,video/avi,video/quicktime,video/x-ms-wmv,image/*"
  style={{ display: "none" }}
  id="raised-button-file"
  type="file"
  onChange={handleMedia}
/>

handleMedia处理函数

const handleMedia = (e) => {
  const files = e.target.files;

  const allowedVideoExtensions = ["mp4", "avi", "mov", "wmv"];

  for (let i = 0; i < files.length; i++) {
    const file = files[i];
    const fileExtension = file.name.split('.').pop().toLowerCase();

    const isVideo = allowedVideoExtensions.includes(fileExtension);
    const isImage = !isVideo; // 不是视频就当作图片
        
    alert(`File Name: ${file.name}, Extension: ${fileExtension}, Is Video: ${isVideo}, fileType: ${file.type}, fileSize: ${file.size / 1024 / 1024}`);

    if (isImage && file.size > 20 * 1024 * 1024) { // 20 MB
      alert("One or more of your images exceed the 20MB size limit. Please select smaller files.");
      return;
    } else if (isVideo && file.size > 200 * 1024 * 1024) { // 200 MB
      alert("One or more of your videos exceed the 200MB size limit. Please select smaller files.");
      return;
    }
  }

  setMedia(Array.from(e.target.files));
};

简化测试组件(问题复现用)

import React from "react";
import { Flexbox, FlexboxCol, Typo } from "../../UI/StyledComponents";
import CameraAltIcon from "@mui/icons-material/CameraAlt";

const TestComponent = () => {
  const handleMedia = (e) => {
    const files = e.target.files;
    const allowedVideoExtensions = ["mp4", "avi", "mov", "wmv"];

    for (let i = 0; i < files.length; i++) {
      const file = files[i];
      const fileExtension = file.name.split(".").pop().toLowerCase();

      const isVideo = allowedVideoExtensions.includes(fileExtension);
      const isImage = !isVideo; // 不是视频就当作图片

      alert(
        `File Name: ${file.name}, Extension: ${fileExtension}, Is Video: ${isVideo}, fileType: ${file.type}, fileSize: ${(file.size / 1024 / 1024).toFixed(2)}MB`
      );
    }
  };

  return (
    <FlexboxCol fWidth fHeight withScrollbar hideScroll fullPadding={24}>
      <FlexboxCol fWidth fullPadding={8} sx={{ border: "1px solid #00ace8" }}>
       <Typo sx={{ fontSize: ".75rem", color: '#fff', backgroundColor: '#000', direction: 'ltr', whiteSpace: "pre-wrap", }}>
          accept="video/mp4,video/avi,video/quicktime,video/x-ms-wmv,image/*"
        </Typo>
        <Flexbox fWidth>
          <input
            hidden
            multiple
            accept="video/mp4,video/avi,video/quicktime,video/x-ms-wmv,image/*"
            style={{ display: "none" }}
            id="raised-button-file"
            type="file"
            onChange={handleMedia}
          />
          <label htmlFor="raised-button-file" style={{ height: 28, width: 28 }}>
            <CameraAltIcon
              sx={{ mx: 0.4 }}
              style={{ color: "#00ace880", height: 28, width: 28 }}
            />
          </label>
        </Flexbox>
      </FlexboxCol>
      <FlexboxCol fWidth fullPadding={8} sx={{ border: "1px solid #00ace8" }}>
       <Typo sx={{ fontSize: ".75rem", color: '#fff', backgroundColor: '#000', direction: 'ltr', whiteSpace: "pre-wrap", }}>accept="video/*,image/*"</Typo>
        <Flexbox fWidth>
          <input
            hidden
            multiple
            accept="video/*,image/*"
            style={{ display: "none" }}
            id="raised-button-file"
            type="file"
            onChange={handleMedia}
          />
          <label htmlFor="raised-button-file" style={{ height: 28, width: 28 }}>
            <CameraAltIcon
              sx={{ mx: 0.4 }}
              style={{ color: "#FF5733", height: 28, width: 28 }}
            />
          </label>
        </Flexbox>
      </FlexboxCol>

      <FlexboxCol fWidth fullPadding={8} sx={{ border: "1px solid #00ace8" }}>
       <Typo sx={{ fontSize: ".75rem", color: '#fff', backgroundColor: '#000', direction: 'ltr', whiteSpace: "pre-wrap", }}>accept="video/*"</Typo>
        <Flexbox fWidth>
          <input
            hidden
            multiple
            accept="video/*"
            style={{ display: "none" }}
            id="raised-button-file"
            type="file"
            onChange={handleMedia}
          />
          <label htmlFor="raised-button-file" style={{ height: 28, width: 28 }}>
            <CameraAltIcon
              sx={{ mx: 0.4 }}
              style={{ color: "#33FFBD", height: 28, width: 28 }}
            />
          </label>
        </Flexbox>
      </FlexboxCol>

      <FlexboxCol fWidth fullPadding={8} sx={{ border: "1px solid #00ace8" }}>
        <Typo sx={{ fontSize: ".75rem", color: '#fff', backgroundColor: '#000', direction: 'ltr', whiteSpace: "pre-wrap", }}>
          accept="video/mp4,video/x-m4v,video/*"
        </Typo>
        <Flexbox fWidth>
          <input
            hidden
            multiple
            accept="video/mp4,video/x-m4v,video/*"
            style={{ display: "none" }}
            id="raised-button-file"
            type="file"
            onChange={handleMedia}
          />
          <label htmlFor="raised-button-file" style={{ height: 28, width: 28 }}>
            <CameraAltIcon
              sx={{ mx: 0.4 }}
              style={{ color: "#9037af", height: 28, width: 28 }}
            />
          </label>
        </Flexbox>
      </FlexboxCol>
    </FlexboxCol>
  );
};

export default TestComponent;

问题原因

这是iOS Safari的特性:当通过照片库选择视频时,系统可能返回视频的缩略图文件而非原始视频,尤其是在accept属性同时包含图片和视频类型时,文件选择器会优先返回预览图。另外iOS的命名逻辑会把视频缩略图命名成和原视频同名但后缀为.jpeg的文件,导致代码误判成图片。

解决方案

  1. 放弃用文件名后缀判断类型:直接通过file.type判断,因为后缀在iOS上不可靠。检查file.type是否以video/开头来识别视频,以image/开头识别图片。
  2. 优化accept属性:如果不需要同时上传图片和视频,分开做两个独立的上传按钮;如果必须同时支持,用accept="video/*,image/*",但要确保代码逻辑依赖MIME类型而非后缀。
  3. 修改handleMedia函数:
const handleMedia = (e) => {
  const files = e.target.files;

  for (let i = 0; i < files.length; i++) {
    const file = files[i];
    const isVideo = file.type.startsWith('video/');
    const isImage = file.type.startsWith('image/');
        
    alert(`File Name: ${file.name}, Is Video: ${isVideo}, fileType: ${file.type}, fileSize: ${file.size / 1024 / 1024}`);

    if (isImage && file.size > 20 * 1024 * 1024) { // 20 MB
      alert("部分图片超过20MB大小限制,请选择更小的文件。");
      return;
    } else if (isVideo && file.size > 200 * 1024 * 1024) { // 200 MB
      alert("部分视频超过200MB大小限制,请选择更小的文件。");
      return;
    }
  }

  setMedia(Array.from(e.target.files));
};

额外提示:如果用户是从照片库选择视频,iOS有时会提供「编辑」选项,编辑后的视频可能会改变文件类型和命名,这时候也需要依赖MIME类型判断。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 00:29:50