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 (
<div className='overlay'>
{timeToStr(state.video.currentTime)} <br />
{state.video.currentFrame}{state.video.isAtMark ? '(mark)' : ''} <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 />
{timeToStr(state.video.currentTime)}+{state.video.currentFrame}{state.video.isAtMark ? '(mark)' : ''} <br />
</div>
);
});

View File

@ -8,35 +8,54 @@ import { timeToStr } from '../utils/time';
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 (
<>
{/* <div style={{ height: 300, width: 600 }}> */}
<Slider
onChange={(values) => {
state.video.seekToTime(values as number);
// console.log('Change:', JSON.stringify(values));
range
allowCross={true}
onChange={(changedValues) => {
const changedValue = findChangedValue(changedValues as number[]);
console.log(`Change: ${JSON.stringify(changedValues)}, ${changedValue}`);
state.video.seekToFrame(changedValue);
}}
// onChangeComplete={(v) => {
// console.log('AfterChange:', v);
// }}
min={0}
max={state.video.metadata ? state.video.metadata?.duration : 0}
value={state.video.currentTime}
included={false}
max={state.video.lastFrame}
count={state.video.progress.count}
value={values}
// included={false}
keyboard={false}
step={state.video.metadata ? 1 / state.video.metadata?.frameRate : undefined}
step={1}
marks={
state.video.marks.reduce(
(acc, 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>
)
}
trackStyle={{ backgroundColor: 'red', height: 10 }}
/>
{/* </div> */}
</>
);
});

View File

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