video piece under cursor selection

This commit is contained in:
Anton 2024-02-08 22:23:34 +03:00
parent 7b2673d626
commit 2334f305f1
4 changed files with 94 additions and 26 deletions

View File

@ -9,8 +9,8 @@ export const VideoEditorOverlay: React.FC = observer(() => {
return ( return (
<div className='overlay'> <div className='overlay'>
{timeToStr(state.video.currentTime)} <br /> {timeToStr(state.video.frameToTime(state.video.progress.start))}+{state.video.progress.start} to {timeToStr(state.video.frameToTime(state.video.progress.end))}+{state.video.progress.end}<br />
{state.video.currentFrame}{state.video.isAtMark ? '(mark)' : ''} <br /> {timeToStr(state.video.currentTime)}+{state.video.currentFrame}{state.video.isAtMark ? '(mark)' : ''} <br />
</div> </div>
); );
}); });

View File

@ -8,35 +8,54 @@ import { timeToStr } from '../utils/time';
export const VideoProgress: React.FC = observer(() => { export const VideoProgress: React.FC = observer(() => {
const values = state.video.progress.values;
// console.dir(JSON.stringify({
// count: state.video.progress.count,
// values,
// }));
function findChangedValue(changedValues: number[]): number {
// we never know which handle rc-slider decides to move
// so whatever handle has changed, it must be our target
for (const value of changedValues as number[]) {
if (!values.includes(value))
return value;
}
return changedValues[0];
}
return ( return (
<> <>
{/* <div style={{ height: 300, width: 600 }}> */} {/* <div style={{ height: 300, width: 600 }}> */}
<Slider <Slider
onChange={(values) => { range
state.video.seekToTime(values as number); allowCross={true}
// console.log('Change:', JSON.stringify(values)); onChange={(changedValues) => {
const changedValue = findChangedValue(changedValues as number[]);
console.log(`Change: ${JSON.stringify(changedValues)}, ${changedValue}`);
state.video.seekToFrame(changedValue);
}} }}
// onChangeComplete={(v) => { // onChangeComplete={(v) => {
// console.log('AfterChange:', v); // console.log('AfterChange:', v);
// }} // }}
min={0} min={0}
max={state.video.metadata ? state.video.metadata?.duration : 0} max={state.video.lastFrame}
value={state.video.currentTime} count={state.video.progress.count}
included={false} value={values}
// included={false}
keyboard={false} keyboard={false}
step={state.video.metadata ? 1 / state.video.metadata?.frameRate : undefined} step={1}
marks={ marks={
state.video.marks.reduce( state.video.marks.reduce(
(acc, mark) => { (acc, mark) => {
const time = state.video.frameToTime(mark); const time = state.video.frameToTime(mark);
acc[time] = <>{timeToStr(time)}<br/>{mark}</>; return acc; acc[mark] = <>{timeToStr(time)}<br />{mark}</>; return acc;
}, },
{} as Record<number, ReactNode> {} as Record<number, ReactNode>
) )
} }
trackStyle={{ backgroundColor: 'red', height: 10 }}
/> />
{/* </div> */}
</> </>
); );
}); });

View File

@ -1,4 +1,5 @@
import { action, computed, makeObservable, observable } from 'mobx'; import { action, computed, makeObservable, observable } from 'mobx';
import { VideoEditorProgressState } from './videoEditorProgress';
export type VideoMetadata = { export type VideoMetadata = {
frameRate: number; frameRate: number;
@ -7,6 +8,9 @@ export type VideoMetadata = {
}; };
export class VideoEditorState { export class VideoEditorState {
public progress = new VideoEditorProgressState(this);
@observable @observable
public url: string | undefined; public url: string | undefined;
@ -40,6 +44,11 @@ export class VideoEditorState {
return (frame + 0.4) / this.metadata.frameRate + this.metadata.firstFrameTime; return (frame + 0.4) / this.metadata.frameRate + this.metadata.firstFrameTime;
} }
@computed
get lastFrame(): number {
return this.metadata ? this.timeToFrame(this.metadata.duration) : 0;
}
@computed @computed
get currentFrame(): number { get currentFrame(): number {
return this.timeToFrame(this.currentTime); return this.timeToFrame(this.currentTime);
@ -66,7 +75,7 @@ export class VideoEditorState {
@action @action
setMetadata(metadata: VideoMetadata) { setMetadata(metadata: VideoMetadata) {
this.metadata = metadata; this.metadata = metadata;
this.marks = [0, this.timeToFrame(metadata.duration)]; this.marks = [0, this.lastFrame];
} }
@action @action
@ -97,12 +106,7 @@ export class VideoEditorState {
if (!this.metadata) if (!this.metadata)
return; return;
const currentFrame = this.currentFrame.valueOf(); this.seekToFrame(this.getPrevMark(this.currentFrame));
const marks = this.marks.filter((m) => m < currentFrame);
const prevMark = marks.length ? marks[marks.length - 1] : 0;
// console.dir({ currentFrame, marks, prevMark });
if (marks)
this.seekToFrame(prevMark);
} }
@action @action
@ -110,12 +114,22 @@ export class VideoEditorState {
if (!this.metadata) if (!this.metadata)
return; return;
const currentFrame = this.currentFrame.valueOf(); this.seekToFrame(this.getNextMark(this.currentFrame));
const marks = this.marks.filter((m) => m > currentFrame); }
// console.dir({ currentFrame, marks });
if (marks.length) public getPrevMark(frame: number): number {
this.seekToFrame(marks[0]); if (!this.metadata)
else return frame;
this.seekToTime(this.metadata?.duration);
const marks = this.marks.filter((m) => m < frame);
return marks.length ? marks[marks.length - 1] : 0;
}
public getNextMark(frame: number): number {
if (!this.metadata)
return frame;
const marks = this.marks.filter((m) => m > frame);
return marks.length ? marks[0] : this.lastFrame;
} }
} }

View File

@ -0,0 +1,35 @@
import { action, computed, makeObservable, observable } from 'mobx';
import { VideoEditorState } from './videoEditor';
export type VideoMetadata = {
frameRate: number;
firstFrameTime: number;
duration: number;
};
export class VideoEditorProgressState {
@observable
public count: number = 2;
constructor(
private readonly editor: VideoEditorState
) {
makeObservable(this);
}
@computed
get values(): number[] {
return [this.start, this.editor.currentFrame, this.end];
}
@computed
public get start(): number {
return this.editor.getPrevMark(this.editor.currentFrame);
}
@computed
public get end(): number {
return this.editor.getNextMark(this.editor.currentFrame);
}
}