@ct-player/embed 1.2.1 → 1.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
+ import Hls from 'hls.js';
1
2
  import * as React__default from 'react';
2
3
  import React__default__default from 'react';
3
4
  import * as react_jsx_runtime from 'react/jsx-runtime';
4
- import Hls from 'hls.js';
5
5
 
6
6
  /**
7
7
  * CT Format Event Types
@@ -11,7 +11,7 @@ import Hls from 'hls.js';
11
11
  /**
12
12
  * All possible event types in the .ct format
13
13
  */
14
- type CTEventType = 'init' | 'code' | 'code_edit' | 'language' | 'terminal' | 'terminal_input' | 'stroke_start' | 'whiteboard_points' | 'stroke_end' | 'whiteboard_stroke' | 'whiteboard_clear' | 'whiteboard_undo' | 'whiteboard' | 'whiteboard_point' | 'excalidraw_scene' | 'excalidraw_clear' | 'excalidraw_pointer' | 'overlay_scene' | 'overlay_clear' | 'overlay_pointer' | 'document_load' | 'document_page' | 'document_zoom' | 'document_scroll' | 'document_clear' | 'file_create' | 'file_update' | 'file_delete' | 'file_rename' | 'file_switch' | 'folder_create' | 'folder_delete' | 'file_select' | 'file_change' | 'file_creation_start' | 'file_creation_input' | 'file_creation_cancel' | 'file_rename_start' | 'file_rename_input' | 'file_rename_cancel' | 'folder_toggle' | 'cursor_move' | 'cursor_click' | 'cursor_enter' | 'cursor_leave' | 'file_hover' | 'text_select' | 'editor_cursor' | 'tool_switch' | 'tool' | 'marker' | 'webcam_state' | 'content_dimensions';
14
+ type CTEventType = 'init' | 'code' | 'code_edit' | 'language' | 'terminal' | 'terminal_input' | 'stroke_start' | 'whiteboard_points' | 'stroke_end' | 'whiteboard_stroke' | 'whiteboard_clear' | 'whiteboard_undo' | 'whiteboard' | 'whiteboard_point' | 'excalidraw_scene' | 'excalidraw_clear' | 'excalidraw_pointer' | 'overlay_scene' | 'overlay_clear' | 'overlay_pointer' | 'document_load' | 'document_page' | 'document_zoom' | 'document_scroll' | 'document_clear' | 'db_seed' | 'db_query' | 'db_result' | 'db_error' | 'db_schema_change' | 'db_snapshot' | 'file_create' | 'file_update' | 'file_delete' | 'file_rename' | 'file_switch' | 'folder_create' | 'folder_delete' | 'file_select' | 'file_change' | 'file_creation_start' | 'file_creation_input' | 'file_creation_cancel' | 'file_rename_start' | 'file_rename_input' | 'file_rename_cancel' | 'folder_toggle' | 'cursor_move' | 'cursor_click' | 'cursor_enter' | 'cursor_leave' | 'file_hover' | 'text_select' | 'editor_cursor' | 'editor_scroll' | 'tool_switch' | 'tool' | 'marker' | 'webcam_state' | 'content_dimensions' | 'panel_resize';
15
15
  /**
16
16
  * Base event structure with generic type parameter
17
17
  */
