refactor(core): CORE-10 type playback properties and add compatible controls view

This commit is contained in:
Harvey Zhao committed 2026-09-11 02:51:55 +08:00
1 parent 44f4e083a9
commit 5e31274f1d
48 files changed
+3846 -248

No files matched your search

+26
View File
@@ -318,6 +318,32 @@ finding, not a claim that a standalone debounce can know when its owner is destr
## Verification and maintenance
### Playback properties (CORE-10)
The toggle, currentTime, seek/forward/backward, volume/muted, playbackRate,
played, loaded/loadedTime and state mixins are TypeScript modules.
`media/playback.ts` defines their minimal timing, notice, storage and display-state
hosts; toggle infers the union of its actual play/pause result types.
Keep JS coercion and read/write differences explicit: currentTime uses parseFloat,
seek emits both the clamped result and original request, and forward/backward
retain native addition/subtraction behavior. Volume uses parseInt-style percentage
formatting and stores nonzero actual media volume. Falsy playbackRate resets to 1;
equal rates do not change the notice. Raw played/loaded ratios may be NaN or Infinity.
Do not replace those rules without a separate compatibility decision.
Most media getters capture the original video; duration reads the current template.
The state setter only disables other modes, never enables its named mode. Native
fullscreen/PiP implementation and event forwarding migrate in their own tasks.
Public command-property getter discrepancies are BASE-TYPE-08, not runtime APIs
to invent. The optional public PlaybackControls view accurately models toggle
without changing the player's historical method declarations.
`test/playback-properties.test.js` covers coercion, storage, descriptors, ranges,
state priority and captured references. Its browser counterpart compares actual
published/candidate media; type fixtures cover source inference and five installed
consumer modes. Existing play/source/canvas tests continue to guard playback.
### Source operations (CORE-09)
`player/urlMix.ts` and `player/switchMix.ts` preserve the public descriptors.
+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,n=Object.getOwnPropertySymbols,a=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={}))a.call(e,r)&&s(t,r,e[r]);if(n)for(var r of n(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,n)=>{var a=t=>{try{s(r.next(t))}catch(e){n(e)}},i=t=>{try{s(r.throw(t))}catch(e){n(e)}},s=t=>t.done?o(t.value):Promise.resolve(t.value).then(a,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 n=t(r);if("boolean"===n)return"boolean";if("string"===n)return"string";if("number"===n)return"number";if("symbol"===n)return"symbol";if("function"===n)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(n=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 n.slice(8,-1).toLowerCase().replace(/\s/g,"")};function o(t){return t.constructor?t.constructor.name:null}function n(t,e){var o=2<arguments.length&&void 0!==arguments[2]?arguments[2]:["option"];return a(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),a(s,l,c),i(s,l,c),n(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),a(l,c,d),i(l,c,d),n(l,c,d)})}}(t,e,o),t}function a(t,e,o){if("string"===r(e)){var n=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 n===t}):e.toLowerCase().trim()===n))throw new Error("[Type Error]: '".concat(o.join("."),"' require '").concat(e,"' type, but got '").concat(n,"'"))}}function i(t,e,o){if("function"===r(e)){var n=e(t,r(t),o);if(!0!==n){var a=r(n);throw"string"===a?new Error(n):"error"===a?n:new Error("[Validator Error]: The scheme for '".concat(o.join("."),"' validator require return true, but got '").concat(n,"'"))}}}return n.kindOf=r,n}()),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,n=Object.getOwnPropertySymbols,a=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={}))a.call(e,r)&&s(t,r,e[r]);if(n)for(var r of n(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,n)=>{var a=t=>{try{s(r.next(t))}catch(e){n(e)}},i=t=>{try{s(r.throw(t))}catch(e){n(e)}},s=t=>t.done?o(t.value):Promise.resolve(t.value).then(a,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 n=t(r);if("boolean"===n)return"boolean";if("string"===n)return"string";if("number"===n)return"number";if("symbol"===n)return"symbol";if("function"===n)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(n=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 n.slice(8,-1).toLowerCase().replace(/\s/g,"")};function o(t){return t.constructor?t.constructor.name:null}function n(t,e){var o=2<arguments.length&&void 0!==arguments[2]?arguments[2]:["option"];return a(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),a(s,l,c),i(s,l,c),n(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),a(l,c,d),i(l,c,d),n(l,c,d)})}}(t,e,o),t}function a(t,e,o){if("string"===r(e)){var n=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 n===t}):e.toLowerCase().trim()===n))throw new Error("[Type Error]: '".concat(o.join("."),"' require '").concat(e,"' type, but got '").concat(n,"'"))}}function i(t,e,o){if("function"===r(e)){var n=e(t,r(t),o);if(!0!==n){var a=r(n);throw"string"===a?new Error(n):"error"===a?n:new Error("[Validator Error]: The scheme for '".concat(o.join("."),"' validator require return true, but got '").concat(n,"'"))}}}return n.kindOf=r,n}()),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
+20 -36
View File
@@ -2753,10 +2753,10 @@ function currentTimeMix(art) {
def(art, "currentTime", {
get: () => $video.currentTime || 0,
set: (time2) => {
time2 = Number.parseFloat(time2);
if (Number.isNaN(time2))
const parsed = Number.parseFloat(time2);
if (Number.isNaN(parsed))
return;
$video.currentTime = clamp(time2, 0, art.duration);
$video.currentTime = clamp(parsed, 0, art.duration);
}
});
}
@@ -3133,13 +3133,10 @@ function fullscreenWebMix(art) {
}
function loadedMix(art) {
const { $video } = art.template;
def(art, "loaded", {
get: () => art.loadedTime / $video.duration
});
const target = art;
def(art, "loaded", { get: () => target.loadedTime / $video.duration });
def(art, "loadedTime", {
get: () => {
return $video.buffered.length ? $video.buffered.end($video.buffered.length - 1) : 0;
}
get: () => $video.buffered.length ? $video.buffered.end($video.buffered.length - 1) : 0
});
}
function miniMix(art) {
@@ -3384,11 +3381,8 @@ function pipMix(art) {
}
}
function playbackRateMix(art) {
const {
template: { $video },
i18n,
notice
} = art;
const { template: { $video }, i18n, notice } = art;
const target = art;
def(art, "playbackRate", {
get() {
return $video.playbackRate;
@@ -3400,15 +3394,13 @@ function playbackRateMix(art) {
$video.playbackRate = rate;
notice.show = `${i18n.get("Rate")}: ${rate === 1 ? i18n.get("Normal") : `${rate}x`}`;
} else {
art.playbackRate = 1;
target.playbackRate = 1;
}
}
});
}
function playedMix(art) {
def(art, "played", {
get: () => art.currentTime / art.duration
});
def(art, "played", { get: () => art.currentTime / art.duration });
}
function playingMix(art) {
const { $video } = art.template;
@@ -3629,20 +3621,20 @@ function seekMix(art) {
def(art, "seek", {
set(time2) {
art.currentTime = time2;
if (art.duration) {
if (art.duration)
notice.show = `${secondToTime(art.currentTime)} / ${secondToTime(art.duration)}`;
}
art.emit("seek", art.currentTime, time2);
}
});
const target = art;
def(art, "forward", {
set(time2) {
art.seek = art.currentTime + time2;
target.seek = art.currentTime + time2;
}
});
def(art, "backward", {
set(time2) {
art.seek = art.currentTime - time2;
target.seek = art.currentTime - time2;
}
});
}
@@ -3653,9 +3645,8 @@ function stateMix(art) {
set(name) {
for (let index = 0; index < states2.length; index++) {
const prop = states2[index];
if (prop !== name && art[prop]) {
if (prop !== name && art[prop])
art[prop] = false;
}
}
}
});
@@ -3902,11 +3893,10 @@ function thumbnailsMix(art) {
function toggleMix(art) {
def(art, "toggle", {
value() {
if (art.playing) {
if (art.playing)
return art.pause();
} else {
else
return art.play();
}
}
});
}
@@ -3995,20 +3985,14 @@ function urlMix(art) {
});
}
function volumeMix(art) {
const {
template: { $video },
i18n,
notice,
storage
} = art;
const { template: { $video }, i18n, notice, storage } = art;
def(art, "volume", {
get: () => $video.volume || 0,
set: (percentage) => {
$video.volume = clamp(percentage, 0, 1);
notice.show = `${i18n.get("Volume")}: ${Number.parseInt($video.volume * 100, 10)}`;
if ($video.volume !== 0) {
notice.show = `${i18n.get("Volume")}: ${Number.parseInt(String($video.volume * 100), 10)}`;
if ($video.volume !== 0)
storage.set("volume", $video.volume);
}
}
});
def(art, "muted", {
+38
View File
@@ -0,0 +1,38 @@
export interface Position {
readonly duration: number
get currentTime(): number
set currentTime(time: number | string)
}
export interface SeekMethods {
get seek(): undefined
set seek(time: number | string)
get forward(): undefined
set forward(time: number)
get backward(): undefined
set backward(time: number)
}
export interface SeekHost extends Position {
notice: { set show(message: string) }
emit: (name: 'seek', currentTime: number, requestedTime: number | string) => unknown
}
export interface PlaybackNotice {
i18n: { get: (key: string) => string }
notice: { set show(message: string) }
}
export interface VolumeHost extends PlaybackNotice {
template: { $video: Pick<HTMLMediaElement, 'volume' | 'muted'> }
storage: { set: (key: 'volume', value: number) => unknown }
emit: (name: 'muted', value: boolean) => unknown
}
export type DisplayState = 'mini' | 'pip' | 'fullscreen' | 'fullscreenWeb'
export type DisplayStates = Record<DisplayState, boolean>
export interface StateProperty {
get state(): DisplayState | 'standard'
set state(name: string)
}
@@ -1,15 +0,0 @@
import { clamp, def } from '../utils'
export default function currentTimeMix(art) {
const { $video } = art.template
def(art, 'currentTime', {
get: () => $video.currentTime || 0,
set: (time) => {
time = Number.parseFloat(time)
if (Number.isNaN(time))
return
$video.currentTime = clamp(time, 0, art.duration)
},
})
}
@@ -0,0 +1,17 @@
import type { MediaHost } from '../media/hosts'
import type { Position } from '../media/playback'
import { clamp, def } from '../utils'
export default function currentTimeMix(art: MediaHost<Pick<HTMLMediaElement, 'currentTime'>> & Pick<Position, 'duration'>): asserts art is typeof art & Position {
const { $video } = art.template
def(art, 'currentTime', {
get: () => $video.currentTime || 0,
set: (time: number | string) => {
// parseFloat performs the historical JS coercion; a cast must not change it.
const parsed = Number.parseFloat(time as string)
if (Number.isNaN(parsed))
return
$video.currentTime = clamp(parsed, 0, art.duration)
},
})
}
@@ -1,15 +0,0 @@
import { def } from '../utils'
export default function loadedMix(art) {
const { $video } = art.template
def(art, 'loaded', {
get: () => art.loadedTime / $video.duration,
})
def(art, 'loadedTime', {
get: () => {
return $video.buffered.length ? $video.buffered.end($video.buffered.length - 1) : 0
},
})
}
@@ -0,0 +1,17 @@
import type { MediaHost } from '../media/hosts'
import { def } from '../utils'
type LoadedHost = MediaHost<Pick<HTMLMediaElement, 'duration' | 'buffered'>>
interface LoadedState {
readonly loaded: number
readonly loadedTime: number
}
export default function loadedMix(art: LoadedHost): asserts art is LoadedHost & LoadedState {
const { $video } = art.template
const target = art as LoadedHost & LoadedState
def(art, 'loaded', { get: () => target.loadedTime / $video.duration })
def(art, 'loadedTime', {
get: () => $video.buffered.length ? $video.buffered.end($video.buffered.length - 1) : 0,
})
}
@@ -1,26 +0,0 @@
import { def } from '../utils'
export default function playbackRateMix(art) {
const {
template: { $video },
i18n,
notice,
} = art
def(art, 'playbackRate', {
get() {
return $video.playbackRate
},
set(rate) {
if (rate) {
if (rate === $video.playbackRate)
return
$video.playbackRate = rate
notice.show = `${i18n.get('Rate')}: ${rate === 1.0 ? i18n.get('Normal') : `${rate}x`}`
}
else {
art.playbackRate = 1
}
},
})
}
@@ -0,0 +1,26 @@
import type { MediaHost } from '../media/hosts'
import type { PlaybackNotice } from '../media/playback'
import { def } from '../utils'
type RateHost = MediaHost<Pick<HTMLMediaElement, 'playbackRate'>> & PlaybackNotice
export default function playbackRateMix(art: RateHost): asserts art is RateHost & { playbackRate: number } {
const { template: { $video }, i18n, notice } = art
const target = art as RateHost & { playbackRate: number }
def(art, 'playbackRate', {
get() {
return $video.playbackRate
},
set(rate: number) {
if (rate) {
if (rate === $video.playbackRate)
return
$video.playbackRate = rate
notice.show = `${i18n.get('Rate')}: ${rate === 1.0 ? i18n.get('Normal') : `${rate}x`}`
}
else {
target.playbackRate = 1
}
},
})
}
@@ -1,7 +0,0 @@
import { def } from '../utils'
export default function playedMix(art) {
def(art, 'played', {
get: () => art.currentTime / art.duration,
})
}
@@ -0,0 +1,6 @@
import type { Position } from '../media/playback'
import { def } from '../utils'
export default function playedMix(art: Position): asserts art is Position & { readonly played: number } {
def(art, 'played', { get: () => art.currentTime / art.duration })
}
-27
View File
@@ -1,27 +0,0 @@
import { def, secondToTime } from '../utils'
export default function seekMix(art) {
const { notice } = art
def(art, 'seek', {
set(time) {
art.currentTime = time
if (art.duration) {
notice.show = `${secondToTime(art.currentTime)} / ${secondToTime(art.duration)}`
}
art.emit('seek', art.currentTime, time)
},
})
def(art, 'forward', {
set(time) {
art.seek = art.currentTime + time
},
})
def(art, 'backward', {
set(time) {
art.seek = art.currentTime - time
},
})
}
+26
View File
@@ -0,0 +1,26 @@
import type { SeekHost, SeekMethods } from '../media/playback'
import { def, secondToTime } from '../utils'
export default function seekMix(art: SeekHost): asserts art is SeekHost & SeekMethods {
const { notice } = art
def(art, 'seek', {
set(time: number | string) {
art.currentTime = time
if (art.duration)
notice.show = `${secondToTime(art.currentTime)} / ${secondToTime(art.duration)}`
art.emit('seek', art.currentTime, time)
},
})
// The seek descriptor is installed above; preserve forwarding through it.
const target = art as SeekHost & SeekMethods
def(art, 'forward', {
set(time: number) {
target.seek = art.currentTime + time
},
})
def(art, 'backward', {
set(time: number) {
target.seek = art.currentTime - time
},
})
}
-16
View File
@@ -1,16 +0,0 @@
import { def } from '../utils'
export default function stateMix(art) {
const states = ['mini', 'pip', 'fullscreen', 'fullscreenWeb']
def(art, 'state', {
get: () => states.find(name => art[name]) || 'standard',
set(name) {
for (let index = 0; index < states.length; index++) {
const prop = states[index]
if (prop !== name && art[prop]) {
art[prop] = false
}
}
},
})
}
+16
View File
@@ -0,0 +1,16 @@
import type { DisplayStates, StateProperty } from '../media/playback'
import { def } from '../utils'
export default function stateMix(art: DisplayStates): asserts art is DisplayStates & StateProperty {
const states = ['mini', 'pip', 'fullscreen', 'fullscreenWeb'] as const
def(art, 'state', {
get: () => states.find(name => art[name]) || 'standard',
set(name: string) {
for (let index = 0; index < states.length; index++) {
const prop = states[index]!
if (prop !== name && art[prop])
art[prop] = false
}
},
})
}
@@ -1,14 +0,0 @@
import { def } from '../utils'
export default function toggleMix(art) {
def(art, 'toggle', {
value() {
if (art.playing) {
return art.pause()
}
else {
return art.play()
}
},
})
}
@@ -0,0 +1,13 @@
import type { PlaybackMethods } from '../media/types'
import { def } from '../utils'
export default function toggleMix<PlayResult, PauseResult>(art: PlaybackMethods<PlayResult, PauseResult> & { readonly playing: boolean }): asserts art is typeof art & { toggle: () => PlayResult | PauseResult } {
def(art, 'toggle', {
value() {
if (art.playing)
return art.pause()
else
return art.play()
},
})
}
@@ -1,29 +0,0 @@
import { clamp, def } from '../utils'
export default function volumeMix(art) {
const {
template: { $video },
i18n,
notice,
storage,
} = art
def(art, 'volume', {
get: () => $video.volume || 0,
set: (percentage) => {
$video.volume = clamp(percentage, 0, 1)
notice.show = `${i18n.get('Volume')}: ${Number.parseInt($video.volume * 100, 10)}`
if ($video.volume !== 0) {
storage.set('volume', $video.volume)
}
},
})
def(art, 'muted', {
get: () => $video.muted,
set: (muted) => {
$video.muted = muted
art.emit('muted', muted)
},
})
}
@@ -0,0 +1,22 @@
import type { VolumeHost } from '../media/playback'
import { clamp, def } from '../utils'
export default function volumeMix(art: VolumeHost): asserts art is VolumeHost & Pick<HTMLMediaElement, 'volume' | 'muted'> {
const { template: { $video }, i18n, notice, storage } = art
def(art, 'volume', {
get: () => $video.volume || 0,
set: (percentage: number) => {
$video.volume = clamp(percentage, 0, 1)
notice.show = `${i18n.get('Volume')}: ${Number.parseInt(String($video.volume * 100), 10)}`
if ($video.volume !== 0)
storage.set('volume', $video.volume)
},
})
def(art, 'muted', {
get: () => $video.muted,
set: (muted: boolean) => {
$video.muted = muted
art.emit('muted', muted)
},
})
}
+1
View File
@@ -40,5 +40,6 @@ export interface SwitchHost extends UrlTarget, SourceEvents {
export interface SwitchMethods {
switchUrl: (url: string) => Promise<void>
switchQuality: (url: string) => Promise<void>
get switch(): undefined
set switch(url: string)
}
+32 -10
View File
@@ -33,16 +33,33 @@ art.on('subtitleBeforeUpdate', (cues: VTTCue[]) => {
## Retained conflicts and owners
| Surface | Actual runtime | Retained historical declaration | Follow-up |
| ------------------------- | ----------------------------------------------------------------------------- | ------------------------------------------------------- | ------------------------ |
| plugins.add | Registry for a synchronous factory; Promise of registry for a Promise factory | Always Promise | CORE-08/21, BASE-TYPE-04 |
| toggle | Pause result or original play Promise, including rejection | void | CORE-10/21, BASE-TYPE-04 |
| debounce/throttle | Wrapper returns undefined; debounce ignores its old context argument | Callback return type | CORE-21, BASE-TYPE-05 |
| def with string key | Native defineProperty returns the target | void, arbitrary descriptor input accepted | CORE-21, BASE-TYPE-05 |
| setting.find | Item or null | Item or undefined | CORE-14/21, BASE-TYPE-07 |
| setting.add/update/remove | Item/item/undefined | Setting registry | CORE-14/21, BASE-TYPE-07 |
| subtitle update events | VTTCue array | Scalar contextual inference unless explicitly annotated | CORE-15/21, BASE-TYPE-07 |
| notice.show getter | Boolean visibility | Historical message/false union | CORE-18/21, BASE-TYPE-07 |
CORE-10 adds an accurate method view without wrapping or replacing the player:
```ts
import type { PlaybackControls } from 'artplayer'
const playback: PlaybackControls = art
const pending = playback.toggle()
if (pending)
pending.catch(error => console.error(error))
```
The original `art.toggle(): void` declaration remains accepted by historical
consumers. The optional view exposes `Promise<void> | void`; play rejection and
the original object identity remain unchanged. BASE-TYPE-04 stays open for the
remaining plugins.add conflict and CORE-21's full facade review.
| Surface | Actual runtime | Retained historical declaration | Follow-up |
| ------------------------------------------ | ----------------------------------------------------------------------------- | ------------------------------------------------------- | ------------------------ |
| plugins.add | Registry for a synchronous factory; Promise of registry for a Promise factory | Always Promise | CORE-08/21, BASE-TYPE-04 |
| toggle | Pause result or original play Promise, including rejection | void | CORE-10/21, BASE-TYPE-04 |
| debounce/throttle | Wrapper returns undefined; debounce ignores its old context argument | Callback return type | CORE-21, BASE-TYPE-05 |
| def with string key | Native defineProperty returns the target | void, arbitrary descriptor input accepted | CORE-21, BASE-TYPE-05 |
| setting.find | Item or null | Item or undefined | CORE-14/21, BASE-TYPE-07 |
| setting.add/update/remove | Item/item/undefined | Setting registry | CORE-14/21, BASE-TYPE-07 |
| subtitle update events | VTTCue array | Scalar contextual inference unless explicitly annotated | CORE-15/21, BASE-TYPE-07 |
| notice.show getter | Boolean visibility | Historical message/false union | CORE-18/21, BASE-TYPE-07 |
| seek/forward/backward/switch/quality reads | Undefined: these descriptors only have setters | Numeric/string/quality-array getters | CORE-21, BASE-TYPE-08 |
These remain open declaration work, not release waivers. CORE-21 must resolve the
public/generated facade strategy against both actual returns and historical source
@@ -52,6 +69,11 @@ and Promise results; ignore timer results, guard setting.find with a truthiness
check, and explicitly annotate cue-array listeners. Do not rely on the inaccurate
return types to call Promise methods on synchronous results or chain settings.
CORE-10 records BASE-TYPE-08 from real published/candidate descriptors. Reading
these command properties returns undefined; use currentTime, option and media
state for observations. Source interfaces model undefined reads independently
of setter inputs. Historical public getter declarations are not removed here.
## Evidence and maintenance
declaration-legacy.ts is a compile-only regression fixture, not recommended runtime
+6
View File
@@ -47,6 +47,12 @@ signature. CORE-21 remains responsible for the generated/public declaration stra
## Internal media migration
`PlaybackControls` is an optional accurate view of play/pause/toggle. Assign the
existing player directly (`const playback: PlaybackControls = art`); it preserves
object identity and reports toggle's Promise-or-void branches. It is exported from
the root and shared type entry in all five supported consumer modes. Historical
`art.toggle(): void` remains available; see COMPATIBILITY.md for the distinction.
CORE-06 uses src/media/types.ts and src/media/hosts.ts for native media, canvas
shims and minimal playback/layout dependencies. These types are not exported by
the package. Public art.video keeps its historical declaration and runtime identity;
+1
View File
@@ -14,6 +14,7 @@ declare namespace Artplayer {
type Option = Definition.Option
type OptionInput = Definition.OptionInput
type Player = Definition.Player
type PlaybackControls = Definition.PlaybackControls
type PluginFactory<Host = Artplayer, Result = unknown> = Definition.PluginFactory<Host, Result>
type Plugins = Definition.Plugins
type Setting = Definition.Setting
+1 -1
View File
@@ -1,2 +1,2 @@
export { default } from './artplayer.cjs'
export type { Config, Emitter, Events, I18n, Icons, Option, OptionInput, Player, PluginFactory, Plugins, Setting, SettingOption, Subtitle, SubtitleUpdateEvents, Template, Utils } from './artplayer.js'
export type { Config, Emitter, Events, I18n, Icons, Option, OptionInput, PlaybackControls, Player, PluginFactory, Plugins, Setting, SettingOption, Subtitle, SubtitleUpdateEvents, Template, Utils } from './artplayer.js'
+2
View File
@@ -5,6 +5,7 @@ import type { Events, SubtitleUpdateEvents } from './events'
import type { I18n } from './i18n'
import type { Icons } from './icons'
import type { Option, OptionInput } from './option'
import type { PlaybackControls } from './playback'
import type { Player } from './player'
import type { PluginFactory, Plugins } from './plugin'
import type { Setting, SettingOption } from './setting'
@@ -20,6 +21,7 @@ export type {
Icons,
Option,
OptionInput,
PlaybackControls,
Player,
PluginFactory,
Plugins,
+6
View File
@@ -0,0 +1,6 @@
/** Accurate playback method view; assign an existing player without a runtime wrapper. */
export interface PlaybackControls {
play: () => Promise<void>
pause: () => void
toggle: () => Promise<void> | void
}