mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-11 13:56:15 -08:00
refactor(core): CORE-10 type playback properties and add compatible controls view
This commit is contained in:
1 parent
44f4e083a9
commit
5e31274f1d
48 files changed
+3846
-248
No files matched your search
Vendored
+8
@@ -367,6 +367,13 @@ export declare class Player {
|
||||
reset(): void
|
||||
}
|
||||
|
||||
/** Accurate playback method view; assign an existing player without a runtime wrapper. */
|
||||
export interface PlaybackControls {
|
||||
play: () => Promise<void>
|
||||
pause: () => void
|
||||
toggle: () => Promise<void> | void
|
||||
}
|
||||
|
||||
export type CustomType
|
||||
= | 'flv'
|
||||
| 'm3u8'
|
||||
@@ -1167,6 +1174,7 @@ export type {
|
||||
Icons,
|
||||
Option,
|
||||
OptionInput,
|
||||
PlaybackControls,
|
||||
Player,
|
||||
PluginFactory,
|
||||
Plugins,
|
||||
|
||||
@@ -4,4 +4,4 @@
|
||||
* (c) 2017-2026 Harvey Zhao
|
||||
* Released under the MIT License.
|
||||
*/
|
||||
!function(t,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"==typeof define&&define.amd?(t.Artplayer=e(),define(function(){return t.Artplayer})):(t="undefined"!==typeof globalThis?globalThis:t||self).Artplayer=e()}(this,function(){"use strict";function t(t){return t&&t.__esModule&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t}var e,r={exports:{}};var o=(e||(e=1,r.exports=function(){function t(e){return(t="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(e)}var e=Object.prototype.toString,r=function(r){if(void 0===r)return"undefined";if(null===r)return"null";var a=t(r);if("boolean"===a)return"boolean";if("string"===a)return"string";if("number"===a)return"number";if("symbol"===a)return"symbol";if("function"===a)return"GeneratorFunction"===o(r)?"generatorfunction":"function";if(function(t){return Array.isArray?Array.isArray(t):t instanceof Array}(r))return"array";if(function(t){return!(!t.constructor||"function"!=typeof t.constructor.isBuffer)&&t.constructor.isBuffer(t)}(r))return"buffer";if(function(t){try{if("number"==typeof t.length&&"function"==typeof t.callee)return!0}catch(e){if(-1!==e.message.indexOf("callee"))return!0}return!1}(r))return"arguments";if(function(t){return t instanceof Date||"function"==typeof t.toDateString&&"function"==typeof t.getDate&&"function"==typeof t.setDate}(r))return"date";if(function(t){return t instanceof Error||"string"==typeof t.message&&t.constructor&&"number"==typeof t.constructor.stackTraceLimit}(r))return"error";if(function(t){return t instanceof RegExp||"string"==typeof t.flags&&"boolean"==typeof t.ignoreCase&&"boolean"==typeof t.multiline&&"boolean"==typeof t.global}(r))return"regexp";switch(o(r)){case"Symbol":return"symbol";case"Promise":return"promise";case"WeakMap":return"weakmap";case"WeakSet":return"weakset";case"Map":return"map";case"Set":return"set";case"Int8Array":return"int8array";case"Uint8Array":return"uint8array";case"Uint8ClampedArray":return"uint8clampedarray";case"Int16Array":return"int16array";case"Uint16Array":return"uint16array";case"Int32Array":return"int32array";case"Uint32Array":return"uint32array";case"Float32Array":return"float32array";case"Float64Array":return"float64array"}if(function(t){return"function"==typeof t.throw&&"function"==typeof t.return&&"function"==typeof t.next}(r))return"generator";switch(a=e.call(r)){case"[object Object]":return"object";case"[object Map Iterator]":return"mapiterator";case"[object Set Iterator]":return"setiterator";case"[object String Iterator]":return"stringiterator";case"[object Array Iterator]":return"arrayiterator"}return a.slice(8,-1).toLowerCase().replace(/\s/g,"")};function o(t){return t.constructor?t.constructor.name:null}function a(t,e){var o=2<arguments.length&&void 0!==arguments[2]?arguments[2]:["option"];return n(t,e,o),i(t,e,o),function(t,e,o){var s=r(e),l=r(t);if("object"===s){if("object"!==l)throw new Error("[Type Error]: '".concat(o.join("."),"' require 'object' type, but got '").concat(l,"'"));Object.keys(e).forEach(function(r){var s=t[r],l=e[r],c=o.slice();c.push(r),n(s,l,c),i(s,l,c),a(s,l,c)})}if("array"===s){if("array"!==l)throw new Error("[Type Error]: '".concat(o.join("."),"' require 'array' type, but got '").concat(l,"'"));t.forEach(function(r,s){var l=t[s],c=e[s]||e[0],d=o.slice();d.push(s),n(l,c,d),i(l,c,d),a(l,c,d)})}}(t,e,o),t}function n(t,e,o){if("string"===r(e)){var a=r(t);if("?"===e[0]&&(e=e.slice(1)+"|undefined"),!(-1<e.indexOf("|")?e.split("|").map(function(t){return t.toLowerCase().trim()}).filter(Boolean).some(function(t){return a===t}):e.toLowerCase().trim()===a))throw new Error("[Type Error]: '".concat(o.join("."),"' require '").concat(e,"' type, but got '").concat(a,"'"))}}function i(t,e,o){if("function"===r(e)){var a=e(t,r(t),o);if(!0!==a){var n=r(a);throw"string"===n?new Error(a):"error"===n?a:new Error("[Validator Error]: The scheme for '".concat(o.join("."),"' validator require return true, but got '").concat(a,"'"))}}}return a.kindOf=r,a}()),r.exports);const a=t(o),n="5.4.1",i={properties:["audioTracks","autoplay","buffered","controller","controls","crossOrigin","currentSrc","currentTime","defaultMuted","defaultPlaybackRate","duration","ended","error","loop","mediaGroup","muted","networkState","paused","playbackRate","played","preload","readyState","seekable","seeking","src","startDate","textTracks","videoTracks","volume"],methods:["addTextTrack","canPlayType","load","play","pause"],events:["abort","canplay","canplaythrough","durationchange","emptied","ended","error","loadeddata","loadedmetadata","loadstart","pause","play","playing","progress","ratechange","seeked","seeking","stalled","suspend","timeupdate","volumechange","waiting"],prototypes:["width","height","videoWidth","videoHeight","poster","webkitDecodedFrameCount","webkitDroppedFrameCount","playsInline","webLine truncated
|
||||
!function(t,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"==typeof define&&define.amd?(t.Artplayer=e(),define(function(){return t.Artplayer})):(t="undefined"!==typeof globalThis?globalThis:t||self).Artplayer=e()}(this,function(){"use strict";function t(t){return t&&t.__esModule&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t}var e,r={exports:{}};var o=(e||(e=1,r.exports=function(){function t(e){return(t="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(e)}var e=Object.prototype.toString,r=function(r){if(void 0===r)return"undefined";if(null===r)return"null";var a=t(r);if("boolean"===a)return"boolean";if("string"===a)return"string";if("number"===a)return"number";if("symbol"===a)return"symbol";if("function"===a)return"GeneratorFunction"===o(r)?"generatorfunction":"function";if(function(t){return Array.isArray?Array.isArray(t):t instanceof Array}(r))return"array";if(function(t){return!(!t.constructor||"function"!=typeof t.constructor.isBuffer)&&t.constructor.isBuffer(t)}(r))return"buffer";if(function(t){try{if("number"==typeof t.length&&"function"==typeof t.callee)return!0}catch(e){if(-1!==e.message.indexOf("callee"))return!0}return!1}(r))return"arguments";if(function(t){return t instanceof Date||"function"==typeof t.toDateString&&"function"==typeof t.getDate&&"function"==typeof t.setDate}(r))return"date";if(function(t){return t instanceof Error||"string"==typeof t.message&&t.constructor&&"number"==typeof t.constructor.stackTraceLimit}(r))return"error";if(function(t){return t instanceof RegExp||"string"==typeof t.flags&&"boolean"==typeof t.ignoreCase&&"boolean"==typeof t.multiline&&"boolean"==typeof t.global}(r))return"regexp";switch(o(r)){case"Symbol":return"symbol";case"Promise":return"promise";case"WeakMap":return"weakmap";case"WeakSet":return"weakset";case"Map":return"map";case"Set":return"set";case"Int8Array":return"int8array";case"Uint8Array":return"uint8array";case"Uint8ClampedArray":return"uint8clampedarray";case"Int16Array":return"int16array";case"Uint16Array":return"uint16array";case"Int32Array":return"int32array";case"Uint32Array":return"uint32array";case"Float32Array":return"float32array";case"Float64Array":return"float64array"}if(function(t){return"function"==typeof t.throw&&"function"==typeof t.return&&"function"==typeof t.next}(r))return"generator";switch(a=e.call(r)){case"[object Object]":return"object";case"[object Map Iterator]":return"mapiterator";case"[object Set Iterator]":return"setiterator";case"[object String Iterator]":return"stringiterator";case"[object Array Iterator]":return"arrayiterator"}return a.slice(8,-1).toLowerCase().replace(/\s/g,"")};function o(t){return t.constructor?t.constructor.name:null}function a(t,e){var o=2<arguments.length&&void 0!==arguments[2]?arguments[2]:["option"];return n(t,e,o),i(t,e,o),function(t,e,o){var s=r(e),l=r(t);if("object"===s){if("object"!==l)throw new Error("[Type Error]: '".concat(o.join("."),"' require 'object' type, but got '").concat(l,"'"));Object.keys(e).forEach(function(r){var s=t[r],l=e[r],c=o.slice();c.push(r),n(s,l,c),i(s,l,c),a(s,l,c)})}if("array"===s){if("array"!==l)throw new Error("[Type Error]: '".concat(o.join("."),"' require 'array' type, but got '").concat(l,"'"));t.forEach(function(r,s){var l=t[s],c=e[s]||e[0],d=o.slice();d.push(s),n(l,c,d),i(l,c,d),a(l,c,d)})}}(t,e,o),t}function n(t,e,o){if("string"===r(e)){var a=r(t);if("?"===e[0]&&(e=e.slice(1)+"|undefined"),!(-1<e.indexOf("|")?e.split("|").map(function(t){return t.toLowerCase().trim()}).filter(Boolean).some(function(t){return a===t}):e.toLowerCase().trim()===a))throw new Error("[Type Error]: '".concat(o.join("."),"' require '").concat(e,"' type, but got '").concat(a,"'"))}}function i(t,e,o){if("function"===r(e)){var a=e(t,r(t),o);if(!0!==a){var n=r(a);throw"string"===n?new Error(a):"error"===n?a:new Error("[Validator Error]: The scheme for '".concat(o.join("."),"' validator require return true, but got '").concat(a,"'"))}}}return a.kindOf=r,a}()),r.exports);const a=t(o),n="5.4.1",i={properties:["audioTracks","autoplay","buffered","controller","controls","crossOrigin","currentSrc","currentTime","defaultMuted","defaultPlaybackRate","duration","ended","error","loop","mediaGroup","muted","networkState","paused","playbackRate","played","preload","readyState","seekable","seeking","src","startDate","textTracks","videoTracks","volume"],methods:["addTextTrack","canPlayType","load","play","pause"],events:["abort","canplay","canplaythrough","durationchange","emptied","ended","error","loadeddata","loadedmetadata","loadstart","pause","play","playing","progress","ratechange","seeked","seeking","stalled","suspend","timeupdate","volumechange","waiting"],prototypes:["width","height","videoWidth","videoHeight","poster","webkitDecodedFrameCount","webkitDroppedFrameCount","playsInline","webLine truncated
|
||||
@@ -4,4 +4,4 @@
|
||||
* (c) 2017-2026 Harvey Zhao
|
||||
* Released under the MIT License.
|
||||
*/
|
||||
!function(t,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"==typeof define&&define.amd?(t.Artplayer=e(),define(function(){return t.Artplayer})):(t="undefined"!==typeof globalThis?globalThis:t||self).Artplayer=e()}(this,function(){"use strict";var t,e=Object.defineProperty,r=Object.defineProperties,o=Object.getOwnPropertyDescriptors,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
@@ -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", {
|
||||
|
||||
+1
-1
@@ -49,7 +49,7 @@
|
||||
"ci:build": "yarn build all && yarn build:i18n && yarn build:ts && yarn build:docs && yarn test:imports",
|
||||
"test:imports": "node --test test/esm.test.js test/i18n.test.js test/ssr.test.js",
|
||||
"typecheck": "node scripts/typecheck.mjs",
|
||||
"test:unit": "node --test test/playback.test.js test/dash-control.test.js test/public-behavior.test.js test/helpers.test.js test/chapter.test.js test/utils.test.js test/resource-scope.test.js test/instance-lifecycle.test.js test/options.test.js test/media-hosts.test.js test/plugins.test.js test/source.test.js",
|
||||
"test:unit": "node --test test/playback.test.js test/dash-control.test.js test/public-behavior.test.js test/helpers.test.js test/chapter.test.js test/utils.test.js test/resource-scope.test.js test/instance-lifecycle.test.js test/options.test.js test/media-hosts.test.js test/plugins.test.js test/source.test.js test/playback-properties.test.js",
|
||||
"test": "yarn test:node && yarn test:baseline",
|
||||
"test:browser": "playwright test",
|
||||
"test:browser:install": "playwright install chromium firefox webkit",
|
||||
|
||||
@@ -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.
|
||||
|
||||
Vendored
+1
-1
@@ -4,4 +4,4 @@
|
||||
* (c) 2017-2026 Harvey Zhao
|
||||
* Released under the MIT License.
|
||||
*/
|
||||
!function(t,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"==typeof define&&define.amd?(t.Artplayer=e(),define(function(){return t.Artplayer})):(t="undefined"!==typeof globalThis?globalThis:t||self).Artplayer=e()}(this,function(){"use strict";function t(t){return t&&t.__esModule&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t}var e,r={exports:{}};var o=(e||(e=1,r.exports=function(){function t(e){return(t="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(e)}var e=Object.prototype.toString,r=function(r){if(void 0===r)return"undefined";if(null===r)return"null";var a=t(r);if("boolean"===a)return"boolean";if("string"===a)return"string";if("number"===a)return"number";if("symbol"===a)return"symbol";if("function"===a)return"GeneratorFunction"===o(r)?"generatorfunction":"function";if(function(t){return Array.isArray?Array.isArray(t):t instanceof Array}(r))return"array";if(function(t){return!(!t.constructor||"function"!=typeof t.constructor.isBuffer)&&t.constructor.isBuffer(t)}(r))return"buffer";if(function(t){try{if("number"==typeof t.length&&"function"==typeof t.callee)return!0}catch(e){if(-1!==e.message.indexOf("callee"))return!0}return!1}(r))return"arguments";if(function(t){return t instanceof Date||"function"==typeof t.toDateString&&"function"==typeof t.getDate&&"function"==typeof t.setDate}(r))return"date";if(function(t){return t instanceof Error||"string"==typeof t.message&&t.constructor&&"number"==typeof t.constructor.stackTraceLimit}(r))return"error";if(function(t){return t instanceof RegExp||"string"==typeof t.flags&&"boolean"==typeof t.ignoreCase&&"boolean"==typeof t.multiline&&"boolean"==typeof t.global}(r))return"regexp";switch(o(r)){case"Symbol":return"symbol";case"Promise":return"promise";case"WeakMap":return"weakmap";case"WeakSet":return"weakset";case"Map":return"map";case"Set":return"set";case"Int8Array":return"int8array";case"Uint8Array":return"uint8array";case"Uint8ClampedArray":return"uint8clampedarray";case"Int16Array":return"int16array";case"Uint16Array":return"uint16array";case"Int32Array":return"int32array";case"Uint32Array":return"uint32array";case"Float32Array":return"float32array";case"Float64Array":return"float64array"}if(function(t){return"function"==typeof t.throw&&"function"==typeof t.return&&"function"==typeof t.next}(r))return"generator";switch(a=e.call(r)){case"[object Object]":return"object";case"[object Map Iterator]":return"mapiterator";case"[object Set Iterator]":return"setiterator";case"[object String Iterator]":return"stringiterator";case"[object Array Iterator]":return"arrayiterator"}return a.slice(8,-1).toLowerCase().replace(/\s/g,"")};function o(t){return t.constructor?t.constructor.name:null}function a(t,e){var o=2<arguments.length&&void 0!==arguments[2]?arguments[2]:["option"];return n(t,e,o),i(t,e,o),function(t,e,o){var s=r(e),l=r(t);if("object"===s){if("object"!==l)throw new Error("[Type Error]: '".concat(o.join("."),"' require 'object' type, but got '").concat(l,"'"));Object.keys(e).forEach(function(r){var s=t[r],l=e[r],c=o.slice();c.push(r),n(s,l,c),i(s,l,c),a(s,l,c)})}if("array"===s){if("array"!==l)throw new Error("[Type Error]: '".concat(o.join("."),"' require 'array' type, but got '").concat(l,"'"));t.forEach(function(r,s){var l=t[s],c=e[s]||e[0],d=o.slice();d.push(s),n(l,c,d),i(l,c,d),a(l,c,d)})}}(t,e,o),t}function n(t,e,o){if("string"===r(e)){var a=r(t);if("?"===e[0]&&(e=e.slice(1)+"|undefined"),!(-1<e.indexOf("|")?e.split("|").map(function(t){return t.toLowerCase().trim()}).filter(Boolean).some(function(t){return a===t}):e.toLowerCase().trim()===a))throw new Error("[Type Error]: '".concat(o.join("."),"' require '").concat(e,"' type, but got '").concat(a,"'"))}}function i(t,e,o){if("function"===r(e)){var a=e(t,r(t),o);if(!0!==a){var n=r(a);throw"string"===n?new Error(a):"error"===n?a:new Error("[Validator Error]: The scheme for '".concat(o.join("."),"' validator require return true, but got '").concat(a,"'"))}}}return a.kindOf=r,a}()),r.exports);const a=t(o),n="5.4.1",i={properties:["audioTracks","autoplay","buffered","controller","controls","crossOrigin","currentSrc","currentTime","defaultMuted","defaultPlaybackRate","duration","ended","error","loop","mediaGroup","muted","networkState","paused","playbackRate","played","preload","readyState","seekable","seeking","src","startDate","textTracks","videoTracks","volume"],methods:["addTextTrack","canPlayType","load","play","pause"],events:["abort","canplay","canplaythrough","durationchange","emptied","ended","error","loadeddata","loadedmetadata","loadstart","pause","play","playing","progress","ratechange","seeked","seeking","stalled","suspend","timeupdate","volumechange","waiting"],prototypes:["width","height","videoWidth","videoHeight","poster","webkitDecodedFrameCount","webkitDroppedFrameCount","playsInline","webLine truncated
|
||||
!function(t,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"==typeof define&&define.amd?(t.Artplayer=e(),define(function(){return t.Artplayer})):(t="undefined"!==typeof globalThis?globalThis:t||self).Artplayer=e()}(this,function(){"use strict";function t(t){return t&&t.__esModule&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t}var e,r={exports:{}};var o=(e||(e=1,r.exports=function(){function t(e){return(t="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(e)}var e=Object.prototype.toString,r=function(r){if(void 0===r)return"undefined";if(null===r)return"null";var a=t(r);if("boolean"===a)return"boolean";if("string"===a)return"string";if("number"===a)return"number";if("symbol"===a)return"symbol";if("function"===a)return"GeneratorFunction"===o(r)?"generatorfunction":"function";if(function(t){return Array.isArray?Array.isArray(t):t instanceof Array}(r))return"array";if(function(t){return!(!t.constructor||"function"!=typeof t.constructor.isBuffer)&&t.constructor.isBuffer(t)}(r))return"buffer";if(function(t){try{if("number"==typeof t.length&&"function"==typeof t.callee)return!0}catch(e){if(-1!==e.message.indexOf("callee"))return!0}return!1}(r))return"arguments";if(function(t){return t instanceof Date||"function"==typeof t.toDateString&&"function"==typeof t.getDate&&"function"==typeof t.setDate}(r))return"date";if(function(t){return t instanceof Error||"string"==typeof t.message&&t.constructor&&"number"==typeof t.constructor.stackTraceLimit}(r))return"error";if(function(t){return t instanceof RegExp||"string"==typeof t.flags&&"boolean"==typeof t.ignoreCase&&"boolean"==typeof t.multiline&&"boolean"==typeof t.global}(r))return"regexp";switch(o(r)){case"Symbol":return"symbol";case"Promise":return"promise";case"WeakMap":return"weakmap";case"WeakSet":return"weakset";case"Map":return"map";case"Set":return"set";case"Int8Array":return"int8array";case"Uint8Array":return"uint8array";case"Uint8ClampedArray":return"uint8clampedarray";case"Int16Array":return"int16array";case"Uint16Array":return"uint16array";case"Int32Array":return"int32array";case"Uint32Array":return"uint32array";case"Float32Array":return"float32array";case"Float64Array":return"float64array"}if(function(t){return"function"==typeof t.throw&&"function"==typeof t.return&&"function"==typeof t.next}(r))return"generator";switch(a=e.call(r)){case"[object Object]":return"object";case"[object Map Iterator]":return"mapiterator";case"[object Set Iterator]":return"setiterator";case"[object String Iterator]":return"stringiterator";case"[object Array Iterator]":return"arrayiterator"}return a.slice(8,-1).toLowerCase().replace(/\s/g,"")};function o(t){return t.constructor?t.constructor.name:null}function a(t,e){var o=2<arguments.length&&void 0!==arguments[2]?arguments[2]:["option"];return n(t,e,o),i(t,e,o),function(t,e,o){var s=r(e),l=r(t);if("object"===s){if("object"!==l)throw new Error("[Type Error]: '".concat(o.join("."),"' require 'object' type, but got '").concat(l,"'"));Object.keys(e).forEach(function(r){var s=t[r],l=e[r],c=o.slice();c.push(r),n(s,l,c),i(s,l,c),a(s,l,c)})}if("array"===s){if("array"!==l)throw new Error("[Type Error]: '".concat(o.join("."),"' require 'array' type, but got '").concat(l,"'"));t.forEach(function(r,s){var l=t[s],c=e[s]||e[0],d=o.slice();d.push(s),n(l,c,d),i(l,c,d),a(l,c,d)})}}(t,e,o),t}function n(t,e,o){if("string"===r(e)){var a=r(t);if("?"===e[0]&&(e=e.slice(1)+"|undefined"),!(-1<e.indexOf("|")?e.split("|").map(function(t){return t.toLowerCase().trim()}).filter(Boolean).some(function(t){return a===t}):e.toLowerCase().trim()===a))throw new Error("[Type Error]: '".concat(o.join("."),"' require '").concat(e,"' type, but got '").concat(a,"'"))}}function i(t,e,o){if("function"===r(e)){var a=e(t,r(t),o);if(!0!==a){var n=r(a);throw"string"===n?new Error(a):"error"===n?a:new Error("[Validator Error]: The scheme for '".concat(o.join("."),"' validator require return true, but got '").concat(a,"'"))}}}return a.kindOf=r,a}()),r.exports);const a=t(o),n="5.4.1",i={properties:["audioTracks","autoplay","buffered","controller","controls","crossOrigin","currentSrc","currentTime","defaultMuted","defaultPlaybackRate","duration","ended","error","loop","mediaGroup","muted","networkState","paused","playbackRate","played","preload","readyState","seekable","seeking","src","startDate","textTracks","videoTracks","volume"],methods:["addTextTrack","canPlayType","load","play","pause"],events:["abort","canplay","canplaythrough","durationchange","emptied","ended","error","loadeddata","loadedmetadata","loadstart","pause","play","playing","progress","ratechange","seeked","seeking","stalled","suspend","timeupdate","volumechange","waiting"],prototypes:["width","height","videoWidth","videoHeight","poster","webkitDecodedFrameCount","webkitDroppedFrameCount","playsInline","webLine truncated
|
||||
+1
-1
@@ -4,4 +4,4 @@
|
||||
* (c) 2017-2026 Harvey Zhao
|
||||
* Released under the MIT License.
|
||||
*/
|
||||
!function(t,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"==typeof define&&define.amd?(t.Artplayer=e(),define(function(){return t.Artplayer})):(t="undefined"!==typeof globalThis?globalThis:t||self).Artplayer=e()}(this,function(){"use strict";var t,e=Object.defineProperty,r=Object.defineProperties,o=Object.getOwnPropertyDescriptors,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
@@ -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", {
|
||||
|
||||
@@ -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 })
|
||||
}
|
||||
@@ -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
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -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
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -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)
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,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
@@ -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
@@ -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
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,40 @@
|
||||
# CORE-10 播放与时间/状态属性
|
||||
|
||||
状态:完成;本地源码、产物、安装消费和三浏览器检查通过。
|
||||
|
||||
## 范围与兼容决策
|
||||
|
||||
迁移 toggle/currentTime/seek/volume/playbackRate/played/loaded/state 八个 mixin,增加 media/playback.ts 最小宿主类型;play/pause/playing/duration 已在 CORE-06/09 迁移,本任务继续复验。显示模式实现归 CORE-16,媒体转发归 CORE-11,剩余通用 attr/type 门面归 CORE-20。
|
||||
|
||||
保留 API-02/03/04/07:toggle 精确返回当前 play/pause 分支结果;currentTime 保持 parseFloat/NaN 忽略/范围限制;seek 保留原输入事件参数;forward 的 JS 加法与 backward 的减法不互换;音量按原 parseInt 百分比格式且非零才持久化;速率假值重置 1、相同值不刷新通知。played/loaded 在未加载或零时长时可能 NaN/Infinity,不能为了测试方便改成 0。保留捕获媒体引用和 duration 动态读取的差别。
|
||||
|
||||
state getter 优先 mini、pip、fullscreen、fullscreenWeb;setter 仅退出其他状态,不主动进入指定状态。未把它变成新的显示模式状态机。
|
||||
|
||||
## 类型策略
|
||||
|
||||
新增纯类型 PlaybackControls,现有 art 可直接赋给该视图,无转换/代理对象;toggle 返回 Promise<void> | void。原 art.toggle(): void 声明保留,使旧合法赋值继续编译。此为 BASE-TYPE-04 的 toggle 分支兼容方案,plugins.add 及总体公开门面仍由 CORE-21 收口。
|
||||
|
||||
发现 BASE-TYPE-08:seek/forward/backward/switch/quality 的运行时描述符只有 setter,读取 undefined;旧公共声明却有数字、字符串或数组 getter。源接口按实际读取值建模,旧公开读取声明暂保留并交 CORE-21,不能凭添加 getter 来伪造兼容。
|
||||
|
||||
## 验证与回退
|
||||
|
||||
完整证据见 [playback-properties-validation.json](../baselines/playback-properties-validation.json),含源码、声明、测试、生成编辑器声明和安装包指纹,以及逐项浏览器结果。
|
||||
|
||||
| 检查 | 结果 |
|
||||
| --- | --- |
|
||||
| yarn ci:check | 145 项:116 单元、4 工程、25 基线;47 个生产 TS 文件严格编译 |
|
||||
| 属性专项 | 新增 8 项 Node,覆盖转换、通知、存储、范围、描述符、状态和引用 |
|
||||
| 安装包消费 | 核心/chapter 的 27 项运行时通过,五组类型模式零诊断 |
|
||||
| UMD 三浏览器 | 195 项通过,无跳过、重试或 flaky |
|
||||
| legacy 三浏览器 | 195 项通过,无跳过、重试或 flaky |
|
||||
| 生成声明 | build:ts 重新生成编辑器声明;PlaybackControls 命名导出进入 CJS/ESM/共享类型入口 |
|
||||
|
||||
测试入口:test/playback-properties.test.js、test/types/playback-properties.ts、test/types/playback-public.ts、test/browser/playback-properties.spec.js;既有 play/source/canvas 及发布基线全部继续通过。新旧类型消费同时包含旧 toggle:void、命令属性读取赋值和新 PlaybackControls 分支。
|
||||
|
||||
没有执行实体设备、远端 CI、完整 fullscreen/PiP 或全部生态包发布验收。本任务不关闭 BASE-TYPE-04/08 的剩余公共门面工作。
|
||||
|
||||
专项浏览器首轮 WebKit 的旧/新版本均在 seek 后出现空 buffered 区间,说明“此时必须大于 0”的测试假设不成立。改为与同一媒体的原始 TimeRanges 最后一个区间精确对照,保留空区间检查;非空/多区间公式由专项单元测试固定验证。未修改实现或添加等待来隐藏差异。
|
||||
|
||||
完整检查另发现风险台账的负向测试依赖“第一项风险尚未关闭”。CORE-09 关闭该项后,重复设置 resolved 已不是非法操作。测试现在显式移除关闭证据,继续验证无证据关闭必定失败,不再依赖台账顺序或当前完成程度。
|
||||
|
||||
未新增依赖或改变锁文件/版本。任务结束单独本地 commit。需要回退时整体回退源码、声明、生成产物和文档;不单独删除类型桥接或源宿主文件。
|
||||
+4
-3
@@ -4,7 +4,7 @@
|
||||
|
||||
基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 214 项,范围 22 个包及工作区/示例。
|
||||
|
||||
状态:todo 169 / doing 0 / blocked 0 / done 45 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
|
||||
状态:todo 168 / doing 0 / blocked 0 / done 46 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
|
||||
|
||||
前置依赖是启动条件;验收是完成条件。任务可以继续拆分,但不能复用或悄悄删除旧 ID。
|
||||
|
||||
@@ -113,7 +113,7 @@
|
||||
| CORE-07 | artplayer<br>逐项协调公开声明差异 | CORE-02, CORE-06, BASE-07 | plugins/toggle/cue/setting/static 的旧新类型对照 | 每项有兼容重载/扩展或待决结论,不能删除旧类型样例;处理 BASE-TYPE-01/04,分别保留旧合法声明消费并验证真实返回;协调 BASE-TYPE-05 的 Utils 导出、descriptor 和 timer 旧声明差异 | H | done |
|
||||
| CORE-08 | artplayer<br>整理插件管理器与扩展类型 | CORE-03, CORE-07 | 注册流程、同步/异步结果、插件/事件扩展接口 | 旧插件同步可见性、重名与命名、销毁中异步插件保持明确语义;处理 baselines/lifecycle-coverage.md 的 BASE-LIFE-03 并增加候选正向回归;接续 CORE-07 / BASE-TYPE-04 的同步/异步注册返回声明冲突,内部返回类型精确,保留历史消费者 | H | done |
|
||||
| CORE-09 | artplayer<br>整理 URL 与切源操作 | CORE-04, CORE-06, CORE-08 | url/switch/customType 的操作身份及取消方案 | 并发切源、错误、同 URL、销毁和 Promise 结算有契约;处理 baselines/lifecycle-coverage.md 的 BASE-LIFE-01/02/06 并增加候选正向回归 | H | done |
|
||||
| CORE-10 | artplayer<br>迁移播放与时间/状态属性 | CORE-09 | play/pause/toggle/seek/volume/rate/duration 等模块 | 旧返回值、拒绝、mutex、位置恢复、属性描述符与事件通过;接续 CORE-07 / BASE-TYPE-04 的 toggle void 与实际 Promise 分支冲突,不吞拒绝 | H | todo |
|
||||
| CORE-10 | artplayer<br>迁移播放与时间/状态属性 | CORE-09 | play/pause/toggle/seek/volume/rate/duration 等模块 | 旧返回值、拒绝、mutex、位置恢复、属性描述符与事件通过;接续 CORE-07 / BASE-TYPE-04 的 toggle void 与实际 Promise 分支冲突,不吞拒绝 | H | done |
|
||||
| CORE-11 | artplayer<br>整理媒体事件、ready 与重连 | CORE-10 | 媒体转发、重连、UI 响应职责分离 | readiness 次数/顺序、过期重连、原错误参数有回归 | H | todo |
|
||||
| CORE-12 | artplayer<br>迁移模板及公开资源入口 | CORE-06, CORE-04, BASE-04 | template/icons/i18n/style、SSR 与 proxy 挂载 | DOM/CSS/global/样式注入、i18n 子路径及 SSR import 保持;不把 SSR import/useSSR 保持扩大为服务端构造播放器;复跑 BASE-05 的 SSR import/模板、浏览器 useSSR 节点复用和非浏览器构造错误;接续 VENDOR-01/02,核对 screenfull/hint 复制版本、修改与分发通知,保留 DOM/CSS/fullscreen 行为 | H | todo |
|
||||
| CORE-13 | artplayer<br>迁移组件注册与控件 | CORE-12, CORE-08 | Component/control/layer/contextmenu 的资源与 TS 边界 | add/update/remove、name、selector、mounted/beforeUnmount 通过;处理 BASE-DOM-02 窄容器控件裁切,保留 controls.update 的旧 undefined 返回值 | H | todo |
|
||||
@@ -124,7 +124,7 @@
|
||||
| CORE-18 | artplayer<br>迁移内置插件和提示模块 | CORE-14, CORE-16, CORE-17 | autoPlayback/autoOrientation/fastForward/lock/miniProgressBar 及 info/notice/loading/mask | 默认启用条件、storage 格式、定时器/动画清理通过;结合 BASE-PERF-01 防止销毁后重新安排 notice timer;接续 BASE-TYPE-07 的 notice.show boolean getter 与历史读类型冲突 | M | todo |
|
||||
| CORE-19 | artplayer<br>整理进度、质量、缩略图和截图 | CORE-10, CORE-13, CORE-16 | 相关 player/control 功能与纯计算分离 | 截图跨域失败、缩略图布局、quality/进度边界保持 | M | todo |
|
||||
| CORE-20 | artplayer<br>收敛核心入口与依赖方向 | CORE-14, CORE-15, CORE-18, CORE-19 | Artplayer 门面、初始化依赖与最终目录 | 所有核心自有源码迁移,公开描述符/静态接口/事件差分通过 | H | todo |
|
||||
| CORE-21 | artplayer<br>生成核心声明并校验包内容 | CORE-20, ENG-07 | 由 TS 生成的既有入口声明与三种发布产物 | 内部类型不泄漏,旧 JS/TS 消费者与 i18n 通过;逐项完成 CORE-07 留下的 BASE-TYPE-04/05/07 声明方案,旧合法消费者与实际返回分别留证;不能以虚假的交叉返回类型或改变运行时来掩盖冲突,未决项阻止核心声明出口 | H | todo |
|
||||
| CORE-21 | artplayer<br>生成核心声明并校验包内容 | CORE-20, ENG-07 | 由 TS 生成的既有入口声明与三种发布产物 | 内部类型不泄漏,旧 JS/TS 消费者与 i18n 通过;逐项完成 CORE-07 留下的 BASE-TYPE-04/05/07 声明方案,旧合法消费者与实际返回分别留证;不能以虚假的交叉返回类型或改变运行时来掩盖冲突,未决项阻止核心声明出口;处理 CORE-10 / BASE-TYPE-08 的 setter-only 属性虚构 getter 声明,并复核 PlaybackControls 精确视图与旧 toggle 声明共存策略 | H | todo |
|
||||
| CORE-23 | artplayer<br>检查并补齐键盘、焦点与可访问名称 | CORE-13, CORE-14, CORE-17, ENG-05, BASE-04 | 主要控件/设置/模式退出的键盘与焦点回归、名称和字幕可用性检查及必要兼容修正 | 保持旧快捷键和 DOM/CSS 钩子;真实浏览器验证,不以静态属性检查代替交互;处理 BASE-DOM-01 的主要控件 Tab 不可达,保留既有名称与快捷键 | H | todo |
|
||||
| CORE-22 | artplayer<br>核心阶段完整验收 | CORE-21, CORE-23, PILOT-01, ENG-08, ENG-10 | 核心与旧插件的可自动化完整回归、资源/性能及明确外部验证缺口 | 核心自动化和公开差异处置通过;真实环境缺口链接包集成/REL 门槛,阶段完成不代表可公开发布 | H | todo |
|
||||
|
||||
@@ -444,6 +444,7 @@
|
||||
- CORE-07: [记录](changes/2026-09-11-CORE-07-declarations.md) [记录](baselines/declarations-validation.json)
|
||||
- CORE-08: [记录](changes/2026-09-11-CORE-08-plugin-manager.md) [记录](baselines/plugins-validation.json)
|
||||
- CORE-09: [记录](changes/2026-09-11-CORE-09-source-operations.md) [记录](baselines/source-operations-validation.json)
|
||||
- CORE-10: [记录](changes/2026-09-11-CORE-10-playback-properties.md) [记录](baselines/playback-properties-validation.json)
|
||||
- PKG-CHAPTER-01: [记录](changes/2026-09-10-PKG-CHAPTER-01-contract.md) [记录](baselines/chapter-contract.md)
|
||||
- PKG-CHAPTER-02: [记录](changes/2026-09-10-PKG-CHAPTER-02-tests.md) [记录](baselines/chapter-validation.json)
|
||||
- PKG-CHAPTER-03: [记录](changes/2026-09-10-PKG-CHAPTER-03-typescript-modules.md) [记录](baselines/chapter-migration-validation.json)
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# 进度与证据
|
||||
|
||||
## 当前实施:CORE-10 播放与时间/状态属性完成
|
||||
|
||||
八个 mixin 迁移 TS,新增最小播放属性宿主;保持转换、通知、存储、进度和状态优先级。新增 PlaybackControls 精确视图,旧 toggle 声明仍兼容。145 项 Node/基线、安装 UMD 与 legacy 各 195 项三浏览器、打包 27 项运行时和五组类型检查通过。见 [交付](changes/2026-09-11-CORE-10-playback-properties.md)。发现 BASE-TYPE-08 的只写属性虚构 getter 声明,已交 CORE-21;没有添加运行时 getter 来掩盖它。当前 214 项,46 完成、168 待办;核心 42 个 TS 文件、chapter 5 个。下一项 CORE-11 媒体事件、ready 与重连。
|
||||
|
||||
## 当前实施:CORE-09 URL 与切源操作完成
|
||||
|
||||
URL/switch 门面迁移 TS,新增四个 source 模块,统一切源取消、事件清理、同步代理事件和 Promise 结算;真实 playMix 同步接入源身份,避免晚到播放覆盖新状态。137 项 Node/基线、安装 UMD 与 legacy 各 183 项三浏览器检查、打包 27 项运行时和五组类型检查通过。见 [交付](changes/2026-09-11-CORE-09-source-operations.md)。关闭 BASE-LIFE-01/02/06;customType 外部 SDK 清理、对象 URL 归属和过期重连仍由对应后续任务处理。当前 214 项,45 完成、169 待办;核心 33 个 TS 文件、chapter 5 个。下一项 CORE-10 播放与时间/状态属性。
|
||||
|
||||
@@ -51,3 +51,4 @@
|
||||
| EVENT-ONCE-01 | resolved / 已复现 | Nested dispatch snapshots invoke the same once registration twice | CORE-02 |
|
||||
| BASE-TYPE-06 | resolved / 已复现 | Public Option requires url and excludes numeric control HTML accepted by existing JS | CORE-07 |
|
||||
| BASE-TYPE-07 | open / 已复现 | Setting returns, subtitle contextual payloads and notice getter conflict with historical declarations | CORE-07, CORE-14, CORE-15, CORE-18, CORE-21 |
|
||||
| BASE-TYPE-08 | open / 已复现 | Write-only player commands have fictitious public getter declarations | CORE-21 |
|
||||
+21
-1
@@ -281,7 +281,8 @@
|
||||
],
|
||||
"compatibleResolution": "保留旧声明接受范围并协调精确重载/扩展,不静默把同步注册改成 async。",
|
||||
"closureCriteria": "返回值类型与运行时差异逐项说明,旧合法消费和拒绝传播回归通过。",
|
||||
"scopeNote": "CORE-07 retains historical legal return assignments and records actual runtime separately; incompatible return tightening remains open through CORE-21. No release waiver."
|
||||
"scopeNote": "CORE-07 retains historical legal return assignments and records actual runtime separately; incompatible return tightening remains open through CORE-21. No release waiver.",
|
||||
"playbackResolution": "CORE-10 adds assignable PlaybackControls with Promise<void> | void toggle; legacy root void remains accepted. All five installed type modes pass. The overall facade and plugins.add conflict stay open through CORE-21."
|
||||
},
|
||||
{
|
||||
"id": "BASE-DIST-01",
|
||||
@@ -871,6 +872,25 @@
|
||||
"compatibleResolution": "Keep legacy consumers; add explicit cue-array listener overloads, type internal implementations accurately, and resolve remaining generated/public facade contracts at CORE-21 without false intersections.",
|
||||
"closureCriteria": "Actual setting/null/void and notice boolean returns, cue array callbacks and old legal type consumers all have an explicit compatible declaration strategy and package tests.",
|
||||
"scopeNote": "CORE-07 provides runtime evidence and explicit cue-array overloads; old inferred scalar event callbacks and return signatures are not claimed corrected."
|
||||
},
|
||||
{
|
||||
"id": "BASE-TYPE-08",
|
||||
"title": "Write-only player commands have fictitious public getter declarations",
|
||||
"confirmation": "reproduced",
|
||||
"status": "open",
|
||||
"owners": [
|
||||
"CORE-21"
|
||||
],
|
||||
"evidence": [
|
||||
"packages/artplayer/types/player.d.ts",
|
||||
"test/playback-properties.test.js",
|
||||
"test/browser/playback-properties.spec.js",
|
||||
"refactor/changes/2026-09-11-CORE-10-playback-properties.md",
|
||||
"refactor/baselines/playback-properties-validation.json",
|
||||
"test/types/declaration-legacy.ts"
|
||||
],
|
||||
"compatibleResolution": "Keep historical consumer acceptance, model source command reads as undefined, and provide an accurate public facade without inventing runtime getters.",
|
||||
"closureCriteria": "seek/forward/backward/switch/quality reads and writes have an explicit accurate compatibility strategy validated against old TypeScript consumers and real package descriptors."
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -13,7 +13,10 @@ test('Risk register rejects omitted findings, missing owners and unsupported clo
|
||||
for (const mutate of [
|
||||
value => value.items = value.items.filter(item => item.id !== 'BASE-LIFE-04'),
|
||||
value => value.items[0].owners = ['NO-SUCH-TASK'],
|
||||
value => value.items[0].status = 'resolved',
|
||||
(value) => {
|
||||
value.items[0].status = 'resolved'
|
||||
delete value.items[0].resolutionEvidence
|
||||
},
|
||||
value => value.items[0].evidence = ['../outside-workspace'],
|
||||
]) {
|
||||
const changed = structuredClone(register)
|
||||
|
||||
+6
-3
@@ -930,11 +930,14 @@
|
||||
"dependsOn": [
|
||||
"CORE-09"
|
||||
],
|
||||
"status": "todo",
|
||||
"status": "done",
|
||||
"risk": "H",
|
||||
"deliverable": "play/pause/toggle/seek/volume/rate/duration 等模块",
|
||||
"acceptance": "旧返回值、拒绝、mutex、位置恢复、属性描述符与事件通过;接续 CORE-07 / BASE-TYPE-04 的 toggle void 与实际 Promise 分支冲突,不吞拒绝",
|
||||
"evidence": []
|
||||
"evidence": [
|
||||
"changes/2026-09-11-CORE-10-playback-properties.md",
|
||||
"baselines/playback-properties-validation.json"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "CORE-11",
|
||||
@@ -1123,7 +1126,7 @@
|
||||
"status": "todo",
|
||||
"risk": "H",
|
||||
"deliverable": "由 TS 生成的既有入口声明与三种发布产物",
|
||||
"acceptance": "内部类型不泄漏,旧 JS/TS 消费者与 i18n 通过;逐项完成 CORE-07 留下的 BASE-TYPE-04/05/07 声明方案,旧合法消费者与实际返回分别留证;不能以虚假的交叉返回类型或改变运行时来掩盖冲突,未决项阻止核心声明出口",
|
||||
"acceptance": "内部类型不泄漏,旧 JS/TS 消费者与 i18n 通过;逐项完成 CORE-07 留下的 BASE-TYPE-04/05/07 声明方案,旧合法消费者与实际返回分别留证;不能以虚假的交叉返回类型或改变运行时来掩盖冲突,未决项阻止核心声明出口;处理 CORE-10 / BASE-TYPE-08 的 setter-only 属性虚构 getter 声明,并复核 PlaybackControls 精确视图与旧 toggle 声明共存策略",
|
||||
"evidence": []
|
||||
},
|
||||
{
|
||||
|
||||
@@ -38,7 +38,7 @@ export function typeConsumers(dir) {
|
||||
for (const [compiler, mode] of [[ts, 'node10-commonjs'], [ts, 'nodenext-cjs'], [ts, 'bundler-esm'], [compat, 'node10-commonjs'], [ts, 'nodenext-esm']]) {
|
||||
const next = mode.startsWith('nodenext')
|
||||
const extension = next ? (mode.endsWith('-cjs') ? 'cts' : 'mts') : 'ts'
|
||||
const inputs = ['public', 'declaration-inputs', 'declaration-legacy', 'plugins-public', 'chapter-options', 'chapter-exports', 'language-value', 'language', 'legacy-plugin']
|
||||
const inputs = ['public', 'declaration-inputs', 'declaration-legacy', 'plugins-public', 'playback-public', 'chapter-options', 'chapter-exports', 'language-value', 'language', 'legacy-plugin']
|
||||
if (mode === 'nodenext-cjs')
|
||||
inputs.push('commonjs')
|
||||
const files = inputs.map((name) => {
|
||||
|
||||
@@ -71,7 +71,7 @@ export function runTypechecks() {
|
||||
console.log(`Strict project passed: ${relative(config)} (${result.files.length} root files)`)
|
||||
}
|
||||
for (const [compiler, mode] of [[ts, 'node10-commonjs'], [ts, 'nodenext-cjs'], [ts, 'bundler-esm'], [ts, 'nodenext-esm'], [compat, 'node10-commonjs']]) {
|
||||
for (const fixture of ['test/types/public.ts', 'test/types/declaration-inputs.ts', 'test/types/declaration-legacy.ts', 'test/types/plugins-public.ts', 'test/types/chapter-options.ts', 'test/types/chapter-exports.ts', 'test/types/language-value.ts', 'refactor/fixtures/consumers/language.ts', 'refactor/fixtures/consumers/legacy-plugin.ts']) {
|
||||
for (const fixture of ['test/types/public.ts', 'test/types/declaration-inputs.ts', 'test/types/declaration-legacy.ts', 'test/types/plugins-public.ts', 'test/types/playback-public.ts', 'test/types/chapter-options.ts', 'test/types/chapter-exports.ts', 'test/types/language-value.ts', 'refactor/fixtures/consumers/language.ts', 'refactor/fixtures/consumers/legacy-plugin.ts']) {
|
||||
assert.deepEqual(checkConsumer(compiler, mode, fs.readFileSync(path.join(root, fixture), 'utf8')), [], `Consumer failed: TS ${compiler.version} ${mode} ${fixture}`)
|
||||
}
|
||||
if (mode === 'nodenext-cjs')
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
import { expect, test } from './fixtures.js'
|
||||
|
||||
for (const core of ['published', 'candidate']) {
|
||||
test(`${core}: real media preserves timeline, volume, rate and property descriptors`, async ({ page }) => {
|
||||
await page.goto(`/test/player.html?core=${core}&chapter=published`)
|
||||
await page.evaluate(() => window.createPlayer('/test/pattern.mp4?properties=media'))
|
||||
await expect.poll(() => page.evaluate(() => window.art.isReady)).toBe(true)
|
||||
const result = await page.evaluate(() => {
|
||||
const { art } = window
|
||||
const seeks = []
|
||||
const muted = []
|
||||
art.on('seek', (...args) => seeks.push(args))
|
||||
art.on('muted', value => muted.push(value))
|
||||
art.seek = '2.5 seconds'
|
||||
const seekNotice = art.template.$noticeInner.textContent
|
||||
art.forward = 1
|
||||
art.backward = 0.5
|
||||
art.volume = 0.42
|
||||
const volumeNotice = art.template.$noticeInner.textContent
|
||||
art.muted = 'truthy'
|
||||
art.playbackRate = 1.5
|
||||
const rateNotice = art.template.$noticeInner.textContent
|
||||
art.playbackRate = 0
|
||||
const resetNotice = art.template.$noticeInner.textContent
|
||||
const descriptors = Object.fromEntries(['currentTime', 'seek', 'forward', 'backward', 'volume', 'muted', 'playbackRate', 'loaded', 'loadedTime', 'played', 'state'].map((name) => {
|
||||
const descriptor = Object.getOwnPropertyDescriptor(art, name)
|
||||
return [name, { getter: typeof descriptor.get === 'function', setter: typeof descriptor.set === 'function', enumerable: descriptor.enumerable, configurable: descriptor.configurable }]
|
||||
}))
|
||||
const unreadable = Object.fromEntries(['seek', 'forward', 'backward', 'switch', 'quality'].map(name => [name, art[name] === undefined]))
|
||||
const buffered = Array.from({ length: art.video.buffered.length }, (_, index) => art.video.buffered.end(index))
|
||||
const result = { seeks, muted, time: art.currentTime, volume: art.volume, mediaMuted: art.video.muted, rate: art.playbackRate, seekNotice, volumeNotice, rateNotice, resetNotice, descriptors, unreadable, played: art.played, duration: art.duration, buffered, loadedTime: art.loadedTime, loaded: art.loaded, error: art.video.error?.code || 0 }
|
||||
art.destroy()
|
||||
return result
|
||||
})
|
||||
expect(result.seeks).toEqual([[2.5, '2.5 seconds'], [3.5, 3.5], [3, 3]])
|
||||
expect(result.muted).toEqual(['truthy'])
|
||||
expect(result.time).toBeCloseTo(3, 2)
|
||||
expect(result.volume).toBeCloseTo(0.42, 6)
|
||||
expect(result.mediaMuted).toBe(true)
|
||||
expect(result.rate).toBe(1)
|
||||
expect(result.seekNotice).toBe('00:02 / 00:08')
|
||||
expect(result.volumeNotice).toBe('Volume: 42')
|
||||
expect(result.rateNotice).toBe('Rate: 1.5x')
|
||||
expect(result.resetNotice).toBe('Rate: Normal')
|
||||
expect(result.played).toBeCloseTo(3 / result.duration, 6)
|
||||
// WebKit may empty its buffered ranges during seek; compare the actual media surface.
|
||||
expect(result.loadedTime).toBe(result.buffered.at(-1) || 0)
|
||||
expect(result.loaded).toBeCloseTo(result.loadedTime / result.duration, 6)
|
||||
expect(result.error).toBe(0)
|
||||
expect(Object.values(result.unreadable)).toEqual([true, true, true, true, true])
|
||||
for (const [name, descriptor] of Object.entries(result.descriptors)) {
|
||||
expect(descriptor).toEqual({
|
||||
getter: !['seek', 'forward', 'backward'].includes(name),
|
||||
setter: !['loaded', 'loadedTime', 'played'].includes(name),
|
||||
enumerable: false,
|
||||
configurable: false,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
test(`${core}: invalid time inputs and rate no-op retain native behavior`, async ({ page }) => {
|
||||
await page.goto(`/test/player.html?core=${core}&chapter=published`)
|
||||
await page.evaluate(() => window.createPlayer('/test/pattern.mp4?properties=invalid'))
|
||||
await expect.poll(() => page.evaluate(() => window.art.isReady)).toBe(true)
|
||||
const result = await page.evaluate(() => {
|
||||
const { art } = window
|
||||
art.currentTime = 2
|
||||
const times = []
|
||||
for (const input of [Number.NaN, undefined, null, '', 'invalid']) {
|
||||
art.currentTime = input
|
||||
times.push(art.currentTime)
|
||||
}
|
||||
art.notice.show = 'keep'
|
||||
art.playbackRate = 1
|
||||
art.playbackRate = Number.NaN
|
||||
const result = { times, rate: art.playbackRate, notice: art.template.$noticeInner.textContent, noRuntimeType: window.Artplayer.PlaybackControls === undefined }
|
||||
art.destroy()
|
||||
return result
|
||||
})
|
||||
expect(result).toEqual({ times: [2, 2, 2, 2, 2], rate: 1, notice: 'keep', noRuntimeType: true })
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
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 } from './helpers/load.js'
|
||||
|
||||
const names = ['currentTimeMix', 'durationMix', 'seekMix', 'volumeMix', 'playbackRateMix', 'playedMix', 'loadedMix', 'stateMix', 'toggleMix']
|
||||
const mixins = await loadModules(Object.fromEntries(names.map(name => [name, `packages/artplayer/src/player/${name}`])))
|
||||
|
||||
function createArt() {
|
||||
const stored = []
|
||||
const emitted = []
|
||||
const video = { currentTime: 0, duration: 100, volume: 0.5, muted: false, playbackRate: 1, buffered: { length: 0, end() {
|
||||
throw new Error('empty')
|
||||
} } }
|
||||
const art = {
|
||||
template: { $video: video },
|
||||
notice: { show: '' },
|
||||
i18n: { get: key => key },
|
||||
storage: { set: (...args) => stored.push(args) },
|
||||
emit: (...args) => emitted.push(args),
|
||||
mini: false,
|
||||
pip: false,
|
||||
fullscreen: false,
|
||||
fullscreenWeb: false,
|
||||
}
|
||||
for (const name of names.filter(name => name !== 'toggleMix'))
|
||||
mixins[name](art)
|
||||
return { art, video, stored, emitted }
|
||||
}
|
||||
|
||||
test('currentTime keeps parseFloat coercion, clamps and ignores NaN', () => {
|
||||
const { art, video } = createArt()
|
||||
for (const [input, expected] of [['12.5 seconds', 12.5], [-2, 0], [200, 100], [Infinity, 100], [-Infinity, 0]]) {
|
||||
art.currentTime = input
|
||||
assert.equal(video.currentTime, expected)
|
||||
}
|
||||
art.currentTime = 7
|
||||
for (const input of [Number.NaN, undefined, null, '', 'invalid']) {
|
||||
art.currentTime = input
|
||||
assert.equal(art.currentTime, 7)
|
||||
}
|
||||
let coerced = 0
|
||||
art.currentTime = { toString() {
|
||||
coerced++
|
||||
return '22s'
|
||||
} }
|
||||
assert.equal(art.currentTime, 22)
|
||||
assert.equal(coerced, 1)
|
||||
assert.throws(() => art.currentTime = Symbol('time'), TypeError)
|
||||
})
|
||||
|
||||
test('seek preserves original payload, notification order and forward/backward coercion', () => {
|
||||
const { art, emitted } = createArt()
|
||||
art.seek = '12.5 seconds'
|
||||
assert.equal(art.currentTime, 12.5)
|
||||
assert.equal(art.notice.show, '00:12 / 01:40')
|
||||
assert.deepEqual(emitted.at(-1), ['seek', 12.5, '12.5 seconds'])
|
||||
art.currentTime = 10
|
||||
art.forward = '2'
|
||||
assert.deepEqual(emitted.at(-1), ['seek', 100, '102'])
|
||||
art.backward = '2'
|
||||
assert.deepEqual(emitted.at(-1), ['seek', 98, 98])
|
||||
for (const name of ['seek', 'forward', 'backward']) {
|
||||
assert.equal(art[name], undefined)
|
||||
const descriptor = Object.getOwnPropertyDescriptor(art, name)
|
||||
assert.equal(descriptor.get, undefined)
|
||||
assert.equal(descriptor.enumerable, false)
|
||||
assert.equal(descriptor.configurable, false)
|
||||
}
|
||||
})
|
||||
|
||||
test('volume uses actual assigned media volume, historical percentage formatting and nonzero persistence', () => {
|
||||
const { art, video, stored, emitted } = createArt()
|
||||
art.volume = 2
|
||||
assert.equal(video.volume, 1)
|
||||
assert.equal(art.notice.show, 'Volume: 100')
|
||||
art.volume = -1
|
||||
assert.equal(video.volume, 0)
|
||||
assert.deepEqual(stored, [['volume', 1]])
|
||||
art.volume = 1e-9
|
||||
assert.equal(art.notice.show, 'Volume: 1')
|
||||
assert.deepEqual(stored.at(-1), ['volume', 1e-9])
|
||||
art.muted = 'legacy-truthy'
|
||||
assert.equal(video.muted, 'legacy-truthy')
|
||||
assert.deepEqual(emitted.at(-1), ['muted', 'legacy-truthy'])
|
||||
})
|
||||
|
||||
test('playbackRate preserves equal-value no-op, falsy normalization and native errors', () => {
|
||||
const { art, video } = createArt()
|
||||
art.notice.show = 'keep'
|
||||
art.playbackRate = 1
|
||||
assert.equal(art.notice.show, 'keep')
|
||||
art.playbackRate = 1.5
|
||||
assert.equal(art.notice.show, 'Rate: 1.5x')
|
||||
art.playbackRate = 0
|
||||
assert.equal(video.playbackRate, 1)
|
||||
assert.equal(art.notice.show, 'Rate: Normal')
|
||||
art.notice.show = 'keep again'
|
||||
art.playbackRate = Number.NaN
|
||||
assert.equal(art.notice.show, 'keep again')
|
||||
const error = new Error('native rate rejected')
|
||||
Object.defineProperty(video, 'playbackRate', { get: () => 1, set() {
|
||||
throw error
|
||||
} })
|
||||
assert.throws(() => art.playbackRate = -1, actual => actual === error)
|
||||
})
|
||||
|
||||
test('progress getters keep raw ratios and the last buffered range', () => {
|
||||
const { art, video } = createArt()
|
||||
assert.equal(art.loadedTime, 0)
|
||||
video.buffered = { length: 2, end: index => [3, 40][index] }
|
||||
assert.equal(art.loadedTime, 40)
|
||||
assert.equal(art.loaded, 0.4)
|
||||
art.currentTime = 25
|
||||
assert.equal(art.played, 0.25)
|
||||
video.duration = Infinity
|
||||
assert.equal(art.duration, 0)
|
||||
assert.equal(art.played, Infinity)
|
||||
assert.equal(art.loaded, 0)
|
||||
video.currentTime = 0
|
||||
video.duration = 0
|
||||
video.buffered = { length: 0 }
|
||||
assert(Number.isNaN(art.played))
|
||||
assert(Number.isNaN(art.loaded))
|
||||
})
|
||||
|
||||
test('state prioritizes mini/pip/fullscreen/web and only disables other states', () => {
|
||||
const { art } = createArt()
|
||||
assert.equal(art.state, 'standard')
|
||||
art.mini = art.pip = art.fullscreen = art.fullscreenWeb = true
|
||||
assert.equal(art.state, 'mini')
|
||||
art.state = 'fullscreen'
|
||||
assert.deepEqual([art.mini, art.pip, art.fullscreen, art.fullscreenWeb], [false, false, true, false])
|
||||
assert.equal(art.state, 'fullscreen')
|
||||
art.state = 'unknown'
|
||||
assert.equal(art.state, 'standard')
|
||||
art.state = 'pip'
|
||||
assert.equal(art.pip, false)
|
||||
})
|
||||
|
||||
test('captured media attributes remain captured while duration follows the current template', () => {
|
||||
const { art, video } = createArt()
|
||||
art.template.$video = { currentTime: 88, duration: 200, volume: 0.7, muted: true, playbackRate: 2, buffered: { length: 0 } }
|
||||
assert.equal(art.duration, 200)
|
||||
art.currentTime = 150
|
||||
assert.equal(video.currentTime, 150)
|
||||
assert.equal(art.template.$video.currentTime, 88)
|
||||
assert.equal(art.volume, 0.5)
|
||||
assert.equal(art.muted, false)
|
||||
assert.equal(art.playbackRate, 1)
|
||||
video.buffered = { length: 1, end: () => 50 }
|
||||
assert.equal(art.loaded, 0.5)
|
||||
assert.equal(art.played, 0.75)
|
||||
})
|
||||
|
||||
test('toggle returns exact dynamic branch results and preserves extracted function binding', async () => {
|
||||
const pending = Promise.resolve(42)
|
||||
const calls = []
|
||||
const art = { playing: false, play() {
|
||||
calls.push(this)
|
||||
return pending
|
||||
}, pause() {
|
||||
calls.push(this)
|
||||
return 'paused'
|
||||
} }
|
||||
mixins.toggleMix(art)
|
||||
const toggle = art.toggle
|
||||
assert.equal(toggle(), pending)
|
||||
art.playing = true
|
||||
assert.equal(toggle.call({}), 'paused')
|
||||
assert.deepEqual(calls, [art, art])
|
||||
assert.equal(await pending, 42)
|
||||
const descriptor = Object.getOwnPropertyDescriptor(art, 'toggle')
|
||||
assert.deepEqual([descriptor.writable, descriptor.configurable, descriptor.enumerable], [false, false, false])
|
||||
})
|
||||
@@ -13,8 +13,13 @@ const throttled: number = Artplayer.utils.throttle(() => 1, 10)()
|
||||
const descriptor: void = Artplayer.utils.def({}, 'value', { value: 1 })
|
||||
const updated: Artplayer['setting'] = art.setting.update({ name: 'entry', html: 'Entry' })
|
||||
const removed: Artplayer['setting'] = art.setting.remove('entry')
|
||||
const seekRead: number = art.seek
|
||||
const forwardRead: number = art.forward
|
||||
const backwardRead: number = art.backward
|
||||
const switchRead: string = art.switch
|
||||
const qualityRead: Artplayer['quality'] = art.quality
|
||||
art.on('subtitleBeforeUpdate', (cue) => {
|
||||
const historicalCue: VTTCue = cue
|
||||
void historicalCue
|
||||
})
|
||||
void [requiredUrl, registration, toggle, debounced, throttled, descriptor, updated, removed]
|
||||
void [requiredUrl, registration, toggle, debounced, throttled, descriptor, updated, removed, seekRead, forwardRead, backwardRead, switchRead, qualityRead]
|
||||
@@ -0,0 +1,33 @@
|
||||
import currentTimeMix from '../../packages/artplayer/src/player/currentTimeMix'
|
||||
import loadedMix from '../../packages/artplayer/src/player/loadedMix'
|
||||
import seekMix from '../../packages/artplayer/src/player/seekMix'
|
||||
import toggleMix from '../../packages/artplayer/src/player/toggleMix'
|
||||
|
||||
declare const timing: {
|
||||
template: { $video: Pick<HTMLMediaElement, 'currentTime' | 'duration' | 'buffered'> }
|
||||
duration: number
|
||||
notice: { show: string }
|
||||
emit: (name: 'seek', actual: number, requested: number | string) => void
|
||||
}
|
||||
currentTimeMix(timing)
|
||||
timing.currentTime = '12.5 seconds'
|
||||
const time: number = timing.currentTime
|
||||
seekMix(timing)
|
||||
timing.seek = 12
|
||||
const noSeekGetter: undefined = timing.seek
|
||||
const noForwardGetter: undefined = timing.forward
|
||||
loadedMix(timing)
|
||||
const loaded: number = timing.loadedTime
|
||||
void [time, loaded, noSeekGetter, noForwardGetter]
|
||||
// @ts-expect-error Buffered state is computed, not writable.
|
||||
timing.loaded = 0.5
|
||||
// @ts-expect-error Time inputs do not advertise booleans.
|
||||
timing.currentTime = true
|
||||
|
||||
declare const playback: { playing: boolean, play: () => Promise<42>, pause: () => 'paused' }
|
||||
toggleMix(playback)
|
||||
const result: Promise<42> | 'paused' = playback.toggle()
|
||||
void result
|
||||
// @ts-expect-error A toggle preserves either branch, not only the play branch.
|
||||
const invalid: Promise<42> = playback.toggle()
|
||||
void invalid
|
||||
@@ -0,0 +1,19 @@
|
||||
import type Artplayer from 'artplayer'
|
||||
import type { PlaybackControls } from 'artplayer'
|
||||
import type { PlaybackControls as SharedControls } from 'artplayer/types'
|
||||
|
||||
declare const art: Artplayer
|
||||
const controls: PlaybackControls = art
|
||||
const shared: SharedControls = controls
|
||||
const toggled: Promise<void> | void = shared.toggle()
|
||||
if (toggled)
|
||||
toggled.catch(() => {})
|
||||
const played: Promise<void> = controls.play()
|
||||
const paused: void = controls.pause()
|
||||
const historical: void = art.toggle()
|
||||
void [played, paused, historical]
|
||||
// @ts-expect-error A paused toggle does not return a Promise.
|
||||
const alwaysAsync: Promise<void> = controls.toggle()
|
||||
// @ts-expect-error Toggle does not report playback as a boolean.
|
||||
const boolean: boolean = controls.toggle()
|
||||
void [alwaysAsync, boolean]
|
||||
Reference in new issue
Block a user