Skip to main content
The InteractionTimeline class records cursor position, scale, and HUD state for each frame of a recording. It enables separate composition of overlays onto clean video.

Constructor

new InteractionTimeline(
  width?: number,
  height?: number,
  options?: {
    zoom?: number;
    fps?: number;
    initialCursor?: { x: number; y: number };
    cursorSvg?: string;
    cursorSize?: number;
    cursorHotspot?: 'top-left' | 'center';
    hud?: Partial<HudConfig>;
    loadedFrames?: FrameData[];
    loadedEvents?: SoundEvent[];
  }
)
width
number
default:"1080"
Viewport width in pixels
height
number
default:"1080"
Viewport height in pixels
options.zoom
number
default:"1"
Display zoom factor (scales cursor and HUD)
options.fps
number
default:"60"
Target frames per second
options.initialCursor
{ x: number; y: number }
Starting cursor position
options.cursorSvg
string
Custom cursor SVG markup
options.cursorSize
number
default:"24"
Cursor size in pixels (before zoom)
options.cursorHotspot
'top-left' | 'center'
default:"'top-left'"
Cursor hotspot alignment
options.hud
Partial<HudConfig>
HUD theme customization
interface HudConfig {
  background: string;      // CSS color
  color: string;           // CSS color
  fontSize: number;        // In pixels
  fontFamily: string;      // CSS font-family
  borderRadius: number;    // In pixels
  position: 'top' | 'bottom';
}
options.loadedFrames
FrameData[]
Pre-existing frame data for loading saved timelines
options.loadedEvents
SoundEvent[]
Pre-existing sound events for loading saved timelines

Methods

setCursorPath()

Set a series of cursor positions to animate across multiple frames.
timeline.setCursorPath(positions)
positions
Point[]
required
Array of { x: number, y: number } positions
Each call to tick() advances through the path until complete.

setCursorScale()

Set the cursor scale factor (for click down/up animation).
timeline.setCursorScale(scale)
scale
number
required
Scale multiplier (e.g., 0.75 for pressed state, 1.0 for normal)

showHud()

Display keyboard shortcut labels.
timeline.showHud(labels)
labels
string[]
required
Array of label strings (e.g., ['⌘', 'K'] for Cmd+K)

hideHud()

Hide the keyboard shortcut HUD.
timeline.hideHud()

addEvent()

Record an interaction event with timestamp.
timeline.addEvent(type)
type
'click' | 'key'
required
Event type for sound effects
Timestamp is calculated from current frame count and FPS.

tick()

Advance the timeline by one frame.
timeline.tick()
This method:
  • Advances cursor along the current path if set
  • Records current cursor and HUD state
  • Resolves any pending waitForNextTick() promises
  • Increments frame count

tickDuplicate()

Duplicate the current frame state without advancing the cursor path.
timeline.tickDuplicate()
Useful when dropped frames need to be compensated for.

waitForNextTick()

Wait for the next frame tick.
await timeline.waitForNextTick()
Used by typeText() to synchronize typing with frame capture.

getEvents()

Get all recorded sound events.
const events = timeline.getEvents()
events
SoundEvent[]
Array of { type: 'click' | 'key', timeMs: number }

getFrameCount()

Get the total number of frames recorded.
const count = timeline.getFrameCount()
count
number
Total frame count

toJSON()

Serialize timeline to JSON.
const data = timeline.toJSON()
data
TimelineData
Complete timeline data structure
interface TimelineData {
  fps: number;
  width: number;
  height: number;
  zoom: number;
  theme: {
    cursorSvg: string;
    cursorSize: number;
    cursorHotspot: 'top-left' | 'center';
    hud: {
      background: string;
      color: string;
      fontSize: number;
      fontFamily: string;
      borderRadius: number;
      position: 'top' | 'bottom';
    };
  };
  frames: FrameData[];
  events: SoundEvent[];
}

save()

Save timeline to a JSON file.
timeline.save(path)
path
string
required
File path to write JSON data
timeline.save('./timeline.json');

load() (static)

Load a timeline from JSON data.
const timeline = InteractionTimeline.load(json)
json
TimelineData
required
Timeline data structure
timeline
InteractionTimeline
Reconstructed timeline instance
import { readFileSync } from 'fs';
import { InteractionTimeline } from '@webreel/core';

const data = JSON.parse(readFileSync('./timeline.json', 'utf-8'));
const timeline = InteractionTimeline.load(data);

Usage Example

Recording with Timeline

import { 
  Recorder, 
  RecordingContext,
  InteractionTimeline,
  clickAt 
} from '@webreel/core';

// Create timeline
const timeline = new InteractionTimeline(1920, 1080, {
  fps: 60,
  cursorSvg: customCursorSvg,
  hud: {
    background: 'rgba(0,0,0,0.8)',
    fontSize: 56
  }
});

// Configure context
const ctx = new RecordingContext();
ctx.setMode('record');
ctx.setTimeline(timeline);

// Record clean video
const recorder = new Recorder(1920, 1080);
recorder.setTimeline(timeline);

await recorder.start(client, './clean.mp4', ctx);

// Perform interactions
await clickAt(ctx, client, 500, 300);

// Stop recording
await recorder.stop();

// Save timeline for compositing
timeline.save('./timeline.json');

Inspecting Timeline Data

const data = timeline.toJSON();

console.log(`Total frames: ${data.frames.length}`);
console.log(`Duration: ${data.frames.length / data.fps}s`);
console.log(`Events: ${data.events.length}`);

// Inspect specific frame
const frame = data.frames[0];
console.log('Cursor position:', frame.cursor.x, frame.cursor.y);
console.log('Cursor scale:', frame.cursor.scale);
if (frame.hud) {
  console.log('HUD labels:', frame.hud.labels);
}

Frame Data Structure

Each frame contains:
interface FrameData {
  cursor: {
    x: number;        // Horizontal position
    y: number;        // Vertical position
    scale: number;    // Scale factor (0.75 when pressed, 1.0 otherwise)
  };
  hud: {
    labels: string[]; // Keyboard shortcut labels
  } | null;
}

Custom Cursor Design

Customize the cursor appearance:
const customCursor = `
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
  <circle cx="16" cy="16" r="12" fill="#ff0000" stroke="#000" stroke-width="2"/>
</svg>
`;

const timeline = new InteractionTimeline(1920, 1080, {
  cursorSvg: customCursor,
  cursorSize: 32,
  cursorHotspot: 'center'
});

HUD Customization

Customize the keyboard shortcut overlay:
const timeline = new InteractionTimeline(1920, 1080, {
  hud: {
    background: 'rgba(30, 30, 30, 0.95)',
    color: 'rgba(255, 255, 255, 0.9)',
    fontSize: 64,
    fontFamily: '"SF Pro Display", system-ui, sans-serif',
    borderRadius: 24,
    position: 'top'
  }
});

Build docs developers (and LLMs) love