mirror of
https://github.com/movie-web/movie-web.git
synced 2024-11-13 08:35:08 +01:00
scraping, topbar, fix timestuff, darkened overlay, fix click targets
Co-authored-by: Jip Frijlink <JipFr@users.noreply.github.com>
This commit is contained in:
parent
fa0ac293b4
commit
3b7df601af
@ -1,3 +1,7 @@
|
||||
export * from "./atoms";
|
||||
export * from "./base/Container";
|
||||
export * from "./base/TopControls";
|
||||
export * from "./base/BottomControls";
|
||||
export * from "./base/BlackOverlay";
|
||||
export * from "./base/BackLink";
|
||||
export * from "./internals/BookmarkButton";
|
||||
|
@ -34,7 +34,7 @@ export function ProgressBar() {
|
||||
return (
|
||||
<div ref={ref}>
|
||||
<div
|
||||
className="group w-full h-8 flex items-center"
|
||||
className="group w-full h-8 flex items-center cursor-pointer"
|
||||
onMouseDown={dragMouseDown}
|
||||
onTouchStart={dragMouseDown}
|
||||
>
|
||||
|
@ -1,19 +1,29 @@
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { VideoPlayerButton } from "@/components/player/internals/Button";
|
||||
import { VideoPlayerTimeFormat } from "@/stores/player/slices/interface";
|
||||
import { usePlayerStore } from "@/stores/player/store";
|
||||
import { formatSeconds } from "@/utils/formatSeconds";
|
||||
|
||||
function durationExceedsHour(secs: number): boolean {
|
||||
return secs > 60 * 60;
|
||||
}
|
||||
|
||||
export function Time() {
|
||||
const [timeMode, setTimeMode] = useState(true);
|
||||
const timeFormat = usePlayerStore((s) => s.interface.timeFormat);
|
||||
const setTimeFormat = usePlayerStore((s) => s.setTimeFormat);
|
||||
|
||||
const { duration, time, draggingTime } = usePlayerStore((s) => s.progress);
|
||||
const { isSeeking } = usePlayerStore((s) => s.interface);
|
||||
const { t } = useTranslation();
|
||||
const hasHours = durationExceedsHour(duration);
|
||||
|
||||
function toggleMode() {
|
||||
setTimeMode(!timeMode);
|
||||
setTimeFormat(
|
||||
timeFormat === VideoPlayerTimeFormat.REGULAR
|
||||
? VideoPlayerTimeFormat.REMAINING
|
||||
: VideoPlayerTimeFormat.REGULAR
|
||||
);
|
||||
}
|
||||
|
||||
const currentTime = Math.min(
|
||||
@ -30,16 +40,23 @@ export function Time() {
|
||||
},
|
||||
});
|
||||
|
||||
const child = timeMode ? (
|
||||
<>
|
||||
{formatSeconds(currentTime)} <span>/ {formatSeconds(duration)}</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{t("videoPlayer.timeLeft", { timeLeft: formatSeconds(secondsRemaining) })}{" "}
|
||||
• {formattedTimeFinished}
|
||||
</>
|
||||
);
|
||||
const child =
|
||||
timeFormat === VideoPlayerTimeFormat.REGULAR ? (
|
||||
<>
|
||||
{formatSeconds(currentTime, hasHours)}{" "}
|
||||
<span>/ {formatSeconds(duration, hasHours)}</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{t("videoPlayer.timeLeft", {
|
||||
timeLeft: formatSeconds(
|
||||
secondsRemaining,
|
||||
durationExceedsHour(secondsRemaining)
|
||||
),
|
||||
})}{" "}
|
||||
• {formattedTimeFinished}
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<VideoPlayerButton onClick={() => toggleMode()}>{child}</VideoPlayerButton>
|
||||
|
23
src/components/player/base/BackLink.tsx
Normal file
23
src/components/player/base/BackLink.tsx
Normal file
@ -0,0 +1,23 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { Icon, Icons } from "@/components/Icon";
|
||||
import { useGoBack } from "@/hooks/useGoBack";
|
||||
|
||||
export function BackLink() {
|
||||
const { t } = useTranslation();
|
||||
const goBack = useGoBack();
|
||||
|
||||
return (
|
||||
<div className="flex items-center">
|
||||
<span
|
||||
onClick={() => goBack()}
|
||||
className="flex items-center cursor-pointer text-type-secondary hover:text-white transition-colors duration-200 font-medium"
|
||||
>
|
||||
<Icon className="mr-2" icon={Icons.ARROW_LEFT} />
|
||||
<span>{t("videoPlayer.backToHomeShort")}</span>
|
||||
</span>
|
||||
<span className="text mx-3 text-type-secondary">/</span>
|
||||
<span>Mr Jeebaloo's Big Ocean Adventure</span>
|
||||
</div>
|
||||
);
|
||||
}
|
11
src/components/player/base/BlackOverlay.tsx
Normal file
11
src/components/player/base/BlackOverlay.tsx
Normal file
@ -0,0 +1,11 @@
|
||||
import { Transition } from "@/components/Transition";
|
||||
|
||||
export function BlackOverlay(props: { show?: boolean }) {
|
||||
return (
|
||||
<Transition
|
||||
animation="fade"
|
||||
show={props.show}
|
||||
className="absolute inset-0 w-full h-full bg-black bg-opacity-20 pointer-events-none"
|
||||
/>
|
||||
);
|
||||
}
|
@ -1,25 +1,19 @@
|
||||
import { Transition } from "@/components/Transition";
|
||||
import { PlayerHoverState } from "@/stores/player/slices/interface";
|
||||
import { usePlayerStore } from "@/stores/player/store";
|
||||
|
||||
export function BottomControls(props: {
|
||||
show?: boolean;
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
const { hovering } = usePlayerStore((s) => s.interface);
|
||||
const visible =
|
||||
(hovering !== PlayerHoverState.NOT_HOVERING || props.show) ?? false;
|
||||
|
||||
return (
|
||||
<div className="w-full text-white">
|
||||
<Transition
|
||||
animation="fade"
|
||||
show={visible}
|
||||
show={props.show}
|
||||
className="pointer-events-none flex justify-end pt-32 bg-gradient-to-t from-black to-transparent [margin-bottom:env(safe-area-inset-bottom)] transition-opacity duration-200 absolute bottom-0 w-full"
|
||||
/>
|
||||
<Transition
|
||||
animation="slide-up"
|
||||
show={visible}
|
||||
show={props.show}
|
||||
className="pointer-events-auto px-4 pb-3 absolute bottom-0 w-full"
|
||||
>
|
||||
{props.children}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { ReactNode, RefObject, useEffect, useRef } from "react";
|
||||
|
||||
import { VideoClickTarget } from "@/components/player/internals/VideoClickTarget";
|
||||
import { VideoContainer } from "@/components/player/internals/VideoContainer";
|
||||
import { PlayerHoverState } from "@/stores/player/slices/interface";
|
||||
import { usePlayerStore } from "@/stores/player/store";
|
||||
@ -36,22 +37,12 @@ function useHovering(containerEl: RefObject<HTMLDivElement>) {
|
||||
if (timeoutRef.current) clearTimeout(timeoutRef.current);
|
||||
}
|
||||
|
||||
function pointerUp(e: PointerEvent) {
|
||||
if (e.pointerType === "mouse") return;
|
||||
if (timeoutRef.current) clearTimeout(timeoutRef.current);
|
||||
if (hovering !== PlayerHoverState.MOBILE_TAPPED)
|
||||
updateInterfaceHovering(PlayerHoverState.MOBILE_TAPPED);
|
||||
else updateInterfaceHovering(PlayerHoverState.NOT_HOVERING);
|
||||
}
|
||||
|
||||
el.addEventListener("pointermove", pointerMove);
|
||||
el.addEventListener("pointerleave", pointerLeave);
|
||||
el.addEventListener("pointerup", pointerUp);
|
||||
|
||||
return () => {
|
||||
el.removeEventListener("pointermove", pointerMove);
|
||||
el.removeEventListener("pointerleave", pointerLeave);
|
||||
el.removeEventListener("pointerup", pointerUp);
|
||||
};
|
||||
}, [containerEl, hovering, updateInterfaceHovering]);
|
||||
}
|
||||
@ -69,7 +60,10 @@ function BaseContainer(props: { children?: ReactNode }) {
|
||||
}, [display, containerEl]);
|
||||
|
||||
return (
|
||||
<div className="relative overflow-hidden h-screen" ref={containerEl}>
|
||||
<div
|
||||
className="relative overflow-hidden h-screen select-none"
|
||||
ref={containerEl}
|
||||
>
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
@ -84,6 +78,7 @@ export function Container(props: PlayerProps) {
|
||||
return (
|
||||
<BaseContainer>
|
||||
<VideoContainer />
|
||||
<VideoClickTarget />
|
||||
{props.children}
|
||||
</BaseContainer>
|
||||
);
|
||||
|
23
src/components/player/base/TopControls.tsx
Normal file
23
src/components/player/base/TopControls.tsx
Normal file
@ -0,0 +1,23 @@
|
||||
import { Transition } from "@/components/Transition";
|
||||
|
||||
export function TopControls(props: {
|
||||
show?: boolean;
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<div className="w-full text-white">
|
||||
<Transition
|
||||
animation="fade"
|
||||
show={props.show}
|
||||
className="pointer-events-none flex justify-end pb-32 bg-gradient-to-b from-black to-transparent [margin-bottom:env(safe-area-inset-bottom)] transition-opacity duration-200 absolute top-0 w-full"
|
||||
/>
|
||||
<Transition
|
||||
animation="slide-down"
|
||||
show={props.show}
|
||||
className="pointer-events-auto px-4 pt-6 absolute top-0 w-full text-white"
|
||||
>
|
||||
{props.children}
|
||||
</Transition>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -21,6 +21,7 @@ export function makeVideoElementDisplayInterface(): DisplayInterface {
|
||||
let containerElement: HTMLElement | null = null;
|
||||
let isFullscreen = false;
|
||||
let isPausedBeforeSeeking = false;
|
||||
let isSeeking = false;
|
||||
|
||||
function setSource() {
|
||||
if (!videoElement || !source) return;
|
||||
@ -78,6 +79,9 @@ export function makeVideoElementDisplayInterface(): DisplayInterface {
|
||||
videoElement?.play();
|
||||
},
|
||||
setSeeking(active) {
|
||||
if (active === isSeeking) return;
|
||||
isSeeking = active;
|
||||
|
||||
// if it was playing when starting to seek, play again
|
||||
if (!active) {
|
||||
if (!isPausedBeforeSeeking) this.play();
|
||||
|
9
src/components/player/hooks/useShouldShowControls.tsx
Normal file
9
src/components/player/hooks/useShouldShowControls.tsx
Normal file
@ -0,0 +1,9 @@
|
||||
import { PlayerHoverState } from "@/stores/player/slices/interface";
|
||||
import { usePlayerStore } from "@/stores/player/store";
|
||||
|
||||
export function useShouldShowControls() {
|
||||
const { hovering } = usePlayerStore((s) => s.interface);
|
||||
const { isPaused } = usePlayerStore((s) => s.mediaPlaying);
|
||||
|
||||
return hovering !== PlayerHoverState.NOT_HOVERING || isPaused;
|
||||
}
|
14
src/components/player/internals/BookmarkButton.tsx
Normal file
14
src/components/player/internals/BookmarkButton.tsx
Normal file
@ -0,0 +1,14 @@
|
||||
import { Icons } from "@/components/Icon";
|
||||
|
||||
import { VideoPlayerButton } from "./Button";
|
||||
|
||||
export function BookmarkButton() {
|
||||
return (
|
||||
<VideoPlayerButton
|
||||
onClick={() => window.open("https://youtu.be/TENzstSjsus", "_blank")}
|
||||
icon={Icons.BOOKMARK_OUTLINE}
|
||||
iconSizeClass="text-base"
|
||||
className="p-3"
|
||||
/>
|
||||
);
|
||||
}
|
@ -4,14 +4,21 @@ export function VideoPlayerButton(props: {
|
||||
children?: React.ReactNode;
|
||||
onClick: () => void;
|
||||
icon?: Icons;
|
||||
iconSizeClass?: string;
|
||||
className?: string;
|
||||
}) {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={props.onClick}
|
||||
className="p-2 rounded-full hover:bg-video-buttonBackground hover:bg-opacity-75 transition-transform duration-100 active:scale-110 active:bg-opacity-100 active:text-white"
|
||||
className={[
|
||||
"p-2 rounded-full hover:bg-video-buttonBackground hover:bg-opacity-75 transition-transform duration-100 active:scale-110 active:bg-opacity-100 active:text-white",
|
||||
props.className ?? "",
|
||||
].join(" ")}
|
||||
>
|
||||
{props.icon && <Icon className="text-2xl" icon={props.icon} />}
|
||||
{props.icon && (
|
||||
<Icon className={props.iconSizeClass || "text-2xl"} icon={props.icon} />
|
||||
)}
|
||||
{props.children}
|
||||
</button>
|
||||
);
|
||||
|
47
src/components/player/internals/VideoClickTarget.tsx
Normal file
47
src/components/player/internals/VideoClickTarget.tsx
Normal file
@ -0,0 +1,47 @@
|
||||
import { PointerEvent, useCallback } from "react";
|
||||
|
||||
import { useShouldShowVideoElement } from "@/components/player/internals/VideoContainer";
|
||||
import { PlayerHoverState } from "@/stores/player/slices/interface";
|
||||
import { usePlayerStore } from "@/stores/player/store";
|
||||
|
||||
export function VideoClickTarget() {
|
||||
const show = useShouldShowVideoElement();
|
||||
const display = usePlayerStore((s) => s.display);
|
||||
const isPaused = usePlayerStore((s) => s.mediaPlaying.isPaused);
|
||||
const updateInterfaceHovering = usePlayerStore(
|
||||
(s) => s.updateInterfaceHovering
|
||||
);
|
||||
const hovering = usePlayerStore((s) => s.interface.hovering);
|
||||
|
||||
const toggleFullscreen = useCallback(() => {
|
||||
display?.toggleFullscreen();
|
||||
}, [display]);
|
||||
|
||||
const togglePause = useCallback(
|
||||
(e: PointerEvent<HTMLDivElement>) => {
|
||||
// pause on mouse click
|
||||
if (e.pointerType === "mouse") {
|
||||
if (e.button !== 0) return;
|
||||
if (isPaused) display?.play();
|
||||
else display?.pause();
|
||||
return;
|
||||
}
|
||||
|
||||
// toggle on other types of clicks
|
||||
if (hovering !== PlayerHoverState.MOBILE_TAPPED)
|
||||
updateInterfaceHovering(PlayerHoverState.MOBILE_TAPPED);
|
||||
else updateInterfaceHovering(PlayerHoverState.NOT_HOVERING);
|
||||
},
|
||||
[display, isPaused, hovering, updateInterfaceHovering]
|
||||
);
|
||||
|
||||
if (!show) return null;
|
||||
|
||||
return (
|
||||
<div
|
||||
className="absolute inset-0"
|
||||
onDoubleClick={toggleFullscreen}
|
||||
onPointerUp={togglePause}
|
||||
/>
|
||||
);
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
import { PointerEvent, useCallback, useEffect, useRef } from "react";
|
||||
import { useEffect, useRef } from "react";
|
||||
|
||||
import { makeVideoElementDisplayInterface } from "@/components/player/display/base";
|
||||
import { playerStatus } from "@/stores/player/slices/source";
|
||||
@ -16,7 +16,7 @@ function useDisplayInterface() {
|
||||
}, [display, setDisplay]);
|
||||
}
|
||||
|
||||
function useShouldShowVideoElement() {
|
||||
export function useShouldShowVideoElement() {
|
||||
const status = usePlayerStore((s) => s.status);
|
||||
|
||||
if (status !== playerStatus.PLAYING) return false;
|
||||
@ -26,20 +26,6 @@ function useShouldShowVideoElement() {
|
||||
function VideoElement() {
|
||||
const videoEl = useRef<HTMLVideoElement>(null);
|
||||
const display = usePlayerStore((s) => s.display);
|
||||
const isPaused = usePlayerStore((s) => s.mediaPlaying.isPaused);
|
||||
|
||||
const toggleFullscreen = useCallback(() => {
|
||||
display?.toggleFullscreen();
|
||||
}, [display]);
|
||||
|
||||
const togglePause = useCallback(
|
||||
(e: PointerEvent<HTMLVideoElement>) => {
|
||||
if (e.pointerType !== "mouse") return;
|
||||
if (isPaused) display?.play();
|
||||
else display?.pause();
|
||||
},
|
||||
[display, isPaused]
|
||||
);
|
||||
|
||||
// report video element to display interface
|
||||
useEffect(() => {
|
||||
@ -48,15 +34,7 @@ function VideoElement() {
|
||||
}
|
||||
}, [display, videoEl]);
|
||||
|
||||
return (
|
||||
<video
|
||||
className="w-full h-screen bg-black"
|
||||
autoPlay
|
||||
ref={videoEl}
|
||||
onDoubleClick={toggleFullscreen}
|
||||
onPointerUp={togglePause}
|
||||
/>
|
||||
);
|
||||
return <video className="w-full h-screen bg-black" autoPlay ref={videoEl} />;
|
||||
}
|
||||
|
||||
export function VideoContainer() {
|
||||
|
@ -1,26 +1,46 @@
|
||||
import { useCallback } from "react";
|
||||
|
||||
import { MWStreamType } from "@/backend/helpers/streams";
|
||||
import { BrandPill } from "@/components/layout/BrandPill";
|
||||
import { Player } from "@/components/player";
|
||||
import { usePlayer } from "@/components/player/hooks/usePlayer";
|
||||
import { useShouldShowControls } from "@/components/player/hooks/useShouldShowControls";
|
||||
import { ScrapingPart } from "@/pages/parts/player/ScrapingPart";
|
||||
import { playerStatus } from "@/stores/player/slices/source";
|
||||
|
||||
export function PlayerView() {
|
||||
const { status, playMedia, setScrapeStatus } = usePlayer();
|
||||
|
||||
const startStream = useCallback(() => {
|
||||
playMedia({
|
||||
type: MWStreamType.MP4,
|
||||
// url: "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4",
|
||||
// url: "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/WhatCarCanYouGetForAGrand.mp4",
|
||||
url: "http://95.111.247.180/frog.mp4",
|
||||
});
|
||||
}, [playMedia]);
|
||||
const { status, setScrapeStatus } = usePlayer();
|
||||
const desktopControlsVisible = useShouldShowControls();
|
||||
|
||||
return (
|
||||
<Player.Container onLoad={setScrapeStatus}>
|
||||
<Player.BottomControls>
|
||||
{status === playerStatus.SCRAPING ? (
|
||||
<ScrapingPart
|
||||
media={{
|
||||
type: "movie",
|
||||
title: "Everything Everywhere All At Once",
|
||||
tmdbId: "545611",
|
||||
releaseYear: 2022,
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
<Player.BlackOverlay show={desktopControlsVisible} />
|
||||
<Player.TopControls show={desktopControlsVisible}>
|
||||
<div className="grid grid-cols-[1fr,auto] xl:grid-cols-3 items-center">
|
||||
<div className="flex space-x-3 items-center">
|
||||
<Player.BackLink />
|
||||
<Player.BookmarkButton />
|
||||
</div>
|
||||
<div className="text-center hidden xl:flex justify-center items-center">
|
||||
<span className="text-white font-medium mr-3">S1 E5</span>
|
||||
<span className="text-type-secondary font-medium">
|
||||
Mr. Jeebaloo discovers Atlantis
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-end">
|
||||
<BrandPill />
|
||||
</div>
|
||||
</div>
|
||||
</Player.TopControls>
|
||||
<Player.BottomControls show={desktopControlsVisible}>
|
||||
<Player.ProgressBar />
|
||||
<div className="flex justify-between">
|
||||
<div className="flex space-x-3 items-center">
|
||||
@ -34,18 +54,6 @@ export function PlayerView() {
|
||||
</div>
|
||||
</div>
|
||||
</Player.BottomControls>
|
||||
|
||||
{status === playerStatus.SCRAPING ? (
|
||||
<ScrapingPart
|
||||
onGetStream={startStream}
|
||||
media={{
|
||||
type: "movie",
|
||||
title: "Hamilton",
|
||||
tmdbId: "556574",
|
||||
releaseYear: 2020,
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
</Player.Container>
|
||||
);
|
||||
}
|
||||
|
@ -1,11 +1,13 @@
|
||||
import { ScrapeMedia } from "@movie-web/providers";
|
||||
import { useCallback, useState } from "react";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
|
||||
import { MWStreamType } from "@/backend/helpers/streams";
|
||||
import { usePlayer } from "@/components/player/hooks/usePlayer";
|
||||
import { providers } from "@/utils/providers";
|
||||
|
||||
export interface ScrapingProps {
|
||||
media: ScrapeMedia;
|
||||
onGetStream?: () => void;
|
||||
// onGetStream?: () => void;
|
||||
}
|
||||
|
||||
export interface ScrapingSegment {
|
||||
@ -32,7 +34,6 @@ function useScrape() {
|
||||
media,
|
||||
events: {
|
||||
init(evt) {
|
||||
console.log("init", evt);
|
||||
setSources(
|
||||
evt.sourceIds
|
||||
.map((v) => {
|
||||
@ -54,14 +55,12 @@ function useScrape() {
|
||||
setSourceOrder(evt.sourceIds.map((v) => ({ id: v, children: [] })));
|
||||
},
|
||||
start(id) {
|
||||
console.log("start", id);
|
||||
setSources((s) => {
|
||||
if (s[id]) s[id].status = "pending";
|
||||
return { ...s };
|
||||
});
|
||||
},
|
||||
update(evt) {
|
||||
console.log("update", evt);
|
||||
setSources((s) => {
|
||||
if (s[evt.id]) {
|
||||
s[evt.id].status = evt.status;
|
||||
@ -72,7 +71,6 @@ function useScrape() {
|
||||
});
|
||||
},
|
||||
discoverEmbeds(evt) {
|
||||
console.log("discoverEmbeds", evt);
|
||||
setSources((s) => {
|
||||
evt.embeds.forEach((v) => {
|
||||
const source = providers.getMetadata(v.embedScraperId);
|
||||
@ -97,7 +95,6 @@ function useScrape() {
|
||||
},
|
||||
});
|
||||
|
||||
console.log(output);
|
||||
return output;
|
||||
},
|
||||
[setSourceOrder, setSources]
|
||||
@ -111,10 +108,26 @@ function useScrape() {
|
||||
}
|
||||
|
||||
export function ScrapingPart(props: ScrapingProps) {
|
||||
const { playMedia } = usePlayer();
|
||||
const { startScraping, sourceOrder, sources } = useScrape();
|
||||
|
||||
const started = useRef(false);
|
||||
useEffect(() => {
|
||||
if (started.current) return;
|
||||
started.current = true;
|
||||
(async () => {
|
||||
const output = await startScraping(props.media);
|
||||
if (output?.stream.type !== "file") return;
|
||||
const firstFile = Object.values(output.stream.qualities)[0];
|
||||
playMedia({
|
||||
type: MWStreamType.MP4,
|
||||
url: firstFile.url,
|
||||
});
|
||||
})();
|
||||
}, [startScraping, props, playMedia]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="h-full w-full flex items-center justify-center flex-col">
|
||||
{sourceOrder.map((order) => {
|
||||
const source = sources[order.id];
|
||||
if (!source) return null;
|
||||
@ -141,20 +154,6 @@ export function ScrapingPart(props: ScrapingProps) {
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => startScraping(props.media)}
|
||||
className="block"
|
||||
>
|
||||
Start scraping
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => props.onGetStream?.()}
|
||||
className="block"
|
||||
>
|
||||
Finish scraping
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -25,6 +25,7 @@ export interface InterfaceSlice {
|
||||
};
|
||||
updateInterfaceHovering(newState: PlayerHoverState): void;
|
||||
setSeeking(seeking: boolean): void;
|
||||
setTimeFormat(format: VideoPlayerTimeFormat): void;
|
||||
}
|
||||
|
||||
export const createInterfaceSlice: MakeSlice<InterfaceSlice> = (set, get) => ({
|
||||
@ -38,6 +39,11 @@ export const createInterfaceSlice: MakeSlice<InterfaceSlice> = (set, get) => ({
|
||||
timeFormat: VideoPlayerTimeFormat.REGULAR,
|
||||
},
|
||||
|
||||
setTimeFormat(format) {
|
||||
set((s) => {
|
||||
s.interface.timeFormat = format;
|
||||
});
|
||||
},
|
||||
updateInterfaceHovering(newState: PlayerHoverState) {
|
||||
set((s) => {
|
||||
s.interface.hovering = newState;
|
||||
|
@ -78,7 +78,8 @@ module.exports = {
|
||||
emphasis: "#FFFFFF",
|
||||
text: "#73739D",
|
||||
dimmed: "#926CAD",
|
||||
divider: "#262632"
|
||||
divider: "#262632",
|
||||
secondary: "#64647B"
|
||||
},
|
||||
|
||||
// search bar
|
||||
|
13
v4-todo.md
13
v4-todo.md
@ -1,7 +1,7 @@
|
||||
player itself:
|
||||
- [ ] BUG Pause should keep controls visible
|
||||
- [ ] BUG Touch on bottoms shouldn't toggle UI
|
||||
- [ ] BUG unpause when controls hover
|
||||
- [x] BUG Pause should keep controls visible
|
||||
- [x] BUG Touch on bottoms shouldn't toggle UI
|
||||
- [x] BUG unpause when controls hover
|
||||
- [ ] keyboard controls
|
||||
- [ ] fullscreen
|
||||
- [ ] barrel roll
|
||||
@ -9,15 +9,16 @@ player itself:
|
||||
- [ ] skip forward/backward
|
||||
- [ ] pause
|
||||
- [ ] volume ui
|
||||
- [ ] header (back, title, logo)
|
||||
- [x] header (back, title, logo)
|
||||
- [ ] touch middle controls (forward, backward, pause)
|
||||
- [ ] volume
|
||||
- [ ] bookmark in header
|
||||
- [ ] airplay
|
||||
- [ ] responsiveness
|
||||
- [ ] chromecast
|
||||
- [ ] thumbnails
|
||||
- [ ] hover darken overlay (10% black)
|
||||
- [x] hover darken overlay (20% black)
|
||||
- [ ] autoplay not working
|
||||
- [ ] play button in middle if cant autoplay
|
||||
|
||||
player views:
|
||||
- [ ] scraping view
|
||||
|
Loading…
Reference in New Issue
Block a user