写一个 Live Photo 组件

写一个 Live Photo 组件

This article is also available in: English.

一个简单的 Live Photo 组件实现。

Github: https://github.com/LynanBreeze/live-photo

预览

我的 Live Photo

html代码
1
<iframe src="/static/live-photo/?picUrl=https://r2-assets.thelynan.com/u/A001_07251216_C317-FfGABM.jpg&videoUrl=https://r2-assets.thelynan.com/u/A001_07251216_C317-DQ6Nj6.mp4" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true" style="width: 100%; aspect-ratio: 16/9;"></iframe>

Apple 官方 Live Photo

html代码
1
<iframe src="/static/live-photo/?picUrl=/static/live-photo/test/live.jpg&videoUrl=/static/live-photo/test/live.mp4&useApple=1" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true" style="width: 100%; aspect-ratio: 16/9;"></iframe>

对比

我的 Apple
☑️ 更好的兼容性 在 iOS 微信中异常
☑️ 声音、静音可选 静音
☑️ 更丰富的播放器效果选项
点击触发 点击触发(桌面端), 长按触发(移动端)
个性化的图标 和相册里的一样

参数

参数名称 类型
photoSrc string, 必须
videoSrc string, 必须
loop boolean
muted boolean
useApple boolean

原理

Live Photo并不是一种神秘的文件格式,而是一张照片和一段视频的组合。

所以需要展示 Live Photo 只需添加一个切换逻辑即可。

Apple SDK

Apple 为开发者提供了官方的 JS SDK。请查看 https://developer.apple.com/documentation/livephotoskitjs

index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="https://cdn.apple-livephotoskit.com/lpk/1/livephotoskit.js"></script>
</head>
<body>
<div
data-live-photo
data-photo-src="https://..."
data-video-src="https://..."
style="width: 320px; height: 320px">
</div>
</body>
</html>

如果要在React中实现,很简单,如下:

index.jsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { useRef, useEffect } from "react";
import * as LivePhotosKit from "livephotoskit";

const LivePhotosKitReact = ({ className, photoSrc, videoSrc }) => {
const nodeRef = useRef(null);

useEffect(() => {
const player = LivePhotosKit.Player(nodeRef.current);
player.photoSrc = photoSrc;
player.videoSrc = videoSrc;
}, []);

return <div ref={nodeRef} className={className}></div>;
};

My Style

我们所需要的只是在 <img><video> 之间切换。

index.jsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
import { useRef, useState } from "react";

const LivePhoto = (props) => {
const { photoSrc, videoSrc, muted, loop, useApple } = props;
const [imageReady, setImageReady] = useState(false);
const [videoPlaying, setVideoPlaying] = useState(false);
const [videoRunning, setVideoRunning] = useState(false);
const [videoReady, setVideoReady] = useState(false);
const videoRef = useRef(null);

const playVideo = () => {
if (videoRunning) {
videoRef.current.pause();
} else {
setVideoPlaying(true);
videoRef.current.play();
}
};

const onImageLoad = () => {
setImageReady(true);
// 解决 onCanPlay and onLoadedMetadata 在 iOS 端微信内不会被触发的 Hack
if (
/iphone/i.test(navigator.userAgent) &&
/micromessenger/i.test(navigator.userAgent)
) {
setTimeout(() => {
setVideoReady(true);
}, 500);
}
};

return (
<div className='live-photo'>
{useApple ? (
<LivePhotosKitReact
className='live-img'
photoSrc={photoSrc}
videoSrc={videoSrc}
/>
) : (
<>
<div
className='live-trigger'
onClick={playVideo}
style={{ opacity: Number(videoReady) }}
>
<div
className='trigger-icon'
style={{
animationPlayState: videoRunning ? "running" : "paused",
}}
></div>
<span className='trigger-text'>LIVE</span>
</div>
<img
className='live-img'
src={photoSrc}
onLoad={onImageLoad}
style={{ opacity: Number(imageReady) }}
/>
<video
playsInline
webkit-playsinline
className='live-video'
loop={loop}
muted={muted}
ref={videoRef}
src={videoSrc}
style={{ opacity: Number(videoPlaying) }}
onCanPlay={() => setVideoReady(true)}
onLoadedMetadata={() => setVideoReady(true)}
onPlaying={() => setVideoRunning(true)}
onPause={() => setVideoRunning(false)}
onEnded={() => setVideoPlaying(false)}
></video>
</>
)}
</div>
);
};

开发 && 构建

开发

1
git clone [email protected]:LynanBreeze/live-photo.git
1
pnpm install && pnpm run dev

构建

1
pnpm build

写一个 Live Photo 组件

https://lynan.cn/live-photo/

作者

Lynan

发布于

2024-08-15

许可协议

评论