Add clip player & offline renderer #49
@ -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`;
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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) {
|
||||||
|
@ -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 };
|
@ -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();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -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...");
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user