1. 什么是 Live Photos
iPhone6s 和 6s Plus 中增加了一个全新特性—— Live Photos,这新特性的作用是在照片拍摄前后录制一段 1.5 秒的动态视频,当用户在照片上深按一下,照片就会自动播放动态效果。用户还可以将 Live Photos 设置为锁屏墙纸,随时重现动态瞬间。(来自百度百科)
2. 使用方式
在评论时使用如下语法即可:
(视频链接)
最终会被渲染成如下 HTML 代码:
data-live-photo
data-effect-type='live'
data-playback-style='full'
data-proactively-loads-video='true'
data-photo-src='图片链接'
data-video-src='视频链接'>
3. 文章中的效果展示
电脑端请将鼠标光标放在图片左上角的 LIVE 字样上开始播放,光标移出停止。
手机端长按图片开始播放,松开停止。
桂花
4. 评论区的使用示例
以下示范步骤均在 Windows 电脑上操作,如果是 Mac 电脑有不同的地方需要大家自己搜索一下解决方案
步骤较多,请耐心观看
参照苹果官方文档中不同设备的导出方式将 Live Photo 照片以及对应的视频导出
检查导出后图片的编码格式,是否是 JPG 格式,不是的话需要自己搜索一下如何导出为 JPG 格式
以宽高比为 3:4 为例(下同),修改图片大小不超过 1008 x 1344
检查导出后视频的编码格式,如果是 HEVC 或 H.265,需要使用例如 ffmpeg 之类的工具将其转为 H.264 编码格式
修改视频大小不超过 720 x 960
将你的图片以及视频上传至一个互联网可访问的地方并提供一个访问链接。且视频的提供方需要支持 206 状态码的 分段传输 功能(这个主要是为了兼容 Safari 浏览器)
在评论区中按照前文中提到的语法输入你的图片以及视频的访问链接即可
4.1. 如何压缩图片
用 windows 自带的图片查看器打开图片,然后鼠标右键选择调整图像大小:
调整图像大小
然后选择定义自定义尺寸:
自定义尺寸
勾选保持纵横比并输入图像的尺寸:
调整尺寸
4.2. 如何查看视频的编码方式以及转换编码
以 ffmpeg 工具为例,控制台输入以下命令查看视频的编码信息:
ffprobe 视频路径 -show_streams -select_streams v -print_format json
如果不是 H.264 使用如下命令将其转为 H.264 编码:
ffmpeg -i 转换前的视频 -c:v libx264 -crf 23 -preset medium -c:a aac -b:a 128k 转换后的视频
4.3. 如何压缩视频到指定大小
依旧以 ffmpeg 为例,将视频压缩到 720 x 960,使用如下命令:
ffmpeg -i 转换前的视频 -vf scale=720:960 转换后的视频 -hide_banner
4.4. 评论区中的展示效果
输入评论内容
点击预览
评论出来的效果
5. 注意事项
图片说明不是必填项,即使不填的话那一对中括号也不能少
图片链接是你的 Live Photo 导出后的后缀为 .JPG 的图片的访问链接,如果是 HEIC 格式的话需要自己处理一下导出为 JPG 格式才行
视频链接是你的 Live Photo 导出后的后缀为 .MOV 的视频的访问链接
重点 1:宽高比为 3:4 比例的图片大小请勿超过 1088 x 1344,其他宽高比暂时没测试过,但越小越好,因为官方的实现原理原因,太大了会导致 safari 浏览器体验效果极差
重点 2:视频格式必须是 .MOV 格式(不区分大小写),且返回的数据的响应头必须为 video/quicktime,且宽高比为 3:4 的视频的大小请勿超过 720 x 960,理由同上
重点 3:视频编码方式需要是 H.264,如果导出来的是 HEVC 编码的,很多浏览器都不兼容这个编码的视频播放,需要自己转换一下
重点 4:存在一定概率连续播放几次后就没法播放了,这个没法解决,只有刷新页面解决
重点 5:这个功能的兼容性不是很好,Google、Safari 这些是没有问题的(版本过低也不行),其他浏览器以及一些应用内置的浏览器可能无法查看,所以如果你无法查看请切换浏览器试试
6. 实现原理
Live Photo 的展示实现是参考苹果官方的LivePhotosKit JS 文档
网站的 Live Photo 图片展示的 Markdown 语法是按照 Marked 官方的 Marked Documentation 来自定义的
扩展代码:
const caret = /(^|[^\[])\^/g
const _label = /(?:\[(?:\\.|[^\[\]\\])*\]|\\.|`[^`]*`|[^\[\]\\`])*?/
const _href = /<(?:\\.|[^\n<>\\])+>|[^\s\x00-\x1f]*/
function edit(regex, opt) {
regex = typeof regex === 'string' ? regex : regex.source
opt = opt || ''
const obj = {
replace: (name, val) => {
val = val.source || val
val = val.replace(caret, '$1')
regex = regex.replace(name, val)
return obj
},
getRegex: () => {
return new RegExp(regex, opt)
}
}
return obj
}
const livePhoto = {
name: 'livePhoto',
level: 'block',
start(src) {
return src.match(/![^!\n]/)?.index
},
tokenizer(src, tokens) {
let rule = /^!?\[(label)\]\(\s*(photoSrc)?\s*\)\(\s*(videoSrc)?\s*\)/
rule = edit(rule)
.replace('label', _label)
.replace('photoSrc', _href)
.replace('videoSrc', _href)
.getRegex()
const match = rule.exec(src)
if (match) {
const token = {
type: 'livePhoto',
raw: match[0],
text: match[0].trim(),
label: match[1],
photoSrc: match[2],
videoSrc: match[3],
tokens: []
}
this.lexer.inline(token.text, token.tokens)
return token
}
},
renderer(token) {
return `
data-live-photo
data-effect-type='live'
data-playback-style='full'
data-proactively-loads-video='true'
data-photo-src='${token.photoSrc}'
data-video-src='${token.videoSrc}'>
`
}
}
marked.use({extensions: [livePhoto]})