fix(dash): [PKG-DASH-MENU-01] preserve settings navigation during SDK refresh

This commit is contained in:
Harvey Zhao committed 2026-09-14 20:35:22 +08:00
1 parent 30f646be97
commit 88ce836f85
21 files changed
+769 -23

No files matched your search

@@ -4,4 +4,4 @@
* (c) 2017-2026 Harvey Zhao
* Released under the MIT License.
*/
!function(t,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"==typeof define&&define.amd?(t.artplayerPluginDashControl=e(),define(function(){return t.artplayerPluginDashControl})):(t="undefined"!==typeof globalThis?globalThis:t||self).artplayerPluginDashControl=e()}(this,function(){"use strict";function t(t){const e=new Map;return t.filter(t=>{if(void 0===t.html)return!0;const n=e.get(t.html);return n?(t.default&&Object.assign(n,t),!1):(e.set(t.html,t),!0)})}function e(t,e,n){let r;const o=new Map;function i(n,r=o.get(n)){if(!r||o.get(n)!==r)return;o.delete(n);const i=t[n];"controls"===n&&i.cache?.get&&i.cache.get(e)?.option?.onSelect!==r||"setting"===n&&i.find&&i.find(e)?.onSelect!==r||i.remove(e)}function c(){let t;r=void 0;for(const[n,r]of[...o])try{i(n,r)}catch(e){t||(t=e)}if(t)throw t}return{update:function(s,a,l){if(!a)return void c();const u={};r=u;const f=()=>u===r&&l(),d=e=>f()?(a.select(e,f),f()?(t.notice.show=`${a.title}: ${e.html}`,f()&&s.control&&t.controls.check(e),f()&&s.setting&&t.setting.check(e),e.html):e.html):e.html;s.control?(o.set("controls",d),t.controls.update({name:e,position:"right",html:a.html,style:{padding:"0 10px"},selector:a.selector,onSelect:d})):i("controls"),f()&&(s.setting?(o.set("setting",d),t.setting.update({name:e,tooltip:a.html,html:a.title,icon:n,width:200,selector:a.selector,onSelect:d})):i("setting"))},clear:c}}function n(t){let e;const n=n=>e===n&&t.active(n.dash),r=t=>t.dash.getSettings().streaming.abr.autoSwitchBitrate.video;function o(){const t=e;if(!t)return;let n;e=void 0,t.epoch++;for(const[e,o]of t.callbacks.splice(0))try{t.off.call(t.dash,e,o)}catch(r){n||(n=r)}if(n)throw n}function i(n,r){if(e===n){try{o()}catch(i){console.warn("ArtPlayer DASH subscription cleanup failed:",i)}if(!e&&t.active(n.dash))try{t.reset()}catch(i){console.warn("ArtPlayer DASH cleanup failed:",i)}}console.warn("ArtPlayer DASH refresh failed:",r)}function c(e){if(!n(e)||e.suspended||e.queued||e.running)return;e.queued=!0;const o=e.epoch;Promise.resolve().then(()=>{if(n(e)&&e.epoch===o&&!e.suspended){e.queued=!1,e.running=!0;try{e.automatic=r(e),n(e)&&t.refresh()}catch(c){i(e,c)}finally{e.running=!1}}})}return{bind:function(s){const a=e;if(a?.dash===s){const t=r(a);return void(n(a)&&(a.automatic=t,a.suspended=!1))}if(o(),e||!t.active(s)||"function"!==typeof s.on||"function"!==typeof s.off)return;const l={dash:s,off:s.off,callbacks:[],epoch:0,queued:!1,running:!1,suspended:!1,automatic:!1};e=l;const u=s.on;try{l.automatic=r(l);const e=["qualityChangeRequested","qualityChangeRendered","trackChangeRendered","streamUpdated","streamInitialized"].map(t=>[t,()=>{"streamUpdated"!==t&&"streamInitialized"!==t||(l.suspended=!1),c(l)}]),o=function(t,e){const n=t;try{if("function"!==typeof n.getVersion||"4.5.2"!==n.getVersion())return;if("function"!==typeof n.getVideoElement||"function"!==typeof n.getActiveStream||"function"!==typeof n.getDashMetrics)return}catch{return}let r=!1;return()=>{if(!r&&e()){r=!0;try{const t=n.getVideoElement?.();if(!t?.seeking||!Number.isFinite(t.currentTime))return;const r=t.currentTime,o=n.getActiveStream?.(),i=n.getDashMetrics?.(),c=o?.getProcessors?.();if(!Array.isArray(c)||"function"!==typeof i?.getCurrentBufferLevel||"function"!==typeof i.addBufferLevel)return;for(const s of c){if(!e())return;const c=s.getType?.();if("audio"!==c&&"video"!==c)continue;const a=s.getBufferController?.();if("function"!==typeof a?.getRangeAt||"function"!==typeof a.getIsPruningInProgress||"function"!==typeof a.getAllRangesWithSafetyFactor)continue;const l=i.getCurrentBufferLevel(c),u=a.getRangeAt(r),f=null===u||u&&Number.isFinite(u.end)&&u.end<=r;if(!Number.isFinite(l)||l<=0||!f||!1!==a.getIsPruningInProgress())continue;const d=a.getAllRangesWithSafetyFactor(r);if(Array.isArray(d)&&0===d.length){if(!e()||n.getActiveStream?.()!==o||n.getDashMetrics?.()!==i||n.getVideoElement?.()!==t||t.currentTime!==r||!t.seeking||!e())return;i.addBufferLevel(c,new Date,0)}}}catch(t){console.warn("ArtPlayer DASH seek buffer refresh failed:",t)}finally{r=!1}}}}(s,()=>n(l)&&!l.suspended);o&&e.push(["playbackSeeking",o]),e.push(["playbackTimeUpdated",()=>{if(n(l)&&!l.suspended)try{const t=r(l);n(l)&&t!==l.automatic&&(l.automatic=t,c(l))}catch(t){i(l,t)}}]),e.push(["streamTeardownComplete",()=>{if(n(l)){l.epoch++,l.queued=!1,l.suspended=!0;try{t.reset()}catch(e){i(l,e)}}}]);for(const t of e){if(!n(l))break;l.callbacks.push(t),u.call(s,...t)}}catch(f){if(e===l)try{o()}catch(d){console.warn("ArtPlayer DASH subscription cleanup failed:",d)}throw f}},release:o}}return function(r={}){return o=>{const i=o,{$video:c}=i.template,{errorHandle:s}=i.constructor.utils;let a=!1,l=0;const u=e(i,"dash-quality",'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" height="18"><path fill="#fff" d="M0 96C0 60.7 28.7 32 64 32l384 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 480c-35.3 0-64-28.7-64-64L0 96zM323.8 202.5c-4.5-6.6-11.9-10.5-19.8-10.5s-15.4 3.9-19.8 10.5l-87 12Line truncated
!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,captureNavigation:function(n){const r=t.setting,i=r.find?.(e),c=o.get("setting");if(n()&&c&&!0===r.show&&i?.onSelect===c&&i.selector&&r.active===i.selector&&"function"===typeof r.render)return()=>{if(!n()||t.setting!==r||!0!==r.show||!r.option||r.active!==r.option)return;const i=r.find?.(e),c=o.get("setting");n()&&c&&i?.onSelect===c&&i.selector&&r.render?.(i.selector)}}}}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,{$viLine truncated
@@ -4,4 +4,4 @@
* (c) 2017-2026 Harvey Zhao
* Released under the MIT License.
*/
!function(t,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"==typeof define&&define.amd?(t.artplayerPluginDashControl=e(),define(function(){return t.artplayerPluginDashControl})):(t="undefined"!==typeof globalThis?globalThis:t||self).artplayerPluginDashControl=e()}(this,function(){"use strict";var t=Object.defineProperty,e=Object.getOwnPropertySymbols,n=Object.prototype.hasOwnProperty,r=Object.prototype.propertyIsEnumerable,o=(e,n,r)=>n in e?t(e,n,{enumerable:!0,configurable:!0,writable:!0,value:r}):e[n]=r,i=(t,i)=>{for(var l in i||(i={}))n.call(i,l)&&o(t,l,i[l]);if(e)for(var l of e(i))r.call(i,l)&&o(t,l,i[l]);return t};function l(t){const e=new Map;return t.filter(t=>{if(void 0===t.html)return!0;const n=e.get(t.html);return n?(t.default&&Object.assign(n,t),!1):(e.set(t.html,t),!0)})}function c(t,e,n){let r;const o=new Map;function i(n,r=o.get(n)){var i,l,c,a;if(!r||o.get(n)!==r)return;o.delete(n);const s=t[n];"controls"===n&&(null==(i=s.cache)?void 0:i.get)&&(null==(c=null==(l=s.cache.get(e))?void 0:l.option)?void 0:c.onSelect)!==r||"setting"===n&&s.find&&(null==(a=s.find(e))?void 0:a.onSelect)!==r||s.remove(e)}function l(){let t;r=void 0;for(const[n,r]of[...o])try{i(n,r)}catch(e){t||(t=e)}if(t)throw t}return{update:function(c,a,s){if(!a)return void l();const u={};r=u;const f=()=>u===r&&s(),d=e=>f()?(a.select(e,f),f()?(t.notice.show=`${a.title}: ${e.html}`,f()&&c.control&&t.controls.check(e),f()&&c.setting&&t.setting.check(e),e.html):e.html):e.html;c.control?(o.set("controls",d),t.controls.update({name:e,position:"right",html:a.html,style:{padding:"0 10px"},selector:a.selector,onSelect:d})):i("controls"),f()&&(c.setting?(o.set("setting",d),t.setting.update({name:e,tooltip:a.html,html:a.title,icon:n,width:200,selector:a.selector,onSelect:d})):i("setting"))},clear:l}}function a(t){let e;const n=n=>e===n&&t.active(n.dash),r=t=>t.dash.getSettings().streaming.abr.autoSwitchBitrate.video;function o(){const t=e;if(!t)return;let n;e=void 0,t.epoch++;for(const[e,o]of t.callbacks.splice(0))try{t.off.call(t.dash,e,o)}catch(r){n||(n=r)}if(n)throw n}function i(n,r){if(e===n){try{o()}catch(i){console.warn("ArtPlayer DASH subscription cleanup failed:",i)}if(!e&&t.active(n.dash))try{t.reset()}catch(i){console.warn("ArtPlayer DASH cleanup failed:",i)}}console.warn("ArtPlayer DASH refresh failed:",r)}function l(e){if(!n(e)||e.suspended||e.queued||e.running)return;e.queued=!0;const o=e.epoch;Promise.resolve().then(()=>{if(n(e)&&e.epoch===o&&!e.suspended){e.queued=!1,e.running=!0;try{e.automatic=r(e),n(e)&&t.refresh()}catch(l){i(e,l)}finally{e.running=!1}}})}return{bind:function(c){const a=e;if((null==a?void 0:a.dash)===c){const t=r(a);return void(n(a)&&(a.automatic=t,a.suspended=!1))}if(o(),e||!t.active(c)||"function"!==typeof c.on||"function"!==typeof c.off)return;const s={dash:c,off:c.off,callbacks:[],epoch:0,queued:!1,running:!1,suspended:!1,automatic:!1};e=s;const u=c.on;try{s.automatic=r(s);const e=["qualityChangeRequested","qualityChangeRendered","trackChangeRendered","streamUpdated","streamInitialized"].map(t=>[t,()=>{"streamUpdated"!==t&&"streamInitialized"!==t||(s.suspended=!1),l(s)}]),o=function(t,e){const n=t;try{if("function"!==typeof n.getVersion||"4.5.2"!==n.getVersion())return;if("function"!==typeof n.getVideoElement||"function"!==typeof n.getActiveStream||"function"!==typeof n.getDashMetrics)return}catch(o){return}let r=!1;return()=>{var t,o,i,l,c,a,s,u,f;if(!r&&e()){r=!0;try{const r=null==(t=n.getVideoElement)?void 0:t.call(n);if(!(null==r?void 0:r.seeking)||!Number.isFinite(r.currentTime))return;const d=r.currentTime,h=null==(o=n.getActiveStream)?void 0:o.call(n),g=null==(i=n.getDashMetrics)?void 0:i.call(n),p=null==(l=null==h?void 0:h.getProcessors)?void 0:l.call(h);if(!Array.isArray(p)||"function"!==typeof(null==g?void 0:g.getCurrentBufferLevel)||"function"!==typeof g.addBufferLevel)return;for(const t of p){if(!e())return;const o=null==(c=t.getType)?void 0:c.call(t);if("audio"!==o&&"video"!==o)continue;const i=null==(a=t.getBufferController)?void 0:a.call(t);if("function"!==typeof(null==i?void 0:i.getRangeAt)||"function"!==typeof i.getIsPruningInProgress||"function"!==typeof i.getAllRangesWithSafetyFactor)continue;const l=g.getCurrentBufferLevel(o),p=i.getRangeAt(d),y=null===p||p&&Number.isFinite(p.end)&&p.end<=d;if(!Number.isFinite(l)||l<=0||!y||!1!==i.getIsPruningInProgress())continue;const v=i.getAllRangesWithSafetyFactor(d);if(Array.isArray(v)&&0===v.length){if(!e()||(null==(s=n.getActiveStream)?void 0:s.call(n))!==h||(null==(u=n.getDashMetrics)?void 0:u.call(n))!==g||(null==(f=n.getVideoElement)?void 0:f.call(n))!==r||r.currentTime!==d||!r.seeking||!e())return;g.addBufferLevel(o,new Date,0)}}}catch(d){console.warn("ArtPlayer DASH seek buffer refresh failed:",d)}finally{r=!1}}}}(c,()=>n(s)&&!s.suspended);o&&e.push(["playbackSeeking",o]),e.push(["playbackTimeUpdated",()=>{if(n(s)&&!s.suspended)try{const t=r(s);n(s)&&t!==s.automatic&&(s.automatic=t,l(s))}catch(t){Line truncated
!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,captureNavigation:function(n){var o;const i=t.setting,l=null==(o=i.find)?void 0:o.call(i,e),c=r.get("setting");if(n()&&c&&!0===i.show&&(null==l?void 0:l.onSelect)===c&&l.selector&&i.active===l.selector&&"function"===typeof i.render)return()=>{var o,l;if(!n()||t.setting!==i||!0!==i.show||!i.option||i.active!==i.option)return;const c=null==(o=i.find)?void 0:o.call(i,e),a=r.get("setting");n()&&a&&(null==c?void 0:c.onSelect)===a&&c.selector&&(null==(l=i.render)||l.call(i,c.selector))}}}}function a(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)}]),r=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(r){return}let o=!1;return()=>{var t,r,i,l,c,a,s,u,f;if(!o&&e()){o=!0;try{const o=null==(t=n.getVideoElement)?void 0:t.call(n);if(!(null==o?void 0:o.seeking)||!Number.isFinite(o.currentTime))return;const d=o.currentTime,h=null==(r=n.getActiveStream)?void 0:r.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 r=null==(c=t.getType)?void 0:c.call(t);if("audio"!==r&&"video"!==r)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(r),p=i.getRangeAt(d),v=null===p||p&&Number.isFinite(p.end)&&p.end<=d;if(!Number.isFinite(l)||l<=0||!v||!1!==i.getIsPruningInProgress())continue;const y=i.getAllRangesWithSafetyFactor(d);if(Array.isArray(y)&&0===y.length){if(!e()||(null==(s=n.getAcLine truncated
@@ -167,7 +167,22 @@ function createMenu(art, name, icon) {
remove("setting");
}
}
return { update, clear };
function captureNavigation(active) {
const setting = art.setting;
const item = setting.find?.(name);
const callback = owned.get("setting");
if (!active() || !callback || setting.show !== true || item?.onSelect !== callback || !item.selector || setting.active !== item.selector || typeof setting.render !== "function")
return;
return () => {
if (!active() || art.setting !== setting || setting.show !== true || !setting.option || setting.active !== setting.option)
return;
const replacement = setting.find?.(name);
const callback2 = owned.get("setting");
if (active() && callback2 && replacement?.onSelect === callback2 && replacement.selector)
setting.render?.(replacement.selector);
};
}
return { update, clear, captureNavigation };
}
const $quality = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" height="18"><path fill="#fff" d="M0 96C0 60.7 28.7 32 64 32l384 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 480c-35.3 0-64-28.7-64-64L0 96zM323.8 202.5c-4.5-6.6-11.9-10.5-19.8-10.5s-15.4 3.9-19.8 10.5l-87 127.6L170.7 297c-4.6-5.7-11.5-9-18.7-9s-14.2 3.3-18.7 9l-64 80c-5.8 7.2-6.9 17.1-2.9 25.4s12.4 13.6 21.6 13.6l96 0 32 0 208 0c8.9 0 17.1-4.9 21.2-12.8s3.6-17.4-1.4-24.7l-120-176zM112 192a48 48 0 1 0 0-96 48 48 0 1 0 0 96z"/></svg>';
function createSeekRecovery(value, active) {
@@ -365,7 +380,18 @@ function artplayerPluginDashControl(option = {}) {
const subscriptions = [];
const observer = observeSDK({
active: (dash) => !closed && !art.isDestroy && art.dash === dash,
refresh: update,
refresh() {
const dash = art.dash;
let version = revision;
const current = () => !closed && !art.isDestroy && art.dash === dash && revision === version;
const restore = quality.captureNavigation(current) || audio.captureNavigation(current);
if (!current())
return;
version++;
update();
if (current())
restore?.();
},
reset() {
const version = ++revision;
clear(() => version === revision);
@@ -8,6 +8,19 @@ destroys it, changes its source, or adds a runtime dependency on dash.js.
## Module map
Automatic SDK refresh captures an open, plugin-owned quality or audio settings
panel before updating both menus. The menu module restores its new selector only
if settings remain visible, navigation was reset to the root, and the replacement
still belongs to this plugin. Restoration runs after both updates because updating
the sibling menu also resets the core settings view. It uses existing optional
`find`, `active`, `option`, `show` and `render` capabilities; no core API change or
new event subscription is required. The entry checks SDK identity, destruction
and the exact update revision before restoration and during its final lookup.
Nested explicit updates invalidate the earlier restore. Closed, foreign, removed
or unsupported panels are not reopened. Explicit public `update()` retains the
historical navigation behavior. Browser tests drive real SDK selection changes
while each submenu is open and then select another option through normal clicks.
| File | Responsibility |
| ------------------------------------------ | --------------------------------------------------------------------------------------- |
| `src/index.ts` | Deferred installation, media identity, update revisions, event subscription and cleanup |
@@ -16,7 +29,7 @@ destroys it, changes its source, or adds a runtime dependency on dash.js.
| `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 |
| `src/menu.ts` | Control/setting ownership, selection callbacks and owned settings navigation |
| `src/audio.svg`, `src/quality.svg` | Existing setting icons |
| `types/artplayer-plugin-dash-control.d.ts` | Public options, generic SDK inputs and callable result |
@@ -49,5 +49,8 @@ MIT © Harvey Zhao
## Maintenance
Automatic SDK refresh keeps an open quality or audio settings menu usable while
updating its selected item. Explicit `update()` keeps its existing behavior.
See [ARCHITECTURE.md](ARCHITECTURE.md) for module ownership, compatibility rules,
test commands and the remaining real SDK playback validation scope.
@@ -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 r;const o=new Map;function i(n,r=o.get(n)){if(!r||o.get(n)!==r)return;o.delete(n);const i=t[n];"controls"===n&&i.cache?.get&&i.cache.get(e)?.option?.onSelect!==r||"setting"===n&&i.find&&i.find(e)?.onSelect!==r||i.remove(e)}function c(){let t;r=void 0;for(const[n,r]of[...o])try{i(n,r)}catch(e){t||(t=e)}if(t)throw t}return{update:function(s,a,l){if(!a)return void c();const u={};r=u;const f=()=>u===r&&l(),d=e=>f()?(a.select(e,f),f()?(t.notice.show=`${a.title}: ${e.html}`,f()&&s.control&&t.controls.check(e),f()&&s.setting&&t.setting.check(e),e.html):e.html):e.html;s.control?(o.set("controls",d),t.controls.update({name:e,position:"right",html:a.html,style:{padding:"0 10px"},selector:a.selector,onSelect:d})):i("controls"),f()&&(s.setting?(o.set("setting",d),t.setting.update({name:e,tooltip:a.html,html:a.title,icon:n,width:200,selector:a.selector,onSelect:d})):i("setting"))},clear:c}}function n(t){let e;const n=n=>e===n&&t.active(n.dash),r=t=>t.dash.getSettings().streaming.abr.autoSwitchBitrate.video;function o(){const t=e;if(!t)return;let n;e=void 0,t.epoch++;for(const[e,o]of t.callbacks.splice(0))try{t.off.call(t.dash,e,o)}catch(r){n||(n=r)}if(n)throw n}function i(n,r){if(e===n){try{o()}catch(i){console.warn("ArtPlayer DASH subscription cleanup failed:",i)}if(!e&&t.active(n.dash))try{t.reset()}catch(i){console.warn("ArtPlayer DASH cleanup failed:",i)}}console.warn("ArtPlayer DASH refresh failed:",r)}function c(e){if(!n(e)||e.suspended||e.queued||e.running)return;e.queued=!0;const o=e.epoch;Promise.resolve().then(()=>{if(n(e)&&e.epoch===o&&!e.suspended){e.queued=!1,e.running=!0;try{e.automatic=r(e),n(e)&&t.refresh()}catch(c){i(e,c)}finally{e.running=!1}}})}return{bind:function(s){const a=e;if(a?.dash===s){const t=r(a);return void(n(a)&&(a.automatic=t,a.suspended=!1))}if(o(),e||!t.active(s)||"function"!==typeof s.on||"function"!==typeof s.off)return;const l={dash:s,off:s.off,callbacks:[],epoch:0,queued:!1,running:!1,suspended:!1,automatic:!1};e=l;const u=s.on;try{l.automatic=r(l);const e=["qualityChangeRequested","qualityChangeRendered","trackChangeRendered","streamUpdated","streamInitialized"].map(t=>[t,()=>{"streamUpdated"!==t&&"streamInitialized"!==t||(l.suspended=!1),c(l)}]),o=function(t,e){const n=t;try{if("function"!==typeof n.getVersion||"4.5.2"!==n.getVersion())return;if("function"!==typeof n.getVideoElement||"function"!==typeof n.getActiveStream||"function"!==typeof n.getDashMetrics)return}catch{return}let r=!1;return()=>{if(!r&&e()){r=!0;try{const t=n.getVideoElement?.();if(!t?.seeking||!Number.isFinite(t.currentTime))return;const r=t.currentTime,o=n.getActiveStream?.(),i=n.getDashMetrics?.(),c=o?.getProcessors?.();if(!Array.isArray(c)||"function"!==typeof i?.getCurrentBufferLevel||"function"!==typeof i.addBufferLevel)return;for(const s of c){if(!e())return;const c=s.getType?.();if("audio"!==c&&"video"!==c)continue;const a=s.getBufferController?.();if("function"!==typeof a?.getRangeAt||"function"!==typeof a.getIsPruningInProgress||"function"!==typeof a.getAllRangesWithSafetyFactor)continue;const l=i.getCurrentBufferLevel(c),u=a.getRangeAt(r),f=null===u||u&&Number.isFinite(u.end)&&u.end<=r;if(!Number.isFinite(l)||l<=0||!f||!1!==a.getIsPruningInProgress())continue;const d=a.getAllRangesWithSafetyFactor(r);if(Array.isArray(d)&&0===d.length){if(!e()||n.getActiveStream?.()!==o||n.getDashMetrics?.()!==i||n.getVideoElement?.()!==t||t.currentTime!==r||!t.seeking||!e())return;i.addBufferLevel(c,new Date,0)}}}catch(t){console.warn("ArtPlayer DASH seek buffer refresh failed:",t)}finally{r=!1}}}}(s,()=>n(l)&&!l.suspended);o&&e.push(["playbackSeeking",o]),e.push(["playbackTimeUpdated",()=>{if(n(l)&&!l.suspended)try{const t=r(l);n(l)&&t!==l.automatic&&(l.automatic=t,c(l))}catch(t){i(l,t)}}]),e.push(["streamTeardownComplete",()=>{if(n(l)){l.epoch++,l.queued=!1,l.suspended=!0;try{t.reset()}catch(e){i(l,e)}}}]);for(const t of e){if(!n(l))break;l.callbacks.push(t),u.call(s,...t)}}catch(f){if(e===l)try{o()}catch(d){console.warn("ArtPlayer DASH subscription cleanup failed:",d)}throw f}},release:o}}return function(r={}){return o=>{const i=o,{$video:c}=i.template,{errorHandle:s}=i.constructor.utils;let a=!1,l=0;const u=e(i,"dash-quality",'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" height="18"><path fill="#fff" d="M0 96C0 60.7 28.7 32 64 32l384 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 480c-35.3 0-64-28.7-64-64L0 96zM323.8 202.5c-4.5-6.6-11.9-10.5-19.8-10.5s-15.4 3.9-19.8 10.5l-87 12Line truncated
!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,captureNavigation:function(n){const r=t.setting,i=r.find?.(e),c=o.get("setting");if(n()&&c&&!0===r.show&&i?.onSelect===c&&i.selector&&r.active===i.selector&&"function"===typeof r.render)return()=>{if(!n()||t.setting!==r||!0!==r.show||!r.option||r.active!==r.option)return;const i=r.find?.(e),c=o.get("setting");n()&&c&&i?.onSelect===c&&i.selector&&r.render?.(i.selector)}}}}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,{$viLine truncated
@@ -4,4 +4,4 @@
* (c) 2017-2026 Harvey Zhao
* Released under the MIT License.
*/
!function(t,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"==typeof define&&define.amd?(t.artplayerPluginDashControl=e(),define(function(){return t.artplayerPluginDashControl})):(t="undefined"!==typeof globalThis?globalThis:t||self).artplayerPluginDashControl=e()}(this,function(){"use strict";var t=Object.defineProperty,e=Object.getOwnPropertySymbols,n=Object.prototype.hasOwnProperty,r=Object.prototype.propertyIsEnumerable,o=(e,n,r)=>n in e?t(e,n,{enumerable:!0,configurable:!0,writable:!0,value:r}):e[n]=r,i=(t,i)=>{for(var l in i||(i={}))n.call(i,l)&&o(t,l,i[l]);if(e)for(var l of e(i))r.call(i,l)&&o(t,l,i[l]);return t};function l(t){const e=new Map;return t.filter(t=>{if(void 0===t.html)return!0;const n=e.get(t.html);return n?(t.default&&Object.assign(n,t),!1):(e.set(t.html,t),!0)})}function c(t,e,n){let r;const o=new Map;function i(n,r=o.get(n)){var i,l,c,a;if(!r||o.get(n)!==r)return;o.delete(n);const s=t[n];"controls"===n&&(null==(i=s.cache)?void 0:i.get)&&(null==(c=null==(l=s.cache.get(e))?void 0:l.option)?void 0:c.onSelect)!==r||"setting"===n&&s.find&&(null==(a=s.find(e))?void 0:a.onSelect)!==r||s.remove(e)}function l(){let t;r=void 0;for(const[n,r]of[...o])try{i(n,r)}catch(e){t||(t=e)}if(t)throw t}return{update:function(c,a,s){if(!a)return void l();const u={};r=u;const f=()=>u===r&&s(),d=e=>f()?(a.select(e,f),f()?(t.notice.show=`${a.title}: ${e.html}`,f()&&c.control&&t.controls.check(e),f()&&c.setting&&t.setting.check(e),e.html):e.html):e.html;c.control?(o.set("controls",d),t.controls.update({name:e,position:"right",html:a.html,style:{padding:"0 10px"},selector:a.selector,onSelect:d})):i("controls"),f()&&(c.setting?(o.set("setting",d),t.setting.update({name:e,tooltip:a.html,html:a.title,icon:n,width:200,selector:a.selector,onSelect:d})):i("setting"))},clear:l}}function a(t){let e;const n=n=>e===n&&t.active(n.dash),r=t=>t.dash.getSettings().streaming.abr.autoSwitchBitrate.video;function o(){const t=e;if(!t)return;let n;e=void 0,t.epoch++;for(const[e,o]of t.callbacks.splice(0))try{t.off.call(t.dash,e,o)}catch(r){n||(n=r)}if(n)throw n}function i(n,r){if(e===n){try{o()}catch(i){console.warn("ArtPlayer DASH subscription cleanup failed:",i)}if(!e&&t.active(n.dash))try{t.reset()}catch(i){console.warn("ArtPlayer DASH cleanup failed:",i)}}console.warn("ArtPlayer DASH refresh failed:",r)}function l(e){if(!n(e)||e.suspended||e.queued||e.running)return;e.queued=!0;const o=e.epoch;Promise.resolve().then(()=>{if(n(e)&&e.epoch===o&&!e.suspended){e.queued=!1,e.running=!0;try{e.automatic=r(e),n(e)&&t.refresh()}catch(l){i(e,l)}finally{e.running=!1}}})}return{bind:function(c){const a=e;if((null==a?void 0:a.dash)===c){const t=r(a);return void(n(a)&&(a.automatic=t,a.suspended=!1))}if(o(),e||!t.active(c)||"function"!==typeof c.on||"function"!==typeof c.off)return;const s={dash:c,off:c.off,callbacks:[],epoch:0,queued:!1,running:!1,suspended:!1,automatic:!1};e=s;const u=c.on;try{s.automatic=r(s);const e=["qualityChangeRequested","qualityChangeRendered","trackChangeRendered","streamUpdated","streamInitialized"].map(t=>[t,()=>{"streamUpdated"!==t&&"streamInitialized"!==t||(s.suspended=!1),l(s)}]),o=function(t,e){const n=t;try{if("function"!==typeof n.getVersion||"4.5.2"!==n.getVersion())return;if("function"!==typeof n.getVideoElement||"function"!==typeof n.getActiveStream||"function"!==typeof n.getDashMetrics)return}catch(o){return}let r=!1;return()=>{var t,o,i,l,c,a,s,u,f;if(!r&&e()){r=!0;try{const r=null==(t=n.getVideoElement)?void 0:t.call(n);if(!(null==r?void 0:r.seeking)||!Number.isFinite(r.currentTime))return;const d=r.currentTime,h=null==(o=n.getActiveStream)?void 0:o.call(n),g=null==(i=n.getDashMetrics)?void 0:i.call(n),p=null==(l=null==h?void 0:h.getProcessors)?void 0:l.call(h);if(!Array.isArray(p)||"function"!==typeof(null==g?void 0:g.getCurrentBufferLevel)||"function"!==typeof g.addBufferLevel)return;for(const t of p){if(!e())return;const o=null==(c=t.getType)?void 0:c.call(t);if("audio"!==o&&"video"!==o)continue;const i=null==(a=t.getBufferController)?void 0:a.call(t);if("function"!==typeof(null==i?void 0:i.getRangeAt)||"function"!==typeof i.getIsPruningInProgress||"function"!==typeof i.getAllRangesWithSafetyFactor)continue;const l=g.getCurrentBufferLevel(o),p=i.getRangeAt(d),y=null===p||p&&Number.isFinite(p.end)&&p.end<=d;if(!Number.isFinite(l)||l<=0||!y||!1!==i.getIsPruningInProgress())continue;const v=i.getAllRangesWithSafetyFactor(d);if(Array.isArray(v)&&0===v.length){if(!e()||(null==(s=n.getActiveStream)?void 0:s.call(n))!==h||(null==(u=n.getDashMetrics)?void 0:u.call(n))!==g||(null==(f=n.getVideoElement)?void 0:f.call(n))!==r||r.currentTime!==d||!r.seeking||!e())return;g.addBufferLevel(o,new Date,0)}}}catch(d){console.warn("ArtPlayer DASH seek buffer refresh failed:",d)}finally{r=!1}}}}(c,()=>n(s)&&!s.suspended);o&&e.push(["playbackSeeking",o]),e.push(["playbackTimeUpdated",()=>{if(n(s)&&!s.suspended)try{const t=r(s);n(s)&&t!==s.automatic&&(s.automatic=t,l(s))}catch(t){Line truncated
!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,captureNavigation:function(n){var o;const i=t.setting,l=null==(o=i.find)?void 0:o.call(i,e),c=r.get("setting");if(n()&&c&&!0===i.show&&(null==l?void 0:l.onSelect)===c&&l.selector&&i.active===l.selector&&"function"===typeof i.render)return()=>{var o,l;if(!n()||t.setting!==i||!0!==i.show||!i.option||i.active!==i.option)return;const c=null==(o=i.find)?void 0:o.call(i,e),a=r.get("setting");n()&&a&&(null==c?void 0:c.onSelect)===a&&c.selector&&(null==(l=i.render)||l.call(i,c.selector))}}}}function a(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)}]),r=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(r){return}let o=!1;return()=>{var t,r,i,l,c,a,s,u,f;if(!o&&e()){o=!0;try{const o=null==(t=n.getVideoElement)?void 0:t.call(n);if(!(null==o?void 0:o.seeking)||!Number.isFinite(o.currentTime))return;const d=o.currentTime,h=null==(r=n.getActiveStream)?void 0:r.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 r=null==(c=t.getType)?void 0:c.call(t);if("audio"!==r&&"video"!==r)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(r),p=i.getRangeAt(d),v=null===p||p&&Number.isFinite(p.end)&&p.end<=d;if(!Number.isFinite(l)||l<=0||!v||!1!==i.getIsPruningInProgress())continue;const y=i.getAllRangesWithSafetyFactor(d);if(Array.isArray(y)&&0===y.length){if(!e()||(null==(s=n.getAcLine truncated
@@ -167,7 +167,22 @@ function createMenu(art, name, icon) {
remove("setting");
}
}
return { update, clear };
function captureNavigation(active) {
const setting = art.setting;
const item = setting.find?.(name);
const callback = owned.get("setting");
if (!active() || !callback || setting.show !== true || item?.onSelect !== callback || !item.selector || setting.active !== item.selector || typeof setting.render !== "function")
return;
return () => {
if (!active() || art.setting !== setting || setting.show !== true || !setting.option || setting.active !== setting.option)
return;
const replacement = setting.find?.(name);
const callback2 = owned.get("setting");
if (active() && callback2 && replacement?.onSelect === callback2 && replacement.selector)
setting.render?.(replacement.selector);
};
}
return { update, clear, captureNavigation };
}
const $quality = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" height="18"><path fill="#fff" d="M0 96C0 60.7 28.7 32 64 32l384 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 480c-35.3 0-64-28.7-64-64L0 96zM323.8 202.5c-4.5-6.6-11.9-10.5-19.8-10.5s-15.4 3.9-19.8 10.5l-87 127.6L170.7 297c-4.6-5.7-11.5-9-18.7-9s-14.2 3.3-18.7 9l-64 80c-5.8 7.2-6.9 17.1-2.9 25.4s12.4 13.6 21.6 13.6l96 0 32 0 208 0c8.9 0 17.1-4.9 21.2-12.8s3.6-17.4-1.4-24.7l-120-176zM112 192a48 48 0 1 0 0-96 48 48 0 1 0 0 96z"/></svg>';
function createSeekRecovery(value, active) {
@@ -365,7 +380,18 @@ function artplayerPluginDashControl(option = {}) {
const subscriptions = [];
const observer = observeSDK({
active: (dash) => !closed && !art.isDestroy && art.dash === dash,
refresh: update,
refresh() {
const dash = art.dash;
let version = revision;
const current = () => !closed && !art.isDestroy && art.dash === dash && revision === version;
const restore = quality.captureNavigation(current) || audio.captureNavigation(current);
if (!current())
return;
version++;
update();
if (current())
restore?.();
},
reset() {
const version = ++revision;
clear(() => version === revision);
@@ -20,7 +20,18 @@ export default function artplayerPluginDashControl<Level extends object = Qualit
const subscriptions: [EventName, Cleanup][] = []
const observer = observeSDK<Level, Track>({
active: dash => !closed && !art.isDestroy && art.dash === dash,
refresh: update,
refresh() {
const dash = art.dash
let version = revision
const current = () => !closed && !art.isDestroy && art.dash === dash && revision === version
const restore = quality.captureNavigation(current) || audio.captureNavigation(current)
if (!current())
return
version++
update()
if (current())
restore?.()
},
reset() {
const version = ++revision
clear(() => version === revision)
@@ -1,4 +1,4 @@
import type { DisplayConfig, Label, MenuHost, MenuModel, SelectorItem, Valid } from './types'
import type { Cleanup, DisplayConfig, Label, MenuHost, MenuModel, SelectorItem, Valid } from './types'
export function createMenu<Item extends SelectorItem>(art: MenuHost, name: string, icon: string) {
let current: object | undefined
@@ -70,5 +70,21 @@ export function createMenu<Item extends SelectorItem>(art: MenuHost, name: strin
}
}
return { update, clear }
function captureNavigation(active: Valid): Cleanup | undefined {
const setting = art.setting
const item = setting.find?.(name)
const callback = owned.get('setting')
if (!active() || !callback || setting.show !== true || item?.onSelect !== callback || !item.selector || setting.active !== item.selector || typeof setting.render !== 'function')
return
return () => {
if (!active() || art.setting !== setting || setting.show !== true || !setting.option || setting.active !== setting.option)
return
const replacement = setting.find?.(name)
const callback = owned.get('setting')
if (active() && callback && replacement?.onSelect === callback && replacement.selector)
setting.render?.(replacement.selector)
}
}
return { update, clear, captureNavigation }
}
@@ -77,7 +77,11 @@ export interface Registry {
remove: (name: string) => unknown
check: (item: SelectorItem) => unknown
cache?: { get?: (name: string) => { option?: { onSelect?: unknown } } | undefined }
find?: (name: string) => { onSelect?: unknown } | undefined
find?: (name: string) => { onSelect?: unknown, selector?: SelectorItem[] } | null | undefined
active?: SelectorItem[] | null
option?: SelectorItem[]
show?: boolean
render?: (option: SelectorItem[]) => unknown
}
export interface MenuHost {
@@ -0,0 +1,415 @@
{
"schemaVersion": 1,
"task": "PKG-DASH-MENU-01",
"baselineCommit": "30f646be97aaa929265392c661fd6cddad2037f9",
"rootCause": "SDK automatic refresh calls setting.update for both menus. Core update renders the root panel; the open quality/audio panel loses art-current before the click. Restore the owned panel only after both automatic updates, guarded by SDK identity, update revision and ownership. Explicit update is unchanged.",
"originalFailure": {
"file": "refactor/.cache/dash-seek-legacy-final-sdk-report/report.json",
"sha256": "74a3924972eeb7f3b22a3595bb1d5450088bba025cc83e022066fd3820989d8a",
"title": "published core / DASH 5.2.1: actual settings quality selection survives SDK refresh",
"project": "chromium",
"trace": {
"file": "refactor/.cache/dash-seek-legacy-final-sdk-report/results/dash-sdk-published-core-DA-81045-ection-survives-SDK-refresh-chromium/trace.zip",
"sha256": "3b3a12dd98abdedb95a4aa0ab74cdb72f51ed132e0df504bd2872d2cd3c6dc4f"
},
"panelBeforeClickAt": 63311.912,
"panelAfterTimeoutAt": 82551.665,
"observation": "Settings remain shown; active quality selector is replaced by root settings panel. Controlled real-SDK refresh reproduces the same navigation reset on all four tested core/SDK pairs."
},
"environment": {
"node": "24.21.0",
"yarn": "1.22.22",
"platform": "win32",
"remoteCI": false,
"physicalDevices": false
},
"source": [
{
"file": "packages/artplayer-plugin-dash-control/src/index.ts",
"sha256LF": "e22cfd0a3a2129913278cb2fbb50e2a7b2333cc9bd53647ec764d3f8f4f30ccc"
},
{
"file": "packages/artplayer-plugin-dash-control/src/menu.ts",
"sha256LF": "f5049045ead1d47c6275b0d13fc862b53a90e2accfe99935f641d07757e06428"
},
{
"file": "packages/artplayer-plugin-dash-control/src/types.ts",
"sha256LF": "d812577c7bd877f6998b55747916117da1f400067977292907e3a910e04c239c"
},
{
"file": "test/dash-events.test.js",
"sha256LF": "b753e21e068b02fc8176bac1cc804dc3b7daaa59f152cdc1f14ca5b910c8a39a"
},
{
"file": "test/browser/dash-sdk.spec.js",
"sha256LF": "e1b2980cfbdd948180308321705e39ca9b8df06d4bde33e421f66abbc5ce2065"
}
],
"artifacts": [
{
"file": "refactor/.cache/dash-package-types-QIKdVC/runtime/artplayer-plugin-dash-control.js",
"member": "package/dist/artplayer-plugin-dash-control.js",
"sha256": "2d639a5c6a7044ada2b1d74e5b039d4cc787996b997c5a186a67724848f36c2a",
"archiveSha256": "83cd29340bbc6788f9b069d18909fd0344aeb2fa8e12515f92796cfd7bd2fdab"
},
{
"file": "refactor/.cache/dash-package-types-QIKdVC/runtime/artplayer-plugin-dash-control.legacy.js",
"member": "package/dist/artplayer-plugin-dash-control.legacy.js",
"sha256": "eef089514bf7304c7910e4f093c7c350495458284c6f55871358ae085f504ddc",
"archiveSha256": "83cd29340bbc6788f9b069d18909fd0344aeb2fa8e12515f92796cfd7bd2fdab"
},
{
"file": "refactor/.cache/dash-package-types-QIKdVC/runtime/artplayer-plugin-dash-control.mjs",
"member": "package/dist/artplayer-plugin-dash-control.mjs",
"sha256": "bec1c9fa15fa6b80841517b9afe29da8c63a4d28fc57a6218c869b0c0e407703",
"archiveSha256": "83cd29340bbc6788f9b069d18909fd0344aeb2fa8e12515f92796cfd7bd2fdab"
}
],
"reports": [
{
"directory": "refactor/.cache/dash-menu-red-report",
"reportSha256": "09c08de619c8b1c67c4abbcd36827ed36a94c8424b3cea69422690d9b9fb974f",
"result": {
"scope": "source",
"exitCode": 1,
"signal": null,
"args": [
"test",
"--config=playwright.source.config.js",
"test/browser/dash-sdk.spec.js",
"--project=chromium",
"--grep",
"SDK refresh preserves the open quality",
"--workers=2"
]
},
"stats": {
"startTime": "2026-09-14T12:21:57.499Z",
"duration": 23198.907,
"expected": 0,
"skipped": 0,
"unexpected": 4,
"flaky": 0
},
"browsers": [
"153.0.8010.12"
],
"inputs": [
{
"sdk": "4.5.2",
"sdkArchiveSha256": "9a0944c48deb0fd8f92b0eea436c5b0d0aea9237c7da0078c0310f1430d0a31f",
"sdkCodeMember": "package/dist/dash.all.min.js",
"candidate": "workspace source build",
"candidateSha256": "b9c81fe7223b79775b5c2d3b9dbc79477b7e993201a8412fba81143df39972cf"
},
{
"sdk": "5.2.1",
"sdkArchiveSha256": "4eacb0cc3525276666ca3f084611cdabb5a2264d128dfe0750fe106fbddd088f",
"sdkCodeMember": "package/dist/modern/umd/dash.all.min.js",
"candidate": "workspace source build",
"candidateSha256": "b9c81fe7223b79775b5c2d3b9dbc79477b7e993201a8412fba81143df39972cf"
}
],
"failures": [
{
"title": "published core / DASH 4.5.2: SDK refresh preserves the open quality settings panel",
"project": "chromium",
"errors": [
"Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mfalse\u001b[39m\n\nCall Log:\n- Timeout 7000ms exceeded while waiting on the predicate\n\n 353 | const changed = type === 'quality' ? '180p' : 'fr'\n 354 | await expect(page.locator(`.art-control-dash-${type} .art-selector-value`)).toHaveText(changed)\n> 355 | await expect.poll(() => page.evaluate(type => window.art.setting.active === window.art.setting.find(`dash-${type}`).selector, type)).toBe(true)\n | ^\n 356 | const label = type === 'quality' ? '90p' : 'en'\n 357 | await page.locator('.art-setting-panel.art-current .art-setting-item-left-text').filter({ hasText: new RegExp(`^${label}$`) }).click()\n 358 | if (type === 'quality')\n at D:\\github\\ArtPlayer\\test\\browser\\dash-sdk.spec.js:355:142"
]
},
{
"title": "published core / DASH 5.2.1: SDK refresh preserves the open quality settings panel",
"project": "chromium",
"errors": [
"Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mfalse\u001b[39m\n\nCall Log:\n- Timeout 7000ms exceeded while waiting on the predicate\n\n 353 | const changed = type === 'quality' ? '180p' : 'fr'\n 354 | await expect(page.locator(`.art-control-dash-${type} .art-selector-value`)).toHaveText(changed)\n> 355 | await expect.poll(() => page.evaluate(type => window.art.setting.active === window.art.setting.find(`dash-${type}`).selector, type)).toBe(true)\n | ^\n 356 | const label = type === 'quality' ? '90p' : 'en'\n 357 | await page.locator('.art-setting-panel.art-current .art-setting-item-left-text').filter({ hasText: new RegExp(`^${label}$`) }).click()\n 358 | if (type === 'quality')\n at D:\\github\\ArtPlayer\\test\\browser\\dash-sdk.spec.js:355:142"
]
},
{
"title": "candidate core / DASH 4.5.2: SDK refresh preserves the open quality settings panel",
"project": "chromium",
"errors": [
"Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mfalse\u001b[39m\n\nCall Log:\n- Timeout 7000ms exceeded while waiting on the predicate\n\n 353 | const changed = type === 'quality' ? '180p' : 'fr'\n 354 | await expect(page.locator(`.art-control-dash-${type} .art-selector-value`)).toHaveText(changed)\n> 355 | await expect.poll(() => page.evaluate(type => window.art.setting.active === window.art.setting.find(`dash-${type}`).selector, type)).toBe(true)\n | ^\n 356 | const label = type === 'quality' ? '90p' : 'en'\n 357 | await page.locator('.art-setting-panel.art-current .art-setting-item-left-text').filter({ hasText: new RegExp(`^${label}$`) }).click()\n 358 | if (type === 'quality')\n at D:\\github\\ArtPlayer\\test\\browser\\dash-sdk.spec.js:355:142"
]
},
{
"title": "candidate core / DASH 5.2.1: SDK refresh preserves the open quality settings panel",
"project": "chromium",
"errors": [
"Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mfalse\u001b[39m\n\nCall Log:\n- Timeout 7000ms exceeded while waiting on the predicate\n\n 353 | const changed = type === 'quality' ? '180p' : 'fr'\n 354 | await expect(page.locator(`.art-control-dash-${type} .art-selector-value`)).toHaveText(changed)\n> 355 | await expect.poll(() => page.evaluate(type => window.art.setting.active === window.art.setting.find(`dash-${type}`).selector, type)).toBe(true)\n | ^\n 356 | const label = type === 'quality' ? '90p' : 'en'\n 357 | await page.locator('.art-setting-panel.art-current .art-setting-item-left-text').filter({ hasText: new RegExp(`^${label}$`) }).click()\n 358 | if (type === 'quality')\n at D:\\github\\ArtPlayer\\test\\browser\\dash-sdk.spec.js:355:142"
]
}
],
"menuCases": 4
},
{
"directory": "refactor/.cache/dash-menu-source-green-report",
"reportSha256": "f793008f42ccca55b431bba347aca88d79602b17a58eb05384b276881079f63e",
"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",
"SDK refresh preserves the open|actual settings",
"--workers=2"
]
},
"stats": {
"startTime": "2026-09-14T12:23:06.427Z",
"duration": 41683.534,
"expected": 32,
"skipped": 0,
"unexpected": 0,
"flaky": 0
},
"browsers": [
"153.0.8010.12",
"155.0"
],
"inputs": [
{
"sdk": "4.5.2",
"sdkArchiveSha256": "9a0944c48deb0fd8f92b0eea436c5b0d0aea9237c7da0078c0310f1430d0a31f",
"sdkCodeMember": "package/dist/dash.all.min.js",
"candidate": "workspace source build",
"candidateSha256": "d5fce5ae917ad8fa3e666aceed9a23f626bfbdd89fd73379db56c85e145ae02d"
},
{
"sdk": "5.2.1",
"sdkArchiveSha256": "4eacb0cc3525276666ca3f084611cdabb5a2264d128dfe0750fe106fbddd088f",
"sdkCodeMember": "package/dist/modern/umd/dash.all.min.js",
"candidate": "workspace source build",
"candidateSha256": "d5fce5ae917ad8fa3e666aceed9a23f626bfbdd89fd73379db56c85e145ae02d"
}
],
"failures": [],
"menuCases": 32
},
{
"directory": "refactor/.cache/dash-menu-source-final-report",
"reportSha256": "7385e41e449f203b7c177355d0a16bafc26e798c7c8104b31fad7d47fa689a73",
"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:26:48.315Z",
"duration": 137303.375,
"expected": 92,
"skipped": 0,
"unexpected": 0,
"flaky": 0
},
"browsers": [
"153.0.8010.12",
"155.0"
],
"inputs": [
{
"sdk": "4.5.2",
"sdkArchiveSha256": "9a0944c48deb0fd8f92b0eea436c5b0d0aea9237c7da0078c0310f1430d0a31f",
"sdkCodeMember": "package/dist/dash.all.min.js",
"candidate": "workspace source build",
"candidateSha256": "5bd2e6b031996c2b42970a790769f6e91752cef76697522d0e57c6d8909fb568"
},
{
"sdk": "5.2.1",
"sdkArchiveSha256": "4eacb0cc3525276666ca3f084611cdabb5a2264d128dfe0750fe106fbddd088f",
"sdkCodeMember": "package/dist/modern/umd/dash.all.min.js",
"candidate": "workspace source build",
"candidateSha256": "5bd2e6b031996c2b42970a790769f6e91752cef76697522d0e57c6d8909fb568"
}
],
"failures": [],
"menuCases": 32
},
{
"directory": "refactor/.cache/dash-menu-main-report",
"reportSha256": "dd4d0ae710851220e7edc007b752d7678c404eb8c7cec4484f925bf81bc74858",
"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",
"SDK refresh preserves the open|actual settings",
"--workers=2"
]
},
"stats": {
"startTime": "2026-09-14T12:30:20.600Z",
"duration": 41334.104,
"expected": 32,
"skipped": 0,
"unexpected": 0,
"flaky": 0
},
"browsers": [
"153.0.8010.12",
"155.0"
],
"inputs": [
{
"sdk": "4.5.2",
"sdkArchiveSha256": "9a0944c48deb0fd8f92b0eea436c5b0d0aea9237c7da0078c0310f1430d0a31f",
"sdkCodeMember": "package/dist/dash.all.min.js",
"candidate": "D:\\github\\ArtPlayer\\refactor\\.cache\\dash-package-types-QIKdVC\\runtime\\artplayer-plugin-dash-control.js",
"candidateSha256": "2d639a5c6a7044ada2b1d74e5b039d4cc787996b997c5a186a67724848f36c2a"
},
{
"sdk": "5.2.1",
"sdkArchiveSha256": "4eacb0cc3525276666ca3f084611cdabb5a2264d128dfe0750fe106fbddd088f",
"sdkCodeMember": "package/dist/modern/umd/dash.all.min.js",
"candidate": "D:\\github\\ArtPlayer\\refactor\\.cache\\dash-package-types-QIKdVC\\runtime\\artplayer-plugin-dash-control.js",
"candidateSha256": "2d639a5c6a7044ada2b1d74e5b039d4cc787996b997c5a186a67724848f36c2a"
}
],
"failures": [],
"menuCases": 32
},
{
"directory": "refactor/.cache/dash-menu-legacy-report",
"reportSha256": "3f1db2574bbfd759a6382b09b55842444df6db3005902460acf3a6839304840a",
"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:31:23.841Z",
"duration": 136092.201,
"expected": 92,
"skipped": 0,
"unexpected": 0,
"flaky": 0
},
"browsers": [
"153.0.8010.12",
"155.0"
],
"inputs": [
{
"sdk": "4.5.2",
"sdkArchiveSha256": "9a0944c48deb0fd8f92b0eea436c5b0d0aea9237c7da0078c0310f1430d0a31f",
"sdkCodeMember": "package/dist/dash.all.min.js",
"candidate": "D:\\github\\ArtPlayer\\refactor\\.cache\\dash-package-types-QIKdVC\\runtime\\artplayer-plugin-dash-control.legacy.js",
"candidateSha256": "eef089514bf7304c7910e4f093c7c350495458284c6f55871358ae085f504ddc"
},
{
"sdk": "5.2.1",
"sdkArchiveSha256": "4eacb0cc3525276666ca3f084611cdabb5a2264d128dfe0750fe106fbddd088f",
"sdkCodeMember": "package/dist/modern/umd/dash.all.min.js",
"candidate": "D:\\github\\ArtPlayer\\refactor\\.cache\\dash-package-types-QIKdVC\\runtime\\artplayer-plugin-dash-control.legacy.js",
"candidateSha256": "eef089514bf7304c7910e4f093c7c350495458284c6f55871358ae085f504ddc"
}
],
"failures": [],
"menuCases": 32
}
],
"node": {
"file": "refactor/.cache/dash-menu-node-artifacts.log",
"sha256": "9abf0919778aa5daac80e6951b5f6bc32238a7972389a08bdbdafcf2d065c8e9",
"passed": 446,
"failed": 0,
"skipped": 0,
"initialReentrancyFailures": {
"file": "refactor/.cache/dash-menu-node-initial.log",
"sha256": "ca9a1394e5ab7f64e1e225b9e25eda8e87a9d99472e1b35d26d8d9ef24f2b0b0"
}
},
"types": {
"file": "refactor/.cache/dash-package-types-QIKdVC/report.json",
"sha256": "5f3fb18d59cb60fcba08e6f6aa43b947f97a0c283d832c1391f66c581ae87b7f",
"archives": [
{
"name": "artplayer",
"sha256": "d4a0cc5dcbe015e254b3bce64d505ee4e010792ab70414bcf8d0139d7c413b75"
},
{
"name": "artplayer-plugin-dash-control",
"sha256": "83cd29340bbc6788f9b069d18909fd0344aeb2fa8e12515f92796cfd7bd2fdab"
}
],
"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
}
],
"method": "Offline Yarn install and frozen reinstall outside workspace; all installed files match tarball. Browser uses extracted tarball runtime, not a complete installed browser suite."
},
"checks": {
"strictTypeScriptExitCode": 0,
"targetedLintExitCode": 0,
"rootLint": {
"file": "refactor/.cache/dash-menu-lint.log",
"sha256": "8f03fe722a69933197e1985dcd5bffe6110fa8c03be9fa9d92313447762ca4bf",
"errors": 0,
"existingWarnings": 1
}
},
"limitations": [
"Full DASH combination and distribution acceptance remains open",
"Standalone SDK 4.5.2 and frozen published-plugin failure controls remain unchanged; only these invocations exclude them from the broad candidate matrix",
"WebKit MSE capability, physical devices, remote CI and global release reviews are not completed here"
]
}
@@ -0,0 +1,63 @@
# PKG-DASH-MENU-01:SDK 自动刷新保留设置子菜单
## 问题和根因
此前最终 legacy SDK 矩阵中,Chromium、旧核心和 dash.js 5.2.1 的 180p 设置点击
超时。trace 在点击前显示质量子菜单拥有 `art-current`;失败时设置仍然显示,
但根菜单重新成为当前面板。选项仍存在于隐藏子菜单,所以 locator 已解析但不可见。
原报告保留于 `refactor/.cache/dash-seek-legacy-final-sdk-report`,不覆盖该失败。
SDK 自动事件刷新通过 `update()` 更新质量、音轨两个菜单。核心 `setting.update()`
会回到根面板;两次更新都会重置导航。新增真实 SDK 选择变化的回归用例,在保持
菜单打开时触发实际质量/音轨选择,检查刷新后的当前面板,并用普通鼠标点击选择
另一项。修复前质量用例在新旧核心、两代 SDK 的四个 Chromium 组合全部失败。
这将 trace 中的导航复位现象与可控的真实 SDK 刷新联系起来,没有伪造 SDK 事件。
## 实现、兼容和类型
`src/menu.ts` 在自动刷新前捕获本插件拥有的打开子菜单,两项更新完成后才恢复
新的 selector。恢复要求设置仍显示、当前面板确实回到根菜单、目标仍由插件拥有。
关闭、移除、其他调用方接管及缺少可选导航能力时不恢复,不强制打开设置。
该职责与菜单所有权放在同一模块,不新增只用于转发的抽象或异步资源。
`src/index.ts` 将此逻辑限定在 SDK observer 的自动 refresh 中;公开同步 `update()`
继续保留原有导航行为。恢复与原 SDK 身份、销毁状态、确切 update revision 绑定。
初版的嵌套显式 update 用例有两项失败,补入 revision 守卫后通过;销毁中的最后
lookup 同样不能重新打开菜单。`src/types.ts` 只添加可选内部导航能力的窄类型。
工厂/option/update 签名、SDK 选择顺序、公开声明、DOM/CSS hooks、入口不变。
没有修改核心 setting.update 的语义,没有强制点击或增加等待超时,没有新增依赖。
包内 README 与 ARCHITECTURE 已解释自动和显式刷新、导航及资源所有权。
## 验证
固定 Node24.21.0/Yarn1.22.22,Windows 的 Chromium153.0.8010.12、Firefox155.0。
源码初轮设置矩阵32项通过;增加重入守卫后,完整候选DASH定向矩阵92项通过。
源码及main/legacy/ESM三格式Node446项通过,包内严格TS与专项lint通过;根lint
0错误/1个既有warning。最终含文档tarball在仓库外离线安装并冻结重装,全部
文件指纹核对,五组类型模式通过且各拒绝八项非法调用。
打包产物的main设置矩阵32项、legacy完整定向矩阵92项均通过,0失败/跳过/重试。
任务完成,DASH-MENU-01按根因和旧红新绿证据关闭;完整组合及发布任务仍开放。
浏览器使用核验tarball解出的运行时,类型检查使用实际安装包;不是完整installed
浏览器验收。[最终报告](../baselines/dash-menu-validation.json)记录每个浏览器的
输入SHA、退出码及失败,不以套件名称替代输入来源证明。
一次tarball提取辅助脚本最初把字节传给需要路径的函数,提取失败;随后运行未能
设置产物路径而实际验证了源码。已按附件输入明确归档为源码92项,修正提取后
main/legacy另行运行并核对候选SHA,未把该源码结果计作打包产物验证。
定向候选矩阵仅在命令行排除已冻结的裸SDK与旧发布插件对照;默认完整文件与CI
仍保留它们的原断言。原DASH-SEEK-01的上游缺陷、PKG-DASH-05/06、WebKit能力、
设备、远端CI及三轮发布复盘不因本次菜单修复而完成。
## 重跑与回退
`yarn test:dash-control`、`yarn test:dash-types-package`;设置专项通过
`yarn test:browser:source test/browser/dash-sdk.spec.js --project=chromium
--project=firefox --grep 'SDK refresh preserves the open|actual settings' --workers=2`。
产物验证必须显式设置 `ARTPLAYER_DASH_ARTIFACT` 为已核验的main/legacy文件。
完整候选范围使用 `--grep-invert 'native SDK track switch|published DASH / 4.5.2'`;
去掉过滤将包含保留的历史SDK/旧插件失败。每次运行前先归档已有报告。
回退本任务独立提交并重建DASH三格式即可,不单独修改生成文件。没有push、部署或发布。
+10
View File
@@ -99,3 +99,13 @@ UMD 测试 VM 显式注入宿主 console,以检查原始错误身份;没有
跳转4失败,新源码8通过,最终main/legacy各8个严格跳转通过。main完整定向76通过;
legacy同范围75通过、1个SDK5.2.1设置点击不可见失败,归PKG-DASH-MENU-01继续调查。
不将局部跳转修复等同于DASH组合/分发验收全部完成。
## 自动刷新设置导航修复
PKG-DASH-MENU-01已确认SDK自动刷新调用setting.update重置根面板,导致打开的
子菜单失去art-current;在真实SDK四组组合复现。插件现在只为自动刷新捕获并
恢复仍由自己拥有、仍打开的子菜单,且在质量和音轨两项更新后恢复。显式update
保持原行为;revision/SDK/销毁/菜单所有权防止陈旧恢复。源码92、main设置32、
legacy候选92均通过;Node446和五组安装类型通过,原始75/1报告保留。
见[实施记录](changes/2026-09-14-PKG-DASH-MENU-01-settings-navigation.md)及
[证据](baselines/dash-menu-validation.json)。DASH-MENU-01关闭,其他SDK/设备/分发门槛不变。
+3 -3
View File
@@ -4,7 +4,7 @@
基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 261 项,范围 22 个包及工作区/示例。
状态:todo 50 / doing 20 / blocked 0 / done 191 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
状态:todo 49 / doing 20 / blocked 0 / done 192 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
前置依赖是启动条件;验收是完成条件。任务可以继续拆分,但不能复用或悄悄删除旧 ID。
@@ -219,7 +219,7 @@
| PKG-DASH-03 | artplayer-plugin-dash-control<br>整理内部职责与资源 | PKG-DASH-02, CORE-11, CORE-14 | 能力适配保留 SDK 4.x 与 5.x,稳定 ID 映射、ABR 状态、UI 清理和生命周期职责分离 | 结构变化和缺陷修复分开记录;原 API/事件/资源生命周期通过 | H | done |
| PKG-DASH-04 | artplayer-plugin-dash-control<br>迁移自有源码和公开类型 | PKG-DASH-03, ENG-04, ENG-06, CORE-07 | dash.js adapter、selector 和回调类型 | 严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容 | H | done |
| PKG-DASH-SEEK-01 | artplayer-plugin-dash-control<br>修复 SDK 4.5.2 暂停跳转后的缓冲指标停滞 | PKG-DASH-04 | 按版本和实测空缓冲限定的指标同步、生命周期守卫及真实 SDK 旧红新绿 | 新插件在新旧核心及 Chromium/Firefox 原位置回归通过;不修改 SDK 方法/配置、媒体时间或删除缓冲;旧 SDK 原生缺陷与旧插件限制独立记录,不宣称第三方本体已修复 | H | done |
| PKG-DASH-MENU-01 | artplayer-plugin-dash-control<br>定位并修复 SDK 刷新期间设置选项不可见 | PKG-DASH-SEEK-01 | 真实 SDK 菜单点击失败根因、对应兼容修复及新旧核心回归 | 保留 legacy/旧核心/SDK5.2.1 的原失败;确定菜单在点击过程中不可见的原因;不靠 force 点击、扩大超时或重复运行覆盖问题;main/legacy 质量与音轨设置及 SDK 刷新组合通过 | H | todo |
| PKG-DASH-MENU-01 | artplayer-plugin-dash-control<br>定位并修复 SDK 刷新期间设置选项不可见 | PKG-DASH-SEEK-01 | 真实 SDK 菜单点击失败根因、对应兼容修复及新旧核心回归 | 保留 legacy/旧核心/SDK5.2.1 的原失败;确定菜单在点击过程中不可见的原因;不靠 force 点击、扩大超时或重复运行覆盖问题;main/legacy 质量与音轨设置及 SDK 刷新组合通过 | H | done |
| PKG-DASH-05 | artplayer-plugin-dash-control<br>验证新旧核心和组合 | PKG-DASH-04, CORE-22, PKG-DASH-SEEK-01 | 固定 dash.js 4.5.2/5.2.1 与本地 DASH 实际清晰度/音轨、高亮、Auto 和换源组合 | 最终核心与原支持范围核心分别通过;设备/SDK 缺证据不能标完成 | H | doing |
| PKG-DASH-06 | artplayer-plugin-dash-control<br>验证分发并同步文档 | PKG-DASH-05, ENG-07, PKG-DASH-MENU-01 | dash.control.js、支持的 dash.js 版本与产物 | tarball 入口/资源、类型、8082 demo 和 README 一致,有回退记录 | H | todo |
@@ -563,7 +563,7 @@
- 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-MENU-01: [记录](changes/2026-09-14-PKG-DASH-MENU-01-settings-navigation.md) [记录](baselines/dash-menu-validation.json) [记录](dash-validation.md)
- 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)
+16
View File
@@ -1,5 +1,21 @@
# 进度与证据
## PKG-DASH-MENU-01 SDK 刷新设置子菜单修复完成
原legacy设置点击失败的trace显示根面板取代质量子菜单,setting仍显示。
真实SDK刷新四组旧红用例全部复现;在插件自动refresh中捕获并在两项菜单更新后
恢复拥有的打开selector,显式update维持旧行为。新旧核心/两SDK/两引擎设置32项
通过,最终源码完整候选92通过;核验tarball的main设置32、legacy完整候选92通过。
三格式及源码Node446、仓库外安装/冻结重装与五组类型通过,严格TS/lint通过,
根lint仍1个既有warning。嵌套update旧版本守卫两项失败补修后通过;销毁和接管不复活。
关闭DASH-MENU-01,原legacy75/1报告与四个新复现均保留,没有force点击、扩大超时
或覆盖旧失败。辅助提取最初失败后的运行按真实输入计作源码;产物修正后独立验证。
见[记录](changes/2026-09-14-PKG-DASH-MENU-01-settings-navigation.md)和
[证据](baselines/dash-menu-validation.json)。261项:192 done、20 doing、49 todo。
DASH-SEEK-01的裸SDK/旧插件限制、DASH05/06、设备/远端CI及全局发布仍开放。
本任务独立本地提交并完成提交审计后继续其他插件与组合验收;没有push或发布。
## PKG-DASH-SEEK-01 暂停跳转缓冲指标修复完成
仅在dash.js 4.5.2真实空缓冲且缓存指标陈旧时同步零值,恢复SDK自身下载调度。
+1 -1
View File
@@ -274,4 +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 |
| DASH-MENU-01 | resolved / 已复现 | SDK 5.2.1 播放中设置质量选项在点击时变为不可见 | PKG-DASH-MENU-01, PKG-DASH-05, REVIEW-02 |
+10 -3
View File
@@ -6275,7 +6275,7 @@
"id": "DASH-MENU-01",
"title": "SDK 5.2.1 播放中设置质量选项在点击时变为不可见",
"confirmation": "reproduced",
"status": "open",
"status": "resolved",
"owners": [
"PKG-DASH-MENU-01",
"PKG-DASH-05",
@@ -6285,8 +6285,15 @@
"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点击或重跑覆盖失败。"
"compatibleResolution": "SDK自动刷新前捕获本插件拥有的打开子菜单,在两项设置更新后按新selector恢复;显式update、关闭/移除/接管以及嵌套更新保持原边界。守卫SDK身份、revision、销毁与菜单所有权。",
"closureCriteria": "定位实际隐藏/刷新时序并提供可复现旧红新绿;新旧核心、main/legacy、质量/音轨、SDK刷新与销毁组合验证,不靠force点击或重跑覆盖失败。",
"resolutionEvidence": [
"refactor/changes/2026-09-14-PKG-DASH-MENU-01-settings-navigation.md",
"refactor/baselines/dash-menu-validation.json",
"test/browser/dash-sdk.spec.js",
"test/dash-events.test.js"
],
"resolutionRationale": "原trace显示设置仍打开但导航退回根面板;真实SDK刷新在四组核心/SDK组合复现相同失败。修复后源码92、main设置32、legacy完整候选92通过,新旧核心/两SDK/两引擎覆盖质量及音轨,原失败与未加force/延长超时的回归均保留。Node446验证三格式及重入/销毁边界;DASH其他SDK/设备/发布风险仍独立开放。"
}
]
}
+4 -3
View File
@@ -2268,13 +2268,14 @@
"dependsOn": [
"PKG-DASH-SEEK-01"
],
"status": "todo",
"status": "done",
"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"
"changes/2026-09-14-PKG-DASH-MENU-01-settings-navigation.md",
"baselines/dash-menu-validation.json",
"dash-validation.md"
]
},
{
+33
View File
@@ -330,6 +330,39 @@ for (const version of ['4.5.2', '5.2.1']) {
for (const core of ['published', 'candidate']) {
for (const version of ['4.5.2', '5.2.1']) {
for (const type of ['quality', 'audio']) {
test(`${core} core / DASH ${version}: SDK refresh preserves the open ${type} settings panel`, async ({ page }, testInfo) => {
await openDash(page, version, core, 'candidate', testInfo)
await expect.poll(() => page.evaluate(() => window.art.isReady)).toBe(true)
await page.locator('#play').click()
await expect.poll(() => page.evaluate(() => window.art.currentTime)).toBeGreaterThan(0.3)
await page.locator('.art-control-setting').click()
await page.locator(`[data-name="dash-${type}"]`).click()
await page.evaluate((type) => {
const { dash } = window.art
if (type === 'quality') {
dash.updateSettings({ streaming: { abr: { autoSwitchBitrate: { video: false } } } })
if (dash.setQualityFor)
dash.setQualityFor('video', 1)
else
dash.setRepresentationForTypeById('video', '1')
}
else {
dash.setCurrentTrack(dash.getTracksFor('audio').find(track => track.lang === 'fr'))
}
}, type)
const changed = type === 'quality' ? '180p' : 'fr'
await expect(page.locator(`.art-control-dash-${type} .art-selector-value`)).toHaveText(changed)
await expect.poll(() => page.evaluate(type => window.art.setting.active === window.art.setting.find(`dash-${type}`).selector, type)).toBe(true)
const label = type === 'quality' ? '90p' : 'en'
await page.locator('.art-setting-panel.art-current .art-setting-item-left-text').filter({ hasText: new RegExp(`^${label}$`) }).click()
if (type === 'quality')
await expect.poll(() => page.evaluate(() => window.art.video.videoHeight)).toBe(90)
else
await expect.poll(() => page.evaluate(() => window.art.dash.getCurrentTrackFor('audio').lang)).toBe('en')
await expect(page.locator(`.art-control-dash-${type} .art-selector-value`)).toHaveText(label)
expect(await page.evaluate(() => window.sdkErrors)).toEqual([])
})
test(`${core} core / DASH ${version}: actual settings ${type} selection survives SDK refresh`, async ({ page }, testInfo) => {
await openDash(page, version, core, 'candidate', testInfo)
await expect.poll(() => page.evaluate(() => window.art.isReady)).toBe(true)
+102
View File
@@ -50,6 +50,108 @@ function seekHost() {
return { ...host, samples, levels, buffers, metrics, seekState: state }
}
function navigationHost(version) {
const host = eventHost(version)
const setting = host.art.setting
const update = setting.update
const renders = []
Object.assign(setting, {
option: [],
active: null,
show: true,
find: name => host.settings.get(name),
render(option) {
setting.active = option
renders.push(option)
},
update(option) {
update(option)
setting.active = setting.option
},
})
return { ...host, renders, open(name) {
setting.active = host.settings.get(name).selector
} }
}
for (const { name, sdk, factory } of candidates) {
test(`DASH ${sdk} (${name}): automatic refresh retains the owned quality or audio panel after both menu updates`, async () => {
for (const name of ['dash-quality', 'dash-audio']) {
const host = navigationHost(sdk)
factory(bothMenus())(host.art).update()
host.open(name)
const previous = host.art.setting.active
host.fire('qualityChangeRendered')
await flush()
assert.equal(host.renders.length, 1)
assert.equal(host.art.setting.active, host.settings.get(name).selector)
assert.notEqual(host.art.setting.active, previous)
}
})
test(`DASH ${sdk} (${name}): explicit update and closed or foreign panels retain existing navigation behavior`, async () => {
for (const mode of ['explicit', 'closed', 'foreign', 'unavailable', 'removed']) {
const host = navigationHost(sdk)
const option = bothMenus()
const plugin = factory(option)(host.art)
plugin.update()
host.open('dash-quality')
if (mode === 'explicit') {
plugin.update()
}
else {
if (mode === 'closed')
host.art.setting.show = false
if (mode === 'foreign')
host.settings.get('dash-quality').onSelect = () => {}
if (mode === 'unavailable')
delete host.art.setting.render
if (mode === 'removed')
option.quality.setting = false
host.fire('qualityChangeRendered')
await flush()
}
assert.equal(host.renders.length, 0)
assert.equal(host.art.setting.active, host.art.setting.option)
}
})
test(`DASH ${sdk} (${name}): navigation lookup cannot restore an obsolete refresh after a nested explicit update`, async () => {
const host = navigationHost(sdk)
const plugin = factory(bothMenus())(host.art)
plugin.update()
host.open('dash-quality')
const find = host.art.setting.find
let reads = 0
host.art.setting.find = (name) => {
if (++reads === 2)
plugin.update()
return find(name)
}
host.fire('qualityChangeRendered')
await flush()
assert.equal(host.renders.length, 0)
assert.equal(host.art.setting.active, host.art.setting.option)
})
test(`DASH ${sdk} (${name}): destruction from navigation lookup cannot reopen settings`, async () => {
const host = navigationHost(sdk)
factory(bothMenus())(host.art).update()
host.open('dash-quality')
const find = host.art.setting.find
let reads = 0
host.art.setting.find = (name) => {
const result = find(name)
if (++reads === 2)
host.art.destroy()
return result
}
host.fire('qualityChangeRendered')
await flush()
assert.equal(host.renders.length, 0)
})
}
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()