diff --git a/src/routes/player/Player.tsx b/src/routes/player/Player.tsx index 211dc2c11362190fa789afbbe4aa7c4cef024836..4b93910cac8c0b8a7d564c347e450a8bc193a9b7 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 58dd9fcef9a836b05b1b4333a6641a94a16a3a84..a3dee23dee95b628e9848d2b219021a5cfdc3a3b 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 71ba1a376af67c5640252afdd4d599d402f29493..21ae663eac6d2a2e6464c5ca6cfdc59c183b0c73 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 84540693e4903461e87ee9c58ba19beb5d483ac4..033c30fc3b88f9f263c7ea9e78cc6aebba446dd9 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 0000000000000000000000000000000000000000..290c16d025d6ade574692448729686f8754f9066 --- /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 8c3a2273c64c3f4d333e3c23f41760d4e4ffa3d6..6d2d36a1f0c1d09031948515131e8859da0c317c 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 d70f24559722d8d52a98332b34d5741311ae3f6b..1c0f735f31353ef6c1f62c4aff5bfe289d21a084 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 8548d3ba2d9bf3d81dea45cbb1068668691c49d3..20b4d498f9980985f770787ed5b5f39e59d883bf 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 c3a922f2c34d9e1e5516d54f344aa575eabb4635..f48bb4b3aceb9fea505370683dab18d28b1ea6ed 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 af8de15228071a4ea8f34b4961cc5ce04f385e19..1d11ed07236b801187758b2612223dea6bf1576c 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);