diff --git a/packages/fakeyou.com/src/components/common/VideoQuickTrim/TrimScrubber.tsx b/packages/fakeyou.com/src/components/common/VideoQuickTrim/TrimScrubber.tsx deleted file mode 100644 index 3b30dad77d..0000000000 --- a/packages/fakeyou.com/src/components/common/VideoQuickTrim/TrimScrubber.tsx +++ /dev/null @@ -1,101 +0,0 @@ -import React, { - useRef, - useState, - PointerEvent -} from 'react'; -import { - faGripDots -} from "@fortawesome/pro-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { QuickTrimData } from './types'; - -export default function TrimScrubber({ - trimStart, - trimDuration, - duration, - width, - boundingWidth, - onChange -}:{ - trimStart:number; - trimDuration: number; - duration: number; - boundingWidth: number; - width:number; - onChange:(val:QuickTrimData)=>void -}){ - const [{ - left, - leftOffset, - pointerStart - }, setState] = useState<{ - left: number, - leftOffset:number - pointerStart:number - }>({ - left: trimStart/duration*boundingWidth, - leftOffset: trimStart/duration*boundingWidth, - pointerStart: -1 - }); - const ref = useRef(null); - - if(ref.current){ - ref.current.style.cursor = pointerStart >= 0 - ? 'grabbing' : 'grab'; - } - - function handleScrubStart(e: PointerEvent){ - e.persist(); - // console.log(`start: ${pointerStart}` ); - setState((curr)=>({ - ...curr, - pointerStart: e.clientX - })); - } - function handleScrubEnd(e: PointerEvent){ - e.persist(); - // console.log(`end` ); - setState((curr)=>({ - ...curr, - pointerStart: -1, - leftOffset: left, - })); - //console.log(`${duration} * ${left} / ${boundingWidth} = ${duration * left / boundingWidth}`) - onChange(({ - trimStartSeconds: duration * left / boundingWidth, - trimEndSeconds: duration * left / boundingWidth + trimDuration, - })) - } - function handleScrubMove(e: PointerEvent){ - e.persist(); - if(pointerStart >= 0 && pointerStart!==null && ref.current){ - // console.log(`${e.clientX} - ${pointerStart} = ${e.clientX-pointerStart}`) - let newLeft = leftOffset + e.clientX - pointerStart; - // console.log(`${newLeft} + ${boundingWidth * widthPercent/100} > ${boundingWidth}`) - if (newLeft + width > boundingWidth) { - newLeft = boundingWidth - width; - }else if(newLeft < 0) { - newLeft = 0; - } - setState((curr)=>({ - ...curr, - left: newLeft - })); - } - } - return( -
- -
- ); -} diff --git a/packages/fakeyou.com/src/components/common/VideoQuickTrim/VideoQuickTrim.tsx b/packages/fakeyou.com/src/components/common/VideoQuickTrim/VideoQuickTrim.tsx index 55503dbbcd..8a11af6729 100644 --- a/packages/fakeyou.com/src/components/common/VideoQuickTrim/VideoQuickTrim.tsx +++ b/packages/fakeyou.com/src/components/common/VideoQuickTrim/VideoQuickTrim.tsx @@ -1,296 +1,198 @@ import React, { memo, useCallback, - useLayoutEffect, - useState, + useEffect, + useReducer, useRef, } from "react"; import { - faArrowsRepeat, faPlay, faPause, - faVolume, - faVolumeSlash, } from "@fortawesome/pro-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { Button, SelectionBubbles, VideoFakeyou } from "components/common"; + +import { useLocalize } from "hooks"; +import { VideoFakeyou } from "components/common"; import { VideoFakeyouProps } from "../VideoFakeyou/VideoFakeyou"; -import { QuickTrimData, TrimStates, PlaybarStates } from "./types"; -import { formatSecondsToHHMMSSCS } from "./helpers"; -import TrimScrbber from "./TrimScrubber"; +import { + reducer, + initialState, + ACTION_TYPES, + PLAYPUASE_STATES, + STATE_STATUSES, +} from "./reducer"; + +import { QuickTrimData } from "./utilities"; + +import { ProgressBar } from "./components/ProgressBar"; +import { ControlBar } from "./components/ControlBar"; import './styles.scss' interface VideoQuickTrimProps extends VideoFakeyouProps { onSelect: (values: QuickTrimData) => void; + trimStartSeconds: number; + trimEndSeconds: number; } -const trimOptions: { [key: string]: number } = { - "3s": 3, - "5s": 5, - "10s": 10, - "15s": 15, -}; - -const initialTrimState: TrimStates = { - canNotTrim: true, - trimDuration: 0, - trimReset: new Date(), - trimStart: 0, - trimEnd: 0, - maxDuration: 0, -}; - -const initialPlaybarState: PlaybarStates = { - playbarWidth: 0, - timeCursorOffset: 0, -}; - -export default memo(function VideoQuickTrim({ +export const VideoQuickTrim = memo(({ onSelect, + trimStartSeconds: propsTrimStartSeconds, + trimEndSeconds : propsTrimEndSeconds, ...rest -}: VideoQuickTrimProps) { - // console.log("VideoQuickTrim Rerender!!"); - +}: VideoQuickTrimProps) => { + const { t } = useLocalize("VideoPlayerQuickTrim"); const videoRef = useRef(null); - const playbarRef = useRef(null); - - const [{ - playbarWidth, - timeCursorOffset - }, setPlaybarState] = useState(initialPlaybarState); - const [playpause, setPlaypause] = useState<'playing'|'paused'|'ended'>('paused'); - const [isMuted, setIsMuted] = useState(false); - const [isRepeatOn, setIsRepatOn] = useState(true); - const [{ - canNotTrim, - trimReset, - trimStart, - trimEnd, - trimDuration, - maxDuration - }, setTrimState] = useState(initialTrimState); + const [compState, dispatchCompState] = useReducer(reducer, initialState); - + if(compState.status === STATE_STATUSES.LOAD_ORDER_ERROR){ + console.log(`${compState.errorMessage[compState.errorMessage.length -1]}`); + } const videoRefCallback = useCallback(node => { - //console.log("videoRefCallback"); + function setPlaypause(newState: PLAYPUASE_STATES){ + dispatchCompState({ + type: ACTION_TYPES.SET_PLAYPUASE, + payload: { playpause: newState} + }) + } 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 - })); - //TODO: this sbould be set in USEEFFECT - //IFF USEEFFECT starts working again - onSelect({ - trimStartSeconds: 0, - trimEndSeconds: 3, - }) - } + dispatchCompState({ + type: ACTION_TYPES.ON_LOADED_METADATA, + payload:{ videoDuration: node.duration,} + }); }; - + node.ontimeupdate = ()=>{ - if(isRepeatOn && - (node.currentTime >= trimEnd || node.currentTime <= trimStart) + if(compState.trimStartSeconds !== undefined && + compState.trimEndSeconds !== undefined && + compState.playbarWidth !== undefined){ + // reset current time when on repeat + if(compState.isRepeatOn && + (node.currentTime >= compState.trimEndSeconds || node.currentTime <= compState.trimStartSeconds) ){ - node.currentTime = trimStart; + node.currentTime = compState.trimStartSeconds; + console.log('play looped'); + } + + dispatchCompState({ + type: ACTION_TYPES.MOVE_TIMECURSOR, + payload: { + timeCursorOffset: (node.currentTime / node.duration) * (compState.playbarWidth) + } + }); + }else{ + console.log('ontimeupdate failed'); } - setPlaybarState((curr)=>({ - ...curr, - timeCursorOffset: (node.currentTime / node.duration) * (playbarWidth-8) - })); - }; - node.onplay = ()=>{ setPlaypause("playing");}; - node.onpause = ()=>{setPlaypause("paused");}; - node.onended = ()=>{setPlaypause("ended");}; - + node.onplay = ()=>setPlaypause(PLAYPUASE_STATES.PLAYING); + node.onpause = ()=>setPlaypause(PLAYPUASE_STATES.PAUSED); + node.onended = ()=>setPlaypause(PLAYPUASE_STATES.ENDED); } // else{} DOM node referenced by ref has been unmounted - }, [playbarWidth, onSelect, isRepeatOn, trimStart, trimEnd]); //END videoRefCallback + }, [ + compState.trimStartSeconds, + compState.trimEndSeconds, + compState.playbarWidth, + compState.isRepeatOn + ]); //END videoRefCallback\ - function handleWindowResize() { - if (playbarRef.current !== null) { - const newWidth = playbarRef.current.getBoundingClientRect().width; - setPlaybarState(curr => ({ - playbarWidth: newWidth, - timeCursorOffset: - (curr.timeCursorOffset / (curr.playbarWidth - 8)) * (newWidth - 8), - })); - } - } - const playbarRefCallback = useCallback(node => { - if (node !== null) { - playbarRef.current = node; - setPlaybarState(curr => ({ - ...curr, - playbarWidth: node.getBoundingClientRect().width, - })); - } - }, []); - useLayoutEffect(() => { - window.addEventListener("resize", handleWindowResize); - return () => { - window.removeEventListener("resize", handleWindowResize); - }; - }, []); - - const handleChangeTrimDuration = (selected: string) =>{ - if(!canNotTrim && maxDuration > 0 && trimOptions[selected] <= maxDuration){ - let newTrimStart = trimStart; - let newTrimEnd = trimStart+ trimOptions[selected]; - if (newTrimEnd > maxDuration){ - newTrimEnd = maxDuration; - newTrimStart = maxDuration - trimOptions[selected]; + useEffect(()=>{ + if( compState.trimStartSeconds + && compState.trimEndSeconds + && ( + compState.trimStartSeconds !== propsTrimStartSeconds + || compState.trimEndSeconds !== propsTrimEndSeconds + ) + ){ + onSelect({ + trimStartSeconds: compState.trimStartSeconds, + trimEndSeconds: compState.trimEndSeconds, + }); } - setTrimState((curr)=>({ - ...curr, - trimReset: new Date(), - trimDuration: trimOptions[selected], - trimStart: newTrimStart, - trimEnd: newTrimEnd, - })) - onSelect({ - trimStartSeconds: newTrimStart, - trimEndSeconds: newTrimEnd, - }); - if (isRepeatOn - && videoRef.current - && videoRef.current.currentTime > newTrimEnd - ){ - videoRef.current.currentTime = newTrimStart; - } - } - } + }, [onSelect, compState.trimStartSeconds, compState.trimEndSeconds, propsTrimStartSeconds, propsTrimEndSeconds]) - const handlePlaypause = ()=>{ - if (playpause === 'paused' || playpause === 'ended'){ + function videoCanPlay(){ + return (compState.playpause === PLAYPUASE_STATES.PAUSED + || compState.playpause === PLAYPUASE_STATES.ENDED + || compState.playpause === PLAYPUASE_STATES.READY + ); + } + function handlePlaypause(){ + if (videoCanPlay()){ videoRef.current?.play(); - }else{ + }else if (compState.playpause === PLAYPUASE_STATES.PLAYING){ videoRef.current?.pause(); + }else { + console.log('Playpause is triggered while it is NOT_READY'); } }; - const trimScrubberWidth = videoRef.current && playbarWidth > 0 - ? trimDuration > 0 && trimDuration < videoRef.current.duration - ? (trimDuration / videoRef.current.duration * playbarWidth) - : playbarWidth - : 0; - return (
- -
- {playpause === "paused" && ( - - )} - {playpause === "playing" && ( - - )} -
- {videoRef.current && canNotTrim && ( + {compState.playpause !== PLAYPUASE_STATES.NOT_READY && +
+ {videoCanPlay() && ( + + )} + {compState.playpause === PLAYPUASE_STATES.PLAYING && ( + + )} +
+ } + { compState.canNotTrim === true &&
-

Warning: Sorry Your Video is TOO Short

+

{t('error.videoTooShort')}

- )} -
- {/* END of Video Wrapper */} -
-
- {trimScrubberWidth > 0 && - playbarWidth > 0 && - maxDuration > 0 && - { - //console.log(val); - setTrimState((curr)=>({ - ...curr, - trimStart: val.trimStartSeconds, - trimEnd: val.trimEndSeconds - })) - onSelect({ - trimStartSeconds: val.trimStartSeconds, - trimEndSeconds: val.trimEndSeconds, - }); - }} - /> } -
-
{/* END of Playbar */} - -
-
-
- -
+
{/* END of Video Wrapper */} + { + if(videoRef.current !== null && videoRef.current.currentTime + && compState.playbarWidth && compState.videoDuration){ + videoRef.current.currentTime = newPos / compState.playbarWidth * compState.videoDuration; + } + }} + /> +
); }); diff --git a/packages/fakeyou.com/src/components/common/VideoQuickTrim/components/ControlBar.tsx b/packages/fakeyou.com/src/components/common/VideoQuickTrim/components/ControlBar.tsx new file mode 100644 index 0000000000..248e9d9f9e --- /dev/null +++ b/packages/fakeyou.com/src/components/common/VideoQuickTrim/components/ControlBar.tsx @@ -0,0 +1,102 @@ +import React, { memo } from 'react' +import { + faArrowsRepeat, + faPlay, + faPause, + faVolume, + faVolumeSlash, +} from "@fortawesome/pro-solid-svg-icons"; + +import { Button, SelectionBubbles } from "components/common"; + +import { + Action, + ACTION_TYPES, + PLAYPUASE_STATES +} from '../reducer'; +import { TRIM_OPTIONS, formatSecondsToHHMMSSCS } from "../utilities"; + +export const ControlBar = memo(({ + readyToMount, + videoCurrentTime, + videoDuration, + isRepeatOn, + isMuted, + playpause, + handlePlaypause, + dispatchCompState +}:{ + readyToMount: boolean; + videoCurrentTime: number | undefined; + videoDuration: number | undefined; + isMuted: boolean, + isRepeatOn: boolean; + playpause: string; + handlePlaypause: ()=>void; + dispatchCompState: (action: Action) => void; +})=>{ + function handleSetTrimDuration(selected: string){ + dispatchCompState({ + type: ACTION_TYPES.SET_TRIM_DURATION, + payload: { + trimDurationString: selected + } + }); + // onSelect({ + // trimStartSeconds: newTrimStart, + // trimEndSeconds: newTrimEnd, + // }); + }; + if(readyToMount){ + return( +
+
+
+ +
+ ); + }else{ + console.log('TODO: ControlBar should rendering loading state instead of null'); + return null; + } + +}); \ No newline at end of file diff --git a/packages/fakeyou.com/src/components/common/VideoQuickTrim/components/PlayCursor.tsx b/packages/fakeyou.com/src/components/common/VideoQuickTrim/components/PlayCursor.tsx new file mode 100644 index 0000000000..f1ed16ae45 --- /dev/null +++ b/packages/fakeyou.com/src/components/common/VideoQuickTrim/components/PlayCursor.tsx @@ -0,0 +1,24 @@ +import React from 'react'; +import { withScrubbing, withScrubbingPropsI } from './withScrubbing'; + +interface PlayCursorPropsI extends withScrubbingPropsI{ + onChanged: (posPercent: number)=>void; +} + +export const PlayCursor = ({ + onChanged, + ...rest +}:PlayCursorPropsI)=>{ + const PlayCursorWithScrubbing = withScrubbing(()=>{ + return( +
+ ); + }); + + return( + + ); +}; diff --git a/packages/fakeyou.com/src/components/common/VideoQuickTrim/components/ProgressBar.tsx b/packages/fakeyou.com/src/components/common/VideoQuickTrim/components/ProgressBar.tsx new file mode 100644 index 0000000000..fbef730a4c --- /dev/null +++ b/packages/fakeyou.com/src/components/common/VideoQuickTrim/components/ProgressBar.tsx @@ -0,0 +1,109 @@ +import React, { + memo, + useCallback, + useLayoutEffect, + useRef +} from 'react'; +import { + Action, + ACTION_TYPES, +} from '../reducer'; +import { TrimScrubber } from './TrimScrubber'; +import { PlayCursor } from './PlayCursor'; + +export const ProgressBar = memo(({ + readyToMount, + timeCursorOffset, + trimStartSeconds, + trimDuration, + playbarWidth, + scrubberWidth, + videoDuration, + videoBuffered, + onPlayCursorChanged, + dispatchCompState +}:{ + readyToMount: boolean; + timeCursorOffset: number; + trimStartSeconds: number; + trimDuration: number; + playbarWidth: number; + scrubberWidth: number; + videoDuration: number; + videoBuffered:TimeRanges | undefined; + onPlayCursorChanged: (newPos: number) => void; + dispatchCompState: (action: Action) => void; +})=>{ + const playbarRef = useRef(null); + + const playbarRefCallback = useCallback(node => { + if (node !== null) { + playbarRef.current = node; + dispatchCompState({ + type: ACTION_TYPES.SET_PLAYBAR_LAYOUT, + payload: { + playbarWidth: node.getBoundingClientRect().width, + } + }) + }else{ + // console.log(node); + } + }, [dispatchCompState]); + + const handleWindowResize = useCallback(()=> { + if (playbarRef.current !== null && readyToMount){ + dispatchCompState({ + type: ACTION_TYPES.SET_PLAYBAR_LAYOUT, + payload: { + playbarWidth: playbarRef.current.getBoundingClientRect().width, + } + }); + } + },[dispatchCompState, readyToMount]); + useLayoutEffect(() => { + window.addEventListener("resize", handleWindowResize); + return () => { + window.removeEventListener("resize", handleWindowResize); + }; + }, [handleWindowResize]); + + if(readyToMount){ + // console.log('progress bar rendering'); + return( +
+
+ {videoBuffered !== undefined && + + } + +
+ { + const newTrimStartSeconds = newPos / (playbarWidth) * videoDuration; + dispatchCompState({ + type: ACTION_TYPES.MOVE_TRIM, + payload: { + trimStartSeconds: newTrimStartSeconds, + trimEndSeconds: newTrimStartSeconds + trimDuration + } + }); + }} + /> + + {/* END of Playbar */}
+ ); + }else{ + console.log('TODO: ProgressBar should rendering loading state instead of null'); + return null; + } +}); \ No newline at end of file diff --git a/packages/fakeyou.com/src/components/common/VideoQuickTrim/components/TrimScrubber.tsx b/packages/fakeyou.com/src/components/common/VideoQuickTrim/components/TrimScrubber.tsx new file mode 100644 index 0000000000..787861a47e --- /dev/null +++ b/packages/fakeyou.com/src/components/common/VideoQuickTrim/components/TrimScrubber.tsx @@ -0,0 +1,42 @@ +import React, { + memo, +} from 'react'; +import { + faGripDots +} from "@fortawesome/pro-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { withScrubbing, withScrubbingPropsI } from './withScrubbing'; + +interface TrimScrubberPropsI extends withScrubbingPropsI { + trimStartSeconds:number; + trimDuration: number; + videoDuration: number; + onChange:(newPos:number)=>void +} +export const TrimScrubber = memo(({ + trimStartSeconds, + trimDuration, + videoDuration, + onChange, + ...rest +}:TrimScrubberPropsI)=>{ + + const TrimScrubberWithScrubbing = withScrubbing(() => { + return( +
+ +
+ ); + }); + + return ( + + ); +}); diff --git a/packages/fakeyou.com/src/components/common/VideoQuickTrim/components/withScrubbing.tsx b/packages/fakeyou.com/src/components/common/VideoQuickTrim/components/withScrubbing.tsx new file mode 100644 index 0000000000..92ba05d760 --- /dev/null +++ b/packages/fakeyou.com/src/components/common/VideoQuickTrim/components/withScrubbing.tsx @@ -0,0 +1,147 @@ +import React,{ + memo, + useEffect, + useRef, + useState, + useLayoutEffect, +} from 'react'; + +export interface withScrubbingPropsI { + boundingWidth: number; + scrubberWidth: number; + scrubPosition?: number; // scrubber location as px + styleOverride?: {[key: string]: string|number }; + onScrubChanged?: (newPos: number)=>void; + //return scrubber location as px +} + +type withSrcubbingStates = { + currLeftOffset: number, + prevLeftOffset: number; + pointerStartPos: number; +} + +export const withScrubbing =

(Component: React.ComponentType

) => memo(({ + boundingWidth, + scrubberWidth, + scrubPosition: propsLeftOffset = 0, + styleOverride = {}, + onScrubChanged, + ...rest +}: withScrubbingPropsI) => { + console.log(`withSCRUBBING reRENDERING!! `); + const refEl = useRef(null); + const refListener = useRef(Date.now()); + + + const [{ + currLeftOffset, pointerStartPos, + prevLeftOffset + }, setStates] = useState({ + currLeftOffset: propsLeftOffset, + prevLeftOffset: propsLeftOffset, + pointerStartPos: -1 // negative denotes pointer not engaged + }); + + useLayoutEffect(() => { + console.log(`withSCRUBBING useLAYOUTeffect!! `); + function handleScrubStart (e: MouseEvent) { + if(refEl.current){ + if(refEl.current.contains(e.target as Node)){ + setStates((curr)=>({ + ...curr, + pointerStartPos: e.clientX + })); + return true; + } + } + }; + function handleScrubEnd (e: MouseEvent){ + e.preventDefault(); + e.stopPropagation(); + setStates((curr)=>({ + ...curr, + pointerStartPos: -1, + prevLeftOffset: curr.currLeftOffset, + setBySelf: Date.now(), + })); + }; + function handleScrubMove (e: MouseEvent){ + e.preventDefault(); + e.stopPropagation(); + setStates((curr)=>{ + if(curr.pointerStartPos >= 0 && curr.pointerStartPos!==null){ + let newLeftOffset = curr.prevLeftOffset + e.clientX - curr.pointerStartPos; + if (newLeftOffset + scrubberWidth > boundingWidth) { + newLeftOffset = boundingWidth - scrubberWidth; + }else if(newLeftOffset < 0) { + newLeftOffset = 0; + } + if(newLeftOffset !== curr.currLeftOffset){ + return{ + ...curr, + currLeftOffset: newLeftOffset, + } + } + } + return curr; + }); + }; + if(!(window as any)[`listener-id-${refListener}`]){ + (window as any)[`listender-id-${refListener}`] = true; + window.addEventListener("mousedown", handleScrubStart); + window.addEventListener("mouseup", handleScrubEnd); + window.addEventListener("mousemove", handleScrubMove); + return () => { + (window as any)[`listener-id-${refListener}`] = false; + window.removeEventListener("mousedown", handleScrubStart); + window.removeEventListener("mouseup", handleScrubEnd); + window.removeEventListener("mousemove", handleScrubMove); + }; + } + }, [scrubberWidth, boundingWidth]); + + useEffect(()=>{ + console.log(`withSCRUBBING useEFFECT!! `); + if(onScrubChanged && boundingWidth > 0 && prevLeftOffset >= 0 && propsLeftOffset !== prevLeftOffset){ + onScrubChanged(prevLeftOffset); + } + },[propsLeftOffset, prevLeftOffset, boundingWidth, onScrubChanged]); + + useEffect(()=>{ + // this takes a forced reset on leftoffset + setStates((curr)=>{ + if(curr.prevLeftOffset === curr.currLeftOffset + && propsLeftOffset !== curr.prevLeftOffset + && propsLeftOffset !== curr.prevLeftOffset + ){ + return { + ...curr, + currLeftOffset: propsLeftOffset, // in pixels + prevLeftOffset: propsLeftOffset, //in pixels + } + }else{ + return curr + } + }); + }, [propsLeftOffset]) + + return( +

=0 ? 'grabbing': 'grab', + ...styleOverride + }} + > + +
+ ); +}); diff --git a/packages/fakeyou.com/src/components/common/VideoQuickTrim/index.ts b/packages/fakeyou.com/src/components/common/VideoQuickTrim/index.ts index 7e49b01a6f..8f25d05b73 100644 --- a/packages/fakeyou.com/src/components/common/VideoQuickTrim/index.ts +++ b/packages/fakeyou.com/src/components/common/VideoQuickTrim/index.ts @@ -1,5 +1,5 @@ -import VideoQuickTrim from './VideoQuickTrim' -import { QuickTrimData } from './types'; +import {VideoQuickTrim} from './VideoQuickTrim' +import { QuickTrimData } from './utilities'; export type { QuickTrimData }; export default VideoQuickTrim; diff --git a/packages/fakeyou.com/src/components/common/VideoQuickTrim/reducer.tsx b/packages/fakeyou.com/src/components/common/VideoQuickTrim/reducer.tsx new file mode 100644 index 0000000000..8e769823b2 --- /dev/null +++ b/packages/fakeyou.com/src/components/common/VideoQuickTrim/reducer.tsx @@ -0,0 +1,215 @@ +import { MIN_VID_DURATION, TIME_CURSOR_WIDTH, TRIM_OPTIONS } from "./utilities"; + +export enum STATE_STATUSES { + INIT = "init", + VIDEO_METADATA_LOADED = "video_metadata_loaded", + LOAD_ORDER_ERROR = "load_order_error", + ACTION_TYPE_ERROR = "no_such_action_type" +}; + +export enum PLAYPUASE_STATES{ + NOT_READY = "not_ready", + READY = "ready", + PLAYING = "playing", + PAUSED = "paused", + ENDED = "ended", +} + +export type State = { + status: string; + errorMessage: string[]; + + playpause: string; + + canNotTrim: boolean | undefined; + trimReset: number; + trimDuration: number | undefined; + trimStartSeconds: number | undefined; + trimEndSeconds: number | undefined; + videoLoadProgress: number | undefined; + videoDuration: number | undefined; + + + playbarWidth: number | undefined; + scrubberWidth: number | undefined; + timeCursorOffset: number | undefined; + + isMuted: boolean; + isRepeatOn: boolean; +}; + +export const initialState = { + status: STATE_STATUSES.INIT, + errorMessage : [], + playpause: PLAYPUASE_STATES.NOT_READY, + canNotTrim: undefined, + trimReset: Date.now(), + trimDuration: undefined, + trimStartSeconds: undefined, + trimEndSeconds: undefined, + videoLoadProgress: undefined, + videoDuration: undefined, + playbarWidth: undefined, + scrubberWidth: undefined, + timeCursorOffset: undefined, + isMuted: false, + isRepeatOn: true, +} + +export enum ACTION_TYPES { + RESET = "reset", + TOGGLE_REPEAT = "toggle_repeat", + TOGGLE_MUTE = "toggle_mute", + ON_LOADED_METADATA = "on_loaded_metadata", + MOVE_TIMECURSOR = "move_timecursor", + SET_PLAYPUASE = "set_playpause", + SET_PLAYBAR_LAYOUT = "set_playbar_layout", + SET_TRIM_DURATION = "set_trim_duration", + SET_VIDEO_LOAD_PROGRESS = "set_video_load_progress", + MOVE_TRIM = "move_trim" +} + +export type Action = + | {type: ACTION_TYPES.RESET} + | {type: ACTION_TYPES.TOGGLE_REPEAT} + | {type: ACTION_TYPES.TOGGLE_MUTE} + | {type: ACTION_TYPES.ON_LOADED_METADATA, payload: {videoDuration: number}} + | {type: ACTION_TYPES.MOVE_TIMECURSOR, payload:{ timeCursorOffset: number}} + | {type: ACTION_TYPES.SET_PLAYPUASE, payload:{ playpause: string}} + | {type: ACTION_TYPES.SET_PLAYBAR_LAYOUT, payload:{ playbarWidth: number}} + | {type: ACTION_TYPES.SET_TRIM_DURATION, payload:{ trimDurationString: string}} + | {type: ACTION_TYPES.SET_VIDEO_LOAD_PROGRESS, payload: { + videoLoadProgress: number + }} + | {type: ACTION_TYPES.MOVE_TRIM, payload:{ + trimStartSeconds: number, + trimEndSeconds: number, + }} +; + +export function reducer(state: State, action: Action): State { + console.log("VideoQuickTrim State Dispatch"); + console.log(action); + + switch(action.type){ + case ACTION_TYPES.SET_PLAYPUASE:{ + return {...state, ...action.payload}; + } + case ACTION_TYPES.TOGGLE_REPEAT:{ + return {...state, isRepeatOn: !state.isRepeatOn} + } + case ACTION_TYPES.TOGGLE_MUTE:{ + return {...state, isMuted: !state.isMuted} + } + case ACTION_TYPES.ON_LOADED_METADATA:{ + if(action.payload.videoDuration >= MIN_VID_DURATION){ + return{ + ...state, + status: STATE_STATUSES.VIDEO_METADATA_LOADED, + videoDuration: action.payload.videoDuration, + canNotTrim: false, + trimDuration: 3, + trimStartSeconds: 0, + trimEndSeconds: 3, + } + }else{ + return { + ...state, + videoDuration: action.payload.videoDuration, + canNotTrim: true + } + } + } + case ACTION_TYPES.SET_VIDEO_LOAD_PROGRESS:{ + return{ + ...state, + videoLoadProgress: action.payload.videoLoadProgress + }; + } + case ACTION_TYPES.SET_PLAYBAR_LAYOUT:{ + const newWidth = action.payload.playbarWidth; + const prevOffset = state.timeCursorOffset || 0; + if(state.trimDuration && state.videoDuration){ + return { + ...state, + playpause: PLAYPUASE_STATES.READY, + playbarWidth: newWidth, + scrubberWidth: newWidth * (state.trimDuration / state.videoDuration), + timeCursorOffset: (newWidth-TIME_CURSOR_WIDTH)*(prevOffset/ state.videoDuration) + }; + }else{ + return{ + ...state, + status: STATE_STATUSES.LOAD_ORDER_ERROR, + errorMessage: [...state.errorMessage, 'Setting playbar layout before video and trim are loaded'] + } + } + } + case ACTION_TYPES.SET_TRIM_DURATION:{ + const selected = action.payload.trimDurationString; + if( state.canNotTrim !== undefined + && state.canNotTrim === false + && state.playbarWidth !==undefined + && state.videoDuration !== undefined + && TRIM_OPTIONS[selected] <= state.videoDuration + && state.trimStartSeconds !== undefined + ){ + let newTrimStart = state.trimStartSeconds; + let newTrimEnd = state.trimStartSeconds + TRIM_OPTIONS[selected]; + if (newTrimEnd > state.videoDuration){ + newTrimEnd = state.videoDuration; + if (state.videoDuration - TRIM_OPTIONS[selected] >=0) { + newTrimStart = state.videoDuration - TRIM_OPTIONS[selected]; + }else{ + newTrimStart = 0; + } + } + return{ + ...state, + trimDuration: newTrimEnd-newTrimStart, + trimStartSeconds: newTrimStart, + trimEndSeconds: newTrimEnd, + scrubberWidth: (newTrimEnd-newTrimStart)/state.videoDuration * state.playbarWidth, + } + }else{ + return { + ...state, + status: STATE_STATUSES.LOAD_ORDER_ERROR, + errorMessage: [...state.errorMessage, 'Setting trim duration before video and trim are loaded'] + } + } + } + case ACTION_TYPES.MOVE_TRIM:{ + return{ + ...state, + trimStartSeconds: action.payload.trimStartSeconds, + trimEndSeconds: action.payload.trimEndSeconds, + } + } + case ACTION_TYPES.MOVE_TIMECURSOR:{ + return { + ...state, + timeCursorOffset: action.payload.timeCursorOffset + } + } + case ACTION_TYPES.RESET:{ + return initialState + } + default:{ + return { + ...state, + status: STATE_STATUSES.ACTION_TYPE_ERROR, + errorMessage: [...state.errorMessage, "Reducer Action switch's default case is reached"] + } + } + } +}; + +/* + const trimScrubberWidth = videoRef.current && playbarWidth > 0 + ? trimDuration > 0 && trimDuration < videoRef.current.duration + ? (trimDuration / videoRef.current.duration * playbarWidth) + : playbarWidth + : 0; +*/ + diff --git a/packages/fakeyou.com/src/components/common/VideoQuickTrim/styles.scss b/packages/fakeyou.com/src/components/common/VideoQuickTrim/styles.scss index d45ce34e48..0dc6db8a49 100644 --- a/packages/fakeyou.com/src/components/common/VideoQuickTrim/styles.scss +++ b/packages/fakeyou.com/src/components/common/VideoQuickTrim/styles.scss @@ -60,11 +60,11 @@ $playbar-height: 2rem; background-color: #e66462; opacity: 70%; } - p { + h1 { position: absolute; - margin-top: 230px; - width: 100%; - top: 0; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); } } .playbar { @@ -80,17 +80,34 @@ $playbar-height: 2rem; left: 0; background-color: $panel-inner-border; border-radius: 0 0 0.5rem 0.5rem; + overflow: hidden; + + .played, .loaded{ + position: absolute; + height: 100%; + top: 0; + left: 0; + opacity: 0.5; + } + .played{ + background-color: $primary-dark; + } + .loaded{ + background-color: $gray-600; + } + } + + .scrubber-wrapper{ + box-shadow: 1px 1px 4px 0px rgba(0,0,0,1); } .trim-scrubber{ - position: absolute; - top: -1rem; height: 3rem; background-color: #e66462; opacity: 0.8; outline: 0.125rem $white solid; border-top: 1rem $white solid; border-radius: 0.25rem 0.25rem 0.125rem 0.125rem; - cursor: grab; + display: flex; justify-content: center; @@ -103,9 +120,12 @@ $playbar-height: 2rem; .playcursor { position: absolute; top: 0; - width: 0.5rem; + width: 0.5rem; //=8px + transform: translate(-3px, 0px); height: $playbar-height; background-color: $white; + border-radius: 1px; + box-shadow: 1px 1px 4px 0px rgba(0,0,0,0.2); } .playpause-external { diff --git a/packages/fakeyou.com/src/components/common/VideoQuickTrim/types.tsx b/packages/fakeyou.com/src/components/common/VideoQuickTrim/types.tsx deleted file mode 100644 index 4e3dd0db0d..0000000000 --- a/packages/fakeyou.com/src/components/common/VideoQuickTrim/types.tsx +++ /dev/null @@ -1,20 +0,0 @@ -//For integration into other components -export type QuickTrimData = { - trimStartSeconds: number; - trimEndSeconds: number; -} - -// INTERNALS -export type TrimStates = { - canNotTrim: boolean; - trimReset: Date; - trimDuration: number; - trimStart: number; - trimEnd: number; - maxDuration: number; -} - -export type PlaybarStates = { - playbarWidth: number; - timeCursorOffset: number; -} diff --git a/packages/fakeyou.com/src/components/common/VideoQuickTrim/helpers.tsx b/packages/fakeyou.com/src/components/common/VideoQuickTrim/utilities.tsx similarity index 63% rename from packages/fakeyou.com/src/components/common/VideoQuickTrim/helpers.tsx rename to packages/fakeyou.com/src/components/common/VideoQuickTrim/utilities.tsx index cbe19a389a..62f1f741fd 100644 --- a/packages/fakeyou.com/src/components/common/VideoQuickTrim/helpers.tsx +++ b/packages/fakeyou.com/src/components/common/VideoQuickTrim/utilities.tsx @@ -1,3 +1,12 @@ +//For integration into other components +export const TIME_CURSOR_WIDTH = 8; +export const MIN_VID_DURATION = 3; + +export type QuickTrimData = { + trimStartSeconds: number; + trimEndSeconds: number; +} + export function roundTo2Dec(floaty:number){ return Math.round(floaty*100)/100; } @@ -11,4 +20,11 @@ export function formatSecondsToHHMMSSCS(seconds:number){ return isoString.substring(11, 19) + "." + isoString.substring(20, 22) else return isoString.substring(14, 19) + "." + isoString.substring(20, 22); -} \ No newline at end of file +} + +export const TRIM_OPTIONS:{[key: string]: number } = { + "3s": 3, + "5s": 5, + "10s":10, + "15s":15 +}; \ No newline at end of file diff --git a/packages/fakeyou.com/src/v2/view/pages/components_library/sandbox.tsx b/packages/fakeyou.com/src/v2/view/pages/components_library/sandbox.tsx index f979b431bb..6b5ea067f6 100644 --- a/packages/fakeyou.com/src/v2/view/pages/components_library/sandbox.tsx +++ b/packages/fakeyou.com/src/v2/view/pages/components_library/sandbox.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useState } from 'react'; import VideoQuickTrim from 'components/common/VideoQuickTrim'; import { NavLink } from 'react-router-dom'; @@ -7,9 +7,13 @@ import {Container, Panel} from 'components/common'; export default function ComponentSandbox( {parentPath}:{parentPath:string} ){ + const [state1, setState1] = useState(undefined); + const [state2, setState2] = useState(undefined); const handleCallback = (val:any)=>{ console.log('onChange is triggered') console.log(val); + setState1(val.trimStartSeconds); + setState2(val.trimEndSeconds); } return ( ← Back @@ -18,18 +22,22 @@ export default function ComponentSandbox(

