diff --git a/crates/compositor/src/live.rs b/crates/compositor/src/live.rs index 015d01d7..c486da8e 100644 --- a/crates/compositor/src/live.rs +++ b/crates/compositor/src/live.rs @@ -76,6 +76,45 @@ struct PrefetchedClip { /// Ouvre + positionne la paire de décodeurs d'un clip (même travail que /// `Player::set_active_clip`, mais autonome — sans instance `Player` existante, pour pouvoir /// tourner sur un thread dédié pendant que le `Player` réel joue encore le clip actif). +/// Ouvre le décodeur webcam, ou un remplaçant quand le clip n'a pas de caméra. +/// +/// La question « ce chemin désigne-t-il une vraie caméra ? » a déjà une réponse dans ce +/// crate : `webcam_is_real`. Elle couvre le chemin vide, le chemin composé d'espaces, et le +/// cas où l'appelant renvoie le chemin de l'écran lui-même — ce que fait `ExportDialog.tsx` +/// et ce que contiennent les scènes plus anciennes. Un simple `is_empty()` en raterait deux +/// sur trois, et le commentaire de `webcam_is_real` rappelle que c'est exactement cet oubli +/// qui avait mis l'enregistrement d'écran dans la vignette caméra (#265). +/// +/// Sans caméra, le remplaçant est l'écran : le moteur veut une paire de décodeurs toujours +/// valide plutôt qu'un `Option` à dérouler sur tout le chemin chaud, et rien ne le dessine +/// puisque la composition ne pose une vignette que si le document déclare une caméra. +/// +/// Avec une caméra déclarée dont le fichier ne s'ouvre pas, c'est l'inverse : la vignette +/// EST dessinée et affiche l'écran. Ce cas-là méritait une trace, et n'en avait aucune. +/// +/// ponytail: on garde le remplaçant plutôt que de passer `wdec` en `Option`, ce qui +/// toucherait 22 sites dont le pool de décodeurs et la boucle de composition `unsafe`. À faire +/// si quelqu'un mesure que le décodeur inutile coûte (VRAM des pools D3D11VA, une ouverture +/// par clip) — l'avertissement ci-dessous dit enfin à quelle fréquence le cas visible arrive. +unsafe fn open_webcam_or_stand_in( + screen_path: &str, + webcam_path: &str, + gpu: &Gpu, +) -> Result { + if !webcam_is_real(webcam_path, screen_path) { + return Decoder::open(screen_path, gpu); + } + match Decoder::open(webcam_path, gpu) { + Ok(d) => Ok(d), + Err(e) => { + eprintln!( + "WARNING: caméra déclarée mais illisible ({webcam_path}) : {e}. La vignette caméra affichera l'enregistrement d'écran ; le média est à relier." + ); + Decoder::open(screen_path, gpu) + } + } +} + unsafe fn open_and_seek_clip( screen_path: &str, webcam_path: &str, @@ -85,10 +124,7 @@ unsafe fn open_and_seek_clip( ) -> Result { let source_time_sec = source_time_sec.max(0.0); let mut sdec = Decoder::open(screen_path, gpu)?; - let mut wdec = match Decoder::open(webcam_path, gpu) { - Ok(d) => d, - Err(_) => Decoder::open(screen_path, gpu)?, - }; + let mut wdec = open_webcam_or_stand_in(screen_path, webcam_path, gpu)?; let sf = sdec.seek_to(source_time_sec)?; let mut wf = wdec.seek_to(webcam_seek_time(source_time_sec, webcam_offset_sec))?; if wf.is_null() { @@ -225,10 +261,7 @@ pub struct Player { impl Player { pub unsafe fn open(screen: &str, webcam: &str, gpu: &Gpu) -> Result { - let wdec = match Decoder::open(webcam, gpu) { - Ok(d) => d, - Err(_) => Decoder::open(screen, gpu)?, - }; + let wdec = open_webcam_or_stand_in(screen, webcam, gpu)?; Ok(Player { sdec: Decoder::open(screen, gpu)?, wdec,