(null);
-
- useEffect(() => {
- return () => {
- if (rafRef.current !== 0) {
- cancelAnimationFrame(rafRef.current);
- }
- };
- }, []);
-
- const handleInputChange = useCallback(
- (value: number) => {
- // Hors drag (flèches du clavier, clic simple sur la piste) : rien à coalescer,
- // c'est un saut unique et il doit prendre effet tout de suite.
- if (!draggingRef.current) {
- onSeek(value);
- return;
- }
- pendingRef.current = value;
- // Visuel d'abord, sans passer par React : latence nulle, comme la tête de lecture
- // de la timeline. `inputMax` est déjà borné à 1 minimum, pas de division par zéro.
- const pct = Math.min(100, Math.max(0, (value / inputMax) * 100));
- if (progressElRef.current) {
- progressElRef.current.style.width = `${pct}%`;
- }
- if (thumbElRef.current) {
- thumbElRef.current.style.left = `${pct}%`;
- }
- if (rafRef.current !== 0) {
- return;
- }
- rafRef.current = requestAnimationFrame(() => {
- rafRef.current = 0;
- const pending = pendingRef.current;
- if (pending !== null) {
- onSeek(pending);
- }
- });
- },
- [onSeek, inputMax],
- );
-
- const endDrag = useCallback(() => {
- if (!draggingRef.current) {
- return;
- }
- draggingRef.current = false;
- setUiProbeScrubbing(false, "bar");
- if (rafRef.current !== 0) {
- cancelAnimationFrame(rafRef.current);
- rafRef.current = 0;
- }
- // Pose la dernière position : sans ce commit, le mouvement compris entre le dernier
- // rAF et le relâchement serait perdu et la tête s'arrêterait un cran avant le doigt.
- const pending = pendingRef.current;
- if (pending !== null) {
- pendingRef.current = null;
- onSeek(pending);
- }
- }, [onSeek]);
return (
@@ -153,45 +52,6 @@ export const TransportBar = memo(function TransportBar({
/
{formatSec(virtualDurationSec)}
-
-
-
handleInputChange(Number(e.target.value))}
- onPointerDown={() => {
- draggingRef.current = true;
- // Sonde de fluidité (diagnostic) : marque la fenêtre de drag comme
- // venant de la BARRE, pour ne pas la confondre avec un drag de
- // timeline — les deux empruntent des chemins de code différents.
- setUiProbeScrubbing(true, "bar");
- }}
- onPointerUp={endDrag}
- // `pointercancel` et `blur` ferment aussi le drag : un pointeur capturé
- // puis interrompu (geste système, perte de focus) ne produit pas de
- // `pointerup`, et la dernière position resterait alors non confirmée.
- onPointerCancel={endDrag}
- onBlur={endDrag}
- className={styles.scrubInput}
- aria-label={te("transport.seekVideo")}
- />
-
-
);
});
diff --git a/src/components/ai-edition/v4/V4Timeline.tsx b/src/components/ai-edition/v4/V4Timeline.tsx
index 65d7ca48f..1dc4cc361 100644
--- a/src/components/ai-edition/v4/V4Timeline.tsx
+++ b/src/components/ai-edition/v4/V4Timeline.tsx
@@ -2029,7 +2029,6 @@ export function V4Timeline({
overrideTimeSec={scrubbingTimeSec}
clips={clips}
onTogglePlay={onTogglePlay}
- onSeek={setCurrentTime}
/>
diff --git a/src/i18n/locales/ar/editor.json b/src/i18n/locales/ar/editor.json
index b8efbe342..b1452a3e6 100644
--- a/src/i18n/locales/ar/editor.json
+++ b/src/i18n/locales/ar/editor.json
@@ -352,8 +352,7 @@
"playbackControls": "عناصر التحكم في التشغيل",
"playPause": "تشغيل / إيقاف مؤقت",
"playPauseTitle": "تشغيل / إيقاف مؤقت (مسافة)",
- "loop": "تكرار",
- "seekVideo": "التنقل في الفيديو"
+ "loop": "تكرار"
},
"shell": {
"aiEditor": "محرّر الذكاء الاصطناعي",
diff --git a/src/i18n/locales/cs/editor.json b/src/i18n/locales/cs/editor.json
index 9a4b758fe..0b96b4024 100644
--- a/src/i18n/locales/cs/editor.json
+++ b/src/i18n/locales/cs/editor.json
@@ -352,8 +352,7 @@
"playbackControls": "Ovládání přehrávání",
"playPause": "Přehrát / Pozastavit",
"playPauseTitle": "Přehrát / Pozastavit (mezerník)",
- "loop": "Opakovat",
- "seekVideo": "Vyhledat ve videu"
+ "loop": "Opakovat"
},
"shell": {
"aiEditor": "AI editor",
diff --git a/src/i18n/locales/de/editor.json b/src/i18n/locales/de/editor.json
index 155f9ae0e..6ee6b7888 100644
--- a/src/i18n/locales/de/editor.json
+++ b/src/i18n/locales/de/editor.json
@@ -352,8 +352,7 @@
"playbackControls": "Wiedergabesteuerung",
"playPause": "Wiedergabe / Pause",
"playPauseTitle": "Wiedergabe / Pause (Leertaste)",
- "loop": "Wiederholen",
- "seekVideo": "Videoposition"
+ "loop": "Wiederholen"
},
"shell": {
"aiEditor": "KI-Editor",
diff --git a/src/i18n/locales/en/editor.json b/src/i18n/locales/en/editor.json
index 1f2080daa..7eef3397c 100644
--- a/src/i18n/locales/en/editor.json
+++ b/src/i18n/locales/en/editor.json
@@ -352,8 +352,7 @@
"playbackControls": "Playback controls",
"playPause": "Play / Pause",
"playPauseTitle": "Play / Pause (Space)",
- "loop": "Loop",
- "seekVideo": "Seek video"
+ "loop": "Loop"
},
"shell": {
"aiEditor": "AI editor",
diff --git a/src/i18n/locales/es/editor.json b/src/i18n/locales/es/editor.json
index ad3e99566..3574f2a8b 100644
--- a/src/i18n/locales/es/editor.json
+++ b/src/i18n/locales/es/editor.json
@@ -352,8 +352,7 @@
"playbackControls": "Controles de reproducción",
"playPause": "Reproducir / Pausar",
"playPauseTitle": "Reproducir / Pausar (Espacio)",
- "loop": "Bucle",
- "seekVideo": "Desplazarse por el vídeo"
+ "loop": "Bucle"
},
"shell": {
"aiEditor": "Editor de IA",
diff --git a/src/i18n/locales/fr/editor.json b/src/i18n/locales/fr/editor.json
index 589c2f28c..f26c00ab8 100644
--- a/src/i18n/locales/fr/editor.json
+++ b/src/i18n/locales/fr/editor.json
@@ -352,8 +352,7 @@
"playbackControls": "Contrôles de lecture",
"playPause": "Lecture / Pause",
"playPauseTitle": "Lecture / Pause (Espace)",
- "loop": "Boucle",
- "seekVideo": "Naviguer dans la vidéo"
+ "loop": "Boucle"
},
"shell": {
"aiEditor": "Éditeur IA",
diff --git a/src/i18n/locales/it/editor.json b/src/i18n/locales/it/editor.json
index 7af2973d9..6275c9549 100644
--- a/src/i18n/locales/it/editor.json
+++ b/src/i18n/locales/it/editor.json
@@ -352,8 +352,7 @@
"playbackControls": "Controlli di riproduzione",
"playPause": "Riproduci / Pausa",
"playPauseTitle": "Riproduci / Pausa (Spazio)",
- "loop": "Ripeti",
- "seekVideo": "Scorri il video"
+ "loop": "Ripeti"
},
"shell": {
"aiEditor": "Editor IA",
diff --git a/src/i18n/locales/ja-JP/editor.json b/src/i18n/locales/ja-JP/editor.json
index d463ad937..0958f7fb6 100644
--- a/src/i18n/locales/ja-JP/editor.json
+++ b/src/i18n/locales/ja-JP/editor.json
@@ -352,8 +352,7 @@
"playbackControls": "再生コントロール",
"playPause": "再生 / 一時停止",
"playPauseTitle": "再生 / 一時停止(スペース)",
- "loop": "ループ",
- "seekVideo": "動画をシーク"
+ "loop": "ループ"
},
"shell": {
"aiEditor": "AI エディター",
diff --git a/src/i18n/locales/ko-KR/editor.json b/src/i18n/locales/ko-KR/editor.json
index 3bc89c1ad..5f147f9dc 100644
--- a/src/i18n/locales/ko-KR/editor.json
+++ b/src/i18n/locales/ko-KR/editor.json
@@ -352,8 +352,7 @@
"playbackControls": "재생 컨트롤",
"playPause": "재생 / 일시정지",
"playPauseTitle": "재생 / 일시정지 (Space)",
- "loop": "반복",
- "seekVideo": "영상 탐색"
+ "loop": "반복"
},
"shell": {
"aiEditor": "AI 편집기",
diff --git a/src/i18n/locales/pt-BR/editor.json b/src/i18n/locales/pt-BR/editor.json
index 2fd1122fc..b2f2ebce4 100644
--- a/src/i18n/locales/pt-BR/editor.json
+++ b/src/i18n/locales/pt-BR/editor.json
@@ -352,8 +352,7 @@
"playbackControls": "Controles de reprodução",
"playPause": "Reproduzir / Pausar",
"playPauseTitle": "Reproduzir / Pausar (Espaço)",
- "loop": "Repetir",
- "seekVideo": "Navegar no vídeo"
+ "loop": "Repetir"
},
"shell": {
"aiEditor": "Editor de IA",
diff --git a/src/i18n/locales/ru/editor.json b/src/i18n/locales/ru/editor.json
index 257b406f3..cc6f324be 100644
--- a/src/i18n/locales/ru/editor.json
+++ b/src/i18n/locales/ru/editor.json
@@ -352,8 +352,7 @@
"playbackControls": "Элементы управления воспроизведением",
"playPause": "Воспроизведение / Пауза",
"playPauseTitle": "Воспроизведение / Пауза (Пробел)",
- "loop": "Повтор",
- "seekVideo": "Перемотка видео"
+ "loop": "Повтор"
},
"shell": {
"aiEditor": "ИИ-редактор",
diff --git a/src/i18n/locales/tr/editor.json b/src/i18n/locales/tr/editor.json
index 0170d454c..2f3c01abf 100644
--- a/src/i18n/locales/tr/editor.json
+++ b/src/i18n/locales/tr/editor.json
@@ -352,8 +352,7 @@
"playbackControls": "Oynatma denetimleri",
"playPause": "Oynat / Duraklat",
"playPauseTitle": "Oynat / Duraklat (Boşluk)",
- "loop": "Döngü",
- "seekVideo": "Videoda gezin"
+ "loop": "Döngü"
},
"shell": {
"aiEditor": "Yapay zekâ düzenleyici",
diff --git a/src/i18n/locales/vi/editor.json b/src/i18n/locales/vi/editor.json
index b921be5ed..b1836927d 100644
--- a/src/i18n/locales/vi/editor.json
+++ b/src/i18n/locales/vi/editor.json
@@ -352,8 +352,7 @@
"playbackControls": "Điều khiển phát lại",
"playPause": "Phát / Tạm dừng",
"playPauseTitle": "Phát / Tạm dừng (Phím cách)",
- "loop": "Lặp lại",
- "seekVideo": "Tua video"
+ "loop": "Lặp lại"
},
"shell": {
"aiEditor": "Trình biên tập AI",
diff --git a/src/i18n/locales/zh-CN/editor.json b/src/i18n/locales/zh-CN/editor.json
index bc741076e..a81e03a50 100644
--- a/src/i18n/locales/zh-CN/editor.json
+++ b/src/i18n/locales/zh-CN/editor.json
@@ -352,8 +352,7 @@
"playbackControls": "播放控件",
"playPause": "播放 / 暂停",
"playPauseTitle": "播放 / 暂停(空格)",
- "loop": "循环",
- "seekVideo": "拖动视频进度"
+ "loop": "循环"
},
"shell": {
"aiEditor": "AI 编辑器",
diff --git a/src/i18n/locales/zh-TW/editor.json b/src/i18n/locales/zh-TW/editor.json
index 9487b2cf3..a58c17635 100644
--- a/src/i18n/locales/zh-TW/editor.json
+++ b/src/i18n/locales/zh-TW/editor.json
@@ -352,8 +352,7 @@
"playbackControls": "播放控制項",
"playPause": "播放 / 暫停",
"playPauseTitle": "播放 / 暫停(空白鍵)",
- "loop": "循環",
- "seekVideo": "拖曳影片進度"
+ "loop": "循環"
},
"shell": {
"aiEditor": "AI 編輯器",
diff --git a/src/lib/ai-edition/perf/uiFrameProbe.ts b/src/lib/ai-edition/perf/uiFrameProbe.ts
index 8fb2adb31..812d39f46 100644
--- a/src/lib/ai-edition/perf/uiFrameProbe.ts
+++ b/src/lib/ai-edition/perf/uiFrameProbe.ts
@@ -21,13 +21,7 @@
* qu'on ne le demande pas — aucun coût en usage normal.
*/
-type BaseState =
- | "repos"
- | "preview"
- | "scrub-tl"
- | "scrub-tl+preview"
- | "scrub-bar"
- | "scrub-bar+preview";
+type BaseState = "repos" | "preview" | "scrub" | "scrub+preview";
/** L'état porte le suffixe `@N` = nombre de bascules de clip déjà vues. Voir
* `noteUiProbeClipSwitch` : sans cette séparation, deux mesures ne sont pas comparables. */
type ProbeState = string;
@@ -42,19 +36,7 @@ let rafHandle = 0;
let lastTs = 0;
/** Frames de preview peintes depuis le dernier tick — dit si la preview travaille. */
let previewFramesSinceTick = 0;
-/**
- * D'OÙ vient le scrub en cours.
- *
- * Les deux chemins n'ont rien à voir : la timeline écrit la tête de lecture directement
- * dans le DOM et coalesce son écriture au store en rAF ; la barre de progression, elle,
- * écrit au store à chaque événement ET repose un `seekTarget` — un état de la RACINE de
- * l'éditeur — ce qui re-render tout et déclenche un seek du `