fix(dash): [PKG-DASH-SEEK-01] recover paused boundary seeks in SDK 4.5.2

This commit is contained in:
Harvey Zhao committed 2026-09-14 20:19:04 +08:00
1 parent 442e1dcc96
commit 30f646be97
22 files changed
+1725 -57

No files matched your search

@@ -4,4 +4,4 @@
* (c) 2017-2026 Harvey Zhao
* Released under the MIT License.
*/
!function(t,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"==typeof define&&define.amd?(t.artplayerPluginDashControl=e(),define(function(){return t.artplayerPluginDashControl})):(t="undefined"!==typeof globalThis?globalThis:t||self).artplayerPluginDashControl=e()}(this,function(){"use strict";function t(t){const e=new Map;return t.filter(t=>{if(void 0===t.html)return!0;const n=e.get(t.html);return n?(t.default&&Object.assign(n,t),!1):(e.set(t.html,t),!0)})}function e(t,e,n){let o;const r=new Map;function i(n,o=r.get(n)){if(!o||r.get(n)!==o)return;r.delete(n);const i=t[n];"controls"===n&&i.cache?.get&&i.cache.get(e)?.option?.onSelect!==o||"setting"===n&&i.find&&i.find(e)?.onSelect!==o||i.remove(e)}function c(){let t;o=void 0;for(const[n,o]of[...r])try{i(n,o)}catch(e){t||(t=e)}if(t)throw t}return{update:function(s,l,a){if(!l)return void c();const u={};o=u;const f=()=>u===o&&a(),d=e=>f()?(l.select(e,f),f()?(t.notice.show=`${l.title}: ${e.html}`,f()&&s.control&&t.controls.check(e),f()&&s.setting&&t.setting.check(e),e.html):e.html):e.html;s.control?(r.set("controls",d),t.controls.update({name:e,position:"right",html:l.html,style:{padding:"0 10px"},selector:l.selector,onSelect:d})):i("controls"),f()&&(s.setting?(r.set("setting",d),t.setting.update({name:e,tooltip:l.html,html:l.title,icon:n,width:200,selector:l.selector,onSelect:d})):i("setting"))},clear:c}}return function(n={}){return o=>{const r=o,{$video:i}=r.template,{errorHandle:c}=r.constructor.utils;let s=!1,l=0;const a=e(r,"dash-quality",'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" height="18"><path fill="#fff" d="M0 96C0 60.7 28.7 32 64 32l384 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 480c-35.3 0-64-28.7-64-64L0 96zM323.8 202.5c-4.5-6.6-11.9-10.5-19.8-10.5s-15.4 3.9-19.8 10.5l-87 127.6L170.7 297c-4.6-5.7-11.5-9-18.7-9s-14.2 3.3-18.7 9l-64 80c-5.8 7.2-6.9 17.1-2.9 25.4s12.4 13.6 21.6 13.6l96 0 32 0 208 0c8.9 0 17.1-4.9 21.2-12.8s3.6-17.4-1.4-24.7l-120-176zM112 192a48 48 0 1 0 0-96 48 48 0 1 0 0 96z"/></svg>'),u=e(r,"dash-audio",'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" height="18"><path fill="#fff" d="M256 80C149.9 80 62.4 159.4 49.6 262c9.4-3.8 19.6-6 30.4-6c26.5 0 48 21.5 48 48l0 128c0 26.5-21.5 48-48 48c-44.2 0-80-35.8-80-80l0-16 0-48 0-48C0 146.6 114.6 32 256 32s256 114.6 256 256l0 48 0 48 0 16c0 44.2-35.8 80-80 80c-26.5 0-48-21.5-48-48l0-128c0-26.5 21.5-48 48-48c10.8 0 21 2.1 30.4 6C449.6 159.4 362.1 80 256 80z"/></svg>'),f=[],d=function(t){let e;const n=n=>e===n&&t.active(n.dash),o=t=>t.dash.getSettings().streaming.abr.autoSwitchBitrate.video;function r(){const t=e;if(!t)return;let n;e=void 0,t.epoch++;for(const[e,r]of t.callbacks.splice(0))try{t.off.call(t.dash,e,r)}catch(o){n||(n=o)}if(n)throw n}function i(n,o){if(e===n){try{r()}catch(i){console.warn("ArtPlayer DASH subscription cleanup failed:",i)}if(!e&&t.active(n.dash))try{t.reset()}catch(i){console.warn("ArtPlayer DASH cleanup failed:",i)}}console.warn("ArtPlayer DASH refresh failed:",o)}function c(e){if(!n(e)||e.suspended||e.queued||e.running)return;e.queued=!0;const r=e.epoch;Promise.resolve().then(()=>{if(n(e)&&e.epoch===r&&!e.suspended){e.queued=!1,e.running=!0;try{e.automatic=o(e),n(e)&&t.refresh()}catch(c){i(e,c)}finally{e.running=!1}}})}return{bind:function(s){const l=e;if(l?.dash===s){const t=o(l);return void(n(l)&&(l.automatic=t,l.suspended=!1))}if(r(),e||!t.active(s)||"function"!==typeof s.on||"function"!==typeof s.off)return;const a={dash:s,off:s.off,callbacks:[],epoch:0,queued:!1,running:!1,suspended:!1,automatic:!1};e=a;const u=s.on;try{a.automatic=o(a);const e=["qualityChangeRequested","qualityChangeRendered","trackChangeRendered","streamUpdated","streamInitialized"].map(t=>[t,()=>{"streamUpdated"!==t&&"streamInitialized"!==t||(a.suspended=!1),c(a)}]);e.push(["playbackTimeUpdated",()=>{if(n(a)&&!a.suspended)try{const t=o(a);n(a)&&t!==a.automatic&&(a.automatic=t,c(a))}catch(t){i(a,t)}}]),e.push(["streamTeardownComplete",()=>{if(n(a)){a.epoch++,a.queued=!1,a.suspended=!0;try{t.reset()}catch(e){i(a,e)}}}]);for(const t of e){if(!n(a))break;a.callbacks.push(t),u.call(s,...t)}}catch(f){if(e===a)try{r()}catch(d){console.warn("ArtPlayer DASH subscription cleanup failed:",d)}throw f}},release:r}}({active:t=>!s&&!r.isDestroy&&r.dash===t,refresh:p,reset(){const t=++l;h(()=>t===l)}});function h(t=()=>!0){let e;for(const o of[a.clear,u.clear]){if(!t())break;try{o()}catch(n){e||(e=n)}}if(e)throw e}function p(){if(s||r.isDestroy)return;const e=++l,o=r.dash,f=()=>!s&&!r.isDestroy&&e===l&&r.dash===o,p=()=>f()&&o.getVideoElement()===i&&f();try{if(c(o.getVideoElement()===i,'Cannot find instance of DASH from "art.dash"'),!f())return;if(d.bind(o),!p())return;const e=n.quality||{},r=function(e,n,o){const r=function(t){const e="function"===typeof t.getRepresentationsByType;return{levels:()=>e?t.getRepresentationsByType("video"):t.getBitrateInfoListFor("video"),current(n){if(e)return t.getCurrentRepresentationForTyLine truncated
!function(t,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"==typeof define&&define.amd?(t.artplayerPluginDashControl=e(),define(function(){return t.artplayerPluginDashControl})):(t="undefined"!==typeof globalThis?globalThis:t||self).artplayerPluginDashControl=e()}(this,function(){"use strict";function t(t){const e=new Map;return t.filter(t=>{if(void 0===t.html)return!0;const n=e.get(t.html);return n?(t.default&&Object.assign(n,t),!1):(e.set(t.html,t),!0)})}function e(t,e,n){let r;const o=new Map;function i(n,r=o.get(n)){if(!r||o.get(n)!==r)return;o.delete(n);const i=t[n];"controls"===n&&i.cache?.get&&i.cache.get(e)?.option?.onSelect!==r||"setting"===n&&i.find&&i.find(e)?.onSelect!==r||i.remove(e)}function c(){let t;r=void 0;for(const[n,r]of[...o])try{i(n,r)}catch(e){t||(t=e)}if(t)throw t}return{update:function(s,a,l){if(!a)return void c();const u={};r=u;const f=()=>u===r&&l(),d=e=>f()?(a.select(e,f),f()?(t.notice.show=`${a.title}: ${e.html}`,f()&&s.control&&t.controls.check(e),f()&&s.setting&&t.setting.check(e),e.html):e.html):e.html;s.control?(o.set("controls",d),t.controls.update({name:e,position:"right",html:a.html,style:{padding:"0 10px"},selector:a.selector,onSelect:d})):i("controls"),f()&&(s.setting?(o.set("setting",d),t.setting.update({name:e,tooltip:a.html,html:a.title,icon:n,width:200,selector:a.selector,onSelect:d})):i("setting"))},clear:c}}function n(t){let e;const n=n=>e===n&&t.active(n.dash),r=t=>t.dash.getSettings().streaming.abr.autoSwitchBitrate.video;function o(){const t=e;if(!t)return;let n;e=void 0,t.epoch++;for(const[e,o]of t.callbacks.splice(0))try{t.off.call(t.dash,e,o)}catch(r){n||(n=r)}if(n)throw n}function i(n,r){if(e===n){try{o()}catch(i){console.warn("ArtPlayer DASH subscription cleanup failed:",i)}if(!e&&t.active(n.dash))try{t.reset()}catch(i){console.warn("ArtPlayer DASH cleanup failed:",i)}}console.warn("ArtPlayer DASH refresh failed:",r)}function c(e){if(!n(e)||e.suspended||e.queued||e.running)return;e.queued=!0;const o=e.epoch;Promise.resolve().then(()=>{if(n(e)&&e.epoch===o&&!e.suspended){e.queued=!1,e.running=!0;try{e.automatic=r(e),n(e)&&t.refresh()}catch(c){i(e,c)}finally{e.running=!1}}})}return{bind:function(s){const a=e;if(a?.dash===s){const t=r(a);return void(n(a)&&(a.automatic=t,a.suspended=!1))}if(o(),e||!t.active(s)||"function"!==typeof s.on||"function"!==typeof s.off)return;const l={dash:s,off:s.off,callbacks:[],epoch:0,queued:!1,running:!1,suspended:!1,automatic:!1};e=l;const u=s.on;try{l.automatic=r(l);const e=["qualityChangeRequested","qualityChangeRendered","trackChangeRendered","streamUpdated","streamInitialized"].map(t=>[t,()=>{"streamUpdated"!==t&&"streamInitialized"!==t||(l.suspended=!1),c(l)}]),o=function(t,e){const n=t;try{if("function"!==typeof n.getVersion||"4.5.2"!==n.getVersion())return;if("function"!==typeof n.getVideoElement||"function"!==typeof n.getActiveStream||"function"!==typeof n.getDashMetrics)return}catch{return}let r=!1;return()=>{if(!r&&e()){r=!0;try{const t=n.getVideoElement?.();if(!t?.seeking||!Number.isFinite(t.currentTime))return;const r=t.currentTime,o=n.getActiveStream?.(),i=n.getDashMetrics?.(),c=o?.getProcessors?.();if(!Array.isArray(c)||"function"!==typeof i?.getCurrentBufferLevel||"function"!==typeof i.addBufferLevel)return;for(const s of c){if(!e())return;const c=s.getType?.();if("audio"!==c&&"video"!==c)continue;const a=s.getBufferController?.();if("function"!==typeof a?.getRangeAt||"function"!==typeof a.getIsPruningInProgress||"function"!==typeof a.getAllRangesWithSafetyFactor)continue;const l=i.getCurrentBufferLevel(c),u=a.getRangeAt(r),f=null===u||u&&Number.isFinite(u.end)&&u.end<=r;if(!Number.isFinite(l)||l<=0||!f||!1!==a.getIsPruningInProgress())continue;const d=a.getAllRangesWithSafetyFactor(r);if(Array.isArray(d)&&0===d.length){if(!e()||n.getActiveStream?.()!==o||n.getDashMetrics?.()!==i||n.getVideoElement?.()!==t||t.currentTime!==r||!t.seeking||!e())return;i.addBufferLevel(c,new Date,0)}}}catch(t){console.warn("ArtPlayer DASH seek buffer refresh failed:",t)}finally{r=!1}}}}(s,()=>n(l)&&!l.suspended);o&&e.push(["playbackSeeking",o]),e.push(["playbackTimeUpdated",()=>{if(n(l)&&!l.suspended)try{const t=r(l);n(l)&&t!==l.automatic&&(l.automatic=t,c(l))}catch(t){i(l,t)}}]),e.push(["streamTeardownComplete",()=>{if(n(l)){l.epoch++,l.queued=!1,l.suspended=!0;try{t.reset()}catch(e){i(l,e)}}}]);for(const t of e){if(!n(l))break;l.callbacks.push(t),u.call(s,...t)}}catch(f){if(e===l)try{o()}catch(d){console.warn("ArtPlayer DASH subscription cleanup failed:",d)}throw f}},release:o}}return function(r={}){return o=>{const i=o,{$video:c}=i.template,{errorHandle:s}=i.constructor.utils;let a=!1,l=0;const u=e(i,"dash-quality",'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" height="18"><path fill="#fff" d="M0 96C0 60.7 28.7 32 64 32l384 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 480c-35.3 0-64-28.7-64-64L0 96zM323.8 202.5c-4.5-6.6-11.9-10.5-19.8-10.5s-15.4 3.9-19.8 10.5l-87 12Line truncated
@@ -4,4 +4,4 @@
* (c) 2017-2026 Harvey Zhao
* Released under the MIT License.
*/
!function(t,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"==typeof define&&define.amd?(t.artplayerPluginDashControl=e(),define(function(){return t.artplayerPluginDashControl})):(t="undefined"!==typeof globalThis?globalThis:t||self).artplayerPluginDashControl=e()}(this,function(){"use strict";var t=Object.defineProperty,e=Object.getOwnPropertySymbols,n=Object.prototype.hasOwnProperty,o=Object.prototype.propertyIsEnumerable,r=(e,n,o)=>n in e?t(e,n,{enumerable:!0,configurable:!0,writable:!0,value:o}):e[n]=o,i=(t,i)=>{for(var l in i||(i={}))n.call(i,l)&&r(t,l,i[l]);if(e)for(var l of e(i))o.call(i,l)&&r(t,l,i[l]);return t};function l(t){const e=new Map;return t.filter(t=>{if(void 0===t.html)return!0;const n=e.get(t.html);return n?(t.default&&Object.assign(n,t),!1):(e.set(t.html,t),!0)})}function c(t,e,n){let o;const r=new Map;function i(n,o=r.get(n)){var i,l,c,a;if(!o||r.get(n)!==o)return;r.delete(n);const s=t[n];"controls"===n&&(null==(i=s.cache)?void 0:i.get)&&(null==(c=null==(l=s.cache.get(e))?void 0:l.option)?void 0:c.onSelect)!==o||"setting"===n&&s.find&&(null==(a=s.find(e))?void 0:a.onSelect)!==o||s.remove(e)}function l(){let t;o=void 0;for(const[n,o]of[...r])try{i(n,o)}catch(e){t||(t=e)}if(t)throw t}return{update:function(c,a,s){if(!a)return void l();const u={};o=u;const f=()=>u===o&&s(),d=e=>f()?(a.select(e,f),f()?(t.notice.show=`${a.title}: ${e.html}`,f()&&c.control&&t.controls.check(e),f()&&c.setting&&t.setting.check(e),e.html):e.html):e.html;c.control?(r.set("controls",d),t.controls.update({name:e,position:"right",html:a.html,style:{padding:"0 10px"},selector:a.selector,onSelect:d})):i("controls"),f()&&(c.setting?(r.set("setting",d),t.setting.update({name:e,tooltip:a.html,html:a.title,icon:n,width:200,selector:a.selector,onSelect:d})):i("setting"))},clear:l}}return function(t={}){return e=>{const n=e,{$video:o}=n.template,{errorHandle:r}=n.constructor.utils;let a=!1,s=0;const u=c(n,"dash-quality",'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" height="18"><path fill="#fff" d="M0 96C0 60.7 28.7 32 64 32l384 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 480c-35.3 0-64-28.7-64-64L0 96zM323.8 202.5c-4.5-6.6-11.9-10.5-19.8-10.5s-15.4 3.9-19.8 10.5l-87 127.6L170.7 297c-4.6-5.7-11.5-9-18.7-9s-14.2 3.3-18.7 9l-64 80c-5.8 7.2-6.9 17.1-2.9 25.4s12.4 13.6 21.6 13.6l96 0 32 0 208 0c8.9 0 17.1-4.9 21.2-12.8s3.6-17.4-1.4-24.7l-120-176zM112 192a48 48 0 1 0 0-96 48 48 0 1 0 0 96z"/></svg>'),f=c(n,"dash-audio",'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" height="18"><path fill="#fff" d="M256 80C149.9 80 62.4 159.4 49.6 262c9.4-3.8 19.6-6 30.4-6c26.5 0 48 21.5 48 48l0 128c0 26.5-21.5 48-48 48c-44.2 0-80-35.8-80-80l0-16 0-48 0-48C0 146.6 114.6 32 256 32s256 114.6 256 256l0 48 0 48 0 16c0 44.2-35.8 80-80 80c-26.5 0-48-21.5-48-48l0-128c0-26.5 21.5-48 48-48c10.8 0 21 2.1 30.4 6C449.6 159.4 362.1 80 256 80z"/></svg>'),d=[],h=function(t){let e;const n=n=>e===n&&t.active(n.dash),o=t=>t.dash.getSettings().streaming.abr.autoSwitchBitrate.video;function r(){const t=e;if(!t)return;let n;e=void 0,t.epoch++;for(const[e,r]of t.callbacks.splice(0))try{t.off.call(t.dash,e,r)}catch(o){n||(n=o)}if(n)throw n}function i(n,o){if(e===n){try{r()}catch(i){console.warn("ArtPlayer DASH subscription cleanup failed:",i)}if(!e&&t.active(n.dash))try{t.reset()}catch(i){console.warn("ArtPlayer DASH cleanup failed:",i)}}console.warn("ArtPlayer DASH refresh failed:",o)}function l(e){if(!n(e)||e.suspended||e.queued||e.running)return;e.queued=!0;const r=e.epoch;Promise.resolve().then(()=>{if(n(e)&&e.epoch===r&&!e.suspended){e.queued=!1,e.running=!0;try{e.automatic=o(e),n(e)&&t.refresh()}catch(l){i(e,l)}finally{e.running=!1}}})}return{bind:function(c){const a=e;if((null==a?void 0:a.dash)===c){const t=o(a);return void(n(a)&&(a.automatic=t,a.suspended=!1))}if(r(),e||!t.active(c)||"function"!==typeof c.on||"function"!==typeof c.off)return;const s={dash:c,off:c.off,callbacks:[],epoch:0,queued:!1,running:!1,suspended:!1,automatic:!1};e=s;const u=c.on;try{s.automatic=o(s);const e=["qualityChangeRequested","qualityChangeRendered","trackChangeRendered","streamUpdated","streamInitialized"].map(t=>[t,()=>{"streamUpdated"!==t&&"streamInitialized"!==t||(s.suspended=!1),l(s)}]);e.push(["playbackTimeUpdated",()=>{if(n(s)&&!s.suspended)try{const t=o(s);n(s)&&t!==s.automatic&&(s.automatic=t,l(s))}catch(t){i(s,t)}}]),e.push(["streamTeardownComplete",()=>{if(n(s)){s.epoch++,s.queued=!1,s.suspended=!0;try{t.reset()}catch(e){i(s,e)}}}]);for(const t of e){if(!n(s))break;s.callbacks.push(t),u.call(c,...t)}}catch(f){if(e===s)try{r()}catch(d){console.warn("ArtPlayer DASH subscription cleanup failed:",d)}throw f}},release:r}}({active:t=>!a&&!n.isDestroy&&n.dash===t,refresh:y,reset(){const t=++s;p(()=>t===s)}});function p(t=()=>!0){let e;for(const o of[u.clear,f.clear]){if(!t())break;try{o()}catch(n){e||(e=n)}}if(e)throw e}function y(){if(a||n.isDestroy)return;const e=++s,c=n.dash,d=()=>!a&&!n.isDestLine truncated
!function(t,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"==typeof define&&define.amd?(t.artplayerPluginDashControl=e(),define(function(){return t.artplayerPluginDashControl})):(t="undefined"!==typeof globalThis?globalThis:t||self).artplayerPluginDashControl=e()}(this,function(){"use strict";var t=Object.defineProperty,e=Object.getOwnPropertySymbols,n=Object.prototype.hasOwnProperty,r=Object.prototype.propertyIsEnumerable,o=(e,n,r)=>n in e?t(e,n,{enumerable:!0,configurable:!0,writable:!0,value:r}):e[n]=r,i=(t,i)=>{for(var l in i||(i={}))n.call(i,l)&&o(t,l,i[l]);if(e)for(var l of e(i))r.call(i,l)&&o(t,l,i[l]);return t};function l(t){const e=new Map;return t.filter(t=>{if(void 0===t.html)return!0;const n=e.get(t.html);return n?(t.default&&Object.assign(n,t),!1):(e.set(t.html,t),!0)})}function c(t,e,n){let r;const o=new Map;function i(n,r=o.get(n)){var i,l,c,a;if(!r||o.get(n)!==r)return;o.delete(n);const s=t[n];"controls"===n&&(null==(i=s.cache)?void 0:i.get)&&(null==(c=null==(l=s.cache.get(e))?void 0:l.option)?void 0:c.onSelect)!==r||"setting"===n&&s.find&&(null==(a=s.find(e))?void 0:a.onSelect)!==r||s.remove(e)}function l(){let t;r=void 0;for(const[n,r]of[...o])try{i(n,r)}catch(e){t||(t=e)}if(t)throw t}return{update:function(c,a,s){if(!a)return void l();const u={};r=u;const f=()=>u===r&&s(),d=e=>f()?(a.select(e,f),f()?(t.notice.show=`${a.title}: ${e.html}`,f()&&c.control&&t.controls.check(e),f()&&c.setting&&t.setting.check(e),e.html):e.html):e.html;c.control?(o.set("controls",d),t.controls.update({name:e,position:"right",html:a.html,style:{padding:"0 10px"},selector:a.selector,onSelect:d})):i("controls"),f()&&(c.setting?(o.set("setting",d),t.setting.update({name:e,tooltip:a.html,html:a.title,icon:n,width:200,selector:a.selector,onSelect:d})):i("setting"))},clear:l}}function a(t){let e;const n=n=>e===n&&t.active(n.dash),r=t=>t.dash.getSettings().streaming.abr.autoSwitchBitrate.video;function o(){const t=e;if(!t)return;let n;e=void 0,t.epoch++;for(const[e,o]of t.callbacks.splice(0))try{t.off.call(t.dash,e,o)}catch(r){n||(n=r)}if(n)throw n}function i(n,r){if(e===n){try{o()}catch(i){console.warn("ArtPlayer DASH subscription cleanup failed:",i)}if(!e&&t.active(n.dash))try{t.reset()}catch(i){console.warn("ArtPlayer DASH cleanup failed:",i)}}console.warn("ArtPlayer DASH refresh failed:",r)}function l(e){if(!n(e)||e.suspended||e.queued||e.running)return;e.queued=!0;const o=e.epoch;Promise.resolve().then(()=>{if(n(e)&&e.epoch===o&&!e.suspended){e.queued=!1,e.running=!0;try{e.automatic=r(e),n(e)&&t.refresh()}catch(l){i(e,l)}finally{e.running=!1}}})}return{bind:function(c){const a=e;if((null==a?void 0:a.dash)===c){const t=r(a);return void(n(a)&&(a.automatic=t,a.suspended=!1))}if(o(),e||!t.active(c)||"function"!==typeof c.on||"function"!==typeof c.off)return;const s={dash:c,off:c.off,callbacks:[],epoch:0,queued:!1,running:!1,suspended:!1,automatic:!1};e=s;const u=c.on;try{s.automatic=r(s);const e=["qualityChangeRequested","qualityChangeRendered","trackChangeRendered","streamUpdated","streamInitialized"].map(t=>[t,()=>{"streamUpdated"!==t&&"streamInitialized"!==t||(s.suspended=!1),l(s)}]),o=function(t,e){const n=t;try{if("function"!==typeof n.getVersion||"4.5.2"!==n.getVersion())return;if("function"!==typeof n.getVideoElement||"function"!==typeof n.getActiveStream||"function"!==typeof n.getDashMetrics)return}catch(o){return}let r=!1;return()=>{var t,o,i,l,c,a,s,u,f;if(!r&&e()){r=!0;try{const r=null==(t=n.getVideoElement)?void 0:t.call(n);if(!(null==r?void 0:r.seeking)||!Number.isFinite(r.currentTime))return;const d=r.currentTime,h=null==(o=n.getActiveStream)?void 0:o.call(n),g=null==(i=n.getDashMetrics)?void 0:i.call(n),p=null==(l=null==h?void 0:h.getProcessors)?void 0:l.call(h);if(!Array.isArray(p)||"function"!==typeof(null==g?void 0:g.getCurrentBufferLevel)||"function"!==typeof g.addBufferLevel)return;for(const t of p){if(!e())return;const o=null==(c=t.getType)?void 0:c.call(t);if("audio"!==o&&"video"!==o)continue;const i=null==(a=t.getBufferController)?void 0:a.call(t);if("function"!==typeof(null==i?void 0:i.getRangeAt)||"function"!==typeof i.getIsPruningInProgress||"function"!==typeof i.getAllRangesWithSafetyFactor)continue;const l=g.getCurrentBufferLevel(o),p=i.getRangeAt(d),y=null===p||p&&Number.isFinite(p.end)&&p.end<=d;if(!Number.isFinite(l)||l<=0||!y||!1!==i.getIsPruningInProgress())continue;const v=i.getAllRangesWithSafetyFactor(d);if(Array.isArray(v)&&0===v.length){if(!e()||(null==(s=n.getActiveStream)?void 0:s.call(n))!==h||(null==(u=n.getDashMetrics)?void 0:u.call(n))!==g||(null==(f=n.getVideoElement)?void 0:f.call(n))!==r||r.currentTime!==d||!r.seeking||!e())return;g.addBufferLevel(o,new Date,0)}}}catch(d){console.warn("ArtPlayer DASH seek buffer refresh failed:",d)}finally{r=!1}}}}(c,()=>n(s)&&!s.suspended);o&&e.push(["playbackSeeking",o]),e.push(["playbackTimeUpdated",()=>{if(n(s)&&!s.suspended)try{const t=r(s);n(s)&&t!==s.automatic&&(s.automatic=t,l(s))}catch(t){Line truncated
@@ -170,6 +170,59 @@ function createMenu(art, name, icon) {
return { update, clear };
}
const $quality = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" height="18"><path fill="#fff" d="M0 96C0 60.7 28.7 32 64 32l384 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 480c-35.3 0-64-28.7-64-64L0 96zM323.8 202.5c-4.5-6.6-11.9-10.5-19.8-10.5s-15.4 3.9-19.8 10.5l-87 127.6L170.7 297c-4.6-5.7-11.5-9-18.7-9s-14.2 3.3-18.7 9l-64 80c-5.8 7.2-6.9 17.1-2.9 25.4s12.4 13.6 21.6 13.6l96 0 32 0 208 0c8.9 0 17.1-4.9 21.2-12.8s3.6-17.4-1.4-24.7l-120-176zM112 192a48 48 0 1 0 0-96 48 48 0 1 0 0 96z"/></svg>';
function createSeekRecovery(value, active) {
const sdk = value;
try {
if (typeof sdk.getVersion !== "function" || sdk.getVersion() !== "4.5.2")
return;
if (typeof sdk.getVideoElement !== "function" || typeof sdk.getActiveStream !== "function" || typeof sdk.getDashMetrics !== "function")
return;
} catch {
return;
}
let running = false;
return () => {
if (running || !active())
return;
running = true;
try {
const video = sdk.getVideoElement?.();
if (!video?.seeking || !Number.isFinite(video.currentTime))
return;
const time = video.currentTime;
const stream = sdk.getActiveStream?.();
const metrics = sdk.getDashMetrics?.();
const processors = stream?.getProcessors?.();
if (!Array.isArray(processors) || typeof metrics?.getCurrentBufferLevel !== "function" || typeof metrics.addBufferLevel !== "function")
return;
for (const processor of processors) {
if (!active())
return;
const type = processor.getType?.();
if (type !== "audio" && type !== "video")
continue;
const buffer = processor.getBufferController?.();
if (typeof buffer?.getRangeAt !== "function" || typeof buffer.getIsPruningInProgress !== "function" || typeof buffer.getAllRangesWithSafetyFactor !== "function")
continue;
const cached = metrics.getCurrentBufferLevel(type);
const range = buffer.getRangeAt(time);
const empty = range === null || range && Number.isFinite(range.end) && range.end <= time;
if (!Number.isFinite(cached) || cached <= 0 || !empty || buffer.getIsPruningInProgress() !== false)
continue;
const removals = buffer.getAllRangesWithSafetyFactor(time);
if (!Array.isArray(removals) || removals.length !== 0)
continue;
if (!active() || sdk.getActiveStream?.() !== stream || sdk.getDashMetrics?.() !== metrics || sdk.getVideoElement?.() !== video || video.currentTime !== time || !video.seeking || !active())
return;
metrics.addBufferLevel(type, /* @__PURE__ */ new Date(), 0);
}
} catch (error) {
console.warn("ArtPlayer DASH seek buffer refresh failed:", error);
} finally {
running = false;
}
};
}
function observeSDK(options) {
let binding;
const active = (record) => binding === record && options.active(record.dash);
@@ -253,6 +306,9 @@ function observeSDK(options) {
record.suspended = false;
schedule(record);
}]);
const recoverSeek = createSeekRecovery(dash, () => active(record) && !record.suspended);
if (recoverSeek)
entries.push(["playbackSeeking", recoverSeek]);
entries.push(["playbackTimeUpdated", () => {
if (!active(record) || record.suspended)
return;
@@ -13,6 +13,7 @@ destroys it, changes its source, or adds a runtime dependency on dash.js.
| `src/index.ts` | Deferred installation, media identity, update revisions, event subscription and cleanup |
| `src/sdk.ts` | 4.x qualityIndex vs 5.x representation ID access and manual/Auto selection |
| `src/sdk-events.ts` | Owned SDK subscriptions, coalesced refresh, teardown and asynchronous errors |
| `src/seek-buffer.ts` | Exact SDK 4.5.2 empty-seek metric recovery and stale/reentrant measurement guards |
| `src/types.ts` | Narrow internal SDK, host, model and cleanup types |
| `src/mapping.ts` | Names, current item matching, duplicate labels and selector ordering |
| `src/menu.ts` | Existing control/setting registries, menu ownership and guarded selection callbacks |
@@ -22,7 +23,7 @@ destroys it, changes its source, or adds a runtime dependency on dash.js.
Dependencies flow from the entry into mapping/menu, and from mapping into the SDK
adapter. Mapping has no DOM dependency. Menu receives a model and validity callback;
it does not choose SDK versions. The event observer receives active/refresh/reset
callbacks and owns no DOM or ArtPlayer object. All six owned modules are checked with strict
callbacks and owns no DOM or ArtPlayer object. All seven owned modules are checked with strict
TypeScript, noUncheckedIndexedAccess, and skipLibCheck=false; there are no remaining
owned JavaScript modules in this package.
@@ -154,7 +155,9 @@ external selection, source topology replacement, retained callbacks and SDK owne
It also includes a native SDK control without an ArtPlayer instance. SDK 4.5.2
paused-seek stalls reproduce independently: its empty buffer-clear path preserves
stale buffer metrics and can prevent further scheduling. A diagnostic backport of
the SDK 5 buffer-level refresh confirms the cause; no production workaround ships.
the SDK 5 buffer-level refresh confirms the cause. The candidate now adds the
bounded compatibility recovery described below; the original SDK and published
plugin controls retain their historical failures.
See the [diagnosis](../../refactor/changes/2026-09-12-PKG-DASH-05-seek-diagnosis.md).
The compatibility risk remains open. Unsupported MSE is
recorded as a playback capability gap, not acceptance. SDK event tests also cover
@@ -166,3 +169,34 @@ Node10/old DOM diagnostics. The exact peer dependency closure is hash-pinned in
`refactor/baselines/dash-type-dependencies.json`; no type shim or paths mapping is used.
See [refactor validation](../../refactor/dash-validation.md) for baseline failures
and [task plan](../../refactor/plan.md) for remaining type and release work.
## SDK 4.5.2 paused seek recovery
`seek-buffer.ts` creates a callback only for version 4.5.2 with the required SDK
capabilities. The existing observer owns its `playbackSeeking` listener and applies
the same SDK identity, teardown and destruction guards. Other SDK generations
retain the original subscription set. No timer or asynchronous retry is added.
The callback reads each active audio/video processor's range at the native media
time. It records a zero metric only while seeking, with a positive stale cached
level, no available forward buffer, no pruning in progress and no pending clear
ranges for that target. It rechecks the active SDK, stream, metrics object, media
identity and time before writing. A reentrancy guard and final active check prevent
late writes during metrics callbacks or destruction. Metric failures warn without
removing otherwise valid menus, and a later seek can try again.
The SDK scheduler reads `DashMetrics.getCurrentBufferLevel`, rather than the buffer
controller getter. Calling its existing `addBufferLevel` records an actual empty
range measurement; it does not replace SDK methods or settings, set media time,
dispatch DOM events, remove SourceBuffer data or reset the SDK. These are internal
SDK capabilities, so the exact-version guard is deliberate. Reading returns
seconds while metric storage uses milliseconds: only zero is written here.
`dash-seek-diagnostics.js` contains separate page-only counterfactuals. The GETTER
and METRICS diagnostic switches run after an original failure and leave that test
failed; they cannot be combined with each other, the synthetic-event diagnostic
or a patched SDK. The shared stable-pause browser flow tests both original native
controls and strict candidate recovery with new/old core versions. Node tests
cover invalid measurements, missing capabilities, SDK replacement, teardown,
reentrancy, identity changes and error recovery. Standalone old SDK/old-plugin
limitations, physical devices and full release acceptance remain separately tracked.
@@ -14,6 +14,10 @@ while paused (with no SDK event), call the existing synchronous
`art.plugins.artplayerPluginDashControl.update()` to refresh immediately.
The plugin removes only its own SDK listeners and never destroys your SDK instance.
For dash.js 4.5.2, the plugin compensates for stale buffer metrics when a paused
seek reaches an empty buffered range. Existing setup and seek calls remain the
same. This targeted recovery retains the caller's SDK configuration and media time.
## TypeScript
The formatter receives the original SDK object. For dash.js 5.2.1 types:
@@ -4,4 +4,4 @@
* (c) 2017-2026 Harvey Zhao
* Released under the MIT License.
*/
!function(t,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"==typeof define&&define.amd?(t.artplayerPluginDashControl=e(),define(function(){return t.artplayerPluginDashControl})):(t="undefined"!==typeof globalThis?globalThis:t||self).artplayerPluginDashControl=e()}(this,function(){"use strict";function t(t){const e=new Map;return t.filter(t=>{if(void 0===t.html)return!0;const n=e.get(t.html);return n?(t.default&&Object.assign(n,t),!1):(e.set(t.html,t),!0)})}function e(t,e,n){let o;const r=new Map;function i(n,o=r.get(n)){if(!o||r.get(n)!==o)return;r.delete(n);const i=t[n];"controls"===n&&i.cache?.get&&i.cache.get(e)?.option?.onSelect!==o||"setting"===n&&i.find&&i.find(e)?.onSelect!==o||i.remove(e)}function c(){let t;o=void 0;for(const[n,o]of[...r])try{i(n,o)}catch(e){t||(t=e)}if(t)throw t}return{update:function(s,l,a){if(!l)return void c();const u={};o=u;const f=()=>u===o&&a(),d=e=>f()?(l.select(e,f),f()?(t.notice.show=`${l.title}: ${e.html}`,f()&&s.control&&t.controls.check(e),f()&&s.setting&&t.setting.check(e),e.html):e.html):e.html;s.control?(r.set("controls",d),t.controls.update({name:e,position:"right",html:l.html,style:{padding:"0 10px"},selector:l.selector,onSelect:d})):i("controls"),f()&&(s.setting?(r.set("setting",d),t.setting.update({name:e,tooltip:l.html,html:l.title,icon:n,width:200,selector:l.selector,onSelect:d})):i("setting"))},clear:c}}return function(n={}){return o=>{const r=o,{$video:i}=r.template,{errorHandle:c}=r.constructor.utils;let s=!1,l=0;const a=e(r,"dash-quality",'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" height="18"><path fill="#fff" d="M0 96C0 60.7 28.7 32 64 32l384 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 480c-35.3 0-64-28.7-64-64L0 96zM323.8 202.5c-4.5-6.6-11.9-10.5-19.8-10.5s-15.4 3.9-19.8 10.5l-87 127.6L170.7 297c-4.6-5.7-11.5-9-18.7-9s-14.2 3.3-18.7 9l-64 80c-5.8 7.2-6.9 17.1-2.9 25.4s12.4 13.6 21.6 13.6l96 0 32 0 208 0c8.9 0 17.1-4.9 21.2-12.8s3.6-17.4-1.4-24.7l-120-176zM112 192a48 48 0 1 0 0-96 48 48 0 1 0 0 96z"/></svg>'),u=e(r,"dash-audio",'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" height="18"><path fill="#fff" d="M256 80C149.9 80 62.4 159.4 49.6 262c9.4-3.8 19.6-6 30.4-6c26.5 0 48 21.5 48 48l0 128c0 26.5-21.5 48-48 48c-44.2 0-80-35.8-80-80l0-16 0-48 0-48C0 146.6 114.6 32 256 32s256 114.6 256 256l0 48 0 48 0 16c0 44.2-35.8 80-80 80c-26.5 0-48-21.5-48-48l0-128c0-26.5 21.5-48 48-48c10.8 0 21 2.1 30.4 6C449.6 159.4 362.1 80 256 80z"/></svg>'),f=[],d=function(t){let e;const n=n=>e===n&&t.active(n.dash),o=t=>t.dash.getSettings().streaming.abr.autoSwitchBitrate.video;function r(){const t=e;if(!t)return;let n;e=void 0,t.epoch++;for(const[e,r]of t.callbacks.splice(0))try{t.off.call(t.dash,e,r)}catch(o){n||(n=o)}if(n)throw n}function i(n,o){if(e===n){try{r()}catch(i){console.warn("ArtPlayer DASH subscription cleanup failed:",i)}if(!e&&t.active(n.dash))try{t.reset()}catch(i){console.warn("ArtPlayer DASH cleanup failed:",i)}}console.warn("ArtPlayer DASH refresh failed:",o)}function c(e){if(!n(e)||e.suspended||e.queued||e.running)return;e.queued=!0;const r=e.epoch;Promise.resolve().then(()=>{if(n(e)&&e.epoch===r&&!e.suspended){e.queued=!1,e.running=!0;try{e.automatic=o(e),n(e)&&t.refresh()}catch(c){i(e,c)}finally{e.running=!1}}})}return{bind:function(s){const l=e;if(l?.dash===s){const t=o(l);return void(n(l)&&(l.automatic=t,l.suspended=!1))}if(r(),e||!t.active(s)||"function"!==typeof s.on||"function"!==typeof s.off)return;const a={dash:s,off:s.off,callbacks:[],epoch:0,queued:!1,running:!1,suspended:!1,automatic:!1};e=a;const u=s.on;try{a.automatic=o(a);const e=["qualityChangeRequested","qualityChangeRendered","trackChangeRendered","streamUpdated","streamInitialized"].map(t=>[t,()=>{"streamUpdated"!==t&&"streamInitialized"!==t||(a.suspended=!1),c(a)}]);e.push(["playbackTimeUpdated",()=>{if(n(a)&&!a.suspended)try{const t=o(a);n(a)&&t!==a.automatic&&(a.automatic=t,c(a))}catch(t){i(a,t)}}]),e.push(["streamTeardownComplete",()=>{if(n(a)){a.epoch++,a.queued=!1,a.suspended=!0;try{t.reset()}catch(e){i(a,e)}}}]);for(const t of e){if(!n(a))break;a.callbacks.push(t),u.call(s,...t)}}catch(f){if(e===a)try{r()}catch(d){console.warn("ArtPlayer DASH subscription cleanup failed:",d)}throw f}},release:r}}({active:t=>!s&&!r.isDestroy&&r.dash===t,refresh:p,reset(){const t=++l;h(()=>t===l)}});function h(t=()=>!0){let e;for(const o of[a.clear,u.clear]){if(!t())break;try{o()}catch(n){e||(e=n)}}if(e)throw e}function p(){if(s||r.isDestroy)return;const e=++l,o=r.dash,f=()=>!s&&!r.isDestroy&&e===l&&r.dash===o,p=()=>f()&&o.getVideoElement()===i&&f();try{if(c(o.getVideoElement()===i,'Cannot find instance of DASH from "art.dash"'),!f())return;if(d.bind(o),!p())return;const e=n.quality||{},r=function(e,n,o){const r=function(t){const e="function"===typeof t.getRepresentationsByType;return{levels:()=>e?t.getRepresentationsByType("video"):t.getBitrateInfoListFor("video"),current(n){if(e)return t.getCurrentRepresentationForTyLine truncated
!function(t,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"==typeof define&&define.amd?(t.artplayerPluginDashControl=e(),define(function(){return t.artplayerPluginDashControl})):(t="undefined"!==typeof globalThis?globalThis:t||self).artplayerPluginDashControl=e()}(this,function(){"use strict";function t(t){const e=new Map;return t.filter(t=>{if(void 0===t.html)return!0;const n=e.get(t.html);return n?(t.default&&Object.assign(n,t),!1):(e.set(t.html,t),!0)})}function e(t,e,n){let r;const o=new Map;function i(n,r=o.get(n)){if(!r||o.get(n)!==r)return;o.delete(n);const i=t[n];"controls"===n&&i.cache?.get&&i.cache.get(e)?.option?.onSelect!==r||"setting"===n&&i.find&&i.find(e)?.onSelect!==r||i.remove(e)}function c(){let t;r=void 0;for(const[n,r]of[...o])try{i(n,r)}catch(e){t||(t=e)}if(t)throw t}return{update:function(s,a,l){if(!a)return void c();const u={};r=u;const f=()=>u===r&&l(),d=e=>f()?(a.select(e,f),f()?(t.notice.show=`${a.title}: ${e.html}`,f()&&s.control&&t.controls.check(e),f()&&s.setting&&t.setting.check(e),e.html):e.html):e.html;s.control?(o.set("controls",d),t.controls.update({name:e,position:"right",html:a.html,style:{padding:"0 10px"},selector:a.selector,onSelect:d})):i("controls"),f()&&(s.setting?(o.set("setting",d),t.setting.update({name:e,tooltip:a.html,html:a.title,icon:n,width:200,selector:a.selector,onSelect:d})):i("setting"))},clear:c}}function n(t){let e;const n=n=>e===n&&t.active(n.dash),r=t=>t.dash.getSettings().streaming.abr.autoSwitchBitrate.video;function o(){const t=e;if(!t)return;let n;e=void 0,t.epoch++;for(const[e,o]of t.callbacks.splice(0))try{t.off.call(t.dash,e,o)}catch(r){n||(n=r)}if(n)throw n}function i(n,r){if(e===n){try{o()}catch(i){console.warn("ArtPlayer DASH subscription cleanup failed:",i)}if(!e&&t.active(n.dash))try{t.reset()}catch(i){console.warn("ArtPlayer DASH cleanup failed:",i)}}console.warn("ArtPlayer DASH refresh failed:",r)}function c(e){if(!n(e)||e.suspended||e.queued||e.running)return;e.queued=!0;const o=e.epoch;Promise.resolve().then(()=>{if(n(e)&&e.epoch===o&&!e.suspended){e.queued=!1,e.running=!0;try{e.automatic=r(e),n(e)&&t.refresh()}catch(c){i(e,c)}finally{e.running=!1}}})}return{bind:function(s){const a=e;if(a?.dash===s){const t=r(a);return void(n(a)&&(a.automatic=t,a.suspended=!1))}if(o(),e||!t.active(s)||"function"!==typeof s.on||"function"!==typeof s.off)return;const l={dash:s,off:s.off,callbacks:[],epoch:0,queued:!1,running:!1,suspended:!1,automatic:!1};e=l;const u=s.on;try{l.automatic=r(l);const e=["qualityChangeRequested","qualityChangeRendered","trackChangeRendered","streamUpdated","streamInitialized"].map(t=>[t,()=>{"streamUpdated"!==t&&"streamInitialized"!==t||(l.suspended=!1),c(l)}]),o=function(t,e){const n=t;try{if("function"!==typeof n.getVersion||"4.5.2"!==n.getVersion())return;if("function"!==typeof n.getVideoElement||"function"!==typeof n.getActiveStream||"function"!==typeof n.getDashMetrics)return}catch{return}let r=!1;return()=>{if(!r&&e()){r=!0;try{const t=n.getVideoElement?.();if(!t?.seeking||!Number.isFinite(t.currentTime))return;const r=t.currentTime,o=n.getActiveStream?.(),i=n.getDashMetrics?.(),c=o?.getProcessors?.();if(!Array.isArray(c)||"function"!==typeof i?.getCurrentBufferLevel||"function"!==typeof i.addBufferLevel)return;for(const s of c){if(!e())return;const c=s.getType?.();if("audio"!==c&&"video"!==c)continue;const a=s.getBufferController?.();if("function"!==typeof a?.getRangeAt||"function"!==typeof a.getIsPruningInProgress||"function"!==typeof a.getAllRangesWithSafetyFactor)continue;const l=i.getCurrentBufferLevel(c),u=a.getRangeAt(r),f=null===u||u&&Number.isFinite(u.end)&&u.end<=r;if(!Number.isFinite(l)||l<=0||!f||!1!==a.getIsPruningInProgress())continue;const d=a.getAllRangesWithSafetyFactor(r);if(Array.isArray(d)&&0===d.length){if(!e()||n.getActiveStream?.()!==o||n.getDashMetrics?.()!==i||n.getVideoElement?.()!==t||t.currentTime!==r||!t.seeking||!e())return;i.addBufferLevel(c,new Date,0)}}}catch(t){console.warn("ArtPlayer DASH seek buffer refresh failed:",t)}finally{r=!1}}}}(s,()=>n(l)&&!l.suspended);o&&e.push(["playbackSeeking",o]),e.push(["playbackTimeUpdated",()=>{if(n(l)&&!l.suspended)try{const t=r(l);n(l)&&t!==l.automatic&&(l.automatic=t,c(l))}catch(t){i(l,t)}}]),e.push(["streamTeardownComplete",()=>{if(n(l)){l.epoch++,l.queued=!1,l.suspended=!0;try{t.reset()}catch(e){i(l,e)}}}]);for(const t of e){if(!n(l))break;l.callbacks.push(t),u.call(s,...t)}}catch(f){if(e===l)try{o()}catch(d){console.warn("ArtPlayer DASH subscription cleanup failed:",d)}throw f}},release:o}}return function(r={}){return o=>{const i=o,{$video:c}=i.template,{errorHandle:s}=i.constructor.utils;let a=!1,l=0;const u=e(i,"dash-quality",'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" height="18"><path fill="#fff" d="M0 96C0 60.7 28.7 32 64 32l384 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 480c-35.3 0-64-28.7-64-64L0 96zM323.8 202.5c-4.5-6.6-11.9-10.5-19.8-10.5s-15.4 3.9-19.8 10.5l-87 12Line truncated
@@ -4,4 +4,4 @@
* (c) 2017-2026 Harvey Zhao
* Released under the MIT License.
*/
!function(t,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"==typeof define&&define.amd?(t.artplayerPluginDashControl=e(),define(function(){return t.artplayerPluginDashControl})):(t="undefined"!==typeof globalThis?globalThis:t||self).artplayerPluginDashControl=e()}(this,function(){"use strict";var t=Object.defineProperty,e=Object.getOwnPropertySymbols,n=Object.prototype.hasOwnProperty,o=Object.prototype.propertyIsEnumerable,r=(e,n,o)=>n in e?t(e,n,{enumerable:!0,configurable:!0,writable:!0,value:o}):e[n]=o,i=(t,i)=>{for(var l in i||(i={}))n.call(i,l)&&r(t,l,i[l]);if(e)for(var l of e(i))o.call(i,l)&&r(t,l,i[l]);return t};function l(t){const e=new Map;return t.filter(t=>{if(void 0===t.html)return!0;const n=e.get(t.html);return n?(t.default&&Object.assign(n,t),!1):(e.set(t.html,t),!0)})}function c(t,e,n){let o;const r=new Map;function i(n,o=r.get(n)){var i,l,c,a;if(!o||r.get(n)!==o)return;r.delete(n);const s=t[n];"controls"===n&&(null==(i=s.cache)?void 0:i.get)&&(null==(c=null==(l=s.cache.get(e))?void 0:l.option)?void 0:c.onSelect)!==o||"setting"===n&&s.find&&(null==(a=s.find(e))?void 0:a.onSelect)!==o||s.remove(e)}function l(){let t;o=void 0;for(const[n,o]of[...r])try{i(n,o)}catch(e){t||(t=e)}if(t)throw t}return{update:function(c,a,s){if(!a)return void l();const u={};o=u;const f=()=>u===o&&s(),d=e=>f()?(a.select(e,f),f()?(t.notice.show=`${a.title}: ${e.html}`,f()&&c.control&&t.controls.check(e),f()&&c.setting&&t.setting.check(e),e.html):e.html):e.html;c.control?(r.set("controls",d),t.controls.update({name:e,position:"right",html:a.html,style:{padding:"0 10px"},selector:a.selector,onSelect:d})):i("controls"),f()&&(c.setting?(r.set("setting",d),t.setting.update({name:e,tooltip:a.html,html:a.title,icon:n,width:200,selector:a.selector,onSelect:d})):i("setting"))},clear:l}}return function(t={}){return e=>{const n=e,{$video:o}=n.template,{errorHandle:r}=n.constructor.utils;let a=!1,s=0;const u=c(n,"dash-quality",'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" height="18"><path fill="#fff" d="M0 96C0 60.7 28.7 32 64 32l384 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 480c-35.3 0-64-28.7-64-64L0 96zM323.8 202.5c-4.5-6.6-11.9-10.5-19.8-10.5s-15.4 3.9-19.8 10.5l-87 127.6L170.7 297c-4.6-5.7-11.5-9-18.7-9s-14.2 3.3-18.7 9l-64 80c-5.8 7.2-6.9 17.1-2.9 25.4s12.4 13.6 21.6 13.6l96 0 32 0 208 0c8.9 0 17.1-4.9 21.2-12.8s3.6-17.4-1.4-24.7l-120-176zM112 192a48 48 0 1 0 0-96 48 48 0 1 0 0 96z"/></svg>'),f=c(n,"dash-audio",'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" height="18"><path fill="#fff" d="M256 80C149.9 80 62.4 159.4 49.6 262c9.4-3.8 19.6-6 30.4-6c26.5 0 48 21.5 48 48l0 128c0 26.5-21.5 48-48 48c-44.2 0-80-35.8-80-80l0-16 0-48 0-48C0 146.6 114.6 32 256 32s256 114.6 256 256l0 48 0 48 0 16c0 44.2-35.8 80-80 80c-26.5 0-48-21.5-48-48l0-128c0-26.5 21.5-48 48-48c10.8 0 21 2.1 30.4 6C449.6 159.4 362.1 80 256 80z"/></svg>'),d=[],h=function(t){let e;const n=n=>e===n&&t.active(n.dash),o=t=>t.dash.getSettings().streaming.abr.autoSwitchBitrate.video;function r(){const t=e;if(!t)return;let n;e=void 0,t.epoch++;for(const[e,r]of t.callbacks.splice(0))try{t.off.call(t.dash,e,r)}catch(o){n||(n=o)}if(n)throw n}function i(n,o){if(e===n){try{r()}catch(i){console.warn("ArtPlayer DASH subscription cleanup failed:",i)}if(!e&&t.active(n.dash))try{t.reset()}catch(i){console.warn("ArtPlayer DASH cleanup failed:",i)}}console.warn("ArtPlayer DASH refresh failed:",o)}function l(e){if(!n(e)||e.suspended||e.queued||e.running)return;e.queued=!0;const r=e.epoch;Promise.resolve().then(()=>{if(n(e)&&e.epoch===r&&!e.suspended){e.queued=!1,e.running=!0;try{e.automatic=o(e),n(e)&&t.refresh()}catch(l){i(e,l)}finally{e.running=!1}}})}return{bind:function(c){const a=e;if((null==a?void 0:a.dash)===c){const t=o(a);return void(n(a)&&(a.automatic=t,a.suspended=!1))}if(r(),e||!t.active(c)||"function"!==typeof c.on||"function"!==typeof c.off)return;const s={dash:c,off:c.off,callbacks:[],epoch:0,queued:!1,running:!1,suspended:!1,automatic:!1};e=s;const u=c.on;try{s.automatic=o(s);const e=["qualityChangeRequested","qualityChangeRendered","trackChangeRendered","streamUpdated","streamInitialized"].map(t=>[t,()=>{"streamUpdated"!==t&&"streamInitialized"!==t||(s.suspended=!1),l(s)}]);e.push(["playbackTimeUpdated",()=>{if(n(s)&&!s.suspended)try{const t=o(s);n(s)&&t!==s.automatic&&(s.automatic=t,l(s))}catch(t){i(s,t)}}]),e.push(["streamTeardownComplete",()=>{if(n(s)){s.epoch++,s.queued=!1,s.suspended=!0;try{t.reset()}catch(e){i(s,e)}}}]);for(const t of e){if(!n(s))break;s.callbacks.push(t),u.call(c,...t)}}catch(f){if(e===s)try{r()}catch(d){console.warn("ArtPlayer DASH subscription cleanup failed:",d)}throw f}},release:r}}({active:t=>!a&&!n.isDestroy&&n.dash===t,refresh:y,reset(){const t=++s;p(()=>t===s)}});function p(t=()=>!0){let e;for(const o of[u.clear,f.clear]){if(!t())break;try{o()}catch(n){e||(e=n)}}if(e)throw e}function y(){if(a||n.isDestroy)return;const e=++s,c=n.dash,d=()=>!a&&!n.isDestLine truncated
!function(t,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"==typeof define&&define.amd?(t.artplayerPluginDashControl=e(),define(function(){return t.artplayerPluginDashControl})):(t="undefined"!==typeof globalThis?globalThis:t||self).artplayerPluginDashControl=e()}(this,function(){"use strict";var t=Object.defineProperty,e=Object.getOwnPropertySymbols,n=Object.prototype.hasOwnProperty,r=Object.prototype.propertyIsEnumerable,o=(e,n,r)=>n in e?t(e,n,{enumerable:!0,configurable:!0,writable:!0,value:r}):e[n]=r,i=(t,i)=>{for(var l in i||(i={}))n.call(i,l)&&o(t,l,i[l]);if(e)for(var l of e(i))r.call(i,l)&&o(t,l,i[l]);return t};function l(t){const e=new Map;return t.filter(t=>{if(void 0===t.html)return!0;const n=e.get(t.html);return n?(t.default&&Object.assign(n,t),!1):(e.set(t.html,t),!0)})}function c(t,e,n){let r;const o=new Map;function i(n,r=o.get(n)){var i,l,c,a;if(!r||o.get(n)!==r)return;o.delete(n);const s=t[n];"controls"===n&&(null==(i=s.cache)?void 0:i.get)&&(null==(c=null==(l=s.cache.get(e))?void 0:l.option)?void 0:c.onSelect)!==r||"setting"===n&&s.find&&(null==(a=s.find(e))?void 0:a.onSelect)!==r||s.remove(e)}function l(){let t;r=void 0;for(const[n,r]of[...o])try{i(n,r)}catch(e){t||(t=e)}if(t)throw t}return{update:function(c,a,s){if(!a)return void l();const u={};r=u;const f=()=>u===r&&s(),d=e=>f()?(a.select(e,f),f()?(t.notice.show=`${a.title}: ${e.html}`,f()&&c.control&&t.controls.check(e),f()&&c.setting&&t.setting.check(e),e.html):e.html):e.html;c.control?(o.set("controls",d),t.controls.update({name:e,position:"right",html:a.html,style:{padding:"0 10px"},selector:a.selector,onSelect:d})):i("controls"),f()&&(c.setting?(o.set("setting",d),t.setting.update({name:e,tooltip:a.html,html:a.title,icon:n,width:200,selector:a.selector,onSelect:d})):i("setting"))},clear:l}}function a(t){let e;const n=n=>e===n&&t.active(n.dash),r=t=>t.dash.getSettings().streaming.abr.autoSwitchBitrate.video;function o(){const t=e;if(!t)return;let n;e=void 0,t.epoch++;for(const[e,o]of t.callbacks.splice(0))try{t.off.call(t.dash,e,o)}catch(r){n||(n=r)}if(n)throw n}function i(n,r){if(e===n){try{o()}catch(i){console.warn("ArtPlayer DASH subscription cleanup failed:",i)}if(!e&&t.active(n.dash))try{t.reset()}catch(i){console.warn("ArtPlayer DASH cleanup failed:",i)}}console.warn("ArtPlayer DASH refresh failed:",r)}function l(e){if(!n(e)||e.suspended||e.queued||e.running)return;e.queued=!0;const o=e.epoch;Promise.resolve().then(()=>{if(n(e)&&e.epoch===o&&!e.suspended){e.queued=!1,e.running=!0;try{e.automatic=r(e),n(e)&&t.refresh()}catch(l){i(e,l)}finally{e.running=!1}}})}return{bind:function(c){const a=e;if((null==a?void 0:a.dash)===c){const t=r(a);return void(n(a)&&(a.automatic=t,a.suspended=!1))}if(o(),e||!t.active(c)||"function"!==typeof c.on||"function"!==typeof c.off)return;const s={dash:c,off:c.off,callbacks:[],epoch:0,queued:!1,running:!1,suspended:!1,automatic:!1};e=s;const u=c.on;try{s.automatic=r(s);const e=["qualityChangeRequested","qualityChangeRendered","trackChangeRendered","streamUpdated","streamInitialized"].map(t=>[t,()=>{"streamUpdated"!==t&&"streamInitialized"!==t||(s.suspended=!1),l(s)}]),o=function(t,e){const n=t;try{if("function"!==typeof n.getVersion||"4.5.2"!==n.getVersion())return;if("function"!==typeof n.getVideoElement||"function"!==typeof n.getActiveStream||"function"!==typeof n.getDashMetrics)return}catch(o){return}let r=!1;return()=>{var t,o,i,l,c,a,s,u,f;if(!r&&e()){r=!0;try{const r=null==(t=n.getVideoElement)?void 0:t.call(n);if(!(null==r?void 0:r.seeking)||!Number.isFinite(r.currentTime))return;const d=r.currentTime,h=null==(o=n.getActiveStream)?void 0:o.call(n),g=null==(i=n.getDashMetrics)?void 0:i.call(n),p=null==(l=null==h?void 0:h.getProcessors)?void 0:l.call(h);if(!Array.isArray(p)||"function"!==typeof(null==g?void 0:g.getCurrentBufferLevel)||"function"!==typeof g.addBufferLevel)return;for(const t of p){if(!e())return;const o=null==(c=t.getType)?void 0:c.call(t);if("audio"!==o&&"video"!==o)continue;const i=null==(a=t.getBufferController)?void 0:a.call(t);if("function"!==typeof(null==i?void 0:i.getRangeAt)||"function"!==typeof i.getIsPruningInProgress||"function"!==typeof i.getAllRangesWithSafetyFactor)continue;const l=g.getCurrentBufferLevel(o),p=i.getRangeAt(d),y=null===p||p&&Number.isFinite(p.end)&&p.end<=d;if(!Number.isFinite(l)||l<=0||!y||!1!==i.getIsPruningInProgress())continue;const v=i.getAllRangesWithSafetyFactor(d);if(Array.isArray(v)&&0===v.length){if(!e()||(null==(s=n.getActiveStream)?void 0:s.call(n))!==h||(null==(u=n.getDashMetrics)?void 0:u.call(n))!==g||(null==(f=n.getVideoElement)?void 0:f.call(n))!==r||r.currentTime!==d||!r.seeking||!e())return;g.addBufferLevel(o,new Date,0)}}}catch(d){console.warn("ArtPlayer DASH seek buffer refresh failed:",d)}finally{r=!1}}}}(c,()=>n(s)&&!s.suspended);o&&e.push(["playbackSeeking",o]),e.push(["playbackTimeUpdated",()=>{if(n(s)&&!s.suspended)try{const t=r(s);n(s)&&t!==s.automatic&&(s.automatic=t,l(s))}catch(t){Line truncated
@@ -170,6 +170,59 @@ function createMenu(art, name, icon) {
return { update, clear };
}
const $quality = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" height="18"><path fill="#fff" d="M0 96C0 60.7 28.7 32 64 32l384 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 480c-35.3 0-64-28.7-64-64L0 96zM323.8 202.5c-4.5-6.6-11.9-10.5-19.8-10.5s-15.4 3.9-19.8 10.5l-87 127.6L170.7 297c-4.6-5.7-11.5-9-18.7-9s-14.2 3.3-18.7 9l-64 80c-5.8 7.2-6.9 17.1-2.9 25.4s12.4 13.6 21.6 13.6l96 0 32 0 208 0c8.9 0 17.1-4.9 21.2-12.8s3.6-17.4-1.4-24.7l-120-176zM112 192a48 48 0 1 0 0-96 48 48 0 1 0 0 96z"/></svg>';
function createSeekRecovery(value, active) {
const sdk = value;
try {
if (typeof sdk.getVersion !== "function" || sdk.getVersion() !== "4.5.2")
return;
if (typeof sdk.getVideoElement !== "function" || typeof sdk.getActiveStream !== "function" || typeof sdk.getDashMetrics !== "function")
return;
} catch {
return;
}
let running = false;
return () => {
if (running || !active())
return;
running = true;
try {
const video = sdk.getVideoElement?.();
if (!video?.seeking || !Number.isFinite(video.currentTime))
return;
const time = video.currentTime;
const stream = sdk.getActiveStream?.();
const metrics = sdk.getDashMetrics?.();
const processors = stream?.getProcessors?.();
if (!Array.isArray(processors) || typeof metrics?.getCurrentBufferLevel !== "function" || typeof metrics.addBufferLevel !== "function")
return;
for (const processor of processors) {
if (!active())
return;
const type = processor.getType?.();
if (type !== "audio" && type !== "video")
continue;
const buffer = processor.getBufferController?.();
if (typeof buffer?.getRangeAt !== "function" || typeof buffer.getIsPruningInProgress !== "function" || typeof buffer.getAllRangesWithSafetyFactor !== "function")
continue;
const cached = metrics.getCurrentBufferLevel(type);
const range = buffer.getRangeAt(time);
const empty = range === null || range && Number.isFinite(range.end) && range.end <= time;
if (!Number.isFinite(cached) || cached <= 0 || !empty || buffer.getIsPruningInProgress() !== false)
continue;
const removals = buffer.getAllRangesWithSafetyFactor(time);
if (!Array.isArray(removals) || removals.length !== 0)
continue;
if (!active() || sdk.getActiveStream?.() !== stream || sdk.getDashMetrics?.() !== metrics || sdk.getVideoElement?.() !== video || video.currentTime !== time || !video.seeking || !active())
return;
metrics.addBufferLevel(type, /* @__PURE__ */ new Date(), 0);
}
} catch (error) {
console.warn("ArtPlayer DASH seek buffer refresh failed:", error);
} finally {
running = false;
}
};
}
function observeSDK(options) {
let binding;
const active = (record) => binding === record && options.active(record.dash);
@@ -253,6 +306,9 @@ function observeSDK(options) {
record.suspended = false;
schedule(record);
}]);
const recoverSeek = createSeekRecovery(dash, () => active(record) && !record.suspended);
if (recoverSeek)
entries.push(["playbackSeeking", recoverSeek]);
entries.push(["playbackTimeUpdated", () => {
if (!active(record) || record.suspended)
return;
@@ -1,4 +1,5 @@
import type { Cleanup, Dash, SDKEventName } from './types'
import { createSeekRecovery } from './seek-buffer'
interface Binding<Level extends object, Track extends object> {
dash: Dash<Level, Track>
@@ -108,6 +109,9 @@ export function observeSDK<Level extends object, Track extends object>(options:
record.suspended = false
schedule(record)
}])
const recoverSeek = createSeekRecovery(dash, () => active(record) && !record.suspended)
if (recoverSeek)
entries.push(['playbackSeeking', recoverSeek])
entries.push(['playbackTimeUpdated', () => {
if (!active(record) || record.suspended)
return
@@ -0,0 +1,85 @@
import type { Cleanup, Valid } from './types'
type MediaType = 'audio' | 'video'
interface BufferController {
getRangeAt?: (time: number) => { end: number } | null
getIsPruningInProgress?: () => boolean
getAllRangesWithSafetyFactor?: (time: number) => unknown[]
}
interface Stream {
getProcessors?: () => {
getType?: () => string
getBufferController?: () => BufferController | null
}[]
}
interface LegacySDK {
getVersion?: () => string
getVideoElement?: () => { currentTime: number, seeking: boolean } | null
getActiveStream?: () => Stream | null
getDashMetrics?: () => {
getCurrentBufferLevel?: (type: MediaType) => number
addBufferLevel?: (type: MediaType, at: Date, level: 0) => void
} | null
}
export function createSeekRecovery(value: object, active: Valid): Cleanup | undefined {
const sdk = value as LegacySDK
try {
if (typeof sdk.getVersion !== 'function' || sdk.getVersion() !== '4.5.2')
return
if (typeof sdk.getVideoElement !== 'function' || typeof sdk.getActiveStream !== 'function' || typeof sdk.getDashMetrics !== 'function')
return
}
catch {
return
}
let running = false
return () => {
if (running || !active())
return
running = true
try {
const video = sdk.getVideoElement?.()
if (!video?.seeking || !Number.isFinite(video.currentTime))
return
const time = video.currentTime
const stream = sdk.getActiveStream?.()
const metrics = sdk.getDashMetrics?.()
const processors = stream?.getProcessors?.()
if (!Array.isArray(processors) || typeof metrics?.getCurrentBufferLevel !== 'function' || typeof metrics.addBufferLevel !== 'function')
return
for (const processor of processors) {
if (!active())
return
const type = processor.getType?.()
if (type !== 'audio' && type !== 'video')
continue
const buffer = processor.getBufferController?.()
if (typeof buffer?.getRangeAt !== 'function' || typeof buffer.getIsPruningInProgress !== 'function' || typeof buffer.getAllRangesWithSafetyFactor !== 'function')
continue
const cached = metrics.getCurrentBufferLevel(type)
const range = buffer.getRangeAt(time)
const empty = range === null || (range && Number.isFinite(range.end) && range.end <= time)
if (!Number.isFinite(cached) || cached <= 0 || !empty || buffer.getIsPruningInProgress() !== false)
continue
const removals = buffer.getAllRangesWithSafetyFactor(time)
if (!Array.isArray(removals) || removals.length !== 0)
continue
if (!active() || sdk.getActiveStream?.() !== stream || sdk.getDashMetrics?.() !== metrics || sdk.getVideoElement?.() !== video || video.currentTime !== time || !video.seeking || !active())
return
// SDK 4.5.2 schedules from metric history, not BufferController's getter.
// Record the measured empty range; leave buffers, media events and time intact.
metrics.addBufferLevel(type, new Date(), 0)
}
}
catch (error) {
console.warn('ArtPlayer DASH seek buffer refresh failed:', error)
}
finally {
running = false
}
}
}
@@ -5,7 +5,7 @@ export type Identifier = string | number
export type Valid = () => boolean
export type Cleanup = () => void
export type EventName = 'ready' | 'restart' | 'destroy'
export type SDKEventName = 'qualityChangeRequested' | 'qualityChangeRendered' | 'trackChangeRendered' | 'streamUpdated' | 'streamInitialized' | 'playbackTimeUpdated' | 'streamTeardownComplete'
export type SDKEventName = 'qualityChangeRequested' | 'qualityChangeRendered' | 'trackChangeRendered' | 'streamUpdated' | 'streamInitialized' | 'playbackTimeUpdated' | 'playbackSeeking' | 'streamTeardownComplete'
export interface QualityFields {
height?: number
@@ -0,0 +1,990 @@
{
"schemaVersion": 1,
"task": "PKG-DASH-SEEK-01",
"sourceHead": "442e1dcc961111b5f91de060d49b750f10a73e0d",
"scope": "Production compatibility repair for dash.js 4.5.2 stale metric scheduling after an empty-boundary seek. Not full DASH or release acceptance.",
"environment": {
"node": "24.21.0",
"yarn": "1.22.22",
"platform": "win32",
"remoteCI": false,
"physicalDevices": false
},
"source": [
{
"file": "packages/artplayer-plugin-dash-control/src/seek-buffer.ts",
"sha256LF": "ed86e48fabeac02a13cb4e7b6bd41b04bc55e432ae1d995cc432917cfaf1c33f"
},
{
"file": "packages/artplayer-plugin-dash-control/src/sdk-events.ts",
"sha256LF": "97fdabfc6f1484ae0e95e2970ee88a6be1d71d52512db36ddf6407f3a7737570"
},
{
"file": "packages/artplayer-plugin-dash-control/src/types.ts",
"sha256LF": "f1a7aaf9c273054d9584432c5d7ce43e00b339091859bb1c926f12e63ed17b03"
},
{
"file": "test/dash-events.test.js",
"sha256LF": "4a952e59a0fd593188defe8c189248a26fbaccb1cf5ce74ae9461565ece0d9c4"
},
{
"file": "test/browser/dash-sdk.spec.js",
"sha256LF": "b633a650c46868c3d7bbd58d386e967f14b0b7bdf1b39a6b5d90b5edd089dbcc"
},
{
"file": "test/browser/dash-seek-diagnostics.js",
"sha256LF": "4020b044968343eae681889a647c33dd0810000150a0f6d0140e32baa58813f6"
}
],
"artifacts": [
{
"file": "refactor/.cache/dash-package-types-tt3sEt/runtime/artplayer-plugin-dash-control.js",
"member": "package/dist/artplayer-plugin-dash-control.js",
"sha256": "a89652cb033d5d77d29d0084c36a5419d0a17b9a3e5a01269e4ab1d44dded55f",
"archiveSha256": "18b3df88479703e596bbf1d8fdca8fabe4ac73e34c34b73133b1749fbc0ef093"
},
{
"file": "refactor/.cache/dash-package-types-tt3sEt/runtime/artplayer-plugin-dash-control.legacy.js",
"member": "package/dist/artplayer-plugin-dash-control.legacy.js",
"sha256": "a21449b34ab4e7e81948117f7803123eb78e6dd63282d6a46f8c67fb7f4f2fc3",
"archiveSha256": "18b3df88479703e596bbf1d8fdca8fabe4ac73e34c34b73133b1749fbc0ef093"
},
{
"file": "refactor/.cache/dash-package-types-tt3sEt/runtime/artplayer-plugin-dash-control.mjs",
"member": "package/dist/artplayer-plugin-dash-control.mjs",
"sha256": "e07c946bf5626ef2a7dcfa6eb6b96ee63009205145ddafd73d8040f10144a6a7",
"archiveSha256": "18b3df88479703e596bbf1d8fdca8fabe4ac73e34c34b73133b1749fbc0ef093"
}
],
"reports": [
{
"role": "Negative counterfactual; original assertion remains failed",
"directory": "refactor/.cache/dash-getter-probe-report",
"reportSha256": "d8887dc7c578238811e388daca8769e268c1e6b13a090ea934ea19a4f5914c17",
"result": {
"scope": "source",
"exitCode": 1,
"signal": null,
"args": [
"test",
"--config=playwright.source.config.js",
"test/browser/dash-sdk.spec.js",
"--project=chromium",
"--project=firefox",
"--grep",
"native SDK track switch",
"--workers=1"
]
},
"stats": {
"startTime": "2026-09-14T11:38:11.152Z",
"duration": 51182.368,
"expected": 2,
"skipped": 0,
"unexpected": 2,
"flaky": 0
},
"cases": 4,
"retries": 0,
"environment": [
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/153.0.8010.12 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:155.0) Gecko/20100101 Firefox/155.0"
],
"inputs": [
{
"sdk": "4.5.2",
"sdkArchiveSha256": "9a0944c48deb0fd8f92b0eea436c5b0d0aea9237c7da0078c0310f1430d0a31f",
"sdkCodeMember": "package/dist/dash.all.min.js",
"candidateSha256": "011ea0e7ee2b15d29f9bd991a7a86644f9d72a02e7f0f330ebe26603a6714acd",
"candidate": "workspace source build",
"diagnostic": null
},
{
"sdk": "5.2.1",
"sdkArchiveSha256": "4eacb0cc3525276666ca3f084611cdabb5a2264d128dfe0750fe106fbddd088f",
"sdkCodeMember": "package/dist/modern/umd/dash.all.min.js",
"candidateSha256": "011ea0e7ee2b15d29f9bd991a7a86644f9d72a02e7f0f330ebe26603a6714acd",
"candidate": "workspace source build",
"diagnostic": null
}
],
"stableBoundaryCases": [],
"failures": [
{
"project": "chromium",
"title": "dash.js 4.5.2: native SDK track switch and paused seek without ArtPlayer",
"status": "failed",
"errors": [
"Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThan\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: > \u001b[32m6.2\u001b[39m\nReceived: \u001b[31m6\u001b[39m\n\nCall Log:\n- Timeout 7000ms exceeded while waiting on the predicate\n\n 323 | })\n 324 | await page.locator('#play').click()\n> 325 | await expect.poll(() => page.evaluate(() => window.nativeVideo.currentTime)).toBeGreaterThan(6.2)\n | ^\n 326 | expect(await page.evaluate(() => window.nativePlay)).toBe('fulfilled')\n 327 | expect(await page.evaluate(() => window.sdkErrors)).toEqual([])\n 328 | expect(await page.evaluate(() => window.Artplayer.instances.length)).toBe(0)\n at D:\\github\\ArtPlayer\\test\\browser\\dash-sdk.spec.js:325:82"
]
},
{
"project": "firefox",
"title": "dash.js 4.5.2: native SDK track switch and paused seek without ArtPlayer",
"status": "failed",
"errors": [
"Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThan\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: > \u001b[32m6.2\u001b[39m\nReceived: \u001b[31m6\u001b[39m\n\nCall Log:\n- Timeout 7000ms exceeded while waiting on the predicate\n\n 323 | })\n 324 | await page.locator('#play').click()\n> 325 | await expect.poll(() => page.evaluate(() => window.nativeVideo.currentTime)).toBeGreaterThan(6.2)\n | ^\n 326 | expect(await page.evaluate(() => window.nativePlay)).toBe('fulfilled')\n 327 | expect(await page.evaluate(() => window.sdkErrors)).toEqual([])\n 328 | expect(await page.evaluate(() => window.Artplayer.instances.length)).toBe(0)\n at D:\\github\\ArtPlayer\\test\\browser\\dash-sdk.spec.js:325:82"
]
}
],
"diagnostics": [
{
"project": "chromium",
"title": "dash.js 4.5.2: native SDK track switch and paused seek without ArtPlayer",
"name": "diagnostic-buffer-getter",
"advanced": false,
"records": [
{
"type": "video",
"reads": 0,
"adjusted": 0
},
{
"type": "audio",
"reads": 0,
"adjusted": 0
}
],
"beforeTime": 6,
"attachmentSha256": "3cd75242d35548d2f5e8c0d21eec5cca01a37267f9e245aab9ca4cb978d005d7"
},
{
"project": "firefox",
"title": "dash.js 4.5.2: native SDK track switch and paused seek without ArtPlayer",
"name": "diagnostic-buffer-getter",
"advanced": false,
"records": [
{
"type": "video",
"reads": 0,
"adjusted": 0
},
{
"type": "audio",
"reads": 0,
"adjusted": 0
}
],
"beforeTime": 6,
"attachmentSha256": "99c16984f7df69902ff3f7eee134a57167a331eff1ec817d438088d597bbdcf7"
}
]
},
{
"role": "Four original failures recover after diagnostic metric write; not acceptance",
"directory": "refactor/.cache/dash-metric-repeat-report",
"reportSha256": "ca2c564e13f0310ccb88b678a5830c6a7e5b3eaa9e5a5637f6b58949209068f8",
"result": {
"scope": "source",
"exitCode": 1,
"signal": null,
"args": [
"test",
"--config=playwright.source.config.js",
"test/browser/dash-sdk.spec.js",
"--project=chromium",
"--project=firefox",
"--grep",
"dash.js 4.5.2: native SDK",
"--repeat-each=2",
"--workers=1"
]
},
"stats": {
"startTime": "2026-09-14T11:42:54.123Z",
"duration": 58608.347,
"expected": 0,
"skipped": 0,
"unexpected": 4,
"flaky": 0
},
"cases": 4,
"retries": 0,
"environment": [
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/153.0.8010.12 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:155.0) Gecko/20100101 Firefox/155.0"
],
"inputs": [
{
"sdk": "4.5.2",
"sdkArchiveSha256": "9a0944c48deb0fd8f92b0eea436c5b0d0aea9237c7da0078c0310f1430d0a31f",
"sdkCodeMember": "package/dist/dash.all.min.js",
"candidateSha256": "011ea0e7ee2b15d29f9bd991a7a86644f9d72a02e7f0f330ebe26603a6714acd",
"candidate": "workspace source build",
"diagnostic": null
}
],
"stableBoundaryCases": [],
"failures": [
{
"project": "chromium",
"title": "dash.js 4.5.2: native SDK track switch and paused seek without ArtPlayer",
"status": "failed",
"errors": [
"Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThan\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: > \u001b[32m6.2\u001b[39m\nReceived: \u001b[31m6\u001b[39m\n\nCall Log:\n- Timeout 7000ms exceeded while waiting on the predicate\n\n 351 | })\n 352 | await page.locator('#play').click()\n> 353 | await expect.poll(() => page.evaluate(() => window.nativeVideo.currentTime)).toBeGreaterThan(6.2)\n | ^\n 354 | expect(await page.evaluate(() => window.nativePlay)).toBe('fulfilled')\n 355 | expect(await page.evaluate(() => window.sdkErrors)).toEqual([])\n 356 | expect(await page.evaluate(() => window.Artplayer.instances.length)).toBe(0)\n at D:\\github\\ArtPlayer\\test\\browser\\dash-sdk.spec.js:353:82"
]
},
{
"project": "chromium",
"title": "dash.js 4.5.2: native SDK track switch and paused seek without ArtPlayer",
"status": "failed",
"errors": [
"Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThan\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: > \u001b[32m6.2\u001b[39m\nReceived: \u001b[31m6\u001b[39m\n\nCall Log:\n- Timeout 7000ms exceeded while waiting on the predicate\n\n 351 | })\n 352 | await page.locator('#play').click()\n> 353 | await expect.poll(() => page.evaluate(() => window.nativeVideo.currentTime)).toBeGreaterThan(6.2)\n | ^\n 354 | expect(await page.evaluate(() => window.nativePlay)).toBe('fulfilled')\n 355 | expect(await page.evaluate(() => window.sdkErrors)).toEqual([])\n 356 | expect(await page.evaluate(() => window.Artplayer.instances.length)).toBe(0)\n at D:\\github\\ArtPlayer\\test\\browser\\dash-sdk.spec.js:353:82"
]
},
{
"project": "firefox",
"title": "dash.js 4.5.2: native SDK track switch and paused seek without ArtPlayer",
"status": "failed",
"errors": [
"Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThan\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: > \u001b[32m6.2\u001b[39m\nReceived: \u001b[31m6\u001b[39m\n\nCall Log:\n- Timeout 7000ms exceeded while waiting on the predicate\n\n 351 | })\n 352 | await page.locator('#play').click()\n> 353 | await expect.poll(() => page.evaluate(() => window.nativeVideo.currentTime)).toBeGreaterThan(6.2)\n | ^\n 354 | expect(await page.evaluate(() => window.nativePlay)).toBe('fulfilled')\n 355 | expect(await page.evaluate(() => window.sdkErrors)).toEqual([])\n 356 | expect(await page.evaluate(() => window.Artplayer.instances.length)).toBe(0)\n at D:\\github\\ArtPlayer\\test\\browser\\dash-sdk.spec.js:353:82"
]
},
{
"project": "firefox",
"title": "dash.js 4.5.2: native SDK track switch and paused seek without ArtPlayer",
"status": "failed",
"errors": [
"Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThan\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: > \u001b[32m6.2\u001b[39m\nReceived: \u001b[31m6\u001b[39m\n\nCall Log:\n- Timeout 7000ms exceeded while waiting on the predicate\n\n 351 | })\n 352 | await page.locator('#play').click()\n> 353 | await expect.poll(() => page.evaluate(() => window.nativeVideo.currentTime)).toBeGreaterThan(6.2)\n | ^\n 354 | expect(await page.evaluate(() => window.nativePlay)).toBe('fulfilled')\n 355 | expect(await page.evaluate(() => window.sdkErrors)).toEqual([])\n 356 | expect(await page.evaluate(() => window.Artplayer.instances.length)).toBe(0)\n at D:\\github\\ArtPlayer\\test\\browser\\dash-sdk.spec.js:353:82"
]
}
],
"diagnostics": [
{
"project": "chromium",
"title": "dash.js 4.5.2: native SDK track switch and paused seek without ArtPlayer",
"name": "diagnostic-buffer-metric",
"advanced": true,
"records": [
{
"type": "video",
"cached": 2.807,
"corrected": 0,
"time": 6,
"range": null
}
],
"settingsUnchanged": true,
"beforeTime": 6,
"afterMetricTime": 6,
"attachmentSha256": "ec72d9603c37207a2023543c9957918845ccfd14a6a8176079c28ca9a10ce83e"
},
{
"project": "chromium",
"title": "dash.js 4.5.2: native SDK track switch and paused seek without ArtPlayer",
"name": "diagnostic-buffer-metric",
"advanced": true,
"records": [
{
"type": "video",
"cached": 2.862,
"corrected": 0,
"time": 6,
"range": null
}
],
"settingsUnchanged": true,
"beforeTime": 6,
"afterMetricTime": 6,
"attachmentSha256": "c15d4f2b1b4392398437b895b08a181020b2ffce700876919d8c7d5c73b8f710"
},
{
"project": "firefox",
"title": "dash.js 4.5.2: native SDK track switch and paused seek without ArtPlayer",
"name": "diagnostic-buffer-metric",
"advanced": true,
"records": [
{
"type": "video",
"cached": 2.788,
"corrected": 0,
"time": 6,
"range": null
}
],
"settingsUnchanged": true,
"beforeTime": 6,
"afterMetricTime": 6,
"attachmentSha256": "646f6191a17f1982682f5f4e00c80a23e5c36c290e2a759f3571ad5af4354052"
},
{
"project": "firefox",
"title": "dash.js 4.5.2: native SDK track switch and paused seek without ArtPlayer",
"name": "diagnostic-buffer-metric",
"advanced": true,
"records": [
{
"type": "video",
"cached": 2.749,
"corrected": 0,
"time": 6,
"range": null
}
],
"settingsUnchanged": true,
"beforeTime": 6,
"afterMetricTime": 6,
"attachmentSha256": "97b5bf72c3cc9c669b7e8755be94cfcdc88cd0be54a825f806f8e8780edfa2ac"
}
]
},
{
"role": "Candidate before this repair, same strict boundary test",
"directory": "refactor/.cache/dash-seek-candidate-red-report",
"reportSha256": "3449fb4735082fa54660375cc99c42ecc2111a01f3877b10abae98865d84237b",
"result": {
"scope": "source",
"exitCode": 1,
"signal": null,
"args": [
"test",
"--config=playwright.source.config.js",
"test/browser/dash-sdk.spec.js",
"--project=chromium",
"--project=firefox",
"--grep",
"DASH 4.5.2: stable paused boundary",
"--workers=1"
]
},
"stats": {
"startTime": "2026-09-14T11:48:44.762Z",
"duration": 58998.359,
"expected": 0,
"skipped": 0,
"unexpected": 4,
"flaky": 0
},
"cases": 4,
"retries": 0,
"environment": [
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/153.0.8010.12 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:155.0) Gecko/20100101 Firefox/155.0"
],
"inputs": [
{
"sdk": "4.5.2",
"sdkArchiveSha256": "9a0944c48deb0fd8f92b0eea436c5b0d0aea9237c7da0078c0310f1430d0a31f",
"sdkCodeMember": "package/dist/dash.all.min.js",
"candidateSha256": "011ea0e7ee2b15d29f9bd991a7a86644f9d72a02e7f0f330ebe26603a6714acd",
"candidate": "D:\\github\\ArtPlayer\\refactor\\.cache\\dash-before-seek-recovery.js",
"diagnostic": null
}
],
"stableBoundaryCases": [
{
"title": "published core / candidate DASH 4.5.2: stable paused boundary seek preserves media and SDK ownership",
"project": "chromium",
"status": "unexpected"
},
{
"title": "candidate core / candidate DASH 4.5.2: stable paused boundary seek preserves media and SDK ownership",
"project": "chromium",
"status": "unexpected"
},
{
"title": "published core / candidate DASH 4.5.2: stable paused boundary seek preserves media and SDK ownership",
"project": "firefox",
"status": "unexpected"
},
{
"title": "candidate core / candidate DASH 4.5.2: stable paused boundary seek preserves media and SDK ownership",
"project": "firefox",
"status": "unexpected"
}
],
"failures": [
{
"project": "chromium",
"title": "published core / candidate DASH 4.5.2: stable paused boundary seek preserves media and SDK ownership",
"status": "failed",
"errors": [
"Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThan\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: > \u001b[32m6.2\u001b[39m\nReceived: \u001b[31m6\u001b[39m\n\nCall Log:\n- Timeout 7000ms exceeded while waiting on the predicate\n\n 317 | })\n 318 | await page.locator('#play').click()\n> 319 | await expect.poll(() => page.evaluate(() => (window.art?.video || window.nativeVideo).currentTime)).toBeGreaterThan(6.2)\n | ^\n 320 | }\n 321 |\n 322 | for (const core of ['published', 'candidate']) {\n at seekAtEmptyBoundary (D:\\github\\ArtPlayer\\test\\browser\\dash-sdk.spec.js:319:103)\n at D:\\github\\ArtPlayer\\test\\browser\\dash-sdk.spec.js:326:7",
"Error: Unhandled browser errors\n\n\u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\n\u001b[32m- Expected - 1\u001b[39m\n\u001b[31m+ Received + 3\u001b[39m\n\n\u001b[32m- Array []\u001b[39m\n\u001b[31m+ Array [\u001b[39m\n\u001b[31m+ \"The play() request was interrupted by a new load request. https://goo.gl/LdLk22\",\u001b[39m\n\u001b[31m+ ]\u001b[39m\n\n at fixtures.js:29\n\n 27 | }).catch(() => null)\n 28 | await testInfo.attach('browser-evidence', { contentType: 'application/json', body: JSON.stringify({ browser: browser.version(), platform: process.platform, manifest, state, errors, consoleErrors, failedRequests }, null, 2) })\n> 29 | expect(errors, 'Unhandled browser errors').toEqual([])\n | ^\n 30 | }, { auto: true }],\n 31 | })\n 32 |\n at Object.base.extend.diagnostics.auto (D:\\github\\ArtPlayer\\test\\browser\\fixtures.js:29:48)"
]
},
{
"project": "chromium",
"title": "candidate core / candidate DASH 4.5.2: stable paused boundary seek preserves media and SDK ownership",
"status": "failed",
"errors": [
"Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThan\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: > \u001b[32m6.2\u001b[39m\nReceived: \u001b[31m6\u001b[39m\n\nCall Log:\n- Timeout 7000ms exceeded while waiting on the predicate\n\n 317 | })\n 318 | await page.locator('#play').click()\n> 319 | await expect.poll(() => page.evaluate(() => (window.art?.video || window.nativeVideo).currentTime)).toBeGreaterThan(6.2)\n | ^\n 320 | }\n 321 |\n 322 | for (const core of ['published', 'candidate']) {\n at seekAtEmptyBoundary (D:\\github\\ArtPlayer\\test\\browser\\dash-sdk.spec.js:319:103)\n at D:\\github\\ArtPlayer\\test\\browser\\dash-sdk.spec.js:326:7",
"Error: Unhandled browser errors\n\n\u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\n\u001b[32m- Expected - 1\u001b[39m\n\u001b[31m+ Received + 3\u001b[39m\n\n\u001b[32m- Array []\u001b[39m\n\u001b[31m+ Array [\u001b[39m\n\u001b[31m+ \"The play() request was interrupted by a new load request. https://goo.gl/LdLk22\",\u001b[39m\n\u001b[31m+ ]\u001b[39m\n\n at fixtures.js:29\n\n 27 | }).catch(() => null)\n 28 | await testInfo.attach('browser-evidence', { contentType: 'application/json', body: JSON.stringify({ browser: browser.version(), platform: process.platform, manifest, state, errors, consoleErrors, failedRequests }, null, 2) })\n> 29 | expect(errors, 'Unhandled browser errors').toEqual([])\n | ^\n 30 | }, { auto: true }],\n 31 | })\n 32 |\n at Object.base.extend.diagnostics.auto (D:\\github\\ArtPlayer\\test\\browser\\fixtures.js:29:48)"
]
},
{
"project": "firefox",
"title": "published core / candidate DASH 4.5.2: stable paused boundary seek preserves media and SDK ownership",
"status": "failed",
"errors": [
"Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThan\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: > \u001b[32m6.2\u001b[39m\nReceived: \u001b[31m6\u001b[39m\n\nCall Log:\n- Timeout 7000ms exceeded while waiting on the predicate\n\n 317 | })\n 318 | await page.locator('#play').click()\n> 319 | await expect.poll(() => page.evaluate(() => (window.art?.video || window.nativeVideo).currentTime)).toBeGreaterThan(6.2)\n | ^\n 320 | }\n 321 |\n 322 | for (const core of ['published', 'candidate']) {\n at seekAtEmptyBoundary (D:\\github\\ArtPlayer\\test\\browser\\dash-sdk.spec.js:319:103)\n at D:\\github\\ArtPlayer\\test\\browser\\dash-sdk.spec.js:326:7",
"Error: Unhandled browser errors\n\n\u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\n\u001b[32m- Expected - 1\u001b[39m\n\u001b[31m+ Received + 3\u001b[39m\n\n\u001b[32m- Array []\u001b[39m\n\u001b[31m+ Array [\u001b[39m\n\u001b[31m+ \"The fetching process for the media resource was aborted by the user agent at the user's request.\",\u001b[39m\n\u001b[31m+ ]\u001b[39m\n\n at fixtures.js:29\n\n 27 | }).catch(() => null)\n 28 | await testInfo.attach('browser-evidence', { contentType: 'application/json', body: JSON.stringify({ browser: browser.version(), platform: process.platform, manifest, state, errors, consoleErrors, failedRequests }, null, 2) })\n> 29 | expect(errors, 'Unhandled browser errors').toEqual([])\n | ^\n 30 | }, { auto: true }],\n 31 | })\n 32 |\n at Object.base.extend.diagnostics.auto (D:\\github\\ArtPlayer\\test\\browser\\fixtures.js:29:48)"
]
},
{
"project": "firefox",
"title": "candidate core / candidate DASH 4.5.2: stable paused boundary seek preserves media and SDK ownership",
"status": "failed",
"errors": [
"Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThan\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: > \u001b[32m6.2\u001b[39m\nReceived: \u001b[31m6\u001b[39m\n\nCall Log:\n- Timeout 7000ms exceeded while waiting on the predicate\n\n 317 | })\n 318 | await page.locator('#play').click()\n> 319 | await expect.poll(() => page.evaluate(() => (window.art?.video || window.nativeVideo).currentTime)).toBeGreaterThan(6.2)\n | ^\n 320 | }\n 321 |\n 322 | for (const core of ['published', 'candidate']) {\n at seekAtEmptyBoundary (D:\\github\\ArtPlayer\\test\\browser\\dash-sdk.spec.js:319:103)\n at D:\\github\\ArtPlayer\\test\\browser\\dash-sdk.spec.js:326:7",
"Error: Unhandled browser errors\n\n\u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\n\u001b[32m- Expected - 1\u001b[39m\n\u001b[31m+ Received + 3\u001b[39m\n\n\u001b[32m- Array []\u001b[39m\n\u001b[31m+ Array [\u001b[39m\n\u001b[31m+ \"The fetching process for the media resource was aborted by the user agent at the user's request.\",\u001b[39m\n\u001b[31m+ ]\u001b[39m\n\n at fixtures.js:29\n\n 27 | }).catch(() => null)\n 28 | await testInfo.attach('browser-evidence', { contentType: 'application/json', body: JSON.stringify({ browser: browser.version(), platform: process.platform, manifest, state, errors, consoleErrors, failedRequests }, null, 2) })\n> 29 | expect(errors, 'Unhandled browser errors').toEqual([])\n | ^\n 30 | }, { auto: true }],\n 31 | })\n 32 |\n at Object.base.extend.diagnostics.auto (D:\\github\\ArtPlayer\\test\\browser\\fixtures.js:29:48)"
]
}
],
"diagnostics": []
},
{
"role": "Source repair strict boundary regression before final identity guard strengthening",
"directory": "refactor/.cache/dash-seek-source-green-report",
"reportSha256": "79a3622dea01bf6a60b9707da481926e5bab4dd5d63586f5c2cde8be4a920574",
"result": {
"scope": "source",
"exitCode": 0,
"signal": null,
"args": [
"test",
"--config=playwright.source.config.js",
"test/browser/dash-sdk.spec.js",
"--project=chromium",
"--project=firefox",
"--grep",
"stable paused boundary",
"--workers=1"
]
},
"stats": {
"startTime": "2026-09-14T11:50:29.292Z",
"duration": 48716.598000000005,
"expected": 8,
"skipped": 0,
"unexpected": 0,
"flaky": 0
},
"cases": 8,
"retries": 0,
"environment": [
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/153.0.8010.12 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:155.0) Gecko/20100101 Firefox/155.0"
],
"inputs": [
{
"sdk": "4.5.2",
"sdkArchiveSha256": "9a0944c48deb0fd8f92b0eea436c5b0d0aea9237c7da0078c0310f1430d0a31f",
"sdkCodeMember": "package/dist/dash.all.min.js",
"candidateSha256": "ef2025408d6df4df5ecd6c33b3c0313b239c8ae1b4bfbad8c6bb9c43f474e866",
"candidate": "workspace source build",
"diagnostic": null
},
{
"sdk": "5.2.1",
"sdkArchiveSha256": "4eacb0cc3525276666ca3f084611cdabb5a2264d128dfe0750fe106fbddd088f",
"sdkCodeMember": "package/dist/modern/umd/dash.all.min.js",
"candidateSha256": "ef2025408d6df4df5ecd6c33b3c0313b239c8ae1b4bfbad8c6bb9c43f474e866",
"candidate": "workspace source build",
"diagnostic": null
}
],
"stableBoundaryCases": [
{
"title": "published core / candidate DASH 4.5.2: stable paused boundary seek preserves media and SDK ownership",
"project": "chromium",
"status": "expected"
},
{
"title": "published core / candidate DASH 5.2.1: stable paused boundary seek preserves media and SDK ownership",
"project": "chromium",
"status": "expected"
},
{
"title": "candidate core / candidate DASH 4.5.2: stable paused boundary seek preserves media and SDK ownership",
"project": "chromium",
"status": "expected"
},
{
"title": "candidate core / candidate DASH 5.2.1: stable paused boundary seek preserves media and SDK ownership",
"project": "chromium",
"status": "expected"
},
{
"title": "published core / candidate DASH 4.5.2: stable paused boundary seek preserves media and SDK ownership",
"project": "firefox",
"status": "expected"
},
{
"title": "published core / candidate DASH 5.2.1: stable paused boundary seek preserves media and SDK ownership",
"project": "firefox",
"status": "expected"
},
{
"title": "candidate core / candidate DASH 4.5.2: stable paused boundary seek preserves media and SDK ownership",
"project": "firefox",
"status": "expected"
},
{
"title": "candidate core / candidate DASH 5.2.1: stable paused boundary seek preserves media and SDK ownership",
"project": "firefox",
"status": "expected"
}
],
"failures": [],
"diagnostics": []
},
{
"role": "Initial artifact, frozen published plugin failure retained",
"directory": "refactor/.cache/dash-seek-main-initial-sdk-report",
"reportSha256": "71e3c1538376369bc3ebfc909459c8ad51bbf3dd7f2a8458553ce49e78899b7c",
"result": {
"scope": "source",
"exitCode": 1,
"signal": null,
"args": [
"test",
"--config=playwright.source.config.js",
"test/browser/dash-sdk.spec.js",
"--project=chromium",
"--project=firefox",
"--grep-invert",
"native SDK track switch",
"--workers=2"
]
},
"stats": {
"startTime": "2026-09-14T11:57:49.211Z",
"duration": 142580.167,
"expected": 79,
"skipped": 0,
"unexpected": 1,
"flaky": 0
},
"cases": 80,
"retries": 0,
"environment": [
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/153.0.8010.12 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:155.0) Gecko/20100101 Firefox/155.0"
],
"inputs": [
{
"sdk": "4.5.2",
"sdkArchiveSha256": "9a0944c48deb0fd8f92b0eea436c5b0d0aea9237c7da0078c0310f1430d0a31f",
"sdkCodeMember": "package/dist/dash.all.min.js",
"candidateSha256": "0a7b4794473ee6f00d3504ca150448b198d7ca970e2e8efca1c236de6ed02d6b",
"candidate": "D:\\github\\ArtPlayer\\refactor\\.cache\\dash-package-types-57Y2kT\\runtime\\artplayer-plugin-dash-control.js",
"diagnostic": null
},
{
"sdk": "5.2.1",
"sdkArchiveSha256": "4eacb0cc3525276666ca3f084611cdabb5a2264d128dfe0750fe106fbddd088f",
"sdkCodeMember": "package/dist/modern/umd/dash.all.min.js",
"candidateSha256": "0a7b4794473ee6f00d3504ca150448b198d7ca970e2e8efca1c236de6ed02d6b",
"candidate": "D:\\github\\ArtPlayer\\refactor\\.cache\\dash-package-types-57Y2kT\\runtime\\artplayer-plugin-dash-control.js",
"diagnostic": null
}
],
"stableBoundaryCases": [
{
"title": "published core / candidate DASH 4.5.2: stable paused boundary seek preserves media and SDK ownership",
"project": "chromium",
"status": "expected"
},
{
"title": "published core / candidate DASH 5.2.1: stable paused boundary seek preserves media and SDK ownership",
"project": "chromium",
"status": "expected"
},
{
"title": "candidate core / candidate DASH 4.5.2: stable paused boundary seek preserves media and SDK ownership",
"project": "chromium",
"status": "expected"
},
{
"title": "candidate core / candidate DASH 5.2.1: stable paused boundary seek preserves media and SDK ownership",
"project": "chromium",
"status": "expected"
},
{
"title": "published core / candidate DASH 4.5.2: stable paused boundary seek preserves media and SDK ownership",
"project": "firefox",
"status": "expected"
},
{
"title": "published core / candidate DASH 5.2.1: stable paused boundary seek preserves media and SDK ownership",
"project": "firefox",
"status": "expected"
},
{
"title": "candidate core / candidate DASH 4.5.2: stable paused boundary seek preserves media and SDK ownership",
"project": "firefox",
"status": "expected"
},
{
"title": "candidate core / candidate DASH 5.2.1: stable paused boundary seek preserves media and SDK ownership",
"project": "firefox",
"status": "expected"
}
],
"failures": [
{
"project": "firefox",
"title": "candidate core / published DASH / 4.5.2: real MPD decoding and UI selection",
"status": "failed",
"errors": [
"Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThan\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: > \u001b[32m6.2\u001b[39m\nReceived: \u001b[31m6\u001b[39m\n\nCall Log:\n- Timeout 7000ms exceeded while waiting on the predicate\n\n 270 | })\n 271 | await page.locator('#play').click()\n> 272 | await expect.poll(() => page.evaluate(() => window.art.currentTime)).toBeGreaterThan(6.2)\n | ^\n 273 | expect(await page.evaluate(() => window.sdkErrors)).toEqual([])\n 274 | await testInfo.attach('dash-sdk-playing', { contentType: 'image/png', body: await page.screenshot() })\n 275 | })\n at D:\\github\\ArtPlayer\\test\\browser\\dash-sdk.spec.js:272:76",
"Error: Unhandled browser errors\n\n\u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\n\u001b[32m- Expected - 1\u001b[39m\n\u001b[31m+ Received + 3\u001b[39m\n\n\u001b[32m- Array []\u001b[39m\n\u001b[31m+ Array [\u001b[39m\n\u001b[31m+ \"The fetching process for the media resource was aborted by the user agent at the user's request.\",\u001b[39m\n\u001b[31m+ ]\u001b[39m\n\n at fixtures.js:29\n\n 27 | }).catch(() => null)\n 28 | await testInfo.attach('browser-evidence', { contentType: 'application/json', body: JSON.stringify({ browser: browser.version(), platform: process.platform, manifest, state, errors, consoleErrors, failedRequests }, null, 2) })\n> 29 | expect(errors, 'Unhandled browser errors').toEqual([])\n | ^\n 30 | }, { auto: true }],\n 31 | })\n 32 |\n at Object.base.extend.diagnostics.auto (D:\\github\\ArtPlayer\\test\\browser\\fixtures.js:29:48)"
]
}
],
"diagnostics": []
},
{
"role": "Final main runtime bytes; historical native SDK and old-plugin cases excluded only from this invocation",
"directory": "refactor/.cache/dash-seek-main-final-sdk-report",
"reportSha256": "0cb1b0abd5782bd5ea359c52a404caffb6345696b24c13ee2ed216a8f3e2e1f0",
"result": {
"scope": "source",
"exitCode": 0,
"signal": null,
"args": [
"test",
"--config=playwright.source.config.js",
"test/browser/dash-sdk.spec.js",
"--project=chromium",
"--project=firefox",
"--grep-invert",
"native SDK track switch|published DASH / 4.5.2",
"--workers=2"
]
},
"stats": {
"startTime": "2026-09-14T12:03:38.248Z",
"duration": 124451.803,
"expected": 76,
"skipped": 0,
"unexpected": 0,
"flaky": 0
},
"cases": 76,
"retries": 0,
"environment": [
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/153.0.8010.12 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:155.0) Gecko/20100101 Firefox/155.0"
],
"inputs": [
{
"sdk": "4.5.2",
"sdkArchiveSha256": "9a0944c48deb0fd8f92b0eea436c5b0d0aea9237c7da0078c0310f1430d0a31f",
"sdkCodeMember": "package/dist/dash.all.min.js",
"candidateSha256": "a89652cb033d5d77d29d0084c36a5419d0a17b9a3e5a01269e4ab1d44dded55f",
"candidate": "D:\\github\\ArtPlayer\\refactor\\.cache\\dash-package-types-tt3sEt\\runtime\\artplayer-plugin-dash-control.js",
"diagnostic": null
},
{
"sdk": "5.2.1",
"sdkArchiveSha256": "4eacb0cc3525276666ca3f084611cdabb5a2264d128dfe0750fe106fbddd088f",
"sdkCodeMember": "package/dist/modern/umd/dash.all.min.js",
"candidateSha256": "a89652cb033d5d77d29d0084c36a5419d0a17b9a3e5a01269e4ab1d44dded55f",
"candidate": "D:\\github\\ArtPlayer\\refactor\\.cache\\dash-package-types-tt3sEt\\runtime\\artplayer-plugin-dash-control.js",
"diagnostic": null
}
],
"stableBoundaryCases": [
{
"title": "published core / candidate DASH 4.5.2: stable paused boundary seek preserves media and SDK ownership",
"project": "chromium",
"status": "expected"
},
{
"title": "published core / candidate DASH 5.2.1: stable paused boundary seek preserves media and SDK ownership",
"project": "chromium",
"status": "expected"
},
{
"title": "candidate core / candidate DASH 4.5.2: stable paused boundary seek preserves media and SDK ownership",
"project": "chromium",
"status": "expected"
},
{
"title": "candidate core / candidate DASH 5.2.1: stable paused boundary seek preserves media and SDK ownership",
"project": "chromium",
"status": "expected"
},
{
"title": "published core / candidate DASH 4.5.2: stable paused boundary seek preserves media and SDK ownership",
"project": "firefox",
"status": "expected"
},
{
"title": "published core / candidate DASH 5.2.1: stable paused boundary seek preserves media and SDK ownership",
"project": "firefox",
"status": "expected"
},
{
"title": "candidate core / candidate DASH 4.5.2: stable paused boundary seek preserves media and SDK ownership",
"project": "firefox",
"status": "expected"
},
{
"title": "candidate core / candidate DASH 5.2.1: stable paused boundary seek preserves media and SDK ownership",
"project": "firefox",
"status": "expected"
}
],
"failures": [],
"diagnostics": []
},
{
"role": "Final legacy runtime bytes; 75 pass and SDK 5.2.1 menu visibility failure retained",
"directory": "refactor/.cache/dash-seek-legacy-final-sdk-report",
"reportSha256": "74a3924972eeb7f3b22a3595bb1d5450088bba025cc83e022066fd3820989d8a",
"result": {
"scope": "source",
"exitCode": 1,
"signal": null,
"args": [
"test",
"--config=playwright.source.config.js",
"test/browser/dash-sdk.spec.js",
"--project=chromium",
"--project=firefox",
"--grep-invert",
"native SDK track switch|published DASH / 4.5.2",
"--workers=2"
]
},
"stats": {
"startTime": "2026-09-14T12:09:50.376Z",
"duration": 142553.80599999998,
"expected": 75,
"skipped": 0,
"unexpected": 1,
"flaky": 0
},
"cases": 76,
"retries": 0,
"environment": [
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/153.0.8010.12 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:155.0) Gecko/20100101 Firefox/155.0"
],
"inputs": [
{
"sdk": "4.5.2",
"sdkArchiveSha256": "9a0944c48deb0fd8f92b0eea436c5b0d0aea9237c7da0078c0310f1430d0a31f",
"sdkCodeMember": "package/dist/dash.all.min.js",
"candidateSha256": "a21449b34ab4e7e81948117f7803123eb78e6dd63282d6a46f8c67fb7f4f2fc3",
"candidate": "D:\\github\\ArtPlayer\\refactor\\.cache\\dash-package-types-tt3sEt\\runtime\\artplayer-plugin-dash-control.legacy.js",
"diagnostic": null
},
{
"sdk": "5.2.1",
"sdkArchiveSha256": "4eacb0cc3525276666ca3f084611cdabb5a2264d128dfe0750fe106fbddd088f",
"sdkCodeMember": "package/dist/modern/umd/dash.all.min.js",
"candidateSha256": "a21449b34ab4e7e81948117f7803123eb78e6dd63282d6a46f8c67fb7f4f2fc3",
"candidate": "D:\\github\\ArtPlayer\\refactor\\.cache\\dash-package-types-tt3sEt\\runtime\\artplayer-plugin-dash-control.legacy.js",
"diagnostic": null
}
],
"stableBoundaryCases": [
{
"title": "published core / candidate DASH 4.5.2: stable paused boundary seek preserves media and SDK ownership",
"project": "chromium",
"status": "expected"
},
{
"title": "published core / candidate DASH 5.2.1: stable paused boundary seek preserves media and SDK ownership",
"project": "chromium",
"status": "expected"
},
{
"title": "candidate core / candidate DASH 4.5.2: stable paused boundary seek preserves media and SDK ownership",
"project": "chromium",
"status": "expected"
},
{
"title": "candidate core / candidate DASH 5.2.1: stable paused boundary seek preserves media and SDK ownership",
"project": "chromium",
"status": "expected"
},
{
"title": "published core / candidate DASH 4.5.2: stable paused boundary seek preserves media and SDK ownership",
"project": "firefox",
"status": "expected"
},
{
"title": "published core / candidate DASH 5.2.1: stable paused boundary seek preserves media and SDK ownership",
"project": "firefox",
"status": "expected"
},
{
"title": "candidate core / candidate DASH 4.5.2: stable paused boundary seek preserves media and SDK ownership",
"project": "firefox",
"status": "expected"
},
{
"title": "candidate core / candidate DASH 5.2.1: stable paused boundary seek preserves media and SDK ownership",
"project": "firefox",
"status": "expected"
}
],
"failures": [
{
"project": "chromium",
"title": "published core / DASH 5.2.1: actual settings quality selection survives SDK refresh",
"status": "timedOut",
"errors": [
"\u001b[31mTest timeout of 20000ms exceeded.\u001b[39m",
"Error: locator.click: Test timeout of 20000ms exceeded.\nCall log:\n\u001b[2m - waiting for locator('.art-setting-panel.art-current .art-setting-item-left-text').filter({ hasText: /^180p$/ })\u001b[22m\n\u001b[2m - locator resolved to <div class=\"art-setting-item-left-text\">180p</div>\u001b[22m\n\u001b[2m - attempting click action\u001b[22m\n\u001b[2m 2 × waiting for element to be visible, enabled and stable\u001b[22m\n\u001b[2m - element is not stable\u001b[22m\n\u001b[2m - retrying click action\u001b[22m\n\u001b[2m - waiting 20ms\u001b[22m\n\u001b[2m 2 × waiting for element to be visible, enabled and stable\u001b[22m\n\u001b[2m - element is not visible\u001b[22m\n\u001b[2m - retrying click action\u001b[22m\n\u001b[2m - waiting 100ms\u001b[22m\n\u001b[2m 36 × waiting for element to be visible, enabled and stable\u001b[22m\n\u001b[2m - element is not visible\u001b[22m\n\u001b[2m - retrying click action\u001b[22m\n\u001b[2m - waiting 500ms\u001b[22m\n\n\n 339 | await page.locator('.art-control-setting').click()\n 340 | await page.locator(`[data-name=\"dash-${type}\"]`).click()\n> 341 | await page.locator('.art-setting-panel.art-current .art-setting-item-left-text').filter({ hasText: new RegExp(`^${label}$`) }).click()\n | ^\n 342 | if (type === 'quality') {\n 343 | await expect.poll(() => page.evaluate(() => ({ height: window.art.video.videoHeight, auto: window.art.dash.getSettings().streaming.abr.autoSwitchBitrate.video }))).toEqual({ height: 180, auto: false })\n 344 | }\n at D:\\github\\ArtPlayer\\test\\browser\\dash-sdk.spec.js:341:136"
]
}
],
"diagnostics": []
},
{
"role": "Extracted diagnostic helper, original bare-SDK assertion remains failed",
"directory": "refactor/.cache/dash-seek-metric-helper-final-report",
"reportSha256": "b3d4c238543f5949944be438761c8390dff27c17624dbd38761601b7ec16bfee",
"result": {
"scope": "source",
"exitCode": 1,
"signal": null,
"args": [
"test",
"--config=playwright.source.config.js",
"test/browser/dash-sdk.spec.js",
"--project=chromium",
"--grep",
"dash.js 4.5.2: native SDK track switch",
"--workers=1"
]
},
"stats": {
"startTime": "2026-09-14T12:13:45.782Z",
"duration": 15693.702,
"expected": 0,
"skipped": 0,
"unexpected": 1,
"flaky": 0
},
"cases": 1,
"retries": 0,
"environment": [
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/153.0.8010.12 Safari/537.36"
],
"inputs": [
{
"sdk": "4.5.2",
"sdkArchiveSha256": "9a0944c48deb0fd8f92b0eea436c5b0d0aea9237c7da0078c0310f1430d0a31f",
"sdkCodeMember": "package/dist/dash.all.min.js",
"candidateSha256": "b9c81fe7223b79775b5c2d3b9dbc79477b7e993201a8412fba81143df39972cf",
"candidate": "workspace source build",
"diagnostic": null
}
],
"stableBoundaryCases": [],
"failures": [
{
"project": "chromium",
"title": "dash.js 4.5.2: native SDK track switch and paused seek without ArtPlayer",
"status": "failed",
"errors": [
"Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThan\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: > \u001b[32m6.2\u001b[39m\nReceived: \u001b[31m6\u001b[39m\n\nCall Log:\n- Timeout 7000ms exceeded while waiting on the predicate\n\n 269 | })\n 270 | await page.locator('#play').click()\n> 271 | await expect.poll(() => page.evaluate(() => (window.art?.video || window.nativeVideo).currentTime)).toBeGreaterThan(6.2)\n | ^\n 272 | }\n 273 |\n 274 | for (const core of ['published', 'candidate']) {\n at seekAtEmptyBoundary (D:\\github\\ArtPlayer\\test\\browser\\dash-sdk.spec.js:271:103)\n at D:\\github\\ArtPlayer\\test\\browser\\dash-sdk.spec.js:323:5"
]
}
],
"diagnostics": [
{
"project": "chromium",
"title": "dash.js 4.5.2: native SDK track switch and paused seek without ArtPlayer",
"name": "diagnostic-buffer-metric",
"advanced": true,
"records": [
{
"type": "video",
"cached": 2.84,
"corrected": 0,
"time": 6,
"range": null
}
],
"settingsUnchanged": true,
"beforeTime": 6,
"afterMetricTime": 6,
"attachmentSha256": "3dfdb05c65d93bff7281db436ad54e1be260198a201a44f3a87da0a127332ee0"
}
]
}
],
"packageTypes": {
"file": "refactor/.cache/dash-package-types-8CjCBf/report.json",
"sha256": "878e65e25a760ff76748b5006da94a4fa501618bc24f290234df1356818efd25",
"archives": [
{
"name": "artplayer",
"sha256": "d4a0cc5dcbe015e254b3bce64d505ee4e010792ab70414bcf8d0139d7c413b75"
},
{
"name": "artplayer-plugin-dash-control",
"sha256": "5f3fa80f04392d6bbd8ed46d2e1a2fedcf742eeb907b07c40b6edb815079f44b"
}
],
"matrix": [
{
"compiler": "5.9.3",
"mode": "node10-commonjs",
"diagnostics": 0,
"invalidRejected": 8
},
{
"compiler": "5.9.3",
"mode": "nodenext-cjs",
"diagnostics": 0,
"invalidRejected": 8
},
{
"compiler": "5.9.3",
"mode": "nodenext-esm",
"diagnostics": 0,
"invalidRejected": 8
},
{
"compiler": "5.9.3",
"mode": "bundler-esm",
"diagnostics": 0,
"invalidRejected": 8
},
{
"compiler": "4.3.5",
"mode": "node10-commonjs",
"diagnostics": 0,
"invalidRejected": 8
}
],
"runtimeBytesMatchBrowserInputs": true,
"method": "Offline Yarn installation and frozen reinstallation outside workspace; all installed file hashes verified. Browser tests load extracted earlier tarball bytes identical to final packed runtime; not a full installed browser suite."
},
"node": {
"file": "refactor/.cache/dash-seek-artifact-node-final.log",
"sha256": "c4ca56f28948de13e8b374e704ef8dd8a5398ae29468fb4ea8cddccce6c32d88",
"passed": 414,
"failed": 0,
"skipped": 0,
"scope": "source plus three extracted tarball formats and historical contracts"
},
"checks": {
"strictPackageTypeScript": "exit 0",
"rootLint": {
"file": "refactor/.cache/dash-seek-lint.log",
"sha256": "51b9b3f7ac85138eea089c5431e7b9bd60736814d6595a3dec9174e42baf7390",
"errors": 0,
"warnings": 1
},
"diagnosticGuards": {
"patchedSdkExitCode": 1,
"mixedExitCode": 1
}
},
"limitations": [
"DASH-SEEK-01 stays open for standalone SDK 4.5.2 and frozen published plugins",
"DASH-MENU-01 tracks legacy SDK 5.2.1 settings click visibility failure; no root-cause claim",
"Full CI source suite remains failing on the original native SDK control; no committed exclusions or expected-failure marks",
"WebKit, devices and final package/global release reviews are not completed by this repair"
]
}
@@ -0,0 +1,78 @@
# PKG-DASH-SEEK-01:旧 SDK 暂停跳转的缓冲指标修复
## 行为与兼容边界
dash.js 4.5.2 切换轨道、稳定暂停后,若 seek 目标正好位于视频缓冲末端,
空裁剪分支没有刷新缓冲量。调度器继续读取暂停前约 2.8 秒的指标,停止下载,
播放时间停在 6 秒。旧候选插件在新旧核心、Chromium/Firefox 的四个严格用例
全部复现。生产修复后,相同目标及顺序恢复播放,SDK 5.2.1 对照不受影响。
公开工厂、option、update、类型、事件、DOM/CSS 和分发入口保持原样。
消费者仍通过原来的 `art.seek = 6` 操作,不需要更换 SDK 或调用新接口。
涉及 API-02/04/05/07/09;不是对所有历史 SDK 本体的修复声明。
## 模块与根因证据
新增 `src/seek-buffer.ts`,仅负责 4.5.2 空缓冲指标同步;`sdk-events.ts`
负责注册和释放 playbackSeeking 回调。精确版本、可用方法、正在 seeking、
有限时间、实际范围为空、缓存量大于零、无正在进行或计划中的裁剪全部满足
后,调用 SDK 自有 `addBufferLevel(type, new Date(), 0)`。读取指标以秒计、
SDK 保存指标以毫秒计,这里只写实测零值,不存在非零单位换算。
写入前再次验证 stream、metrics、video、时间与插件生命周期身份;销毁、
替换 SDK、teardown 和重入均使陈旧回调失效。错误保留现有菜单并允许后续
seek 重试。不创建异步任务、计时器或新资源管理器;回调复用现有订阅归属。
只改 BufferController.getBufferLevel 的诊断在两引擎都未恢复,读取次数为零;
调度器实际从 DashMetrics 读取。四个原生失败后,仅同步真实空范围对应指标,
四次均恢复,配置和同步前后的时间不变。诊断保留原失败退出码,不能计入通过。
诊断函数抽到 `test/browser/dash-seek-diagnostics.js`,显式开关不可混用,也不能
搭配修改后的 SDK。抽取后另一次原生失败及随后的指标恢复验证了该诊断入口。
拒绝 pruneAllSafely:该 SDK 方法不传时间时可能清空全部缓冲。
拒绝伪造零长裁剪范围:不能提供真实裁剪完成语义。生产实现不替换 SDK 方法、
不改 SDK 配置、不清缓冲、不设置媒体时间、不发送合成 DOM 媒体事件。
根因源码及历史诊断另见 [原始记录](2026-09-12-PKG-DASH-05-seek-diagnosis.md)。
## 实际验证
环境为 Windows、Node 24.21.0、Yarn 1.22.22,Playwright Chromium 153.0.8010.12
及 Firefox 155.0。完整输入指纹、退出码及失败内容见
[机器证据](../baselines/dash-seek-recovery-validation.json)。
| 范围 | 结果 |
| --- | --- |
| 修复前候选,4.5.2 × 新旧核心 × 两引擎,严格暂停跳转 | 4 失败,原报告保留 |
| 修复后源码,4.5.2/5.2.1 × 新旧核心 × 两引擎,同一严格用例 | 8 通过 |
| 最终源码与 main/legacy/ESM 三格式 Node 行为/生命周期/历史契约 | 414 通过、0 失败/跳过 |
| 最终 main 的真实 SDK 定向矩阵 | 76 通过、0 跳过/重试 |
| 最终 legacy 的同一矩阵 | 75 通过、1 失败,0 跳过/重试;其中严格跳转 8 项全通过 |
| 最终含文档包,仓库外离线安装及冻结重装、公开类型 | 5 模式通过,每模式 8 个非法调用被拒绝 |
| 包内严格 TypeScript、根 lint | 通过;根 lint 0 错误、1 个既有 warning |
| 诊断开关混用、诊断搭配已改 SDK | 两种均在收集阶段拒绝 |
浏览器输入来自 tt3sEt tarball 解出的运行时;最终含文档 8CjCBf tarball 的三个
运行时 SHA 与它们逐一相等,安装检查也验证了所有文件。类型检查确实从仓库外
安装包解析;浏览器用的是相同 tarball 字节,不能将其称作全包 installed 浏览器验收。
main 初轮较宽矩阵 79 通过、1 个旧发布插件/4.5.2 失败也保留。最终 76 项命令
仅在本次调用中排除了裸 SDK 与旧发布插件的历史对照;默认测试文件与 CI 没有
添加 skip、预期失败、过滤或延长超时。完整源码 CI 中的裸 4.5.2 仍会失败。
legacy 唯一失败是“旧核心 + 新插件 + SDK 5.2.1”的设置质量选择:180p 元素
解析后先不稳定、随后不可见,点击超时。该 SDK 不进入本次 4.5.2 修复分支;
这不证明菜单失败的根因或历史来源。登记 DASH-MENU-01 / PKG-DASH-MENU-01
继续调查,保留截图、trace 和原断言,不以强制点击、重跑或忽略失败结案。
## 交付与后续
本次 4.5.2 修复任务完成;PKG-DASH-05/06、DASH-SEEK-01 的旧 SDK/旧插件边界、
新菜单失败、WebKit/设备及全局三轮发布复盘仍开放。没有新增依赖、锁文件或
公共声明变更。生产构建由仓库脚本生成,并同步 docs/compiled 三种格式。
重跑使用 Node 固定版本和 Yarn:`yarn test:dash-control`、
`yarn test:dash-types-package`;浏览器使用 `ARTPLAYER_DASH_ARTIFACT` 指定核验过的
main/legacy 文件后执行 `yarn test:browser:source test/browser/dash-sdk.spec.js
--project=chromium --project=firefox --workers=2`。不加过滤会包含原始历史失败对照。
回退本任务的独立提交并重建 DASH 三格式即可恢复修复前行为。禁止只回退生成文件。
本地提交后运行提交审计再启动下一项;没有 push、部署或 npm 发布。
+14 -2
View File
@@ -83,7 +83,19 @@ UMD 测试 VM 显式注入宿主 console,以检查原始错误身份;没有
## 05 暂停 seek 诊断
实际 SDK 4.5.2 在不创建 ArtPlayer 的稳定暂停用例中也会停滞,SDK 5.2.1 的对应用例通过。
根因与空裁剪分支漏更新缓冲指标有关,DASH-SEEK-01 仍 open;没有发布兼容补丁。
`dash-buffer-observer.js` 记录原生 SourceBuffer/SDK 指标,两个显式诊断环境变量只用于
根因与空裁剪分支漏更新缓冲指标有关,DASH-SEEK-01 仍 open。PKG-DASH-SEEK-01
已完成仅针对4.5.2的插件兼容处理;原始SDK本体及旧插件对照仍保留失败。
`dash-buffer-observer.js` 记录原生 SourceBuffer/SDK 指标,原有显式诊断环境变量只用于
失败后的合成事件和内存 SDK 补丁对照,不能用于正式验收。命令、证据、限制和回退见
[seek 根因记录](changes/2026-09-12-PKG-DASH-05-seek-diagnosis.md)。
新增GETTER/METRICS诊断由 `dash-seek-diagnostics.js` 承担,原断言失败后才执行,
退出码仍失败。缓冲控制器getter替换未恢复调度;记录实际空缓冲指标后两引擎
恢复。这些诊断不能混用或与修改后的SDK组合。正式实现 `src/seek-buffer.ts`
不替换getter,在已有SDK事件订阅的生命周期内同步零指标,并检查旧SDK版本、
真实目标范围、stream/media/metrics身份、清理与重入;生产及产物回归单独验收。
见[修复及验证](changes/2026-09-14-PKG-DASH-SEEK-01-buffer-metrics.md):旧候选严格
跳转4失败,新源码8通过,最终main/legacy各8个严格跳转通过。main完整定向76通过;
legacy同范围75通过、1个SDK5.2.1设置点击不可见失败,归PKG-DASH-MENU-01继续调查。
不将局部跳转修复等同于DASH组合/分发验收全部完成。
+9 -5
View File
@@ -2,9 +2,9 @@
> 由 tasks.json 生成。请修改数据后运行 `node refactor/scripts/plan.mjs --write`,不要手改本表。
基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 259 项,范围 22 个包及工作区/示例。
基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 261 项,范围 22 个包及工作区/示例。
状态:todo 49 / doing 20 / blocked 0 / done 190 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
状态:todo 50 / doing 20 / blocked 0 / done 191 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
前置依赖是启动条件;验收是完成条件。任务可以继续拆分,但不能复用或悄悄删除旧 ID。
@@ -24,7 +24,7 @@
| artplayer-plugin-chromecast | 1.1.0 | PKG-CAST-01, PKG-CAST-02, PKG-CAST-03, PKG-CAST-04, PKG-CAST-05, PKG-CAST-06 |
| artplayer-plugin-danmuku | 5.3.0 | PKG-DANMUKU-01, PKG-DANMUKU-02, PKG-DANMUKU-03, PKG-DANMUKU-04, PKG-DANMUKU-05, PKG-DANMUKU-06, PKG-DANMUKU-07, PKG-DANMUKU-08, PKG-DANMUKU-09, PKG-DANMUKU-10, PKG-DANMUKU-11, PKG-DANMUKU-12 |
| artplayer-plugin-danmuku-mask | 1.1.0 | PKG-MASK-01, PKG-MASK-02, PKG-MASK-03, PKG-MASK-04, PKG-MASK-05, PKG-MASK-06, ENG-LINT-01 |
| artplayer-plugin-dash-control | 1.1.0 | PKG-DASH-01, PKG-DASH-02, PKG-DASH-03, PKG-DASH-04, PKG-DASH-05, PKG-DASH-06 |
| artplayer-plugin-dash-control | 1.1.0 | PKG-DASH-01, PKG-DASH-02, PKG-DASH-03, PKG-DASH-04, PKG-DASH-SEEK-01, PKG-DASH-MENU-01, PKG-DASH-05, PKG-DASH-06 |
| artplayer-plugin-document-pip | 1.1.0 | PKG-DPIP-01, PKG-DPIP-02, PKG-DPIP-03, PKG-DPIP-04, PKG-DPIP-05, PKG-DPIP-06 |
| artplayer-plugin-hls-control | 1.1.0 | PKG-HLS-01, PKG-HLS-02, PKG-HLS-03, PKG-HLS-04, PKG-HLS-SDK-01, PKG-HLS-05, PKG-HLS-06 |
| artplayer-plugin-jassub | 1.1.0 | PKG-JASSUB-01, PKG-JASSUB-02, PKG-JASSUB-03, PKG-JASSUB-04, PKG-JASSUB-05, PKG-JASSUB-06, PKG-JASSUB-07, PKG-JASSUB-08, PKG-JASSUB-09, PKG-JASSUB-10 |
@@ -218,8 +218,10 @@
| PKG-DASH-02 | artplayer-plugin-dash-control<br>建立特有行为与错误测试 | PKG-DASH-01, ENG-03, ENG-05 | 保留已有 5 项稳定 ID 回归,补发布版 SDK 4.x/当前 5.x、音轨/空列表/过滤/换源与关闭引用 | 旧版本行为可重跑,成功/失败/切源/销毁有必要断言 | H | done |
| PKG-DASH-03 | artplayer-plugin-dash-control<br>整理内部职责与资源 | PKG-DASH-02, CORE-11, CORE-14 | 能力适配保留 SDK 4.x 与 5.x,稳定 ID 映射、ABR 状态、UI 清理和生命周期职责分离 | 结构变化和缺陷修复分开记录;原 API/事件/资源生命周期通过 | H | done |
| PKG-DASH-04 | artplayer-plugin-dash-control<br>迁移自有源码和公开类型 | PKG-DASH-03, ENG-04, ENG-06, CORE-07 | dash.js adapter、selector 和回调类型 | 严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容 | H | done |
| PKG-DASH-05 | artplayer-plugin-dash-control<br>验证新旧核心和组合 | PKG-DASH-04, CORE-22 | 固定 dash.js 4.5.2/5.2.1 与本地 DASH 实际清晰度/音轨、高亮、Auto 和换源组合 | 最终核心与原支持范围核心分别通过;设备/SDK 缺证据不能标完成 | H | doing |
| PKG-DASH-06 | artplayer-plugin-dash-control<br>验证分发并同步文档 | PKG-DASH-05, ENG-07 | dash.control.js、支持的 dash.js 版本与产物 | tarball 入口/资源、类型、8082 demo 和 README 一致,有回退记录 | H | todo |
| PKG-DASH-SEEK-01 | artplayer-plugin-dash-control<br>修复 SDK 4.5.2 暂停跳转后的缓冲指标停滞 | PKG-DASH-04 | 按版本和实测空缓冲限定的指标同步、生命周期守卫及真实 SDK 旧红新绿 | 新插件在新旧核心及 Chromium/Firefox 原位置回归通过;不修改 SDK 方法/配置、媒体时间或删除缓冲;旧 SDK 原生缺陷与旧插件限制独立记录,不宣称第三方本体已修复 | H | done |
| PKG-DASH-MENU-01 | artplayer-plugin-dash-control<br>定位并修复 SDK 刷新期间设置选项不可见 | PKG-DASH-SEEK-01 | 真实 SDK 菜单点击失败根因、对应兼容修复及新旧核心回归 | 保留 legacy/旧核心/SDK5.2.1 的原失败;确定菜单在点击过程中不可见的原因;不靠 force 点击、扩大超时或重复运行覆盖问题;main/legacy 质量与音轨设置及 SDK 刷新组合通过 | H | todo |
| PKG-DASH-05 | artplayer-plugin-dash-control<br>验证新旧核心和组合 | PKG-DASH-04, CORE-22, PKG-DASH-SEEK-01 | 固定 dash.js 4.5.2/5.2.1 与本地 DASH 实际清晰度/音轨、高亮、Auto 和换源组合 | 最终核心与原支持范围核心分别通过;设备/SDK 缺证据不能标完成 | H | doing |
| PKG-DASH-06 | artplayer-plugin-dash-control<br>验证分发并同步文档 | PKG-DASH-05, ENG-07, PKG-DASH-MENU-01 | dash.control.js、支持的 dash.js 版本与产物 | tarball 入口/资源、类型、8082 demo 和 README 一致,有回退记录 | H | todo |
## 5 包迁移:artplayer-plugin-multiple-subtitles
@@ -560,6 +562,8 @@
- PKG-DASH-02: [记录](changes/2026-09-12-PKG-DASH-02-tests.md) [记录](dash-validation.md) [记录](baselines/dash-validation.json)
- PKG-DASH-03: [记录](changes/2026-09-12-PKG-DASH-03-runtime.md) [记录](baselines/dash-runtime.json) [记录](dash-validation.md)
- PKG-DASH-04: [记录](changes/2026-09-12-PKG-DASH-04-types.md) [记录](baselines/dash-types-validation.json) [记录](dash-validation.md)
- PKG-DASH-SEEK-01: [记录](changes/2026-09-14-PKG-DASH-SEEK-01-buffer-metrics.md) [记录](baselines/dash-seek-recovery-validation.json)
- PKG-DASH-MENU-01: [记录](changes/2026-09-14-PKG-DASH-SEEK-01-buffer-metrics.md) [记录](baselines/dash-seek-recovery-validation.json)
- PKG-DASH-05: [记录](changes/2026-09-12-PKG-DASH-05-sdk-checkpoint.md) [记录](baselines/dash-sdk.json) [记录](baselines/dash-sdk-checkpoint.json) [记录](dash-validation.md) [记录](changes/2026-09-12-PKG-DASH-05-sdk-types.md) [记录](baselines/dash-sdk-types-validation.json) [记录](changes/2026-09-12-PKG-DASH-05-sdk-events.md) [记录](baselines/dash-sdk-events-validation.json) [记录](changes/2026-09-12-PKG-DASH-05-seek-diagnosis.md) [记录](baselines/dash-seek-diagnosis.json)
- PKG-MULTI-SUB-01: [记录](baselines/multiple-subtitles-release.json) [记录](baselines/multiple-subtitles-contract.md) [记录](baselines/multiple-subtitles-contract-validation.json) [记录](changes/2026-09-13-PKG-MULTI-SUB-01-contract.md)
- PKG-MULTI-SUB-02: [记录](baselines/multiple-subtitles-failures.json) [记录](changes/2026-09-13-PKG-MULTI-SUB-02-failures.md)
+18
View File
@@ -1,5 +1,23 @@
# 进度与证据
## PKG-DASH-SEEK-01 暂停跳转缓冲指标修复完成
仅在dash.js 4.5.2真实空缓冲且缓存指标陈旧时同步零值,恢复SDK自身下载调度。
模块复用SDK事件订阅的所有权,检查版本、stream/video/metrics身份、暂停跳转、
裁剪和重入边界;不替换SDK方法、不清缓冲、不改时间或发送合成媒体事件。
修复前新旧核心两引擎严格跳转4失败;修复后源码8通过,最终main/legacy各8通过。
源码和三格式Node414通过;最终含文档包离线安装/冻结重装、五组类型通过。
严格TS、根lint和专项lint通过,根lint仍有1个既有warning。
main定向SDK矩阵76通过;legacy75通过1失败,原报告保持:SDK5.2.1/旧核心下
质量设置180p在点击过程中不可见。根因尚未确认,新增PKG-DASH-MENU-01及
DASH-MENU-01接续处理,不靠force点击、扩大超时或重跑覆盖。裸4.5.2与旧插件
原始停滞仍保留,DASH-SEEK-01/PKG-DASH-05/06及全局发布门槛保持开放。
诊断getter无效和指标同步有效的反事实报告仍以失败退出,未混入验收通过数。
见[变更](changes/2026-09-14-PKG-DASH-SEEK-01-buffer-metrics.md)和
[机器证据](baselines/dash-seek-recovery-validation.json)。261项:191 done、20 doing、
50 todo。此任务独立本地提交并完成提交审计后,继续设置菜单失败调查;没有push或发布。
## CI-01 Chromium 完整源码范围回归检查点
固定fc566f14d源码、Node24.21.0/Yarn1.22.22,完整152文件1481项,双worker运行
+2 -1
View File
@@ -140,7 +140,7 @@
| DASH-STATE-01 | resolved / 已复现 | DASH 当前项高亮使用真值 ID、音轨身份和标签去重,边界待核实 | PKG-DASH-02, PKG-DASH-03, PKG-DASH-05 |
| DASH-TYPE-01 | resolved / 源码/产物事实 | DASH 类型未表达默认工厂参数、SDK 回调对象和现代模块入口 | PKG-DASH-04 |
| DASH-DEMO-01 | open / 源码/产物事实 | DASH 示例仅新 SDK 且重复安装会累积销毁回调 | PKG-DASH-06 |
| DASH-SEEK-01 | open / 已复现 | dash.js 4.5.2 空裁剪漏更新缓冲量,稳定暂停 seek 后调度停滞 | PKG-DASH-05, REVIEW-02 |
| DASH-SEEK-01 | open / 已复现 | dash.js 4.5.2 空裁剪漏更新缓冲量,稳定暂停 seek 后调度停滞 | PKG-DASH-05, REVIEW-02, PKG-DASH-SEEK-01 |
| ADS-TYPE-01 | accepted-with-scope / 已复现 | Ads 声明丢失真实 html/video/url,旧 totalDuration 为 string,当前 source/type 未实现 | PKG-ADS-02, PKG-ADS-04 |
| ADS-DIST-01 | open / 已复现 | Ads npm 1.0.6 default namespace 与未发布 2.1.0 callable/exports 分发不同 | PKG-ADS-04, PKG-ADS-06, REL-01 |
| ADS-CORE-01 | resolved / 源码/产物事实 | Ads 旧发布关联核心 4.5.5,当前源码新增 >=5 门槛和 document 事件桥依赖 | PKG-ADS-02, PKG-ADS-03, PKG-ADS-05 |
@@ -274,3 +274,4 @@
| CHAPTER-FULLSCREEN-GEOMETRY-01 | resolved / 已复现 | Chromium 原生全屏后章节 hover 标题曾出现后又隐藏 | PKG-CHAPTER-05, PKG-CHAPTER-HOVER-01 |
| CANVAS-SUBTITLE-01 | resolved / 已复现 | Canvas底层video缺初始字幕轨道且普通Canvas挂载不能加载原生字幕 | PKG-CANVAS-SUBTITLE-01, PKG-CANVAS-05 |
| DPIP-INSTALLED-PROVENANCE-01 | resolved / 已复现 | Document PiP测试加载器忽略显式安装map并回退到源码 | PKG-CANVAS-05, CI-01 |
| DASH-MENU-01 | open / 已复现 | SDK 5.2.1 播放中设置质量选项在点击时变为不可见 | PKG-DASH-MENU-01, PKG-DASH-05, REVIEW-02 |
+23 -3
View File
@@ -3055,7 +3055,8 @@
"status": "open",
"owners": [
"PKG-DASH-05",
"REVIEW-02"
"REVIEW-02",
"PKG-DASH-SEEK-01"
],
"evidence": [
"test/browser/dash-sdk.spec.js",
@@ -3065,9 +3066,11 @@
"refactor/baselines/dash-seek-diagnosis.json",
"test/browser/dash-buffer-observer.js",
"refactor/changes/2026-09-14-CI-01-source-chromium.md",
"refactor/baselines/ci-source-chromium-validation.json"
"refactor/baselines/ci-source-chromium-validation.json",
"refactor/changes/2026-09-14-PKG-DASH-SEEK-01-buffer-metrics.md",
"refactor/baselines/dash-seek-recovery-validation.json"
],
"compatibleResolution": "已用原生 SDK、SourceBuffer/调度指标和 SDK 5 空裁剪刷新的一行诊断回植确认根因。兼容处置尚未落地;不在核心伪造 timeupdate、不偏移 seek、不强制所有旧用户升级 SDK,也不将诊断补丁计为正式上游通过。",
"compatibleResolution": "PKG-DASH-SEEK-01 已在候选插件按精确版本和真实空缓冲同步 SDK 指标,新旧核心及两引擎严格跳转通过。裸 SDK 4.5.2 和冻结旧插件仍保留原始失败;不伪造媒体事件、不偏移 seek、不强制升级 SDK,完整组合及发布验收仍开放。",
"closureCriteria": "原失败的新旧核心/插件组合有确定根因与对应修复/可验证环境边界;原生对照及原顺序、原目标回归成立,后续偶然通过不能关闭。"
},
{
@@ -6267,6 +6270,23 @@
"refactor/scripts/ci-workflow.test.mjs"
],
"resolutionRationale": "Old loader test reports missing expected rejection; updated loader rejects missing/stale maps and conflicting modes. Five-package isolated build/pack/install supplies main and legacy native PiP tests with package hashes. Local workflow mutation protects the required DPIP preparation; remote runs remain unverified."
},
{
"id": "DASH-MENU-01",
"title": "SDK 5.2.1 播放中设置质量选项在点击时变为不可见",
"confirmation": "reproduced",
"status": "open",
"owners": [
"PKG-DASH-MENU-01",
"PKG-DASH-05",
"REVIEW-02"
],
"evidence": [
"refactor/changes/2026-09-14-PKG-DASH-SEEK-01-buffer-metrics.md",
"refactor/baselines/dash-seek-recovery-validation.json"
],
"compatibleResolution": "最终 legacy 定向矩阵复现:旧核心、新插件、Chromium 的180p选项先不稳定后不可见;保留点击失败与trace。SDK5.2.1不进入4.5.2指标修复分支,但菜单根因及历史来源尚未确定。",
"closureCriteria": "定位实际隐藏/刷新时序并提供可复现旧红新绿;新旧核心、main/legacy、质量/音轨、SDK刷新与销毁组合验证,不靠force点击或重跑覆盖失败。"
}
]
}
+42 -2
View File
@@ -2239,6 +2239,44 @@
"dash-validation.md"
]
},
{
"id": "PKG-DASH-SEEK-01",
"phase": "5 包迁移:artplayer-plugin-dash-control",
"title": "修复 SDK 4.5.2 暂停跳转后的缓冲指标停滞",
"scope": [
"artplayer-plugin-dash-control"
],
"dependsOn": [
"PKG-DASH-04"
],
"status": "done",
"risk": "H",
"deliverable": "按版本和实测空缓冲限定的指标同步、生命周期守卫及真实 SDK 旧红新绿",
"acceptance": "新插件在新旧核心及 Chromium/Firefox 原位置回归通过;不修改 SDK 方法/配置、媒体时间或删除缓冲;旧 SDK 原生缺陷与旧插件限制独立记录,不宣称第三方本体已修复",
"evidence": [
"changes/2026-09-14-PKG-DASH-SEEK-01-buffer-metrics.md",
"baselines/dash-seek-recovery-validation.json"
]
},
{
"id": "PKG-DASH-MENU-01",
"phase": "5 包迁移:artplayer-plugin-dash-control",
"title": "定位并修复 SDK 刷新期间设置选项不可见",
"scope": [
"artplayer-plugin-dash-control"
],
"dependsOn": [
"PKG-DASH-SEEK-01"
],
"status": "todo",
"risk": "H",
"deliverable": "真实 SDK 菜单点击失败根因、对应兼容修复及新旧核心回归",
"acceptance": "保留 legacy/旧核心/SDK5.2.1 的原失败;确定菜单在点击过程中不可见的原因;不靠 force 点击、扩大超时或重复运行覆盖问题;main/legacy 质量与音轨设置及 SDK 刷新组合通过",
"evidence": [
"changes/2026-09-14-PKG-DASH-SEEK-01-buffer-metrics.md",
"baselines/dash-seek-recovery-validation.json"
]
},
{
"id": "PKG-DASH-05",
"phase": "5 包迁移:artplayer-plugin-dash-control",
@@ -2248,7 +2286,8 @@
],
"dependsOn": [
"PKG-DASH-04",
"CORE-22"
"CORE-22",
"PKG-DASH-SEEK-01"
],
"status": "doing",
"risk": "H",
@@ -2276,7 +2315,8 @@
],
"dependsOn": [
"PKG-DASH-05",
"ENG-07"
"ENG-07",
"PKG-DASH-MENU-01"
],
"status": "todo",
"risk": "H",
+75 -37
View File
@@ -4,6 +4,7 @@ import process from 'node:process'
import { ensureArchive, hash, readMember } from '../../refactor/scripts/releases.mjs'
import { compilePackage } from '../helpers/load.js'
import { observeDashBuffers } from './dash-buffer-observer.js'
import { probeBufferGetter, probeBufferMetric } from './dash-seek-diagnostics.js'
import { expect, test } from './fixtures.js'
const sdks = new Map()
@@ -14,6 +15,9 @@ let mediaManifest
let pluginRelease
const diagnosticSDK = process.env.ARTPLAYER_DASH_DIAGNOSTIC_SDK || 'none'
assert(['none', 'upstream4', 'bufferlevel4'].includes(diagnosticSDK), 'Unknown diagnostic SDK mode')
const recoveryModes = ['ARTPLAYER_DASH_DIAGNOSE_STALL', 'ARTPLAYER_DASH_DIAGNOSE_GETTER', 'ARTPLAYER_DASH_DIAGNOSE_METRICS'].filter(name => process.env[name] === '1')
assert(recoveryModes.length <= 1, 'Choose only one DASH recovery diagnostic')
assert(!recoveryModes.length || diagnosticSDK === 'none', 'Recovery diagnostics require the unchanged published SDK')
test.beforeAll(async () => {
const baseline = JSON.parse(fs.readFileSync(new URL('../../refactor/baselines/dash-sdk.json', import.meta.url)))
@@ -81,6 +85,22 @@ test.afterEach(async ({ page }, testInfo) => {
recovery.final = await page.evaluate(() => window.dashBuffers.snapshot())
await testInfo.attach('diagnostic-synthetic-timeupdate', { contentType: 'application/json', body: JSON.stringify(recovery) })
}
if (process.env.ARTPLAYER_DASH_DIAGNOSE_GETTER === '1' && testInfo.status !== testInfo.expectedStatus && state.seeking && state.time === 6) {
const recovery = await page.evaluate(probeBufferGetter)
recovery.advanced = await page.waitForFunction(() => (window.art?.video || window.nativeVideo).currentTime > 6.2, null, { timeout: 3000 }).then(() => true, () => false)
recovery.final = await page.evaluate(() => {
for (const restore of window.dashGetterRestores)
restore()
return { state: window.dashBuffers.snapshot(), records: window.dashGetterRecords }
})
await testInfo.attach('diagnostic-buffer-getter', { contentType: 'application/json', body: JSON.stringify(recovery) })
}
if (process.env.ARTPLAYER_DASH_DIAGNOSE_METRICS === '1' && testInfo.status !== testInfo.expectedStatus && state.seeking && state.time === 6) {
const recovery = await page.evaluate(probeBufferMetric)
recovery.advanced = await page.waitForFunction(() => (window.art?.video || window.nativeVideo).currentTime > 6.2, null, { timeout: 3000 }).then(() => true, () => false)
recovery.final = await page.evaluate(() => window.dashBuffers.snapshot())
await testInfo.attach('diagnostic-buffer-metric', { contentType: 'application/json', body: JSON.stringify(recovery) })
}
if (!page.isClosed()) {
await page.evaluate(() => {
if (window.art && !window.art.isDestroy)
@@ -208,6 +228,60 @@ for (const core of ['published', 'candidate']) {
}
}
async function seekAtEmptyBoundary(page) {
await expect.poll(() => page.evaluate(() => (window.art?.video || window.nativeVideo).readyState)).toBeGreaterThanOrEqual(2)
await page.locator('#play').click()
await expect.poll(() => page.evaluate(() => (window.art?.video || window.nativeVideo).currentTime)).toBeGreaterThan(0.3)
await page.evaluate(() => {
const dash = (window.art?.dash || window.nativeDash)
dash.updateSettings({ streaming: { abr: { autoSwitchBitrate: { video: false } } } })
if (dash.setQualityFor)
dash.setQualityFor('video', 1)
else
dash.setRepresentationForTypeById('video', '1')
})
await expect.poll(() => page.evaluate(() => (window.art?.video || window.nativeVideo).videoHeight)).toBe(180)
// Match the failed integrated run: video data ends at the next seek target.
await expect.poll(() => page.evaluate(() => {
const video = (window.art?.video || window.nativeVideo)
return video.currentTime >= 2.8 && Math.abs(video.buffered.end(video.buffered.length - 1) - 6) < 0.0001
})).toBe(true)
await page.evaluate(() => {
const dash = (window.art?.dash || window.nativeDash)
dash.updateSettings({ streaming: { abr: { autoSwitchBitrate: { video: true } } } })
dash.setCurrentTrack(dash.getTracksFor('audio').find(track => track.lang === 'fr'))
})
await expect.poll(() => page.evaluate(() => (window.art?.dash || window.nativeDash).getCurrentTrackFor('audio').lang)).toBe('fr')
// The recorded integrated stall happened after the audio switch was rendered.
await expect.poll(() => page.evaluate(() => window.sdkEvents.some(event => event.name === 'TRACK_CHANGE_RENDERED' && event.mediaType === 'audio'))).toBe(true)
await page.locator('#pause').click()
// Observe a genuinely held pause: no clock/append events for half a second.
// This separates the seek from progress events still queued by the track switch.
await page.waitForFunction(() => (window.art?.video || window.nativeVideo).paused && performance.now() - window.dashBuffers.events.at(-1).at >= 500)
expect(await page.evaluate(() => (window.art?.video || window.nativeVideo).paused)).toBe(true)
await page.evaluate(() => {
window.dashBuffers.mark('before-seek')
if (window.art)
window.art.seek = 6
else
window.nativeVideo.currentTime = 6
window.dashBuffers.mark('assigned-seek')
})
await page.locator('#play').click()
await expect.poll(() => page.evaluate(() => (window.art?.video || window.nativeVideo).currentTime)).toBeGreaterThan(6.2)
}
for (const core of ['published', 'candidate']) {
for (const version of ['4.5.2', '5.2.1']) {
test(`${core} core / candidate DASH ${version}: stable paused boundary seek preserves media and SDK ownership`, async ({ page }, testInfo) => {
await openDash(page, version, core, 'candidate', testInfo)
await seekAtEmptyBoundary(page)
expect(await page.evaluate(() => window.sdkErrors)).toEqual([])
expect(await page.evaluate(() => window.art.dash.getVideoElement() === window.art.video && window.sdkDestroyed === 0)).toBe(true)
})
}
}
for (const version of ['4.5.2', '5.2.1']) {
test(`dash.js ${version}: native SDK track switch and paused seek without ArtPlayer`, async ({ page }, testInfo) => {
const capability = await loadSDK(page, version, 'candidate', testInfo)
@@ -246,43 +320,7 @@ for (const version of ['4.5.2', '5.2.1']) {
}
document.querySelector('#pause').onclick = () => video.pause()
})
await expect.poll(() => page.evaluate(() => window.nativeVideo.readyState)).toBeGreaterThanOrEqual(2)
await page.locator('#play').click()
await expect.poll(() => page.evaluate(() => window.nativeVideo.currentTime)).toBeGreaterThan(0.3)
await page.evaluate(() => {
const dash = window.nativeDash
dash.updateSettings({ streaming: { abr: { autoSwitchBitrate: { video: false } } } })
if (dash.setQualityFor)
dash.setQualityFor('video', 1)
else
dash.setRepresentationForTypeById('video', '1')
})
await expect.poll(() => page.evaluate(() => window.nativeVideo.videoHeight)).toBe(180)
// Match the failed integrated run: video data ends at the next seek target.
await expect.poll(() => page.evaluate(() => {
const video = window.nativeVideo
return video.currentTime >= 2.8 && Math.abs(video.buffered.end(video.buffered.length - 1) - 6) < 0.0001
})).toBe(true)
await page.evaluate(() => {
const dash = window.nativeDash
dash.updateSettings({ streaming: { abr: { autoSwitchBitrate: { video: true } } } })
dash.setCurrentTrack(dash.getTracksFor('audio').find(track => track.lang === 'fr'))
})
await expect.poll(() => page.evaluate(() => window.nativeDash.getCurrentTrackFor('audio').lang)).toBe('fr')
// The recorded integrated stall happened after the audio switch was rendered.
await expect.poll(() => page.evaluate(() => window.sdkEvents.some(event => event.name === 'TRACK_CHANGE_RENDERED' && event.mediaType === 'audio'))).toBe(true)
await page.locator('#pause').click()
// Observe a genuinely held pause: no clock/append events for half a second.
// This separates the seek from progress events still queued by the track switch.
await page.waitForFunction(() => window.nativeVideo.paused && performance.now() - window.dashBuffers.events.at(-1).at >= 500)
expect(await page.evaluate(() => window.nativeVideo.paused)).toBe(true)
await page.evaluate(() => {
window.dashBuffers.mark('before-seek')
window.nativeVideo.currentTime = 6
window.dashBuffers.mark('assigned-seek')
})
await page.locator('#play').click()
await expect.poll(() => page.evaluate(() => window.nativeVideo.currentTime)).toBeGreaterThan(6.2)
await seekAtEmptyBoundary(page)
expect(await page.evaluate(() => window.nativePlay)).toBe('fulfilled')
expect(await page.evaluate(() => window.sdkErrors)).toEqual([])
expect(await page.evaluate(() => window.Artplayer.instances.length)).toBe(0)
+54
View File
@@ -0,0 +1,54 @@
// Page-only counterfactuals, called after a failed SDK assertion. Never production recovery.
export function probeBufferGetter() {
const dash = window.art?.dash || window.nativeDash
const video = window.art?.video || window.nativeVideo
const before = window.dashBuffers.snapshot()
const records = []
window.dashGetterRestores = []
for (const processor of dash.getActiveStream().getProcessors()) {
const buffer = processor.getBufferController()
const original = buffer.getBufferLevel
const record = { type: processor.getType(), reads: 0, adjusted: 0 }
const wrapped = function (...args) {
const cached = Reflect.apply(original, this, args)
record.reads++
const time = video.currentTime
const range = buffer.getRangeAt(time)
if (video.seeking && cached > 0 && (!range || range.end <= time) && !buffer.getIsPruningInProgress() && buffer.getAllRangesWithSafetyFactor(time).length === 0) {
record.adjusted++
return 0
}
return cached
}
buffer.getBufferLevel = wrapped
records.push(record)
window.dashGetterRestores.push(() => {
if (buffer.getBufferLevel === wrapped)
buffer.getBufferLevel = original
})
}
window.dashGetterRecords = records
return { before, mode: 'conditional-buffer-getter', acceptance: false }
}
export function probeBufferMetric() {
const dash = window.art?.dash || window.nativeDash
const video = window.art?.video || window.nativeVideo
const metrics = dash.getDashMetrics()
const before = window.dashBuffers.snapshot()
const settings = JSON.stringify(dash.getSettings())
const records = []
for (const processor of dash.getActiveStream().getProcessors()) {
const type = processor.getType()
const buffer = processor.getBufferController()
const time = video.currentTime
const cached = metrics.getCurrentBufferLevel(type)
const range = buffer.getRangeAt(time)
if (video.seeking && cached > 0 && (!range || range.end <= time) && !buffer.getIsPruningInProgress() && buffer.getAllRangesWithSafetyFactor(time).length === 0) {
metrics.addBufferLevel(type, new Date(), 0)
records.push({ type, cached, corrected: metrics.getCurrentBufferLevel(type), time, range })
}
}
return { before, mode: 'measured-empty-buffer-metric', acceptance: false, records, afterMetric: window.dashBuffers.snapshot(), settingsUnchanged: settings === JSON.stringify(dash.getSettings()) }
}
+174
View File
@@ -20,6 +20,180 @@ function eventHost(version) {
}, listenerCount: () => [...events.values()].reduce((sum, callbacks) => sum + callbacks.size, 0) }
}
function seekHost() {
const host = eventHost(4)
Object.assign(host.video, { currentTime: 6, seeking: true, dispatchEvent() {
assert.fail('Recovery must not dispatch native media events')
} })
const samples = []
const levels = { video: 3, audio: 5 }
const buffers = Object.fromEntries(['video', 'audio'].map(type => [type, {
end: type === 'video' ? 6 : 11,
pruning: false,
removals: [],
getRangeAt(time) { return this.end > time ? { end: this.end } : null },
getIsPruningInProgress() { return this.pruning },
getAllRangesWithSafetyFactor() { return this.removals },
clearBuffers() { assert.fail('Recovery must not clear buffers') },
pruneAllSafely() { assert.fail('Recovery must not prune buffers') },
}]))
const metrics = {
getCurrentBufferLevel(type) { return levels[type] },
addBufferLevel(type, at, level) {
assert(Number.isFinite(at.valueOf()))
samples.push({ type, level })
levels[type] = level
},
}
const state = { stream: { getProcessors: () => Object.entries(buffers).map(([type, buffer]) => ({ getType: () => type, getBufferController: () => buffer })) } }
Object.assign(host.dash, { getVersion: () => '4.5.2', getDashMetrics: () => metrics, getActiveStream: () => state.stream })
return { ...host, samples, levels, buffers, metrics, seekState: state }
}
for (const { name, factory } of candidates.filter(item => item.sdk === 4)) {
test(`DASH (${name}): seeking records only measured empty stale metrics without changing SDK methods or media`, () => {
const host = seekHost()
const getters = [host.metrics.getCurrentBufferLevel, host.buffers.video.getRangeAt]
const settings = JSON.stringify(host.state.settings)
factory(bothMenus())(host.art).update()
assert.equal(host.listenerCount(), 8)
host.fire('playbackSeeking')
host.fire('playbackSeeking')
assert.deepEqual(host.samples, [{ type: 'video', level: 0 }])
assert.equal(host.levels.audio, 5)
assert.equal(host.video.currentTime, 6)
assert.equal(JSON.stringify(host.state.settings), settings)
assert.deepEqual([host.metrics.getCurrentBufferLevel, host.buffers.video.getRangeAt], getters)
})
test(`DASH (${name}): recovery excludes other SDK versions and unavailable optional capabilities`, () => {
for (const version of ['5.2.1', '4.5.1', undefined]) {
const host = seekHost()
host.dash.getVersion = () => version
factory(bothMenus())(host.art).update()
assert.equal(host.listenerCount(), 7)
host.fire('playbackSeeking')
assert.equal(host.samples.length, 0)
}
const host = seekHost()
delete host.dash.getDashMetrics
factory(bothMenus())(host.art).update()
assert.equal(host.listenerCount(), 7)
})
test(`DASH (${name}): nonempty, pruning, unready and invalid seek measurements do not overwrite metrics`, () => {
const changes = [
h => h.video.seeking = false,
h => h.video.currentTime = Number.NaN,
h => h.levels.video = 0,
h => h.levels.video = Number.NaN,
h => h.levels.video = Number.POSITIVE_INFINITY,
h => h.buffers.video.end = 8,
h => h.buffers.video.getRangeAt = () => ({ end: Number.NaN }),
h => h.buffers.video.getRangeAt = () => undefined,
h => h.buffers.video.pruning = true,
h => h.buffers.video.pruning = undefined,
h => h.buffers.video.removals.push({ start: 0, end: 1 }),
h => delete h.buffers.video.getRangeAt,
h => h.seekState.stream = null,
]
for (const change of changes) {
const host = seekHost()
factory(bothMenus())(host.art).update()
change(host)
host.fire('playbackSeeking')
assert.equal(host.samples.length, 0)
}
})
test(`DASH (${name}): escaped recovery callbacks are inert after SDK replacement or destruction`, () => {
const host = seekHost()
factory(bothMenus())(host.art).update()
const retained = [...host.events.get('playbackSeeking')][0]
host.art.dash = {}
retained()
host.art.dash = host.dash
host.art.destroy()
retained()
assert.equal(host.samples.length, 0)
assert.equal(host.listenerCount(), 0)
})
test(`DASH (${name}): stream teardown suspends recovery until the active stream is initialized`, async () => {
const host = seekHost()
factory(bothMenus())(host.art).update()
host.fire('streamTeardownComplete')
host.fire('playbackSeeking')
assert.equal(host.samples.length, 0)
host.fire('streamInitialized')
await flush()
host.fire('playbackSeeking')
assert.deepEqual(host.samples, [{ type: 'video', level: 0 }])
})
test(`DASH (${name}): reentrant metrics and destruction cannot write a second stream measurement`, () => {
const host = seekHost()
host.buffers.audio.end = 6
const add = host.metrics.addBufferLevel
host.metrics.addBufferLevel = (...args) => {
host.fire('playbackSeeking')
add(...args)
host.art.destroy()
}
factory(bothMenus())(host.art).update()
host.fire('playbackSeeking')
assert.deepEqual(host.samples, [{ type: 'video', level: 0 }])
assert.equal(host.listenerCount(), 0)
})
test(`DASH (${name}): source, video and time changes during measurement cancel the stale write`, () => {
for (const change of [h => h.seekState.stream = {}, h => h.state.video = {}, h => h.video.currentTime = 7, h => h.dash.getDashMetrics = () => null]) {
const host = seekHost()
host.buffers.video.getRangeAt = () => {
change(host)
return null
}
factory(bothMenus())(host.art).update()
host.fire('playbackSeeking')
assert.equal(host.samples.length, 0)
}
})
test(`DASH (${name}): metric errors keep menus and allow a later recovery attempt`, (t) => {
const host = seekHost()
const add = host.metrics.addBufferLevel
const failure = new Error('Metric write failed')
const warnings = []
t.mock.method(console, 'warn', (...args) => warnings.push(args))
host.metrics.addBufferLevel = () => {
throw failure
}
factory(bothMenus())(host.art).update()
host.fire('playbackSeeking')
assert.equal(warnings.length, 1)
assert.equal(warnings[0][1], failure)
assert.equal(host.controls.size + host.settings.size, 4)
assert.equal(host.listenerCount(), 8)
host.metrics.addBufferLevel = add
host.fire('playbackSeeking')
assert.equal(host.samples.length, 1)
})
test(`DASH (${name}): destruction from the final SDK media check prevents a late metric write`, () => {
const host = seekHost()
factory(bothMenus())(host.art).update()
let reads = 0
host.dash.getVideoElement = () => {
if (++reads === 2)
host.art.destroy()
return host.video
}
host.fire('playbackSeeking')
assert.equal(host.samples.length, 0)
assert.equal(host.listenerCount(), 0)
})
}
for (const { name, sdk, factory } of candidates) {
test(`DASH ${sdk} (${name}): SDK events coalesce without interrupting synchronous selection`, async () => {
const host = eventHost(sdk)