Skip to content

Commit

Permalink
Fixed bugs and added resume episode
Browse files Browse the repository at this point in the history
Added a feature to resume an episode from where you left off.

Fixed a bug with the media not loading from the continue watching section.
  • Loading branch information
synthofficial committed Sep 10, 2024
1 parent 507a7df commit 22aa775
Show file tree
Hide file tree
Showing 11 changed files with 167 additions and 63 deletions.
4 changes: 2 additions & 2 deletions release/app/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion release/app/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "streamflix",
"version": "1.3.2",
"version": "1.3.3",
"description": "A desktop app for streaming movies, tv series and anime.",
"license": "MIT",
"author": {
Expand Down
9 changes: 6 additions & 3 deletions src/components/TitleBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,29 +27,32 @@ const TitleBar: React.FC = () => {
bg={"dark.200"}
className="draggable overflow-hidden"
>
<Flex marginLeft={'auto'} gap={4}>
<Flex marginLeft={'auto'} gap={1}>
<IconButton
aria-label="Minimize"
borderRadius={0}
icon={<FaMinus />}
size="sm"
variant="ghost"
color={iconColor}
cursor={'pointer'}
_hover={{ bg: 'gray.700' }}
_hover={{ bg: 'dark.300' }}
onClick={handleMinimize}
/>
<IconButton
aria-label="Maximize"
borderRadius={0}
icon={<FaRegSquare />}
size="sm"
variant="ghost"
color={iconColor}
cursor={'pointer'}
_hover={{ bg: 'gray.700' }}
_hover={{ bg: 'dark.300' }}
onClick={handleMaximize}
/>
<IconButton
aria-label="Close"
borderRadius={0}
icon={<FaTimes />}
size="sm"
variant="ghost"
Expand Down
11 changes: 5 additions & 6 deletions src/components/VideoPlayer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { getEpisodeSource } from "../modules/api/Movies";
import discordRPCManager from "../constants/DiscordRPC";
import { useVideoPlayer } from "../renderer/contexts/VideoPlayerContext";
import { getAnimeSource } from "../modules/api/Anime";
import { getWatchingList, isInWatchingList, isInWatchlist, updateWatchingListMovieTime, updateWatchlistMovieTime } from "../modules/functions";
import { convertDurationToSeconds, getWatchingList, isInWatchingList, isInWatchlist, updateEpisodeWatchTime, updateWatchingListMovieTime, updateWatchlistMovieTime } from "../modules/functions";

const convertSecondsToTime = (seconds: number) => {
const hours = Math.floor(seconds / 3600);
Expand All @@ -30,8 +30,6 @@ const VideoPlayer: React.FC = () => {
setCurrentUrl
} = useVideoPlayer();

console.log(movieData, episodeData, episodeUrl);

const videoRef = useRef<HTMLVideoElement>(null);
const [hlsData, setHlsData] = useState<Hls>();
const [episodeDetails, setEpisodeDetails] = useState<any>(episodeData);
Expand Down Expand Up @@ -130,6 +128,7 @@ const VideoPlayer: React.FC = () => {

const onTimeUpdate = () => {
setCurrentTime(convertSecondsToTime(video.currentTime));

if(isInWatchingList(movieData?.id as string)){
updateWatchingListMovieTime(movieData?.id as string, video.currentTime);
}
Expand All @@ -156,9 +155,9 @@ const VideoPlayer: React.FC = () => {

const handlePlayerClose = () => {
setShowPlayer(false);
setCurrentMedia(null);
setCurrentEpisode(null);
setCurrentUrl(null);
if(movieData?.type === "Show"){
updateEpisodeWatchTime(movieData?.id as string, episodeData?.id, Math.floor(videoRef.current!.currentTime), convertDurationToSeconds(movieData?.duration) as number);
}
};

const handleFullscreen = () => {
Expand Down
4 changes: 2 additions & 2 deletions src/main/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,8 @@ const createWindow = async () => {

mainWindow = new BrowserWindow({
show: false,
width: 1024,
height: 728,
width: 1920,
height: 1080,
icon: getAssetPath('icon.png'),
autoHideMenuBar: true,
titleBarStyle: "hidden",
Expand Down
56 changes: 55 additions & 1 deletion src/modules/api/Anime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { fetchAnimeOrShowById } from "./Anilist";
import { ANIME_BASE_URL } from "../../constants/API";

const api = new Anilist();
const sources = new Gogoanime();

export const getAnimeSource = async (episodeId: string) => {
console.log("lets get source for episode: " + episodeId);
Expand All @@ -22,6 +21,61 @@ export const fetchEpisodeInfo = async (id: string) => {
return data;
}

export const searchAnime = async (query: string) => {
const response = await api.search(query, 1);

const data: Anime[] = await Promise.all(
response.results[0].map(async (anime : any) => {
// Fetch basic anime data
const animeData = await api.fetchAnimeInfo(anime.id);
console.log(animeData)
// Fetch detailed episode information
const anilistData = await fetchEpisodeInfo(anime.id);
// Extract the title object
const title: ITitle = animeData.title as ITitle;

// Combine episode data from both sources
const combinedEpisodes = animeData.episodes?.map((ep: any, index: number) => {
const anilistEp = anilistData.episodes[index + 1];
return {
id: ep.id,
absoluteEpisodeNumber: index,
number: ep.number,
title: anilistEp?.title?.en,
description: anilistEp?.overview,
image: anilistEp?.image || '',
releaseDate: anilistEp?.airDate || '',
};
}) || [];

// Return an object conforming to the Anime type
return {
title: {
english: title.english as string,
},
episodes: combinedEpisodes,
id: animeData.id as string,
production: animeData.studios![0] as string,
genres: animeData.genres as string[],
description: animeData.description?.replaceAll("<br>", "").replaceAll("</br>", "") as string,
duration: `${animeData.duration} min`,
totalEpisodes: animeData.totalEpisodes as number,
hasSub: animeData.hasSub as boolean,
hasDub: animeData.hasDub as boolean,
isAdult: animeData.isAdult as boolean,
season: animeData.season as string,
cover: animeData.cover as string,
trailer: animeData.trailer?.site as string,
releaseDate: `${animeData.startDate?.year}-${animeData.startDate?.month}-${animeData.startDate?.day}`,
endDate: `${animeData.endDate?.year}-${animeData.endDate?.month}-${animeData.endDate?.day}`,
thumbnail: animeData.image as string,
rating: animeData.rating as number,
status: animeData.status as string,
type: "Anime",
};
})
);
}

export const getTrendingAnime = async(): Promise<Anime[]> => {
// Fetch popular anime
Expand Down
41 changes: 24 additions & 17 deletions src/modules/api/Movies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import Goku from "@consumet/extensions/dist/providers/movies/goku";

import axios from "axios";
import { IMovieResult, ISearch } from "@consumet/extensions";
import { fetchAnimeOrShowById } from "./Anilist";
import { searchAnime } from "./Anime";

const api = new MovieHdWatch();
const flix = new FlixHQ();
Expand Down Expand Up @@ -109,23 +111,28 @@ export const searchMedia = async (query: string): Promise<SearchResult[]> => {
let episodes : any = [];

if(type == "Show"){
const tmdbData = await fetchTVSeriesDetails(data.title as string);
const epData = await fetchEpisodeDetails(tmdbData.id);

episodes = await Promise.all(
await epData.map(async(episode : any) => {
if(!episode.id) return;
const absoluteEpisodeId = data.episodes!.find((episodeData : any) => episodeData.number === episode.number && episodeData.season === episode.season);
return {
id: absoluteEpisodeId?.id,
title: episode.name,
number: episode.number,
season: episode.season,
thumbnail: episode.image,
description: episode.summary,
}
})
)
if(data.genres?.includes("Animation") && data.country == "Japan"){
const animeData = await searchAnime(result.title as string);
console.log(animeData);
}else{
const tmdbData = await fetchTVSeriesDetails(data.title as string);
const epData = await fetchEpisodeDetails(tmdbData.id);

episodes = await Promise.all(
await epData.map(async(episode : any) => {
if(!episode.id) return;
const absoluteEpisodeId = data.episodes!.find((episodeData : any) => episodeData.number === episode.number && episodeData.season === episode.season);
return {
id: absoluteEpisodeId?.id,
title: episode.name,
number: episode.number,
season: episode.season,
thumbnail: episode.image,
description: episode.summary,
}
})
)
}
}

return {
Expand Down
14 changes: 14 additions & 0 deletions src/modules/functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,20 @@ export const addToWatchingList = (item : WatchlistItem) => {
}
}

export const updateEpisodeWatchTime = (id : string, episodeId : string, time : number, finishTime : number) => {
const watchlist = getWatchingList();
const index = watchlist.findIndex(item => item.id === id);
if(index !== -1){
const episodeIndex : number = watchlist[index].episodes?.findIndex(ep => ep.id === episodeId)!;
if(episodeIndex !== -1){
console.log(time, finishTime);
watchlist[index].episodes![episodeIndex].timestamp = time;
watchlist[index].episodes![episodeIndex].completed = time >= finishTime ? true : false;
localStorage.setItem("watchinglist", JSON.stringify(watchlist));
}
}
}

export const updateWatchingListMovieTime = (id : string, time : number) => {
const watchlist = getWatchingList();
const index = watchlist.findIndex(item => item.id === id);
Expand Down
2 changes: 2 additions & 0 deletions src/modules/interfaces/WatchlistItem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ interface WatchlistItem {
description?: string;
thumbnail?: string;
url?: string;
completed? : boolean;
timestamp? : number;
}[];
rating : number;
production : string;
Expand Down
37 changes: 30 additions & 7 deletions src/renderer/pages/modals/MediaModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,11 @@ import {
MenuButton,
MenuList,
MenuItem,
Progress,
} from '@chakra-ui/react';
import { FaPlay, FaStar, FaClock, FaCalendar } from 'react-icons/fa';
import { IoIosArrowDown } from 'react-icons/io';
import { convertMinutesToHours, getWatchingList } from '../../../modules/functions';
import { convertDurationToSeconds, convertMinutesToHours, getWatchingList, getWatchlist } from '../../../modules/functions';
import { getEpisodeSource } from '../../../modules/api/Movies';
import { getAnimeSource } from '../../../modules/api/Anime';

Expand All @@ -47,6 +48,7 @@ const MediaModal: React.FC<MediaModalProps> = ({ isOpen, onClose, media, onPlayC
if (!media) return null;
const [selectedRange, setSelectedRange] = useState<number>(0);
const [selectedSeason, setSelectedSeason] = useState<number>(1);
const [watchList, setWatchlist] = useState<WatchlistItem[]>(getWatchlist());
const [episodes, setEpisodes] = useState<any[]>([]);
const toast = useToast();

Expand Down Expand Up @@ -151,9 +153,9 @@ const MediaModal: React.FC<MediaModalProps> = ({ isOpen, onClose, media, onPlayC
};

return (
<Modal isOpen={isOpen} onClose={onClose} size="6xl" motionPreset="slideInBottom">
<Modal isOpen={isOpen} onClose={onClose} size="7xl" motionPreset="slideInTop">
<ModalOverlay bg="blackAlpha.800" />
<ModalContent bg="transparent" overflow="hidden">
<ModalContent bg="transparent" overflow="hidden" padding={8}>
<ModalBody p={0}>
<Flex height="80vh" direction={isMovie ? 'column' : 'row'}>
<Box
Expand All @@ -165,7 +167,7 @@ const MediaModal: React.FC<MediaModalProps> = ({ isOpen, onClose, media, onPlayC
<Image
src={media.cover}
alt={isAnime ? media.title.english : media.title.toString()}
objectFit="cover"
objectFit="fill"
w="100%"
h="100%"
/>
Expand Down Expand Up @@ -290,8 +292,14 @@ const MediaModal: React.FC<MediaModalProps> = ({ isOpen, onClose, media, onPlayC
borderRadius: '24px',
},
}}>
{filteredEpisodes.map((episode, index) => (
<Flex
{filteredEpisodes.map((episode, index) => {
const watchedEpisode = watchingList.find((item) => item.id === media.id)?.episodes?.find((ep) => ep.id === episode.id);
const watchedProgress = watchedEpisode ? watchedEpisode.timestamp : 0;
const totalDuration = convertDurationToSeconds(episode.duration || media.duration);

console.log(`Episode ${episode.number} progress:`, watchedProgress, 'out of', totalDuration);
return(
<Flex
key={episode.id}
p={4}
borderBottom="1px solid"
Expand All @@ -309,6 +317,20 @@ const MediaModal: React.FC<MediaModalProps> = ({ isOpen, onClose, media, onPlayC
height="100%"
borderRadius="md"
/>
<Progress
position="absolute"
bottom={1}
left={0}
right={0}
colorScheme='brand'
m={1}
size={"sm"}
borderRadius={"10px"}
className='group-hover:opacity-0 transition-opacity duration-300'
bg={"dark.300"}
value={watchedProgress}
max={convertDurationToSeconds(media.duration) as number}
/>
<Flex
position="absolute"
top={0}
Expand Down Expand Up @@ -348,7 +370,8 @@ const MediaModal: React.FC<MediaModalProps> = ({ isOpen, onClose, media, onPlayC
</Text>
</Flex>
</Flex>
))}
)
})}
</Box>
</Flex>
</Box>
Expand Down
Loading

0 comments on commit 22aa775

Please sign in to comment.