refactor(core): CORE-08 type plugin registration and guard closed instances

This commit is contained in:
Harvey Zhao committed 2026-09-11 02:09:36 +08:00
1 parent f9707bad71
commit 874dac9f22
36 files changed
+2865 -142

No files matched your search

+13 -7
View File
@@ -248,6 +248,15 @@ export interface Quality {
url: string
}
export type PluginFactory<Host = Artplayer, Result = unknown> = (this: Host, art: Host) => Result
/** Augment this interface with installed plugin results; augmentation does not register a plugin. */
export interface Plugins {
/** Legacy signature: synchronous factories return this registry; Promise factories return a Promise of it. */
add: (plugin: PluginFactory) => Promise<Plugins>
[name: string]: unknown
}
export type AspectRatio = 'default' | '4:3' | '16:9' | (`${number}:${number}` & Record<never, never>)
export type PlaybackRate = 0.5 | 0.75 | 1.0 | 1.25 | 1.5 | 1.75 | 2.0 | (number & Record<never, never>)
export type Flip = 'normal' | 'horizontal' | 'vertical' | (string & Record<never, never>)
@@ -591,7 +600,7 @@ export interface Option {
/**
* Custom plugin list
*/
plugins?: ((this: Artplayer, art: Artplayer) => unknown | Promise<unknown>)[]
plugins?: PluginFactory[]
/**
* Custom layer list
@@ -1159,6 +1168,8 @@ export type {
Option,
OptionInput,
Player,
PluginFactory,
Plugins,
Setting,
SettingOption,
Subtitle,
@@ -1344,12 +1355,7 @@ export default class Artplayer extends Player {
remove: (name: string) => Artplayer['setting']
} & Component
readonly plugins: {
/** Legacy signature: synchronous factories return this registry directly; Promise factories return a Promise of it. */
add: (
plugin: (this: Artplayer, art: Artplayer) => unknown | Promise<unknown>,
) => Promise<Artplayer['plugins']>
} & Record<string, unknown>
readonly plugins: Plugins
}
export = Artplayer
+1 -1
View File
@@ -4,4 +4,4 @@
* (c) 2017-2026 Harvey Zhao
* Released under the MIT License.
*/
!function(t,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"==typeof define&&define.amd?(t.Artplayer=e(),define(function(){return t.Artplayer})):(t="undefined"!==typeof globalThis?globalThis:t||self).Artplayer=e()}(this,function(){"use strict";function t(t){return t&&t.__esModule&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t}var e,r={exports:{}};var o=(e||(e=1,r.exports=function(){function t(e){return(t="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(e)}var e=Object.prototype.toString,r=function(r){if(void 0===r)return"undefined";if(null===r)return"null";var a=t(r);if("boolean"===a)return"boolean";if("string"===a)return"string";if("number"===a)return"number";if("symbol"===a)return"symbol";if("function"===a)return"GeneratorFunction"===o(r)?"generatorfunction":"function";if(function(t){return Array.isArray?Array.isArray(t):t instanceof Array}(r))return"array";if(function(t){return!(!t.constructor||"function"!=typeof t.constructor.isBuffer)&&t.constructor.isBuffer(t)}(r))return"buffer";if(function(t){try{if("number"==typeof t.length&&"function"==typeof t.callee)return!0}catch(e){if(-1!==e.message.indexOf("callee"))return!0}return!1}(r))return"arguments";if(function(t){return t instanceof Date||"function"==typeof t.toDateString&&"function"==typeof t.getDate&&"function"==typeof t.setDate}(r))return"date";if(function(t){return t instanceof Error||"string"==typeof t.message&&t.constructor&&"number"==typeof t.constructor.stackTraceLimit}(r))return"error";if(function(t){return t instanceof RegExp||"string"==typeof t.flags&&"boolean"==typeof t.ignoreCase&&"boolean"==typeof t.multiline&&"boolean"==typeof t.global}(r))return"regexp";switch(o(r)){case"Symbol":return"symbol";case"Promise":return"promise";case"WeakMap":return"weakmap";case"WeakSet":return"weakset";case"Map":return"map";case"Set":return"set";case"Int8Array":return"int8array";case"Uint8Array":return"uint8array";case"Uint8ClampedArray":return"uint8clampedarray";case"Int16Array":return"int16array";case"Uint16Array":return"uint16array";case"Int32Array":return"int32array";case"Uint32Array":return"uint32array";case"Float32Array":return"float32array";case"Float64Array":return"float64array"}if(function(t){return"function"==typeof t.throw&&"function"==typeof t.return&&"function"==typeof t.next}(r))return"generator";switch(a=e.call(r)){case"[object Object]":return"object";case"[object Map Iterator]":return"mapiterator";case"[object Set Iterator]":return"setiterator";case"[object String Iterator]":return"stringiterator";case"[object Array Iterator]":return"arrayiterator"}return a.slice(8,-1).toLowerCase().replace(/\s/g,"")};function o(t){return t.constructor?t.constructor.name:null}function a(t,e){var o=2<arguments.length&&void 0!==arguments[2]?arguments[2]:["option"];return n(t,e,o),i(t,e,o),function(t,e,o){var s=r(e),l=r(t);if("object"===s){if("object"!==l)throw new Error("[Type Error]: '".concat(o.join("."),"' require 'object' type, but got '").concat(l,"'"));Object.keys(e).forEach(function(r){var s=t[r],l=e[r],c=o.slice();c.push(r),n(s,l,c),i(s,l,c),a(s,l,c)})}if("array"===s){if("array"!==l)throw new Error("[Type Error]: '".concat(o.join("."),"' require 'array' type, but got '").concat(l,"'"));t.forEach(function(r,s){var l=t[s],c=e[s]||e[0],d=o.slice();d.push(s),n(l,c,d),i(l,c,d),a(l,c,d)})}}(t,e,o),t}function n(t,e,o){if("string"===r(e)){var a=r(t);if("?"===e[0]&&(e=e.slice(1)+"|undefined"),!(-1<e.indexOf("|")?e.split("|").map(function(t){return t.toLowerCase().trim()}).filter(Boolean).some(function(t){return a===t}):e.toLowerCase().trim()===a))throw new Error("[Type Error]: '".concat(o.join("."),"' require '").concat(e,"' type, but got '").concat(a,"'"))}}function i(t,e,o){if("function"===r(e)){var a=e(t,r(t),o);if(!0!==a){var n=r(a);throw"string"===n?new Error(a):"error"===n?a:new Error("[Validator Error]: The scheme for '".concat(o.join("."),"' validator require return true, but got '").concat(a,"'"))}}}return a.kindOf=r,a}()),r.exports);const a=t(o),n="5.4.1",i={properties:["audioTracks","autoplay","buffered","controller","controls","crossOrigin","currentSrc","currentTime","defaultMuted","defaultPlaybackRate","duration","ended","error","loop","mediaGroup","muted","networkState","paused","playbackRate","played","preload","readyState","seekable","seeking","src","startDate","textTracks","videoTracks","volume"],methods:["addTextTrack","canPlayType","load","play","pause"],events:["abort","canplay","canplaythrough","durationchange","emptied","ended","error","loadeddata","loadedmetadata","loadstart","pause","play","playing","progress","ratechange","seeked","seeking","stalled","suspend","timeupdate","volumechange","waiting"],prototypes:["width","height","videoWidth","videoHeight","poster","webkitDecodedFrameCount","webkitDroppedFrameCount","playsInline","webLine truncated
!function(t,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"==typeof define&&define.amd?(t.Artplayer=e(),define(function(){return t.Artplayer})):(t="undefined"!==typeof globalThis?globalThis:t||self).Artplayer=e()}(this,function(){"use strict";function t(t){return t&&t.__esModule&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t}var e,r={exports:{}};var o=(e||(e=1,r.exports=function(){function t(e){return(t="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(e)}var e=Object.prototype.toString,r=function(r){if(void 0===r)return"undefined";if(null===r)return"null";var a=t(r);if("boolean"===a)return"boolean";if("string"===a)return"string";if("number"===a)return"number";if("symbol"===a)return"symbol";if("function"===a)return"GeneratorFunction"===o(r)?"generatorfunction":"function";if(function(t){return Array.isArray?Array.isArray(t):t instanceof Array}(r))return"array";if(function(t){return!(!t.constructor||"function"!=typeof t.constructor.isBuffer)&&t.constructor.isBuffer(t)}(r))return"buffer";if(function(t){try{if("number"==typeof t.length&&"function"==typeof t.callee)return!0}catch(e){if(-1!==e.message.indexOf("callee"))return!0}return!1}(r))return"arguments";if(function(t){return t instanceof Date||"function"==typeof t.toDateString&&"function"==typeof t.getDate&&"function"==typeof t.setDate}(r))return"date";if(function(t){return t instanceof Error||"string"==typeof t.message&&t.constructor&&"number"==typeof t.constructor.stackTraceLimit}(r))return"error";if(function(t){return t instanceof RegExp||"string"==typeof t.flags&&"boolean"==typeof t.ignoreCase&&"boolean"==typeof t.multiline&&"boolean"==typeof t.global}(r))return"regexp";switch(o(r)){case"Symbol":return"symbol";case"Promise":return"promise";case"WeakMap":return"weakmap";case"WeakSet":return"weakset";case"Map":return"map";case"Set":return"set";case"Int8Array":return"int8array";case"Uint8Array":return"uint8array";case"Uint8ClampedArray":return"uint8clampedarray";case"Int16Array":return"int16array";case"Uint16Array":return"uint16array";case"Int32Array":return"int32array";case"Uint32Array":return"uint32array";case"Float32Array":return"float32array";case"Float64Array":return"float64array"}if(function(t){return"function"==typeof t.throw&&"function"==typeof t.return&&"function"==typeof t.next}(r))return"generator";switch(a=e.call(r)){case"[object Object]":return"object";case"[object Map Iterator]":return"mapiterator";case"[object Set Iterator]":return"setiterator";case"[object String Iterator]":return"stringiterator";case"[object Array Iterator]":return"arrayiterator"}return a.slice(8,-1).toLowerCase().replace(/\s/g,"")};function o(t){return t.constructor?t.constructor.name:null}function a(t,e){var o=2<arguments.length&&void 0!==arguments[2]?arguments[2]:["option"];return n(t,e,o),i(t,e,o),function(t,e,o){var s=r(e),l=r(t);if("object"===s){if("object"!==l)throw new Error("[Type Error]: '".concat(o.join("."),"' require 'object' type, but got '").concat(l,"'"));Object.keys(e).forEach(function(r){var s=t[r],l=e[r],c=o.slice();c.push(r),n(s,l,c),i(s,l,c),a(s,l,c)})}if("array"===s){if("array"!==l)throw new Error("[Type Error]: '".concat(o.join("."),"' require 'array' type, but got '").concat(l,"'"));t.forEach(function(r,s){var l=t[s],c=e[s]||e[0],d=o.slice();d.push(s),n(l,c,d),i(l,c,d),a(l,c,d)})}}(t,e,o),t}function n(t,e,o){if("string"===r(e)){var a=r(t);if("?"===e[0]&&(e=e.slice(1)+"|undefined"),!(-1<e.indexOf("|")?e.split("|").map(function(t){return t.toLowerCase().trim()}).filter(Boolean).some(function(t){return a===t}):e.toLowerCase().trim()===a))throw new Error("[Type Error]: '".concat(o.join("."),"' require '").concat(e,"' type, but got '").concat(a,"'"))}}function i(t,e,o){if("function"===r(e)){var a=e(t,r(t),o);if(!0!==a){var n=r(a);throw"string"===n?new Error(a):"error"===n?a:new Error("[Validator Error]: The scheme for '".concat(o.join("."),"' validator require return true, but got '").concat(a,"'"))}}}return a.kindOf=r,a}()),r.exports);const a=t(o),n="5.4.1",i={properties:["audioTracks","autoplay","buffered","controller","controls","crossOrigin","currentSrc","currentTime","defaultMuted","defaultPlaybackRate","duration","ended","error","loop","mediaGroup","muted","networkState","paused","playbackRate","played","preload","readyState","seekable","seeking","src","startDate","textTracks","videoTracks","volume"],methods:["addTextTrack","canPlayType","load","play","pause"],events:["abort","canplay","canplaythrough","durationchange","emptied","ended","error","loadeddata","loadedmetadata","loadstart","pause","play","playing","progress","ratechange","seeked","seeking","stalled","suspend","timeupdate","volumechange","waiting"],prototypes:["width","height","videoWidth","videoHeight","poster","webkitDecodedFrameCount","webkitDroppedFrameCount","playsInline","webLine truncated
+1 -1
View File
@@ -4,4 +4,4 @@
* (c) 2017-2026 Harvey Zhao
* Released under the MIT License.
*/
!function(t,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"==typeof define&&define.amd?(t.Artplayer=e(),define(function(){return t.Artplayer})):(t="undefined"!==typeof globalThis?globalThis:t||self).Artplayer=e()}(this,function(){"use strict";var t,e=Object.defineProperty,r=Object.defineProperties,o=Object.getOwnPropertyDescriptors,a=Object.getOwnPropertySymbols,n=Object.prototype.hasOwnProperty,i=Object.prototype.propertyIsEnumerable,s=(t,r,o)=>r in t?e(t,r,{enumerable:!0,configurable:!0,writable:!0,value:o}):t[r]=o,l=(t,e)=>{for(var r in e||(e={}))n.call(e,r)&&s(t,r,e[r]);if(a)for(var r of a(e))i.call(e,r)&&s(t,r,e[r]);return t},c=(t,e)=>r(t,o(e)),d=(t,e,r)=>new Promise((o,a)=>{var n=t=>{try{s(r.next(t))}catch(e){a(e)}},i=t=>{try{s(r.throw(t))}catch(e){a(e)}},s=t=>t.done?o(t.value):Promise.resolve(t.value).then(n,i);s((r=r.apply(t,e)).next())});function p(t){return t&&t.__esModule&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t}var h,u={exports:{}};var m=(h||(h=1,u.exports=function(){function t(e){return(t="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(e)}var e=Object.prototype.toString,r=function(r){if(void 0===r)return"undefined";if(null===r)return"null";var a=t(r);if("boolean"===a)return"boolean";if("string"===a)return"string";if("number"===a)return"number";if("symbol"===a)return"symbol";if("function"===a)return"GeneratorFunction"===o(r)?"generatorfunction":"function";if(function(t){return Array.isArray?Array.isArray(t):t instanceof Array}(r))return"array";if(function(t){return!(!t.constructor||"function"!=typeof t.constructor.isBuffer)&&t.constructor.isBuffer(t)}(r))return"buffer";if(function(t){try{if("number"==typeof t.length&&"function"==typeof t.callee)return!0}catch(e){if(-1!==e.message.indexOf("callee"))return!0}return!1}(r))return"arguments";if(function(t){return t instanceof Date||"function"==typeof t.toDateString&&"function"==typeof t.getDate&&"function"==typeof t.setDate}(r))return"date";if(function(t){return t instanceof Error||"string"==typeof t.message&&t.constructor&&"number"==typeof t.constructor.stackTraceLimit}(r))return"error";if(function(t){return t instanceof RegExp||"string"==typeof t.flags&&"boolean"==typeof t.ignoreCase&&"boolean"==typeof t.multiline&&"boolean"==typeof t.global}(r))return"regexp";switch(o(r)){case"Symbol":return"symbol";case"Promise":return"promise";case"WeakMap":return"weakmap";case"WeakSet":return"weakset";case"Map":return"map";case"Set":return"set";case"Int8Array":return"int8array";case"Uint8Array":return"uint8array";case"Uint8ClampedArray":return"uint8clampedarray";case"Int16Array":return"int16array";case"Uint16Array":return"uint16array";case"Int32Array":return"int32array";case"Uint32Array":return"uint32array";case"Float32Array":return"float32array";case"Float64Array":return"float64array"}if(function(t){return"function"==typeof t.throw&&"function"==typeof t.return&&"function"==typeof t.next}(r))return"generator";switch(a=e.call(r)){case"[object Object]":return"object";case"[object Map Iterator]":return"mapiterator";case"[object Set Iterator]":return"setiterator";case"[object String Iterator]":return"stringiterator";case"[object Array Iterator]":return"arrayiterator"}return a.slice(8,-1).toLowerCase().replace(/\s/g,"")};function o(t){return t.constructor?t.constructor.name:null}function a(t,e){var o=2<arguments.length&&void 0!==arguments[2]?arguments[2]:["option"];return n(t,e,o),i(t,e,o),function(t,e,o){var s=r(e),l=r(t);if("object"===s){if("object"!==l)throw new Error("[Type Error]: '".concat(o.join("."),"' require 'object' type, but got '").concat(l,"'"));Object.keys(e).forEach(function(r){var s=t[r],l=e[r],c=o.slice();c.push(r),n(s,l,c),i(s,l,c),a(s,l,c)})}if("array"===s){if("array"!==l)throw new Error("[Type Error]: '".concat(o.join("."),"' require 'array' type, but got '").concat(l,"'"));t.forEach(function(r,s){var l=t[s],c=e[s]||e[0],d=o.slice();d.push(s),n(l,c,d),i(l,c,d),a(l,c,d)})}}(t,e,o),t}function n(t,e,o){if("string"===r(e)){var a=r(t);if("?"===e[0]&&(e=e.slice(1)+"|undefined"),!(-1<e.indexOf("|")?e.split("|").map(function(t){return t.toLowerCase().trim()}).filter(Boolean).some(function(t){return a===t}):e.toLowerCase().trim()===a))throw new Error("[Type Error]: '".concat(o.join("."),"' require '").concat(e,"' type, but got '").concat(a,"'"))}}function i(t,e,o){if("function"===r(e)){var a=e(t,r(t),o);if(!0!==a){var n=r(a);throw"string"===n?new Error(a):"error"===n?a:new Error("[Validator Error]: The scheme for '".concat(o.join("."),"' validator require return true, but got '").concat(a,"'"))}}}return a.kindOf=r,a}()),u.exports);const f=p(m),g="5.4.1",v={properties:["audioTracks","autoplay","buffered","controller","controls","crossOrigin","currentSrc","currentTime","defaultMuted","defaultPlaybackRate","duration","ended","error","loop","meLine truncated
!function(t,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"==typeof define&&define.amd?(t.Artplayer=e(),define(function(){return t.Artplayer})):(t="undefined"!==typeof globalThis?globalThis:t||self).Artplayer=e()}(this,function(){"use strict";var t,e=Object.defineProperty,r=Object.defineProperties,o=Object.getOwnPropertyDescriptors,a=Object.getOwnPropertySymbols,n=Object.prototype.hasOwnProperty,i=Object.prototype.propertyIsEnumerable,s=(t,r,o)=>r in t?e(t,r,{enumerable:!0,configurable:!0,writable:!0,value:o}):t[r]=o,l=(t,e)=>{for(var r in e||(e={}))n.call(e,r)&&s(t,r,e[r]);if(a)for(var r of a(e))i.call(e,r)&&s(t,r,e[r]);return t},c=(t,e)=>r(t,o(e)),d=(t,e,r)=>new Promise((o,a)=>{var n=t=>{try{s(r.next(t))}catch(e){a(e)}},i=t=>{try{s(r.throw(t))}catch(e){a(e)}},s=t=>t.done?o(t.value):Promise.resolve(t.value).then(n,i);s((r=r.apply(t,e)).next())});function p(t){return t&&t.__esModule&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t}var h,u={exports:{}};var m=(h||(h=1,u.exports=function(){function t(e){return(t="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(e)}var e=Object.prototype.toString,r=function(r){if(void 0===r)return"undefined";if(null===r)return"null";var a=t(r);if("boolean"===a)return"boolean";if("string"===a)return"string";if("number"===a)return"number";if("symbol"===a)return"symbol";if("function"===a)return"GeneratorFunction"===o(r)?"generatorfunction":"function";if(function(t){return Array.isArray?Array.isArray(t):t instanceof Array}(r))return"array";if(function(t){return!(!t.constructor||"function"!=typeof t.constructor.isBuffer)&&t.constructor.isBuffer(t)}(r))return"buffer";if(function(t){try{if("number"==typeof t.length&&"function"==typeof t.callee)return!0}catch(e){if(-1!==e.message.indexOf("callee"))return!0}return!1}(r))return"arguments";if(function(t){return t instanceof Date||"function"==typeof t.toDateString&&"function"==typeof t.getDate&&"function"==typeof t.setDate}(r))return"date";if(function(t){return t instanceof Error||"string"==typeof t.message&&t.constructor&&"number"==typeof t.constructor.stackTraceLimit}(r))return"error";if(function(t){return t instanceof RegExp||"string"==typeof t.flags&&"boolean"==typeof t.ignoreCase&&"boolean"==typeof t.multiline&&"boolean"==typeof t.global}(r))return"regexp";switch(o(r)){case"Symbol":return"symbol";case"Promise":return"promise";case"WeakMap":return"weakmap";case"WeakSet":return"weakset";case"Map":return"map";case"Set":return"set";case"Int8Array":return"int8array";case"Uint8Array":return"uint8array";case"Uint8ClampedArray":return"uint8clampedarray";case"Int16Array":return"int16array";case"Uint16Array":return"uint16array";case"Int32Array":return"int32array";case"Uint32Array":return"uint32array";case"Float32Array":return"float32array";case"Float64Array":return"float64array"}if(function(t){return"function"==typeof t.throw&&"function"==typeof t.return&&"function"==typeof t.next}(r))return"generator";switch(a=e.call(r)){case"[object Object]":return"object";case"[object Map Iterator]":return"mapiterator";case"[object Set Iterator]":return"setiterator";case"[object String Iterator]":return"stringiterator";case"[object Array Iterator]":return"arrayiterator"}return a.slice(8,-1).toLowerCase().replace(/\s/g,"")};function o(t){return t.constructor?t.constructor.name:null}function a(t,e){var o=2<arguments.length&&void 0!==arguments[2]?arguments[2]:["option"];return n(t,e,o),i(t,e,o),function(t,e,o){var s=r(e),l=r(t);if("object"===s){if("object"!==l)throw new Error("[Type Error]: '".concat(o.join("."),"' require 'object' type, but got '").concat(l,"'"));Object.keys(e).forEach(function(r){var s=t[r],l=e[r],c=o.slice();c.push(r),n(s,l,c),i(s,l,c),a(s,l,c)})}if("array"===s){if("array"!==l)throw new Error("[Type Error]: '".concat(o.join("."),"' require 'array' type, but got '").concat(l,"'"));t.forEach(function(r,s){var l=t[s],c=e[s]||e[0],d=o.slice();d.push(s),n(l,c,d),i(l,c,d),a(l,c,d)})}}(t,e,o),t}function n(t,e,o){if("string"===r(e)){var a=r(t);if("?"===e[0]&&(e=e.slice(1)+"|undefined"),!(-1<e.indexOf("|")?e.split("|").map(function(t){return t.toLowerCase().trim()}).filter(Boolean).some(function(t){return a===t}):e.toLowerCase().trim()===a))throw new Error("[Type Error]: '".concat(o.join("."),"' require '").concat(e,"' type, but got '").concat(a,"'"))}}function i(t,e,o){if("function"===r(e)){var a=e(t,r(t),o);if(!0!==a){var n=r(a);throw"string"===n?new Error(a):"error"===n?a:new Error("[Validator Error]: The scheme for '".concat(o.join("."),"' validator require return true, but got '").concat(a,"'"))}}}return a.kindOf=r,a}()),u.exports);const f=p(m),g="5.4.1",v={properties:["audioTracks","autoplay","buffered","controller","controls","crossOrigin","currentSrc","currentTime","defaultMuted","defaultPlaybackRate","duration","ended","error","loop","meLine truncated
+47 -26
View File
@@ -1162,6 +1162,10 @@ function stateOf(owner) {
function getScope(owner) {
return stateOf(owner).scope;
}
function isClosing(owner) {
const state2 = stateOf(owner);
return state2.destroying || state2.scope.closed;
}
function ownContainer(owner, container, rollback) {
const current = containers.get(container);
if (current && current !== owner)
@@ -4133,46 +4137,63 @@ function miniProgressBar(art) {
});
return { name: "mini-progress-bar" };
}
function installBuiltins(registry, option) {
if (!isClosing(registry.art) && option.miniProgressBar && !option.isLive)
registry.add(miniProgressBar);
if (!isClosing(registry.art) && option.lock && isMobile)
registry.add(lock);
if (!isClosing(registry.art) && option.autoPlayback && !option.isLive)
registry.add(autoPlayback);
if (!isClosing(registry.art) && option.autoOrientation && isMobile)
registry.add(autoOrientation);
if (!isClosing(registry.art) && option.fastForward && isMobile && !option.isLive)
registry.add(fastForward);
}
function registerPlugin(registry, plugin, result) {
if (isClosing(registry.art))
return registry;
const name = result && result.name || plugin.name || `plugin${registry.id}`;
if (isClosing(registry.art))
return registry;
let key = name;
const duplicate = has(registry, key);
if (isClosing(registry.art))
return registry;
const message = `Cannot add a plugin that already has the same name: ${name}`;
if (isClosing(registry.art))
return registry;
errorHandle(!duplicate, message);
if (typeof name === "object" && name !== null || typeof name === "function")
key = Reflect.ownKeys({ [key]: void 0 })[0];
if (!isClosing(registry.art))
def(registry, key, { value: result });
return registry;
}
class Plugins {
constructor(art) {
this.art = art;
this.id = 0;
const { option } = art;
if (option.miniProgressBar && !option.isLive) {
this.add(miniProgressBar);
}
if (option.lock && isMobile) {
this.add(lock);
}
if (option.autoPlayback && !option.isLive) {
this.add(autoPlayback);
}
if (option.autoOrientation && isMobile) {
this.add(autoOrientation);
}
if (option.fastForward && isMobile && !option.isLive) {
this.add(fastForward);
}
installBuiltins(this, option);
for (let index = 0; index < option.plugins.length; index++) {
this.add(option.plugins[index]);
if (isClosing(art))
return;
const registration = this.add(option.plugins[index]);
if (registration instanceof Promise) {
registration.catch((error2) => console.warn("Failed to initialize ArtPlayer plugin:", error2));
}
}
}
add(plugin) {
errorHandle(!isClosing(this.art), "Cannot add a plugin after ArtPlayer is destroyed");
this.id += 1;
const result = plugin.call(this.art, this.art);
if (result instanceof Promise) {
if (result instanceof Promise)
return result.then((res) => this.next(plugin, res));
} else {
return this.next(plugin, result);
}
return this.next(plugin, result);
}
next(plugin, result) {
const pluginName = result && result.name || plugin.name || `plugin${this.id}`;
errorHandle(!has(this, pluginName), `Cannot add a plugin that already has the same name: ${pluginName}`);
def(this, pluginName, {
value: result
});
return this;
return registerPlugin(this, plugin, result);
}
}
function aspectRatio(art) {
+1 -1
View File
@@ -49,7 +49,7 @@
"ci:build": "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",
"typecheck": "node scripts/typecheck.mjs",
"test:unit": "node --test test/playback.test.js test/dash-control.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:unit": "node --test test/playback.test.js test/dash-control.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": "yarn test:node && yarn test:baseline",
"test:browser": "playwright test",
"test:browser:install": "playwright install chromium firefox webkit",
+50
View File
@@ -4,6 +4,56 @@ ArtPlayer keeps its existing constructor, player mixins, plugins and DOM/CSS hoo
The production entry is still `src/index.js`; this document marks actual migrated
boundaries rather than describing the entire core as TypeScript.
## Plugin registration and ownership
`src/plugins/index.ts` orchestrates registration; types.ts defines generic factories,
minimal option hosts and internal sync/Promise return types. builtins.ts installs the
five existing builtins in their original order and reads each condition at its turn.
It keeps the mobile/live exclusions, including no fastForward for live media. The
constructor captures option once before builtin installation, then traverses the
same live user-plugin array. Builtin implementations remain JS until CORE-18.
registration.ts owns result naming and non-enumerable/non-writable/non-configurable
registry properties. Names still prefer result.name, then factory.name, then the
current completion-time id. Duplicate detection uses own properties; failed factory
calls still consume an id. No new name restrictions or thenable assimilation are
introduced. Native object-key coercion remains in its three historical positions;
the final coercion is resolved before defining so a reentrant destroy cannot publish
a property on a closing registry. Dynamic name/key assertions are local to this
legacy boundary and do not claim that arbitrary plugin returns are validated.
Lifecycle.isClosing covers both the reset phase and a disposed scope. Public add
after closure throws before incrementing id or executing the factory. Pending add
fulfillments still settle with the original registry, but discard late results;
factory rejection identity remains unchanged. A synchronous close during construction
stops subsequent factories. Constructor-owned Promise rejections are observed and
reported once with console.warn because no caller receives those promises. Public
add retains its rejecting Promise rather than hiding failures.
The manager does not call a result's destroy method: existing result methods are not
an agreed disposal protocol. Plugins own their subscriptions and external SDK/media
resources; arbitrary work created by a factory after an await still needs that
plugin's lifecycle guards. Preventing late registry writes does not certify every
plugin's cleanup. Names that shadow prototype methods and completion-time fallback
naming keep their historical semantics.
Public PluginFactory and Plugins are type-only contracts. Augment `artplayer/types`
for a common plugin-result/event interface across old TS, NodeNext CJS/ESM and Bundler;
augmenting the CJS root alias directly is not equivalent. The new subpath has only
a types condition and typesVersions fallback: runtime require/import must reject it.
Public add's legacy return signature remains tracked by BASE-TYPE-04/CORE-21; internal
registration types preserve known synchronous/Promise results and use a union for
unknown results. The Promise distinction retains instanceof semantics: foreign
Promises and ordinary thenables are stored synchronously, not assimilated.
Tests: plugins.test.js covers naming/coercion, sync/async/error identity, closed scopes,
reentry and constructor-owned rejection. plugins-source.ts checks internal returns;
plugins-public.ts checks the common augmentation path in every package consumer mode.
Browser plugins.spec.js compares old/new late registration, constructor close/reporting,
and builtin order/live exclusions under a mobile user agent. That UA test is not a
physical-device or builtin-feature acceptance test. Run the full installed-artifact
browser suite after modifying the manager or its lifecycle boundaries.
## Input and resolved configuration
`src/option/defaults.ts` creates fresh defaults on every Artplayer.option access,
+1 -1
View File
@@ -4,4 +4,4 @@
* (c) 2017-2026 Harvey Zhao
* Released under the MIT License.
*/
!function(t,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"==typeof define&&define.amd?(t.Artplayer=e(),define(function(){return t.Artplayer})):(t="undefined"!==typeof globalThis?globalThis:t||self).Artplayer=e()}(this,function(){"use strict";function t(t){return t&&t.__esModule&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t}var e,r={exports:{}};var o=(e||(e=1,r.exports=function(){function t(e){return(t="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(e)}var e=Object.prototype.toString,r=function(r){if(void 0===r)return"undefined";if(null===r)return"null";var a=t(r);if("boolean"===a)return"boolean";if("string"===a)return"string";if("number"===a)return"number";if("symbol"===a)return"symbol";if("function"===a)return"GeneratorFunction"===o(r)?"generatorfunction":"function";if(function(t){return Array.isArray?Array.isArray(t):t instanceof Array}(r))return"array";if(function(t){return!(!t.constructor||"function"!=typeof t.constructor.isBuffer)&&t.constructor.isBuffer(t)}(r))return"buffer";if(function(t){try{if("number"==typeof t.length&&"function"==typeof t.callee)return!0}catch(e){if(-1!==e.message.indexOf("callee"))return!0}return!1}(r))return"arguments";if(function(t){return t instanceof Date||"function"==typeof t.toDateString&&"function"==typeof t.getDate&&"function"==typeof t.setDate}(r))return"date";if(function(t){return t instanceof Error||"string"==typeof t.message&&t.constructor&&"number"==typeof t.constructor.stackTraceLimit}(r))return"error";if(function(t){return t instanceof RegExp||"string"==typeof t.flags&&"boolean"==typeof t.ignoreCase&&"boolean"==typeof t.multiline&&"boolean"==typeof t.global}(r))return"regexp";switch(o(r)){case"Symbol":return"symbol";case"Promise":return"promise";case"WeakMap":return"weakmap";case"WeakSet":return"weakset";case"Map":return"map";case"Set":return"set";case"Int8Array":return"int8array";case"Uint8Array":return"uint8array";case"Uint8ClampedArray":return"uint8clampedarray";case"Int16Array":return"int16array";case"Uint16Array":return"uint16array";case"Int32Array":return"int32array";case"Uint32Array":return"uint32array";case"Float32Array":return"float32array";case"Float64Array":return"float64array"}if(function(t){return"function"==typeof t.throw&&"function"==typeof t.return&&"function"==typeof t.next}(r))return"generator";switch(a=e.call(r)){case"[object Object]":return"object";case"[object Map Iterator]":return"mapiterator";case"[object Set Iterator]":return"setiterator";case"[object String Iterator]":return"stringiterator";case"[object Array Iterator]":return"arrayiterator"}return a.slice(8,-1).toLowerCase().replace(/\s/g,"")};function o(t){return t.constructor?t.constructor.name:null}function a(t,e){var o=2<arguments.length&&void 0!==arguments[2]?arguments[2]:["option"];return n(t,e,o),i(t,e,o),function(t,e,o){var s=r(e),l=r(t);if("object"===s){if("object"!==l)throw new Error("[Type Error]: '".concat(o.join("."),"' require 'object' type, but got '").concat(l,"'"));Object.keys(e).forEach(function(r){var s=t[r],l=e[r],c=o.slice();c.push(r),n(s,l,c),i(s,l,c),a(s,l,c)})}if("array"===s){if("array"!==l)throw new Error("[Type Error]: '".concat(o.join("."),"' require 'array' type, but got '").concat(l,"'"));t.forEach(function(r,s){var l=t[s],c=e[s]||e[0],d=o.slice();d.push(s),n(l,c,d),i(l,c,d),a(l,c,d)})}}(t,e,o),t}function n(t,e,o){if("string"===r(e)){var a=r(t);if("?"===e[0]&&(e=e.slice(1)+"|undefined"),!(-1<e.indexOf("|")?e.split("|").map(function(t){return t.toLowerCase().trim()}).filter(Boolean).some(function(t){return a===t}):e.toLowerCase().trim()===a))throw new Error("[Type Error]: '".concat(o.join("."),"' require '").concat(e,"' type, but got '").concat(a,"'"))}}function i(t,e,o){if("function"===r(e)){var a=e(t,r(t),o);if(!0!==a){var n=r(a);throw"string"===n?new Error(a):"error"===n?a:new Error("[Validator Error]: The scheme for '".concat(o.join("."),"' validator require return true, but got '").concat(a,"'"))}}}return a.kindOf=r,a}()),r.exports);const a=t(o),n="5.4.1",i={properties:["audioTracks","autoplay","buffered","controller","controls","crossOrigin","currentSrc","currentTime","defaultMuted","defaultPlaybackRate","duration","ended","error","loop","mediaGroup","muted","networkState","paused","playbackRate","played","preload","readyState","seekable","seeking","src","startDate","textTracks","videoTracks","volume"],methods:["addTextTrack","canPlayType","load","play","pause"],events:["abort","canplay","canplaythrough","durationchange","emptied","ended","error","loadeddata","loadedmetadata","loadstart","pause","play","playing","progress","ratechange","seeked","seeking","stalled","suspend","timeupdate","volumechange","waiting"],prototypes:["width","height","videoWidth","videoHeight","poster","webkitDecodedFrameCount","webkitDroppedFrameCount","playsInline","webLine truncated
!function(t,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"==typeof define&&define.amd?(t.Artplayer=e(),define(function(){return t.Artplayer})):(t="undefined"!==typeof globalThis?globalThis:t||self).Artplayer=e()}(this,function(){"use strict";function t(t){return t&&t.__esModule&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t}var e,r={exports:{}};var o=(e||(e=1,r.exports=function(){function t(e){return(t="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(e)}var e=Object.prototype.toString,r=function(r){if(void 0===r)return"undefined";if(null===r)return"null";var a=t(r);if("boolean"===a)return"boolean";if("string"===a)return"string";if("number"===a)return"number";if("symbol"===a)return"symbol";if("function"===a)return"GeneratorFunction"===o(r)?"generatorfunction":"function";if(function(t){return Array.isArray?Array.isArray(t):t instanceof Array}(r))return"array";if(function(t){return!(!t.constructor||"function"!=typeof t.constructor.isBuffer)&&t.constructor.isBuffer(t)}(r))return"buffer";if(function(t){try{if("number"==typeof t.length&&"function"==typeof t.callee)return!0}catch(e){if(-1!==e.message.indexOf("callee"))return!0}return!1}(r))return"arguments";if(function(t){return t instanceof Date||"function"==typeof t.toDateString&&"function"==typeof t.getDate&&"function"==typeof t.setDate}(r))return"date";if(function(t){return t instanceof Error||"string"==typeof t.message&&t.constructor&&"number"==typeof t.constructor.stackTraceLimit}(r))return"error";if(function(t){return t instanceof RegExp||"string"==typeof t.flags&&"boolean"==typeof t.ignoreCase&&"boolean"==typeof t.multiline&&"boolean"==typeof t.global}(r))return"regexp";switch(o(r)){case"Symbol":return"symbol";case"Promise":return"promise";case"WeakMap":return"weakmap";case"WeakSet":return"weakset";case"Map":return"map";case"Set":return"set";case"Int8Array":return"int8array";case"Uint8Array":return"uint8array";case"Uint8ClampedArray":return"uint8clampedarray";case"Int16Array":return"int16array";case"Uint16Array":return"uint16array";case"Int32Array":return"int32array";case"Uint32Array":return"uint32array";case"Float32Array":return"float32array";case"Float64Array":return"float64array"}if(function(t){return"function"==typeof t.throw&&"function"==typeof t.return&&"function"==typeof t.next}(r))return"generator";switch(a=e.call(r)){case"[object Object]":return"object";case"[object Map Iterator]":return"mapiterator";case"[object Set Iterator]":return"setiterator";case"[object String Iterator]":return"stringiterator";case"[object Array Iterator]":return"arrayiterator"}return a.slice(8,-1).toLowerCase().replace(/\s/g,"")};function o(t){return t.constructor?t.constructor.name:null}function a(t,e){var o=2<arguments.length&&void 0!==arguments[2]?arguments[2]:["option"];return n(t,e,o),i(t,e,o),function(t,e,o){var s=r(e),l=r(t);if("object"===s){if("object"!==l)throw new Error("[Type Error]: '".concat(o.join("."),"' require 'object' type, but got '").concat(l,"'"));Object.keys(e).forEach(function(r){var s=t[r],l=e[r],c=o.slice();c.push(r),n(s,l,c),i(s,l,c),a(s,l,c)})}if("array"===s){if("array"!==l)throw new Error("[Type Error]: '".concat(o.join("."),"' require 'array' type, but got '").concat(l,"'"));t.forEach(function(r,s){var l=t[s],c=e[s]||e[0],d=o.slice();d.push(s),n(l,c,d),i(l,c,d),a(l,c,d)})}}(t,e,o),t}function n(t,e,o){if("string"===r(e)){var a=r(t);if("?"===e[0]&&(e=e.slice(1)+"|undefined"),!(-1<e.indexOf("|")?e.split("|").map(function(t){return t.toLowerCase().trim()}).filter(Boolean).some(function(t){return a===t}):e.toLowerCase().trim()===a))throw new Error("[Type Error]: '".concat(o.join("."),"' require '").concat(e,"' type, but got '").concat(a,"'"))}}function i(t,e,o){if("function"===r(e)){var a=e(t,r(t),o);if(!0!==a){var n=r(a);throw"string"===n?new Error(a):"error"===n?a:new Error("[Validator Error]: The scheme for '".concat(o.join("."),"' validator require return true, but got '").concat(a,"'"))}}}return a.kindOf=r,a}()),r.exports);const a=t(o),n="5.4.1",i={properties:["audioTracks","autoplay","buffered","controller","controls","crossOrigin","currentSrc","currentTime","defaultMuted","defaultPlaybackRate","duration","ended","error","loop","mediaGroup","muted","networkState","paused","playbackRate","played","preload","readyState","seekable","seeking","src","startDate","textTracks","videoTracks","volume"],methods:["addTextTrack","canPlayType","load","play","pause"],events:["abort","canplay","canplaythrough","durationchange","emptied","ended","error","loadeddata","loadedmetadata","loadstart","pause","play","playing","progress","ratechange","seeked","seeking","stalled","suspend","timeupdate","volumechange","waiting"],prototypes:["width","height","videoWidth","videoHeight","poster","webkitDecodedFrameCount","webkitDroppedFrameCount","playsInline","webLine truncated
+1 -1
View File
@@ -4,4 +4,4 @@
* (c) 2017-2026 Harvey Zhao
* Released under the MIT License.
*/
!function(t,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"==typeof define&&define.amd?(t.Artplayer=e(),define(function(){return t.Artplayer})):(t="undefined"!==typeof globalThis?globalThis:t||self).Artplayer=e()}(this,function(){"use strict";var t,e=Object.defineProperty,r=Object.defineProperties,o=Object.getOwnPropertyDescriptors,a=Object.getOwnPropertySymbols,n=Object.prototype.hasOwnProperty,i=Object.prototype.propertyIsEnumerable,s=(t,r,o)=>r in t?e(t,r,{enumerable:!0,configurable:!0,writable:!0,value:o}):t[r]=o,l=(t,e)=>{for(var r in e||(e={}))n.call(e,r)&&s(t,r,e[r]);if(a)for(var r of a(e))i.call(e,r)&&s(t,r,e[r]);return t},c=(t,e)=>r(t,o(e)),d=(t,e,r)=>new Promise((o,a)=>{var n=t=>{try{s(r.next(t))}catch(e){a(e)}},i=t=>{try{s(r.throw(t))}catch(e){a(e)}},s=t=>t.done?o(t.value):Promise.resolve(t.value).then(n,i);s((r=r.apply(t,e)).next())});function p(t){return t&&t.__esModule&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t}var h,u={exports:{}};var m=(h||(h=1,u.exports=function(){function t(e){return(t="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(e)}var e=Object.prototype.toString,r=function(r){if(void 0===r)return"undefined";if(null===r)return"null";var a=t(r);if("boolean"===a)return"boolean";if("string"===a)return"string";if("number"===a)return"number";if("symbol"===a)return"symbol";if("function"===a)return"GeneratorFunction"===o(r)?"generatorfunction":"function";if(function(t){return Array.isArray?Array.isArray(t):t instanceof Array}(r))return"array";if(function(t){return!(!t.constructor||"function"!=typeof t.constructor.isBuffer)&&t.constructor.isBuffer(t)}(r))return"buffer";if(function(t){try{if("number"==typeof t.length&&"function"==typeof t.callee)return!0}catch(e){if(-1!==e.message.indexOf("callee"))return!0}return!1}(r))return"arguments";if(function(t){return t instanceof Date||"function"==typeof t.toDateString&&"function"==typeof t.getDate&&"function"==typeof t.setDate}(r))return"date";if(function(t){return t instanceof Error||"string"==typeof t.message&&t.constructor&&"number"==typeof t.constructor.stackTraceLimit}(r))return"error";if(function(t){return t instanceof RegExp||"string"==typeof t.flags&&"boolean"==typeof t.ignoreCase&&"boolean"==typeof t.multiline&&"boolean"==typeof t.global}(r))return"regexp";switch(o(r)){case"Symbol":return"symbol";case"Promise":return"promise";case"WeakMap":return"weakmap";case"WeakSet":return"weakset";case"Map":return"map";case"Set":return"set";case"Int8Array":return"int8array";case"Uint8Array":return"uint8array";case"Uint8ClampedArray":return"uint8clampedarray";case"Int16Array":return"int16array";case"Uint16Array":return"uint16array";case"Int32Array":return"int32array";case"Uint32Array":return"uint32array";case"Float32Array":return"float32array";case"Float64Array":return"float64array"}if(function(t){return"function"==typeof t.throw&&"function"==typeof t.return&&"function"==typeof t.next}(r))return"generator";switch(a=e.call(r)){case"[object Object]":return"object";case"[object Map Iterator]":return"mapiterator";case"[object Set Iterator]":return"setiterator";case"[object String Iterator]":return"stringiterator";case"[object Array Iterator]":return"arrayiterator"}return a.slice(8,-1).toLowerCase().replace(/\s/g,"")};function o(t){return t.constructor?t.constructor.name:null}function a(t,e){var o=2<arguments.length&&void 0!==arguments[2]?arguments[2]:["option"];return n(t,e,o),i(t,e,o),function(t,e,o){var s=r(e),l=r(t);if("object"===s){if("object"!==l)throw new Error("[Type Error]: '".concat(o.join("."),"' require 'object' type, but got '").concat(l,"'"));Object.keys(e).forEach(function(r){var s=t[r],l=e[r],c=o.slice();c.push(r),n(s,l,c),i(s,l,c),a(s,l,c)})}if("array"===s){if("array"!==l)throw new Error("[Type Error]: '".concat(o.join("."),"' require 'array' type, but got '").concat(l,"'"));t.forEach(function(r,s){var l=t[s],c=e[s]||e[0],d=o.slice();d.push(s),n(l,c,d),i(l,c,d),a(l,c,d)})}}(t,e,o),t}function n(t,e,o){if("string"===r(e)){var a=r(t);if("?"===e[0]&&(e=e.slice(1)+"|undefined"),!(-1<e.indexOf("|")?e.split("|").map(function(t){return t.toLowerCase().trim()}).filter(Boolean).some(function(t){return a===t}):e.toLowerCase().trim()===a))throw new Error("[Type Error]: '".concat(o.join("."),"' require '").concat(e,"' type, but got '").concat(a,"'"))}}function i(t,e,o){if("function"===r(e)){var a=e(t,r(t),o);if(!0!==a){var n=r(a);throw"string"===n?new Error(a):"error"===n?a:new Error("[Validator Error]: The scheme for '".concat(o.join("."),"' validator require return true, but got '").concat(a,"'"))}}}return a.kindOf=r,a}()),u.exports);const f=p(m),g="5.4.1",v={properties:["audioTracks","autoplay","buffered","controller","controls","crossOrigin","currentSrc","currentTime","defaultMuted","defaultPlaybackRate","duration","ended","error","loop","meLine truncated
!function(t,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"==typeof define&&define.amd?(t.Artplayer=e(),define(function(){return t.Artplayer})):(t="undefined"!==typeof globalThis?globalThis:t||self).Artplayer=e()}(this,function(){"use strict";var t,e=Object.defineProperty,r=Object.defineProperties,o=Object.getOwnPropertyDescriptors,a=Object.getOwnPropertySymbols,n=Object.prototype.hasOwnProperty,i=Object.prototype.propertyIsEnumerable,s=(t,r,o)=>r in t?e(t,r,{enumerable:!0,configurable:!0,writable:!0,value:o}):t[r]=o,l=(t,e)=>{for(var r in e||(e={}))n.call(e,r)&&s(t,r,e[r]);if(a)for(var r of a(e))i.call(e,r)&&s(t,r,e[r]);return t},c=(t,e)=>r(t,o(e)),d=(t,e,r)=>new Promise((o,a)=>{var n=t=>{try{s(r.next(t))}catch(e){a(e)}},i=t=>{try{s(r.throw(t))}catch(e){a(e)}},s=t=>t.done?o(t.value):Promise.resolve(t.value).then(n,i);s((r=r.apply(t,e)).next())});function p(t){return t&&t.__esModule&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t}var h,u={exports:{}};var m=(h||(h=1,u.exports=function(){function t(e){return(t="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(e)}var e=Object.prototype.toString,r=function(r){if(void 0===r)return"undefined";if(null===r)return"null";var a=t(r);if("boolean"===a)return"boolean";if("string"===a)return"string";if("number"===a)return"number";if("symbol"===a)return"symbol";if("function"===a)return"GeneratorFunction"===o(r)?"generatorfunction":"function";if(function(t){return Array.isArray?Array.isArray(t):t instanceof Array}(r))return"array";if(function(t){return!(!t.constructor||"function"!=typeof t.constructor.isBuffer)&&t.constructor.isBuffer(t)}(r))return"buffer";if(function(t){try{if("number"==typeof t.length&&"function"==typeof t.callee)return!0}catch(e){if(-1!==e.message.indexOf("callee"))return!0}return!1}(r))return"arguments";if(function(t){return t instanceof Date||"function"==typeof t.toDateString&&"function"==typeof t.getDate&&"function"==typeof t.setDate}(r))return"date";if(function(t){return t instanceof Error||"string"==typeof t.message&&t.constructor&&"number"==typeof t.constructor.stackTraceLimit}(r))return"error";if(function(t){return t instanceof RegExp||"string"==typeof t.flags&&"boolean"==typeof t.ignoreCase&&"boolean"==typeof t.multiline&&"boolean"==typeof t.global}(r))return"regexp";switch(o(r)){case"Symbol":return"symbol";case"Promise":return"promise";case"WeakMap":return"weakmap";case"WeakSet":return"weakset";case"Map":return"map";case"Set":return"set";case"Int8Array":return"int8array";case"Uint8Array":return"uint8array";case"Uint8ClampedArray":return"uint8clampedarray";case"Int16Array":return"int16array";case"Uint16Array":return"uint16array";case"Int32Array":return"int32array";case"Uint32Array":return"uint32array";case"Float32Array":return"float32array";case"Float64Array":return"float64array"}if(function(t){return"function"==typeof t.throw&&"function"==typeof t.return&&"function"==typeof t.next}(r))return"generator";switch(a=e.call(r)){case"[object Object]":return"object";case"[object Map Iterator]":return"mapiterator";case"[object Set Iterator]":return"setiterator";case"[object String Iterator]":return"stringiterator";case"[object Array Iterator]":return"arrayiterator"}return a.slice(8,-1).toLowerCase().replace(/\s/g,"")};function o(t){return t.constructor?t.constructor.name:null}function a(t,e){var o=2<arguments.length&&void 0!==arguments[2]?arguments[2]:["option"];return n(t,e,o),i(t,e,o),function(t,e,o){var s=r(e),l=r(t);if("object"===s){if("object"!==l)throw new Error("[Type Error]: '".concat(o.join("."),"' require 'object' type, but got '").concat(l,"'"));Object.keys(e).forEach(function(r){var s=t[r],l=e[r],c=o.slice();c.push(r),n(s,l,c),i(s,l,c),a(s,l,c)})}if("array"===s){if("array"!==l)throw new Error("[Type Error]: '".concat(o.join("."),"' require 'array' type, but got '").concat(l,"'"));t.forEach(function(r,s){var l=t[s],c=e[s]||e[0],d=o.slice();d.push(s),n(l,c,d),i(l,c,d),a(l,c,d)})}}(t,e,o),t}function n(t,e,o){if("string"===r(e)){var a=r(t);if("?"===e[0]&&(e=e.slice(1)+"|undefined"),!(-1<e.indexOf("|")?e.split("|").map(function(t){return t.toLowerCase().trim()}).filter(Boolean).some(function(t){return a===t}):e.toLowerCase().trim()===a))throw new Error("[Type Error]: '".concat(o.join("."),"' require '").concat(e,"' type, but got '").concat(a,"'"))}}function i(t,e,o){if("function"===r(e)){var a=e(t,r(t),o);if(!0!==a){var n=r(a);throw"string"===n?new Error(a):"error"===n?a:new Error("[Validator Error]: The scheme for '".concat(o.join("."),"' validator require return true, but got '").concat(a,"'"))}}}return a.kindOf=r,a}()),u.exports);const f=p(m),g="5.4.1",v={properties:["audioTracks","autoplay","buffered","controller","controls","crossOrigin","currentSrc","currentTime","defaultMuted","defaultPlaybackRate","duration","ended","error","loop","meLine truncated
+47 -26
View File
@@ -1162,6 +1162,10 @@ function stateOf(owner) {
function getScope(owner) {
return stateOf(owner).scope;
}
function isClosing(owner) {
const state2 = stateOf(owner);
return state2.destroying || state2.scope.closed;
}
function ownContainer(owner, container, rollback) {
const current = containers.get(container);
if (current && current !== owner)
@@ -4133,46 +4137,63 @@ function miniProgressBar(art) {
});
return { name: "mini-progress-bar" };
}
function installBuiltins(registry, option) {
if (!isClosing(registry.art) && option.miniProgressBar && !option.isLive)
registry.add(miniProgressBar);
if (!isClosing(registry.art) && option.lock && isMobile)
registry.add(lock);
if (!isClosing(registry.art) && option.autoPlayback && !option.isLive)
registry.add(autoPlayback);
if (!isClosing(registry.art) && option.autoOrientation && isMobile)
registry.add(autoOrientation);
if (!isClosing(registry.art) && option.fastForward && isMobile && !option.isLive)
registry.add(fastForward);
}
function registerPlugin(registry, plugin, result) {
if (isClosing(registry.art))
return registry;
const name = result && result.name || plugin.name || `plugin${registry.id}`;
if (isClosing(registry.art))
return registry;
let key = name;
const duplicate = has(registry, key);
if (isClosing(registry.art))
return registry;
const message = `Cannot add a plugin that already has the same name: ${name}`;
if (isClosing(registry.art))
return registry;
errorHandle(!duplicate, message);
if (typeof name === "object" && name !== null || typeof name === "function")
key = Reflect.ownKeys({ [key]: void 0 })[0];
if (!isClosing(registry.art))
def(registry, key, { value: result });
return registry;
}
class Plugins {
constructor(art) {
this.art = art;
this.id = 0;
const { option } = art;
if (option.miniProgressBar && !option.isLive) {
this.add(miniProgressBar);
}
if (option.lock && isMobile) {
this.add(lock);
}
if (option.autoPlayback && !option.isLive) {
this.add(autoPlayback);
}
if (option.autoOrientation && isMobile) {
this.add(autoOrientation);
}
if (option.fastForward && isMobile && !option.isLive) {
this.add(fastForward);
}
installBuiltins(this, option);
for (let index = 0; index < option.plugins.length; index++) {
this.add(option.plugins[index]);
if (isClosing(art))
return;
const registration = this.add(option.plugins[index]);
if (registration instanceof Promise) {
registration.catch((error2) => console.warn("Failed to initialize ArtPlayer plugin:", error2));
}
}
}
add(plugin) {
errorHandle(!isClosing(this.art), "Cannot add a plugin after ArtPlayer is destroyed");
this.id += 1;
const result = plugin.call(this.art, this.art);
if (result instanceof Promise) {
if (result instanceof Promise)
return result.then((res) => this.next(plugin, res));
} else {
return this.next(plugin, result);
}
return this.next(plugin, result);
}
next(plugin, result) {
const pluginName = result && result.name || plugin.name || `plugin${this.id}`;
errorHandle(!has(this, pluginName), `Cannot add a plugin that already has the same name: ${pluginName}`);
def(this, pluginName, {
value: result
});
return this;
return registerPlugin(this, plugin, result);
}
}
function aspectRatio(art) {
+6
View File
@@ -28,6 +28,9 @@
"default": "./dist/artplayer.js"
}
},
"./types": {
"types": "./types/artplayer.d.ts"
},
"./legacy": {
"import": {
"types": "./types/artplayer.d.cts",
@@ -54,6 +57,9 @@
"types": "./types/artplayer.d.ts",
"typesVersions": {
"*": {
"types": [
"types/artplayer.d.ts"
],
"i18n/*": [
"types/legacy/language.d.ts"
],
@@ -32,6 +32,11 @@ export function getScope(owner: object): ResourceScope {
return stateOf(owner).scope
}
export function isClosing(owner: object): boolean {
const state = stateOf(owner)
return state.destroying || state.scope.closed
}
export function ownContainer(owner: object, container: Element, rollback: () => void): void {
const current = containers.get(container)
if (current && current !== owner)
@@ -0,0 +1,22 @@
import type { BuiltinRegistry, PluginHost } from './types'
import { isClosing } from '../lifecycle/instance'
import { isMobile } from '../utils/compatibility'
import autoOrientation from './autoOrientation'
import autoPlayback from './autoPlayback'
import fastForward from './fastForward'
import lock from './lock'
import miniProgressBar from './miniProgressBar'
export default function installBuiltins<Host extends PluginHost<Host>>(registry: BuiltinRegistry<Host>, option: PluginHost<Host>['option']): void {
// Read each condition at its original turn; earlier plugins may change options.
if (!isClosing(registry.art) && option.miniProgressBar && !option.isLive)
registry.add(miniProgressBar)
if (!isClosing(registry.art) && option.lock && isMobile)
registry.add(lock)
if (!isClosing(registry.art) && option.autoPlayback && !option.isLive)
registry.add(autoPlayback)
if (!isClosing(registry.art) && option.autoOrientation && isMobile)
registry.add(autoOrientation)
if (!isClosing(registry.art) && option.fastForward && isMobile && !option.isLive)
registry.add(fastForward)
}
-59
View File
@@ -1,59 +0,0 @@
import { def, errorHandle, has, isMobile } from '../utils'
import autoOrientation from './autoOrientation'
import autoPlayback from './autoPlayback'
import fastForward from './fastForward'
import lock from './lock'
import miniProgressBar from './miniProgressBar'
export default class Plugins {
constructor(art) {
this.art = art
this.id = 0
const { option } = art
if (option.miniProgressBar && !option.isLive) {
this.add(miniProgressBar)
}
if (option.lock && isMobile) {
this.add(lock)
}
if (option.autoPlayback && !option.isLive) {
this.add(autoPlayback)
}
if (option.autoOrientation && isMobile) {
this.add(autoOrientation)
}
if (option.fastForward && isMobile && !option.isLive) {
this.add(fastForward)
}
for (let index = 0; index < option.plugins.length; index++) {
this.add(option.plugins[index])
}
}
add(plugin) {
this.id += 1
const result = plugin.call(this.art, this.art)
if (result instanceof Promise) {
return result.then(res => this.next(plugin, res))
}
else {
return this.next(plugin, result)
}
}
next(plugin, result) {
const pluginName = (result && result.name) || plugin.name || `plugin${this.id}`
errorHandle(!has(this, pluginName), `Cannot add a plugin that already has the same name: ${pluginName}`)
def(this, pluginName, {
value: result,
})
return this
}
}
+40
View File
@@ -0,0 +1,40 @@
import type { PluginFactory, PluginHost, PluginRegistration } from './types'
import { isClosing } from '../lifecycle/instance'
import { errorHandle } from '../utils/error'
import installBuiltins from './builtins'
import registerPlugin from './registration'
export default class Plugins<Host extends PluginHost<Host>> {
declare art: Host
declare id: number
constructor(art: Host) {
this.art = art
this.id = 0
const { option } = art
installBuiltins(this, option)
for (let index = 0; index < option.plugins.length; index++) {
if (isClosing(art))
return
const registration = this.add(option.plugins[index]!)
if (registration instanceof Promise) {
// Constructor registrations have no caller-owned Promise to observe.
registration.catch(error => console.warn('Failed to initialize ArtPlayer plugin:', error))
}
}
}
add<Result>(plugin: PluginFactory<Host, Result>): PluginRegistration<Result, this>
add(plugin: PluginFactory<Host>): this | Promise<this> {
errorHandle(!isClosing(this.art), 'Cannot add a plugin after ArtPlayer is destroyed')
this.id += 1
const result = plugin.call(this.art, this.art)
if (result instanceof Promise)
return result.then(res => this.next(plugin, res))
return this.next(plugin, result)
}
next(plugin: PluginFactory<Host>, result: unknown): this {
return registerPlugin(this, plugin, result)
}
}
@@ -0,0 +1,27 @@
import { isClosing } from '../lifecycle/instance'
import { errorHandle } from '../utils/error'
import { def, has } from '../utils/property'
export default function registerPlugin<Registry extends { art: object, id: number }>(registry: Registry, plugin: { name: string }, result: unknown): Registry {
if (isClosing(registry.art))
return registry
// Keep the historical duck-typed name lookup and native property-key coercion.
const name = (result && (result as { name?: unknown }).name) || plugin.name || `plugin${registry.id}`
if (isClosing(registry.art))
return registry
let key = name as PropertyKey
const duplicate = has(registry, key)
if (isClosing(registry.art))
return registry
const message = `Cannot add a plugin that already has the same name: ${name}`
if (isClosing(registry.art))
return registry
errorHandle(!duplicate, message)
// Object keys can execute user code during native coercion. Keep that third
// coercion in its historical position, then check closure before defining.
if ((typeof name === 'object' && name !== null) || typeof name === 'function')
key = Reflect.ownKeys({ [key]: undefined })[0]!
if (!isClosing(registry.art))
def(registry, key, { value: result })
return registry
}
+19
View File
@@ -0,0 +1,19 @@
import type { PluginFactory } from '../../types/plugin'
import type { ResolvedOption } from '../option/types'
export type { PluginFactory } from '../../types/plugin'
export type PluginRegistration<Result, Registry>
= unknown extends Result ? Registry | Promise<Registry>
: Result extends Promise<unknown> ? Promise<Registry> : Registry
export interface PluginHost<Host> {
option: Pick<ResolvedOption, 'miniProgressBar' | 'isLive' | 'lock' | 'autoPlayback' | 'autoOrientation' | 'fastForward'> & {
plugins: PluginFactory<Host>[]
}
}
export interface BuiltinRegistry<Host extends PluginHost<Host>> {
art: Host
add: (plugin: PluginFactory<Host>) => unknown
}
+27
View File
@@ -55,3 +55,30 @@ declaration. Optional capability checks belong to their consumers. Full construc
type integration is tracked by CORE-20; proxy package migration validates each real
adapter separately. Source type fixtures and real canvas integration live in
test/types/media-hosts.ts and test/browser/media-hosts.spec.js.
## Plugin and event augmentation
Use the type-only `artplayer/types` entry to augment the shared Plugins and Events
interfaces. This reaches the same definition through Node10, NodeNext CJS/ESM and
Bundler. Directly augmenting the root CJS namespace type alias is not equivalent.
```ts
import Artplayer from 'artplayer'
declare module 'artplayer/types' {
interface Plugins {
customPlugin?: { update: (value: number) => void }
}
}
const art = new Artplayer({ container: '#player' })
art.plugins.customPlugin?.update(1)
```
Types do not register a plugin or prove that it is installed; retain a presence
guard unless the application owns and verifies installation. PluginFactory describes
the existing single art argument and this receiver. Public add keeps its historical
return signature; internal manager types model actual returns separately. The new
subpath has no runtime export; use it only for types/augmentation. plugins-public.ts
tests the same declarations in all installed consumer modes, and package runtime
checks reject both require and import of artplayer/types.
+2
View File
@@ -14,6 +14,8 @@ declare namespace Artplayer {
type Option = Definition.Option
type OptionInput = Definition.OptionInput
type Player = Definition.Player
type PluginFactory<Host = Artplayer, Result = unknown> = Definition.PluginFactory<Host, Result>
type Plugins = Definition.Plugins
type Setting = Definition.Setting
type SettingOption = Definition.SettingOption
type Subtitle = Definition.Subtitle
+1 -1
View File
@@ -1,2 +1,2 @@
export { default } from './artplayer.cjs'
export type { Config, Emitter, Events, I18n, Icons, Option, OptionInput, Player, Setting, SettingOption, Subtitle, SubtitleUpdateEvents, Template, Utils } from './artplayer.js'
export type { Config, Emitter, Events, I18n, Icons, Option, OptionInput, Player, PluginFactory, Plugins, Setting, SettingOption, Subtitle, SubtitleUpdateEvents, Template, Utils } from './artplayer.js'
+4 -6
View File
@@ -6,6 +6,7 @@ import type { I18n } from './i18n'
import type { Icons } from './icons'
import type { Option, OptionInput } from './option'
import type { Player } from './player'
import type { PluginFactory, Plugins } from './plugin'
import type { Setting, SettingOption } from './setting'
import type { Subtitle } from './subtitle'
import type { Template } from './template'
@@ -20,6 +21,8 @@ export type {
Option,
OptionInput,
Player,
PluginFactory,
Plugins,
Setting,
SettingOption,
Subtitle,
@@ -205,10 +208,5 @@ export default class Artplayer extends Player {
remove: (name: string) => Artplayer['setting']
} & Component
readonly plugins: {
/** Legacy signature: synchronous factories return this registry directly; Promise factories return a Promise of it. */
add: (
plugin: (this: Artplayer, art: Artplayer) => unknown | Promise<unknown>,
) => Promise<Artplayer['plugins']>
} & Record<string, unknown>
readonly plugins: Plugins
}
+2 -1
View File
@@ -3,6 +3,7 @@ import type { ComponentInput, ComponentOption } from './component'
import type { CssVar } from './cssVar'
import type { I18n } from './i18n'
import type { Icons } from './icons'
import type { PluginFactory } from './plugin'
import type { Quality } from './quality'
import type { Setting } from './setting'
import type { Subtitle } from './subtitle'
@@ -240,7 +241,7 @@ export interface Option {
/**
* Custom plugin list
*/
plugins?: ((this: Artplayer, art: Artplayer) => unknown | Promise<unknown>)[]
plugins?: PluginFactory[]
/**
* Custom layer list
+10
View File
@@ -0,0 +1,10 @@
import type Artplayer from './artplayer'
export type PluginFactory<Host = Artplayer, Result = unknown> = (this: Host, art: Host) => Result
/** Augment this interface with installed plugin results; augmentation does not register a plugin. */
export interface Plugins {
/** Legacy signature: synchronous factories return this registry; Promise factories return a Promise of it. */
add: (plugin: PluginFactory) => Promise<Plugins>
[name: string]: unknown
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,65 @@
# CORE-08:插件注册与扩展类型
## 结构与类型
plugins/index.js 拆为四个 TS 文件:index.ts 管理调用与同步/异步结算,builtins.ts 按原顺序
装配内置插件,registration.ts 负责命名与属性写入,types.ts 定义最小宿主和内部返回类型。
内置插件实现仍由 CORE-18 逐项迁移;本任务没有把它们计作已 TS 化。
内部已知同步工厂返回 registry,当前 realm 的 Promise 工厂返回 Promise registry,unknown
返回保留 union;保持 instanceof Promise 的历史识别规则,不用 Promise.resolve 同化普通
thenable 或外部 realm Promise。PluginFactory 只有原来的一个 art 参数与 this receiver。
新增公开类型 PluginFactory/Plugins,并加入仅含 types 条件的 artplayer/types 子路径及旧
typesVersions 回退。该路径指向共享声明,允许跨 Node10、NodeNext CJS/ESM、Bundler 和
TS 4.3.5 扩展同一个 Plugins/Events 接口;运行时 require/import 均被拒绝。没有新增 JS
导出或依赖。根 CJS namespace 的类型别名不能直接当成可合并接口,故采用共享类型入口。
插件类型扩展不会安装插件;示例保留可选属性与存在性检查。
公开 plugins.add 的历史 Promise 返回声明不在这里静默收紧;BASE-TYPE-04 继续开放至
CORE-21,内部精确类型与旧消费者测试并存。
## 兼容与故障修正
API-03/04/05/06/07/09/11:保留内置/用户顺序、一次 option 捕获、后续实时条件读取、用户
数组实时遍历、返回结果优先命名、函数名回退和 completion-time id 回退。失败调用仍消耗 id;
结果属性仍不可枚举、不可写、不可配置;重复名称与同步/异步错误继续保留原始错误对象。
没有新增名称黑名单,保留旧 prototype 方法遮蔽语义。
动态 name 读取与 PropertyKey 边界保留局部断言;普通活跃注册中的对象键三次原生转换位置
不变。只有对象/函数键的最终转换在临时对象上提前完成,以便写入前再次检查是否发生销毁,
不因 ToPropertyKey 中的重入而在关闭后新增不可删除的属性。名称 getter、重复检查和消息
转换之后也检查关闭状态。
修复 BASE-LIFE-03:销毁前发起的异步 add 仍按原 Promise 成功/失败规则结算,但晚到结果
不再进入注册表。既有拒绝继续传播;没有引入新的“取消拒绝”。销毁后直接 add 则在 id
递增及工厂执行前同步抛 ArtPlayerError,明确阻止继续初始化已经关闭的实例。
生命周期新增内部 isClosing,覆盖 reset 已开始而 scope 尚未 dispose 的重入窗口。
同步工厂关闭构造时停止后续插件;构造器自己发起、没有外部接收者的 Promise 注册增加
拒绝观察,console.warn 一次原错误,避免孤立的 unhandled rejection。公开 add 的 Promise
不被该观察器吞掉。未将任何结果的 destroy 方法自动当成清理协议;工厂 await 后自行创建
的 DOM/SDK/监听器仍需各插件负责,不能把阻止晚到注册宣称为整个生态无资源泄漏。
## 验证
- 八项 Node 测试覆盖 this/参数/描述符/id/命名、同步/Promise 返回、完成时 id、原始错误、
thenable/跨 realm、迟到结果、四个命名重入位置、构造停止与拒绝观察、reset 重入。
- 内部类型正反例检查同步/异步/unknown union 和宿主参数;公开插件结果及事件扩展在五组
工作区和实际安装消费模式通过。所有原声明消费者继续编译,新增类型路径无运行时入口。
- yarn ci:check 共 120 项:91 单元、4 工程、25 基线;32 个生产 TS 文件严格检查,其中
核心 27 个、chapter 5 个。build:ts、build artplayer、build:i18n 由仓库脚本生成产物。
- 严格打包消费 27 项运行时及五组类型零诊断;实际安装 UMD 三浏览器全套 162 项、legacy
插件/章节/生命周期 90 项通过,无重试或跳过。新增插件专项 18 项含旧版迟到注册负例、
候选修复、同步关闭/拒绝观察以及模拟 mobile UA 的内置顺序与直播排除;不是真机证据。
验证曾捕获拆分时遗漏的 fastForward 直播排除条件;已与 Git 起点和发布版对照恢复,保留
对照测试。Firefox 的 console 文本只显示 Error,测试改为核对 console 参数中原错误对象
的 identity/message,而非放宽错误要求。初始失败日志保留在本地 cache。
详见 [验证报告](../baselines/plugins-validation.json)、[核心维护地图](../../packages/artplayer/ARCHITECTURE.md)
与 [类型说明](../../packages/artplayer/types/README.md)。依赖/锁文件未变,test:unit 加入插件
测试,工作区及安装类型脚本加入扩展用例。没有远端 CI、推送、发布或真机通过结论。
本任务独立提交,回退提交可恢复管理器、生命周期辅助函数、声明/子路径、测试、生成产物和
状态。下一项 CORE-09 处理 URL/切源操作身份及异步结算;内置插件资源清理仍属 CORE-18。
+3 -2
View File
@@ -4,7 +4,7 @@
基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 214 项,范围 22 个包及工作区/示例。
状态:todo 171 / doing 0 / blocked 0 / done 43 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
状态:todo 170 / doing 0 / blocked 0 / done 44 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
前置依赖是启动条件;验收是完成条件。任务可以继续拆分,但不能复用或悄悄删除旧 ID。
@@ -111,7 +111,7 @@
| CORE-05 | artplayer<br>区分输入配置和内部配置 | CORE-01, ENG-04 | Option/ResolvedOption、默认值与校验整理 | 配置合并/错误时机不变,JS 输入仍运行时校验 | M | done |
| CORE-06 | artplayer<br>建立内部媒体与宿主类型 | CORE-05 | 原生 video、canvas shim、UI host 类型 | 不更改公开 art.video 用法,不以 HTMLVideoElement 断言掩盖 shim | M | done |
| CORE-07 | artplayer<br>逐项协调公开声明差异 | CORE-02, CORE-06, BASE-07 | plugins/toggle/cue/setting/static 的旧新类型对照 | 每项有兼容重载/扩展或待决结论,不能删除旧类型样例;处理 BASE-TYPE-01/04,分别保留旧合法声明消费并验证真实返回;协调 BASE-TYPE-05 的 Utils 导出、descriptor 和 timer 旧声明差异 | H | done |
| CORE-08 | artplayer<br>整理插件管理器与扩展类型 | CORE-03, CORE-07 | 注册流程、同步/异步结果、插件/事件扩展接口 | 旧插件同步可见性、重名与命名、销毁中异步插件保持明确语义;处理 baselines/lifecycle-coverage.md 的 BASE-LIFE-03 并增加候选正向回归;接续 CORE-07 / BASE-TYPE-04 的同步/异步注册返回声明冲突,内部返回类型精确,保留历史消费者 | H | todo |
| CORE-08 | artplayer<br>整理插件管理器与扩展类型 | CORE-03, CORE-07 | 注册流程、同步/异步结果、插件/事件扩展接口 | 旧插件同步可见性、重名与命名、销毁中异步插件保持明确语义;处理 baselines/lifecycle-coverage.md 的 BASE-LIFE-03 并增加候选正向回归;接续 CORE-07 / BASE-TYPE-04 的同步/异步注册返回声明冲突,内部返回类型精确,保留历史消费者 | H | done |
| CORE-09 | artplayer<br>整理 URL 与切源操作 | CORE-04, CORE-06, CORE-08 | url/switch/customType 的操作身份及取消方案 | 并发切源、错误、同 URL、销毁和 Promise 结算有契约;处理 baselines/lifecycle-coverage.md 的 BASE-LIFE-01/02/06 并增加候选正向回归 | H | todo |
| CORE-10 | artplayer<br>迁移播放与时间/状态属性 | CORE-09 | play/pause/toggle/seek/volume/rate/duration 等模块 | 旧返回值、拒绝、mutex、位置恢复、属性描述符与事件通过;接续 CORE-07 / BASE-TYPE-04 的 toggle void 与实际 Promise 分支冲突,不吞拒绝 | H | todo |
| CORE-11 | artplayer<br>整理媒体事件、ready 与重连 | CORE-10 | 媒体转发、重连、UI 响应职责分离 | readiness 次数/顺序、过期重连、原错误参数有回归 | H | todo |
@@ -442,6 +442,7 @@
- CORE-05: [记录](changes/2026-09-11-CORE-05-resolved-options.md) [记录](baselines/options-validation.json)
- CORE-06: [记录](changes/2026-09-11-CORE-06-media-hosts.md) [记录](baselines/media-hosts-validation.json)
- CORE-07: [记录](changes/2026-09-11-CORE-07-declarations.md) [记录](baselines/declarations-validation.json)
- CORE-08: [记录](changes/2026-09-11-CORE-08-plugin-manager.md) [记录](baselines/plugins-validation.json)
- PKG-CHAPTER-01: [记录](changes/2026-09-10-PKG-CHAPTER-01-contract.md) [记录](baselines/chapter-contract.md)
- PKG-CHAPTER-02: [记录](changes/2026-09-10-PKG-CHAPTER-02-tests.md) [记录](baselines/chapter-validation.json)
- PKG-CHAPTER-03: [记录](changes/2026-09-10-PKG-CHAPTER-03-typescript-modules.md) [记录](baselines/chapter-migration-validation.json)
+4
View File
@@ -1,5 +1,9 @@
# 进度与证据
## 当前实施:CORE-08 插件管理器 TS 完成
插件注册、内置装配、命名与内部类型拆成四个 TS 模块;修复销毁后晚到注册,保持 Promise 结算与同步可见性,新增共享类型扩展入口。120 项 Node/基线、安装 UMD 三浏览器 162 项及 legacy 插件/章节/生命周期 90 项通过;严格打包 27 项运行时、五组类型零诊断。见 [交付](changes/2026-09-11-CORE-08-plugin-manager.md)。关闭 BASE-LIFE-03;公开返回声明和各插件自有资源清理仍有后续责任。当前 214 项,44 完成、170 待办;核心 27 个 TS 文件、chapter 5 个。下一项 CORE-09 URL/切源操作。
## 当前实施:CORE-07 公开声明逐项协调完成
OptionInput、数字组件输入、Utils 缺失项、静态 Emitter 和显式 cue 数组重载已补充,旧合法返回赋值样例保留。112 项 Node/基线、安装 UMD 三浏览器 144 项及 legacy 声明/配置 30 项通过;严格打包 27 项运行时与五组类型零诊断。见 [交付](changes/2026-09-11-CORE-07-declarations.md)。关闭 BASE-TYPE-06;BASE-TYPE-04/05/07 的返回/隐式推断冲突保持开放,已接入模块任务与 CORE-21 出口,不代表全部公开类型已精确化。当前 214 项,43 完成、171 待办;下一项 CORE-08 插件管理器与扩展类型。
+1 -1
View File
@@ -7,7 +7,7 @@
| BASE-LIFE-06 | open / 已复现 | Published switch leaves an unhandled rejection and an unsettled promise when resume fails | CORE-09 |
| BASE-LIFE-01 | open / 已复现 | Superseded switch resolves along with the latest source | CORE-09 |
| BASE-LIFE-02 | open / 已复现 | Switch remains unsettled during the bounded observation after destroy | CORE-09 |
| BASE-LIFE-03 | open / 已复现 | Async plugin registers its result after destroy | CORE-08 |
| BASE-LIFE-03 | resolved / 已复现 | Async plugin registers its result after destroy | CORE-08 |
| BASE-LIFE-04 | resolved / 已复现 | Destroying an instance twice unregisters a different live instance | CORE-04 |
| BASE-LIFE-05 | resolved / 已复现 | Failed constructor leaves initialized markup/listeners until explicit probe cleanup | CORE-04 |
| BASE-DOM-01 | open / 已复现 | 控件 Tab 不可达 | CORE-23, CORE-17 |
+9 -2
View File
@@ -56,7 +56,7 @@
"id": "BASE-LIFE-03",
"title": "Async plugin registers its result after destroy",
"confirmation": "reproduced",
"status": "open",
"status": "resolved",
"owners": [
"CORE-08"
],
@@ -65,7 +65,14 @@
"refactor/baselines/lifecycle-coverage.md"
],
"compatibleResolution": "按对应 CORE 任务修复,保持合法旧调用;异步取消需明确结算策略,不把 pending 或重复销毁当正常兼容要求。",
"closureCriteria": "候选正向回归和旧新差异记录通过;有界观察问题不得仅靠延长等待隐藏。"
"closureCriteria": "候选正向回归和旧新差异记录通过;有界观察问题不得仅靠延长等待隐藏。",
"resolutionEvidence": [
"refactor/changes/2026-09-11-CORE-08-plugin-manager.md",
"refactor/baselines/plugins-validation.json",
"test/plugins.test.js",
"test/browser/plugins.spec.js"
],
"resolutionRationale": "Closing guards stop late writes, post-close factories and reentrant name/coercion/reset registration. Pending success still fulfills with the registry and public rejection identity is preserved. This closes late registration only, not arbitrary plugin resource ownership."
},
{
"id": "BASE-LIFE-04",
+5 -2
View File
@@ -890,11 +890,14 @@
"CORE-03",
"CORE-07"
],
"status": "todo",
"status": "done",
"risk": "H",
"deliverable": "注册流程、同步/异步结果、插件/事件扩展接口",
"acceptance": "旧插件同步可见性、重名与命名、销毁中异步插件保持明确语义;处理 baselines/lifecycle-coverage.md 的 BASE-LIFE-03 并增加候选正向回归;接续 CORE-07 / BASE-TYPE-04 的同步/异步注册返回声明冲突,内部返回类型精确,保留历史消费者",
"evidence": []
"evidence": [
"changes/2026-09-11-CORE-08-plugin-manager.md",
"baselines/plugins-validation.json"
]
},
{
"id": "CORE-09",
+1 -1
View File
@@ -38,7 +38,7 @@ export function typeConsumers(dir) {
for (const [compiler, mode] of [[ts, 'node10-commonjs'], [ts, 'nodenext-cjs'], [ts, 'bundler-esm'], [compat, 'node10-commonjs'], [ts, 'nodenext-esm']]) {
const next = mode.startsWith('nodenext')
const extension = next ? (mode.endsWith('-cjs') ? 'cts' : 'mts') : 'ts'
const inputs = ['public', 'declaration-inputs', 'declaration-legacy', 'chapter-options', 'chapter-exports', 'language-value', 'language', 'legacy-plugin']
const inputs = ['public', 'declaration-inputs', 'declaration-legacy', 'plugins-public', 'chapter-options', 'chapter-exports', 'language-value', 'language', 'legacy-plugin']
if (mode === 'nodenext-cjs')
inputs.push('commonjs')
const files = inputs.map((name) => {
+1 -1
View File
@@ -71,7 +71,7 @@ export function runTypechecks() {
console.log(`Strict project passed: ${relative(config)} (${result.files.length} root files)`)
}
for (const [compiler, mode] of [[ts, 'node10-commonjs'], [ts, 'nodenext-cjs'], [ts, 'bundler-esm'], [ts, 'nodenext-esm'], [compat, 'node10-commonjs']]) {
for (const fixture of ['test/types/public.ts', 'test/types/declaration-inputs.ts', 'test/types/declaration-legacy.ts', 'test/types/chapter-options.ts', 'test/types/chapter-exports.ts', 'test/types/language-value.ts', 'refactor/fixtures/consumers/language.ts', 'refactor/fixtures/consumers/legacy-plugin.ts']) {
for (const fixture of ['test/types/public.ts', 'test/types/declaration-inputs.ts', 'test/types/declaration-legacy.ts', 'test/types/plugins-public.ts', 'test/types/chapter-options.ts', 'test/types/chapter-exports.ts', 'test/types/language-value.ts', 'refactor/fixtures/consumers/language.ts', 'refactor/fixtures/consumers/legacy-plugin.ts']) {
assert.deepEqual(checkConsumer(compiler, mode, fs.readFileSync(path.join(root, fixture), 'utf8')), [], `Consumer failed: TS ${compiler.version} ${mode} ${fixture}`)
}
if (mode === 'nodenext-cjs')
+94
View File
@@ -0,0 +1,94 @@
import { expect, test } from './fixtures.js'
for (const core of ['published', 'candidate']) {
test(`${core}: late plugin registration preserves fulfillment without reviving a closed candidate`, async ({ page }) => {
await page.goto(`/test/player.html?core=${core}&chapter=published`)
const result = await page.evaluate(async () => {
const container = document.createElement('div')
document.body.append(container)
const art = new window.Artplayer({ container, url: '' })
const registry = art.plugins
let resolve
let disposed = 0
const pending = registry.add(() => new Promise(done => resolve = done))
art.destroy()
resolve({ name: 'late', destroy() {
disposed++
} })
const fulfilled = await pending
const before = registry.id
let calls = 0
let error = ''
try {
registry.add(() => {
calls++
return { name: 'afterClose' }
})
}
catch (caught) {
error = caught.message
}
return { same: fulfilled === registry, late: Object.hasOwn(registry, 'late'), disposed, calls, error, idDelta: registry.id - before }
})
expect(result).toEqual(core === 'published'
? { same: true, late: true, disposed: 0, calls: 1, error: '', idDelta: 1 }
: { same: true, late: false, disposed: 0, calls: 0, error: 'Cannot add a plugin after ArtPlayer is destroyed', idDelta: 0 })
})
}
test('candidate: a plugin that closes construction prevents subsequent factories', async ({ page }) => {
await page.goto('/test/player.html?core=candidate&chapter=published')
const result = await page.evaluate(() => {
const container = document.createElement('div')
document.body.append(container)
let calls = 0
const art = new window.Artplayer({ container, url: '', plugins: [(art) => {
art.destroy()
return { name: 'closing' }
}, () => {
calls++
return { name: 'never' }
}] })
return { calls, id: art.plugins.id, closing: Object.hasOwn(art.plugins, 'closing'), destroyed: art.isDestroy, instances: window.Artplayer.instances.length }
})
expect(result).toEqual({ calls: 0, id: 1, closing: false, destroyed: true, instances: 0 })
})
test('candidate: constructor-owned rejection is reported once without an unhandled rejection', async ({ page }) => {
const warnings = []
page.on('console', (message) => {
if (message.type() === 'warning' && message.text().startsWith('Failed to initialize ArtPlayer plugin:'))
warnings.push(message.args()[1].evaluate(error => ({ message: error.message, same: error === window.pluginFailure })))
})
await page.goto('/test/player.html?core=candidate&chapter=published')
await page.evaluate(() => {
const container = document.createElement('div')
document.body.append(container)
window.pluginFailure = new Error('owned-plugin-rejection')
window.art = new window.Artplayer({ container, url: '', plugins: [() => Promise.reject(window.pluginFailure)] })
})
await expect.poll(() => warnings.length).toBe(1)
expect(await warnings[0]).toEqual({ message: 'owned-plugin-rejection', same: true })
await page.evaluate(() => window.art.destroy())
})
test.describe('mobile user-agent builtin selection (not physical-device evidence)', () => {
test.use({ userAgent: 'Mozilla/5.0 (Linux; Android 13) AppleWebKit/537.36 Chrome/120.0.0.0 Mobile Safari/537.36' })
for (const core of ['published', 'candidate']) {
test(`${core}: builtin order and live exclusions precede user plugins`, async ({ page }) => {
await page.goto(`/test/player.html?core=${core}&chapter=published`)
const results = await page.evaluate(() => [false, true].map((isLive) => {
const container = document.createElement('div')
document.body.append(container)
const art = new window.Artplayer({ container, url: '', isLive, miniProgressBar: true, lock: true, autoPlayback: true, autoOrientation: true, fastForward: true, plugins: [() => ({ name: 'user' })] })
const names = Object.getOwnPropertyNames(art.plugins).filter(name => !['art', 'id'].includes(name))
art.destroy()
return names
}))
expect(results).toEqual([
['mini-progress-bar', 'lock', 'auto-playback', 'autoOrientation', 'fastForward', 'user'],
['lock', 'autoOrientation', 'user'],
])
})
}
})
+3 -2
View File
@@ -75,7 +75,7 @@ const vm = require('node:vm');
}
check('DIST.i18n-cjs-esm-global', languages.length > 0)
observations.blockedDeepImports = []
for (const specifier of ['artplayer/dist/artplayer.js', 'artplayer/types/artplayer.d.ts', 'artplayer-plugin-chapter/dist/artplayer-plugin-chapter.js']) {
for (const specifier of ['artplayer/dist/artplayer.js', 'artplayer/types/artplayer.d.ts', 'artplayer-plugin-chapter/dist/artplayer-plugin-chapter.js', 'artplayer/types']) {
let code
try {
require.resolve(specifier)
@@ -84,7 +84,8 @@ const vm = require('node:vm');
assert.equal(code, 'ERR_PACKAGE_PATH_NOT_EXPORTED')
observations.blockedDeepImports.push({ specifier, code })
}
check('DIST.exports-boundary', observations.blockedDeepImports.length === 3)
await assert.rejects(import('artplayer/types'), error => error.code === 'ERR_PACKAGE_PATH_NOT_EXPORTED')
check('DIST.exports-boundary', observations.blockedDeepImports.length === 4)
observations.resolutions = {}
for (const specifier of ['artplayer', 'artplayer/legacy', 'artplayer-plugin-chapter', 'artplayer-plugin-chapter/legacy']) {
const resolved = require.resolve(specifier)
+150
View File
@@ -0,0 +1,150 @@
import assert from 'node:assert/strict'
// eslint-disable-next-line test/no-import-node-test -- Use the repository Node runner.
import { test } from 'node:test'
import vm from 'node:vm'
import { loadModules } from './helpers/load.js'
const lifecycle = 'packages/artplayer/src/lifecycle/instance'
const { Plugins, beginLifecycle, getScope, destroyInstance } = await loadModules({
Plugins: 'packages/artplayer/src/plugins/index',
...Object.fromEntries(['beginLifecycle', 'getScope', 'destroyInstance'].map(name => [name, { file: lifecycle, name }])),
})
function create(plugins = []) {
const art = { option: { miniProgressBar: false, isLive: false, lock: false, autoPlayback: false, autoOrientation: false, fastForward: false, plugins } }
beginLifecycle(art)
return { art, registry: new Plugins(art) }
}
test('plugin registration keeps receiver, arguments, id, descriptors and naming precedence', () => {
const { art, registry } = create()
const result = { name: 'returnedName' }
function factory(value) {
assert.equal(this, art)
assert.equal(value, art)
assert.equal(arguments.length, 1)
return result
}
assert.equal(registry.add(factory), registry)
assert.equal(registry.returnedName, result)
assert.equal(registry.id, 1)
assert.deepEqual(Object.getOwnPropertyDescriptor(registry, 'returnedName'), { value: result, writable: false, enumerable: false, configurable: false })
function named() {}
assert.equal(registry.add(named), registry)
assert(Object.hasOwn(registry, 'named'))
registry.add((0, () => null))
assert.equal(registry.plugin3, null)
assert.throws(() => registry.add(() => result), /Cannot add a plugin that already has the same name: returnedName/)
assert.equal(registry.id, 4)
assert.deepEqual(Object.keys(registry), ['art', 'id'])
})
test('async factories preserve pending visibility, completion-time fallback and registry fulfillment', async () => {
const { registry } = create()
let resolve
const result = registry.add((0, () => new Promise(done => resolve = done)))
assert(result instanceof Promise)
assert(!Object.hasOwn(registry, 'plugin1'))
registry.add(() => ({ name: 'second' }))
resolve(undefined)
assert.equal(await result, registry)
assert(Object.hasOwn(registry, 'plugin2'))
assert(!Object.hasOwn(registry, 'plugin1'))
const failure = new Error('factory failure')
await assert.rejects(registry.add(() => Promise.reject(failure)), error => error === failure)
assert.throws(() => registry.add(() => {
throw failure
}), error => error === failure)
})
test('only same-realm Promises are awaited, preserving catch-less thenables and foreign promises', () => {
const { registry } = create()
const thenable = { name: 'thenable', then() {
throw new Error('must not assimilate')
} }
assert.equal(registry.add(() => thenable), registry)
assert.equal(registry.thenable, thenable)
const foreign = vm.runInNewContext('Promise.resolve({ name: "foreignResult" })')
const foreignFactory = () => foreign
assert.equal(registry.add(foreignFactory), registry)
assert.equal(registry.foreignFactory, foreign)
})
test('closed registries ignore late results without inspecting or destroying them and reject new factories', async () => {
const { art, registry } = create()
let resolve
const pending = registry.add(() => new Promise(done => resolve = done))
getScope(art).dispose()
resolve({ get name() {
throw new Error('late result must not be inspected')
}, destroy() {
throw new Error('not a lifecycle hook')
} })
assert.equal(await pending, registry)
assert.deepEqual(Object.getOwnPropertyNames(registry), ['art', 'id'])
assert.throws(() => registry.add(() => {
throw new Error('factory must not run')
}), /Cannot add a plugin after ArtPlayer is destroyed/)
assert.equal(registry.id, 1)
})
test('reentrant destruction in a name getter or property-key coercion cannot publish a result', () => {
for (const closeAt of [0, 1, 2, 3]) {
const { art, registry } = create()
const coercions = []
const name = { [Symbol.toPrimitive](hint) {
coercions.push(hint)
if (coercions.length === closeAt)
getScope(art).dispose()
return 'closing'
} }
const result = closeAt > 0
? { name }
: { get name() {
getScope(art).dispose()
return 'closing'
} }
assert.equal(registry.add(() => result), registry)
assert(!Object.hasOwn(registry, 'closing'))
assert.deepEqual(coercions, Array.from({ length: closeAt }, () => 'string'))
}
})
test('live key coercion order and symbol-name failure retain native behavior', () => {
const { registry } = create()
const calls = []
const result = { name: { [Symbol.toPrimitive](hint) {
calls.push(hint)
return `key${calls.length}`
} } }
registry.add(() => result)
assert.equal(registry.key3, result)
assert.deepEqual(calls, ['string', 'string', 'string'])
assert.throws(() => registry.add(() => ({ name: Symbol('name') })), TypeError)
})
test('constructor stops after a synchronous close and observes its own async rejection', async (context) => {
const failure = new Error('initial async failure')
const warnings = []
context.mock.method(console, 'warn', (...args) => warnings.push(args))
const { art } = create([() => Promise.reject(failure), function close() {
getScope(this).dispose()
}, () => {
throw new Error('must not start')
}])
assert(getScope(art).closed)
await new Promise(resolve => setImmediate(resolve))
assert.deepEqual(warnings, [['Failed to initialize ArtPlayer plugin:', failure]])
})
test('registration during reset is rejected before resource scope disposal', () => {
const { art, registry } = create()
Object.assign(art, { isDestroy: false, reset() {
assert.equal(getScope(art).closed, false)
assert.throws(() => registry.add(() => {
throw new Error('must not execute during reset')
}), /Cannot add a plugin after ArtPlayer is destroyed/)
}, template: { $video: {}, destroy() {} }, emit() {} })
destroyInstance(art, [art], true, true)
assert.equal(registry.id, 0)
})
+29
View File
@@ -0,0 +1,29 @@
import type { PluginFactory, Plugins } from 'artplayer'
import Artplayer from 'artplayer'
declare module 'artplayer/types' {
interface Plugins {
typedExtension?: { update: (count: number) => void }
}
interface Events {
'typed-extension:update': [count: number]
}
}
const factory: PluginFactory = function (art) {
const receiver: number = this.id
return { name: 'typedExtension', update(count: number) {
art.emit('typed-extension:update', count + receiver)
} }
}
const art = new Artplayer({ container: '#player', plugins: [factory] }, function (instance) {
this.plugins.typedExtension?.update(1)
instance.plugins.typedExtension?.update(2)
})
const plugins: Plugins = art.plugins
plugins.typedExtension?.update(3)
art.on('typed-extension:update', count => count.toFixed())
// @ts-expect-error Installed result methods retain their argument types.
plugins.typedExtension?.update('bad')
// @ts-expect-error Declared event listener payloads remain checked.
art.on('typed-extension:update', (count: string) => count)
+21
View File
@@ -0,0 +1,21 @@
import type { PluginFactory, PluginHost } from '../../packages/artplayer/src/plugins/types'
import Plugins from '../../packages/artplayer/src/plugins'
interface Host extends PluginHost<Host> {
id: number
}
declare const host: Host
const registry = new Plugins(host)
const sync: typeof registry = registry.add(function (art) {
const id: number = this.id + art.id
return { name: 'sync', id }
})
const asyncResult: Promise<typeof registry> = registry.add(async () => ({ name: 'async' }))
const mixed: typeof registry | Promise<typeof registry> = registry.add(() => Math.random() ? Promise.resolve(1) : 1)
const untyped: PluginFactory<Host> = () => null
const unknown: typeof registry | Promise<typeof registry> = registry.add(untyped)
// @ts-expect-error Synchronous plugins do not always return a Promise.
const bad: Promise<typeof registry> = registry.add(() => ({ name: 'sync' }))
// @ts-expect-error The factory receiver and argument must match the actual host.
registry.add((art: { incompatible: string }) => art)
void [sync, asyncResult, mixed, unknown, bad]