movie-web/src/stores/player/slices/source.ts

212 lines
5.2 KiB
TypeScript
Raw Normal View History

import { ScrapeMedia } from "@movie-web/providers";
2023-07-23 16:30:22 +02:00
import { MakeSlice } from "@/stores/player/slices/types";
import {
SourceQuality,
SourceSliceSource,
selectQuality,
} from "@/stores/player/utils/qualities";
2023-10-18 20:31:03 +02:00
import { useQualityStore } from "@/stores/quality";
2023-07-23 16:30:22 +02:00
import { ValuesOf } from "@/utils/typeguard";
export const playerStatus = {
IDLE: "idle",
SCRAPING: "scraping",
PLAYING: "playing",
SCRAPE_NOT_FOUND: "scrapeNotFound",
PLAYBACK_ERROR: "playbackError",
2023-07-23 16:30:22 +02:00
} as const;
export type PlayerStatus = ValuesOf<typeof playerStatus>;
2023-10-20 17:29:10 +02:00
export interface PlayerMetaEpisode {
number: number;
tmdbId: string;
title: string;
}
export interface PlayerMeta {
type: "movie" | "show";
title: string;
tmdbId: string;
imdbId?: string;
releaseYear: number;
2023-10-17 16:01:26 +02:00
poster?: string;
2023-10-20 17:29:10 +02:00
episodes?: PlayerMetaEpisode[];
episode?: PlayerMetaEpisode;
season?: {
number: number;
tmdbId: string;
title: string;
};
}
2023-10-18 14:30:52 +02:00
export interface Caption {
id: string;
2023-10-18 14:30:52 +02:00
language: string;
url?: string;
srtData: string;
}
export interface CaptionListItem {
id: string;
language: string;
url: string;
needsProxy: boolean;
2024-03-12 23:45:34 +01:00
hls?: boolean;
}
2023-07-23 16:30:22 +02:00
export interface SourceSlice {
status: PlayerStatus;
2023-08-15 19:55:48 +02:00
source: SourceSliceSource | null;
2023-10-18 16:08:33 +02:00
sourceId: string | null;
qualities: SourceQuality[];
currentQuality: SourceQuality | null;
captionList: CaptionListItem[];
2023-10-18 14:30:52 +02:00
caption: {
selected: Caption | null;
asTrack: boolean;
};
meta: PlayerMeta | null;
2023-07-23 16:30:22 +02:00
setStatus(status: PlayerStatus): void;
setSource(
stream: SourceSliceSource,
captions: CaptionListItem[],
startAt: number,
): void;
switchQuality(quality: SourceQuality): void;
setMeta(meta: PlayerMeta, status?: PlayerStatus): void;
2023-10-18 14:30:52 +02:00
setCaption(caption: Caption | null): void;
2023-10-18 16:08:33 +02:00
setSourceId(id: string | null): void;
enableAutomaticQuality(): void;
2023-10-20 23:24:37 +02:00
redisplaySource(startAt: number): void;
}
export function metaToScrapeMedia(meta: PlayerMeta): ScrapeMedia {
if (meta.type === "show") {
if (!meta.episode || !meta.season) throw new Error("missing show data");
return {
title: meta.title,
releaseYear: meta.releaseYear,
tmdbId: meta.tmdbId,
type: "show",
imdbId: meta.imdbId,
episode: meta.episode,
season: meta.season,
};
}
return {
title: meta.title,
releaseYear: meta.releaseYear,
tmdbId: meta.tmdbId,
type: "movie",
imdbId: meta.imdbId,
};
2023-07-23 16:30:22 +02:00
}
export const createSourceSlice: MakeSlice<SourceSlice> = (set, get) => ({
2023-07-23 16:30:22 +02:00
source: null,
2023-10-18 16:08:33 +02:00
sourceId: null,
qualities: [],
captionList: [],
currentQuality: null,
2023-07-23 16:30:22 +02:00
status: playerStatus.IDLE,
meta: null,
2023-10-18 14:30:52 +02:00
caption: {
selected: null,
asTrack: false,
},
2023-10-18 16:08:33 +02:00
setSourceId(id) {
set((s) => {
s.status = playerStatus.PLAYING;
2023-10-18 16:08:33 +02:00
s.sourceId = id;
});
},
2023-07-23 16:30:22 +02:00
setStatus(status: PlayerStatus) {
set((s) => {
s.status = status;
});
},
setMeta(meta, newStatus) {
set((s) => {
s.meta = meta;
s.interface.hideNextEpisodeBtn = false;
if (newStatus) s.status = newStatus;
});
},
2023-10-18 14:30:52 +02:00
setCaption(caption) {
const store = get();
store.display?.setCaption(caption);
2023-10-18 14:30:52 +02:00
set((s) => {
s.caption.selected = caption;
});
},
setSource(
stream: SourceSliceSource,
captions: CaptionListItem[],
startAt: number,
) {
let qualities: string[] = [];
if (stream.type === "file") qualities = Object.keys(stream.qualities);
2023-10-18 20:31:03 +02:00
const qualityPreferences = useQualityStore.getState();
const loadableStream = selectQuality(stream, qualityPreferences.quality);
2023-07-23 16:30:22 +02:00
set((s) => {
s.source = stream;
s.qualities = qualities as SourceQuality[];
s.currentQuality = loadableStream.quality;
s.captionList = captions;
s.interface.error = undefined;
s.status = playerStatus.PLAYING;
2023-07-23 16:30:22 +02:00
});
2023-10-20 23:24:37 +02:00
const store = get();
store.redisplaySource(startAt);
},
redisplaySource(startAt: number) {
const store = get();
const quality = store.currentQuality;
if (!store.source) return;
const qualityPreferences = useQualityStore.getState();
const loadableStream = selectQuality(store.source, {
automaticQuality: qualityPreferences.quality.automaticQuality,
lastChosenQuality: quality,
});
set((s) => {
s.interface.error = undefined;
s.status = playerStatus.PLAYING;
});
store.display?.load({
source: loadableStream.stream,
startAt,
automaticQuality: qualityPreferences.quality.automaticQuality,
preferredQuality: qualityPreferences.quality.lastChosenQuality,
});
},
switchQuality(quality) {
const store = get();
if (!store.source) return;
if (store.source.type === "file") {
const selectedQuality = store.source.qualities[quality];
if (!selectedQuality) return;
set((s) => {
s.currentQuality = quality;
s.status = playerStatus.PLAYING;
s.interface.error = undefined;
});
store.display?.load({
source: selectedQuality,
startAt: store.progress.time,
automaticQuality: false,
preferredQuality: quality,
});
} else if (store.source.type === "hls") {
store.display?.changeQuality(false, quality);
}
2023-07-23 16:30:22 +02:00
},
enableAutomaticQuality() {
const store = get();
store.display?.changeQuality(true, null);
},
2023-07-23 16:30:22 +02:00
});