The Making of 'Live Photo' Component

The Making of 'Live Photo' Component

本文同时提供阅读于:简体中文

A simple live-photo component.

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

Preview

My Style

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 SDK

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>

Comparison

My Style Apple SDK
☑️ Better Compatibility
☑️ Audio, Muted Optional Muted
☑️ More Player Properties
Trigger by Click Trigger by Hover(Desktop), Long Press(Mobile)
Personalized Icon Look Exactly Same as Photos

Options

Key Type
photoSrc string, required
videoSrc string, required
loop boolean
muted boolean
useApple boolean

The Making

At first, we should be aware that a Live Photo is not a mysterious file format but a combination of 1 photo and 1 video.

Apple SDK

Apple has an official JS SDK for developers. Check 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>

If you want to implement it in React, it’s simple as follows:

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

All we need is switch between a <img> and a <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 won't be fired in Wechat(iOS) environment
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>
);
};

Develop && Build

Develop

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

Build

1
pnpm build

The Making of 'Live Photo' Component

https://thelynan.com/live-photo/

Author

Lynan

Posted on

2024-08-15

Licensed under

Comments