diff --git a/docs/compiled/artplayer-plugin-dash-control.js b/docs/compiled/artplayer-plugin-dash-control.js
index 1c0a0de1f..3925d4075 100644
--- a/docs/compiled/artplayer-plugin-dash-control.js
+++ b/docs/compiled/artplayer-plugin-dash-control.js
@@ -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",''),u=e(r,"dash-audio",''),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.getCurrentRepresentationForType("video");const o=t.getQualityFor("video");return n.find(t=>t.qualityIndex===o)},item:(t,n,o,r)=>e?{value:n,id:t.id,default:!r&&null!=o&&t.id===o.id}:{value:t.qualityIndex,default:!r&&null!=o&&t.qualityIndex===o.qualityIndex},select(n,o){const r="auto"===n.value;t.updateSettings({streaming:{abr:{autoSwitchBitrate:{video:r}}}}),!r&&o()&&(e?t.setRepresentationForTypeById("video",n.id):t.setQualityFor("video",n.value))}}}(e),i=r.levels();if(!o()||!i?.length)return null;const c=n.auto||"Auto",s=n.getName||(t=>`${t.height}p`),l=r.current(i);if(!o())return null;const a=e.getSettings().streaming.abr.autoSwitchBitrate.video;if(!o())return null;const u=!a&&l?s(l):c;if(!o())return null;const f=[];for(const[t,h]of i.entries()){const e=s(h);if(!o())return null;f.push({html:e,...r.item(h,t,l,a)})}const d=t(f).sort((t,e)=>e.value-t.value);return d.push({html:c,value:"auto",default:a}),{html:u,title:n.title||"Quality",selector:d,select:r.select}}(o,e,p);if(!p())return;if(a.update(e,r,p),!p())return;const s=n.audio||{},l=function(e,n,o){const r=e.getTracksFor("audio");if(!o()||!r?.length)return null;const i=n.auto||"Auto",c=n.getName||(t=>t.lang||t.id),s=e.getCurrentTrackFor("audio")||r[0];if(!o())return null;const l=s?c(s):i;if(!o())return null;const a=function(t,e){if(t.includes(e))return e;if(null==e.id&&null==e.index)return;const n=["id","index","lang"],o=t.filter(t=>n.every(n=>null==e[n]||t[n]===e[n]));return 1===o.length?o[0]:void 0}(r,s),u=[];for(const t of r){const e=c(t);if(!o())return null;u.push({html:e,value:t,default:t===a})}const f=t(u);return{html:l,title:n.title||"Audio",selector:f,select:t=>e.setCurrentTrack(t.value)}}(o,s,p);p()&&u.update(s,l,p)}catch(g){if(f()){const t=++l;for(const e of[d.release,()=>h(()=>l===t)])try{e()}catch(y){console.warn("ArtPlayer DASH cleanup failed:",y)}}throw g}}function g(){if(s)return;let t;s=!0,l++;const e=[d.release,h,...f.splice(0).map(([t,e])=>()=>r.off(t,e))];for(const o of e)try{o()}catch(n){t||(t=n)}if(t)throw t}try{const t=[["ready",p],["restart",p],["destroy",g]];for(const e of t){if(s)break;f.push(e),r.on(...e)}}catch(y){try{g()}catch(m){console.warn("ArtPlayer DASH subscription cleanup failed:",m)}throw y}return{name:"artplayerPluginDashControl",update:p}}}});
+!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",''),f=e(i,"dash-audio",''),d=[],h=n({active:t=>!a&&!i.isDestroy&&i.dash===t,refresh:p,reset(){const t=++l;g(()=>t===l)}});function g(t=()=>!0){let e;for(const r of[u.clear,f.clear]){if(!t())break;try{r()}catch(n){e||(e=n)}}if(e)throw e}function p(){if(a||i.isDestroy)return;const e=++l,n=i.dash,o=()=>!a&&!i.isDestroy&&e===l&&i.dash===n,d=()=>o()&&n.getVideoElement()===c&&o();try{if(s(n.getVideoElement()===c,'Cannot find instance of DASH from "art.dash"'),!o())return;if(h.bind(n),!d())return;const e=r.quality||{},i=function(e,n,r){const o=function(t){const e="function"===typeof t.getRepresentationsByType;return{levels:()=>e?t.getRepresentationsByType("video"):t.getBitrateInfoListFor("video"),current(n){if(e)return t.getCurrentRepresentationForType("video");const r=t.getQualityFor("video");return n.find(t=>t.qualityIndex===r)},item:(t,n,r,o)=>e?{value:n,id:t.id,default:!o&&null!=r&&t.id===r.id}:{value:t.qualityIndex,default:!o&&null!=r&&t.qualityIndex===r.qualityIndex},select(n,r){const o="auto"===n.value;t.updateSettings({streaming:{abr:{autoSwitchBitrate:{video:o}}}}),!o&&r()&&(e?t.setRepresentationForTypeById("video",n.id):t.setQualityFor("video",n.value))}}}(e),i=o.levels();if(!r()||!i?.length)return null;const c=n.auto||"Auto",s=n.getName||(t=>`${t.height}p`),a=o.current(i);if(!r())return null;const l=e.getSettings().streaming.abr.autoSwitchBitrate.video;if(!r())return null;const u=!l&&a?s(a):c;if(!r())return null;const f=[];for(const[t,h]of i.entries()){const e=s(h);if(!r())return null;f.push({html:e,...o.item(h,t,a,l)})}const d=t(f).sort((t,e)=>e.value-t.value);return d.push({html:c,value:"auto",default:l}),{html:u,title:n.title||"Quality",selector:d,select:o.select}}(n,e,d);if(!d())return;if(u.update(e,i,d),!d())return;const a=r.audio||{},l=function(e,n,r){const o=e.getTracksFor("audio");if(!r()||!o?.length)return null;const i=n.auto||"Auto",c=n.getName||(t=>t.lang||t.id),s=e.getCurrentTrackFor("audio")||o[0];if(!r())return null;const a=s?c(s):i;if(!r())return null;const l=function(t,e){if(t.includes(e))return e;if(null==e.id&&null==e.index)return;const n=["id","index","lang"],r=t.filter(t=>n.every(n=>null==e[n]||t[n]===e[n]));return 1===r.length?r[0]:void 0}(o,s),u=[];for(const t of o){const e=c(t);if(!r())return null;u.push({html:e,value:t,default:t===l})}const f=t(u);return{html:a,title:n.title||"Audio",selector:f,select:t=>e.setCurrentTrack(t.value)}}(n,a,d);d()&&f.update(a,l,d)}catch(p){if(o()){const t=++l;for(const e of[h.release,()=>g(()=>l===t)])try{e()}catch(y){console.warn("ArtPlayer DASH cleanup failed:",y)}}throw p}}function y(){if(a)return;let t;a=!0,l++;const e=[h.release,g,...d.splice(0).map(([t,e])=>()=>i.off(t,e))];for(const r of e)try{r()}catch(n){t||(t=n)}if(t)throw t}try{const t=[["ready",p],["restart",p],["destroy",y]];for(const e of t){if(a)break;d.push(e),i.on(...e)}}catch(m){try{y()}catch(v){console.warn("ArtPlayer DASH subscription cleanup failed:",v)}throw m}return{name:"artplayerPluginDashControl",update:p}}}});
diff --git a/docs/compiled/artplayer-plugin-dash-control.legacy.js b/docs/compiled/artplayer-plugin-dash-control.legacy.js
index 666ed3af8..e259b2e74 100644
--- a/docs/compiled/artplayer-plugin-dash-control.legacy.js
+++ b/docs/compiled/artplayer-plugin-dash-control.legacy.js
@@ -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",''),f=c(n,"dash-audio",''),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.isDestroy&&e===s&&n.dash===c,y=()=>d()&&c.getVideoElement()===o&&d();try{if(r(c.getVideoElement()===o,'Cannot find instance of DASH from "art.dash"'),!d())return;if(h.bind(c),!y())return;const e=t.quality||{},n=function(t,e,n){const o=function(t){const e="function"===typeof t.getRepresentationsByType;return{levels:()=>e?t.getRepresentationsByType("video"):t.getBitrateInfoListFor("video"),current(n){if(e)return t.getCurrentRepresentationForType("video");const o=t.getQualityFor("video");return n.find(t=>t.qualityIndex===o)},item:(t,n,o,r)=>e?{value:n,id:t.id,default:!r&&null!=o&&t.id===o.id}:{value:t.qualityIndex,default:!r&&null!=o&&t.qualityIndex===o.qualityIndex},select(n,o){const r="auto"===n.value;t.updateSettings({streaming:{abr:{autoSwitchBitrate:{video:r}}}}),!r&&o()&&(e?t.setRepresentationForTypeById("video",n.id):t.setQualityFor("video",n.value))}}}(t),r=o.levels();if(!n()||!(null==r?void 0:r.length))return null;const c=e.auto||"Auto",a=e.getName||(t=>`${t.height}p`),s=o.current(r);if(!n())return null;const u=t.getSettings().streaming.abr.autoSwitchBitrate.video;if(!n())return null;const f=!u&&s?a(s):c;if(!n())return null;const d=[];for(const[l,p]of r.entries()){const t=a(p);if(!n())return null;d.push(i({html:t},o.item(p,l,s,u)))}const h=l(d).sort((t,e)=>e.value-t.value);return h.push({html:c,value:"auto",default:u}),{html:f,title:e.title||"Quality",selector:h,select:o.select}}(c,e,y);if(!y())return;if(u.update(e,n,y),!y())return;const a=t.audio||{},s=function(t,e,n){const o=t.getTracksFor("audio");if(!n()||!(null==o?void 0:o.length))return null;const r=e.auto||"Auto",i=e.getName||(t=>t.lang||t.id),c=t.getCurrentTrackFor("audio")||o[0];if(!n())return null;const a=c?i(c):r;if(!n())return null;const s=function(t,e){if(t.includes(e))return e;if(null==e.id&&null==e.index)return;const n=["id","index","lang"],o=t.filter(t=>n.every(n=>null==e[n]||t[n]===e[n]));return 1===o.length?o[0]:void 0}(o,c),u=[];for(const l of o){const t=i(l);if(!n())return null;u.push({html:t,value:l,default:l===s})}const f=l(u);return{html:a,title:e.title||"Audio",selector:f,select:e=>t.setCurrentTrack(e.value)}}(c,a,y);y()&&f.update(a,s,y)}catch(g){if(d()){const t=++s;for(const e of[h.release,()=>p(()=>s===t)])try{e()}catch(m){console.warn("ArtPlayer DASH cleanup failed:",m)}}throw g}}function g(){if(a)return;let t;a=!0,s++;const e=[h.release,p,...d.splice(0).map(([t,e])=>()=>n.off(t,e))];for(const n of e)try{n()}catch(o){t||(t=o)}if(t)throw t}try{const t=[["ready",y],["restart",y],["destroy",g]];for(const e of t){if(a)break;d.push(e),n.on(...e)}}catch(m){try{g()}catch(v){console.warn("ArtPlayer DASH subscription cleanup failed:",v)}throw m}return{name:"artplayerPluginDashControl",update:y}}}});
+!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){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{o()}catch(d){console.warn("ArtPlayer DASH subscription cleanup failed:",d)}throw f}},release:o}}return function(t={}){return e=>{const n=e,{$video:r}=n.template,{errorHandle:o}=n.constructor.utils;let s=!1,u=0;const f=c(n,"dash-quality",''),d=c(n,"dash-audio",''),h=[],g=a({active:t=>!s&&!n.isDestroy&&n.dash===t,refresh:y,reset(){const t=++u;p(()=>t===u)}});function p(t=()=>!0){let e;for(const r of[f.clear,d.clear]){if(!t())break;try{r()}catch(n){e||(e=n)}}if(e)throw e}function y(){if(s||n.isDestroy)return;const e=++u,c=n.dash,a=()=>!s&&!n.isDestroy&&e===u&&n.dash===c,h=()=>a()&&c.getVideoElement()===r&&a();try{if(o(c.getVideoElement()===r,'Cannot find instance of DASH from "art.dash"'),!a())return;if(g.bind(c),!h())return;const e=t.quality||{},n=function(t,e,n){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.getCurrentRepresentationForType("video");const r=t.getQualityFor("video");return n.find(t=>t.qualityIndex===r)},item:(t,n,r,o)=>e?{value:n,id:t.id,default:!o&&null!=r&&t.id===r.id}:{value:t.qualityIndex,default:!o&&null!=r&&t.qualityIndex===r.qualityIndex},select(n,r){const o="auto"===n.value;t.updateSettings({streaming:{abr:{autoSwitchBitrate:{video:o}}}}),!o&&r()&&(e?t.setRepresentationForTypeById("video",n.id):t.setQualityFor("video",n.value))}}}(t),o=r.levels();if(!n()||!(null==o?void 0:o.length))return null;const c=e.auto||"Auto",a=e.getName||(t=>`${t.height}p`),s=r.current(o);if(!n())return null;const u=t.getSettings().streaming.abr.autoSwitchBitrate.video;if(!n())return null;const f=!u&&s?a(s):c;if(!n())return null;const d=[];for(const[l,g]of o.entries()){const t=a(g);if(!n())return null;d.push(i({html:t},r.item(g,l,s,u)))}const h=l(d).sort((t,e)=>e.value-t.value);return h.push({html:c,value:"auto",default:u}),{html:f,title:e.title||"Quality",selector:h,select:r.select}}(c,e,h);if(!h())return;if(f.update(e,n,h),!h())return;const s=t.audio||{},u=function(t,e,n){const r=t.getTracksFor("audio");if(!n()||!(null==r?void 0:r.length))return null;const o=e.auto||"Auto",i=e.getName||(t=>t.lang||t.id),c=t.getCurrentTrackFor("audio")||r[0];if(!n())return null;const a=c?i(c):o;if(!n())return null;const s=function(t,e){if(t.includes(e))return e;if(null==e.id&&null==e.index)return;const n=["id","index","lang"],r=t.filter(t=>n.every(n=>null==e[n]||t[n]===e[n]));return 1===r.length?r[0]:void 0}(r,c),u=[];for(const l of r){const t=i(l);if(!n())return null;u.push({html:t,value:l,default:l===s})}const f=l(u);return{html:a,title:e.title||"Audio",selector:f,select:e=>t.setCurrentTrack(e.value)}}(c,s,h);h()&&d.update(s,u,h)}catch(y){if(a()){const t=++u;for(const e of[g.release,()=>p(()=>u===t)])try{e()}catch(v){console.warn("ArtPlayer DASH cleanup failed:",v)}}throw y}}function v(){if(s)return;let t;s=!0,u++;const e=[g.release,p,...h.splice(0).map(([t,e])=>()=>n.off(t,e))];for(const n of e)try{n()}catch(r){t||(t=r)}if(t)throw t}try{const t=[["ready",y],["restart",y],["destroy",v]];for(const e of t){if(s)break;h.push(e),n.on(...e)}}catch(m){try{v()}catch(w){console.warn("ArtPlayer DASH subscription cleanup failed:",w)}throw m}return{name:"artplayerPluginDashControl",update:y}}}});
diff --git a/docs/compiled/artplayer-plugin-dash-control.mjs b/docs/compiled/artplayer-plugin-dash-control.mjs
index ad928bc30..90b9a2295 100644
--- a/docs/compiled/artplayer-plugin-dash-control.mjs
+++ b/docs/compiled/artplayer-plugin-dash-control.mjs
@@ -170,6 +170,59 @@ function createMenu(art, name, icon) {
return { update, clear };
}
const $quality = '';
+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;
diff --git a/packages/artplayer-plugin-dash-control/ARCHITECTURE.md b/packages/artplayer-plugin-dash-control/ARCHITECTURE.md
index 876e12ce5..30911cfab 100644
--- a/packages/artplayer-plugin-dash-control/ARCHITECTURE.md
+++ b/packages/artplayer-plugin-dash-control/ARCHITECTURE.md
@@ -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.
diff --git a/packages/artplayer-plugin-dash-control/README.md b/packages/artplayer-plugin-dash-control/README.md
index 36fc57bb4..a55858402 100644
--- a/packages/artplayer-plugin-dash-control/README.md
+++ b/packages/artplayer-plugin-dash-control/README.md
@@ -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:
diff --git a/packages/artplayer-plugin-dash-control/dist/artplayer-plugin-dash-control.js b/packages/artplayer-plugin-dash-control/dist/artplayer-plugin-dash-control.js
index 1c0a0de1f..3925d4075 100644
--- a/packages/artplayer-plugin-dash-control/dist/artplayer-plugin-dash-control.js
+++ b/packages/artplayer-plugin-dash-control/dist/artplayer-plugin-dash-control.js
@@ -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",''),u=e(r,"dash-audio",''),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.getCurrentRepresentationForType("video");const o=t.getQualityFor("video");return n.find(t=>t.qualityIndex===o)},item:(t,n,o,r)=>e?{value:n,id:t.id,default:!r&&null!=o&&t.id===o.id}:{value:t.qualityIndex,default:!r&&null!=o&&t.qualityIndex===o.qualityIndex},select(n,o){const r="auto"===n.value;t.updateSettings({streaming:{abr:{autoSwitchBitrate:{video:r}}}}),!r&&o()&&(e?t.setRepresentationForTypeById("video",n.id):t.setQualityFor("video",n.value))}}}(e),i=r.levels();if(!o()||!i?.length)return null;const c=n.auto||"Auto",s=n.getName||(t=>`${t.height}p`),l=r.current(i);if(!o())return null;const a=e.getSettings().streaming.abr.autoSwitchBitrate.video;if(!o())return null;const u=!a&&l?s(l):c;if(!o())return null;const f=[];for(const[t,h]of i.entries()){const e=s(h);if(!o())return null;f.push({html:e,...r.item(h,t,l,a)})}const d=t(f).sort((t,e)=>e.value-t.value);return d.push({html:c,value:"auto",default:a}),{html:u,title:n.title||"Quality",selector:d,select:r.select}}(o,e,p);if(!p())return;if(a.update(e,r,p),!p())return;const s=n.audio||{},l=function(e,n,o){const r=e.getTracksFor("audio");if(!o()||!r?.length)return null;const i=n.auto||"Auto",c=n.getName||(t=>t.lang||t.id),s=e.getCurrentTrackFor("audio")||r[0];if(!o())return null;const l=s?c(s):i;if(!o())return null;const a=function(t,e){if(t.includes(e))return e;if(null==e.id&&null==e.index)return;const n=["id","index","lang"],o=t.filter(t=>n.every(n=>null==e[n]||t[n]===e[n]));return 1===o.length?o[0]:void 0}(r,s),u=[];for(const t of r){const e=c(t);if(!o())return null;u.push({html:e,value:t,default:t===a})}const f=t(u);return{html:l,title:n.title||"Audio",selector:f,select:t=>e.setCurrentTrack(t.value)}}(o,s,p);p()&&u.update(s,l,p)}catch(g){if(f()){const t=++l;for(const e of[d.release,()=>h(()=>l===t)])try{e()}catch(y){console.warn("ArtPlayer DASH cleanup failed:",y)}}throw g}}function g(){if(s)return;let t;s=!0,l++;const e=[d.release,h,...f.splice(0).map(([t,e])=>()=>r.off(t,e))];for(const o of e)try{o()}catch(n){t||(t=n)}if(t)throw t}try{const t=[["ready",p],["restart",p],["destroy",g]];for(const e of t){if(s)break;f.push(e),r.on(...e)}}catch(y){try{g()}catch(m){console.warn("ArtPlayer DASH subscription cleanup failed:",m)}throw y}return{name:"artplayerPluginDashControl",update:p}}}});
+!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",''),f=e(i,"dash-audio",''),d=[],h=n({active:t=>!a&&!i.isDestroy&&i.dash===t,refresh:p,reset(){const t=++l;g(()=>t===l)}});function g(t=()=>!0){let e;for(const r of[u.clear,f.clear]){if(!t())break;try{r()}catch(n){e||(e=n)}}if(e)throw e}function p(){if(a||i.isDestroy)return;const e=++l,n=i.dash,o=()=>!a&&!i.isDestroy&&e===l&&i.dash===n,d=()=>o()&&n.getVideoElement()===c&&o();try{if(s(n.getVideoElement()===c,'Cannot find instance of DASH from "art.dash"'),!o())return;if(h.bind(n),!d())return;const e=r.quality||{},i=function(e,n,r){const o=function(t){const e="function"===typeof t.getRepresentationsByType;return{levels:()=>e?t.getRepresentationsByType("video"):t.getBitrateInfoListFor("video"),current(n){if(e)return t.getCurrentRepresentationForType("video");const r=t.getQualityFor("video");return n.find(t=>t.qualityIndex===r)},item:(t,n,r,o)=>e?{value:n,id:t.id,default:!o&&null!=r&&t.id===r.id}:{value:t.qualityIndex,default:!o&&null!=r&&t.qualityIndex===r.qualityIndex},select(n,r){const o="auto"===n.value;t.updateSettings({streaming:{abr:{autoSwitchBitrate:{video:o}}}}),!o&&r()&&(e?t.setRepresentationForTypeById("video",n.id):t.setQualityFor("video",n.value))}}}(e),i=o.levels();if(!r()||!i?.length)return null;const c=n.auto||"Auto",s=n.getName||(t=>`${t.height}p`),a=o.current(i);if(!r())return null;const l=e.getSettings().streaming.abr.autoSwitchBitrate.video;if(!r())return null;const u=!l&&a?s(a):c;if(!r())return null;const f=[];for(const[t,h]of i.entries()){const e=s(h);if(!r())return null;f.push({html:e,...o.item(h,t,a,l)})}const d=t(f).sort((t,e)=>e.value-t.value);return d.push({html:c,value:"auto",default:l}),{html:u,title:n.title||"Quality",selector:d,select:o.select}}(n,e,d);if(!d())return;if(u.update(e,i,d),!d())return;const a=r.audio||{},l=function(e,n,r){const o=e.getTracksFor("audio");if(!r()||!o?.length)return null;const i=n.auto||"Auto",c=n.getName||(t=>t.lang||t.id),s=e.getCurrentTrackFor("audio")||o[0];if(!r())return null;const a=s?c(s):i;if(!r())return null;const l=function(t,e){if(t.includes(e))return e;if(null==e.id&&null==e.index)return;const n=["id","index","lang"],r=t.filter(t=>n.every(n=>null==e[n]||t[n]===e[n]));return 1===r.length?r[0]:void 0}(o,s),u=[];for(const t of o){const e=c(t);if(!r())return null;u.push({html:e,value:t,default:t===l})}const f=t(u);return{html:a,title:n.title||"Audio",selector:f,select:t=>e.setCurrentTrack(t.value)}}(n,a,d);d()&&f.update(a,l,d)}catch(p){if(o()){const t=++l;for(const e of[h.release,()=>g(()=>l===t)])try{e()}catch(y){console.warn("ArtPlayer DASH cleanup failed:",y)}}throw p}}function y(){if(a)return;let t;a=!0,l++;const e=[h.release,g,...d.splice(0).map(([t,e])=>()=>i.off(t,e))];for(const r of e)try{r()}catch(n){t||(t=n)}if(t)throw t}try{const t=[["ready",p],["restart",p],["destroy",y]];for(const e of t){if(a)break;d.push(e),i.on(...e)}}catch(m){try{y()}catch(v){console.warn("ArtPlayer DASH subscription cleanup failed:",v)}throw m}return{name:"artplayerPluginDashControl",update:p}}}});
diff --git a/packages/artplayer-plugin-dash-control/dist/artplayer-plugin-dash-control.legacy.js b/packages/artplayer-plugin-dash-control/dist/artplayer-plugin-dash-control.legacy.js
index 666ed3af8..e259b2e74 100644
--- a/packages/artplayer-plugin-dash-control/dist/artplayer-plugin-dash-control.legacy.js
+++ b/packages/artplayer-plugin-dash-control/dist/artplayer-plugin-dash-control.legacy.js
@@ -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",''),f=c(n,"dash-audio",''),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.isDestroy&&e===s&&n.dash===c,y=()=>d()&&c.getVideoElement()===o&&d();try{if(r(c.getVideoElement()===o,'Cannot find instance of DASH from "art.dash"'),!d())return;if(h.bind(c),!y())return;const e=t.quality||{},n=function(t,e,n){const o=function(t){const e="function"===typeof t.getRepresentationsByType;return{levels:()=>e?t.getRepresentationsByType("video"):t.getBitrateInfoListFor("video"),current(n){if(e)return t.getCurrentRepresentationForType("video");const o=t.getQualityFor("video");return n.find(t=>t.qualityIndex===o)},item:(t,n,o,r)=>e?{value:n,id:t.id,default:!r&&null!=o&&t.id===o.id}:{value:t.qualityIndex,default:!r&&null!=o&&t.qualityIndex===o.qualityIndex},select(n,o){const r="auto"===n.value;t.updateSettings({streaming:{abr:{autoSwitchBitrate:{video:r}}}}),!r&&o()&&(e?t.setRepresentationForTypeById("video",n.id):t.setQualityFor("video",n.value))}}}(t),r=o.levels();if(!n()||!(null==r?void 0:r.length))return null;const c=e.auto||"Auto",a=e.getName||(t=>`${t.height}p`),s=o.current(r);if(!n())return null;const u=t.getSettings().streaming.abr.autoSwitchBitrate.video;if(!n())return null;const f=!u&&s?a(s):c;if(!n())return null;const d=[];for(const[l,p]of r.entries()){const t=a(p);if(!n())return null;d.push(i({html:t},o.item(p,l,s,u)))}const h=l(d).sort((t,e)=>e.value-t.value);return h.push({html:c,value:"auto",default:u}),{html:f,title:e.title||"Quality",selector:h,select:o.select}}(c,e,y);if(!y())return;if(u.update(e,n,y),!y())return;const a=t.audio||{},s=function(t,e,n){const o=t.getTracksFor("audio");if(!n()||!(null==o?void 0:o.length))return null;const r=e.auto||"Auto",i=e.getName||(t=>t.lang||t.id),c=t.getCurrentTrackFor("audio")||o[0];if(!n())return null;const a=c?i(c):r;if(!n())return null;const s=function(t,e){if(t.includes(e))return e;if(null==e.id&&null==e.index)return;const n=["id","index","lang"],o=t.filter(t=>n.every(n=>null==e[n]||t[n]===e[n]));return 1===o.length?o[0]:void 0}(o,c),u=[];for(const l of o){const t=i(l);if(!n())return null;u.push({html:t,value:l,default:l===s})}const f=l(u);return{html:a,title:e.title||"Audio",selector:f,select:e=>t.setCurrentTrack(e.value)}}(c,a,y);y()&&f.update(a,s,y)}catch(g){if(d()){const t=++s;for(const e of[h.release,()=>p(()=>s===t)])try{e()}catch(m){console.warn("ArtPlayer DASH cleanup failed:",m)}}throw g}}function g(){if(a)return;let t;a=!0,s++;const e=[h.release,p,...d.splice(0).map(([t,e])=>()=>n.off(t,e))];for(const n of e)try{n()}catch(o){t||(t=o)}if(t)throw t}try{const t=[["ready",y],["restart",y],["destroy",g]];for(const e of t){if(a)break;d.push(e),n.on(...e)}}catch(m){try{g()}catch(v){console.warn("ArtPlayer DASH subscription cleanup failed:",v)}throw m}return{name:"artplayerPluginDashControl",update:y}}}});
+!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){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{o()}catch(d){console.warn("ArtPlayer DASH subscription cleanup failed:",d)}throw f}},release:o}}return function(t={}){return e=>{const n=e,{$video:r}=n.template,{errorHandle:o}=n.constructor.utils;let s=!1,u=0;const f=c(n,"dash-quality",''),d=c(n,"dash-audio",''),h=[],g=a({active:t=>!s&&!n.isDestroy&&n.dash===t,refresh:y,reset(){const t=++u;p(()=>t===u)}});function p(t=()=>!0){let e;for(const r of[f.clear,d.clear]){if(!t())break;try{r()}catch(n){e||(e=n)}}if(e)throw e}function y(){if(s||n.isDestroy)return;const e=++u,c=n.dash,a=()=>!s&&!n.isDestroy&&e===u&&n.dash===c,h=()=>a()&&c.getVideoElement()===r&&a();try{if(o(c.getVideoElement()===r,'Cannot find instance of DASH from "art.dash"'),!a())return;if(g.bind(c),!h())return;const e=t.quality||{},n=function(t,e,n){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.getCurrentRepresentationForType("video");const r=t.getQualityFor("video");return n.find(t=>t.qualityIndex===r)},item:(t,n,r,o)=>e?{value:n,id:t.id,default:!o&&null!=r&&t.id===r.id}:{value:t.qualityIndex,default:!o&&null!=r&&t.qualityIndex===r.qualityIndex},select(n,r){const o="auto"===n.value;t.updateSettings({streaming:{abr:{autoSwitchBitrate:{video:o}}}}),!o&&r()&&(e?t.setRepresentationForTypeById("video",n.id):t.setQualityFor("video",n.value))}}}(t),o=r.levels();if(!n()||!(null==o?void 0:o.length))return null;const c=e.auto||"Auto",a=e.getName||(t=>`${t.height}p`),s=r.current(o);if(!n())return null;const u=t.getSettings().streaming.abr.autoSwitchBitrate.video;if(!n())return null;const f=!u&&s?a(s):c;if(!n())return null;const d=[];for(const[l,g]of o.entries()){const t=a(g);if(!n())return null;d.push(i({html:t},r.item(g,l,s,u)))}const h=l(d).sort((t,e)=>e.value-t.value);return h.push({html:c,value:"auto",default:u}),{html:f,title:e.title||"Quality",selector:h,select:r.select}}(c,e,h);if(!h())return;if(f.update(e,n,h),!h())return;const s=t.audio||{},u=function(t,e,n){const r=t.getTracksFor("audio");if(!n()||!(null==r?void 0:r.length))return null;const o=e.auto||"Auto",i=e.getName||(t=>t.lang||t.id),c=t.getCurrentTrackFor("audio")||r[0];if(!n())return null;const a=c?i(c):o;if(!n())return null;const s=function(t,e){if(t.includes(e))return e;if(null==e.id&&null==e.index)return;const n=["id","index","lang"],r=t.filter(t=>n.every(n=>null==e[n]||t[n]===e[n]));return 1===r.length?r[0]:void 0}(r,c),u=[];for(const l of r){const t=i(l);if(!n())return null;u.push({html:t,value:l,default:l===s})}const f=l(u);return{html:a,title:e.title||"Audio",selector:f,select:e=>t.setCurrentTrack(e.value)}}(c,s,h);h()&&d.update(s,u,h)}catch(y){if(a()){const t=++u;for(const e of[g.release,()=>p(()=>u===t)])try{e()}catch(v){console.warn("ArtPlayer DASH cleanup failed:",v)}}throw y}}function v(){if(s)return;let t;s=!0,u++;const e=[g.release,p,...h.splice(0).map(([t,e])=>()=>n.off(t,e))];for(const n of e)try{n()}catch(r){t||(t=r)}if(t)throw t}try{const t=[["ready",y],["restart",y],["destroy",v]];for(const e of t){if(s)break;h.push(e),n.on(...e)}}catch(m){try{v()}catch(w){console.warn("ArtPlayer DASH subscription cleanup failed:",w)}throw m}return{name:"artplayerPluginDashControl",update:y}}}});
diff --git a/packages/artplayer-plugin-dash-control/dist/artplayer-plugin-dash-control.mjs b/packages/artplayer-plugin-dash-control/dist/artplayer-plugin-dash-control.mjs
index ad928bc30..90b9a2295 100644
--- a/packages/artplayer-plugin-dash-control/dist/artplayer-plugin-dash-control.mjs
+++ b/packages/artplayer-plugin-dash-control/dist/artplayer-plugin-dash-control.mjs
@@ -170,6 +170,59 @@ function createMenu(art, name, icon) {
return { update, clear };
}
const $quality = '';
+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;
diff --git a/packages/artplayer-plugin-dash-control/src/sdk-events.ts b/packages/artplayer-plugin-dash-control/src/sdk-events.ts
index ec0f936a0..64ccc562f 100644
--- a/packages/artplayer-plugin-dash-control/src/sdk-events.ts
+++ b/packages/artplayer-plugin-dash-control/src/sdk-events.ts
@@ -1,4 +1,5 @@
import type { Cleanup, Dash, SDKEventName } from './types'
+import { createSeekRecovery } from './seek-buffer'
interface Binding {
dash: Dash
@@ -108,6 +109,9 @@ export 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
diff --git a/packages/artplayer-plugin-dash-control/src/seek-buffer.ts b/packages/artplayer-plugin-dash-control/src/seek-buffer.ts
new file mode 100644
index 000000000..5bdead44d
--- /dev/null
+++ b/packages/artplayer-plugin-dash-control/src/seek-buffer.ts
@@ -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
+ }
+ }
+}
diff --git a/packages/artplayer-plugin-dash-control/src/types.ts b/packages/artplayer-plugin-dash-control/src/types.ts
index 7765cd0c7..af69ef94a 100644
--- a/packages/artplayer-plugin-dash-control/src/types.ts
+++ b/packages/artplayer-plugin-dash-control/src/types.ts
@@ -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
diff --git a/refactor/baselines/dash-seek-recovery-validation.json b/refactor/baselines/dash-seek-recovery-validation.json
new file mode 100644
index 000000000..22448de40
--- /dev/null
+++ b/refactor/baselines/dash-seek-recovery-validation.json
@@ -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 180p
\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"
+ ]
+}
diff --git a/refactor/changes/2026-09-14-PKG-DASH-SEEK-01-buffer-metrics.md b/refactor/changes/2026-09-14-PKG-DASH-SEEK-01-buffer-metrics.md
new file mode 100644
index 000000000..cda9c9286
--- /dev/null
+++ b/refactor/changes/2026-09-14-PKG-DASH-SEEK-01-buffer-metrics.md
@@ -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 发布。
diff --git a/refactor/dash-validation.md b/refactor/dash-validation.md
index 86fa40d76..b7522f608 100644
--- a/refactor/dash-validation.md
+++ b/refactor/dash-validation.md
@@ -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组合/分发验收全部完成。
diff --git a/refactor/plan.md b/refactor/plan.md
index 67e1a459c..0b8194a18 100644
--- a/refactor/plan.md
+++ b/refactor/plan.md
@@ -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
建立特有行为与错误测试 | PKG-DASH-01, ENG-03, ENG-05 | 保留已有 5 项稳定 ID 回归,补发布版 SDK 4.x/当前 5.x、音轨/空列表/过滤/换源与关闭引用 | 旧版本行为可重跑,成功/失败/切源/销毁有必要断言 | H | done |
| PKG-DASH-03 | artplayer-plugin-dash-control
整理内部职责与资源 | 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
迁移自有源码和公开类型 | PKG-DASH-03, ENG-04, ENG-06, CORE-07 | dash.js adapter、selector 和回调类型 | 严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容 | H | done |
-| PKG-DASH-05 | artplayer-plugin-dash-control
验证新旧核心和组合 | 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
验证分发并同步文档 | PKG-DASH-05, ENG-07 | dash.control.js、支持的 dash.js 版本与产物 | tarball 入口/资源、类型、8082 demo 和 README 一致,有回退记录 | H | todo |
+| PKG-DASH-SEEK-01 | artplayer-plugin-dash-control
修复 SDK 4.5.2 暂停跳转后的缓冲指标停滞 | PKG-DASH-04 | 按版本和实测空缓冲限定的指标同步、生命周期守卫及真实 SDK 旧红新绿 | 新插件在新旧核心及 Chromium/Firefox 原位置回归通过;不修改 SDK 方法/配置、媒体时间或删除缓冲;旧 SDK 原生缺陷与旧插件限制独立记录,不宣称第三方本体已修复 | H | done |
+| PKG-DASH-MENU-01 | artplayer-plugin-dash-control
定位并修复 SDK 刷新期间设置选项不可见 | PKG-DASH-SEEK-01 | 真实 SDK 菜单点击失败根因、对应兼容修复及新旧核心回归 | 保留 legacy/旧核心/SDK5.2.1 的原失败;确定菜单在点击过程中不可见的原因;不靠 force 点击、扩大超时或重复运行覆盖问题;main/legacy 质量与音轨设置及 SDK 刷新组合通过 | H | todo |
+| PKG-DASH-05 | artplayer-plugin-dash-control
验证新旧核心和组合 | 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
验证分发并同步文档 | 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)
diff --git a/refactor/progress.md b/refactor/progress.md
index e4c0d7576..35d2deaac 100644
--- a/refactor/progress.md
+++ b/refactor/progress.md
@@ -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运行
diff --git a/refactor/risk-table.md b/refactor/risk-table.md
index 479d4512f..0767afef7 100644
--- a/refactor/risk-table.md
+++ b/refactor/risk-table.md
@@ -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 |
diff --git a/refactor/risks.json b/refactor/risks.json
index b90e066b4..3f8bfa7d4 100644
--- a/refactor/risks.json
+++ b/refactor/risks.json
@@ -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点击或重跑覆盖失败。"
}
]
}
diff --git a/refactor/tasks.json b/refactor/tasks.json
index c01e0cdd2..5d23dfa51 100644
--- a/refactor/tasks.json
+++ b/refactor/tasks.json
@@ -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",
diff --git a/test/browser/dash-sdk.spec.js b/test/browser/dash-sdk.spec.js
index 73cc544f1..b1229479e 100644
--- a/test/browser/dash-sdk.spec.js
+++ b/test/browser/dash-sdk.spec.js
@@ -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)
diff --git a/test/browser/dash-seek-diagnostics.js b/test/browser/dash-seek-diagnostics.js
new file mode 100644
index 000000000..4d80ce71f
--- /dev/null
+++ b/test/browser/dash-seek-diagnostics.js
@@ -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()) }
+}
diff --git a/test/dash-events.test.js b/test/dash-events.test.js
index 80f5ffbdd..d432d94b8 100644
--- a/test/dash-events.test.js
+++ b/test/dash-events.test.js
@@ -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)