mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-06 02:00:46 +00:00
Merge pull request #523 from storytold/feature/comp/playbar-w-trim
Feature/comp/playbar w trim
This commit is contained in:
6 files changed
+157
-151
No files matched your search
@@ -0,0 +1,49 @@
|
||||
import React, {
|
||||
useRef,
|
||||
// useState
|
||||
} from 'react';
|
||||
import {
|
||||
faGripDots
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
export default function TrimScrubber({
|
||||
width
|
||||
}:{
|
||||
width:number
|
||||
}){
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
return(
|
||||
<div className="trim-scrubber"
|
||||
ref={ref}
|
||||
style={{width: width + "%"}}
|
||||
// onPointerDown={()=>{
|
||||
// if(trimZoneRef.current){
|
||||
// trimZoneRef.current.style.cursor = 'grabbing';
|
||||
// setTrimState((curr)=>({
|
||||
// ...curr,
|
||||
// isScrubbingTrim: true,
|
||||
// }))
|
||||
// }
|
||||
// }}
|
||||
// onPointerUp={()=>{
|
||||
// if(trimZoneRef.current) {
|
||||
// trimZoneRef.current.style.cursor = 'grab';
|
||||
// setTrimState((curr)=>({
|
||||
// ...curr,
|
||||
// isScrubbingTrim: false,
|
||||
// }));
|
||||
// }
|
||||
// }}
|
||||
// onPointerMove={(e: PointerEvent<HTMLDivElement>)=>{
|
||||
// if(trimZoneRef.current && playbarRef.current && isScrubbingTrim){
|
||||
// // console.log(fractionToPercentage(
|
||||
// // (e.clientX - trimZoneRef.current.getBoundingClientRect().left) / (playbarRef.current.getBoundingClientRect().width)
|
||||
// // ) + "%");
|
||||
// }
|
||||
// }}
|
||||
>
|
||||
<FontAwesomeIcon icon={faGripDots} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -4,12 +4,12 @@ import React, {
|
||||
useLayoutEffect,
|
||||
useState,
|
||||
useRef,
|
||||
PointerEvent,
|
||||
// PointerEvent,
|
||||
} from "react";
|
||||
import {
|
||||
faArrowsRepeat,
|
||||
faPlay,
|
||||
faPause,
|
||||
faGripDots,
|
||||
faVolume,
|
||||
faVolumeSlash,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
@@ -19,7 +19,8 @@ import { VideoFakeyouProps } from "../VideoFakeyou/VideoFakeyou";
|
||||
|
||||
import { QuickTrimData, TrimStates, PlaybarStates } from "./types";
|
||||
import { formatSecondsToHHMMSSCS } from "./helpers";
|
||||
import "./styles.scss";
|
||||
import TrimScrbber from "./TrimScrubber";
|
||||
import './styles.scss'
|
||||
|
||||
interface VideoQuickTrimProps extends VideoFakeyouProps {
|
||||
onSelect: (values: QuickTrimData) => void;
|
||||
@@ -54,58 +55,65 @@ export default memo(function VideoQuickTrim({
|
||||
|
||||
const videoRef = useRef<HTMLVideoElement | null>(null);
|
||||
const playbarRef = useRef<HTMLDivElement | null>(null);
|
||||
const trimZoneRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const [{ playbarWidth, timeCursorOffset }, setPlaybarState] =
|
||||
useState<PlaybarStates>(initialPlaybarState);
|
||||
const [playpause, setPlaypause] = useState<"playing" | "paused" | "stopped">(
|
||||
"paused"
|
||||
);
|
||||
const [{
|
||||
playbarWidth,
|
||||
timeCursorOffset
|
||||
}, setPlaybarState] = useState<PlaybarStates>(initialPlaybarState);
|
||||
const [playpause, setPlaypause] = useState<'playing'|'paused'|'ended'>('paused');
|
||||
const [isMuted, setIsMuted] = useState<boolean>(false);
|
||||
const [
|
||||
{
|
||||
canNotTrim,
|
||||
trimStart,
|
||||
// trimEnd,
|
||||
trimDuration,
|
||||
// isScrubbingTrim,
|
||||
},
|
||||
setTrimState,
|
||||
] = useState<TrimStates>(initialTrimState);
|
||||
const [isRepeatOn, setIsRepatOn] = useState<boolean>(true);
|
||||
const [{
|
||||
canNotTrim,
|
||||
trimStart,
|
||||
trimEnd,
|
||||
trimDuration,
|
||||
// isScrubbingTrim,
|
||||
}, setTrimState] = useState<TrimStates>(initialTrimState);
|
||||
|
||||
|
||||
const videoRefCallback = useCallback(
|
||||
node => {
|
||||
if (node !== null) {
|
||||
// DOM node referenced by ref has changed and exists
|
||||
videoRef.current = node;
|
||||
|
||||
node.onloadedmetadata = () => {
|
||||
if (node.duration >= 3) {
|
||||
setTrimState(curr => ({
|
||||
...curr,
|
||||
canNotTrim: false,
|
||||
trimDuration: 3,
|
||||
trimEnd: 3,
|
||||
maxDuration: node.duration,
|
||||
}));
|
||||
onSelect({
|
||||
trimStartSeconds: 0,
|
||||
trimEndSeconds: 3,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
node.ontimeupdate = (e: PointerEvent) => {
|
||||
setPlaybarState(curr => ({
|
||||
const videoRefCallback = useCallback(node => {
|
||||
console.log("videoRefCallback");
|
||||
if (node !== null) {
|
||||
// DOM node referenced by ref has changed and exists
|
||||
videoRef.current = node;
|
||||
|
||||
node.onloadedmetadata = ()=>{
|
||||
if (node.duration >= 3){
|
||||
setTrimState((curr)=>({
|
||||
...curr,
|
||||
timeCursorOffset:
|
||||
(node.currentTime / node.duration) * (playbarWidth - 8),
|
||||
canNotTrim: false,
|
||||
trimDuration: 3,
|
||||
trimEnd: 3,
|
||||
maxDuration: node.duration
|
||||
}));
|
||||
};
|
||||
} // else{} DOM node referenced by ref has been unmounted
|
||||
},
|
||||
[playbarWidth, onSelect]
|
||||
); //END videoRefCallback
|
||||
//TODO: this sbould be set in USEEFFECT
|
||||
//IFF USEEFFECT starts working again
|
||||
onSelect({
|
||||
trimStartSeconds: 0,
|
||||
trimEndSeconds: 3,
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
node.ontimeupdate = ()=>{
|
||||
if(isRepeatOn && node.currentTime >= trimEnd){
|
||||
node.currentTime = 0;
|
||||
}
|
||||
setPlaybarState((curr)=>({
|
||||
...curr,
|
||||
timeCursorOffset: (node.currentTime / node.duration) * (playbarWidth-8)
|
||||
}));
|
||||
|
||||
};
|
||||
|
||||
node.onplay = ()=>{ setPlaypause("playing");};
|
||||
node.onpause = ()=>{setPlaypause("paused");};
|
||||
node.onended = ()=>{setPlaypause("ended");};
|
||||
|
||||
} // else{} DOM node referenced by ref has been unmounted
|
||||
}, [playbarWidth, onSelect, isRepeatOn, trimEnd]); //END videoRefCallback
|
||||
|
||||
function handleWindowResize() {
|
||||
if (playbarRef.current !== null) {
|
||||
@@ -133,33 +141,38 @@ export default memo(function VideoQuickTrim({
|
||||
};
|
||||
}, []);
|
||||
|
||||
const handleChangeTrimDuration = (selected: string) => {
|
||||
if (!canNotTrim) {
|
||||
setTrimState(curr => ({
|
||||
const handleChangeTrimDuration = (selected: string) =>{
|
||||
if(!canNotTrim){
|
||||
const newTrimEnd = trimStart+ trimOptions[selected];
|
||||
setTrimState((curr)=>({
|
||||
...curr,
|
||||
trimDuration: trimOptions[selected],
|
||||
trimEnd: trimStart + trimOptions[selected],
|
||||
}));
|
||||
trimEnd: newTrimEnd,
|
||||
}))
|
||||
onSelect({
|
||||
trimStartSeconds: trimStart,
|
||||
trimEndSeconds: trimStart + trimOptions[selected],
|
||||
trimEndSeconds: newTrimEnd,
|
||||
});
|
||||
if (isRepeatOn
|
||||
&& videoRef.current
|
||||
&& videoRef.current.currentTime > newTrimEnd
|
||||
){
|
||||
videoRef.current.currentTime = trimStart;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const handlePlaypause = () => {
|
||||
if (playpause === "paused" || playpause === "stopped") {
|
||||
const handlePlaypause = ()=>{
|
||||
if (playpause === 'paused' || playpause === 'ended'){
|
||||
videoRef.current?.play();
|
||||
setPlaypause("playing");
|
||||
} else {
|
||||
}else{
|
||||
videoRef.current?.pause();
|
||||
setPlaypause("paused");
|
||||
}
|
||||
};
|
||||
|
||||
const trimZoneWidth = videoRef.current
|
||||
? trimDuration > 0 && trimDuration < videoRef.current.duration
|
||||
? (trimDuration / videoRef.current.duration) * 100
|
||||
const trimScrubberWidth = videoRef.current
|
||||
? trimDuration > 0 && trimDuration < videoRef.current.duration
|
||||
? (trimDuration / videoRef.current.duration * 100)
|
||||
: 100
|
||||
: 0;
|
||||
|
||||
@@ -199,41 +212,10 @@ export default memo(function VideoQuickTrim({
|
||||
{/* END of Video Wrapper */}
|
||||
<div className="playbar" ref={playbarRefCallback}>
|
||||
<div className="playbar-bg" />
|
||||
<div
|
||||
className="trimzone"
|
||||
ref={trimZoneRef}
|
||||
style={{ width: trimZoneWidth + "%" }}
|
||||
// onPointerDown={()=>{
|
||||
// if(trimZoneRef.current){
|
||||
// trimZoneRef.current.style.cursor = 'grabbing';
|
||||
// setTrimState((curr)=>({
|
||||
// ...curr,
|
||||
// isScrubbingTrim: true,
|
||||
// }))
|
||||
// }
|
||||
// }}
|
||||
// onPointerUp={()=>{
|
||||
// if(trimZoneRef.current) {
|
||||
// trimZoneRef.current.style.cursor = 'grab';
|
||||
// setTrimState((curr)=>({
|
||||
// ...curr,
|
||||
// isScrubbingTrim: false,
|
||||
// }));
|
||||
// }
|
||||
// }}
|
||||
// onPointerMove={(e: PointerEvent<HTMLDivElement>)=>{
|
||||
// if(trimZoneRef.current && playbarRef.current && isScrubbingTrim){
|
||||
// // console.log(fractionToPercentage(
|
||||
// // (e.clientX - trimZoneRef.current.getBoundingClientRect().left) / (playbarRef.current.getBoundingClientRect().width)
|
||||
// // ) + "%");
|
||||
// }
|
||||
// }}
|
||||
>
|
||||
<FontAwesomeIcon icon={faGripDots} />
|
||||
</div>
|
||||
<div className="playcursor" style={{ left: timeCursorOffset + "px" }} />
|
||||
</div>{" "}
|
||||
{/* END of Playbar */}
|
||||
<TrimScrbber width={trimScrubberWidth} />
|
||||
<div className="playcursor" style={{left: timeCursorOffset+"px"}}/>
|
||||
</div> {/* END of Playbar */}
|
||||
|
||||
<div className="d-flex w-100 justify-content-between mt-3 flex-wrap">
|
||||
<div className="playpause-external d-flex align-items-center flex-wrap mb-2">
|
||||
<Button
|
||||
@@ -242,14 +224,20 @@ export default memo(function VideoQuickTrim({
|
||||
variant="secondary"
|
||||
onClick={handlePlaypause}
|
||||
/>
|
||||
<Button
|
||||
className="button-repeat"
|
||||
icon={faArrowsRepeat}
|
||||
variant={isRepeatOn ? "primary":"secondary"}
|
||||
onClick={()=>setIsRepatOn((curr)=>(!curr))}
|
||||
/>
|
||||
<Button
|
||||
className="button-mute"
|
||||
icon={isMuted ? faVolumeSlash : faVolume}
|
||||
variant="secondary"
|
||||
onClick={() => setIsMuted(curr => !curr)}
|
||||
/>
|
||||
<div className="playtime ms-2 d-flex">
|
||||
<span>
|
||||
<div className="playtime d-flex">
|
||||
<span >
|
||||
<p>
|
||||
{`${formatSecondsToHHMMSSCS(
|
||||
videoRef.current?.currentTime || 0
|
||||
|
||||
@@ -78,7 +78,7 @@ $playbar-height: 2rem;
|
||||
background-color: $panel-inner-border;
|
||||
border-radius: 0 0 0.5rem 0.5rem;
|
||||
}
|
||||
.trimzone {
|
||||
.trim-scrubber{
|
||||
position: absolute;
|
||||
top: -1rem;
|
||||
height: 3rem;
|
||||
@@ -111,13 +111,15 @@ $playbar-height: 2rem;
|
||||
height: 40px;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
.button-mute {
|
||||
.button-mute, .button-repeat{
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
.playtime {
|
||||
span {
|
||||
width: 60px;
|
||||
.playtime{
|
||||
font-size: smaller;
|
||||
span{
|
||||
width: 50px;
|
||||
}
|
||||
div {
|
||||
width: 10px;
|
||||
|
||||
+4
-4
@@ -20,10 +20,10 @@ interface Props {
|
||||
function StudioIntroResultPage(props: Props) {
|
||||
const { jobToken } = useParams<{ jobToken: string }>();
|
||||
const history = useHistory();
|
||||
const [mediaToken, setMediaToken] = useState(
|
||||
"m_f5kp3hm74qeq16eq7536jb73jkbvkh"
|
||||
); // Set the media token after polling success
|
||||
|
||||
// const [mediaToken, setMediaToken] = useState(
|
||||
// "m_f5kp3hm74qeq16eq7536jb73jkbvkh"
|
||||
// ); // Set the media token after polling success
|
||||
const mediaToken = "m_f5kp3hm74qeq16eq7536jb73jkbvkh"
|
||||
// const [jobExists, setJobExists] = useState<boolean | null>(null);
|
||||
|
||||
const [mediaFile, setMediaFile] = useState<MediaFileType>();
|
||||
|
||||
+4
-3
@@ -20,9 +20,10 @@ export default function PageVSTApp() {
|
||||
const { jobToken } = useParams<{ jobToken: string }>();
|
||||
const videoRef = useRef<HTMLVideoElement>(null);
|
||||
const history = useHistory();
|
||||
const [mediaToken, setMediaToken] = useState(
|
||||
"m_wnnz9jey96dwhss9qk1891v9gmvnj8" // Currently hardcoded video media token value. Let a job polling function set the value when job returns media on success.
|
||||
);
|
||||
// const [mediaToken, setMediaToken] = useState(
|
||||
// "m_wnnz9jey96dwhss9qk1891v9gmvnj8" // Currently hardcoded video media token value. Let a job polling function set the value when job returns media on success.
|
||||
// );
|
||||
const mediaToken = "m_wnnz9jey96dwhss9qk1891v9gmvnj8";
|
||||
// const [jobExists, setJobExists] = useState<boolean | null>(null);
|
||||
const [vstValues, setVstValues] = useState<VSTType>({
|
||||
...initialValues,
|
||||
|
||||
+4
-38
@@ -1,4 +1,7 @@
|
||||
import React, { useRef, useState } from "react";
|
||||
import React, {
|
||||
// useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import { NavLink } from 'react-router-dom';
|
||||
import { useParams, useHistory } from "react-router-dom";
|
||||
|
||||
@@ -14,7 +17,6 @@ import EnqueueVideoStyleTransfer from "@storyteller/components/src/api/video_sty
|
||||
|
||||
|
||||
import { Action, State } from "../../reducer";
|
||||
// import { TableOfKeyValues } from "../../commons";
|
||||
import { initialValues } from "./defaultValues";
|
||||
import {
|
||||
mapRequest,
|
||||
@@ -33,7 +35,6 @@ export default function PageVSTApp({
|
||||
parentPath: string;
|
||||
dispatchPageState: (action: Action) => void;
|
||||
}) {
|
||||
const videoRef = useRef<HTMLVideoElement>(null);
|
||||
const { mediaToken } = useParams<any>();
|
||||
|
||||
const [vstValues, setVstValues] = useState<VSTType>({
|
||||
@@ -41,34 +42,6 @@ export default function PageVSTApp({
|
||||
fileToken: pageState.mediaFileToken || mediaToken,
|
||||
});
|
||||
|
||||
|
||||
if(videoRef?.current){
|
||||
if(debug) console.log("set-up video element listeners");
|
||||
const ve = videoRef.current
|
||||
ve.onloadedmetadata = () =>{
|
||||
const newValues : {
|
||||
width?: number;
|
||||
height?: number;
|
||||
maxDuration?: number;
|
||||
} = {};
|
||||
// if (ve.videoWidth && ve.videoHeight) {
|
||||
// const aspectRatio = ve.videoWidth/ve.videoHeight
|
||||
// if (aspectRatio > 1) newValues.width = 960;
|
||||
// else if (aspectRatio < 1) newValues.height = 960
|
||||
// else if (debug) console.log(`aspectRaio: ${aspectRatio}`);
|
||||
// }
|
||||
if(ve.duration){
|
||||
newValues.maxDuration = ve.duration
|
||||
// newValues.framesCap = newValues.maxFrames;
|
||||
}
|
||||
setVstValues((curr)=>({
|
||||
...curr,
|
||||
...newValues,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const handleOnChange = (val:{[key: string]: number|string|boolean|undefined}) => {
|
||||
setVstValues((curr)=>({...curr, ...val}));
|
||||
}
|
||||
@@ -119,13 +92,6 @@ export default function PageVSTApp({
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/* <div className="col-12 col-md-6">
|
||||
<Label label={t("image.label.preview")}/>
|
||||
{debug && <TableOfKeyValues keyValues={vstValues} height={400}/>}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div className="row g-3 mb-4"> */}
|
||||
<div className="col-12 col-md-6">
|
||||
<TextArea
|
||||
{...{
|
||||
|
||||
Reference in new issue
Block a user