@@ -317,6 +317,29 @@ const SmartVideoPlayerInner = React.forwardRef(function SmartVideoPlayerInner(
317317 return / \. m 3 u 8 ( $ | \? ) / i. test ( src )
318318 } , [ isEmbed , src ] )
319319
320+ // 根据文件扩展名推断 MIME 类型
321+ const videoMimeType = useMemo ( ( ) => {
322+ if ( isEmbed || ! src || typeof src !== 'string' ) return ''
323+ if ( isHlsSrc ) return 'application/x-mpegURL'
324+ const lower = src . toLowerCase ( )
325+ if ( / \. w e b m ( $ | \? ) / i. test ( lower ) ) return 'video/webm'
326+ if ( / \. o g g ( $ | \? ) / i. test ( lower ) ) return 'video/ogg'
327+ if ( / \. o g v ( $ | \? ) / i. test ( lower ) ) return 'video/ogg'
328+ if ( / \. m o v ( $ | \? ) / i. test ( lower ) ) return 'video/quicktime'
329+ if ( / \. a v i ( $ | \? ) / i. test ( lower ) ) return 'video/x-msvideo'
330+ if ( / \. w m v ( $ | \? ) / i. test ( lower ) ) return 'video/x-ms-wmv'
331+ if ( / \. f l v ( $ | \? ) / i. test ( lower ) ) return 'video/x-flv'
332+ if ( / \. m k v ( $ | \? ) / i. test ( lower ) ) return 'video/x-matroska'
333+ // 默认 mp4
334+ return 'video/mp4'
335+ } , [ isEmbed , isHlsSrc , src ] )
336+
337+ // 检查 src 是否有效
338+ const hasValidSrc = useMemo ( ( ) => {
339+ if ( isEmbed ) return true
340+ return typeof src === 'string' && src . length > 0
341+ } , [ isEmbed , src ] )
342+
320343 const hlsRef = useRef ( null )
321344
322345 const [ mediaLoading , setMediaLoading ] = useState ( false )
@@ -354,6 +377,14 @@ const SmartVideoPlayerInner = React.forwardRef(function SmartVideoPlayerInner(
354377 const videoEl = useVideoRef . current
355378 if ( ! videoEl ) return
356379
380+ // 检查是否有有效的视频源
381+ if ( ! hasValidSrc ) {
382+ if ( withFeedback ) {
383+ reportError ( t ( 'svp.noVideoSource' ) )
384+ }
385+ return
386+ }
387+
357388 // Autoplay: 大多数浏览器要求 muted 才允许自动播放;这里做同步兜底,避免 effect 时序导致首次 play 被拦
358389 if ( configRef . current ?. autoMute && ! videoEl . muted ) {
359390 videoEl . muted = true
@@ -404,7 +435,7 @@ const SmartVideoPlayerInner = React.forwardRef(function SmartVideoPlayerInner(
404435 } )
405436 } )
406437 } ,
407- [ emitEvent , isHlsSrc , reportError , t ]
438+ [ emitEvent , hasValidSrc , isHlsSrc , reportError , t ]
408439 )
409440
410441 const kickAutoPlayNow = useCallback ( ( ) => {
@@ -522,6 +553,15 @@ const SmartVideoPlayerInner = React.forwardRef(function SmartVideoPlayerInner(
522553 const mod = await import ( 'hls.js' )
523554 const Hls = mod ?. default
524555 if ( ! Hls || typeof Hls . isSupported !== 'function' || ! Hls . isSupported ( ) ) {
556+ // hls.js 不可用,尝试直接设置 src 作为最后的回退
557+ // 某些环境下浏览器可能仍能播放
558+ try {
559+ videoEl . src = src
560+ videoEl . load ( )
561+ emitEvent ( 'hlsFallback' , { reason : 'hls_not_supported' } )
562+ } catch ( _ ) {
563+ // ignore
564+ }
525565 reportError ( t ( 'svp.hlsNotSupported' ) , {
526566 type : 'hls' ,
527567 reason : 'not_supported' ,
@@ -532,8 +572,14 @@ const SmartVideoPlayerInner = React.forwardRef(function SmartVideoPlayerInner(
532572 if ( cancelled ) return
533573
534574 const hls = new Hls ( {
535- // keep defaults; avoid aggressive low-latency tuning here
536- enableWorker : true ,
575+ // 构建后 Worker 路径可能有问题,优先禁用 Worker 以确保兼容性
576+ // 如果需要 Worker 性能,可以配置 workerPath
577+ enableWorker : false ,
578+ // 低延迟模式下的优化配置
579+ lowLatencyMode : false ,
580+ // 增加缓冲区以提高稳定性
581+ maxBufferLength : 30 ,
582+ maxMaxBufferLength : 60 ,
537583 } )
538584 hlsRef . current = hls
539585
@@ -579,6 +625,14 @@ const SmartVideoPlayerInner = React.forwardRef(function SmartVideoPlayerInner(
579625 }
580626 } )
581627 } catch ( err ) {
628+ // hls.js 导入或初始化失败,尝试直接设置 src 作为最后的回退
629+ try {
630+ videoEl . src = src
631+ videoEl . load ( )
632+ emitEvent ( 'hlsFallback' , { reason : 'hls_init_failed' } )
633+ } catch ( _ ) {
634+ // ignore
635+ }
582636 reportError ( t ( 'svp.hlsInitFailed' ) , {
583637 type : 'hls' ,
584638 name : err ?. name ,
@@ -697,6 +751,9 @@ const SmartVideoPlayerInner = React.forwardRef(function SmartVideoPlayerInner(
697751 const onPlaying = ( ) => setPlayError ( '' )
698752 const onError = ( ) => {
699753 const code = videoEl . error ?. code
754+ const nativeMessage = videoEl . error ?. message || ''
755+ // 获取更详细的错误信息
756+ const currentSrc = videoEl . currentSrc || videoEl . src || src || ''
700757 const msg =
701758 code === 1
702759 ? t ( 'svp.videoLoadAborted' )
@@ -707,7 +764,7 @@ const SmartVideoPlayerInner = React.forwardRef(function SmartVideoPlayerInner(
707764 : code === 4
708765 ? t ( 'svp.videoSourceNotSupported' )
709766 : t ( 'svp.videoLoadFailed' )
710- reportError ( msg , { code } )
767+ reportError ( msg , { code, nativeMessage , currentSrc , providedSrc : src , isHlsSrc , hasValidSrc } )
711768 }
712769
713770 videoEl . addEventListener ( 'playing' , onPlaying )
@@ -716,7 +773,7 @@ const SmartVideoPlayerInner = React.forwardRef(function SmartVideoPlayerInner(
716773 videoEl . removeEventListener ( 'playing' , onPlaying )
717774 videoEl . removeEventListener ( 'error' , onError )
718775 }
719- } , [ isEmbed , reportError , src , t ] )
776+ } , [ hasValidSrc , isEmbed , isHlsSrc , reportError , src , t ] )
720777
721778 useEffect ( ( ) => {
722779 if ( isEmbed ) return
@@ -1774,7 +1831,7 @@ const SmartVideoPlayerInner = React.forwardRef(function SmartVideoPlayerInner(
17741831 onClick = { handleTogglePause }
17751832 onDoubleClick = { toggleFullscreen }
17761833 >
1777- { ! isHlsSrc ? < source src = { src } type = " video/mp4" /> : null }
1834+ { ! isHlsSrc && hasValidSrc ? < source src = { src } type = { videoMimeType || ' video/mp4' } /> : null }
17781835 { computedTrackSrc ? (
17791836 < track
17801837 kind = "captions"
0 commit comments