Skip to content
Snippets Groups Projects
Verified Commit d2a84204 authored by Janne Mareike Koschinski's avatar Janne Mareike Koschinski
Browse files

Implement basic preview seeking

parent b1decb21
Branches
No related tags found
No related merge requests found
import React, {useMemo} from "react";
import {createUseStyles} from "react-jss";
import {useOffsetRef} from "../../util/offset/useOffsetRef";
import {PreviewViewer} from "./PreviewViewer";
interface Props {
previewTrack: HTMLTrackElement | null,
duration: number,
position: number,
}
export function PreviewBar({previewTrack, duration, position}: Props) {
const classes = useStyles();
const [previewBarRef, previewHeadRef, offset] = useOffsetRef(position);
return useMemo(() => (
<div
ref={previewBarRef}
className={classes.previewBar}
>
<div
ref={previewHeadRef}
className={classes.previewHead}
style={{
transform: `translate3d(${offset}px, 0, 0)`
}}
>
<PreviewViewer
previewTrack={previewTrack}
position={position * duration}
/>
</div>
</div>
), [classes.previewHead, classes.previewBar, duration, offset, position, previewBarRef, previewHeadRef, previewTrack]);
}
const useStyles = createUseStyles({
previewBar: {
position: "relative",
width: "40rem",
height: "6rem",
background: "#7c7",
},
previewHead: {
position: "absolute",
top: 0,
bottom: 0,
left: "-5rem",
width: "10rem",
display: "flex",
flexDirection: "column-reverse",
background: "#f00",
}
});
import React, {useMemo} from "react";
import {createUseStyles} from "react-jss";
import {useImage} from "../../util/media/useImage";
import {useTextTrackCues} from "../../util/media/useTextTrackCues";
import {parseImageSprite} from "../../util/sprite/parseImageSprite";
import {useImageSprite} from "../../util/sprite/useImageSprite";
interface Props {
previewTrack: HTMLTrackElement,
position: number,
previewTrack: HTMLTrackElement | null,
position: number | null,
}
export function PreviewViewer({previewTrack, position}: Props) {
const classes = useStyles();
const cues = useTextTrackCues(previewTrack);
const activeCue = cues.find(it => it.startTime <= position && it.endTime >= position)
const activeUrl = 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);
return useMemo(() => (
<img alt="" src={sprite || undefined}/>
<img alt="" className={classes.preview} src={sprite || undefined}/>
), [sprite]);
}
const useStyles = createUseStyles({
preview: {
maxWidth: "100%",
maxHeight: "100%",
}
});
import React, {MouseEvent, useCallback, useMemo, useRef, useState} from "react";
import React, {MouseEvent, useCallback, useMemo, useState} from "react";
import {createUseStyles} from "react-jss";
import {PreviewViewer} from "./PreviewViewer";
import {useOffsetRef} from "../../util/offset/useOffsetRef";
import {PreviewBar} from "./PreviewBar";
interface Props {
previewTrack: HTMLTrackElement | null,
......@@ -27,10 +28,11 @@ function getMousePosition(event: MouseEvent<HTMLDivElement>): MousePosition | nu
export function SeekBar({video, previewTrack, duration, position}: Props) {
const classes = useStyles();
const seekHeadRef = useRef<HTMLDivElement | null>(null);
const [seekPosition, setSeekPosition] = useState<number | null>(null);
const isVisible = seekPosition !== null;
const [seekBarRef, seekHeadRef, offset] = useOffsetRef(seekPosition || 0);
const onMouseLeave = useCallback(() => {
setSeekPosition(null)
}, [setSeekPosition]);
......@@ -50,38 +52,35 @@ export function SeekBar({video, previewTrack, duration, position}: Props) {
if (position === null) {
return;
}
if (seekHeadRef.current) {
seekHeadRef.current.style.transform = `translate3d(${position.absolute}px, 0, 0)`
}
window.requestAnimationFrame(() => {
setSeekPosition(position.relative * duration)
setSeekPosition(position.relative)
})
}, [duration]);
}, []);
const seekBar = useMemo(() => (
<div
ref={seekBarRef}
className={classes.seekBar}
onMouseLeave={onMouseLeave}
onClick={onClick}
onMouseMove={onMouseMove}
>
<div
className={classes.seekHead}
ref={seekHeadRef}
className={classes.seekHead}
style={{
opacity: isVisible ? 1 : 0
opacity: isVisible ? 1 : 0,
transform: `translate3d(${offset}px, 0, 0)`
}}
/>
</div>
), [classes.seekBar, classes.seekHead, isVisible, onClick, onMouseLeave, onMouseMove]);
), [classes.seekBar, classes.seekHead, isVisible, offset, onClick, onMouseLeave, onMouseMove, seekBarRef, seekHeadRef]);
return (
<div>
<React.Fragment>
<PreviewBar previewTrack={previewTrack} duration={duration} position={seekPosition || 0}/>
{seekBar}
{seekPosition !== null && previewTrack && (
<PreviewViewer previewTrack={previewTrack} position={seekPosition}/>
)}
</div>
</React.Fragment>
)
}
......
import {useEffect, useState} from "react";
export const useTextTrackCues = (track: HTMLTrackElement) => {
export const useTextTrackCues = (track: HTMLTrackElement | null) => {
const [cues, setCues] = useState<TextTrackCue[]>([]);
useEffect(() => {
if (track !== null) {
track.track.mode = "hidden";
if (track.readyState >= 1) {
setCues(Array.from(track.track.cues || []))
......@@ -21,6 +22,7 @@ export const useTextTrackCues = (track: HTMLTrackElement) => {
}
}
}
}
}, [track]);
return cues;
}
import {useWidth} from "./useWidth";
export const useOffset = <T extends HTMLElement, U extends HTMLElement>(parent: T | null, child: U | null, position: number) => {
const parentWidth = useWidth(parent);
const childWidth = useWidth(child);
const offset = parentWidth * position;
const minOffset = childWidth / 2;
const maxOffset = parentWidth - minOffset;
return Math.max(minOffset, Math.min(offset, maxOffset));
}
import {useState} from "react";
import {useOffset} from "./useOffset";
export const useOffsetRef = <T extends HTMLElement, U extends HTMLElement>(position: number):
[(it: T | null) => void, (it: U | null) => void, number] => {
const [parent, setParent] = useState<T | null>(null);
const [child, setChild] = useState<U | null>(null);
return [setParent, setChild, useOffset(parent, child, position)];
}
import {useEffect, useState} from "react";
export const useWidth = <T extends HTMLElement>(it: T | null) => {
const [width, setWidth] = useState<number>(0);
useEffect(() => {
let animationFrame: number | null = null;
const listener = () => {
animationFrame = window.requestAnimationFrame(() => {
animationFrame = null;
setWidth(it?.offsetWidth || 0);
});
};
listener();
window.addEventListener("resize", listener);
return () => {
window.removeEventListener("resize", listener);
if (animationFrame !== null) {
window.cancelAnimationFrame(animationFrame);
}
}
}, [it]);
return width;
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment