video piece under cursor selection
This commit is contained in:
parent
7b2673d626
commit
2334f305f1
|
|
@ -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>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
@ -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> */}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue