mirror of
https://github.com/truewhile/MeBox.git
synced 2026-10-01 20:16:36 +08:00
bug处理
This commit is contained in:
+19
-4
@@ -148,13 +148,28 @@ export function streamURL(mediaId: string): string {
|
||||
// hlsURL returns the m3u8 playlist URL fed into hls.js.
|
||||
// startSec > 0 asks the server to (re)start ffmpeg from that source offset.
|
||||
export function hlsURL(mediaId: string, startSec = 0): string {
|
||||
const start =
|
||||
startSec > 0.05 ? `&start=${encodeURIComponent(String(Math.round(startSec * 1000) / 1000))}` : ''
|
||||
// Cache-bust so a seek restart cannot reuse a stale start=0 playlist from disk/browser.
|
||||
const bust = startSec > 0.05 ? `&_seek=${Date.now()}` : ''
|
||||
const safeStart = Math.max(0, Math.round(startSec * 1000) / 1000)
|
||||
// Always send start= (including 0) so the server can tell an intentional
|
||||
// restart-from-head apart from a missing query on a stale refresh.
|
||||
const start = `&start=${encodeURIComponent(String(safeStart))}`
|
||||
// Monotonic-ish client generation: newer seeks win; older in-flight playlist
|
||||
// requests must not cancel the active ffmpeg job back to t=0.
|
||||
const bust = `&_seek=${Date.now()}`
|
||||
return `/api/hls/${encodeURIComponent(mediaId)}/index.m3u8?${tokenQuery()}${profileQuery()}${start}${bust}`
|
||||
}
|
||||
|
||||
// Stop an on-demand HLS job. keepalive makes the request survive page
|
||||
// navigation/tab close, where an axios promise can be discarded by browsers.
|
||||
export function stopHLSJob(mediaId: string): void {
|
||||
const url = `/api/hls/${encodeURIComponent(mediaId)}?${tokenQuery()}${profileQuery()}`
|
||||
void fetch(url, {
|
||||
method: 'DELETE',
|
||||
credentials: 'same-origin',
|
||||
keepalive: true,
|
||||
cache: 'no-store',
|
||||
}).catch(() => undefined)
|
||||
}
|
||||
|
||||
// imageURL converts a remote poster URL into a same-origin proxy URL so it
|
||||
// can never be blocked by CORS / GFW. Empty strings pass through unchanged.
|
||||
export type ImageURLOptions =
|
||||
|
||||
@@ -4,7 +4,7 @@ import type Hls from 'hls.js'
|
||||
import toast from 'react-hot-toast'
|
||||
|
||||
import { mediaAPI, libraryAPI } from '../api/library'
|
||||
import { api, hlsURL, streamURL } from '../api/client'
|
||||
import { hlsURL, stopHLSJob, streamURL } from '../api/client'
|
||||
import { danmakuAPI, type DanmakuAnime, type DanmakuLoadedInfo } from '../api/danmaku'
|
||||
import { playbackAPI } from '../api/playback'
|
||||
import { subtitlesAPI, type SubtitleTrack } from '../api/subtitles'
|
||||
@@ -97,14 +97,11 @@ export function PlayerPage() {
|
||||
const [playlistEpisodes, setPlaylistEpisodes] = useState<Media[]>([])
|
||||
const [playlistOpen, setPlaylistOpen] = useState(false)
|
||||
|
||||
const teardownHls = useCallback((mediaId?: string, stopServer = false) => {
|
||||
const teardownHls = useCallback(() => {
|
||||
if (hlsRef.current) {
|
||||
hlsRef.current.destroy()
|
||||
hlsRef.current = null
|
||||
}
|
||||
if (stopServer && mediaId) {
|
||||
api.delete(`/hls/${encodeURIComponent(mediaId)}`).catch(() => undefined)
|
||||
}
|
||||
}, [])
|
||||
|
||||
const backTarget = useCallback(() => {
|
||||
@@ -251,6 +248,7 @@ export function PlayerPage() {
|
||||
if (!mediaId || !ref.current) return
|
||||
const currentMedia = mediaRef.current
|
||||
if (!currentMedia) return
|
||||
let cancelled = false
|
||||
teardownHls()
|
||||
|
||||
const video = ref.current
|
||||
@@ -258,6 +256,7 @@ export function PlayerPage() {
|
||||
if (mode === 'hls') {
|
||||
const url = hlsURL(mediaId, hlsStartSec)
|
||||
void import('hls.js').then(({ default: HlsCtor }) => {
|
||||
if (cancelled || !ref.current) return
|
||||
if (HlsCtor.isSupported()) {
|
||||
const hls = new HlsCtor({ enableWorker: true, lowLatencyMode: false })
|
||||
hls.loadSource(url)
|
||||
@@ -282,6 +281,10 @@ export function PlayerPage() {
|
||||
setParams(params, { replace: true })
|
||||
}
|
||||
})
|
||||
if (cancelled) {
|
||||
hls.destroy()
|
||||
return
|
||||
}
|
||||
hlsRef.current = hls
|
||||
} else if (video.canPlayType('application/vnd.apple.mpegurl')) {
|
||||
video.src = url
|
||||
@@ -293,6 +296,7 @@ export function PlayerPage() {
|
||||
}
|
||||
void video.play().catch(() => undefined)
|
||||
}).catch(() => {
|
||||
if (cancelled) return
|
||||
setHlsUnavailable(true)
|
||||
setPlayerError('HLS 播放组件加载失败,正在尝试直接播放。')
|
||||
setMode('direct')
|
||||
@@ -304,17 +308,30 @@ export function PlayerPage() {
|
||||
}
|
||||
void video.play().catch(() => undefined)
|
||||
}
|
||||
return () => teardownHls()
|
||||
return () => {
|
||||
cancelled = true
|
||||
teardownHls()
|
||||
}
|
||||
}, [hlsUnavailable, hlsStartSec, mediaId, mode, params, setParams, teardownHls])
|
||||
|
||||
// Stop the host ffmpeg job only when leaving HLS for this media (not on mid-file seek restarts).
|
||||
// Stop host ffmpeg when leaving this HLS player. The keepalive request also
|
||||
// survives route navigation while the component is being torn down.
|
||||
useEffect(() => {
|
||||
if (!mediaId || mode !== 'hls') return
|
||||
return () => {
|
||||
api.delete(`/hls/${encodeURIComponent(mediaId)}`).catch(() => undefined)
|
||||
stopHLSJob(mediaId)
|
||||
}
|
||||
}, [mediaId, mode])
|
||||
|
||||
// React cleanup is not guaranteed when a tab/window closes. pagehide fires
|
||||
// while the document can still dispatch a keepalive request.
|
||||
useEffect(() => {
|
||||
if (!mediaId || mode !== 'hls') return
|
||||
const stopOnPageExit = () => stopHLSJob(mediaId)
|
||||
window.addEventListener('pagehide', stopOnPageExit)
|
||||
return () => window.removeEventListener('pagehide', stopOnPageExit)
|
||||
}, [mediaId, mode])
|
||||
|
||||
// 自动拉取已有的播放进度并恢复播放位置
|
||||
useEffect(() => {
|
||||
if (!id) return
|
||||
|
||||
Reference in New Issue
Block a user