refactor(core): CORE-05 separate typed defaults and resolved options

This commit is contained in:
Harvey Zhao committed 2026-09-11 01:00:15 +08:00
1 parent 4eec8112fa
commit 97d320bd74
27 files changed
+2278 -240

No files matched your search

+4 -74
View File
@@ -13,6 +13,8 @@ import { beginLifecycle, destroyInstance, finishLifecycle, getScope } from './li
import Loading from './loading'
import Mask from './mask'
import Notice from './notice'
import createDefaults from './option/defaults'
import resolveOption from './option/resolve'
import Player from './player'
import Plugins from './plugins'
import scheme from './scheme'
@@ -36,10 +38,7 @@ export default class Artplayer extends Emitter {
this.id = ++id
const mergeOption = utils.mergeDeep(Artplayer.option, option)
mergeOption.container = option.container
this.option = validator(mergeOption, scheme)
this.option = resolveOption(option, Artplayer.option)
this.isLock = false
this.isReady = false
@@ -135,76 +134,7 @@ export default class Artplayer extends Emitter {
}
static get option() {
return {
id: '',
container: '#artplayer',
url: '',
poster: '',
type: '',
theme: '#f00',
volume: 0.7,
isLive: false,
muted: false,
autoplay: false,
autoSize: false,
autoMini: false,
loop: false,
flip: false,
playbackRate: false,
aspectRatio: false,
screenshot: false,
setting: false,
hotkey: true,
pip: false,
mutex: true,
backdrop: true,
fullscreen: false,
fullscreenWeb: false,
subtitleOffset: false,
miniProgressBar: false,
useSSR: false,
playsInline: true,
lock: false,
gesture: true,
fastForward: false,
autoPlayback: false,
autoOrientation: false,
airplay: false,
proxy: undefined,
layers: [],
contextmenu: [],
controls: [],
settings: [],
quality: [],
highlight: [],
plugins: [],
thumbnails: {
url: '',
number: 60,
column: 10,
width: 0,
height: 0,
scale: 1,
},
subtitle: {
url: '',
type: '',
style: {},
name: '',
escape: true,
encoding: 'utf-8',
onVttLoad: vtt => vtt,
},
moreVideoAttr: {
controls: false,
preload: utils.isSafari ? 'auto' : 'metadata',
},
i18n: {},
icons: {},
cssVar: {},
customType: {},
lang: navigator?.language.toLowerCase(),
}
return createDefaults()
}
get proxy() {
+75
View File
@@ -0,0 +1,75 @@
import type { ResolvedOption } from './types'
import { isSafari } from '../utils/compatibility'
export default function createDefaults(): ResolvedOption {
return {
id: '',
container: '#artplayer',
url: '',
poster: '',
type: '',
theme: '#f00',
volume: 0.7,
isLive: false,
muted: false,
autoplay: false,
autoSize: false,
autoMini: false,
loop: false,
flip: false,
playbackRate: false,
aspectRatio: false,
screenshot: false,
setting: false,
hotkey: true,
pip: false,
mutex: true,
backdrop: true,
fullscreen: false,
fullscreenWeb: false,
subtitleOffset: false,
miniProgressBar: false,
useSSR: false,
playsInline: true,
lock: false,
gesture: true,
fastForward: false,
autoPlayback: false,
autoOrientation: false,
airplay: false,
proxy: undefined,
layers: [],
contextmenu: [],
controls: [],
settings: [],
quality: [],
highlight: [],
plugins: [],
thumbnails: {
url: '',
number: 60,
column: 10,
width: 0,
height: 0,
scale: 1,
},
subtitle: {
url: '',
type: '',
style: {},
name: '',
escape: true,
encoding: 'utf-8',
onVttLoad: vtt => vtt,
},
moreVideoAttr: {
controls: false,
preload: isSafari ? 'auto' : 'metadata',
},
i18n: {},
icons: {},
cssVar: {},
customType: {},
lang: navigator?.language.toLowerCase(),
}
}
+11
View File
@@ -0,0 +1,11 @@
import type { Option, ResolvedInput, ResolvedOption } from './types'
import validator from 'option-validator'
import scheme from '../scheme'
import { mergeDeep } from '../utils/property'
export default function resolveOption<Input extends Option>(input: Input, defaults: ResolvedOption): ResolvedInput<Input> {
const merged = mergeDeep(defaults, input)
merged.container = input.container
// Keep the existing runtime validator. It checks the known schema, not extension data.
return validator(merged, scheme) as ResolvedInput<Input>
}
+13
View File
@@ -0,0 +1,13 @@
import type { Option, Thumbnails } from '../../types/option'
import type { Subtitle } from '../../types/subtitle'
export type { Option } from '../../types/option'
// A successfully merged typed input, not a general guard for arbitrary JS objects.
export interface ResolvedOption extends Omit<Required<Option>, 'proxy' | 'subtitle' | 'thumbnails'> {
proxy: Option['proxy']
subtitle: Required<Subtitle>
thumbnails: Required<Thumbnails>
}
export type ResolvedInput<Input extends Option> = ResolvedOption & Omit<Input, keyof Option>
@@ -1,3 +1,5 @@
import type { Scheme, ValidatorPath } from 'option-validator'
import type { Option } from '../option/types'
import { errorHandle } from '../utils'
const a = 'array'
@@ -7,7 +9,7 @@ const n = 'number'
const o = 'object'
const f = 'function'
function validElement(value, type, paths) {
function validElement(value: unknown, type: string, paths: ValidatorPath) {
return errorHandle(
type === s || type === n || value instanceof Element,
`${paths.join('.')} require '${s}' or 'Element' type`,
@@ -33,7 +35,7 @@ export const ComponentOption = {
onChange: `?${f}`,
}
export default {
const scheme = {
id: s,
container: validElement,
url: s,
@@ -77,10 +79,10 @@ export default {
controls: [
{
...ComponentOption,
position: (value, _, paths) => {
position: (value: unknown, _: string, paths: ValidatorPath) => {
const position = ['top', 'left', 'right']
return errorHandle(
position.includes(value),
typeof value === 'string' && position.includes(value),
`${paths.join('.')} only accept ${position.toString()} as parameters`,
)
},
@@ -121,4 +123,6 @@ export default {
icons: o,
cssVar: o,
customType: o,
}
} satisfies Record<keyof Option, Scheme>
export default scheme