Skip to content

Commit 888a039

Browse files
authored
feat: adjust live photo interaction (#97)
1 parent 7f07497 commit 888a039

11 files changed

Lines changed: 76 additions & 42 deletions

File tree

apps/web/src/components/ui/photo-viewer/LivePhotoBadge.tsx

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { AnimatePresence, m } from 'motion/react'
22
import type { FC } from 'react'
3-
import { useCallback, useRef } from 'react'
3+
import { useCallback } from 'react'
44
import { useTranslation } from 'react-i18next'
55

66
import { clsxm } from '~/lib/cn'
@@ -13,7 +13,6 @@ export const LivePhotoBadge: FC<LivePhotoBadgeProps> = ({
1313
isLivePhotoPlaying,
1414
}) => {
1515
const { t } = useTranslation()
16-
const hoverTimerRef = useRef<NodeJS.Timeout | null>(null)
1716

1817
const handlePlay = useCallback(async () => {
1918
if (!livePhotoRef.current?.getIsVideoLoaded() || isLivePhotoPlaying) return
@@ -25,32 +24,36 @@ export const LivePhotoBadge: FC<LivePhotoBadgeProps> = ({
2524
livePhotoRef.current?.stop()
2625
}, [livePhotoRef, isLivePhotoPlaying])
2726

28-
// Desktop hover logic
29-
const handleBadgeMouseEnter = useCallback(() => {
30-
if (isMobileDevice) return
31-
if (hoverTimerRef.current) clearTimeout(hoverTimerRef.current)
32-
hoverTimerRef.current = setTimeout(handlePlay, 200)
33-
}, [handlePlay])
27+
const handleClick = useCallback(() => {
28+
if (!livePhotoRef.current?.getIsVideoLoaded()) return
3429

35-
const handleBadgeMouseLeave = useCallback(() => {
36-
if (isMobileDevice) return
37-
if (hoverTimerRef.current) clearTimeout(hoverTimerRef.current)
38-
handleStop()
39-
}, [handleStop])
30+
if (isLivePhotoPlaying) {
31+
handleStop()
32+
} else {
33+
handlePlay()
34+
}
35+
}, [livePhotoRef, isLivePhotoPlaying, handlePlay, handleStop])
4036

4137
return (
4238
<>
4339
{/* Live Photo 标识 */}
4440
<div
4541
className={clsxm(
4642
'absolute z-20 flex items-center space-x-1 rounded-xl bg-black/50 px-1 py-1 text-xs text-white transition-all duration-200',
47-
!isMobileDevice && 'cursor-pointer hover:bg-black/70',
43+
'cursor-pointer hover:bg-black/70',
44+
isLivePhotoPlaying && 'bg-accent/70 hover:bg-accent/80',
4845
import.meta.env.DEV ? 'top-16 right-4' : 'top-12 lg:top-4 left-4',
4946
)}
50-
onMouseEnter={handleBadgeMouseEnter}
51-
onMouseLeave={handleBadgeMouseLeave}
47+
onClick={handleClick}
5248
>
53-
<i className="i-mingcute-live-photo-line size-4" />
49+
<i
50+
className={clsxm(
51+
'size-4',
52+
isLivePhotoPlaying
53+
? 'i-mingcute-live-photo-fill'
54+
: 'i-mingcute-live-photo-line',
55+
)}
56+
/>
5457
<span className="mr-1">{t('photo.live.badge')}</span>
5558
</div>
5659

@@ -64,7 +67,7 @@ export const LivePhotoBadge: FC<LivePhotoBadgeProps> = ({
6467
className="pointer-events-none absolute bottom-4 left-1/2 z-20 -translate-x-1/2"
6568
>
6669
<div className="flex items-center gap-2 rounded bg-black/50 px-2 py-1 text-xs text-white">
67-
<i className="i-mingcute-live-photo-line" />
70+
<i className="i-mingcute-live-photo-fill" />
6871
<span>{t('photo.live.playing')}</span>
6972
</div>
7073
</m.div>

apps/web/src/components/ui/photo-viewer/LivePhotoVideo.tsx

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ interface LivePhotoVideoProps {
2424
/** 自定义样式类名 */
2525
className?: string
2626
onPlayingChange?: (isPlaying: boolean) => void
27+
/** 是否自动播放一次 */
28+
shouldAutoPlayOnce?: boolean
2729
}
2830

2931
export interface LivePhotoVideoHandle {
@@ -40,12 +42,14 @@ export const LivePhotoVideo = ({
4042
isCurrentImage,
4143
className,
4244
onPlayingChange,
45+
shouldAutoPlayOnce = false,
4346
}: LivePhotoVideoProps & {
4447
ref?: React.RefObject<LivePhotoVideoHandle | null>
4548
}) => {
4649
const [isPlayingLivePhoto, setIsPlayingLivePhoto] = useState(false)
4750
const [livePhotoVideoLoaded, setLivePhotoVideoLoaded] = useState(false)
4851
const [isConvertingVideo, setIsConvertingVideo] = useState(false)
52+
const hasAutoPlayedRef = useRef(false)
4953

5054
const videoRef = useRef<HTMLVideoElement>(null)
5155
const videoAnimateController = useAnimationControls()
@@ -98,6 +102,7 @@ export const LivePhotoVideo = ({
98102
setIsPlayingLivePhoto(false)
99103
setLivePhotoVideoLoaded(false)
100104
setIsConvertingVideo(false)
105+
hasAutoPlayedRef.current = false
101106

102107
videoAnimateController.set({ opacity: 0 })
103108
}
@@ -138,6 +143,28 @@ export const LivePhotoVideo = ({
138143
setIsPlayingLivePhoto(false)
139144
}, [isPlayingLivePhoto, videoAnimateController])
140145

146+
// Auto-play effect - play once when video is loaded
147+
useEffect(() => {
148+
if (
149+
shouldAutoPlayOnce &&
150+
isCurrentImage &&
151+
livePhotoVideoLoaded &&
152+
!isPlayingLivePhoto &&
153+
!isConvertingVideo &&
154+
!hasAutoPlayedRef.current
155+
) {
156+
hasAutoPlayedRef.current = true
157+
play()
158+
}
159+
}, [
160+
shouldAutoPlayOnce,
161+
isCurrentImage,
162+
livePhotoVideoLoaded,
163+
isPlayingLivePhoto,
164+
isConvertingVideo,
165+
play,
166+
])
167+
141168
useImperativeHandle(ref, () => ({
142169
play,
143170
stop,

apps/web/src/components/ui/photo-viewer/PhotoViewer.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,7 @@ export const PhotoViewer = ({
314314
// Live Photo props
315315
isLivePhoto={photo.isLivePhoto}
316316
livePhotoVideoUrl={photo.livePhotoVideoUrl}
317+
shouldAutoPlayLivePhotoOnce={isCurrentImage}
317318
// HDR props
318319
isHDR={photo.isHDR}
319320
/>

apps/web/src/components/ui/photo-viewer/ProgressiveImage.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ export const ProgressiveImage = ({
4040
isCurrentImage = false,
4141
isLivePhoto = false,
4242
livePhotoVideoUrl,
43+
shouldAutoPlayLivePhotoOnce: shouldAutoPlayLivePhoto = false,
4344
isHDR = false,
4445
loadingIndicatorRef,
4546
}: ProgressiveImageProps) => {
@@ -159,6 +160,7 @@ export const ProgressiveImage = ({
159160
loadingIndicatorRef={loadingIndicatorRef}
160161
isCurrentImage={isCurrentImage}
161162
onPlayingChange={setState.setIsLivePhotoPlaying}
163+
shouldAutoPlayOnce={shouldAutoPlayLivePhoto}
162164
/>
163165
)}
164166
</DOMImageViewer>

apps/web/src/components/ui/photo-viewer/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export interface ProgressiveImageProps {
2828
// Live Photo 相关 props
2929
isLivePhoto?: boolean
3030
livePhotoVideoUrl?: string
31+
shouldAutoPlayLivePhotoOnce?: boolean
3132

3233
// HDR 相关 props
3334
isHDR?: boolean

locales/app/en.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -318,10 +318,10 @@
318318
"photo.live.converting.detail": "Converting video format using {{method}}...",
319319
"photo.live.converting.video": "Converting Live Photo video",
320320
"photo.live.playing": "Playing Live Photo",
321-
"photo.live.tooltip.desktop.main": "Hover to play Live Photo",
322-
"photo.live.tooltip.desktop.zoom": "Hover to play Live Photo / Double-click to zoom",
323-
"photo.live.tooltip.mobile.main": "Long press to play Live Photo",
324-
"photo.live.tooltip.mobile.zoom": "Long press to play Live Photo / Double-tap to zoom",
321+
"photo.live.tooltip.desktop.main": "Click the Live badge to play Live Photo",
322+
"photo.live.tooltip.desktop.zoom": "Click the Live badge to play Live Photo / Double-click to zoom",
323+
"photo.live.tooltip.mobile.main": "Tap the Live badge to play Live Photo",
324+
"photo.live.tooltip.mobile.zoom": "Tap the Live badge to play Live Photo / Double-tap to zoom",
325325
"photo.reaction.success": "Reaction added",
326326
"photo.share.actions": "Actions",
327327
"photo.share.copy.failed": "Copy failed",

locales/app/jp.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -314,10 +314,10 @@
314314
"photo.live.converting.detail": "{{method}}を使用してビデオ形式を変換しています...",
315315
"photo.live.converting.video": "Live Photo のビデオを変換中",
316316
"photo.live.playing": "ライブ写真を再生中",
317-
"photo.live.tooltip.desktop.main": "ホバーして Live Photo を再生",
318-
"photo.live.tooltip.desktop.zoom": "ホバーして Live Photo を再生 / ダブルクリックしてズーム",
319-
"photo.live.tooltip.mobile.main": "長押ししてライブフォトを再生",
320-
"photo.live.tooltip.mobile.zoom": "長押ししてライブフォトを再生/ダブルタップしてズーム",
317+
"photo.live.tooltip.desktop.main": "ライブバッジをクリックして Live Photo を再生",
318+
"photo.live.tooltip.desktop.zoom": "ライブバッジをクリックして Live Photo を再生 / ダブルクリックしてズーム",
319+
"photo.live.tooltip.mobile.main": "ライブバッジをタップしてライブフォトを再生",
320+
"photo.live.tooltip.mobile.zoom": "ライブバッジをタップしてライブフォトを再生/ダブルタップしてズーム",
321321
"photo.share.actions": "アクション",
322322
"photo.share.copy.failed": "コピーに失敗しました",
323323
"photo.share.copy.link": "リンクをコピー",

locales/app/ko.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -314,10 +314,10 @@
314314
"photo.live.converting.detail": "{{method}}를 사용하여 비디오 형식을 변환하는 중...",
315315
"photo.live.converting.video": "라이브 포토 비디오 변환 중",
316316
"photo.live.playing": "라이브 포토 재생 중",
317-
"photo.live.tooltip.desktop.main": "호버하여 라이브 포토 재생",
318-
"photo.live.tooltip.desktop.zoom": "호버하여 라이브 포토 재생 / 더블 클릭하여 확대",
319-
"photo.live.tooltip.mobile.main": "길게 눌러 라이브 포토 재생",
320-
"photo.live.tooltip.mobile.zoom": "길게 눌러 라이브 포토 재생/더블 탭하여 확대",
317+
"photo.live.tooltip.desktop.main": "라이브 배지를 클릭하여 라이브 포토 재생",
318+
"photo.live.tooltip.desktop.zoom": "라이브 배지를 클릭하여 라이브 포토 재생 / 더블 클릭하여 확대",
319+
"photo.live.tooltip.mobile.main": "라이브 배지를 탭하여 라이브 포토 재생",
320+
"photo.live.tooltip.mobile.zoom": "라이브 배지를 탭하여 라이브 포토 재생/더블 탭하여 확대",
321321
"photo.share.actions": "작업",
322322
"photo.share.copy.failed": "복사 실패",
323323
"photo.share.copy.link": "링크 복사",

locales/app/zh-CN.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -315,10 +315,10 @@
315315
"photo.live.converting.detail": "正在使用 {{method}} 转换视频格式...",
316316
"photo.live.converting.video": "正在转换实况照片视频",
317317
"photo.live.playing": "正在播放实况照片",
318-
"photo.live.tooltip.desktop.main": "悬停以播放实况照片",
319-
"photo.live.tooltip.desktop.zoom": "悬停播放实况照片 / 双击缩放",
320-
"photo.live.tooltip.mobile.main": "长按以播放实况照片",
321-
"photo.live.tooltip.mobile.zoom": "长按以播放实况照片/双击以缩放",
318+
"photo.live.tooltip.desktop.main": "点击实况标识播放实况照片",
319+
"photo.live.tooltip.desktop.zoom": "点击实况标识播放实况照片 / 双击缩放",
320+
"photo.live.tooltip.mobile.main": "轻点实况标识播放实况照片",
321+
"photo.live.tooltip.mobile.zoom": "轻点实况标识播放实况照片/双击以缩放",
322322
"photo.reaction.success": "点赞成功",
323323
"photo.share.actions": "操作",
324324
"photo.share.copy.failed": "复制失败",

locales/app/zh-HK.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -314,10 +314,10 @@
314314
"photo.live.converting.detail": "正在使用 {{method}} 轉換影片格式...",
315315
"photo.live.converting.video": "正在轉換原況照片影片",
316316
"photo.live.playing": "正在播放原況照片",
317-
"photo.live.tooltip.desktop.main": "懸停以播放原況照片",
318-
"photo.live.tooltip.desktop.zoom": "懸停播放原況照片 / 輕按兩下縮放",
319-
"photo.live.tooltip.mobile.main": "長按以播放原況照片",
320-
"photo.live.tooltip.mobile.zoom": "長按以播放原況照片/雙擊以縮放",
317+
"photo.live.tooltip.desktop.main": "點擊原況標識播放原況照片",
318+
"photo.live.tooltip.desktop.zoom": "點擊原況標識播放原況照片 / 輕按兩下縮放",
319+
"photo.live.tooltip.mobile.main": "輕點原況標識播放原況照片",
320+
"photo.live.tooltip.mobile.zoom": "輕點原況標識播放原況照片/雙擊以縮放",
321321
"photo.share.actions": "操作",
322322
"photo.share.copy.failed": "複製失敗",
323323
"photo.share.copy.link": "複製連結",

0 commit comments

Comments
 (0)