From a0547095e23a9396c5bbfb118e6f4708d42e7211 Mon Sep 17 00:00:00 2001 From: MrKBear Date: Sun, 1 May 2022 12:34:03 +0800 Subject: [PATCH] Add clip player logic --- source/Component/ClipList/ClipList.tsx | 2 +- source/Component/Recorder/Recorder.scss | 5 + source/Model/Actuator.ts | 145 ++++++++++++++++++++++- source/Model/Clip.ts | 35 +++++- source/Panel/ClipPlayer/ClipPlayer.tsx | 29 +++-- source/Panel/ClipPlayer/ClipRecorder.tsx | 39 +++++- 6 files changed, 237 insertions(+), 18 deletions(-) diff --git a/source/Component/ClipList/ClipList.tsx b/source/Component/ClipList/ClipList.tsx index bb19940..e3d58b0 100644 --- a/source/Component/ClipList/ClipList.tsx +++ b/source/Component/ClipList/ClipList.tsx @@ -39,7 +39,7 @@ class ClipList extends Component { } private getClipInfo(clip: Clip): string { - let fps = Math.floor(clip.frames.length / clip.time); + let fps = Math.round((clip.frames.length - 1) / clip.time); if (isNaN(fps)) fps = 0; return `${this.parseTime(clip.time)} ${fps}fps`; } diff --git a/source/Component/Recorder/Recorder.scss b/source/Component/Recorder/Recorder.scss index 2f03595..febaf83 100644 --- a/source/Component/Recorder/Recorder.scss +++ b/source/Component/Recorder/Recorder.scss @@ -7,9 +7,11 @@ div.recorder-root { div.recorder-slider { width: 100%; + transition: none; div.ms-Slider-slideBox { height: 16px; + transition: none; } span.ms-Slider-thumb { @@ -17,15 +19,18 @@ div.recorder-root { height: 12px; line-height: 16px; border-width: 3px; + transition: none; top: -4px; } span.ms-Slider-active { height: 3px; + transition: none; } span.ms-Slider-inactive { height: 3px; + animation: none; } } diff --git a/source/Model/Actuator.ts b/source/Model/Actuator.ts index cc57d27..8479ad1 100644 --- a/source/Model/Actuator.ts +++ b/source/Model/Actuator.ts @@ -1,6 +1,6 @@ import { Model } from "@Model/Model"; import { Emitter } from "@Model/Emitter"; -import { Clip } from "@Model/Clip"; +import { Clip, IFrame } from "@Model/Clip"; enum ActuatorModel { Play = 1, @@ -45,6 +45,21 @@ class Actuator extends Emitter { */ public recordClip?: Clip; + /** + * 播放剪辑 + */ + public playClip?: Clip; + + /** + * 播放帧 + */ + public playFrame?: IFrame; + + /** + * 播放帧数 + */ + public playFrameId: number = 0; + /** * 开始录制 */ @@ -76,6 +91,98 @@ class Actuator extends Emitter { this.mod = ActuatorModel.View; } + public startPlay(clip: Clip) { + + // 如果仿真正在进行,停止仿真 + if (this.start()) this.start(false); + + // 如果正在录制,阻止播放 + if (this.mod === ActuatorModel.Record) { + return; + } + + // 如果正在播放,暂停播放 + if (this.mod === ActuatorModel.Play) { + this.pausePlay(); + } + + // 设置播放对象 + this.playClip = clip; + + // 设置播放帧数 + this.playFrameId = 0; + this.playFrame = clip.frames[this.playFrameId]; + + // 播放第一帧 + clip.play(this.playFrame); + + // 激发时钟状态事件 + this.emit("startChange", true); + } + + public endPlay() { + + // 如果正在播放,暂停播放 + if (this.mod === ActuatorModel.Play) { + this.pausePlay(); + } + + // 更新模式 + this.mod = ActuatorModel.View; + + // 清除状态 + this.playClip = undefined; + this.playFrameId = 0; + this.playFrame = undefined; + + // 渲染模型 + this.model.draw(); + + // 激发时钟状态事件 + this.emit("startChange", false); + } + + /** + * 是否播放完毕 + */ + public isPlayEnd() { + if (this.playClip && this.playFrame) { + if (this.playFrameId >= (this.playClip.frames.length - 1)) { + return true; + } else { + return false; + } + } else { + return true; + } + } + + public playing() { + + // 如果播放完毕了,从头开始播放 + if (this.isPlayEnd() && this.playClip) { + this.startPlay(this.playClip); + } + + // 更新模式 + this.mod = ActuatorModel.Play; + + // 启动播放时钟 + this.playTicker(); + + // 激发时钟状态事件 + this.emit("startChange", false); + } + + public pausePlay() { + + // 更新模式 + this.mod = ActuatorModel.View; + + // 激发时钟状态事件 + this.emit("startChange", false); + } + /** * 主时钟状态控制 */ @@ -108,6 +215,42 @@ class Actuator extends Emitter { public tickerType: 1 | 2 = 2; + private playTickerTimer?: number; + + /** + * 播放时钟 + */ + private playTicker() { + + if (this.playClip && this.playFrame && this.mod === ActuatorModel.Play) { + + // 播放当前帧 + this.playClip.play(this.playFrame); + + // 没有完成播放,继续播放 + if (!this.isPlayEnd()) { + + // 跳转值下一帧 + this.playFrameId ++; + this.playFrame = this.playClip.frames[this.playFrameId]; + this.emit("record", this.playFrame.duration); + + // 清除计时器,保证时钟唯一性 + clearTimeout(this.playTickerTimer); + + // 延时 + this.playTickerTimer = setTimeout(() => { + this.playTicker(); + }, this.playFrame.duration * 1000) as any; + + } else { + this.pausePlay(); + } + } else { + this.pausePlay(); + } + } + private ticker(t: number) { if (this.startFlag && t !== 0) { if (this.lastTime === 0) { diff --git a/source/Model/Clip.ts b/source/Model/Clip.ts index 64008b8..be292d6 100644 --- a/source/Model/Clip.ts +++ b/source/Model/Clip.ts @@ -15,6 +15,7 @@ interface IDrawCommand { interface IFrame { commands: IDrawCommand[]; duration: number; + process: number; } /** @@ -81,17 +82,41 @@ class Clip { } } + const dt = this.frames.length <= 0 ? 0 : t; + this.time += dt; + const frame: IFrame = { commands: commands, - duration: t + duration: dt, + process: this.time }; - - this.time += t; + this.frames.push(frame); - return frame; } + /** + * 播放一帧 + */ + public play(frame: IFrame) { + + // 清除全部渲染状态 + this.model.renderer.clean(); + + // 执行全部渲染指令 + for (let i = 0; i < frame.commands.length; i++) { + const command: IDrawCommand = frame.commands[i]; + + if (command.type === "cube") { + this.model.renderer.cube(command.id, command.position, command.radius, command.parameter); + } + + else if (frame.commands[i].type === "points") { + this.model.renderer.points(command.id, command.data, command.parameter); + } + } + } + public equal(clip?: Clip) { return clip === this || clip?.id === this.id; } @@ -102,4 +127,4 @@ class Clip { } } -export { Clip }; \ No newline at end of file +export { Clip, IFrame }; \ No newline at end of file diff --git a/source/Panel/ClipPlayer/ClipPlayer.tsx b/source/Panel/ClipPlayer/ClipPlayer.tsx index cb20fb3..efa5a0a 100644 --- a/source/Panel/ClipPlayer/ClipPlayer.tsx +++ b/source/Panel/ClipPlayer/ClipPlayer.tsx @@ -17,19 +17,20 @@ class ClipPlayer extends Component { return ; } + private isClipListDisable() { + return !this.props.status?.focusClip && + ( + this.props.status?.actuator.mod === ActuatorModel.Record || + this.props.status?.actuator.mod === ActuatorModel.Offline + ); + } + private renderClipList(clipList: Clip[]): ReactNode { - - const disable = - !this.props.status?.focusClip && - ( - this.props.status?.actuator.mod === ActuatorModel.Record || - this.props.status?.actuator.mod === ActuatorModel.Offline - ); return { this.isInnerClick = true; const status = this.props.status; @@ -40,7 +41,8 @@ class ClipPlayer extends Component { yesI18n: "Popup.Action.Objects.Confirm.Delete", red: "yes", yes: () => { - status.setClipObject() + status.setClipObject(); + this.props.status?.actuator.endPlay(); status.model.deleteClip(clip.id); } }); @@ -52,6 +54,7 @@ class ClipPlayer extends Component { click={(clip) => { this.isInnerClick = true; this.props.status?.setClipObject(clip); + this.props.status?.actuator.startPlay(clip); }} />; } @@ -64,11 +67,19 @@ class ClipPlayer extends Component { fontLevel={FontLevel.normal} backgroundLevel={BackgroundLevel.Level4} onClick={()=>{ + + // 拦截禁用状态的事件 + if (this.isClipListDisable()) { + return; + } + if (this.isInnerClick) { this.isInnerClick = false; } + else { this.props.status?.setClipObject(); + this.props.status?.actuator.endPlay(); } }} > diff --git a/source/Panel/ClipPlayer/ClipRecorder.tsx b/source/Panel/ClipPlayer/ClipRecorder.tsx index 0f75315..260f744 100644 --- a/source/Panel/ClipPlayer/ClipRecorder.tsx +++ b/source/Panel/ClipPlayer/ClipRecorder.tsx @@ -9,8 +9,12 @@ class ClipRecorder extends Component { let mod: "P" | "R" = this.props.status?.focusClip ? "P" : "R"; let runner: boolean = false; - let currentTime: number = 0; + let currentTime: number | undefined = 0; + let allTime: number | undefined = 0; let name: string | undefined; + let currentFrame: number | undefined = 0; + let allFrame: number | undefined = 0; + let fps: number | undefined = 0; // 是否开始录制 if (mod === "R") { @@ -28,15 +32,30 @@ class ClipRecorder extends Component { // 是否正在播放 runner = this.props.status?.actuator.mod === ActuatorModel.Play; - name = this.props.status?.focusClip?.name; + allTime = this.props.status?.focusClip?.time; + allFrame = this.props.status?.focusClip?.frames.length; + currentFrame = this.props.status?.actuator.playFrameId; + currentTime = this.props.status?.actuator.playFrame?.process; + + if (allFrame !== undefined) { + allFrame --; + } + + if (allTime !== undefined && allFrame !== undefined) { + fps = Math.round(allFrame / allTime); + } } return { // 开启录制 @@ -57,6 +76,22 @@ class ClipRecorder extends Component { this.props.status?.actuator.endRecord(); console.log("ClipRecorder: Rec end..."); } + + // 开始播放 + if (mod === "P" && !runner) { + + // 启动播放时钟 + this.props.status?.actuator.playing(); + console.log("ClipRecorder: Play start..."); + } + + // 暂停播放 + if (mod === "P" && runner) { + + // 启动播放时钟 + this.props.status?.actuator.pausePlay(); + console.log("ClipRecorder: Pause start..."); + } }} /> }