mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-25 16:11:20 -08:00
refactor(core): CORE-09 isolate source operations and settle cancelled switches
This commit is contained in:
1 parent
874dac9f22
commit
44f4e083a9
29 files changed
+4512
-255
No files matched your search
@@ -1,5 +1,6 @@
|
||||
import type { PlayHost } from '../media/hosts'
|
||||
import type { PlaybackMethods } from '../media/types'
|
||||
import { captureSource } from '../source/operation'
|
||||
import { def } from '../utils'
|
||||
|
||||
export default function playMix<Media extends Pick<PlaybackMethods, 'play'>>(art: PlayHost<Media>): asserts art is PlayHost<Media> & { play: () => Promise<Awaited<ReturnType<Media['play']>>> } {
|
||||
@@ -13,12 +14,22 @@ export default function playMix<Media extends Pick<PlaybackMethods, 'play'>>(art
|
||||
|
||||
def(art, 'play', {
|
||||
async value() {
|
||||
const active = captureSource(art)
|
||||
const result = await $video.play()
|
||||
notice.show = i18n.get('Play')
|
||||
if (!active())
|
||||
return result
|
||||
const message = i18n.get('Play')
|
||||
if (!active())
|
||||
return result
|
||||
notice.show = message
|
||||
if (!active())
|
||||
return result
|
||||
art.emit('play')
|
||||
|
||||
if (option.mutex) {
|
||||
for (let index = 0; index < instances.length; index++) {
|
||||
if (!active())
|
||||
break
|
||||
const instance = instances[index]!
|
||||
if (instance !== art) {
|
||||
instance.pause()
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
import { def, silencePromise } from '../utils'
|
||||
|
||||
export default function switchMix(art) {
|
||||
function switchUrl(url, currentTime) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (url === art.url) {
|
||||
resolve()
|
||||
return
|
||||
}
|
||||
const { playing, aspectRatio, playbackRate } = art
|
||||
|
||||
art.pause()
|
||||
art.url = url
|
||||
art.notice.show = ''
|
||||
|
||||
const handlers = {}
|
||||
|
||||
handlers.error = (error) => {
|
||||
art.off('video:canplay', handlers.canplay)
|
||||
art.off('video:loadedmetadata', handlers.metadata)
|
||||
reject(error)
|
||||
}
|
||||
|
||||
handlers.metadata = () => {
|
||||
art.currentTime = currentTime
|
||||
}
|
||||
|
||||
handlers.canplay = async () => {
|
||||
art.off('video:error', handlers.error)
|
||||
art.playbackRate = playbackRate
|
||||
art.aspectRatio = aspectRatio
|
||||
|
||||
if (playing) {
|
||||
await silencePromise(art.play())
|
||||
}
|
||||
|
||||
art.notice.show = ''
|
||||
|
||||
resolve()
|
||||
}
|
||||
|
||||
art.once('video:error', handlers.error)
|
||||
art.once('video:loadedmetadata', handlers.metadata)
|
||||
art.once('video:canplay', handlers.canplay)
|
||||
})
|
||||
}
|
||||
|
||||
def(art, 'switchQuality', {
|
||||
value: (url) => {
|
||||
return switchUrl(url, art.currentTime)
|
||||
},
|
||||
})
|
||||
|
||||
def(art, 'switchUrl', {
|
||||
value: (url) => {
|
||||
return switchUrl(url, 0)
|
||||
},
|
||||
})
|
||||
|
||||
def(art, 'switch', {
|
||||
set: art.switchUrl,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import type { SwitchHost, SwitchMethods } from '../source/types'
|
||||
import { switchSource } from '../source/switch'
|
||||
import { def } from '../utils'
|
||||
|
||||
export default function switchMix(art: SwitchHost): asserts art is SwitchHost & SwitchMethods {
|
||||
const switchUrl = (url: string) => switchSource(art, url, 0)
|
||||
def(art, 'switchQuality', { value: (url: string) => switchSource(art, url, art.currentTime) })
|
||||
def(art, 'switchUrl', { value: switchUrl })
|
||||
def(art, 'switch', { set: switchUrl })
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
import { getScope } from '../lifecycle/instance'
|
||||
import { wait } from '../lifecycle/resources'
|
||||
import { def, getExt } from '../utils'
|
||||
|
||||
export default function urlMix(art) {
|
||||
const {
|
||||
option,
|
||||
template: { $video },
|
||||
} = art
|
||||
|
||||
def(art, 'url', {
|
||||
get() {
|
||||
return $video.src
|
||||
},
|
||||
async set(newUrl) {
|
||||
if (newUrl) {
|
||||
const oldUrl = art.url
|
||||
const typeName = option.type || getExt(newUrl)
|
||||
const typeCallback = option.customType[typeName]
|
||||
|
||||
if (typeName && typeCallback) {
|
||||
if (!await wait(getScope(art)) || getScope(art).closed)
|
||||
return
|
||||
art.loading.show = true
|
||||
typeCallback.call(art, $video, newUrl, art)
|
||||
}
|
||||
else {
|
||||
URL.revokeObjectURL(oldUrl)
|
||||
$video.src = newUrl
|
||||
}
|
||||
|
||||
if (oldUrl !== art.url) {
|
||||
art.option.url = newUrl
|
||||
if (art.isReady && oldUrl) {
|
||||
art.once('video:canplay', () => {
|
||||
art.emit('restart', newUrl)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (!await wait(getScope(art)) || getScope(art).closed)
|
||||
return
|
||||
art.loading.show = true
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
import type { SourceMedia, UrlHost } from '../source/types'
|
||||
import { isClosing } from '../lifecycle/instance'
|
||||
import { wait } from '../lifecycle/resources'
|
||||
import { listenSource } from '../source/listen'
|
||||
import { failSource, finishAssignment, takeAssignment } from '../source/operation'
|
||||
import { def, getExt } from '../utils'
|
||||
|
||||
export default function urlMix<Media extends SourceMedia, Host extends UrlHost<Media, Host>>(art: Host): void {
|
||||
const { option, template: { $video } } = art
|
||||
def(art, 'url', {
|
||||
get() {
|
||||
return $video.src
|
||||
},
|
||||
async set(newUrl: string) {
|
||||
if (isClosing(art))
|
||||
return
|
||||
const operation = takeAssignment(art)
|
||||
operation.acceptingEvents = false
|
||||
try {
|
||||
if (!newUrl) {
|
||||
if (await wait(operation.scope) && operation.active())
|
||||
art.loading.show = true
|
||||
return
|
||||
}
|
||||
const oldUrl = art.url
|
||||
const typeName = option.type || getExt(newUrl)
|
||||
const typeCallback = option.customType[typeName]
|
||||
if (typeName && typeCallback) {
|
||||
if (!await wait(operation.scope) || !operation.active())
|
||||
return
|
||||
art.loading.show = true
|
||||
}
|
||||
if (!operation.active())
|
||||
return
|
||||
// Proxies can synchronously dispatch readiness from their src setter.
|
||||
const capture = operation.scope.child()
|
||||
let ready = false
|
||||
let failed = false
|
||||
listenSource(capture, art, 'video:canplay', () => {
|
||||
ready = !failed
|
||||
})
|
||||
listenSource(capture, art, 'video:error', () => {
|
||||
failed = true
|
||||
})
|
||||
try {
|
||||
operation.acceptingEvents = true
|
||||
if (typeName && typeCallback) {
|
||||
const result = typeCallback.call(art, $video, newUrl, art)
|
||||
// Preserve immediate customType invocation semantics while owning rejections.
|
||||
void Promise.resolve(result).catch(error => failSource(operation, error))
|
||||
}
|
||||
else {
|
||||
if (oldUrl)
|
||||
URL.revokeObjectURL(oldUrl)
|
||||
if (!operation.active())
|
||||
return
|
||||
$video.src = newUrl
|
||||
}
|
||||
}
|
||||
finally {
|
||||
capture.dispose()
|
||||
}
|
||||
if (!operation.active())
|
||||
return
|
||||
if (oldUrl !== art.url) {
|
||||
art.option.url = newUrl
|
||||
if (operation.active() && art.isReady && oldUrl) {
|
||||
const restart = () => {
|
||||
if (operation.active())
|
||||
art.emit('restart', newUrl)
|
||||
}
|
||||
if (ready)
|
||||
restart()
|
||||
else if (!failed)
|
||||
listenSource(operation.scope, art, 'video:canplay', restart)
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
failSource(operation, error)
|
||||
}
|
||||
finally {
|
||||
finishAssignment(operation)
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
Reference in new issue
Block a user