diff --git a/__old/DecoratedVideoPlayer.tsx b/__old/DecoratedVideoPlayer.tsx
deleted file mode 100644
index 700b0149..00000000
--- a/__old/DecoratedVideoPlayer.tsx
+++ /dev/null
@@ -1,184 +0,0 @@
-import { DetailedMeta } from "@/backend/metadata/getmeta";
-import { useIsMobile } from "@/hooks/useIsMobile";
-import { useCallback, useRef, useState } from "react";
-import { CSSTransition } from "react-transition-group";
-import { AirplayControl } from "./controls/AirplayControl";
-import { BackdropControl } from "./controls/BackdropControl";
-import { ChromeCastControl } from "./controls/ChromeCastControl";
-import { FullscreenControl } from "./controls/FullscreenControl";
-import { LoadingControl } from "./controls/LoadingControl";
-import { MiddlePauseControl } from "./controls/MiddlePauseControl";
-import { MobileCenterControl } from "./controls/MobileCenterControl";
-import { PageTitleControl } from "./controls/PageTitleControl";
-import { PauseControl } from "./controls/PauseControl";
-import { ProgressControl } from "./controls/ProgressControl";
-import { QualityDisplayControl } from "./controls/QualityDisplayControl";
-import { SeriesSelectionControl } from "./controls/SeriesSelectionControl";
-import { ShowTitleControl } from "./controls/ShowTitleControl";
-import { SkipTime } from "./controls/SkipTime";
-import { SourceSelectionControl } from "./controls/SourceSelectionControl";
-import { TimeControl } from "./controls/TimeControl";
-import { VolumeControl } from "./controls/VolumeControl";
-import { VideoPlayerError } from "./parts/VideoPlayerError";
-import { VideoPlayerHeader } from "./parts/VideoPlayerHeader";
-import { useVideoPlayerState } from "./VideoContext";
-import { VideoPlayer, VideoPlayerProps } from "./VideoPlayer";
-
-interface DecoratedVideoPlayerProps {
- media?: DetailedMeta;
- onGoBack?: () => void;
-}
-
-function LeftSideControls() {
- const { videoState } = useVideoPlayerState();
-
- const handleMouseEnter = useCallback(() => {
- videoState.setLeftControlsHover(true);
- }, [videoState]);
- const handleMouseLeave = useCallback(() => {
- videoState.setLeftControlsHover(false);
- }, [videoState]);
-
- return (
- <>
-
-
- >
- );
-}
-
-export function DecoratedVideoPlayer(
- props: VideoPlayerProps & DecoratedVideoPlayerProps
-) {
- const top = useRef(null);
- const center = useRef(null);
- const bottom = useRef(null);
- const [show, setShow] = useState(false);
- const { isMobile } = useIsMobile();
-
- const onBackdropChange = useCallback(
- (showing: boolean) => {
- setShow(showing);
- },
- [setShow]
- );
-
- return (
-
-
-
-
-
-
-
-
-
-
- {isMobile ? (
-
-
-
-
-
- ) : (
- ""
- )}
-
-
-
-
-
-
-
-
-
- {isMobile ? (
-
- ) : (
- <>
-
-
-
-
-
-
-
-
- >
- )}
-
-
-
-
- {props.children}
-
-
- );
-}
diff --git a/__old/VideoContext.tsx b/__old/VideoContext.tsx
deleted file mode 100644
index 10941206..00000000
--- a/__old/VideoContext.tsx
+++ /dev/null
@@ -1,98 +0,0 @@
-import { MWStreamQuality, MWStreamType } from "@/backend/helpers/streams";
-import React, {
- createContext,
- MutableRefObject,
- useContext,
- useEffect,
- useReducer,
-} from "react";
-import {
- initialPlayerState,
- PlayerContext,
- useVideoPlayer,
-} from "./hooks/useVideoPlayer";
-
-interface VideoPlayerContextType {
- source: string | null;
- sourceType: MWStreamType;
- quality: MWStreamQuality;
- state: PlayerContext;
-}
-const initial: VideoPlayerContextType = {
- source: null,
- sourceType: MWStreamType.MP4,
- quality: MWStreamQuality.QUNKNOWN,
- state: initialPlayerState,
-};
-
-type VideoPlayerContextAction =
- | {
- type: "SET_SOURCE";
- url: string;
- sourceType: MWStreamType;
- quality: MWStreamQuality;
- }
- | {
- type: "UPDATE_PLAYER";
- state: PlayerContext;
- };
-
-function videoPlayerContextReducer(
- original: VideoPlayerContextType,
- action: VideoPlayerContextAction
-): VideoPlayerContextType {
- const video = { ...original };
- if (action.type === "SET_SOURCE") {
- video.source = action.url;
- video.sourceType = action.sourceType;
- video.quality = action.quality;
- return video;
- }
- if (action.type === "UPDATE_PLAYER") {
- video.state = action.state;
- return video;
- }
-
- return original;
-}
-
-export const VideoPlayerContext =
- createContext(initial);
-export const VideoPlayerDispatchContext = createContext<
- React.Dispatch
->(null as any);
-
-export function VideoPlayerContextProvider(props: {
- children: React.ReactNode;
- player: MutableRefObject;
- wrapper: MutableRefObject;
-}) {
- const { playerState } = useVideoPlayer(props.player, props.wrapper);
- const [videoData, dispatch] = useReducer(
- videoPlayerContextReducer,
- initial
- );
-
- useEffect(() => {
- dispatch({
- type: "UPDATE_PLAYER",
- state: playerState,
- });
- }, [playerState]);
-
- return (
-
-
- {props.children}
-
-
- );
-}
-
-export function useVideoPlayerState() {
- const { state } = useContext(VideoPlayerContext);
-
- return {
- videoState: state,
- };
-}
diff --git a/__old/VideoPlayer.tsx b/__old/VideoPlayer.tsx
deleted file mode 100644
index 4642c5ec..00000000
--- a/__old/VideoPlayer.tsx
+++ /dev/null
@@ -1,69 +0,0 @@
-import { useGoBack } from "@/hooks/useGoBack";
-import { useVolumeControl } from "@/hooks/useVolumeToggle";
-import { forwardRef, useContext, useEffect, useRef } from "react";
-import { VideoErrorBoundary } from "./parts/VideoErrorBoundary";
-import {
- useVideoPlayerState,
- VideoPlayerContext,
- VideoPlayerContextProvider,
-} from "./VideoContext";
-
-export interface VideoPlayerProps {
- autoPlay?: boolean;
- children?: React.ReactNode;
-}
-
-const VideoPlayerInternals = forwardRef<
- HTMLVideoElement,
- { autoPlay: boolean }
->((props, ref) => {
- const video = useContext(VideoPlayerContext);
- const didInitialize = useRef<{ source: string | null } | null>(null);
- const { videoState } = useVideoPlayerState();
- const { toggleVolume } = useVolumeControl();
-
- useEffect(() => {
- const value = { source: video.source };
- const hasChanged = value.source !== didInitialize.current?.source;
- if (!hasChanged) return;
- if (!video.state.hasInitialized || !video.source) return;
- video.state.initPlayer(video.source, video.sourceType);
- didInitialize.current = value;
- }, [didInitialize, video]);
-
- // muted attribute is required for safari, as they cant change the volume itself
- return (
-
- );
-});
-
-export function VideoPlayer(props: VideoPlayerProps) {
- const playerRef = useRef(null);
- const playerWrapperRef = useRef(null);
- const goBack = useGoBack();
-
- // TODO move error boundary to only decorated, shouldn't have styling
-
- return (
-
-
-
-
- {props.children}
-
-
-
- );
-}
diff --git a/__old/controls/AirplayControl.tsx b/__old/controls/AirplayControl.tsx
deleted file mode 100644
index 55ba7ec4..00000000
--- a/__old/controls/AirplayControl.tsx
+++ /dev/null
@@ -1,26 +0,0 @@
-import { Icons } from "@/components/Icon";
-import { useCallback } from "react";
-import { VideoPlayerIconButton } from "../parts/VideoPlayerIconButton";
-import { useVideoPlayerState } from "../VideoContext";
-
-interface Props {
- className?: string;
-}
-
-export function AirplayControl(props: Props) {
- const { videoState } = useVideoPlayerState();
-
- const handleClick = useCallback(() => {
- videoState.startAirplay();
- }, [videoState]);
-
- if (!videoState.canAirplay) return null;
-
- return (
-
- );
-}
diff --git a/__old/controls/BackdropControl.tsx b/__old/controls/BackdropControl.tsx
deleted file mode 100644
index af9a9967..00000000
--- a/__old/controls/BackdropControl.tsx
+++ /dev/null
@@ -1,89 +0,0 @@
-import React, { useCallback, useEffect, useRef, useState } from "react";
-import { useVideoPlayerState } from "../VideoContext";
-
-interface BackdropControlProps {
- children?: React.ReactNode;
- onBackdropChange?: (showing: boolean) => void;
-}
-
-export function BackdropControl(props: BackdropControlProps) {
- const { videoState } = useVideoPlayerState();
- const [moved, setMoved] = useState(false);
- const timeout = useRef | null>(null);
- const clickareaRef = useRef(null);
-
- const handleMouseMove = useCallback(() => {
- if (!moved) setMoved(true);
- if (timeout.current) clearTimeout(timeout.current);
- timeout.current = setTimeout(() => {
- if (moved) setMoved(false);
- timeout.current = null;
- }, 3000);
- }, [setMoved, moved]);
-
- const handleMouseLeave = useCallback(() => {
- setMoved(false);
- }, [setMoved]);
-
- const handleClick = useCallback(
- (e: React.MouseEvent) => {
- if (!clickareaRef.current || clickareaRef.current !== e.target) return;
-
- if (videoState.popout !== null) return;
-
- if (videoState.isPlaying) videoState.pause();
- else videoState.play();
- },
- [videoState, clickareaRef]
- );
- const handleDoubleClick = useCallback(
- (e: React.MouseEvent) => {
- if (!clickareaRef.current || clickareaRef.current !== e.target) return;
-
- if (!videoState.isFullscreen) videoState.enterFullscreen();
- else videoState.exitFullscreen();
- },
- [videoState, clickareaRef]
- );
-
- const lastBackdropValue = useRef(null);
- useEffect(() => {
- const currentValue = moved || videoState.isPaused;
- if (currentValue !== lastBackdropValue.current) {
- lastBackdropValue.current = currentValue;
- if (!currentValue) videoState.closePopout();
- props.onBackdropChange?.(currentValue);
- }
- }, [videoState, moved, props]);
- const showUI = moved || videoState.isPaused;
-
- return (
-
-
-
-
-
- {props.children}
-
-
- );
-}
diff --git a/__old/controls/ChromeCastControl.tsx b/__old/controls/ChromeCastControl.tsx
deleted file mode 100644
index 27cd6dc0..00000000
--- a/__old/controls/ChromeCastControl.tsx
+++ /dev/null
@@ -1,15 +0,0 @@
-declare global {
- // eslint-disable-next-line @typescript-eslint/no-namespace
- namespace JSX {
- interface IntrinsicElements {
- "google-cast-launcher": React.DetailedHTMLProps<
- React.HTMLAttributes,
- HTMLElement
- >;
- }
- }
-}
-
-export function ChromeCastControl() {
- return ;
-}
diff --git a/__old/controls/FullscreenControl.tsx b/__old/controls/FullscreenControl.tsx
deleted file mode 100644
index 9d44264a..00000000
--- a/__old/controls/FullscreenControl.tsx
+++ /dev/null
@@ -1,28 +0,0 @@
-import { Icons } from "@/components/Icon";
-import { canFullscreen } from "@/utils/detectFeatures";
-import { useCallback } from "react";
-import { VideoPlayerIconButton } from "../parts/VideoPlayerIconButton";
-import { useVideoPlayerState } from "../VideoContext";
-
-interface Props {
- className?: string;
-}
-
-export function FullscreenControl(props: Props) {
- const { videoState } = useVideoPlayerState();
-
- const handleClick = useCallback(() => {
- if (videoState.isFullscreen) videoState.exitFullscreen();
- else videoState.enterFullscreen();
- }, [videoState]);
-
- if (!canFullscreen()) return null;
-
- return (
-
- );
-}
diff --git a/__old/controls/LoadingControl.tsx b/__old/controls/LoadingControl.tsx
deleted file mode 100644
index 8b38cef9..00000000
--- a/__old/controls/LoadingControl.tsx
+++ /dev/null
@@ -1,12 +0,0 @@
-import { Spinner } from "@/components/layout/Spinner";
-import { useVideoPlayerState } from "../VideoContext";
-
-export function LoadingControl() {
- const { videoState } = useVideoPlayerState();
-
- const isLoading = videoState.isFirstLoading || videoState.isLoading;
-
- if (!isLoading) return null;
-
- return ;
-}
diff --git a/__old/controls/MiddlePauseControl.tsx b/__old/controls/MiddlePauseControl.tsx
deleted file mode 100644
index b934db0a..00000000
--- a/__old/controls/MiddlePauseControl.tsx
+++ /dev/null
@@ -1,28 +0,0 @@
-import { Icon, Icons } from "@/components/Icon";
-import { useCallback } from "react";
-import { useVideoPlayerState } from "../VideoContext";
-
-export function MiddlePauseControl() {
- const { videoState } = useVideoPlayerState();
-
- const handleClick = useCallback(() => {
- if (videoState?.isPlaying) videoState.pause();
- else videoState.play();
- }, [videoState]);
-
- if (videoState.hasPlayedOnce) return null;
- if (videoState.isPlaying) return null;
- if (videoState.isFirstLoading) return null;
-
- return (
-
-
-
- );
-}
diff --git a/__old/controls/MobileCenterControl.tsx b/__old/controls/MobileCenterControl.tsx
deleted file mode 100644
index ecf842e8..00000000
--- a/__old/controls/MobileCenterControl.tsx
+++ /dev/null
@@ -1,20 +0,0 @@
-import { useVideoPlayerState } from "../VideoContext";
-import { PauseControl } from "./PauseControl";
-import { SkipTimeBackward, SkipTimeForward } from "./TimeControl";
-
-export function MobileCenterControl() {
- const { videoState } = useVideoPlayerState();
-
- const isLoading = videoState.isFirstLoading || videoState.isLoading;
-
- return (
-
- );
-}
diff --git a/__old/controls/PageTitleControl.tsx b/__old/controls/PageTitleControl.tsx
deleted file mode 100644
index 7f8305c7..00000000
--- a/__old/controls/PageTitleControl.tsx
+++ /dev/null
@@ -1,23 +0,0 @@
-import { MWMediaMeta } from "@/backend/metadata/types";
-import { Helmet } from "react-helmet";
-import { useCurrentSeriesEpisodeInfo } from "../hooks/useCurrentSeriesEpisodeInfo";
-
-interface PageTitleControlProps {
- media?: MWMediaMeta;
-}
-
-export function PageTitleControl(props: PageTitleControlProps) {
- const { isSeries, humanizedEpisodeId } = useCurrentSeriesEpisodeInfo();
-
- if (!props.media) return null;
-
- const title = isSeries
- ? `${props.media.title} - ${humanizedEpisodeId}`
- : props.media.title;
-
- return (
-
- {title}
-
- );
-}
diff --git a/__old/controls/PauseControl.tsx b/__old/controls/PauseControl.tsx
deleted file mode 100644
index f5719d31..00000000
--- a/__old/controls/PauseControl.tsx
+++ /dev/null
@@ -1,30 +0,0 @@
-import { Icons } from "@/components/Icon";
-import { useCallback } from "react";
-import { VideoPlayerIconButton } from "../parts/VideoPlayerIconButton";
-import { useVideoPlayerState } from "../VideoContext";
-
-interface Props {
- className?: string;
- iconSize?: string;
-}
-
-export function PauseControl(props: Props) {
- const { videoState } = useVideoPlayerState();
-
- const handleClick = useCallback(() => {
- if (videoState?.isPlaying) videoState.pause();
- else videoState.play();
- }, [videoState]);
-
- const icon =
- videoState.isPlaying || videoState.isSeeking ? Icons.PAUSE : Icons.PLAY;
-
- return (
-
- );
-}
diff --git a/__old/controls/ProgressControl.tsx b/__old/controls/ProgressControl.tsx
deleted file mode 100644
index 4d303746..00000000
--- a/__old/controls/ProgressControl.tsx
+++ /dev/null
@@ -1,77 +0,0 @@
-import {
- makePercentage,
- makePercentageString,
- useProgressBar,
-} from "@/hooks/useProgressBar";
-import { useCallback, useEffect, useRef } from "react";
-import { useVideoPlayerState } from "../VideoContext";
-
-export function ProgressControl() {
- const { videoState } = useVideoPlayerState();
- const ref = useRef(null);
- const dragRef = useRef(false);
-
- const commitTime = useCallback(
- (percentage) => {
- videoState.setTime(percentage * videoState.duration);
- },
- [videoState]
- );
- const { dragging, dragPercentage, dragMouseDown } = useProgressBar(
- ref,
- commitTime
- );
-
- // TODO make dragging update timer
- useEffect(() => {
- if (dragRef.current === dragging) return;
- dragRef.current = dragging;
- videoState.setSeeking(dragging);
- }, [dragRef, dragging, videoState]);
-
- let watchProgress = makePercentageString(
- makePercentage((videoState.time / videoState.duration) * 100)
- );
- if (dragging)
- watchProgress = makePercentageString(makePercentage(dragPercentage));
-
- const bufferProgress = makePercentageString(
- makePercentage((videoState.buffered / videoState.duration) * 100)
- );
-
- return (
-
- );
-}
diff --git a/__old/controls/ProgressListenerControl.tsx b/__old/controls/ProgressListenerControl.tsx
deleted file mode 100644
index 6c23bb18..00000000
--- a/__old/controls/ProgressListenerControl.tsx
+++ /dev/null
@@ -1,41 +0,0 @@
-import { useEffect, useMemo, useRef } from "react";
-import throttle from "lodash.throttle";
-import { useVideoPlayerState } from "../VideoContext";
-
-interface Props {
- startAt?: number;
- onProgress?: (time: number, duration: number) => void;
-}
-
-export function ProgressListenerControl(props: Props) {
- const { videoState } = useVideoPlayerState();
- const didInitialize = useRef(null);
-
- // time updates (throttled)
- const updateTime = useMemo(
- () => throttle((a: number, b: number) => props.onProgress?.(a, b), 1000),
- [props]
- );
- useEffect(() => {
- if (!videoState.isPlaying) return;
- if (videoState.duration === 0 || videoState.time === 0) return;
- updateTime(videoState.time, videoState.duration);
- }, [videoState, updateTime]);
- useEffect(() => {
- return () => {
- updateTime.cancel();
- };
- }, [updateTime]);
-
- // initialize
- useEffect(() => {
- if (didInitialize.current) return;
- if (!videoState.hasInitialized || Number.isNaN(videoState.duration)) return;
- if (props.startAt !== undefined) {
- videoState.setTime(props.startAt);
- }
- didInitialize.current = true;
- }, [didInitialize, props, videoState]);
-
- return null;
-}
diff --git a/__old/controls/QualityDisplayControl.tsx b/__old/controls/QualityDisplayControl.tsx
deleted file mode 100644
index 3a282e2b..00000000
--- a/__old/controls/QualityDisplayControl.tsx
+++ /dev/null
@@ -1,14 +0,0 @@
-import { useContext } from "react";
-import { VideoPlayerContext } from "../VideoContext";
-
-export function QualityDisplayControl() {
- const videoPlayerContext = useContext(VideoPlayerContext);
-
- return (
-
-
- {videoPlayerContext.quality}
-
-
- );
-}
diff --git a/__old/controls/SeriesSelectionControl.tsx b/__old/controls/SeriesSelectionControl.tsx
deleted file mode 100644
index 0d376912..00000000
--- a/__old/controls/SeriesSelectionControl.tsx
+++ /dev/null
@@ -1,194 +0,0 @@
-import React, { useCallback, useMemo, useState } from "react";
-import { useParams } from "react-router-dom";
-import { Icon, Icons } from "@/components/Icon";
-import { useLoading } from "@/hooks/useLoading";
-import { MWMediaType, MWSeasonWithEpisodeMeta } from "@/backend/metadata/types";
-import { getMetaFromId } from "@/backend/metadata/getmeta";
-import { decodeJWId } from "@/backend/metadata/justwatch";
-import { Loading } from "@/components/layout/Loading";
-import { IconPatch } from "@/components/buttons/IconPatch";
-import { useVideoPlayerState } from "../VideoContext";
-import { VideoPlayerIconButton } from "../parts/VideoPlayerIconButton";
-import { VideoPopout } from "../parts/VideoPopout";
-
-interface Props {
- className?: string;
-}
-
-function PopupSection(props: {
- children?: React.ReactNode;
- className?: string;
-}) {
- return (
-
- {props.children}
-
- );
-}
-
-function PopupEpisodeSelect() {
- const params = useParams<{
- media: string;
- }>();
- const { videoState } = useVideoPlayerState();
- const [isPickingSeason, setIsPickingSeason] = useState(false);
- const { current, seasons } = videoState.seasonData;
- const [currentVisibleSeason, setCurrentVisibleSeason] = useState<{
- seasonId: string;
- season?: MWSeasonWithEpisodeMeta;
- } | null>(null);
- const [reqSeasonMeta, loading, error] = useLoading(
- (id: string, seasonId: string) => {
- return getMetaFromId(MWMediaType.SERIES, id, seasonId);
- }
- );
- const requestSeason = useCallback(
- (sId: string) => {
- setCurrentVisibleSeason({
- seasonId: sId,
- season: undefined,
- });
- setIsPickingSeason(false);
- reqSeasonMeta(decodeJWId(params.media)?.id as string, sId).then((v) => {
- if (v?.meta.type !== MWMediaType.SERIES) return;
- setCurrentVisibleSeason({
- seasonId: sId,
- season: v?.meta.seasonData,
- });
- });
- },
- [reqSeasonMeta, params.media]
- );
-
- const currentSeasonId = currentVisibleSeason?.seasonId ?? current?.seasonId;
-
- const setCurrent = useCallback(
- (seasonId: string, episodeId: string) => {
- videoState.setCurrentEpisode(seasonId, episodeId);
- },
- [videoState]
- );
-
- const currentSeasonInfo = useMemo(() => {
- return seasons?.find((season) => season.id === currentSeasonId);
- }, [seasons, currentSeasonId]);
-
- const currentSeasonEpisodes = useMemo(() => {
- if (currentVisibleSeason?.season) {
- return currentVisibleSeason?.season?.episodes;
- }
- return videoState?.seasonData.seasons?.find?.(
- (season) => season && season.id === currentSeasonId
- )?.episodes;
- }, [videoState, currentSeasonId, currentVisibleSeason]);
-
- const toggleIsPickingSeason = () => {
- setIsPickingSeason(!isPickingSeason);
- };
-
- const setSeason = (id: string) => {
- requestSeason(id);
- setCurrentVisibleSeason({ seasonId: id });
- };
-
- if (isPickingSeason)
- return (
- <>
-
- Pick a season
-
-
-
- {currentSeasonInfo
- ? videoState?.seasonData?.seasons?.map?.((season) => (
-
setSeason(season.id)}
- >
- {season.title}
-
- ))
- : "No season"}
-
-
- >
- );
-
- return (
- <>
-
-
- {currentSeasonInfo?.title || ""}
-
-
- {loading ? (
-
-
-
- ) : error ? (
-
-
-
-
- Something went wrong loading the episodes for{" "}
- {currentSeasonInfo?.title?.toLowerCase()}
-
-
-
- ) : (
-
- {currentSeasonEpisodes && currentSeasonInfo
- ? currentSeasonEpisodes.map((e) => (
-
setCurrent(currentSeasonInfo.id, e.id)}
- key={e.id}
- >
- {e.number}. {e.title}
-
- ))
- : "No episodes"}
-
- )}
-
- >
- );
-}
-
-export function SeriesSelectionControl(props: Props) {
- const { videoState } = useVideoPlayerState();
-
- if (!videoState.seasonData.isSeries) return null;
-
- return (
-
-
-
-
-
-
videoState.openPopout("episodes")}
- />
-
-
- );
-}
diff --git a/__old/controls/ShowControl.tsx b/__old/controls/ShowControl.tsx
deleted file mode 100644
index 162870b7..00000000
--- a/__old/controls/ShowControl.tsx
+++ /dev/null
@@ -1,69 +0,0 @@
-import {
- MWSeasonMeta,
- MWSeasonWithEpisodeMeta,
-} from "@/backend/metadata/types";
-import { useEffect, useRef } from "react";
-import { PlayerContext } from "../hooks/useVideoPlayer";
-import { useVideoPlayerState } from "../VideoContext";
-
-interface ShowControlProps {
- series?: {
- episodeId: string;
- seasonId: string;
- };
- seasons: MWSeasonMeta[];
- seasonData: MWSeasonWithEpisodeMeta;
- onSelect?: (state: { episodeId?: string; seasonId?: string }) => void;
-}
-
-function setVideoShowState(videoState: PlayerContext, props: ShowControlProps) {
- const seasonsWithEpisodes = props.seasons.map((v) => {
- if (v.id === props.seasonData.id)
- return {
- ...v,
- episodes: props.seasonData.episodes,
- };
- return v;
- });
-
- videoState.setShowData({
- current: props.series,
- isSeries: !!props.series,
- seasons: seasonsWithEpisodes,
- });
-}
-
-export function ShowControl(props: ShowControlProps) {
- const { videoState } = useVideoPlayerState();
- const lastState = useRef<{
- episodeId?: string;
- seasonId?: string;
- } | null>({
- episodeId: props.series?.episodeId,
- seasonId: props.series?.seasonId,
- });
-
- const hasInitialized = useRef(false);
- useEffect(() => {
- if (hasInitialized.current) return;
- if (!videoState.hasInitialized) return;
- setVideoShowState(videoState, props);
- hasInitialized.current = true;
- }, [props, videoState]);
-
- useEffect(() => {
- const currentState = {
- episodeId: videoState.seasonData.current?.episodeId,
- seasonId: videoState.seasonData.current?.seasonId,
- };
- if (
- currentState.episodeId !== lastState.current?.episodeId ||
- currentState.seasonId !== lastState.current?.seasonId
- ) {
- lastState.current = currentState;
- props.onSelect?.(currentState);
- }
- }, [videoState, props]);
-
- return null;
-}
diff --git a/__old/controls/ShowTitleControl.tsx b/__old/controls/ShowTitleControl.tsx
deleted file mode 100644
index dbaa5399..00000000
--- a/__old/controls/ShowTitleControl.tsx
+++ /dev/null
@@ -1,15 +0,0 @@
-import { useCurrentSeriesEpisodeInfo } from "../hooks/useCurrentSeriesEpisodeInfo";
-
-export function ShowTitleControl() {
- const { isSeries, currentEpisodeInfo, humanizedEpisodeId } =
- useCurrentSeriesEpisodeInfo();
-
- if (!isSeries) return null;
-
- return (
-
- {humanizedEpisodeId}
- {currentEpisodeInfo?.title}
-
- );
-}
diff --git a/__old/controls/SkipTime.tsx b/__old/controls/SkipTime.tsx
deleted file mode 100644
index 5e47cb90..00000000
--- a/__old/controls/SkipTime.tsx
+++ /dev/null
@@ -1,47 +0,0 @@
-import { useVideoPlayerState } from "../VideoContext";
-
-function durationExceedsHour(secs: number): boolean {
- return secs > 60 * 60;
-}
-
-function formatSeconds(secs: number, showHours = false): string {
- if (Number.isNaN(secs)) {
- if (showHours) return "0:00:00";
- return "0:00";
- }
-
- let time = secs;
- const seconds = Math.floor(time % 60);
-
- time /= 60;
- const minutes = Math.floor(time % 60);
-
- time /= 60;
- const hours = Math.floor(time);
-
- const paddedSecs = seconds.toString().padStart(2, "0");
- const paddedMins = minutes.toString().padStart(2, "0");
-
- if (!showHours) return [paddedMins, paddedSecs].join(":");
- return [hours, paddedMins, paddedSecs].join(":");
-}
-
-interface Props {
- className?: string;
- noDuration?: boolean;
-}
-
-export function SkipTime(props: Props) {
- const { videoState } = useVideoPlayerState();
- const hasHours = durationExceedsHour(videoState.duration);
- const time = formatSeconds(videoState.time, hasHours);
- const duration = formatSeconds(videoState.duration, hasHours);
-
- return (
-
-
- {time} {props.noDuration ? "" : `/ ${duration}`}
-
-
- );
-}
diff --git a/__old/controls/SourceControl.tsx b/__old/controls/SourceControl.tsx
deleted file mode 100644
index c8cccfbc..00000000
--- a/__old/controls/SourceControl.tsx
+++ /dev/null
@@ -1,27 +0,0 @@
-import { MWStreamQuality, MWStreamType } from "@/backend/helpers/streams";
-import { useContext, useEffect, useRef } from "react";
-import { VideoPlayerDispatchContext } from "../VideoContext";
-
-interface SourceControlProps {
- source: string;
- type: MWStreamType;
- quality: MWStreamQuality;
-}
-
-export function SourceControl(props: SourceControlProps) {
- const dispatch = useContext(VideoPlayerDispatchContext);
- const didInitialize = useRef(false);
-
- useEffect(() => {
- if (didInitialize.current) return;
- dispatch({
- type: "SET_SOURCE",
- url: props.source,
- sourceType: props.type,
- quality: props.quality,
- });
- didInitialize.current = true;
- }, [props, dispatch]);
-
- return null;
-}
diff --git a/__old/controls/SourceSelectionControl.tsx b/__old/controls/SourceSelectionControl.tsx
deleted file mode 100644
index 08566e48..00000000
--- a/__old/controls/SourceSelectionControl.tsx
+++ /dev/null
@@ -1,185 +0,0 @@
-import { useParams } from "react-router-dom";
-import { useCallback, useContext, useMemo, useState } from "react";
-import { Icon, Icons } from "@/components/Icon";
-import { getProviders } from "@/backend/helpers/register";
-import { useLoading } from "@/hooks/useLoading";
-import { DetailedMeta } from "@/backend/metadata/getmeta";
-import { MWMediaType } from "@/backend/metadata/types";
-import { MWProviderScrapeResult } from "@/backend/helpers/provider";
-import { runProvider } from "@/backend/helpers/run";
-import { IconPatch } from "@/components/buttons/IconPatch";
-import { Loading } from "@/components/layout/Loading";
-import {
- useVideoPlayerState,
- VideoPlayerDispatchContext,
-} from "../VideoContext";
-import { VideoPlayerIconButton } from "../parts/VideoPlayerIconButton";
-import { VideoPopout } from "../parts/VideoPopout";
-
-interface Props {
- className?: string;
- media?: DetailedMeta;
-}
-
-function PopoutSourceSelect(props: { media: DetailedMeta }) {
- const dispatch = useContext(VideoPlayerDispatchContext);
- const providers = useMemo(
- () => getProviders().filter((v) => v.type.includes(props.media.meta.type)),
- [props]
- );
- const { episode, season } = useParams<{ episode: string; season: string }>();
- const [selected, setSelected] = useState(null);
- const selectedProvider = useMemo(
- () => providers.find((v) => v.id === selected),
- [selected, providers]
- );
-
- const [scrapeData, setScrapeData] = useState(
- null
- );
- const [scrapeProvider, loadingProvider, errorProvider] = useLoading(
- async (providerId: string) => {
- const theProvider = providers.find((v) => v.id === providerId);
- if (!theProvider) throw new Error("Invalid provider");
- return runProvider(theProvider, {
- media: props.media,
- progress: () => {},
- type: props.media.meta.type,
- episode: (props.media.meta.type === MWMediaType.SERIES
- ? episode
- : undefined) as any,
- season: (props.media.meta.type === MWMediaType.SERIES
- ? season
- : undefined) as any,
- });
- }
- );
-
- // TODO add embed support
- // TODO restore startAt when changing source
- // TODO auto choose when only one option
- // TODO close when selecting item
- // TODO show currently selected provider
- // TODO clear error state when switching
- // const [scrapeEmbed, embedLoading, embedError] = useLoading(
- // async (embed: MWEmbed) => {
- // if (!embed.type) throw new Error("Invalid embed type");
- // const theScraper = getEmbedScraperByType(embed.type);
- // if (!theScraper) throw new Error("Invalid scraper");
- // return runEmbedScraper(theScraper, {
- // progress: () => {},
- // url: embed.url,
- // });
- // }
- // );
-
- const selectProvider = useCallback(
- (id: string) => {
- scrapeProvider(id).then((v) => {
- if (!v) throw new Error("No scrape result");
- setScrapeData(v);
- });
- setSelected(id);
- },
- [setSelected, scrapeProvider]
- );
-
- if (!selectedProvider)
- return (
- <>
-
- Select video source
-
-
-
- {providers.map((e) => (
-
selectProvider(e.id)}
- key={e.id}
- >
- {e.displayName}
-
- ))}
-
-
- >
- );
-
- return (
- <>
-
-
- {selectedProvider.displayName}
-
-
- {loadingProvider ? (
-
-
-
- ) : errorProvider ? (
-
-
-
-
- Something went wrong loading streams.
-
-
-
- ) : scrapeData ? (
-
- {scrapeData.stream ? (
-
- scrapeData.stream &&
- dispatch({
- url: scrapeData.stream.streamUrl,
- quality: scrapeData.stream.quality,
- sourceType: scrapeData.stream.type,
- type: "SET_SOURCE",
- })
- }
- >
- {selectedProvider.displayName}
-
- ) : null}
-
- ) : null}
-
- >
- );
-}
-
-export function SourceSelectionControl(props: Props) {
- const { videoState } = useVideoPlayerState();
-
- if (!props.media) return null;
-
- return (
-
-
-
-
-
-
videoState.openPopout("source")}
- />
-
-
- );
-}
diff --git a/__old/controls/TimeControl.tsx b/__old/controls/TimeControl.tsx
deleted file mode 100644
index 75eef38e..00000000
--- a/__old/controls/TimeControl.tsx
+++ /dev/null
@@ -1,42 +0,0 @@
-import { Icons } from "@/components/Icon";
-import { VideoPlayerIconButton } from "../parts/VideoPlayerIconButton";
-import { useVideoPlayerState } from "../VideoContext";
-
-interface Props {
- className?: string;
-}
-
-export function SkipTimeBackward() {
- const { videoState } = useVideoPlayerState();
-
- const skipBackward = () => {
- videoState.setTime(videoState.time - 10);
- };
-
- return (
-
- );
-}
-
-export function SkipTimeForward() {
- const { videoState } = useVideoPlayerState();
-
- const skipForward = () => {
- videoState.setTime(videoState.time + 10);
- };
-
- return (
-
- );
-}
-
-export function TimeControl(props: Props) {
- return (
-
- );
-}
diff --git a/__old/controls/VolumeControl.tsx b/__old/controls/VolumeControl.tsx
deleted file mode 100644
index 707a88bb..00000000
--- a/__old/controls/VolumeControl.tsx
+++ /dev/null
@@ -1,86 +0,0 @@
-import { Icon, Icons } from "@/components/Icon";
-import {
- makePercentage,
- makePercentageString,
- useProgressBar,
-} from "@/hooks/useProgressBar";
-import { useVolumeControl } from "@/hooks/useVolumeToggle";
-import { canChangeVolume } from "@/utils/detectFeatures";
-import { useCallback, useEffect, useRef, useState } from "react";
-import { useVideoPlayerState } from "../VideoContext";
-
-interface Props {
- className?: string;
-}
-
-export function VolumeControl(props: Props) {
- const { videoState } = useVideoPlayerState();
- const ref = useRef(null);
- const { setStoredVolume, toggleVolume } = useVolumeControl();
- const [hoveredOnce, setHoveredOnce] = useState(false);
-
- const commitVolume = useCallback(
- (percentage) => {
- videoState.setVolume(percentage);
- setStoredVolume(percentage);
- },
- [videoState, setStoredVolume]
- );
- const { dragging, dragPercentage, dragMouseDown } = useProgressBar(
- ref,
- commitVolume,
- true
- );
-
- useEffect(() => {
- if (!videoState.leftControlHovering) setHoveredOnce(false);
- }, [videoState, setHoveredOnce]);
-
- const handleClick = useCallback(() => {
- toggleVolume();
- }, [toggleVolume]);
-
- const handleMouseEnter = useCallback(async () => {
- if (await canChangeVolume()) setHoveredOnce(true);
- }, [setHoveredOnce]);
-
- let percentage = makePercentage(videoState.volume * 100);
- if (dragging) percentage = makePercentage(dragPercentage);
- const percentageString = makePercentageString(percentage);
-
- return (
-
-
-
- 0 ? Icons.VOLUME : Icons.VOLUME_X} />
-
-
-
-
- );
-}
diff --git a/__old/hooks/controlVideo.ts b/__old/hooks/controlVideo.ts
deleted file mode 100644
index a8552b0b..00000000
--- a/__old/hooks/controlVideo.ts
+++ /dev/null
@@ -1,204 +0,0 @@
-import Hls from "hls.js";
-import {
- canChangeVolume,
- canFullscreen,
- canFullscreenAnyElement,
- canWebkitFullscreen,
-} from "@/utils/detectFeatures";
-import { MWStreamType } from "@/backend/helpers/streams";
-import fscreen from "fscreen";
-import React, { RefObject } from "react";
-import { PlayerState } from "./useVideoPlayer";
-import { getStoredVolume, setStoredVolume } from "./volumeStore";
-
-interface ShowData {
- current?: {
- episodeId: string;
- seasonId: string;
- };
- isSeries: boolean;
- seasons?: {
- id: string;
- number: number;
- title: string;
- episodes?: {
- id: string;
- number: number;
- title: string;
- }[];
- }[];
-}
-
-export interface PlayerControls {
- play(): void;
- pause(): void;
- exitFullscreen(): void;
- enterFullscreen(): void;
- setTime(time: number): void;
- setVolume(volume: number): void;
- setSeeking(active: boolean): void;
- setLeftControlsHover(hovering: boolean): void;
- initPlayer(sourceUrl: string, sourceType: MWStreamType): void;
- setShowData(data: ShowData): void;
- setCurrentEpisode(sId: string, eId: string): void;
- startAirplay(): void;
- openPopout(id: string): void;
- closePopout(): void;
-}
-
-export const initialControls: PlayerControls = {
- play: () => null,
- pause: () => null,
- enterFullscreen: () => null,
- exitFullscreen: () => null,
- setTime: () => null,
- setVolume: () => null,
- setSeeking: () => null,
- setLeftControlsHover: () => null,
- initPlayer: () => null,
- setShowData: () => null,
- startAirplay: () => null,
- setCurrentEpisode: () => null,
- openPopout: () => null,
- closePopout: () => null,
-};
-
-export function populateControls(
- playerEl: HTMLVideoElement,
- wrapperEl: HTMLDivElement,
- update: (s: React.SetStateAction) => void,
- state: RefObject
-): PlayerControls {
- const player = playerEl;
- const wrapper = wrapperEl;
-
- return {
- play() {
- player.play();
- },
- pause() {
- player.pause();
- },
- enterFullscreen() {
- if (!canFullscreen() || fscreen.fullscreenElement) return;
- if (canFullscreenAnyElement()) {
- fscreen.requestFullscreen(wrapper);
- return;
- }
- if (canWebkitFullscreen()) {
- (player as any).webkitEnterFullscreen();
- }
- },
- exitFullscreen() {
- if (!fscreen.fullscreenElement) return;
- fscreen.exitFullscreen();
- },
- setTime(t) {
- // clamp time between 0 and max duration
- let time = Math.min(t, player.duration);
- time = Math.max(0, time);
-
- if (Number.isNaN(time)) return;
-
- // update state
- player.currentTime = time;
- update((s) => ({ ...s, time }));
- },
- async setVolume(v) {
- // clamp time between 0 and 1
- let volume = Math.min(v, 1);
- volume = Math.max(0, volume);
-
- // update state
- if (await canChangeVolume()) player.volume = volume;
- update((s) => ({ ...s, volume }));
-
- // update localstorage
- setStoredVolume(volume);
- },
- setSeeking(active) {
- const currentState = state.current;
- if (!currentState) return;
-
- // if it was playing when starting to seek, play again
- if (!active) {
- if (!currentState.pausedWhenSeeking) this.play();
- return;
- }
-
- // when seeking we pause the video
- update((s) => ({ ...s, pausedWhenSeeking: s.isPaused }));
- this.pause();
- },
- setLeftControlsHover(hovering) {
- update((s) => ({ ...s, leftControlHovering: hovering }));
- },
- openPopout(id: string) {
- update((s) => ({ ...s, popout: id }));
- },
- closePopout() {
- update((s) => ({ ...s, popout: null }));
- },
- setShowData(data) {
- update((s) => ({ ...s, seasonData: data }));
- },
- setCurrentEpisode(sId: string, eId: string) {
- update((s) => ({
- ...s,
- seasonData: {
- ...s.seasonData,
- current: {
- seasonId: sId,
- episodeId: eId,
- },
- },
- }));
- },
- startAirplay() {
- const videoPlayer = player as any;
- if (videoPlayer.webkitShowPlaybackTargetPicker)
- videoPlayer.webkitShowPlaybackTargetPicker();
- },
- initPlayer(sourceUrl: string, sourceType: MWStreamType) {
- this.setVolume(getStoredVolume());
-
- if (sourceType === MWStreamType.HLS) {
- if (player.canPlayType("application/vnd.apple.mpegurl")) {
- player.src = sourceUrl;
- } else {
- // HLS support
- if (!Hls.isSupported()) {
- update((s) => ({
- ...s,
- error: {
- name: `Not supported`,
- description: "Your browser does not support HLS video",
- },
- }));
- return;
- }
-
- const hls = new Hls({ enableWorker: false });
-
- hls.on(Hls.Events.ERROR, (event, data) => {
- if (data.fatal) {
- update((s) => ({
- ...s,
- error: {
- name: `error ${data.details}`,
- description: data.error?.message ?? "Something went wrong",
- },
- }));
- }
- console.error("HLS error", data);
- });
-
- hls.attachMedia(player);
- hls.loadSource(sourceUrl);
- }
- } else if (sourceType === MWStreamType.MP4) {
- player.src = sourceUrl;
- }
- },
- };
-}
diff --git a/__old/hooks/useCurrentSeriesEpisodeInfo.ts b/__old/hooks/useCurrentSeriesEpisodeInfo.ts
deleted file mode 100644
index a1e432d1..00000000
--- a/__old/hooks/useCurrentSeriesEpisodeInfo.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-import { useMemo } from "react";
-import { useVideoPlayerState } from "../VideoContext";
-
-export function useCurrentSeriesEpisodeInfo() {
- const { videoState } = useVideoPlayerState();
-
- const { current, seasons } = videoState.seasonData;
-
- const currentSeasonInfo = useMemo(() => {
- return seasons?.find((season) => season.id === current?.seasonId);
- }, [seasons, current]);
-
- const currentEpisodeInfo = useMemo(() => {
- return currentSeasonInfo?.episodes?.find(
- (episode) => episode.id === current?.episodeId
- );
- }, [currentSeasonInfo, current]);
-
- const isSeries = Boolean(
- videoState.seasonData.isSeries && videoState.seasonData.current
- );
-
- if (!isSeries) return { isSeries: false };
-
- const humanizedEpisodeId = `S${currentSeasonInfo?.number} E${currentEpisodeInfo?.number}`;
-
- return {
- isSeries: true,
- humanizedEpisodeId,
- currentSeasonInfo,
- currentEpisodeInfo,
- };
-}
diff --git a/__old/hooks/useVideoPlayer.ts b/__old/hooks/useVideoPlayer.ts
deleted file mode 100644
index 9bc9421d..00000000
--- a/__old/hooks/useVideoPlayer.ts
+++ /dev/null
@@ -1,262 +0,0 @@
-import { canChangeVolume } from "@/utils/detectFeatures";
-import fscreen from "fscreen";
-import React, { MutableRefObject, useEffect, useRef, useState } from "react";
-import {
- initialControls,
- PlayerControls,
- populateControls,
-} from "./controlVideo";
-import { handleBuffered } from "./utils";
-
-export type PlayerState = {
- isPlaying: boolean;
- isPaused: boolean;
- isSeeking: boolean;
- isLoading: boolean;
- isFirstLoading: boolean;
- isFullscreen: boolean;
- time: number;
- duration: number;
- volume: number;
- buffered: number;
- pausedWhenSeeking: boolean;
- hasInitialized: boolean;
- leftControlHovering: boolean;
- hasPlayedOnce: boolean;
- popout: string | null;
- isFocused: boolean;
- seasonData: {
- isSeries: boolean;
- current?: {
- episodeId: string;
- seasonId: string;
- };
- seasons?: {
- id: string;
- number: number;
- title: string;
- episodes?: { id: string; number: number; title: string }[];
- }[];
- };
- error: null | {
- name: string;
- description: string;
- };
- canAirplay: boolean;
-};
-
-export type PlayerContext = PlayerState & PlayerControls;
-
-export const initialPlayerState: PlayerContext = {
- isPlaying: false,
- isPaused: true,
- isFullscreen: false,
- isFocused: false,
- isLoading: false,
- isSeeking: false,
- isFirstLoading: true,
- time: 0,
- duration: 0,
- volume: 0,
- buffered: 0,
- pausedWhenSeeking: false,
- hasInitialized: false,
- leftControlHovering: false,
- hasPlayedOnce: false,
- error: null,
- popout: null,
- seasonData: {
- isSeries: false,
- },
- canAirplay: false,
- ...initialControls,
-};
-
-type SetPlayer = (s: React.SetStateAction) => void;
-
-function readState(player: HTMLVideoElement, update: SetPlayer) {
- const state = {
- ...initialPlayerState,
- };
- state.isPaused = player.paused;
- state.isPlaying = !player.paused;
- state.isFullscreen = !!document.fullscreenElement;
- state.isSeeking = player.seeking;
- state.time = player.currentTime;
- state.duration = player.duration;
- state.volume = player.volume;
- state.buffered = handleBuffered(player.currentTime, player.buffered);
- state.isLoading = false;
- state.hasInitialized = true;
- state.error = null;
-
- update((s) => ({
- ...state,
- pausedWhenSeeking: s.pausedWhenSeeking,
- hasPlayedOnce: s.hasPlayedOnce,
- isFirstLoading: s.isFirstLoading,
- }));
-}
-
-function registerListeners(player: HTMLVideoElement, update: SetPlayer) {
- const pause = () => {
- update((s) => ({
- ...s,
- isPaused: true,
- isPlaying: false,
- }));
- };
- const playing = () => {
- update((s) => ({
- ...s,
- isPaused: false,
- isPlaying: true,
- isLoading: false,
- hasPlayedOnce: true,
- }));
- };
- const seeking = () => {
- update((s) => ({ ...s, isSeeking: true }));
- };
- const seeked = () => {
- update((s) => ({ ...s, isSeeking: false }));
- };
- const waiting = () => {
- update((s) => ({ ...s, isLoading: true }));
- };
- const fullscreenchange = () => {
- update((s) => ({ ...s, isFullscreen: !!document.fullscreenElement }));
- };
- const timeupdate = () => {
- update((s) => ({
- ...s,
- duration: player.duration,
- time: player.currentTime,
- }));
- };
- const loadedmetadata = () => {
- update((s) => ({
- ...s,
- duration: player.duration,
- }));
- };
- const volumechange = async () => {
- if (await canChangeVolume())
- update((s) => ({
- ...s,
- volume: player.volume,
- }));
- };
- const progress = () => {
- update((s) => ({
- ...s,
- buffered: handleBuffered(player.currentTime, player.buffered),
- }));
- };
- const canplay = () => {
- update((s) => ({
- ...s,
- isFirstLoading: false,
- }));
- };
- const error = () => {
- console.error("Native video player threw error", player.error);
- update((s) => ({
- ...s,
- error: player.error
- ? {
- description: player.error.message,
- name: `Error ${player.error.code}`,
- }
- : null,
- }));
- };
- const canAirplay = (e: any) => {
- if (e.availability === "available") {
- update((s) => ({
- ...s,
- canAirplay: true,
- }));
- }
- };
- const isFocused = (evt: any) => {
- update((s) => ({
- ...s,
- isFocused: evt.type !== "mouseleave",
- }));
- };
-
- const playerWrapper = player.closest(".is-video-player");
- if (!playerWrapper) return;
-
- playerWrapper.addEventListener("click", isFocused);
- playerWrapper.addEventListener("mouseenter", isFocused);
- playerWrapper.addEventListener("mouseleave", isFocused);
- player.addEventListener("pause", pause);
- player.addEventListener("playing", playing);
- player.addEventListener("seeking", seeking);
- player.addEventListener("seeked", seeked);
- fscreen.addEventListener("fullscreenchange", fullscreenchange);
- player.addEventListener("timeupdate", timeupdate);
- player.addEventListener("loadedmetadata", loadedmetadata);
- player.addEventListener("volumechange", volumechange);
- player.addEventListener("progress", progress);
- player.addEventListener("waiting", waiting);
- player.addEventListener("canplay", canplay);
- player.addEventListener("error", error);
- player.addEventListener(
- "webkitplaybacktargetavailabilitychanged",
- canAirplay
- );
-
- return () => {
- playerWrapper.removeEventListener("click", isFocused);
- playerWrapper.removeEventListener("mouseenter", isFocused);
- playerWrapper.removeEventListener("mouseleave", isFocused);
- player.removeEventListener("pause", pause);
- player.removeEventListener("playing", playing);
- player.removeEventListener("seeking", seeking);
- player.removeEventListener("seeked", seeked);
- fscreen.removeEventListener("fullscreenchange", fullscreenchange);
- player.removeEventListener("timeupdate", timeupdate);
- player.removeEventListener("loadedmetadata", loadedmetadata);
- player.removeEventListener("volumechange", volumechange);
- player.removeEventListener("progress", progress);
- player.removeEventListener("waiting", waiting);
- player.removeEventListener("canplay", canplay);
- player.removeEventListener("error", error);
- player.removeEventListener(
- "webkitplaybacktargetavailabilitychanged",
- canAirplay
- );
- };
-}
-
-export function useVideoPlayer(
- ref: MutableRefObject,
- wrapperRef: MutableRefObject
-) {
- const [state, setState] = useState(initialPlayerState);
- const stateRef = useRef(null);
-
- useEffect(() => {
- const player = ref.current;
- const wrapper = wrapperRef.current;
- if (player && wrapper) {
- readState(player, setState);
- registerListeners(player, setState);
- setState((s) => ({
- ...s,
- ...populateControls(player, wrapper, setState as any, stateRef),
- }));
- }
- }, [ref, wrapperRef, stateRef]);
-
- useEffect(() => {
- stateRef.current = state;
- }, [state, stateRef]);
-
- return {
- playerState: state,
- };
-}
diff --git a/__old/hooks/utils.ts b/__old/hooks/utils.ts
deleted file mode 100644
index ee19ae6a..00000000
--- a/__old/hooks/utils.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-export function handleBuffered(time: number, buffered: TimeRanges): number {
- for (let i = 0; i < buffered.length; i += 1) {
- if (buffered.start(buffered.length - 1 - i) < time) {
- return buffered.end(buffered.length - 1 - i);
- }
- }
- return 0;
-}
diff --git a/__old/hooks/volumeStore.ts b/__old/hooks/volumeStore.ts
deleted file mode 100644
index 3b328810..00000000
--- a/__old/hooks/volumeStore.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import { versionedStoreBuilder } from "@/utils/storage";
-
-export const volumeStore = versionedStoreBuilder()
- .setKey("mw-volume")
- .addVersion({
- version: 0,
- create() {
- return {
- volume: 1,
- };
- },
- })
- .build();
-
-export function getStoredVolume(): number {
- const store = volumeStore.get();
- return store.volume;
-}
-
-export function setStoredVolume(volume: number) {
- const store = volumeStore.get();
- store.save({
- volume,
- });
-}
diff --git a/__old/parts/VideoErrorBoundary.tsx b/__old/parts/VideoErrorBoundary.tsx
deleted file mode 100644
index 205e27ae..00000000
--- a/__old/parts/VideoErrorBoundary.tsx
+++ /dev/null
@@ -1,83 +0,0 @@
-import { MWMediaMeta } from "@/backend/metadata/types";
-import { ErrorMessage } from "@/components/layout/ErrorBoundary";
-import { Link } from "@/components/text/Link";
-import { conf } from "@/setup/config";
-import { Component, ReactNode } from "react";
-import { VideoPlayerHeader } from "./VideoPlayerHeader";
-
-interface ErrorBoundaryState {
- hasError: boolean;
- error?: {
- name: string;
- description: string;
- path: string;
- };
-}
-
-interface VideoErrorBoundaryProps {
- children?: ReactNode;
- media?: MWMediaMeta;
- onGoBack?: () => void;
-}
-
-export class VideoErrorBoundary extends Component<
- VideoErrorBoundaryProps,
- ErrorBoundaryState
-> {
- constructor(props: VideoErrorBoundaryProps) {
- super(props);
- this.state = {
- hasError: false,
- };
- }
-
- static getDerivedStateFromError() {
- return {
- hasError: true,
- };
- }
-
- componentDidCatch(error: any, errorInfo: any) {
- console.error("Render error caught", error, errorInfo);
- if (error instanceof Error) {
- const realError: Error = error as Error;
- this.setState((s) => ({
- ...s,
- hasError: true,
- error: {
- name: realError.name,
- description: realError.message,
- path: errorInfo.componentStack.split("\n")[1],
- },
- }));
- }
- }
-
- render() {
- if (!this.state.hasError) return this.props.children;
-
- // TODO make responsive, needs to work in tiny player
-
- return (
-
-
-
-
-
- The video player encounted a fatal error, please report it to the{" "}
-
- Discord server
- {" "}
- or on{" "}
-
- GitHub
-
- .
-
-
- );
- }
-}
diff --git a/__old/parts/VideoPlayerError.tsx b/__old/parts/VideoPlayerError.tsx
deleted file mode 100644
index 4b3e42da..00000000
--- a/__old/parts/VideoPlayerError.tsx
+++ /dev/null
@@ -1,36 +0,0 @@
-import { MWMediaMeta } from "@/backend/metadata/types";
-import { IconPatch } from "@/components/buttons/IconPatch";
-import { Icons } from "@/components/Icon";
-import { Title } from "@/components/text/Title";
-import { ReactNode } from "react";
-import { useVideoPlayerState } from "../VideoContext";
-import { VideoPlayerHeader } from "./VideoPlayerHeader";
-
-interface VideoPlayerErrorProps {
- media?: MWMediaMeta;
- onGoBack?: () => void;
- children?: ReactNode;
-}
-
-export function VideoPlayerError(props: VideoPlayerErrorProps) {
- const { videoState } = useVideoPlayerState();
-
- const err = videoState.error;
-
- if (!err) return props.children as any;
-
- return (
-
-
-
-
Failed to load media
-
- {err.name}: {err.description}
-
-
-
-
-
-
- );
-}
diff --git a/__old/parts/VideoPlayerHeader.tsx b/__old/parts/VideoPlayerHeader.tsx
deleted file mode 100644
index e66769d3..00000000
--- a/__old/parts/VideoPlayerHeader.tsx
+++ /dev/null
@@ -1,68 +0,0 @@
-import { MWMediaMeta } from "@/backend/metadata/types";
-import { IconPatch } from "@/components/buttons/IconPatch";
-import { Icon, Icons } from "@/components/Icon";
-import { BrandPill } from "@/components/layout/BrandPill";
-import {
- getIfBookmarkedFromPortable,
- useBookmarkContext,
-} from "@/state/bookmark";
-import { AirplayControl } from "../controls/AirplayControl";
-import { ChromeCastControl } from "../controls/ChromeCastControl";
-
-interface VideoPlayerHeaderProps {
- media?: MWMediaMeta;
- onClick?: () => void;
- isMobile?: boolean;
-}
-
-export function VideoPlayerHeader(props: VideoPlayerHeaderProps) {
- const { bookmarkStore, setItemBookmark } = useBookmarkContext();
- const isBookmarked = props.media
- ? getIfBookmarkedFromPortable(bookmarkStore.bookmarks, props.media)
- : false;
- const showDivider = props.media && props.onClick;
- return (
-
-
-
- {props.onClick ? (
-
-
- Back to home
-
- ) : null}
- {showDivider ? (
-
- ) : null}
- {props.media ? (
-
- {props.media.title}
-
- ) : null}
-
- {props.media && (
-
- props.media && setItemBookmark(props.media, !isBookmarked)
- }
- />
- )}
-
- {props.isMobile ? (
- <>
-
-
- >
- ) : (
-
- )}
-
- );
-}
diff --git a/__old/parts/VideoPlayerIconButton.tsx b/__old/parts/VideoPlayerIconButton.tsx
deleted file mode 100644
index 4db685da..00000000
--- a/__old/parts/VideoPlayerIconButton.tsx
+++ /dev/null
@@ -1,27 +0,0 @@
-import { Icon, Icons } from "@/components/Icon";
-import React from "react";
-
-export interface VideoPlayerIconButtonProps {
- onClick?: (e: React.MouseEvent) => void;
- icon: Icons;
- text?: string;
- className?: string;
- iconSize?: string;
-}
-
-export function VideoPlayerIconButton(props: VideoPlayerIconButtonProps) {
- return (
-
-
-
- );
-}
diff --git a/__old/parts/VideoPopout.tsx b/__old/parts/VideoPopout.tsx
deleted file mode 100644
index f61d6d56..00000000
--- a/__old/parts/VideoPopout.tsx
+++ /dev/null
@@ -1,60 +0,0 @@
-import { useEffect, useRef } from "react";
-import { useVideoPlayerState } from "../VideoContext";
-
-interface Props {
- children?: React.ReactNode;
- id?: string;
- className?: string;
-}
-
-// TODO store popout in router history so you can press back to yeet
-// TODO add transition
-export function VideoPopout(props: Props) {
- const { videoState } = useVideoPlayerState();
- const popoutRef = useRef(null);
- const isOpen = videoState.popout === props.id;
-
- useEffect(() => {
- if (!isOpen) return;
- const popoutEl = popoutRef.current;
- function windowClick(e: MouseEvent) {
- const rect = popoutEl?.getBoundingClientRect();
- if (rect) {
- if (
- e.pageX >= rect.x &&
- e.pageX <= rect.x + rect.width &&
- e.pageY >= rect.y &&
- e.pageY <= rect.y + rect.height
- ) {
- // inside bounding box of popout
- return;
- }
- }
-
- videoState.closePopout();
- }
-
- window.addEventListener("click", windowClick);
- return () => {
- window.removeEventListener("click", windowClick);
- };
- }, [isOpen, videoState]);
-
- return (
-
-
-
- {isOpen ? props.children : null}
-
-
-
- );
-}