@@ -27,7 +27,7 @@ interface CTEventBase<T extends CTEventType = CTEventType> {
27
27
  * Discriminated union of all event types.
28
28
  * This enables proper type narrowing in switch statements.
29
29
  */
30
- type CTEvent = CTEventBase<'init'> | CTEventBase<'code'> | CTEventBase<'code_edit'> | CTEventBase<'language'> | CTEventBase<'terminal'> | CTEventBase<'terminal_input'> | CTEventBase<'stroke_start'> | CTEventBase<'whiteboard_points'> | CTEventBase<'stroke_end'> | CTEventBase<'whiteboard_stroke'> | CTEventBase<'whiteboard_clear'> | CTEventBase<'whiteboard_undo'> | CTEventBase<'whiteboard'> | CTEventBase<'whiteboard_point'> | CTEventBase<'excalidraw_scene'> | CTEventBase<'excalidraw_clear'> | CTEventBase<'excalidraw_pointer'> | CTEventBase<'overlay_scene'> | CTEventBase<'overlay_clear'> | CTEventBase<'overlay_pointer'> | CTEventBase<'document_load'> | CTEventBase<'document_page'> | CTEventBase<'document_zoom'> | CTEventBase<'document_scroll'> | CTEventBase<'document_clear'> | CTEventBase<'file_create'> | CTEventBase<'file_update'> | CTEventBase<'file_delete'> | CTEventBase<'file_rename'> | CTEventBase<'file_switch'> | CTEventBase<'folder_create'> | CTEventBase<'folder_delete'> | CTEventBase<'file_select'> | CTEventBase<'file_change'> | CTEventBase<'file_creation_start'> | CTEventBase<'file_creation_input'> | CTEventBase<'file_creation_cancel'> | CTEventBase<'file_rename_start'> | CTEventBase<'file_rename_input'> | CTEventBase<'file_rename_cancel'> | CTEventBase<'folder_toggle'> | CTEventBase<'cursor_move'> | CTEventBase<'cursor_click'> | CTEventBase<'cursor_enter'> | CTEventBase<'cursor_leave'> | CTEventBase<'file_hover'> | CTEventBase<'text_select'> | CTEventBase<'editor_cursor'> | CTEventBase<'tool_switch'> | CTEventBase<'tool'> | CTEventBase<'marker'> | CTEventBase<'webcam_state'> | CTEventBase<'content_dimensions'>;
30
+ type CTEvent = CTEventBase<'init'> | CTEventBase<'code'> | CTEventBase<'code_edit'> | CTEventBase<'language'> | CTEventBase<'terminal'> | CTEventBase<'terminal_input'> | CTEventBase<'stroke_start'> | CTEventBase<'whiteboard_points'> | CTEventBase<'stroke_end'> | CTEventBase<'whiteboard_stroke'> | CTEventBase<'whiteboard_clear'> | CTEventBase<'whiteboard_undo'> | CTEventBase<'whiteboard'> | CTEventBase<'whiteboard_point'> | CTEventBase<'excalidraw_scene'> | CTEventBase<'excalidraw_clear'> | CTEventBase<'excalidraw_pointer'> | CTEventBase<'overlay_scene'> | CTEventBase<'overlay_clear'> | CTEventBase<'overlay_pointer'> | CTEventBase<'document_load'> | CTEventBase<'document_page'> | CTEventBase<'document_zoom'> | CTEventBase<'document_scroll'> | CTEventBase<'document_clear'> | CTEventBase<'db_seed'> | CTEventBase<'db_query'> | CTEventBase<'db_result'> | CTEventBase<'db_error'> | CTEventBase<'db_schema_change'> | CTEventBase<'db_snapshot'> | CTEventBase<'file_create'> | CTEventBase<'file_update'> | CTEventBase<'file_delete'> | CTEventBase<'file_rename'> | CTEventBase<'file_switch'> | CTEventBase<'folder_create'> | CTEventBase<'folder_delete'> | CTEventBase<'file_select'> | CTEventBase<'file_change'> | CTEventBase<'file_creation_start'> | CTEventBase<'file_creation_input'> | CTEventBase<'file_creation_cancel'> | CTEventBase<'file_rename_start'> | CTEventBase<'file_rename_input'> | CTEventBase<'file_rename_cancel'> | CTEventBase<'folder_toggle'> | CTEventBase<'cursor_move'> | CTEventBase<'cursor_click'> | CTEventBase<'cursor_enter'> | CTEventBase<'cursor_leave'> | CTEventBase<'file_hover'> | CTEventBase<'text_select'> | CTEventBase<'editor_cursor'> | CTEventBase<'editor_scroll'> | CTEventBase<'tool_switch'> | CTEventBase<'tool'> | CTEventBase<'marker'> | CTEventBase<'webcam_state'> | CTEventBase<'content_dimensions'> | CTEventBase<'panel_resize'>;
31
31
  /**
32
32
  * Mapping of event types to their data structures
33
33
  */
@@ -55,6 +55,12 @@ interface CTEventDataMap {
55
55
  document_zoom: DocumentZoomEventData;
56
56
  document_scroll: DocumentScrollEventData;
57
57
  document_clear: DocumentClearEventData;
58
+ db_seed: DbSeedEventData;
59
+ db_query: DbQueryEventData;
60
+ db_result: DbResultEventData;
61
+ db_error: DbErrorEventData;
62
+ db_schema_change: DbSchemaChangeEventData;
63
+ db_snapshot: DbSnapshotEventData;
58
64
  file_create: FileCreateEventData;
59
65
  file_update: FileUpdateEventData;
60
66
  file_delete: FileDeleteEventData;
@@ -76,10 +82,12 @@ interface CTEventDataMap {
76
82
  file_hover: FileHoverEventData;
77
83
  text_select: TextSelectEventData;
78
84
  editor_cursor: EditorCursorEventData;
85
+ editor_scroll: EditorScrollEventData;
79
86
  tool_switch: ToolSwitchEventData;
80
87
  marker: MarkerEventData;
81
88
  webcam_state: WebcamStateEventData;
82
89
  content_dimensions: ContentDimensionsEventData;
90
+ panel_resize: PanelResizeEventData;
83
91
  tool: ToolEventData;
84
92
  whiteboard: WhiteboardEventData;
85
93
  whiteboard_point: WhiteboardPointEventData;
@@ -231,6 +239,8 @@ interface InitEventData {
231
239
  width: number;
232
240
  height: number;
233
241
  };
242
+ dbSeedSql?: string;
243
+ dbSnapshot?: string;
234
244
  }
235
245
  /**
236
246
  * Code content change
@@ -434,6 +444,65 @@ interface DocumentScrollEventData {
434
444
  */
435
445
  interface DocumentClearEventData {
436
446
  }
447
+ /**
448
+ * Instructor loads seed SQL (CREATE TABLE + INSERT).
449
+ * Fired once at the start of a DB session.
450
+ */
451
+ interface DbSeedEventData {
452
+ /** The seed SQL text */
453
+ sql: string;
454
+ /** List of table names created by the seed */
455
+ tables: string[];
456
+ }
457
+ /**
458
+ * A SQL query was executed (instructor or student).
459
+ */
460
+ interface DbQueryEventData {
461
+ /** The raw SQL string that was executed */
462
+ sql: string;
463
+ }
464
+ /**
465
+ * The result of a successfully executed query.
466
+ */
467
+ interface DbResultEventData {
468
+ /** Column headers */
469
+ columns: string[];
470
+ /** Row data — each inner array is one row, values are primitives */
471
+ rows: (string | number | null)[][];
472
+ /** Total rows returned */
473
+ rowCount: number;
474
+ /** Wall-clock execution time in milliseconds */
475
+ executionTimeMs: number;
476
+ }
477
+ /**
478
+ * A query failed.
479
+ */
480
+ interface DbErrorEventData {
481
+ /** The query that failed */
482
+ sql: string;
483
+ /** The error message */
484
+ message: string;
485
+ }
486
+ /**
487
+ * The schema changed (after DDL). Contains current schema state.
488
+ */
489
+ interface DbSchemaChangeEventData {
490
+ /** Current schema — output of SELECT name, sql FROM sqlite_master */
491
+ tables: {
492
+ name: string;
493
+ sql: string;
494
+ }[];
495
+ }
496
+ /**
497
+ * Full database binary snapshot (base64-encoded).
498
+ * Used for seeking.
499
+ */
500
+ interface DbSnapshotEventData {
501
+ /** The full database serialized as a base64 string */
502
+ snapshot: string;
503
+ /** Current table names for quick reference */
504
+ tables: string[];
505
+ }
437
506
  /**
438
507
  * Create new file
439
508
  */
@@ -594,6 +663,13 @@ interface EditorCursorEventData {
594
663
  /** Column number (1-based) */
595
664
  column: number;
596
665
  }
666
+ /**
667
+ * Editor scroll position (code editor)
668
+ */
669
+ interface EditorScrollEventData {
670
+ /** Vertical scroll offset in pixels */
671
+ scrollTop: number;
672
+ }
597
673
  /**
598
674
  * Switch active tool
599
675
  */
@@ -623,6 +699,15 @@ interface ContentDimensionsEventData {
623
699
  width: number;
624
700
  height: number;
625
701
  }
702
+ /**
703
+ * Panel resize event — captures explorer width and terminal height changes
704
+ */
705
+ interface PanelResizeEventData {
706
+ /** Which panel was resized */
707
+ panel: 'explorer' | 'terminal';
708
+ /** New size in pixels (width for explorer, height for terminal) */
709
+ size: number;
710
+ }
626
711
  /**
627
712
  * Legacy tool event (same as tool_switch)
628
713
  */
@@ -704,6 +789,7 @@ interface PlaybackState$1 {
704
789
  y: number;
705
790
  visible: boolean;
706
791
  clicking?: boolean;
792
+ clickTimestamp?: number;
707
793
  };
708
794
  /** File creation UI state - for showing inline input during playback */
709
795
  fileCreation: FileCreationState | null;
@@ -717,6 +803,8 @@ interface PlaybackState$1 {
717
803
  textSelection: TextSelectionState | null;
718
804
  /** Editor typing cursor (caret) position */
719
805
  editorCursor: EditorCursorState | null;
806
+ /** Editor scroll position (pixels from top) */
807
+ editorScrollTop: number;
720
808
  /** Whether webcam is in maximized (full overlay) mode */
721
809
  webcamMaximized: boolean;
722
810
  /** Content area dimensions from the recording session.
@@ -743,6 +831,32 @@ interface PlaybackState$1 {
743
831
  tool: 'pointer' | 'laser';
744
832
  button: 'down' | 'up';
745
833
  } | null;
834
+ /** Whether a sql.js database is active in this recording */
835
+ dbInitialized: boolean;
836
+ /** The seed SQL that was loaded (null if no DB session) */
837
+ dbSeedSql: string | null;
838
+ /** The most recently executed SQL query */
839
+ dbLastQuery: string | null;
840
+ /** The most recently returned query result */
841
+ dbLastResult: DbResultEventData | null;
842
+ /** The most recent error message (null if last query succeeded) */
843
+ dbLastError: string | null;
844
+ /** Current schema (tables + their CREATE statements) */
845
+ dbSchema: {
846
+ name: string;
847
+ sql: string;
848
+ }[];
849
+ /** Most recent base64 snapshot (for seeking) */
850
+ dbSnapshot: string | null;
851
+ /** History of queries executed so far (for query history panel) */
852
+ dbQueryHistory: {
853
+ sql: string;
854
+ time: number;
855
+ }[];
856
+ /** File explorer panel width in pixels (null = use default) */
857
+ explorerWidth: number | null;
858
+ /** Terminal panel height in pixels (null = use default) */
859
+ terminalHeight: number | null;
746
860
  }
747
861
  /**
748
862
  * State for file/folder creation UI display during playback
@@ -1614,6 +1728,10 @@ interface IDEPanelProps extends BasePanelProps {
1614
1728
  line: number;
1615
1729
  column: number;
1616
1730
  }) => void;
1731
+ /** Editor scroll position in pixels from top (playback display) */
1732
+ editorScrollTop?: number;
1733
+ /** Editor scroll change callback (recording) */
1734
+ onEditorScrollChange?: (scrollTop: number) => void;
1617
1735
  /**
1618
1736
  * Called when user interacts with the IDE (clicks explorer, terminal, etc).
1619
1737
  * Use this to pause playback when user starts interacting.
@@ -1629,6 +1747,41 @@ interface IDEPanelProps extends BasePanelProps {
1629
1747
  aiDebugEnabled?: boolean;
1630
1748
  /** API endpoint for AI debug analysis. Default: '/api/ai/debug' */
1631
1749
  aiDebugEndpoint?: string;
1750
+ /** Whether a sql.js database is active in this recording */
1751
+ dbInitialized?: boolean;
1752
+ /** Current database schema */
1753
+ dbSchema?: {
1754
+ name: string;
1755
+ sql: string;
1756
+ }[];
1757
+ /** Most recently executed SQL query */
1758
+ dbLastQuery?: string | null;
1759
+ /** Most recent query result */
1760
+ dbLastResult?: {
1761
+ columns: string[];
1762
+ rows: (string | number | null)[][];
1763
+ rowCount: number;
1764
+ executionTimeMs: number;
1765
+ } | null;
1766
+ /** Most recent error message */
1767
+ dbLastError?: string | null;
1768
+ /** Query execution history */
1769
+ dbQueryHistory?: {
1770
+ sql: string;
1771
+ time: number;
1772
+ }[];
1773
+ /** Execute a SQL query (recording/interactive) */
1774
+ onDbQueryExecute?: (sql: string) => void | Promise<void>;
1775
+ /** Load seed SQL (recording) */
1776
+ onDbSeedLoad?: (sql: string) => void | Promise<void>;
1777
+ /** Reset database (recording) */
1778
+ onDbReset?: () => void;
1779
+ /** External explorer width (for playback — overrides local state) */
1780
+ explorerWidthState?: number | null;
1781
+ /** External terminal height (for playback — overrides local state) */
1782
+ terminalHeightState?: number | null;
1783
+ /** Called when a panel is resized (for recording) */
1784
+ onPanelResize?: (panel: 'explorer' | 'terminal', size: number) => void;
1632
1785
  }
1633
1786
  /**
1634
1787
  * Excalidraw scene structure (simplified)
@@ -1882,7 +2035,7 @@ interface VirtualFileSystem {
1882
2035
  notifyChanged?: () => void;
1883
2036
  mkdir?: (path: string) => void;
1884
2037
  }
1885
- declare function IDEPanel({ fileSystem, tree: externalTree, files: externalFiles, code: externalCode, language: externalLanguage, currentFile: externalCurrentFile, onCodeChange, onFileChange, onFileCreate, onFileDelete, onFileRename, onFileSelect, onFolderCreate, onFolderDelete, onTerminalCommand, onTerminalClear, onTerminalAddLines, fileCreationState, onFileCreationStart, onFileCreationInput, onFileCreationCancel, fileRenamingState, onFileRenameStart, onFileRenameInput, onFileRenameCancel, expandedFoldersState, onFolderToggle, terminalLines: externalTerminalLines, terminalInput: externalTerminalInput, onTerminalInputChange, onExecute, onTerminalExecute, mode, isPlaying, defaultShowExplorer, defaultShowTerminal, showRunButton, interactiveLabel, initialFiles, initialActiveFile, hoveredFile, onFileHover, textSelection, onTextSelect, editorCursor, onEditorCursorChange, onInteractionStart, aiSuggestionsEnabled, aiSuggestionsEndpoint, aiSuggestionsDebounceMs, aiDebugEnabled, aiDebugEndpoint, }: IDEPanelProps & {
2038
+ declare function IDEPanel({ fileSystem, tree: externalTree, files: externalFiles, code: externalCode, language: externalLanguage, currentFile: externalCurrentFile, onCodeChange, onFileChange, onFileCreate, onFileDelete, onFileRename, onFileSelect, onFolderCreate, onFolderDelete, onTerminalCommand, onTerminalClear, onTerminalAddLines, fileCreationState, onFileCreationStart, onFileCreationInput, onFileCreationCancel, fileRenamingState, onFileRenameStart, onFileRenameInput, onFileRenameCancel, expandedFoldersState, onFolderToggle, terminalLines: externalTerminalLines, terminalInput: externalTerminalInput, onTerminalInputChange, onExecute, onTerminalExecute, mode, isPlaying, defaultShowExplorer, defaultShowTerminal, showRunButton, interactiveLabel, initialFiles, initialActiveFile, hoveredFile, onFileHover, textSelection, onTextSelect, editorCursor, onEditorCursorChange, editorScrollTop, onEditorScrollChange, onInteractionStart, aiSuggestionsEnabled, aiSuggestionsEndpoint, aiSuggestionsDebounceMs, aiDebugEnabled, aiDebugEndpoint, dbInitialized, dbSchema, dbLastQuery, dbLastResult, dbLastError, dbQueryHistory, onDbQueryExecute, onDbSeedLoad, onDbReset, explorerWidthState, terminalHeightState, onPanelResize, }: IDEPanelProps & {
1886
2039
  fileSystem?: VirtualFileSystem;
1887
2040
  }): react_jsx_runtime.JSX.Element;
1888
2041
 
@@ -1891,52 +2044,100 @@ declare function FileExplorer({ tree, selectedFile, expandedFolders, fileCreatio
1891
2044
  declare const Terminal: React__default.ForwardRefExoticComponent<TerminalProps & React__default.RefAttributes<TerminalRef>>;
1892
2045
 
1893
2046
  /**
1894
- * Learner Branch Types
2047
+ * PlaybackStateMachine
1895
2048
  *
1896
- * A branch is a saved snapshot of the learner's code edits at a specific
1897
- * moment in the instructor's timeline. Branches are learner-side only
1898
- * they never modify the .ct file, events, or manifest.
2049
+ * Pure TypeScript finite state machine for player state management.
2050
+ * No React, no DOM, no side effects only state and transitions.
2051
+ *
2052
+ * Replaces the 13 booleans scattered across useRemoteStreamingPlayback
2053
+ * with a single enum value and enforced valid transitions.
1899
2054
  *
1900
2055
  * @packageDocumentation
1901
2056
  */
1902
- /**
1903
- * A point-in-time capture of the IDE state.
1904
- * Used for both the instructor's original state and the learner's edited state.
1905
- */
1906
- interface BranchSnapshot {
1907
- /** Active file's code content */
1908
- code: string;
1909
- /** Programming language at snapshot time */
1910
- language: string;
1911
- /** Which file tab was active (null if single-file mode) */
1912
- currentFile: string | null;
1913
- /** Full file system snapshot — path → content */
1914
- fileSystem: Record<string, string>;
1915
- /** Terminal output lines if any existed */
1916
- terminalOutput?: string[];
1917
- /** Which tool panel was open (code / terminal / whiteboard / document) */
1918
- activeTool: string;
1919
- /** Folder paths created by the learner during interactive mode */
1920
- folders?: string[];
2057
+ type PlayerState = 'IDLE' | 'LOADING' | 'READY' | 'PLAYING' | 'PAUSED' | 'BUFFERING' | 'SEEKING' | 'ENDED' | 'ERROR';
2058
+ type PlayerAction = 'LOAD' | 'SOURCES_READY' | 'PLAY' | 'PAUSE' | 'BUFFER_EMPTY' | 'BUFFER_READY' | 'SEEK' | 'SEEK_COMPLETE' | 'COMPLETE' | 'ERROR' | 'RESET' | 'DESTROY';
2059
+ interface Transition {
2060
+ from: PlayerState;
2061
+ to: PlayerState;
2062
+ action: PlayerAction;
2063
+ }
2064
+ type TransitionListener = (transition: Transition) => void;
2065
+ declare class PlaybackStateMachine {
2066
+ private _state;
2067
+ private _listeners;
2068
+ private _preSeekState;
2069
+ private _destroyed;
2070
+ get state(): PlayerState;
2071
+ get preSeekState(): PlayerState | null;
2072
+ /**
2073
+ * Dispatch an action. Returns true if the transition was valid, false otherwise.
2074
+ * Invalid transitions are logged as warnings they are programmer errors.
2075
+ */
2076
+ dispatch(action: PlayerAction): boolean;
2077
+ /**
2078
+ * Subscribe to state transitions. Returns an unsubscribe function.
2079
+ */
2080
+ onTransition(fn: TransitionListener): () => void;
2081
+ /**
2082
+ * Check if the current state is one of the given states.
2083
+ */
2084
+ is(...states: PlayerState[]): boolean;
1921
2085
  }
2086
+
1922
2087
  /**
1923
- * A learner branch — a saved code snapshot at a point in the timeline.
2088
+ * MasterClock
2089
+ *
2090
+ * The single source of time for the entire player. Nothing else reads
2091
+ * `audio.currentTime` directly — everything goes through this clock.
2092
+ *
2093
+ * Two tick sources work together:
2094
+ * 1. `timeupdate` event (primary) — fires ~4x/sec from audio element.
2095
+ * Reliable in background tabs. Source of truth for "what time is it."
2096
+ * 2. `requestAnimationFrame` (interpolation) — fires ~60fps when visible.
2097
+ * Between timeupdate ticks, interpolates linearly for smooth UI.
2098
+ *
2099
+ * No React, no DOM ownership — pure TypeScript.
2100
+ *
2101
+ * @packageDocumentation
1924
2102
  */
1925
- interface LearnerBranch {
1926
- /** Unique branch ID (crypto.randomUUID) */
1927
- id: string;
1928
- /** Lesson/recording this branch belongs to */
1929
- lessonId: string;
1930
- /** Playback time in milliseconds where the learner paused */
1931
- timeMs: number;
1932
- /** Instructor's state at that moment (for comparison / restoring) */
1933
- originalState: BranchSnapshot;
1934
- /** Learner's edited state */
1935
- learnerState: BranchSnapshot;
1936
- /** ISO timestamp of when the branch was created */
1937
- createdAt: string;
1938
- /** User-provided label, defaults to "Branch at MM:SS" */
1939
- label?: string;
2103
+ type ClockTickCallback = (timeMs: number) => void;
2104
+ interface ClockSource {
2105
+ getCurrentTimeMs(): number;
2106
+ onTick(fn: ClockTickCallback): () => void;
2107
+ setPlaybackRate(rate: number): void;
2108
+ getPlaybackRate(): number;
2109
+ }
2110
+ declare class MasterClock {
2111
+ private source;
2112
+ private sourceUnsub;
2113
+ private subscribers;
2114
+ private rafId;
2115
+ private lastTickTimeMs;
2116
+ private lastTickTimestamp;
2117
+ private playbackRate;
2118
+ private running;
2119
+ /** Attach a clock source (e.g., audio element wrapper). */
2120
+ setSource(source: ClockSource): void;
2121
+ /**
2122
+ * Get current time in ms.
2123
+ * Between timeupdate ticks, interpolates linearly using elapsed wall-clock time.
2124
+ */
2125
+ getCurrentTimeMs(): number;
2126
+ /** Start the RAF interpolation loop for smooth UI updates. */
2127
+ start(): void;
2128
+ /** Stop the RAF loop. timeupdate events still arrive from the source. */
2129
+ stop(): void;
2130
+ /** Subscribe to clock ticks (called on every RAF frame + every timeupdate). */
2131
+ subscribe(fn: ClockTickCallback): () => void;
2132
+ setPlaybackRate(rate: number): void;
2133
+ getPlaybackRate(): number;
2134
+ /** Force-set the clock position (used after seek). */
2135
+ seekTo(timeMs: number): void;
2136
+ /** Whether the RAF loop is running. */
2137
+ isRunning(): boolean;
2138
+ private rafLoop;
2139
+ private notify;
2140
+ destroy(): void;
1940
2141
  }
1941
2142
 
1942
2143
  /**
@@ -2090,33 +2291,6 @@ interface StreamingManifest {
2090
2291
  */
2091
2292
  interactionPoints?: InteractionPointManifest[];
2092
2293
  }
2093
- /**
2094
- * Options for useRemoteStreamingPlayback hook
2095
- */
2096
- interface UseRemoteStreamingPlaybackOptions {
2097
- /** URL to the streaming manifest.json */
2098
- manifestUrl: string;
2099
- /** Auto-play when ready */
2100
- autoPlay?: boolean;
2101
- /** Initial playback speed (default: 1.0) */
2102
- defaultSpeed?: number;
2103
- /** Maximum chunks to keep in memory (default: 5) */
2104
- maxCacheSize?: number;
2105
- /** Called when streaming is ready to play */
2106
- onReady?: () => void;
2107
- /** Called when playback starts */
2108
- onPlay?: () => void;
2109
- /** Called when playback pauses */
2110
- onPause?: () => void;
2111
- /** Called on playback progress */
2112
- onProgress?: (timeMs: number, duration: number) => void;
2113
- /** Called when playback completes */
2114
- onComplete?: () => void;
2115
- /** Called when buffering state changes */
2116
- onBuffering?: (isBuffering: boolean) => void;
2117
- /** Called on error */
2118
- onError?: (error: Error) => void;
2119
- }
2120
2294
  /**
2121
2295
  * Buffer health status for remote streaming
2122
2296
  */
@@ -2132,77 +2306,6 @@ interface RemoteBufferHealth {
2132
2306
  /** Total buffered time in milliseconds */
2133
2307
  bufferedTimeMs: number;
2134
2308
  }
2135
- /**
2136
- * Performance metrics for remote streaming
2137
- */
2138
- interface RemoteStreamingMetrics {
2139
- /** Average chunk fetch time in ms */
2140
- avgChunkFetchTimeMs: number;
2141
- /** Cache hit rate (0-1) */
2142
- cacheHitRate: number;
2143
- /** Number of times playback had to wait for buffering */
2144
- bufferUnderrunCount: number;
2145
- /** Average seek time in ms */
2146
- avgSeekTimeMs: number;
2147
- /** Total bytes downloaded */
2148
- totalBytesLoaded: number;
2149
- /** Peak number of chunks in cache */
2150
- peakChunksCached: number;
2151
- }
2152
- /**
2153
- * Complete state returned by the remote streaming hook
2154
- */
2155
- interface RemoteStreamingPlaybackState {
2156
- /** Current playback state (code, terminal, etc.) */
2157
- state: PlaybackState$1;
2158
- /** Current playback time in milliseconds */
2159
- currentTime: number;
2160
- /** Total duration in milliseconds */
2161
- duration: number;
2162
- /** Whether playback is active */
2163
- isPlaying: boolean;
2164
- /** Current playback speed */
2165
- speed: number;
2166
- /** Whether the player is ready (UI can render — audio+events loaded) */
2167
- isReady: boolean;
2168
- /** Whether ALL sources are loaded and synced (audio+video+events) */
2169
- allSourcesReady: boolean;
2170
- /** Whether currently buffering */
2171
- isBuffering: boolean;
2172
- /** Buffer health information */
2173
- bufferHealth: RemoteBufferHealth;
2174
- /** Streaming metrics */
2175
- metrics: RemoteStreamingMetrics;
2176
- /** Current error (if any) */
2177
- error: Error | null;
2178
- /** The loaded manifest */
2179
- manifest: StreamingManifest | null;
2180
- }
2181
- /**
2182
- * Controls for remote streaming playback
2183
- */
2184
- interface RemoteStreamingPlaybackControls {
2185
- /** Start playback */
2186
- play(): void;
2187
- /** Pause playback */
2188
- pause(): void;
2189
- /** Seek to a specific time (milliseconds) */
2190
- seek(timeMs: number): Promise<void>;
2191
- /** Set playback speed */
2192
- setSpeed(speed: number): void;
2193
- /** Get the audio element for custom controls */
2194
- getAudioElement(): HTMLAudioElement | null;
2195
- /** Get the video element for webcam overlay (null if no video) */
2196
- getVideoElement(): HTMLVideoElement | null;
2197
- /** Attach HLS video player to a video element (from WebcamOverlay onVideoRef) */
2198
- initVideoHls(videoElement: HTMLVideoElement | null): void;
2199
- /** Get available webcam video quality levels */
2200
- getVideoQualityLevels(): HLSQualityLevel[];
2201
- /** Set webcam video quality level (-1 for auto) */
2202
- setVideoQualityLevel(index: number): void;
2203
- /** Get current webcam video quality level (-1 = auto) */
2204
- getVideoQualityLevel(): number;
2205
- }
2206
2309
  /**
2207
2310
  * HLS player configuration
2208
2311
  */
@@ -2215,6 +2318,8 @@ interface HLSPlayerConfig {
2215
2318
  startLevel?: number;
2216
2319
  /** Auto-start load */
2217
2320
  autoStartLoad?: boolean;
2321
+ /** Cap quality level to the video element's display dimensions */
2322
+ capLevelToPlayerSize?: boolean;
2218
2323
  }
2219
2324
  /**
2220
2325
  * HLS quality level info
@@ -2233,96 +2338,579 @@ interface HLSQualityLevel {
2233
2338
  }
2234
2339
 
2235
2340
  /**
2236
- * @ct-courses/player - Type Definitions
2341
+ * Remote Chunk Fetcher
2342
+ *
2343
+ * Handles fetching and decompressing event chunks from CDN.
2344
+ * Supports gzip compressed chunks and provides retry logic.
2345
+ *
2346
+ * @packageDocumentation
2237
2347
  */
2238
2348
 
2239
- /**
2240
- * Backend adapter interface for loading recordings
2241
- */
2242
- interface BackendAdapter {
2243
- getRecording(id: string): Promise<InternalRecording | null>;
2244
- getMediaUrl?(recordingId: string): Promise<string | null>;
2245
- }
2246
- /**
2247
- * Internal recording structure (from backend)
2248
- */
2249
- interface InternalRecording {
2250
- id: string;
2251
- name?: string;
2252
- duration: number;
2253
- events: InternalEvent[];
2254
- markers?: RecordingMarker[];
2255
- hasMedia?: boolean;
2256
- mediaUrl?: string;
2257
- createdAt?: number;
2258
- }
2259
- /**
2260
- * Internal event structure (from backend)
2261
- */
2262
- interface InternalEvent {
2263
- type: string;
2264
- time: number;
2265
- data: Record<string, unknown>;
2349
+ interface FetchResult<T> {
2350
+ data: T | null;
2351
+ error: string | null;
2352
+ durationMs: number;
2353
+ bytesLoaded: number;
2266
2354
  }
2267
- /**
2268
- * Recording marker for chapters
2269
- */
2270
- interface RecordingMarker {
2271
- id: string;
2272
- time: number;
2273
- label: string;
2274
- color?: string;
2355
+ interface ChunkFetchResult extends FetchResult<CTEventChunk> {
2356
+ chunkId: number;
2275
2357
  }
2276
- /**
2277
- * Cursor position for replay
2278
- */
2279
- interface CursorPosition {
2280
- x: number;
2281
- y: number;
2282
- visible: boolean;
2358
+ interface FetcherConfig {
2359
+ /** Maximum retry attempts */
2360
+ maxRetries: number;
2361
+ /** Base delay between retries (ms) */
2362
+ retryDelayMs: number;
2363
+ /** Request timeout (ms) */
2364
+ timeoutMs: number;
2365
+ }
2366
+ /**
2367
+ * Fetcher for streaming manifest and event chunks
2368
+ */
2369
+ declare class ChunkFetcher {
2370
+ private config;
2371
+ private chunkIndex;
2372
+ private eventsBaseUrl;
2373
+ private compressed;
2374
+ private pendingFetches;
2375
+ private abortController;
2376
+ private metrics;
2377
+ constructor(config?: Partial<FetcherConfig>);
2378
+ /**
2379
+ * Fetch the streaming manifest
2380
+ */
2381
+ fetchManifest(manifestUrl: string): Promise<FetchResult<StreamingManifest>>;
2382
+ /**
2383
+ * Fetch the events chunk index
2384
+ */
2385
+ fetchChunkIndex(indexUrl: string): Promise<FetchResult<CTEventChunkIndex>>;
2386
+ /**
2387
+ * Initialize the fetcher with a manifest
2388
+ */
2389
+ initialize(manifest: StreamingManifest, chunkIndex: CTEventChunkIndex): void;
2390
+ /**
2391
+ * Fetch a specific chunk by ID
2392
+ */
2393
+ fetchChunk(chunkId: number): Promise<ChunkFetchResult>;
2394
+ /**
2395
+ * Prefetch multiple chunks
2396
+ */
2397
+ prefetchChunks(chunkIds: number[]): Promise<void>;
2398
+ /**
2399
+ * Get fetcher metrics
2400
+ */
2401
+ getMetrics(): {
2402
+ totalFetches: number;
2403
+ totalBytes: number;
2404
+ avgFetchTimeMs: number;
2405
+ errorRate: number;
2406
+ };
2407
+ /**
2408
+ * Get chunk info by ID
2409
+ */
2410
+ getChunkInfo(chunkId: number): CTChunkInfo | null;
2411
+ /**
2412
+ * Get total chunk count
2413
+ */
2414
+ getTotalChunks(): number;
2415
+ /**
2416
+ * Reset fetcher state
2417
+ */
2418
+ reset(): void;
2283
2419
  }
2420
+
2284
2421
  /**
2285
- * File system snapshot
2422
+ * MediaSourceAdapter Interface for pluggable media sources.
2423
+ *
2424
+ * Abstracts how the PlayerEngine obtains audio, video, and event data.
2425
+ * Implementations:
2426
+ * - HlsStreamingAdapter: CDN/HLS streaming via manifest URL
2427
+ * - LocalFileAdapter: Local .ct file playback from a CTRecording blob
2428
+ *
2429
+ * @packageDocumentation
2286
2430
  */
2287
- interface FileSystemSnapshot {
2288
- [path: string]: string;
2431
+
2432
+ type AudioSourceResult = {
2433
+ type: 'hls';
2434
+ url: string;
2435
+ } | {
2436
+ type: 'url';
2437
+ url: string;
2438
+ };
2439
+ type VideoSourceResult = {
2440
+ type: 'hls';
2441
+ url: string;
2442
+ } | {
2443
+ type: 'url';
2444
+ url: string;
2445
+ } | null;
2446
+ type EventsSourceResult = {
2447
+ type: 'chunked';
2448
+ manifestUrl: string;
2449
+ } | {
2450
+ type: 'inline';
2451
+ events: CTEvent[];
2452
+ duration: number;
2453
+ };
2454
+ interface RecordingInfo {
2455
+ duration: number;
2456
+ name?: string;
2457
+ hasVideo: boolean;
2458
+ }
2459
+ interface MediaSourceAdapter {
2460
+ /** Get the audio source configuration */
2461
+ getAudioSource(): Promise<AudioSourceResult>;
2462
+ /** Get the events source configuration */
2463
+ getEventsSource(): Promise<EventsSourceResult>;
2464
+ /** Get basic recording metadata */
2465
+ getRecordingInfo(): RecordingInfo;
2466
+ /** Get the video source configuration (null if no video) */
2467
+ getVideoSource(): Promise<VideoSourceResult>;
2468
+ /** Get the streaming manifest (only available for chunked sources) */
2469
+ getManifest(): StreamingManifest | null;
2470
+ /** Cleanup any resources (e.g., revoke blob URLs) */
2471
+ dispose(): void;
2289
2472
  }
2473
+
2290
2474
  /**
2291
- * Document data with content (extends DocumentInfo with raw data)
2475
+ * Engine-only types no React dependencies.
2476
+ *
2477
+ * @packageDocumentation
2292
2478
  */
2293
- interface PlayerDocumentData extends DocumentInfo {
2294
- /** Raw document data (base64 for PDFs, etc.) */
2295
- data: string;
2296
- }
2479
+
2297
2480
  /**
2298
- * Playback engine configuration
2481
+ * Configuration for creating a PlayerEngine instance.
2482
+ * No React types — this is framework-agnostic.
2299
2483
  */
2300
- interface PlaybackEngineConfig {
2301
- /** Recording ID to load */
2302
- recordingId?: string;
2303
- /** Backend adapter for loading */
2304
- backendAdapter?: BackendAdapter;
2305
- /** Pre-loaded recording */
2306
- recording?: InternalRecording;
2307
- /** CT file blob to load */
2308
- ctFile?: Blob;
2309
- /** Auto-play on load */
2484
+ interface PlayerEngineConfig {
2485
+ /** URL to the streaming manifest.json (used for HLS streaming) */
2486
+ manifestUrl?: string;
2487
+ /** Pluggable media source adapter (takes precedence over manifestUrl) */
2488
+ source?: MediaSourceAdapter;
2489
+ /** Unique content ID for resume position storage (defaults to manifestUrl hash) */
2490
+ contentId?: string;
2491
+ /** Auto-play when ready */
2310
2492
  autoPlay?: boolean;
2311
- /** Initial playback speed */
2312
- initialSpeed?: number;
2313
- /** Initial volume (0-1) */
2314
- initialVolume?: number;
2493
+ /** Initial playback speed (default: 1.0) */
2494
+ defaultSpeed?: number;
2495
+ /** Maximum chunks to keep in memory (default: 10) */
2496
+ maxCacheSize?: number;
2315
2497
  onReady?: () => void;
2316
2498
  onPlay?: () => void;
2317
2499
  onPause?: () => void;
2318
- onEnded?: () => void;
2319
- onTimeUpdate?: (time: number) => void;
2500
+ onProgress?: (timeMs: number, duration: number) => void;
2501
+ onComplete?: () => void;
2502
+ onBuffering?: (isBuffering: boolean) => void;
2320
2503
  onError?: (error: Error) => void;
2321
2504
  }
2322
2505
  /**
2323
- * Playback engine state
2506
+ * Immutable snapshot of the entire player state.
2507
+ * React subscribes to this via `onSnapshot()`.
2508
+ *
2509
+ * One object instead of 12 separate useState calls.
2510
+ * Pattern used by Zustand, Redux, XState, TanStack Query.
2324
2511
  */
2325
- interface PlaybackState {
2512
+ interface PlayerSnapshot {
2513
+ /** Current state machine state */
2514
+ machineState: PlayerState;
2515
+ /** Current playback state (code, terminal, whiteboard, etc.) */
2516
+ playbackState: PlaybackState$1;
2517
+ /** Current playback time in milliseconds */
2518
+ currentTime: number;
2519
+ /** Total duration in milliseconds */
2520
+ duration: number;
2521
+ /** Current playback speed */
2522
+ speed: number;
2523
+ /** Buffer health information */
2524
+ bufferHealth: RemoteBufferHealth;
2525
+ /** Current error (if any) */
2526
+ error: Error | null;
2527
+ /** The loaded manifest */
2528
+ manifest: StreamingManifest | null;
2529
+ /** Saved resume position in ms (null if none or past 95%) */
2530
+ resumePosition: number | null;
2531
+ /** Whether playback is active (derived from machineState) */
2532
+ isPlaying: boolean;
2533
+ /** Whether the player is ready (audio + events loaded, UI can render) */
2534
+ isReady: boolean;
2535
+ /** Whether ALL sources are loaded and synced (audio + video + events) */
2536
+ allSourcesReady: boolean;
2537
+ /** Whether currently buffering */
2538
+ isBuffering: boolean;
2539
+ }
2540
+
2541
+ type SnapshotListener = (snapshot: PlayerSnapshot) => void;
2542
+ declare class PlayerEngine {
2543
+ private machine;
2544
+ private clock;
2545
+ private scheduler;
2546
+ private media;
2547
+ private buffer;
2548
+ private config;
2549
+ private resumeKey;
2550
+ private sourceAdapter;
2551
+ private _snapshot;
2552
+ private _listeners;
2553
+ private _lastTimeEmitTs;
2554
+ private audioElement;
2555
+ private videoElement;
2556
+ private _destroyed;
2557
+ private audioReady;
2558
+ private eventsReady;
2559
+ private videoReady;
2560
+ private hasVideo;
2561
+ private pendingPlay;
2562
+ private seekGeneration;
2563
+ private lastBufferHealthCheckTs;
2564
+ private bufferingDebounceTimer;
2565
+ private videoListenersCleanup;
2566
+ private clockUnsub;
2567
+ private schedulerUnsub;
2568
+ private machineUnsub;
2569
+ private audioListenersCleanup;
2570
+ private tickActive;
2571
+ constructor(config: PlayerEngineConfig);
2572
+ /**
2573
+ * Load media and events via the configured source adapter.
2574
+ * Supports both HLS streaming (chunked) and local file (inline) sources.
2575
+ * Transitions: IDLE → LOADING → READY (or ERROR).
2576
+ */
2577
+ load(): Promise<void>;
2578
+ /**
2579
+ * Start playback. Gates on allSourcesReady.
2580
+ */
2581
+ play(): void;
2582
+ /**
2583
+ * Pause playback.
2584
+ */
2585
+ pause(): void;
2586
+ /**
2587
+ * Seek to a specific time in milliseconds.
2588
+ */
2589
+ seek(timeMs: number): Promise<void>;
2590
+ /**
2591
+ * Set playback speed.
2592
+ */
2593
+ setSpeed(speed: number): void;
2594
+ /**
2595
+ * Set volume level (0–2+, values > 1 amplify via Web Audio).
2596
+ */
2597
+ setVolume(level: number): void;
2598
+ /**
2599
+ * Set muted state (uses GainNode, not audio.muted).
2600
+ */
2601
+ setMuted(muted: boolean): void;
2602
+ /**
2603
+ * Initialize video for a given video element.
2604
+ * Supports both HLS (streaming) and direct URL (local file) sources.
2605
+ * Called from React when the <video> element mounts.
2606
+ */
2607
+ initVideo(element: HTMLVideoElement | null): Promise<void>;
2608
+ /**
2609
+ * Toggle webcam visibility — NEVER destroys HLS.
2610
+ */
2611
+ setVideoVisible(visible: boolean): void;
2612
+ getVideoQualityLevels(): HLSQualityLevel[];
2613
+ setVideoQualityLevel(index: number): void;
2614
+ getVideoQualityLevel(): number;
2615
+ getAudioElement(): HTMLAudioElement | null;
2616
+ getVideoElement(): HTMLVideoElement | null;
2617
+ /**
2618
+ * Get buffer/fetch metrics for diagnostics (polled by hook for metrics state).
2619
+ */
2620
+ getBufferMetrics(): {
2621
+ avgFetchTimeMs: number;
2622
+ cacheHitRate: number;
2623
+ peakCache: number;
2624
+ fetcherMetrics: ReturnType<ChunkFetcher["getMetrics"]>;
2625
+ };
2626
+ /**
2627
+ * Get the source adapter (for adapter-specific operations like LocalFileAdapter.getRecording()).
2628
+ */
2629
+ getSourceAdapter(): MediaSourceAdapter | null;
2630
+ /**
2631
+ * Get the current snapshot.
2632
+ */
2633
+ getSnapshot(): PlayerSnapshot;
2634
+ /**
2635
+ * Subscribe to snapshot changes. Returns an unsubscribe function.
2636
+ * Time-only updates are throttled to ~4/sec.
2637
+ * State transitions emit immediately.
2638
+ */
2639
+ onSnapshot(fn: SnapshotListener): () => void;
2640
+ resumeAudioContext(): void;
2641
+ destroy(): void;
2642
+ private handleTransition;
2643
+ private handleSchedulerEvent;
2644
+ private startTickLoop;
2645
+ private stopTickLoop;
2646
+ private wireAudioEvents;
2647
+ private wireVideoEvents;
2648
+ private cleanupVideoListeners;
2649
+ private checkAllSourcesReady;
2650
+ private syncVideoToClock;
2651
+ private rebuildStateAtTime;
2652
+ private waitForVideoSeek;
2653
+ private loadResumePosition;
2654
+ private saveResumePosition;
2655
+ private clearBufferingDebounce;
2656
+ /**
2657
+ * Update snapshot and notify listeners.
2658
+ * This is the ONLY way to mutate the snapshot.
2659
+ */
2660
+ private updateSnapshot;
2661
+ private emitSnapshot;
2662
+ }
2663
+
2664
+ /**
2665
+ * usePlayerEngine — Thin React binding for PlayerEngine
2666
+ *
2667
+ * 1 ref, 1 state, 1 effect, 1 memo. That's the entire React layer.
2668
+ * Replaces 65 refs, 120 effects from the old hooks.
2669
+ *
2670
+ * @packageDocumentation
2671
+ */
2672
+
2673
+ interface PlayerControls$1 {
2674
+ play(): void;
2675
+ pause(): void;
2676
+ seek(timeMs: number): Promise<void>;
2677
+ setSpeed(speed: number): void;
2678
+ setVolume(level: number): void;
2679
+ setMuted(muted: boolean): void;
2680
+ setVideoVisible(visible: boolean): void;
2681
+ initVideo(element: HTMLVideoElement | null): void;
2682
+ getAudioElement(): HTMLAudioElement | null;
2683
+ getVideoElement(): HTMLVideoElement | null;
2684
+ getVideoQualityLevels(): HLSQualityLevel[];
2685
+ setVideoQualityLevel(index: number): void;
2686
+ getVideoQualityLevel(): number;
2687
+ resumeAudioContext(): void;
2688
+ getSourceAdapter(): MediaSourceAdapter | null;
2689
+ }
2690
+ interface UsePlayerEngineResult {
2691
+ snapshot: PlayerSnapshot;
2692
+ controls: PlayerControls$1;
2693
+ }
2694
+ declare function usePlayerEngine(config: PlayerEngineConfig): UsePlayerEngineResult;
2695
+
2696
+ /**
2697
+ * ErrorBoundary — catches render errors in the player component tree.
2698
+ * Class component because React error boundaries require getDerivedStateFromError.
2699
+ */
2700
+
2701
+ interface ErrorBoundaryProps {
2702
+ /** Fallback UI to render on error */
2703
+ fallback: React__default__default.ReactNode | ((error: Error) => React__default__default.ReactNode);
2704
+ /** Optional error callback */
2705
+ onError?: (error: Error, errorInfo: React__default__default.ErrorInfo) => void;
2706
+ children: React__default__default.ReactNode;
2707
+ }
2708
+ interface ErrorBoundaryState {
2709
+ error: Error | null;
2710
+ }
2711
+ declare class PlayerErrorBoundary extends React__default__default.Component<ErrorBoundaryProps, ErrorBoundaryState> {
2712
+ state: ErrorBoundaryState;
2713
+ static getDerivedStateFromError(error: Error): ErrorBoundaryState;
2714
+ componentDidCatch(error: Error, info: React__default__default.ErrorInfo): void;
2715
+ render(): React__default__default.ReactNode;
2716
+ }
2717
+
2718
+ /**
2719
+ * HlsStreamingAdapter — MediaSourceAdapter for CDN/HLS streaming.
2720
+ *
2721
+ * Wraps a manifest URL and provides HLS audio, chunked events,
2722
+ * and (optionally) HLS video sources to the PlayerEngine.
2723
+ *
2724
+ * @packageDocumentation
2725
+ */
2726
+
2727
+ declare class HlsStreamingAdapter implements MediaSourceAdapter {
2728
+ private manifestUrl;
2729
+ private manifest;
2730
+ constructor(manifestUrl: string);
2731
+ /** Inject a pre-fetched manifest (avoids double fetch when engine already loaded it). */
2732
+ setManifest(manifest: StreamingManifest): void;
2733
+ private ensureManifest;
2734
+ getAudioSource(): Promise<AudioSourceResult>;
2735
+ getEventsSource(): Promise<EventsSourceResult>;
2736
+ getRecordingInfo(): RecordingInfo;
2737
+ getVideoSource(): Promise<VideoSourceResult>;
2738
+ getManifest(): StreamingManifest | null;
2739
+ dispose(): void;
2740
+ }
2741
+
2742
+ /**
2743
+ * LocalFileAdapter — MediaSourceAdapter for local .ct file playback.
2744
+ *
2745
+ * Wraps a CTRecording, exposing its mediaBlob as a blob-URL audio source
2746
+ * and its events as inline events (no chunk fetching needed).
2747
+ *
2748
+ * @packageDocumentation
2749
+ */
2750
+
2751
+ declare class LocalFileAdapter implements MediaSourceAdapter {
2752
+ private recording;
2753
+ private blobUrl;
2754
+ constructor(recording: CTRecording);
2755
+ getAudioSource(): Promise<AudioSourceResult>;
2756
+ getEventsSource(): Promise<EventsSourceResult>;
2757
+ getRecordingInfo(): RecordingInfo;
2758
+ getVideoSource(): Promise<VideoSourceResult>;
2759
+ getManifest(): StreamingManifest | null;
2760
+ /** Get the underlying recording for interactive mode features. */
2761
+ getRecording(): CTRecording;
2762
+ dispose(): void;
2763
+ }
2764
+
2765
+ /**
2766
+ * Learner Branch Types
2767
+ *
2768
+ * A branch is a saved snapshot of the learner's code edits at a specific
2769
+ * moment in the instructor's timeline. Branches are learner-side only —
2770
+ * they never modify the .ct file, events, or manifest.
2771
+ *
2772
+ * @packageDocumentation
2773
+ */
2774
+ /**
2775
+ * A point-in-time capture of the IDE state.
2776
+ * Used for both the instructor's original state and the learner's edited state.
2777
+ */
2778
+ interface BranchSnapshot {
2779
+ /** Active file's code content */
2780
+ code: string;
2781
+ /** Programming language at snapshot time */
2782
+ language: string;
2783
+ /** Which file tab was active (null if single-file mode) */
2784
+ currentFile: string | null;
2785
+ /** Full file system snapshot — path → content */
2786
+ fileSystem: Record<string, string>;
2787
+ /** Terminal output lines if any existed */
2788
+ terminalOutput?: string[];
2789
+ /** Which tool panel was open (code / terminal / whiteboard / document) */
2790
+ activeTool: string;
2791
+ /** Folder paths created by the learner during interactive mode */
2792
+ folders?: string[];
2793
+ /** Base64 snapshot of the sql.js database (learner's modified DB) */
2794
+ dbSnapshot?: string;
2795
+ /** The seed SQL used to initialize the database */
2796
+ dbSeedSql?: string;
2797
+ /** Current database schema at snapshot time */
2798
+ dbSchema?: {
2799
+ name: string;
2800
+ sql: string;
2801
+ }[];
2802
+ }
2803
+ /**
2804
+ * A learner branch — a saved code snapshot at a point in the timeline.
2805
+ */
2806
+ interface LearnerBranch {
2807
+ /** Unique branch ID (crypto.randomUUID) */
2808
+ id: string;
2809
+ /** Lesson/recording this branch belongs to */
2810
+ lessonId: string;
2811
+ /** Playback time in milliseconds where the learner paused */
2812
+ timeMs: number;
2813
+ /** Instructor's state at that moment (for comparison / restoring) */
2814
+ originalState: BranchSnapshot;
2815
+ /** Learner's edited state */
2816
+ learnerState: BranchSnapshot;
2817
+ /** ISO timestamp of when the branch was created */
2818
+ createdAt: string;
2819
+ /** User-provided label, defaults to "Branch at MM:SS" */
2820
+ label?: string;
2821
+ }
2822
+
2823
+ /**
2824
+ * @ct-courses/player - Type Definitions
2825
+ */
2826
+
2827
+ /**
2828
+ * Backend adapter interface for loading recordings
2829
+ */
2830
+ interface BackendAdapter {
2831
+ getRecording(id: string): Promise<InternalRecording | null>;
2832
+ getMediaUrl?(recordingId: string): Promise<string | null>;
2833
+ }
2834
+ /**
2835
+ * Internal recording structure (from backend)
2836
+ */
2837
+ interface InternalRecording {
2838
+ id: string;
2839
+ name?: string;
2840
+ duration: number;
2841
+ events: InternalEvent[];
2842
+ markers?: RecordingMarker[];
2843
+ hasMedia?: boolean;
2844
+ mediaUrl?: string;
2845
+ createdAt?: number;
2846
+ }
2847
+ /**
2848
+ * Internal event structure (from backend)
2849
+ */
2850
+ interface InternalEvent {
2851
+ type: string;
2852
+ time: number;
2853
+ data: Record<string, unknown>;
2854
+ }
2855
+ /**
2856
+ * Recording marker for chapters
2857
+ */
2858
+ interface RecordingMarker {
2859
+ id: string;
2860
+ time: number;
2861
+ label: string;
2862
+ color?: string;
2863
+ }
2864
+ /**
2865
+ * Cursor position for replay
2866
+ */
2867
+ interface CursorPosition {
2868
+ x: number;
2869
+ y: number;
2870
+ visible: boolean;
2871
+ }
2872
+ /**
2873
+ * File system snapshot
2874
+ */
2875
+ interface FileSystemSnapshot {
2876
+ [path: string]: string;
2877
+ }
2878
+ /**
2879
+ * Document data with content (extends DocumentInfo with raw data)
2880
+ */
2881
+ interface PlayerDocumentData extends DocumentInfo {
2882
+ /** Raw document data (base64 for PDFs, etc.) */
2883
+ data: string;
2884
+ }
2885
+ /**
2886
+ * Playback engine configuration
2887
+ */
2888
+ interface PlaybackEngineConfig {
2889
+ /** Recording ID to load */
2890
+ recordingId?: string;
2891
+ /** Backend adapter for loading */
2892
+ backendAdapter?: BackendAdapter;
2893
+ /** Pre-loaded recording */
2894
+ recording?: InternalRecording;
2895
+ /** CT file blob to load */
2896
+ ctFile?: Blob;
2897
+ /** Auto-play on load */
2898
+ autoPlay?: boolean;
2899
+ /** Initial playback speed */
2900
+ initialSpeed?: number;
2901
+ /** Initial volume (0-1) */
2902
+ initialVolume?: number;
2903
+ onReady?: () => void;
2904
+ onPlay?: () => void;
2905
+ onPause?: () => void;
2906
+ onEnded?: () => void;
2907
+ onTimeUpdate?: (time: number) => void;
2908
+ onError?: (error: Error) => void;
2909
+ }
2910
+ /**
2911
+ * Playback engine state
2912
+ */
2913
+ interface PlaybackState {
2326
2914
  loading: boolean;
2327
2915
  error: string | null;
2328
2916
  recording: InternalRecording | null;
@@ -2353,7 +2941,7 @@ interface PlaybackState {
2353
2941
  /**
2354
2942
  * Playback controls
2355
2943
  */
2356
- interface PlaybackControls$1 {
2944
+ interface PlaybackControls {
2357
2945
  setAudioElement: (element: HTMLAudioElement | null) => void;
2358
2946
  setVolume: (volume: number) => void;
2359
2947
  setMuted: (muted: boolean) => void;
@@ -2380,7 +2968,7 @@ interface PlaybackControls$1 {
2380
2968
  /**
2381
2969
  * Full playback engine result
2382
2970
  */
2383
- type UsePlaybackEngineResult = PlaybackState & PlaybackControls$1;
2971
+ type UsePlaybackEngineResult = PlaybackState & PlaybackControls;
2384
2972
  /**
2385
2973
  * Player component props
2386
2974
  */
@@ -2521,93 +3109,53 @@ interface MCQResult {
2521
3109
  correctOptionIds?: string[];
2522
3110
  }
2523
3111
 
2524
- /**
2525
- * Active tool type - matches ToolType from @ct-courses/ui
2526
- */
2527
- type ActiveTool$1 = ToolType;
3112
+ type ActiveTool$2 = ToolType;
2528
3113
  /**
2529
3114
  * Props for the CoursePlayer component
2530
3115
  */
2531
3116
  interface CoursePlayerProps {
2532
- /** Pre-loaded recording data */
2533
3117
  recording?: CTRecording;
2534
- /** URL to a .ct file */
2535
3118
  recordingUrl?: string;
2536
- /** Recording ID to fetch from API */
2537
3119
  recordingId?: string;
2538
- /** Base API URL for fetching recordings */
2539
3120
  apiUrl?: string;
2540
- /** Authentication token */
2541
3121
  authToken?: string;
2542
- /** Go-Judge server URL */
2543
3122
  goJudgeUrl?: string;
2544
- /** Go-Judge API key */
2545
3123
  goJudgeApiKey?: string;
2546
- /** Theme setting */
2547
3124
  theme?: "light" | "dark" | "system";
2548
- /** Aspect ratio of the player */
2549
3125
  aspectRatio?: "16:9" | "4:3" | "auto";
2550
- /** Show playback controls toolbar */
2551
3126
  showToolbar?: boolean;
2552
- /** Show timeline below player */
2553
3127
  showTimeline?: boolean;
2554
- /** Show chapter markers */
2555
3128
  showChapters?: boolean;
2556
- /** Show tool sidebar */
2557
3129
  showToolSidebar?: boolean;
2558
- /** Auto-play when loaded */
2559
3130
  autoPlay?: boolean;
2560
- /** Default playback speed */
2561
3131
  defaultSpeed?: number;
2562
- /** Allow fullscreen mode */
2563
3132
  allowFullscreen?: boolean;
2564
- /** Default active tool */
2565
- defaultTool?: ActiveTool$1;
2566
- /** Called when player is ready */
3133
+ defaultTool?: ActiveTool$2;
2567
3134
  onReady?: () => void;
2568
- /** Called when playback starts */
2569
3135
  onPlay?: () => void;
2570
- /** Called when playback pauses */
2571
3136
  onPause?: () => void;
2572
- /** Called on progress update */
2573
3137
  onProgress?: (time: number, duration: number) => void;
2574
- /** Called when playback completes */
2575
3138
  onComplete?: () => void;
2576
- /** Called on error */
2577
3139
  onError?: (error: Error) => void;
2578
- /** Called when entering interactive mode */
2579
3140
  onInteractionStart?: () => void;
2580
- /** Called when exiting interactive mode */
2581
3141
  onInteractionEnd?: (code: string) => void;
2582
- /** Called when tool changes */
2583
3142
  onToolChange?: (tool: string) => void;
2584
- /** Interaction points (MCQs) to display during playback */
2585
3143
  interactionPoints?: InteractionPoint[];
2586
- /** Pre-answered point IDs (skip these MCQs) */
2587
3144
  answeredPointIds?: Set<string>;
2588
- /** Called when a learner submits an MCQ answer. Host app validates server-side. */
2589
3145
  onSubmitAnswer?: (pointId: string, selectedOptionIds: string[]) => Promise<MCQResult>;
2590
- /** Info about the next lesson for auto-advance overlay */
2591
3146
  nextLesson?: {
2592
3147
  id: string;
2593
3148
  title: string;
2594
3149
  } | null;
2595
- /** Called when the player wants to navigate to the next lesson */
2596
3150
  onNavigateToLesson?: (lessonId: string) => void;
2597
- /** Called when the current lesson completes (before transition) */
2598
3151
  onLessonComplete?: () => void;
2599
- /** Lesson ID used to scope saved branches in localStorage */
2600
3152
  lessonId?: string;
2601
- /** Enable the branch save feature (default: true) */
2602
3153
  enableBranches?: boolean;
2603
- /** Called after a branch is saved */
2604
3154
  onBranchSave?: (branch: LearnerBranch) => void;
2605
- /** Called when a learner restores a saved branch */
2606
3155
  onBranchRestore?: (branch: LearnerBranch) => void;
2607
- /** Called after a branch is deleted */
2608
3156
  onBranchDelete?: (branchId: string) => void;
2609
- /** Maximum branches per lesson (default: 50) */
2610
3157
  maxBranches?: number;
3158
+ sqlJsWasmUrl?: string;
2611
3159
  }
2612
3160
  /**
2613
3161
  * Imperative handle for CoursePlayer
@@ -2622,81 +3170,58 @@ interface CoursePlayerRef {
2622
3170
  getState(): PlaybackState$1;
2623
3171
  enterInteractiveMode(): void;
2624
3172
  exitInteractiveMode(): void;
2625
- setActiveTool(tool: ActiveTool$1): void;
3173
+ setActiveTool(tool: ActiveTool$2): void;
2626
3174
  }
2627
- /**
2628
- * CoursePlayer component
2629
- */
2630
3175
  declare const CoursePlayer: React__default.ForwardRefExoticComponent<CoursePlayerProps & React__default.RefAttributes<CoursePlayerRef>>;
2631
3176
 
2632
- type ActiveTool = ToolType;
3177
+ type ActiveTool$1 = ToolType;
2633
3178
  /**
2634
3179
  * Props for StreamingCoursePlayer
2635
3180
  */
2636
3181
  interface StreamingCoursePlayerProps {
2637
3182
  /** URL to the streaming manifest.json */
2638
3183
  manifestUrl: string;
2639
- /** Theme setting */
2640
3184
  theme?: "light" | "dark" | "system";
2641
- /** Aspect ratio of the player */
2642
3185
  aspectRatio?: "16:9" | "4:3" | "auto";
2643
- /** Show playback controls toolbar */
2644
3186
  showToolbar?: boolean;
2645
- /** Show tool sidebar */
2646
3187
  showToolSidebar?: boolean;
2647
- /** Auto-play when loaded */
2648
3188
  autoPlay?: boolean;
2649
- /** Default playback speed */
2650
3189
  defaultSpeed?: number;
2651
- /** Allow fullscreen mode */
2652
3190
  allowFullscreen?: boolean;
2653
- /** Default active tool */
2654
- defaultTool?: ActiveTool;
2655
- /** Maximum chunks to cache */
3191
+ defaultTool?: ActiveTool$1;
2656
3192
  maxCacheSize?: number;
2657
- /** Additional CSS class */
3193
+ showWebcam?: boolean;
2658
3194
  className?: string;
2659
- /** Called when player is ready */
2660
3195
  onReady?: () => void;
2661
- /** Called when playback starts */
2662
3196
  onPlay?: () => void;
2663
- /** Called when playback pauses */
2664
3197
  onPause?: () => void;
2665
- /** Called on progress update */
2666
3198
  onProgress?: (time: number, duration: number) => void;
2667
- /** Called when playback completes */
2668
3199
  onComplete?: () => void;
2669
- /** Called on error */
2670
3200
  onError?: (error: Error) => void;
2671
- /** Called when buffering state changes */
2672
3201
  onBuffering?: (isBuffering: boolean) => void;
2673
- /** Called when tool changes */
2674
3202
  onToolChange?: (tool: string) => void;
2675
- /** Go-Judge server URL */
2676
3203
  goJudgeUrl?: string;
2677
- /** Go-Judge API key */
2678
3204
  goJudgeApiKey?: string;
2679
- /** Show chapter markers */
2680
3205
  showChapters?: boolean;
2681
- /** Called when entering interactive mode */
3206
+ showTimeline?: boolean;
2682
3207
  onInteractionStart?: () => void;
2683
- /** Called when exiting interactive mode */
2684
3208
  onInteractionEnd?: (code: string) => void;
2685
- /** Interaction points (MCQs) to display during playback */
2686
3209
  interactionPoints?: InteractionPoint[];
2687
- /** Pre-answered point IDs (skip these MCQs) */
2688
3210
  answeredPointIds?: Set<string>;
2689
- /** Called when a learner submits an MCQ answer. Host app validates server-side. */
2690
3211
  onSubmitAnswer?: (pointId: string, selectedOptionIds: string[]) => Promise<MCQResult>;
2691
- /** Info about the next lesson for auto-advance overlay */
2692
3212
  nextLesson?: {
2693
3213
  id: string;
2694
3214
  title: string;
2695
3215
  } | null;
2696
- /** Called when the player wants to navigate to the next lesson */
2697
3216
  onNavigateToLesson?: (lessonId: string) => void;
2698
- /** Called when the current lesson completes (before transition) */
2699
3217
  onLessonComplete?: () => void;
3218
+ lessonId?: string;
3219
+ enableBranches?: boolean;
3220
+ onBranchSave?: (branch: LearnerBranch) => void;
3221
+ onBranchRestore?: (branch: LearnerBranch) => void;
3222
+ onBranchDelete?: (branchId: string) => void;
3223
+ maxBranches?: number;
3224
+ sqlJsWasmUrl?: string;
2700
3225
  }
2701
3226
  /**
2702
3227
  * Imperative handle for StreamingCoursePlayer
@@ -2711,398 +3236,72 @@ interface StreamingCoursePlayerRef {
2711
3236
  getState(): PlaybackState$1;
2712
3237
  enterInteractiveMode(): void;
2713
3238
  exitInteractiveMode(): void;
2714
- setActiveTool(tool: ActiveTool): void;
3239
+ setActiveTool(tool: ActiveTool$1): void;
2715
3240
  getManifest(): StreamingManifest | null;
2716
3241
  getBufferHealth(): RemoteBufferHealth;
2717
3242
  }
2718
3243
  declare const StreamingCoursePlayer: React__default.ForwardRefExoticComponent<StreamingCoursePlayerProps & React__default.RefAttributes<StreamingCoursePlayerRef>>;
2719
3244
 
2720
- /**
2721
- * usePlayback Hook
2722
- *
2723
- * Core playback engine hook using @ct-courses/core StateEngine.
2724
- * Supports audio synchronization as master clock (no video/webcam).
2725
- *
2726
- * CRITICAL SYNC ARCHITECTURE:
2727
- * - AUDIO is the MASTER CLOCK when media exists
2728
- * - Events are applied based on audio.currentTime
2729
- * - Without media, use requestAnimationFrame timing
2730
- */
2731
-
2732
- /**
2733
- * Playback state returned by the hook
2734
- */
2735
- interface PlaybackHookState {
2736
- /** Current playback state */
2737
- state: PlaybackState$1;
2738
- /** Current time in milliseconds */
2739
- currentTime: number;
2740
- /** Total duration in milliseconds */
2741
- duration: number;
2742
- /** Whether currently playing */
2743
- isPlaying: boolean;
2744
- /** Current playback speed */
2745
- speed: number;
2746
- /** Whether recording is loaded */
2747
- isLoaded: boolean;
2748
- /** Volume level (0-1) */
2749
- volume: number;
2750
- /** Whether audio is muted */
2751
- muted: boolean;
2752
- /** Whether user is interacting (paused with edits) */
2753
- isInteracting: boolean;
2754
- }
2755
- /**
2756
- * Playback controls
2757
- */
2758
- interface PlaybackControls {
2759
- play(): void;
2760
- pause(): void;
2761
- seek(timeMs: number): void;
2762
- setSpeed(speed: number): void;
2763
- setVolume(volume: number): void;
2764
- setMuted(muted: boolean): void;
2765
- setAudioElement(audio: HTMLAudioElement | null): void;
2766
- setVideoElement(video: HTMLVideoElement | null): void;
2767
- enterInteractiveMode(): void;
2768
- exitInteractiveMode(): void;
2769
- setInteractiveCode(code: string): void;
2770
- setInteractiveLanguage(language: string): void;
2771
- setInteractiveTerminalLines(lines: PlaybackState$1['terminalLines']): void;
2772
- addInteractiveTerminalLines(lines: PlaybackState$1['terminalLines']): void;
2773
- setInteractiveExcalidrawScene(scene: PlaybackState$1['excalidrawScene']): void;
2774
- setInteractiveDocumentPage(page: number): void;
2775
- setInteractiveDocumentZoom(zoom: number): void;
2776
- setInteractiveDocumentScroll(scroll: {
2777
- x: number;
2778
- y: number;
2779
- }): void;
2780
- /** Inject a full branch snapshot into the interactive state. Only works when isInteracting is true. */
2781
- injectInteractiveState(snapshot: BranchSnapshot): void;
2782
- /** Get the engine's (instructor's) state at a given time, without affecting playback position. */
2783
- getEngineStateAtTime(timeMs: number): PlaybackState$1 | null;
2784
- }
2785
- /**
2786
- * Options for usePlayback hook
2787
- */
2788
- interface UsePlaybackOptions {
2789
- recording: CTRecording | null;
3245
+ interface SharedProps {
3246
+ theme?: "light" | "dark" | "system";
3247
+ aspectRatio?: "16:9" | "4:3" | "auto";
3248
+ showToolbar?: boolean;
3249
+ showTimeline?: boolean;
3250
+ showChapters?: boolean;
3251
+ showToolSidebar?: boolean;
3252
+ className?: string;
2790
3253
  autoPlay?: boolean;
2791
3254
  defaultSpeed?: number;
2792
- defaultVolume?: number;
3255
+ allowFullscreen?: boolean;
3256
+ defaultTool?: ToolType;
3257
+ onReady?: () => void;
2793
3258
  onPlay?: () => void;
2794
3259
  onPause?: () => void;
2795
3260
  onProgress?: (time: number, duration: number) => void;
2796
3261
  onComplete?: () => void;
2797
3262
  onError?: (error: Error) => void;
2798
- }
2799
- /**
2800
- * Hook for managing playback of a CT recording
2801
- * Supports audio synchronization as master clock
2802
- */
2803
- declare function usePlayback(options: UsePlaybackOptions): PlaybackHookState & {
2804
- controls: PlaybackControls;
2805
- };
2806
-
2807
- /**
2808
- * useStreamingPlayback Hook
2809
- *
2810
- * Streaming playback engine hook for large recordings.
2811
- * Supports lazy chunk loading, adaptive buffering, and efficient seeking.
2812
- *
2813
- * @packageDocumentation
2814
- */
2815
-
2816
- /**
2817
- * Buffer health status
2818
- */
2819
- interface BufferHealth {
2820
- status: 'healthy' | 'buffering' | 'critical' | 'empty';
2821
- loadedChunks: number;
2822
- totalChunks: number;
2823
- currentChunk: number;
2824
- bufferedTimeMs: number;
2825
- percentLoaded: number;
2826
- }
2827
- /**
2828
- * Streaming metrics for performance monitoring
2829
- */
2830
- interface StreamingMetrics {
2831
- avgChunkLoadTimeMs: number;
2832
- chunkCacheHitRate: number;
2833
- bufferUnderrunCount: number;
2834
- avgBufferHealthPercent: number;
2835
- avgSeekTimeMs: number;
2836
- seekCacheHitRate: number;
2837
- peakChunksCached: number;
2838
- totalBytesLoaded: number;
2839
- }
2840
- /**
2841
- * Streaming playback state
2842
- */
2843
- interface StreamingPlaybackState {
2844
- state: PlaybackState$1;
2845
- currentTime: number;
2846
- duration: number;
2847
- isPlaying: boolean;
2848
- speed: number;
2849
- isLoaded: boolean;
2850
- isBuffering: boolean;
2851
- bufferHealth: BufferHealth;
2852
- isStreaming: boolean;
2853
- metrics: StreamingMetrics;
2854
- }
2855
- /**
2856
- * Streaming playback controls
2857
- */
2858
- interface StreamingPlaybackControls {
2859
- play(): void;
2860
- pause(): void;
2861
- seek(timeMs: number): Promise<void>;
2862
- setSpeed(speed: number): void;
2863
- preloadRange(startMs: number, endMs: number): Promise<void>;
2864
- }
2865
- /**
2866
- * Options for useStreamingPlayback hook
2867
- */
2868
- interface UseStreamingPlaybackOptions {
2869
- recording: CTRecording | null;
2870
- chunkIndex?: CTEventChunkIndex | null;
2871
- loadChunk?: (chunkNumber: number) => Promise<ChunkLoadResult>;
2872
- autoPlay?: boolean;
2873
- defaultSpeed?: number;
2874
- bufferAhead?: number;
2875
- bufferBehind?: number;
2876
- maxChunksInMemory?: number;
2877
- onPlay?: () => void;
2878
- onPause?: () => void;
2879
- onProgress?: (time: number, duration: number) => void;
2880
- onComplete?: () => void;
2881
3263
  onBuffering?: (isBuffering: boolean) => void;
2882
- onChunkLoad?: (chunkNumber: number) => void;
2883
- onError?: (error: Error) => void;
2884
- }
2885
- /**
2886
- * Hook for streaming playback of large CT recordings
2887
- */
2888
- declare function useStreamingPlayback(options: UseStreamingPlaybackOptions): StreamingPlaybackState & {
2889
- controls: StreamingPlaybackControls;
2890
- };
2891
-
2892
- /**
2893
- * useRemoteStreamingPlayback Hook
2894
- *
2895
- * React hook for streaming playback from a remote CDN/R2 storage.
2896
- * Handles HLS audio streaming and lazy-loading of event chunks.
2897
- *
2898
- * This is designed for the streaming service architecture where:
2899
- * - Audio is served via HLS adaptive bitrate streaming
2900
- * - Events are stored in gzip-compressed chunks on CDN
2901
- * - The manifest provides URLs for all resources
2902
- *
2903
- * @packageDocumentation
2904
- */
2905
-
2906
- /**
2907
- * Hook for remote streaming playback from CDN
2908
- */
2909
- declare function useRemoteStreamingPlayback(options: UseRemoteStreamingPlaybackOptions): RemoteStreamingPlaybackState & {
2910
- controls: RemoteStreamingPlaybackControls;
2911
- };
2912
-
2913
- /**
2914
- * useInteractiveMode Hook
2915
- *
2916
- * Manages interactive mode where users can edit code while paused.
2917
- */
2918
-
2919
- /**
2920
- * Interactive mode state
2921
- */
2922
- interface InteractiveModeState {
2923
- /** Whether interactive mode is active */
2924
- isActive: boolean;
2925
- /** Modified code (user edits) */
2926
- modifiedCode: string;
2927
- /** Whether code has been modified from original */
2928
- hasChanges: boolean;
2929
- }
2930
- /**
2931
- * Interactive mode controls
2932
- */
2933
- interface InteractiveModeControls {
2934
- /** Enter interactive mode */
2935
- enter(): void;
2936
- /** Exit interactive mode */
2937
- exit(): void;
2938
- /** Update the modified code */
2939
- updateCode(code: string): void;
2940
- /** Reset code to original */
2941
- resetCode(): void;
2942
- /** Run the modified code */
2943
- runCode(): Promise<string>;
2944
- }
2945
- /**
2946
- * Options for useInteractiveMode hook
2947
- */
2948
- interface UseInteractiveModeOptions {
2949
- /** Current playback state */
2950
- playbackState: PlaybackState$1;
2951
- /** Callback when entering interactive mode */
2952
- onEnter?: () => void;
2953
- /** Callback when exiting interactive mode */
2954
- onExit?: (code: string, hasChanges: boolean) => void;
2955
- /** Code execution function */
2956
- executeCode?: (code: string, language: string) => Promise<string>;
2957
- }
2958
- /**
2959
- * Hook for managing interactive mode
2960
- */
2961
- declare function useInteractiveMode(options: UseInteractiveModeOptions): InteractiveModeState & {
2962
- controls: InteractiveModeControls;
2963
- };
2964
-
2965
- /**
2966
- * useAudioSync Hook
2967
- *
2968
- * Synchronizes audio playback with event state.
2969
- * Handles audio/event drift and maintains sync within tolerance.
2970
- *
2971
- * @packageDocumentation
2972
- */
2973
- /**
2974
- * Audio sync state
2975
- */
2976
- interface AudioSyncState {
2977
- /** Current audio time in ms */
2978
- audioTime: number;
2979
- /** Current event time in ms */
2980
- eventTime: number;
2981
- /** Drift between audio and events (ms) */
2982
- drift: number;
2983
- /** Is sync healthy (within tolerance) */
2984
- isSynced: boolean;
2985
- /** Is audio loaded */
2986
- isAudioReady: boolean;
2987
- }
2988
- /**
2989
- * Audio sync controls
2990
- */
2991
- interface AudioSyncControls {
2992
- /** Sync events to audio time */
2993
- syncToAudio(): void;
2994
- /** Sync audio to event time */
2995
- syncAudioTo(timeMs: number): void;
2996
- /** Force resync */
2997
- forceResync(): void;
2998
- }
2999
- /**
3000
- * Options for useAudioSync hook
3001
- */
3002
- interface UseAudioSyncOptions {
3003
- /** Reference to audio element */
3004
- audioRef: React.RefObject<HTMLAudioElement>;
3005
- /** Current event time */
3006
- eventTime: number;
3007
- /** Is currently playing */
3008
- isPlaying: boolean;
3009
- /** Playback speed */
3010
- speed: number;
3011
- /** Maximum allowed drift in ms before resync (default: 500ms) */
3012
- driftTolerance?: number;
3013
- /** Callback when audio time updates */
3014
- onAudioTimeUpdate?: (timeMs: number) => void;
3015
- /** Callback when sync is lost */
3016
- onSyncLost?: (drift: number) => void;
3017
- /** Callback when sync is restored */
3018
- onSyncRestored?: () => void;
3019
- }
3020
- /**
3021
- * Hook for synchronizing audio playback with event state
3022
- */
3023
- declare function useAudioSync(options: UseAudioSyncOptions): AudioSyncState & {
3024
- controls: AudioSyncControls;
3025
- };
3026
- /**
3027
- * Hook for managing audio element lifecycle
3028
- */
3029
- declare function useAudioElement(src: string | null): {
3030
- audioRef: React__default.MutableRefObject<HTMLAudioElement | null>;
3031
- isLoaded: boolean;
3032
- error: Error | null;
3033
- duration: number;
3034
- };
3035
-
3036
- /**
3037
- * useVideoSync Hook
3038
- *
3039
- * Synchronizes video playback with event state.
3040
- * Handles video/event drift and maintains sync within tolerance.
3041
- *
3042
- * @packageDocumentation
3043
- */
3044
- /**
3045
- * Video sync state
3046
- */
3047
- interface VideoSyncState {
3048
- /** Current video time in ms */
3049
- videoTime: number;
3050
- /** Current event time in ms */
3051
- eventTime: number;
3052
- /** Drift between video and events (ms) */
3053
- drift: number;
3054
- /** Is sync healthy (within tolerance) */
3055
- isSynced: boolean;
3056
- /** Is video loaded */
3057
- isVideoReady: boolean;
3058
- }
3059
- /**
3060
- * Video sync controls
3061
- */
3062
- interface VideoSyncControls {
3063
- /** Sync events to video time */
3064
- syncToVideo(): void;
3065
- /** Sync video to event time */
3066
- syncVideoTo(timeMs: number): void;
3067
- /** Force resync */
3068
- forceResync(): void;
3264
+ onToolChange?: (tool: string) => void;
3265
+ onInteractionStart?: () => void;
3266
+ onInteractionEnd?: (code: string) => void;
3267
+ goJudgeUrl?: string;
3268
+ goJudgeApiKey?: string;
3269
+ interactionPoints?: InteractionPoint[];
3270
+ answeredPointIds?: Set<string>;
3271
+ onSubmitAnswer?: (pointId: string, selectedOptionIds: string[]) => Promise<MCQResult>;
3272
+ nextLesson?: {
3273
+ id: string;
3274
+ title: string;
3275
+ } | null;
3276
+ onNavigateToLesson?: (lessonId: string) => void;
3277
+ onLessonComplete?: () => void;
3278
+ lessonId?: string;
3279
+ enableBranches?: boolean;
3280
+ onBranchSave?: (branch: LearnerBranch) => void;
3281
+ onBranchRestore?: (branch: LearnerBranch) => void;
3282
+ onBranchDelete?: (branchId: string) => void;
3283
+ maxBranches?: number;
3284
+ sqlJsWasmUrl?: string;
3069
3285
  }
3070
- /**
3071
- * Options for useVideoSync hook
3072
- */
3073
- interface UseVideoSyncOptions {
3074
- /** Reference to video element */
3075
- videoRef: React.RefObject<HTMLVideoElement>;
3076
- /** Current event time */
3077
- eventTime: number;
3078
- /** Is currently playing */
3079
- isPlaying: boolean;
3080
- /** Playback speed */
3081
- speed: number;
3082
- /** Maximum allowed drift in ms before resync (default: 500ms) */
3083
- driftTolerance?: number;
3084
- /** Callback when video time updates */
3085
- onVideoTimeUpdate?: (timeMs: number) => void;
3086
- /** Callback when sync is lost */
3087
- onSyncLost?: (drift: number) => void;
3088
- /** Callback when sync is restored */
3089
- onSyncRestored?: () => void;
3286
+ interface CTPlayerProps extends SharedProps {
3287
+ /**
3288
+ * Content source URL.
3289
+ * - Ending in `.json` or containing `/manifest` → HLS streaming mode
3290
+ * - Ending in `.ct` or any other URL → local file mode
3291
+ * - Omit if passing `recording` directly
3292
+ */
3293
+ src?: string;
3294
+ /** Pre-loaded CTRecording object (skips fetch). */
3295
+ recording?: CTRecording;
3296
+ maxCacheSize?: number;
3297
+ showWebcam?: boolean;
3298
+ apiUrl?: string;
3299
+ authToken?: string;
3300
+ recordingId?: string;
3090
3301
  }
3091
- /**
3092
- * Hook for synchronizing video playback with event state
3093
- */
3094
- declare function useVideoSync(options: UseVideoSyncOptions): VideoSyncState & {
3095
- controls: VideoSyncControls;
3096
- };
3097
- /**
3098
- * Hook for managing video element lifecycle
3099
- */
3100
- declare function useVideoElement(src: string | null): {
3101
- videoRef: React__default.MutableRefObject<HTMLVideoElement | null>;
3102
- isLoaded: boolean;
3103
- error: Error | null;
3104
- duration: number;
3105
- };
3302
+ /** Union ref — exposes whichever component's ref is active. */
3303
+ type CTPlayerRef = CoursePlayerRef | StreamingCoursePlayerRef;
3304
+ declare const CTPlayer: React__default.ForwardRefExoticComponent<CTPlayerProps & React__default.RefAttributes<CTPlayerRef>>;
3106
3305
 
3107
3306
  /**
3108
3307
  * useInteractionPoints — monitors playback time and triggers MCQ pauses.
@@ -3398,11 +3597,15 @@ interface PlayerControlsProps {
3398
3597
  onBranchPanelToggle?: () => void;
3399
3598
  /** Whether the learner is in interactive/editing mode */
3400
3599
  isInteractive?: boolean;
3600
+ /** Whether webcam overlay is visible */
3601
+ webcamVisible?: boolean;
3602
+ /** Toggle webcam visibility */
3603
+ onWebcamToggle?: () => void;
3401
3604
  showMarkers?: boolean;
3402
3605
  compact?: boolean;
3403
3606
  containerRef?: React__default__default.RefObject<HTMLElement>;
3404
3607
  }
3405
- declare function PlayerControls({ isPlaying, currentTime, duration, volume, muted, playbackSpeed, markers, isFullscreen, videoQualityLevels, videoQualityLevel, onVideoQualityChange, onPlayPause, onSeek, onSeekStart, onSeekEnd, onSkip, onRestart, onVolumeChange, onMuteToggle, onSpeedChange, onFullscreenToggle, onEditorInteraction, interactionPoints, branches, onBranchClick, branchCount, isBranchPanelOpen, onBranchPanelToggle, isInteractive, showMarkers, compact, }: PlayerControlsProps): react_jsx_runtime.JSX.Element;
3608
+ declare function PlayerControls({ isPlaying, currentTime, duration, volume, muted, playbackSpeed, markers, isFullscreen, videoQualityLevels, videoQualityLevel, onVideoQualityChange, onPlayPause, onSeek, onSeekStart, onSeekEnd, onSkip, onRestart, onVolumeChange, onMuteToggle, onSpeedChange, onFullscreenToggle, onEditorInteraction, interactionPoints, branches, onBranchClick, branchCount, isBranchPanelOpen, onBranchPanelToggle, isInteractive, webcamVisible, onWebcamToggle, showMarkers, compact, }: PlayerControlsProps): react_jsx_runtime.JSX.Element;
3406
3609
 
3407
3610
  /**
3408
3611
  * Timeline Component
@@ -3681,9 +3884,6 @@ interface CustomSize {
3681
3884
  }
3682
3885
  interface WebcamOverlayProps {
3683
3886
  src: string | null;
3684
- currentTime: number;
3685
- isPlaying: boolean;
3686
- playbackRate?: number;
3687
3887
  position?: OverlayPosition;
3688
3888
  customPosition?: CustomPosition;
3689
3889
  size?: OverlaySize;
@@ -3698,9 +3898,6 @@ interface WebcamOverlayProps {
3698
3898
  onSizeChange?: (size: CustomSize) => void;
3699
3899
  onVisibilityToggle?: () => void;
3700
3900
  onVideoRef?: (video: HTMLVideoElement | null) => void;
3701
- muted?: boolean;
3702
- volume?: number;
3703
- syncTolerance?: number;
3704
3901
  /** Whether webcam is in maximized (full overlay) mode - from playback events */
3705
3902
  maximized?: boolean;
3706
3903
  /** Callback when user clicks minimize button */
@@ -3708,6 +3905,6 @@ interface WebcamOverlayProps {
3708
3905
  /** Callback when user clicks maximize button */
3709
3906
  onMaximize?: () => void;
3710
3907
  }
3711
- declare function WebcamOverlay({ src, currentTime: _currentTime, isPlaying: _isPlaying, playbackRate: _playbackRate, position, customPosition, size, customSize, visible, draggable, resizable: _resizable, showControls: _showControls, borderRadius, opacity, onPositionChange, onSizeChange: _onSizeChange, onVisibilityToggle: _onVisibilityToggle, onVideoRef, muted: _muted, volume: _volume, syncTolerance: _syncTolerance, maximized, onMinimize, onMaximize, }: WebcamOverlayProps): JSX.Element | null;
3908
+ declare function WebcamOverlay({ src, position, customPosition, size, customSize, visible, draggable, resizable: _resizable, showControls: _showControls, borderRadius, opacity, onPositionChange, onSizeChange: _onSizeChange, onVisibilityToggle: _onVisibilityToggle, onVideoRef, maximized, onMinimize, onMaximize, }: WebcamOverlayProps): JSX.Element;
3712
3909
 
3713
- export { AudioOverlay, type AudioOverlayProps, type AudioQuality, type AudioSyncControls, type AudioSyncState, type BackendAdapter, BranchPanel, type BranchPanelProps, BranchSavedToast, type BranchSavedToastProps, type BranchSnapshot, BranchTransition, type BranchTransitionProps, type BufferHealth, type CTEvent, type CTManifest, type CTMarker, type CTReadOptions, type CTRecording, CoursePlayer, type CoursePlayerProps, type CoursePlayerRef, type CursorPosition, type CustomPosition$1 as CustomPosition, DocumentPanel, type DocumentPanelProps, FileExplorer, type FileExplorerProps, type FileSystemSnapshot, type HLSPlayerInstance, HLS_EVENTS, IDEPanel, type IDEPanelProps, type InteractionPoint, type InternalEvent, type InternalRecording, type LearnerBranch, MCQOverlay, type MCQOverlayProps, type MCQResult, type MCQSubmission, type OverlayPosition$1 as OverlayPosition, type PanelMode, type PlaybackControls$1 as PlaybackControls, type PlaybackEngineConfig, type PlaybackHookState, type PlaybackState, PlayerControls, type PlayerControlsProps$1 as PlayerControlsProps, type PlayerProps, type PlayerRef, type RecordingMarker, type RemoteBufferHealth, type RemoteStreamingMetrics, type RemoteStreamingPlaybackControls, type RemoteStreamingPlaybackState, SaveBranchButton, type SaveBranchButtonProps, type SpeedSelectorProps, type StreamingAudioConfig, type StreamingConfig, StreamingCoursePlayer, type StreamingCoursePlayerProps, type StreamingCoursePlayerRef, type StreamingEventsConfig, type StreamingManifest, type StreamingMetrics, type StreamingPlaybackControls, type StreamingPlaybackState, type StreamingRecordingInfo, Terminal, type TerminalLineData, type TerminalProps, Timeline, type TimelineProps$1 as TimelineProps, ToolSidebar, type ToolSidebarProps, type ToolType, UnsavedBranchPrompt, type UnsavedBranchPromptProps, type UseAudioSyncOptions, type UseBranchesOptions, type UseBranchesResult, type UseBranchingOptions, type UseBranchingResult, type UseInteractionPointsOptions, type UseInteractionPointsResult, type UsePlaybackEngineResult, type UsePlaybackOptions, type UseRemoteStreamingPlaybackOptions, type UseVideoSyncOptions, type ValidationResult$1 as ValidationResult, type VideoSyncControls, type VideoSyncState, type VolumeControlProps, WebcamOverlay, type WebcamOverlayProps, WhiteboardPanel, type WhiteboardPanelProps, type WriteCTOptions, canPlayHls, createEmptyRecording, createHlsPlayer, deleteBranch as deleteBranchFromStorage, destroyHlsPlayer, formatBranchTime, hasNativeHlsSupport, isHlsSupported, loadBranches, readCTFile, readCTManifest, saveBranch as saveBranchToStorage, updateBranchLabel, useAudioElement, useAudioSync, useBranches, useBranching, useInteractionPoints, useInteractiveMode, usePlayback, usePlayback as usePlaybackEngine, useRemoteStreamingPlayback, useStreamingPlayback, useVideoElement, useVideoSync, validateCTFile, writeCTFile };
3910
+ export { AudioOverlay, type AudioOverlayProps, type AudioQuality, type BackendAdapter, BranchPanel, type BranchPanelProps, BranchSavedToast, type BranchSavedToastProps, type BranchSnapshot, BranchTransition, type BranchTransitionProps, type CTEvent, type CTManifest, type CTMarker, CTPlayer, type CTPlayerProps, type CTPlayerRef, type CTReadOptions, type CTRecording, CoursePlayer, type CoursePlayerProps, type CoursePlayerRef, type CursorPosition, type CustomPosition$1 as CustomPosition, DocumentPanel, type DocumentPanelProps, FileExplorer, type FileExplorerProps, type FileSystemSnapshot, type HLSPlayerInstance, HLS_EVENTS, HlsStreamingAdapter, IDEPanel, type IDEPanelProps, type InteractionPoint, type InternalEvent, type InternalRecording, type LearnerBranch, LocalFileAdapter, MCQOverlay, type MCQOverlayProps, type MCQResult, type MCQSubmission, MasterClock, type MediaSourceAdapter, type OverlayPosition$1 as OverlayPosition, type PanelMode, type PlaybackControls, type PlaybackEngineConfig, type PlaybackState, PlaybackStateMachine, type PlayerAction, PlayerControls, type PlayerControlsProps$1 as PlayerControlsProps, PlayerEngine, type PlayerEngineConfig, PlayerErrorBoundary, type PlayerProps, type PlayerRef, type PlayerSnapshot, type PlayerState, type RecordingMarker, type RemoteBufferHealth, SaveBranchButton, type SaveBranchButtonProps, type SpeedSelectorProps, type StreamingAudioConfig, type StreamingConfig, StreamingCoursePlayer, type StreamingCoursePlayerProps, type StreamingCoursePlayerRef, type StreamingEventsConfig, type StreamingManifest, type StreamingRecordingInfo, Terminal, type TerminalLineData, type TerminalProps, Timeline, type TimelineProps$1 as TimelineProps, ToolSidebar, type ToolSidebarProps, type ToolType, UnsavedBranchPrompt, type UnsavedBranchPromptProps, type UseBranchesOptions, type UseBranchesResult, type UseBranchingOptions, type UseBranchingResult, type UseInteractionPointsOptions, type UseInteractionPointsResult, type UsePlaybackEngineResult, type UsePlayerEngineResult, type ValidationResult$1 as ValidationResult, type VolumeControlProps, WebcamOverlay, type WebcamOverlayProps, WhiteboardPanel, type WhiteboardPanelProps, type WriteCTOptions, canPlayHls, createEmptyRecording, createHlsPlayer, deleteBranch as deleteBranchFromStorage, destroyHlsPlayer, formatBranchTime, hasNativeHlsSupport, isHlsSupported, loadBranches, readCTFile, readCTManifest, saveBranch as saveBranchToStorage, updateBranchLabel, useBranches, useBranching, useInteractionPoints, usePlayerEngine, validateCTFile, writeCTFile };