Reworked interface folder, added fetching to /watch

main
Guus van Meerveld 2 years ago
parent 8e2b0e5d6b
commit 8fb529d21c
Signed by: Guusvanmeerveld
GPG Key ID: 2BA7D7912771966E

@ -1,4 +1,4 @@
name: Deploy to Github Pages name: Deploy to Github Pages / Docker hub
on: on:
push: push:

@ -15,7 +15,7 @@ COPY . .
COPY --from=deps /app/node_modules ./node_modules COPY --from=deps /app/node_modules ./node_modules
ENV NEXT_TELEMETRY_DISABLED 1; ENV NEXT_TELEMETRY_DISABLED 1;
ENV NODE_ENV production; ENV NODE_ENV "production"
RUN yarn export RUN yarn export

@ -0,0 +1,19 @@
import { FC } from "react";
import Box from "@mui/material/Box";
import CircularProgress from "@mui/material/CircularProgress";
const Loading: FC = () => (
<Box
sx={{
display: "flex",
justifyContent: "center",
alignItems: "center",
mt: 5
}}
>
<CircularProgress />
</Box>
);
export default Loading;

@ -47,21 +47,21 @@ const Navbar: FC = () => {
}; };
const pages = [ const pages = [
{ name: "Trending", icon: <Whatshot />, link: "trending" }, { name: "Trending", icon: <Whatshot />, link: "/trending" },
{ {
name: "Subscriptions", name: "Subscriptions",
icon: <Subscriptions />, icon: <Subscriptions />,
link: "subscriptions" link: "/subscriptions"
}, },
{ {
name: "Watch History", name: "Watch History",
icon: <History />, icon: <History />,
link: "history" link: "/history"
}, },
{ {
name: "Playlists", name: "Playlists",
icon: <PlaylistAddCheck />, icon: <PlaylistAddCheck />,
link: "playlists" link: "/playlists"
} }
]; ];
@ -81,7 +81,10 @@ const Navbar: FC = () => {
edge="start" edge="start"
color="inherit" color="inherit"
aria-label="menu" aria-label="menu"
sx={{ mr: 2, display: { lg: "none", xs: "flex" } }} sx={{
mr: { md: 2, xs: 0 },
display: { lg: "none", xs: "flex" }
}}
onClick={() => setDrawerState(!drawerIsOpen)} onClick={() => setDrawerState(!drawerIsOpen)}
> >
<Menu /> <Menu />
@ -104,7 +107,7 @@ const Navbar: FC = () => {
}} }}
> >
{pages.map((page, i) => ( {pages.map((page, i) => (
<Link key={i} href={"/" + page.link} passHref> <Link key={i} href={page.link} passHref>
<Button <Button
sx={{ sx={{
color: "white", color: "white",

@ -44,18 +44,16 @@ const Video: FC<VideoModel> = ({
fields: requestFields.join(",") fields: requestFields.join(",")
} }
}) })
.then((res) => res.data), .then((res) => res.data)
{
retry: 1,
retryDelay: 5000
}
); );
const router = useRouter(); const router = useRouter();
return ( return (
<Card sx={{ width: "100%" }}> <Card sx={{ width: "100%" }}>
<CardActionArea onClick={() => router.push(`/watch?v=${id}`)}> <CardActionArea
onClick={() => router.push({ pathname: "/watch", query: { v: id } })}
>
<CardMedia <CardMedia
height="270" height="270"
component="img" component="img"

@ -1,6 +1,6 @@
import { Video } from "@interfaces/video"; import Video from "@interfaces/api/trending";
export interface Channel { interface Channel {
author: string; author: string;
authorId: string; authorId: string;
authorUrl: string; authorUrl: string;
@ -18,14 +18,14 @@ export interface Channel {
relatedChannels: RelatedChannel[]; relatedChannels: RelatedChannel[];
} }
export interface AuthorBanner { interface AuthorBanner {
url: string; url: string;
width: number; width: number;
height: number; height: number;
quality?: Quality; quality?: Quality;
} }
export enum Quality { enum Quality {
Default = "default", Default = "default",
End = "end", End = "end",
High = "high", High = "high",
@ -37,9 +37,11 @@ export enum Quality {
Start = "start" Start = "start"
} }
export interface RelatedChannel { interface RelatedChannel {
author: string; author: string;
authorId: string; authorId: string;
authorUrl: string; authorUrl: string;
authorThumbnails: AuthorBanner[]; authorThumbnails: AuthorBanner[];
} }
export default Channel;

@ -0,0 +1,27 @@
interface Trending {
type: string;
title: string;
videoId: string;
author: string;
authorId: string;
authorUrl: string;
videoThumbnails: VideoThumbnail[];
description: string;
descriptionHtml: string;
viewCount: number;
published: number;
publishedText: string;
lengthSeconds: number;
liveNow: boolean;
premium: boolean;
isUpcoming: boolean;
}
interface VideoThumbnail {
quality: string;
url: string;
width: number;
height: number;
}
export default Trending;

@ -0,0 +1,129 @@
export interface Video {
type: string;
title: string;
videoId: string;
videoThumbnails: Thumbnail[];
storyboards: Storyboard[];
description: string;
descriptionHtml: string;
published: number;
publishedText: string;
keywords: string[];
viewCount: number;
likeCount: number;
dislikeCount: number;
paid: boolean;
premium: boolean;
isFamilyFriendly: boolean;
allowedRegions: string[];
premiereTimestamp?: number;
genre: string;
genreUrl: string;
author: string;
authorId: string;
authorUrl: string;
authorThumbnails: Thumbnail[];
subCountText: string;
lengthSeconds: number;
allowRatings: boolean;
rating: number;
isListed: boolean;
liveNow: boolean;
isUpcoming: boolean;
dashUrl: string;
adaptiveFormats: AdaptiveFormat[];
formatStreams: FormatStream[];
captions: Caption[];
recommendedVideos: RecommendedVideo[];
}
export interface AdaptiveFormat {
index: string;
bitrate: string;
init: string;
url: string;
itag: string;
type: string;
clen: string;
lmt: string;
projectionType: ProjectionType;
fps?: number;
container?: Container;
encoding?: string;
resolution?: string;
qualityLabel?: string;
}
export interface FormatStream {
url: string;
itag: string;
type: string;
quality: string;
fps: number;
container: string;
encoding: string;
resolution: string;
qualityLabel: string;
size: string;
}
enum Container {
M4A = "m4a",
Mp4 = "mp4",
Webm = "webm"
}
enum ProjectionType {
Rectangular = "RECTANGULAR"
}
interface Thumbnail {
url: string;
width: number;
height: number;
quality?: Quality;
}
enum Quality {
Default = "default",
End = "end",
High = "high",
Maxres = "maxres",
Maxresdefault = "maxresdefault",
Medium = "medium",
Middle = "middle",
Sddefault = "sddefault",
Start = "start"
}
export interface Caption {
label: string;
language_code: string;
url: string;
}
export interface RecommendedVideo {
videoId: string;
title: string;
videoThumbnails: Thumbnail[];
author: string;
authorUrl: string;
authorId: string;
lengthSeconds: number;
viewCountText: string;
viewCount: number;
}
interface Storyboard {
url: string;
templateUrl: string;
width: number;
height: number;
count: number;
interval: number;
storyboardWidth: number;
storyboardHeight: number;
storyboardCount: number;
}
export default Video;

@ -1,3 +1,10 @@
import {
AdaptiveFormat,
Caption,
FormatStream,
RecommendedVideo
} from "@interfaces/api/video";
export interface Video { export interface Video {
thumbnail: string; thumbnail: string;
title: string; title: string;
@ -13,34 +20,36 @@ export interface Video {
}; };
views: number; views: number;
published: { published: {
time: number; time: Date;
text: string; text: string;
}; };
length: number; length: number;
} live: boolean;
export interface Trending {
type: string;
title: string;
videoId: string;
author: string;
authorId: string;
authorUrl: string;
videoThumbnails: VideoThumbnail[];
description: string;
descriptionHtml: string;
viewCount: number;
published: number;
publishedText: string;
lengthSeconds: number;
liveNow: boolean;
premium: boolean; premium: boolean;
isUpcoming: boolean;
} }
interface VideoThumbnail { export interface FullVideo extends Video {
quality: string; keywords: string[];
url: string; likes: number;
width: number; dislikes: number;
height: number; familyFriendly: boolean;
genre: {
type: string;
url: string;
};
author: {
thumbnail: string;
name: string;
id: string;
url: string;
};
subscriptions: string;
rating: number;
premiered: Date | undefined;
recommendedVideos: RecommendedVideo[];
adaptiveFormats: AdaptiveFormat[];
formatStreams: FormatStream[];
captions: Caption[];
} }
export default Video;

@ -14,7 +14,9 @@ import SEO from "@src/next-seo.config";
import createTheme from "@src/theme"; import createTheme from "@src/theme";
const queryClient = new QueryClient({ const queryClient = new QueryClient({
defaultOptions: { queries: { refetchOnWindowFocus: false } } defaultOptions: {
queries: { refetchOnWindowFocus: false, retry: 5, retryDelay: 5000 }
}
}); });
const App = ({ Component, pageProps }: AppProps) => { const App = ({ Component, pageProps }: AppProps) => {

@ -9,18 +9,15 @@ import { useQuery } from "react-query";
import Box from "@mui/material/Box"; import Box from "@mui/material/Box";
import Chip from "@mui/material/Chip"; import Chip from "@mui/material/Chip";
import CircularProgress from "@mui/material/CircularProgress";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import { Error } from "@interfaces/api"; import { Error } from "@interfaces/api";
import { import TrendingModel from "@interfaces/api/trending";
Trending as TrendingModel,
Video as VideoModel
} from "@interfaces/video";
import { trendingToVideo } from "@utils/conversions"; import { trendingToVideo } from "@utils/conversions";
import Layout from "@components/Layout"; import Layout from "@components/Layout";
import Loading from "@components/Loading";
import Grid from "@components/Video/Grid"; import Grid from "@components/Video/Grid";
const Trending: NextPage = () => { const Trending: NextPage = () => {
@ -29,34 +26,28 @@ const Trending: NextPage = () => {
const { isLoading, error, data } = useQuery< const { isLoading, error, data } = useQuery<
TrendingModel[], TrendingModel[],
AxiosError<Error> AxiosError<Error>
>( >("trendingData", () =>
"trendingData", axios
() => .get("https://invidious.privacy.gd/api/v1/trending", {
axios params: {
.get("https://invidious.privacy.gd/api/v1/trending", { fields: [
params: { "title",
fields: [ "description",
"title", "descriptionHtml",
"description", "videoId",
"descriptionHtml", "author",
"videoId", "authorId",
"author", "authorUrl",
"authorId", "lengthSeconds",
"authorUrl", "published",
"lengthSeconds", "publishedText",
"published", "viewCount",
"publishedText", "videoThumbnails"
"viewCount", ].join(","),
"videoThumbnails" type: selectedCategory
].join(","), }
type: selectedCategory })
} .then((res) => res.data)
})
.then((res) => res.data),
{
retry: 5,
retryDelay: 5000
}
); );
return ( return (
@ -64,17 +55,7 @@ const Trending: NextPage = () => {
<NextSeo title="Trending" /> <NextSeo title="Trending" />
<Layout> <Layout>
<Box sx={{ px: { xs: 1, sm: 2, md: 5 } }}> <Box sx={{ px: { xs: 1, sm: 2, md: 5 } }}>
{isLoading && ( {isLoading && <Loading />}
<Box
sx={{
display: "flex",
justifyContent: "center",
alignItems: "center"
}}
>
<CircularProgress />
</Box>
)}
{error && <Box>{error.response?.data.error}</Box>} {error && <Box>{error.response?.data.error}</Box>}
{!isLoading && !error && data && ( {!isLoading && !error && data && (
<> <>

@ -1,9 +1,61 @@
import NotFound from "./404";
import axios, { AxiosError } from "axios";
import { NextPage } from "next"; import { NextPage } from "next";
import { NextSeo } from "next-seo";
import { useRouter } from "next/router";
import { useEffect } from "react";
import { useQuery } from "react-query";
import { Error } from "@interfaces/api";
import Video from "@interfaces/api/video";
import { videoToVideo } from "@utils/conversions";
import Layout from "@components/Layout"; import Layout from "@components/Layout";
import Loading from "@components/Loading";
const Watch: NextPage = () => { const Watch: NextPage = () => {
return <Layout></Layout>; const router = useRouter();
const videoId = router.query["v"];
const { isLoading, error, data, refetch } = useQuery<
Video,
AxiosError<Error>
>(
["videoData", videoId],
() =>
axios
.get(`https://invidious.privacy.gd/api/v1/videos/${videoId}`, {
params: {}
})
.then((res) => res.data),
{ enabled: false }
);
useEffect(() => {
if (videoId) refetch();
}, [videoId, refetch]);
if (!videoId) {
return <NotFound />;
}
return (
<>
<NextSeo
title={data ? data.title : isLoading ? "Loading video..." : "Not Found"}
/>
<Layout>
{isLoading && <Loading />}
{}
</Layout>
</>
);
}; };
export default Watch; export default Watch;

@ -1,6 +1,8 @@
import { Trending, Video } from "@interfaces/video"; import TrendingAPI from "@interfaces/api/trending";
import VideoAPI from "@interfaces/api/video";
import Video, { FullVideo } from "@interfaces/video";
export const trendingToVideo = (item: Trending): Video => { export const trendingToVideo = (item: TrendingAPI): Video => {
return { return {
title: item.title, title: item.title,
description: { description: {
@ -15,12 +17,58 @@ export const trendingToVideo = (item: Trending): Video => {
}, },
length: item.lengthSeconds, length: item.lengthSeconds,
published: { published: {
time: item.published, time: new Date(item.published),
text: item.publishedText text: item.publishedText
}, },
views: item.viewCount, views: item.viewCount,
live: item.liveNow,
premium: item.premium,
thumbnail: item.videoThumbnails.find( thumbnail: item.videoThumbnails.find(
(thumbnail) => thumbnail.quality == "maxresdefault" (thumbnail) => thumbnail.quality == "maxresdefault"
)?.url as string )?.url as string
}; };
}; };
export const videoToVideo = (item: VideoAPI): FullVideo => {
return {
title: item.title,
views: item.viewCount,
likes: item.likeCount,
dislikes: item.dislikeCount,
id: item.videoId,
description: { html: item.descriptionHtml, text: item.description },
length: item.lengthSeconds,
live: item.liveNow,
premiered: item.premiereTimestamp
? new Date(item.premiereTimestamp)
: undefined,
premium: item.premium,
published: {
time: new Date(item.published),
text: item.publishedText
},
rating: item.rating,
genre: {
type: item.genre,
url: item.genreUrl
},
keywords: item.keywords,
familyFriendly: item.isFamilyFriendly,
subscriptions: item.subCountText,
thumbnail: item.videoThumbnails.find(
(thumbnail) => thumbnail.quality == "maxresdefault"
)?.url as string,
author: {
id: item.authorId,
name: item.author,
url: item.authorUrl,
thumbnail: item.authorThumbnails.find(
(thumbnail) => thumbnail.width == 100
)?.url as string
},
adaptiveFormats: item.adaptiveFormats,
recommendedVideos: item.recommendedVideos,
formatStreams: item.formatStreams,
captions: item.captions
};
};

Loading…
Cancel
Save