mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-09 11:56:15 -08:00
refactor(jassub): [PKG-JASSUB-04] migrate owned TypeScript and preserve public types
This commit is contained in:
1 parent
38e7b37878
commit
5e445b1af6
33 files changed
+3636
-61
No files matched your search
+37
-34
@@ -1,38 +1,41 @@
|
||||
export interface JassubOption {
|
||||
workerUrl: string
|
||||
wasmUrl: string
|
||||
modernWasmUrl: string
|
||||
subUrl?: string
|
||||
subContent?: string
|
||||
timeOffset?: number
|
||||
debug?: boolean
|
||||
prescaleFactor?: number
|
||||
prescaleHeightLimit?: number
|
||||
maxRenderHeight?: number
|
||||
fonts?: string[] | Uint8Array[]
|
||||
availableFonts?: Record<string, Uint8Array | string>
|
||||
fallbackFont?: string
|
||||
useLocalFonts?: boolean
|
||||
libassMemoryLimit?: number
|
||||
libassGlyphLimit?: number
|
||||
[key: string]: any
|
||||
// Generated from the package public declaration by yarn build:ts. Do not edit.
|
||||
/* eslint-disable ts/no-redeclare -- Callable and public type namespace intentionally merge. */
|
||||
declare namespace artplayerPluginJassubDefinitions {
|
||||
export interface JassubOption {
|
||||
workerUrl: string
|
||||
wasmUrl: string
|
||||
modernWasmUrl: string
|
||||
subUrl?: string
|
||||
subContent?: string
|
||||
timeOffset?: number
|
||||
debug?: boolean
|
||||
prescaleFactor?: number
|
||||
prescaleHeightLimit?: number
|
||||
maxRenderHeight?: number
|
||||
fonts?: string[] | Uint8Array[]
|
||||
availableFonts?: Record<string, Uint8Array | string>
|
||||
fallbackFont?: string
|
||||
useLocalFonts?: boolean
|
||||
libassMemoryLimit?: number
|
||||
libassGlyphLimit?: number
|
||||
[key: string]: any
|
||||
}
|
||||
export interface JassubInstance {
|
||||
resize: (force?: boolean, width?: number, height?: number, top?: number, left?: number) => Promise<void>
|
||||
setVideo: (video: HTMLVideoElement) => Promise<void>
|
||||
destroy: () => Promise<void>
|
||||
[key: string]: any
|
||||
}
|
||||
export interface Result {
|
||||
name: 'artplayerPluginJassub'
|
||||
instance: JassubInstance
|
||||
}
|
||||
export const artplayerPluginJassub: (option: JassubOption) => (art: Artplayer) => Result
|
||||
}
|
||||
|
||||
export interface JassubInstance {
|
||||
resize: (force?: boolean, width?: number, height?: number, top?: number, left?: number) => Promise<void>
|
||||
setVideo: (video: HTMLVideoElement) => Promise<void>
|
||||
destroy: () => Promise<void>
|
||||
[key: string]: any
|
||||
declare const artplayerPluginJassub: typeof artplayerPluginJassubDefinitions.artplayerPluginJassub
|
||||
declare namespace artplayerPluginJassub {
|
||||
export type JassubOption = artplayerPluginJassubDefinitions.JassubOption
|
||||
export type JassubInstance = artplayerPluginJassubDefinitions.JassubInstance
|
||||
}
|
||||
|
||||
interface Result {
|
||||
name: 'artplayerPluginJassub'
|
||||
instance: JassubInstance
|
||||
}
|
||||
|
||||
declare const artplayerPluginJassub: (option: JassubOption) => (art: Artplayer) => Result
|
||||
|
||||
export default artplayerPluginJassub
|
||||
|
||||
export = artplayerPluginJassub
|
||||
export as namespace artplayerPluginJassub;
|
||||
+4
-2
@@ -51,7 +51,7 @@
|
||||
"ci:build": "yarn build:types && yarn build all && yarn build:i18n && yarn build:ts && yarn build:docs && yarn test:imports",
|
||||
"test:imports": "node --test test/esm.test.js test/i18n.test.js test/ssr.test.js test/asr-distribution.test.js",
|
||||
"typecheck": "node scripts/typecheck.mjs",
|
||||
"test:unit": "node --test test/danmuku-setting.test.js test/danmuku-heatmap.test.js test/danmuku-renderer.test.js test/danmuku-scheduler.test.js test/danmuku-worker-client.test.js test/danmuku-input.test.js test/danmuku-parser.test.js test/danmuku-failures.test.js test/danmuku-mask-failures.test.js test/danmuku-mask-lifecycle.test.js test/asr-encoding.test.js test/asr-lifecycle.test.js test/asr-audio.test.js test/asr.test.js test/jassub.test.js test/jassub-registration.test.js test/multiple-subtitles-merge.test.js test/multiple-subtitles-lifecycle.test.js test/multiple-subtitles-failures.test.js test/multiple-subtitles.test.js test/multiple-subtitles-vendor.test.js test/vtt-thumbnail-parser.test.js test/vtt-thumbnail-lifecycle.test.js test/vtt-thumbnail.test.js test/auto-thumbnail-frames.test.js test/auto-thumbnail-lifecycle.test.js test/auto-thumbnail.test.js test/thumbnail-emitter.test.js test/thumbnail-runtime.test.js test/thumbnail-vendor.test.js test/thumbnail-lifecycle.test.js test/thumbnail-input.test.js test/thumbnail.test.js test/iframe-navigation.test.js test/iframe-boundaries.test.js test/iframe-lifecycle.test.js test/iframe.test.js test/mediabunny.test.js test/mediabunny-shim.test.js test/mediabunny-coordination.test.js test/mediabunny-video.test.js test/mediabunny-audio.test.js test/mediabunny-hls.test.js test/mediabunny-entry.test.js test/mediabunny-capability.test.js test/mediabunny-load.test.js test/mediabunny-input.test.js test/dpip.test.js test/dpip-lifecycle.test.js test/canvas.test.js test/canvas-lifecycle.test.js test/ambilight.test.js test/ambilight-lifecycle.test.js test/ambilight-proxy.test.js test/vast.test.js test/vast-lifecycle.test.js test/ads.test.js test/ads-lifecycle.test.js test/playback.test.js test/dash-control.test.js test/dash-contract.test.js test/dash-lifecycle.test.js test/dash-events.test.js test/hls-control.test.js test/audio-track.test.js test/public-behavior.test.js test/helpers.test.js test/chapter.test.js test/utils.test.js test/resource-scope.test.js test/instance-lifecycle.test.js test/options.test.js test/media-hosts.test.js test/plugins.test.js test/source.test.js test/playback-properties.test.js test/media-events.test.js test/template-resources.test.js test/core-vendor.test.js test/component-resources.test.js test/setting-model.test.js test/setting-layout.test.js test/setting-resources.test.js test/subtitle.test.js test/display-native.test.js test/display-video-fullscreen.test.js test/display-pip.test.js test/display-mini.test.js test/display-sizing.test.js test/display-orientation.test.js test/hotkey.test.js test/listener-registry.test.js test/global-events.test.js test/pointer-events.test.js test/gesture.test.js test/event-scheduling.test.js test/notice.test.js test/fast-forward.test.js test/auto-playback.test.js test/builtin-layers.test.js test/prompt-components.test.js test/screenshot.test.js test/thumbnails.test.js test/progress.test.js test/environment.test.js test/storage.test.js test/facade-properties.test.js test/dom-boundaries.test.js test/initialization.test.js test/entry.test.js test/accessibility-button.test.js test/accessibility-focus.test.js test/accessibility-slider.test.js test/asr-fallback-routing.test.js test/asr-local-example.test.js test/asr-explicit-capture.test.js test/chromecast.test.js test/chromecast-failures.test.js test/chromecast-runtime.test.js",
|
||||
"test:unit": "node --test test/danmuku-setting.test.js test/danmuku-heatmap.test.js test/danmuku-renderer.test.js test/danmuku-scheduler.test.js test/danmuku-worker-client.test.js test/danmuku-input.test.js test/danmuku-parser.test.js test/danmuku-failures.test.js test/danmuku-mask-failures.test.js test/danmuku-mask-lifecycle.test.js test/asr-encoding.test.js test/asr-lifecycle.test.js test/asr-audio.test.js test/asr.test.js test/jassub.test.js test/jassub-registration.test.js test/jassub-runtime.test.js test/multiple-subtitles-merge.test.js test/multiple-subtitles-lifecycle.test.js test/multiple-subtitles-failures.test.js test/multiple-subtitles.test.js test/multiple-subtitles-vendor.test.js test/vtt-thumbnail-parser.test.js test/vtt-thumbnail-lifecycle.test.js test/vtt-thumbnail.test.js test/auto-thumbnail-frames.test.js test/auto-thumbnail-lifecycle.test.js test/auto-thumbnail.test.js test/thumbnail-emitter.test.js test/thumbnail-runtime.test.js test/thumbnail-vendor.test.js test/thumbnail-lifecycle.test.js test/thumbnail-input.test.js test/thumbnail.test.js test/iframe-navigation.test.js test/iframe-boundaries.test.js test/iframe-lifecycle.test.js test/iframe.test.js test/mediabunny.test.js test/mediabunny-shim.test.js test/mediabunny-coordination.test.js test/mediabunny-video.test.js test/mediabunny-audio.test.js test/mediabunny-hls.test.js test/mediabunny-entry.test.js test/mediabunny-capability.test.js test/mediabunny-load.test.js test/mediabunny-input.test.js test/dpip.test.js test/dpip-lifecycle.test.js test/canvas.test.js test/canvas-lifecycle.test.js test/ambilight.test.js test/ambilight-lifecycle.test.js test/ambilight-proxy.test.js test/vast.test.js test/vast-lifecycle.test.js test/ads.test.js test/ads-lifecycle.test.js test/playback.test.js test/dash-control.test.js test/dash-contract.test.js test/dash-lifecycle.test.js test/dash-events.test.js test/hls-control.test.js test/audio-track.test.js test/public-behavior.test.js test/helpers.test.js test/chapter.test.js test/utils.test.js test/resource-scope.test.js test/instance-lifecycle.test.js test/options.test.js test/media-hosts.test.js test/plugins.test.js test/source.test.js test/playback-properties.test.js test/media-events.test.js test/template-resources.test.js test/core-vendor.test.js test/component-resources.test.js test/setting-model.test.js test/setting-layout.test.js test/setting-resources.test.js test/subtitle.test.js test/display-native.test.js test/display-video-fullscreen.test.js test/display-pip.test.js test/display-mini.test.js test/display-sizing.test.js test/display-orientation.test.js test/hotkey.test.js test/listener-registry.test.js test/global-events.test.js test/pointer-events.test.js test/gesture.test.js test/event-scheduling.test.js test/notice.test.js test/fast-forward.test.js test/auto-playback.test.js test/builtin-layers.test.js test/prompt-components.test.js test/screenshot.test.js test/thumbnails.test.js test/progress.test.js test/environment.test.js test/storage.test.js test/facade-properties.test.js test/dom-boundaries.test.js test/initialization.test.js test/entry.test.js test/accessibility-button.test.js test/accessibility-focus.test.js test/accessibility-slider.test.js test/asr-fallback-routing.test.js test/asr-local-example.test.js test/asr-explicit-capture.test.js test/chromecast.test.js test/chromecast-failures.test.js test/chromecast-runtime.test.js",
|
||||
"test:coverage": "node --test test/coverage.test.js && node scripts/coverage.mjs",
|
||||
"test": "yarn test:node && yarn test:baseline",
|
||||
"test:browser": "playwright test",
|
||||
@@ -90,7 +90,9 @@
|
||||
"test:vtt-thumbnail-types-package": "node refactor/scripts/vtt-thumbnail-package-types.mjs",
|
||||
"test:multiple-subtitles": "node --test test/multiple-subtitles-merge.test.js test/multiple-subtitles-lifecycle.test.js test/multiple-subtitles-failures.test.js test/multiple-subtitles.test.js test/multiple-subtitles-vendor.test.js refactor/scripts/multiple-subtitles-contract.test.mjs refactor/scripts/multiple-subtitles-runtime-types.test.mjs refactor/scripts/multiple-subtitles-types.test.mjs",
|
||||
"test:multiple-subtitles-types-package": "node refactor/scripts/multiple-subtitles-package-types.mjs",
|
||||
"test:jassub": "node --test test/jassub.test.js test/jassub-registration.test.js refactor/scripts/jassub-contract.test.mjs refactor/scripts/jassub-provenance.test.mjs refactor/scripts/jassub-failures.test.mjs",
|
||||
"test:jassub": "node --test test/jassub.test.js test/jassub-registration.test.js test/jassub-runtime.test.js refactor/scripts/jassub-contract.test.mjs refactor/scripts/jassub-provenance.test.mjs refactor/scripts/jassub-failures.test.mjs refactor/scripts/jassub-types.test.mjs",
|
||||
"test:jassub-types": "node --test refactor/scripts/jassub-types.test.mjs",
|
||||
"test:jassub-types-package": "node refactor/scripts/jassub-package-types.mjs",
|
||||
"check:ci": "node refactor/scripts/ci-workflow.mjs",
|
||||
"test:ci": "node --test test/ci-summary.test.js refactor/scripts/ci-workflow.test.mjs refactor/scripts/impact.test.mjs test/package-runtime.test.js",
|
||||
"test:package:runtime": "node scripts/package-runtime.mjs --canonical",
|
||||
|
||||
@@ -2,4 +2,5 @@ node_modules
|
||||
.DS_Store
|
||||
yarn.lock
|
||||
package-lock.json
|
||||
src
|
||||
src
|
||||
tsconfig.json
|
||||
@@ -1,7 +1,7 @@
|
||||
# JASSUB maintenance
|
||||
|
||||
`src/index.js` preserves the lazy factory and delegates registration to
|
||||
`src/registration.js`. The registration module constructs the actual vendor object
|
||||
`src/index.ts` preserves the lazy factory and delegates registration to
|
||||
`src/registration.ts`. The registration module constructs the actual vendor object
|
||||
with `{ video: art.video, ...option }`, styles only a vendor-created canvas parent,
|
||||
binds host cleanup and synchronously returns `{ name: 'artplayerPluginJassub', instance }`.
|
||||
Options remain live until registration; a supplied video wins. This small split keeps
|
||||
@@ -27,11 +27,27 @@ messages. Keep this third-party file separate from the adapter's TypeScript migr
|
||||
files. The local demo explicitly hosts its resources under `docs/assets/jassub/` and selects
|
||||
fonts in `docs/assets/example/jassub.js`. Do not move or rename those URLs as an internal cleanup.
|
||||
|
||||
`types/artplayer-plugin-jassub.d.ts` currently requires three resource URLs and describes
|
||||
resize/setVideo/destroy as Promise-returning. Actual historical methods are synchronous, and
|
||||
resize is width/height/top/left/force. Keep the existing declarations until the dedicated public
|
||||
type compatibility work has assessed extraction, callbacks and replacement consumers.
|
||||
Both option and instance expose extension indexes; they are existing compatibility boundaries.
|
||||
`types/artplayer-plugin-jassub.d.ts` preserves the actual npm 1.0.0/1.1.0 declarations,
|
||||
including three required resource URLs, Promise-returning resize/setVideo/destroy and
|
||||
the historical force-first resize signature. Both option and instance retain their existing
|
||||
extension indexes. Do not correct this root file by narrowing fields or adding overloads:
|
||||
complete factory assignment and Parameters/ReturnType are compatibility boundaries.
|
||||
|
||||
`types/runtime-api.d.ts` separately describes the actual optional options, synchronous
|
||||
registration and methods, width/height/top/left/force resize, EventTarget events and
|
||||
Worker query/mutation data. `types/runtime.d.ts`, `.d.cts` and `.d.mts` expose it through
|
||||
the optional `/runtime` entry; it resolves to the same main/ESM JavaScript as the root.
|
||||
There is no second implementation and no new factory.default property. Root/legacy
|
||||
types remain unchanged; exact typesVersions mappings support old Node module resolution.
|
||||
See [type migration notes](types/README.md) before changing either surface.
|
||||
|
||||
`src/jassub.es.d.ts` is a private bridge for the frozen vendor JS, not a public export.
|
||||
It adds only the adapter's `_destroyed` read and `_canvasParent.style.zIndex` write to the
|
||||
accurate instance. The minimal style contract permits the historical numeric 20, which
|
||||
the native DOM setter converts to a string. The owned TS modules depend on RuntimeOption
|
||||
and RuntimeResult, while JassubHost needs only video/on/off. The implementation fixture
|
||||
checks actual Artplayer assignability. No broad any index, allowJs or ts-nocheck hides
|
||||
owned code; the isolated third-party JS remains an explicit provenance/07 exception.
|
||||
|
||||
The wrapper matches upstream jassub 1.8.8 apart from formatting and the ESLint header;
|
||||
worker JS and default font match that archive byte-for-byte. Local WASM instead matches
|
||||
@@ -48,6 +64,10 @@ Use the pinned Node/Yarn toolchain:
|
||||
|
||||
```sh
|
||||
yarn test:jassub
|
||||
yarn test:jassub-types
|
||||
yarn test:jassub-types-package
|
||||
yarn typecheck
|
||||
yarn build:ts artplayer-plugin-jassub
|
||||
node --test test/jassub-registration.test.js
|
||||
node refactor/scripts/jassub-provenance.test.mjs --network
|
||||
yarn build artplayer-plugin-jassub
|
||||
|
||||
@@ -11,6 +11,10 @@ jassub plugin for ArtPlayer
|
||||
See [maintenance notes](ARCHITECTURE.md) for adapter/vendor boundaries, resource paths,
|
||||
historical declaration differences and the current validation scope.
|
||||
|
||||
Existing imports keep their historical types. The optional
|
||||
[`artplayer-plugin-jassub/runtime` entry](types/README.md) provides accurate options,
|
||||
synchronous instance methods and subtitle query types using the same implementation.
|
||||
|
||||
## License
|
||||
|
||||
MIT © Harvey Zhao
|
||||
@@ -27,11 +27,31 @@
|
||||
"types": "./types/artplayer-plugin-jassub.d.ts",
|
||||
"import": "./dist/artplayer-plugin-jassub.legacy.js",
|
||||
"require": "./dist/artplayer-plugin-jassub.legacy.js"
|
||||
},
|
||||
"./runtime": {
|
||||
"import": {
|
||||
"types": "./types/runtime.d.mts",
|
||||
"default": "./dist/artplayer-plugin-jassub.mjs"
|
||||
},
|
||||
"require": {
|
||||
"types": "./types/runtime.d.cts",
|
||||
"default": "./dist/artplayer-plugin-jassub.js"
|
||||
}
|
||||
}
|
||||
},
|
||||
"main": "dist/artplayer-plugin-jassub.js",
|
||||
"module": "./dist/artplayer-plugin-jassub.mjs",
|
||||
"types": "types/artplayer-plugin-jassub.d.ts",
|
||||
"legacy": "dist/artplayer-plugin-jassub.legacy.js",
|
||||
"browserslist": "last 1 Chrome version"
|
||||
"browserslist": "last 1 Chrome version",
|
||||
"typesVersions": {
|
||||
"*": {
|
||||
"legacy": [
|
||||
"types/artplayer-plugin-jassub.d.ts"
|
||||
],
|
||||
"runtime": [
|
||||
"types/runtime.d.ts"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
import { registerJassub } from './registration.js'
|
||||
|
||||
export default function artplayerPluginJassub(option) {
|
||||
return art => registerJassub(art, option)
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import type { RuntimeOption } from '../types/runtime-api.js'
|
||||
import type { JassubHost } from './registration.js'
|
||||
import { registerJassub } from './registration.js'
|
||||
|
||||
export default function artplayerPluginJassub(option?: RuntimeOption) {
|
||||
return (art: JassubHost) => registerJassub(art, option)
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import type { RuntimeInstance, RuntimeOption } from '../types/runtime-api.js'
|
||||
|
||||
interface VendorInstance extends RuntimeInstance {
|
||||
_destroyed?: boolean
|
||||
// Minimal write contract: the DOM setter coerces the historical numeric 20.
|
||||
_canvasParent?: { style: { zIndex: string | number } }
|
||||
}
|
||||
|
||||
declare const JASSUB: {
|
||||
new (option: RuntimeOption): VendorInstance
|
||||
}
|
||||
export default JASSUB
|
||||
+8
-1
@@ -1,6 +1,13 @@
|
||||
import type { RuntimeOption, RuntimeResult } from '../types/runtime-api.js'
|
||||
import JASSUB from './jassub.es.js'
|
||||
|
||||
export function registerJassub(art, option) {
|
||||
export interface JassubHost {
|
||||
video: HTMLVideoElement
|
||||
on: (name: 'destroy', callback: () => void) => unknown
|
||||
off: (name: 'destroy', callback: () => void) => unknown
|
||||
}
|
||||
|
||||
export function registerJassub(art: JassubHost, option?: RuntimeOption): RuntimeResult {
|
||||
const instance = new JASSUB({ video: art.video, ...option })
|
||||
let disposed = false
|
||||
const dispose = () => {
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": { "allowJs": false },
|
||||
"include": ["src/**/*.ts", "../../refactor/fixtures/implementation/jassub.ts"]
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
# JASSUB type compatibility
|
||||
|
||||
The root and `/legacy` declarations retain the complete npm 1.0.0 and 1.1.0 type
|
||||
shape, including known mistakes. Existing TypeScript consumers do not need to
|
||||
change imports as part of this migration. Both releases used the same declarations.
|
||||
The generated online-editor global also preserves this historical surface.
|
||||
|
||||
For accurate types, explicitly opt into the additive entry:
|
||||
|
||||
```ts
|
||||
import jassub from 'artplayer-plugin-jassub/runtime'
|
||||
|
||||
const register = jassub({
|
||||
workerUrl: '/assets/jassub-worker.js',
|
||||
wasmUrl: '/assets/jassub-worker.wasm',
|
||||
modernWasmUrl: '/assets/jassub-worker-modern.wasm',
|
||||
subUrl: '/subtitles.ass',
|
||||
})
|
||||
// Add register to the existing Artplayer plugins option.
|
||||
// When called with an Artplayer, it synchronously returns { name, instance }.
|
||||
```
|
||||
|
||||
`/runtime` uses the same JavaScript file as the root entry. It does not change
|
||||
defaults, wrap instances, download resources differently or introduce a second
|
||||
implementation. URLs are optional because the vendor has defaults; this does not
|
||||
mean its default relative URLs will exist on your site. Continue hosting compatible
|
||||
worker, WASM and font files and supplying the paths your deployment needs.
|
||||
|
||||
| Area | Historical root types | Accurate `/runtime` types and existing JavaScript |
|
||||
| --- | --- | --- |
|
||||
| Factory options | Required, with three required URLs | Optional options; optional resource URLs |
|
||||
| Instance resize | force, width, height, top, left | width, height, top, left, force |
|
||||
| resize/setVideo/destroy | Promise<void> | Synchronous; no-argument calls return void |
|
||||
| sendMessage | Unchecked extension | Promise<void>, resolves after posting, not after Worker completion |
|
||||
| Events/styles | Unchecked extension | Typed callbacks and partial mutation objects |
|
||||
| ASS event Style | Unchecked extension | Numeric libass style index |
|
||||
| ASS Start/Duration | Unchecked extension | Milliseconds; setCurrentTime uses seconds |
|
||||
| Unknown fields/methods | Existing any indexes | Named supported fields; unknown members rejected |
|
||||
|
||||
Switching to `/runtime` intentionally changes type inference. For example,
|
||||
`instance.resize(640, 360, 0, 0, true)` describes the actual call order.
|
||||
`await instance.destroy()` remains legal JavaScript, but `.then()` on that result
|
||||
never worked at runtime. Resource teardown completion is not represented by a Promise.
|
||||
The callback error channel is `Error | ErrorEvent | null`; an error may omit the data.
|
||||
The currently frozen vendor has a request timeout/error bug that throws before user
|
||||
callbacks run. PKG-JASSUB-07 tracks the fix; typing that channel does not fix or guarantee
|
||||
callback delivery. Query outputs do not contain the upstream npm declaration's `_index`.
|
||||
|
||||
The accurate instance extends EventTarget and preserves its normal listener APIs.
|
||||
`destroy(error)` returns the original Error, or converts a nonempty string to Error;
|
||||
an empty string is returned unchanged. Repeated direct destruction and construction/
|
||||
video clock failures remain separate vendor lifecycle work, documented in ARCHITECTURE.md.
|
||||
|
||||
NodeNext ESM retains the root declaration's historical CommonJS namespace behavior.
|
||||
If existing code uses the root namespace's `.default` to satisfy that declaration,
|
||||
keep that form or opt into `/runtime`, whose conditional `.d.mts` is directly callable.
|
||||
CommonJS accurate types use `import jassub = require('artplayer-plugin-jassub/runtime')`.
|
||||
Do not invent `jassub.default`: the current JavaScript factory has no self alias.
|
||||
Older TypeScript resolves `/legacy` and `/runtime` through exact typesVersions mappings.
|
||||
Existing direct declaration paths are retained; no wildcard remaps unrelated paths.
|
||||
|
||||
Maintain `runtime-api.d.ts` against the frozen wrapper and worker behavior, not a new
|
||||
upstream version. Keep CJS/ESM entry aliases synchronized. Run `yarn test:jassub-types`,
|
||||
`yarn test:jassub-types-package`, `yarn test:jassub`, and `yarn typecheck` after changes.
|
||||
The package runner installs actual historical archives and the candidate outside the
|
||||
workspace, checks strict positive/negative consumers, and verifies artifact identity.
|
||||
This is type/distribution evidence, not browser playback or final release acceptance.
|
||||
@@ -0,0 +1,155 @@
|
||||
// Core exposes a default constructor in Node10/ESM and export= in NodeNext CJS.
|
||||
// Extract the same instance without requiring esModuleInterop in consumers.
|
||||
type CoreModule = typeof import('artplayer')
|
||||
type Artplayer = CoreModule extends { default: { prototype: infer Instance } }
|
||||
? Instance
|
||||
: CoreModule extends { prototype: infer Instance } ? Instance : never
|
||||
|
||||
export type FontSource = string | Uint8Array
|
||||
|
||||
/** Options are read when the registrar runs; the host supplies video by default. */
|
||||
export interface RuntimeOption {
|
||||
video?: HTMLVideoElement | null
|
||||
canvas?: HTMLCanvasElement
|
||||
blendMode?: 'js' | 'wasm'
|
||||
asyncRender?: boolean
|
||||
offscreenRender?: boolean
|
||||
onDemandRender?: boolean
|
||||
targetFps?: number
|
||||
timeOffset?: number
|
||||
debug?: boolean
|
||||
prescaleFactor?: number
|
||||
prescaleHeightLimit?: number
|
||||
maxRenderHeight?: number
|
||||
dropAllAnimations?: boolean
|
||||
dropAllBlur?: boolean
|
||||
workerUrl?: string
|
||||
wasmUrl?: string
|
||||
legacyWasmUrl?: string
|
||||
modernWasmUrl?: string
|
||||
subUrl?: string
|
||||
subContent?: string
|
||||
fonts?: FontSource[]
|
||||
availableFonts?: Record<string, FontSource>
|
||||
fallbackFont?: string
|
||||
useLocalFonts?: boolean
|
||||
libassMemoryLimit?: number
|
||||
libassGlyphLimit?: number
|
||||
}
|
||||
|
||||
/** Fields actually serialized by the frozen worker's getEvents response. */
|
||||
export interface AssEvent {
|
||||
/** Milliseconds; distinct from setCurrentTime's seconds. */
|
||||
Start: number
|
||||
Duration: number
|
||||
/** Numeric libass style index, not the ASS text format's style name. */
|
||||
Style: number
|
||||
Name: string
|
||||
MarginL: number
|
||||
MarginR: number
|
||||
MarginV: number
|
||||
Effect: string
|
||||
Text: string
|
||||
ReadOrder: number
|
||||
Layer: number
|
||||
}
|
||||
|
||||
/** Fields actually serialized by the frozen worker's getStyles response. */
|
||||
export interface AssStyle {
|
||||
Name: string
|
||||
FontName: string
|
||||
FontSize: number
|
||||
PrimaryColour: number
|
||||
SecondaryColour: number
|
||||
OutlineColour: number
|
||||
BackColour: number
|
||||
Bold: number
|
||||
Italic: number
|
||||
Underline: number
|
||||
StrikeOut: number
|
||||
ScaleX: number
|
||||
ScaleY: number
|
||||
Spacing: number
|
||||
Angle: number
|
||||
BorderStyle: number
|
||||
Outline: number
|
||||
Shadow: number
|
||||
Alignment: number
|
||||
MarginL: number
|
||||
MarginR: number
|
||||
MarginV: number
|
||||
Encoding: number
|
||||
treat_fontname_as_pattern: number
|
||||
Blur: number
|
||||
Justify: number
|
||||
}
|
||||
|
||||
/** Worker mutation handlers assign supplied keys; unspecified fields are not sent. */
|
||||
export type AssEventInput = Partial<AssEvent>
|
||||
export type AssStyleInput = Partial<AssStyle>
|
||||
|
||||
/** Timeout errors and native Worker error events are separate channels. */
|
||||
export type WorkerRequestError = Error | ErrorEvent
|
||||
/**
|
||||
* Success supplies null plus an array. Error payloads are absent.
|
||||
* The frozen vendor currently throws before notifying this callback on request
|
||||
* errors/timeouts; PKG-JASSUB-07 owns that defect. These types do not guarantee
|
||||
* callback delivery or claim that the error path has been repaired.
|
||||
*/
|
||||
export type EventsCallback = (error: WorkerRequestError | null, events?: AssEvent[]) => void
|
||||
export type StylesCallback = (error: WorkerRequestError | null, styles?: AssStyle[]) => void
|
||||
|
||||
export interface RuntimeEventMap {
|
||||
ready: CustomEvent<null>
|
||||
error: ErrorEvent
|
||||
}
|
||||
|
||||
/** The actual vendor EventTarget instance, rather than an adapter copy. */
|
||||
export interface RuntimeInstance extends EventTarget {
|
||||
timeOffset: number
|
||||
debug: boolean
|
||||
prescaleFactor: number
|
||||
prescaleHeightLimit: number
|
||||
maxRenderHeight: number
|
||||
/** Present when the selected rendering path uses demand/busy state. */
|
||||
busy?: boolean
|
||||
/** These two fields were explicitly exposed by npm jassub 1.8.8. */
|
||||
_canvas: HTMLCanvasElement
|
||||
_ctx: CanvasRenderingContext2D | false | null
|
||||
|
||||
resize: (width?: number, height?: number, top?: number, left?: number, force?: boolean) => void
|
||||
setVideo: (video: HTMLVideoElement) => void
|
||||
runBenchmark: () => void
|
||||
setTrackByUrl: (url: string) => void
|
||||
setTrack: (content: string) => void
|
||||
freeTrack: () => void
|
||||
setIsPaused: (isPaused: boolean) => void
|
||||
setRate: (rate: number) => void
|
||||
/** Current time is measured in seconds. */
|
||||
setCurrentTime: (isPaused?: boolean, currentTime?: number, rate?: number) => void
|
||||
createEvent: (event: AssEventInput) => void
|
||||
setEvent: (event: AssEventInput, index: number) => void
|
||||
removeEvent: (index: number) => void
|
||||
getEvents: (callback: EventsCallback) => void
|
||||
createStyle: (style: AssStyleInput) => void
|
||||
setStyle: (style: AssStyleInput, index: number) => void
|
||||
removeStyle: (index: number) => void
|
||||
getStyles: (callback: StylesCallback) => void
|
||||
styleOverride: (style: AssStyleInput) => void
|
||||
disableStyleOverride: () => void
|
||||
setDefaultFont: (font: string) => void
|
||||
addFont: (font: FontSource) => void
|
||||
/** Resolves after posting the message; it is not a Worker-operation acknowledgement. */
|
||||
sendMessage: (target: string, data?: Record<string, unknown> | null, transferable?: Transferable[]) => Promise<void>
|
||||
destroy: (() => void) & ((error: Error) => Error) & ((error: string) => Error | '') & ((error: Error | string | undefined) => Error | '' | undefined)
|
||||
|
||||
addEventListener: (<Name extends keyof RuntimeEventMap>(type: Name, listener: (this: RuntimeInstance, event: RuntimeEventMap[Name]) => void, options?: boolean | AddEventListenerOptions) => void) & ((type: string, listener: EventListenerOrEventListenerObject | null, options?: boolean | AddEventListenerOptions) => void)
|
||||
removeEventListener: (<Name extends keyof RuntimeEventMap>(type: Name, listener: (this: RuntimeInstance, event: RuntimeEventMap[Name]) => void, options?: boolean | EventListenerOptions) => void) & ((type: string, listener: EventListenerOrEventListenerObject | null, options?: boolean | EventListenerOptions) => void)
|
||||
}
|
||||
|
||||
export interface RuntimeResult {
|
||||
name: 'artplayerPluginJassub'
|
||||
instance: RuntimeInstance
|
||||
}
|
||||
|
||||
export type RuntimeFactory = (option?: RuntimeOption) => (art: Artplayer) => RuntimeResult
|
||||
@@ -0,0 +1,20 @@
|
||||
import type { RuntimeFactory } from './runtime-api.js'
|
||||
|
||||
declare const artplayerPluginJassub: RuntimeFactory
|
||||
// eslint-disable-next-line ts/no-redeclare -- Ambient merge exposes types on the CommonJS factory.
|
||||
declare namespace artplayerPluginJassub {
|
||||
type FontSource = import('./runtime-api.js').FontSource
|
||||
type RuntimeOption = import('./runtime-api.js').RuntimeOption
|
||||
type AssEvent = import('./runtime-api.js').AssEvent
|
||||
type AssStyle = import('./runtime-api.js').AssStyle
|
||||
type AssEventInput = import('./runtime-api.js').AssEventInput
|
||||
type AssStyleInput = import('./runtime-api.js').AssStyleInput
|
||||
type WorkerRequestError = import('./runtime-api.js').WorkerRequestError
|
||||
type EventsCallback = import('./runtime-api.js').EventsCallback
|
||||
type StylesCallback = import('./runtime-api.js').StylesCallback
|
||||
type RuntimeEventMap = import('./runtime-api.js').RuntimeEventMap
|
||||
type RuntimeInstance = import('./runtime-api.js').RuntimeInstance
|
||||
type RuntimeResult = import('./runtime-api.js').RuntimeResult
|
||||
type RuntimeFactory = import('./runtime-api.js').RuntimeFactory
|
||||
}
|
||||
export = artplayerPluginJassub
|
||||
@@ -0,0 +1,5 @@
|
||||
import type { RuntimeFactory } from './runtime-api.js'
|
||||
|
||||
declare const artplayerPluginJassub: RuntimeFactory
|
||||
export default artplayerPluginJassub
|
||||
export type { AssEvent, AssEventInput, AssStyle, AssStyleInput, EventsCallback, FontSource, RuntimeEventMap, RuntimeFactory, RuntimeInstance, RuntimeOption, RuntimeResult, StylesCallback, WorkerRequestError } from './runtime-api.js'
|
||||
@@ -0,0 +1,20 @@
|
||||
import type { RuntimeFactory } from './runtime-api.js'
|
||||
|
||||
declare const artplayerPluginJassub: RuntimeFactory
|
||||
// eslint-disable-next-line ts/no-redeclare -- Ambient merge exposes types on the CommonJS factory.
|
||||
declare namespace artplayerPluginJassub {
|
||||
type FontSource = import('./runtime-api.js').FontSource
|
||||
type RuntimeOption = import('./runtime-api.js').RuntimeOption
|
||||
type AssEvent = import('./runtime-api.js').AssEvent
|
||||
type AssStyle = import('./runtime-api.js').AssStyle
|
||||
type AssEventInput = import('./runtime-api.js').AssEventInput
|
||||
type AssStyleInput = import('./runtime-api.js').AssStyleInput
|
||||
type WorkerRequestError = import('./runtime-api.js').WorkerRequestError
|
||||
type EventsCallback = import('./runtime-api.js').EventsCallback
|
||||
type StylesCallback = import('./runtime-api.js').StylesCallback
|
||||
type RuntimeEventMap = import('./runtime-api.js').RuntimeEventMap
|
||||
type RuntimeInstance = import('./runtime-api.js').RuntimeInstance
|
||||
type RuntimeResult = import('./runtime-api.js').RuntimeResult
|
||||
type RuntimeFactory = import('./runtime-api.js').RuntimeFactory
|
||||
}
|
||||
export = artplayerPluginJassub
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,67 @@
|
||||
# PKG-JASSUB-04 自有 TS 与兼容类型
|
||||
|
||||
## 结构与运行时边界
|
||||
|
||||
自有 index/registration 迁为严格 TS,职责延续 03 的工厂与资源注册分离。
|
||||
JassubHost 只需要 video/on/off,真实 Artplayer fixture 验证结构兼容;内部返回
|
||||
RuntimeResult,不使用已知不准确的旧根声明来约束实现。私有 jassub.es.d.ts
|
||||
桥接 vendor,只补 adapter 使用的 _destroyed 与最小样式写入契约,保留数值 20
|
||||
经 DOM setter 转字符串的历史行为。allowJs=false,不用 any/ts-nocheck 隐藏自有实现。
|
||||
|
||||
vendor JS 是明确的第三方例外,本次完全不改字节和算法。Worker/WASM/font 来源
|
||||
和 07 修复责任继续保留。两个自有模块经 esbuild 去类型后与 38e7b378 的 JS 完全
|
||||
相同;正常构建 main/legacy/ESM 与该提交一致(仅归一化 LF),docs 副本逐字节一致。
|
||||
因此不以这次类型迁移声称新增浏览器播放验收,03 的原生验证范围和限制仍适用。
|
||||
|
||||
## API-09/11 兼容策略
|
||||
|
||||
实际 npm 1.0.0/1.1.0 根声明相同,本次逐字保留(忽略换行):资源 URL 必填、
|
||||
force-first resize、三个 Promise 返回值和两个扩展索引都没有收窄。工厂完整
|
||||
双向赋值、Parameters/ReturnType 和原有非法调用的错误位置继续验证。
|
||||
|
||||
新增可选 /runtime 与根入口复用同一 JavaScript,准确声明可选 options、同步
|
||||
registration/resize/setVideo/destroy、width/height/top/left/force 顺序,以及
|
||||
EventTarget、字幕/样式/字体方法。sendMessage 实际是 Promise<void>,完成仅代表
|
||||
发出消息;不声称 Worker 已处理。ASS Style 是 libass 数值索引,Start/Duration
|
||||
是毫秒,查询结果没有上游 npm 类型误写的 _index。旧入口消费者不需要迁移,
|
||||
选择准确入口时的推导差异和调用示例写在包内 types/README.md。
|
||||
|
||||
精确 typesVersions 修复旧 Node10 下 /legacy 找不到声明;实际两旧包保留原 2307
|
||||
证据,候选仅允许消除该 import 行的错误。NodeNext ESM 旧根 namespace 行为及
|
||||
直接调用失败保持一致;/runtime 使用 conditional .d.mts/.d.cts 与旧编译器 .d.ts。
|
||||
不添加不存在的 factory.default。在线编辑器旧生成方式同时包含 default/export=
|
||||
而报 2309,两个编译器已复现;接入已有语义生成器并验证生成文件及正反例。
|
||||
|
||||
## 类型审查发现的真实缺陷
|
||||
|
||||
JASSUB-QUERY-01:getEvents/getStyles 的 timeout 和 Worker-error 路径缺少第二个
|
||||
参数,公开 wrapper 先解构 undefined 抛 TypeError,用户回调为零次,两个请求
|
||||
监听器保留;Worker-error 路径还保留定时器。冻结实现的四个失败测试保留此行为。
|
||||
该问题纳入 PKG-JASSUB-07 的独立 vendor 修复,不在 04 中混改,不标为已修复。
|
||||
准确回调类型允许 Error/ErrorEvent 与缺失数组,不能代表错误回调已经可靠送达。
|
||||
|
||||
## 验证、命令与限制
|
||||
|
||||
完整证据与最终安装报告摘要见 [类型验证](../baselines/jassub-types-validation.json)。
|
||||
验证包括:严格 TS 自有项目及全仓流程,实际旧声明正反例、生成编辑器,源码和
|
||||
main/legacy 注册回归,完整 vendor Worker JS/本地 WASM 的字幕查询及部分字段更新。
|
||||
WASM 测试控制消息传输与 fetch,不声称浏览器 Worker、绘制或完整字体加载验收。
|
||||
|
||||
仓库外实际安装两个历史 npm 归档与 Yarn pack 候选,逐文件核对 hash 和非链接,
|
||||
离线安装后强制 frozen 重装并检查锁文件。TS 5.9.3 Node10/NodeNext CJS/ESM/Bundler
|
||||
及 TS 4.3.5 Node10 共 15 组;旧入口七项非法调用、准确入口十四项非法调用逐条
|
||||
验证。CJS/ESM 根/legacy 工厂和 root/runtime 同一对象从实际安装包执行,不启动
|
||||
vendor 或模拟一次浏览器播放。历史 raw 错误与正确 namespace 消费均单独记录。
|
||||
|
||||
复跑:yarn test:jassub、yarn test:jassub-types、yarn test:jassub-types-package、
|
||||
yarn typecheck、yarn build:ts artplayer-plugin-jassub、yarn build artplayer-plugin-jassub。
|
||||
新增脚本复用既有依赖,无新增依赖或锁变更;类型测试进入既有 baseline glob,
|
||||
运行测试进入 test:unit/test:jassub。隔离安装脚本是可复跑的分包验证,尚不声称
|
||||
全部生态安装矩阵已经接入远程 CI 或运行通过。
|
||||
|
||||
## 剩余任务与回退
|
||||
|
||||
07 继续 vendor 生命周期、时钟和查询错误清理;05 继续新旧核心/浏览器/真实设备
|
||||
组合;06 继续完整分发和许可通知,VENDOR-04/05 保留。版本仍由各包独立 major
|
||||
任务统一落地。本任务没有发布、推送或减少最终三轮复盘要求。
|
||||
回退本任务并正常重建即可恢复旧 JS 入口/类型生成选择;03 修复仍是独立提交。
|
||||
@@ -0,0 +1,8 @@
|
||||
import type Artplayer from 'artplayer'
|
||||
import type { RuntimeFactory, RuntimeResult } from '../../../packages/artplayer-plugin-jassub/types/runtime-api.js'
|
||||
import factory from '../../../packages/artplayer-plugin-jassub/src'
|
||||
|
||||
declare const art: Artplayer
|
||||
const registration: RuntimeResult = factory()(art)
|
||||
const publicFactory: RuntimeFactory = factory
|
||||
export { publicFactory, registration }
|
||||
+4
-3
@@ -4,7 +4,7 @@
|
||||
|
||||
基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 232 项,范围 22 个包及工作区/示例。
|
||||
|
||||
状态:todo 64 / doing 14 / blocked 0 / done 154 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
|
||||
状态:todo 63 / doing 14 / blocked 0 / done 155 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
|
||||
|
||||
前置依赖是启动条件;验收是完成条件。任务可以继续拆分,但不能复用或悄悄删除旧 ID。
|
||||
|
||||
@@ -232,10 +232,10 @@
|
||||
| PKG-JASSUB-01 | artplayer-plugin-jassub<br>核对包契约与历史用法 | BASE-05 | 选项透传、result.instance、vendor 来源、worker/WASM/font 路径 | 源码/声明/README/demo/发布包差异已登记;公开形状和版本范围冻结;接续 VENDOR-04/05,核对 wrapper/worker/WASM/font 的独立来源与通知,保持资源路径和选项透传 | H | done |
|
||||
| PKG-JASSUB-02 | artplayer-plugin-jassub<br>建立特有行为与错误测试 | PKG-JASSUB-01, ENG-03, ENG-05 | ASS 字体、时钟/seek/倍率/resize、加载失败和销毁 | 旧版本行为可重跑,成功/失败/切源/销毁有必要断言 | H | done |
|
||||
| PKG-JASSUB-03 | artplayer-plugin-jassub<br>整理内部职责与资源 | PKG-JASSUB-02, CORE-15, CORE-16 | 仅整理自有 adapter/销毁;保留第三方文件及来源 | 结构变化和缺陷修复分开记录;原 API/事件/资源生命周期通过 | H | done |
|
||||
| PKG-JASSUB-04 | artplayer-plugin-jassub<br>迁移自有源码和公开类型 | PKG-JASSUB-03, ENG-04, ENG-06, CORE-07 | JASSUB option/instance 的兼容类型包装,vendor JS 例外记录 | 严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容 | H | todo |
|
||||
| PKG-JASSUB-04 | artplayer-plugin-jassub<br>迁移自有源码和公开类型 | PKG-JASSUB-03, ENG-04, ENG-06, CORE-07 | JASSUB option/instance 的兼容类型包装,vendor JS 例外记录 | 严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容 | H | done |
|
||||
| PKG-JASSUB-05 | artplayer-plugin-jassub<br>验证新旧核心和组合 | PKG-JASSUB-04, CORE-22, PKG-JASSUB-07 | 真实 worker/WASM 字幕渲染与全屏、旧核心测试 | 最终核心与原支持范围核心分别通过;设备/SDK 缺证据不能标完成 | H | todo |
|
||||
| PKG-JASSUB-06 | artplayer-plugin-jassub<br>验证分发并同步文档 | PKG-JASSUB-05, ENG-07 | jassub.js、外部资源路径、许可和离线失败记录;接续 VENDOR-04/05 完整组件/字体通知、LGPL 源码分发核对及六份未明确字体的分发处置 | tarball 入口/资源、类型、8082 demo 和 README 一致,有回退记录;来源身份已由01冻结,完整通知和未明确字体的发布处理必须闭环,不能以来源相同替代许可结论 | H | todo |
|
||||
| PKG-JASSUB-07 | artplayer-plugin-jassub<br>修复 vendor 生命周期与字幕时钟 | PKG-JASSUB-03, PKG-JASSUB-02 | 独立记录 vendor 适配差异;幂等销毁、跨容器 setVideo、构造回滚、逐帧归属和倍率/能力边界 | 保持公开真实 instance、同步方法与资源路径;保留原 vendor 来源指纹并记录每项实际补丁;候选修复正例和真实字幕/失败/时钟验证通过,未证实设备不得冒充通过 | H | todo |
|
||||
| PKG-JASSUB-07 | artplayer-plugin-jassub<br>修复 vendor 生命周期与字幕时钟 | PKG-JASSUB-03, PKG-JASSUB-02 | 独立记录 vendor 适配差异;幂等销毁、跨容器 setVideo、构造回滚、逐帧归属和倍率/能力边界;查询 timeout/Worker error 的缺失数据回调及每请求监听器/定时器清理(JASSUB-QUERY-01) | 保持公开真实 instance、同步方法与资源路径;保留原 vendor 来源指纹并记录每项实际补丁;候选修复正例和真实字幕/失败/时钟验证通过,未证实设备不得冒充通过 | H | todo |
|
||||
|
||||
## 5 包迁移:artplayer-plugin-danmuku-mask
|
||||
|
||||
@@ -535,6 +535,7 @@
|
||||
- PKG-JASSUB-01: [记录](baselines/jassub-release.json) [记录](baselines/jassub-vendor.json) [记录](baselines/jassub-font-metadata.json) [记录](baselines/jassub-contract.md) [记录](baselines/jassub-contract-validation.json) [记录](changes/2026-09-13-PKG-JASSUB-01-baseline.md) [记录](baselines/jassub-provenance.json) [记录](baselines/jassub-provenance-validation.json) [记录](changes/2026-09-14-PKG-JASSUB-01-provenance.md)
|
||||
- PKG-JASSUB-02: [记录](changes/2026-09-14-PKG-JASSUB-02-failure-baseline.md) [记录](baselines/jassub-failures-validation.json)
|
||||
- PKG-JASSUB-03: [记录](changes/2026-09-14-PKG-JASSUB-03-registration.md) [记录](baselines/jassub-registration-validation.json)
|
||||
- PKG-JASSUB-04: [记录](changes/2026-09-14-PKG-JASSUB-04-types.md) [记录](baselines/jassub-types-validation.json)
|
||||
- PKG-MASK-01: [记录](baselines/danmuku-mask-release.json) [记录](baselines/danmuku-mask-registry.json) [记录](baselines/danmuku-mask-contract.md) [记录](baselines/danmuku-mask-contract-validation.json) [记录](changes/2026-09-13-PKG-MASK-01-contract.md)
|
||||
- PKG-MASK-02: [记录](changes/2026-09-13-PKG-MASK-02-failures.md) [记录](baselines/danmuku-mask-failures-validation.json)
|
||||
- PKG-MASK-03: [记录](changes/2026-09-13-PKG-MASK-03-lifecycle.md) [记录](baselines/danmuku-mask-lifecycle-validation.json)
|
||||
|
||||
@@ -1,5 +1,18 @@
|
||||
# 进度与证据
|
||||
|
||||
## PKG-JASSUB-04 自有 TS 与准确类型入口完成
|
||||
|
||||
两个自有模块已迁为严格 TS,编译后的 JS 和三种正常构建产物保持上一任务行为。
|
||||
实际 npm 两版旧根声明完整保留,新增 /runtime 描述真实同步方法/可选参数,
|
||||
修复旧 Node10 legacy 解析与编辑器导出冲突;无 interop 的 CJS 类型引用也通过。
|
||||
联合 139 项、源码/main/legacy 各 15 项、406 生产 TS 全仓检查通过;实际两旧包及
|
||||
候选的仓库外安装 15 编译器组合通过,新增真实 WASM 查询/修改测试。
|
||||
[变更记录](changes/2026-09-14-PKG-JASSUB-04-types.md)和
|
||||
[安装与类型证据](baselines/jassub-types-validation.json)保留中间失败及最终候选。
|
||||
新增 JASSUB-QUERY-01 有冻结错误复现,交由下一项 07 修复;没有宣称新浏览器
|
||||
播放或发布验收。下一步继续 vendor 生命周期/时钟/查询清理,05/06 仍未完成。
|
||||
|
||||
|
||||
## PKG-JASSUB-03 自有注册/清理改造完成
|
||||
|
||||
入口与 registration 分离;自定义 canvas、直接实例销毁后的宿主清理、重入与
|
||||
|
||||
@@ -248,3 +248,4 @@
|
||||
| JASSUB-VIDEO-FRAME-01 | open / 已复现 | Old video frame callbacks can drive replacement subtitle time and duplicate chains | PKG-JASSUB-02, PKG-JASSUB-07 |
|
||||
| JASSUB-RATE-01 | open / 已复现 | Fallback ratechange sends an Event instead of numeric playbackRate | PKG-JASSUB-02, PKG-JASSUB-07 |
|
||||
| JASSUB-NATIVE-FRAME-01 | open / 已复现 | Windows WebKit default frame-clock path does not produce subtitle demands in actual playback | PKG-JASSUB-02, PKG-JASSUB-07, PKG-JASSUB-05 |
|
||||
| JASSUB-QUERY-01 | open / 已复现 | Subtitle query errors throw before calling the consumer and leave request listeners attached | PKG-JASSUB-07, PKG-JASSUB-05 |
|
||||
+18
-2
@@ -2,7 +2,7 @@
|
||||
"schemaVersion": 1,
|
||||
"task": "BASE-07",
|
||||
"sourceCommit": "e01f39ff",
|
||||
"updatedAt": "2026-09-13",
|
||||
"updatedAt": "2026-09-14",
|
||||
"note": "Open means closure evidence is outstanding; source-observed and unverified are not reproduced runtime bugs. A source issue already mitigated in the workspace still requires candidate regression evidence. No accepted-with-scope item is implied by baseline task completion.",
|
||||
"items": [
|
||||
{
|
||||
@@ -779,7 +779,7 @@
|
||||
"PKG-JASSUB-05"
|
||||
],
|
||||
"evidence": [
|
||||
"packages/artplayer-plugin-jassub/src/index.js",
|
||||
"packages/artplayer-plugin-jassub/src/index.ts",
|
||||
"docs/assets/example/jassub.js"
|
||||
],
|
||||
"compatibleResolution": "Preserve options pass-through, result.instance and worker/wasm/font URLs. Wrapper, worker and WASM must be a verified compatible set.",
|
||||
@@ -5535,6 +5535,22 @@
|
||||
],
|
||||
"compatibleResolution": "Investigate native frame delivery and establish a compatible capability response; explicit event-clock success is diagnostic only.",
|
||||
"closureCriteria": "Default native behavior is verified or an explicit reviewed environment limitation is recorded; no silent global fallback or substitution of diagnostic mode for default acceptance."
|
||||
},
|
||||
{
|
||||
"id": "JASSUB-QUERY-01",
|
||||
"title": "Subtitle query errors throw before calling the consumer and leave request listeners attached",
|
||||
"confirmation": "reproduced",
|
||||
"status": "open",
|
||||
"owners": [
|
||||
"PKG-JASSUB-07",
|
||||
"PKG-JASSUB-05"
|
||||
],
|
||||
"evidence": [
|
||||
"refactor/changes/2026-09-14-PKG-JASSUB-04-types.md",
|
||||
"test/jassub-runtime.test.js"
|
||||
],
|
||||
"compatibleResolution": "Handle absent worker error payloads, release per-request listeners and timers before invoking user callbacks, and preserve successful synchronous query registration and callback data.",
|
||||
"closureCriteria": "Actual vendor timeout and Worker-error paths invoke the consumer exactly once with the original error, leave no request listeners/timers, and successful real-WASM events/styles remain unchanged. Browser error/recovery acceptance remains PKG-JASSUB-05."
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import fs from 'node:fs'
|
||||
|
||||
export const jassubInvalidStatements = [
|
||||
'jassub()',
|
||||
'jassub(undefined)',
|
||||
'jassub({ workerUrl: "/worker.js", wasmUrl: "/worker.wasm" })',
|
||||
'jassub({ workerUrl: 1, wasmUrl: "/worker.wasm", modernWasmUrl: "/modern.wasm" })',
|
||||
'instance.resize(640)',
|
||||
'const synchronousDestroy: void = instance.destroy()',
|
||||
'const wrongName: "other" = result.name',
|
||||
]
|
||||
|
||||
export function jassubConsumerSource(mode, raw = false) {
|
||||
let source = fs.readFileSync(new URL('../../test/types/jassub-public.ts', import.meta.url), 'utf8')
|
||||
if (mode === 'nodenext-esm' && !raw) {
|
||||
source = source
|
||||
.replace('import jassub from \'artplayer-plugin-jassub\'', 'import jassubModule from \'artplayer-plugin-jassub\'\nconst jassub = jassubModule.default')
|
||||
.replace('import legacy from \'artplayer-plugin-jassub/legacy\'', 'import legacyModule from \'artplayer-plugin-jassub/legacy\'\nconst legacy = legacyModule.default')
|
||||
}
|
||||
return source
|
||||
}
|
||||
|
||||
export function jassubRuntimeSource(mode) {
|
||||
const source = fs.readFileSync(new URL('../../test/types/jassub-runtime.ts', import.meta.url), 'utf8')
|
||||
return mode.endsWith('-no-interop')
|
||||
? source
|
||||
.replace('import runtime from \'artplayer-plugin-jassub/runtime\'', 'import runtime = require(\'artplayer-plugin-jassub/runtime\')')
|
||||
.replace('import type Artplayer from \'artplayer\'', 'type Core = typeof import(\'artplayer\'); type Artplayer = Core extends { default: { prototype: infer I } } ? I : Core extends { prototype: infer I } ? I : never')
|
||||
: source
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import process from 'node:process'
|
||||
import ts from 'typescript'
|
||||
import compat from 'typescript-compat'
|
||||
import { checkFiles, packedFiles } from '../../scripts/package-check.mjs'
|
||||
import { consumerDirectory, removeConsumer, run, workspace, writeJson } from '../../scripts/package-consumer.mjs'
|
||||
import { jassubConsumerSource, jassubInvalidStatements, jassubRuntimeSource } from './jassub-consumer.mjs'
|
||||
import { verifyJassubContract } from './jassub-contract.mjs'
|
||||
import { hash, readMember } from './releases.mjs'
|
||||
|
||||
const name = 'artplayer-plugin-jassub'
|
||||
const declaration = `package/types/${name}.d.ts`
|
||||
const shape = diagnostics => diagnostics.map(({ code, file, line }) => ({ code, file, line }))
|
||||
let evidenceOutput
|
||||
|
||||
function installedBytes(consumer, packages) {
|
||||
for (const pkg of packages) {
|
||||
const root = path.join(consumer, 'node_modules', pkg.name)
|
||||
assert.equal(fs.realpathSync(root), root, 'Installed package must not be a workspace link')
|
||||
assert(!fs.lstatSync(root).isSymbolicLink(), 'Installed package root is a link')
|
||||
for (const [member, expected] of Object.entries(pkg.files)) {
|
||||
const file = path.join(root, member.slice(8))
|
||||
assert.equal(fs.realpathSync(file), file, `Installed member resolves through a link: ${member}`)
|
||||
assert(fs.lstatSync(file).isFile(), `Installed member is not a regular file: ${member}`)
|
||||
assert.equal(hash(fs.readFileSync(file)), expected, `Installed bytes differ: ${member}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function runtimeProbe(candidate, old) {
|
||||
return `const assert = require('node:assert/strict');
|
||||
global.self = global;
|
||||
const names = ${JSON.stringify([name, `${name}/legacy`, ...(candidate ? [`${name}/runtime`] : [])])};
|
||||
const old = ${old};
|
||||
const commonjs = names.map(name => require(name));
|
||||
for (const value of commonjs) {
|
||||
assert.equal(typeof value, old ? 'object' : 'function');
|
||||
assert.equal(typeof value.default, old ? 'function' : 'undefined');
|
||||
const factory = old ? value.default : value;
|
||||
assert.equal(typeof factory(), 'function');
|
||||
assert.equal(typeof factory({workerUrl: '/worker.js', wasmUrl: '/wasm.wasm', modernWasmUrl: '/modern.wasm'}), 'function');
|
||||
}
|
||||
${candidate ? 'assert.equal(commonjs[2], commonjs[0]); assert.equal(require.resolve(names[2]), require.resolve(names[0]));' : ''}
|
||||
Promise.all(names.map(name => import(name))).then(modules => {
|
||||
assert.equal(typeof modules[0].default, 'function');
|
||||
assert.equal(typeof modules[0].default(), 'function');
|
||||
assert.equal(modules[1].default, commonjs[1]);
|
||||
const legacy = old ? modules[1].default.default : modules[1].default;
|
||||
assert.equal(typeof legacy(), 'function');
|
||||
${candidate ? 'assert.equal(modules[2].default, modules[0].default); assert.equal(typeof modules[2].default(), \'function\');' : ''}
|
||||
console.log('Installed CJS/ESM root/legacy lazy factories and conditional runtime identity passed. Registrars were not invoked; no vendor initialization.');
|
||||
}).catch(error => { console.error(error); process.exitCode = 1; });
|
||||
`
|
||||
}
|
||||
|
||||
async function main() {
|
||||
assert.equal(process.version.slice(1), fs.readFileSync(path.join(workspace, '.node-version'), 'utf8').trim(), 'Use the repository Node version')
|
||||
assert.equal(process.env.npm_config_user_agent?.split(' ')[0], 'yarn/1.22.22', 'Use yarn test:jassub-types-package')
|
||||
const yarn = process.env.npm_execpath
|
||||
assert(yarn && fs.existsSync(yarn))
|
||||
const output = fs.mkdtempSync(path.join(workspace, 'refactor/.cache/jassub-package-types-'))
|
||||
evidenceOutput = output
|
||||
console.log(`JASSUB isolated installed consumer evidence: ${output}`)
|
||||
const fixtureHashes = () => Object.fromEntries(['refactor/scripts/jassub-consumer.mjs', 'test/types/jassub-public.ts', 'test/types/jassub-runtime.ts'].map(file => [file, hash(fs.readFileSync(path.join(workspace, file)))]))
|
||||
const fixtures = fixtureHashes()
|
||||
const contract = await verifyJassubContract()
|
||||
const releases = [...contract.baseline.previous, contract.baseline.release]
|
||||
assert.deepEqual(releases.map(release => release.version), ['1.0.0', '1.1.0'])
|
||||
const packages = []
|
||||
for (const packageName of ['artplayer', name]) {
|
||||
const archive = path.join(output, `${packageName}.tgz`)
|
||||
fs.writeFileSync(path.join(output, `${packageName}-pack.log`), run([yarn, 'pack', '--filename', archive], path.join(workspace, 'packages', packageName)))
|
||||
const manifest = JSON.parse(readMember(archive, 'package/package.json'))
|
||||
const files = packedFiles(archive)
|
||||
checkFiles(manifest, files, packageName === name ? Object.keys(contract.baseline.release.files) : [])
|
||||
packages.push({ name: packageName, archive, manifest, sha256: hash(fs.readFileSync(archive)), files: Object.fromEntries(files.map(member => [member, hash(readMember(archive, member))])) })
|
||||
}
|
||||
const [core, candidate] = packages
|
||||
const rootDeclarations = releases.map((release) => {
|
||||
const current = readMember(candidate.archive, declaration)
|
||||
const previous = readMember(contract.archives.get(release.version), declaration)
|
||||
const normalize = bytes => bytes.toString('utf8').replaceAll('\r\n', '\n')
|
||||
assert.equal(normalize(current), normalize(previous), `Preserve npm ${release.version} root declaration; only checkout CRLF may differ`)
|
||||
return { version: release.version, candidateSha256: hash(current), publishedSha256: hash(previous), bytesEqual: current.equals(previous), normalizedLFSha256: hash(normalize(current)) }
|
||||
})
|
||||
const exports = candidate.manifest.exports
|
||||
assert.equal(exports['./runtime'].import.types, './types/runtime.d.mts')
|
||||
assert.equal(exports['./runtime'].require.types, './types/runtime.d.cts')
|
||||
assert.equal(exports['./runtime'].import.default, exports['.'].import)
|
||||
assert.equal(exports['./runtime'].require.default, exports['.'].require)
|
||||
assert.deepEqual(candidate.manifest.typesVersions['*'].runtime, ['types/runtime.d.ts'])
|
||||
assert.deepEqual(candidate.manifest.typesVersions['*'].legacy, [`types/${name}.d.ts`])
|
||||
const plugins = [
|
||||
...releases.map(release => ({ ...release, archive: contract.archives.get(release.version), label: `published-${release.version}` })),
|
||||
{ ...candidate, label: 'candidate' },
|
||||
]
|
||||
const matrix = []
|
||||
const historical = new Map()
|
||||
for (const plugin of plugins) {
|
||||
const consumer = consumerDirectory()
|
||||
try {
|
||||
writeJson(path.join(consumer, 'package.json'), { name: 'jassub-isolated-consumer', private: true, dependencies: Object.fromEntries([core, plugin].map(pkg => [pkg.name, `file:${pkg.archive.replaceAll('\\', '/')}`])) })
|
||||
fs.copyFileSync(path.join(workspace, 'yarn.lock'), path.join(consumer, 'yarn.lock'))
|
||||
fs.writeFileSync(path.join(output, `${plugin.label}-install.log`), run([yarn, 'install', '--offline', '--ignore-scripts', '--non-interactive'], consumer))
|
||||
installedBytes(consumer, [core, plugin])
|
||||
const lock = fs.readFileSync(path.join(consumer, 'yarn.lock'))
|
||||
fs.writeFileSync(path.join(output, `${plugin.label}-frozen-install.log`), run([yarn, 'install', '--offline', '--frozen-lockfile', '--force', '--ignore-scripts', '--non-interactive'], consumer))
|
||||
assert.deepEqual(fs.readFileSync(path.join(consumer, 'yarn.lock')), lock, 'Frozen reinstall changed consumer lock')
|
||||
installedBytes(consumer, [core, plugin])
|
||||
fs.writeFileSync(path.join(output, `${plugin.label}-yarn.lock`), lock)
|
||||
const runtimeFile = path.join(consumer, 'exports.cjs')
|
||||
fs.writeFileSync(runtimeFile, runtimeProbe(plugin.label === 'candidate', plugin.version === '1.0.0'))
|
||||
fs.writeFileSync(path.join(output, `${plugin.label}-runtime.log`), run([runtimeFile], consumer))
|
||||
for (const [compiler, mode] of [[ts, 'node10-commonjs'], [ts, 'nodenext-cjs'], [ts, 'nodenext-esm'], [ts, 'bundler-esm'], [compat, 'node10-commonjs']]) {
|
||||
const next = mode.startsWith('nodenext')
|
||||
const filename = path.join(consumer, `consumer.${next ? mode.endsWith('-cjs') ? 'cts' : 'mts' : 'ts'}`)
|
||||
const options = { strict: true, noEmit: true, skipLibCheck: false, types: [], esModuleInterop: true, target: compiler.ScriptTarget.ES2020, lib: ['lib.es2020.d.ts', 'lib.dom.d.ts'], module: next ? compiler.ModuleKind.NodeNext : mode === 'bundler-esm' ? compiler.ModuleKind.ESNext : compiler.ModuleKind.CommonJS, moduleResolution: next ? compiler.ModuleResolutionKind.NodeNext : mode === 'bundler-esm' ? compiler.ModuleResolutionKind.Bundler : compiler.ModuleResolutionKind.NodeJs }
|
||||
let declarations = []
|
||||
const compile = (source, esModuleInterop = options.esModuleInterop) => {
|
||||
fs.writeFileSync(filename, source)
|
||||
const program = compiler.createProgram([filename], { ...options, esModuleInterop })
|
||||
for (const file of program.getSourceFiles()) {
|
||||
const actual = fs.realpathSync(file.fileName)
|
||||
assert(actual.startsWith(fs.realpathSync(consumer) + path.sep) || (program.isSourceFileDefaultLibrary(file) && path.dirname(actual) === fs.realpathSync(path.dirname(compiler.sys.getExecutingFilePath()))), `Type escaped consumer: ${actual}`)
|
||||
}
|
||||
declarations = program.getSourceFiles().filter(file => !program.isSourceFileDefaultLibrary(file)).map(file => path.relative(consumer, file.fileName).replaceAll('\\', '/')).sort()
|
||||
return compiler.getPreEmitDiagnostics(program).map(diagnostic => ({ code: diagnostic.code, file: diagnostic.file ? path.relative(consumer, diagnostic.file.fileName).replaceAll('\\', '/') : null, line: diagnostic.file && diagnostic.start !== undefined ? diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start).line + 1 : null, message: compiler.flattenDiagnosticMessageText(diagnostic.messageText, '\n').replaceAll(consumer.replaceAll('\\', '/'), '<consumer>').replaceAll(consumer, '<consumer>') }))
|
||||
}
|
||||
const rawSource = jassubConsumerSource(mode, true)
|
||||
const raw = compile(rawSource)
|
||||
const key = `${compiler.version}:${mode}`
|
||||
const entry = { plugin: plugin.label, compiler: compiler.version, mode, raw, diagnostics: null, invalid: null, runtime: null }
|
||||
matrix.push(entry)
|
||||
writeJson(path.join(output, 'matrix-progress.json'), matrix)
|
||||
const legacyLines = rawSource.split('\n').flatMap((line, index) => /from ['"]artplayer-plugin-jassub\/legacy['"]/.test(line) ? [index + 1] : [])
|
||||
assert.equal(legacyLines.length, 1, 'The raw consumer must contain one identifiable legacy import')
|
||||
const legacyFailure = { code: 2307, file: path.basename(filename), line: legacyLines[0] }
|
||||
if (plugin.label === 'published-1.0.0') {
|
||||
if (mode === 'node10-commonjs')
|
||||
assert.deepEqual(shape(raw), [legacyFailure], 'Preserve the exact old Node10 legacy resolution error')
|
||||
if (mode === 'nodenext-esm')
|
||||
assert(raw.length > 0, 'Keep raw historical NodeNext namespace failures visible')
|
||||
historical.set(key, { raw: shape(raw) })
|
||||
}
|
||||
else {
|
||||
const expected = historical.get(key).raw
|
||||
assert.deepEqual(shape(raw), plugin.label === 'candidate' && mode === 'node10-commonjs' ? expected.filter(item => item.code !== legacyFailure.code || item.file !== legacyFailure.file || item.line !== legacyFailure.line) : expected, 'Only the exact recorded old Node10 legacy resolution failure may be corrected')
|
||||
}
|
||||
let source = jassubConsumerSource(mode)
|
||||
if (plugin.label !== 'candidate' && mode === 'node10-commonjs')
|
||||
source = source.replace(/(['"])artplayer-plugin-jassub\/legacy\1/g, `'artplayer-plugin-jassub/types/artplayer-plugin-jassub'`)
|
||||
entry.diagnostics = compile(source)
|
||||
assert.deepEqual(entry.diagnostics, [], `${plugin.label} ${compiler.version} ${mode}: explicit historical namespace consumer`)
|
||||
entry.invalid = compile(`${source}\n${jassubInvalidStatements.join('\n')}\n`)
|
||||
assert.equal(jassubInvalidStatements.length, 7, 'Retain all seven historical negative consumers')
|
||||
assert.equal(entry.invalid.length, jassubInvalidStatements.length)
|
||||
assert.deepEqual(entry.invalid.map(item => item.line), jassubInvalidStatements.map((_, index) => source.split('\n').length + 1 + index), 'Every old-entry negative statement must fail at its exact line')
|
||||
if (plugin.label === 'published-1.0.0')
|
||||
historical.get(key).invalid = shape(entry.invalid)
|
||||
else assert.deepEqual(shape(entry.invalid), historical.get(key).invalid, 'Both published packages and candidate preserve exact invalid diagnostic codes and lines')
|
||||
if (plugin.label === 'candidate') {
|
||||
const runtimeSource = jassubRuntimeSource(mode)
|
||||
const diagnostics = compile(runtimeSource)
|
||||
const runtimeDeclarations = declarations
|
||||
assert.deepEqual(diagnostics, [], `Accurate runtime consumer: ${compiler.version} ${mode}`)
|
||||
const lines = runtimeSource.split('\n')
|
||||
const negative = []
|
||||
for (let index = 0; index < lines.length; index++) {
|
||||
if (!/^[ \t]*\/\/ @ts-expect-error/.test(lines[index]))
|
||||
continue
|
||||
assert(lines[index + 1]?.trim() && !lines[index + 1].trim().startsWith('//'), 'Each runtime directive must precede one negative statement')
|
||||
const invalid = compile(lines.map((line, offset) => offset === index ? '' : line).join('\n'))
|
||||
assert(invalid.length > 0 && invalid.every(item => item.file === path.basename(filename) && item.line === index + 2), `Runtime negative statement ${index + 2} must independently fail on its own line`)
|
||||
negative.push({ line: index + 2, diagnostics: invalid })
|
||||
}
|
||||
assert.equal(negative.length, 14, 'Retain all fourteen runtime negative consumers')
|
||||
entry.runtime = { diagnostics, negative, declarations: runtimeDeclarations, sourceSha256: hash(runtimeSource) }
|
||||
if ((compiler === ts && mode === 'nodenext-cjs') || (compiler === compat && mode === 'node10-commonjs')) {
|
||||
const commonjsSource = jassubRuntimeSource(`${mode}-no-interop`)
|
||||
assert(commonjsSource.includes('import runtime = require(\'artplayer-plugin-jassub/runtime\')'), 'Exercise the promised CommonJS import assignment')
|
||||
const commonjsDiagnostics = compile(commonjsSource, false)
|
||||
entry.runtime.commonjs = { esModuleInterop: false, diagnostics: commonjsDiagnostics, declarations, sourceSha256: hash(commonjsSource) }
|
||||
writeJson(path.join(output, 'matrix-progress.json'), matrix)
|
||||
assert.deepEqual(commonjsDiagnostics, [], `Runtime import=require without interop: ${compiler.version} ${mode}`)
|
||||
}
|
||||
}
|
||||
writeJson(path.join(output, 'matrix-progress.json'), matrix)
|
||||
}
|
||||
}
|
||||
finally { removeConsumer(consumer) }
|
||||
}
|
||||
assert.deepEqual(fixtureHashes(), fixtures, 'Consumer fixtures changed during the installed matrix; rerun with final inputs')
|
||||
writeJson(path.join(output, 'report.json'), { task: 'PKG-JASSUB-04', node: process.version, fixtures, rootDeclarations, packages, published: plugins.slice(0, 2).map(plugin => ({ label: plugin.label, archive: plugin.archive, sha256: hash(fs.readFileSync(plugin.archive)) })), matrix, scope: 'Actual npm 1.0.0/1.1.0 and Yarn-packed candidate/core installed outside workspace; offline installation and forced frozen reinstall with unchanged lock, strictly verified installed member hashes and non-link checks; root declaration text unchanged after CRLF normalization with both raw hashes recorded, exact old diagnostics, conditional accurate runtime declarations and Node lazy factory/legacy identity. Registrars are never invoked in Node; this is not browser Worker/WASM/font rendering or publication acceptance.' })
|
||||
console.log(`JASSUB isolated installed consumers passed ${matrix.length} compiler modes: ${output}`)
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
if (evidenceOutput)
|
||||
writeJson(path.join(evidenceOutput, 'failure.json'), { message: error.message, stack: error.stack })
|
||||
console.error(error)
|
||||
process.exitCode = 1
|
||||
})
|
||||
@@ -0,0 +1,74 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import fs from 'node:fs'
|
||||
// eslint-disable-next-line test/no-import-node-test -- Repository baseline runner.
|
||||
import test from 'node:test'
|
||||
import { ESLint } from 'eslint'
|
||||
import ts from 'typescript'
|
||||
import compat from 'typescript-compat'
|
||||
import { checkPluginEditorDeclaration, generatePluginEditorDeclaration } from '../../scripts/plugin-editor-types.mjs'
|
||||
import { checkConsumer } from '../../scripts/typecheck.mjs'
|
||||
import { jassubConsumerSource, jassubInvalidStatements, jassubRuntimeSource } from './jassub-consumer.mjs'
|
||||
import { verifyJassubContract } from './jassub-contract.mjs'
|
||||
import { readMember } from './releases.mjs'
|
||||
|
||||
const modes = [[ts, 'node10-commonjs'], [ts, 'nodenext-cjs'], [ts, 'nodenext-esm'], [ts, 'bundler-esm'], [compat, 'node10-commonjs']]
|
||||
|
||||
test('JASSUB keeps both actual published root declarations without changing historical extraction or replacement types', async () => {
|
||||
const contract = await verifyJassubContract()
|
||||
const candidate = fs.readFileSync('packages/artplayer-plugin-jassub/types/artplayer-plugin-jassub.d.ts', 'utf8').replaceAll('\r\n', '\n')
|
||||
for (const archive of contract.archives.values())
|
||||
assert.equal(candidate, readMember(archive, 'package/types/artplayer-plugin-jassub.d.ts').toString().replaceAll('\r\n', '\n'))
|
||||
for (const [compiler, mode] of modes) {
|
||||
const source = jassubConsumerSource(mode)
|
||||
assert.deepEqual(checkConsumer(compiler, mode, source), [], `${compiler.version} ${mode}`)
|
||||
const errors = checkConsumer(compiler, mode, `${source}\n${jassubInvalidStatements.join('\n')}\n`)
|
||||
assert.deepEqual(errors.map(item => item.line), jassubInvalidStatements.map((_, index) => source.split('\n').length + 1 + index))
|
||||
}
|
||||
})
|
||||
|
||||
test('JASSUB runtime entry checks optional options, sync instances, worker messages, callbacks and invalid consumers', () => {
|
||||
for (const [compiler, mode] of modes) {
|
||||
const source = jassubRuntimeSource(mode)
|
||||
assert.deepEqual(checkConsumer(compiler, mode, source), [], `${compiler.version} ${mode}`)
|
||||
const invalid = checkConsumer(compiler, mode, source.replaceAll(/\/\/ @ts-expect-error[^\n]*\n/g, ''))
|
||||
const expected = []
|
||||
let line = 1
|
||||
for (const text of source.split('\n')) {
|
||||
if (text.startsWith('// @ts-expect-error'))
|
||||
expected.push(line)
|
||||
else line++
|
||||
}
|
||||
assert.equal(expected.length, 14)
|
||||
assert.deepEqual(invalid.map(item => item.line), expected, `${compiler.version} ${mode}: each negative statement must fail independently`)
|
||||
}
|
||||
assert.deepEqual(checkConsumer(ts, 'nodenext-cjs', `import root = require('artplayer-plugin-jassub'); import legacy = require('artplayer-plugin-jassub/legacy'); import runtime = require('artplayer-plugin-jassub/runtime');
|
||||
const option: root.JassubOption = {workerUrl: '/worker.js', wasmUrl: '/worker.wasm', modernWasmUrl: '/modern.wasm'};
|
||||
root.default(option); legacy.default(option); runtime(); const value: runtime.RuntimeOption = {}; runtime(value);`), [])
|
||||
})
|
||||
|
||||
test('JASSUB generated editor declaration preserves the old global API and is valid in both compilers', async () => {
|
||||
const source = fs.readFileSync('packages/artplayer-plugin-jassub/types/artplayer-plugin-jassub.d.ts', 'utf8')
|
||||
const generated = generatePluginEditorDeclaration(source, 'artplayerPluginJassub')
|
||||
const file = 'docs/assets/ts/artplayer-plugin-jassub.d.ts'
|
||||
const [formatted] = await new ESLint({ fix: true, fixTypes: ['layout'] }).lintText(generated, { filePath: file })
|
||||
assert.equal(formatted.errorCount, 0)
|
||||
assert.equal(fs.readFileSync(file, 'utf8').replaceAll('\r\n', '\n'), formatted.output || generated)
|
||||
const core = fs.readFileSync('docs/assets/ts/artplayer.d.ts', 'utf8')
|
||||
const consumer = `declare const art: Artplayer;
|
||||
const option: artplayerPluginJassub.JassubOption = {workerUrl: '/worker.js', wasmUrl: '/worker.wasm', modernWasmUrl: '/modern.wasm'};
|
||||
const result = artplayerPluginJassub(option)(art);
|
||||
const promise: Promise<void> = result.instance.resize(true, 640, 360);
|
||||
const replacement: typeof artplayerPluginJassub = (_option) => (_art) => result;
|
||||
// @ts-expect-error Keep the historical required options argument.
|
||||
artplayerPluginJassub();
|
||||
// @ts-expect-error Keep the historical required resource URLs.
|
||||
artplayerPluginJassub({});
|
||||
// @ts-expect-error Global editor uses the historical force-first resize signature.
|
||||
result.instance.resize(640);`
|
||||
for (const compiler of [ts, compat]) {
|
||||
assert.deepEqual(checkPluginEditorDeclaration(generated, core, consumer, compiler), [])
|
||||
assert.equal(checkPluginEditorDeclaration(generated, core, consumer.replaceAll(/\/\/ @ts-expect-error[^\n]*\n/g, ''), compiler).length, 3)
|
||||
const oldEditor = `${source.replace(/^import.*$/gim, '')}\nexport = artplayerPluginJassub;\nexport as namespace artplayerPluginJassub;\n`
|
||||
assert(checkPluginEditorDeclaration(oldEditor, core, '', compiler).some(item => item.code === 2309), 'Reproduce the former conflicting export assignment before claiming the editor fix')
|
||||
}
|
||||
})
|
||||
+6
-3
@@ -2477,11 +2477,14 @@
|
||||
"ENG-06",
|
||||
"CORE-07"
|
||||
],
|
||||
"status": "todo",
|
||||
"status": "done",
|
||||
"risk": "H",
|
||||
"deliverable": "JASSUB option/instance 的兼容类型包装,vendor JS 例外记录",
|
||||
"acceptance": "严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容",
|
||||
"evidence": []
|
||||
"evidence": [
|
||||
"changes/2026-09-14-PKG-JASSUB-04-types.md",
|
||||
"baselines/jassub-types-validation.json"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "PKG-JASSUB-05",
|
||||
@@ -2531,7 +2534,7 @@
|
||||
],
|
||||
"status": "todo",
|
||||
"risk": "H",
|
||||
"deliverable": "独立记录 vendor 适配差异;幂等销毁、跨容器 setVideo、构造回滚、逐帧归属和倍率/能力边界",
|
||||
"deliverable": "独立记录 vendor 适配差异;幂等销毁、跨容器 setVideo、构造回滚、逐帧归属和倍率/能力边界;查询 timeout/Worker error 的缺失数据回调及每请求监听器/定时器清理(JASSUB-QUERY-01)",
|
||||
"acceptance": "保持公开真实 instance、同步方法与资源路径;保留原 vendor 来源指纹并记录每项实际补丁;候选修复正例和真实字幕/失败/时钟验证通过,未证实设备不得冒充通过",
|
||||
"evidence": []
|
||||
},
|
||||
|
||||
@@ -1258,7 +1258,7 @@
|
||||
"PKG-JASSUB-05"
|
||||
],
|
||||
"sourceFiles": [
|
||||
"packages/artplayer-plugin-jassub/src/index.js",
|
||||
"packages/artplayer-plugin-jassub/src/index.ts",
|
||||
"docs/assets/example/jassub.js"
|
||||
],
|
||||
"contract": "Preserve options pass-through, result.instance and worker/wasm/font URLs. Wrapper, worker and WASM must be a verified compatible set.",
|
||||
|
||||
@@ -35,3 +35,8 @@ Danmuku的PKG-DANMUKU-06保留实际npm 5.3.0根声明的完整类型形状与
|
||||
points、静态icons和回调receiver;根/legacy/runtime复用原分发实现,不添加不存在
|
||||
的factory.default。旧入口用户不需要因本次类型迁移修改代码。切换/runtime是可选
|
||||
的准确类型入口,历史声明与运行时的区别见包README;这里没有新增旧类型破坏授权。
|
||||
|
||||
JASSUB 的 PKG-JASSUB-04 保留实际 npm 1.0.0/1.1.0 相同的根声明(忽略换行),
|
||||
不改变必填资源 URL、旧 resize 参数顺序、Promise 返回声明和扩展索引。新增
|
||||
/runtime 描述真实可选参数、同步方法及 Worker 数据,并复用同一运行产物。
|
||||
这项实施没有新增旧类型破坏,不声称用户另行批准过 JASSUB 专属类型冲突。
|
||||
+1
-1
@@ -53,7 +53,7 @@ console.log(`✨ Built ${artplayerTSoutput}`);
|
||||
const type = pluginsTS[index]
|
||||
const { name, file } = parsePluginInfo(type)
|
||||
const source = String(fs.readFileSync(type))
|
||||
const semanticPlugin = ['artplayerPluginDanmukuMask', 'artplayerPluginDanmuku', 'artplayerPluginChromecast', 'artplayerPluginAsr', 'artplayerPluginMultipleSubtitles', 'artplayerPluginVttThumbnail', 'artplayerPluginHlsControl', 'artplayerPluginAudioTrack', 'artplayerPluginDashControl', 'artplayerPluginAds', 'artplayerPluginAmbilight', 'artplayerProxyCanvas', 'artplayerProxyMediabunny', 'artplayerPluginDocumentPip', 'ArtplayerToolIframe', 'ArtplayerToolThumbnail'].includes(name)
|
||||
const semanticPlugin = ['artplayerPluginJassub', 'artplayerPluginDanmukuMask', 'artplayerPluginDanmuku', 'artplayerPluginChromecast', 'artplayerPluginAsr', 'artplayerPluginMultipleSubtitles', 'artplayerPluginVttThumbnail', 'artplayerPluginHlsControl', 'artplayerPluginAudioTrack', 'artplayerPluginDashControl', 'artplayerPluginAds', 'artplayerPluginAmbilight', 'artplayerProxyCanvas', 'artplayerProxyMediabunny', 'artplayerPluginDocumentPip', 'ArtplayerToolIframe', 'ArtplayerToolThumbnail'].includes(name)
|
||||
const localTypes = name === 'artplayerProxyMediabunny' ? { './media': fs.readFileSync(path.join(path.dirname(type), 'media.d.ts'), 'utf8') } : {}
|
||||
const code = semanticPlugin
|
||||
? generatePluginEditorDeclaration(source, name, localTypes)
|
||||
|
||||
@@ -0,0 +1,236 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import fs from 'node:fs'
|
||||
// eslint-disable-next-line test/no-import-node-test -- Accurate runtime contracts use the repository runner.
|
||||
import test from 'node:test'
|
||||
import { isPromise } from 'node:util/types'
|
||||
import vm from 'node:vm'
|
||||
import { jassubCandidate, jassubEnvironment, jassubHistorical } from './helpers/jassub.js'
|
||||
|
||||
const candidate = await jassubCandidate()
|
||||
const frozen = (await jassubHistorical()).find(item => item.name === 'frozen-workspace-source')
|
||||
|
||||
function environment(implementation) {
|
||||
const code = `${implementation.code}\nObject.defineProperty(module.exports.default || module.exports, '__runtimePlatform', { value: { ErrorEvent, setGlobal: (name, value) => { globalThis[name] = value } } });`
|
||||
const env = jassubEnvironment({ ...implementation, code })
|
||||
env.platform = env.factory.__runtimePlatform
|
||||
return env
|
||||
}
|
||||
|
||||
function requestListeners(worker) {
|
||||
const listeners = new Map()
|
||||
worker.addEventListener = (type, callback) => {
|
||||
if (!listeners.has(type))
|
||||
listeners.set(type, new Set())
|
||||
listeners.get(type).add(callback)
|
||||
}
|
||||
worker.removeEventListener = (type, callback) => listeners.get(type)?.delete(callback)
|
||||
return {
|
||||
size: () => [...listeners.values()].reduce((sum, entries) => sum + entries.size, 0),
|
||||
emit(type, event) {
|
||||
for (const callback of [...listeners.get(type) || []]) callback(event)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
test('JASSUB accurate runtime resize uses width/height/top/left/force and lifecycle methods return synchronously', async () => {
|
||||
const env = environment(candidate)
|
||||
const { instance } = env.factory()(env.art)
|
||||
await env.ready()
|
||||
assert.equal(instance.resize(320, 180, 4, 7, false), undefined)
|
||||
assert.equal(instance._canvas.style.top, '4px')
|
||||
assert.equal(instance._canvas.style.left, '7px')
|
||||
await env.flush()
|
||||
const resize = env.workers[0].messages.at(-1)
|
||||
assert.equal(resize.target, 'canvas')
|
||||
assert.equal(resize.width, 320)
|
||||
assert.equal(resize.height, 180)
|
||||
assert.equal(resize.force, false)
|
||||
const video = env.createVideo()
|
||||
assert.equal(instance.setVideo(video), undefined)
|
||||
assert.equal(instance._video, video)
|
||||
assert.equal(instance.destroy(), undefined)
|
||||
assert.equal(env.workers[0].terminated, 1)
|
||||
assert.equal(env.parent.children.some(child => child.className === 'JASSUB'), false)
|
||||
})
|
||||
|
||||
test('JASSUB accurate sendMessage returns a Promise waiting for ready and resolving after the actual post', async () => {
|
||||
const env = environment(candidate)
|
||||
const { instance } = env.factory()(env.art)
|
||||
const payload = { value: 42 }
|
||||
const pending = instance.sendMessage('runtime-probe', payload)
|
||||
assert(isPromise(pending))
|
||||
let settled = false
|
||||
pending.then(() => {
|
||||
settled = true
|
||||
})
|
||||
await env.flush()
|
||||
assert.equal(settled, false)
|
||||
assert.deepEqual(env.workers[0].messages.map(message => message.target), ['init'])
|
||||
await env.ready()
|
||||
assert.equal(await pending, undefined)
|
||||
assert.equal(settled, true)
|
||||
assert.equal(env.workers[0].messages.at(-1).target, 'runtime-probe')
|
||||
assert.equal(env.workers[0].messages.at(-1).value, 42)
|
||||
instance.destroy()
|
||||
})
|
||||
|
||||
test('JASSUB accurate font options preserve mixed sources and font methods return synchronously', async () => {
|
||||
const env = environment(candidate)
|
||||
const bytes = new Uint8Array([1, 2, 3])
|
||||
const fonts = ['fixture.woff2', bytes]
|
||||
const availableFonts = { fixture: bytes, fallback: 'fallback.woff2' }
|
||||
const { instance } = env.factory({ fonts, availableFonts })(env.art)
|
||||
await env.ready()
|
||||
const init = env.workers[0].messages[0]
|
||||
assert.equal(init.fonts, fonts)
|
||||
assert.equal(init.fonts[1], bytes)
|
||||
assert.equal(init.availableFonts, availableFonts)
|
||||
assert.equal(instance.addFont(bytes), undefined)
|
||||
assert.equal(instance.setDefaultFont('fixture'), undefined)
|
||||
await env.flush()
|
||||
const added = env.workers[0].messages.find(message => message.target === 'addFont')
|
||||
assert.equal(added.font, bytes)
|
||||
assert.equal(env.workers[0].messages.at(-1).target, 'defaultFont')
|
||||
assert.equal(env.workers[0].messages.at(-1).font, 'fixture')
|
||||
instance.destroy()
|
||||
})
|
||||
|
||||
const subtitles = `[Script Info]
|
||||
ScriptType: v4.00+
|
||||
PlayResX: 640
|
||||
PlayResY: 360
|
||||
[V4+ Styles]
|
||||
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
|
||||
Style: Default,Arial,24,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,2,0,2,10,10,10,1
|
||||
[Events]
|
||||
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
|
||||
Dialogue: 0,0:00:01.00,0:00:03.00,Default,,0,0,0,,Runtime fixture`
|
||||
|
||||
test('JASSUB real Worker JS/WASM queries return numeric style indices and full event/style data through controlled transport', { timeout: 10000 }, async (context) => {
|
||||
const env = environment(candidate)
|
||||
env.platform.setGlobal('setTimeout', setTimeout)
|
||||
env.platform.setGlobal('clearTimeout', clearTimeout)
|
||||
const { instance } = env.factory({ subContent: subtitles, availableFonts: {}, fallbackFont: 'fixture', wasmUrl: '/runtime-fixture.wasm', onDemandRender: false, offscreenRender: false, asyncRender: false })(env.art)
|
||||
const worker = env.workers[0]
|
||||
const listeners = requestListeners(worker)
|
||||
const wasm = fs.readFileSync('packages/artplayer-plugin-jassub/worker/jassub-worker.wasm')
|
||||
const source = fs.readFileSync('packages/artplayer-plugin-jassub/worker/jassub-worker.js', 'utf8')
|
||||
const requests = []
|
||||
const messages = []
|
||||
let initialized = false
|
||||
let stopped = false
|
||||
const workerContext = {
|
||||
WebAssembly,
|
||||
TextDecoder,
|
||||
TextEncoder,
|
||||
console,
|
||||
onmessage: null,
|
||||
async fetch(url) {
|
||||
assert.equal(url, '/runtime-fixture.wasm')
|
||||
requests.push(url)
|
||||
return new Response(wasm, { headers: { 'Content-Type': 'application/wasm' } })
|
||||
},
|
||||
postMessage(message) {
|
||||
const data = structuredClone(message)
|
||||
messages.push(data)
|
||||
if (data.target === 'ready')
|
||||
initialized = true
|
||||
queueMicrotask(() => {
|
||||
if (stopped)
|
||||
return
|
||||
worker.onmessage({ data })
|
||||
listeners.emit('message', { data })
|
||||
})
|
||||
},
|
||||
}
|
||||
workerContext.self = workerContext
|
||||
vm.createContext(workerContext)
|
||||
// Execute complete production Worker JS and actual WASM. Only transport and fetch are controlled.
|
||||
vm.runInContext(source, workerContext, { timeout: 5000 })
|
||||
const post = worker.postMessage
|
||||
worker.postMessage = function (message) {
|
||||
post.call(this, message)
|
||||
if (!stopped)
|
||||
workerContext.onmessage({ data: structuredClone(message) })
|
||||
}
|
||||
const terminate = worker.terminate
|
||||
worker.terminate = function () {
|
||||
if (initialized && !stopped)
|
||||
workerContext.onmessage({ data: { target: 'destroy' } })
|
||||
stopped = true
|
||||
terminate.call(this)
|
||||
}
|
||||
context.after(() => instance.destroy())
|
||||
let readyEvents = 0
|
||||
instance.addEventListener('ready', (event) => {
|
||||
readyEvents++
|
||||
assert.equal(event.detail, null)
|
||||
assert.equal(event.target, instance)
|
||||
})
|
||||
assert(instance instanceof EventTarget)
|
||||
await new Promise(resolve => instance.addEventListener('ready', resolve, { once: true }))
|
||||
assert.equal(readyEvents, 1)
|
||||
assert.deepEqual(requests, ['/runtime-fixture.wasm'])
|
||||
const query = method => new Promise((resolve, reject) => {
|
||||
assert.equal(instance[method]((error, rows) => {
|
||||
if (error !== null) {
|
||||
reject(error ?? new Error('Successful queries must pass null as their error argument'))
|
||||
return
|
||||
}
|
||||
resolve(rows)
|
||||
}), undefined)
|
||||
})
|
||||
const events = await query('getEvents')
|
||||
const styles = await query('getStyles')
|
||||
assert.equal(events.length, 1)
|
||||
assert.equal(events[0].Start, 1000)
|
||||
assert.equal(events[0].Duration, 2000)
|
||||
assert.equal(events[0].Text, 'Runtime fixture')
|
||||
assert.equal(typeof events[0].Style, 'number')
|
||||
assert.equal(Object.hasOwn(events[0], '_index'), false)
|
||||
assert.equal(styles[events[0].Style].FontName, 'Arial')
|
||||
assert.equal(styles[events[0].Style].FontSize, 24)
|
||||
assert.equal(typeof styles[events[0].Style].PrimaryColour, 'number')
|
||||
assert.equal(listeners.size(), 0)
|
||||
assert.deepEqual(messages.find(message => message.target === 'getEvents').events, events)
|
||||
assert.deepEqual(messages.find(message => message.target === 'getStyles').styles, styles)
|
||||
assert.equal(instance.setEvent({ Text: 'Updated through Worker', Style: events[0].Style }, 0), undefined)
|
||||
await env.flush()
|
||||
assert.equal((await query('getEvents'))[0].Text, 'Updated through Worker')
|
||||
assert.equal(instance.setStyle({ FontSize: 32 }, events[0].Style), undefined)
|
||||
await env.flush()
|
||||
assert.equal((await query('getStyles'))[events[0].Style].FontSize, 32)
|
||||
})
|
||||
|
||||
for (const method of ['getEvents', 'getStyles']) {
|
||||
for (const mode of ['timeout', 'worker-error']) {
|
||||
test(`JASSUB frozen PKG-JASSUB-07 defect: ${method} ${mode} throws before the public error callback`, async () => {
|
||||
const env = environment(frozen)
|
||||
const timers = new Map()
|
||||
let next = 0
|
||||
env.platform.setGlobal('setTimeout', (callback) => {
|
||||
timers.set(++next, callback)
|
||||
return next
|
||||
})
|
||||
env.platform.setGlobal('clearTimeout', id => timers.delete(id))
|
||||
const { instance } = env.factory()(env.art)
|
||||
await env.ready()
|
||||
const listeners = requestListeners(env.workers[0])
|
||||
let calls = 0
|
||||
instance[method](() => calls++)
|
||||
assert.equal(listeners.size(), 2)
|
||||
const trigger = mode === 'timeout'
|
||||
? () => {
|
||||
const [id, callback] = [...timers][0]
|
||||
timers.delete(id)
|
||||
callback()
|
||||
}
|
||||
: () => listeners.emit('error', new env.platform.ErrorEvent('error', { error: new Error('controlled Worker error') }))
|
||||
assert.throws(trigger, { name: 'TypeError', message: /Cannot destructure property/ })
|
||||
assert.equal(calls, 0)
|
||||
assert.equal(listeners.size(), 2)
|
||||
assert.equal(timers.size, mode === 'timeout' ? 0 : 1)
|
||||
instance.destroy()
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import type Artplayer from 'artplayer'
|
||||
import type { JassubInstance, JassubOption } from 'artplayer-plugin-jassub'
|
||||
import jassub from 'artplayer-plugin-jassub'
|
||||
import legacy from 'artplayer-plugin-jassub/legacy'
|
||||
|
||||
type Equal<A, B> = (<T>() => T extends A ? 1 : 2) extends (<T>() => T extends B ? 1 : 2) ? true : false
|
||||
type Assert<T extends true> = T
|
||||
interface OldResult { name: 'artplayerPluginJassub', instance: JassubInstance }
|
||||
type OldFactory = (option: JassubOption) => (art: Artplayer) => OldResult
|
||||
type FactoryCheck = Assert<Equal<typeof jassub, OldFactory>>
|
||||
type ArgumentCheck = Assert<Equal<Parameters<typeof jassub>, [option: JassubOption]>>
|
||||
type ReturnCheck = Assert<Equal<ReturnType<ReturnType<typeof jassub>>, OldResult>>
|
||||
type ResizeCheck = Assert<Equal<Parameters<JassubInstance['resize']>, [force?: boolean, width?: number, height?: number, top?: number, left?: number]>>
|
||||
|
||||
declare const art: Artplayer
|
||||
declare const instance: JassubInstance
|
||||
const option: JassubOption = { workerUrl: '/worker.js', wasmUrl: '/worker.wasm', modernWasmUrl: '/modern.wasm', futureOption: true }
|
||||
const replacement: typeof jassub = (_option: JassubOption) => (_art: Artplayer) => ({ name: 'artplayerPluginJassub', instance })
|
||||
const reverse: OldFactory = jassub
|
||||
const legacyReplacement: typeof legacy = replacement
|
||||
const result: OldResult = jassub(option)(art)
|
||||
legacy(option)(art)
|
||||
const resize: Promise<void> = result.instance.resize(true, 640, 360, 0, 0)
|
||||
const video: Promise<void> = result.instance.setVideo(art.video)
|
||||
const destroy: Promise<void> = result.instance.destroy()
|
||||
const extension: string = result.instance.futureMethod(option.futureOption)
|
||||
export type { ArgumentCheck, FactoryCheck, ResizeCheck, ReturnCheck }
|
||||
export { destroy, extension, legacyReplacement, replacement, resize, reverse, video }
|
||||
@@ -0,0 +1,101 @@
|
||||
import type Artplayer from 'artplayer'
|
||||
import type { AssEvent, AssStyle, RuntimeFactory, RuntimeInstance, RuntimeOption, RuntimeResult } from 'artplayer-plugin-jassub/runtime'
|
||||
import runtime from 'artplayer-plugin-jassub/runtime'
|
||||
|
||||
type Equal<A, B> = (<T>() => T extends A ? 1 : 2) extends (<T>() => T extends B ? 1 : 2) ? true : false
|
||||
type Assert<T extends true> = T
|
||||
export type HostCheck = Assert<Equal<Parameters<ReturnType<RuntimeFactory>>, [art: Artplayer]>>
|
||||
|
||||
declare const art: Artplayer
|
||||
declare const canvas: HTMLCanvasElement
|
||||
const option: RuntimeOption = { canvas, fonts: ['/font.woff2', new Uint8Array([0])], availableFonts: { fallback: new Uint8Array([0]) }, onDemandRender: false }
|
||||
const factory: RuntimeFactory = runtime
|
||||
const result: RuntimeResult = runtime(option)(art)
|
||||
const replacement: typeof runtime = (_option?: RuntimeOption) => (_art: Artplayer) => result
|
||||
const defaults: RuntimeResult = runtime()(art)
|
||||
runtime(undefined)(art)
|
||||
runtime({ video: null, canvas })(art)
|
||||
const instance: RuntimeInstance = result.instance
|
||||
const target: EventTarget = instance
|
||||
const resize: void = instance.resize(640, 360, 0, 0, true)
|
||||
const video: void = instance.setVideo(art.video)
|
||||
const destroyed: void = instance.destroy()
|
||||
const error: Error = instance.destroy(new Error('test'))
|
||||
const errorOrEmpty: Error | '' = instance.destroy('failure')
|
||||
const sent: Promise<void> = instance.sendMessage('custom', { payload: new Uint8Array([1]) }, [])
|
||||
instance.setCurrentTime(false, 10, 2)
|
||||
instance.setTrackByUrl('/sub.ass')
|
||||
instance.setTrack('[Script Info]')
|
||||
instance.freeTrack()
|
||||
instance.setIsPaused(true)
|
||||
instance.setRate(2)
|
||||
instance.createEvent({ Start: 1000, Duration: 2000, Style: 0, Text: 'Subtitle' })
|
||||
instance.setEvent({ Text: 'Replacement' }, 0)
|
||||
instance.removeEvent(0)
|
||||
instance.createStyle({ Name: 'Default', FontSize: 24 })
|
||||
instance.setStyle({ Bold: 1 }, 0)
|
||||
instance.removeStyle(0)
|
||||
instance.styleOverride({ FontSize: 30 })
|
||||
instance.disableStyleOverride()
|
||||
instance.addFont(new Uint8Array([0]))
|
||||
instance.setDefaultFont('fallback')
|
||||
instance.runBenchmark()
|
||||
instance.getEvents((failure, events) => {
|
||||
const caught: Error | ErrorEvent | null = failure
|
||||
const values: AssEvent[] | undefined = events
|
||||
if (events) {
|
||||
for (const event of events) {
|
||||
const style: number = event.Style
|
||||
const start: number = event.Start
|
||||
void [style, start]
|
||||
}
|
||||
}
|
||||
void [caught, values]
|
||||
})
|
||||
instance.getStyles((_failure, styles) => {
|
||||
const values: AssStyle[] | undefined = styles
|
||||
void values
|
||||
})
|
||||
instance.addEventListener('ready', function (event) {
|
||||
function acceptsInstance(_instance: RuntimeInstance) {}
|
||||
acceptsInstance(this)
|
||||
const detail: null = event.detail
|
||||
void detail
|
||||
})
|
||||
instance.addEventListener('error', (event) => {
|
||||
const message: string = event.message
|
||||
void message
|
||||
})
|
||||
instance.addEventListener('custom', { handleEvent(event) {
|
||||
const type: string = event.type
|
||||
void type
|
||||
} })
|
||||
// @ts-expect-error Runtime URL fields are optional strings, not numbers.
|
||||
runtime({ workerUrl: 42 })
|
||||
// @ts-expect-error Accurate entry does not silently accept unknown options.
|
||||
runtime({ typoOption: true })
|
||||
// @ts-expect-error Resize starts with width; force is the fifth parameter.
|
||||
instance.resize(true)
|
||||
// @ts-expect-error Actual destroy is synchronous.
|
||||
const asyncDestroy: Promise<void> = instance.destroy()
|
||||
// @ts-expect-error Actual setVideo is synchronous.
|
||||
const asyncVideo: Promise<void> = instance.setVideo(art.video)
|
||||
// @ts-expect-error Actual resize is synchronous.
|
||||
const asyncResize: Promise<void> = instance.resize()
|
||||
// @ts-expect-error Registration is synchronous.
|
||||
runtime()(art).then(() => {})
|
||||
// @ts-expect-error sendMessage waits for initialization and returns a Promise.
|
||||
const syncMessage: void = instance.sendMessage('custom')
|
||||
// @ts-expect-error Style is the numeric libass index.
|
||||
instance.createEvent({ Style: 'Default' })
|
||||
// @ts-expect-error Worker responses do not contain _index.
|
||||
const index: number = ({} as AssEvent)._index
|
||||
// @ts-expect-error Callback errors may omit the event list.
|
||||
instance.getEvents((_error, events) => { const required: AssEvent[] = events; void required }) // eslint-disable-line style/max-statements-per-line -- Keep this rejected callback on one diagnostic line.
|
||||
// @ts-expect-error Font data must be a URL or bytes.
|
||||
instance.addFont(1)
|
||||
// @ts-expect-error Unknown instance methods are not hidden by an any index.
|
||||
instance.nonexistentMethod()
|
||||
// @ts-expect-error Only two blend modes are supported.
|
||||
runtime({ blendMode: 'gpu' })
|
||||
void [asyncDestroy, asyncVideo, asyncResize, defaults, destroyed, error, errorOrEmpty, factory, index, replacement, resize, sent, syncMessage, target, video]
|
||||
Reference in new issue
Block a user