Add clip player & offline renderer #49

Merged
MrKBear merged 6 commits from dev-mrkbear into master 2022-05-01 18:41:12 +08:00
6 changed files with 237 additions and 18 deletions
Showing only changes of commit a0547095e2 - Show all commits

View File

@ -39,7 +39,7 @@ class ClipList extends Component<IClipListProps> {
} }
private getClipInfo(clip: Clip): string { 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; if (isNaN(fps)) fps = 0;
return `${this.parseTime(clip.time)} ${fps}fps`; return `${this.parseTime(clip.time)} ${fps}fps`;
} }

View File

@ -7,9 +7,11 @@ div.recorder-root {
div.recorder-slider { div.recorder-slider {
width: 100%; width: 100%;
transition: none;
div.ms-Slider-slideBox { div.ms-Slider-slideBox {
height: 16px; height: 16px;
transition: none;
} }
span.ms-Slider-thumb { span.ms-Slider-thumb {
@ -17,15 +19,18 @@ div.recorder-root {
height: 12px; height: 12px;
line-height: 16px; line-height: 16px;
border-width: 3px; border-width: 3px;
transition: none;
top: -4px; top: -4px;
} }
span.ms-Slider-active { span.ms-Slider-active {
height: 3px; height: 3px;
transition: none;
} }
span.ms-Slider-inactive { span.ms-Slider-inactive {
height: 3px; height: 3px;
animation: none;
} }
} }

View File

@ -1,6 +1,6 @@
import { Model } from "@Model/Model"; import { Model } from "@Model/Model";
import { Emitter } from "@Model/Emitter"; import { Emitter } from "@Model/Emitter";
import { Clip } from "@Model/Clip"; import { Clip, IFrame } from "@Model/Clip";
enum ActuatorModel { enum ActuatorModel {
Play = 1, Play = 1,
@ -45,6 +45,21 @@ class Actuator extends Emitter<IActuatorEvent> {
*/ */
public recordClip?: Clip; public recordClip?: Clip;
/**
*
*/
public playClip?: Clip;
/**
*
*/
public playFrame?: IFrame;
/**
*
*/
public playFrameId: number = 0;
/** /**
* *
*/ */
@ -76,6 +91,98 @@ class Actuator extends Emitter<IActuatorEvent> {
this.mod = ActuatorModel.View; 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<IActuatorEvent> {
public tickerType: 1 | 2 = 2; 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) { private ticker(t: number) {
if (this.startFlag && t !== 0) { if (this.startFlag && t !== 0) {
if (this.lastTime === 0) { if (this.lastTime === 0) {

View File

@ -15,6 +15,7 @@ interface IDrawCommand {
interface IFrame { interface IFrame {
commands: IDrawCommand[]; commands: IDrawCommand[];
duration: number; duration: number;
process: number;
} }
/** /**
@ -81,17 +82,41 @@ class Clip {
} }
} }
const dt = this.frames.length <= 0 ? 0 : t;
this.time += dt;
const frame: IFrame = { const frame: IFrame = {
commands: commands, commands: commands,
duration: t duration: dt,
process: this.time
}; };
this.time += t;
this.frames.push(frame); this.frames.push(frame);
return 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) { public equal(clip?: Clip) {
return clip === this || clip?.id === this.id; return clip === this || clip?.id === this.id;
} }
@ -102,4 +127,4 @@ class Clip {
} }
} }
export { Clip }; export { Clip, IFrame };

View File

@ -17,19 +17,20 @@ class ClipPlayer extends Component<IMixinStatusProps> {
return <Message i18nKey="Panel.Info.Clip.List.Error.Nodata"/>; return <Message i18nKey="Panel.Info.Clip.List.Error.Nodata"/>;
} }
private renderClipList(clipList: Clip[]): ReactNode { private isClipListDisable() {
return !this.props.status?.focusClip &&
const disable =
!this.props.status?.focusClip &&
( (
this.props.status?.actuator.mod === ActuatorModel.Record || this.props.status?.actuator.mod === ActuatorModel.Record ||
this.props.status?.actuator.mod === ActuatorModel.Offline this.props.status?.actuator.mod === ActuatorModel.Offline
); );
}
private renderClipList(clipList: Clip[]): ReactNode {
return <ClipList return <ClipList
focus={this.props.status?.focusClip} focus={this.props.status?.focusClip}
clips={clipList} clips={clipList}
disable={disable} disable={this.isClipListDisable()}
delete={(clip) => { delete={(clip) => {
this.isInnerClick = true; this.isInnerClick = true;
const status = this.props.status; const status = this.props.status;
@ -40,7 +41,8 @@ class ClipPlayer extends Component<IMixinStatusProps> {
yesI18n: "Popup.Action.Objects.Confirm.Delete", yesI18n: "Popup.Action.Objects.Confirm.Delete",
red: "yes", red: "yes",
yes: () => { yes: () => {
status.setClipObject() status.setClipObject();
this.props.status?.actuator.endPlay();
status.model.deleteClip(clip.id); status.model.deleteClip(clip.id);
} }
}); });
@ -52,6 +54,7 @@ class ClipPlayer extends Component<IMixinStatusProps> {
click={(clip) => { click={(clip) => {
this.isInnerClick = true; this.isInnerClick = true;
this.props.status?.setClipObject(clip); this.props.status?.setClipObject(clip);
this.props.status?.actuator.startPlay(clip);
}} }}
/>; />;
} }
@ -64,11 +67,19 @@ class ClipPlayer extends Component<IMixinStatusProps> {
fontLevel={FontLevel.normal} fontLevel={FontLevel.normal}
backgroundLevel={BackgroundLevel.Level4} backgroundLevel={BackgroundLevel.Level4}
onClick={()=>{ onClick={()=>{
// 拦截禁用状态的事件
if (this.isClipListDisable()) {
return;
}
if (this.isInnerClick) { if (this.isInnerClick) {
this.isInnerClick = false; this.isInnerClick = false;
} }
else { else {
this.props.status?.setClipObject(); this.props.status?.setClipObject();
this.props.status?.actuator.endPlay();
} }
}} }}
> >

View File

@ -9,8 +9,12 @@ class ClipRecorder extends Component<IMixinStatusProps> {
let mod: "P" | "R" = this.props.status?.focusClip ? "P" : "R"; let mod: "P" | "R" = this.props.status?.focusClip ? "P" : "R";
let runner: boolean = false; let runner: boolean = false;
let currentTime: number = 0; let currentTime: number | undefined = 0;
let allTime: number | undefined = 0;
let name: string | undefined; let name: string | undefined;
let currentFrame: number | undefined = 0;
let allFrame: number | undefined = 0;
let fps: number | undefined = 0;
// 是否开始录制 // 是否开始录制
if (mod === "R") { if (mod === "R") {
@ -28,15 +32,30 @@ class ClipRecorder extends Component<IMixinStatusProps> {
// 是否正在播放 // 是否正在播放
runner = this.props.status?.actuator.mod === ActuatorModel.Play; runner = this.props.status?.actuator.mod === ActuatorModel.Play;
name = this.props.status?.focusClip?.name; 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 <Recorder return <Recorder
name={name} name={name}
currentTime={currentTime} currentTime={currentTime}
allTime={allTime}
currentFrame={currentFrame}
allFrame={allFrame}
mode={mod} mode={mod}
running={runner} running={runner}
fps={fps}
action={() => { action={() => {
// 开启录制 // 开启录制
@ -57,6 +76,22 @@ class ClipRecorder extends Component<IMixinStatusProps> {
this.props.status?.actuator.endRecord(); this.props.status?.actuator.endRecord();
console.log("ClipRecorder: Rec end..."); 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...");
}
}} }}
/> />
} }