mirror of
https://github.com/sussy-code/smov.git
synced 2024-12-21 14:47:41 +01:00
meta data in video player
This commit is contained in:
parent
27ef9be6b1
commit
bb14d63a9c
20 changed files with 492 additions and 168 deletions
|
@ -2,13 +2,18 @@ import { Transition } from "@/components/Transition";
|
||||||
import { useIsMobile } from "@/hooks/useIsMobile";
|
import { useIsMobile } from "@/hooks/useIsMobile";
|
||||||
import { BackdropAction } from "@/video/components/actions/BackdropAction";
|
import { BackdropAction } from "@/video/components/actions/BackdropAction";
|
||||||
import { FullscreenAction } from "@/video/components/actions/FullscreenAction";
|
import { FullscreenAction } from "@/video/components/actions/FullscreenAction";
|
||||||
|
import { HeaderAction } from "@/video/components/actions/HeaderAction";
|
||||||
import { LoadingAction } from "@/video/components/actions/LoadingAction";
|
import { LoadingAction } from "@/video/components/actions/LoadingAction";
|
||||||
import { MiddlePauseAction } from "@/video/components/actions/MiddlePauseAction";
|
import { MiddlePauseAction } from "@/video/components/actions/MiddlePauseAction";
|
||||||
import { MobileCenterAction } from "@/video/components/actions/MobileCenterAction";
|
import { MobileCenterAction } from "@/video/components/actions/MobileCenterAction";
|
||||||
|
import { PageTitleAction } from "@/video/components/actions/PageTitleAction";
|
||||||
import { PauseAction } from "@/video/components/actions/PauseAction";
|
import { PauseAction } from "@/video/components/actions/PauseAction";
|
||||||
import { ProgressAction } from "@/video/components/actions/ProgressAction";
|
import { ProgressAction } from "@/video/components/actions/ProgressAction";
|
||||||
|
import { QualityDisplayAction } from "@/video/components/actions/QualityDisplayAction";
|
||||||
|
import { ShowTitleAction } from "@/video/components/actions/ShowTitleAction";
|
||||||
import { SkipTimeAction } from "@/video/components/actions/SkipTimeAction";
|
import { SkipTimeAction } from "@/video/components/actions/SkipTimeAction";
|
||||||
import { TimeAction } from "@/video/components/actions/TimeAction";
|
import { TimeAction } from "@/video/components/actions/TimeAction";
|
||||||
|
import { VideoPlayerError } from "@/video/components/parts/VideoPlayerError";
|
||||||
import {
|
import {
|
||||||
VideoPlayerBase,
|
VideoPlayerBase,
|
||||||
VideoPlayerBaseProps,
|
VideoPlayerBaseProps,
|
||||||
|
@ -17,6 +22,10 @@ import { useVideoPlayerDescriptor } from "@/video/state/hooks";
|
||||||
import { useControls } from "@/video/state/logic/controls";
|
import { useControls } from "@/video/state/logic/controls";
|
||||||
import { ReactNode, useCallback, useState } from "react";
|
import { ReactNode, useCallback, useState } from "react";
|
||||||
|
|
||||||
|
type Props = VideoPlayerBaseProps & {
|
||||||
|
onGoBack?: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
function CenterPosition(props: { children: ReactNode }) {
|
function CenterPosition(props: { children: ReactNode }) {
|
||||||
return (
|
return (
|
||||||
<div className="absolute inset-0 flex items-center justify-center">
|
<div className="absolute inset-0 flex items-center justify-center">
|
||||||
|
@ -48,12 +57,12 @@ function LeftSideControls() {
|
||||||
{/* <VolumeControl className="mr-2" /> */}
|
{/* <VolumeControl className="mr-2" /> */}
|
||||||
<TimeAction />
|
<TimeAction />
|
||||||
</div>
|
</div>
|
||||||
{/* <ShowTitleControl /> */}
|
<ShowTitleAction />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function VideoPlayer(props: VideoPlayerBaseProps) {
|
export function VideoPlayer(props: Props) {
|
||||||
const [show, setShow] = useState(false);
|
const [show, setShow] = useState(false);
|
||||||
const { isMobile } = useIsMobile();
|
const { isMobile } = useIsMobile();
|
||||||
|
|
||||||
|
@ -65,76 +74,72 @@ export function VideoPlayer(props: VideoPlayerBaseProps) {
|
||||||
);
|
);
|
||||||
|
|
||||||
// TODO autoplay
|
// TODO autoplay
|
||||||
// TODO meta data
|
// TODO safe area only if full screen or fill screen
|
||||||
return (
|
return (
|
||||||
<VideoPlayerBase>
|
<VideoPlayerBase>
|
||||||
{/* <PageTitleControl media={props.media?.meta} /> */}
|
<PageTitleAction />
|
||||||
{/* <VideoPlayerError media={props.media?.meta} onGoBack={props.onGoBack}> */}
|
<VideoPlayerError onGoBack={props.onGoBack}>
|
||||||
<BackdropAction onBackdropChange={onBackdropChange}>
|
<BackdropAction onBackdropChange={onBackdropChange}>
|
||||||
<CenterPosition>
|
<CenterPosition>
|
||||||
<LoadingAction />
|
<LoadingAction />
|
||||||
</CenterPosition>
|
</CenterPosition>
|
||||||
<CenterPosition>
|
<CenterPosition>
|
||||||
<MiddlePauseAction />
|
<MiddlePauseAction />
|
||||||
</CenterPosition>
|
</CenterPosition>
|
||||||
{isMobile ? (
|
{isMobile ? (
|
||||||
|
<Transition
|
||||||
|
animation="fade"
|
||||||
|
show={show}
|
||||||
|
className="absolute inset-0 flex items-center justify-center"
|
||||||
|
>
|
||||||
|
<MobileCenterAction />
|
||||||
|
</Transition>
|
||||||
|
) : (
|
||||||
|
""
|
||||||
|
)}
|
||||||
<Transition
|
<Transition
|
||||||
animation="fade"
|
animation="slide-down"
|
||||||
show={show}
|
show={show}
|
||||||
className="absolute inset-0 flex items-center justify-center"
|
className="pointer-events-auto absolute inset-x-0 top-0 flex flex-col py-6 px-8 pb-2"
|
||||||
>
|
>
|
||||||
<MobileCenterAction />
|
<HeaderAction showControls={isMobile} onClick={props.onGoBack} />
|
||||||
</Transition>
|
</Transition>
|
||||||
) : (
|
<Transition
|
||||||
""
|
animation="slide-up"
|
||||||
)}
|
show={show}
|
||||||
<Transition
|
className="pointer-events-auto absolute inset-x-0 bottom-0 flex flex-col px-4 pb-2 [margin-bottom:env(safe-area-inset-bottom)]"
|
||||||
animation="slide-down"
|
>
|
||||||
show={show}
|
<div className="flex w-full items-center space-x-3">
|
||||||
className="pointer-events-auto absolute inset-x-0 top-0 flex flex-col py-6 px-8 pb-2"
|
{isMobile && <TimeAction noDuration />}
|
||||||
>
|
<ProgressAction />
|
||||||
{/* <VideoPlayerHeader
|
</div>
|
||||||
media={props.media?.meta}
|
<div className="flex items-center">
|
||||||
onClick={props.onGoBack}
|
{isMobile ? (
|
||||||
isMobile={isMobile}
|
<div className="grid w-full grid-cols-[56px,1fr,56px] items-center">
|
||||||
/> */}
|
<div />
|
||||||
</Transition>
|
<div className="flex items-center justify-center">
|
||||||
<Transition
|
{/* <SeriesSelectionControl /> */}
|
||||||
animation="slide-up"
|
{/* <SourceSelectionControl media={props.media} /> */}
|
||||||
show={show}
|
</div>
|
||||||
className="pointer-events-auto absolute inset-x-0 bottom-0 flex flex-col px-4 pb-2 [margin-bottom:env(safe-area-inset-bottom)]"
|
<FullscreenAction />
|
||||||
>
|
|
||||||
<div className="flex w-full items-center space-x-3">
|
|
||||||
{isMobile && <TimeAction noDuration />}
|
|
||||||
<ProgressAction />
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center">
|
|
||||||
{isMobile ? (
|
|
||||||
<div className="grid w-full grid-cols-[56px,1fr,56px] items-center">
|
|
||||||
<div />
|
|
||||||
<div className="flex items-center justify-center">
|
|
||||||
{/* <SeriesSelectionControl /> */}
|
|
||||||
{/* <SourceSelectionControl media={props.media} /> */}
|
|
||||||
</div>
|
</div>
|
||||||
<FullscreenAction />
|
) : (
|
||||||
</div>
|
<>
|
||||||
) : (
|
<LeftSideControls />
|
||||||
<>
|
<div className="flex-1" />
|
||||||
<LeftSideControls />
|
<QualityDisplayAction />
|
||||||
<div className="flex-1" />
|
{/* <SeriesSelectionControl />
|
||||||
{/* <QualityDisplayControl />
|
|
||||||
<SeriesSelectionControl />
|
|
||||||
<SourceSelectionControl media={props.media} />
|
<SourceSelectionControl media={props.media} />
|
||||||
<AirplayControl />
|
<AirplayControl />
|
||||||
<ChromeCastControl /> */}
|
<ChromeCastControl /> */}
|
||||||
<FullscreenAction />
|
<FullscreenAction />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</Transition>
|
</Transition>
|
||||||
</BackdropAction>
|
</BackdropAction>
|
||||||
{props.children}
|
{props.children}
|
||||||
{/* </VideoPlayerError> */}
|
</VideoPlayerError>
|
||||||
</VideoPlayerBase>
|
</VideoPlayerBase>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
15
src/video/components/actions/HeaderAction.tsx
Normal file
15
src/video/components/actions/HeaderAction.tsx
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import { VideoPlayerHeader } from "@/video/components/parts/VideoPlayerHeader";
|
||||||
|
import { useVideoPlayerDescriptor } from "@/video/state/hooks";
|
||||||
|
import { useMeta } from "@/video/state/logic/meta";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
onClick?: () => void;
|
||||||
|
showControls?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function HeaderAction(props: Props) {
|
||||||
|
const descriptor = useVideoPlayerDescriptor();
|
||||||
|
const meta = useMeta(descriptor);
|
||||||
|
|
||||||
|
return <VideoPlayerHeader media={meta?.meta} {...props} />;
|
||||||
|
}
|
19
src/video/components/actions/PageTitleAction.tsx
Normal file
19
src/video/components/actions/PageTitleAction.tsx
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import { useVideoPlayerDescriptor } from "@/video/state/hooks";
|
||||||
|
import { Helmet } from "react-helmet";
|
||||||
|
import { useCurrentSeriesEpisodeInfo } from "../hooks/useCurrentSeriesEpisodeInfo";
|
||||||
|
|
||||||
|
export function PageTitleAction() {
|
||||||
|
const descriptor = useVideoPlayerDescriptor();
|
||||||
|
const { isSeries, humanizedEpisodeId, meta } =
|
||||||
|
useCurrentSeriesEpisodeInfo(descriptor);
|
||||||
|
|
||||||
|
if (!meta) return null;
|
||||||
|
|
||||||
|
const title = isSeries ? `${meta.title} - ${humanizedEpisodeId}` : meta.title;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Helmet>
|
||||||
|
<title>{title}</title>
|
||||||
|
</Helmet>
|
||||||
|
);
|
||||||
|
}
|
17
src/video/components/actions/QualityDisplayAction.tsx
Normal file
17
src/video/components/actions/QualityDisplayAction.tsx
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import { useVideoPlayerDescriptor } from "@/video/state/hooks";
|
||||||
|
import { useSource } from "@/video/state/logic/source";
|
||||||
|
|
||||||
|
export function QualityDisplayAction() {
|
||||||
|
const descriptor = useVideoPlayerDescriptor();
|
||||||
|
const source = useSource(descriptor);
|
||||||
|
|
||||||
|
if (!source.source) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="rounded-md bg-denim-300 py-1 px-2 transition-colors">
|
||||||
|
<p className="text-center text-xs font-bold text-slate-300 transition-colors">
|
||||||
|
{source.source.quality}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
17
src/video/components/actions/ShowTitleAction.tsx
Normal file
17
src/video/components/actions/ShowTitleAction.tsx
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import { useVideoPlayerDescriptor } from "@/video/state/hooks";
|
||||||
|
import { useCurrentSeriesEpisodeInfo } from "../hooks/useCurrentSeriesEpisodeInfo";
|
||||||
|
|
||||||
|
export function ShowTitleAction() {
|
||||||
|
const descriptor = useVideoPlayerDescriptor();
|
||||||
|
const { isSeries, currentEpisodeInfo, humanizedEpisodeId } =
|
||||||
|
useCurrentSeriesEpisodeInfo(descriptor);
|
||||||
|
|
||||||
|
if (!isSeries) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<p className="ml-8 select-none space-x-2 text-white">
|
||||||
|
<span>{humanizedEpisodeId}</span>
|
||||||
|
<span className="opacity-50">{currentEpisodeInfo?.title}</span>
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
}
|
19
src/video/components/controllers/MetaController.tsx
Normal file
19
src/video/components/controllers/MetaController.tsx
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import { MWMediaMeta } from "@/backend/metadata/types";
|
||||||
|
import { useVideoPlayerDescriptor } from "@/video/state/hooks";
|
||||||
|
import { useControls } from "@/video/state/logic/controls";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
|
||||||
|
interface MetaControllerProps {
|
||||||
|
meta?: MWMediaMeta;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function MetaController(props: MetaControllerProps) {
|
||||||
|
const descriptor = useVideoPlayerDescriptor();
|
||||||
|
const controls = useControls(descriptor);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
controls.setMeta(props.meta);
|
||||||
|
}, [props, controls]);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
35
src/video/components/hooks/useCurrentSeriesEpisodeInfo.ts
Normal file
35
src/video/components/hooks/useCurrentSeriesEpisodeInfo.ts
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
import { MWMediaType } from "@/backend/metadata/types";
|
||||||
|
import { useMeta } from "@/video/state/logic/meta";
|
||||||
|
import { useMemo } from "react";
|
||||||
|
|
||||||
|
export function useCurrentSeriesEpisodeInfo(descriptor: string) {
|
||||||
|
const meta = useMeta(descriptor);
|
||||||
|
|
||||||
|
const currentSeasonInfo = useMemo(() => {
|
||||||
|
return meta?.seasons?.find(
|
||||||
|
(season) => season.id === meta?.episode?.seasonId
|
||||||
|
);
|
||||||
|
}, [meta]);
|
||||||
|
|
||||||
|
const currentEpisodeInfo = useMemo(() => {
|
||||||
|
return currentSeasonInfo?.episodes?.find(
|
||||||
|
(episode) => episode.id === meta?.episode?.episodeId
|
||||||
|
);
|
||||||
|
}, [currentSeasonInfo, meta]);
|
||||||
|
|
||||||
|
const isSeries = Boolean(
|
||||||
|
meta?.meta?.type === MWMediaType.SERIES && meta?.episode
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!isSeries) return { isSeries: false };
|
||||||
|
|
||||||
|
const humanizedEpisodeId = `S${currentSeasonInfo?.number} E${currentEpisodeInfo?.number}`;
|
||||||
|
|
||||||
|
return {
|
||||||
|
isSeries: true,
|
||||||
|
humanizedEpisodeId,
|
||||||
|
currentSeasonInfo,
|
||||||
|
currentEpisodeInfo,
|
||||||
|
meta: meta?.meta,
|
||||||
|
};
|
||||||
|
}
|
37
src/video/components/parts/VideoPlayerError.tsx
Normal file
37
src/video/components/parts/VideoPlayerError.tsx
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
import { IconPatch } from "@/components/buttons/IconPatch";
|
||||||
|
import { Icons } from "@/components/Icon";
|
||||||
|
import { Title } from "@/components/text/Title";
|
||||||
|
import { useVideoPlayerDescriptor } from "@/video/state/hooks";
|
||||||
|
import { useMeta } from "@/video/state/logic/meta";
|
||||||
|
import { ReactNode } from "react";
|
||||||
|
import { VideoPlayerHeader } from "./VideoPlayerHeader";
|
||||||
|
|
||||||
|
interface VideoPlayerErrorProps {
|
||||||
|
onGoBack?: () => void;
|
||||||
|
children?: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function VideoPlayerError(props: VideoPlayerErrorProps) {
|
||||||
|
const descriptor = useVideoPlayerDescriptor();
|
||||||
|
const meta = useMeta(descriptor);
|
||||||
|
// TODO add error state
|
||||||
|
|
||||||
|
const err = null as any;
|
||||||
|
|
||||||
|
if (!err) return props.children as any;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="absolute inset-0 flex flex-col items-center justify-center bg-denim-100">
|
||||||
|
<IconPatch icon={Icons.WARNING} className="mb-6 text-red-400" />
|
||||||
|
<Title>Failed to load media</Title>
|
||||||
|
<p className="my-6 max-w-lg">
|
||||||
|
{err?.name}: {err?.description}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="pointer-events-auto absolute inset-x-0 top-0 flex flex-col py-6 px-8 pb-2">
|
||||||
|
<VideoPlayerHeader media={meta?.meta} onClick={props.onGoBack} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
65
src/video/components/parts/VideoPlayerHeader.tsx
Normal file
65
src/video/components/parts/VideoPlayerHeader.tsx
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
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";
|
||||||
|
|
||||||
|
interface VideoPlayerHeaderProps {
|
||||||
|
media?: MWMediaMeta;
|
||||||
|
onClick?: () => void;
|
||||||
|
showControls?: 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 (
|
||||||
|
<div className="flex items-center">
|
||||||
|
<div className="flex flex-1 items-center">
|
||||||
|
<p className="flex items-center">
|
||||||
|
{props.onClick ? (
|
||||||
|
<span
|
||||||
|
onClick={props.onClick}
|
||||||
|
className="flex cursor-pointer items-center py-1 text-white opacity-50 transition-opacity hover:opacity-100"
|
||||||
|
>
|
||||||
|
<Icon className="mr-2" icon={Icons.ARROW_LEFT} />
|
||||||
|
<span>Back to home</span>
|
||||||
|
</span>
|
||||||
|
) : null}
|
||||||
|
{showDivider ? (
|
||||||
|
<span className="mx-4 h-6 w-[1.5px] rotate-[30deg] bg-white opacity-50" />
|
||||||
|
) : null}
|
||||||
|
{props.media ? (
|
||||||
|
<span className="flex items-center text-white">
|
||||||
|
<span>{props.media.title}</span>
|
||||||
|
</span>
|
||||||
|
) : null}
|
||||||
|
</p>
|
||||||
|
{props.media && (
|
||||||
|
<IconPatch
|
||||||
|
clickable
|
||||||
|
transparent
|
||||||
|
icon={isBookmarked ? Icons.BOOKMARK : Icons.BOOKMARK_OUTLINE}
|
||||||
|
className="ml-2 text-white"
|
||||||
|
onClick={() =>
|
||||||
|
props.media && setItemBookmark(props.media, !isBookmarked)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{props.showControls ? null : (
|
||||||
|
// <>
|
||||||
|
// <AirplayControl />
|
||||||
|
// <ChromeCastControl />
|
||||||
|
// </>
|
||||||
|
<BrandPill />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
|
@ -2,7 +2,8 @@ export type VideoPlayerEvent =
|
||||||
| "mediaplaying"
|
| "mediaplaying"
|
||||||
| "source"
|
| "source"
|
||||||
| "progress"
|
| "progress"
|
||||||
| "interface";
|
| "interface"
|
||||||
|
| "meta";
|
||||||
|
|
||||||
function createEventString(id: string, event: VideoPlayerEvent): string {
|
function createEventString(id: string, event: VideoPlayerEvent): string {
|
||||||
return `_vid:::${id}:::${event}`;
|
return `_vid:::${id}:::${event}`;
|
||||||
|
|
|
@ -4,29 +4,38 @@ import { VideoPlayerState } from "./types";
|
||||||
|
|
||||||
function initPlayer(): VideoPlayerState {
|
function initPlayer(): VideoPlayerState {
|
||||||
return {
|
return {
|
||||||
isPlaying: false,
|
interface: {
|
||||||
isPaused: true,
|
popout: null,
|
||||||
isFullscreen: false,
|
isFullscreen: false,
|
||||||
isFocused: false,
|
isFocused: false,
|
||||||
isLoading: false,
|
leftControlHovering: false,
|
||||||
isSeeking: false,
|
},
|
||||||
isFirstLoading: true,
|
|
||||||
time: 0,
|
mediaPlaying: {
|
||||||
duration: 0,
|
isPlaying: false,
|
||||||
|
isPaused: true,
|
||||||
|
isLoading: false,
|
||||||
|
isSeeking: false,
|
||||||
|
isFirstLoading: true,
|
||||||
|
hasPlayedOnce: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
progress: {
|
||||||
|
time: 0,
|
||||||
|
duration: 0,
|
||||||
|
buffered: 0,
|
||||||
|
},
|
||||||
|
|
||||||
|
meta: null,
|
||||||
|
source: null,
|
||||||
|
error: null,
|
||||||
|
|
||||||
volume: 0,
|
volume: 0,
|
||||||
buffered: 0,
|
|
||||||
pausedWhenSeeking: false,
|
pausedWhenSeeking: false,
|
||||||
hasInitialized: false,
|
hasInitialized: false,
|
||||||
leftControlHovering: false,
|
|
||||||
hasPlayedOnce: false,
|
|
||||||
error: null,
|
|
||||||
popout: null,
|
|
||||||
seasonData: {
|
|
||||||
isSeries: false,
|
|
||||||
},
|
|
||||||
canAirplay: false,
|
canAirplay: false,
|
||||||
|
|
||||||
stateProvider: null,
|
stateProvider: null,
|
||||||
source: null,
|
|
||||||
wrapperElement: null,
|
wrapperElement: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
|
import { MWMediaMeta } from "@/backend/metadata/types";
|
||||||
import { updateInterface } from "@/video/state/logic/interface";
|
import { updateInterface } from "@/video/state/logic/interface";
|
||||||
|
import { updateMeta } from "@/video/state/logic/meta";
|
||||||
import { getPlayerState } from "../cache";
|
import { getPlayerState } from "../cache";
|
||||||
import { VideoPlayerStateController } from "../providers/providerTypes";
|
import { VideoPlayerStateController } from "../providers/providerTypes";
|
||||||
|
|
||||||
|
@ -7,6 +9,7 @@ type ControlMethods = {
|
||||||
closePopout(): void;
|
closePopout(): void;
|
||||||
setLeftControlsHover(hovering: boolean): void;
|
setLeftControlsHover(hovering: boolean): void;
|
||||||
setFocused(focused: boolean): void;
|
setFocused(focused: boolean): void;
|
||||||
|
setMeta(meta?: MWMediaMeta): void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function useControls(
|
export function useControls(
|
||||||
|
@ -40,20 +43,30 @@ export function useControls(
|
||||||
|
|
||||||
// other controls
|
// other controls
|
||||||
setLeftControlsHover(hovering) {
|
setLeftControlsHover(hovering) {
|
||||||
state.leftControlHovering = hovering;
|
state.interface.leftControlHovering = hovering;
|
||||||
updateInterface(descriptor, state);
|
updateInterface(descriptor, state);
|
||||||
},
|
},
|
||||||
openPopout(id: string) {
|
openPopout(id: string) {
|
||||||
state.popout = id;
|
state.interface.popout = id;
|
||||||
updateInterface(descriptor, state);
|
updateInterface(descriptor, state);
|
||||||
},
|
},
|
||||||
closePopout() {
|
closePopout() {
|
||||||
state.popout = null;
|
state.interface.popout = null;
|
||||||
updateInterface(descriptor, state);
|
updateInterface(descriptor, state);
|
||||||
},
|
},
|
||||||
setFocused(focused) {
|
setFocused(focused) {
|
||||||
state.isFocused = focused;
|
state.interface.isFocused = focused;
|
||||||
updateInterface(descriptor, state);
|
updateInterface(descriptor, state);
|
||||||
},
|
},
|
||||||
|
setMeta(meta) {
|
||||||
|
if (!meta) {
|
||||||
|
state.meta = null;
|
||||||
|
} else {
|
||||||
|
state.meta = {
|
||||||
|
meta,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
updateMeta(descriptor, state);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,10 +12,10 @@ export type VideoInterfaceEvent = {
|
||||||
|
|
||||||
function getInterfaceFromState(state: VideoPlayerState): VideoInterfaceEvent {
|
function getInterfaceFromState(state: VideoPlayerState): VideoInterfaceEvent {
|
||||||
return {
|
return {
|
||||||
popout: state.popout,
|
popout: state.interface.popout,
|
||||||
leftControlHovering: state.leftControlHovering,
|
leftControlHovering: state.interface.leftControlHovering,
|
||||||
isFocused: state.isFocused,
|
isFocused: state.interface.isFocused,
|
||||||
isFullscreen: state.isFullscreen,
|
isFullscreen: state.interface.isFullscreen,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,12 +16,12 @@ function getMediaPlayingFromState(
|
||||||
state: VideoPlayerState
|
state: VideoPlayerState
|
||||||
): VideoMediaPlayingEvent {
|
): VideoMediaPlayingEvent {
|
||||||
return {
|
return {
|
||||||
hasPlayedOnce: state.hasPlayedOnce,
|
hasPlayedOnce: state.mediaPlaying.hasPlayedOnce,
|
||||||
isLoading: state.isLoading,
|
isLoading: state.mediaPlaying.isLoading,
|
||||||
isPaused: state.isPaused,
|
isPaused: state.mediaPlaying.isPaused,
|
||||||
isPlaying: state.isPlaying,
|
isPlaying: state.mediaPlaying.isPlaying,
|
||||||
isSeeking: state.isSeeking,
|
isSeeking: state.mediaPlaying.isSeeking,
|
||||||
isFirstLoading: state.isFirstLoading,
|
isFirstLoading: state.mediaPlaying.isFirstLoading,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
35
src/video/state/logic/meta.ts
Normal file
35
src/video/state/logic/meta.ts
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { getPlayerState } from "../cache";
|
||||||
|
import { listenEvent, sendEvent, unlistenEvent } from "../events";
|
||||||
|
import { VideoPlayerMeta, VideoPlayerState } from "../types";
|
||||||
|
|
||||||
|
export type VideoMetaEvent = VideoPlayerMeta | null;
|
||||||
|
|
||||||
|
function getMetaFromState(state: VideoPlayerState): VideoMetaEvent {
|
||||||
|
return state.meta
|
||||||
|
? {
|
||||||
|
...state.meta,
|
||||||
|
}
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateMeta(descriptor: string, state: VideoPlayerState) {
|
||||||
|
sendEvent<VideoMetaEvent>(descriptor, "meta", getMetaFromState(state));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useMeta(descriptor: string): VideoMetaEvent {
|
||||||
|
const state = getPlayerState(descriptor);
|
||||||
|
const [data, setData] = useState<VideoMetaEvent>(getMetaFromState(state));
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
function update(payload: CustomEvent<VideoMetaEvent>) {
|
||||||
|
setData(payload.detail);
|
||||||
|
}
|
||||||
|
listenEvent(descriptor, "meta", update);
|
||||||
|
return () => {
|
||||||
|
unlistenEvent(descriptor, "meta", update);
|
||||||
|
};
|
||||||
|
}, [descriptor]);
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
|
@ -11,9 +11,9 @@ export type VideoProgressEvent = {
|
||||||
|
|
||||||
function getProgressFromState(state: VideoPlayerState): VideoProgressEvent {
|
function getProgressFromState(state: VideoPlayerState): VideoProgressEvent {
|
||||||
return {
|
return {
|
||||||
time: state.time,
|
time: state.progress.time,
|
||||||
duration: state.duration,
|
duration: state.progress.duration,
|
||||||
buffered: state.buffered,
|
buffered: state.progress.buffered,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ import {
|
||||||
} from "@/utils/detectFeatures";
|
} from "@/utils/detectFeatures";
|
||||||
import { MWStreamType } from "@/backend/helpers/streams";
|
import { MWStreamType } from "@/backend/helpers/streams";
|
||||||
import { updateInterface } from "@/video/state/logic/interface";
|
import { updateInterface } from "@/video/state/logic/interface";
|
||||||
|
import { updateSource } from "@/video/state/logic/source";
|
||||||
import { getPlayerState } from "../cache";
|
import { getPlayerState } from "../cache";
|
||||||
import { updateMediaPlaying } from "../logic/mediaplaying";
|
import { updateMediaPlaying } from "../logic/mediaplaying";
|
||||||
import { VideoPlayerStateProvider } from "./providerTypes";
|
import { VideoPlayerStateProvider } from "./providerTypes";
|
||||||
|
@ -51,7 +52,7 @@ export function createVideoStateProvider(
|
||||||
|
|
||||||
// update state
|
// update state
|
||||||
player.currentTime = time;
|
player.currentTime = time;
|
||||||
state.time = time;
|
state.progress.time = time;
|
||||||
updateProgress(descriptor, state);
|
updateProgress(descriptor, state);
|
||||||
},
|
},
|
||||||
setSeeking(active) {
|
setSeeking(active) {
|
||||||
|
@ -63,12 +64,14 @@ export function createVideoStateProvider(
|
||||||
|
|
||||||
// when seeking we pause the video
|
// when seeking we pause the video
|
||||||
// this variables isnt reactive, just used so the state can be remembered next unseek
|
// this variables isnt reactive, just used so the state can be remembered next unseek
|
||||||
state.pausedWhenSeeking = state.isPaused;
|
state.pausedWhenSeeking = state.mediaPlaying.isPaused;
|
||||||
this.pause();
|
this.pause();
|
||||||
},
|
},
|
||||||
setSource(source) {
|
setSource(source) {
|
||||||
if (!source) {
|
if (!source) {
|
||||||
player.src = "";
|
player.src = "";
|
||||||
|
state.source = null;
|
||||||
|
updateSource(descriptor, state);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,52 +108,63 @@ export function createVideoStateProvider(
|
||||||
} else if (source.type === MWStreamType.MP4) {
|
} else if (source.type === MWStreamType.MP4) {
|
||||||
player.src = source.source;
|
player.src = source.source;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// update state
|
||||||
|
state.source = {
|
||||||
|
quality: source.quality,
|
||||||
|
type: source.type,
|
||||||
|
url: source.source,
|
||||||
|
};
|
||||||
|
updateSource(descriptor, state);
|
||||||
},
|
},
|
||||||
providerStart() {
|
providerStart() {
|
||||||
// TODO stored volume
|
// TODO stored volume
|
||||||
const pause = () => {
|
const pause = () => {
|
||||||
state.isPaused = true;
|
state.mediaPlaying.isPaused = true;
|
||||||
state.isPlaying = false;
|
state.mediaPlaying.isPlaying = false;
|
||||||
updateMediaPlaying(descriptor, state);
|
updateMediaPlaying(descriptor, state);
|
||||||
};
|
};
|
||||||
const playing = () => {
|
const playing = () => {
|
||||||
state.isPaused = false;
|
state.mediaPlaying.isPaused = false;
|
||||||
state.isPlaying = true;
|
state.mediaPlaying.isPlaying = true;
|
||||||
state.isLoading = false;
|
state.mediaPlaying.isLoading = false;
|
||||||
state.hasPlayedOnce = true;
|
state.mediaPlaying.hasPlayedOnce = true;
|
||||||
updateMediaPlaying(descriptor, state);
|
updateMediaPlaying(descriptor, state);
|
||||||
};
|
};
|
||||||
const waiting = () => {
|
const waiting = () => {
|
||||||
state.isLoading = true;
|
state.mediaPlaying.isLoading = true;
|
||||||
updateMediaPlaying(descriptor, state);
|
updateMediaPlaying(descriptor, state);
|
||||||
};
|
};
|
||||||
const seeking = () => {
|
const seeking = () => {
|
||||||
state.isSeeking = true;
|
state.mediaPlaying.isSeeking = true;
|
||||||
updateMediaPlaying(descriptor, state);
|
updateMediaPlaying(descriptor, state);
|
||||||
};
|
};
|
||||||
const seeked = () => {
|
const seeked = () => {
|
||||||
state.isSeeking = false;
|
state.mediaPlaying.isSeeking = false;
|
||||||
updateMediaPlaying(descriptor, state);
|
updateMediaPlaying(descriptor, state);
|
||||||
};
|
};
|
||||||
const loadedmetadata = () => {
|
const loadedmetadata = () => {
|
||||||
state.duration = player.duration;
|
state.progress.duration = player.duration;
|
||||||
updateProgress(descriptor, state);
|
updateProgress(descriptor, state);
|
||||||
};
|
};
|
||||||
const timeupdate = () => {
|
const timeupdate = () => {
|
||||||
state.duration = player.duration;
|
state.progress.duration = player.duration;
|
||||||
state.time = player.currentTime;
|
state.progress.time = player.currentTime;
|
||||||
updateProgress(descriptor, state);
|
updateProgress(descriptor, state);
|
||||||
};
|
};
|
||||||
const progress = () => {
|
const progress = () => {
|
||||||
state.buffered = handleBuffered(player.currentTime, player.buffered);
|
state.progress.buffered = handleBuffered(
|
||||||
|
player.currentTime,
|
||||||
|
player.buffered
|
||||||
|
);
|
||||||
updateProgress(descriptor, state);
|
updateProgress(descriptor, state);
|
||||||
};
|
};
|
||||||
const canplay = () => {
|
const canplay = () => {
|
||||||
state.isFirstLoading = false;
|
state.mediaPlaying.isFirstLoading = false;
|
||||||
updateMediaPlaying(descriptor, state);
|
updateMediaPlaying(descriptor, state);
|
||||||
};
|
};
|
||||||
const fullscreenchange = () => {
|
const fullscreenchange = () => {
|
||||||
state.isFullscreen = !!document.fullscreenElement;
|
state.interface.isFullscreen = !!document.fullscreenElement;
|
||||||
updateInterface(descriptor, state);
|
updateInterface(descriptor, state);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,47 +1,66 @@
|
||||||
import { MWStreamQuality, MWStreamType } from "@/backend/helpers/streams";
|
import { MWStreamQuality, MWStreamType } from "@/backend/helpers/streams";
|
||||||
|
import { MWMediaMeta } from "@/backend/metadata/types";
|
||||||
import { VideoPlayerStateProvider } from "./providers/providerTypes";
|
import { VideoPlayerStateProvider } from "./providers/providerTypes";
|
||||||
|
|
||||||
|
export type VideoPlayerMeta = {
|
||||||
|
meta: MWMediaMeta;
|
||||||
|
episode?: {
|
||||||
|
episodeId: string;
|
||||||
|
seasonId: string;
|
||||||
|
};
|
||||||
|
seasons?: {
|
||||||
|
id: string;
|
||||||
|
number: number;
|
||||||
|
title: string;
|
||||||
|
episodes?: { id: string; number: number; title: string }[];
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
|
||||||
export type VideoPlayerState = {
|
export type VideoPlayerState = {
|
||||||
isPlaying: boolean;
|
// state related to the user interface
|
||||||
isPaused: boolean;
|
interface: {
|
||||||
isSeeking: boolean;
|
isFullscreen: boolean;
|
||||||
isLoading: boolean;
|
popout: string | null; // id of current popout (eg source select, episode select)
|
||||||
isFirstLoading: boolean;
|
isFocused: boolean; // is the video player the users focus? (shortcuts only works when its focused)
|
||||||
isFullscreen: boolean;
|
leftControlHovering: boolean; // is the cursor hovered over the left side of player controls
|
||||||
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 | {
|
// state related to the playing state of the media
|
||||||
name: string;
|
mediaPlaying: {
|
||||||
description: string;
|
isPlaying: boolean;
|
||||||
|
isPaused: boolean;
|
||||||
|
isSeeking: boolean; // seeking with progress bar
|
||||||
|
isLoading: boolean; // buffering or not
|
||||||
|
isFirstLoading: boolean; // first buffering of the video, used to show
|
||||||
|
hasPlayedOnce: boolean; // has the video played at all?
|
||||||
};
|
};
|
||||||
canAirplay: boolean;
|
|
||||||
stateProvider: VideoPlayerStateProvider | null;
|
// state related to video progress
|
||||||
|
progress: {
|
||||||
|
time: number;
|
||||||
|
duration: number;
|
||||||
|
buffered: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
// meta data of video
|
||||||
|
meta: null | VideoPlayerMeta;
|
||||||
source: null | {
|
source: null | {
|
||||||
quality: MWStreamQuality;
|
quality: MWStreamQuality;
|
||||||
url: string;
|
url: string;
|
||||||
type: MWStreamType;
|
type: MWStreamType;
|
||||||
};
|
};
|
||||||
|
error: null | {
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
// misc
|
||||||
|
volume: number;
|
||||||
|
pausedWhenSeeking: boolean;
|
||||||
|
hasInitialized: boolean;
|
||||||
|
canAirplay: boolean;
|
||||||
|
|
||||||
|
// backing fields
|
||||||
|
stateProvider: VideoPlayerStateProvider | null;
|
||||||
wrapperElement: HTMLDivElement | null;
|
wrapperElement: HTMLDivElement | null;
|
||||||
};
|
};
|
||||||
|
|
|
@ -5,15 +5,10 @@
|
||||||
// import { useEffect, useRef } from "react";
|
// import { useEffect, useRef } from "react";
|
||||||
|
|
||||||
import { MWStreamQuality, MWStreamType } from "@/backend/helpers/streams";
|
import { MWStreamQuality, MWStreamType } from "@/backend/helpers/streams";
|
||||||
import { LoadingAction } from "@/video/components/actions/LoadingAction";
|
import { MWMediaType } from "@/backend/metadata/types";
|
||||||
import { MiddlePauseAction } from "@/video/components/actions/MiddlePauseAction";
|
import { MetaController } from "@/video/components/controllers/MetaController";
|
||||||
import { PauseAction } from "@/video/components/actions/PauseAction";
|
|
||||||
import { ProgressAction } from "@/video/components/actions/ProgressAction";
|
|
||||||
import { SkipTimeAction } from "@/video/components/actions/SkipTimeAction";
|
|
||||||
import { TimeAction } from "@/video/components/actions/TimeAction";
|
|
||||||
import { SourceController } from "@/video/components/controllers/SourceController";
|
import { SourceController } from "@/video/components/controllers/SourceController";
|
||||||
import { VideoPlayer } from "@/video/components/VideoPlayer";
|
import { VideoPlayer } from "@/video/components/VideoPlayer";
|
||||||
import { VideoPlayerBase } from "@/video/components/VideoPlayerBase";
|
|
||||||
|
|
||||||
// function ChromeCastButton() {
|
// function ChromeCastButton() {
|
||||||
// const ref = useRef<HTMLDivElement>(null);
|
// const ref = useRef<HTMLDivElement>(null);
|
||||||
|
@ -31,12 +26,21 @@ import { VideoPlayerBase } from "@/video/components/VideoPlayerBase";
|
||||||
|
|
||||||
export function TestView() {
|
export function TestView() {
|
||||||
return (
|
return (
|
||||||
<VideoPlayer>
|
<VideoPlayer onGoBack={() => alert("hello world")}>
|
||||||
<SourceController
|
<SourceController
|
||||||
quality={MWStreamQuality.QUNKNOWN}
|
quality={MWStreamQuality.QUNKNOWN}
|
||||||
source="http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/Sintel.mp4"
|
source="http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/Sintel.mp4"
|
||||||
type={MWStreamType.MP4}
|
type={MWStreamType.MP4}
|
||||||
/>
|
/>
|
||||||
|
<MetaController
|
||||||
|
meta={{
|
||||||
|
id: "test",
|
||||||
|
title: "Hello world",
|
||||||
|
type: MWMediaType.MOVIE,
|
||||||
|
year: "1234",
|
||||||
|
seasons: undefined,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</VideoPlayer>
|
</VideoPlayer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,22 +1,21 @@
|
||||||
import { useHistory, useParams } from "react-router-dom";
|
import { useHistory, useParams } from "react-router-dom";
|
||||||
import { Helmet } from "react-helmet";
|
import { Helmet } from "react-helmet";
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { DecoratedVideoPlayer } from "@/../__old/DecoratedVideoPlayer";
|
|
||||||
import { MWStream } from "@/backend/helpers/streams";
|
import { MWStream } from "@/backend/helpers/streams";
|
||||||
import { SelectedMediaData, useScrape } from "@/hooks/useScrape";
|
import { SelectedMediaData, useScrape } from "@/hooks/useScrape";
|
||||||
import { VideoPlayerHeader } from "@/../__old/parts/VideoPlayerHeader";
|
|
||||||
import { DetailedMeta, getMetaFromId } from "@/backend/metadata/getmeta";
|
import { DetailedMeta, getMetaFromId } from "@/backend/metadata/getmeta";
|
||||||
import { decodeJWId } from "@/backend/metadata/justwatch";
|
import { decodeJWId } from "@/backend/metadata/justwatch";
|
||||||
import { SourceControl } from "@/../__old/controls/SourceControl";
|
|
||||||
import { Loading } from "@/components/layout/Loading";
|
import { Loading } from "@/components/layout/Loading";
|
||||||
import { useLoading } from "@/hooks/useLoading";
|
import { useLoading } from "@/hooks/useLoading";
|
||||||
import { MWMediaType } from "@/backend/metadata/types";
|
import { MWMediaType } from "@/backend/metadata/types";
|
||||||
import { useGoBack } from "@/hooks/useGoBack";
|
import { useGoBack } from "@/hooks/useGoBack";
|
||||||
import { IconPatch } from "@/components/buttons/IconPatch";
|
import { IconPatch } from "@/components/buttons/IconPatch";
|
||||||
|
import { VideoPlayer } from "@/video/components/VideoPlayer";
|
||||||
|
import { MetaController } from "@/video/components/controllers/MetaController";
|
||||||
|
import { SourceController } from "@/video/components/controllers/SourceController";
|
||||||
import { Icons } from "@/components/Icon";
|
import { Icons } from "@/components/Icon";
|
||||||
|
import { VideoPlayerHeader } from "@/video/components/parts/VideoPlayerHeader";
|
||||||
import { useWatchedItem } from "@/state/watched";
|
import { useWatchedItem } from "@/state/watched";
|
||||||
import { ProgressListenerControl } from "@/../__old/controls/ProgressListenerControl";
|
|
||||||
import { ShowControl } from "@/../__old/controls/ShowControl";
|
|
||||||
import { MediaFetchErrorView } from "./MediaErrorView";
|
import { MediaFetchErrorView } from "./MediaErrorView";
|
||||||
import { MediaScrapeLog } from "./MediaScrapeLog";
|
import { MediaScrapeLog } from "./MediaScrapeLog";
|
||||||
import { NotFoundMedia, NotFoundWrapper } from "../notfound/NotFoundView";
|
import { NotFoundMedia, NotFoundWrapper } from "../notfound/NotFoundView";
|
||||||
|
@ -113,17 +112,18 @@ export function MediaViewPlayer(props: MediaViewPlayerProps) {
|
||||||
<Helmet>
|
<Helmet>
|
||||||
<html data-full="true" />
|
<html data-full="true" />
|
||||||
</Helmet>
|
</Helmet>
|
||||||
<DecoratedVideoPlayer media={props.meta} onGoBack={goBack} autoPlay>
|
<VideoPlayer onGoBack={goBack}>
|
||||||
<SourceControl
|
<MetaController meta={props.meta.meta} />
|
||||||
|
<SourceController
|
||||||
source={props.stream.streamUrl}
|
source={props.stream.streamUrl}
|
||||||
type={props.stream.type}
|
type={props.stream.type}
|
||||||
quality={props.stream.quality}
|
quality={props.stream.quality}
|
||||||
/>
|
/>
|
||||||
<ProgressListenerControl
|
{/* <ProgressListenerControl
|
||||||
startAt={firstStartTime.current}
|
startAt={firstStartTime.current}
|
||||||
onProgress={updateProgress}
|
onProgress={updateProgress}
|
||||||
/>
|
/> */}
|
||||||
{props.selected.type === MWMediaType.SERIES &&
|
{/* {props.selected.type === MWMediaType.SERIES &&
|
||||||
props.meta.meta.type === MWMediaType.SERIES ? (
|
props.meta.meta.type === MWMediaType.SERIES ? (
|
||||||
<ShowControl
|
<ShowControl
|
||||||
series={{
|
series={{
|
||||||
|
@ -138,8 +138,8 @@ export function MediaViewPlayer(props: MediaViewPlayerProps) {
|
||||||
seasonData={props.meta.meta.seasonData}
|
seasonData={props.meta.meta.seasonData}
|
||||||
seasons={props.meta.meta.seasons}
|
seasons={props.meta.meta.seasons}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null} */}
|
||||||
</DecoratedVideoPlayer>
|
</VideoPlayer>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue