From e66f4cdf1d3f26de1f751a83718a5545d0934f7c Mon Sep 17 00:00:00 2001 From: Michael SCHAL Date: Wed, 29 Apr 2026 08:15:10 +0200 Subject: [PATCH] fix: suppression clignotement barre de lecture pendant la lecture MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extraction du composant MiniPlayer hors d'AudioPlayer pour stabiliser sa référence. Avant: MiniPlayer était une fonction imbriquée recréée à chaque rendu (4x/sec avec onTimeUpdate), React le traitait comme un nouveau type de composant → démontage/remontage → animation slide-up rejouée → clignotement. Co-Authored-By: Claude Opus 4.7 --- frontend/src/components/AudioPlayer.tsx | 175 +++++++++++++++--------- 1 file changed, 109 insertions(+), 66 deletions(-) diff --git a/frontend/src/components/AudioPlayer.tsx b/frontend/src/components/AudioPlayer.tsx index 3ec0999..63a7fa4 100644 --- a/frontend/src/components/AudioPlayer.tsx +++ b/frontend/src/components/AudioPlayer.tsx @@ -16,6 +16,103 @@ interface AudioPlayerProps { mode?: 'floating' | 'inline'; } +// Extracted as stable component to prevent remount on every currentTime update +interface MiniPlayerProps { + episode: Episode; + podcast: { title: string; imageUrl?: string } | null; + isPlaying: boolean; + isResuming: boolean; + currentTime: number; + duration: number; + playbackSpeed: number; + progress: number; + fullWidth?: boolean; + onTogglePlay: () => void; + onSeekRelative: (delta: number) => void; + onChangeSpeed: () => void; + onExpand: () => void; + onClose: () => void; + onProgressChange: (e: React.ChangeEvent) => void; +} + +function MiniPlayer({ + episode, podcast, isPlaying, isResuming, + currentTime, duration, playbackSpeed, progress, fullWidth, + onTogglePlay, onSeekRelative, onChangeSpeed, onExpand, onClose, onProgressChange, +}: MiniPlayerProps) { + const fmt = (s: number) => { + if (!isFinite(s)) return '0:00'; + const m = Math.floor(s / 60); + const sec = Math.floor(s % 60); + return `${m}:${sec.toString().padStart(2, '0')}`; + }; + + return ( +
+
+
+
+ {(episode.imageUrl || podcast?.imageUrl) + ? + :
🎙️
} +
+ +
+

{episode.title}

+ {isResuming + ?

Reprise...

+ :

{podcast?.title || ''}

} +
+ +
+ + + +
+ +
+ {fmt(currentTime)}/{fmt(duration)} +
+ +
+ + + +
+
+
+ +
+
+
+
+
+
+ ); +} + export default function AudioPlayer({ episode, onClose, userId, mode = 'floating' }: AudioPlayerProps) { const { isPlaying, currentTime, duration, volume, playbackSpeed, @@ -75,70 +172,16 @@ export default function AudioPlayer({ episode, onClose, userId, mode = 'floating const closeAlert = () => { setError(null); onClose(); }; - const MiniPlayer = ({ fullWidth }: { fullWidth?: boolean }) => ( -
-
-
-
- {(episode.imageUrl || podcast?.imageUrl) - ? - :
🎙️
} -
- -
-

{episode.title}

- {isResuming - ?

Reprise...

- :

{podcast?.title || ''}

} -
- -
- - - -
- -
- {fmt(currentTime)}/{fmt(duration)} -
- -
- - - -
-
-
- -
-
-
-
-
-
- ); + const miniPlayerProps = { + episode, podcast, isPlaying, isResuming, currentTime, duration, + playbackSpeed, progress, + onTogglePlay: togglePlay, + onSeekRelative: seekRelative, + onChangeSpeed: changeSpeed, + onExpand: () => setIsExpanded(true), + onClose, + onProgressChange: handleProgress, + }; // ── INLINE MODE ── if (mode === 'inline') { @@ -240,7 +283,7 @@ export default function AudioPlayer({ episode, onClose, userId, mode = 'floating
- {showMiniBar && createPortal(, document.body)} + {showMiniBar && createPortal(, document.body)} ); } @@ -251,7 +294,7 @@ export default function AudioPlayer({ episode, onClose, userId, mode = 'floating {!isExpanded ? ( - + ) : (