fix(dash): [PKG-DASH-SEEK-01] recover paused boundary seeks in SDK 4.5.2

This commit is contained in:
Harvey Zhao committed 2026-09-14 20:19:04 +08:00
1 parent 442e1dcc96
commit 30f646be97
22 files changed
+1725 -57

No files matched your search

@@ -1,4 +1,5 @@
import type { Cleanup, Dash, SDKEventName } from './types'
import { createSeekRecovery } from './seek-buffer'
interface Binding<Level extends object, Track extends object> {
dash: Dash<Level, Track>
@@ -108,6 +109,9 @@ export function observeSDK<Level extends object, Track extends object>(options:
record.suspended = false
schedule(record)
}])
const recoverSeek = createSeekRecovery(dash, () => active(record) && !record.suspended)
if (recoverSeek)
entries.push(['playbackSeeking', recoverSeek])
entries.push(['playbackTimeUpdated', () => {
if (!active(record) || record.suspended)
return
@@ -0,0 +1,85 @@
import type { Cleanup, Valid } from './types'
type MediaType = 'audio' | 'video'
interface BufferController {
getRangeAt?: (time: number) => { end: number } | null
getIsPruningInProgress?: () => boolean
getAllRangesWithSafetyFactor?: (time: number) => unknown[]
}
interface Stream {
getProcessors?: () => {
getType?: () => string
getBufferController?: () => BufferController | null
}[]
}
interface LegacySDK {
getVersion?: () => string
getVideoElement?: () => { currentTime: number, seeking: boolean } | null
getActiveStream?: () => Stream | null
getDashMetrics?: () => {
getCurrentBufferLevel?: (type: MediaType) => number
addBufferLevel?: (type: MediaType, at: Date, level: 0) => void
} | null
}
export function createSeekRecovery(value: object, active: Valid): Cleanup | undefined {
const sdk = value as LegacySDK
try {
if (typeof sdk.getVersion !== 'function' || sdk.getVersion() !== '4.5.2')
return
if (typeof sdk.getVideoElement !== 'function' || typeof sdk.getActiveStream !== 'function' || typeof sdk.getDashMetrics !== 'function')
return
}
catch {
return
}
let running = false
return () => {
if (running || !active())
return
running = true
try {
const video = sdk.getVideoElement?.()
if (!video?.seeking || !Number.isFinite(video.currentTime))
return
const time = video.currentTime
const stream = sdk.getActiveStream?.()
const metrics = sdk.getDashMetrics?.()
const processors = stream?.getProcessors?.()
if (!Array.isArray(processors) || typeof metrics?.getCurrentBufferLevel !== 'function' || typeof metrics.addBufferLevel !== 'function')
return
for (const processor of processors) {
if (!active())
return
const type = processor.getType?.()
if (type !== 'audio' && type !== 'video')
continue
const buffer = processor.getBufferController?.()
if (typeof buffer?.getRangeAt !== 'function' || typeof buffer.getIsPruningInProgress !== 'function' || typeof buffer.getAllRangesWithSafetyFactor !== 'function')
continue
const cached = metrics.getCurrentBufferLevel(type)
const range = buffer.getRangeAt(time)
const empty = range === null || (range && Number.isFinite(range.end) && range.end <= time)
if (!Number.isFinite(cached) || cached <= 0 || !empty || buffer.getIsPruningInProgress() !== false)
continue
const removals = buffer.getAllRangesWithSafetyFactor(time)
if (!Array.isArray(removals) || removals.length !== 0)
continue
if (!active() || sdk.getActiveStream?.() !== stream || sdk.getDashMetrics?.() !== metrics || sdk.getVideoElement?.() !== video || video.currentTime !== time || !video.seeking || !active())
return
// SDK 4.5.2 schedules from metric history, not BufferController's getter.
// Record the measured empty range; leave buffers, media events and time intact.
metrics.addBufferLevel(type, new Date(), 0)
}
}
catch (error) {
console.warn('ArtPlayer DASH seek buffer refresh failed:', error)
}
finally {
running = false
}
}
}
@@ -5,7 +5,7 @@ export type Identifier = string | number
export type Valid = () => boolean
export type Cleanup = () => void
export type EventName = 'ready' | 'restart' | 'destroy'
export type SDKEventName = 'qualityChangeRequested' | 'qualityChangeRendered' | 'trackChangeRendered' | 'streamUpdated' | 'streamInitialized' | 'playbackTimeUpdated' | 'streamTeardownComplete'
export type SDKEventName = 'qualityChangeRequested' | 'qualityChangeRendered' | 'trackChangeRendered' | 'streamUpdated' | 'streamInitialized' | 'playbackTimeUpdated' | 'playbackSeeking' | 'streamTeardownComplete'
export interface QualityFields {
height?: number