Skip to content

Commit

Permalink
Update 1.4.0
Browse files Browse the repository at this point in the history
  • Loading branch information
synthofficial committed Sep 11, 2024
1 parent 22aa775 commit 3531057
Show file tree
Hide file tree
Showing 9 changed files with 253 additions and 16 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
</a>
</div>

**⚠️ 1.4.0 IS THE FINAL UPDATE FOR A WHILE. THE APP IS BEING REWORKED! ⚠️**

I have decided to completely re-work this project, mainly because it is hard to add new features with all the useless code in this solution. I don't feel like going through each file and removing the useless code, and this gives me a chance to make a new UI that feels fresh 😃

## About the project
I got tired of trying to watch stuff and constantly met with ads, so I made this program to start my journey in various tools.

Expand Down
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.3",
"version": "1.4.0",
"description": "A desktop app for streaming movies, tv series and anime.",
"license": "MIT",
"author": {
Expand Down
126 changes: 121 additions & 5 deletions src/components/VideoPlayer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { IoIosArrowBack } from "react-icons/io";
import { MdFullscreen, MdFullscreenExit } from "react-icons/md";
import { FaPlay, FaPause, FaVolumeUp, FaVolumeMute, FaVolumeDown } from "react-icons/fa";
import { TbRewindBackward10, TbRewindForward10, TbPlayerTrackNextFilled } from "react-icons/tb";
import { getEpisodeSource } from "../modules/api/Movies";
import { getEpisodeSource, getEpisodeSubtitles } from "../modules/api/Movies";
import discordRPCManager from "../constants/DiscordRPC";
import { useVideoPlayer } from "../renderer/contexts/VideoPlayerContext";
import { getAnimeSource } from "../modules/api/Anime";
Expand All @@ -18,6 +18,12 @@ const convertSecondsToTime = (seconds: number) => {
return `${hours > 0 ? `${hours}:` : ''}${String(minutes).padStart(2, '0')}:${String(secs).padStart(2, '0')}`;
};

interface Cue {
start: number;
end: number;
text: string;
}

const VideoPlayer: React.FC = () => {
const {
showPlayer,
Expand All @@ -37,15 +43,20 @@ const VideoPlayer: React.FC = () => {
const [duration, setDuration] = useState<string>("00:00");
const [playing, setPlaying] = useState<boolean>(false);
const [showControls, setShowControls] = useState<boolean>(false);
const [fullscreen, setFullscreen] = useState<boolean>(false);
const [fullscreen] = useState<boolean>(false);
const [showVolume, setShowVolume] = useState<boolean>(false);
const [volume, setVolume] = useState<number>(50);
const [hideTimeout, setHideTimeout] = useState<NodeJS.Timeout | null>(null);
const [subtitles, setSubtitles] = useState<Cue[]>([]);
const [currentSubtitle, setCurrentSubtitle] = useState<string>('');
const [subtitleUrl, setSubtitleUrl] = useState<string | null>(null);

const watchingList = getWatchingList();

const handleControlsVisibility = useCallback(() => {
setShowControls(true);
if (!showControls) {
setShowControls(true);
}

if (hideTimeout) {
clearTimeout(hideTimeout);
Expand All @@ -58,6 +69,95 @@ const VideoPlayer: React.FC = () => {
setHideTimeout(newTimeout);
}, [hideTimeout]);

useEffect(() => {
const fetchSubtitles = async () => {
if (episodeData && movieData) {
try {
const subtitleData = await getEpisodeSubtitles(episodeData.id, movieData.id as string);
console.log("Subtitle data:", subtitleData);
if (subtitleData && subtitleData.file) {
setSubtitleUrl(subtitleData.file);
console.log("Subtitles URL:", subtitleData.file);
} else {
console.log("No subtitle URL available");
}
} catch (error) {
console.error("Error fetching subtitle URL:", error);
}
}else if(movieData){
if(movieData.type == "Movie" && movieData.subtitles.file){
setSubtitleUrl(movieData.subtitles.file);
console.log("Subtitles URL:", movieData.subtitles.file);
}
}
};
fetchSubtitles();
}, [episodeData, movieData]);
const parseWebVTT = (vttText: string): Cue[] => {
const lines = vttText.trim().split('\n');
const cues: Cue[] = [];
let currentCue: Partial<Cue> | null = null;

for (let i = 0; i < lines.length; i++) {
const line = lines[i].trim();
if (line.includes('-->')) {
const [start, end] = line.split('-->').map(timeString => {
const [minutes, seconds] = timeString.trim().split(':');
return parseInt(minutes) * 60 + parseFloat(seconds);
});
currentCue = { start, end, text: '' };
} else if (currentCue) {
if (line !== '') {
currentCue.text += (currentCue.text ? '\n' : '') + line;
} else if (currentCue.text) {
cues.push(currentCue as Cue);
currentCue = null;
}
}
}
if (currentCue && currentCue.text) {
cues.push(currentCue as Cue);
}
return cues;
};

useEffect(() => {
const loadSubtitles = async () => {
if (subtitleUrl) {
try {
const response = await fetch(subtitleUrl);
const text = await response.text();
const parsedCues = parseWebVTT(text);
console.log("Parsed subtitles:", parsedCues);
setSubtitles(parsedCues);
} catch (error) {
console.error("Error loading subtitles:", error);
}
}
};

loadSubtitles();
}, [subtitleUrl]);

useEffect(() => {
const video = videoRef.current;
if (!video || subtitles.length === 0) return;

const updateSubtitle = () => {
const currentTime = video.currentTime;
const currentCue = subtitles.find(
cue => currentTime >= cue.start && currentTime <= cue.end
);
setCurrentSubtitle(currentCue ? currentCue.text : '');
};

video.addEventListener('timeupdate', updateSubtitle);

return () => {
video.removeEventListener('timeupdate', updateSubtitle);
};
}, [subtitles]);

useEffect(() => {
const handleMouseMove = () => {
handleControlsVisibility();
Expand Down Expand Up @@ -99,7 +199,7 @@ const VideoPlayer: React.FC = () => {
}
});

if(watchingList.find((m) => m.id == movieData?.id)){
if(watchingList.find((m) => m.id == movieData?.id) && movieData?.type === "Movie"){
videoRef.current.currentTime = getWatchingList().find((m) => m.id == movieData?.id)?.timestamp || 0;
}

Expand Down Expand Up @@ -228,7 +328,23 @@ const VideoPlayer: React.FC = () => {
onMouseLeave={() => setShowControls(false)}
>
<video ref={videoRef} className="w-full h-full object-cover" />

<Box
position="absolute"
bottom="10%"
left="50%"
transform="translateX(-50%)"
textAlign="center"
color="white"
fontSize="xl"
fontWeight="bold"
textShadow="0 0 4px black"
padding="0.5rem"
backgroundColor="rgba(0, 0, 0, 0.5)"
borderRadius="md"
maxWidth="80%"
>
{currentSubtitle.replaceAll(/(<([^>]+)>)/ig, "")}
</Box>
<Box
position="absolute"
top="0"
Expand Down
2 changes: 1 addition & 1 deletion src/main/menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export default class MenuBuilder {

buildDarwinTemplate(): MenuItemConstructorOptions[] {
const subMenuAbout: DarwinMenuItemConstructorOptions = {
label: 'Electron',
label: 'Streamflix',
submenu: [
{
label: 'About ElectronReact',
Expand Down
64 changes: 62 additions & 2 deletions src/modules/api/Movies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ import { fetchAnimeOrShowById } from "./Anilist";
import { searchAnime } from "./Anime";

const api = new MovieHdWatch();
const flix = new FlixHQ();
const fmovies = new Goku();

interface FileData {
file: string;
Expand Down Expand Up @@ -39,6 +37,7 @@ function getFileFromJson(jsonString: string): string | null {
export const getMovieSource = async (episodeId: string, movieId: string) => {
try {
const servers = await api.fetchEpisodeServers(episodeId, movieId);
console.log(servers);

if (servers.length > 0) {
const url = servers.find((server) => server.url.includes("rabbitstream"))?.url;
Expand All @@ -64,6 +63,35 @@ export const getMovieSource = async (episodeId: string, movieId: string) => {
}
}

export const getMovieSubtitles = async (episodeId : string, movieId: string) => {
try {
const servers = await api.fetchEpisodeServers(episodeId, movieId);
console.log(servers);

if (servers.length > 0) {
const url = servers.find((server) => server.url.includes("rabbitstream"))?.url;
if (!url) {
console.log("No rabbitstream URL found");
return null;
}
const regex = /embed-4\/([a-zA-Z0-9_-]+)/;
const match = url.match(regex);
if (!match) {
console.log("No match found in URL");
return null;
}
const source = await axios.get(`https://api.fffapifree.cam/get-source?id=${match[1]}`);
console.log(source.data.data.tracks)
const data = source.data.data.tracks.find((label : any) => label.label.includes("English"));
return data
}
return null;
} catch (error) {
console.error("Error in getMovieSource:", error);
return null;
}
}

export const getEpisodeSource = async(episodeId : string, movieId : string) => {
console.log(episodeId, movieId)

Expand All @@ -78,6 +106,7 @@ export const getEpisodeSource = async(episodeId : string, movieId : string) => {
}
const regex = /embed-4\/([a-zA-Z0-9_-]+)/;
const match = url.match(regex);
console.log(match![1])
if (!match) {
console.log("No match found in URL");
return null;
Expand All @@ -91,7 +120,35 @@ export const getEpisodeSource = async(episodeId : string, movieId : string) => {
console.log(e)
return null;
}
}

export const getEpisodeSubtitles = async(episodeId : string, movieId : string) => {
try{
const servers = await api.fetchEpisodeServers(episodeId, movieId);

if (servers.length > 0) {
const url = servers.find((server) => server.url.includes("rabbitstream"))?.url;
if (!url) {
console.log("No rabbitstream URL found");
return null;
}
const regex = /embed-4\/([a-zA-Z0-9_-]+)/;
const match = url.match(regex);
console.log(match![1])
if (!match) {
console.log("No match found in URL");
return null;
}
const source = await axios.get(`https://api.fffapifree.cam/get-source?id=${match[1]}`);
console.log(source.data.data.tracks)
const data = source.data.data.tracks.find((label : any) => label.label.includes("English"));
return data
}
return null;
}catch(e){
console.log(e)
return null;
}
}

export const searchMedia = async (query: string): Promise<SearchResult[]> => {
Expand Down Expand Up @@ -179,6 +236,7 @@ export const getMediaInfo = async (id: string) => {
duration: data.duration as string,
rating: data.rating as number ?? "N/A",
production: data.production as string,
subtitles: await getMovieSubtitles(data.episodes![0].id, data.id)
}
case "TV Series":

Expand Down Expand Up @@ -245,6 +303,7 @@ export const getTopTrendingMovie = async () : Promise<Movie[]> => {
releaseDate: movieData.releaseDate as string,
type: "Movie",
thumbnail: movieData.image as string,
subtitles: await getMovieSubtitles(movieData.episodes![0].id, movies[0].id)
});

return data;
Expand Down Expand Up @@ -273,6 +332,7 @@ export const getTrendingMovies = async () : Promise<Movie[]> => {
releaseDate: movieData.releaseDate as string,
type: "Movie",
thumbnail: movieData.image as string,
subtitles: await getMovieSubtitles(movieData.episodes![0].id, movie.id)
}
})
);
Expand Down
4 changes: 4 additions & 0 deletions src/modules/interfaces/Movie.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,8 @@ interface Movie {
type : "Movie";
thumbnail : string;
episodes?: [];
subtitles : {
file : string;
label : string;
};
}
12 changes: 8 additions & 4 deletions src/renderer/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { MediaProvider } from './contexts/MediaContext';
import { useVideoPlayer, VideoPlayerProvider } from './contexts/VideoPlayerContext';
import { getMediaInfo, searchMedia } from '../modules/api/Movies';
import TitleBar from '../components/TitleBar';
import { ipcRenderer } from 'electron';
import { app, ipcRenderer } from 'electron';
import UpdateModal from './pages/modals/UpdateModal';

interface SearchResult {
Expand Down Expand Up @@ -112,10 +112,13 @@ const AppContent = () => {
bg={bgColor}
color="white"
>
<Flex align="center" mr={5}>
<Flex align="center" direction={'column'} mr={5}>
<Box fontWeight="bold" fontSize="xl" color="brand.500">
Streamflix
</Box>
<Box ml={2} color="gray.400">
v1.4.0
</Box>
</Flex>

<Flex align="center" flexGrow={1} justifyContent="center" position="relative">
Expand All @@ -125,14 +128,15 @@ const AppContent = () => {
onChange={(e) => handleSearch(e.target.value)}
onFocus={() => setIsSearchFocused(true)}
onBlur={() => setTimeout(() => setIsSearchFocused(false), 200)}
borderColor={"brand.500"}
/>
{searchTerm.length > 2 && (searchResults.length > 0 || isSearching) && isSearchFocused && (
<Box
position="absolute"
top="100%"
left="50%"
transform="translateX(-50%)"
width="400px"
width="600px"
bg="dark.200"
mt={2}
borderRadius="md"
Expand All @@ -148,7 +152,7 @@ const AppContent = () => {
<Flex
key={result.id}
p={2}
_hover={{ bg: 'gray.600' }}
_hover={{ bg: 'dark.300' }}
cursor="pointer"
onClick={() => {
console.log("Result clicked in render:", result.id);
Expand Down
Loading

0 comments on commit 3531057

Please sign in to comment.