fix(dash): [PKG-DASH-CLEANUP-01] preserve first cleanup exception

This commit is contained in:
Harvey Zhao committed 2026-09-15 15:57:04 +08:00
1 parent 1c522f181b
commit 25faf88e80
23 files changed
+49546 -147

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,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
!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,e=()=>!0){let n,r=!1;for(const i of t){if(!e())break;try{i()}catch(o){r||(r=!0,n=o)}}if(r)throw n}function e(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 n(e,n,r){let o;const i=new Map;function s(t,r=i.get(t)){if(!r||i.get(t)!==r)return;i.delete(t);const o=e[t];"controls"===t&&o.cache?.get&&o.cache.get(n)?.option?.onSelect!==r||"setting"===t&&o.find&&o.find(n)?.onSelect!==r||o.remove(n)}function c(){o=void 0,t([...i].map(([t,e])=>()=>s(t,e)))}return{update:function(t,a,l){if(!a)return void c();const u={};o=u;const f=()=>u===o&&l(),d=n=>f()?(a.select(n,f),f()?(e.notice.show=`${a.title}: ${n.html}`,f()&&t.control&&e.controls.check(n),f()&&t.setting&&e.setting.check(n),n.html):n.html):n.html;t.control?(i.set("controls",d),e.controls.update({name:n,position:"right",html:a.html,style:{padding:"0 10px"},selector:a.selector,onSelect:d})):s("controls"),f()&&(t.setting?(i.set("setting",d),e.setting.update({name:n,tooltip:a.html,html:a.title,icon:r,width:200,selector:a.selector,onSelect:d})):s("setting"))},clear:c,captureNavigation:function(t){const r=e.setting,o=r.find?.(n),s=i.get("setting");if(t()&&s&&!0===r.show&&o?.onSelect===s&&o.selector&&r.active===o.selector&&"function"===typeof r.render)return()=>{if(!t()||e.setting!==r||!0!==r.show||!r.option||r.active!==r.option)return;const o=r.find?.(n),s=i.get("setting");t()&&s&&o?.onSelect===s&&o.selector&&r.render?.(o.selector)}}}}function r(e){let n;const r=t=>n===t&&e.active(t.dash),o=t=>t.dash.getSettings().streaming.abr.autoSwitchBitrate.video;function i(){const e=n;e&&(n=void 0,e.epoch++,t(e.callbacks.splice(0).map(([t,n])=>()=>e.off.call(e.dash,t,n))))}function s(t,r){if(n===t){try{i()}catch(o){console.warn("ArtPlayer DASH subscription cleanup failed:",o)}if(!n&&e.active(t.dash))try{e.reset()}catch(o){console.warn("ArtPlayer DASH cleanup failed:",o)}}console.warn("ArtPlayer DASH refresh failed:",r)}function c(t){if(!r(t)||t.suspended||t.queued||t.running)return;t.queued=!0;const n=t.epoch;Promise.resolve().then(()=>{if(r(t)&&t.epoch===n&&!t.suspended){t.queued=!1,t.running=!0;try{t.automatic=o(t),r(t)&&e.refresh()}catch(i){s(t,i)}finally{t.running=!1}}})}return{bind:function(t){const a=n;if(a?.dash===t){const t=o(a);return void(r(a)&&(a.automatic=t,a.suspended=!1))}if(i(),n||!e.active(t)||"function"!==typeof t.on||"function"!==typeof t.off)return;const l={dash:t,off:t.off,callbacks:[],epoch:0,queued:!1,running:!1,suspended:!1,automatic:!1};n=l;const u=t.on;try{l.automatic=o(l);const n=["qualityChangeRequested","qualityChangeRendered","trackChangeRendered","streamUpdated","streamInitialized"].map(t=>[t,()=>{"streamUpdated"!==t&&"streamInitialized"!==t||(l.suspended=!1),c(l)}]),i=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?.(),s=o?.getProcessors?.();if(!Array.isArray(s)||"function"!==typeof i?.getCurrentBufferLevel||"function"!==typeof i.addBufferLevel)return;for(const c of s){if(!e())return;const s=c.getType?.();if("audio"!==s&&"video"!==s)continue;const a=c.getBufferController?.();if("function"!==typeof a?.getRangeAt||"function"!==typeof a.getIsPruningInProgress||"function"!==typeof a.getAllRangesWithSafetyFactor)continue;const l=i.getCurrentBufferLevel(s),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(s,new Date,0)}}}catch(t){console.warn("ArtPlayer DASH seek buffer refresh failed:",t)}finally{r=!1}}}}(t,()=>r(l)&&!l.suspended);i&&n.push(["playbackSeeking",i]),n.push(["playbackTimeUpdated",()=>{if(r(l)&&!l.suspended)try{const t=o(l);r(l)&&t!==l.automatic&&(l.automatic=t,c(l))}catch(t){s(l,t)}}]),n.push(["streamTeardownComplete",()=>{if(r(l)){l.epoch++,l.queued=!1,l.suspended=!0;try{e.reset()}catch(t){s(l,t)}}}]);for(const e of n){if(!r(l))break;l.callbacks.push(e),u.call(t,...e)}}catch(f){if(n===l)try{i()}catch(d){console.warn("ArtPlayer DASH subscription cleanup failed:",d)}throw f}},release:i}}return function(oLine truncated
@@ -4,4 +4,4 @@
* (c) 2017-2026 Harvey Zhao
* Released under the MIT License.
*/
!function(t,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"==typeof define&&define.amd?(t.artplayerPluginDashControl=e(),define(function(){return t.artplayerPluginDashControl})):(t="undefined"!==typeof globalThis?globalThis:t||self).artplayerPluginDashControl=e()}(this,function(){"use strict";var t=Object.defineProperty,e=Object.getOwnPropertySymbols,n=Object.prototype.hasOwnProperty,o=Object.prototype.propertyIsEnumerable,r=(e,n,o)=>n in e?t(e,n,{enumerable:!0,configurable:!0,writable:!0,value:o}):e[n]=o,i=(t,i)=>{for(var l in i||(i={}))n.call(i,l)&&r(t,l,i[l]);if(e)for(var l of e(i))o.call(i,l)&&r(t,l,i[l]);return t};function l(t){const e=new Map;return t.filter(t=>{if(void 0===t.html)return!0;const n=e.get(t.html);return n?(t.default&&Object.assign(n,t),!1):(e.set(t.html,t),!0)})}function c(t,e,n){let o;const r=new Map;function i(n,o=r.get(n)){var i,l,c,a;if(!o||r.get(n)!==o)return;r.delete(n);const s=t[n];"controls"===n&&(null==(i=s.cache)?void 0:i.get)&&(null==(c=null==(l=s.cache.get(e))?void 0:l.option)?void 0:c.onSelect)!==o||"setting"===n&&s.find&&(null==(a=s.find(e))?void 0:a.onSelect)!==o||s.remove(e)}function l(){let t;o=void 0;for(const[n,o]of[...r])try{i(n,o)}catch(e){t||(t=e)}if(t)throw t}return{update:function(c,a,s){if(!a)return void l();const u={};o=u;const f=()=>u===o&&s(),d=e=>f()?(a.select(e,f),f()?(t.notice.show=`${a.title}: ${e.html}`,f()&&c.control&&t.controls.check(e),f()&&c.setting&&t.setting.check(e),e.html):e.html):e.html;c.control?(r.set("controls",d),t.controls.update({name:e,position:"right",html:a.html,style:{padding:"0 10px"},selector:a.selector,onSelect:d})):i("controls"),f()&&(c.setting?(r.set("setting",d),t.setting.update({name:e,tooltip:a.html,html:a.title,icon:n,width:200,selector:a.selector,onSelect:d})):i("setting"))},clear:l,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
!function(e,t){"object"===typeof exports&&"undefined"!==typeof module?module.exports=t():"function"==typeof define&&define.amd?(e.artplayerPluginDashControl=t(),define(function(){return e.artplayerPluginDashControl})):(e="undefined"!==typeof globalThis?globalThis:e||self).artplayerPluginDashControl=t()}(this,function(){"use strict";var e=Object.defineProperty,t=Object.getOwnPropertySymbols,n=Object.prototype.hasOwnProperty,o=Object.prototype.propertyIsEnumerable,r=(t,n,o)=>n in t?e(t,n,{enumerable:!0,configurable:!0,writable:!0,value:o}):t[n]=o,i=(e,i)=>{for(var l in i||(i={}))n.call(i,l)&&r(e,l,i[l]);if(t)for(var l of t(i))o.call(i,l)&&r(e,l,i[l]);return e};function l(e,t=()=>!0){let n,o=!1;for(const i of e){if(!t())break;try{i()}catch(r){o||(o=!0,n=r)}}if(o)throw n}function c(e){const t=new Map;return e.filter(e=>{if(void 0===e.html)return!0;const n=t.get(e.html);return n?(e.default&&Object.assign(n,e),!1):(t.set(e.html,e),!0)})}function a(e,t,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=e[n];"controls"===n&&(null==(i=s.cache)?void 0:i.get)&&(null==(c=null==(l=s.cache.get(t))?void 0:l.option)?void 0:c.onSelect)!==o||"setting"===n&&s.find&&(null==(a=s.find(t))?void 0:a.onSelect)!==o||s.remove(t)}function c(){o=void 0,l([...r].map(([e,t])=>()=>i(e,t)))}return{update:function(l,a,s){if(!a)return void c();const u={};o=u;const f=()=>u===o&&s(),d=t=>f()?(a.select(t,f),f()?(e.notice.show=`${a.title}: ${t.html}`,f()&&l.control&&e.controls.check(t),f()&&l.setting&&e.setting.check(t),t.html):t.html):t.html;l.control?(r.set("controls",d),e.controls.update({name:t,position:"right",html:a.html,style:{padding:"0 10px"},selector:a.selector,onSelect:d})):i("controls"),f()&&(l.setting?(r.set("setting",d),e.setting.update({name:t,tooltip:a.html,html:a.title,icon:n,width:200,selector:a.selector,onSelect:d})):i("setting"))},clear:c,captureNavigation:function(n){var o;const i=e.setting,l=null==(o=i.find)?void 0:o.call(i,t),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()||e.setting!==i||!0!==i.show||!i.option||i.active!==i.option)return;const c=null==(o=i.find)?void 0:o.call(i,t),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 s(e){let t;const n=n=>t===n&&e.active(n.dash),o=e=>e.dash.getSettings().streaming.abr.autoSwitchBitrate.video;function r(){const e=t;e&&(t=void 0,e.epoch++,l(e.callbacks.splice(0).map(([t,n])=>()=>e.off.call(e.dash,t,n))))}function i(n,o){if(t===n){try{r()}catch(i){console.warn("ArtPlayer DASH subscription cleanup failed:",i)}if(!t&&e.active(n.dash))try{e.reset()}catch(i){console.warn("ArtPlayer DASH cleanup failed:",i)}}console.warn("ArtPlayer DASH refresh failed:",o)}function c(t){if(!n(t)||t.suspended||t.queued||t.running)return;t.queued=!0;const r=t.epoch;Promise.resolve().then(()=>{if(n(t)&&t.epoch===r&&!t.suspended){t.queued=!1,t.running=!0;try{t.automatic=o(t),n(t)&&e.refresh()}catch(l){i(t,l)}finally{t.running=!1}}})}return{bind:function(l){const a=t;if((null==a?void 0:a.dash)===l){const e=o(a);return void(n(a)&&(a.automatic=e,a.suspended=!1))}if(r(),t||!e.active(l)||"function"!==typeof l.on||"function"!==typeof l.off)return;const s={dash:l,off:l.off,callbacks:[],epoch:0,queued:!1,running:!1,suspended:!1,automatic:!1};t=s;const u=l.on;try{s.automatic=o(s);const t=["qualityChangeRequested","qualityChangeRendered","trackChangeRendered","streamUpdated","streamInitialized"].map(e=>[e,()=>{"streamUpdated"!==e&&"streamInitialized"!==e||(s.suspended=!1),c(s)}]),r=function(e,t){const n=e;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 e,r,i,l,c,a,s,u,f;if(!o&&t()){o=!0;try{const o=null==(e=n.getVideoElement)?void 0:e.call(n);if(!(null==o?void 0:o.seeking)||!Number.isFinite(o.currentTime))return;const d=o.currentTime,g=null==(r=n.getActiveStream)?void 0:r.call(n),h=null==(i=n.getDashMetrics)?void 0:i.call(n),p=null==(l=null==g?void 0:g.getProcessors)?void 0:l.call(g);if(!Array.isArray(p)||"function"!==typeof(null==h?void 0:h.getCurrentBufferLevel)||"function"!==typeof h.addBufferLevel)return;for(const e of p){if(!t())return;const r=null==(c=e.getType)?void 0:c.call(e);if("audio"!==r&&"video"!==r)continue;const i=null==(a=e.getBufferController)?void 0:a.call(e);if("function"!==typeof(null==i?void 0:i.getRangeAt)||"function"!==typeof i.getIsPruningInProgress||"function"!==typeof i.getAllRangesWithSafetyFactor)continue;const l=h.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.lengLine truncated
+22 -42
View File
@@ -5,6 +5,24 @@
* Released under the MIT License.
*/
const $audio = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" height="18"><path fill="#fff" d="M256 80C149.9 80 62.4 159.4 49.6 262c9.4-3.8 19.6-6 30.4-6c26.5 0 48 21.5 48 48l0 128c0 26.5-21.5 48-48 48c-44.2 0-80-35.8-80-80l0-16 0-48 0-48C0 146.6 114.6 32 256 32s256 114.6 256 256l0 48 0 48 0 16c0 44.2-35.8 80-80 80c-26.5 0-48-21.5-48-48l0-128c0-26.5 21.5-48 48-48c10.8 0 21 2.1 30.4 6C449.6 159.4 362.1 80 256 80z"/></svg>';
function runCleanups(actions, current = () => true) {
let failed = false;
let failure;
for (const action of actions) {
if (!current())
break;
try {
action();
} catch (error) {
if (!failed) {
failed = true;
failure = error;
}
}
}
if (failed)
throw failure;
}
function qualityAdapter(dash) {
const modern = typeof dash.getRepresentationsByType === "function";
return {
@@ -120,16 +138,7 @@ function createMenu(art, name, icon) {
}
function clear() {
current = void 0;
let failure;
for (const [surface, callback] of [...owned]) {
try {
remove(surface, callback);
} catch (error) {
failure || (failure = error);
}
}
if (failure)
throw failure;
runCleanups([...owned].map(([surface, callback]) => () => remove(surface, callback)));
}
function update(config, model, active) {
if (!model) {
@@ -248,16 +257,7 @@ function observeSDK(options) {
return;
binding = void 0;
record.epoch++;
let failure;
for (const [name, callback] of record.callbacks.splice(0)) {
try {
record.off.call(record.dash, name, callback);
} catch (error) {
failure || (failure = error);
}
}
if (failure)
throw failure;
runCleanups(record.callbacks.splice(0).map(([name, callback]) => () => record.off.call(record.dash, name, callback)));
}
function fail(record, error) {
if (binding === record) {
@@ -398,18 +398,7 @@ function artplayerPluginDashControl(option = {}) {
}
});
function clear(current = () => true) {
let failure;
for (const cleanup of [quality.clear, audio.clear]) {
if (!current())
break;
try {
cleanup();
} catch (error) {
failure || (failure = error);
}
}
if (failure)
throw failure;
runCleanups([quality.clear, audio.clear], current);
}
function update() {
if (closed || art.isDestroy)
@@ -455,17 +444,8 @@ function artplayerPluginDashControl(option = {}) {
return;
closed = true;
revision++;
let failure;
const actions = [observer.release, clear, ...subscriptions.splice(0).map(([name, callback]) => () => art.off(name, callback))];
for (const action of actions) {
try {
action();
} catch (error) {
failure || (failure = error);
}
}
if (failure)
throw failure;
runCleanups(actions);
}
try {
const entries = [["ready", update], ["restart", update], ["destroy", destroy]];
+2 -2
View File
@@ -37,7 +37,7 @@
"test:ads": "node --test test/ads.test.js test/ads-lifecycle.test.js",
"trans:docs": "node ./scripts/trans-docs.js",
"create:plugin": "node ./scripts/plugin/create.js",
"test:dash-control": "node --test test/dash-control.test.js test/dash-contract.test.js test/dash-lifecycle.test.js test/dash-events.test.js",
"test:dash-control": "node --test test/dash-control.test.js test/dash-contract.test.js test/dash-lifecycle.test.js test/dash-events.test.js test/dash-cleanup-errors.test.js",
"dev": "npx cross-env NODE_ENV=development node ./scripts/dev.js",
"build": "npx cross-env NODE_ENV=production node ./scripts/build.js",
"lint": "eslint \"packages/*/{src,public,types,package.json}\" \"scripts/*.{js,mjs}\" \"packages/artplayer-vitepress/{browser,build}/**/*.ts\" \"scripts/{docs-smoke,editor-declarations,documentation,site-build,library,pages,site-vendor,browser-validation}/**/*.ts\" \"scripts/plugin/*.{js,ts}\" \"test/*\" \"docs/assets/ts/*\" \"types/*.d.ts\" \"playwright*.config.js\" --no-fix",
@@ -51,7 +51,7 @@
"ci:build": "yarn build:types && yarn build all && yarn build:i18n && yarn build:ts && yarn build:llm && yarn build:vconsole && yarn build:console && yarn build:site-notices && yarn build:test && yarn build:docs && yarn test:imports",
"test:imports": "node --test test/esm.test.js test/i18n.test.js test/ssr.test.js test/asr-distribution.test.js",
"typecheck": "node scripts/typecheck.mjs",
"test:unit": "node --test test/danmuku-setting.test.js test/danmuku-heatmap.test.js test/danmuku-renderer.test.js test/danmuku-scheduler.test.js test/danmuku-worker-client.test.js test/danmuku-input.test.js test/danmuku-parser.test.js test/danmuku-failures.test.js test/danmuku-mask-failures.test.js test/danmuku-mask-lifecycle.test.js test/asr-encoding.test.js test/asr-lifecycle.test.js test/asr-audio.test.js test/asr.test.js test/jassub.test.js test/jassub-registration.test.js test/jassub-runtime.test.js test/jassub-vendor-lifecycle.test.js test/jassub-frame-polyfill.test.js test/jassub-offscreen.test.js test/multiple-subtitles-ass.test.js test/multiple-subtitles-caption.test.js test/multiple-subtitles-merge.test.js test/multiple-subtitles-entities.test.js test/multiple-subtitles-lifecycle.test.js test/multiple-subtitles-failures.test.js test/multiple-subtitles.test.js test/multiple-subtitles-vendor.test.js test/vtt-thumbnail-parser.test.js test/vtt-thumbnail-lifecycle.test.js test/vtt-thumbnail.test.js test/auto-thumbnail-exports.test.js test/auto-thumbnail-canvas.test.js test/auto-thumbnail-frames.test.js test/auto-thumbnail-encoding.test.js test/auto-thumbnail-metadata.test.js test/auto-thumbnail-lifecycle.test.js test/auto-thumbnail.test.js test/thumbnail-emitter.test.js test/thumbnail-runtime.test.js test/thumbnail-vendor.test.js test/thumbnail-lifecycle.test.js test/thumbnail-input.test.js test/thumbnail.test.js test/iframe-navigation.test.js test/iframe-boundaries.test.js test/iframe-lifecycle.test.js test/iframe.test.js test/mediabunny.test.js test/mediabunny-shim.test.js test/mediabunny-coordination.test.js test/mediabunny-video.test.js test/mediabunny-audio.test.js test/mediabunny-hls.test.js test/mediabunny-entry.test.js test/mediabunny-capability.test.js test/mediabunny-load.test.js test/mediabunny-input.test.js test/dpip.test.js test/dpip-lifecycle.test.js test/canvas.test.js test/canvas-lifecycle.test.js test/ambilight.test.js test/ambilight-lifecycle.test.js test/ambilight-proxy.test.js test/vast-exports.test.js test/vast.test.js test/vast-lifecycle.test.js test/vast-compatibility.test.js test/ads.test.js test/ads-lifecycle.test.js test/playback.test.js test/dash-control.test.js test/dash-contract.test.js test/dash-lifecycle.test.js test/dash-events.test.js test/hls-control.test.js test/audio-track.test.js test/public-behavior.test.js test/helpers.test.js test/chapter.test.js test/utils.test.js test/resource-scope.test.js test/instance-lifecycle.test.js test/options.test.js test/media-hosts.test.js test/plugins.test.js test/source.test.js test/playback-properties.test.js test/media-events.test.js test/template-resources.test.js test/core-vendor.test.js test/component-resources.test.js test/setting-model.test.js test/setting-layout.test.js test/setting-resources.test.js test/subtitle.test.js test/display-native.test.js test/display-video-fullscreen.test.js test/display-pip.test.js test/display-mini.test.js test/display-sizing.test.js test/display-orientation.test.js test/hotkey.test.js test/listener-registry.test.js test/global-events.test.js test/pointer-events.test.js test/gesture.test.js test/event-scheduling.test.js test/notice.test.js test/fast-forward.test.js test/auto-playback.test.js test/builtin-layers.test.js test/prompt-components.test.js test/screenshot.test.js test/thumbnails.test.js test/progress.test.js test/environment.test.js test/storage.test.js test/facade-properties.test.js test/dom-boundaries.test.js test/initialization.test.js test/entry.test.js test/accessibility-button.test.js test/accessibility-focus.test.js test/accessibility-slider.test.js test/asr-fallback-routing.test.js test/asr-local-example.test.js test/asr-explicit-capture.test.js test/chromecast.test.js test/chromecast-failures.test.js test/chromecast-runtime.test.js test/asr-media-error.test.js test/thumbnail-cleanup-errors.test.js",
"test:unit": "node --test test/danmuku-setting.test.js test/danmuku-heatmap.test.js test/danmuku-renderer.test.js test/danmuku-scheduler.test.js test/danmuku-worker-client.test.js test/danmuku-input.test.js test/danmuku-parser.test.js test/danmuku-failures.test.js test/danmuku-mask-failures.test.js test/danmuku-mask-lifecycle.test.js test/asr-encoding.test.js test/asr-lifecycle.test.js test/asr-audio.test.js test/asr.test.js test/jassub.test.js test/jassub-registration.test.js test/jassub-runtime.test.js test/jassub-vendor-lifecycle.test.js test/jassub-frame-polyfill.test.js test/jassub-offscreen.test.js test/multiple-subtitles-ass.test.js test/multiple-subtitles-caption.test.js test/multiple-subtitles-merge.test.js test/multiple-subtitles-entities.test.js test/multiple-subtitles-lifecycle.test.js test/multiple-subtitles-failures.test.js test/multiple-subtitles.test.js test/multiple-subtitles-vendor.test.js test/vtt-thumbnail-parser.test.js test/vtt-thumbnail-lifecycle.test.js test/vtt-thumbnail.test.js test/auto-thumbnail-exports.test.js test/auto-thumbnail-canvas.test.js test/auto-thumbnail-frames.test.js test/auto-thumbnail-encoding.test.js test/auto-thumbnail-metadata.test.js test/auto-thumbnail-lifecycle.test.js test/auto-thumbnail.test.js test/thumbnail-emitter.test.js test/thumbnail-runtime.test.js test/thumbnail-vendor.test.js test/thumbnail-lifecycle.test.js test/thumbnail-input.test.js test/thumbnail.test.js test/iframe-navigation.test.js test/iframe-boundaries.test.js test/iframe-lifecycle.test.js test/iframe.test.js test/mediabunny.test.js test/mediabunny-shim.test.js test/mediabunny-coordination.test.js test/mediabunny-video.test.js test/mediabunny-audio.test.js test/mediabunny-hls.test.js test/mediabunny-entry.test.js test/mediabunny-capability.test.js test/mediabunny-load.test.js test/mediabunny-input.test.js test/dpip.test.js test/dpip-lifecycle.test.js test/canvas.test.js test/canvas-lifecycle.test.js test/ambilight.test.js test/ambilight-lifecycle.test.js test/ambilight-proxy.test.js test/vast-exports.test.js test/vast.test.js test/vast-lifecycle.test.js test/vast-compatibility.test.js test/ads.test.js test/ads-lifecycle.test.js test/playback.test.js test/dash-control.test.js test/dash-contract.test.js test/dash-lifecycle.test.js test/dash-events.test.js test/hls-control.test.js test/audio-track.test.js test/public-behavior.test.js test/helpers.test.js test/chapter.test.js test/utils.test.js test/resource-scope.test.js test/instance-lifecycle.test.js test/options.test.js test/media-hosts.test.js test/plugins.test.js test/source.test.js test/playback-properties.test.js test/media-events.test.js test/template-resources.test.js test/core-vendor.test.js test/component-resources.test.js test/setting-model.test.js test/setting-layout.test.js test/setting-resources.test.js test/subtitle.test.js test/display-native.test.js test/display-video-fullscreen.test.js test/display-pip.test.js test/display-mini.test.js test/display-sizing.test.js test/display-orientation.test.js test/hotkey.test.js test/listener-registry.test.js test/global-events.test.js test/pointer-events.test.js test/gesture.test.js test/event-scheduling.test.js test/notice.test.js test/fast-forward.test.js test/auto-playback.test.js test/builtin-layers.test.js test/prompt-components.test.js test/screenshot.test.js test/thumbnails.test.js test/progress.test.js test/environment.test.js test/storage.test.js test/facade-properties.test.js test/dom-boundaries.test.js test/initialization.test.js test/entry.test.js test/accessibility-button.test.js test/accessibility-focus.test.js test/accessibility-slider.test.js test/asr-fallback-routing.test.js test/asr-local-example.test.js test/asr-explicit-capture.test.js test/chromecast.test.js test/chromecast-failures.test.js test/chromecast-runtime.test.js test/asr-media-error.test.js test/thumbnail-cleanup-errors.test.js test/dash-cleanup-errors.test.js",
"test:coverage": "node --test test/coverage.test.js && node scripts/coverage.mjs",
"test": "yarn test:node && yarn test:baseline",
"test:browser": "playwright test",
@@ -26,6 +26,7 @@ while each submenu is open and then select another option through normal clicks.
| `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/cleanup.ts` | Ordered cleanup, revision guards and first thrown value preservation |
| `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 |
@@ -36,7 +37,7 @@ while each submenu is open and then select another option through normal clicks.
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 seven owned modules are checked with strict
callbacks and owns no DOM or ArtPlayer object. All eight owned modules are checked with strict
TypeScript, noUncheckedIndexedAccess, and skipLibCheck=false; there are no remaining
owned JavaScript modules in this package.
@@ -98,6 +99,22 @@ or destroy an SDK to handle a plugin rendering failure.
## Types and compatibility
`cleanup.ts` is shared by menu removal, SDK unsubscription and entry teardown.
It records whether an action threw separately from the thrown value, so `undefined`,
`null`, `false`, zero (including negative zero), empty strings and `NaN` propagate
unchanged. Remaining owned cleanup actions still run; a newer update revision stops
stale menu cleanup before the next action. Later failures cannot replace the first.
This does not change the observer's asynchronous warning policy or make the caller's
SDK plugin-owned. A caller's throwing `beforeUnmount` can prevent that particular
core control from being removed; the plugin releases its sibling surfaces and
propagates the original error rather than bypassing the core callback contract.
`test/dash-cleanup-errors.test.js` covers both SDK method generations, all seven
values, secondary errors, complete unsubscription and repeated destruction. The
browser regression uses public control updates with fresh selector items: core
selector items acquire non-configurable DOM references and must not be reused as
new input objects. It exercises actual old/new core `beforeUnmount` callbacks.
`src/types.ts` describes the narrow SDK and registry surfaces, menu models, event
names, labels, and cleanup callbacks. SDK keys may be absent in malformed external
data; the internal types retain that possibility instead of asserting every value
@@ -155,8 +172,11 @@ legacy fixture is checked against the actual published declaration and candidate
`test:dash-types-package` packs the current core and DASH with Yarn, installs the
tarballs offline outside the workspace, repeats a frozen install and checks all
public type modes without resolving any workspace source. It also verifies that
implementation files/config do not leak into the archive. This is type acceptance;
the full installed runtime and SDK release matrix still belongs to PKG-DASH-06/05.
implementation files/config do not leak into the archive. This is type acceptance.
Verified installed files are retained under the report's `installed-artifacts/`
directory for subsequent runtime/browser checks; every retained file is checked
against its tarball hash before the temporary outside-workspace consumer is removed.
The full installed runtime and SDK release matrix still belongs to PKG-DASH-06/05.
`dash-control.spec.js` tests exercise real old/new core DOM, menu clicks, native MP4 and cleanup,
with controlled SDK methods. They do not load dash.js or test MPD/ABR decoding.
PKG-DASH-05 must verify pinned dash.js 4.5.2 and 5.2.1 with real adaptive media and
@@ -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,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
!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,e=()=>!0){let n,r=!1;for(const i of t){if(!e())break;try{i()}catch(o){r||(r=!0,n=o)}}if(r)throw n}function e(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 n(e,n,r){let o;const i=new Map;function s(t,r=i.get(t)){if(!r||i.get(t)!==r)return;i.delete(t);const o=e[t];"controls"===t&&o.cache?.get&&o.cache.get(n)?.option?.onSelect!==r||"setting"===t&&o.find&&o.find(n)?.onSelect!==r||o.remove(n)}function c(){o=void 0,t([...i].map(([t,e])=>()=>s(t,e)))}return{update:function(t,a,l){if(!a)return void c();const u={};o=u;const f=()=>u===o&&l(),d=n=>f()?(a.select(n,f),f()?(e.notice.show=`${a.title}: ${n.html}`,f()&&t.control&&e.controls.check(n),f()&&t.setting&&e.setting.check(n),n.html):n.html):n.html;t.control?(i.set("controls",d),e.controls.update({name:n,position:"right",html:a.html,style:{padding:"0 10px"},selector:a.selector,onSelect:d})):s("controls"),f()&&(t.setting?(i.set("setting",d),e.setting.update({name:n,tooltip:a.html,html:a.title,icon:r,width:200,selector:a.selector,onSelect:d})):s("setting"))},clear:c,captureNavigation:function(t){const r=e.setting,o=r.find?.(n),s=i.get("setting");if(t()&&s&&!0===r.show&&o?.onSelect===s&&o.selector&&r.active===o.selector&&"function"===typeof r.render)return()=>{if(!t()||e.setting!==r||!0!==r.show||!r.option||r.active!==r.option)return;const o=r.find?.(n),s=i.get("setting");t()&&s&&o?.onSelect===s&&o.selector&&r.render?.(o.selector)}}}}function r(e){let n;const r=t=>n===t&&e.active(t.dash),o=t=>t.dash.getSettings().streaming.abr.autoSwitchBitrate.video;function i(){const e=n;e&&(n=void 0,e.epoch++,t(e.callbacks.splice(0).map(([t,n])=>()=>e.off.call(e.dash,t,n))))}function s(t,r){if(n===t){try{i()}catch(o){console.warn("ArtPlayer DASH subscription cleanup failed:",o)}if(!n&&e.active(t.dash))try{e.reset()}catch(o){console.warn("ArtPlayer DASH cleanup failed:",o)}}console.warn("ArtPlayer DASH refresh failed:",r)}function c(t){if(!r(t)||t.suspended||t.queued||t.running)return;t.queued=!0;const n=t.epoch;Promise.resolve().then(()=>{if(r(t)&&t.epoch===n&&!t.suspended){t.queued=!1,t.running=!0;try{t.automatic=o(t),r(t)&&e.refresh()}catch(i){s(t,i)}finally{t.running=!1}}})}return{bind:function(t){const a=n;if(a?.dash===t){const t=o(a);return void(r(a)&&(a.automatic=t,a.suspended=!1))}if(i(),n||!e.active(t)||"function"!==typeof t.on||"function"!==typeof t.off)return;const l={dash:t,off:t.off,callbacks:[],epoch:0,queued:!1,running:!1,suspended:!1,automatic:!1};n=l;const u=t.on;try{l.automatic=o(l);const n=["qualityChangeRequested","qualityChangeRendered","trackChangeRendered","streamUpdated","streamInitialized"].map(t=>[t,()=>{"streamUpdated"!==t&&"streamInitialized"!==t||(l.suspended=!1),c(l)}]),i=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?.(),s=o?.getProcessors?.();if(!Array.isArray(s)||"function"!==typeof i?.getCurrentBufferLevel||"function"!==typeof i.addBufferLevel)return;for(const c of s){if(!e())return;const s=c.getType?.();if("audio"!==s&&"video"!==s)continue;const a=c.getBufferController?.();if("function"!==typeof a?.getRangeAt||"function"!==typeof a.getIsPruningInProgress||"function"!==typeof a.getAllRangesWithSafetyFactor)continue;const l=i.getCurrentBufferLevel(s),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(s,new Date,0)}}}catch(t){console.warn("ArtPlayer DASH seek buffer refresh failed:",t)}finally{r=!1}}}}(t,()=>r(l)&&!l.suspended);i&&n.push(["playbackSeeking",i]),n.push(["playbackTimeUpdated",()=>{if(r(l)&&!l.suspended)try{const t=o(l);r(l)&&t!==l.automatic&&(l.automatic=t,c(l))}catch(t){s(l,t)}}]),n.push(["streamTeardownComplete",()=>{if(r(l)){l.epoch++,l.queued=!1,l.suspended=!0;try{e.reset()}catch(t){s(l,t)}}}]);for(const e of n){if(!r(l))break;l.callbacks.push(e),u.call(t,...e)}}catch(f){if(n===l)try{i()}catch(d){console.warn("ArtPlayer DASH subscription cleanup failed:",d)}throw f}},release:i}}return function(oLine truncated
@@ -4,4 +4,4 @@
* (c) 2017-2026 Harvey Zhao
* Released under the MIT License.
*/
!function(t,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"==typeof define&&define.amd?(t.artplayerPluginDashControl=e(),define(function(){return t.artplayerPluginDashControl})):(t="undefined"!==typeof globalThis?globalThis:t||self).artplayerPluginDashControl=e()}(this,function(){"use strict";var t=Object.defineProperty,e=Object.getOwnPropertySymbols,n=Object.prototype.hasOwnProperty,o=Object.prototype.propertyIsEnumerable,r=(e,n,o)=>n in e?t(e,n,{enumerable:!0,configurable:!0,writable:!0,value:o}):e[n]=o,i=(t,i)=>{for(var l in i||(i={}))n.call(i,l)&&r(t,l,i[l]);if(e)for(var l of e(i))o.call(i,l)&&r(t,l,i[l]);return t};function l(t){const e=new Map;return t.filter(t=>{if(void 0===t.html)return!0;const n=e.get(t.html);return n?(t.default&&Object.assign(n,t),!1):(e.set(t.html,t),!0)})}function c(t,e,n){let o;const r=new Map;function i(n,o=r.get(n)){var i,l,c,a;if(!o||r.get(n)!==o)return;r.delete(n);const s=t[n];"controls"===n&&(null==(i=s.cache)?void 0:i.get)&&(null==(c=null==(l=s.cache.get(e))?void 0:l.option)?void 0:c.onSelect)!==o||"setting"===n&&s.find&&(null==(a=s.find(e))?void 0:a.onSelect)!==o||s.remove(e)}function l(){let t;o=void 0;for(const[n,o]of[...r])try{i(n,o)}catch(e){t||(t=e)}if(t)throw t}return{update:function(c,a,s){if(!a)return void l();const u={};o=u;const f=()=>u===o&&s(),d=e=>f()?(a.select(e,f),f()?(t.notice.show=`${a.title}: ${e.html}`,f()&&c.control&&t.controls.check(e),f()&&c.setting&&t.setting.check(e),e.html):e.html):e.html;c.control?(r.set("controls",d),t.controls.update({name:e,position:"right",html:a.html,style:{padding:"0 10px"},selector:a.selector,onSelect:d})):i("controls"),f()&&(c.setting?(r.set("setting",d),t.setting.update({name:e,tooltip:a.html,html:a.title,icon:n,width:200,selector:a.selector,onSelect:d})):i("setting"))},clear:l,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
!function(e,t){"object"===typeof exports&&"undefined"!==typeof module?module.exports=t():"function"==typeof define&&define.amd?(e.artplayerPluginDashControl=t(),define(function(){return e.artplayerPluginDashControl})):(e="undefined"!==typeof globalThis?globalThis:e||self).artplayerPluginDashControl=t()}(this,function(){"use strict";var e=Object.defineProperty,t=Object.getOwnPropertySymbols,n=Object.prototype.hasOwnProperty,o=Object.prototype.propertyIsEnumerable,r=(t,n,o)=>n in t?e(t,n,{enumerable:!0,configurable:!0,writable:!0,value:o}):t[n]=o,i=(e,i)=>{for(var l in i||(i={}))n.call(i,l)&&r(e,l,i[l]);if(t)for(var l of t(i))o.call(i,l)&&r(e,l,i[l]);return e};function l(e,t=()=>!0){let n,o=!1;for(const i of e){if(!t())break;try{i()}catch(r){o||(o=!0,n=r)}}if(o)throw n}function c(e){const t=new Map;return e.filter(e=>{if(void 0===e.html)return!0;const n=t.get(e.html);return n?(e.default&&Object.assign(n,e),!1):(t.set(e.html,e),!0)})}function a(e,t,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=e[n];"controls"===n&&(null==(i=s.cache)?void 0:i.get)&&(null==(c=null==(l=s.cache.get(t))?void 0:l.option)?void 0:c.onSelect)!==o||"setting"===n&&s.find&&(null==(a=s.find(t))?void 0:a.onSelect)!==o||s.remove(t)}function c(){o=void 0,l([...r].map(([e,t])=>()=>i(e,t)))}return{update:function(l,a,s){if(!a)return void c();const u={};o=u;const f=()=>u===o&&s(),d=t=>f()?(a.select(t,f),f()?(e.notice.show=`${a.title}: ${t.html}`,f()&&l.control&&e.controls.check(t),f()&&l.setting&&e.setting.check(t),t.html):t.html):t.html;l.control?(r.set("controls",d),e.controls.update({name:t,position:"right",html:a.html,style:{padding:"0 10px"},selector:a.selector,onSelect:d})):i("controls"),f()&&(l.setting?(r.set("setting",d),e.setting.update({name:t,tooltip:a.html,html:a.title,icon:n,width:200,selector:a.selector,onSelect:d})):i("setting"))},clear:c,captureNavigation:function(n){var o;const i=e.setting,l=null==(o=i.find)?void 0:o.call(i,t),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()||e.setting!==i||!0!==i.show||!i.option||i.active!==i.option)return;const c=null==(o=i.find)?void 0:o.call(i,t),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 s(e){let t;const n=n=>t===n&&e.active(n.dash),o=e=>e.dash.getSettings().streaming.abr.autoSwitchBitrate.video;function r(){const e=t;e&&(t=void 0,e.epoch++,l(e.callbacks.splice(0).map(([t,n])=>()=>e.off.call(e.dash,t,n))))}function i(n,o){if(t===n){try{r()}catch(i){console.warn("ArtPlayer DASH subscription cleanup failed:",i)}if(!t&&e.active(n.dash))try{e.reset()}catch(i){console.warn("ArtPlayer DASH cleanup failed:",i)}}console.warn("ArtPlayer DASH refresh failed:",o)}function c(t){if(!n(t)||t.suspended||t.queued||t.running)return;t.queued=!0;const r=t.epoch;Promise.resolve().then(()=>{if(n(t)&&t.epoch===r&&!t.suspended){t.queued=!1,t.running=!0;try{t.automatic=o(t),n(t)&&e.refresh()}catch(l){i(t,l)}finally{t.running=!1}}})}return{bind:function(l){const a=t;if((null==a?void 0:a.dash)===l){const e=o(a);return void(n(a)&&(a.automatic=e,a.suspended=!1))}if(r(),t||!e.active(l)||"function"!==typeof l.on||"function"!==typeof l.off)return;const s={dash:l,off:l.off,callbacks:[],epoch:0,queued:!1,running:!1,suspended:!1,automatic:!1};t=s;const u=l.on;try{s.automatic=o(s);const t=["qualityChangeRequested","qualityChangeRendered","trackChangeRendered","streamUpdated","streamInitialized"].map(e=>[e,()=>{"streamUpdated"!==e&&"streamInitialized"!==e||(s.suspended=!1),c(s)}]),r=function(e,t){const n=e;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 e,r,i,l,c,a,s,u,f;if(!o&&t()){o=!0;try{const o=null==(e=n.getVideoElement)?void 0:e.call(n);if(!(null==o?void 0:o.seeking)||!Number.isFinite(o.currentTime))return;const d=o.currentTime,g=null==(r=n.getActiveStream)?void 0:r.call(n),h=null==(i=n.getDashMetrics)?void 0:i.call(n),p=null==(l=null==g?void 0:g.getProcessors)?void 0:l.call(g);if(!Array.isArray(p)||"function"!==typeof(null==h?void 0:h.getCurrentBufferLevel)||"function"!==typeof h.addBufferLevel)return;for(const e of p){if(!t())return;const r=null==(c=e.getType)?void 0:c.call(e);if("audio"!==r&&"video"!==r)continue;const i=null==(a=e.getBufferController)?void 0:a.call(e);if("function"!==typeof(null==i?void 0:i.getRangeAt)||"function"!==typeof i.getIsPruningInProgress||"function"!==typeof i.getAllRangesWithSafetyFactor)continue;const l=h.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.lengLine truncated
@@ -5,6 +5,24 @@
* Released under the MIT License.
*/
const $audio = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" height="18"><path fill="#fff" d="M256 80C149.9 80 62.4 159.4 49.6 262c9.4-3.8 19.6-6 30.4-6c26.5 0 48 21.5 48 48l0 128c0 26.5-21.5 48-48 48c-44.2 0-80-35.8-80-80l0-16 0-48 0-48C0 146.6 114.6 32 256 32s256 114.6 256 256l0 48 0 48 0 16c0 44.2-35.8 80-80 80c-26.5 0-48-21.5-48-48l0-128c0-26.5 21.5-48 48-48c10.8 0 21 2.1 30.4 6C449.6 159.4 362.1 80 256 80z"/></svg>';
function runCleanups(actions, current = () => true) {
let failed = false;
let failure;
for (const action of actions) {
if (!current())
break;
try {
action();
} catch (error) {
if (!failed) {
failed = true;
failure = error;
}
}
}
if (failed)
throw failure;
}
function qualityAdapter(dash) {
const modern = typeof dash.getRepresentationsByType === "function";
return {
@@ -120,16 +138,7 @@ function createMenu(art, name, icon) {
}
function clear() {
current = void 0;
let failure;
for (const [surface, callback] of [...owned]) {
try {
remove(surface, callback);
} catch (error) {
failure || (failure = error);
}
}
if (failure)
throw failure;
runCleanups([...owned].map(([surface, callback]) => () => remove(surface, callback)));
}
function update(config, model, active) {
if (!model) {
@@ -248,16 +257,7 @@ function observeSDK(options) {
return;
binding = void 0;
record.epoch++;
let failure;
for (const [name, callback] of record.callbacks.splice(0)) {
try {
record.off.call(record.dash, name, callback);
} catch (error) {
failure || (failure = error);
}
}
if (failure)
throw failure;
runCleanups(record.callbacks.splice(0).map(([name, callback]) => () => record.off.call(record.dash, name, callback)));
}
function fail(record, error) {
if (binding === record) {
@@ -398,18 +398,7 @@ function artplayerPluginDashControl(option = {}) {
}
});
function clear(current = () => true) {
let failure;
for (const cleanup of [quality.clear, audio.clear]) {
if (!current())
break;
try {
cleanup();
} catch (error) {
failure || (failure = error);
}
}
if (failure)
throw failure;
runCleanups([quality.clear, audio.clear], current);
}
function update() {
if (closed || art.isDestroy)
@@ -455,17 +444,8 @@ function artplayerPluginDashControl(option = {}) {
return;
closed = true;
revision++;
let failure;
const actions = [observer.release, clear, ...subscriptions.splice(0).map(([name, callback]) => () => art.off(name, callback))];
for (const action of actions) {
try {
action();
} catch (error) {
failure || (failure = error);
}
}
if (failure)
throw failure;
runCleanups(actions);
}
try {
const entries = [["ready", update], ["restart", update], ["destroy", destroy]];
@@ -0,0 +1,21 @@
import type { Cleanup, Valid } from './types'
export function runCleanups(actions: Cleanup[], current: Valid = () => true): void {
let failed = false
let failure: unknown
for (const action of actions) {
if (!current())
break
try {
action()
}
catch (error) {
if (!failed) {
failed = true
failure = error
}
}
}
if (failed)
throw failure
}
@@ -2,6 +2,7 @@ import type Artplayer from 'artplayer'
import type { AudioTrack, Option, QualityLevel, Result } from '../types/artplayer-plugin-dash-control'
import type { AudioItem, Cleanup, EventName, Host, QualityItem, Valid } from './types'
import $audio from './audio.svg?raw'
import { runCleanups } from './cleanup'
import { audioModel, qualityModel } from './mapping'
import { createMenu } from './menu'
import $quality from './quality.svg?raw'
@@ -39,19 +40,7 @@ export default function artplayerPluginDashControl<Level extends object = Qualit
})
function clear(current: Valid = () => true): void {
let failure: unknown
for (const cleanup of [quality.clear, audio.clear]) {
if (!current())
break
try {
cleanup()
}
catch (error) {
failure ||= error
}
}
if (failure)
throw failure
runCleanups([quality.clear, audio.clear], current)
}
function update(): void {
@@ -101,18 +90,8 @@ export default function artplayerPluginDashControl<Level extends object = Qualit
return
closed = true
revision++
let failure: unknown
const actions = [observer.release, clear, ...subscriptions.splice(0).map(([name, callback]) => () => art.off(name, callback))]
for (const action of actions) {
try {
action()
}
catch (error) {
failure ||= error
}
}
if (failure)
throw failure
runCleanups(actions)
}
try {
@@ -1,4 +1,5 @@
import type { Cleanup, DisplayConfig, Label, MenuHost, MenuModel, SelectorItem, Valid } from './types'
import { runCleanups } from './cleanup'
export function createMenu<Item extends SelectorItem>(art: MenuHost, name: string, icon: string) {
let current: object | undefined
@@ -18,17 +19,7 @@ export function createMenu<Item extends SelectorItem>(art: MenuHost, name: strin
function clear(): void {
current = undefined
let failure: unknown
for (const [surface, callback] of [...owned]) {
try {
remove(surface, callback)
}
catch (error) {
failure ||= error
}
}
if (failure)
throw failure
runCleanups([...owned].map(([surface, callback]) => () => remove(surface, callback)))
}
function update(config: DisplayConfig, model: MenuModel<Item> | null, active: Valid): void {
@@ -1,4 +1,5 @@
import type { Cleanup, Dash, SDKEventName } from './types'
import { runCleanups } from './cleanup'
import { createSeekRecovery } from './seek-buffer'
interface Binding<Level extends object, Track extends object> {
@@ -27,17 +28,7 @@ export function observeSDK<Level extends object, Track extends object>(options:
return
binding = undefined
record.epoch++
let failure: unknown
for (const [name, callback] of record.callbacks.splice(0)) {
try {
record.off.call(record.dash, name, callback)
}
catch (error) {
failure ||= error
}
}
if (failure)
throw failure
runCleanups(record.callbacks.splice(0).map(([name, callback]) => () => record.off.call(record.dash, name, callback)))
}
function fail(record: Binding<Level, Track>, error: unknown): void {
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,71 @@
# PKG-DASH-CLEANUP-01 保留 DASH 清理的首个原始异常
来源 HEAD:`1c522f181be42f1c3fe5ce309cd42d85e58ac4dc`。
对应 API-04 异常传播、API-05 生命周期和 API-07 控件集成。
## 缺陷、结构与兼容边界
原来四处清理循环使用 `failure ||= error` 记录失败,并用 `if (failure)` 判断
是否抛出。用户通过公开 controls.update 注册的 beforeUnmount 抛出 undefined、
null、false、0、-0、空字符串或 NaN 时,这些值被吞掉,或被后续清理的 Error
覆盖。SDK off 的自定义包装抛出同类值也会受到影响。
新增私有 cleanup.ts,把菜单、SDK 订阅、整个插件的清理调度归到一个实现。
独立 failed 标记与 unknown 原值分别表示“发生异常”和“异常是什么”;继续
执行快照中其余清理,最后原样抛出第一个值。入口的 revision 检查继续在每个
菜单清理动作之前执行;SDK 绑定和菜单所有权仍先失效,防止重入及迟到回调。
八个模块的责任与维护入口在包内 ARCHITECTURE.md 同步说明。
不改公开工厂、同步 update、选项、DOM/CSS、类型及分发路径;没有新运行时依赖、
版本或锁文件变化。SDK 仍由调用方所有。异步 SDK 刷新失败继续清理并 warn;
同步 update/destroy 才沿原调用栈传播错误,未将其改成 Promise。
核心 beforeUnmount 自身的异常可阻止该控件删除。插件会清理其他菜单并传播
原值,不绕过核心回调删除这个控件,也不承诺出错后整个 ArtPlayer.destroy
仍然完成。重复插件销毁、失效回调和剩余监听器释放分别由回归覆盖。
## 复现与验证
新增六项源码回归在未修复候选上全部失败;修复后每项实际遍历七种值,用
独立 threw 标记和 Object.is 检查,区分未抛出/throw undefined、0/-0、NaN。
覆盖 update、destroy、SDK off、后续错误覆盖、兄弟资源释放和重复销毁。
浏览器首轮四项失败来自测试输入复用旧 selector 对象:核心已经在其上定义
不可重新定义的 $control_option。修正测试为传入新的 selector 对象后,仍使用
公开 controls.update 和 beforeUnmount;这次在未修复产物上四项 Chromium
回归均因异常被吞掉而失败。两份失败报告分别保留,首轮不计产品缺陷复现。
源码专项 167 项通过;加载仓库外实际安装并保留的 main/legacy/module 三种
产物后,加上源码及历史对照共 470 项通过。不是新增了 470 个独立回归。
严格包类型、定向 lint、三格式正常构建及严格工具链检查通过。
Yarn Classic 1.22.22 / Node 24.21.0;Yarn pack 后在仓库外离线安装,再冻结锁
重装并逐文件核对。5 组 TS 5.9.3/4.3.5 模块配置的正例零诊断,每组拒绝
8 项非法调用,共 40 个预期错误。脚本保留核对后的安装副本,支持随后直接
验证该份文件,而不重新构建冒充安装产物。
浏览器采用 Windows 上 Playwright 管理的 Chromium 153.0.8010.12、Firefox
155.0 和 WebKit 26.6。结果如下;没有用 Chrome 扩展连接或模拟 SDK 代替实播。
| 输入与范围 | 通过 | 跳过 |
| --- | --- | --- |
| 源码,新增真实核心回调回归 | 12 | 0 |
| 安装 main,完整受控 SDK/DOM/原生 MP4 套件 | 96 | 0 |
| 安装 legacy,同套件的候选插件场景 | 72 | 0 |
| 安装 main,真实 SDK 解码/UI、换源清理、异步错误恢复 | 24 | 12 |
| 安装 legacy,同组真实 SDK 场景 | 24 | 12 |
合计 228 项通过、24 项能力跳过,没有失败或重试;main 中额外的发布旧插件
对照不在 legacy 轮重复执行。三格式 dist、docs/compiled 与安装副本逐字节一致,
安装包 ARCHITECTURE.md 与当前包内文档也一致。根 test:dash-control/test:unit
已纳入新增回归。本轮浏览器和分发核对结果见
[机器证据](../baselines/dash-cleanup-errors-validation.json)。
真实 SDK 播放与受控 SDK 的 DOM 测试分开记录;Windows WebKit 缺失 MSE 的
真实 MPD 场景不能算播放通过。物理设备、完整安装/SDK 矩阵和发布验收仍由
PKG-DASH-05/06 负责。本轮没有推送、部署或发布。
## 回退与交付
本任务单独提交实现、测试、打包产物、文档、风险和任务状态。回退该提交会
恢复四个有缺陷的清理循环,并撤销本次回归和安装副本保留能力;不会撤销
此前 TS 迁移、SDK 自动刷新、菜单导航或 SDK 4.5.2 seek 恢复。
+6 -3
View File
@@ -2,14 +2,15 @@
测试保护真实 npm 1.1.0 的既有调用契约,以及重构前工作区的 SDK 5.x 稳定 ID 修正。
版本来源见 [发布契约](baselines/dash-control-contract.md)。02 建立基线,03 改造运行行为,
04 已把五个自有模块迁移为严格 TS 并补齐公开类型与编辑器生成;05 新增第六个 SDK 事件模块。
04 把五个自有模块迁移为严格 TS 并补齐公开类型与编辑器生成;后续增加 SDK 事件、
4.5.2 seek 恢复和清理调度,当前八个自有模块均为严格 TS。
实际模块地图与生命周期规则见 [包内架构](../packages/artplayer-plugin-dash-control/ARCHITECTURE.md)。
## 可重跑的测试
- `yarn test:dash-control`:原有 118 项与两代 SDK 各 13 项事件/清理/重入回归,共 144 项。
- `yarn test:dash-control`:当前 167 项,覆盖历史契约、两代 SDK、事件/清理/重入及原始异常。
该脚本和 `test:unit` 都保留旧测试,完整 CI 自动执行。
- `yarn test:browser test/browser/dash-control.spec.js --trace on`:84 项,两个核心、发布插件 SDK 4/
- `yarn test:browser test/browser/dash-control.spec.js --trace on`:96 项,两个核心、发布插件 SDK 4/
候选插件 SDK 4/5、两类菜单、两种选择及候选清理、Chromium/Firefox/WebKit。
浏览器套件串行运行,避免共享端口和报告覆盖。
- `yarn ci:check`:工具链、计划、lint、声明漂移、严格类型和 Node/基线测试。
@@ -19,6 +20,8 @@
TS 4.3.5/5.9.3、五种模块模式、八条非法用法及实际 npm 旧声明的消费者对照。
- `yarn test:dash-types-package`:Yarn 打包核心/DASH、工作区外离线安装与冻结锁重装,
检查五种模式、CommonJS callable/命名空间、每模式八条非法调用,并拒绝解析回工作区。
报告中的 `installedArtifacts` 保留已逐文件核对 tarball 哈希的安装副本,供后续
`ARTPLAYER_TEST_DASH` 和 `ARTPLAYER_DASH_ARTIFACT` 运行时验证。
- `yarn test:browser test/browser/dash-sdk.spec.js --trace on`:真实 npm dash.js 4.5.2/5.2.1,
本地多画质/多音轨 MPD,新旧核心/插件、外部同步 update、自动事件刷新、同 SDK 换源、
formatter 错误恢复、实际设置点击和 SDK 原生对照。目前完整定义 114 项。
+6 -4
View File
@@ -2,9 +2,9 @@
> 由 tasks.json 生成。请修改数据后运行 `node refactor/scripts/plan.mjs --write`,不要手改本表。
基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 268 项,范围 22 个包及工作区/示例。
基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 269 项,范围 22 个包及工作区/示例。
状态:todo 43 / doing 21 / blocked 0 / done 204 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
状态:todo 43 / doing 21 / blocked 0 / done 205 / 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, PKG-DANMUKU-MASK-LOAD-01 |
| 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, PKG-DANMUKU-MASK-LOAD-01 |
| 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-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, PKG-DASH-CLEANUP-01 |
| 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, PKG-HLS-PACK-01 |
| 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 |
@@ -222,8 +222,9 @@
| 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 | 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-05 | artplayer-plugin-dash-control<br>验证新旧核心和组合 | PKG-DASH-04, CORE-22, PKG-DASH-SEEK-01, PKG-DASH-CLEANUP-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 |
| PKG-DASH-CLEANUP-01 | artplayer-plugin-dash-control<br>保留菜单和SDK清理的首个原始异常 | PKG-DASH-04 | 集中DASH清理调度与首错传播,保留重入保护;falsy异常、真实控件beforeUnmount、分发及文档验证 | 七种falsy值不被吞掉或后续错误覆盖;后续资源释放和生命周期回归通过,不更改公开API、SDK归属或异步刷新边界 | M | done |
## 5 包迁移:artplayer-plugin-multiple-subtitles
@@ -702,3 +703,4 @@
- SITE-CONSOLE-01: [记录](console-modernization.md) [记录](changes/2026-09-15-SITE-CONSOLE-01-typescript.md) [记录](baselines/site-console01-validation.json)
- PKG-AUTO-THUMB-12: [记录](changes/2026-09-15-PKG-AUTO-THUMB-12-metadata.md) [记录](baselines/auto-thumbnail-metadata-validation.json)
- PKG-TOOL-THUMB-07: [记录](changes/2026-09-15-PKG-TOOL-THUMB-07-cleanup-errors.md) [记录](baselines/thumbnail-cleanup-errors-validation.json)
- PKG-DASH-CLEANUP-01: [记录](changes/2026-09-15-PKG-DASH-CLEANUP-01-errors.md) [记录](baselines/dash-cleanup-errors-validation.json)
+14
View File
@@ -1,5 +1,19 @@
# 进度与证据
## PKG-DASH-CLEANUP-01 清理异常与职责归一
四处清理循环改为私有 cleanup.ts,保留首个原始异常值、剩余资源释放和重入保护。
未修复源码6失败,修正测试输入后的未修复产物4项Chromium失败;首轮输入复用
导致的4项setup失败单独保留。修复后源码167通过,安装三格式与历史/源码共470通过。
source: 12通过/0跳过;main: 96通过/0跳过;legacy: 72通过/0跳过;sdk-main: 24通过/12跳过;sdk-legacy: 24通过/12跳过。
受控SDK的DOM验证与真实dash.js 4.5.2/5.2.1播放/换源/异步恢复分开记录;
Windows WebKit MSE缺失的跳过不计实际播放。5组隔离类型正例和40预期负例成立,
三格式、文档副本与安装文件字节一致。详见[记录](changes/2026-09-15-PKG-DASH-CLEANUP-01-errors.md)和[证据](baselines/dash-cleanup-errors-validation.json)。
任务完成,DASH-05继续doing;205 done、21 doing、43 todo,共269项。
无新依赖、公开类型、版本或锁文件修改;本轮没有推送、部署或发布。
## PKG-TOOL-THUMB-07 清理异常传播修复
修复cleanupAll吞掉destroy监听器抛出的falsy值、或被后续异常覆盖的兼容回归。
+1
View File
@@ -282,3 +282,4 @@
| AUTO-THUMB-METADATA-01 | resolved / 已复现 | Private metadata loading can retain its decoder indefinitely without metadata or media errors | PKG-AUTO-THUMB-12 |
| ASR-MEDIA-01 | resolved / 已复现 | ASR accepts obsolete recognition after native media error without pause | PKG-ASR-10, PKG-ASR-05 |
| THUMB-CLEANUP-01 | resolved / 已复现 | Thumbnail 清理吞掉 falsy 监听器异常或覆盖第一个异常值 | PKG-TOOL-THUMB-07, PKG-TOOL-THUMB-04 |
| DASH-CLEANUP-01 | resolved / 已复现 | DASH 菜单和 SDK 清理吞掉 falsy 异常或丢失首个错误 | PKG-DASH-CLEANUP-01, PKG-DASH-05 |
+24
View File
@@ -6629,6 +6629,30 @@
"refactor/baselines/thumbnail-cleanup-errors-validation.json"
],
"resolutionRationale": "Frozen historical destroy contracts preserve all seven falsy values; the prepatch candidate fails exception propagation and first-value ownership. Fixed source and rebuilt main/legacy pass cleanup/resource and actual browser DOM regressions. Installed artifact and type checks pass; historical defaults and WebKit media capability gaps remain outside this narrowly resolved regression."
},
{
"id": "DASH-CLEANUP-01",
"title": "DASH 菜单和 SDK 清理吞掉 falsy 异常或丢失首个错误",
"confirmation": "reproduced",
"status": "resolved",
"owners": [
"PKG-DASH-CLEANUP-01",
"PKG-DASH-05"
],
"evidence": [
"test/dash-cleanup-errors.test.js",
"test/browser/dash-control.spec.js",
"refactor/changes/2026-09-15-PKG-DASH-CLEANUP-01-errors.md",
"refactor/baselines/dash-cleanup-errors-validation.json"
],
"compatibleResolution": "共用独立失败标记的清理调度,保存并传播第一个原始值,同时完成剩余资源释放,保留重入/SDK归属及异步警告策略。",
"closureCriteria": "七种falsy值首错身份、兄弟资源释放、重入/销毁专项、真实控件回调、三格式安装和真实SDK回归有证据;设备能力缺口单独保留。",
"workspaceState": "Prepatch: six source failures and four valid Chromium failures; fixed source 167 pass, installed three formats plus source/historical 470 pass. Browser reports distinguish controlled DOM, actual SDK and Windows WebKit MSE skips.",
"resolutionEvidence": [
"refactor/changes/2026-09-15-PKG-DASH-CLEANUP-01-errors.md",
"refactor/baselines/dash-cleanup-errors-validation.json"
],
"resolutionRationale": "Corrected public beforeUnmount input reproduces swallowed values against the prepatch artifact. Separate threw and Object.is assertions cover all seven values. Rebuilt and independently installed artifacts pass lifecycle/DOM/SDK regressions; public declarations are unchanged. This resolves the cleanup defect only, not full package device/release acceptance."
}
]
}
+5
View File
@@ -38,6 +38,11 @@ async function main() {
assert.equal(fs.realpathSync(root), root, 'Installed package is a workspace link')
for (const [member, expected] of Object.entries(pkg.files))
assert.equal(hash(fs.readFileSync(path.join(root, member.slice(8)))), expected, `Installed bytes differ: ${member}`)
const retained = path.join(output, 'installed-artifacts', pkg.name)
fs.cpSync(root, retained, { recursive: true })
for (const [member, expected] of Object.entries(pkg.files))
assert.equal(hash(fs.readFileSync(path.join(retained, member.slice(8)))), expected, `Retained installed bytes differ: ${member}`)
pkg.installedArtifacts = retained
}
const matrix = []
for (const [compiler, mode] of [[ts, 'node10-commonjs'], [ts, 'nodenext-cjs'], [ts, 'nodenext-esm'], [ts, 'bundler-esm'], [compat, 'node10-commonjs']]) {
+21 -1
View File
@@ -2328,7 +2328,8 @@
"dependsOn": [
"PKG-DASH-04",
"CORE-22",
"PKG-DASH-SEEK-01"
"PKG-DASH-SEEK-01",
"PKG-DASH-CLEANUP-01"
],
"status": "doing",
"risk": "H",
@@ -5766,6 +5767,25 @@
"changes/2026-09-15-PKG-TOOL-THUMB-07-cleanup-errors.md",
"baselines/thumbnail-cleanup-errors-validation.json"
]
},
{
"id": "PKG-DASH-CLEANUP-01",
"phase": "5 包迁移:artplayer-plugin-dash-control",
"title": "保留菜单和SDK清理的首个原始异常",
"scope": [
"artplayer-plugin-dash-control"
],
"dependsOn": [
"PKG-DASH-04"
],
"status": "done",
"risk": "M",
"deliverable": "集中DASH清理调度与首错传播,保留重入保护;falsy异常、真实控件beforeUnmount、分发及文档验证",
"acceptance": "七种falsy值不被吞掉或后续错误覆盖;后续资源释放和生命周期回归通过,不更改公开API、SDK归属或异步刷新边界",
"evidence": [
"changes/2026-09-15-PKG-DASH-CLEANUP-01-errors.md",
"baselines/dash-cleanup-errors-validation.json"
]
}
]
}
+59
View File
@@ -99,6 +99,65 @@ async function openDash(page, core, plugin, sdk, testInfo) {
await expect(page.locator('.art-control-dash-audio .art-selector-value')).toHaveText('en')
}
for (const core of ['published', 'candidate']) {
for (const sdk of [4, 5]) {
test(`${core} core + candidate DASH SDK ${sdk}: falsy unmount errors preserve identity and release sibling menus`, async ({ page }, testInfo) => {
await openDash(page, core, 'candidate', sdk, testInfo)
const result = await page.evaluate((sdk) => {
const art = window.art
const plugin = art.plugins.artplayerPluginDashControl
const method = sdk === 4 ? 'getBitrateInfoListFor' : 'getRepresentationsByType'
const original = art.dash[method]
const results = []
for (const failure of [undefined, null, false, 0, -0, '', Number.NaN]) {
let armed = false
art.controls.update({ name: 'dash-quality', selector: art.controls.cache.get('dash-quality').option.selector.map(item => ({ ...item })), beforeUnmount() {
if (armed)
throw failure
} })
armed = true
art.dash[method] = () => []
let threw = false
let same = false
try {
plugin.update()
}
catch (error) {
threw = true
same = Object.is(error, failure)
}
results.push({ threw, same, audioRemoved: !art.controls['dash-audio'], settingsRemoved: !art.setting.find('dash-quality') && !art.setting.find('dash-audio') })
armed = false
art.dash[method] = original
if (art.controls['dash-quality'])
art.controls.remove('dash-quality')
plugin.update()
}
let armed = false
art.controls.update({ name: 'dash-quality', selector: art.controls.cache.get('dash-quality').option.selector.map(item => ({ ...item })), beforeUnmount() {
if (armed)
// eslint-disable-next-line no-throw-literal -- Verify the public callback's primitive exception identity.
throw 0
} })
armed = true
let destroyThrew = false
let destroySame = false
try {
art.destroy(false)
}
catch (error) {
destroyThrew = true
destroySame = Object.is(error, 0)
}
const siblingRemoved = !art.controls['dash-audio']
armed = false
if (art.controls['dash-quality'])
art.controls.remove('dash-quality')
return { results, destroyThrew, destroySame, siblingRemoved }
}, sdk)
await testInfo.attach('dash-cleanup-errors', { contentType: 'application/json', body: JSON.stringify({ result, scope: 'Real core beforeUnmount callbacks and native DOM; controlled SDK topology, no adaptive playback claim.' }) })
expect(result).toEqual({ results: Array.from({ length: 7 }, () => ({ threw: true, same: true, audioRemoved: true, settingsRemoved: true })), destroyThrew: true, destroySame: true, siblingRemoved: true })
})
}
for (const [plugin, sdk] of [['published', 4], ['candidate', 4], ['candidate', 5]]) {
for (const surface of ['control', 'setting']) {
for (const type of ['quality', 'audio']) {
+87
View File
@@ -0,0 +1,87 @@
import assert from 'node:assert/strict'
// eslint-disable-next-line test/no-import-node-test -- Cleanup exception identity uses the Node lifecycle runner.
import test from 'node:test'
import { bothMenus, dashHost, dashImplementations } from './helpers/dash-control.js'
const implementations = (await dashImplementations()).filter(item => item.name.startsWith('source') || item.name.startsWith('artifact'))
const failures = [undefined, null, false, 0, -0, '', Number.NaN]
function attempt(callback) {
try {
callback()
return { threw: false }
}
catch (error) {
return { threw: true, error }
}
}
for (const { sdk, name, factory } of implementations) {
for (const trigger of ['update', 'destroy']) {
test(`DASH ${sdk} (${name}): ${trigger} preserves the first falsy menu cleanup failure`, (t) => {
const warnings = []
t.mock.method(console, 'warn', (...args) => warnings.push(args))
for (const failure of failures) {
const host = dashHost(sdk)
const plugin = factory(bothMenus())(host.art)
plugin.update()
const calls = []
for (const surface of ['controls', 'setting']) {
const remove = host.art[surface].remove
host.art[surface].remove = (key) => {
remove(key)
calls.push([surface, key])
if (surface === 'controls' && key === 'dash-quality')
throw failure
throw new Error(`later ${surface}/${key} failure`)
}
}
if (trigger === 'update') {
host.state.levels = []
host.state.tracks = []
}
const result = attempt(() => trigger === 'update' ? plugin.update() : host.art.destroy())
assert.equal(result.threw, true)
assert(Object.is(result.error, failure), 'Later menu cleanup errors cannot replace the first value')
assert.deepEqual(calls, [['controls', 'dash-quality'], ['setting', 'dash-quality'], ['controls', 'dash-audio'], ['setting', 'dash-audio']])
assert.equal(host.controls.size + host.settings.size, 0)
if (trigger === 'destroy') {
assert.equal([...host.listeners.values()].reduce((sum, list) => sum + list.size, 0), 0)
assert.doesNotThrow(() => host.art.destroy())
}
}
if (trigger === 'update') {
assert.equal(warnings.length, failures.length)
assert(warnings.every(([message, error]) => message === 'ArtPlayer DASH cleanup failed:' && error instanceof Error))
}
else {
assert.equal(warnings.length, 0)
}
})
}
test(`DASH ${sdk} (${name}): SDK unsubscribe preserves a falsy first failure while releasing all resources`, () => {
for (const failure of failures) {
const host = dashHost(sdk)
const events = new Map()
host.dash.on = (name, callback) => events.set(name, callback)
let offCalls = 0
host.dash.off = (name) => {
events.delete(name)
if (++offCalls === 1)
throw failure
throw new Error('later SDK removal failure')
}
factory(bothMenus())(host.art).update()
const count = events.size
assert(count > 0)
const result = attempt(() => host.art.destroy())
assert.equal(result.threw, true)
assert(Object.is(result.error, failure))
assert.equal(offCalls, count)
assert.equal(events.size, 0)
assert.equal(host.controls.size + host.settings.size, 0)
assert.equal([...host.listeners.values()].reduce((sum, list) => sum + list.size, 0), 0)
assert(!host.calls.some(call => call[0] === 'dash.destroy'), 'SDK remains caller-owned')
}
})
}