feat: implement image optimization and extract mobile play/pause logic into a new hook.

This commit is contained in:
kuekhaoyang
2025-11-24 16:59:13 +08:00
parent 66be634b53
commit 1384de946c
5 changed files with 83 additions and 21 deletions
+2 -1
View File
@@ -8,6 +8,7 @@ import Image from 'next/image';
import Link from 'next/link';
import { Card } from '@/components/ui/Card';
import { Icons } from '@/components/ui/Icon';
import { getOptimizedImageUrl } from '@/lib/utils/image-utils';
interface DoubanMovie {
id: string;
@@ -39,7 +40,7 @@ export const MovieCard = memo(function MovieCard({ movie, onMovieClick }: MovieC
<Card hover className="overflow-hidden p-0 h-full" blur={false}>
<div className="relative aspect-[2/3] overflow-hidden bg-[var(--glass-bg)] rounded-[var(--radius-2xl)]">
<Image
src={movie.cover}
src={getOptimizedImageUrl(movie.cover, 400)}
alt={movie.title}
fill
className="object-cover transition-transform duration-300 group-hover:scale-105 rounded-[var(--radius-2xl)]"
@@ -1,6 +1,7 @@
import { useCallback, useEffect } from 'react';
import { formatTime } from '@/lib/utils/format-utils';
import { usePlaybackPolling } from '../usePlaybackPolling';
import { useMobileTogglePlay } from './useMobileTogglePlay';
interface UseMobilePlaybackProps {
videoRef: React.RefObject<HTMLVideoElement>;
@@ -39,25 +40,14 @@ export function useMobilePlaybackControls({
isDraggingProgressRef,
isTogglingRef
}: UseMobilePlaybackProps) {
const togglePlay = useCallback(async () => {
if (!videoRef.current || isTogglingRef.current) return;
isTogglingRef.current = true;
try {
if (isPlaying) {
videoRef.current.pause();
} else {
setShowMoreMenu(false);
setShowVolumeMenu(false);
setShowSpeedMenu(false);
await videoRef.current.play();
}
} catch (error) {
console.warn('Play/pause error:', error);
} finally {
isTogglingRef.current = false;
}
}, [isPlaying, videoRef, isTogglingRef, setShowMoreMenu, setShowVolumeMenu, setShowSpeedMenu]);
const togglePlay = useMobileTogglePlay({
videoRef,
isPlaying,
isTogglingRef,
setShowMoreMenu,
setShowVolumeMenu,
setShowSpeedMenu
});
const handlePlay = useCallback(() => setIsPlaying(true), [setIsPlaying]);
const handlePause = useCallback(() => setIsPlaying(false), [setIsPlaying]);
@@ -0,0 +1,39 @@
import { useCallback } from 'react';
interface UseMobileTogglePlayProps {
videoRef: React.RefObject<HTMLVideoElement>;
isPlaying: boolean;
isTogglingRef: React.MutableRefObject<boolean>;
setShowMoreMenu: (show: boolean) => void;
setShowVolumeMenu: (show: boolean) => void;
setShowSpeedMenu: (show: boolean) => void;
}
export function useMobileTogglePlay({
videoRef,
isPlaying,
isTogglingRef,
setShowMoreMenu,
setShowVolumeMenu,
setShowSpeedMenu
}: UseMobileTogglePlayProps) {
return useCallback(async () => {
if (!videoRef.current || isTogglingRef.current) return;
isTogglingRef.current = true;
try {
if (isPlaying) {
videoRef.current.pause();
} else {
setShowMoreMenu(false);
setShowVolumeMenu(false);
setShowSpeedMenu(false);
await videoRef.current.play();
}
} catch (error) {
console.warn('Play/pause error:', error);
} finally {
isTogglingRef.current = false;
}
}, [isPlaying, videoRef, isTogglingRef, setShowMoreMenu, setShowVolumeMenu, setShowSpeedMenu]);
}
+2 -1
View File
@@ -7,6 +7,7 @@ import { Card } from '@/components/ui/Card';
import { Badge } from '@/components/ui/Badge';
import { Icons } from '@/components/ui/Icon';
import { LatencyBadge } from '@/components/ui/LatencyBadge';
import { getOptimizedImageUrl } from '@/lib/utils/image-utils';
import { Video } from '@/lib/types';
@@ -54,7 +55,7 @@ export const VideoCard = memo<VideoCardProps>(({
<div className="relative aspect-[2/3] bg-[color-mix(in_srgb,var(--glass-bg)_50%,transparent)] overflow-hidden rounded-[var(--radius-2xl)]">
{video.vod_pic ? (
<Image
src={video.vod_pic}
src={getOptimizedImageUrl(video.vod_pic, 400)}
alt={video.vod_name}
fill
className="object-cover rounded-[var(--radius-2xl)]"
+31
View File
@@ -0,0 +1,31 @@
/**
* Utility to optimize images using images.weserv.nl
* This service caches images and converts them to WebP, improving load times
* and bypassing some referrer-based blocks.
*/
export function getOptimizedImageUrl(url: string, width?: number, quality: number = 80): string {
if (!url) return '';
// Skip optimization for local images or already optimized images
if (url.startsWith('/') || url.includes('weserv.nl')) {
return url;
}
// Remove protocol for weserv usage
const cleanUrl = url.replace(/^https?:\/\//, '');
// Construct weserv URL
// url: source url (without protocol)
// w: width
// q: quality
// output: webp (for better compression)
// l: load (lazy load, though handled by next/image)
// n: no-redirect (returns error image instead of redirecting)
const baseUrl = `https://images.weserv.nl/?url=${encodeURIComponent(cleanUrl)}&output=webp&q=${quality}&n=-1`;
if (width) {
return `${baseUrl}&w=${width}`;
}
return baseUrl;
}