refactor(hls): [PKG-HLS-04] migrate modules and compatible types

This commit is contained in:
Harvey Zhao committed 2026-09-12 00:49:50 +08:00
1 parent ba93b98be5
commit 2f9bee4ee0
34 files changed
+4860 -88

No files matched your search

+67
View File
@@ -0,0 +1,67 @@
import fs from 'node:fs'
import process from 'node:process'
import { hash } from '../../refactor/scripts/releases.mjs'
import { compilePackage } from '../helpers/load.js'
import { expect, test } from './fixtures.js'
test('Monaco consumes HLS global types and executes the emitted plugin factory', async ({ page }, testInfo) => {
const code = process.env.ARTPLAYER_HLS_ARTIFACT
? fs.readFileSync(process.env.ARTPLAYER_HLS_ARTIFACT, 'utf8')
: await compilePackage('artplayer-plugin-hls-control', 'umd')
const declarations = Object.fromEntries(['artplayer', 'artplayer-plugin-hls-control'].map(name => [name, hash(fs.readFileSync(new URL(`../../docs/assets/ts/${name}.d.ts`, import.meta.url)))]))
const inputs = { candidate: process.env.ARTPLAYER_HLS_ARTIFACT || 'workspace source build', sha256: hash(code), declarations }
await testInfo.attach('hls-editor-inputs', { contentType: 'application/json', body: JSON.stringify(inputs) })
await page.goto('/test/player.html?core=candidate&chapter=published')
await page.addScriptTag({ content: code })
await page.addScriptTag({ url: '/assets/js/vs/loader.js' })
const result = await page.evaluate(async () => {
window.require.config({ paths: { vs: '/assets/js/vs' } })
await new Promise((resolve, reject) => window.require(['vs/editor/editor.main'], resolve, reject))
const api = window.monaco
const types = api.languages.typescript
types.typescriptDefaults.setCompilerOptions({ target: types.ScriptTarget.ES2020, strict: true, skipLibCheck: false, noEmit: false, types: [], allowNonTsExtensions: true })
const names = ['artplayer', 'artplayer-plugin-hls-control']
const libraries = await Promise.all(names.map(async (name) => {
const uri = `file:///${name}.d.ts`
const source = await (await fetch(`/assets/ts/${name}.d.ts`)).text()
return { uri, handle: types.typescriptDefaults.addExtraLib(source, uri) }
}))
const model = api.editor.createModel(`
const hlsOption: artplayerPluginHlsControl.Option = {
quality: { getName: (level, index) => level.height + ':' + index },
audio: { getName: track => track.name },
}
const oldQuality: NonNullable<Parameters<typeof artplayerPluginHlsControl>[0]['quality']> = { getName: (value: object) => String(value) }
const typedFactory: (art: Artplayer) => artplayerPluginHlsControl.Result = artplayerPluginHlsControl(hlsOption)
const defaultFactory = artplayerPluginHlsControl()
`, 'typescript', api.Uri.parse('file:///hls-consumer.ts'))
const invalid = api.editor.createModel('artplayerPluginHlsControl({ quality: { getName: level => level.height } })', 'typescript', api.Uri.parse('file:///hls-invalid.ts'))
try {
const worker = await (await types.getTypeScriptWorker())(model.uri, invalid.uri)
const syntax = await worker.getSyntacticDiagnostics(model.uri.toString())
const semantic = await worker.getSemanticDiagnostics(model.uri.toString())
const declarations = (await Promise.all(libraries.map(lib => worker.getSemanticDiagnostics(lib.uri)))).flat()
const bad = await worker.getSemanticDiagnostics(invalid.uri.toString())
if (syntax.length || semantic.length || declarations.length)
return { syntax, semantic, declarations, invalid: bad.map(item => item.code), executed: false }
const output = await worker.getEmitOutput(model.uri.toString())
const script = output.outputFiles.find(file => file.name.endsWith('.js'))
if (!script)
throw new Error('Monaco did not emit the HLS fixture')
// eslint-disable-next-line no-new-func -- Exercise the local editor Run action with its real emitted code.
const executed = new Function(`${script.text}\nreturn typeof typedFactory === 'function' && typeof defaultFactory === 'function'`)()
return { syntax, semantic, declarations, invalid: bad.map(item => item.code), executed }
}
finally {
model.dispose()
invalid.dispose()
for (const library of libraries)
library.handle.dispose()
}
})
expect(result.syntax).toEqual([])
expect(result.semantic).toEqual([])
expect(result.declarations).toEqual([])
expect(result.invalid).toEqual([2769])
expect(result.executed).toBe(true)
})
+43
View File
@@ -0,0 +1,43 @@
import type { AudioTrack, Config, Option, QualityLevel, Result } from 'artplayer-plugin-hls-control'
import Artplayer from 'artplayer'
import hlsControl from 'artplayer-plugin-hls-control'
import legacy from 'artplayer-plugin-hls-control/legacy'
const option: Option = {
quality: { control: true, getName: (level, index) => `${level.height}P ${index ?? ''}` },
audio: { setting: true, getName: track => track.name },
}
const art = new Artplayer({ container: '#player', url: 'video.m3u8', plugins: [hlsControl(), hlsControl(option), legacy()] })
const result: Result = hlsControl(option)(art)
type HistoricalOption = Parameters<typeof hlsControl>[0]
const historicalQuality: HistoricalOption['quality'] = { getName: (item: object) => String(item) }
hlsControl({ quality: historicalQuality })
hlsControl(undefined)
const done: void = result.update()
const generic: Config = { getName: (item: object) => String(item) }
hlsControl({ quality: generic, audio: generic })
const level: QualityLevel = { height: 720 }
const track: AudioTrack = { id: 0, name: 'English' }
interface CustomLevel { height: number, bitrate: number, label: string }
interface CustomTrack { id: number, name: string, groupId: string }
hlsControl({ quality: { getName: (value: CustomLevel) => value.label }, audio: { getName: (value: CustomTrack) => value.groupId } })
hlsControl<CustomLevel, CustomTrack>({ quality: { getName: value => String(value.bitrate) } })
// @ts-expect-error update stays synchronous and returns void.
const promise: Promise<void> = result.update()
// @ts-expect-error update takes no configuration argument.
result.update({})
// @ts-expect-error The result name remains a literal.
const invalidName: Result['name'] = 'hls'
// @ts-expect-error Selected-label calls do not supply the index.
hlsControl({ quality: { getName: (value: QualityLevel, index: number) => String(index) } })
// @ts-expect-error A formatter must return a string.
hlsControl({ audio: { getName: () => 123 } })
// @ts-expect-error Control visibility is boolean.
legacy({ quality: { control: 'yes' } })
// @ts-expect-error Null was never a valid options object.
hlsControl(null)
// @ts-expect-error Default quality fields are typed, not an any escape.
hlsControl({ quality: { getName: value => value.unknownProperty } })
void [done, level, track, promise, invalidName]
+9
View File
@@ -0,0 +1,9 @@
import type PublicFactory from 'artplayer-plugin-hls-control'
import factory from '../../packages/artplayer-plugin-hls-control/src/index'
const compatible: typeof PublicFactory = factory
const implementation: typeof factory = compatible
implementation({ quality: { getName: level => `${level.height}P` }, audio: { getName: track => track.name } })
compatible()
// @ts-expect-error Source and public declaration both reject non-string formatter results.
factory({ quality: { getName: () => 1 } })