refactor(core): CORE-05 separate typed defaults and resolved options

This commit is contained in:
Harvey Zhao committed 2026-09-11 01:00:15 +08:00
1 parent 4eec8112fa
commit 97d320bd74
27 files changed
+2278 -240

No files matched your search

+1 -1
View File
@@ -4,4 +4,4 @@
* (c) 2017-2026 Harvey Zhao
* Released under the MIT License.
*/
!function(t,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"==typeof define&&define.amd?(t.Artplayer=e(),define(function(){return t.Artplayer})):(t="undefined"!==typeof globalThis?globalThis:t||self).Artplayer=e()}(this,function(){"use strict";function t(t){return t&&t.__esModule&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t}var e,r={exports:{}};var o=(e||(e=1,r.exports=function(){function t(e){return(t="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(e)}var e=Object.prototype.toString,r=function(r){if(void 0===r)return"undefined";if(null===r)return"null";var a=t(r);if("boolean"===a)return"boolean";if("string"===a)return"string";if("number"===a)return"number";if("symbol"===a)return"symbol";if("function"===a)return"GeneratorFunction"===o(r)?"generatorfunction":"function";if(function(t){return Array.isArray?Array.isArray(t):t instanceof Array}(r))return"array";if(function(t){return!(!t.constructor||"function"!=typeof t.constructor.isBuffer)&&t.constructor.isBuffer(t)}(r))return"buffer";if(function(t){try{if("number"==typeof t.length&&"function"==typeof t.callee)return!0}catch(e){if(-1!==e.message.indexOf("callee"))return!0}return!1}(r))return"arguments";if(function(t){return t instanceof Date||"function"==typeof t.toDateString&&"function"==typeof t.getDate&&"function"==typeof t.setDate}(r))return"date";if(function(t){return t instanceof Error||"string"==typeof t.message&&t.constructor&&"number"==typeof t.constructor.stackTraceLimit}(r))return"error";if(function(t){return t instanceof RegExp||"string"==typeof t.flags&&"boolean"==typeof t.ignoreCase&&"boolean"==typeof t.multiline&&"boolean"==typeof t.global}(r))return"regexp";switch(o(r)){case"Symbol":return"symbol";case"Promise":return"promise";case"WeakMap":return"weakmap";case"WeakSet":return"weakset";case"Map":return"map";case"Set":return"set";case"Int8Array":return"int8array";case"Uint8Array":return"uint8array";case"Uint8ClampedArray":return"uint8clampedarray";case"Int16Array":return"int16array";case"Uint16Array":return"uint16array";case"Int32Array":return"int32array";case"Uint32Array":return"uint32array";case"Float32Array":return"float32array";case"Float64Array":return"float64array"}if(function(t){return"function"==typeof t.throw&&"function"==typeof t.return&&"function"==typeof t.next}(r))return"generator";switch(a=e.call(r)){case"[object Object]":return"object";case"[object Map Iterator]":return"mapiterator";case"[object Set Iterator]":return"setiterator";case"[object String Iterator]":return"stringiterator";case"[object Array Iterator]":return"arrayiterator"}return a.slice(8,-1).toLowerCase().replace(/\s/g,"")};function o(t){return t.constructor?t.constructor.name:null}function a(t,e){var o=2<arguments.length&&void 0!==arguments[2]?arguments[2]:["option"];return n(t,e,o),i(t,e,o),function(t,e,o){var s=r(e),l=r(t);if("object"===s){if("object"!==l)throw new Error("[Type Error]: '".concat(o.join("."),"' require 'object' type, but got '").concat(l,"'"));Object.keys(e).forEach(function(r){var s=t[r],l=e[r],c=o.slice();c.push(r),n(s,l,c),i(s,l,c),a(s,l,c)})}if("array"===s){if("array"!==l)throw new Error("[Type Error]: '".concat(o.join("."),"' require 'array' type, but got '").concat(l,"'"));t.forEach(function(r,s){var l=t[s],c=e[s]||e[0],d=o.slice();d.push(s),n(l,c,d),i(l,c,d),a(l,c,d)})}}(t,e,o),t}function n(t,e,o){if("string"===r(e)){var a=r(t);if("?"===e[0]&&(e=e.slice(1)+"|undefined"),!(-1<e.indexOf("|")?e.split("|").map(function(t){return t.toLowerCase().trim()}).filter(Boolean).some(function(t){return a===t}):e.toLowerCase().trim()===a))throw new Error("[Type Error]: '".concat(o.join("."),"' require '").concat(e,"' type, but got '").concat(a,"'"))}}function i(t,e,o){if("function"===r(e)){var a=e(t,r(t),o);if(!0!==a){var n=r(a);throw"string"===n?new Error(a):"error"===n?a:new Error("[Validator Error]: The scheme for '".concat(o.join("."),"' validator require return true, but got '").concat(a,"'"))}}}return a.kindOf=r,a}()),r.exports);const a=t(o),n="5.4.1",i={properties:["audioTracks","autoplay","buffered","controller","controls","crossOrigin","currentSrc","currentTime","defaultMuted","defaultPlaybackRate","duration","ended","error","loop","mediaGroup","muted","networkState","paused","playbackRate","played","preload","readyState","seekable","seeking","src","startDate","textTracks","videoTracks","volume"],methods:["addTextTrack","canPlayType","load","play","pause"],events:["abort","canplay","canplaythrough","durationchange","emptied","ended","error","loadeddata","loadedmetadata","loadstart","pause","play","playing","progress","ratechange","seeked","seeking","stalled","suspend","timeupdate","volumechange","waiting"],prototypes:["width","height","videoWidth","videoHeight","poster","webkitDecodedFrameCount","webkitDroppedFrameCount","playsInline","webLine truncated
!function(t,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"==typeof define&&define.amd?(t.Artplayer=e(),define(function(){return t.Artplayer})):(t="undefined"!==typeof globalThis?globalThis:t||self).Artplayer=e()}(this,function(){"use strict";function t(t){return t&&t.__esModule&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t}var e,r={exports:{}};var o=(e||(e=1,r.exports=function(){function t(e){return(t="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(e)}var e=Object.prototype.toString,r=function(r){if(void 0===r)return"undefined";if(null===r)return"null";var a=t(r);if("boolean"===a)return"boolean";if("string"===a)return"string";if("number"===a)return"number";if("symbol"===a)return"symbol";if("function"===a)return"GeneratorFunction"===o(r)?"generatorfunction":"function";if(function(t){return Array.isArray?Array.isArray(t):t instanceof Array}(r))return"array";if(function(t){return!(!t.constructor||"function"!=typeof t.constructor.isBuffer)&&t.constructor.isBuffer(t)}(r))return"buffer";if(function(t){try{if("number"==typeof t.length&&"function"==typeof t.callee)return!0}catch(e){if(-1!==e.message.indexOf("callee"))return!0}return!1}(r))return"arguments";if(function(t){return t instanceof Date||"function"==typeof t.toDateString&&"function"==typeof t.getDate&&"function"==typeof t.setDate}(r))return"date";if(function(t){return t instanceof Error||"string"==typeof t.message&&t.constructor&&"number"==typeof t.constructor.stackTraceLimit}(r))return"error";if(function(t){return t instanceof RegExp||"string"==typeof t.flags&&"boolean"==typeof t.ignoreCase&&"boolean"==typeof t.multiline&&"boolean"==typeof t.global}(r))return"regexp";switch(o(r)){case"Symbol":return"symbol";case"Promise":return"promise";case"WeakMap":return"weakmap";case"WeakSet":return"weakset";case"Map":return"map";case"Set":return"set";case"Int8Array":return"int8array";case"Uint8Array":return"uint8array";case"Uint8ClampedArray":return"uint8clampedarray";case"Int16Array":return"int16array";case"Uint16Array":return"uint16array";case"Int32Array":return"int32array";case"Uint32Array":return"uint32array";case"Float32Array":return"float32array";case"Float64Array":return"float64array"}if(function(t){return"function"==typeof t.throw&&"function"==typeof t.return&&"function"==typeof t.next}(r))return"generator";switch(a=e.call(r)){case"[object Object]":return"object";case"[object Map Iterator]":return"mapiterator";case"[object Set Iterator]":return"setiterator";case"[object String Iterator]":return"stringiterator";case"[object Array Iterator]":return"arrayiterator"}return a.slice(8,-1).toLowerCase().replace(/\s/g,"")};function o(t){return t.constructor?t.constructor.name:null}function a(t,e){var o=2<arguments.length&&void 0!==arguments[2]?arguments[2]:["option"];return n(t,e,o),i(t,e,o),function(t,e,o){var s=r(e),l=r(t);if("object"===s){if("object"!==l)throw new Error("[Type Error]: '".concat(o.join("."),"' require 'object' type, but got '").concat(l,"'"));Object.keys(e).forEach(function(r){var s=t[r],l=e[r],c=o.slice();c.push(r),n(s,l,c),i(s,l,c),a(s,l,c)})}if("array"===s){if("array"!==l)throw new Error("[Type Error]: '".concat(o.join("."),"' require 'array' type, but got '").concat(l,"'"));t.forEach(function(r,s){var l=t[s],c=e[s]||e[0],d=o.slice();d.push(s),n(l,c,d),i(l,c,d),a(l,c,d)})}}(t,e,o),t}function n(t,e,o){if("string"===r(e)){var a=r(t);if("?"===e[0]&&(e=e.slice(1)+"|undefined"),!(-1<e.indexOf("|")?e.split("|").map(function(t){return t.toLowerCase().trim()}).filter(Boolean).some(function(t){return a===t}):e.toLowerCase().trim()===a))throw new Error("[Type Error]: '".concat(o.join("."),"' require '").concat(e,"' type, but got '").concat(a,"'"))}}function i(t,e,o){if("function"===r(e)){var a=e(t,r(t),o);if(!0!==a){var n=r(a);throw"string"===n?new Error(a):"error"===n?a:new Error("[Validator Error]: The scheme for '".concat(o.join("."),"' validator require return true, but got '").concat(a,"'"))}}}return a.kindOf=r,a}()),r.exports);const a=t(o),n="5.4.1",i={properties:["audioTracks","autoplay","buffered","controller","controls","crossOrigin","currentSrc","currentTime","defaultMuted","defaultPlaybackRate","duration","ended","error","loop","mediaGroup","muted","networkState","paused","playbackRate","played","preload","readyState","seekable","seeking","src","startDate","textTracks","videoTracks","volume"],methods:["addTextTrack","canPlayType","load","play","pause"],events:["abort","canplay","canplaythrough","durationchange","emptied","ended","error","loadeddata","loadedmetadata","loadstart","pause","play","playing","progress","ratechange","seeked","seeking","stalled","suspend","timeupdate","volumechange","waiting"],prototypes:["width","height","videoWidth","videoHeight","poster","webkitDecodedFrameCount","webkitDroppedFrameCount","playsInline","webLine truncated
+1 -1
View File
@@ -4,4 +4,4 @@
* (c) 2017-2026 Harvey Zhao
* Released under the MIT License.
*/
!function(t,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"==typeof define&&define.amd?(t.Artplayer=e(),define(function(){return t.Artplayer})):(t="undefined"!==typeof globalThis?globalThis:t||self).Artplayer=e()}(this,function(){"use strict";var t,e=Object.defineProperty,r=Object.defineProperties,o=Object.getOwnPropertyDescriptors,a=Object.getOwnPropertySymbols,n=Object.prototype.hasOwnProperty,i=Object.prototype.propertyIsEnumerable,s=(t,r,o)=>r in t?e(t,r,{enumerable:!0,configurable:!0,writable:!0,value:o}):t[r]=o,l=(t,e)=>{for(var r in e||(e={}))n.call(e,r)&&s(t,r,e[r]);if(a)for(var r of a(e))i.call(e,r)&&s(t,r,e[r]);return t},c=(t,e)=>r(t,o(e)),d=(t,e,r)=>new Promise((o,a)=>{var n=t=>{try{s(r.next(t))}catch(e){a(e)}},i=t=>{try{s(r.throw(t))}catch(e){a(e)}},s=t=>t.done?o(t.value):Promise.resolve(t.value).then(n,i);s((r=r.apply(t,e)).next())});function p(t){return t&&t.__esModule&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t}var h,u={exports:{}};var m=(h||(h=1,u.exports=function(){function t(e){return(t="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(e)}var e=Object.prototype.toString,r=function(r){if(void 0===r)return"undefined";if(null===r)return"null";var a=t(r);if("boolean"===a)return"boolean";if("string"===a)return"string";if("number"===a)return"number";if("symbol"===a)return"symbol";if("function"===a)return"GeneratorFunction"===o(r)?"generatorfunction":"function";if(function(t){return Array.isArray?Array.isArray(t):t instanceof Array}(r))return"array";if(function(t){return!(!t.constructor||"function"!=typeof t.constructor.isBuffer)&&t.constructor.isBuffer(t)}(r))return"buffer";if(function(t){try{if("number"==typeof t.length&&"function"==typeof t.callee)return!0}catch(e){if(-1!==e.message.indexOf("callee"))return!0}return!1}(r))return"arguments";if(function(t){return t instanceof Date||"function"==typeof t.toDateString&&"function"==typeof t.getDate&&"function"==typeof t.setDate}(r))return"date";if(function(t){return t instanceof Error||"string"==typeof t.message&&t.constructor&&"number"==typeof t.constructor.stackTraceLimit}(r))return"error";if(function(t){return t instanceof RegExp||"string"==typeof t.flags&&"boolean"==typeof t.ignoreCase&&"boolean"==typeof t.multiline&&"boolean"==typeof t.global}(r))return"regexp";switch(o(r)){case"Symbol":return"symbol";case"Promise":return"promise";case"WeakMap":return"weakmap";case"WeakSet":return"weakset";case"Map":return"map";case"Set":return"set";case"Int8Array":return"int8array";case"Uint8Array":return"uint8array";case"Uint8ClampedArray":return"uint8clampedarray";case"Int16Array":return"int16array";case"Uint16Array":return"uint16array";case"Int32Array":return"int32array";case"Uint32Array":return"uint32array";case"Float32Array":return"float32array";case"Float64Array":return"float64array"}if(function(t){return"function"==typeof t.throw&&"function"==typeof t.return&&"function"==typeof t.next}(r))return"generator";switch(a=e.call(r)){case"[object Object]":return"object";case"[object Map Iterator]":return"mapiterator";case"[object Set Iterator]":return"setiterator";case"[object String Iterator]":return"stringiterator";case"[object Array Iterator]":return"arrayiterator"}return a.slice(8,-1).toLowerCase().replace(/\s/g,"")};function o(t){return t.constructor?t.constructor.name:null}function a(t,e){var o=2<arguments.length&&void 0!==arguments[2]?arguments[2]:["option"];return n(t,e,o),i(t,e,o),function(t,e,o){var s=r(e),l=r(t);if("object"===s){if("object"!==l)throw new Error("[Type Error]: '".concat(o.join("."),"' require 'object' type, but got '").concat(l,"'"));Object.keys(e).forEach(function(r){var s=t[r],l=e[r],c=o.slice();c.push(r),n(s,l,c),i(s,l,c),a(s,l,c)})}if("array"===s){if("array"!==l)throw new Error("[Type Error]: '".concat(o.join("."),"' require 'array' type, but got '").concat(l,"'"));t.forEach(function(r,s){var l=t[s],c=e[s]||e[0],d=o.slice();d.push(s),n(l,c,d),i(l,c,d),a(l,c,d)})}}(t,e,o),t}function n(t,e,o){if("string"===r(e)){var a=r(t);if("?"===e[0]&&(e=e.slice(1)+"|undefined"),!(-1<e.indexOf("|")?e.split("|").map(function(t){return t.toLowerCase().trim()}).filter(Boolean).some(function(t){return a===t}):e.toLowerCase().trim()===a))throw new Error("[Type Error]: '".concat(o.join("."),"' require '").concat(e,"' type, but got '").concat(a,"'"))}}function i(t,e,o){if("function"===r(e)){var a=e(t,r(t),o);if(!0!==a){var n=r(a);throw"string"===n?new Error(a):"error"===n?a:new Error("[Validator Error]: The scheme for '".concat(o.join("."),"' validator require return true, but got '").concat(a,"'"))}}}return a.kindOf=r,a}()),u.exports);const f=p(m),g="5.4.1",v={properties:["audioTracks","autoplay","buffered","controller","controls","crossOrigin","currentSrc","currentTime","defaultMuted","defaultPlaybackRate","duration","ended","error","loop","meLine truncated
!function(t,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"==typeof define&&define.amd?(t.Artplayer=e(),define(function(){return t.Artplayer})):(t="undefined"!==typeof globalThis?globalThis:t||self).Artplayer=e()}(this,function(){"use strict";var t,e=Object.defineProperty,r=Object.defineProperties,o=Object.getOwnPropertyDescriptors,a=Object.getOwnPropertySymbols,n=Object.prototype.hasOwnProperty,i=Object.prototype.propertyIsEnumerable,s=(t,r,o)=>r in t?e(t,r,{enumerable:!0,configurable:!0,writable:!0,value:o}):t[r]=o,l=(t,e)=>{for(var r in e||(e={}))n.call(e,r)&&s(t,r,e[r]);if(a)for(var r of a(e))i.call(e,r)&&s(t,r,e[r]);return t},c=(t,e)=>r(t,o(e)),d=(t,e,r)=>new Promise((o,a)=>{var n=t=>{try{s(r.next(t))}catch(e){a(e)}},i=t=>{try{s(r.throw(t))}catch(e){a(e)}},s=t=>t.done?o(t.value):Promise.resolve(t.value).then(n,i);s((r=r.apply(t,e)).next())});function p(t){return t&&t.__esModule&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t}var h,u={exports:{}};var m=(h||(h=1,u.exports=function(){function t(e){return(t="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(e)}var e=Object.prototype.toString,r=function(r){if(void 0===r)return"undefined";if(null===r)return"null";var a=t(r);if("boolean"===a)return"boolean";if("string"===a)return"string";if("number"===a)return"number";if("symbol"===a)return"symbol";if("function"===a)return"GeneratorFunction"===o(r)?"generatorfunction":"function";if(function(t){return Array.isArray?Array.isArray(t):t instanceof Array}(r))return"array";if(function(t){return!(!t.constructor||"function"!=typeof t.constructor.isBuffer)&&t.constructor.isBuffer(t)}(r))return"buffer";if(function(t){try{if("number"==typeof t.length&&"function"==typeof t.callee)return!0}catch(e){if(-1!==e.message.indexOf("callee"))return!0}return!1}(r))return"arguments";if(function(t){return t instanceof Date||"function"==typeof t.toDateString&&"function"==typeof t.getDate&&"function"==typeof t.setDate}(r))return"date";if(function(t){return t instanceof Error||"string"==typeof t.message&&t.constructor&&"number"==typeof t.constructor.stackTraceLimit}(r))return"error";if(function(t){return t instanceof RegExp||"string"==typeof t.flags&&"boolean"==typeof t.ignoreCase&&"boolean"==typeof t.multiline&&"boolean"==typeof t.global}(r))return"regexp";switch(o(r)){case"Symbol":return"symbol";case"Promise":return"promise";case"WeakMap":return"weakmap";case"WeakSet":return"weakset";case"Map":return"map";case"Set":return"set";case"Int8Array":return"int8array";case"Uint8Array":return"uint8array";case"Uint8ClampedArray":return"uint8clampedarray";case"Int16Array":return"int16array";case"Uint16Array":return"uint16array";case"Int32Array":return"int32array";case"Uint32Array":return"uint32array";case"Float32Array":return"float32array";case"Float64Array":return"float64array"}if(function(t){return"function"==typeof t.throw&&"function"==typeof t.return&&"function"==typeof t.next}(r))return"generator";switch(a=e.call(r)){case"[object Object]":return"object";case"[object Map Iterator]":return"mapiterator";case"[object Set Iterator]":return"setiterator";case"[object String Iterator]":return"stringiterator";case"[object Array Iterator]":return"arrayiterator"}return a.slice(8,-1).toLowerCase().replace(/\s/g,"")};function o(t){return t.constructor?t.constructor.name:null}function a(t,e){var o=2<arguments.length&&void 0!==arguments[2]?arguments[2]:["option"];return n(t,e,o),i(t,e,o),function(t,e,o){var s=r(e),l=r(t);if("object"===s){if("object"!==l)throw new Error("[Type Error]: '".concat(o.join("."),"' require 'object' type, but got '").concat(l,"'"));Object.keys(e).forEach(function(r){var s=t[r],l=e[r],c=o.slice();c.push(r),n(s,l,c),i(s,l,c),a(s,l,c)})}if("array"===s){if("array"!==l)throw new Error("[Type Error]: '".concat(o.join("."),"' require 'array' type, but got '").concat(l,"'"));t.forEach(function(r,s){var l=t[s],c=e[s]||e[0],d=o.slice();d.push(s),n(l,c,d),i(l,c,d),a(l,c,d)})}}(t,e,o),t}function n(t,e,o){if("string"===r(e)){var a=r(t);if("?"===e[0]&&(e=e.slice(1)+"|undefined"),!(-1<e.indexOf("|")?e.split("|").map(function(t){return t.toLowerCase().trim()}).filter(Boolean).some(function(t){return a===t}):e.toLowerCase().trim()===a))throw new Error("[Type Error]: '".concat(o.join("."),"' require '").concat(e,"' type, but got '").concat(a,"'"))}}function i(t,e,o){if("function"===r(e)){var a=e(t,r(t),o);if(!0!==a){var n=r(a);throw"string"===n?new Error(a):"error"===n?a:new Error("[Validator Error]: The scheme for '".concat(o.join("."),"' validator require return true, but got '").concat(a,"'"))}}}return a.kindOf=r,a}()),u.exports);const f=p(m),g="5.4.1",v={properties:["audioTracks","autoplay","buffered","controller","controls","crossOrigin","currentSrc","currentTime","defaultMuted","defaultPlaybackRate","duration","ended","error","loop","meLine truncated
+80 -74
View File
@@ -735,7 +735,7 @@ const scheme = {
position: (value, _, paths) => {
const position = ["top", "left", "right"];
return errorHandle(
position.includes(value),
typeof value === "string" && position.includes(value),
`${paths.join(".")} only accept ${position.toString()} as parameters`
);
}
@@ -2533,6 +2533,83 @@ class Notice {
return $player.classList.contains("art-notice-show");
}
}
function createDefaults() {
return {
id: "",
container: "#artplayer",
url: "",
poster: "",
type: "",
theme: "#f00",
volume: 0.7,
isLive: false,
muted: false,
autoplay: false,
autoSize: false,
autoMini: false,
loop: false,
flip: false,
playbackRate: false,
aspectRatio: false,
screenshot: false,
setting: false,
hotkey: true,
pip: false,
mutex: true,
backdrop: true,
fullscreen: false,
fullscreenWeb: false,
subtitleOffset: false,
miniProgressBar: false,
useSSR: false,
playsInline: true,
lock: false,
gesture: true,
fastForward: false,
autoPlayback: false,
autoOrientation: false,
airplay: false,
proxy: void 0,
layers: [],
contextmenu: [],
controls: [],
settings: [],
quality: [],
highlight: [],
plugins: [],
thumbnails: {
url: "",
number: 60,
column: 10,
width: 0,
height: 0,
scale: 1
},
subtitle: {
url: "",
type: "",
style: {},
name: "",
escape: true,
encoding: "utf-8",
onVttLoad: (vtt) => vtt
},
moreVideoAttr: {
controls: false,
preload: isSafari ? "auto" : "metadata"
},
i18n: {},
icons: {},
cssVar: {},
customType: {},
lang: navigator?.language.toLowerCase()
};
}
function resolveOption(input, defaults) {
const merged = mergeDeep(defaults, input);
merged.container = input.container;
return validator(merged, scheme);
}
function airplayMix(art) {
const {
i18n,
@@ -5085,9 +5162,7 @@ class Artplayer extends Emitter {
throw new Error("Artplayer can only be used in the browser environment");
}
this.id = ++id;
const mergeOption = mergeDeep(Artplayer.option, option);
mergeOption.container = option.container;
this.option = validator(mergeOption, scheme);
this.option = resolveOption(option, Artplayer.option);
this.isLock = false;
this.isReady = false;
this.isFocus = false;
@@ -5164,76 +5239,7 @@ class Artplayer extends Emitter {
return Template.html;
}
static get option() {
return {
id: "",
container: "#artplayer",
url: "",
poster: "",
type: "",
theme: "#f00",
volume: 0.7,
isLive: false,
muted: false,
autoplay: false,
autoSize: false,
autoMini: false,
loop: false,
flip: false,
playbackRate: false,
aspectRatio: false,
screenshot: false,
setting: false,
hotkey: true,
pip: false,
mutex: true,
backdrop: true,
fullscreen: false,
fullscreenWeb: false,
subtitleOffset: false,
miniProgressBar: false,
useSSR: false,
playsInline: true,
lock: false,
gesture: true,
fastForward: false,
autoPlayback: false,
autoOrientation: false,
airplay: false,
proxy: void 0,
layers: [],
contextmenu: [],
controls: [],
settings: [],
quality: [],
highlight: [],
plugins: [],
thumbnails: {
url: "",
number: 60,
column: 10,
width: 0,
height: 0,
scale: 1
},
subtitle: {
url: "",
type: "",
style: {},
name: "",
escape: true,
encoding: "utf-8",
onVttLoad: (vtt) => vtt
},
moreVideoAttr: {
controls: false,
preload: isSafari ? "auto" : "metadata"
},
i18n: {},
icons: {},
cssVar: {},
customType: {},
lang: navigator?.language.toLowerCase()
};
return createDefaults();
}
get proxy() {
return this.events.proxy;
+3 -3
View File
@@ -38,10 +38,10 @@
"test:dash-control": "node --test test/dash-control.test.js",
"dev": "npx cross-env NODE_ENV=development node ./scripts/dev.js",
"build": "npx cross-env NODE_ENV=production node ./scripts/build.js",
"lint": "eslint \"packages/*/{src,types,package.json}\" \"scripts/*.{js,mjs}\" \"test/*\" \"docs/assets/ts/*\" playwright.config.js --no-fix",
"lint": "eslint \"packages/*/{src,types,package.json}\" \"scripts/*.{js,mjs}\" \"test/*\" \"docs/assets/ts/*\" \"types/*.d.ts\" playwright.config.js --no-fix",
"build:all": "yarn ci:build && yarn lint",
"check:toolchain": "node scripts/check-toolchain.mjs",
"lint:fix": "eslint \"packages/*/{src,types,package.json}\" \"scripts/*.{js,mjs}\" \"test/*\" \"docs/assets/ts/*\" playwright.config.js --fix",
"lint:fix": "eslint \"packages/*/{src,types,package.json}\" \"scripts/*.{js,mjs}\" \"test/*\" \"docs/assets/ts/*\" \"types/*.d.ts\" playwright.config.js --fix",
"check:plan": "node refactor/scripts/plan.mjs --check",
"test:node": "yarn test:unit && node --test test/toolchain.test.js test/build-docs.test.js test/package-check.test.js",
"test:baseline": "node --test refactor/scripts/*.test.mjs",
@@ -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: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": "yarn test:node && yarn test:baseline",
"test:browser": "playwright test",
"test:browser:install": "playwright install chromium firefox webkit",
+41
View File
@@ -4,6 +4,47 @@ 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.
## Input and resolved configuration
`src/option/defaults.ts` creates fresh defaults on every Artplayer.option access,
including nested objects, arrays and the identity onVttLoad callback. It reads browser
language at the same getter call and retains the existing Safari preload decision.
The entry passes Artplayer.option to `option/resolve.ts`, which performs the existing
deep merge, restores input.container by reference and invokes the same option-validator
function. Allocation of the instance id still precedes validation; DOM mounting follows it.
`option/types.ts` reuses the public Option as the typed input contract. ResolvedOption
requires top-level defaults and subtitle/thumbnail defaults, while explicitly allowing
proxy to remain undefined. ResolvedInput preserves extra typed application fields without
adding a global any index signature. The one assertion after validation connects the
existing dynamic merge to a successfully resolved typed input; this function is not an
unknown-to-safe-object guard. Arbitrary JS still passes through runtime validation,
and permissive nested map/extension data needs checks in its consuming module.
`src/scheme/index.ts` preserves the mutable runtime schema, shared ComponentOption
objects and callback validation messages. Its keys are checked against Option at compile
time. `types/option-validator.d.ts` at the repository root describes the installed 2.0.6
identity validator, callback paths and kindOf; it adds no runtime wrapper or dependency.
Root/core strict configs include that declaration and lint covers root declaration files.
The public Artplayer.validator and scheme references remain available as before.
Merge behavior includes unknown own keys, collection item references, two reads of an
enumerable input container getter, and restoration of an inherited container. Explicit
undefined can still fail the existing schema instead of being silently defaulted. This
task changes neither validation rules nor public consumer declarations.
Two verified declaration gaps are tracked as BASE-TYPE-06 for CORE-07: existing JS can
omit url and use numeric control HTML, while the current public types reject those forms.
The historical rejection fixtures must become positive consumer checks when reconciled.
Other permissive validator cases are not blanket guarantees that later DOM/media code
can consume every accepted value. Preserve failure stages while migrating those consumers.
Run `yarn test:unit` for published/current defaults, merge and error comparisons, and
`yarn typecheck` for required resolved fields and retained callback/application types.
`options.spec.js` exercises actual candidate construction, invalid input timing and
real-media customType/ready callbacks in the three browser engines. Keep those cases
when changing defaults, schema or resolution; also rerun installed-package validation.
## Current TypeScript boundary: utilities
| Module | Responsibility and constraints |
+1 -1
View File
@@ -4,4 +4,4 @@
* (c) 2017-2026 Harvey Zhao
* Released under the MIT License.
*/
!function(t,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"==typeof define&&define.amd?(t.Artplayer=e(),define(function(){return t.Artplayer})):(t="undefined"!==typeof globalThis?globalThis:t||self).Artplayer=e()}(this,function(){"use strict";function t(t){return t&&t.__esModule&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t}var e,r={exports:{}};var o=(e||(e=1,r.exports=function(){function t(e){return(t="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(e)}var e=Object.prototype.toString,r=function(r){if(void 0===r)return"undefined";if(null===r)return"null";var a=t(r);if("boolean"===a)return"boolean";if("string"===a)return"string";if("number"===a)return"number";if("symbol"===a)return"symbol";if("function"===a)return"GeneratorFunction"===o(r)?"generatorfunction":"function";if(function(t){return Array.isArray?Array.isArray(t):t instanceof Array}(r))return"array";if(function(t){return!(!t.constructor||"function"!=typeof t.constructor.isBuffer)&&t.constructor.isBuffer(t)}(r))return"buffer";if(function(t){try{if("number"==typeof t.length&&"function"==typeof t.callee)return!0}catch(e){if(-1!==e.message.indexOf("callee"))return!0}return!1}(r))return"arguments";if(function(t){return t instanceof Date||"function"==typeof t.toDateString&&"function"==typeof t.getDate&&"function"==typeof t.setDate}(r))return"date";if(function(t){return t instanceof Error||"string"==typeof t.message&&t.constructor&&"number"==typeof t.constructor.stackTraceLimit}(r))return"error";if(function(t){return t instanceof RegExp||"string"==typeof t.flags&&"boolean"==typeof t.ignoreCase&&"boolean"==typeof t.multiline&&"boolean"==typeof t.global}(r))return"regexp";switch(o(r)){case"Symbol":return"symbol";case"Promise":return"promise";case"WeakMap":return"weakmap";case"WeakSet":return"weakset";case"Map":return"map";case"Set":return"set";case"Int8Array":return"int8array";case"Uint8Array":return"uint8array";case"Uint8ClampedArray":return"uint8clampedarray";case"Int16Array":return"int16array";case"Uint16Array":return"uint16array";case"Int32Array":return"int32array";case"Uint32Array":return"uint32array";case"Float32Array":return"float32array";case"Float64Array":return"float64array"}if(function(t){return"function"==typeof t.throw&&"function"==typeof t.return&&"function"==typeof t.next}(r))return"generator";switch(a=e.call(r)){case"[object Object]":return"object";case"[object Map Iterator]":return"mapiterator";case"[object Set Iterator]":return"setiterator";case"[object String Iterator]":return"stringiterator";case"[object Array Iterator]":return"arrayiterator"}return a.slice(8,-1).toLowerCase().replace(/\s/g,"")};function o(t){return t.constructor?t.constructor.name:null}function a(t,e){var o=2<arguments.length&&void 0!==arguments[2]?arguments[2]:["option"];return n(t,e,o),i(t,e,o),function(t,e,o){var s=r(e),l=r(t);if("object"===s){if("object"!==l)throw new Error("[Type Error]: '".concat(o.join("."),"' require 'object' type, but got '").concat(l,"'"));Object.keys(e).forEach(function(r){var s=t[r],l=e[r],c=o.slice();c.push(r),n(s,l,c),i(s,l,c),a(s,l,c)})}if("array"===s){if("array"!==l)throw new Error("[Type Error]: '".concat(o.join("."),"' require 'array' type, but got '").concat(l,"'"));t.forEach(function(r,s){var l=t[s],c=e[s]||e[0],d=o.slice();d.push(s),n(l,c,d),i(l,c,d),a(l,c,d)})}}(t,e,o),t}function n(t,e,o){if("string"===r(e)){var a=r(t);if("?"===e[0]&&(e=e.slice(1)+"|undefined"),!(-1<e.indexOf("|")?e.split("|").map(function(t){return t.toLowerCase().trim()}).filter(Boolean).some(function(t){return a===t}):e.toLowerCase().trim()===a))throw new Error("[Type Error]: '".concat(o.join("."),"' require '").concat(e,"' type, but got '").concat(a,"'"))}}function i(t,e,o){if("function"===r(e)){var a=e(t,r(t),o);if(!0!==a){var n=r(a);throw"string"===n?new Error(a):"error"===n?a:new Error("[Validator Error]: The scheme for '".concat(o.join("."),"' validator require return true, but got '").concat(a,"'"))}}}return a.kindOf=r,a}()),r.exports);const a=t(o),n="5.4.1",i={properties:["audioTracks","autoplay","buffered","controller","controls","crossOrigin","currentSrc","currentTime","defaultMuted","defaultPlaybackRate","duration","ended","error","loop","mediaGroup","muted","networkState","paused","playbackRate","played","preload","readyState","seekable","seeking","src","startDate","textTracks","videoTracks","volume"],methods:["addTextTrack","canPlayType","load","play","pause"],events:["abort","canplay","canplaythrough","durationchange","emptied","ended","error","loadeddata","loadedmetadata","loadstart","pause","play","playing","progress","ratechange","seeked","seeking","stalled","suspend","timeupdate","volumechange","waiting"],prototypes:["width","height","videoWidth","videoHeight","poster","webkitDecodedFrameCount","webkitDroppedFrameCount","playsInline","webLine truncated
!function(t,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"==typeof define&&define.amd?(t.Artplayer=e(),define(function(){return t.Artplayer})):(t="undefined"!==typeof globalThis?globalThis:t||self).Artplayer=e()}(this,function(){"use strict";function t(t){return t&&t.__esModule&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t}var e,r={exports:{}};var o=(e||(e=1,r.exports=function(){function t(e){return(t="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(e)}var e=Object.prototype.toString,r=function(r){if(void 0===r)return"undefined";if(null===r)return"null";var a=t(r);if("boolean"===a)return"boolean";if("string"===a)return"string";if("number"===a)return"number";if("symbol"===a)return"symbol";if("function"===a)return"GeneratorFunction"===o(r)?"generatorfunction":"function";if(function(t){return Array.isArray?Array.isArray(t):t instanceof Array}(r))return"array";if(function(t){return!(!t.constructor||"function"!=typeof t.constructor.isBuffer)&&t.constructor.isBuffer(t)}(r))return"buffer";if(function(t){try{if("number"==typeof t.length&&"function"==typeof t.callee)return!0}catch(e){if(-1!==e.message.indexOf("callee"))return!0}return!1}(r))return"arguments";if(function(t){return t instanceof Date||"function"==typeof t.toDateString&&"function"==typeof t.getDate&&"function"==typeof t.setDate}(r))return"date";if(function(t){return t instanceof Error||"string"==typeof t.message&&t.constructor&&"number"==typeof t.constructor.stackTraceLimit}(r))return"error";if(function(t){return t instanceof RegExp||"string"==typeof t.flags&&"boolean"==typeof t.ignoreCase&&"boolean"==typeof t.multiline&&"boolean"==typeof t.global}(r))return"regexp";switch(o(r)){case"Symbol":return"symbol";case"Promise":return"promise";case"WeakMap":return"weakmap";case"WeakSet":return"weakset";case"Map":return"map";case"Set":return"set";case"Int8Array":return"int8array";case"Uint8Array":return"uint8array";case"Uint8ClampedArray":return"uint8clampedarray";case"Int16Array":return"int16array";case"Uint16Array":return"uint16array";case"Int32Array":return"int32array";case"Uint32Array":return"uint32array";case"Float32Array":return"float32array";case"Float64Array":return"float64array"}if(function(t){return"function"==typeof t.throw&&"function"==typeof t.return&&"function"==typeof t.next}(r))return"generator";switch(a=e.call(r)){case"[object Object]":return"object";case"[object Map Iterator]":return"mapiterator";case"[object Set Iterator]":return"setiterator";case"[object String Iterator]":return"stringiterator";case"[object Array Iterator]":return"arrayiterator"}return a.slice(8,-1).toLowerCase().replace(/\s/g,"")};function o(t){return t.constructor?t.constructor.name:null}function a(t,e){var o=2<arguments.length&&void 0!==arguments[2]?arguments[2]:["option"];return n(t,e,o),i(t,e,o),function(t,e,o){var s=r(e),l=r(t);if("object"===s){if("object"!==l)throw new Error("[Type Error]: '".concat(o.join("."),"' require 'object' type, but got '").concat(l,"'"));Object.keys(e).forEach(function(r){var s=t[r],l=e[r],c=o.slice();c.push(r),n(s,l,c),i(s,l,c),a(s,l,c)})}if("array"===s){if("array"!==l)throw new Error("[Type Error]: '".concat(o.join("."),"' require 'array' type, but got '").concat(l,"'"));t.forEach(function(r,s){var l=t[s],c=e[s]||e[0],d=o.slice();d.push(s),n(l,c,d),i(l,c,d),a(l,c,d)})}}(t,e,o),t}function n(t,e,o){if("string"===r(e)){var a=r(t);if("?"===e[0]&&(e=e.slice(1)+"|undefined"),!(-1<e.indexOf("|")?e.split("|").map(function(t){return t.toLowerCase().trim()}).filter(Boolean).some(function(t){return a===t}):e.toLowerCase().trim()===a))throw new Error("[Type Error]: '".concat(o.join("."),"' require '").concat(e,"' type, but got '").concat(a,"'"))}}function i(t,e,o){if("function"===r(e)){var a=e(t,r(t),o);if(!0!==a){var n=r(a);throw"string"===n?new Error(a):"error"===n?a:new Error("[Validator Error]: The scheme for '".concat(o.join("."),"' validator require return true, but got '").concat(a,"'"))}}}return a.kindOf=r,a}()),r.exports);const a=t(o),n="5.4.1",i={properties:["audioTracks","autoplay","buffered","controller","controls","crossOrigin","currentSrc","currentTime","defaultMuted","defaultPlaybackRate","duration","ended","error","loop","mediaGroup","muted","networkState","paused","playbackRate","played","preload","readyState","seekable","seeking","src","startDate","textTracks","videoTracks","volume"],methods:["addTextTrack","canPlayType","load","play","pause"],events:["abort","canplay","canplaythrough","durationchange","emptied","ended","error","loadeddata","loadedmetadata","loadstart","pause","play","playing","progress","ratechange","seeked","seeking","stalled","suspend","timeupdate","volumechange","waiting"],prototypes:["width","height","videoWidth","videoHeight","poster","webkitDecodedFrameCount","webkitDroppedFrameCount","playsInline","webLine truncated
+1 -1
View File
@@ -4,4 +4,4 @@
* (c) 2017-2026 Harvey Zhao
* Released under the MIT License.
*/
!function(t,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"==typeof define&&define.amd?(t.Artplayer=e(),define(function(){return t.Artplayer})):(t="undefined"!==typeof globalThis?globalThis:t||self).Artplayer=e()}(this,function(){"use strict";var t,e=Object.defineProperty,r=Object.defineProperties,o=Object.getOwnPropertyDescriptors,a=Object.getOwnPropertySymbols,n=Object.prototype.hasOwnProperty,i=Object.prototype.propertyIsEnumerable,s=(t,r,o)=>r in t?e(t,r,{enumerable:!0,configurable:!0,writable:!0,value:o}):t[r]=o,l=(t,e)=>{for(var r in e||(e={}))n.call(e,r)&&s(t,r,e[r]);if(a)for(var r of a(e))i.call(e,r)&&s(t,r,e[r]);return t},c=(t,e)=>r(t,o(e)),d=(t,e,r)=>new Promise((o,a)=>{var n=t=>{try{s(r.next(t))}catch(e){a(e)}},i=t=>{try{s(r.throw(t))}catch(e){a(e)}},s=t=>t.done?o(t.value):Promise.resolve(t.value).then(n,i);s((r=r.apply(t,e)).next())});function p(t){return t&&t.__esModule&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t}var h,u={exports:{}};var m=(h||(h=1,u.exports=function(){function t(e){return(t="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(e)}var e=Object.prototype.toString,r=function(r){if(void 0===r)return"undefined";if(null===r)return"null";var a=t(r);if("boolean"===a)return"boolean";if("string"===a)return"string";if("number"===a)return"number";if("symbol"===a)return"symbol";if("function"===a)return"GeneratorFunction"===o(r)?"generatorfunction":"function";if(function(t){return Array.isArray?Array.isArray(t):t instanceof Array}(r))return"array";if(function(t){return!(!t.constructor||"function"!=typeof t.constructor.isBuffer)&&t.constructor.isBuffer(t)}(r))return"buffer";if(function(t){try{if("number"==typeof t.length&&"function"==typeof t.callee)return!0}catch(e){if(-1!==e.message.indexOf("callee"))return!0}return!1}(r))return"arguments";if(function(t){return t instanceof Date||"function"==typeof t.toDateString&&"function"==typeof t.getDate&&"function"==typeof t.setDate}(r))return"date";if(function(t){return t instanceof Error||"string"==typeof t.message&&t.constructor&&"number"==typeof t.constructor.stackTraceLimit}(r))return"error";if(function(t){return t instanceof RegExp||"string"==typeof t.flags&&"boolean"==typeof t.ignoreCase&&"boolean"==typeof t.multiline&&"boolean"==typeof t.global}(r))return"regexp";switch(o(r)){case"Symbol":return"symbol";case"Promise":return"promise";case"WeakMap":return"weakmap";case"WeakSet":return"weakset";case"Map":return"map";case"Set":return"set";case"Int8Array":return"int8array";case"Uint8Array":return"uint8array";case"Uint8ClampedArray":return"uint8clampedarray";case"Int16Array":return"int16array";case"Uint16Array":return"uint16array";case"Int32Array":return"int32array";case"Uint32Array":return"uint32array";case"Float32Array":return"float32array";case"Float64Array":return"float64array"}if(function(t){return"function"==typeof t.throw&&"function"==typeof t.return&&"function"==typeof t.next}(r))return"generator";switch(a=e.call(r)){case"[object Object]":return"object";case"[object Map Iterator]":return"mapiterator";case"[object Set Iterator]":return"setiterator";case"[object String Iterator]":return"stringiterator";case"[object Array Iterator]":return"arrayiterator"}return a.slice(8,-1).toLowerCase().replace(/\s/g,"")};function o(t){return t.constructor?t.constructor.name:null}function a(t,e){var o=2<arguments.length&&void 0!==arguments[2]?arguments[2]:["option"];return n(t,e,o),i(t,e,o),function(t,e,o){var s=r(e),l=r(t);if("object"===s){if("object"!==l)throw new Error("[Type Error]: '".concat(o.join("."),"' require 'object' type, but got '").concat(l,"'"));Object.keys(e).forEach(function(r){var s=t[r],l=e[r],c=o.slice();c.push(r),n(s,l,c),i(s,l,c),a(s,l,c)})}if("array"===s){if("array"!==l)throw new Error("[Type Error]: '".concat(o.join("."),"' require 'array' type, but got '").concat(l,"'"));t.forEach(function(r,s){var l=t[s],c=e[s]||e[0],d=o.slice();d.push(s),n(l,c,d),i(l,c,d),a(l,c,d)})}}(t,e,o),t}function n(t,e,o){if("string"===r(e)){var a=r(t);if("?"===e[0]&&(e=e.slice(1)+"|undefined"),!(-1<e.indexOf("|")?e.split("|").map(function(t){return t.toLowerCase().trim()}).filter(Boolean).some(function(t){return a===t}):e.toLowerCase().trim()===a))throw new Error("[Type Error]: '".concat(o.join("."),"' require '").concat(e,"' type, but got '").concat(a,"'"))}}function i(t,e,o){if("function"===r(e)){var a=e(t,r(t),o);if(!0!==a){var n=r(a);throw"string"===n?new Error(a):"error"===n?a:new Error("[Validator Error]: The scheme for '".concat(o.join("."),"' validator require return true, but got '").concat(a,"'"))}}}return a.kindOf=r,a}()),u.exports);const f=p(m),g="5.4.1",v={properties:["audioTracks","autoplay","buffered","controller","controls","crossOrigin","currentSrc","currentTime","defaultMuted","defaultPlaybackRate","duration","ended","error","loop","meLine truncated
!function(t,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"==typeof define&&define.amd?(t.Artplayer=e(),define(function(){return t.Artplayer})):(t="undefined"!==typeof globalThis?globalThis:t||self).Artplayer=e()}(this,function(){"use strict";var t,e=Object.defineProperty,r=Object.defineProperties,o=Object.getOwnPropertyDescriptors,a=Object.getOwnPropertySymbols,n=Object.prototype.hasOwnProperty,i=Object.prototype.propertyIsEnumerable,s=(t,r,o)=>r in t?e(t,r,{enumerable:!0,configurable:!0,writable:!0,value:o}):t[r]=o,l=(t,e)=>{for(var r in e||(e={}))n.call(e,r)&&s(t,r,e[r]);if(a)for(var r of a(e))i.call(e,r)&&s(t,r,e[r]);return t},c=(t,e)=>r(t,o(e)),d=(t,e,r)=>new Promise((o,a)=>{var n=t=>{try{s(r.next(t))}catch(e){a(e)}},i=t=>{try{s(r.throw(t))}catch(e){a(e)}},s=t=>t.done?o(t.value):Promise.resolve(t.value).then(n,i);s((r=r.apply(t,e)).next())});function p(t){return t&&t.__esModule&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t}var h,u={exports:{}};var m=(h||(h=1,u.exports=function(){function t(e){return(t="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(e)}var e=Object.prototype.toString,r=function(r){if(void 0===r)return"undefined";if(null===r)return"null";var a=t(r);if("boolean"===a)return"boolean";if("string"===a)return"string";if("number"===a)return"number";if("symbol"===a)return"symbol";if("function"===a)return"GeneratorFunction"===o(r)?"generatorfunction":"function";if(function(t){return Array.isArray?Array.isArray(t):t instanceof Array}(r))return"array";if(function(t){return!(!t.constructor||"function"!=typeof t.constructor.isBuffer)&&t.constructor.isBuffer(t)}(r))return"buffer";if(function(t){try{if("number"==typeof t.length&&"function"==typeof t.callee)return!0}catch(e){if(-1!==e.message.indexOf("callee"))return!0}return!1}(r))return"arguments";if(function(t){return t instanceof Date||"function"==typeof t.toDateString&&"function"==typeof t.getDate&&"function"==typeof t.setDate}(r))return"date";if(function(t){return t instanceof Error||"string"==typeof t.message&&t.constructor&&"number"==typeof t.constructor.stackTraceLimit}(r))return"error";if(function(t){return t instanceof RegExp||"string"==typeof t.flags&&"boolean"==typeof t.ignoreCase&&"boolean"==typeof t.multiline&&"boolean"==typeof t.global}(r))return"regexp";switch(o(r)){case"Symbol":return"symbol";case"Promise":return"promise";case"WeakMap":return"weakmap";case"WeakSet":return"weakset";case"Map":return"map";case"Set":return"set";case"Int8Array":return"int8array";case"Uint8Array":return"uint8array";case"Uint8ClampedArray":return"uint8clampedarray";case"Int16Array":return"int16array";case"Uint16Array":return"uint16array";case"Int32Array":return"int32array";case"Uint32Array":return"uint32array";case"Float32Array":return"float32array";case"Float64Array":return"float64array"}if(function(t){return"function"==typeof t.throw&&"function"==typeof t.return&&"function"==typeof t.next}(r))return"generator";switch(a=e.call(r)){case"[object Object]":return"object";case"[object Map Iterator]":return"mapiterator";case"[object Set Iterator]":return"setiterator";case"[object String Iterator]":return"stringiterator";case"[object Array Iterator]":return"arrayiterator"}return a.slice(8,-1).toLowerCase().replace(/\s/g,"")};function o(t){return t.constructor?t.constructor.name:null}function a(t,e){var o=2<arguments.length&&void 0!==arguments[2]?arguments[2]:["option"];return n(t,e,o),i(t,e,o),function(t,e,o){var s=r(e),l=r(t);if("object"===s){if("object"!==l)throw new Error("[Type Error]: '".concat(o.join("."),"' require 'object' type, but got '").concat(l,"'"));Object.keys(e).forEach(function(r){var s=t[r],l=e[r],c=o.slice();c.push(r),n(s,l,c),i(s,l,c),a(s,l,c)})}if("array"===s){if("array"!==l)throw new Error("[Type Error]: '".concat(o.join("."),"' require 'array' type, but got '").concat(l,"'"));t.forEach(function(r,s){var l=t[s],c=e[s]||e[0],d=o.slice();d.push(s),n(l,c,d),i(l,c,d),a(l,c,d)})}}(t,e,o),t}function n(t,e,o){if("string"===r(e)){var a=r(t);if("?"===e[0]&&(e=e.slice(1)+"|undefined"),!(-1<e.indexOf("|")?e.split("|").map(function(t){return t.toLowerCase().trim()}).filter(Boolean).some(function(t){return a===t}):e.toLowerCase().trim()===a))throw new Error("[Type Error]: '".concat(o.join("."),"' require '").concat(e,"' type, but got '").concat(a,"'"))}}function i(t,e,o){if("function"===r(e)){var a=e(t,r(t),o);if(!0!==a){var n=r(a);throw"string"===n?new Error(a):"error"===n?a:new Error("[Validator Error]: The scheme for '".concat(o.join("."),"' validator require return true, but got '").concat(a,"'"))}}}return a.kindOf=r,a}()),u.exports);const f=p(m),g="5.4.1",v={properties:["audioTracks","autoplay","buffered","controller","controls","crossOrigin","currentSrc","currentTime","defaultMuted","defaultPlaybackRate","duration","ended","error","loop","meLine truncated
+80 -74
View File
@@ -735,7 +735,7 @@ const scheme = {
position: (value, _, paths) => {
const position = ["top", "left", "right"];
return errorHandle(
position.includes(value),
typeof value === "string" && position.includes(value),
`${paths.join(".")} only accept ${position.toString()} as parameters`
);
}
@@ -2533,6 +2533,83 @@ class Notice {
return $player.classList.contains("art-notice-show");
}
}
function createDefaults() {
return {
id: "",
container: "#artplayer",
url: "",
poster: "",
type: "",
theme: "#f00",
volume: 0.7,
isLive: false,
muted: false,
autoplay: false,
autoSize: false,
autoMini: false,
loop: false,
flip: false,
playbackRate: false,
aspectRatio: false,
screenshot: false,
setting: false,
hotkey: true,
pip: false,
mutex: true,
backdrop: true,
fullscreen: false,
fullscreenWeb: false,
subtitleOffset: false,
miniProgressBar: false,
useSSR: false,
playsInline: true,
lock: false,
gesture: true,
fastForward: false,
autoPlayback: false,
autoOrientation: false,
airplay: false,
proxy: void 0,
layers: [],
contextmenu: [],
controls: [],
settings: [],
quality: [],
highlight: [],
plugins: [],
thumbnails: {
url: "",
number: 60,
column: 10,
width: 0,
height: 0,
scale: 1
},
subtitle: {
url: "",
type: "",
style: {},
name: "",
escape: true,
encoding: "utf-8",
onVttLoad: (vtt) => vtt
},
moreVideoAttr: {
controls: false,
preload: isSafari ? "auto" : "metadata"
},
i18n: {},
icons: {},
cssVar: {},
customType: {},
lang: navigator?.language.toLowerCase()
};
}
function resolveOption(input, defaults) {
const merged = mergeDeep(defaults, input);
merged.container = input.container;
return validator(merged, scheme);
}
function airplayMix(art) {
const {
i18n,
@@ -5085,9 +5162,7 @@ class Artplayer extends Emitter {
throw new Error("Artplayer can only be used in the browser environment");
}
this.id = ++id;
const mergeOption = mergeDeep(Artplayer.option, option);
mergeOption.container = option.container;
this.option = validator(mergeOption, scheme);
this.option = resolveOption(option, Artplayer.option);
this.isLock = false;
this.isReady = false;
this.isFocus = false;
@@ -5164,76 +5239,7 @@ class Artplayer extends Emitter {
return Template.html;
}
static get option() {
return {
id: "",
container: "#artplayer",
url: "",
poster: "",
type: "",
theme: "#f00",
volume: 0.7,
isLive: false,
muted: false,
autoplay: false,
autoSize: false,
autoMini: false,
loop: false,
flip: false,
playbackRate: false,
aspectRatio: false,
screenshot: false,
setting: false,
hotkey: true,
pip: false,
mutex: true,
backdrop: true,
fullscreen: false,
fullscreenWeb: false,
subtitleOffset: false,
miniProgressBar: false,
useSSR: false,
playsInline: true,
lock: false,
gesture: true,
fastForward: false,
autoPlayback: false,
autoOrientation: false,
airplay: false,
proxy: void 0,
layers: [],
contextmenu: [],
controls: [],
settings: [],
quality: [],
highlight: [],
plugins: [],
thumbnails: {
url: "",
number: 60,
column: 10,
width: 0,
height: 0,
scale: 1
},
subtitle: {
url: "",
type: "",
style: {},
name: "",
escape: true,
encoding: "utf-8",
onVttLoad: (vtt) => vtt
},
moreVideoAttr: {
controls: false,
preload: isSafari ? "auto" : "metadata"
},
i18n: {},
icons: {},
cssVar: {},
customType: {},
lang: navigator?.language.toLowerCase()
};
return createDefaults();
}
get proxy() {
return this.events.proxy;
+4 -74
View File
@@ -13,6 +13,8 @@ import { beginLifecycle, destroyInstance, finishLifecycle, getScope } from './li
import Loading from './loading'
import Mask from './mask'
import Notice from './notice'
import createDefaults from './option/defaults'
import resolveOption from './option/resolve'
import Player from './player'
import Plugins from './plugins'
import scheme from './scheme'
@@ -36,10 +38,7 @@ export default class Artplayer extends Emitter {
this.id = ++id
const mergeOption = utils.mergeDeep(Artplayer.option, option)
mergeOption.container = option.container
this.option = validator(mergeOption, scheme)
this.option = resolveOption(option, Artplayer.option)
this.isLock = false
this.isReady = false
@@ -135,76 +134,7 @@ export default class Artplayer extends Emitter {
}
static get option() {
return {
id: '',
container: '#artplayer',
url: '',
poster: '',
type: '',
theme: '#f00',
volume: 0.7,
isLive: false,
muted: false,
autoplay: false,
autoSize: false,
autoMini: false,
loop: false,
flip: false,
playbackRate: false,
aspectRatio: false,
screenshot: false,
setting: false,
hotkey: true,
pip: false,
mutex: true,
backdrop: true,
fullscreen: false,
fullscreenWeb: false,
subtitleOffset: false,
miniProgressBar: false,
useSSR: false,
playsInline: true,
lock: false,
gesture: true,
fastForward: false,
autoPlayback: false,
autoOrientation: false,
airplay: false,
proxy: undefined,
layers: [],
contextmenu: [],
controls: [],
settings: [],
quality: [],
highlight: [],
plugins: [],
thumbnails: {
url: '',
number: 60,
column: 10,
width: 0,
height: 0,
scale: 1,
},
subtitle: {
url: '',
type: '',
style: {},
name: '',
escape: true,
encoding: 'utf-8',
onVttLoad: vtt => vtt,
},
moreVideoAttr: {
controls: false,
preload: utils.isSafari ? 'auto' : 'metadata',
},
i18n: {},
icons: {},
cssVar: {},
customType: {},
lang: navigator?.language.toLowerCase(),
}
return createDefaults()
}
get proxy() {
+75
View File
@@ -0,0 +1,75 @@
import type { ResolvedOption } from './types'
import { isSafari } from '../utils/compatibility'
export default function createDefaults(): ResolvedOption {
return {
id: '',
container: '#artplayer',
url: '',
poster: '',
type: '',
theme: '#f00',
volume: 0.7,
isLive: false,
muted: false,
autoplay: false,
autoSize: false,
autoMini: false,
loop: false,
flip: false,
playbackRate: false,
aspectRatio: false,
screenshot: false,
setting: false,
hotkey: true,
pip: false,
mutex: true,
backdrop: true,
fullscreen: false,
fullscreenWeb: false,
subtitleOffset: false,
miniProgressBar: false,
useSSR: false,
playsInline: true,
lock: false,
gesture: true,
fastForward: false,
autoPlayback: false,
autoOrientation: false,
airplay: false,
proxy: undefined,
layers: [],
contextmenu: [],
controls: [],
settings: [],
quality: [],
highlight: [],
plugins: [],
thumbnails: {
url: '',
number: 60,
column: 10,
width: 0,
height: 0,
scale: 1,
},
subtitle: {
url: '',
type: '',
style: {},
name: '',
escape: true,
encoding: 'utf-8',
onVttLoad: vtt => vtt,
},
moreVideoAttr: {
controls: false,
preload: isSafari ? 'auto' : 'metadata',
},
i18n: {},
icons: {},
cssVar: {},
customType: {},
lang: navigator?.language.toLowerCase(),
}
}
+11
View File
@@ -0,0 +1,11 @@
import type { Option, ResolvedInput, ResolvedOption } from './types'
import validator from 'option-validator'
import scheme from '../scheme'
import { mergeDeep } from '../utils/property'
export default function resolveOption<Input extends Option>(input: Input, defaults: ResolvedOption): ResolvedInput<Input> {
const merged = mergeDeep(defaults, input)
merged.container = input.container
// Keep the existing runtime validator. It checks the known schema, not extension data.
return validator(merged, scheme) as ResolvedInput<Input>
}
+13
View File
@@ -0,0 +1,13 @@
import type { Option, Thumbnails } from '../../types/option'
import type { Subtitle } from '../../types/subtitle'
export type { Option } from '../../types/option'
// A successfully merged typed input, not a general guard for arbitrary JS objects.
export interface ResolvedOption extends Omit<Required<Option>, 'proxy' | 'subtitle' | 'thumbnails'> {
proxy: Option['proxy']
subtitle: Required<Subtitle>
thumbnails: Required<Thumbnails>
}
export type ResolvedInput<Input extends Option> = ResolvedOption & Omit<Input, keyof Option>
@@ -1,3 +1,5 @@
import type { Scheme, ValidatorPath } from 'option-validator'
import type { Option } from '../option/types'
import { errorHandle } from '../utils'
const a = 'array'
@@ -7,7 +9,7 @@ const n = 'number'
const o = 'object'
const f = 'function'
function validElement(value, type, paths) {
function validElement(value: unknown, type: string, paths: ValidatorPath) {
return errorHandle(
type === s || type === n || value instanceof Element,
`${paths.join('.')} require '${s}' or 'Element' type`,
@@ -33,7 +35,7 @@ export const ComponentOption = {
onChange: `?${f}`,
}
export default {
const scheme = {
id: s,
container: validElement,
url: s,
@@ -77,10 +79,10 @@ export default {
controls: [
{
...ComponentOption,
position: (value, _, paths) => {
position: (value: unknown, _: string, paths: ValidatorPath) => {
const position = ['top', 'left', 'right']
return errorHandle(
position.includes(value),
typeof value === 'string' && position.includes(value),
`${paths.join('.')} only accept ${position.toString()} as parameters`,
)
},
@@ -121,4 +123,6 @@ export default {
icons: o,
cssVar: o,
customType: o,
}
} satisfies Record<keyof Option, Scheme>
export default scheme
+11 -1
View File
@@ -1,4 +1,14 @@
{
"extends": "../../tsconfig.base.json",
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.mts", "src/**/*.cts", "types/**/*.d.ts", "types/**/*.d.mts", "types/**/*.d.cts", "../../types/assets.d.ts"]
"include": [
"src/**/*.ts",
"src/**/*.tsx",
"src/**/*.mts",
"src/**/*.cts",
"types/**/*.d.ts",
"types/**/*.d.mts",
"types/**/*.d.cts",
"../../types/assets.d.ts",
"../../types/option-validator.d.ts"
]
}
+10
View File
@@ -26,3 +26,13 @@ When changing types, run `yarn typecheck`, `yarn test:baseline` and
chapter/legacy factories, language data, invalid inputs, NodeNext/Bundler and old
TS 4.3.5 consumers. Run `yarn build:ts` to refresh editor declarations. Full editor
behavior and other core declaration/runtime mismatches have CORE-07/SITE tasks.
## Configuration migration boundary
CORE-05 keeps the public Option unchanged. Internal src/option/types.ts distinguishes
typed input from defaults-filled ResolvedOption; it is not a new public export. The
existing public url requirement and exclusion of numeric control HTML differ from
verified JS behavior (BASE-TYPE-06, CORE-07). test/types/options-source.ts records the
current rejection; replace those two negative cases with positive consumer coverage
when expanding the public declarations. Do not change runtime validation to match a
narrower historical declaration. Other optional fields and callback signatures remain
subject to the existing public compatibility contract.
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,53 @@
# CORE-05:输入配置与内部配置
## 文件与数据流
核心入口直接调用 option/resolve.ts,默认值移入 option/defaults.ts,scheme/index.js 迁移为
scheme/index.ts。option/types.ts 区分公开 Option 输入、补齐默认字段的
ResolvedOption 和保留应用扩展字段的 ResolvedInput。主入口不再内嵌默认值列表和合并步骤,
但仍是 JS 兼容门面,完整 TS 收敛属于 CORE-20。
数据流保持 Artplayer.option → mergeDeep(defaults, input) → 恢复 input.container 引用 →
原 option-validator 校验 → 后续生命周期。每次默认 getter 都生成独立对象、数组和 onVttLoad
回调;Safari preload、读取 navigator.language 的时点、字段次序与默认值不变。
实例 ID 在校验前递增,非法 JS 配置仍在挂载 DOM/调用 proxy 前抛错。
ResolvedOption 只用于符合已声明输入合同且成功合并的配置,不宣称是任意 unknown 的完整安全
类型守卫。顶层及 subtitle/thumbnails 默认字段成为必需,proxy 明确保留 undefined;集合成员、
回调和应用扩展沿用输入类型,不引入新的全局 any 索引签名。动态 merge 与该合同之间保留一处
有注释的结果断言,未知 JS/扩展数据仍须在具体消费者中核实。
scheme 的所有键以 Record<keyof Option, Scheme> 检查,保留原可变对象、ComponentOption
共享引用和错误消息。根 types/option-validator.d.ts 根据现有 2.0.6 实现补充 identity validator、
schema 回调路径、kindOf 类型;没有更换依赖、包装运行时或改变 Artplayer.validator 的引用。
根和核心 tsconfig 引入该声明,lint 同时覆盖根声明文件;test:unit 加入配置对照测试。
## 保留行为与声明缺口
API-01/03/04/07/09/11:容器 identity、输入 getter 的顺序/次数、继承容器恢复、unknown 自有
字段、数组元素引用、嵌套对象合并和原始 callback 引用均保留。显式 undefined 仍可能被原校验
拒绝,不改成悄悄填默认值;错误 path、首次错误及抛出类型与真实发布版对照。
新增 BASE-TYPE-06,交 CORE-07:真实旧/新 JS 均允许省略 url,并可将数字用作控件 HTML,
当前公开 Option/ComponentOption 却拒绝这两种形式。真实浏览器和类型反例分别证明差异;
公开声明本次保持,后续将这些类型负例转为正向消费者检查,不能修改运行时去迎合旧声明。
更宽松的底层 validator 接受某个值也不表示之后的 DOM/媒体模块一定接受它,阶段边界继续保留。
## 验证
- 八项 Node 配置测试:真实发布/当前默认对象隔离、完整默认值/字段顺序、合并所有权、getter
读取、继承容器、九组非法字段输入的错误对照。
- 严格类型包含五项正常类型约束反例和两项已登记的公开声明缺口反例;回调 receiver、已补齐
字段和应用扩展字段有正例。yarn ci:check 共 107 项:79 单元、4 工程、24 冻结基线;21 个
自有 TS 源文件进入严格检查。
- 实际安装 UMD 候选三浏览器完整 126 项通过,其中 18 项配置场景验证正常/错误/声明差异,
包含真实媒体 customType 与 ready 回调;其他播放、插件及生命周期回归同时通过。
- 实际安装 legacy 候选三浏览器配置与生命周期共 48 项通过,无源码回退、重试或跳过。
- yarn build artplayer / yarn build:i18n 生成核心 dist 与 docs/compiled;严格 tarball 仓库外
消费通过 27 项运行时、五组类型零诊断。包内架构/类型说明更新后重新打包,最终 JS 指纹与
UMD/legacy 浏览器候选匹配。
详见 [验证报告](../baselines/options-validation.json)、[核心架构](../../packages/artplayer/ARCHITECTURE.md)
和 [类型维护说明](../../packages/artplayer/types/README.md)。没有修改冻结发布数据、升级依赖、
更改版本或推送/发布。本任务独立提交,回退提交可恢复入口、模块、类型配置及生成产物。
下一项 CORE-06 建立内部媒体与宿主类型,随后 CORE-07 逐项协调公开声明差异。
+3 -2
View File
@@ -4,7 +4,7 @@
基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 214 项,范围 22 个包及工作区/示例。
状态:todo 174 / doing 0 / blocked 0 / done 40 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
状态:todo 173 / doing 0 / blocked 0 / done 41 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
前置依赖是启动条件;验收是完成条件。任务可以继续拆分,但不能复用或悄悄删除旧 ID。
@@ -108,7 +108,7 @@
| CORE-02 | artplayer<br>迁移内部 Emitter | CORE-01, BASE-03 | 带类型事件映射的原 Emitter 实现 | ctx、once/off、重入/异常和链式返回保持 | H | done |
| CORE-03 | artplayer<br>建立内部资源作用域 | CORE-02 | 监听/订阅/定时器/RAF/请求/URL 的资源登记 | 实例与操作作用域分离,单项清理失败不阻断其他资源 | H | done |
| CORE-04 | artplayer<br>修复初始化与销毁生命周期 | CORE-03, ENG-05, BASE-07 | 构造失败回收、重复/重入 destroy、instances 修复 | 事件顺序、removeHtml、多实例和销毁中异步有真实回归;处理 baselines/lifecycle-coverage.md 的 BASE-LIFE-04/05 并增加候选正向回归;协调 BASE-PERF-01 销毁后定时器与回调归属 | H | done |
| CORE-05 | artplayer<br>区分输入配置和内部配置 | CORE-01, ENG-04 | Option/ResolvedOption、默认值与校验整理 | 配置合并/错误时机不变,JS 输入仍运行时校验 | M | todo |
| 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 | todo |
| 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 | todo |
| CORE-08 | artplayer<br>整理插件管理器与扩展类型 | CORE-03, CORE-07 | 注册流程、同步/异步结果、插件/事件扩展接口 | 旧插件同步可见性、重名与命名、销毁中异步插件保持明确语义;处理 baselines/lifecycle-coverage.md 的 BASE-LIFE-03 并增加候选正向回归 | H | todo |
@@ -439,6 +439,7 @@
- CORE-02: [记录](changes/2026-09-11-CORE-02-typed-emitter.md) [记录](baselines/emitter-validation.json)
- CORE-03: [记录](changes/2026-09-11-CORE-03-resource-scope.md) [记录](baselines/resource-scope-validation.json)
- CORE-04: [记录](changes/2026-09-11-CORE-04-instance-lifecycle.md) [记录](baselines/instance-lifecycle-validation.json)
- CORE-05: [记录](changes/2026-09-11-CORE-05-resolved-options.md) [记录](baselines/options-validation.json)
- PKG-CHAPTER-01: [记录](changes/2026-09-10-PKG-CHAPTER-01-contract.md) [记录](baselines/chapter-contract.md)
- PKG-CHAPTER-02: [记录](changes/2026-09-10-PKG-CHAPTER-02-tests.md) [记录](baselines/chapter-validation.json)
- PKG-CHAPTER-03: [记录](changes/2026-09-10-PKG-CHAPTER-03-typescript-modules.md) [记录](baselines/chapter-migration-validation.json)
+4
View File
@@ -1,5 +1,9 @@
# 进度与证据
## 当前实施:CORE-05 配置与默认值 TS 拆分完成
核心入口接入独立默认值/解析模块,scheme 迁移 TS,区分 Option 与 ResolvedOption 并保留应用扩展类型。107 项 Node/基线、安装 UMD 三浏览器 126 项及 legacy 配置/生命周期 48 项通过;严格打包 27 项运行时、五组类型零诊断。见 [交付](changes/2026-09-11-CORE-05-resolved-options.md)。新登记 BASE-TYPE-06(省略 URL/数字控件 HTML 的声明缺口)交 CORE-07。当前 214 项,41 完成、173 待办;核心十六个 TS 文件、chapter 五个。下一项 CORE-06 内部媒体与宿主类型。
## 当前实施:CORE-04 构造/销毁生命周期完成
核心入口接入实例作用域,修复重复/重入销毁、失败构造回滚和 pending resize;正常 destroy 回调仍可挂载替代实例。99 项 Node/基线、安装 UMD 三浏览器 108 项及 legacy 三浏览器 30 项通过;严格打包 27 项运行时、五组类型零诊断。见 [交付](changes/2026-09-11-CORE-04-instance-lifecycle.md)。关闭 BASE-LIFE-04/05 与 BASE-PERF-01,字幕/异步插件等后续任务继续开放。当前 214 项,40 完成、174 待办;核心十二个 TS 文件、chapter 五个,主入口仍为 JS 兼容门面。下一项 CORE-05 输入与内部配置类型。
+1
View File
@@ -49,3 +49,4 @@
| BASE-TYPE-05 | open / 源码/产物事实 | Public Utils declarations omit exports and misstate timer/descriptor signatures | CORE-07 |
| EVENT-NAME-01 | resolved / 已复现 | Emitter event names collide with Object.prototype | CORE-02 |
| EVENT-ONCE-01 | resolved / 已复现 | Nested dispatch snapshots invoke the same once registration twice | CORE-02 |
| BASE-TYPE-06 | open / 已复现 | Public Option requires url and excludes numeric control HTML accepted by existing JS | CORE-07 |
+17
View File
@@ -782,6 +782,23 @@
"test/browser/emitter.spec.js"
],
"resolutionRationale": "The wrapper records consumption before unsubscribe/invocation; ordinary dispatch snapshot semantics are retained."
},
{
"id": "BASE-TYPE-06",
"title": "Public Option requires url and excludes numeric control HTML accepted by existing JS",
"confirmation": "reproduced",
"status": "open",
"owners": [
"CORE-07"
],
"evidence": [
"test/browser/options.spec.js",
"test/types/options-source.ts",
"refactor/baselines/options-validation.json",
"refactor/changes/2026-09-11-CORE-05-resolved-options.md"
],
"compatibleResolution": "Expand public declarations for the verified legal JS forms; preserve existing typed inputs and runtime defaults.",
"closureCriteria": "Replace the two historical negative fixtures with positive public consumers and verify old/new TypeScript plus installed package modes."
}
]
}
+5 -2
View File
@@ -830,11 +830,14 @@
"CORE-01",
"ENG-04"
],
"status": "todo",
"status": "done",
"risk": "M",
"deliverable": "Option/ResolvedOption、默认值与校验整理",
"acceptance": "配置合并/错误时机不变,JS 输入仍运行时校验",
"evidence": []
"evidence": [
"changes/2026-09-11-CORE-05-resolved-options.md",
"baselines/options-validation.json"
]
},
{
"id": "CORE-06",
+93
View File
@@ -0,0 +1,93 @@
import { expect, test } from './fixtures.js'
for (const core of ['published', 'candidate']) {
test(`${core}: historical JS option forms remain accepted despite declaration gaps`, async ({ page }) => {
await page.goto(`/test/player.html?core=${core}&chapter=published`)
const result = await page.evaluate(() => {
const container = document.createElement('div')
document.body.append(container)
const art = new window.Artplayer({ container, controls: [{ name: 'numeric', html: 42, position: 'left' }] })
const result = { url: art.option.url, html: art.controls.numeric.textContent }
art.destroy()
return result
})
expect(result).toEqual({ url: '', html: '42' })
})
test(`${core}: configuration errors occur before mounting and preserve allocation timing`, async ({ page }) => {
await page.goto(`/test/player.html?core=${core}&chapter=published`)
const result = await page.evaluate(() => {
const { Artplayer } = window
const container = document.createElement('div')
container.innerHTML = '<span>before</span>'
document.body.append(container)
let proxyCalls = 0
let error
try {
void new Artplayer({ container, url: '', volume: 'loud', proxy() {
proxyCalls++
return document.createElement('video')
} })
}
catch (actual) { error = { name: actual.name, message: actual.message } }
const untouched = {
html: container.innerHTML,
id: container.getAttribute('data-art-id'),
proxyCalls,
count: Artplayer.instances.length,
}
const art = new Artplayer({ container, url: '' })
const allocatedId = art.id
art.destroy()
return { error, untouched, allocatedId }
})
expect(result).toEqual({ error: { name: 'Error', message: '[Type Error]: \'option.volume\' require \'number\' type, but got \'string\'' }, untouched: { html: '<span>before</span>', id: null, proxyCalls: 0, count: 0 }, allocatedId: 2 })
})
test(`${core}: container identity, partial defaults, custom fields and normal callbacks`, async ({ page }) => {
await page.goto(`/test/player.html?core=${core}&chapter=published`)
const result = await page.evaluate(async () => {
const { Artplayer } = window
const container = document.createElement('div')
document.body.append(container)
const extension = { custom: true }
let customCalls = 0
let callbackShape = false
let readyShape = false
const customType = function (video, url, art) {
customCalls++
callbackShape = this === art && video === art.video
video.src = url
}
const input = { container, url: '/test/pattern.mp4', subtitle: { style: { color: 'red' } }, customType: { mp4: customType }, extension, muted: true }
let art
await new Promise((resolve) => {
art = new Artplayer(input, function (player) {
readyShape = this === player && player === art
resolve()
})
})
const defaults = Artplayer.option
const result = {
sameContainer: art.option.container === container,
sameExtension: art.option.extension === extension,
customCallback: art.option.customType.mp4 === customType,
customCalls,
callbackShape,
readyShape,
encoding: art.option.subtitle.encoding,
color: art.option.subtitle.style.color,
columns: art.option.thumbnails.column,
inputSubtitleKeys: Object.keys(input.subtitle),
freshDefaults: defaults !== Artplayer.option,
preload: Artplayer.option.moreVideoAttr.preload,
expectedPreload: Artplayer.utils.isSafari ? 'auto' : 'metadata',
}
art.destroy()
return result
})
const { preload, expectedPreload, ...behavior } = result
expect(preload).toBe(expectedPreload)
expect(behavior).toEqual({ sameContainer: true, sameExtension: true, customCallback: true, customCalls: 1, callbackShape: true, readyShape: true, encoding: 'utf-8', color: 'red', columns: 10, inputSubtitleKeys: ['style'], freshDefaults: true })
})
}
+117
View File
@@ -0,0 +1,117 @@
import assert from 'node:assert/strict'
// eslint-disable-next-line test/no-import-node-test -- Use the repository Node test runner.
import { test } from 'node:test'
import { loadModules, loadPackage, loadPublishedCore } from './helpers/load.js'
const { resolveOption } = await loadModules({ resolveOption: 'packages/artplayer/src/option/resolve' })
const published = await loadPublishedCore({ navigator: { language: navigator.language, userAgent: navigator.userAgent } })
const current = {
name: 'workspace',
Artplayer: (await loadPackage('artplayer')).default,
}
function oldResolve(input, defaults) {
const merged = published.Artplayer.utils.mergeDeep(defaults, input)
merged.container = input.container
return published.Artplayer.validator(merged, published.Artplayer.scheme)
}
for (const { name, Artplayer } of [published, current]) {
test(`option defaults are fresh, keep all fields and callback shape: ${name}`, () => {
const first = Artplayer.option
const second = Artplayer.option
assert.notEqual(first, second)
for (const key of ['plugins', 'layers', 'controls', 'settings', 'quality', 'highlight', 'contextmenu', 'thumbnails', 'subtitle', 'moreVideoAttr', 'icons', 'i18n', 'customType', 'cssVar'])
assert.notEqual(first[key], second[key], key)
assert.notEqual(first.subtitle.style, second.subtitle.style)
assert.notEqual(first.subtitle.onVttLoad, second.subtitle.onVttLoad)
assert.equal(first.subtitle.onVttLoad(' text '), ' text ')
assert.equal(first.subtitle.onVttLoad.length, 1)
assert.equal(first.subtitle.onVttLoad.name, 'onVttLoad')
assert(Object.hasOwn(first, 'proxy'))
assert.equal(first.proxy, undefined)
assert.equal(first.lang, navigator.language.toLowerCase())
assert.equal(Artplayer.scheme.layers[0], Artplayer.scheme.settings[0])
assert.equal(Artplayer.scheme.layers[0], Artplayer.scheme.contextmenu[0])
})
}
test('default values, keys and nested order match the published getter', () => {
assert.equal(JSON.stringify(current.Artplayer.option), JSON.stringify(published.Artplayer.option))
assert.deepEqual(Object.keys(current.Artplayer.option), Object.keys(published.Artplayer.option))
})
for (const [name, resolve, Artplayer] of [['published', oldResolve, published.Artplayer], ['workspace', resolveOption, current.Artplayer]]) {
test(`resolved options preserve unknown fields, merge ownership and collection item identity: ${name}`, () => {
const callback = () => {}
const item = { html: 'Quality', url: 'a.mp4' }
const extension = { value: 1 }
const input = { container: '#player', url: 'video.mp4', subtitle: { style: { color: 'red' } }, thumbnails: { url: 'sprite.jpg' }, quality: [item], customType: { mp4: callback }, extension }
const defaults = Artplayer.option
const option = resolve(input, defaults)
assert.equal(option.container, input.container)
assert.notEqual(option, input)
assert.notEqual(option.subtitle, input.subtitle)
assert.notEqual(option.subtitle.style, input.subtitle.style)
assert.equal(option.subtitle.style.color, 'red')
assert.equal(option.subtitle.encoding, 'utf-8')
assert.equal(option.thumbnails.column, 10)
assert.notEqual(option.quality, input.quality)
assert.equal(option.quality[0], item)
assert.equal(option.customType.mp4, callback)
assert.equal(option.extension, extension)
assert.deepEqual(Object.keys(input.subtitle), ['style'])
assert.deepEqual(Object.keys(defaults.subtitle.style), [])
})
test(`getter reads and inherited container restoration retain their order: ${name}`, () => {
const reads = []
const input = {
get container() {
reads.push('container')
return '#player'
},
get url() {
reads.push('url')
return 'video.mp4'
},
get volume() {
reads.push('volume')
return 0.5
},
}
const option = resolve(input, Artplayer.option)
assert.deepEqual(reads, ['container', 'url', 'volume', 'container'])
assert.equal(option.volume, 0.5)
const inherited = Object.create({ container: '#inherited', url: 'ignored.mp4' })
inherited.customField = 'own'
const result = resolve(inherited, Artplayer.option)
assert.equal(result.container, '#inherited')
assert.equal(result.url, '')
assert.equal(result.customField, 'own')
})
}
test('invalid JS input keeps the published validation path, first error and thrown kind', () => {
const cases = [
{ volume: undefined },
{ volume: 'loud', muted: 'yes' },
{ subtitle: { encoding: undefined } },
{ thumbnails: { number: 'many' } },
{ plugins: [42] },
{ controls: [{ html: 'Button', position: 'middle' }] },
{ quality: [{ html: 'HD', url: 42 }] },
{ proxy: null },
{ moreVideoAttr: [] },
]
for (const patch of cases) {
const input = { container: '#player', url: 'video.mp4', ...patch }
const errors = []
for (const [resolve, Artplayer] of [[oldResolve, published.Artplayer], [resolveOption, current.Artplayer]]) {
assert.throws(() => resolve(input, Artplayer.option), (error) => {
errors.push({ name: error.name, message: error.message })
return true
})
}
assert.deepEqual(errors[0], errors[1])
}
})
+33
View File
@@ -0,0 +1,33 @@
import type { Option, ResolvedOption } from '../../packages/artplayer/src/option/types'
import createDefaults from '../../packages/artplayer/src/option/defaults'
import resolveOption from '../../packages/artplayer/src/option/resolve'
const input: Option = { container: '#player', url: 'video.mp4', subtitle: { encoding: 'utf-8' } }
const resolved: ResolvedOption = resolveOption(input, createDefaults())
resolved.subtitle.onVttLoad(resolved.subtitle.url)
resolved.thumbnails.column.toFixed()
resolved.volume.toFixed()
resolved.plugins.push(function (art) {
return { name: 'typed', receiverId: this.id, argumentId: art.id }
})
resolved.proxy = undefined
const extended = resolveOption({ container: '#player', url: '', application: { token: 1 } }, createDefaults())
const token: number = extended.application.token
void token
// @ts-expect-error Input remains optional before resolution.
input.subtitle.onVttLoad('text')
// @ts-expect-error Known fields cannot be widened by generic extension inference.
resolveOption({ container: '#player', url: '', volume: 'loud' }, createDefaults())
// @ts-expect-error Resolved nested defaults are required.
resolved.thumbnails.column = undefined
// @ts-expect-error Existing callback return types remain checked.
resolved.subtitle.onVttLoad = () => 42
// @ts-expect-error Application fields retain their own type.
extended.application.token = 'invalid'
// Historical public declaration gaps; CORE-07 must replace these with positive checks.
// @ts-expect-error Runtime accepts an omitted URL, but the current public Option requires it.
const omittedUrl: Option = { container: '#player' }
// @ts-expect-error Runtime accepts numeric control HTML, but the current public type excludes it.
const numericHtml: NonNullable<Option['controls']>[number] = { html: 42, position: 'left' }
void [omittedUrl, numericHtml]
+4 -1
View File
@@ -1,4 +1,7 @@
{
"extends": "./tsconfig.base.json",
"include": ["test/types/*.ts"]
"include": [
"test/types/*.ts",
"types/option-validator.d.ts"
]
}
+11
View File
@@ -0,0 +1,11 @@
declare module 'option-validator' {
export type ValidatorPath = (string | number)[]
export type Validator = (value: unknown, type: string, paths: ValidatorPath) => unknown
export type Scheme = string | Validator | Scheme[] | { [key: string]: Scheme }
function optionValidator<T>(option: T, scheme: Scheme, paths?: ValidatorPath): T
namespace optionValidator {
const kindOf: (value: unknown) => string
}
export default optionValidator
}