Live Photo · 把一动一静放上网页
iPhone 相册里那张「按住会动」的照片,在文件系统里从来不是一个文件。它是一张 HEIC 静图加一段 QuickTime MOV,两者靠一个 asset identifier 认亲;MOV 里还有一条 timed metadata track,标出静图对应视频的哪一帧——所以它的播放锚点在中间,不在零。
把这种「一动一静」搬上网页,要分别回答两个互不相干的问题。格式层:一动一静如何配成对、能否塞进一个文件(Google 的 Motion Photo 选了单文件封装,把 MP4 追加在 JPEG 尾部,用 XMP 描述布局)。平台层:浏览器实际解得开哪一半。
第二个问题的常见答案是「Chrome 既不认 MOV 容器也不解 HEVC,所以只能整个转码」。本系列把这两条都实测了一遍,结论与之相反:容器根本不是障碍,canPlayType 在这件事上给的信号与实际行为直接矛盾;真正解不开的是静图那一半。测量口径与复现方式见 解得开哪一半。
双文件与配对
Live Photo 是两个文件加一个 asset identifier,另有一条 metadata track 标出静图对应哪一帧。Motion Photo 则把视频追加到 JPEG 尾部,用 XMP 描述布局。本页拆两种封装的字节结构。
解得开哪一半
实测 demuxer 既不看 MIME 标签也不看 ftyp 品牌,真 HEVC + QuickTime 文件照播,canPlayType 的答复与实际行为矛盾。解不开的是静图那一半。
静图与视频之间
没有解码帧的 video 是透明的,所以「露出黑底」与「画面跳一下」是两个不同的失效,各有各的解法。requestVideoFrameCallback 是唯一能确认「已有一帧在屏上」的信号。
触发与降级
桌面靠 hover、移动端靠长按,两者都不是「事件到了就播」——掠过不算意图,滑动中的按压不算长按。视频在意图确认前不该进 DOM。reduced-motion 下自动播放要整条让出,退到显式入口。
真文件的配对与逐张播放
把前四讲的结论用在真文件上:同名配对、单文件拆包、两半各自探测能否解码、静图解不开时从视频抽一帧当封面,然后逐张播放。iPhone 导出的 HEIC 在 Chrome 里必然解不开,这一页正好当作现场对照。
一条主线
素材与测量
MediaRecorder 现录、Motion Photo 由 lab 现场拼装再拆回。唯一的例外是 decode 页内联的一张 947 B 真 HEIC(base64),用来让「静图这一半解不开」当场可验。代价是合成素材用的是 H.264 / MP4,而非真机的 HEVC;涉及真机格式的断言另用 ffmpeg 产出的文件测过,均标注了核对时点与测量环境。延伸阅读
- Motion Photo format 1.0 developer.android.com Google 单文件封装的规范本体:Camera / Container / Item 三个 XMP 命名空间、Item:Semantic 取值与视频定位算法。
- LivePhotosKit JS developer.apple.com Apple 官方的 web 播放器,接收 photo + video 两个资源。npm 上最后一版 1.5.6 发布于 2019 年。
- PHLivePhoto developer.apple.com iOS 侧的 Live Photo 抽象,双资源模型与 asset identifier 配对语义的出处。
- MotionPhoto / MicroVideo File Formats on Pixel Phones timojyrinki.gitlab.io 逐字节拆解 Pixel 两代封装格式,含已废弃的 GCamera:MicroVideoOffset 与新 Container:Directory 的对照。
- Convert iPhone Live Photo to Google Photos motion photo joelkitching.com 把双文件形态转成单文件封装的实操记录,两种设计的差异在转换步骤里看得最清楚。