mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-11 05:26:15 -08:00
refactor(core): CORE-08 type plugin registration and guard closed instances
This commit is contained in:
1 parent
f9707bad71
commit
874dac9f22
36 files changed
+2865
-142
No files matched your search
Vendored
+13
-7
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
@@ -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
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
|
||||
Vendored
+1
-1
@@ -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
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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.
|
||||
@@ -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,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
@@ -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
|
||||
}
|
||||
Vendored
+2
-1
@@ -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
|
||||
|
||||
Vendored
+10
@@ -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
@@ -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)
|
||||
|
||||
@@ -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 插件管理器与扩展类型。
|
||||
|
||||
@@ -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
@@ -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
@@ -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",
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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'],
|
||||
])
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
@@ -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)
|
||||
@@ -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]
|
||||
Reference in new issue
Block a user