ChandimaPrabath commited on
Commit
7d1bb2e
·
1 Parent(s): 36185fe

Rating Overlay fix

Browse files
frontend/TODO.md CHANGED
@@ -5,8 +5,7 @@ Project Description
5
  <em>[TODO.md spec & Kanban Board](https://bit.ly/3fCwKfM)</em>
6
 
7
  ### Todo
8
- [] reset link fetcher's state after cancelling and closing
9
- [] create searching page
10
 
11
  ### In Progress
12
 
 
5
  <em>[TODO.md spec & Kanban Board](https://bit.ly/3fCwKfM)</em>
6
 
7
  ### Todo
8
+ [] movie player progressbar hover time is inaccurate.
 
9
 
10
  ### In Progress
11
 
frontend/app/watch/movie/[title]/page.tsx CHANGED
@@ -3,19 +3,21 @@
3
  import { useRouter } from 'next/navigation';
4
  import MoviePlayer from '@/components/movie/MoviePlayer';
5
  import { useParams } from 'next/navigation';
6
- import { useEffect, useState } from 'react';
7
  import { getMovieMetadata } from '@/lib/lb';
8
 
9
  interface Movie {
10
  name: string;
11
  image: string;
12
- translations?: any;
 
 
13
  year?: number;
14
- genres?: any[];
15
  score?: number;
16
- contentRatings?: any[];
17
  characters?: any;
18
- artworks?: any[];
19
  }
20
 
21
  interface ContentRating {
@@ -24,8 +26,12 @@ interface ContentRating {
24
  description: string;
25
  }
26
 
 
 
 
 
 
27
  const MoviePlayerPage = () => {
28
- // Assuming you get the movie title from the route params
29
  const router = useRouter();
30
  const { title } = useParams();
31
  const videoTitle = Array.isArray(title) ? title[0] : title;
@@ -37,55 +43,72 @@ const MoviePlayerPage = () => {
37
  const [movie, setMovie] = useState<Movie | null>(null);
38
 
39
  useEffect(() => {
40
- async function fetchMovie() {
41
  try {
42
  const data = await getMovieMetadata(decodeURIComponent(videoTitle));
43
  setMovie(data.data as Movie);
44
- console.log(data.data?.contentRatings);
45
  } catch (error) {
46
  console.error('Failed to fetch movie details', error);
47
  }
 
 
 
 
48
  }
49
- fetchMovie();
50
  }, [videoTitle]);
51
 
52
- function formatContentRatings(contentRatings: any[]): ContentRating[] | undefined {
53
- if (!contentRatings || contentRatings.length === 0) return undefined;
54
- return contentRatings.map(({ country, name, description }) => ({ country, name, description }));
55
- }
56
-
57
- const formattedContentRating = movie?.contentRatings ? formatContentRatings(movie.contentRatings) : undefined;
58
-
59
- console.log(formattedContentRating);
60
-
61
- // Select a random background image (type 15) for the thumbnail
62
- const getRandomThumbnail = (artworks?: any[]): string => {
63
- if (!artworks) return '';
64
- const backgrounds = artworks.filter((artwork) => artwork.type === 15); // Filter for type 15 (backgrounds)
65
- if (backgrounds.length === 0) return ''; // No backgrounds found, return default empty string
66
- const randomBackground = backgrounds[Math.floor(Math.random() * backgrounds.length)]; // Randomly select one
67
- return randomBackground.thumbnail || ''; // Return the thumbnail (or fallback if undefined)
68
- };
69
 
70
- const randomThumbnail = getRandomThumbnail(movie?.artworks);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71
 
72
- if (!title) {
73
- handleClose();
74
  }
75
 
76
  return (
77
- <>
78
- {movie && (
79
- <MoviePlayer
80
- videoTitle={decodeURIComponent(videoTitle) as string}
81
- movieTitle={movie.name}
82
- thumbnail={randomThumbnail || movie.image}
83
- poster={movie.image}
84
- contentRatings={formattedContentRating}
85
- onClosePlayer={handleClose}
86
- />
87
- )}
88
- </>
89
  );
90
  };
91
 
 
3
  import { useRouter } from 'next/navigation';
4
  import MoviePlayer from '@/components/movie/MoviePlayer';
5
  import { useParams } from 'next/navigation';
6
+ import { useEffect, useState, useMemo } from 'react';
7
  import { getMovieMetadata } from '@/lib/lb';
8
 
9
  interface Movie {
10
  name: string;
11
  image: string;
12
+ translations?: {
13
+ nameTranslations: { language: string; name: string; isAlias?: boolean }[];
14
+ };
15
  year?: number;
16
+ genres?: string[];
17
  score?: number;
18
+ contentRatings?: ContentRating[];
19
  characters?: any;
20
+ artworks?: Artwork[];
21
  }
22
 
23
  interface ContentRating {
 
26
  description: string;
27
  }
28
 
29
+ interface Artwork {
30
+ type: number;
31
+ thumbnail: string;
32
+ }
33
+
34
  const MoviePlayerPage = () => {
 
35
  const router = useRouter();
36
  const { title } = useParams();
37
  const videoTitle = Array.isArray(title) ? title[0] : title;
 
43
  const [movie, setMovie] = useState<Movie | null>(null);
44
 
45
  useEffect(() => {
46
+ const fetchMovie = async () => {
47
  try {
48
  const data = await getMovieMetadata(decodeURIComponent(videoTitle));
49
  setMovie(data.data as Movie);
 
50
  } catch (error) {
51
  console.error('Failed to fetch movie details', error);
52
  }
53
+ };
54
+
55
+ if (videoTitle) {
56
+ fetchMovie();
57
  }
 
58
  }, [videoTitle]);
59
 
60
+ const formatContentRatings = useMemo(() => {
61
+ return (contentRatings: ContentRating[] = []) => {
62
+ if (contentRatings.length === 0) return undefined;
63
+ return contentRatings.map(({ country, name, description }) => ({ country, name, description }));
64
+ };
65
+ }, []);
 
 
 
 
 
 
 
 
 
 
 
66
 
67
+ const formattedContentRating = useMemo(() => {
68
+ return movie?.contentRatings ? formatContentRatings(movie.contentRatings) : undefined;
69
+ }, [movie, formatContentRatings]);
70
+
71
+ const getRandomThumbnail = useMemo(() => {
72
+ return (artworks?: Artwork[]): string => {
73
+ if (!artworks) return '';
74
+ const backgrounds = artworks.filter((artwork) => artwork.type === 15);
75
+ if (backgrounds.length === 0) return '';
76
+ const randomBackground = backgrounds[Math.floor(Math.random() * backgrounds.length)];
77
+ return randomBackground.thumbnail || '';
78
+ };
79
+ }, []);
80
+
81
+ const randomThumbnail = useMemo(() => getRandomThumbnail(movie?.artworks), [getRandomThumbnail, movie]);
82
+
83
+ // Fallback for movie title if undefined
84
+ const movieTitle = useMemo(() => {
85
+ return (
86
+ movie?.translations?.nameTranslations?.find((t) => t.language === 'eng')?.name ||
87
+ movie?.translations?.nameTranslations?.find((t) => t.isAlias && t.language === 'eng')?.name ||
88
+ movie?.name ||
89
+ 'Unknown Title'
90
+ );
91
+ }, [movie]);
92
+
93
+ useEffect(() => {
94
+ if (!title) {
95
+ handleClose();
96
+ }
97
+ }, [title]);
98
 
99
+ if (!movie) {
100
+ return <div>Loading...</div>;
101
  }
102
 
103
  return (
104
+ <MoviePlayer
105
+ videoTitle={decodeURIComponent(videoTitle)}
106
+ movieTitle={movieTitle}
107
+ thumbnail={randomThumbnail || movie.image}
108
+ poster={movie.image}
109
+ contentRatings={formattedContentRating}
110
+ onClosePlayer={handleClose}
111
+ />
 
 
 
 
112
  );
113
  };
114
 
frontend/components/movie/MoviePlayer.tsx CHANGED
@@ -9,7 +9,7 @@ import {
9
  ForwardIcon,
10
  BackwardIcon,
11
  } from '@heroicons/react/24/solid';
12
- import { XCircleIcon, FilmIcon } from '@heroicons/react/24/outline';
13
  import { getMovieLinkByTitle } from '@/lib/lb';
14
  import { motion, AnimatePresence } from 'framer-motion';
15
 
@@ -507,7 +507,7 @@ const MoviePlayer: React.FC<MoviePlayerProps> = ({
507
  setRatingShown(true);
508
  setTimeout(() => {
509
  setShowRatingOverlay(true);
510
- setTimeout(() => setShowRatingOverlay(false), 5000);
511
  }, 800);
512
  }
513
  };
@@ -671,25 +671,14 @@ const MoviePlayer: React.FC<MoviePlayerProps> = ({
671
  >
672
  <source src={videoBlobUrl || videoUrl} type="video/webm" />
673
  </video>
674
-
675
- {/* Controls Overlay */}
676
- <div
677
- className={`absolute inset-0 flex flex-col justify-between transition-opacity duration-300 z-[1000] ${showControls || localSpinnerLoading ? 'opacity-100 pointer-events-auto' : 'opacity-0 pointer-events-none'}`}
678
- >
679
- {/* Top Controls */}
680
- <div className="flex items-center justify-between p-6 bg-gradient-to-b from-black/90 via-black/60 to-transparent">
681
- <div className="flex flex-col">
682
- <h1 className="text-white text-3xl font-extrabold bg-clip-text text-transparent bg-gradient-to-r from-white to-gray-300">
683
- {movieTitle || 'Video Player'}
684
- </h1>
685
- <AnimatePresence>
686
  {showRatingOverlay && contentRatings.length > 0 && (
687
  <motion.div
688
  initial={{ opacity: 0, y: 20 }}
689
  animate={{ opacity: 1, y: 0 }}
690
  exit={{ opacity: 0, y: -20 }}
691
  transition={{ duration: 0.5 }}
692
- className="mt-2 inline-flex flex-col items-start bg-black/60 backdrop-blur-sm text-white text-sm px-3 py-1 rounded-md border border-gray-700/50"
693
  >
694
  {/* Rating Name */}
695
  <motion.div className="flex items-center text-xl">
@@ -712,6 +701,17 @@ const MoviePlayer: React.FC<MoviePlayerProps> = ({
712
  </motion.div>
713
  )}
714
  </AnimatePresence>
 
 
 
 
 
 
 
 
 
 
 
715
  </div>
716
 
717
  {onClosePlayer && (
@@ -719,7 +719,7 @@ const MoviePlayer: React.FC<MoviePlayerProps> = ({
719
  onClick={handleClose}
720
  className="text-white hover:text-red-400 transition-colors p-2 rounded-full hover:bg-black/30"
721
  >
722
- <XCircleIcon className="size-10" />
723
  </button>
724
  )}
725
  </div>
 
9
  ForwardIcon,
10
  BackwardIcon,
11
  } from '@heroicons/react/24/solid';
12
+ import { XCircleIcon, FilmIcon, ArrowLeftStartOnRectangleIcon } from '@heroicons/react/24/outline';
13
  import { getMovieLinkByTitle } from '@/lib/lb';
14
  import { motion, AnimatePresence } from 'framer-motion';
15
 
 
507
  setRatingShown(true);
508
  setTimeout(() => {
509
  setShowRatingOverlay(true);
510
+ setTimeout(() => setShowRatingOverlay(false), 8000);
511
  }, 800);
512
  }
513
  };
 
671
  >
672
  <source src={videoBlobUrl || videoUrl} type="video/webm" />
673
  </video>
674
+ <AnimatePresence>
 
 
 
 
 
 
 
 
 
 
 
675
  {showRatingOverlay && contentRatings.length > 0 && (
676
  <motion.div
677
  initial={{ opacity: 0, y: 20 }}
678
  animate={{ opacity: 1, y: 0 }}
679
  exit={{ opacity: 0, y: -20 }}
680
  transition={{ duration: 0.5 }}
681
+ className="fixed top-[5rem] ml-4 inline-flex flex-col items-start bg-black/60 backdrop-blur-sm text-white text-sm px-3 py-1 rounded-md border border-gray-700/50"
682
  >
683
  {/* Rating Name */}
684
  <motion.div className="flex items-center text-xl">
 
701
  </motion.div>
702
  )}
703
  </AnimatePresence>
704
+ {/* Controls Overlay */}
705
+ <div
706
+ className={`absolute inset-0 flex flex-col justify-between transition-opacity duration-300 z-[1000] ${showControls || localSpinnerLoading ? 'opacity-100 pointer-events-auto' : 'opacity-0 pointer-events-none'}`}
707
+ >
708
+ {/* Top Controls */}
709
+ <div className="flex items-center justify-between p-6 bg-gradient-to-b from-black/90 via-black/60 to-transparent">
710
+ <div className="flex flex-col">
711
+ <h1 className="text-white text-3xl font-extrabold bg-clip-text text-transparent bg-gradient-to-r from-white to-gray-300">
712
+ {movieTitle || 'Video Player'}
713
+ </h1>
714
+
715
  </div>
716
 
717
  {onClosePlayer && (
 
719
  onClick={handleClose}
720
  className="text-white hover:text-red-400 transition-colors p-2 rounded-full hover:bg-black/30"
721
  >
722
+ <ArrowLeftStartOnRectangleIcon className="size-10" />
723
  </button>
724
  )}
725
  </div>
frontend/lib/config.ts CHANGED
@@ -1,2 +1,2 @@
1
- export const WEB_VERSION = 'v0.2.2 beta';
2
  export const SEARCH_API_URL = 'https://hans-den-search.hf.space';
 
1
+ export const WEB_VERSION = 'v0.2.3 beta';
2
  export const SEARCH_API_URL = 'https://hans-den-search.hf.space';