How to Use the jumpToAction Method in OpenMAIC's Playback Engine

The jumpToAction method allows you to move the playback cursor to a specific action index without replaying the entire lecture, provided the engine is not in live mode and the target lies within the reconstructable prefix of the action history.

The OpenMAIC Playback Engine (lib/playback/engine.ts) drives lecture playback and navigation through recorded sequences of whiteboard actions. Unlike sequential playback that processes every frame, jumpToAction enables instant navigation by silently reconstructing the canvas state up to a specific target index. This article covers the prerequisites, internal mechanics, and practical integration patterns for programmatic navigation.

Prerequisites for Calling jumpToAction

Before invoking jumpToAction, the engine validates two critical conditions in the canJumpToAction helper (lines 174–180 of lib/playback/engine.ts):

  • Not in live mode: The engine must be processing recorded playback. Jumping is disabled during live discussions to prevent synchronization conflicts.
  • Within reconstructable prefix: The target index must fall within the portion of the action list that can be safely replayed as whiteboard-only actions. The helper canJumpWithinReconstructablePrefix verifies that all actions before the target can be silently executed without side effects.

Always validate jumps using engine.canJumpToAction(targetIndex) before calling the main method to avoid runtime failures.

How jumpToAction Works Internally

When jumpToAction(targetIndex, options) executes successfully (implementation spans lines 182–218), it performs the following operations in order:

  1. Invalidates the current generation to cancel any pending timers or async operations from the previous playback position.
  2. Resets visual state via actionEngine.resetPlaybackVisualState() to clear the canvas before reconstruction.
  3. Silently executes all whiteboard actions from the start up to the target index without triggering animations or audio playback.
  4. Updates internal pointers (sceneIndex and actionIndex) to reflect the new position in the timeline.
  5. Fires the onProgress callback so UI components can synchronize their state with the new playback position.
  6. Handles autoplay logic: If the autoplay option is true (default when the engine is already playing), playback continues immediately from the new position; otherwise, the engine pauses.

The method returns a Promise<boolean> indicating success or failure.

Implementation Workflow

Standard integration follows a three-step pattern:

  1. Check feasibility: Call engine.canJumpToAction(targetIndex) to verify the jump is legal.
  2. Execute jump: Await engine.jumpToAction(targetIndex, { autoplay?: boolean }).
  3. Handle result: Check the returned boolean to determine if navigation succeeded or if the generation changed during execution.

Practical Code Examples

Jumping with Autoplay from a UI Button

This example demonstrates a button that jumps to action 12 and immediately resumes playback:

import { PlaybackEngine } from '@/lib/playback/engine';

function JumpButton({ engine }: { engine: PlaybackEngine }) {
  const goToAction = async (index: number) => {
    if (!engine.canJumpToAction(index)) {
      alert('Cannot jump to this point – live mode or unreconstructable prefix.');
      return;
    }
    const success = await engine.jumpToAction(index, { autoplay: true });
    if (!success) {
      console.error('Jump failed – generation may have changed.');
    }
  };

  return <button onClick={() => goToAction(12)}>Jump to Action 12</button>;
}

Use this pattern when users select a specific slide in a sidebar and want to pause for examination:

function SlideNavigator({ engine }: { engine: PlaybackEngine }) {
  const onSelectSlide = async (actionIdx: number) => {
    // Pause playback after jumping so the user can explore the slide first
    const jumped = await engine.jumpToAction(actionIdx, { autoplay: false });
    if (jumped) {
      console.log(`Paused at action ${actionIdx}`);
    }
  };

  return (
    <ul>
      {[0, 5, 10].map(idx => (
        <li key={idx} onClick={() => onSelectSlide(idx)}>
          Slide {idx}
        </li>
      ))}
    </ul>
  );
}

Unit Test Patterns

The test suite in tests/playback/action-navigation.test.ts demonstrates edge case handling:

// Valid jump within reconstructable prefix
expect(await engine.jumpToAction(saved!.actionIndex, { autoplay: false })).toBe(true);

// Out-of-range indices return false
expect(await engine.jumpToAction(-1)).toBe(false);      
expect(await engine.jumpToAction(99)).toBe(false);     

The UI component components/edit/PlaybackChromeRoot.tsx provides a production example of integrating jumpToAction for resume-point selection.

Summary

  • Always validate first: Use canJumpToAction to check live mode status and reconstructable prefix boundaries before attempting navigation.
  • Silent reconstruction: The method rebuilds canvas state by executing whiteboard actions without visual or audio playback, making it efficient for large leaps in the timeline.
  • Generation safety: The engine invalidates the current generation to prevent race conditions, returning false if the jump cannot complete atomically.
  • Flexible control: The autoplay option lets you choose between pausing at the destination or continuing playback seamlessly.

Frequently Asked Questions

Can I use jumpToAction during a live lecture?

No. The canJumpToAction method explicitly returns false when the engine is in live mode (lines 174–180 of lib/playback/engine.ts). This restriction prevents synchronization conflicts between the instructor's live actions and client-side navigation. Jumping is only supported for recorded playback sessions.

What happens if the target index is outside the reconstructable prefix?

The method returns false without modifying the playback state. The reconstructable prefix includes all actions that can be safely replayed as whiteboard-only operations without triggering side effects like audio playback or external API calls. Indices beyond this boundary are considered unsafe for silent reconstruction.

Why does jumpToAction return a boolean instead of throwing exceptions?

The boolean return pattern follows the engine's design philosophy of graceful degradation. A false result typically indicates that the generation changed during the jump operation or the preconditions failed, allowing your UI to handle the failure case (such as showing a loading spinner or retry button) without crashing the component tree.

How do I synchronize my UI components with the new playback position?

The engine automatically fires the onProgress callback after updating the internal sceneIndex and actionIndex pointers (lines 206–218). Connect your UI state management to this callback to ensure scrubber bars, slide thumbnails, and timeline indicators reflect the new position immediately after jumpToAction completes.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →