@@ -76,6 +76,7 @@ const SmartVideoPlayer = ({
7676 const settingsPanelRef = useRef ( null )
7777 const userPausedRef = useRef ( false )
7878 const autoPausedRef = useRef ( false )
79+ const isPiPRef = useRef ( false )
7980 const inViewRef = useRef ( true )
8081 const fullyOutRef = useRef ( false )
8182 const miniDismissedRef = useRef ( false )
@@ -188,6 +189,33 @@ const SmartVideoPlayer = ({
188189 configRef . current = config
189190 } , [ config ] )
190191
192+ useEffect ( ( ) => {
193+ const videoEl = useVideoRef . current
194+ if ( ! videoEl ) return
195+
196+ const onPause = ( ) => {
197+ // Ignore pauses we triggered for lazy-play
198+ if ( autoPausedRef . current ) return
199+ // Ignore natural end
200+ if ( videoEl . ended ) return
201+ // Treat any other pause (e.g. PiP native controls / media keys) as user intent
202+ userPausedRef . current = true
203+ }
204+
205+ const onPlay = ( ) => {
206+ // When playback resumes (including PiP native controls), clear both flags.
207+ userPausedRef . current = false
208+ autoPausedRef . current = false
209+ }
210+
211+ videoEl . addEventListener ( 'pause' , onPause )
212+ videoEl . addEventListener ( 'play' , onPlay )
213+ return ( ) => {
214+ videoEl . removeEventListener ( 'pause' , onPause )
215+ videoEl . removeEventListener ( 'play' , onPlay )
216+ }
217+ } , [ src ] )
218+
191219 useEffect ( ( ) => {
192220 const videoEl = useVideoRef . current
193221 if ( ! videoEl ) return
@@ -328,8 +356,14 @@ const SmartVideoPlayer = ({
328356 if ( ! videoEl ) return
329357 if ( ! ( 'requestPictureInPicture' in videoEl ) ) return
330358
331- const onEnter = ( ) => setIsPiP ( true )
332- const onLeave = ( ) => setIsPiP ( false )
359+ const onEnter = ( ) => {
360+ isPiPRef . current = true
361+ setIsPiP ( true )
362+ }
363+ const onLeave = ( ) => {
364+ isPiPRef . current = false
365+ setIsPiP ( false )
366+ }
333367
334368 videoEl . addEventListener ( 'enterpictureinpicture' , onEnter )
335369 videoEl . addEventListener ( 'leavepictureinpicture' , onLeave )
@@ -393,6 +427,10 @@ const SmartVideoPlayer = ({
393427 setInView ( nextInView )
394428 }
395429
430+ // In PiP, user expects playback to be independent from viewport visibility.
431+ // Avoid IO-driven pause/play fighting with PiP native controls.
432+ if ( isPiPRef . current ) return
433+
396434 const shouldMini = Boolean ( config . miniPlayer && nextFullyOut && ! isPiP && ! miniDismissedRef . current )
397435
398436 if ( ! nextInView ) {
@@ -436,11 +474,15 @@ const SmartVideoPlayer = ({
436474
437475 useEffect ( ( ) => {
438476 if ( ! config . autoPlay ) return
477+ // Keep current playback state when entering PiP; avoid any autoPlay race.
478+ if ( isPiPRef . current ) return
439479 if ( ! inView ) return
440480 if ( userPausedRef . current ) return
441481 if ( ! isPaused ) return
482+ const videoEl = useVideoRef . current
483+ if ( videoEl ?. ended ) return
442484 safePlay ( )
443- } , [ config . autoPlay , inView , isPaused , safePlay ] )
485+ } , [ config . autoPlay , inView , isPaused , isPiP , safePlay ] )
444486
445487 const handleTogglePause = useCallback ( ( ) => {
446488 const videoEl = useVideoRef . current
@@ -635,6 +677,20 @@ const SmartVideoPlayer = ({
635677 Your browser does not support the video tag.
636678 </ video >
637679
680+ < div className = { `${ styles . centerToggle } ${ isPaused ? styles . centerToggleVisible : '' } ` } aria-hidden = { false } >
681+ < button
682+ type = "button"
683+ className = { styles . centerToggleButton }
684+ aria-label = { isPaused ? '播放' : '暂停' }
685+ onClick = { ( e ) => {
686+ e . stopPropagation ( )
687+ handleTogglePause ( )
688+ } }
689+ >
690+ { isPaused ? < Play size = { 34 } /> : < Pause size = { 34 } /> }
691+ </ button >
692+ </ div >
693+
638694 < div className = { styles . videoControls } aria-label = "视频控制条" >
639695 < div className = { styles . controlsTop } role = "group" aria-label = "播放与工具" >
640696 { ! isMini ? < IconButton Icon = { SkipBack } label = "后退 10 秒" onClick = { ( ) => back ( 10 ) } /> : null }
0 commit comments