fix loop problem when repeating play

This commit is contained in:
wil committed 2024-03-12 15:54:03 -04:00
1 parent 2834872fd8
commit 0bc21c761b
8 files changed
+86 -28

No files matched your search

@@ -14,6 +14,7 @@ import './styles.scss';
export interface VideoFakeyouProps{
debug?: boolean;
width?: number|string;
height?: number|string;
hideIfNoMedia?: boolean;
@@ -30,6 +31,7 @@ export interface VideoFakeyouProps{
type Ref = HTMLVideoElement;
const VideoFakeyou = memo(forwardRef<Ref, VideoFakeyouProps>(({
debug: propsDebug = false,
width,
height,
hideIfNoMedia = false,
@@ -44,11 +46,15 @@ const VideoFakeyou = memo(forwardRef<Ref, VideoFakeyouProps>(({
...rest
}: VideoFakeyouProps, ref) => {
//console.log(`Video Player rerender: ${mediaToken}`);
const debug = false;// || propsDebug;
if(debug) console.log("VideoFakeyou reRENDER!!");
const [mediaFile, setMediaFile] = useState<MediaFileType|null>(null);
useEffect(()=>{
setMediaFile(null);
},[mediaToken])
useMedia({
mediaToken: mediaToken,
onSuccess: (res: any) => {
@@ -23,24 +23,28 @@ import {
STATE_STATUSES,
} from "./reducer";
import { QuickTrimData } from "./utilities";
import { QuickTrimData, ONE_MS } from "./utilities";
import { ProgressBar } from "./components/ProgressBar";
import { ControlBar } from "./components/ControlBar";
import './styles.scss'
interface VideoQuickTrimProps extends VideoFakeyouProps {
debug?: boolean
onSelect: (values: QuickTrimData) => void;
trimStartSeconds: number;
trimEndSeconds: number;
}
export const VideoQuickTrim = memo(({
debug: propsDebug = false,
onSelect,
trimStartSeconds: propsTrimStartSeconds,
trimEndSeconds : propsTrimEndSeconds,
...rest
}: VideoQuickTrimProps) => {
const debug = true || propsDebug;
const { t } = useLocalize("VideoPlayerQuickTrim");
const videoRef = useRef<HTMLVideoElement | null>(null);
const [compState, dispatchCompState] = useReducer(reducer, initialState);
@@ -72,10 +76,13 @@ export const VideoQuickTrim = memo(({
compState.playbarWidth !== undefined){
// reset current time when on repeat
if(compState.isRepeatOn &&
(node.currentTime >= compState.trimEndSeconds || node.currentTime <= compState.trimStartSeconds)
(node.currentTime > compState.trimEndSeconds || node.currentTime < compState.trimStartSeconds)
){
node.currentTime = compState.trimStartSeconds;
console.log('play looped');
node.currentTime = compState.trimStartSeconds + ONE_MS;
// if (debug){
// console.log(`Loop Playing trimStart@${compState.trimStartSeconds}`);
// console.log(`Loop Playing currentTime@${node.currentTime}`);
// }
}
dispatchCompState({
@@ -92,6 +99,16 @@ export const VideoQuickTrim = memo(({
node.onplay = ()=>setPlaypause(PLAYPUASE_STATES.PLAYING);
node.onpause = ()=>setPlaypause(PLAYPUASE_STATES.PAUSED);
node.onended = ()=>setPlaypause(PLAYPUASE_STATES.ENDED);
node.onprogress = ()=>{
console.log(node.buffered);
if(node.buffered.length > 0){
dispatchCompState({
type: ACTION_TYPES.SET_VIDEO_LOAD_PROGRESS,
payload: {videoLoadProgress: node.buffered}
});
}
}
} // else{} DOM node referenced by ref has been unmounted
}, [
compState.trimStartSeconds,
@@ -130,11 +147,17 @@ export const VideoQuickTrim = memo(({
console.log('Playpause is triggered while it is NOT_READY');
}
};
function disableRepeatOn(){
dispatchCompState({
type: ACTION_TYPES.TOGGLE_REPEAT,
payload: {isRepeatOn: false}
});
}
return (
<div className="fy-video-quicktrim">
<div className="video-wrapper">
<VideoFakeyou
debug={false}
height={500}
controls={false}
muted={compState.isMuted}
@@ -167,23 +190,33 @@ export const VideoQuickTrim = memo(({
}
</div>{/* END of Video Wrapper */}
<ProgressBar
debug={debug}
readyToMount={(compState.status === STATE_STATUSES.VIDEO_METADATA_LOADED)}
timeCursorOffset={compState.timeCursorOffset ||0}
trimStartSeconds={compState.trimStartSeconds ||0}
trimDuration={compState.trimDuration ||0}
playbarWidth={compState.playbarWidth ||0}
scrubberWidth={compState.scrubberWidth ||0}
videoBuffered={videoRef.current?.buffered}
videoBuffered={compState.videoLoadProgress || undefined}
videoDuration={compState.videoDuration ||0}
dispatchCompState={dispatchCompState}
onPlayCursorChanged={(newPos: number)=>{
if(videoRef.current !== null && videoRef.current.currentTime
&& compState.playbarWidth && compState.videoDuration){
videoRef.current.currentTime = newPos / compState.playbarWidth * compState.videoDuration;
const newTime = newPos / compState.playbarWidth * compState.videoDuration;
if(compState.trimStartSeconds
&& compState.trimEndSeconds
&& (newTime < compState.trimStartSeconds
|| newTime > compState.trimEndSeconds
)){
disableRepeatOn();
}
videoRef.current.currentTime = newTime;
}
}}
/>
<ControlBar
debug={debug}
readyToMount={(compState.status === STATE_STATUSES.VIDEO_METADATA_LOADED)}
videoCurrentTime={videoRef.current?.currentTime}
videoDuration={videoRef.current?.duration}
@@ -17,6 +17,7 @@ import {
import { TRIM_OPTIONS, formatSecondsToHHMMSSCS } from "../utilities";
export const ControlBar = memo(({
debug: propsDebug = false,
readyToMount,
videoCurrentTime,
videoDuration,
@@ -26,6 +27,7 @@ export const ControlBar = memo(({
handlePlaypause,
dispatchCompState
}:{
debug?:boolean;
readyToMount: boolean;
videoCurrentTime: number | undefined;
videoDuration: number | undefined;
@@ -35,6 +37,9 @@ export const ControlBar = memo(({
handlePlaypause: ()=>void;
dispatchCompState: (action: Action) => void;
})=>{
const debug = false || propsDebug;
if (debug) console.log("ControlBar reRENDERED!!");
function handleSetTrimDuration(selected: string){
dispatchCompState({
type: ACTION_TYPES.SET_TRIM_DURATION,
@@ -42,10 +47,6 @@ export const ControlBar = memo(({
trimDurationString: selected
}
});
// onSelect({
// trimStartSeconds: newTrimStart,
// trimEndSeconds: newTrimEnd,
// });
};
if(readyToMount){
return(
@@ -12,6 +12,7 @@ import { TrimScrubber } from './TrimScrubber';
import { PlayCursor } from './PlayCursor';
export const ProgressBar = memo(({
debug: propsDebug = false,
readyToMount,
timeCursorOffset,
trimStartSeconds,
@@ -23,6 +24,7 @@ export const ProgressBar = memo(({
onPlayCursorChanged,
dispatchCompState
}:{
debug?: boolean;
readyToMount: boolean;
timeCursorOffset: number;
trimStartSeconds: number;
@@ -30,10 +32,12 @@ export const ProgressBar = memo(({
playbarWidth: number;
scrubberWidth: number;
videoDuration: number;
videoBuffered:TimeRanges | undefined;
videoBuffered: TimeRanges | undefined;
onPlayCursorChanged: (newPos: number) => void;
dispatchCompState: (action: Action) => void;
})=>{
const debug = false || propsDebug;
const playbarRef = useRef<HTMLDivElement | null>(null);
const playbarRefCallback = useCallback(node => {
@@ -78,6 +82,7 @@ export const ProgressBar = memo(({
<span className="played" style={{width: timeCursorOffset+"px"}} />
</div>
<TrimScrubber
debug={debug}
boundingWidth={playbarWidth}
scrubberWidth={scrubberWidth}
trimStartSeconds={trimStartSeconds}
@@ -14,12 +14,14 @@ interface TrimScrubberPropsI extends withScrubbingPropsI {
onChange:(newPos:number)=>void
}
export const TrimScrubber = memo(({
debug: propsDebug = false,
trimStartSeconds,
trimDuration,
videoDuration,
onChange,
...rest
}:TrimScrubberPropsI)=>{
const debug = false || propsDebug;
const TrimScrubberWithScrubbing = withScrubbing<TrimScrubberPropsI>(() => {
return(
@@ -31,6 +33,7 @@ export const TrimScrubber = memo(({
return (
<TrimScrubberWithScrubbing
debug={debug}
styleOverride={{
top: '-1rem',
}}
@@ -7,6 +7,7 @@ import React,{
} from 'react';
export interface withScrubbingPropsI {
debug?: boolean
boundingWidth: number;
scrubberWidth: number;
scrubPosition?: number; // scrubber location as px
@@ -22,6 +23,7 @@ type withSrcubbingStates = {
}
export const withScrubbing = <P extends withScrubbingPropsI>(Component: React.ComponentType<P>) => memo(({
debug: propsDebug = false,
boundingWidth,
scrubberWidth,
scrubPosition: propsLeftOffset = 0,
@@ -29,11 +31,12 @@ export const withScrubbing = <P extends withScrubbingPropsI>(Component: React.Co
onScrubChanged,
...rest
}: withScrubbingPropsI) => {
console.log(`withSCRUBBING reRENDERING!! `);
const debug = false ;//|| propsDebug;
if (debug) console.log(`withSCRUBBING reRENDERING!! `);
const refEl = useRef<HTMLDivElement| null>(null);
const refListener = useRef<number>(Date.now());
const [{
currLeftOffset, pointerStartPos,
prevLeftOffset
@@ -44,7 +47,7 @@ export const withScrubbing = <P extends withScrubbingPropsI>(Component: React.Co
});
useLayoutEffect(() => {
console.log(`withSCRUBBING useLAYOUTeffect!! `);
// if (debug) console.log(`withSCRUBBING useLAYOUTeffect!! `);
function handleScrubStart (e: MouseEvent) {
if(refEl.current){
if(refEl.current.contains(e.target as Node)){
@@ -102,7 +105,7 @@ export const withScrubbing = <P extends withScrubbingPropsI>(Component: React.Co
}, [scrubberWidth, boundingWidth]);
useEffect(()=>{
console.log(`withSCRUBBING useEFFECT!! `);
// if (debug) console.log(`withSCRUBBING useEFFECT!! `);
if(onScrubChanged && boundingWidth > 0 && prevLeftOffset >= 0 && propsLeftOffset !== prevLeftOffset){
onScrubChanged(prevLeftOffset);
}
@@ -1,4 +1,9 @@
import { MIN_VID_DURATION, TIME_CURSOR_WIDTH, TRIM_OPTIONS } from "./utilities";
import {
MIN_VID_DURATION,
TIME_CURSOR_WIDTH,
TRIM_OPTIONS,
roundToMilliseconds
} from "./utilities";
export enum STATE_STATUSES {
INIT = "init",
@@ -26,7 +31,7 @@ export type State = {
trimDuration: number | undefined;
trimStartSeconds: number | undefined;
trimEndSeconds: number | undefined;
videoLoadProgress: number | undefined;
videoLoadProgress: TimeRanges | undefined;
videoDuration: number | undefined;
@@ -71,7 +76,7 @@ export enum ACTION_TYPES {
export type Action =
| {type: ACTION_TYPES.RESET}
| {type: ACTION_TYPES.TOGGLE_REPEAT}
| {type: ACTION_TYPES.TOGGLE_REPEAT, payload?:{isRepeatOn: boolean}}
| {type: ACTION_TYPES.TOGGLE_MUTE}
| {type: ACTION_TYPES.ON_LOADED_METADATA, payload: {videoDuration: number}}
| {type: ACTION_TYPES.MOVE_TIMECURSOR, payload:{ timeCursorOffset: number}}
@@ -79,7 +84,7 @@ export type Action =
| {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
videoLoadProgress: TimeRanges
}}
| {type: ACTION_TYPES.MOVE_TRIM, payload:{
trimStartSeconds: number,
@@ -96,6 +101,9 @@ export function reducer(state: State, action: Action): State {
return {...state, ...action.payload};
}
case ACTION_TYPES.TOGGLE_REPEAT:{
if(action.payload){
return {...state, isRepeatOn: action.payload.isRepeatOn}
}
return {...state, isRepeatOn: !state.isRepeatOn}
}
case ACTION_TYPES.TOGGLE_MUTE:{
@@ -159,7 +167,7 @@ export function reducer(state: State, action: Action): State {
if (newTrimEnd > state.videoDuration){
newTrimEnd = state.videoDuration;
if (state.videoDuration - TRIM_OPTIONS[selected] >=0) {
newTrimStart = state.videoDuration - TRIM_OPTIONS[selected];
newTrimStart = roundToMilliseconds(state.videoDuration - TRIM_OPTIONS[selected]);
}else{
newTrimStart = 0;
}
@@ -182,8 +190,8 @@ export function reducer(state: State, action: Action): State {
case ACTION_TYPES.MOVE_TRIM:{
return{
...state,
trimStartSeconds: action.payload.trimStartSeconds,
trimEndSeconds: action.payload.trimEndSeconds,
trimStartSeconds: roundToMilliseconds(action.payload.trimStartSeconds),
trimEndSeconds: roundToMilliseconds(action.payload.trimEndSeconds),
}
}
case ACTION_TYPES.MOVE_TIMECURSOR:{
@@ -1,18 +1,17 @@
//For integration into other components
export const TIME_CURSOR_WIDTH = 8;
export const MIN_VID_DURATION = 3;
export const ONE_MS = 0.001;
export type QuickTrimData = {
trimStartSeconds: number;
trimEndSeconds: number;
}
export function roundTo2Dec(floaty:number){
return Math.round(floaty*100)/100;
}
export function fractionToPercentage(fraction:number){
return roundTo2Dec(fraction * 100);
export function roundToMilliseconds(floaty:number){
return Math.round(floaty*1000)/1000;
}
export function formatSecondsToHHMMSSCS(seconds:number){
//example of the ISO String: 1970-01-01T00:01:40.774Z
const isoString = new Date(seconds * 1000).toISOString();