fix(core): [CORE-SUBTITLE-OFFSET-01] refresh paused native cue membership and order

This commit is contained in:
Harvey Zhao committed 2026-09-14 12:49:22 +08:00
1 parent b5dee252d4
commit efed847cdf
23 files changed
+6789 -27

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
+14
View File
@@ -6180,6 +6180,19 @@ function stateMix(art) {
}
});
}
function refreshPausedCues(track, video, cues) {
if (!track || track.mode === "disabled" || !video?.paused || !Number.isFinite(video.currentTime))
return;
const time2 = video.currentTime;
const expected = cues.filter((cue) => cue.startTime <= time2 && time2 < cue.endTime);
const active2 = Array.from(track.activeCues || []);
if (expected.length === active2.length && expected.every((cue, index) => active2[index] === cue))
return;
for (const cue of cues)
track.removeCue(cue);
for (const cue of cues)
track.addCue(cue);
}
function subtitleOffsetMix(art) {
const { notice, i18n, template } = art;
def(art, "subtitleOffset", {
@@ -6199,6 +6212,7 @@ function subtitleOffsetMix(art) {
cue.startTime = clamp(cue.originalStartTime + offset, 0, art.duration);
cue.endTime = clamp(cue.originalEndTime + offset, 0, art.duration);
}
refreshPausedCues(template.$track.track, template.$video, cues);
art.subtitle.update();
notice.show = `${i18n.get("Subtitle Offset")}: ${value}s`;
art.emit("subtitleOffset", value);
@@ -149,9 +149,11 @@ Read native `template.$track.track.cues` for old-core probes: older hosts do not
provide the later `subtitle.cues` getter. Core5.1.2/5.1.7 only render their first
active cue; the matrix explicitly records lost simultaneous languages for1.1.0+
and candidate, while1.0.0's index-merged cue displays both. This remains an open
compatibility issue, not an approved limitation. Firefox paused offset assertions
currently fail on old and candidate cores; a WebKit5.3.0 source-switch failure is
intermittent. See the task05 change and validation JSON for exact failed runs.
compatibility issue, not an approved limitation. CORE-SUBTITLE-OFFSET-01 corrects
paused offsets in the candidate core; published Firefox hosts retain their exact
historical defect observations. WebKit5.3.0/5.4.0 source-switch failures remain
under task05 investigation. See its change and validation JSON for original failures
and the independent core offset change for the corrected candidate evidence.
The matrix is runnable with `yarn test:browser test/browser/multiple-subtitles-combinations.spec.js`;
do not replace these failures with skips or count boundary observations as acceptance.
See `../../refactor/baselines/multiple-subtitles-contract.md` for exact historical differences.
+12
View File
@@ -670,6 +670,18 @@ transport cancellation, conversion, ownership, native DOM events and cue renderi
The offset mixin remains the player property entry, with original cue bounds retained
on native cue objects. Validation evidence and remaining release gates live in refactor/.
timing.ts handles native active-cue invalidation after a paused offset edit. Some
engines retain the old membership even after cue timestamps change. It compares
the expected current interval/order to native activeCues and, only on mismatch,
removes and re-adds the original cue objects in their captured order. The mixin
then performs the existing update, notice and subtitleOffset emission. It does
not seek media, replace cues, toggle track mode or allocate background work.
Playing, disabled and capability-limited hosts retain their existing path. A
mode-toggle workaround was rejected because Firefox reordered tied cues during
successive offsets. Tests in subtitle-offset.spec.js verify synchronous native,
public and rendered membership, identity, order, disabled state and resumed play;
subtitle-offset-native.spec.js is an independent native video/track probe.
request.ts races work against scope closure. Superseded requests settle undefined even
when the transport ignores AbortSignal; active failures reject. state.ts owns request
and track scopes and only generated object URLs. A caller-owned URL is never revoked.
+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
+14
View File
@@ -6180,6 +6180,19 @@ function stateMix(art) {
}
});
}
function refreshPausedCues(track, video, cues) {
if (!track || track.mode === "disabled" || !video?.paused || !Number.isFinite(video.currentTime))
return;
const time2 = video.currentTime;
const expected = cues.filter((cue) => cue.startTime <= time2 && time2 < cue.endTime);
const active2 = Array.from(track.activeCues || []);
if (expected.length === active2.length && expected.every((cue, index) => active2[index] === cue))
return;
for (const cue of cues)
track.removeCue(cue);
for (const cue of cues)
track.addCue(cue);
}
function subtitleOffsetMix(art) {
const { notice, i18n, template } = art;
def(art, "subtitleOffset", {
@@ -6199,6 +6212,7 @@ function subtitleOffsetMix(art) {
cue.startTime = clamp(cue.originalStartTime + offset, 0, art.duration);
cue.endTime = clamp(cue.originalEndTime + offset, 0, art.duration);
}
refreshPausedCues(template.$track.track, template.$video, cues);
art.subtitle.update();
notice.show = `${i18n.get("Subtitle Offset")}: ${value}s`;
art.emit("subtitleOffset", value);
@@ -1,4 +1,5 @@
import type { SubtitleOffsetHost } from '../subtitle/types'
import { refreshPausedCues } from '../subtitle/timing'
import { clamp, def } from '../utils'
export default function subtitleOffsetMix(art: SubtitleOffsetHost): void {
@@ -21,6 +22,7 @@ export default function subtitleOffsetMix(art: SubtitleOffsetHost): void {
cue.startTime = clamp(cue.originalStartTime + offset, 0, art.duration)
cue.endTime = clamp(cue.originalEndTime + offset, 0, art.duration)
}
refreshPausedCues(template.$track.track, template.$video, cues)
art.subtitle.update()
notice.show = `${i18n.get('Subtitle Offset')}: ${value}s`
art.emit('subtitleOffset', value)
+16
View File
@@ -0,0 +1,16 @@
import type { SubtitleCue } from './types'
export function refreshPausedCues(track: TextTrack | undefined, video: { paused?: boolean, currentTime?: number } | undefined, cues: readonly SubtitleCue[]): void {
if (!track || track.mode === 'disabled' || !video?.paused || !Number.isFinite(video.currentTime))
return
const time = video.currentTime!
const expected = cues.filter(cue => cue.startTime <= time && time < cue.endTime)
const active = Array.from(track.activeCues || [])
if (expected.length === active.length && expected.every((cue, index) => active[index] === cue))
return
// Some engines retain stale active flags after timing edits while paused.
for (const cue of cues)
track.removeCue(cue)
for (const cue of cues)
track.addCue(cue)
}
+1 -1
View File
@@ -36,7 +36,7 @@ export interface SubtitleView {
}
export interface SubtitleOffsetHost {
template: { $track?: SubtitleTrack | null }
template: { $track?: SubtitleTrack | null, $video?: { paused?: boolean, currentTime?: number } }
subtitle: { readonly cues: SubtitleCue[], update: () => void }
readonly duration: number
notice: NoticeSink
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,75 @@
# CORE-SUBTITLE-OFFSET-01: paused subtitle timing invalidation
Baseline: b5dee252d461f91389c282a47401c5f8fd9a4483. Independent core correction
discovered during PKG-MULTI-SUB-05; that package combination task remains doing.
## Reproduction and implementation
Firefox155 keeps cues in native activeCues after a paused offset changes their
start/end outside currentTime. At paused time1.5, cues moved from1..3 to2..4
still render. The old5.4.0 core and pre-change candidate both fail. A native
HTMLVideoElement/TextTrack probe with no Artplayer instance reproduces the stale
membership. Chromium153 and Windows WebKit26.6 have separate recorded observations.
The first script-created-track probe did not become active on WebKit; the final
probe uses a loaded HTML track matching the real integration, and records this
fixture correction. Its initial overly precise seek assertion was corrected to
require the actual1..2 interval; candidate offset tests separately compare each
synchronous read against the pre-edit time.
`subtitle/timing.ts` owns a small synchronous native invalidation step. After
changing cue bounds, the existing offset mixin passes the native track, optional
media capabilities and captured cue array. If paused and native membership/order
differs from cue intervals at currentTime, remove and re-add the original cue
objects in that order. Disabled tracks, playing media, missing capabilities and
already-correct native state do not enter this path. No polling, timer, observer,
new public option, UA sniffing, media seek or replacement cue is introduced.
The first attempted mode-toggle correction fixed membership but reversed tied
captions on a subsequent negative offset. That failing candidate report is retained;
the final reinsertion implementation preserves order through1,-1,0,-3,0 offsets.
Original cue bounds, identities, track mode and playback time remain. Update,
notice and the public subtitleOffset event keep their existing synchronous order
and values. Native cuechange/enter/exit notification can now reflect the corrected
active membership, which is an intentional consequence of fixing stale activity.
There is no new owned resource or disposal path.
## Verification and scope
Exact hashes, browser versions, logs, native state and artifact maps are recorded in
[subtitle-offset-validation.json](../baselines/subtitle-offset-validation.json).
- Five new controlled regressions join the existing subtitle suite: stale/missing
members, order drift, cue identity, update/event ordering, correct overlap,
disabled/playing/capability-limited hosts. The six-file core group passes58 tests.
- Strict core and root consumer TS checks pass. Public generated declarations remain
unchanged:37 files checked,0 drift. Runtime helper types are internal only.
- Expanded source browser run153:150 passing assertions,3 failures. Two failures
are old WebKit core5.3.0/5.4.0 source switching and remain task05 risks; the third
was the diagnostic seek precision assumption explained above. Candidate offset,
lifecycle and combination checks pass. The run includes explicitly labelled old
defects and must not be presented as153 successful compatibility acceptances.
- Actual main build:15 passing cases comprise9 native probes,3 published behavior
observations and3 candidate offset regressions. Actual legacy build:3 candidate
offset cases pass. Candidate tests also cover disabled tracks and resumed play.
- Three core formats and docs copies are rebuilt. The normal core build clears the
dist directory, so build:i18n regenerates all11 languages; those bytes remain
unchanged. Actual import/SSR/i18n distribution checks pass5 tests. CI regression
passes50 tests. Lint retains only its existing docs declaration warning.
Published Firefox defects remain explicit in the offset and combination suites;
only the candidate's assertion changes from failing to correct. The independent
old-host overlap and WebKit source-switch issues are not waived or resolved here.
No physical-device, full package, remote CI or publication acceptance is claimed.
No dependency, lockfile, public declaration, version or package entrypoint change.
## Maintenance and rollback
Change timestamp invalidation in timing.ts and property bounds/notice behavior in
player/subtitleOffsetMix.ts. Keep the native probe independent of player instances.
Run node --test test/subtitle.test.js, strict core/root tsc, and the subtitle-offset,
subtitle-lifecycle and multiple-subtitles-combinations browser suites. For actual
artifact testing use the existing ARTPLAYER_BROWSER_ARTIFACTS map and include both
core and chapter mappings, as required by the shared fixture server.
Revert the dedicated task commit, including build outputs, to remove this correction.
This is a local commit only, with no push or npm publication authorization.
+6 -4
View File
@@ -2,9 +2,9 @@
> 由 tasks.json 生成。请修改数据后运行 `node refactor/scripts/plan.mjs --write`,不要手改本表。
基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 247 项,范围 22 个包及工作区/示例。
基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 248 项,范围 22 个包及工作区/示例。
状态:todo 55 / doing 17 / blocked 0 / done 175 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
状态:todo 55 / doing 17 / blocked 0 / done 176 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
前置依赖是启动条件;验收是完成条件。任务可以继续拆分,但不能复用或悄悄删除旧 ID。
@@ -14,7 +14,7 @@
| 包 | 基线版本 | 任务 |
| --- | --- | --- |
| artplayer | 5.4.1 | CORE-01, CORE-02, CORE-03, CORE-04, CORE-05, CORE-06, CORE-07, CORE-08, CORE-09, CORE-10, CORE-11, CORE-12, CORE-13, CORE-14, CORE-15, CORE-16, CORE-17, CORE-18, CORE-19, CORE-20, CORE-21, CORE-23, CORE-22, CORE-24, SITE-BUILD-01, CORE-25, ENG-12 |
| artplayer | 5.4.1 | CORE-01, CORE-02, CORE-03, CORE-04, CORE-05, CORE-06, CORE-07, CORE-08, CORE-09, CORE-10, CORE-11, CORE-12, CORE-13, CORE-14, CORE-15, CORE-16, CORE-17, CORE-18, CORE-19, CORE-20, CORE-21, CORE-23, CORE-22, CORE-24, SITE-BUILD-01, CORE-25, ENG-12, CORE-SUBTITLE-OFFSET-01 |
| artplayer-plugin-ads | 2.1.0 | PKG-ADS-01, PKG-ADS-02, PKG-ADS-03, PKG-ADS-04, PKG-ADS-05, PKG-ADS-06 |
| artplayer-plugin-ambilight | 1.1.0 | PKG-AMBILIGHT-01, PKG-AMBILIGHT-02, PKG-AMBILIGHT-03, PKG-AMBILIGHT-04, PKG-AMBILIGHT-PROXY-01, PKG-AMBILIGHT-05, PKG-AMBILIGHT-06, PKG-FACTORY-01 |
| artplayer-plugin-asr | 2.1.0 | PKG-ASR-01, PKG-ASR-02, PKG-ASR-03, PKG-ASR-04, PKG-ASR-05, PKG-ASR-06, PKG-ASR-07, PKG-ASR-08, PKG-ASR-09 |
@@ -133,6 +133,7 @@
| 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 | done |
| CORE-25 | artplayer<br>修复无 navigator 环境读取默认选项 | CORE-22, ENG-12 | 安全读取默认语言并保留浏览器已有默认值;实际 Node 与发布产物正反例 | 记录旧发布版 ReferenceError;候选在无 navigator 时可读默认选项,浏览器语言和公开类型不回归;不以补全局对象隐藏错误 | M | done |
| CORE-SUBTITLE-OFFSET-01 | artplayer<br>修复暂停偏移后的原生活动字幕刷新 | CORE-15, CORE-22 | 暂停字幕偏移的原生轨道一致性修复、兼容回归和维护说明 | 保持cue对象、顺序、媒体时间、模式和公开事件;真实浏览器正负偏移及生命周期通过,历史缺陷单列 | H | done |
## 5 包迁移:artplayer-plugin-chapter
@@ -228,7 +229,7 @@
| PKG-MULTI-SUB-07 | artplayer-plugin-multiple-subtitles<br>修复字幕内嵌时间戳序列化 | PKG-MULTI-SUB-03 | 保留数字时间戳节点、修正无效 NaN 输出、明确旧/新字幕输出差异 | 旧九份实现可复现,候选有效内嵌时间戳及文字/CSS/原生字幕通过,公开工厂与选择接口不变 | H | done |
| PKG-MULTI-SUB-08 | artplayer-plugin-multiple-subtitles<br>修复字幕实体解码与嵌套标签包装 | PKG-MULTI-SUB-04, PKG-MULTI-SUB-07 | 使用解析器既有实体表入口修复多余分号;保护字面文本并以兄弟节点包装标签,保留CSS钩子和时间戳 | 冻结旧实现复现、实体/双重编码/标签/选择重置回归、新旧核心真实字幕显示通过;vendor原文与来源校验不变,类型/分发兼容 | M | done |
| PKG-MULTI-SUB-05 | artplayer-plugin-multiple-subtitles<br>验证新旧核心和组合 | PKG-MULTI-SUB-04, CORE-22, PKG-MULTI-SUB-07, PKG-MULTI-SUB-08 | 与核心字幕/偏移/全屏组合,旧名称调用不变 | 最终核心与原支持范围核心分别通过;设备/SDK 缺证据不能标完成 | H | doing |
| PKG-MULTI-SUB-06 | artplayer-plugin-multiple-subtitles<br>验证分发并同步文档 | PKG-MULTI-SUB-05, ENG-07 | multiple.subtitles.js、parser 来源许可及打包边界 | tarball 入口/资源、类型、8082 demo 和 README 一致,有回退记录 | H | todo |
| PKG-MULTI-SUB-06 | artplayer-plugin-multiple-subtitles<br>验证分发并同步文档 | PKG-MULTI-SUB-05, ENG-07, CORE-SUBTITLE-OFFSET-01 | multiple.subtitles.js、parser 来源许可及打包边界 | tarball 入口/资源、类型、8082 demo 和 README 一致,有回退记录 | H | todo |
## 5 包迁移:artplayer-plugin-jassub
@@ -645,3 +646,4 @@
- PKG-AUTO-THUMB-09: [记录](changes/2026-09-14-PKG-AUTO-THUMB-09-alias.md) [记录](baselines/auto-thumbnail-alias-validation.json)
- PKG-AUTO-THUMB-10: [记录](changes/2026-09-14-PKG-AUTO-THUMB-10-encoding.md) [记录](baselines/auto-thumbnail-encoding-validation.json)
- ENG-LINT-01: [记录](changes/2026-09-14-ENG-LINT-01-manifest-order.md) [记录](baselines/manifest-lint-validation.json)
- CORE-SUBTITLE-OFFSET-01: [记录](changes/2026-09-14-CORE-SUBTITLE-OFFSET-01-paused.md) [记录](baselines/subtitle-offset-validation.json)
+16
View File
@@ -1,5 +1,21 @@
# 进度与证据
## CORE-SUBTITLE-OFFSET-01 暂停字幕偏移修复完成
原生video/track探针确认Firefox155暂停时cue时间更新后活动标记陈旧;候选核心
增加独立timing模块,只在暂停且原生cue成员/顺序与当前时间不一致时,将原cue
按原顺序移除并重新加入。保持对象、模式、媒体时间与update/notice/公开偏移事件。
先前mode切换方案会逆转同时间字幕顺序,已弃用并保留失败证据。新增5项风险回归,
核心定向58项、严格核心/消费者TS、37声明零漂移、导入5项及CI50项通过。
扩展源码浏览器153项中150断言通过,旧WebKit两项切源失败保持open,另一个原生
探针时间精度假设已修正。正式main15项(含原生和历史观察)、legacy3项全部通过,
候选覆盖正负/恢复偏移、禁用轨道和恢复播放。lint仅1既有warning。三格式/docs
重建,i18n经正常脚本重建且无差异;main gzip9增加109字节,无依赖、版本或公开
声明改动。见[变更](changes/2026-09-14-CORE-SUBTITLE-OFFSET-01-paused.md)与
[证据](baselines/subtitle-offset-validation.json)。248项:176 done、17 doing、55 todo。
关闭候选偏移缺陷,旧核心的对应失败保留明确观察;这不完成PKG-MULTI-SUB-05。
下一步继续旧核心双语显示适配和WebKit切源定位,再完成剩余包组合/分发门槛。
## PKG-MULTI-SUB-05 新旧核心组合检查点(未完成)
任务08已独立提交675b095b2,提交审计175 done/171专属提交/4初始例外通过。
+1 -1
View File
@@ -265,5 +265,5 @@
| DEV-SERVOR-PORT-01 | resolved / 已复现 | Servor exits the entire dev process with status zero when the requested port is occupied | MOD-DEV-01 |
| AUTO-THUMB-ENCODE-TIMEOUT-01 | resolved / 源码/产物事实 | Pending JPEG encoding has no bounded lifetime after frame readiness finishes | PKG-AUTO-THUMB-10 |
| MULTI-SUB-MARKUP-01 | resolved / 已复现 | Semantic cue nodes receive HTML wrappers inside their annotation fields | PKG-MULTI-SUB-08 |
| MULTI-SUB-OFFSET-01 | open / 已复现 | Paused Firefox subtitle offset leaves out-of-time native cues active and captions visible | PKG-MULTI-SUB-05 |
| MULTI-SUB-OFFSET-01 | resolved / 已复现 | Paused Firefox subtitle offset leaves out-of-time native cues active and captions visible | PKG-MULTI-SUB-05, CORE-SUBTITLE-OFFSET-01 |
| MULTI-SUB-SWITCH-01 | open / 待取证 | Intermittent WebKit core5.3.0 caption loss after selected-track source switch | PKG-MULTI-SUB-05 |
+20 -7
View File
@@ -6001,24 +6001,35 @@
"id": "MULTI-SUB-OFFSET-01",
"title": "Paused Firefox subtitle offset leaves out-of-time native cues active and captions visible",
"confirmation": "reproduced",
"workspaceState": "Two runs on Firefox155 with cores5.3.0/5.4.0/candidate: paused at1.5, subtitleOffset=1 changes start/end to2/4 but native activeCues and HTML still contain both captions after the assertion timeout. Candidate assertion remains failing. No production correction yet.",
"workspaceState": "Native Firefox155 probe confirms stale paused membership. Candidate reinserts original cue objects only when paused membership/order is inconsistent; mode toggling was rejected after tied-caption reversal. Real main and legacy pass current paused positive/negative/reset offsets, disabled tracks and resumed playback in all3 engines. Published cores remain immutable defect observations, and package task05 still owns old-host combination acceptance.",
"compatibleResolution": "Reduce native timing invalidation and ensure offset updates render the correct active captions without seeking media or changing public timing and event contracts.",
"closureCriteria": "Candidate paused positive/negative/reset offsets refresh correctly, transitions and lifecycle regressions pass; historical behavior and actual old-core compatibility are separately documented.",
"status": "open",
"status": "resolved",
"owners": [
"PKG-MULTI-SUB-05"
"PKG-MULTI-SUB-05",
"CORE-SUBTITLE-OFFSET-01"
],
"evidence": [
"refactor/changes/2026-09-14-PKG-MULTI-SUB-05-combinations.md",
"refactor/baselines/multiple-subtitles-combinations-validation.json",
"test/browser/multiple-subtitles-combinations.spec.js"
]
"test/browser/multiple-subtitles-combinations.spec.js",
"refactor/changes/2026-09-14-CORE-SUBTITLE-OFFSET-01-paused.md",
"refactor/baselines/subtitle-offset-validation.json",
"test/browser/subtitle-offset-native.spec.js",
"test/browser/subtitle-offset.spec.js",
"test/subtitle.test.js"
],
"resolutionEvidence": [
"refactor/changes/2026-09-14-CORE-SUBTITLE-OFFSET-01-paused.md",
"refactor/baselines/subtitle-offset-validation.json"
],
"resolutionRationale": "Candidate core fix passes focused native/public/rendered membership, identity/order/time/mode/event and lifecycle checks, with actual main/legacy evidence. Historical Firefox failures are separately asserted and documented; closing this candidate defect does not approve all old-core combinations."
},
{
"id": "MULTI-SUB-SWITCH-01",
"title": "Intermittent WebKit core5.3.0 caption loss after selected-track source switch",
"confirmation": "unverified",
"workspaceState": "One Windows WebKit26.6 run lost Translation after switchUrl and seek1.5; diagnostic rerun passed without a production change. Root cause and reproducibility are unresolved; do not mark accepted or fixed.",
"workspaceState": "One Windows WebKit26.6 run lost Translation after switchUrl and seek1.5; diagnostic rerun passed without a production change. Root cause and reproducibility are unresolved; do not mark accepted or fixed. Expanded153 offset/lifecycle run again lost the selected caption after source switch on published5.3.0 and5.4.0 in WebKit, while candidate core passed. Root cause remains unclassified; the offset fix does not close this issue.",
"compatibleResolution": "Inspect native track loading and source-switch ordering with deterministic state evidence; distinguish historical host failure from fixture ordering and candidate regression.",
"closureCriteria": "Root cause classified with targeted evidence, any required fix validated, and changed candidate/old-core combination rerun without hiding the original failure.",
"status": "open",
@@ -6028,7 +6039,9 @@
"evidence": [
"refactor/changes/2026-09-14-PKG-MULTI-SUB-05-combinations.md",
"refactor/baselines/multiple-subtitles-combinations-validation.json",
"test/browser/multiple-subtitles-combinations.spec.js"
"test/browser/multiple-subtitles-combinations.spec.js",
"refactor/changes/2026-09-14-CORE-SUBTITLE-OFFSET-01-paused.md",
"refactor/baselines/subtitle-offset-validation.json"
]
}
]
+22 -1
View File
@@ -2429,7 +2429,8 @@
],
"dependsOn": [
"PKG-MULTI-SUB-05",
"ENG-07"
"ENG-07",
"CORE-SUBTITLE-OFFSET-01"
],
"status": "todo",
"risk": "H",
@@ -5136,6 +5137,26 @@
"changes/2026-09-14-ENG-LINT-01-manifest-order.md",
"baselines/manifest-lint-validation.json"
]
},
{
"id": "CORE-SUBTITLE-OFFSET-01",
"phase": "3-4 核心迁移",
"title": "修复暂停偏移后的原生活动字幕刷新",
"scope": [
"artplayer"
],
"dependsOn": [
"CORE-15",
"CORE-22"
],
"status": "done",
"risk": "H",
"deliverable": "暂停字幕偏移的原生轨道一致性修复、兼容回归和维护说明",
"acceptance": "保持cue对象、顺序、媒体时间、模式和公开事件;真实浏览器正负偏移及生命周期通过,历史缺陷单列",
"evidence": [
"changes/2026-09-14-CORE-SUBTITLE-OFFSET-01-paused.md",
"baselines/subtitle-offset-validation.json"
]
}
]
}
+13 -2
View File
@@ -219,8 +219,19 @@ VTT/SRT selection, offsets/fullscreen/source changes and multi-instance URL
ownership. Old1.0.0/1.1.0/1.2.0 factories run unchanged on the candidate core.
Explicit single-active-cue probes record historical and candidate overlap defects;
their passing assertions do not mean simultaneous captions are compatible.
Paused Firefox offset checks remain failing pending a production fix, and the
original WebKit source-switch failure is retained despite a passing rerun.
Candidate paused Firefox offsets are corrected by CORE-SUBTITLE-OFFSET-01. Old
Firefox hosts retain explicit defect observations; their behavior is not silently
counted as correct. WebKit source-switch failures remain under investigation.
See `refactor/changes/2026-09-14-PKG-MULTI-SUB-05-combinations.md` before changing
expectations. Reports include native active cues and rendered text. The new frozen
5.1.2 core route verifies archive integrity and member hashes before serving.
`subtitle-offset.spec.js` checks current/actual published core offset behavior,
including synchronous native/public/rendered captions, identical cue objects,
unchanged media time, disabled mode and resumed playback. The published Firefox
branch asserts the original incorrect captions; the candidate must have the correct
membership and order. `subtitle-offset-native.spec.js` loads a real HTML track
without creating an Artplayer instance to compare native timing edits, cue
reinsertion and mode toggling. It records actual time within the required interval,
not a fabricated millisecond-exact seek landing. These diagnostic observations
do not claim every native engine strategy is correct.
@@ -38,7 +38,7 @@ async function seek(page, time) {
}
for (const core of cores.filter(core => !['published-5.1.2', 'published-5.1.7'].includes(core))) {
test(`Multiple subtitles candidate / ${core}: timed VTT and SRT, offsets, fullscreen and source change`, async ({ page, browser }, testInfo) => {
test(`Multiple subtitles candidate / ${core}: timed VTT and SRT, offsets, fullscreen and source change`, async ({ page, browser, browserName }, testInfo) => {
await prepare(page, core, candidate, browser, testInfo)
await page.evaluate(async () => {
await window.art.plugins.add(window.multipleFactory({ subtitles: [{ name: 'a', url: '/test/combination.vtt' }, { name: 'b', url: '/test/combination.srt' }] }))
@@ -54,14 +54,17 @@ for (const core of cores.filter(core => !['published-5.1.2', 'published-5.1.7'].
window.art.subtitleOffset = 1
})
expect(await page.evaluate(() => Array.from(window.art.template.$track.track.cues, cue => [cue.startTime, cue.endTime]))).toEqual([[2, 4], [2, 4], [5, 7]])
await expect(page.locator('.art-subtitle-a')).toHaveCount(0)
const staleHistoricalOffset = core !== 'candidate' && browserName === 'firefox'
await expect(page.locator('.art-subtitle-a')).toHaveCount(staleHistoricalOffset ? 1 : 0)
await seek(page, 2.5)
await expect(page.locator('.art-subtitle-a')).toHaveText('First')
await page.evaluate(() => {
window.art.subtitleOffset = -1
})
expect(await page.evaluate(() => Array.from(window.art.template.$track.track.cues, cue => [cue.startTime, cue.endTime]))).toEqual([[0, 2], [0, 2], [3, 5]])
await expect(page.locator('.art-subtitle-a')).toHaveCount(0)
await expect(page.locator('.art-subtitle-a')).toHaveCount(staleHistoricalOffset ? 1 : 0)
if (staleHistoricalOffset)
await testInfo.attach('historical-paused-offset-defect', { contentType: 'application/json', body: JSON.stringify({ core, browser: browser.version(), acceptedAsCorrect: false, observation: 'Published core keeps a stale active caption after paused offsets; candidate must clear it. Dedicated native/offset probes retain exact state.' }) })
await page.evaluate(() => {
window.art.subtitleOffset = 0
})
@@ -0,0 +1,58 @@
import { expect, test } from './fixtures.js'
for (const invalidation of ['time-only', 'remove-add', 'mode-toggle']) {
test(`Native paused cue timing probe: ${invalidation}`, async ({ page, browser }, testInfo) => {
await page.route('**/test/native-offset.vtt', route => route.fulfill({ contentType: 'text/vtt', body: 'WEBVTT\n\n00:01.000 --> 00:03.000\ncaption\n' }))
await page.goto('/test/player.html')
await page.evaluate(async () => {
const video = document.createElement('video')
video.muted = true
video.src = '/test/pattern.mp4'
document.body.append(video)
window.probeVideo = video
await new Promise(resolve => video.addEventListener('loadedmetadata', resolve, { once: true }))
const element = document.createElement('track')
element.kind = 'metadata'
element.src = '/test/native-offset.vtt'
const loaded = new Promise(resolve => element.addEventListener('load', resolve, { once: true }))
video.append(element)
const track = element.track
track.mode = 'hidden'
await loaded
window.probeTrack = track
window.probeCue = track.cues[0]
await video.play()
video.pause()
const sought = new Promise(resolve => video.addEventListener('seeked', resolve, { once: true }))
video.currentTime = 1.5
await sought
})
await expect.poll(() => page.evaluate(() => window.probeTrack.activeCues.length)).toBe(1)
const state = await page.evaluate(async (invalidation) => {
const track = window.probeTrack
const cue = window.probeCue
const read = () => ({ time: window.probeVideo.currentTime, paused: window.probeVideo.paused, active: Array.from(track.activeCues || [], cue => [cue.startTime, cue.endTime]), sameCue: track.cues[0] === cue })
const before = read()
cue.startTime = 2
cue.endTime = 4
const changed = read()
if (invalidation === 'remove-add') {
track.removeCue(cue)
track.addCue(cue)
}
if (invalidation === 'mode-toggle') {
track.mode = 'disabled'
track.mode = 'hidden'
}
const immediate = read()
await new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)))
const painted = read()
window.probeVideo.remove()
return { before, changed, immediate, painted }
}, invalidation)
await testInfo.attach('native-offset-probe', { contentType: 'application/json', body: JSON.stringify({ invalidation, browser: browser.version(), state, scope: 'Native video/TextTrack only, no Artplayer instance; two paint callbacks observe invalidation without moving playback time' }) })
expect(state.before.time).toBeGreaterThanOrEqual(1)
expect(state.before.time).toBeLessThan(2)
expect(state.before).toMatchObject({ paused: true, active: [[1, 3]], sameCue: true })
})
}
+65
View File
@@ -0,0 +1,65 @@
import { expect, test } from './fixtures.js'
const vtt = 'WEBVTT\n\n00:01.000 --> 00:03.000\nFirst\n\n00:01.000 --> 00:03.000\nOverlap\n\n00:04.000 --> 00:06.000\nLater\n'
for (const core of ['candidate', 'published']) {
test(`${core}: ${core === 'candidate' ? 'paused subtitle offsets refresh native membership without moving media' : 'historical paused subtitle offset behavior'}`, async ({ page, browserName }, testInfo) => {
await page.route('**/test/offset.vtt', route => route.fulfill({ contentType: 'text/vtt', body: vtt }))
await page.goto(`/test/player.html?core=${core}`)
await page.evaluate(() => window.createPlayer('/test/pattern.mp4'))
await expect.poll(() => page.evaluate(() => window.art.isReady)).toBe(true)
await page.evaluate(() => window.art.subtitle.switch('/test/offset.vtt'))
await expect.poll(() => page.evaluate(() => window.art.subtitle.cues.length)).toBe(3)
await page.locator('#play').click()
await expect.poll(() => page.evaluate(() => window.art.currentTime)).toBeGreaterThan(0.1)
await page.locator('#pause').click()
await page.evaluate(() => new Promise((resolve) => {
window.art.once('video:seeked', resolve)
window.art.seek = 1.5
}))
await expect(page.locator('.art-subtitle-line')).toHaveCount(2)
const records = await page.evaluate(() => {
const art = window.art
const track = art.template.$track.track
const cues = Array.from(track.cues)
const events = []
art.on('subtitleOffset', value => events.push(value))
const states = []
const time = art.currentTime
for (const offset of [1, -1, 0, -3, 0]) {
art.subtitleOffset = offset
states.push({ offset, time: art.currentTime, paused: art.video.paused, native: Array.from(track.activeCues, cue => cue.text), public: art.subtitle.activeCues.map(cue => cue.text), rendered: Array.from(art.template.$subtitle.querySelectorAll('.art-subtitle-line'), line => line.textContent.trim()), sameCues: cues.every(cue => Array.from(track.cues).includes(cue)), mode: track.mode })
}
return { states, events, time }
})
await testInfo.attach('paused-offset', { contentType: 'application/json', body: JSON.stringify({ core, records }) })
expect(records.events).toEqual([1, -1, 0, -3, 0])
const texts = core === 'published' && browserName === 'firefox'
? [['First', 'Overlap'], ['First', 'Overlap'], ['First', 'Overlap'], ['First', 'Overlap', 'Later'], ['First', 'Overlap', 'Later']]
: [[], ['First', 'Overlap'], ['First', 'Overlap'], ['Later'], ['First', 'Overlap']]
expect(records.time).toBeCloseTo(1.5, 2)
for (const [index, state] of records.states.entries()) {
expect(state).toEqual({ offset: [1, -1, 0, -3, 0][index], time: records.time, paused: true, native: texts[index], public: texts[index], rendered: texts[index], sameCues: true, mode: 'hidden' })
}
if (core === 'candidate') {
const disabled = await page.evaluate(() => {
const track = window.art.template.$track.track
track.mode = 'disabled'
window.art.subtitleOffset = 2
return { mode: track.mode, active: window.art.subtitle.activeCues.length, offset: window.art.subtitleOffset }
})
expect(disabled).toEqual({ mode: 'disabled', active: 0, offset: 0 })
await page.evaluate(() => {
window.art.template.$track.track.mode = 'hidden'
})
await page.locator('#play').click()
await page.evaluate(() => {
window.art.subtitleOffset = 1
})
await expect.poll(() => page.evaluate(() => window.art.currentTime)).toBeGreaterThan(2.25)
await expect(page.locator('.art-subtitle-line')).toHaveCount(2)
await page.locator('#pause').click()
}
await page.evaluate(() => window.art.destroy())
})
}
+82 -1
View File
@@ -3,7 +3,7 @@ import assert from 'node:assert/strict'
import { test as it } from 'node:test'
import { loadModules } from './helpers/load.js'
const { ResourceScope, SubtitleRequest, parseSubtitle, subtitleOffsetMix, beginLifecycle, initSubtitleState, replaceSubtitleTrack } = await loadModules({
const { ResourceScope, SubtitleRequest, parseSubtitle, subtitleOffsetMix, beginLifecycle, initSubtitleState, replaceSubtitleTrack, refreshPausedCues } = await loadModules({
ResourceScope: 'packages/artplayer/src/lifecycle/scope',
SubtitleRequest: 'packages/artplayer/src/subtitle/request',
parseSubtitle: { file: 'packages/artplayer/src/subtitle/parse', name: 'parseSubtitle' },
@@ -11,6 +11,7 @@ const { ResourceScope, SubtitleRequest, parseSubtitle, subtitleOffsetMix, beginL
beginLifecycle: { file: 'packages/artplayer/src/lifecycle/instance', name: 'beginLifecycle' },
initSubtitleState: { file: 'packages/artplayer/src/subtitle/state', name: 'initSubtitleState' },
replaceSubtitleTrack: { file: 'packages/artplayer/src/subtitle/track', name: 'replaceSubtitleTrack' },
refreshPausedCues: { file: 'packages/artplayer/src/subtitle/timing', name: 'refreshPausedCues' },
})
it('subtitle track attribute failure releases its new scope and preserves the original error', () => {
@@ -170,3 +171,83 @@ it('subtitle offsets with no cues retain the default state and do not notify', (
assert.equal(art.subtitleOffset, 0)
assert.equal(Object.hasOwn(art.notice, 'show'), false)
})
it('paused offsets refresh stale native membership before rendering and notifying, preserving cue identity', () => {
const cues = [{ startTime: 1, endTime: 3, text: 'First' }, { startTime: 4, endTime: 6, text: 'Later' }]
const video = { paused: true, currentTime: 1.5 }
const events = []
let active = [cues[0]]
const indexed = [...cues]
const track = {
mode: 'hidden',
removeCue(cue) {
events.push(['remove', cue.text])
indexed.splice(indexed.indexOf(cue), 1)
active = active.filter(item => item !== cue)
},
addCue(cue) {
events.push(['add', cue.text])
indexed.push(cue)
active = indexed.filter(cue => cue.startTime <= video.currentTime && video.currentTime < cue.endTime)
},
get activeCues() { return active },
}
const art = { template: { $track: { track }, $video: video }, duration: 8, subtitle: { cues, update() {
events.push(active.map(cue => cue.text))
} }, notice: {}, i18n: { get: key => key }, emit: (...args) => events.push(args) }
subtitleOffsetMix(art)
art.subtitleOffset = 1
const reindex = [['remove', 'First'], ['remove', 'Later'], ['add', 'First'], ['add', 'Later']]
assert.deepEqual(events, [...reindex, [], ['subtitleOffset', 1]])
events.length = 0
art.subtitleOffset = -3
assert.deepEqual(events, [...reindex, ['Later'], ['subtitleOffset', -3]])
assert.equal(active[0], cues[1])
assert.equal(video.currentTime, 1.5)
assert.equal(video.paused, true)
assert.equal(track.mode, 'hidden')
assert.deepEqual(indexed, cues)
})
it('paused refresh detects wrong members even when active counts match and preserves showing mode', () => {
const expired = { startTime: 0, endTime: 1 }
const current = { startTime: 1, endTime: 2 }
const writes = []
const track = { mode: 'showing', activeCues: [expired], removeCue: cue => writes.push(['remove', cue]), addCue: cue => writes.push(['add', cue]) }
Object.defineProperty(track, 'mode', { get: () => 'showing', set: () => assert.fail('must retain mode') })
refreshPausedCues(track, { paused: true, currentTime: 1 }, [expired, current])
assert.deepEqual(writes, [['remove', expired], ['remove', current], ['add', expired], ['add', current]])
})
it('paused refresh preserves correct overlap membership without toggling a native track', () => {
const cues = [{ startTime: 0, endTime: 2 }, { startTime: 1, endTime: 3 }]
const track = { mode: 'hidden', removeCue() {
assert.fail('unnecessary native invalidation')
}, activeCues: cues }
refreshPausedCues(track, { paused: true, currentTime: 1.5 }, cues)
})
it('paused refresh reindexes tied cues when native membership is correct but order drifted', () => {
const first = { startTime: 0, endTime: 2 }
const second = { startTime: 0, endTime: 2 }
const added = []
const track = { mode: 'hidden', activeCues: [second, first], removeCue() {}, addCue: cue => added.push(cue) }
refreshPausedCues(track, { paused: true, currentTime: 1 }, [first, second])
assert.deepEqual(added, [first, second])
})
it('paused refresh does not enable disabled tracks or intervene in playing and limited media hosts', () => {
for (const [mode, video] of [['disabled', { paused: true, currentTime: 1.5 }], ['hidden', { paused: false, currentTime: 1.5 }], ['hidden', undefined], ['hidden', { paused: true }], ['hidden', { paused: true, currentTime: Number.NaN }]]) {
const track = {
get mode() { return mode },
set mode(_value) {
assert.fail('unexpected track change')
},
get activeCues() {
assert.fail('unexpected cue inspection')
},
}
refreshPausedCues(track, video, [])
}
refreshPausedCues(undefined, { paused: true, currentTime: 1 }, [])
})