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:
push:

@ -15,7 +15,7 @@ COPY . .
COPY --from=deps /app/node_modules ./node_modules
ENV NEXT_TELEMETRY_DISABLED 1;
ENV NODE_ENV production;
ENV NODE_ENV "production"
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 = [
{ name: "Trending", icon: <Whatshot />, link: "trending" },
{ name: "Trending", icon: <Whatshot />, link: "/trending" },
{
name: "Subscriptions",
icon: <Subscriptions />,
link: "subscriptions"
link: "/subscriptions"
},
{
name: "Watch History",
icon: <History />,
link: "history"
link: "/history"
},
{
name: "Playlists",
icon: <PlaylistAddCheck />,
link: "playlists"
link: "/playlists"
}
];
@ -81,7 +81,10 @@ const Navbar: FC = () => {
edge="start"
color="inherit"
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)}
>
<Menu />
@ -104,7 +107,7 @@ const Navbar: FC = () => {
}}
>
{pages.map((page, i) => (
<Link key={i} href={"/" + page.link} passHref>
<Link key={i} href={page.link} passHref>
<Button
sx={{
color: "white",

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

@ -1,6 +1,6 @@
import { Video } from "@interfaces/video";
import Video from "@interfaces/api/trending";
export interface Channel {
interface Channel {
author: string;
authorId: string;
authorUrl: string;
@ -18,14 +18,14 @@ export interface Channel {
relatedChannels: RelatedChannel[];
}
export interface AuthorBanner {
interface AuthorBanner {
url: string;
width: number;
height: number;
quality?: Quality;
}
export enum Quality {
enum Quality {
Default = "default",
End = "end",
High = "high",
@ -37,9 +37,11 @@ export enum Quality {
Start = "start"
}
export interface RelatedChannel {
interface RelatedChannel {
author: string;
authorId: string;
authorUrl: string;
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 {
thumbnail: string;
title: string;
@ -13,34 +20,36 @@ export interface Video {
};
views: number;
published: {
time: number;
time: Date;
text: string;
};
length: number;
}
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;
live: boolean;
premium: boolean;
isUpcoming: boolean;
}
interface VideoThumbnail {
quality: string;
url: string;
width: number;
height: number;
export interface FullVideo extends Video {
keywords: string[];
likes: number;
dislikes: 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";
const queryClient = new QueryClient({
defaultOptions: { queries: { refetchOnWindowFocus: false } }
defaultOptions: {
queries: { refetchOnWindowFocus: false, retry: 5, retryDelay: 5000 }
}
});
const App = ({ Component, pageProps }: AppProps) => {

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

@ -1,9 +1,61 @@
import NotFound from "./404";
import axios, { AxiosError } from "axios";
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 Loading from "@components/Loading";
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;

@ -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 {
title: item.title,
description: {
@ -15,12 +17,58 @@ export const trendingToVideo = (item: Trending): Video => {
},
length: item.lengthSeconds,
published: {
time: item.published,
time: new Date(item.published),
text: item.publishedText
},
views: item.viewCount,
live: item.liveNow,
premium: item.premium,
thumbnail: item.videoThumbnails.find(
(thumbnail) => thumbnail.quality == "maxresdefault"
)?.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