watch: started work on stream backend
continuous-integration/drone/push Build is failing
Details
continuous-integration/drone/push Build is failing
Details
parent
1ffb926631
commit
f24c17d35e
@ -0,0 +1,11 @@
|
|||||||
|
meta {
|
||||||
|
name: Stream
|
||||||
|
type: http
|
||||||
|
seq: 2
|
||||||
|
}
|
||||||
|
|
||||||
|
get {
|
||||||
|
url: https://invidious.drgns.space/api/v1/videos/CcHevgjAnV0
|
||||||
|
body: none
|
||||||
|
auth: none
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
meta {
|
||||||
|
name: Stream
|
||||||
|
type: http
|
||||||
|
seq: 2
|
||||||
|
}
|
||||||
|
|
||||||
|
get {
|
||||||
|
url: https://pipedapi.kavin.rocks/streams/CcHevgjAnV0
|
||||||
|
body: none
|
||||||
|
auth: none
|
||||||
|
}
|
@ -1,12 +1,25 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import { useClient } from "@/hooks/useClient";
|
||||||
import { Component } from "@/typings/component";
|
import { Component } from "@/typings/component";
|
||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { useSearchParams } from "next/navigation";
|
import { useSearchParams } from "next/navigation";
|
||||||
|
|
||||||
export const Watch: Component = () => {
|
export const Watch: Component = () => {
|
||||||
|
const client = useClient();
|
||||||
|
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
|
|
||||||
const videoId = searchParams.get("v");
|
const videoId = searchParams.get("v");
|
||||||
|
|
||||||
|
const { data, error } = useQuery({
|
||||||
|
queryKey: ["watch", videoId],
|
||||||
|
queryFn: () => {
|
||||||
|
return client.getStream(videoId ?? "");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(data, error);
|
||||||
|
|
||||||
return <></>;
|
return <></>;
|
||||||
};
|
};
|
||||||
|
@ -0,0 +1,15 @@
|
|||||||
|
import z from "zod";
|
||||||
|
|
||||||
|
export const StoryboardModel = z.object({
|
||||||
|
url: z.string(),
|
||||||
|
templateUrl: z.string().url(),
|
||||||
|
width: z.number(),
|
||||||
|
height: z.number(),
|
||||||
|
count: z.number(),
|
||||||
|
interval: z.number(),
|
||||||
|
storyboardWidth: z.number(),
|
||||||
|
storyboardHeight: z.number(),
|
||||||
|
storyboardCount: z.number()
|
||||||
|
});
|
||||||
|
|
||||||
|
export type Storyboard = z.infer<typeof StoryboardModel>;
|
@ -0,0 +1,104 @@
|
|||||||
|
import z from "zod";
|
||||||
|
import { AuthorThumbnailModel, ThumbnailModel } from "./thumbnail";
|
||||||
|
import { StoryboardModel } from "./storyboard";
|
||||||
|
import { VideoModel } from "./video";
|
||||||
|
|
||||||
|
export const AdaptiveFormatModel = z.object({
|
||||||
|
index: z.string(),
|
||||||
|
bitrate: z.string(),
|
||||||
|
init: z.string(),
|
||||||
|
url: z.string().url(),
|
||||||
|
itag: z.string(),
|
||||||
|
type: z.string(),
|
||||||
|
clen: z.string(),
|
||||||
|
lmt: z.string(),
|
||||||
|
projectionType: z.number().or(z.string()),
|
||||||
|
container: z.string().optional(),
|
||||||
|
encoding: z.string().optional(),
|
||||||
|
qualityLabel: z.string().optional(),
|
||||||
|
resolution: z.string().optional(),
|
||||||
|
audioQuality: z.string().optional(),
|
||||||
|
audioSampleRate: z.number().optional(),
|
||||||
|
audioChannels: z.number().optional()
|
||||||
|
});
|
||||||
|
|
||||||
|
export const FormatStreamModel = z.object({
|
||||||
|
url: z.string().url(),
|
||||||
|
itag: z.string(),
|
||||||
|
type: z.string(),
|
||||||
|
quality: z.string(),
|
||||||
|
fps: z.number(),
|
||||||
|
container: z.string(),
|
||||||
|
encoding: z.string(),
|
||||||
|
resolution: z.string(),
|
||||||
|
qualityLabel: z.string(),
|
||||||
|
size: z.string()
|
||||||
|
});
|
||||||
|
|
||||||
|
export const CaptionModel = z.object({
|
||||||
|
label: z.string(),
|
||||||
|
language_code: z.string(),
|
||||||
|
url: z.string()
|
||||||
|
});
|
||||||
|
|
||||||
|
export const RecommendedVideoModel = z.object({
|
||||||
|
title: z.string(),
|
||||||
|
videoId: z.string(),
|
||||||
|
videoThumbnails: ThumbnailModel.array(),
|
||||||
|
|
||||||
|
lengthSeconds: z.number(),
|
||||||
|
viewCount: z.number(),
|
||||||
|
|
||||||
|
author: z.string(),
|
||||||
|
authorId: z.string(),
|
||||||
|
authorUrl: z.string(),
|
||||||
|
|
||||||
|
liveNow: z.boolean().optional().default(false),
|
||||||
|
paid: z.boolean().optional().default(false),
|
||||||
|
premium: z.boolean().optional().default(false)
|
||||||
|
});
|
||||||
|
|
||||||
|
export type RecommendedVideo = z.infer<typeof RecommendedVideoModel>;
|
||||||
|
|
||||||
|
export const StreamModel = z.object({
|
||||||
|
type: z.string(),
|
||||||
|
title: z.string(),
|
||||||
|
videoId: z.string(),
|
||||||
|
videoThumbnails: ThumbnailModel.array(),
|
||||||
|
storyboards: StoryboardModel.array(),
|
||||||
|
description: z.string(),
|
||||||
|
descriptionHtml: z.string(),
|
||||||
|
published: z.number(),
|
||||||
|
publishedText: z.string(),
|
||||||
|
keywords: z.string().array(),
|
||||||
|
viewCount: z.number(),
|
||||||
|
likeCount: z.number(),
|
||||||
|
dislikeCount: z.number(),
|
||||||
|
paid: z.boolean().optional().default(false),
|
||||||
|
premium: z.boolean().optional().default(false),
|
||||||
|
isFamilyFriendly: z.boolean(),
|
||||||
|
allowedRegions: z.string().array(),
|
||||||
|
genre: z.string(),
|
||||||
|
genreUrl: z.string(),
|
||||||
|
author: z.string(),
|
||||||
|
authorId: z.string(),
|
||||||
|
authorUrl: z.string(),
|
||||||
|
authorVerified: z.boolean(),
|
||||||
|
authorThumbnails: AuthorThumbnailModel.array(),
|
||||||
|
subCountText: z.string(),
|
||||||
|
lengthSeconds: z.number(),
|
||||||
|
allowRatings: z.boolean(),
|
||||||
|
rating: z.number(),
|
||||||
|
isListed: z.boolean(),
|
||||||
|
liveNow: z.boolean().optional().default(false),
|
||||||
|
isUpcoming: z.boolean(),
|
||||||
|
dashUrl: z.string().url(),
|
||||||
|
adaptiveFormats: AdaptiveFormatModel.array(),
|
||||||
|
formatStreams: FormatStreamModel.array(),
|
||||||
|
captions: CaptionModel.array(),
|
||||||
|
recommendedVideos: RecommendedVideoModel.array()
|
||||||
|
});
|
||||||
|
|
||||||
|
type Stream = z.infer<typeof StreamModel>;
|
||||||
|
|
||||||
|
export default Stream;
|
@ -0,0 +1,41 @@
|
|||||||
|
import z from "zod";
|
||||||
|
import { VideoModel } from "./video";
|
||||||
|
|
||||||
|
export const VideoItemModel = z
|
||||||
|
.object({
|
||||||
|
type: z.literal("stream")
|
||||||
|
})
|
||||||
|
.and(VideoModel);
|
||||||
|
|
||||||
|
export const ChannelItemModel = z.object({
|
||||||
|
type: z.literal("channel"),
|
||||||
|
url: z.string(),
|
||||||
|
name: z.string(),
|
||||||
|
thumbnail: z.string().url(),
|
||||||
|
description: z.string().nullable(),
|
||||||
|
subscribers: z.number(),
|
||||||
|
videos: z.number(),
|
||||||
|
verified: z.boolean()
|
||||||
|
});
|
||||||
|
|
||||||
|
export const PlaylistItemModel = z.object({
|
||||||
|
type: z.literal("playlist"),
|
||||||
|
url: z.string(),
|
||||||
|
name: z.string(),
|
||||||
|
thumbnail: z.string().url(),
|
||||||
|
uploaderName: z.string(),
|
||||||
|
uploaderUrl: z.string(),
|
||||||
|
uploaderVerified: z.boolean(),
|
||||||
|
playlistType: z.string(),
|
||||||
|
videos: z.number()
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ItemModel = z.union([
|
||||||
|
VideoItemModel,
|
||||||
|
ChannelItemModel,
|
||||||
|
PlaylistItemModel
|
||||||
|
]);
|
||||||
|
|
||||||
|
type Item = z.infer<typeof ItemModel>;
|
||||||
|
|
||||||
|
export default Item;
|
@ -0,0 +1,100 @@
|
|||||||
|
import z from "zod";
|
||||||
|
import { ItemModel } from "./item";
|
||||||
|
|
||||||
|
export const AudioStreamModel = z.object({
|
||||||
|
url: z.string().url(),
|
||||||
|
format: z.string(),
|
||||||
|
quality: z.string(),
|
||||||
|
mimeType: z.string(),
|
||||||
|
codec: z.string().nullable(),
|
||||||
|
audioTrackId: z.null(),
|
||||||
|
audioTrackName: z.null(),
|
||||||
|
audioTrackType: z.null(),
|
||||||
|
audioTrackLocale: z.null(),
|
||||||
|
videoOnly: z.boolean(),
|
||||||
|
itag: z.number(),
|
||||||
|
bitrate: z.number(),
|
||||||
|
initStart: z.number(),
|
||||||
|
initEnd: z.number(),
|
||||||
|
indexStart: z.number(),
|
||||||
|
indexEnd: z.number(),
|
||||||
|
width: z.number(),
|
||||||
|
height: z.number(),
|
||||||
|
fps: z.number(),
|
||||||
|
contentLength: z.number()
|
||||||
|
});
|
||||||
|
|
||||||
|
export const VideoStreamModel = z.object({
|
||||||
|
url: z.string(),
|
||||||
|
format: z.string(),
|
||||||
|
quality: z.string(),
|
||||||
|
mimeType: z.string(),
|
||||||
|
codec: z.string().nullable(),
|
||||||
|
audioTrackId: z.null(),
|
||||||
|
audioTrackName: z.null(),
|
||||||
|
audioTrackType: z.null(),
|
||||||
|
audioTrackLocale: z.null(),
|
||||||
|
videoOnly: z.boolean(),
|
||||||
|
itag: z.number(),
|
||||||
|
bitrate: z.number(),
|
||||||
|
initStart: z.number(),
|
||||||
|
initEnd: z.number(),
|
||||||
|
indexStart: z.number(),
|
||||||
|
indexEnd: z.number(),
|
||||||
|
width: z.number(),
|
||||||
|
height: z.number(),
|
||||||
|
fps: z.number(),
|
||||||
|
contentLength: z.number()
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ChapterModel = z.object({
|
||||||
|
title: z.string(),
|
||||||
|
image: z.string(),
|
||||||
|
start: z.number()
|
||||||
|
});
|
||||||
|
|
||||||
|
export const PreviewFrameModel = z.object({
|
||||||
|
urls: z.array(z.string()),
|
||||||
|
frameWidth: z.number(),
|
||||||
|
frameHeight: z.number(),
|
||||||
|
totalCount: z.number(),
|
||||||
|
durationPerFrame: z.number(),
|
||||||
|
framesPerPageX: z.number(),
|
||||||
|
framesPerPageY: z.number()
|
||||||
|
});
|
||||||
|
|
||||||
|
export const StreamModel = z.object({
|
||||||
|
title: z.string(),
|
||||||
|
description: z.string(),
|
||||||
|
uploadDate: z.coerce.date(),
|
||||||
|
uploader: z.string(),
|
||||||
|
uploaderUrl: z.string(),
|
||||||
|
uploaderAvatar: z.string().url(),
|
||||||
|
thumbnailUrl: z.string().url(),
|
||||||
|
hls: z.string().url(),
|
||||||
|
dash: z.null(),
|
||||||
|
lbryId: z.null(),
|
||||||
|
category: z.string(),
|
||||||
|
license: z.string(),
|
||||||
|
visibility: z.string(),
|
||||||
|
tags: z.array(z.string()),
|
||||||
|
metaInfo: z.array(z.unknown()),
|
||||||
|
uploaderVerified: z.boolean(),
|
||||||
|
duration: z.number(),
|
||||||
|
views: z.number(),
|
||||||
|
likes: z.number(),
|
||||||
|
dislikes: z.number(),
|
||||||
|
uploaderSubscriberCount: z.number(),
|
||||||
|
audioStreams: AudioStreamModel.array(),
|
||||||
|
videoStreams: VideoStreamModel.array(),
|
||||||
|
relatedStreams: ItemModel.array(),
|
||||||
|
subtitles: z.array(z.unknown()),
|
||||||
|
livestream: z.boolean(),
|
||||||
|
proxyUrl: z.string().url(),
|
||||||
|
chapters: ChapterModel.array(),
|
||||||
|
previewFrames: PreviewFrameModel.array()
|
||||||
|
});
|
||||||
|
|
||||||
|
type Stream = z.infer<typeof StreamModel>;
|
||||||
|
|
||||||
|
export default Stream;
|
@ -0,0 +1,33 @@
|
|||||||
|
import { Video } from "./video";
|
||||||
|
|
||||||
|
export type VideoItem = Video & { type: "video" };
|
||||||
|
|
||||||
|
export interface ChannelItem {
|
||||||
|
type: "channel";
|
||||||
|
name: string;
|
||||||
|
id: string;
|
||||||
|
thumbnail: string;
|
||||||
|
subscribers: number;
|
||||||
|
videos: number;
|
||||||
|
description: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PlaylistItem {
|
||||||
|
type: "playlist";
|
||||||
|
title: string;
|
||||||
|
id: string;
|
||||||
|
author: {
|
||||||
|
name: string;
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
numberOfVideos: number;
|
||||||
|
thumbnail: string;
|
||||||
|
videos?: {
|
||||||
|
title: string;
|
||||||
|
id: string;
|
||||||
|
duration: number;
|
||||||
|
thumbnail: string;
|
||||||
|
}[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Item = VideoItem | ChannelItem | PlaylistItem;
|
@ -1,38 +1,6 @@
|
|||||||
import { Video } from "../video";
|
import { Item } from "../item";
|
||||||
|
|
||||||
export type VideoResult = Video & { type: "video" };
|
|
||||||
|
|
||||||
export interface ChannelResult {
|
|
||||||
type: "channel";
|
|
||||||
name: string;
|
|
||||||
id: string;
|
|
||||||
thumbnail: string;
|
|
||||||
subscribers: number;
|
|
||||||
videos: number;
|
|
||||||
description: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PlaylistResult {
|
|
||||||
type: "playlist";
|
|
||||||
title: string;
|
|
||||||
id: string;
|
|
||||||
author: {
|
|
||||||
name: string;
|
|
||||||
id: string;
|
|
||||||
};
|
|
||||||
numberOfVideos: number;
|
|
||||||
thumbnail: string;
|
|
||||||
videos?: {
|
|
||||||
title: string;
|
|
||||||
id: string;
|
|
||||||
duration: number;
|
|
||||||
thumbnail: string;
|
|
||||||
}[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export type SearchItems = (VideoResult | ChannelResult | PlaylistResult)[];
|
|
||||||
|
|
||||||
export interface SearchResults {
|
export interface SearchResults {
|
||||||
items: SearchItems;
|
items: Item[];
|
||||||
nextCursor: string;
|
nextCursor: string;
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,11 @@
|
|||||||
|
import { Item } from "./item";
|
||||||
|
import { Video } from "./video";
|
||||||
|
|
||||||
|
export interface Stream {
|
||||||
|
video: Video;
|
||||||
|
keywords: string[];
|
||||||
|
likes: number;
|
||||||
|
dislikes: number;
|
||||||
|
category: string;
|
||||||
|
related: Item[];
|
||||||
|
}
|
@ -0,0 +1,4 @@
|
|||||||
|
export const videoUrl = (videoId: string): string => `/watch?v=${videoId}`;
|
||||||
|
|
||||||
|
export const channelUrl = (channelId: string): string =>
|
||||||
|
`/channel/${channelId}`;
|
Loading…
Reference in new issue