nextav/src/components/unified-video-player.tsx

207 lines
5.9 KiB
TypeScript

'use client';
import { useState, useEffect, useCallback } from 'react';
import { detectVideoFormat, VideoFile } from '@/lib/video-format-detector';
import ArtPlayerWrapper from '@/components/artplayer-wrapper';
interface UnifiedVideoPlayerProps {
video: VideoFile;
isOpen: boolean;
onClose: () => void;
playerType?: 'modal' | 'inline';
useArtPlayer?: boolean;
onProgress?: (time: number) => void;
onBookmark?: (videoId: number) => void;
onUnbookmark?: (videoId: number) => void;
onRate?: (videoId: number, rating: number) => void;
showBookmarks?: boolean;
showRatings?: boolean;
scrollPosition?: number;
formatFileSize?: (bytes: number) => string;
autoplay?: boolean;
}
export default function UnifiedVideoPlayer({
video,
isOpen,
onClose,
playerType = 'modal',
useArtPlayer: forceArtPlayer = true, // Always use ArtPlayer now
onProgress,
onBookmark,
onUnbookmark,
onRate,
showBookmarks = false,
showRatings = false,
scrollPosition,
formatFileSize,
autoplay = true
}: UnifiedVideoPlayerProps) {
const [format, setFormat] = useState<ReturnType<typeof detectVideoFormat> | null>(null);
const [isLoading, setIsLoading] = useState(true);
const [isBookmarked, setIsBookmarked] = useState(false);
const [bookmarkCheckLoading, setBookmarkCheckLoading] = useState(true);
const [currentRating, setCurrentRating] = useState(0);
const [ratingCheckLoading, setRatingCheckLoading] = useState(true);
// Check current bookmark status and rating when video opens
useEffect(() => {
if (isOpen && video.id) {
checkBookmarkStatus();
checkRatingStatus();
}
}, [isOpen, video.id]);
const checkBookmarkStatus = async () => {
if (!video.id) return;
setBookmarkCheckLoading(true);
try {
const response = await fetch(`/api/bookmarks/${video.id}`);
if (response.ok) {
const data = await response.json();
setIsBookmarked(data.isBookmarked || false);
} else {
setIsBookmarked(false);
}
} catch (error) {
console.error('Error checking bookmark status:', error);
setIsBookmarked(false);
} finally {
setBookmarkCheckLoading(false);
}
};
const checkRatingStatus = async () => {
if (!video.id) return;
setRatingCheckLoading(true);
try {
const response = await fetch(`/api/stars/${video.id}`);
if (response.ok) {
const data = await response.json();
setCurrentRating(data.rating || 0);
} else {
setCurrentRating(0);
}
} catch (error) {
console.error('Error checking rating status:', error);
setCurrentRating(0);
} finally {
setRatingCheckLoading(false);
}
};
// Detect format on mount
useEffect(() => {
if (video) {
const detectedFormat = detectVideoFormat(video);
setFormat(detectedFormat);
setIsLoading(false);
}
}, [video]);
// Handle ArtPlayer errors with recovery
const handleArtPlayerError = useCallback((error: string) => {
console.log('ArtPlayer encountered error:', error);
// Try to recover by using direct streaming if HLS failed
if (format?.type === 'hls') {
console.log('HLS failed, trying direct streaming fallback...');
const directFormat = {
...format,
type: 'direct' as const,
url: `/api/stream/direct/${video.id}`,
supportLevel: 'native' as const
};
setFormat(directFormat);
} else {
console.log('ArtPlayer error with direct streaming, logging only');
// Just log the error, no more fallbacks needed
}
}, [format, video.id]);
// Handle progress updates
const handleProgressUpdate = useCallback((time: number) => {
if (onProgress) {
onProgress(time);
}
}, [onProgress]);
// Handle bookmark toggle
const handleBookmarkToggle = useCallback(async (videoId: number) => {
if (onBookmark) {
await onBookmark(videoId);
setIsBookmarked(true); // Update local state
}
}, [onBookmark]);
// Handle unbookmark
const handleUnbookmark = useCallback(async (videoId: number) => {
if (onUnbookmark) {
await onUnbookmark(videoId);
setIsBookmarked(false); // Update local state
}
}, [onUnbookmark]);
// Handle rating
const handleRatingUpdate = useCallback(async (videoId: number, rating: number) => {
if (onRate) {
await onRate(videoId, rating);
setCurrentRating(rating); // Update local state
}
}, [onRate]);
// Always render ArtPlayer (no more fallbacks)
const renderPlayer = () => {
// Always use ArtPlayer for both modal and inline modes
return (
<ArtPlayerWrapper
video={video}
isOpen={isOpen}
onClose={onClose}
onProgress={handleProgressUpdate}
onBookmark={handleBookmarkToggle}
onUnbookmark={handleUnbookmark}
onRate={handleRatingUpdate}
onError={handleArtPlayerError}
useArtPlayer={true}
isBookmarked={isBookmarked}
bookmarkCount={video.bookmark_count || 0}
avgRating={currentRating}
showBookmarks={showBookmarks}
showRatings={showRatings}
autoplay={autoplay}
/>
);
};
if (isLoading || bookmarkCheckLoading || ratingCheckLoading) {
return (
<div className="fixed inset-0 bg-black/90 z-50 flex items-center justify-center">
<div className="text-white text-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-white mx-auto mb-4"></div>
<p>Loading ArtPlayer...</p>
</div>
</div>
);
}
return (
<div className="unified-video-player">
{/* ArtPlayer indicator (for debugging) */}
{process.env.NODE_ENV === 'development' && format && (
<div className="fixed top-4 left-4 z-50 bg-blue-500/20 text-blue-400 rounded-full px-3 py-1.5 text-xs">
ArtPlayer - {format.supportLevel}
</div>
)}
{renderPlayer()}
</div>
);
}
// Re-export for external use
export { detectVideoFormat };