-
+

{`Start Seconds: ${state1}`}

+

{`Start Seconds: ${state2}`}

+ {/*
-
+
*/} ); } \ No newline at end of file diff --git a/packages/fakeyou.com/src/v2/view/pages/storyteller_studio_intro/StudioVST/StudioVSTPage.tsx b/packages/fakeyou.com/src/v2/view/pages/storyteller_studio_intro/StudioVST/StudioVSTPage.tsx index c0dc897d5f..88f65b8da2 100644 --- a/packages/fakeyou.com/src/v2/view/pages/storyteller_studio_intro/StudioVST/StudioVSTPage.tsx +++ b/packages/fakeyou.com/src/v2/view/pages/storyteller_studio_intro/StudioVST/StudioVSTPage.tsx @@ -126,6 +126,8 @@ export default function PageVSTApp() {
{job.isSuccessful && job.maybe_result ? ( handleOnChange({ diff --git a/packages/fakeyou.com/src/v2/view/pages/video_styletransfer/components/pageVSTApp/pageVSTApp.tsx b/packages/fakeyou.com/src/v2/view/pages/video_styletransfer/components/pageVSTApp/pageVSTApp.tsx index 1fbe3b2d29..3d081a2290 100644 --- a/packages/fakeyou.com/src/v2/view/pages/video_styletransfer/components/pageVSTApp/pageVSTApp.tsx +++ b/packages/fakeyou.com/src/v2/view/pages/video_styletransfer/components/pageVSTApp/pageVSTApp.tsx @@ -70,6 +70,8 @@ export default function PageVSTApp({
handleOnChange({