mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-21 04:31:20 -08:00
refactor(core): CORE-19 type capture and preserve progress lifetimes
This commit is contained in:
1 parent
98a1800032
commit
ccf77c4ed2
49 files changed
+31570
-681
No files matched your search
@@ -0,0 +1,49 @@
|
||||
import type { SwitchHost } from './types'
|
||||
import { positionRevision } from '../media/position-revision'
|
||||
|
||||
// Native media clocks may round within a frame. Larger misses need one correction.
|
||||
const POSITION_TOLERANCE = 0.05
|
||||
|
||||
export function positionRestoration(art: Pick<SwitchHost, 'currentTime' | 'template'>, target: number, active: () => boolean) {
|
||||
let expected: number | undefined
|
||||
let observedSeek = false
|
||||
let corrected = false
|
||||
let manual = false
|
||||
let revision = positionRevision(art)
|
||||
const manualPosition = () => manual || positionRevision(art) !== revision
|
||||
const seeking = () => !!art.template?.$video?.seeking
|
||||
return {
|
||||
restore(): void {
|
||||
if (!active() || manualPosition())
|
||||
return
|
||||
const previousRevision = positionRevision(art)
|
||||
art.currentTime = target
|
||||
revision = positionRevision(art)
|
||||
// Nested public writes supersede the restoration's own setter call.
|
||||
if (revision > previousRevision + 1)
|
||||
manual = true
|
||||
if (!active())
|
||||
return
|
||||
// Keep the actual clamped value accepted by the player setter.
|
||||
expected = art.currentTime
|
||||
if (!active() || manualPosition())
|
||||
return
|
||||
observedSeek = seeking()
|
||||
},
|
||||
manual(): void {
|
||||
manual = true
|
||||
},
|
||||
ready(): boolean {
|
||||
if (!active() || seeking())
|
||||
return false
|
||||
const missed = expected !== undefined && Math.abs(art.currentTime - expected) > POSITION_TOLERANCE
|
||||
if (!manualPosition() && observedSeek && !corrected && expected !== undefined && missed) {
|
||||
corrected = true
|
||||
if (!active())
|
||||
return false
|
||||
art.currentTime = expected
|
||||
}
|
||||
return active() && !seeking()
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import type { SourceEvent, SourceListener, SwitchHost } from './types'
|
||||
import { isClosing } from '../lifecycle/instance'
|
||||
import { listenSource } from './listen'
|
||||
import { assignUrl, beginSource } from './operation'
|
||||
import { positionRestoration } from './restore-position'
|
||||
|
||||
export function switchSource(art: SwitchHost, url: string, currentTime: number): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
@@ -39,44 +40,67 @@ export function switchSource(art: SwitchHost, url: string, currentTime: number):
|
||||
return
|
||||
|
||||
const readiness = scope.child()
|
||||
let canPlay = false
|
||||
let resuming = false
|
||||
let rateRestored = false
|
||||
const position = positionRestoration(art, currentTime, active)
|
||||
const resume = async () => {
|
||||
art.aspectRatio = aspectRatio
|
||||
if (!active())
|
||||
return
|
||||
if (playing) {
|
||||
// The public play promise still rejects; this internal resume is best effort.
|
||||
try {
|
||||
await art.play()
|
||||
}
|
||||
catch {}
|
||||
}
|
||||
if (!active())
|
||||
return
|
||||
art.notice.show = ''
|
||||
settle()
|
||||
}
|
||||
const resumeWhenReady = () => {
|
||||
if (!active() || resuming || !canPlay)
|
||||
return
|
||||
try {
|
||||
// canplay can arrive before the metadata restoration seek has settled.
|
||||
if (art.template?.$video?.seeking)
|
||||
return
|
||||
if (!active())
|
||||
return
|
||||
if (!rateRestored) {
|
||||
rateRestored = true
|
||||
art.playbackRate = playbackRate
|
||||
}
|
||||
if (!position.ready() || !active() || resuming)
|
||||
return
|
||||
resuming = true
|
||||
readiness.dispose()
|
||||
void resume().catch(fail)
|
||||
}
|
||||
catch (error) {
|
||||
fail(error)
|
||||
}
|
||||
}
|
||||
const handlers = {
|
||||
'video:error': fail,
|
||||
'video:loadedmetadata': () => {
|
||||
if (!active())
|
||||
return
|
||||
try {
|
||||
art.currentTime = currentTime
|
||||
position.restore()
|
||||
}
|
||||
catch (error) {
|
||||
fail(error)
|
||||
}
|
||||
},
|
||||
'video:canplay': () => {
|
||||
if (!active())
|
||||
return
|
||||
// Detach readiness handlers before awaiting a possibly pending play.
|
||||
readiness.dispose()
|
||||
const resume = async () => {
|
||||
art.playbackRate = playbackRate
|
||||
if (!active())
|
||||
return
|
||||
art.aspectRatio = aspectRatio
|
||||
if (!active())
|
||||
return
|
||||
if (playing) {
|
||||
// The public play promise still rejects; this internal resume is best effort.
|
||||
try {
|
||||
await art.play()
|
||||
}
|
||||
catch {}
|
||||
}
|
||||
if (!active())
|
||||
return
|
||||
art.notice.show = ''
|
||||
settle()
|
||||
}
|
||||
void resume().catch(fail)
|
||||
canPlay = true
|
||||
resumeWhenReady()
|
||||
},
|
||||
'video:seeked': resumeWhenReady,
|
||||
'seek': position.manual,
|
||||
}
|
||||
const capture = scope.child()
|
||||
const queued: [SourceEvent, unknown][] = []
|
||||
@@ -101,7 +125,7 @@ export function switchSource(art: SwitchHost, url: string, currentTime: number):
|
||||
}
|
||||
const dispatch = {} as Record<SourceEvent, SourceListener>
|
||||
for (const name of names)
|
||||
dispatch[name] = listenSource(readiness, art, name, handlers[name])
|
||||
dispatch[name] = listenSource(readiness, art, name, handlers[name], name !== 'video:seeked')
|
||||
for (const [name, event] of queued) {
|
||||
if (!active() || readiness.closed)
|
||||
break
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export type SourceEvent = 'video:error' | 'video:loadedmetadata' | 'video:canplay'
|
||||
export type SourceEvent = 'video:error' | 'video:loadedmetadata' | 'video:canplay' | 'video:seeked' | 'seek'
|
||||
export type SourceListener = (event?: unknown) => void
|
||||
|
||||
export interface SourceEvents {
|
||||
@@ -28,6 +28,7 @@ export interface UrlHost<Media extends SourceMedia, Host> extends UrlTarget, Sou
|
||||
}
|
||||
|
||||
export interface SwitchHost extends UrlTarget, SourceEvents {
|
||||
template?: { $video?: { readonly seeking?: boolean } }
|
||||
currentTime: number
|
||||
readonly playing: boolean
|
||||
aspectRatio: string
|
||||
|
||||
Reference in new issue
Block a user