refactor(chromecast): [PKG-CAST-04] preserve published types and add runtime entry

This commit is contained in:
Harvey Zhao committed 2026-09-13 17:34:49 +08:00
1 parent b0cfbfe3a0
commit 8ec9fbe83a
27 files changed
+994 -52

No files matched your search

+43 -13
View File
@@ -1,17 +1,47 @@
interface Option {
url?: string
sdk?: string
icon?: string
mimeType?: string
// 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 artplayerPluginChromecastDefinitions {
export interface Option {
url?: string
sdk?: string
icon?: string
mimeType?: string
}
export interface Chromecast {
name: 'artplayerPluginChromecast'
}
/** Published 1.1.0 result; actual registration is asynchronous. */
export type Result = Chromecast
export type Factory = (option: Option) => (art: Artplayer) => Chromecast
export type ConnectionState = 'disconnected' | 'connecting' | 'connected' | 'disconnecting'
export interface RuntimeOption extends Option {
onStateChange?: (this: RuntimeOption, state: ConnectionState) => void
onCastAvailable?: (this: RuntimeOption, available: boolean) => void
onCastStart?: (this: RuntimeOption) => void
onError?: (this: RuntimeOption, error: unknown) => void
}
export interface RuntimeResult extends Chromecast {
/** Last raw SDK SessionState, initially null; not the normalized callback state. */
getCastState: () => string | null
/** Whether this controller retains a session; does not prove receiver playback. */
isCasting: () => boolean
}
export interface RuntimeFactory {
(option: RuntimeOption): (art: Artplayer) => Promise<RuntimeResult>
default: RuntimeFactory
}
export const artplayerPluginChromecast: (option: Option) => (art: Artplayer) => Chromecast
}
interface Chromecast {
name: 'artplayerPluginChromecast'
declare const artplayerPluginChromecast: typeof artplayerPluginChromecastDefinitions.artplayerPluginChromecast
declare namespace artplayerPluginChromecast {
export type Option = artplayerPluginChromecastDefinitions.Option
export type Chromecast = artplayerPluginChromecastDefinitions.Chromecast
export type Result = artplayerPluginChromecastDefinitions.Result
export type Factory = artplayerPluginChromecastDefinitions.Factory
export type ConnectionState = artplayerPluginChromecastDefinitions.ConnectionState
export type RuntimeOption = artplayerPluginChromecastDefinitions.RuntimeOption
export type RuntimeResult = artplayerPluginChromecastDefinitions.RuntimeResult
export type RuntimeFactory = artplayerPluginChromecastDefinitions.RuntimeFactory
}
declare const artplayerPluginChromecast: (option: Option) => (art: Artplayer) => Chromecast
export default artplayerPluginChromecast
export = artplayerPluginChromecast
export as namespace artplayerPluginChromecast;
+2 -1
View File
@@ -97,7 +97,8 @@
"test:asr": "node --test test/asr.test.js test/asr-audio.test.js test/asr-encoding.test.js test/asr-lifecycle.test.js test/asr-distribution.test.js test/asr-fallback-routing.test.js test/asr-local-example.test.js test/asr-explicit-capture.test.js",
"test:asr-types-package": "node refactor/scripts/asr-package-types.mjs",
"test:asr-demo": "node refactor/scripts/asr-local-demo.mjs",
"test:chromecast": "node --test test/chromecast.test.js test/chromecast-failures.test.js test/chromecast-runtime.test.js"
"test:chromecast": "node --test test/chromecast.test.js test/chromecast-failures.test.js test/chromecast-runtime.test.js",
"test:chromecast-types-package": "node refactor/scripts/chromecast-package-types.mjs"
},
"browserslist": "last 1 Chrome version",
"devDependencies": {
@@ -2,4 +2,5 @@ node_modules
.DS_Store
yarn.lock
package-lock.json
src
src
tsconfig.json
@@ -20,9 +20,11 @@ The control HTML is captured at registration, as before.
Dependencies point from the factory to the controller, then to the SDK loader and
media builder. The loader and media builder do not receive an ArtPlayer instance.
Public declarations remain in `types/`; their historical mismatch with async
registration is tracked separately by PKG-CAST-04. Internal strict typing does not
silently change those declarations.
Public declarations remain in `types/`. The root and legacy entries retain npm
1.1.0's required option argument and synchronous name-only factory type, including
ordinary replacement-function assignment. `/runtime` describes the actual async
registration and callbacks using the same JavaScript files. Internal option and
result contracts reference these precise public types to prevent another drift.
## Initialization and ownership
@@ -111,6 +113,9 @@ node --test test/chromecast.test.js test/chromecast-failures.test.js test/chrome
yarn exec tsc -p packages/artplayer-plugin-chromecast/tsconfig.json
yarn exec eslint packages/artplayer-plugin-chromecast/src test/chromecast-runtime.test.js
yarn build artplayer-plugin-chromecast
yarn build:ts artplayer-plugin-chromecast
node --test refactor/scripts/chromecast-types.test.mjs
yarn test:chromecast-types-package
```
`ARTPLAYER_CHROMECAST_ARTIFACT` selects a built main or legacy file for the candidate
@@ -129,3 +134,25 @@ receiver playback. PKG-CAST-05 still requires the supported Chrome/HTTPS sender,
actual receiver, network-reachable media, source changes and disconnection evidence.
The default SDK URL is mutable; no remote SDK version, physical receiver or Safari
support is established by this source migration.
## Type and module compatibility
ADR-027 preserves the latest actual npm declaration, not a claim that all past
incompatible declarations can coexist. npm 1.0.0 used `export =` and its CommonJS
module exposed `.default`; npm 1.1.0 uses a default declaration and direct CommonJS
factory. Both legal JavaScript forms work in the candidate through a writable
self `.default`. Root types remain a plain factory without that required member.
The historical types-first root mapping also retains the latest declaration's
NodeNext ESM namespace shape and its existing direct-call diagnostic. Its legal
namespace consumers and full module replacement are tested independently. Choose
`/runtime` for a directly callable modern ESM default and precise async results;
its `.d.mts`, `.d.cts` and Node10 `export =` declarations share eight named types.
The option argument remains required on both type surfaces. Callbacks have the
live option object as `this`; errors are unknown, raw session state is string or
null, and `isCasting()` does not establish receiver playback.
The installed consumer verifier compares both actual historical tarballs and an
isolated candidate package, validates individual negative statement diagnostics,
and checks runtime entry identity. The editor declaration is generated semantically
from the root declaration. Keep internal `tsconfig.json` and source out of npm packs.
@@ -6,6 +6,76 @@ Chromecast plugin for ArtPlayer
[https://artplayer.org](https://artplayer.org/?libs=./uncompiled/artplayer-plugin-chromecast/index.js&example=chromecast)
## Usage
```js
import Artplayer from 'artplayer'
import artplayerPluginChromecast from 'artplayer-plugin-chromecast'
const art = new Artplayer({
container: '.artplayer-app',
url: 'https://example.com/video.mp4',
plugins: [artplayerPluginChromecast({
onStateChange(state) {
console.log(state)
},
onError(error) {
console.error(error)
},
})],
})
```
Replace the example URL with media reachable by your receiver. SDK loading starts
when the Cast control is clicked. Use a supported Chrome sender on HTTPS and an
available Cast receiver; loading the demo or a local video alone does not verify
receiver playback. The optional `url`, `sdk`, `icon` and `mimeType` override the
media URL, SDK script URL, control HTML and inferred media type respectively.
Callbacks are `onStateChange`, `onCastAvailable`, `onCastStart` and `onError`.
## TypeScript compatibility
The root and `legacy` declarations preserve the latest published 1.1.0 type shape:
a required options object and a synchronous name-only registration type. JavaScript
registration is actually asynchronous. New TypeScript consumers can use the
additive `artplayer-plugin-chromecast/runtime` entry for precise callbacks and a
`Promise<RuntimeResult>`, without loading another implementation:
```ts
import artplayerPluginChromecast from 'artplayer-plugin-chromecast/runtime'
import type { RuntimeOption } from 'artplayer-plugin-chromecast/runtime'
const options: RuntimeOption = {
onStateChange(state) {
// disconnected | connecting | connected | disconnecting
console.log(state, this.url)
},
onError(error) {
// unknown: SDK and user callbacks can throw arbitrary values.
console.error(error)
},
}
const register = artplayerPluginChromecast(options)
// In an existing ArtPlayer setup, register(art) returns Promise<RuntimeResult>.
// Supply register once through the player's plugins option.
```
The resolved result contains `name`, `getCastState(): string | null` and
`isCasting(): boolean`. The raw state is initially null and differs from the
normalized callback state. A retained session does not prove successful playback.
Version 1.0.0 used `export =` declarations and a CommonJS `.default` factory;
1.1.0 changed to a default declaration and direct CommonJS factory. Those old type
shapes cannot both describe the same root. The candidate keeps 1.1.0's root type
and supports both historical JavaScript factory forms. For older `import = require`
TypeScript consumers, migrate to `import cast = require('artplayer-plugin-chromecast/runtime')`.
Native ESM consumers should use the runtime default import shown above. Root
NodeNext namespace typing retains 1.1.0's existing behavior; its direct ESM default
call was already a type error. The runtime entry fixes that use without changing
the old root's module replacement contracts.
Maintenance and validation details are in [ARCHITECTURE.md](ARCHITECTURE.md).
## License
MIT © Harvey Zhao
@@ -27,11 +27,27 @@
"types": "./types/artplayer-plugin-chromecast.d.ts",
"import": "./dist/artplayer-plugin-chromecast.legacy.js",
"require": "./dist/artplayer-plugin-chromecast.legacy.js"
},
"./runtime": {
"import": {
"types": "./types/runtime.d.mts",
"default": "./dist/artplayer-plugin-chromecast.mjs"
},
"require": {
"types": "./types/runtime.d.cts",
"default": "./dist/artplayer-plugin-chromecast.js"
}
}
},
"main": "./dist/artplayer-plugin-chromecast.js",
"module": "./dist/artplayer-plugin-chromecast.mjs",
"types": "./types/artplayer-plugin-chromecast.d.ts",
"legacy": "./dist/artplayer-plugin-chromecast.legacy.js",
"browserslist": "last 1 Chrome version"
"browserslist": "last 1 Chrome version",
"typesVersions": {
"*": {
"legacy": ["types/artplayer-plugin-chromecast.d.ts"],
"runtime": ["types/runtime.d.ts"]
}
}
}
@@ -1,10 +1,11 @@
import type Artplayer from 'artplayer'
import type { RuntimeResult } from '../types/artplayer-plugin-chromecast'
import type { AvailabilityEvent, CastContext, CastSdk, CastSession, ChromecastOptions, ConnectionState, SessionEvent } from './types'
import icon from './cast.svg?raw'
import { loadMedia } from './media'
import { configure, loadSdk } from './sdk'
export function createController(art: Artplayer, option: ChromecastOptions) {
export function createController(art: Artplayer, option: ChromecastOptions): RuntimeResult {
let disposed = false
let sdk: CastSdk | undefined
let context: CastContext | undefined
@@ -1,15 +1,5 @@
export type ConnectionState = 'disconnected' | 'connecting' | 'connected' | 'disconnecting'
export interface ChromecastOptions {
url?: string
sdk?: string
icon?: string
mimeType?: string
onStateChange?: (this: ChromecastOptions, state: ConnectionState) => void
onCastAvailable?: (this: ChromecastOptions, available: boolean) => void
onCastStart?: (this: ChromecastOptions) => void
onError?: (this: ChromecastOptions, error: unknown) => void
}
export type { ConnectionState } from '../types/artplayer-plugin-chromecast'
export type ChromecastOptions = import('../types/artplayer-plugin-chromecast').RuntimeOption
export interface MediaInfo {
contentId: string
@@ -1,16 +1,41 @@
import type Artplayer from 'artplayer'
interface Option {
export interface Option {
url?: string
sdk?: string
icon?: string
mimeType?: string
}
interface Chromecast {
export interface Chromecast {
name: 'artplayerPluginChromecast'
}
/** Published 1.1.0 result; actual registration is asynchronous. */
export type Result = Chromecast
export type Factory = (option: Option) => (art: Artplayer) => Chromecast
export type ConnectionState = 'disconnected' | 'connecting' | 'connected' | 'disconnecting'
export interface RuntimeOption extends Option {
onStateChange?: (this: RuntimeOption, state: ConnectionState) => void
onCastAvailable?: (this: RuntimeOption, available: boolean) => void
onCastStart?: (this: RuntimeOption) => void
onError?: (this: RuntimeOption, error: unknown) => void
}
export interface RuntimeResult extends Chromecast {
/** Last raw SDK SessionState, initially null; not the normalized callback state. */
getCastState: () => string | null
/** Whether this controller retains a session; does not prove receiver playback. */
isCasting: () => boolean
}
export interface RuntimeFactory {
(option: RuntimeOption): (art: Artplayer) => Promise<RuntimeResult>
default: RuntimeFactory
}
declare const artplayerPluginChromecast: (option: Option) => (art: Artplayer) => Chromecast
export default artplayerPluginChromecast
@@ -0,0 +1,15 @@
import type { RuntimeFactory } from './artplayer-plugin-chromecast.js'
declare const artplayerPluginChromecast: RuntimeFactory
// eslint-disable-next-line ts/no-redeclare -- Ambient merge exposes types on the CommonJS factory.
declare namespace artplayerPluginChromecast {
type Chromecast = import('./artplayer-plugin-chromecast.js').Chromecast
type ConnectionState = import('./artplayer-plugin-chromecast.js').ConnectionState
type Factory = import('./artplayer-plugin-chromecast.js').Factory
type Option = import('./artplayer-plugin-chromecast.js').Option
type Result = import('./artplayer-plugin-chromecast.js').Result
type RuntimeFactory = import('./artplayer-plugin-chromecast.js').RuntimeFactory
type RuntimeOption = import('./artplayer-plugin-chromecast.js').RuntimeOption
type RuntimeResult = import('./artplayer-plugin-chromecast.js').RuntimeResult
}
export = artplayerPluginChromecast
@@ -0,0 +1,5 @@
import type { RuntimeFactory } from './artplayer-plugin-chromecast.js'
declare const artplayerPluginChromecast: RuntimeFactory
export default artplayerPluginChromecast
export type { Chromecast, ConnectionState, Factory, Option, Result, RuntimeFactory, RuntimeOption, RuntimeResult } from './artplayer-plugin-chromecast.js'
+15
View File
@@ -0,0 +1,15 @@
import type { RuntimeFactory } from './artplayer-plugin-chromecast.js'
declare const artplayerPluginChromecast: RuntimeFactory
// eslint-disable-next-line ts/no-redeclare -- Ambient merge exposes types on the CommonJS factory.
declare namespace artplayerPluginChromecast {
type Chromecast = import('./artplayer-plugin-chromecast.js').Chromecast
type ConnectionState = import('./artplayer-plugin-chromecast.js').ConnectionState
type Factory = import('./artplayer-plugin-chromecast.js').Factory
type Option = import('./artplayer-plugin-chromecast.js').Option
type Result = import('./artplayer-plugin-chromecast.js').Result
type RuntimeFactory = import('./artplayer-plugin-chromecast.js').RuntimeFactory
type RuntimeOption = import('./artplayer-plugin-chromecast.js').RuntimeOption
type RuntimeResult = import('./artplayer-plugin-chromecast.js').RuntimeResult
}
export = artplayerPluginChromecast
@@ -0,0 +1,277 @@
{
"schemaVersion": 1,
"task": "PKG-CAST-04",
"status": "done",
"capturedAt": "2026-09-13T09:34:22.272Z",
"policy": "ADR-027; latest actual npm 1.1.0 root type preserved, older 1.0.0 conflicts documented, legal historical JavaScript forms verified separately.",
"installed": {
"file": "refactor/.cache/chromecast-package-types-1aMMh2/report.json",
"sha256": "69546139f319c05efe115682fcd5ea34a092221fe0aeb0489763c7bba827125b",
"configurations": 17,
"candidateConfigurations": 7,
"negativeStatementsPerCandidate": 17,
"packages": [
{
"name": "artplayer",
"archive": "D:\\github\\ArtPlayer\\refactor\\.cache\\chromecast-package-types-1aMMh2\\artplayer.tgz",
"sha256": "0c0ea52d41575261b836aefac68280733b119c75834ecad6d4bc78edb073f4c1",
"files": {
"package/ARCHITECTURE.md": "bee98fc9d93a3911d58e98ef15f2dbe07e12f2274c444d989094248a22079e46",
"package/README.md": "1e185e65f67fc2081b4182bbc6c4558ea05b6e3fa3acc5dd0c3ac7df1d542b09",
"package/THIRD_PARTY_NOTICES": "b77b5b46b79fa0cda850b0781f78c3b1112c4ed9672fa291956fe8eff53098e7",
"package/dist/artplayer.js": "6dc8ca6d6eb34aa8a056cbc31328efd634530b3c5c60f0014cb7ce719fabc335",
"package/dist/artplayer.legacy.js": "80f74a8a39df4baf64cc00c65d21cd57c011bfcdead0da38eea077fbc67c5c04",
"package/dist/artplayer.mjs": "432e2b0ad92009a09743e020bf7c928c8b8f18a52496622b16259ec9ab20059f",
"package/dist/i18n/ar.js": "bd0bef65f4f7dc3f8ed562904cc6fa9071ff4537fb64c1e0fc3d554141bcd3cb",
"package/dist/i18n/ar.mjs": "59310b7bbeea83bbb7d73aadc5ca82a6ec269c3530d19aa741c94d5c64fc5da8",
"package/dist/i18n/cs.js": "6e2c93daba24c2e48dfa206bcf4f2f2a8a02713b856e404d2e4e4ecd8309ca2f",
"package/dist/i18n/cs.mjs": "c73fba18797d62d7b2581e67fe7340c35fe84510b2250ff1ecaad253d9cabbac",
"package/dist/i18n/es.js": "f4133ac6ea06b69a474b33c4a1809a4066e2376bb9464a3f3aa8330f01f1944a",
"package/dist/i18n/es.mjs": "1390bca3def2be115e811de337df0a0c3f63998e51359f283023246245c552a7",
"package/dist/i18n/fa.js": "8d3ef2e0f0cc0ea480c2167b6fcb87cdf444e90759c3519e8daa139fec576da4",
"package/dist/i18n/fa.mjs": "ab441e8a2d79043b29ceca87c783bf06874bb9d3a149c1604bc167eb481be2a9",
"package/dist/i18n/fr.js": "89dc4194a6c0a962b9ce756b52c90401c1acf3b159f265f3bc0d7d6d7d987f03",
"package/dist/i18n/fr.mjs": "99491878430fb7b6f2b7bf93da0588a831704b412e01116f23ba1d9d45863816",
"package/dist/i18n/id.js": "4c6dc86db741bcc2857df55a9d037e82e91d375fa6fd22e0562809ff621b92db",
"package/dist/i18n/id.mjs": "383e5803a6b6686c6de4e867770241de75b6f517dcca2bdedc6f2004bf6e1fb3",
"package/dist/i18n/pl.js": "3a38828b1c3d719c0aca9906cf6a46c23555a8411f8d57645f0e0ce8147dc664",
"package/dist/i18n/pl.mjs": "079ed96732979035297af387c64a7b99519b5a43107fb86735a654ea4fe8e9b5",
"package/dist/i18n/ru.js": "62fac9dddb302eb16382e6e9301fc4b2c66f5f69d4a358a26b67026a5fa4e787",
"package/dist/i18n/ru.mjs": "a893aeaee1d79409db72e324fbff200e3864f33ed168f04d91d25f7cf2efa765",
"package/dist/i18n/tr.js": "1492c36b069a7d73db4f3913bd18085faff7afd8b3c158138fe264e31600e38c",
"package/dist/i18n/tr.mjs": "a90478cac078836b76c2a98261907610fee07f3e4b28671b8e7dbf533778f918",
"package/dist/i18n/vi.js": "c7216ef7e608a79b7e2c479a07bf310d4ab79577947ac4a62d676faaced0e9cf",
"package/dist/i18n/vi.mjs": "66270fdfa0b5da396322d1769169b7df37a030ba15ac18eb30bd87b3af8074ec",
"package/dist/i18n/zh-tw.js": "11c49857d6d40a5ffd224ffa59c9026e71621b345f63c5a086326db80a22371a",
"package/dist/i18n/zh-tw.mjs": "a38e630cb96de96c15d824686aec750e25cf6c8ada01285a292442a81566314f",
"package/package.json": "82e92699e7c29847f32ac6767ece0401290e7d7a12854722bdbcfd09b90b7954",
"package/types/COMPATIBILITY.md": "a7da9e102a116104b513df4f7451247b7759bc502d3375f30bd5b14a541826e0",
"package/types/README.md": "1bd9868e773610e6312fd48a1c5aa20772d29df0af40c3db66e75b7ab4a1aee0",
"package/types/artplayer.d.cts": "3685065e4263abcfd9d953176e461d5b3eb990b0363b54689a7f55deba770fef",
"package/types/artplayer.d.mts": "b664fec51ee5c5381f2ceab25d99d52aee56feab04a2ee7b4ea5b2145ea5fa0e",
"package/types/artplayer.d.ts": "5d0162adf7ac6d0f2bb9b5cfe361f28657c4d9c540cadbf36eba3cc954ce50f8",
"package/types/component.d.ts": "ae5bc1ddeaa1ee1ebbc62573e32423993727079205c64751a5a314170e588a32",
"package/types/config.d.ts": "36f9a84cd436f17701a0d11d3417fc1fe0414e0c52c80083b94a85dfc329e37e",
"package/types/cssVar.d.ts": "93217860fff2ebf750f606d95ddd22220a58837119e63bdf2f5f78fd7488458b",
"package/types/emitter.d.ts": "7e1f9a2a8ce65feb95734f1ed52d1334e57aa388bbef2a43d8bc53a9b6f6623c",
"package/types/events.d.ts": "f83624c9268a284477e8bd793fe3562a2363b72f2a44d5c64fe8c6a6cede8830",
"package/types/i18n.d.ts": "f4f2f05bdfda73c74fef34767cb18ee8e11b743120b1c4598e9714d7846018e5",
"package/types/icons.d.ts": "326a67ba7bdcf52862c5b2a269e540140aff98d05c7b96196485a77f84fe5960",
"package/types/language.d.cts": "f7928e108cf7b7814c2d75cb0a525333e95873fca6cd6d89a740450656b9c1cd",
"package/types/language.d.mts": "6619a26d604b7cccbe2f596243d87ae37cfaae33b8b52874ec071d9787446f9e",
"package/types/legacy/language.d.ts": "c7abda4d506fd933c350e9030290bf9e64347a921d683a66115da1995ebe0a81",
"package/types/option.d.ts": "ccf2c8ddc537dc65c9682d39f044726e23bff18f476b622512d7e6a4b949b5f6",
"package/types/playback.d.ts": "58705c273df6e76a77a6575e769a9ec6c013785c25080f2551aa22415788d870",
"package/types/player.d.ts": "92b00211b70ac71336e495899df972a0519dd138111846044fe4e0f967966177",
"package/types/plugin.d.ts": "fc3594a41c51d8248713e33c486fb1f6a518ac5c7ce31be6988a6cd8d052c5f3",
"package/types/quality.d.ts": "1ac1a48b4f74ff9ba1ce60443e1463d50d1c6a2e5d87d63dc7d2a8eb7f4c2c34",
"package/types/runtime.d.cts": "7ee988dfea9a0ac71a34a656f96f7b74e78aa92547cfdfab7ff7e1139a77d4a3",
"package/types/runtime.d.mts": "dcb85f59a8b7714918ced0c1d18c2bb24e8b80b56d8c594e019dcbdb5ad88b08",
"package/types/runtime.d.ts": "9c6ccd39a1be293f17ae46dae8ea077053e12635ee79d73a5e3b3a559230fcff",
"package/types/runtime/component.d.ts": "1b4c0cee44568649e80140fe778febfcae88af0ac85515f1cbeff78ab0c6d14f",
"package/types/runtime/construction.d.ts": "7a87bedf6f7ffff0c46078aa5f1551e0077d35574e286516121f5f935e2d9ce4",
"package/types/runtime/events.d.ts": "a1e2f9d387475453eaf95c2a5ee79e228636c0bb859bda3fcc46da46574a0c31",
"package/types/runtime/media.d.ts": "8fc89401be7fe25542c169d07dd5675f10916ed20be8493e19695966cf3864a8",
"package/types/runtime/option.d.ts": "6e334e1a76fdcc40b4665c02523bd31c3994d96078fadeb40e23422aa54c1a1d",
"package/types/runtime/player.d.ts": "8ac6383502e4872e83659838844856903031a81a0adc8e8535feb78453aa042c",
"package/types/runtime/plugin.d.ts": "8528f2b17843149f42f9cc16d6fd70baa238c9dc2c329b67d2c38096799c69bd",
"package/types/runtime/services.d.ts": "604c9f8e4b6c0da818f96576bf9f4b0e96085a9003927b2f7ba30027b1ba237e",
"package/types/runtime/setting.d.ts": "349c1fcb205ad312916bd3ee55e6bf241ea8a569fb2ab6c80822e69fc518f7d7",
"package/types/runtime/subtitle.d.ts": "269ff84678d327b3a9dc2f5bc6cc0d731d1e081b85422e474621d5d792efb153",
"package/types/runtime/template.d.ts": "7a62f968a38b4bf9109a4439557c02074eb1d62ba1f6e21a8eb194e91f0badfe",
"package/types/runtime/utils.d.ts": "6bf731705d86853e066dbaf3b8d4003b6548423fe615b7412b722bd0d3a6e48b",
"package/types/setting.d.ts": "08305a75dff96f06490430e099338b463df471b550c1d0dac3abc8995b39eb09",
"package/types/subtitle.d.ts": "b18314e2d64efdb6045830b6e12f615ae2ed585416130e562329ad3ea337f6ce",
"package/types/template.d.ts": "4792bc0c2cbcb7966e4375118bd34b326166e0831862e94ace4cf6b1bbe6a8c8",
"package/types/utils.d.ts": "fca0d3d814df94a25f9eb067c86fa0cf272e2791bac793b91229d57680797712"
}
},
{
"name": "artplayer-plugin-chromecast",
"archive": "D:\\github\\ArtPlayer\\refactor\\.cache\\chromecast-package-types-1aMMh2\\artplayer-plugin-chromecast.tgz",
"sha256": "19a75ec78ebefdf65c1fea39b1d7521427bc3e7deda28a521af0ffa3c4356ce1",
"files": {
"package/ARCHITECTURE.md": "9bb5037a502538fba71f57cfb3781237643e17781d0530a338ca9523dd0812b7",
"package/README.md": "67210276efa07486360a58356eb9269cd4f81603ced66ba7fa05581d2c1dd7e3",
"package/dist/artplayer-plugin-chromecast.js": "80b12b3081086488c9d80f03979c336d4a6fdb44dca298e218dfeaaadb295d00",
"package/dist/artplayer-plugin-chromecast.legacy.js": "b21a30852403914a3bf3defc89aff11f2cc23b044ed68ccf667164b477f04172",
"package/dist/artplayer-plugin-chromecast.mjs": "68d81fcab469f74fe9d9740842d989e10144f7ff28c7ade91ed22adee90f44d7",
"package/package.json": "e55b546bc362c688a14be8a9303ca2f1be07715f48a22ff6dfb15f3006a3808f",
"package/types/artplayer-plugin-chromecast.d.ts": "0d1988c1b96bf1689a09dfd7fe4fdc759a9eb35cd84736f4aaf07c8d10f1e21a",
"package/types/runtime.d.cts": "4ee4eb8696a46468056ff49d61929563ec4c87f0eff7824cf6358ad81803927f",
"package/types/runtime.d.mts": "9af013354b8f917ecfe1408021898ad0b2c75d5033140999c7d2a290ffea09d2",
"package/types/runtime.d.ts": "4ee4eb8696a46468056ff49d61929563ec4c87f0eff7824cf6358ad81803927f"
}
}
],
"fixtures": {
"test/types/chromecast-public.ts": "3ebfe431def890cd47f67117138c0d73d91246cf4c80428ec463dac10484c90f",
"refactor/fixtures/consumers/chromecast-published.ts": "ee4aaa79833bec89f8369b9eed53a99885c45546e20fd888284a09ea9411d6cc"
}
},
"checks": [
{
"file": "refactor/.cache/chromecast04-types.log",
"sha256": "92a7439bbb2fa17525d848a11da5cdc88985785e4fd3188cf51b32445798843f"
},
{
"file": "refactor/.cache/chromecast04-editor.log",
"sha256": "f1b5e5ce6aa295eeeff0ec21ffd02916c8b858a8622f48f8ad01c654bbb0726b"
},
{
"file": "refactor/.cache/chromecast04-build.log",
"sha256": "d6438362aa196e9cf28d14ec392519324f7f07ecab43285157187a892d35e03f"
},
{
"file": "refactor/.cache/chromecast04-typecheck.log",
"sha256": "e0afab94d5d4170527f7a5943255f982fafc8506dae165228803030c8389b8d6"
},
{
"file": "refactor/.cache/chromecast04-lint-final.log",
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
{
"file": "refactor/.cache/chromecast04-installed-docs-final.log",
"sha256": "ca0f7f736066498942d0fb9a412a8da67a432808270f96dcf52f1b34e61d16b1"
}
],
"sourceInputs": [
{
"path": "docs/assets/ts/artplayer-plugin-chromecast.d.ts",
"algorithm": "sha256-lf",
"sha256": "0a0b932081cac0efbcedea28dc1077484963b7f406808378704062661a4c0338"
},
{
"path": "packages/artplayer-plugin-chromecast/.npmignore",
"algorithm": "sha256",
"sha256": "771df7bdc177956bd2f3493875159425bf1c2dd11d7d7386cb494b07620c0786"
},
{
"path": "packages/artplayer-plugin-chromecast/src/cast.svg",
"algorithm": "sha256-lf",
"sha256": "48470d66b5874569c54d66889b0ec62c33bf809670d4baf2eded2bfa83d6d726"
},
{
"path": "packages/artplayer-plugin-chromecast/src/controller.ts",
"algorithm": "sha256-lf",
"sha256": "1bf55c5a6a4a83290ee591d4286452acd7ba0b27d01e4b6293e16d61772655c2"
},
{
"path": "packages/artplayer-plugin-chromecast/src/index.ts",
"algorithm": "sha256-lf",
"sha256": "951d8bbaf273c56722d3767a514a3f8a65050122d934612e5c53c8349d1400e7"
},
{
"path": "packages/artplayer-plugin-chromecast/src/media.ts",
"algorithm": "sha256-lf",
"sha256": "6c16b3cc7b5c570dbb6ae7a4ff9c1492f8e6a07d07662db2b417ff825b7f0dd1"
},
{
"path": "packages/artplayer-plugin-chromecast/src/sdk.ts",
"algorithm": "sha256-lf",
"sha256": "eee5ff443406c400bd54822b71b20e1bb8a9191e15c625eb329cb3207a01902f"
},
{
"path": "packages/artplayer-plugin-chromecast/src/types.ts",
"algorithm": "sha256-lf",
"sha256": "184088b4a641ac3c40a7ea9904b9033617ceba99b6046d959ea3604007cd5e20"
},
{
"path": "packages/artplayer-plugin-chromecast/types/artplayer-plugin-chromecast.d.ts",
"algorithm": "sha256-lf",
"sha256": "2a1a872fe393fa2b30277de5ca3b739233a02464c7349710b1a721c9c822dd1a"
},
{
"path": "packages/artplayer-plugin-chromecast/types/runtime.d.cts",
"algorithm": "sha256-lf",
"sha256": "4ee4eb8696a46468056ff49d61929563ec4c87f0eff7824cf6358ad81803927f"
},
{
"path": "packages/artplayer-plugin-chromecast/types/runtime.d.mts",
"algorithm": "sha256-lf",
"sha256": "9af013354b8f917ecfe1408021898ad0b2c75d5033140999c7d2a290ffea09d2"
},
{
"path": "packages/artplayer-plugin-chromecast/types/runtime.d.ts",
"algorithm": "sha256-lf",
"sha256": "4ee4eb8696a46468056ff49d61929563ec4c87f0eff7824cf6358ad81803927f"
},
{
"path": "refactor/fixtures/consumers/chromecast-published.ts",
"algorithm": "sha256-lf",
"sha256": "ee4aaa79833bec89f8369b9eed53a99885c45546e20fd888284a09ea9411d6cc"
},
{
"path": "refactor/scripts/chromecast-package-types.mjs",
"algorithm": "sha256-lf",
"sha256": "1c5666d0e71e694a7845ab3229f1c7f5112f5098885da99586c808d13f659126"
},
{
"path": "refactor/scripts/chromecast-types.test.mjs",
"algorithm": "sha256-lf",
"sha256": "ed3ca3a6ad0a6b1dbf4788a0eb653d08e610a320a833042d75f8254d677e81ee"
},
{
"path": "scripts/build-ts.js",
"algorithm": "sha256-lf",
"sha256": "3fee651375aa90673cee28b2b80c0b35490833fed0d12ae219aa7ab69a36295c"
},
{
"path": "test/types/chromecast-public.ts",
"algorithm": "sha256-lf",
"sha256": "3ebfe431def890cd47f67117138c0d73d91246cf4c80428ec463dac10484c90f"
}
],
"unchangedArtifactsComparedWith": "b0cfbfe3a09a84a295a257e45e4577847588d1cb",
"artifacts": [
{
"file": "packages/artplayer-plugin-chromecast/dist/artplayer-plugin-chromecast.js",
"oldSha256": "80b12b3081086488c9d80f03979c336d4a6fdb44dca298e218dfeaaadb295d00",
"sha256": "80b12b3081086488c9d80f03979c336d4a6fdb44dca298e218dfeaaadb295d00",
"bytesEqual": true,
"textEqual": true
},
{
"file": "packages/artplayer-plugin-chromecast/dist/artplayer-plugin-chromecast.legacy.js",
"oldSha256": "b21a30852403914a3bf3defc89aff11f2cc23b044ed68ccf667164b477f04172",
"sha256": "b21a30852403914a3bf3defc89aff11f2cc23b044ed68ccf667164b477f04172",
"bytesEqual": true,
"textEqual": true
},
{
"file": "packages/artplayer-plugin-chromecast/dist/artplayer-plugin-chromecast.mjs",
"oldSha256": "68d81fcab469f74fe9d9740842d989e10144f7ff28c7ade91ed22adee90f44d7",
"sha256": "68d81fcab469f74fe9d9740842d989e10144f7ff28c7ade91ed22adee90f44d7",
"bytesEqual": true,
"textEqual": true
},
{
"file": "docs/compiled/artplayer-plugin-chromecast.js",
"oldSha256": "80b12b3081086488c9d80f03979c336d4a6fdb44dca298e218dfeaaadb295d00",
"sha256": "80b12b3081086488c9d80f03979c336d4a6fdb44dca298e218dfeaaadb295d00",
"bytesEqual": true,
"textEqual": true
},
{
"file": "docs/compiled/artplayer-plugin-chromecast.legacy.js",
"oldSha256": "b21a30852403914a3bf3defc89aff11f2cc23b044ed68ccf667164b477f04172",
"sha256": "b21a30852403914a3bf3defc89aff11f2cc23b044ed68ccf667164b477f04172",
"bytesEqual": true,
"textEqual": true
},
{
"file": "docs/compiled/artplayer-plugin-chromecast.mjs",
"oldSha256": "68d81fcab469f74fe9d9740842d989e10144f7ff28c7ade91ed22adee90f44d7",
"sha256": "68d81fcab469f74fe9d9740842d989e10144f7ff28c7ade91ed22adee90f44d7",
"bytesEqual": true,
"textEqual": true
}
],
"priorFailure": {
"file": "refactor/.cache/chromecast04-installed.log",
"sha256": "b8684603b95c258bb42bd8bd8a9ddfaf4a8799c6a8e0f0639efaf29863ca623b",
"cause": "Implementation tsconfig leaked into candidate tarball; excluded by package .npmignore before final isolated install."
},
"browserEvidence": "Unchanged JavaScript bytes retain CAST03 validation; no new real SDK or physical receiver evidence.",
"releaseReady": false
}
@@ -0,0 +1,31 @@
# PKG-CAST-04 Chromecast 类型与安装消费者完成
根/legacy保留真实npm 1.1.0的必填Option、同步name结果和完整普通工厂替换关系。
新增/runtime用同一JS实现准确描述异步注册、四个回调、live option的this、原始
string|null状态和isCasting。Node10 export=、NodeNext CJS/ESM均提供八个命名类型,
self.default保持可写。内部选项和controller返回类型引用公共精确类型,减少漂移。
真实1.0.0是export=声明及CJS对象.default,1.1.0是default声明及CJS直接函数。
候选继续支持两种合法JS调用;1.1.0根NodeNext namespace形状及原有直接调用错误
保持,较早import=require调用迁移到/runtime。README和ARCHITECTURE给出具体说明。
这是已确认历史类型规则的应用,没有再次改变最新根签名或取得新的发布授权。
顺手纠正决策编号冲突:Iframe原ADR-025/026不变,历史类型策略改为ADR-027。
中央文档解释旧类型记录中的ADR-025别名,不改写冻结报告或已验证包内容。
验证覆盖两个真实旧归档和候选的17种安装配置。7个候选配置全部正例通过,
各自按行拒绝17个反例;包括TS5.9.3/4.3.5 noInterop。旧1.1.0 NodeNext直接消费
八个诊断的code和line与候选相同;旧1.0.0的export=差异单独保留。实际离线安装、
frozen重装、全部成员字节和CJS/ESM入口身份通过。4项专项测试验证完整工厂替换、
真实历史声明、独立编辑器和运行时导出。编辑器经语义生成,产物与源声明一致。
首轮pack发现内部tsconfig.json混入发布包,已更新.npmignore并重跑;最终tarball
包括完成后的README/ARCHITECTURE,十个文件逐字匹配。没有用源码路径替代安装消费。
正常构建后,dist及docs/compiled的六个文件与b0cfbfe3a逐字节相同。因此本次不重跑
已在PKG-CAST-03完成的135项浏览器用例,不增加新的设备结论。全局366个生产TS文件
strict和既有消费者通过,专属lint通过。一次误用不存在的typecheck:strict命令已
改为仓库实际yarn typecheck并成功;原日志保留,不把失败算作通过。
CAST-TYPE-01关闭。CAST05仍需要真实HTTPS sender、SDK和Cast设备投屏证据;CAST06
的完整分发、演示及发布前审查仍待完成。详见
[机器证据](../baselines/chromecast-types-validation.json)。此任务独立本地提交,无推送、标签或npm发布。
+6 -1
View File
@@ -29,7 +29,12 @@
| ADR-022 | 采用方向 | Chrome 不可用时使用 Codex 内置浏览器 | 用户明确授权;记录实际环境和能力范围,必要设备测试仍独立验收 |
| ADR-023 | 已验证于 Audio Track | 参数类型纠正会破坏旧推断时,默认入口保留兼容类型,/runtime 复用相同运行文件提供精确类型 | PKG-AUDIO-04 的 Parameters 与无注解 update 实现证明 Partial/重载仍可能破坏旧用户;沿用核心的类型分层方向,具体包需独立编译与入口身份验证,不增加第二套实现或用 any 绕过 |
| ADR-024 | 已验证于 CORE-24 | 切源播放意图归属当前操作,在公开 play/pause 命令入口更新,内部暂停不覆盖用户意图 | Audio-05 三引擎复现连续切源最终停在 0;新操作继承未完成操作的意图,显式暂停及其修订号终止旧恢复副作用,过期 Promise 仍保留原结算语义;不依赖用户回调之后才执行的事件监听器 |
| ADR-025 | 用户已确认,待实施验证 | 历史声明冲突以最新已发布类型形状为准,较早差异提供迁移,合法旧JS继续兼容,/runtime提供精确类型 | 2026-09-13明确答复“接受这条规则并继续”;覆盖Canvas/Ambilight/VTT Thumbnail/Multiple Subtitles,见[type-compatibility-policy.md](type-compatibility-policy.md),不等同任务完成或发布授权 |
| ADR-027 | 用户已确认,逐包实施验证 | 历史声明冲突以最新已发布类型形状为准,较早差异提供迁移,合法旧JS继续兼容,/runtime提供精确类型 | 2026-09-13明确答复“接受这条规则并继续”;Canvas/Ambilight/VTT Thumbnail/Multiple Subtitles已完成相关类型任务,后续各包独立验证,见[type-compatibility-policy.md](type-compatibility-policy.md),不等同全包完成或发布授权 |
编号更正(2026-09-13):历史类型策略最初误用已由Iframe消息边界占用的ADR-025,
现唯一编号为ADR-027。此前类型相关变更记录、包架构及冻结报告中的“ADR-025”
指向[type-compatibility-policy.md](type-compatibility-policy.md),应读作ADR-027;
不改写已归档证据或已验证包内容。Iframe原ADR-025/026及其引用保持原意。
## 开始实现前需补齐的决定
@@ -0,0 +1,17 @@
import Artplayer from 'artplayer'
import chromecast from 'artplayer-plugin-chromecast'
interface Option { url?: string, sdk?: string, icon?: string, mimeType?: string }
interface Result { name: 'artplayerPluginChromecast' }
type Factory = (option: Option) => (art: Artplayer) => Result
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
type SameArguments = Assert<Equal<Parameters<typeof chromecast>, [option: Option]>>
type SameResult = Assert<Equal<ReturnType<ReturnType<typeof chromecast>>, Result>>
const replacement: Factory = (_option: Option) => (_art: Artplayer) => ({ name: 'artplayerPluginChromecast' })
const toOld: Factory = chromecast
const fromOld: typeof chromecast = replacement
const art = new Artplayer({ container: '#player', url: '/video.mp4', plugins: [chromecast({})] })
const name: 'artplayerPluginChromecast' = chromecast({ url: '/cast.mp4', sdk: '/sdk.js', icon: '<b>Cast</b>', mimeType: 'video/mp4' })(art).name
void [toOld, fromOld, name]
export type { SameArguments, SameResult }
+5 -4
View File
@@ -4,7 +4,7 @@
基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 228 项,范围 22 个包及工作区/示例。
状态:todo 78 / doing 14 / blocked 0 / done 136 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
状态:todo 75 / doing 16 / blocked 0 / done 137 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
前置依赖是启动条件;验收是完成条件。任务可以继续拆分,但不能复用或悄悄删除旧 ID。
@@ -240,7 +240,7 @@
| ID | 范围 / 步骤 | 前置依赖 | 交付物 | 验收条件 | 风险 | 状态 |
| --- | --- | --- | --- | --- | --- | --- |
| PKG-MASK-01 | artplayer-plugin-danmuku-mask<br>核对包契约与历史用法 | BASE-05 | 模型参数、start/stop、默认下载路径及 mask 样式 | 源码/声明/README/demo/发布包差异已登记;公开形状和版本范围冻结;接续 SDK-08,分别核实 Yarn 解析版本、未固定版本的模型 solutionPath 和资源来源 | H | todo |
| PKG-MASK-01 | artplayer-plugin-danmuku-mask<br>核对包契约与历史用法 | BASE-05 | 模型参数、start/stop、默认下载路径及 mask 样式 | 源码/声明/README/demo/发布包差异已登记;公开形状和版本范围冻结;接续 SDK-08,分别核实 Yarn 解析版本、未固定版本的模型 solutionPath 和资源来源 | H | doing |
| PKG-MASK-02 | artplayer-plugin-danmuku-mask<br>建立特有行为与错误测试 | PKG-MASK-01, ENG-03, ENG-05 | 加载期间停止/销毁、重复启动、推理失败、WebGL/CPU 边界 | 旧版本行为可重跑,成功/失败/切源/销毁有必要断言 | H | todo |
| PKG-MASK-03 | artplayer-plugin-danmuku-mask<br>整理内部职责与资源 | PKG-MASK-02, CORE-18, PKG-DANMUKU-07 | 模型加载/推理/画布输出分离,阻止重叠推理与过期写入 | 结构变化和缺陷修复分开记录;原 API/事件/资源生命周期通过 | H | todo |
| PKG-MASK-04 | artplayer-plugin-danmuku-mask<br>迁移自有源码和公开类型 | PKG-MASK-03, ENG-04, ENG-06, CORE-07 | 模型 adapter、canvas 和选项的精确类型 | 严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容 | H | todo |
@@ -290,7 +290,7 @@
| PKG-CAST-01 | artplayer-plugin-chromecast<br>核对包契约与历史用法 | BASE-05 | url/sdk/icon/mimeType、loader、会话、全局 SDK 与结果 | 源码/声明/README/demo/发布包差异已登记;公开形状和版本范围冻结 | H | done |
| PKG-CAST-02 | artplayer-plugin-chromecast<br>建立特有行为与错误测试 | PKG-CAST-01, ENG-03, ENG-05 | SDK 脚本加载失败/重入、无设备/拒绝、重复实例与销毁 | 旧版本行为可重跑,成功/失败/切源/销毁有必要断言 | H | done |
| PKG-CAST-03 | artplayer-plugin-chromecast<br>整理内部职责与资源 | PKG-CAST-02, CORE-11, CORE-13 | SDK singleton/实例订阅/会话与控件职责分离 | 结构变化和缺陷修复分开记录;原 API/事件/资源生命周期通过 | H | done |
| PKG-CAST-04 | artplayer-plugin-chromecast<br>迁移自有源码和公开类型 | PKG-CAST-03, ENG-04, ENG-06, CORE-07 | Cast 能力 adapter、全局对象和元数据类型 | 严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容 | H | todo |
| PKG-CAST-04 | artplayer-plugin-chromecast<br>迁移自有源码和公开类型 | PKG-CAST-03, ENG-04, ENG-06, CORE-07 | Cast 能力 adapter、全局对象和元数据类型 | 严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容 | H | done |
| PKG-CAST-05 | artplayer-plugin-chromecast<br>验证新旧核心和组合 | PKG-CAST-04, CORE-22 | stub 测试之外记录实际 Cast 设备会话、源更新和断开 | 最终核心与原支持范围核心分别通过;设备/SDK 缺证据不能标完成 | H | todo |
| PKG-CAST-06 | artplayer-plugin-chromecast<br>验证分发并同步文档 | PKG-CAST-05, ENG-07 | chromecast.js、SDK URL/权限能力及待支持环境说明 | tarball 入口/资源、类型、8082 demo 和 README 一致,有回退记录 | H | todo |
@@ -309,7 +309,7 @@
| ID | 范围 / 步骤 | 前置依赖 | 交付物 | 验收条件 | 风险 | 状态 |
| --- | --- | --- | --- | --- | --- | --- |
| PKG-DANMUKU-01 | artplayer-plugin-danmuku<br>核对弹幕全部公开契约 | BASE-05 | emit/load/config/hide/show/reset/mount、option/isHide/isStop/icons 与事件清单 | 源码、声明、发布包、Bilibili 输入和设置入口对照 | H | todo |
| PKG-DANMUKU-01 | artplayer-plugin-danmuku<br>核对弹幕全部公开契约 | BASE-05 | emit/load/config/hide/show/reset/mount、option/isHide/isStop/icons 与事件清单 | 源码、声明、发布包、Bilibili 输入和设置入口对照 | H | doing |
| PKG-DANMUKU-02 | artplayer-plugin-danmuku<br>建立弹幕算法与浏览器基线 | PKG-DANMUKU-01, ENG-05, ENG-08 | 密集/稀疏弹幕、过滤/异步输入、seek/倍率/重载测试 | 轨道选择、发射顺序、事件、内存和可视结果可重跑 | H | todo |
| PKG-DANMUKU-03 | artplayer-plugin-danmuku<br>整理加载、解析与配置 | PKG-DANMUKU-02, CORE-08 | bilibili/input/parser/config 的分层与取消 | 旧格式、回调、过滤、追加/替换语义保持 | M | todo |
| PKG-DANMUKU-04 | artplayer-plugin-danmuku<br>整理时钟、队列与轨道调度 | PKG-DANMUKU-03, CORE-10 | danmuku 调度器和确定性时钟测试 | seek/暂停/倍率/长时间运行无顺序和碰撞回归 | H | todo |
@@ -545,6 +545,7 @@
- PKG-CAST-01: [记录](baselines/chromecast-release.json) [记录](baselines/chromecast-contract.md) [记录](baselines/chromecast-contract-validation.json) [记录](changes/2026-09-13-PKG-CAST-01-contracts.md)
- PKG-CAST-02: [记录](changes/2026-09-13-PKG-CAST-02-failures.md) [记录](baselines/chromecast-failures-validation.json)
- PKG-CAST-03: [记录](changes/2026-09-13-PKG-CAST-03-runtime.md) [记录](baselines/chromecast-runtime-validation.json)
- PKG-CAST-04: [记录](changes/2026-09-13-PKG-CAST-04-types.md) [记录](baselines/chromecast-types-validation.json)
- PKG-DPIP-01: [记录](changes/2026-09-12-PKG-DPIP-01-contract.md) [记录](baselines/dpip-release.json) [记录](baselines/dpip-contract.md) [记录](baselines/dpip-contract-validation.json) [记录](dpip-validation.md)
- PKG-DPIP-02: [记录](changes/2026-09-12-PKG-DPIP-02-tests.md) [记录](baselines/dpip-behavior-validation.json) [记录](dpip-validation.md)
- PKG-DPIP-03: [记录](changes/2026-09-12-PKG-DPIP-03-lifecycle.md) [记录](baselines/dpip-lifecycle-validation.json) [记录](dpip-validation.md)
+11
View File
@@ -1,5 +1,16 @@
# 进度与证据
## PKG-CAST-04 公开类型与隔离安装完成
最新npm 1.1.0根类型保持,/runtime精确描述异步注册、回调与查询。17安装配置、
7候选配置各17逐行反例、4项专项测试和全局366生产TS strict通过。pack混入tsconfig
已修复,最终文档包重新安装通过;正常构建的六个JS文件与CAST03逐字节一致。
见[变更](changes/2026-09-13-PKG-CAST-04-types.md)及[证据](baselines/chromecast-types-validation.json)。
类型策略重号更正为ADR-027,旧冻结记录保留并中央解释。
228项:137 done、16 doing、75 todo。弹幕/遮罩契约并行推进,
Cast05真实SDK设备与06完整分发仍待完成;没有推送或发布。
## PKG-CAST-03 Chromecast生产重构完成
5个TS模块与35个候选测试落地,SDK等待/会话返回值/过期结果/实例图标/错误Promise
+1 -1
View File
@@ -215,7 +215,7 @@
| ASR-CORS-01 | open / 已复现 | Default WebAudio binding silences CORS-cross-origin media that otherwise advances normally | PKG-ASR-05 |
| ASR-RESTART-01 | resolved / 已复现 | Delayed captured-source restart resumes recognition after a newer pause or stop | PKG-ASR-09 |
| VENDOR-10 | resolved / 源码/产物事实 | Chromecast embeds a Font Awesome Pro path with no established redistribution entitlement | PKG-CAST-03, PKG-CAST-06 |
| CAST-TYPE-01 | open / 源码/产物事实 | Chromecast declarations describe a synchronous name-only registration and omit existing runtime callbacks and query methods | PKG-CAST-04 |
| CAST-TYPE-01 | resolved / 已复现 | Chromecast declarations describe a synchronous name-only registration and omit existing runtime callbacks and query methods | PKG-CAST-04 |
| CAST-SDK-01 | resolved / 已复现 | Cast requestSession completion is incorrectly passed to loadMedia as a session | PKG-CAST-03, PKG-CAST-05 |
| CAST-LOADER-01 | resolved / 已复现 | Cast readiness waits hang or overwrite each other across ready SDKs, repeated clicks and multiple instances | PKG-CAST-03, PKG-CAST-05 |
| CAST-LIFE-01 | resolved / 已复现 | Cast listeners and continuations survive player destruction and update another player icon | PKG-CAST-03, PKG-CAST-05 |
+10 -3
View File
@@ -4765,8 +4765,8 @@
{
"id": "CAST-TYPE-01",
"title": "Chromecast declarations describe a synchronous name-only registration and omit existing runtime callbacks and query methods",
"confirmation": "source-observed",
"status": "open",
"confirmation": "reproduced",
"status": "resolved",
"owners": [
"PKG-CAST-04"
],
@@ -4775,7 +4775,14 @@
"refactor/baselines/chromecast-contract.md"
],
"compatibleResolution": "Preserve the latest published root factory type and provide a precise runtime type view; verify valid historical JavaScript module forms independently.",
"closureCriteria": "Frozen type consumers and replacements pass, accurate async/callback/state declarations work through an additive entry, and actual installed exports are tested."
"closureCriteria": "Frozen type consumers and replacements pass, accurate async/callback/state declarations work through an additive entry, and actual installed exports are tested.",
"resolutionEvidence": [
"refactor/changes/2026-09-13-PKG-CAST-04-types.md",
"refactor/baselines/chromecast-types-validation.json",
"refactor/scripts/chromecast-types.test.mjs",
"refactor/scripts/chromecast-package-types.mjs"
],
"resolutionRationale": "Actual npm 1.1.0 root factory and namespace contracts retained; older export-assignment conflict documented. Additive runtime describes real async/callback/state API through the same JS. Four tests and 17 installed configurations verify identities, replacements, editor and 17 individual negative statements per candidate mode. Real receivers remain CAST05."
},
{
"id": "CAST-SDK-01",
@@ -0,0 +1,144 @@
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 { verifyChromecastContract } from './chromecast-contract.mjs'
import { hash, readMember } from './releases.mjs'
async function main() {
assert.equal(process.env.npm_config_user_agent?.split(' ')[0], 'yarn/1.22.22', 'Use yarn test:chromecast-types-package')
const yarn = process.env.npm_execpath
assert(yarn && fs.existsSync(yarn))
const output = fs.mkdtempSync(path.join(workspace, 'refactor/.cache/chromecast-package-types-'))
console.log(`Chromecast isolated package evidence: ${output}`)
const contract = await verifyChromecastContract()
const fixtures = Object.fromEntries(['test/types/chromecast-public.ts', 'refactor/fixtures/consumers/chromecast-published.ts'].map(file => [file, fs.readFileSync(path.join(workspace, file), 'utf8')]))
const packages = []
for (const name of ['artplayer', 'artplayer-plugin-chromecast']) {
const archive = path.join(output, `${name}.tgz`)
fs.writeFileSync(path.join(output, `${name}-pack.log`), run([yarn, 'pack', '--filename', archive], path.join(workspace, 'packages', name)))
const manifest = JSON.parse(readMember(archive, 'package/package.json'))
const files = packedFiles(archive)
checkFiles(manifest, files, name === contract.baseline.release.name ? Object.keys(contract.baseline.release.files) : [])
packages.push({ name, archive, sha256: hash(fs.readFileSync(archive)), files: Object.fromEntries(files.map(member => [member, hash(readMember(archive, member))])) })
}
const matrix = []
const publishedDiagnostics = new Map()
const oldNamespaceCodes = [2344, 2344, 2344, 2344, 2322, 2322, 2349, 2349]
const core = packages[0]
const candidates = [
...[contract.baseline.previous[0], contract.baseline.release].map(release => ({ ...release, archive: contract.archives.get(release.version), label: `published-${release.version}` })),
{ ...packages[1], label: 'candidate' },
]
for (const plugin of candidates) {
const consumer = consumerDirectory()
try {
writeJson(path.join(consumer, 'package.json'), { name: 'chromecast-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))
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', '--ignore-scripts', '--non-interactive'], consumer))
assert.deepEqual(fs.readFileSync(path.join(consumer, 'yarn.lock')), lock)
fs.writeFileSync(path.join(output, `${plugin.label}-yarn.lock`), lock)
for (const pkg of [core, plugin]) {
const root = path.join(consumer, 'node_modules', pkg.name)
assert.equal(fs.realpathSync(root), root, 'Installed package must not be a workspace link')
for (const [member, expected] of Object.entries(pkg.files))
assert.equal(hash(fs.readFileSync(path.join(root, member.slice(8)))), expected, `Installed bytes differ: ${member}`)
}
const runtimeFile = path.join(consumer, 'exports.cjs')
fs.writeFileSync(runtimeFile, plugin.label === 'candidate'
? `const assert = require('node:assert/strict');
for (const name of ['artplayer-plugin-chromecast', 'artplayer-plugin-chromecast/legacy', 'artplayer-plugin-chromecast/runtime']) { const factory = require(name); assert.equal(typeof factory, 'function'); assert.equal(factory.default, factory); assert.equal(Object.getOwnPropertyDescriptor(factory, 'default').writable, true); assert.equal(typeof factory({}), 'function'); }
assert.equal(require('artplayer-plugin-chromecast/runtime'), require('artplayer-plugin-chromecast'));
Promise.all(['artplayer-plugin-chromecast', 'artplayer-plugin-chromecast/runtime', 'artplayer-plugin-chromecast/legacy'].map(name => import(name))).then(([root, runtime, legacy]) => { assert.deepEqual(Object.keys(root), ['default']); assert.deepEqual(Object.keys(runtime), ['default']); assert.equal(runtime.default, root.default); assert.equal(root.default.default, root.default); assert.equal(legacy.default, require('artplayer-plugin-chromecast/legacy')); }).catch(error => { console.error(error); process.exitCode = 1; });`
: `const assert = require('node:assert/strict'); global.window = {}; const value = require('artplayer-plugin-chromecast'); assert.equal(typeof value, '${plugin.version === '1.0.0' ? 'object' : 'function'}'); assert.equal(typeof (value.default || value)({}), 'function');`)
fs.writeFileSync(path.join(output, `${plugin.label}-runtime.log`), run([runtimeFile], consumer))
const modes = [[ts, 'node10-commonjs'], [ts, 'nodenext-cjs'], [ts, 'nodenext-esm'], [ts, 'bundler-esm'], [compat, 'node10-commonjs']]
if (plugin.label === 'candidate')
modes.push([ts, 'node10-commonjs-no-interop'], [compat, 'node10-commonjs-no-interop'])
for (const [compiler, mode] of modes) {
const next = mode.startsWith('nodenext')
const extension = next ? mode.endsWith('-cjs') ? 'cts' : 'mts' : 'ts'
let source = fixtures[plugin.label === 'candidate' ? 'test/types/chromecast-public.ts' : 'refactor/fixtures/consumers/chromecast-published.ts']
if (plugin.label === 'candidate' && mode === 'nodenext-esm') {
source = source
.replace('import chromecast from \'artplayer-plugin-chromecast\'', 'import chromecastModule from \'artplayer-plugin-chromecast\'\nconst chromecast = chromecastModule.default')
.replace('import legacy from \'artplayer-plugin-chromecast/legacy\'', 'import legacyModule from \'artplayer-plugin-chromecast/legacy\'\nconst legacy = legacyModule.default')
.replace('namespace.default', 'namespace.default.default')
}
if (mode.endsWith('-no-interop'))
source = source.replace('import runtime from \'artplayer-plugin-chromecast/runtime\'', 'import runtime = require(\'artplayer-plugin-chromecast/runtime\')')
const filename = path.join(consumer, `consumer.${extension}`)
const files = [filename]
if (plugin.label === 'candidate' && mode !== 'bundler-esm' && mode !== 'nodenext-esm') {
const commonjs = path.join(consumer, `commonjs.${extension}`)
fs.writeFileSync(commonjs, `import chromecast = require('artplayer-plugin-chromecast'); import legacy = require('artplayer-plugin-chromecast/legacy'); import runtime = require('artplayer-plugin-chromecast/runtime');
import type Artplayer from 'artplayer';
const replacement: typeof chromecast.default = (_option) => (_art: Artplayer) => ({name: 'artplayerPluginChromecast'});
const legacyReplacement: typeof legacy.default = replacement;
chromecast.default({}); legacy.default({}); runtime({}); runtime.default({});
const option: runtime.RuntimeOption = { onError(error) { const self: runtime.RuntimeOption = this; const value: unknown = error; void [self, value]; } }; runtime(option); void legacyReplacement;`)
files.push(commonjs)
}
const options = { strict: true, noEmit: true, skipLibCheck: false, types: [], esModuleInterop: !mode.endsWith('-no-interop'), 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 }
function compile(code) {
fs.writeFileSync(filename, code)
const program = compiler.createProgram(files, options)
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()))), `Types escaped installed consumer: ${actual}`)
}
return compiler.getPreEmitDiagnostics(program).map(item => ({ code: item.code, line: item.file && item.start !== undefined ? item.file.getLineAndCharacterOfPosition(item.start).line + 1 : null, message: compiler.flattenDiagnosticMessageText(item.messageText, '\n').replaceAll(consumer.replaceAll('\\', '/'), '<consumer>') }))
}
const diagnostics = compile(source)
const historicalFailure = plugin.label === 'published-1.1.0' && mode === 'nodenext-esm'
assert.deepEqual(diagnostics.map(item => item.code), historicalFailure ? oldNamespaceCodes : [], `${plugin.label} ${compiler.version} ${mode}`)
const invalid = plugin.label === 'candidate' ? compile(source.replaceAll(/\/\/ @ts-expect-error[^\n]*\n/g, '')) : []
if (plugin.label === 'candidate') {
const expectedLines = []
let line = 1
for (const text of source.split('\n')) {
if (text.startsWith('// @ts-expect-error'))
expectedLines.push(line)
else line++
}
assert.equal(expectedLines.length, 17, 'Keep all callback, Promise, state and historical factory rejection fixtures')
assert.deepEqual(invalid.map(item => item.line).sort((a, b) => a - b), expectedLines, 'Each invalid statement must fail at its own line')
}
const published = plugin.label === 'candidate' ? compile(fixtures['refactor/fixtures/consumers/chromecast-published.ts']) : diagnostics
assert.deepEqual(published.map(item => item.code), (plugin.label === 'candidate' || plugin.label === 'published-1.1.0') && mode === 'nodenext-esm' ? oldNamespaceCodes : [], 'Preserve exact historical direct-consumer diagnostic codes')
const diagnosticsKey = `${compiler.version}:${mode}`
if (plugin.label === 'published-1.1.0')
publishedDiagnostics.set(diagnosticsKey, published.map(({ code, line }) => ({ code, line })))
if (plugin.label === 'candidate' && publishedDiagnostics.has(diagnosticsKey))
assert.deepEqual(published.map(({ code, line }) => ({ code, line })), publishedDiagnostics.get(diagnosticsKey), 'Candidate retains the latest published diagnostic codes and source positions')
const namespace = plugin.label !== 'published-1.0.0' && mode === 'nodenext-esm'
? compile(`import chromecast from 'artplayer-plugin-chromecast'; import type Artplayer from 'artplayer';
const replacement: typeof chromecast.default = (_option) => (_art: Artplayer) => ({name: 'artplayerPluginChromecast'});
chromecast.default({}); void replacement;`)
: []
assert.deepEqual(namespace, [], 'Latest published NodeNext namespace remains valid')
const olderRequire = mode !== 'bundler-esm' && mode !== 'nodenext-esm'
? compile(`import chromecast = require('artplayer-plugin-chromecast'); chromecast({url: '/movie.mp4', sdk: '/sender.js', icon: '<b>cast</b>', mimeType: 'video/mp4'});`)
: []
assert.deepEqual(olderRequire.map(item => item.code), mode !== 'bundler-esm' && mode !== 'nodenext-esm' && plugin.label !== 'published-1.0.0' ? [2349] : [], 'Earlier export-assignment typing conflicts are retained and documented')
matrix.push({ plugin: plugin.label, compiler: compiler.version, mode, historicalFailure, diagnostics, invalid, published, namespace, olderRequire })
}
}
finally {
removeConsumer(consumer)
}
}
writeJson(path.join(output, 'report.json'), { suite: 'chromecast-isolated-package-types', introducedBy: 'PKG-CAST-04', scope: 'Two actual published plugins and packed candidate installed outside workspace with packed candidate core; offline and frozen reinstall, exact bytes, full factory replacements and required root argument. Runtime adds precise callbacks, asynchronous registration and self alias. Published 1.1.0 NodeNext ESM namespace and eight original direct-consumer diagnostics are retained. Published 1.0.0 export-assignment typing conflicts are recorded separately; both versions had four optional option fields. This is not real SDK, device or complete distribution acceptance.', fixtures: Object.fromEntries(Object.entries(fixtures).map(([file, source]) => [file, hash(source)])), packages, published: candidates.slice(0, 2).map(pkg => ({ label: pkg.label, archive: pkg.archive, sha256: pkg.sha256 })), matrix })
console.log(`Chromecast installed types passed: ${matrix.length} cases; ${output}`)
}
main().catch((error) => {
console.error(error)
process.exitCode = 1
})
+147
View File
@@ -0,0 +1,147 @@
import assert from 'node:assert/strict'
import fs from 'node:fs'
import path from 'node:path'
// eslint-disable-next-line test/no-import-node-test -- Node baseline runner.
import test from 'node:test'
import vm from 'node:vm'
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 { verifyChromecastContract } from './chromecast-contract.mjs'
import { readMember } from './releases.mjs'
const modes = [[ts, 'node10-commonjs'], [ts, 'nodenext-cjs'], [ts, 'nodenext-esm'], [ts, 'bundler-esm'], [compat, 'node10-commonjs']]
function publicSource(source, mode) {
return mode === 'nodenext-esm'
? source.replace('import chromecast from \'artplayer-plugin-chromecast\'', 'import chromecastModule from \'artplayer-plugin-chromecast\'\nconst chromecast = chromecastModule.default')
.replace('import legacy from \'artplayer-plugin-chromecast/legacy\'', 'import legacyModule from \'artplayer-plugin-chromecast/legacy\'\nconst legacy = legacyModule.default')
.replace('namespace.default', 'namespace.default.default')
: source
}
function negativeLines(source) {
const expected = []
let line = 1
for (const text of source.split('\n')) {
if (text.startsWith('// @ts-expect-error'))
expected.push(line)
else
line++
}
return expected
}
test('Chromecast keeps the exact historical factory type and exposes accurate runtime callback and Promise types', () => {
const source = fs.readFileSync('test/types/chromecast-public.ts', 'utf8')
for (const [compiler, mode] of modes) {
const adapted = publicSource(source, mode)
assert.deepEqual(checkConsumer(compiler, mode, adapted), [], `${compiler.version} ${mode}`)
const invalid = checkConsumer(compiler, mode, adapted.replaceAll(/\/\/ @ts-expect-error[^\n]*\n/g, ''))
const expected = negativeLines(adapted)
assert(expected.length >= 12, 'Keep callback, factory, Promise and old entry negative coverage')
assert.deepEqual(invalid.map(item => item.line).sort((a, b) => a - b), expected, `${compiler.version} ${mode}: every invalid statement must fail independently`)
}
assert.deepEqual(checkConsumer(ts, 'nodenext-cjs', `import chromecast = require('artplayer-plugin-chromecast'); import legacy = require('artplayer-plugin-chromecast/legacy');
import runtime = require('artplayer-plugin-chromecast/runtime');
const option: chromecast.Option = {}; chromecast.default(option); legacy.default(option); runtime(option); runtime.default(option);`), [])
})
test('Chromecast exact historical declarations retain required argument, optional field and pure factory substitution contracts', async () => {
const contract = await verifyChromecastContract()
const source = fs.readFileSync('refactor/fixtures/consumers/chromecast-published.ts', 'utf8')
for (const version of ['1.0.0', '1.1.0']) {
const old = readMember(contract.archives.get(version), 'package/types/artplayer-plugin-chromecast.d.ts').toString()
assert.equal(old.includes('export = artplayerPluginChromecast'), version === '1.0.0')
const checks = `
type OriginalOption = {url?: string; sdk?: string; icon?: string; mimeType?: string};
type OriginalResult = {name: 'artplayerPluginChromecast'};
type OriginalFactory = (option: OriginalOption) => (art: Artplayer) => OriginalResult;
type OriginalEqual<A, B> = (<T>() => T extends A ? 1 : 2) extends (<T>() => T extends B ? 1 : 2) ? true : false;
type OriginalAssert<T extends true> = T;
type FactoryCheck = OriginalAssert<OriginalEqual<typeof chromecast, OriginalFactory>>;
type ArgumentCheck = OriginalAssert<OriginalEqual<Parameters<typeof chromecast>, [option: OriginalOption]>>;
type ResultCheck = OriginalAssert<OriginalEqual<ReturnType<ReturnType<typeof chromecast>>, OriginalResult>>;
const plainReplacement: typeof chromecast = (_option) => (_art) => ({name: 'artplayerPluginChromecast'});
const reverse: OriginalFactory = chromecast;
void [plainReplacement, reverse];`
for (const compiler of [ts, compat]) {
const filename = path.resolve('test/types/chromecast-history.ts')
const declaration = path.resolve('test/types/chromecast-history.d.ts')
function historical(code) {
const host = compiler.createCompilerHost({})
const original = host.getSourceFile.bind(host)
host.getSourceFile = (file, language, ...rest) => path.resolve(file) === filename
? compiler.createSourceFile(file, code.replace('from \'artplayer-plugin-chromecast\'', 'from \'./chromecast-history\''), language, true)
: path.resolve(file) === declaration ? compiler.createSourceFile(file, old, language, true) : original(file, language, ...rest)
const exists = host.fileExists.bind(host)
host.fileExists = file => path.resolve(file) === declaration || exists(file)
const program = compiler.createProgram([filename, declaration], { strict: true, noEmit: true, skipLibCheck: false, types: [], esModuleInterop: true, module: compiler.ModuleKind.CommonJS, moduleResolution: compiler.ModuleResolutionKind.NodeJs, target: compiler.ScriptTarget.ES2020, lib: ['lib.es2020.d.ts', 'lib.dom.d.ts'] }, host)
assert(program.getSourceFile(declaration))
return compiler.getPreEmitDiagnostics(program).map(item => item.code)
}
assert.deepEqual(historical(`${source}\n${checks}`), [])
const invalid = `${source}\nchromecast(); chromecast(undefined); const url: string = ({} as Parameters<typeof chromecast>[0]).url;`
assert.deepEqual(historical(invalid), [2554, 2345, 2322])
assert.deepEqual(checkConsumer(compiler, 'node10-commonjs', `${source}\n${checks}`), [])
assert.deepEqual(checkConsumer(compiler, 'node10-commonjs', invalid).map(item => item.code), [2554, 2345, 2322])
}
}
})
test('Chromecast editor global remains a required-argument pure historical factory', async () => {
const source = fs.readFileSync('packages/artplayer-plugin-chromecast/types/artplayer-plugin-chromecast.d.ts', 'utf8')
const generated = generatePluginEditorDeclaration(source, 'artplayerPluginChromecast')
const file = 'docs/assets/ts/artplayer-plugin-chromecast.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 = `const option: artplayerPluginChromecast.Option = {}; artplayerPluginChromecast(option);
const replacement: typeof artplayerPluginChromecast = (_option) => (_art) => ({name: 'artplayerPluginChromecast'});
// @ts-expect-error The published factory argument is required.
artplayerPluginChromecast();
// @ts-expect-error Self alias typing belongs to the runtime module.
artplayerPluginChromecast.default(option);
// @ts-expect-error URL remains a string.
artplayerPluginChromecast({url: 2});
// @ts-expect-error Runtime callbacks are available from the runtime module.
artplayerPluginChromecast({onCastStart() {}});`
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, 4)
}
})
test('Chromecast actual old CJS forms and candidate main, legacy, global and ESM identities remain explicit', async () => {
const contract = await verifyChromecastContract()
for (const version of ['1.0.0', '1.1.0']) {
for (const suffix of ['js', 'legacy.js']) {
const code = readMember(contract.archives.get(version), `package/dist/artplayer-plugin-chromecast.${suffix}`).toString()
const module = { exports: {} }
vm.runInNewContext(code, { module, exports: module.exports, window: {} })
assert.equal(typeof module.exports, version === '1.0.0' ? 'object' : 'function')
assert.deepEqual(Object.keys(module.exports), version === '1.0.0' ? ['default'] : [])
const factory = version === '1.0.0' ? module.exports.default : module.exports
assert.equal(typeof factory({}), 'function')
}
}
for (const suffix of ['js', 'legacy.js']) {
const code = fs.readFileSync(`packages/artplayer-plugin-chromecast/dist/artplayer-plugin-chromecast.${suffix}`, 'utf8')
const module = { exports: {} }
vm.runInNewContext(code, { module, exports: module.exports, window: {} })
assert.equal(typeof module.exports, 'function')
assert.equal(module.exports.default, module.exports)
assert.equal(typeof module.exports({}), 'function')
const global = { window: {} }
vm.runInNewContext(code, global)
const factory = global.artplayerPluginChromecast || global.window.artplayerPluginChromecast
assert.equal(factory.default, factory)
}
const source = fs.readFileSync('packages/artplayer-plugin-chromecast/dist/artplayer-plugin-chromecast.mjs', 'utf8')
const module = await import(`data:text/javascript,${encodeURIComponent(source)}`)
assert.deepEqual(Object.keys(module), ['default'])
assert.equal(module.default.default, module.default)
})
+7 -4
View File
@@ -2518,7 +2518,7 @@
"dependsOn": [
"BASE-05"
],
"status": "todo",
"status": "doing",
"risk": "H",
"deliverable": "模型参数、start/stop、默认下载路径及 mask 样式",
"acceptance": "源码/声明/README/demo/发布包差异已登记;公开形状和版本范围冻结;接续 SDK-08,分别核实 Yarn 解析版本、未固定版本的模型 solutionPath 和资源来源",
@@ -3065,11 +3065,14 @@
"ENG-06",
"CORE-07"
],
"status": "todo",
"status": "done",
"risk": "H",
"deliverable": "Cast 能力 adapter、全局对象和元数据类型",
"acceptance": "严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容",
"evidence": []
"evidence": [
"changes/2026-09-13-PKG-CAST-04-types.md",
"baselines/chromecast-types-validation.json"
]
},
{
"id": "PKG-CAST-05",
@@ -3243,7 +3246,7 @@
"dependsOn": [
"BASE-05"
],
"status": "todo",
"status": "doing",
"risk": "H",
"deliverable": "emit/load/config/hide/show/reset/mount、option/isHide/isStop/icons 与事件清单",
"acceptance": "源码、声明、发布包、Bilibili 输入和设置入口对照",
+5 -3
View File
@@ -1086,7 +1086,7 @@
],
"sourceStatus": "Former commercial SVG removed. Current cast.svg is an original geometric icon authored for this repository during PKG-CAST-03; controller imports that file directly.",
"licenseStatus": "Current original repository artwork follows the package MIT license; no entitlement in or relicensing of the removed Font Awesome Pro artwork is asserted.",
"updatePolicy": "Keep original artwork and custom icon/wrapper support; review any replacement provenance and verify actual rebuilt/shipped assets. Historical commercial-source evidence remains immutable.",
"updatePolicy": "Keep original artwork and custom icon/wrapper support; review any replacement provenance and verify actual rebuilt/shipped assets. Historical commercial-source evidence remains immutable. CAST04 refreshes the controller fingerprint only for type imports/return annotation; six rebuilt/copied JavaScript files are byte-identical to CAST03.",
"fingerprints": [
{
"path": "packages/artplayer-plugin-chromecast/src/cast.svg",
@@ -1096,14 +1096,16 @@
{
"path": "packages/artplayer-plugin-chromecast/src/controller.ts",
"algorithm": "sha256-lf",
"sha256": "e61760f082e233da1f4e94ca98c68b4f07357117a66b2b5c448659dd57cb193d"
"sha256": "1bf55c5a6a4a83290ee591d4286452acd7ba0b27d01e4b6293e16d61772655c2"
}
],
"reviewEvidence": [
"refactor/baselines/chromecast-release.json",
"refactor/changes/2026-09-13-PKG-CAST-01-contracts.md",
"refactor/changes/2026-09-13-PKG-CAST-03-runtime.md",
"refactor/baselines/chromecast-runtime-validation.json"
"refactor/baselines/chromecast-runtime-validation.json",
"refactor/changes/2026-09-13-PKG-CAST-04-types.md",
"refactor/baselines/chromecast-types-validation.json"
]
}
],
+9 -1
View File
@@ -1,4 +1,7 @@
# 已确认的历史类型冲突规则
# ADR-027:已确认的历史类型冲突规则
本规则先前误记为ADR-025,与Iframe决策重号;2026-09-13改为ADR-027。旧类型相关
记录中的ADR-025是本规则的历史别名,冻结测试报告和已验证包内容保留原字节。
2026-09-13,用户对以下完整问题回答“接受这条规则并继续”:保留各包最新已发布
版本的旧类型形状,较早版本的冲突写清迁移说明;所有合法旧JavaScript调用继续
@@ -20,3 +23,8 @@ Multiple Subtitles。此确认独立于此前Ads的参数推导修正。
适用任务:PKG-FACTORY-01、PKG-VTT-THUMB-04、PKG-MULTI-SUB-04。后续记录必须注明
采用哪一个真实版本、哪些旧类型用法需要迁移,以及/runtime的具体形状。
Chromecast的PKG-CAST-04沿用此规则保留最新npm 1.1.0根声明,不引入新的根类型
破坏。1.0.0的export=与1.1.0的default export已有历史差异,分别保留测试证据;
准确异步注册、回调和查询类型通过新增/runtime提供。此处记录规则的应用,
不声称用户另行确认过Cast专属问题,也不扩大到未验证的运行时变化。
+1 -1
View File
@@ -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 = ['artplayerPluginAsr', 'artplayerPluginMultipleSubtitles', 'artplayerPluginVttThumbnail', 'artplayerPluginHlsControl', 'artplayerPluginAudioTrack', 'artplayerPluginDashControl', 'artplayerPluginAds', 'artplayerPluginAmbilight', 'artplayerProxyCanvas', 'artplayerProxyMediabunny', 'artplayerPluginDocumentPip', 'ArtplayerToolIframe', 'ArtplayerToolThumbnail'].includes(name)
const semanticPlugin = ['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)
+93
View File
@@ -0,0 +1,93 @@
import type { Chromecast, ConnectionState, Factory, Option, Result, RuntimeFactory, RuntimeOption, RuntimeResult } from 'artplayer-plugin-chromecast'
import type { RuntimeFactory as EntryFactory, RuntimeOption as EntryOption, RuntimeResult as EntryResult } from 'artplayer-plugin-chromecast/runtime'
import Artplayer from 'artplayer'
import chromecast from 'artplayer-plugin-chromecast'
import * as namespace from 'artplayer-plugin-chromecast'
import legacy from 'artplayer-plugin-chromecast/legacy'
import runtime from 'artplayer-plugin-chromecast/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
interface OldOption { url?: string, sdk?: string, icon?: string, mimeType?: string }
interface OldResult { name: 'artplayerPluginChromecast' }
type OldFactory = (option: OldOption) => (art: Artplayer) => OldResult
type RootFactory = Assert<Equal<typeof chromecast, OldFactory>>
type RootArguments = Assert<Equal<Parameters<typeof chromecast>, [option: OldOption]>>
type RootResult = Assert<Equal<ReturnType<ReturnType<typeof chromecast>>, OldResult>>
type RootNames = Assert<Equal<[Option, Result, Chromecast], [OldOption, OldResult, OldResult]>>
type RuntimeArguments = Assert<Equal<Parameters<typeof runtime>, [option: RuntimeOption]>>
type RuntimeReturns = Assert<Equal<ReturnType<ReturnType<typeof runtime>>, Promise<RuntimeResult>>>
type RuntimeNames = Assert<Equal<[EntryFactory, EntryOption, EntryResult], [RuntimeFactory, RuntimeOption, RuntimeResult]>>
const replacement: OldFactory = (_option: OldOption) => (_art: Artplayer) => ({ name: 'artplayerPluginChromecast' })
const toOld: OldFactory = chromecast
const fromOld: typeof chromecast = replacement
const named: Factory = replacement
const fromNamespace: OldFactory = namespace.default
const oldEntry: typeof legacy = replacement
function acceptOptions(_option: RuntimeOption) {}
const option: RuntimeOption = {
url: '/video.mp4',
onStateChange(state) {
acceptOptions(this)
const value: ConnectionState = state
void value
},
onCastAvailable(available) {
const value: boolean = available
void value
},
onCastStart() { acceptOptions(this) },
onError(error) {
const value: unknown = error
void value
},
}
const art = new Artplayer({ container: '#player', url: '/video.mp4', plugins: [chromecast({}), runtime(option), legacy({})] })
const historic: 'artplayerPluginChromecast' = chromecast({})(art).name
const promise: Promise<RuntimeResult> = runtime(option)(art)
runtime.default = runtime
runtime.default.default({})
async function consume() {
const result = await runtime({})(art)
const state: string | null = result.getCastState()
const casting: boolean = result.isCasting()
const name: 'artplayerPluginChromecast' = result.name
return [state, casting, name]
}
// @ts-expect-error Historical required argument is retained.
chromecast()
// @ts-expect-error Undefined is not the required options object.
chromecast(undefined)
// @ts-expect-error Runtime also requires an options object.
runtime()
// @ts-expect-error No callback fields are silently added to root inference.
chromecast({ onCastStart() {} })
// @ts-expect-error URL remains text.
runtime({ url: 4 })
// @ts-expect-error SDK location remains text.
runtime({ sdk: false })
// @ts-expect-error Icon remains HTML text.
runtime({ icon: document.createElement('i') })
// @ts-expect-error MIME remains text.
runtime({ mimeType: 3 })
// @ts-expect-error Connection state is normalized text, not a number.
runtime({ onStateChange: (_state: number) => {} })
// @ts-expect-error Availability is boolean.
runtime({ onCastAvailable: (_available: string) => {} })
// @ts-expect-error An SDK rejection may be any thrown value.
runtime({ onError: (_error: Error) => {} })
// @ts-expect-error Root remains synchronous for historical type extraction.
const asynchronousRoot: Promise<Result> = chromecast({})(art)
// @ts-expect-error Runtime registration must be awaited.
const synchronousRuntime: RuntimeResult = runtime({})(art)
// @ts-expect-error Historical result only exposed name.
chromecast({})(art).getCastState()
// @ts-expect-error A plain historical replacement need not have a self alias.
chromecast.default({})
// @ts-expect-error The accurate runtime replacement must include its alias and Promise.
const inaccurate: RuntimeFactory = replacement
// @ts-expect-error Raw SDK state starts null and must be handled.
const unknownInitial: string = (null as unknown as RuntimeResult).getCastState()
void [toOld, fromOld, named, fromNamespace, oldEntry, historic, promise, consume, asynchronousRoot, synchronousRuntime, inaccurate, unknownInitial]
export type { RootArguments, RootFactory, RootNames, RootResult, RuntimeArguments, RuntimeNames, RuntimeReturns }