mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-06 18:21:12 +00:00
fix loop problem when repeating play
This commit is contained in:
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(
|
||||
|
||||
+6
-1
@@ -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
-4
@@ -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();
|
||||
|
||||
Reference in new issue
Block a user