fix(core): [CORE-24] preserve source playback intent and explicit pauses

This commit is contained in:
Harvey Zhao committed 2026-09-12 03:26:26 +08:00
1 parent b5e96664ee
commit 0218196df9
22 files changed
+12889 -30

No files matched your search

+1 -1
View File
@@ -67,4 +67,4 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
!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 n=(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 o=t(r);if("boolean"===o)return"boolean";if("string"===o)return"string";if("number"===o)return"number";if("symbol"===o)return"symbol";if("function"===o)return"GeneratorFunction"===n(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(n(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(o=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 o.slice(8,-1).toLowerCase().replace(/\s/g,"")};function n(t){return t.constructor?t.constructor.name:null}function o(t,e){var n=2<arguments.length&&void 0!==arguments[2]?arguments[2]:["option"];return i(t,e,n),a(t,e,n),function(t,e,n){var s=r(e),c=r(t);if("object"===s){if("object"!==c)throw new Error("[Type Error]: '".concat(n.join("."),"' require 'object' type, but got '").concat(c,"'"));Object.keys(e).forEach(function(r){var s=t[r],c=e[r],l=n.slice();l.push(r),i(s,c,l),a(s,c,l),o(s,c,l)})}if("array"===s){if("array"!==c)throw new Error("[Type Error]: '".concat(n.join("."),"' require 'array' type, but got '").concat(c,"'"));t.forEach(function(r,s){var c=t[s],l=e[s]||e[0],d=n.slice();d.push(s),i(c,l,d),a(c,l,d),o(c,l,d)})}}(t,e,n),t}function i(t,e,n){if("string"===r(e)){var o=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 o===t}):e.toLowerCase().trim()===o))throw new Error("[Type Error]: '".concat(n.join("."),"' require '").concat(e,"' type, but got '").concat(o,"'"))}}function a(t,e,n){if("function"===r(e)){var o=e(t,r(t),n);if(!0!==o){var i=r(o);throw"string"===i?new Error(o):"error"===i?o:new Error("[Validator Error]: The scheme for '".concat(n.join("."),"' validator require return true, but got '").concat(o,"'"))}}}return o.kindOf=r,o}()),r.exports);const o=t(n),i="5.4.1";function a(t,e){let r=document.getElementById(t);r||(r=document.createElement("style"),r.id=t,"loading"===document.readyState?document.addEventListener("DOMContentLoaded",()=>{document.head.appendChild(r)}):(document.head||document.documentElement).appendChild(r)),r.textContent=e}const s=globalThis.CUSTOM_USER_AGENT??("undefined"===typeof navigator?"":navigator.userAgent),c=/^(?:(?!chrome|android).)*safari/i.test(s),l=/iPad|iPhone|iPod/i.test(s)&&("undefined"===typeof window||!window.MSStream),d=l||s.includes("Macintosh")&&"undefined"!==typeof navigator&&navigator.maxTouchPoints>=1,u=/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(s)||d,p="undefined"!==typeof window&&"undefined"!==typeof document;const f={properties:["audioTracks","autoplay","buffered","controller","controlLine 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 n=(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 o=t(r);if("boolean"===o)return"boolean";if("string"===o)return"string";if("number"===o)return"number";if("symbol"===o)return"symbol";if("function"===o)return"GeneratorFunction"===n(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(n(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(o=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 o.slice(8,-1).toLowerCase().replace(/\s/g,"")};function n(t){return t.constructor?t.constructor.name:null}function o(t,e){var n=2<arguments.length&&void 0!==arguments[2]?arguments[2]:["option"];return i(t,e,n),a(t,e,n),function(t,e,n){var s=r(e),c=r(t);if("object"===s){if("object"!==c)throw new Error("[Type Error]: '".concat(n.join("."),"' require 'object' type, but got '").concat(c,"'"));Object.keys(e).forEach(function(r){var s=t[r],c=e[r],l=n.slice();l.push(r),i(s,c,l),a(s,c,l),o(s,c,l)})}if("array"===s){if("array"!==c)throw new Error("[Type Error]: '".concat(n.join("."),"' require 'array' type, but got '").concat(c,"'"));t.forEach(function(r,s){var c=t[s],l=e[s]||e[0],d=n.slice();d.push(s),i(c,l,d),a(c,l,d),o(c,l,d)})}}(t,e,n),t}function i(t,e,n){if("string"===r(e)){var o=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 o===t}):e.toLowerCase().trim()===o))throw new Error("[Type Error]: '".concat(n.join("."),"' require '").concat(e,"' type, but got '").concat(o,"'"))}}function a(t,e,n){if("function"===r(e)){var o=e(t,r(t),n);if(!0!==o){var i=r(o);throw"string"===i?new Error(o):"error"===i?o:new Error("[Validator Error]: The scheme for '".concat(n.join("."),"' validator require return true, but got '").concat(o,"'"))}}}return o.kindOf=r,o}()),r.exports);const o=t(n),i="5.4.1";function a(t,e){let r=document.getElementById(t);r||(r=document.createElement("style"),r.id=t,"loading"===document.readyState?document.addEventListener("DOMContentLoaded",()=>{document.head.appendChild(r)}):(document.head||document.documentElement).appendChild(r)),r.textContent=e}const s=globalThis.CUSTOM_USER_AGENT??("undefined"===typeof navigator?"":navigator.userAgent),c=/^(?:(?!chrome|android).)*safari/i.test(s),l=/iPad|iPhone|iPod/i.test(s)&&("undefined"===typeof window||!window.MSStream),d=l||s.includes("Macintosh")&&"undefined"!==typeof navigator&&navigator.maxTouchPoints>=1,u=/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(s)||d,p="undefined"!==typeof window&&"undefined"!==typeof document;const f={properties:["audioTracks","autoplay","buffered","controller","controlLine truncated
+1 -1
View File
@@ -67,4 +67,4 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
!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=Object.defineProperty,e=Object.defineProperties,r=Object.getOwnPropertyDescriptors,n=Object.getOwnPropertySymbols,o=Object.prototype.hasOwnProperty,i=Object.prototype.propertyIsEnumerable,a=(e,r,n)=>r in e?t(e,r,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[r]=n,s=(t,e)=>{for(var r in e||(e={}))o.call(e,r)&&a(t,r,e[r]);if(n)for(var r of n(e))i.call(e,r)&&a(t,r,e[r]);return t},l=(t,n)=>e(t,r(n)),c=(t,e,r)=>new Promise((n,o)=>{var i=t=>{try{s(r.next(t))}catch(e){o(e)}},a=t=>{try{s(r.throw(t))}catch(e){o(e)}},s=t=>t.done?n(t.value):Promise.resolve(t.value).then(i,a);s((r=r.apply(t,e)).next())});function d(t){return t&&t.__esModule&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t}var u,p={exports:{}};var f=(u||(u=1,p.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 o=t(r);if("boolean"===o)return"boolean";if("string"===o)return"string";if("number"===o)return"number";if("symbol"===o)return"symbol";if("function"===o)return"GeneratorFunction"===n(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(n(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(o=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 o.slice(8,-1).toLowerCase().replace(/\s/g,"")};function n(t){return t.constructor?t.constructor.name:null}function o(t,e){var n=2<arguments.length&&void 0!==arguments[2]?arguments[2]:["option"];return i(t,e,n),a(t,e,n),function(t,e,n){var s=r(e),l=r(t);if("object"===s){if("object"!==l)throw new Error("[Type Error]: '".concat(n.join("."),"' require 'object' type, but got '").concat(l,"'"));Object.keys(e).forEach(function(r){var s=t[r],l=e[r],c=n.slice();c.push(r),i(s,l,c),a(s,l,c),o(s,l,c)})}if("array"===s){if("array"!==l)throw new Error("[Type Error]: '".concat(n.join("."),"' require 'array' type, but got '").concat(l,"'"));t.forEach(function(r,s){var l=t[s],c=e[s]||e[0],d=n.slice();d.push(s),i(l,c,d),a(l,c,d),o(l,c,d)})}}(t,e,n),t}function i(t,e,n){if("string"===r(e)){var o=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 o===t}):e.toLowerCase().trim()===o))throw new Error("[Type Error]: '".concat(n.join("."),"' require '").concat(e,"' type, but got '").concat(o,"'"))}}function a(t,e,n){if("function"===r(e)){var o=e(t,r(t),n);if(!0!==o){var i=r(o);throw"string"===i?new Error(o):"error"===i?o:new Error("[Validator Error]: The scheme for '".concat(n.join("."),"' validator require return true, but got '").concat(o,"'"))}}}return o.kindOf=r,o}()),p.exports);const h=d(f),m="5.4.1";function v(t,e){let r=document.getElementById(t);r||(r=document.createElement("style"),r.id=t,"loading"===document.readyState?document.addEventListener("DOMContentLoaded",()=>{document.head.apLine 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=Object.defineProperty,e=Object.defineProperties,r=Object.getOwnPropertyDescriptors,n=Object.getOwnPropertySymbols,o=Object.prototype.hasOwnProperty,i=Object.prototype.propertyIsEnumerable,a=(e,r,n)=>r in e?t(e,r,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[r]=n,s=(t,e)=>{for(var r in e||(e={}))o.call(e,r)&&a(t,r,e[r]);if(n)for(var r of n(e))i.call(e,r)&&a(t,r,e[r]);return t},l=(t,n)=>e(t,r(n)),c=(t,e,r)=>new Promise((n,o)=>{var i=t=>{try{s(r.next(t))}catch(e){o(e)}},a=t=>{try{s(r.throw(t))}catch(e){o(e)}},s=t=>t.done?n(t.value):Promise.resolve(t.value).then(i,a);s((r=r.apply(t,e)).next())});function d(t){return t&&t.__esModule&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t}var u,p={exports:{}};var f=(u||(u=1,p.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 o=t(r);if("boolean"===o)return"boolean";if("string"===o)return"string";if("number"===o)return"number";if("symbol"===o)return"symbol";if("function"===o)return"GeneratorFunction"===n(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(n(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(o=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 o.slice(8,-1).toLowerCase().replace(/\s/g,"")};function n(t){return t.constructor?t.constructor.name:null}function o(t,e){var n=2<arguments.length&&void 0!==arguments[2]?arguments[2]:["option"];return i(t,e,n),a(t,e,n),function(t,e,n){var s=r(e),l=r(t);if("object"===s){if("object"!==l)throw new Error("[Type Error]: '".concat(n.join("."),"' require 'object' type, but got '").concat(l,"'"));Object.keys(e).forEach(function(r){var s=t[r],l=e[r],c=n.slice();c.push(r),i(s,l,c),a(s,l,c),o(s,l,c)})}if("array"===s){if("array"!==l)throw new Error("[Type Error]: '".concat(n.join("."),"' require 'array' type, but got '").concat(l,"'"));t.forEach(function(r,s){var l=t[s],c=e[s]||e[0],d=n.slice();d.push(s),i(l,c,d),a(l,c,d),o(l,c,d)})}}(t,e,n),t}function i(t,e,n){if("string"===r(e)){var o=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 o===t}):e.toLowerCase().trim()===o))throw new Error("[Type Error]: '".concat(n.join("."),"' require '").concat(e,"' type, but got '").concat(o,"'"))}}function a(t,e,n){if("function"===r(e)){var o=e(t,r(t),n);if(!0!==o){var i=r(o);throw"string"===i?new Error(o):"error"===i?o:new Error("[Validator Error]: The scheme for '".concat(n.join("."),"' validator require return true, but got '").concat(o,"'"))}}}return o.kindOf=r,o}()),p.exports);const h=d(f),m="5.4.1";function v(t,e){let r=document.getElementById(t);r||(r=document.createElement("style"),r.id=t,"loading"===document.readyState?document.addEventListener("DOMContentLoaded",()=>{document.head.apLine truncated
+38 -3
View File
@@ -2096,6 +2096,22 @@ function playAndPause(option) {
const current = /* @__PURE__ */ new WeakMap();
const assignments = /* @__PURE__ */ new WeakMap();
const sources = /* @__PURE__ */ new WeakMap();
function sourcePlaybackIntent(owner) {
const operation = current.get(owner);
return operation?.active() ? operation.resumePlaying : void 0;
}
function requestSourcePlayback(owner, playing) {
const operation = current.get(owner);
if (!operation?.active() || operation.resumePlaying === void 0)
return;
if (!playing && operation.internalPause) {
operation.internalPause = false;
} else {
operation.resumePlaying = playing;
if (!playing)
operation.pauseRevision++;
}
}
function getSourceScope(owner) {
return sources.get(owner) || getScope(owner);
}
@@ -2104,6 +2120,12 @@ function captureSource(owner) {
const source = sources.get(owner);
return () => current.get(owner) === operation && (!operation || operation.active()) && sources.get(owner) === source && (!source || !source.closed);
}
function captureSourcePlayback(owner) {
const active2 = captureSource(owner);
const operation = current.get(owner);
const revision = operation?.pauseRevision;
return () => active2() && operation?.pauseRevision === revision;
}
function beginSource(owner) {
const previous = sources.get(owner);
const source = getScope(owner).child();
@@ -2117,6 +2139,7 @@ function beginSource(owner) {
scope,
assigned: false,
acceptingEvents: false,
pauseRevision: 0,
active: () => !isClosing(owner) && !scope.closed && current.get(owner) === operation
};
current.set(owner, operation);
@@ -5713,6 +5736,7 @@ function pauseMix(art) {
} = art;
def(art, "pause", {
value() {
requestSourcePlayback(art, false);
const result = $video.pause();
notice.show = i18n.get("Pause");
art.emit("pause");
@@ -5960,7 +5984,8 @@ function playMix(art) {
} = art;
def(art, "play", {
async value() {
const active2 = captureSource(art);
requestSourcePlayback(art, true);
const active2 = captureSourcePlayback(art);
const result = await $video.play();
if (!active2())
return result;
@@ -6203,13 +6228,16 @@ function switchSource(art, url, currentTime) {
resolve();
return;
}
const playing = sourcePlaybackIntent(art) ?? art.playing;
const operation = beginSource(art);
operation.resumePlaying = playing;
const scope = operation.scope.child();
let settled = false;
const settle = (failed = false, error2) => {
if (settled)
return;
settled = true;
operation.resumePlaying = void 0;
operation.onError = void 0;
operation.onAssigned = void 0;
scope.dispose();
@@ -6227,8 +6255,13 @@ function switchSource(art, url, currentTime) {
const fail = (error2) => settle(true, error2);
operation.onError = fail;
try {
const { playing, aspectRatio: aspectRatio2, playbackRate: playbackRate2 } = art;
const { aspectRatio: aspectRatio2, playbackRate: playbackRate2 } = art;
operation.internalPause = true;
try {
art.pause();
} finally {
operation.internalPause = false;
}
if (!active2())
return;
const readiness = scope.child();
@@ -6237,10 +6270,11 @@ function switchSource(art, url, currentTime) {
let rateRestored = false;
const position = positionRestoration(art, currentTime, active2);
const resume = async () => {
const pauseRevision = operation.pauseRevision;
art.aspectRatio = aspectRatio2;
if (!active2())
return;
if (playing) {
if (operation.resumePlaying) {
try {
await art.play();
} catch {
@@ -6248,6 +6282,7 @@ function switchSource(art, url, currentTime) {
}
if (!active2())
return;
if (operation.pauseRevision === pauseRevision)
art.notice.show = "";
settle();
};
+11
View File
@@ -260,6 +260,17 @@ twice. media/position-revision.ts also tracks legal public currentTime writes;
a consumer assigning currentTime directly during loading supersedes the automatic
restoration, including writes before metadata. Invalid NaN assignments remain no-ops.
Source cancellation still settles pending work and removes all listeners.
CORE-24 keeps the desired playback state on the pending source operation. A new switch inherits
that intent before disposing its predecessor; it must not infer the user's intent from the
temporary native pause performed by the previous switch. playMix/pauseMix update intent at
command entry, before native calls or public callbacks can reenter. The switch's internal pause
consumes one marker; subsequent explicit pauses remain effective, including inside callbacks.
The pause revision invalidates late play notifications and prevents an old resume from clearing
a newer pause notice. It does not change the public play promise's original value or rejection.
Completed, failed or cancelled operations cannot lend intent to a later independent source.
Do not replace these command hooks with late event listeners: an earlier user pause listener
may synchronously start another switch. See test/source.test.js, test/browser/source-intent.spec.js
and refactor/changes/2026-09-12-CORE-24-source-intent.md for the regressions and validation scope.
Use test/source.test.js, test/progress.test.js and the browser source and
progress-quality specs. Keep native media events in evidence when changing this
sequence; the original rate-interruption hypothesis was not sufficient to explain
+1 -1
View File
@@ -67,4 +67,4 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
!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 n=(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 o=t(r);if("boolean"===o)return"boolean";if("string"===o)return"string";if("number"===o)return"number";if("symbol"===o)return"symbol";if("function"===o)return"GeneratorFunction"===n(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(n(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(o=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 o.slice(8,-1).toLowerCase().replace(/\s/g,"")};function n(t){return t.constructor?t.constructor.name:null}function o(t,e){var n=2<arguments.length&&void 0!==arguments[2]?arguments[2]:["option"];return i(t,e,n),a(t,e,n),function(t,e,n){var s=r(e),c=r(t);if("object"===s){if("object"!==c)throw new Error("[Type Error]: '".concat(n.join("."),"' require 'object' type, but got '").concat(c,"'"));Object.keys(e).forEach(function(r){var s=t[r],c=e[r],l=n.slice();l.push(r),i(s,c,l),a(s,c,l),o(s,c,l)})}if("array"===s){if("array"!==c)throw new Error("[Type Error]: '".concat(n.join("."),"' require 'array' type, but got '").concat(c,"'"));t.forEach(function(r,s){var c=t[s],l=e[s]||e[0],d=n.slice();d.push(s),i(c,l,d),a(c,l,d),o(c,l,d)})}}(t,e,n),t}function i(t,e,n){if("string"===r(e)){var o=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 o===t}):e.toLowerCase().trim()===o))throw new Error("[Type Error]: '".concat(n.join("."),"' require '").concat(e,"' type, but got '").concat(o,"'"))}}function a(t,e,n){if("function"===r(e)){var o=e(t,r(t),n);if(!0!==o){var i=r(o);throw"string"===i?new Error(o):"error"===i?o:new Error("[Validator Error]: The scheme for '".concat(n.join("."),"' validator require return true, but got '").concat(o,"'"))}}}return o.kindOf=r,o}()),r.exports);const o=t(n),i="5.4.1";function a(t,e){let r=document.getElementById(t);r||(r=document.createElement("style"),r.id=t,"loading"===document.readyState?document.addEventListener("DOMContentLoaded",()=>{document.head.appendChild(r)}):(document.head||document.documentElement).appendChild(r)),r.textContent=e}const s=globalThis.CUSTOM_USER_AGENT??("undefined"===typeof navigator?"":navigator.userAgent),c=/^(?:(?!chrome|android).)*safari/i.test(s),l=/iPad|iPhone|iPod/i.test(s)&&("undefined"===typeof window||!window.MSStream),d=l||s.includes("Macintosh")&&"undefined"!==typeof navigator&&navigator.maxTouchPoints>=1,u=/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(s)||d,p="undefined"!==typeof window&&"undefined"!==typeof document;const f={properties:["audioTracks","autoplay","buffered","controller","controlLine 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 n=(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 o=t(r);if("boolean"===o)return"boolean";if("string"===o)return"string";if("number"===o)return"number";if("symbol"===o)return"symbol";if("function"===o)return"GeneratorFunction"===n(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(n(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(o=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 o.slice(8,-1).toLowerCase().replace(/\s/g,"")};function n(t){return t.constructor?t.constructor.name:null}function o(t,e){var n=2<arguments.length&&void 0!==arguments[2]?arguments[2]:["option"];return i(t,e,n),a(t,e,n),function(t,e,n){var s=r(e),c=r(t);if("object"===s){if("object"!==c)throw new Error("[Type Error]: '".concat(n.join("."),"' require 'object' type, but got '").concat(c,"'"));Object.keys(e).forEach(function(r){var s=t[r],c=e[r],l=n.slice();l.push(r),i(s,c,l),a(s,c,l),o(s,c,l)})}if("array"===s){if("array"!==c)throw new Error("[Type Error]: '".concat(n.join("."),"' require 'array' type, but got '").concat(c,"'"));t.forEach(function(r,s){var c=t[s],l=e[s]||e[0],d=n.slice();d.push(s),i(c,l,d),a(c,l,d),o(c,l,d)})}}(t,e,n),t}function i(t,e,n){if("string"===r(e)){var o=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 o===t}):e.toLowerCase().trim()===o))throw new Error("[Type Error]: '".concat(n.join("."),"' require '").concat(e,"' type, but got '").concat(o,"'"))}}function a(t,e,n){if("function"===r(e)){var o=e(t,r(t),n);if(!0!==o){var i=r(o);throw"string"===i?new Error(o):"error"===i?o:new Error("[Validator Error]: The scheme for '".concat(n.join("."),"' validator require return true, but got '").concat(o,"'"))}}}return o.kindOf=r,o}()),r.exports);const o=t(n),i="5.4.1";function a(t,e){let r=document.getElementById(t);r||(r=document.createElement("style"),r.id=t,"loading"===document.readyState?document.addEventListener("DOMContentLoaded",()=>{document.head.appendChild(r)}):(document.head||document.documentElement).appendChild(r)),r.textContent=e}const s=globalThis.CUSTOM_USER_AGENT??("undefined"===typeof navigator?"":navigator.userAgent),c=/^(?:(?!chrome|android).)*safari/i.test(s),l=/iPad|iPhone|iPod/i.test(s)&&("undefined"===typeof window||!window.MSStream),d=l||s.includes("Macintosh")&&"undefined"!==typeof navigator&&navigator.maxTouchPoints>=1,u=/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(s)||d,p="undefined"!==typeof window&&"undefined"!==typeof document;const f={properties:["audioTracks","autoplay","buffered","controller","controlLine truncated
+1 -1
View File
@@ -67,4 +67,4 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
!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=Object.defineProperty,e=Object.defineProperties,r=Object.getOwnPropertyDescriptors,n=Object.getOwnPropertySymbols,o=Object.prototype.hasOwnProperty,i=Object.prototype.propertyIsEnumerable,a=(e,r,n)=>r in e?t(e,r,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[r]=n,s=(t,e)=>{for(var r in e||(e={}))o.call(e,r)&&a(t,r,e[r]);if(n)for(var r of n(e))i.call(e,r)&&a(t,r,e[r]);return t},l=(t,n)=>e(t,r(n)),c=(t,e,r)=>new Promise((n,o)=>{var i=t=>{try{s(r.next(t))}catch(e){o(e)}},a=t=>{try{s(r.throw(t))}catch(e){o(e)}},s=t=>t.done?n(t.value):Promise.resolve(t.value).then(i,a);s((r=r.apply(t,e)).next())});function d(t){return t&&t.__esModule&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t}var u,p={exports:{}};var f=(u||(u=1,p.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 o=t(r);if("boolean"===o)return"boolean";if("string"===o)return"string";if("number"===o)return"number";if("symbol"===o)return"symbol";if("function"===o)return"GeneratorFunction"===n(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(n(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(o=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 o.slice(8,-1).toLowerCase().replace(/\s/g,"")};function n(t){return t.constructor?t.constructor.name:null}function o(t,e){var n=2<arguments.length&&void 0!==arguments[2]?arguments[2]:["option"];return i(t,e,n),a(t,e,n),function(t,e,n){var s=r(e),l=r(t);if("object"===s){if("object"!==l)throw new Error("[Type Error]: '".concat(n.join("."),"' require 'object' type, but got '").concat(l,"'"));Object.keys(e).forEach(function(r){var s=t[r],l=e[r],c=n.slice();c.push(r),i(s,l,c),a(s,l,c),o(s,l,c)})}if("array"===s){if("array"!==l)throw new Error("[Type Error]: '".concat(n.join("."),"' require 'array' type, but got '").concat(l,"'"));t.forEach(function(r,s){var l=t[s],c=e[s]||e[0],d=n.slice();d.push(s),i(l,c,d),a(l,c,d),o(l,c,d)})}}(t,e,n),t}function i(t,e,n){if("string"===r(e)){var o=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 o===t}):e.toLowerCase().trim()===o))throw new Error("[Type Error]: '".concat(n.join("."),"' require '").concat(e,"' type, but got '").concat(o,"'"))}}function a(t,e,n){if("function"===r(e)){var o=e(t,r(t),n);if(!0!==o){var i=r(o);throw"string"===i?new Error(o):"error"===i?o:new Error("[Validator Error]: The scheme for '".concat(n.join("."),"' validator require return true, but got '").concat(o,"'"))}}}return o.kindOf=r,o}()),p.exports);const h=d(f),m="5.4.1";function v(t,e){let r=document.getElementById(t);r||(r=document.createElement("style"),r.id=t,"loading"===document.readyState?document.addEventListener("DOMContentLoaded",()=>{document.head.apLine 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=Object.defineProperty,e=Object.defineProperties,r=Object.getOwnPropertyDescriptors,n=Object.getOwnPropertySymbols,o=Object.prototype.hasOwnProperty,i=Object.prototype.propertyIsEnumerable,a=(e,r,n)=>r in e?t(e,r,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[r]=n,s=(t,e)=>{for(var r in e||(e={}))o.call(e,r)&&a(t,r,e[r]);if(n)for(var r of n(e))i.call(e,r)&&a(t,r,e[r]);return t},l=(t,n)=>e(t,r(n)),c=(t,e,r)=>new Promise((n,o)=>{var i=t=>{try{s(r.next(t))}catch(e){o(e)}},a=t=>{try{s(r.throw(t))}catch(e){o(e)}},s=t=>t.done?n(t.value):Promise.resolve(t.value).then(i,a);s((r=r.apply(t,e)).next())});function d(t){return t&&t.__esModule&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t}var u,p={exports:{}};var f=(u||(u=1,p.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 o=t(r);if("boolean"===o)return"boolean";if("string"===o)return"string";if("number"===o)return"number";if("symbol"===o)return"symbol";if("function"===o)return"GeneratorFunction"===n(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(n(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(o=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 o.slice(8,-1).toLowerCase().replace(/\s/g,"")};function n(t){return t.constructor?t.constructor.name:null}function o(t,e){var n=2<arguments.length&&void 0!==arguments[2]?arguments[2]:["option"];return i(t,e,n),a(t,e,n),function(t,e,n){var s=r(e),l=r(t);if("object"===s){if("object"!==l)throw new Error("[Type Error]: '".concat(n.join("."),"' require 'object' type, but got '").concat(l,"'"));Object.keys(e).forEach(function(r){var s=t[r],l=e[r],c=n.slice();c.push(r),i(s,l,c),a(s,l,c),o(s,l,c)})}if("array"===s){if("array"!==l)throw new Error("[Type Error]: '".concat(n.join("."),"' require 'array' type, but got '").concat(l,"'"));t.forEach(function(r,s){var l=t[s],c=e[s]||e[0],d=n.slice();d.push(s),i(l,c,d),a(l,c,d),o(l,c,d)})}}(t,e,n),t}function i(t,e,n){if("string"===r(e)){var o=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 o===t}):e.toLowerCase().trim()===o))throw new Error("[Type Error]: '".concat(n.join("."),"' require '").concat(e,"' type, but got '").concat(o,"'"))}}function a(t,e,n){if("function"===r(e)){var o=e(t,r(t),n);if(!0!==o){var i=r(o);throw"string"===i?new Error(o):"error"===i?o:new Error("[Validator Error]: The scheme for '".concat(n.join("."),"' validator require return true, but got '").concat(o,"'"))}}}return o.kindOf=r,o}()),p.exports);const h=d(f),m="5.4.1";function v(t,e){let r=document.getElementById(t);r||(r=document.createElement("style"),r.id=t,"loading"===document.readyState?document.addEventListener("DOMContentLoaded",()=>{document.head.apLine truncated
+38 -3
View File
@@ -2096,6 +2096,22 @@ function playAndPause(option) {
const current = /* @__PURE__ */ new WeakMap();
const assignments = /* @__PURE__ */ new WeakMap();
const sources = /* @__PURE__ */ new WeakMap();
function sourcePlaybackIntent(owner) {
const operation = current.get(owner);
return operation?.active() ? operation.resumePlaying : void 0;
}
function requestSourcePlayback(owner, playing) {
const operation = current.get(owner);
if (!operation?.active() || operation.resumePlaying === void 0)
return;
if (!playing && operation.internalPause) {
operation.internalPause = false;
} else {
operation.resumePlaying = playing;
if (!playing)
operation.pauseRevision++;
}
}
function getSourceScope(owner) {
return sources.get(owner) || getScope(owner);
}
@@ -2104,6 +2120,12 @@ function captureSource(owner) {
const source = sources.get(owner);
return () => current.get(owner) === operation && (!operation || operation.active()) && sources.get(owner) === source && (!source || !source.closed);
}
function captureSourcePlayback(owner) {
const active2 = captureSource(owner);
const operation = current.get(owner);
const revision = operation?.pauseRevision;
return () => active2() && operation?.pauseRevision === revision;
}
function beginSource(owner) {
const previous = sources.get(owner);
const source = getScope(owner).child();
@@ -2117,6 +2139,7 @@ function beginSource(owner) {
scope,
assigned: false,
acceptingEvents: false,
pauseRevision: 0,
active: () => !isClosing(owner) && !scope.closed && current.get(owner) === operation
};
current.set(owner, operation);
@@ -5713,6 +5736,7 @@ function pauseMix(art) {
} = art;
def(art, "pause", {
value() {
requestSourcePlayback(art, false);
const result = $video.pause();
notice.show = i18n.get("Pause");
art.emit("pause");
@@ -5960,7 +5984,8 @@ function playMix(art) {
} = art;
def(art, "play", {
async value() {
const active2 = captureSource(art);
requestSourcePlayback(art, true);
const active2 = captureSourcePlayback(art);
const result = await $video.play();
if (!active2())
return result;
@@ -6203,13 +6228,16 @@ function switchSource(art, url, currentTime) {
resolve();
return;
}
const playing = sourcePlaybackIntent(art) ?? art.playing;
const operation = beginSource(art);
operation.resumePlaying = playing;
const scope = operation.scope.child();
let settled = false;
const settle = (failed = false, error2) => {
if (settled)
return;
settled = true;
operation.resumePlaying = void 0;
operation.onError = void 0;
operation.onAssigned = void 0;
scope.dispose();
@@ -6227,8 +6255,13 @@ function switchSource(art, url, currentTime) {
const fail = (error2) => settle(true, error2);
operation.onError = fail;
try {
const { playing, aspectRatio: aspectRatio2, playbackRate: playbackRate2 } = art;
const { aspectRatio: aspectRatio2, playbackRate: playbackRate2 } = art;
operation.internalPause = true;
try {
art.pause();
} finally {
operation.internalPause = false;
}
if (!active2())
return;
const readiness = scope.child();
@@ -6237,10 +6270,11 @@ function switchSource(art, url, currentTime) {
let rateRestored = false;
const position = positionRestoration(art, currentTime, active2);
const resume = async () => {
const pauseRevision = operation.pauseRevision;
art.aspectRatio = aspectRatio2;
if (!active2())
return;
if (playing) {
if (operation.resumePlaying) {
try {
await art.play();
} catch {
@@ -6248,6 +6282,7 @@ function switchSource(art, url, currentTime) {
}
if (!active2())
return;
if (operation.pauseRevision === pauseRevision)
art.notice.show = "";
settle();
};
@@ -1,5 +1,6 @@
import type { PauseHost } from '../media/hosts'
import type { PlaybackMethods } from '../media/types'
import { requestSourcePlayback } from '../source/operation'
import { def } from '../utils'
export default function pauseMix<Media extends Pick<PlaybackMethods, 'pause'>>(art: PauseHost<Media>): asserts art is PauseHost<Media> & { pause: () => ReturnType<Media['pause']> } {
@@ -11,6 +12,7 @@ export default function pauseMix<Media extends Pick<PlaybackMethods, 'pause'>>(a
def(art, 'pause', {
value() {
requestSourcePlayback(art, false)
const result = $video.pause()
notice.show = i18n.get('Pause')
art.emit('pause')
+3 -2
View File
@@ -1,6 +1,6 @@
import type { PlayHost } from '../media/hosts'
import type { PlaybackMethods } from '../media/types'
import { captureSource } from '../source/operation'
import { captureSourcePlayback, requestSourcePlayback } from '../source/operation'
import { def } from '../utils'
export default function playMix<Media extends Pick<PlaybackMethods, 'play'>>(art: PlayHost<Media>): asserts art is PlayHost<Media> & { play: () => Promise<Awaited<ReturnType<Media['play']>>> } {
@@ -14,7 +14,8 @@ export default function playMix<Media extends Pick<PlaybackMethods, 'play'>>(art
def(art, 'play', {
async value() {
const active = captureSource(art)
requestSourcePlayback(art, true)
const active = captureSourcePlayback(art)
const result = await $video.play()
if (!active())
return result
@@ -6,6 +6,9 @@ export interface SourceOperation {
scope: ResourceScope
assigned: boolean
acceptingEvents: boolean
pauseRevision: number
resumePlaying?: boolean
internalPause?: boolean
onAssigned?: () => void
onError?: (error: unknown) => void
active: () => boolean
@@ -15,6 +18,25 @@ const current = new WeakMap<object, SourceOperation>()
const assignments = new WeakMap<object, SourceOperation>()
const sources = new WeakMap<object, ResourceScope>()
export function sourcePlaybackIntent(owner: object): boolean | undefined {
const operation = current.get(owner)
return operation?.active() ? operation.resumePlaying : undefined
}
export function requestSourcePlayback(owner: object, playing: boolean): void {
const operation = current.get(owner)
if (!operation?.active() || operation.resumePlaying === undefined)
return
if (!playing && operation.internalPause) {
operation.internalPause = false
}
else {
operation.resumePlaying = playing
if (!playing)
operation.pauseRevision++
}
}
export function getSourceScope(owner: object): ResourceScope {
return sources.get(owner) || getScope(owner)
}
@@ -26,6 +48,13 @@ export function captureSource(owner: object): () => boolean {
&& sources.get(owner) === source && (!source || !source.closed)
}
export function captureSourcePlayback(owner: object): () => boolean {
const active = captureSource(owner)
const operation = current.get(owner)
const revision = operation?.pauseRevision
return () => active() && operation?.pauseRevision === revision
}
export function beginSource(owner: object): SourceOperation {
const previous = sources.get(owner)
const source = getScope(owner).child()
@@ -39,6 +68,7 @@ export function beginSource(owner: object): SourceOperation {
scope,
assigned: false,
acceptingEvents: false,
pauseRevision: 0,
active: () => !isClosing(owner) && !scope.closed && current.get(owner) === operation,
}
current.set(owner, operation)
+18 -7
View File
@@ -1,7 +1,7 @@
import type { SourceEvent, SourceListener, SwitchHost } from './types'
import type { SourceListener, SwitchHost } from './types'
import { isClosing } from '../lifecycle/instance'
import { listenSource } from './listen'
import { assignUrl, beginSource } from './operation'
import { assignUrl, beginSource, sourcePlaybackIntent } from './operation'
import { positionRestoration } from './restore-position'
export function switchSource(art: SwitchHost, url: string, currentTime: number): Promise<void> {
@@ -10,13 +10,16 @@ export function switchSource(art: SwitchHost, url: string, currentTime: number):
resolve()
return
}
const playing = sourcePlaybackIntent(art) ?? art.playing
const operation = beginSource(art)
operation.resumePlaying = playing
const scope = operation.scope.child()
let settled = false
const settle = (failed = false, error?: unknown) => {
if (settled)
return
settled = true
operation.resumePlaying = undefined
operation.onError = undefined
operation.onAssigned = undefined
scope.dispose()
@@ -34,8 +37,14 @@ export function switchSource(art: SwitchHost, url: string, currentTime: number):
const fail = (error: unknown) => settle(true, error)
operation.onError = fail
try {
const { playing, aspectRatio, playbackRate } = art
const { aspectRatio, playbackRate } = art
operation.internalPause = true
try {
art.pause()
}
finally {
operation.internalPause = false
}
if (!active())
return
@@ -45,10 +54,11 @@ export function switchSource(art: SwitchHost, url: string, currentTime: number):
let rateRestored = false
const position = positionRestoration(art, currentTime, active)
const resume = async () => {
const pauseRevision = operation.pauseRevision
art.aspectRatio = aspectRatio
if (!active())
return
if (playing) {
if (operation.resumePlaying) {
// The public play promise still rejects; this internal resume is best effort.
try {
await art.play()
@@ -57,6 +67,7 @@ export function switchSource(art: SwitchHost, url: string, currentTime: number):
}
if (!active())
return
if (operation.pauseRevision === pauseRevision)
art.notice.show = ''
settle()
}
@@ -103,8 +114,8 @@ export function switchSource(art: SwitchHost, url: string, currentTime: number):
'seek': position.manual,
}
const capture = scope.child()
const queued: [SourceEvent, unknown][] = []
const names = Object.keys(handlers) as SourceEvent[]
const queued: [keyof typeof handlers, unknown][] = []
const names = Object.keys(handlers) as (keyof typeof handlers)[]
for (const name of names) {
listenSource(capture, art, name, (event) => {
if (operation.acceptingEvents)
@@ -123,7 +134,7 @@ export function switchSource(art: SwitchHost, url: string, currentTime: number):
settle()
return
}
const dispatch = {} as Record<SourceEvent, SourceListener>
const dispatch = {} as Record<keyof typeof handlers, SourceListener>
for (const name of names)
dispatch[name] = listenSource(readiness, art, name, handlers[name], name !== 'video:seeked')
for (const [name, event] of queued) {
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,63 @@
# CORE-24:连续切源的播放意图
状态:完成验收,独立本地提交;起点 b5e96664。Audio-05 的首轮三引擎证据确认六项候选核心回归。
原切源每次读取 art.playing 并立即 art.pause()。第二次同步切源读取到第一次内部
暂停后的 false;旧操作被正确取消后,原先负责恢复播放的旧监听器不再执行,新
操作也没有恢复意图。修复必须传递最新切源的播放意图,同时保留过期操作无副作用。
将意图归属当前 source operation,在仍待恢复的操作间转移;用户明确 pause 可以
取消恢复。内部 pause 仍走既有公开事件,不能把它误当作用户放弃播放。补真实暂停
状态的受控宿主、重入/取消/失败/销毁和真实浏览器测试。完成后独立提交并恢复 Audio-05。
## 实现与兼容
source/operation.ts 保存待恢复意图、内部暂停标记及暂停修订号;switch.ts 在取消旧
操作前取得意图,结算时清除,避免完整结束/失败操作的状态泄漏到之后的来源。
playMix/pauseMix 在原生命令之前更新意图。内部暂停标记由进入 pauseMix 的第一次调用
消费,嵌套用户暂停仍然生效,不改变现有 pause 事件、同步返回或 play Promise 值/拒绝。
play 的副作用还核对暂停修订号:晚到的 native play 不能在用户暂停之后更新提示、
发出 play 或执行互斥,switch 的旧恢复也不能清空新暂停提示。
最初尝试监听 play/pause 事件,已有用例虽通过,但无法保证比用户预注册的回调先执行;
审查发现用户 pause 回调同步切源时可能先继承旧意图。因此采用命令入口方案(ADR-024),
没有增加额外监听器、定时器、依赖或公开字段。新用例使用真实 pauseMix/playMix,
不再让受控宿主的 pause 只计数却不改变播放状态。
一项既有晚到恢复测试原来只写 art.playing=false,未表达暂停命令;它现在同时调用
真实 art.pause,保留其旧操作不能覆盖新提示的断言。早期事件方案因此悬挂的运行已
主动终止并保留日志,没有当作通过。新增“恢复未完成时用户暂停”也先复现了错误 play
事件,再以修订号修复,失败日志均保留。
## 验证结果
- 源切换 34 项单元通过;新增 8 项覆盖连续操作、显式暂停、嵌套暂停、用户监听器重入、
未兑现的公开 play 意图、晚到恢复,以及直接 URL/失败/完成/销毁边界。
- 完整 `yarn ci:check` 594 项通过(548 单元、11 工程、35 基线),严格 TS 与声明检查通过。
后续新增一个浏览器命令顺序用例,最终单独 lint 和下面两套矩阵验证。
- 正常 `yarn build artplayer`、`yarn build:i18n` 通过;后者恢复核心构建清理的语言产物,
i18n 与上次提交无差异。三种核心 JS 与 docs/compiled 一致,公开声明/版本未改变。
- 正式 main/legacy 产物的 source-intent/source/media-events/playback-properties
三引擎最终各 81 项通过,无重试或跳过。早期事件方案的 39 项及新增用例前的 78 项
保留为过程记录,不充当最终实现证据。
- 原先失败的六项“候选核心 + 旧/新 Audio”连续切源用例全部通过,测试断言未改。
该组合重跑使用核心源码构建与明确指定的 Audio main 产物;正式核心产物另由上述矩阵验证。
- `yarn test:package` 在仓库外实际安装 tarball 并冻结重装:34 个运行时检查,5 组兼容类型
与 8 组精确类型通过。打包核心 JS 与浏览器所测的正式文件逐字节比对。
- `yarn test:coverage` 通过,224 个运行时文件进入报告、26 个明确排除,0 项门槛违例。
总体 Node 行覆盖 61.04%、分支 86.70%、函数 66.75%;不能将浏览器数量当作覆盖率。
变动文件的独立计数及产物体积差异见 [冻结证据](../baselines/core-source-intent-validation.json)。
main gzip 从 58,555 增至 58,748 字节(+193),legacy +181、ESM +248;这是兼容行为
修复的实际体积成本,不声称有性能/体积优化收益。source/operation.ts 的行/分支/函数
覆盖均 100%,source/switch.ts 行 98.08%、分支 94.20%,其他浏览器路径仍有 Node 覆盖缺口。
## 后续与回退
CORE-SOURCE-01 按上述公开命令与连续切源范围关闭;AUDIO-RESUME-01(旧核心 + 新音频
在 WebKit 上恢复)、原生偏移边界对照和真实网络缓冲仍由 Audio-05 接续,未一并关闭。
核心完整安装包全量矩阵、性能/体积风险和物理设备/发布复盘门槛仍需后续最终候选验证;
本任务的 81 项矩阵不等于重复了 CORE-22 的所有场景,也不代表 npm 发布放行。
回退此独立提交会恢复上次核心运行实现及三种构建文件,同时重新打开 CORE-SOURCE-01;
不应只回退命令入口或操作状态的一半。无 push/tag/publish/merge。
+3 -2
View File
@@ -5,8 +5,9 @@ CORE-22 汇总核心自有源码、公开声明、生成产物和当前固定消
[交付记录](changes/2026-09-11-CORE-22-core-acceptance.md);任务状态以 tasks.json 为准。
后续 Audio-05 的新组合测试复现了候选核心连续切源丢失播放意图,见
[首轮证据](baselines/audio-combinations-first.json)。CORE-SOURCE-01/CORE-24 作为新发现
继续修复,当前不能据本文件历史阶段结果放行;原有通过记录保留其当时的测试范围。
[首轮证据](baselines/audio-combinations-first.json)。CORE-24 已修复并通过针对性正式
产物/隔离安装验收,见 [修复记录](changes/2026-09-12-CORE-24-source-intent.md)。
原有通过记录保留其当时的测试范围,不能据历史阶段结果替代最终候选的全量发布验收。
## 契约与回归入口
+1
View File
@@ -28,6 +28,7 @@
| ADR-021 | 采用方向 | 用户指定 Yarn,固定 Classic 1.22.22 和唯一 yarn.lock | 与原 v1 锁格式一致;保留 Node 24.21.0 和既有依赖版本,ENG-PM-01 验证;替代 ENG-01 的 npm 默认工具选择 |
| ADR-022 | 采用方向 | Chrome 不可用时使用 Codex 内置浏览器 | 用户明确授权;记录实际环境和能力范围,必要设备测试仍独立验收 |
| ADR-023 | 已验证于 Audio Track | 参数类型纠正会破坏旧推断时,默认入口保留兼容类型,/runtime 复用相同运行文件提供精确类型 | PKG-AUDIO-04 的 Parameters 与无注解 update 实现证明 Partial/重载仍可能破坏旧用户;沿用核心的类型分层方向,具体包需独立编译与入口身份验证,不增加第二套实现或用 any 绕过 |
| ADR-024 | 已验证于 CORE-24 | 切源播放意图归属当前操作,在公开 play/pause 命令入口更新,内部暂停不覆盖用户意图 | Audio-05 三引擎复现连续切源最终停在 0;新操作继承未完成操作的意图,显式暂停及其修订号终止旧恢复副作用,过期 Promise 仍保留原结算语义;不依赖用户回调之后才执行的事件监听器 |
## 开始实现前需补齐的决定
+3 -3
View File
@@ -4,7 +4,7 @@
基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 217 项,范围 22 个包及工作区/示例。
状态:todo 147 / doing 1 / blocked 0 / done 69 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
状态:todo 146 / doing 1 / blocked 0 / done 70 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
前置依赖是启动条件;验收是完成条件。任务可以继续拆分,但不能复用或悄悄删除旧 ID。
@@ -128,7 +128,7 @@
| 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 | done |
| CORE-23 | artplayer<br>检查并补齐键盘、焦点与可访问名称 | CORE-13, CORE-14, CORE-17, ENG-05, BASE-04 | 主要控件/设置/模式退出的键盘与焦点回归、名称和字幕可用性检查及必要兼容修正 | 保持旧快捷键和 DOM/CSS 钩子;真实浏览器验证,不以静态属性检查代替交互;处理 BASE-DOM-01 的主要控件 Tab 不可达,保留既有名称与快捷键 | H | done |
| CORE-22 | artplayer<br>核心阶段完整验收 | CORE-21, CORE-23, PILOT-01, ENG-08, ENG-10, ENG-11 | 核心与旧插件的可自动化完整回归、资源/性能及明确外部验证缺口 | 核心自动化和公开差异处置通过;真实环境缺口链接包集成/REL 门槛,阶段完成不代表可公开发布 | H | done |
| CORE-24 | artplayer<br>修复连续切源的播放意图继承 | CORE-22, PKG-AUDIO-04 | 连续切源保留原播放意图,同时尊重显式暂停与最新来源,回归旧/新 audio | 真实暂停状态的受控测试、重入/取消/失败/销毁、三引擎和 main/legacy 产物验证通过;不恢复过期来源的公开副作用 | H | todo |
| CORE-24 | artplayer<br>修复连续切源的播放意图继承 | CORE-22, PKG-AUDIO-04 | 连续切源保留原播放意图,同时尊重显式暂停与最新来源,回归旧/新 audio | 真实暂停状态的受控测试、重入/取消/失败/销毁、三引擎和 main/legacy 产物验证通过;不恢复过期来源的公开副作用 | H | done |
## 5 包迁移:artplayer-plugin-chapter
@@ -463,7 +463,7 @@
- CORE-21: [记录](changes/2026-09-11-CORE-21-public-declarations.md) [记录](baselines/core-declarations-validation.json) [记录](core-public-types.md) [记录](typechecking.md)
- CORE-23: [记录](changes/2026-09-11-CORE-23-keyboard-focus.md) [记录](baselines/keyboard-validation.json)
- CORE-22: [记录](changes/2026-09-11-CORE-22-core-acceptance.md) [记录](baselines/core-setting-navigation-partial.json) [记录](core-acceptance.md) [记录](baselines/core-acceptance-validation.json)
- CORE-24: [记录](baselines/audio-combinations-first.json)
- CORE-24: [记录](baselines/audio-combinations-first.json) [记录](changes/2026-09-12-CORE-24-source-intent.md) [记录](baselines/core-source-intent-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)
+15
View File
@@ -1,5 +1,20 @@
# 进度与证据
## 最新完成:CORE-24 连续切源播放意图修复
Audio-05 检查点已提交 b5e96664,原六项候选核心回归保持原断言重跑全部通过。
切源现在继承尚未完成操作的播放意图,公开 play/pause 在用户回调前更新它;内部
暂停不覆盖意图,显式暂停和暂停修订号阻止晚到恢复的事件/提示副作用。
源切换单元 34 项、完整 CI 594 项通过;正式 main/legacy 三引擎各 81 项通过;
隔离 tarball 的 34 项运行时、5 组兼容类型、8 组精确类型通过,运行字节与正式文件一致。
覆盖率 0 门槛违例,Node 总行覆盖 61.04%,不声称全部源码均已覆盖。
CORE-SOURCE-01 关闭,AUDIO-RESUME-01、偏移原生对照、真实缓冲及 HLS 风险仍开放。
当前 217 项、70 done、1 doing、146 todo。本任务独立本地提交核实后恢复 Audio-05。
详见 [修复记录](changes/2026-09-12-CORE-24-source-intent.md) 和
[冻结证据](baselines/core-source-intent-validation.json)。未推送或发布,全局目标保持不变。
## 当前实施:Audio-05 发现连续切源回归,拆分 CORE-24
Audio-04 已独立提交 13c898c1。新组合测试覆盖旧/新核心、旧/新 audio 的连续切源、
+1 -1
View File
@@ -130,5 +130,5 @@
| AUDIO-SYNC-01 | open / 已复现 | Audio Track 未同步原生暂停/结束,偏移边界与切源/缓冲仍待完整验证 | PKG-AUDIO-02, PKG-AUDIO-03, PKG-AUDIO-05 |
| AUDIO-TYPE-01 | accepted-with-scope / 源码/产物事实 | Audio Track update 部分配置与公开/编辑器声明不一致 | PKG-AUDIO-04 |
| AUDIO-DEMO-01 | open / 源码/产物事实 | Audio Track README 示例链接名称不匹配且缺维护说明 | PKG-AUDIO-06 |
| CORE-SOURCE-01 | open / 已复现 | 候选核心连续切源丢失原播放意图 | CORE-24 |
| CORE-SOURCE-01 | resolved / 已复现 | 候选核心连续切源丢失原播放意图 | CORE-24 |
| AUDIO-RESUME-01 | open / 已复现 | WebKit 旧核心切源后候选音频保持暂停 | PKG-AUDIO-05 |
+9 -2
View File
@@ -2731,7 +2731,7 @@
"id": "CORE-SOURCE-01",
"title": "候选核心连续切源丢失原播放意图",
"confirmation": "reproduced",
"status": "open",
"status": "resolved",
"owners": [
"CORE-24"
],
@@ -2740,7 +2740,14 @@
"packages/artplayer/src/source/switch.ts"
],
"compatibleResolution": "继承被替代切源操作的播放意图;显式暂停可以取消恢复,过期操作继续只结算不发布副作用。",
"closureCriteria": "新旧对照的连续切源、手动暂停、重入/取消/错误/销毁和正式产物三引擎验证通过。"
"closureCriteria": "新旧对照的连续切源、手动暂停、重入/取消/错误/销毁和正式产物三引擎验证通过。",
"resolutionEvidence": [
"refactor/changes/2026-09-12-CORE-24-source-intent.md",
"refactor/baselines/core-source-intent-validation.json",
"test/source.test.js",
"test/browser/source-intent.spec.js"
],
"resolutionRationale": "待完成切源操作继承播放意图,公开 play/pause 在原生调用及用户监听器之前更新;内部暂停标记仅消费一次,暂停修订号阻止晚到恢复副作用。34 项源测试、594 项 CI、正式 main/legacy 各 81 项三引擎和原六项 Audio 失败重跑通过,隔离 tarball 字节/运行时/类型验证通过。范围是公开命令及切源操作,不关闭独立 AUDIO-RESUME-01、真机或完整发布门槛。"
},
{
"id": "AUDIO-RESUME-01",
+4 -2
View File
@@ -1249,12 +1249,14 @@
"CORE-22",
"PKG-AUDIO-04"
],
"status": "todo",
"status": "done",
"risk": "H",
"deliverable": "连续切源保留原播放意图,同时尊重显式暂停与最新来源,回归旧/新 audio",
"acceptance": "真实暂停状态的受控测试、重入/取消/失败/销毁、三引擎和 main/legacy 产物验证通过;不恢复过期来源的公开副作用",
"evidence": [
"baselines/audio-combinations-first.json"
"baselines/audio-combinations-first.json",
"changes/2026-09-12-CORE-24-source-intent.md",
"baselines/core-source-intent-validation.json"
]
},
{
+60
View File
@@ -0,0 +1,60 @@
import { expect, test } from './fixtures.js'
async function start(page, core) {
await page.goto(`/test/player.html?core=${core}`)
await page.evaluate(() => window.createPlayer('/test/pattern.mp4?intent=initial'))
await expect.poll(() => page.evaluate(() => window.art.isReady)).toBe(true)
await page.locator('#play').click()
await expect.poll(() => page.evaluate(() => window.art.currentTime)).toBeGreaterThan(0.3)
}
for (const core of ['published', 'candidate']) {
test(`${core}: rapid source switches preserve playing on the latest real video without plugins`, async ({ page }, testInfo) => {
await start(page, core)
await page.evaluate(async () => {
window.restarted = []
window.art.on('restart', url => window.restarted.push(url))
await Promise.all(['one', 'two', 'last'].map(name => window.art.switchUrl(`/test/pattern.mp4?intent=${name}`)))
})
await expect.poll(() => page.evaluate(() => window.art.currentTime)).toBeGreaterThan(0.3)
expect(await page.evaluate(() => window.art.video.paused)).toBe(false)
expect(await page.evaluate(() => window.art.video.currentSrc)).toContain('intent=last')
expect(await page.evaluate(() => window.restarted)).toEqual(core === 'candidate' ? ['/test/pattern.mp4?intent=last'] : ['one', 'two', 'last'].map(name => `/test/pattern.mp4?intent=${name}`))
await testInfo.attach('source-intent', { contentType: 'application/json', body: JSON.stringify(await page.evaluate(() => ({ time: window.art.currentTime, paused: window.art.video.paused, restarts: window.restarted }))) })
await page.evaluate(() => window.art.destroy())
})
}
for (const command of ['pause-before-next', 'pause-after-next', 'reentrant-pause', 'pause-listener-switch']) {
test(`candidate: ${command} cancels automatic resume until the next user play`, async ({ page }, testInfo) => {
await start(page, 'candidate')
await page.evaluate(async (command) => {
const { art } = window
if (command === 'reentrant-pause')
art.once('pause', () => art.pause())
const first = art.switchUrl('/test/pattern.mp4?intent=first')
if (command === 'pause-before-next')
art.pause()
let last
if (command === 'pause-listener-switch') {
art.once('pause', () => {
last = art.switchUrl('/test/pattern.mp4?intent=last')
})
art.pause()
}
else {
last = art.switchUrl('/test/pattern.mp4?intent=last')
}
if (command === 'pause-after-next')
art.pause()
await Promise.all([first, last])
}, command)
expect(await page.evaluate(() => window.art.video.paused)).toBe(true)
expect(await page.evaluate(() => window.art.video.currentSrc)).toContain('intent=last')
await testInfo.attach('source-paused', { contentType: 'application/json', body: JSON.stringify(await page.evaluate(() => ({ paused: window.art.video.paused, readyState: window.art.video.readyState }))) })
await page.locator('#play').click()
await expect.poll(() => page.evaluate(() => window.art.currentTime)).toBeGreaterThan(0.3)
expect(await page.evaluate(() => window.art.video.paused)).toBe(false)
await page.evaluate(() => window.art.destroy())
})
}
+161 -1
View File
@@ -4,11 +4,12 @@ import { mock, test } from 'node:test'
import { setImmediate } from 'node:timers/promises'
import { loadModules } from './helpers/load.js'
const { Emitter, urlMix, switchMix, playMix, beginLifecycle, getScope, positionRestoration, currentTimeMix } = await loadModules({
const { Emitter, urlMix, switchMix, playMix, pauseMix, beginLifecycle, getScope, positionRestoration, currentTimeMix } = await loadModules({
Emitter: 'packages/artplayer/src/utils/emitter',
urlMix: 'packages/artplayer/src/player/urlMix',
switchMix: 'packages/artplayer/src/player/switchMix',
playMix: 'packages/artplayer/src/player/playMix',
pauseMix: 'packages/artplayer/src/player/pauseMix',
beginLifecycle: { file: 'packages/artplayer/src/lifecycle/instance', name: 'beginLifecycle' },
getScope: { file: 'packages/artplayer/src/lifecycle/instance', name: 'getScope' },
positionRestoration: { file: 'packages/artplayer/src/source/restore-position', name: 'positionRestoration' },
@@ -26,15 +27,172 @@ function createArt(customType) {
aspectRatio: '16:9',
loading: { show: false },
notice: { show: '' },
i18n: { get: key => key },
pause: mock.fn(),
play: mock.fn(() => Promise.resolve()),
})
beginLifecycle(art)
art.template.$video.pause = () => {
art.playing = false
}
pauseMix(art)
art.pause = mock.fn(art.pause)
urlMix(art)
switchMix(art)
return art
}
function createPlayingArt() {
const art = createArt()
const video = art.template.$video
video.paused = false
Object.defineProperty(art, 'playing', { get: () => !video.paused })
video.pause = () => {
video.paused = true
}
video.play = async () => {
video.paused = false
}
art.constructor = { instances: [] }
playMix(art)
art.play = mock.fn(art.play)
return art
}
test('rapid switches inherit playback intent despite their own native pauses', async () => {
const art = createPlayingArt()
const first = art.switchUrl('first.mp4')
assert.equal(art.playing, false)
const second = art.switchUrl('second.mp4')
const third = art.switchUrl('last.mp4')
await Promise.all([first, second])
assert.equal(art.play.mock.callCount(), 0)
art.emit('video:canplay')
await third
assert.equal(art.url, 'last.mp4')
assert.equal(art.playing, true)
assert.equal(art.play.mock.callCount(), 1)
assert.deepEqual(Object.keys(art.e), [])
})
test('explicit pause during a switch cancels inherited playback for subsequent switches', async () => {
const art = createPlayingArt()
const first = art.switchUrl('first.mp4')
art.pause()
const second = art.switchUrl('last.mp4')
art.emit('video:canplay')
await Promise.all([first, second])
assert.equal(art.play.mock.callCount(), 0)
assert.equal(art.playing, false)
assert.deepEqual(Object.keys(art.e), [])
})
test('explicit pause before readiness prevents the current switch from resuming', async () => {
const art = createPlayingArt()
const pending = art.switchUrl('pending.mp4')
art.pause()
art.emit('video:canplay')
await pending
assert.equal(art.playing, false)
assert.equal(art.play.mock.callCount(), 0)
})
test('a reentrant explicit pause is not swallowed by the internal switch pause', async () => {
const art = createPlayingArt()
art.once('pause', () => art.pause())
const pending = art.switchUrl('pending.mp4')
art.emit('video:canplay')
await pending
assert.equal(art.playing, false)
assert.equal(art.play.mock.callCount(), 0)
assert.deepEqual(Object.keys(art.e), [])
})
test('a user pause updates intent before an existing pause listener starts another switch', async () => {
const art = createPlayingArt()
const first = art.switchUrl('first.mp4')
let second
art.once('pause', () => {
second = art.switchUrl('last.mp4')
})
art.pause()
art.emit('video:canplay')
await Promise.all([first, second])
assert.equal(art.url, 'last.mp4')
assert.equal(art.playing, false)
assert.equal(art.play.mock.callCount(), 0)
assert.deepEqual(Object.keys(art.e), [])
})
test('a pending explicit play command survives replacement even before native play fulfills', async () => {
const art = createPlayingArt()
const first = art.switchUrl('first.mp4')
art.pause()
let finish
let calls = 0
art.template.$video.play = () => {
if (++calls === 1)
return new Promise(resolve => finish = resolve)
art.template.$video.paused = false
return Promise.resolve()
}
const playing = art.play()
const second = art.switchUrl('last.mp4')
await first
art.emit('video:canplay')
await second
finish(42)
assert.equal(await playing, 42)
assert.equal(calls, 2)
assert.equal(art.playing, true)
})
test('a late native resume cannot publish play or clear notice after an explicit pause', async () => {
const art = createPlayingArt()
let finish
const events = []
art.on('play', () => events.push('play'))
art.template.$video.play = () => new Promise(resolve => finish = resolve)
const pending = art.switchUrl('pending.mp4')
art.emit('video:canplay')
art.pause()
finish(42)
await pending
assert.deepEqual(events, [])
assert.equal(art.notice.show, 'Pause')
assert.equal(art.playing, false)
})
test('cancelled, failed and completed switches never leak playback intent to a new source', async () => {
for (const boundary of ['direct-url', 'failure', 'completed', 'destroy']) {
const art = createPlayingArt()
const pending = art.switchUrl('first.mp4')
if (boundary === 'failure') {
const error = new Error('Controlled media failure')
const rejected = assert.rejects(pending, actual => actual === error)
art.emit('video:error', error)
await rejected
}
else {
if (boundary === 'direct-url')
art.url = 'direct.mp4'
if (boundary === 'completed')
art.emit('video:canplay')
if (boundary === 'destroy')
getScope(art).dispose()
await pending
}
art.pause()
const played = art.play.mock.callCount()
const next = art.switchUrl('next.mp4')
art.emit('video:canplay')
await next
assert.equal(art.play.mock.callCount(), played, boundary)
assert.equal(art.playing, false, boundary)
assert.deepEqual(Object.keys(art.e), [])
}
})
test('source assignment does not revoke a caller-owned Blob URL still used elsewhere', async (t) => {
const art = createArt()
const blob = new Blob(['shared media'], { type: 'video/mp4' })
@@ -349,6 +507,8 @@ test('late resume fulfillment or rejection cannot clear a newer operation notice
const first = art.switchUrl('first.mp4')
art.emit('video:canplay')
art.playing = false
// Model the explicit pause command, not only a read-only playback-state snapshot.
art.pause()
const second = art.switchUrl('second.mp4')
await first
art.notice.show = 'new source notice'