DNDGamePlayer: Electron редактор сцен, презентация, упаковка electron-builder
Made-with: Cursor
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
import type { VideoPlaybackEvent, VideoPlaybackState } from '../../shared/types';
|
||||
|
||||
function nowMs(): number {
|
||||
return Date.now();
|
||||
}
|
||||
|
||||
function clamp(v: number, min: number, max: number): number {
|
||||
return Math.max(min, Math.min(max, v));
|
||||
}
|
||||
|
||||
export class VideoPlaybackStore {
|
||||
private state: VideoPlaybackState = {
|
||||
revision: 1,
|
||||
serverNowMs: nowMs(),
|
||||
targetAssetId: null,
|
||||
playing: false,
|
||||
playbackRate: 1,
|
||||
anchorServerMs: nowMs(),
|
||||
anchorVideoTimeSec: 0,
|
||||
};
|
||||
|
||||
getState(): VideoPlaybackState {
|
||||
return { ...this.state, serverNowMs: nowMs() };
|
||||
}
|
||||
|
||||
dispatch(event: VideoPlaybackEvent): VideoPlaybackState {
|
||||
const s = this.getState();
|
||||
const curTime = computeTimeSec(s, s.serverNowMs);
|
||||
const bump = (patch: Omit<VideoPlaybackState, 'revision' | 'serverNowMs'>): VideoPlaybackState => ({
|
||||
...patch,
|
||||
revision: s.revision + 1,
|
||||
serverNowMs: nowMs(),
|
||||
});
|
||||
|
||||
switch (event.kind) {
|
||||
case 'target.set': {
|
||||
const nextTarget = event.assetId ?? null;
|
||||
const next: Omit<VideoPlaybackState, 'revision' | 'serverNowMs'> = {
|
||||
...s,
|
||||
targetAssetId: nextTarget,
|
||||
playing: Boolean(event.autostart) && nextTarget !== null,
|
||||
playbackRate: s.playbackRate,
|
||||
anchorServerMs: s.serverNowMs,
|
||||
anchorVideoTimeSec: 0,
|
||||
};
|
||||
this.state = bump(next);
|
||||
return this.state;
|
||||
}
|
||||
case 'play': {
|
||||
this.state = bump({
|
||||
...s,
|
||||
playing: true,
|
||||
anchorServerMs: s.serverNowMs,
|
||||
anchorVideoTimeSec: curTime,
|
||||
});
|
||||
return this.state;
|
||||
}
|
||||
case 'pause': {
|
||||
this.state = bump({
|
||||
...s,
|
||||
playing: false,
|
||||
anchorServerMs: s.serverNowMs,
|
||||
anchorVideoTimeSec: curTime,
|
||||
});
|
||||
return this.state;
|
||||
}
|
||||
case 'stop': {
|
||||
this.state = bump({
|
||||
...s,
|
||||
playing: false,
|
||||
anchorServerMs: s.serverNowMs,
|
||||
anchorVideoTimeSec: 0,
|
||||
});
|
||||
return this.state;
|
||||
}
|
||||
case 'seek': {
|
||||
const t = clamp(event.timeSec, 0, 1_000_000);
|
||||
this.state = bump({
|
||||
...s,
|
||||
anchorServerMs: s.serverNowMs,
|
||||
anchorVideoTimeSec: t,
|
||||
});
|
||||
return this.state;
|
||||
}
|
||||
case 'rate.set': {
|
||||
const rate = clamp(event.rate, 0.25, 3);
|
||||
this.state = bump({
|
||||
...s,
|
||||
playbackRate: rate,
|
||||
anchorServerMs: s.serverNowMs,
|
||||
anchorVideoTimeSec: curTime,
|
||||
});
|
||||
return this.state;
|
||||
}
|
||||
default: {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const _x: never = event;
|
||||
return this.state;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function computeTimeSec(state: VideoPlaybackState, atServerNowMs: number): number {
|
||||
if (!state.playing) return state.anchorVideoTimeSec;
|
||||
const dt = Math.max(0, atServerNowMs - state.anchorServerMs);
|
||||
return state.anchorVideoTimeSec + (dt / 1000) * state.playbackRate;
|
||||
}
|
||||
Reference in New Issue
Block a user