mirror of
https://github.com/movie-web/movie-web.git
synced 2024-12-24 18:51:53 +01:00
add back standard video UI
This commit is contained in:
parent
a0c24209bb
commit
27ef9be6b1
68
src/components/Transition.tsx
Normal file
68
src/components/Transition.tsx
Normal file
@ -0,0 +1,68 @@
|
||||
import { ReactNode, useRef } from "react";
|
||||
import { CSSTransition } from "react-transition-group";
|
||||
import { CSSTransitionClassNames } from "react-transition-group/CSSTransition";
|
||||
|
||||
type TransitionAnimations = "slide-down" | "slide-up" | "fade";
|
||||
|
||||
interface Props {
|
||||
show: boolean;
|
||||
duration?: number;
|
||||
animation: TransitionAnimations;
|
||||
className?: string;
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
function getClasses(
|
||||
animation: TransitionAnimations,
|
||||
duration: number
|
||||
): CSSTransitionClassNames {
|
||||
if (animation === "slide-down") {
|
||||
return {
|
||||
exit: `transition-[transform,opacity] translate-y-0 duration-${duration} opacity-100`,
|
||||
exitActive: "!-translate-y-4 !opacity-0",
|
||||
exitDone: "hidden",
|
||||
enter: `transition-[transform,opacity] -translate-y-4 duration-${duration} opacity-0`,
|
||||
enterActive: "!translate-y-0 !opacity-100",
|
||||
};
|
||||
}
|
||||
|
||||
if (animation === "slide-up") {
|
||||
return {
|
||||
exit: `transition-[transform,opacity] translate-y-0 duration-${duration} opacity-100`,
|
||||
exitActive: "!translate-y-4 !opacity-0",
|
||||
exitDone: "hidden",
|
||||
enter: `transition-[transform,opacity] translate-y-4 duration-${duration} opacity-0`,
|
||||
enterActive: "!translate-y-0 !opacity-100",
|
||||
};
|
||||
}
|
||||
|
||||
if (animation === "fade") {
|
||||
return {
|
||||
exit: `transition-[transform,opacity] duration-${duration} opacity-100`,
|
||||
exitActive: "!opacity-0",
|
||||
exitDone: "hidden",
|
||||
enter: `transition-[transform,opacity] duration-${duration} opacity-0`,
|
||||
enterActive: "!opacity-100",
|
||||
};
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
export function Transition(props: Props) {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const duration = props.duration ?? 200;
|
||||
|
||||
return (
|
||||
<CSSTransition
|
||||
nodeRef={ref}
|
||||
in={props.show}
|
||||
timeout={200}
|
||||
classNames={getClasses(props.animation, duration)}
|
||||
>
|
||||
<div ref={ref} className={props.className}>
|
||||
{props.children}
|
||||
</div>
|
||||
</CSSTransition>
|
||||
);
|
||||
}
|
140
src/video/components/VideoPlayer.tsx
Normal file
140
src/video/components/VideoPlayer.tsx
Normal file
@ -0,0 +1,140 @@
|
||||
import { Transition } from "@/components/Transition";
|
||||
import { useIsMobile } from "@/hooks/useIsMobile";
|
||||
import { BackdropAction } from "@/video/components/actions/BackdropAction";
|
||||
import { FullscreenAction } from "@/video/components/actions/FullscreenAction";
|
||||
import { LoadingAction } from "@/video/components/actions/LoadingAction";
|
||||
import { MiddlePauseAction } from "@/video/components/actions/MiddlePauseAction";
|
||||
import { MobileCenterAction } from "@/video/components/actions/MobileCenterAction";
|
||||
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 {
|
||||
VideoPlayerBase,
|
||||
VideoPlayerBaseProps,
|
||||
} from "@/video/components/VideoPlayerBase";
|
||||
import { useVideoPlayerDescriptor } from "@/video/state/hooks";
|
||||
import { useControls } from "@/video/state/logic/controls";
|
||||
import { ReactNode, useCallback, useState } from "react";
|
||||
|
||||
function CenterPosition(props: { children: ReactNode }) {
|
||||
return (
|
||||
<div className="absolute inset-0 flex items-center justify-center">
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function LeftSideControls() {
|
||||
const descriptor = useVideoPlayerDescriptor();
|
||||
const controls = useControls(descriptor);
|
||||
|
||||
const handleMouseEnter = useCallback(() => {
|
||||
controls.setLeftControlsHover(true);
|
||||
}, [controls]);
|
||||
const handleMouseLeave = useCallback(() => {
|
||||
controls.setLeftControlsHover(false);
|
||||
}, [controls]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className="flex items-center px-2"
|
||||
onMouseLeave={handleMouseLeave}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
>
|
||||
<PauseAction />
|
||||
<SkipTimeAction />
|
||||
{/* <VolumeControl className="mr-2" /> */}
|
||||
<TimeAction />
|
||||
</div>
|
||||
{/* <ShowTitleControl /> */}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function VideoPlayer(props: VideoPlayerBaseProps) {
|
||||
const [show, setShow] = useState(false);
|
||||
const { isMobile } = useIsMobile();
|
||||
|
||||
const onBackdropChange = useCallback(
|
||||
(showing: boolean) => {
|
||||
setShow(showing);
|
||||
},
|
||||
[setShow]
|
||||
);
|
||||
|
||||
// TODO autoplay
|
||||
// TODO meta data
|
||||
return (
|
||||
<VideoPlayerBase>
|
||||
{/* <PageTitleControl media={props.media?.meta} /> */}
|
||||
{/* <VideoPlayerError media={props.media?.meta} onGoBack={props.onGoBack}> */}
|
||||
<BackdropAction onBackdropChange={onBackdropChange}>
|
||||
<CenterPosition>
|
||||
<LoadingAction />
|
||||
</CenterPosition>
|
||||
<CenterPosition>
|
||||
<MiddlePauseAction />
|
||||
</CenterPosition>
|
||||
{isMobile ? (
|
||||
<Transition
|
||||
animation="fade"
|
||||
show={show}
|
||||
className="absolute inset-0 flex items-center justify-center"
|
||||
>
|
||||
<MobileCenterAction />
|
||||
</Transition>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
<Transition
|
||||
animation="slide-down"
|
||||
show={show}
|
||||
className="pointer-events-auto absolute inset-x-0 top-0 flex flex-col py-6 px-8 pb-2"
|
||||
>
|
||||
{/* <VideoPlayerHeader
|
||||
media={props.media?.meta}
|
||||
onClick={props.onGoBack}
|
||||
isMobile={isMobile}
|
||||
/> */}
|
||||
</Transition>
|
||||
<Transition
|
||||
animation="slide-up"
|
||||
show={show}
|
||||
className="pointer-events-auto absolute inset-x-0 bottom-0 flex flex-col px-4 pb-2 [margin-bottom:env(safe-area-inset-bottom)]"
|
||||
>
|
||||
<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>
|
||||
<FullscreenAction />
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<LeftSideControls />
|
||||
<div className="flex-1" />
|
||||
{/* <QualityDisplayControl />
|
||||
<SeriesSelectionControl />
|
||||
<SourceSelectionControl media={props.media} />
|
||||
<AirplayControl />
|
||||
<ChromeCastControl /> */}
|
||||
<FullscreenAction />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</Transition>
|
||||
</BackdropAction>
|
||||
{props.children}
|
||||
{/* </VideoPlayerError> */}
|
||||
</VideoPlayerBase>
|
||||
);
|
||||
}
|
@ -1,3 +1,5 @@
|
||||
import { WrapperRegisterInternal } from "@/video/components/internal/WrapperRegisterInternal";
|
||||
import { useRef } from "react";
|
||||
import { VideoPlayerContextProvider } from "../state/hooks";
|
||||
import { VideoElementInternal } from "./internal/VideoElementInternal";
|
||||
|
||||
@ -6,14 +8,19 @@ export interface VideoPlayerBaseProps {
|
||||
}
|
||||
|
||||
export function VideoPlayerBase(props: VideoPlayerBaseProps) {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
// TODO error boundary
|
||||
// TODO move error boundary to only decorated, <VideoPlayer /> shouldn't have styling
|
||||
// TODO internal controls
|
||||
|
||||
return (
|
||||
<VideoPlayerContextProvider>
|
||||
<div className="is-video-player relative h-full w-full select-none overflow-hidden bg-black [border-left:env(safe-area-inset-left)_solid_transparent] [border-right:env(safe-area-inset-right)_solid_transparent]">
|
||||
<div
|
||||
ref={ref}
|
||||
className="is-video-player relative h-full w-full select-none overflow-hidden bg-black [border-left:env(safe-area-inset-left)_solid_transparent] [border-right:env(safe-area-inset-right)_solid_transparent]"
|
||||
>
|
||||
<VideoElementInternal />
|
||||
<WrapperRegisterInternal wrapper={ref.current} />
|
||||
<div className="absolute inset-0">{props.children}</div>
|
||||
</div>
|
||||
</VideoPlayerContextProvider>
|
||||
|
96
src/video/components/actions/BackdropAction.tsx
Normal file
96
src/video/components/actions/BackdropAction.tsx
Normal file
@ -0,0 +1,96 @@
|
||||
import { useVideoPlayerDescriptor } from "@/video/state/hooks";
|
||||
import { useControls } from "@/video/state/logic/controls";
|
||||
import { useInterface } from "@/video/state/logic/interface";
|
||||
import { useMediaPlaying } from "@/video/state/logic/mediaplaying";
|
||||
import React, { useCallback, useEffect, useRef, useState } from "react";
|
||||
|
||||
interface BackdropActionProps {
|
||||
children?: React.ReactNode;
|
||||
onBackdropChange?: (showing: boolean) => void;
|
||||
}
|
||||
|
||||
export function BackdropAction(props: BackdropActionProps) {
|
||||
const descriptor = useVideoPlayerDescriptor();
|
||||
const controls = useControls(descriptor);
|
||||
const mediaPlaying = useMediaPlaying(descriptor);
|
||||
const videoInterface = useInterface(descriptor);
|
||||
|
||||
const [moved, setMoved] = useState(false);
|
||||
const timeout = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
const clickareaRef = useRef<HTMLDivElement>(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<HTMLDivElement>) => {
|
||||
if (!clickareaRef.current || clickareaRef.current !== e.target) return;
|
||||
|
||||
if (videoInterface.popout !== null) return;
|
||||
|
||||
if (mediaPlaying.isPlaying) controls.pause();
|
||||
else controls.play();
|
||||
},
|
||||
[controls, mediaPlaying, videoInterface]
|
||||
);
|
||||
const handleDoubleClick = useCallback(
|
||||
(e: React.MouseEvent<HTMLDivElement>) => {
|
||||
if (!clickareaRef.current || clickareaRef.current !== e.target) return;
|
||||
|
||||
if (!videoInterface.isFullscreen) controls.enterFullscreen();
|
||||
else controls.exitFullscreen();
|
||||
},
|
||||
[controls, videoInterface]
|
||||
);
|
||||
|
||||
const lastBackdropValue = useRef<boolean | null>(null);
|
||||
useEffect(() => {
|
||||
const currentValue = moved || mediaPlaying.isPaused;
|
||||
if (currentValue !== lastBackdropValue.current) {
|
||||
lastBackdropValue.current = currentValue;
|
||||
if (!currentValue) controls.closePopout();
|
||||
props.onBackdropChange?.(currentValue);
|
||||
}
|
||||
}, [controls, moved, mediaPlaying, props]);
|
||||
const showUI = moved || mediaPlaying.isPaused;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`absolute inset-0 ${!showUI ? "cursor-none" : ""}`}
|
||||
onMouseMove={handleMouseMove}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
ref={clickareaRef}
|
||||
onClick={handleClick}
|
||||
onDoubleClick={handleDoubleClick}
|
||||
>
|
||||
<div
|
||||
className={`pointer-events-none absolute inset-0 bg-black bg-opacity-20 transition-opacity duration-200 ${
|
||||
!showUI ? "!opacity-0" : ""
|
||||
}`}
|
||||
/>
|
||||
<div
|
||||
className={`pointer-events-none absolute inset-x-0 bottom-0 h-[20%] bg-gradient-to-t from-black to-transparent transition-opacity duration-200 ${
|
||||
!showUI ? "!opacity-0" : ""
|
||||
}`}
|
||||
/>
|
||||
<div
|
||||
className={`pointer-events-none absolute inset-x-0 top-0 h-[20%] bg-gradient-to-b from-black to-transparent transition-opacity duration-200 ${
|
||||
!showUI ? "!opacity-0" : ""
|
||||
}`}
|
||||
/>
|
||||
<div className="pointer-events-none absolute inset-0">
|
||||
{props.children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
32
src/video/components/actions/FullscreenAction.tsx
Normal file
32
src/video/components/actions/FullscreenAction.tsx
Normal file
@ -0,0 +1,32 @@
|
||||
import { Icons } from "@/components/Icon";
|
||||
import { canFullscreen } from "@/utils/detectFeatures";
|
||||
import { useVideoPlayerDescriptor } from "@/video/state/hooks";
|
||||
import { useControls } from "@/video/state/logic/controls";
|
||||
import { useInterface } from "@/video/state/logic/interface";
|
||||
import { useCallback } from "react";
|
||||
import { VideoPlayerIconButton } from "../parts/VideoPlayerIconButton";
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function FullscreenAction(props: Props) {
|
||||
const descriptor = useVideoPlayerDescriptor();
|
||||
const videoInterface = useInterface(descriptor);
|
||||
const controls = useControls(descriptor);
|
||||
|
||||
const handleClick = useCallback(() => {
|
||||
if (videoInterface.isFullscreen) controls.exitFullscreen();
|
||||
else controls.enterFullscreen();
|
||||
}, [controls, videoInterface]);
|
||||
|
||||
if (!canFullscreen()) return null;
|
||||
|
||||
return (
|
||||
<VideoPlayerIconButton
|
||||
className={props.className}
|
||||
onClick={handleClick}
|
||||
icon={videoInterface.isFullscreen ? Icons.COMPRESS : Icons.EXPAND}
|
||||
/>
|
||||
);
|
||||
}
|
25
src/video/components/actions/MobileCenterAction.tsx
Normal file
25
src/video/components/actions/MobileCenterAction.tsx
Normal file
@ -0,0 +1,25 @@
|
||||
import { PauseAction } from "@/video/components/actions/PauseAction";
|
||||
import {
|
||||
SkipTimeBackwardAction,
|
||||
SkipTimeForwardAction,
|
||||
} from "@/video/components/actions/SkipTimeAction";
|
||||
import { useVideoPlayerDescriptor } from "@/video/state/hooks";
|
||||
import { useMediaPlaying } from "@/video/state/logic/mediaplaying";
|
||||
|
||||
export function MobileCenterAction() {
|
||||
const descriptor = useVideoPlayerDescriptor();
|
||||
const mediaPlaying = useMediaPlaying(descriptor);
|
||||
|
||||
const isLoading = mediaPlaying.isFirstLoading || mediaPlaying.isLoading;
|
||||
|
||||
return (
|
||||
<div className="flex items-center space-x-8">
|
||||
<SkipTimeBackwardAction />
|
||||
<PauseAction
|
||||
iconSize="text-5xl"
|
||||
className={isLoading ? "pointer-events-none opacity-0" : ""}
|
||||
/>
|
||||
<SkipTimeForwardAction />
|
||||
</div>
|
||||
);
|
||||
}
|
16
src/video/components/internal/WrapperRegisterInternal.tsx
Normal file
16
src/video/components/internal/WrapperRegisterInternal.tsx
Normal file
@ -0,0 +1,16 @@
|
||||
import { getPlayerState } from "@/video/state/cache";
|
||||
import { useVideoPlayerDescriptor } from "@/video/state/hooks";
|
||||
import { useEffect } from "react";
|
||||
|
||||
export function WrapperRegisterInternal(props: {
|
||||
wrapper: HTMLDivElement | null;
|
||||
}) {
|
||||
const descriptor = useVideoPlayerDescriptor();
|
||||
|
||||
useEffect(() => {
|
||||
const state = getPlayerState(descriptor);
|
||||
state.wrapperElement = props.wrapper;
|
||||
}, [props.wrapper, descriptor]);
|
||||
|
||||
return null;
|
||||
}
|
@ -1,4 +1,8 @@
|
||||
export type VideoPlayerEvent = "mediaplaying" | "source" | "progress";
|
||||
export type VideoPlayerEvent =
|
||||
| "mediaplaying"
|
||||
| "source"
|
||||
| "progress"
|
||||
| "interface";
|
||||
|
||||
function createEventString(id: string, event: VideoPlayerEvent): string {
|
||||
return `_vid:::${id}:::${event}`;
|
||||
|
@ -27,6 +27,7 @@ function initPlayer(): VideoPlayerState {
|
||||
canAirplay: false,
|
||||
stateProvider: null,
|
||||
source: null,
|
||||
wrapperElement: null,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -1,10 +1,21 @@
|
||||
import { updateInterface } from "@/video/state/logic/interface";
|
||||
import { getPlayerState } from "../cache";
|
||||
import { VideoPlayerStateController } from "../providers/providerTypes";
|
||||
|
||||
export function useControls(descriptor: string): VideoPlayerStateController {
|
||||
type ControlMethods = {
|
||||
openPopout(id: string): void;
|
||||
closePopout(): void;
|
||||
setLeftControlsHover(hovering: boolean): void;
|
||||
setFocused(focused: boolean): void;
|
||||
};
|
||||
|
||||
export function useControls(
|
||||
descriptor: string
|
||||
): VideoPlayerStateController & ControlMethods {
|
||||
const state = getPlayerState(descriptor);
|
||||
|
||||
return {
|
||||
// state provider controls
|
||||
pause() {
|
||||
state.stateProvider?.pause();
|
||||
},
|
||||
@ -20,5 +31,29 @@ export function useControls(descriptor: string): VideoPlayerStateController {
|
||||
setTime(time) {
|
||||
state.stateProvider?.setTime(time);
|
||||
},
|
||||
exitFullscreen() {
|
||||
state.stateProvider?.exitFullscreen();
|
||||
},
|
||||
enterFullscreen() {
|
||||
state.stateProvider?.enterFullscreen();
|
||||
},
|
||||
|
||||
// other controls
|
||||
setLeftControlsHover(hovering) {
|
||||
state.leftControlHovering = hovering;
|
||||
updateInterface(descriptor, state);
|
||||
},
|
||||
openPopout(id: string) {
|
||||
state.popout = id;
|
||||
updateInterface(descriptor, state);
|
||||
},
|
||||
closePopout() {
|
||||
state.popout = null;
|
||||
updateInterface(descriptor, state);
|
||||
},
|
||||
setFocused(focused) {
|
||||
state.isFocused = focused;
|
||||
updateInterface(descriptor, state);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
47
src/video/state/logic/interface.ts
Normal file
47
src/video/state/logic/interface.ts
Normal file
@ -0,0 +1,47 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { getPlayerState } from "../cache";
|
||||
import { listenEvent, sendEvent, unlistenEvent } from "../events";
|
||||
import { VideoPlayerState } from "../types";
|
||||
|
||||
export type VideoInterfaceEvent = {
|
||||
popout: string | null;
|
||||
leftControlHovering: boolean;
|
||||
isFocused: boolean;
|
||||
isFullscreen: boolean;
|
||||
};
|
||||
|
||||
function getInterfaceFromState(state: VideoPlayerState): VideoInterfaceEvent {
|
||||
return {
|
||||
popout: state.popout,
|
||||
leftControlHovering: state.leftControlHovering,
|
||||
isFocused: state.isFocused,
|
||||
isFullscreen: state.isFullscreen,
|
||||
};
|
||||
}
|
||||
|
||||
export function updateInterface(descriptor: string, state: VideoPlayerState) {
|
||||
sendEvent<VideoInterfaceEvent>(
|
||||
descriptor,
|
||||
"interface",
|
||||
getInterfaceFromState(state)
|
||||
);
|
||||
}
|
||||
|
||||
export function useInterface(descriptor: string): VideoInterfaceEvent {
|
||||
const state = getPlayerState(descriptor);
|
||||
const [data, setData] = useState<VideoInterfaceEvent>(
|
||||
getInterfaceFromState(state)
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
function update(payload: CustomEvent<VideoInterfaceEvent>) {
|
||||
setData(payload.detail);
|
||||
}
|
||||
listenEvent(descriptor, "interface", update);
|
||||
return () => {
|
||||
unlistenEvent(descriptor, "interface", update);
|
||||
};
|
||||
}, [descriptor]);
|
||||
|
||||
return data;
|
||||
}
|
@ -12,6 +12,8 @@ export type VideoPlayerStateController = {
|
||||
setSource: (source: VideoPlayerSource) => void;
|
||||
setTime(time: number): void;
|
||||
setSeeking(active: boolean): void;
|
||||
exitFullscreen(): void;
|
||||
enterFullscreen(): void;
|
||||
};
|
||||
|
||||
export type VideoPlayerStateProvider = VideoPlayerStateController & {
|
||||
|
@ -1,5 +1,12 @@
|
||||
import Hls from "hls.js";
|
||||
import fscreen from "fscreen";
|
||||
import {
|
||||
canFullscreen,
|
||||
canFullscreenAnyElement,
|
||||
canWebkitFullscreen,
|
||||
} from "@/utils/detectFeatures";
|
||||
import { MWStreamType } from "@/backend/helpers/streams";
|
||||
import { updateInterface } from "@/video/state/logic/interface";
|
||||
import { getPlayerState } from "../cache";
|
||||
import { updateMediaPlaying } from "../logic/mediaplaying";
|
||||
import { VideoPlayerStateProvider } from "./providerTypes";
|
||||
@ -20,6 +27,21 @@ export function createVideoStateProvider(
|
||||
pause() {
|
||||
player.pause();
|
||||
},
|
||||
exitFullscreen() {
|
||||
if (!fscreen.fullscreenElement) return;
|
||||
fscreen.exitFullscreen();
|
||||
},
|
||||
enterFullscreen() {
|
||||
if (!canFullscreen() || fscreen.fullscreenElement) return;
|
||||
if (canFullscreenAnyElement()) {
|
||||
if (state.wrapperElement)
|
||||
fscreen.requestFullscreen(state.wrapperElement);
|
||||
return;
|
||||
}
|
||||
if (canWebkitFullscreen()) {
|
||||
(player as any).webkitEnterFullscreen();
|
||||
}
|
||||
},
|
||||
setTime(t) {
|
||||
// clamp time between 0 and max duration
|
||||
let time = Math.min(t, player.duration);
|
||||
@ -127,6 +149,10 @@ export function createVideoStateProvider(
|
||||
state.isFirstLoading = false;
|
||||
updateMediaPlaying(descriptor, state);
|
||||
};
|
||||
const fullscreenchange = () => {
|
||||
state.isFullscreen = !!document.fullscreenElement;
|
||||
updateInterface(descriptor, state);
|
||||
};
|
||||
|
||||
player.addEventListener("pause", pause);
|
||||
player.addEventListener("playing", playing);
|
||||
@ -137,6 +163,7 @@ export function createVideoStateProvider(
|
||||
player.addEventListener("timeupdate", timeupdate);
|
||||
player.addEventListener("loadedmetadata", loadedmetadata);
|
||||
player.addEventListener("canplay", canplay);
|
||||
fscreen.addEventListener("fullscreenchange", fullscreenchange);
|
||||
return {
|
||||
destroy: () => {
|
||||
player.removeEventListener("pause", pause);
|
||||
@ -148,6 +175,7 @@ export function createVideoStateProvider(
|
||||
player.removeEventListener("progress", progress);
|
||||
player.removeEventListener("waiting", waiting);
|
||||
player.removeEventListener("canplay", canplay);
|
||||
fscreen.removeEventListener("fullscreenchange", fullscreenchange);
|
||||
},
|
||||
};
|
||||
},
|
||||
|
@ -43,4 +43,5 @@ export type VideoPlayerState = {
|
||||
url: string;
|
||||
type: MWStreamType;
|
||||
};
|
||||
wrapperElement: HTMLDivElement | null;
|
||||
};
|
||||
|
@ -12,6 +12,7 @@ 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 { VideoPlayer } from "@/video/components/VideoPlayer";
|
||||
import { VideoPlayerBase } from "@/video/components/VideoPlayerBase";
|
||||
|
||||
// function ChromeCastButton() {
|
||||
@ -30,18 +31,12 @@ import { VideoPlayerBase } from "@/video/components/VideoPlayerBase";
|
||||
|
||||
export function TestView() {
|
||||
return (
|
||||
<VideoPlayerBase>
|
||||
<PauseAction />
|
||||
<VideoPlayer>
|
||||
<SourceController
|
||||
quality={MWStreamQuality.QUNKNOWN}
|
||||
source="http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/Sintel.mp4"
|
||||
type={MWStreamType.MP4}
|
||||
/>
|
||||
<MiddlePauseAction />
|
||||
<ProgressAction />
|
||||
<LoadingAction />
|
||||
<TimeAction />
|
||||
<SkipTimeAction />
|
||||
</VideoPlayerBase>
|
||||
</VideoPlayer>
|
||||
);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user