import { ICastEvent, ICastObject } from './Cast' import { Slice } from './Utils' import { IDisposable } from './Types' export interface IFrame { readonly startTime: number readonly endTime: number prev: IFrame | null duration(): number data(endTime: number, startTime?: number): string snapshot(): string } class NullFrame implements IFrame { public prev: IFrame | null = null constructor( public readonly startTime: number = 0, public readonly endTime: number = 0 ) { } duration(): number { return this.endTime - this.startTime } data(endTime: number, startTime?: number): string { return '' } snapshot(): string { if (this.prev) { return this.prev.snapshot() } return '' } } export type FrameSnapshotFn = (s: string) => string export const DEFAULT_FRAME_SNAPSHOT_FN = (s: string) => s export const NULL_FRAME: IFrame = Object.freeze(new NullFrame()) export const START_FRAME: IFrame = NULL_FRAME export class CastEventsFrame implements IFrame { private _prev: IFrame | null = null private _snapshotCache: string | null = null constructor( public readonly startTime: number, public readonly endTime: number, private _events: Slice, private _snapshotFn: FrameSnapshotFn = DEFAULT_FRAME_SNAPSHOT_FN ) { if (!_events.len()) { throw new Error('Invalid frame: empty events') } if ((startTime < 0) || ((endTime - startTime) <= 0)) { // TODO: re-evaluate if endTime - startTime can be ZERO throw new Error('Invalid frame: inccorrect time or size') } if (_events.get(0).time >= endTime) { throw new Error('Invalid frame: invalid events') } } public set prev(f: IFrame | null) { if (f !== this._prev) { this._prev = f this._snapshotCache = null } } public get prev(): IFrame | null { return this._prev } public duration(): number { return this.endTime - this.startTime } data(endTime: number, startTime: number = -1): string { if ((endTime < this.startTime) || (endTime >= this.endTime)) { throw new Error(`Cannot get data of time(${endTime})`) } const tmp: string[] = [] for (let i = 0; i < this._events.len(); i++) { const ev = this._events.get(i) if (ev.time > endTime) { break } if (startTime < ev.time && ev.time <= endTime) { tmp.push(ev.data) } } return tmp.join('') } snapshot(): string { if (this._snapshotCache !== null) { return this._snapshotCache } const tmp: string[] = new Array(this._events.len()) for (let i = 0; i < this._events.len(); i++) { tmp[i] = this._events.get(i).data } const ret = (this.prev ? this._snapshotFn(this.prev.snapshot() + tmp.join('')) : tmp.join('')) return this._snapshotCache = ret } } const DEFAULT_FRAME_EVENTS_STEP = 30 export interface IFrameQueue extends IDisposable { isEnd(frame: IFrame): boolean len(): number frame(time: number): IFrame } export class NullFrameQueue implements IFrameQueue { isEnd(frame: IFrame): boolean { return true } len(): number { return 0 } frame(time: number): IFrame { return NULL_FRAME } dispose(): void { } } export class CastFrameQueue implements IFrameQueue { private _endFrame: IFrame private _frames: Array