Skip to content

Commit 5c826fd

Browse files
committed
fix: fix HLS
1 parent 22ed8af commit 5c826fd

6 files changed

Lines changed: 74 additions & 12 deletions

File tree

src/components/stateless/SmartVideoPlayer/index.jsx

Lines changed: 63 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,29 @@ const SmartVideoPlayerInner = React.forwardRef(function SmartVideoPlayerInner(
317317
return /\.m3u8($|\?)/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 (/\.webm($|\?)/i.test(lower)) return 'video/webm'
326+
if (/\.ogg($|\?)/i.test(lower)) return 'video/ogg'
327+
if (/\.ogv($|\?)/i.test(lower)) return 'video/ogg'
328+
if (/\.mov($|\?)/i.test(lower)) return 'video/quicktime'
329+
if (/\.avi($|\?)/i.test(lower)) return 'video/x-msvideo'
330+
if (/\.wmv($|\?)/i.test(lower)) return 'video/x-ms-wmv'
331+
if (/\.flv($|\?)/i.test(lower)) return 'video/x-flv'
332+
if (/\.mkv($|\?)/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"

src/components/stateless/Video/index.jsx

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,7 @@ export const VideoJS = (props) => {
99
const { options, onReady } = props
1010

1111
React.useEffect(() => {
12-
// Make sure Video.js player is only initialized once
1312
if (!playerRef.current) {
14-
// The Video.js player needs to be _inside_ the component el for React 18 Strict Mode.
1513
const videoElement = document.createElement('video-js')
1614

1715
videoElement.classList.add('vjs-big-play-centered')
@@ -22,9 +20,6 @@ export const VideoJS = (props) => {
2220

2321
onReady?.(player)
2422
}))
25-
26-
// You could update an existing player in the `else` block here
27-
// on prop change, for example:
2823
} else {
2924
const player = playerRef.current
3025

@@ -33,7 +28,6 @@ export const VideoJS = (props) => {
3328
}
3429
}, [options, videoRef])
3530

36-
// Dispose the Video.js player when the functional component unmounts
3731
React.useEffect(() => {
3832
const player = playerRef.current
3933

src/locales/en/translation.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,7 @@ const en = {
196196
videoDecodeError: 'Decode error (unsupported format/codec)',
197197
videoSourceNotSupported: 'Video source unavailable (404/CORS/type unsupported)',
198198
videoLoadFailed: 'Failed to load video',
199+
noVideoSource: 'No valid video source provided',
199200
},
200201
}
201202

src/locales/zh/translation.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,7 @@ const zh = {
196196
videoDecodeError: '视频解码失败(格式/编码不兼容)',
197197
videoSourceNotSupported: '视频源不可用(404/跨域/类型不支持)',
198198
videoLoadFailed: '视频加载失败',
199+
noVideoSource: '未提供有效的视频源',
199200
},
200201
}
201202

vite.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ export default defineConfig(({ mode }) => {
160160
'vendor-zustand': ['zustand', 'zustand/middleware', 'zustand/middleware/immer', 'immer'],
161161
'vendor-react': ['react', 'react-dom', 'react-router-dom'],
162162
'vendor-antd': ['antd', '@ant-design/icons'],
163+
'vendor-hls': ['hls.js'],
163164
},
164165
},
165166
},

webpack/webpack.prod.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,14 @@ const prodWebpackConfig = merge(common, {
153153
priority: 20,
154154
enforce: true,
155155
},
156+
// 确保 hls.js 被完整打包,避免构建后 HLS 播放问题
157+
hls: {
158+
test: /[\\/]node_modules[\\/]hls\.js[\\/]/,
159+
name: 'vendor-hls',
160+
chunks: 'all',
161+
priority: 20,
162+
enforce: true,
163+
},
156164
// commons: {
157165
// name: 'commons',
158166
// minChunks: 2,

0 commit comments

Comments
 (0)