mirror of
https://github.com/KuekHaoYang/KVideo.git
synced 2026-08-15 16:53:42 +08:00
feat: implement image optimization and extract mobile play/pause logic into a new hook.
This commit is contained in:
@@ -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]);
|
||||
}
|
||||
@@ -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)]"
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user