Web 平台 API / Live Photo · 把一动一静放上网页 / 真文件的配对与逐张播放 待审核 5 / 5
own · 配对 / 逐张播放

真文件的配对与逐张播放

前四讲用的都是合成素材。本页收真文件,把已有结论逐条兑现在自己相册导出的东西上。

1 · 三种到手形态

从设备导出的结果不止一种,取决于导出方与源系统:

到手的东西 来源 识别方式
两个同名文件(IMG_0001.HEICIMG_0001.MOV iPhone 直接导出 按基名配对
一个 JPEG,尾部嵌着 MP4 Android 的 Motion Photo 扫 APP1 里的 XMP
只有静图,或只有视频 传输中拆散,或导出方只给一半 配不上对

第一行的配对判据是基名相同。这条规则不来自任何规范,而是导出工具的惯例——真正的配对依据是双方元数据里的 asset identifier(见双文件与配对 §2),但那个字段藏在 Apple maker note 与 QuickTime 元数据里,读它要解 EXIF 与 ISO-BMFF 的 box 树。文件名是同一信息的廉价代理,代价是重命名过的文件会配不上。

第三行值得单独收下而不是丢弃:拆散是双文件设计最常见的失效方式,把它显式标出来比静默忽略有用。

2 · 每一半各自探测

判断能否解码,唯一可靠的做法是真去加载:静图交给一个 Image,看 load 还是 error;视频交给一个 <video>,看 loadedmetadata 还是 error。不问 canPlayType,理由见解得开哪一半 §5。

两半的结果是独立的,四种组合都会出现。其中最常见的一种是 Chrome 上的「静图解不开、视频可播」:iPhone 导出的静图是 HEIC,Chrome 没有 HEIF 解码器。

这一格有一条退路:既然视频那一半解得开,就从视频里抽一帧当封面。currentTime 拨到锚点(单文件封装里读 MotionPhotoPresentationTimestampUs,双文件里没有现成值就取中点),等 seekeddrawImage 到 canvas。得到的封面在内容上正是静图那一帧的近似,只是画质经过一次有损重编码。

警示 · 抽帧的前提是画布未被污染:视频与页面同源,或跨域但带 crossorigin 且服务端放行 CORS。本页的文件来自 <input type="file">,包成 blob URL 后同源,canvas 不会被污染;两个条件都不满足时,drawImage 之后 toDataURL 会抛 SecurityError

图 2-1 · 多选文件后逐张播放。同名的静图与视频自动配对,单文件 Motion Photo 会被拆包,两半各自探测解码能力并在图注里标明结果。可单击任一张单独播放,或按「逐张播放」顺序过一遍。

3 · 逐张播放的顺序

顺序播放是一串串行的等待:播一张,等它 ended,再播下一张。有两处需要兜底。

ended 不保证到达。视频在播放中途解码失败、或被浏览器暂停,都会让等待悬在那里,整条队列卡死。所以除了监听 endederror,还要挂一个按时长估算的超时——图 2-1 取「时长加一秒」。

另一处是 play() 的拒绝。逐张播放由按钮触发,静音让整条队列不依赖任何激活状态:muted 的自动播放在主流配置下无需手势资格(Safari 的站点级「永不自动播放」与 iOS 低电量模式仍会拒绝,故 play() 的 promise 照样要 catch)。至于有声播放,取决于各浏览器的 autoplay policy——Chrome 在用户与本域交互过之后即放行,用户点击产生的是黏性激活、不会在 await 之后失效;Safari 口径更严。跨浏览器不可依赖(核对于 2026-08)。

4 · 可当场核对的三条

选入真文件之后,前几讲的结论都能对照:

  1. HEIC 静图在 Chrome 里解不开,且与 MIME 标签无关(解得开哪一半 §4)
  2. 同一批文件里的 .MOV 照常可播,容器不是障碍(同上 §2、§3)
  3. 单文件 Motion Photo 从末尾回退 Item:Length 就能切出视频(双文件与配对 §3)

第 1 条与第 2 条在同一张卡片上同时出现时,那个不对称最直观:一张 Live Photo 送进 Chrome,会动的那一半好端端的,不动的那一半反而是缺口。