fix(overlay): drive timing review playhead from mpv playback position

- Preview session observes time-pos, waits for loaded media before seeking, and acks each command
- Forward positions to the modal over media-timing-review:preview-position
- Cursor follows the real position, pauses while buffering, and stays at the clip end when done
- Replace the fixed end-of-clip timer with a 15s stall timeout
- Document preview cursor behavior and ownership in docs
This commit is contained in:
sudacode committed 2026-09-29 18:07:07 -07:00
1 parent 04b878dd93
commit fdb0a31fff
14 files changed
+335 -57

No files matched your search

+9 -7
View File
@@ -5,6 +5,7 @@ import type {
MediaTimingReviewDecision,
MediaTimingReviewOpenPayload,
MediaTimingReviewPreviewRequest,
MediaTimingReviewPreviewPosition,
MediaTimingReviewRequest,
MediaTimingReviewResolveRequest,
MediaTimingReviewFrameRequest,
@@ -13,6 +14,7 @@ import type {
MediaTimingReviewWaveformResult,
} from '../../types/anki';
import type { SpeechWaveformOptions } from '../../core/services/media-timing-waveform';
import type { MediaTimingPreviewStartOptions } from '../../core/services/media-timing-preview';
import type { MediaTimingFrameOptions } from '../../core/services/media-timing-frame';
import {
isRemoteMediaWindowSourcePath,
@@ -36,13 +38,7 @@ interface ReviewMpvClient {
}
interface PreviewSession {
start(options: {
mediaPath: string;
executablePath?: string;
audioTrackId?: number;
volume?: number;
absoluteTimestamps?: boolean;
}): Promise<void>;
start(options: MediaTimingPreviewStartOptions): Promise<void>;
play(startTime: number, endTime: number): Promise<void>;
stop(): Promise<void>;
/** Fires when the player reaches the end of the clip started by play(). */
@@ -113,6 +109,7 @@ export interface MediaTimingReviewRuntimeDeps {
openModal: (payload: MediaTimingReviewOpenPayload, signal: AbortSignal) => Promise<boolean>;
/** Tells the modal that the hidden player finished the previewed clip. */
onPreviewEnded?: (reviewId: string) => void;
onPreviewPosition?: (position: MediaTimingReviewPreviewPosition) => void;
showStatus: (message: string) => void;
}
@@ -354,6 +351,11 @@ export function createMediaTimingReviewRuntime(deps: MediaTimingReviewRuntimeDep
}
await session.start({
mediaPath,
onPlaybackPosition: (time) => {
if (active === review && review.preview?.session === started) {
deps.onPreviewPosition?.({ reviewId: review.payload.reviewId, time });
}
},
...previewOptions,
// A cached window keeps one audio stream, so mpv's track id from the source no longer applies.
...(window