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
canJumpWithinReconstructablePrefixverifies 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:
- Invalidates the current generation to cancel any pending timers or async operations from the previous playback position.
- Resets visual state via
actionEngine.resetPlaybackVisualState()to clear the canvas before reconstruction. - Silently executes all whiteboard actions from the start up to the target index without triggering animations or audio playback.
- Updates internal pointers (
sceneIndexandactionIndex) to reflect the new position in the timeline. - Fires the
onProgresscallback so UI components can synchronize their state with the new playback position. - Handles autoplay logic: If the
autoplayoption istrue(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:
- Check feasibility: Call
engine.canJumpToAction(targetIndex)to verify the jump is legal. - Execute jump: Await
engine.jumpToAction(targetIndex, { autoplay?: boolean }). - 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>;
}
Navigation Without Autoplay for Slide Selection
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
canJumpToActionto 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
falseif the jump cannot complete atomically. - Flexible control: The
autoplayoption 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →