From f553dd36e55ade5d70f40615f238c6615639505b Mon Sep 17 00:00:00 2001 From: Janne Koschinski <janne@kuschku.de> Date: Fri, 15 Jan 2021 16:05:06 +0100 Subject: [PATCH] Further cleanup --- src/routes/player/Player.tsx | 20 ++++----- src/routes/player/PreviewBar.tsx | 1 - src/routes/player/PreviewViewer.tsx | 27 +++++++----- src/routes/player/SeekBar.tsx | 40 ++++++------------ src/routes/player/SeekBarContainer.tsx | 41 +++++++++++++++++++ .../player/subtitles/SubtitleRenderer.tsx | 24 +++++++++-- src/routes/player/video/AudioSelection.tsx | 10 ++--- src/routes/player/video/PlayerControls.tsx | 10 ++--- src/routes/player/video/Track.tsx | 9 ++-- src/routes/player/video/VideoContext.tsx | 7 ++-- 10 files changed, 111 insertions(+), 78 deletions(-) create mode 100644 src/routes/player/SeekBarContainer.tsx diff --git a/src/routes/player/Player.tsx b/src/routes/player/Player.tsx index 211dc2c..4b93910 100644 --- a/src/routes/player/Player.tsx +++ b/src/routes/player/Player.tsx @@ -5,15 +5,14 @@ import {ContentMeta} from "../../api/models/dto/ContentMeta"; import {Media} from "../../api/models/Media"; import {getLocalizedDescription, getLocalizedName, getLocalizedRating} from "../../api/models/Content"; import {useLocale} from "../../util/locale/LocalizedContext"; -import {SeekBar} from "./SeekBar"; +import {SeekBarContainer} from "./SeekBarContainer"; import {VideoElement} from "./video/VideoElement"; import {PlayerApi} from "./video/PlayerApi"; import {Subtitle} from "../../api/models/Subtitle"; import {MousePosition} from "../../util/mouse/MousePosition"; -import {VideoProvider} from "./video/VideoContext"; +import {PlayerProvider} from "./video/VideoContext"; import {SubtitleRenderer} from "./subtitles/SubtitleRenderer"; import {useShowEpisodes} from "../../api/ApiHooks"; -import {useDuration} from "../../util/media/useDuration"; import {AudioSelection} from "./video/AudioSelection"; import {SubtitleSelection} from "./video/SubtitleSelection"; import {EpisodeSelection} from "./video/EpisodeSelection"; @@ -45,10 +44,8 @@ export function Player( const [playerApi, setPlayerApi] = useState<PlayerApi | null>(null); const [mousePosition, setMousePosition] = useState<MousePosition | null>(null); - const duration = useDuration(playerApi); - return ( - <VideoProvider value={playerApi?.getVideoElement() || null}> + <PlayerProvider value={playerApi}> <div> <Link to="/">Back</Link> <h2>{name?.name}</h2> @@ -78,29 +75,26 @@ export function Player( /> <SubtitleRenderer className={classes.subtitleCanvas} - videoElement={playerApi?.getVideoElement() || null} subtitle={subtitle} - duration={duration} /> </div> </div> - <PlayerControls playerApi={playerApi} /> + <PlayerControls/> <h3>Audio</h3> - <AudioSelection playerApi={playerApi} /> + <AudioSelection/> <h3>Subtitles</h3> <SubtitleSelection subtitles={[null, ...content.subtitles]} subtitle={subtitle} setSubtitle={setSubtitle} /> - <SeekBar - videoApi={playerApi} + <SeekBarContainer previewSrc={content.preview} mousePosition={mousePosition} setMousePosition={setMousePosition} /> </div> - </VideoProvider> + </PlayerProvider> ); } diff --git a/src/routes/player/PreviewBar.tsx b/src/routes/player/PreviewBar.tsx index 58dd9fc..a3dee23 100644 --- a/src/routes/player/PreviewBar.tsx +++ b/src/routes/player/PreviewBar.tsx @@ -13,7 +13,6 @@ interface Props { } export function PreviewBar({previewSrc, duration, position, hidden}: Props) { - //console.log("Rendering PreviewBar") const classes = useStyles(); const [previewTrack, setPreviewTrack] = useState<HTMLTrackElement | null>(null); diff --git a/src/routes/player/PreviewViewer.tsx b/src/routes/player/PreviewViewer.tsx index 71ba1a3..21ae663 100644 --- a/src/routes/player/PreviewViewer.tsx +++ b/src/routes/player/PreviewViewer.tsx @@ -1,4 +1,4 @@ -import { Fragment, useMemo } from "react"; +import {Fragment, useMemo} from "react"; import {createUseStyles} from "react-jss"; import {HeadPortal} from "../../util/head/HeadPortal"; import {useImage} from "../../util/media/useImage"; @@ -12,23 +12,28 @@ interface Props { } export function PreviewViewer({previewTrack, position}: Props) { - //console.log("Rendering PreviewViewer#1") - const classes = useStyles(); const cues = useTextTrackCues(previewTrack); - const activeCue = position === null ? null : cues.find(it => it.startTime <= position && it.endTime >= position); - const activeUrl = previewTrack && activeCue ? new URL(activeCue.text, previewTrack.src).toString() : null; + const activeCue = position === null + ? null + : cues.find(it => it.startTime <= position && it.endTime >= position); + const activeUrl = previewTrack && activeCue + ? new URL(activeCue.text, previewTrack.src).toString() + : null; const imageSprite = useMemo(() => parseImageSprite(activeUrl), [activeUrl]); const image = useImage(imageSprite?.src || null); const sprite = useImageSprite(imageSprite, image); - const sources = useMemo(() => previewTrack === null ? null : Array.from(new Set(cues.map(it => { - const url = new URL(it.text, previewTrack.src); - url.hash = ""; - return url.toString(); - }))), [cues, previewTrack]); + const sources = useMemo(() => previewTrack === null + ? null + : Array.from(new Set(cues.map(it => { + const url = new URL(it.text, previewTrack.src); + url.hash = ""; + return url.toString(); + }))), + [cues, previewTrack] + ); return useMemo(() => { - //console.log("Rendering PreviewViewer#2") return ( <Fragment> <HeadPortal> diff --git a/src/routes/player/SeekBar.tsx b/src/routes/player/SeekBar.tsx index 8454069..033c30f 100644 --- a/src/routes/player/SeekBar.tsx +++ b/src/routes/player/SeekBar.tsx @@ -1,34 +1,33 @@ -import {Fragment, MouseEvent, useCallback, useMemo, useState} from "react"; +import {MouseEvent, useCallback, useMemo, useState} from "react"; import {createUseStyles} from "react-jss"; -import {getMousePosition} from "../../util/mouse/getMousePosition"; -import {MousePosition} from "../../util/mouse/MousePosition"; import {useOffsetAbsolute} from "../../util/offset/useOffsetAbsolute"; import {useOffsetRelative} from "../../util/offset/useOffsetRelative"; -import {PreviewBar} from "./PreviewBar"; -import {PlayerApi} from "./video/PlayerApi"; -import {formatDuration} from "../../util/formatDuration"; +import {getMousePosition} from "../../util/mouse/getMousePosition"; +import {MousePosition} from "../../util/mouse/MousePosition"; +import {usePlayerApi} from "./video/VideoContext"; import {useCurrentTime} from "../../util/media/useCurrentTime"; import {useDuration} from "../../util/media/useDuration"; interface Props { - videoApi: PlayerApi | null, - previewSrc: string | null, mousePosition: MousePosition | null, setMousePosition: (position: MousePosition | null) => void, } -export function SeekBar({videoApi, previewSrc, mousePosition, setMousePosition}: Props) { +export function SeekBar( + {mousePosition, setMousePosition}: Props +) { + const videoApi = usePlayerApi(); const classes = useStyles(); const isVisible = mousePosition !== null; + const position = useCurrentTime(videoApi) + const duration = useDuration(videoApi); + const [seekBarRef, setSeekBarRef] = useState<HTMLDivElement | null>(null); const [seekHeadRef, setSeekHeadRef] = useState<HTMLDivElement | null>(null); const [playHeadRef, setPlayHeadRef] = useState<HTMLDivElement | null>(null); - const position = useCurrentTime(videoApi) - const duration = useDuration(videoApi); - const seekHeadOffset = useOffsetAbsolute(seekBarRef, seekHeadRef, mousePosition?.absolute || 0); const playHeadOffset = useOffsetRelative(seekBarRef, playHeadRef, position / duration); @@ -73,7 +72,7 @@ export function SeekBar({videoApi, previewSrc, mousePosition, setMousePosition}: /> ), [classes.playHead, playHeadOffset]); - const seekBar = useMemo(() => ( + return ( <div ref={setSeekBarRef} className={classes.seekBar} @@ -84,21 +83,6 @@ export function SeekBar({videoApi, previewSrc, mousePosition, setMousePosition}: {seekHead} {playHead} </div> - ), [classes.seekBar, onClick, onMouseLeave, onMouseMove, playHead, seekHead]); - - return ( - <Fragment> - <p style={{fontVariant: "tabular-nums"}}> - {formatDuration(mousePosition ? (mousePosition.relative * duration) : position)} / {formatDuration(duration)} - </p> - <PreviewBar - previewSrc={previewSrc} - duration={duration} - position={mousePosition} - hidden={mousePosition === null} - /> - {seekBar} - </Fragment> ); } diff --git a/src/routes/player/SeekBarContainer.tsx b/src/routes/player/SeekBarContainer.tsx new file mode 100644 index 0000000..290c16d --- /dev/null +++ b/src/routes/player/SeekBarContainer.tsx @@ -0,0 +1,41 @@ +import {Fragment} from "react"; +import {MousePosition} from "../../util/mouse/MousePosition"; +import {PreviewBar} from "./PreviewBar"; +import {formatDuration} from "../../util/formatDuration"; +import {useCurrentTime} from "../../util/media/useCurrentTime"; +import {useDuration} from "../../util/media/useDuration"; +import {usePlayerApi} from "./video/VideoContext"; +import {SeekBar} from "./SeekBar"; + +interface Props { + previewSrc: string | null, + mousePosition: MousePosition | null, + setMousePosition: (position: MousePosition | null) => void, +} + +export function SeekBarContainer({previewSrc, mousePosition, setMousePosition}: Props) { + const videoApi = usePlayerApi(); + + const position = useCurrentTime(videoApi) + const duration = useDuration(videoApi); + + return ( + <Fragment> + <p style={{fontVariant: "tabular-nums"}}> + {formatDuration(mousePosition + ? (mousePosition.relative * duration) + : position)} / {formatDuration(duration)} + </p> + <PreviewBar + previewSrc={previewSrc} + duration={duration} + position={mousePosition} + hidden={mousePosition === null} + /> + <SeekBar + mousePosition={mousePosition} + setMousePosition={setMousePosition} + /> + </Fragment> + ); +} diff --git a/src/routes/player/subtitles/SubtitleRenderer.tsx b/src/routes/player/subtitles/SubtitleRenderer.tsx index 8c3a227..6d2d36a 100644 --- a/src/routes/player/subtitles/SubtitleRenderer.tsx +++ b/src/routes/player/subtitles/SubtitleRenderer.tsx @@ -2,22 +2,38 @@ import {Subtitle} from "../../../api/models/Subtitle"; import {Fragment} from "react"; import {TtmlRenderer} from "./TtmlRenderer"; import {SsaRenderer} from "./SsaRenderer"; +import {usePlayerApi} from "../video/VideoContext"; +import {useDuration} from "../../../util/media/useDuration"; interface Props { - videoElement: HTMLVideoElement | null, subtitle: Subtitle | null, - duration: number, className?: string, } export function SubtitleRenderer( props: Props ) { + const playerApi = usePlayerApi(); + const videoElement = playerApi?.getVideoElement() || null; + const duration = useDuration(playerApi); + switch (props.subtitle?.format) { case "ttml": - return (<TtmlRenderer {...props} />); + return ( + <TtmlRenderer + videoElement={videoElement} + duration={duration} + {...props} + /> + ); case "ass": - return (<SsaRenderer {...props} />); + return ( + <SsaRenderer + videoElement={videoElement} + duration={duration} + {...props} + /> + ); default: return (<Fragment/>); } diff --git a/src/routes/player/video/AudioSelection.tsx b/src/routes/player/video/AudioSelection.tsx index d70f245..1c0f735 100644 --- a/src/routes/player/video/AudioSelection.tsx +++ b/src/routes/player/video/AudioSelection.tsx @@ -1,13 +1,9 @@ -import {PlayerApi} from "./PlayerApi"; import {useAudioTracks} from "../../../util/media/useAudioTracks"; +import {usePlayerApi} from "./VideoContext"; -interface Props { - playerApi: PlayerApi | null -} +export function AudioSelection() { + const playerApi = usePlayerApi(); -export function AudioSelection( - {playerApi}: Props -) { const [audioTracks, currentTrack, setCurrentTrack] = useAudioTracks(playerApi); return ( diff --git a/src/routes/player/video/PlayerControls.tsx b/src/routes/player/video/PlayerControls.tsx index 8548d3b..20b4d49 100644 --- a/src/routes/player/video/PlayerControls.tsx +++ b/src/routes/player/video/PlayerControls.tsx @@ -1,14 +1,10 @@ import {ChangeEvent, Fragment, useCallback, useState} from "react"; -import {PlayerApi} from "./PlayerApi"; import {usePaused} from "../../../util/media/usePaused"; +import {usePlayerApi} from "./VideoContext"; -interface Props { - playerApi: PlayerApi | null, -} +export function PlayerControls() { + const playerApi = usePlayerApi(); -export function PlayerControls( - {playerApi}: Props -) { const [volume, setVolume] = useState<number>(1); const paused = usePaused(playerApi); diff --git a/src/routes/player/video/Track.tsx b/src/routes/player/video/Track.tsx index c3a922f..f48bb4b 100644 --- a/src/routes/player/video/Track.tsx +++ b/src/routes/player/video/Track.tsx @@ -1,15 +1,16 @@ import {forwardRef, Fragment, HTMLProps} from "react"; import {createPortal} from "react-dom"; -import {useVideo} from "./VideoContext"; +import {usePlayerApi} from "./VideoContext"; export const Track = forwardRef<HTMLTrackElement, HTMLProps<HTMLTrackElement>>(function ( props, ref ) { - const video = useVideo(); - if (video) { + const playerApi = usePlayerApi(); + const videoElement = playerApi?.getVideoElement(); + if (videoElement) { return createPortal( <track ref={ref} {...props} />, - video + videoElement ); } else { return <Fragment/>; diff --git a/src/routes/player/video/VideoContext.tsx b/src/routes/player/video/VideoContext.tsx index af8de15..1d11ed0 100644 --- a/src/routes/player/video/VideoContext.tsx +++ b/src/routes/player/video/VideoContext.tsx @@ -1,5 +1,6 @@ import {createContext, useContext} from "react"; +import {PlayerApi} from "./PlayerApi"; -const videoContext = createContext<HTMLVideoElement | null>(null); -export const VideoProvider = videoContext.Provider; -export const useVideo = () => useContext<HTMLVideoElement | null>(videoContext); +const playerContext = createContext<PlayerApi | null>(null); +export const PlayerProvider = playerContext.Provider; +export const usePlayerApi = () => useContext<PlayerApi | null>(playerContext); -- GitLab