mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-08 19:06:15 -08:00
refactor(dash): [PKG-DASH-03] adapt SDK generations and own menu lifecycles
This commit is contained in:
1 parent
762cc63431
commit
7de04573b6
24 files changed
+2107
-533
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,e){const o=new Map;return t.filter(t=>{const n=t[e];return void 0===n||!o.has(n)&&o.set(n,1)})}return function(e={}){return o=>{const{$video:n}=o.template,{errorHandle:i}=o.constructor.utils;function a(){i(o.dash.getVideoElement()===n,'Cannot find instance of DASH from "art.dash"'),function(n){const i=n.getRepresentationsByType("video");if(!i||!i.length)return;const a=e.quality||{},l=a.auto||"Auto",s=a.title||"Quality",r=a.getName||(t=>`${t.height}p`),c=n.getCurrentRepresentationForType("video"),u=c&&c.id,d=n.getSettings().streaming.abr.autoSwitchBitrate.video,h=!d&&c?r(c):l,g=t(i.map((t,e)=>({html:r(t),value:e,id:t.id,default:!d&&!!u&&t.id===u})),"html").sort((t,e)=>e.value-t.value);g.push({html:l,value:"auto",default:d});const p=t=>("auto"===t.value?n.updateSettings({streaming:{abr:{autoSwitchBitrate:{video:!0}}}}):(n.updateSettings({streaming:{abr:{autoSwitchBitrate:{video:!1}}}}),n.setRepresentationForTypeById("video",t.id)),o.notice.show=`${s}: ${t.html}`,a.control&&o.controls.check(t),a.setting&&o.setting.check(t),t.html);a.control&&o.controls.update({name:"dash-quality",position:"right",html:h,style:{padding:"0 10px"},selector:g,onSelect:p}),a.setting&&o.setting.update({name:"dash-quality",tooltip:h,html:s,icon:'<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>',width:200,selector:g,onSelect:p})}(o.dash),function(n){const i=n.getTracksFor("audio");if(!i||!i.length)return;const a=e.audio||{},l=a.auto||"Auto",s=a.title||"Audio",r=a.getName||(t=>t.lang||t.id),c=n.getCurrentTrackFor("audio")||i[0],u=c?r(c):l,d=t(i.map(t=>({html:r(t),value:t,default:c===t})),"html"),h=t=>(n.setCurrentTrack(t.value),o.notice.show=`${s}: ${t.html}`,a.control&&o.controls.check(t),a.setting&&o.setting.check(t),t.html);a.control&&o.controls.update({name:"dash-audio",position:"right",html:u,style:{padding:"0 10px"},selector:d,onSelect:h}),a.setting&&o.setting.update({name:"dash-audio",tooltip:u,html:s,icon:'<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>',width:200,selector:d,onSelect:h})}(o.dash)}return o.on("ready",a),o.on("restart",a),{name:"artplayerPluginDashControl",update:a}}}});
|
||||
!function(t,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"==typeof define&&define.amd?(t.artplayerPluginDashControl=e(),define(function(){return t.artplayerPluginDashControl})):(t="undefined"!==typeof globalThis?globalThis:t||self).artplayerPluginDashControl=e()}(this,function(){"use strict";function t(t){const e=new Map;return t.filter(t=>{if(void 0===t.html)return!0;const n=e.get(t.html);return n?(t.default&&Object.assign(n,t),!1):(e.set(t.html,t),!0)})}function e(t,e,n){let o;const r=new Map;function l(n,o=r.get(n)){if(!o||r.get(n)!==o)return;r.delete(n);const l=t[n];"controls"===n&&l.cache?.get&&l.cache.get(e)?.option?.onSelect!==o||"setting"===n&&l.find&&l.find(e)?.onSelect!==o||l.remove(e)}function i(){let t;o=void 0;for(const[n,o]of[...r])try{l(n,o)}catch(e){t||(t=e)}if(t)throw t}return{update:function(c,s,u){if(!s)return void i();const a={};o=a;const f=()=>a===o&&u(),d=e=>f()?(s.select(e,f),f()?(t.notice.show=`${s.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:s.html,style:{padding:"0 10px"},selector:s.selector,onSelect:d})):l("controls"),f()&&(c.setting?(r.set("setting",d),t.setting.update({name:e,tooltip:s.html,html:s.title,icon:n,width:200,selector:s.selector,onSelect:d})):l("setting"))},clear:i}}return function(n={}){return o=>{const{$video:r}=o.template,{errorHandle:l}=o.constructor.utils;let i=!1,c=0;const s=e(o,"dash-quality",'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" height="18"><path fill="#fff" d="M0 96C0 60.7 28.7 32 64 32l384 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 480c-35.3 0-64-28.7-64-64L0 96zM323.8 202.5c-4.5-6.6-11.9-10.5-19.8-10.5s-15.4 3.9-19.8 10.5l-87 127.6L170.7 297c-4.6-5.7-11.5-9-18.7-9s-14.2 3.3-18.7 9l-64 80c-5.8 7.2-6.9 17.1-2.9 25.4s12.4 13.6 21.6 13.6l96 0 32 0 208 0c8.9 0 17.1-4.9 21.2-12.8s3.6-17.4-1.4-24.7l-120-176zM112 192a48 48 0 1 0 0-96 48 48 0 1 0 0 96z"/></svg>'),u=e(o,"dash-audio",'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" height="18"><path fill="#fff" d="M256 80C149.9 80 62.4 159.4 49.6 262c9.4-3.8 19.6-6 30.4-6c26.5 0 48 21.5 48 48l0 128c0 26.5-21.5 48-48 48c-44.2 0-80-35.8-80-80l0-16 0-48 0-48C0 146.6 114.6 32 256 32s256 114.6 256 256l0 48 0 48 0 16c0 44.2-35.8 80-80 80c-26.5 0-48-21.5-48-48l0-128c0-26.5 21.5-48 48-48c10.8 0 21 2.1 30.4 6C449.6 159.4 362.1 80 256 80z"/></svg>'),a=[];function f(t=()=>!0){let e;for(const o of[s.clear,u.clear]){if(!t())break;try{o()}catch(n){e||(e=n)}}if(e)throw e}function d(){if(i||o.isDestroy)return;const e=++c,a=o.dash,d=()=>!i&&!o.isDestroy&&e===c&&o.dash===a,h=()=>d()&&a.getVideoElement()===r&&d();try{if(l(a.getVideoElement()===r,'Cannot find instance of DASH from "art.dash"'),!d())return;const e=n.quality||{},o=function(e,n,o){const r=function(t){const e="function"===typeof t.getRepresentationsByType;return{levels:()=>e?t.getRepresentationsByType("video"):t.getBitrateInfoListFor("video"),current(n){if(e)return t.getCurrentRepresentationForType("video");const o=t.getQualityFor("video");return n.find(t=>t.qualityIndex===o)},item:(t,n,o,r)=>e?{value:n,id:t.id,default:!r&&null!=o&&t.id===o.id}:{value:t.qualityIndex,default:!r&&null!=o&&t.qualityIndex===o.qualityIndex},select(n,o){const r="auto"===n.value;t.updateSettings({streaming:{abr:{autoSwitchBitrate:{video:r}}}}),!r&&o()&&(e?t.setRepresentationForTypeById("video",n.id):t.setQualityFor("video",n.value))}}}(e),l=r.levels();if(!o()||!l?.length)return null;const i=n.auto||"Auto",c=n.getName||(t=>`${t.height}p`),s=r.current(l);if(!o())return null;const u=e.getSettings().streaming.abr.autoSwitchBitrate.video;if(!o())return null;const a=!u&&s?c(s):i;if(!o())return null;const f=[];for(const[t,h]of l.entries()){const e=c(h);if(!o())return null;f.push({html:e,...r.item(h,t,s,u)})}const d=t(f).sort((t,e)=>e.value-t.value);return d.push({html:i,value:"auto",default:u}),{html:a,title:n.title||"Quality",selector:d,select:r.select}}(a,e,h);if(!h())return;if(s.update(e,o,h),!h())return;const i=n.audio||{},c=function(e,n,o){const r=e.getTracksFor("audio");if(!o()||!r?.length)return null;const l=n.auto||"Auto",i=n.getName||(t=>t.lang||t.id),c=e.getCurrentTrackFor("audio")||r[0];if(!o())return null;const s=c?i(c):l;if(!o())return null;const u=function(t,e){if(t.includes(e))return e;if(null==e.id&&null==e.index)return;const n=t.filter(t=>["id","index","lang"].every(n=>null==e[n]||t[n]===e[n]));return 1===n.length?n[0]:void 0}(r,c),a=[];for(const t of r){const e=i(t);if(!o())return null;a.push({html:e,value:t,default:t===u})}const f=t(a);return{html:s,title:n.title||"Audio",selector:f,select:t=>e.setCurrentTrack(t.value)}}(a,i,h);h()&&u.update(i,c,h)}catch(g){if(d()){const t=++c;try{f(()=>c===t)}catch(p){console.warn("ArtPlayer DASH cleanup failed:",p)}}throw g}}function h(){if(i)return;let t;i=!0,c++;const e=[f,...a.splice(0).map(([t,e])=>()=>o.off(t,e))Line 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";function t(t,e){const o=new Map;return t.filter(t=>{const n=t[e];return void 0===n||!o.has(n)&&o.set(n,1)})}return function(e={}){return o=>{const{$video:n}=o.template,{errorHandle:i}=o.constructor.utils;function a(){i(o.dash.getVideoElement()===n,'Cannot find instance of DASH from "art.dash"'),function(n){const i=n.getRepresentationsByType("video");if(!i||!i.length)return;const a=e.quality||{},l=a.auto||"Auto",s=a.title||"Quality",r=a.getName||(t=>`${t.height}p`),c=n.getCurrentRepresentationForType("video"),u=c&&c.id,d=n.getSettings().streaming.abr.autoSwitchBitrate.video,h=!d&&c?r(c):l,g=t(i.map((t,e)=>({html:r(t),value:e,id:t.id,default:!d&&!!u&&t.id===u})),"html").sort((t,e)=>e.value-t.value);g.push({html:l,value:"auto",default:d});const p=t=>("auto"===t.value?n.updateSettings({streaming:{abr:{autoSwitchBitrate:{video:!0}}}}):(n.updateSettings({streaming:{abr:{autoSwitchBitrate:{video:!1}}}}),n.setRepresentationForTypeById("video",t.id)),o.notice.show=`${s}: ${t.html}`,a.control&&o.controls.check(t),a.setting&&o.setting.check(t),t.html);a.control&&o.controls.update({name:"dash-quality",position:"right",html:h,style:{padding:"0 10px"},selector:g,onSelect:p}),a.setting&&o.setting.update({name:"dash-quality",tooltip:h,html:s,icon:'<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>',width:200,selector:g,onSelect:p})}(o.dash),function(n){const i=n.getTracksFor("audio");if(!i||!i.length)return;const a=e.audio||{},l=a.auto||"Auto",s=a.title||"Audio",r=a.getName||(t=>t.lang||t.id),c=n.getCurrentTrackFor("audio")||i[0],u=c?r(c):l,d=t(i.map(t=>({html:r(t),value:t,default:c===t})),"html"),h=t=>(n.setCurrentTrack(t.value),o.notice.show=`${s}: ${t.html}`,a.control&&o.controls.check(t),a.setting&&o.setting.check(t),t.html);a.control&&o.controls.update({name:"dash-audio",position:"right",html:u,style:{padding:"0 10px"},selector:d,onSelect:h}),a.setting&&o.setting.update({name:"dash-audio",tooltip:u,html:s,icon:'<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>',width:200,selector:d,onSelect:h})}(o.dash)}return o.on("ready",a),o.on("restart",a),{name:"artplayerPluginDashControl",update:a}}}});
|
||||
!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,l=(t,l)=>{for(var i in l||(l={}))n.call(l,i)&&r(t,i,l[i]);if(e)for(var i of e(l))o.call(l,i)&&r(t,i,l[i]);return t};function i(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 l(n,o=r.get(n)){var l,i,c,u;if(!o||r.get(n)!==o)return;r.delete(n);const s=t[n];"controls"===n&&(null==(l=s.cache)?void 0:l.get)&&(null==(c=null==(i=s.cache.get(e))?void 0:i.option)?void 0:c.onSelect)!==o||"setting"===n&&s.find&&(null==(u=s.find(e))?void 0:u.onSelect)!==o||s.remove(e)}function i(){let t;o=void 0;for(const[n,o]of[...r])try{l(n,o)}catch(e){t||(t=e)}if(t)throw t}return{update:function(c,u,s){if(!u)return void i();const a={};o=a;const f=()=>a===o&&s(),d=e=>f()?(u.select(e,f),f()?(t.notice.show=`${u.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:u.html,style:{padding:"0 10px"},selector:u.selector,onSelect:d})):l("controls"),f()&&(c.setting?(r.set("setting",d),t.setting.update({name:e,tooltip:u.html,html:u.title,icon:n,width:200,selector:u.selector,onSelect:d})):l("setting"))},clear:i}}return function(t={}){return e=>{const{$video:n}=e.template,{errorHandle:o}=e.constructor.utils;let r=!1,u=0;const s=c(e,"dash-quality",'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" height="18"><path fill="#fff" d="M0 96C0 60.7 28.7 32 64 32l384 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 480c-35.3 0-64-28.7-64-64L0 96zM323.8 202.5c-4.5-6.6-11.9-10.5-19.8-10.5s-15.4 3.9-19.8 10.5l-87 127.6L170.7 297c-4.6-5.7-11.5-9-18.7-9s-14.2 3.3-18.7 9l-64 80c-5.8 7.2-6.9 17.1-2.9 25.4s12.4 13.6 21.6 13.6l96 0 32 0 208 0c8.9 0 17.1-4.9 21.2-12.8s3.6-17.4-1.4-24.7l-120-176zM112 192a48 48 0 1 0 0-96 48 48 0 1 0 0 96z"/></svg>'),a=c(e,"dash-audio",'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" height="18"><path fill="#fff" d="M256 80C149.9 80 62.4 159.4 49.6 262c9.4-3.8 19.6-6 30.4-6c26.5 0 48 21.5 48 48l0 128c0 26.5-21.5 48-48 48c-44.2 0-80-35.8-80-80l0-16 0-48 0-48C0 146.6 114.6 32 256 32s256 114.6 256 256l0 48 0 48 0 16c0 44.2-35.8 80-80 80c-26.5 0-48-21.5-48-48l0-128c0-26.5 21.5-48 48-48c10.8 0 21 2.1 30.4 6C449.6 159.4 362.1 80 256 80z"/></svg>'),f=[];function d(t=()=>!0){let e;for(const o of[s.clear,a.clear]){if(!t())break;try{o()}catch(n){e||(e=n)}}if(e)throw e}function h(){if(r||e.isDestroy)return;const c=++u,f=e.dash,h=()=>!r&&!e.isDestroy&&c===u&&e.dash===f,p=()=>h()&&f.getVideoElement()===n&&h();try{if(o(f.getVideoElement()===n,'Cannot find instance of DASH from "art.dash"'),!h())return;const e=t.quality||{},r=function(t,e,n){const o=function(t){const e="function"===typeof t.getRepresentationsByType;return{levels:()=>e?t.getRepresentationsByType("video"):t.getBitrateInfoListFor("video"),current(n){if(e)return t.getCurrentRepresentationForType("video");const o=t.getQualityFor("video");return n.find(t=>t.qualityIndex===o)},item:(t,n,o,r)=>e?{value:n,id:t.id,default:!r&&null!=o&&t.id===o.id}:{value:t.qualityIndex,default:!r&&null!=o&&t.qualityIndex===o.qualityIndex},select(n,o){const r="auto"===n.value;t.updateSettings({streaming:{abr:{autoSwitchBitrate:{video:r}}}}),!r&&o()&&(e?t.setRepresentationForTypeById("video",n.id):t.setQualityFor("video",n.value))}}}(t),r=o.levels();if(!n()||!(null==r?void 0:r.length))return null;const c=e.auto||"Auto",u=e.getName||(t=>`${t.height}p`),s=o.current(r);if(!n())return null;const a=t.getSettings().streaming.abr.autoSwitchBitrate.video;if(!n())return null;const f=!a&&s?u(s):c;if(!n())return null;const d=[];for(const[i,p]of r.entries()){const t=u(p);if(!n())return null;d.push(l({html:t},o.item(p,i,s,a)))}const h=i(d).sort((t,e)=>e.value-t.value);return h.push({html:c,value:"auto",default:a}),{html:f,title:e.title||"Quality",selector:h,select:o.select}}(f,e,p);if(!p())return;if(s.update(e,r,p),!p())return;const c=t.audio||{},u=function(t,e,n){const o=t.getTracksFor("audio");if(!n()||!(null==o?void 0:o.length))return null;const r=e.auto||"Auto",l=e.getName||(t=>t.lang||t.id),c=t.getCurrentTrackFor("audio")||o[0];if(!n())return null;const u=c?l(c):r;if(!n())return null;const s=function(t,e){if(t.includes(e))return e;if(null==e.id&&null==e.index)return;const n=t.filter(t=>["id","index","lang"].every(n=>null==e[n]||t[n]===e[n]));return 1===n.lLine truncated
|
||||
@@ -5,163 +5,260 @@
|
||||
* 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>';
|
||||
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 uniqBy(array, property) {
|
||||
const seen = /* @__PURE__ */ new Map();
|
||||
return array.filter((item) => {
|
||||
const key = item[property];
|
||||
if (key === void 0) {
|
||||
return true;
|
||||
function qualityAdapter(dash) {
|
||||
const modern = typeof dash.getRepresentationsByType === "function";
|
||||
return {
|
||||
levels: () => modern ? dash.getRepresentationsByType("video") : dash.getBitrateInfoListFor("video"),
|
||||
current(levels) {
|
||||
if (modern)
|
||||
return dash.getCurrentRepresentationForType("video");
|
||||
const quality = dash.getQualityFor("video");
|
||||
return levels.find((level) => level.qualityIndex === quality);
|
||||
},
|
||||
item(level, index, selected, automatic) {
|
||||
return modern ? { value: index, id: level.id, default: !automatic && selected != null && level.id === selected.id } : { value: level.qualityIndex, default: !automatic && selected != null && level.qualityIndex === selected.qualityIndex };
|
||||
},
|
||||
select(item, valid) {
|
||||
const automatic = item.value === "auto";
|
||||
dash.updateSettings({ streaming: { abr: { autoSwitchBitrate: { video: automatic } } } });
|
||||
if (!automatic && valid()) {
|
||||
if (modern)
|
||||
dash.setRepresentationForTypeById("video", item.id);
|
||||
else
|
||||
dash.setQualityFor("video", item.value);
|
||||
}
|
||||
}
|
||||
return !seen.has(key) && seen.set(key, 1);
|
||||
};
|
||||
}
|
||||
function uniqueLabels(items) {
|
||||
const seen = /* @__PURE__ */ new Map();
|
||||
return items.filter((item) => {
|
||||
if (item.html === void 0)
|
||||
return true;
|
||||
const first = seen.get(item.html);
|
||||
if (first) {
|
||||
if (item.default)
|
||||
Object.assign(first, item);
|
||||
return false;
|
||||
}
|
||||
seen.set(item.html, item);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
function qualityModel(dash, config, active) {
|
||||
const adapter = qualityAdapter(dash);
|
||||
const levels = adapter.levels();
|
||||
if (!active() || !levels?.length)
|
||||
return null;
|
||||
const auto = config.auto || "Auto";
|
||||
const getName = config.getName || ((level) => `${level.height}p`);
|
||||
const selected = adapter.current(levels);
|
||||
if (!active())
|
||||
return null;
|
||||
const automatic = dash.getSettings().streaming.abr.autoSwitchBitrate.video;
|
||||
if (!active())
|
||||
return null;
|
||||
const html = !automatic && selected ? getName(selected) : auto;
|
||||
if (!active())
|
||||
return null;
|
||||
const items = [];
|
||||
for (const [index, level] of levels.entries()) {
|
||||
const label = getName(level);
|
||||
if (!active())
|
||||
return null;
|
||||
items.push({ html: label, ...adapter.item(level, index, selected, automatic) });
|
||||
}
|
||||
const selector = uniqueLabels(items).sort((left, right) => right.value - left.value);
|
||||
selector.push({ html: auto, value: "auto", default: automatic });
|
||||
return { html, title: config.title || "Quality", selector, select: adapter.select };
|
||||
}
|
||||
function selectedTrack(tracks, current) {
|
||||
if (tracks.includes(current))
|
||||
return current;
|
||||
if (current.id == null && current.index == null)
|
||||
return void 0;
|
||||
const matching = tracks.filter((track) => ["id", "index", "lang"].every((key) => current[key] == null || track[key] === current[key]));
|
||||
return matching.length === 1 ? matching[0] : void 0;
|
||||
}
|
||||
function audioModel(dash, config, active) {
|
||||
const tracks = dash.getTracksFor("audio");
|
||||
if (!active() || !tracks?.length)
|
||||
return null;
|
||||
const auto = config.auto || "Auto";
|
||||
const getName = config.getName || ((track) => track.lang || track.id);
|
||||
const current = dash.getCurrentTrackFor("audio") || tracks[0];
|
||||
if (!active())
|
||||
return null;
|
||||
const html = current ? getName(current) : auto;
|
||||
if (!active())
|
||||
return null;
|
||||
const selected = selectedTrack(tracks, current);
|
||||
const items = [];
|
||||
for (const track of tracks) {
|
||||
const label = getName(track);
|
||||
if (!active())
|
||||
return null;
|
||||
items.push({ html: label, value: track, default: track === selected });
|
||||
}
|
||||
const selector = uniqueLabels(items);
|
||||
return { html, title: config.title || "Audio", selector, select: (item) => dash.setCurrentTrack(item.value) };
|
||||
}
|
||||
function createMenu(art, name, icon) {
|
||||
let current;
|
||||
const owned = /* @__PURE__ */ new Map();
|
||||
function remove(surface, callback = owned.get(surface)) {
|
||||
if (!callback || owned.get(surface) !== callback)
|
||||
return;
|
||||
owned.delete(surface);
|
||||
const registry = art[surface];
|
||||
if (surface === "controls" && registry.cache?.get && registry.cache.get(name)?.option?.onSelect !== callback)
|
||||
return;
|
||||
if (surface === "setting" && registry.find && registry.find(name)?.onSelect !== callback)
|
||||
return;
|
||||
registry.remove(name);
|
||||
}
|
||||
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;
|
||||
}
|
||||
function update(config, model, active) {
|
||||
if (!model) {
|
||||
clear();
|
||||
return;
|
||||
}
|
||||
const state = {};
|
||||
current = state;
|
||||
const valid = () => state === current && active();
|
||||
const onSelect = (item) => {
|
||||
if (!valid())
|
||||
return item.html;
|
||||
model.select(item, valid);
|
||||
if (!valid())
|
||||
return item.html;
|
||||
art.notice.show = `${model.title}: ${item.html}`;
|
||||
if (valid() && config.control)
|
||||
art.controls.check(item);
|
||||
if (valid() && config.setting)
|
||||
art.setting.check(item);
|
||||
return item.html;
|
||||
};
|
||||
if (config.control) {
|
||||
owned.set("controls", onSelect);
|
||||
art.controls.update({ name, position: "right", html: model.html, style: { padding: "0 10px" }, selector: model.selector, onSelect });
|
||||
} else {
|
||||
remove("controls");
|
||||
}
|
||||
if (!valid())
|
||||
return;
|
||||
if (config.setting) {
|
||||
owned.set("setting", onSelect);
|
||||
art.setting.update({ name, tooltip: model.html, html: model.title, icon, width: 200, selector: model.selector, onSelect });
|
||||
} else {
|
||||
remove("setting");
|
||||
}
|
||||
}
|
||||
return { update, clear };
|
||||
}
|
||||
const $quality = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" height="18"><path fill="#fff" d="M0 96C0 60.7 28.7 32 64 32l384 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 480c-35.3 0-64-28.7-64-64L0 96zM323.8 202.5c-4.5-6.6-11.9-10.5-19.8-10.5s-15.4 3.9-19.8 10.5l-87 127.6L170.7 297c-4.6-5.7-11.5-9-18.7-9s-14.2 3.3-18.7 9l-64 80c-5.8 7.2-6.9 17.1-2.9 25.4s12.4 13.6 21.6 13.6l96 0 32 0 208 0c8.9 0 17.1-4.9 21.2-12.8s3.6-17.4-1.4-24.7l-120-176zM112 192a48 48 0 1 0 0-96 48 48 0 1 0 0 96z"/></svg>';
|
||||
function artplayerPluginDashControl(option = {}) {
|
||||
return (art) => {
|
||||
const { $video } = art.template;
|
||||
const { errorHandle } = art.constructor.utils;
|
||||
function updateQuality(dash) {
|
||||
const qualities = dash.getRepresentationsByType("video");
|
||||
if (!qualities || !qualities.length)
|
||||
return;
|
||||
const config = option.quality || {};
|
||||
const auto = config.auto || "Auto";
|
||||
const title = config.title || "Quality";
|
||||
const getName = config.getName || ((level) => `${level.height}p`);
|
||||
const currentRepresentation = dash.getCurrentRepresentationForType("video");
|
||||
const currentId = currentRepresentation && currentRepresentation.id;
|
||||
const currentAuto = dash.getSettings().streaming.abr.autoSwitchBitrate.video;
|
||||
const defaultHtml = !currentAuto && currentRepresentation ? getName(currentRepresentation) : auto;
|
||||
const selector = uniqBy(
|
||||
qualities.map((item, index) => {
|
||||
return {
|
||||
html: getName(item),
|
||||
value: index,
|
||||
id: item.id,
|
||||
default: !currentAuto && !!currentId && item.id === currentId
|
||||
};
|
||||
}),
|
||||
"html"
|
||||
).sort((a, b) => b.value - a.value);
|
||||
selector.push({
|
||||
html: auto,
|
||||
value: "auto",
|
||||
default: currentAuto
|
||||
});
|
||||
const onSelect = (item) => {
|
||||
if (item.value === "auto") {
|
||||
dash.updateSettings({
|
||||
streaming: {
|
||||
abr: {
|
||||
autoSwitchBitrate: {
|
||||
video: true
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
dash.updateSettings({
|
||||
streaming: {
|
||||
abr: {
|
||||
autoSwitchBitrate: {
|
||||
video: false
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
dash.setRepresentationForTypeById("video", item.id);
|
||||
let closed = false;
|
||||
let revision = 0;
|
||||
const quality = createMenu(art, "dash-quality", $quality);
|
||||
const audio = createMenu(art, "dash-audio", $audio);
|
||||
const subscriptions = [];
|
||||
function clear(current = () => true) {
|
||||
let failure;
|
||||
for (const cleanup of [quality.clear, audio.clear]) {
|
||||
if (!current())
|
||||
break;
|
||||
try {
|
||||
cleanup();
|
||||
} catch (error) {
|
||||
failure || (failure = error);
|
||||
}
|
||||
art.notice.show = `${title}: ${item.html}`;
|
||||
if (config.control)
|
||||
art.controls.check(item);
|
||||
if (config.setting)
|
||||
art.setting.check(item);
|
||||
return item.html;
|
||||
};
|
||||
if (config.control) {
|
||||
art.controls.update({
|
||||
name: "dash-quality",
|
||||
position: "right",
|
||||
html: defaultHtml,
|
||||
style: { padding: "0 10px" },
|
||||
selector,
|
||||
onSelect
|
||||
});
|
||||
}
|
||||
if (config.setting) {
|
||||
art.setting.update({
|
||||
name: "dash-quality",
|
||||
tooltip: defaultHtml,
|
||||
html: title,
|
||||
icon: $quality,
|
||||
width: 200,
|
||||
selector,
|
||||
onSelect
|
||||
});
|
||||
}
|
||||
}
|
||||
function updateAudio(dash) {
|
||||
const audioTracks = dash.getTracksFor("audio");
|
||||
if (!audioTracks || !audioTracks.length)
|
||||
return;
|
||||
const config = option.audio || {};
|
||||
const auto = config.auto || "Auto";
|
||||
const title = config.title || "Audio";
|
||||
const getName = config.getName || ((track) => track.lang || track.id);
|
||||
const currentTrack = dash.getCurrentTrackFor("audio") || audioTracks[0];
|
||||
const defaultHtml = currentTrack ? getName(currentTrack) : auto;
|
||||
const selector = uniqBy(
|
||||
audioTracks.map((item) => {
|
||||
return {
|
||||
html: getName(item),
|
||||
value: item,
|
||||
default: currentTrack === item
|
||||
};
|
||||
}),
|
||||
"html"
|
||||
);
|
||||
const onSelect = (item) => {
|
||||
dash.setCurrentTrack(item.value);
|
||||
art.notice.show = `${title}: ${item.html}`;
|
||||
if (config.control)
|
||||
art.controls.check(item);
|
||||
if (config.setting)
|
||||
art.setting.check(item);
|
||||
return item.html;
|
||||
};
|
||||
if (config.control) {
|
||||
art.controls.update({
|
||||
name: "dash-audio",
|
||||
position: "right",
|
||||
html: defaultHtml,
|
||||
style: { padding: "0 10px" },
|
||||
selector,
|
||||
onSelect
|
||||
});
|
||||
}
|
||||
if (config.setting) {
|
||||
art.setting.update({
|
||||
name: "dash-audio",
|
||||
tooltip: defaultHtml,
|
||||
html: title,
|
||||
icon: $audio,
|
||||
width: 200,
|
||||
selector,
|
||||
onSelect
|
||||
});
|
||||
}
|
||||
if (failure)
|
||||
throw failure;
|
||||
}
|
||||
function update() {
|
||||
errorHandle(art.dash.getVideoElement() === $video, 'Cannot find instance of DASH from "art.dash"');
|
||||
updateQuality(art.dash);
|
||||
updateAudio(art.dash);
|
||||
if (closed || art.isDestroy)
|
||||
return;
|
||||
const version = ++revision;
|
||||
const dash = art.dash;
|
||||
const current = () => !closed && !art.isDestroy && version === revision && art.dash === dash;
|
||||
const valid = () => current() && dash.getVideoElement() === $video && current();
|
||||
try {
|
||||
errorHandle(dash.getVideoElement() === $video, 'Cannot find instance of DASH from "art.dash"');
|
||||
if (!current())
|
||||
return;
|
||||
const qualityConfig = option.quality || {};
|
||||
const qualities = qualityModel(dash, qualityConfig, valid);
|
||||
if (!valid())
|
||||
return;
|
||||
quality.update(qualityConfig, qualities, valid);
|
||||
if (!valid())
|
||||
return;
|
||||
const audioConfig = option.audio || {};
|
||||
const tracks = audioModel(dash, audioConfig, valid);
|
||||
if (valid())
|
||||
audio.update(audioConfig, tracks, valid);
|
||||
} catch (error) {
|
||||
if (current()) {
|
||||
const cleanupVersion = ++revision;
|
||||
try {
|
||||
clear(() => revision === cleanupVersion);
|
||||
} catch (cleanupError) {
|
||||
console.warn("ArtPlayer DASH cleanup failed:", cleanupError);
|
||||
}
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
art.on("ready", update);
|
||||
art.on("restart", update);
|
||||
return {
|
||||
name: "artplayerPluginDashControl",
|
||||
update
|
||||
};
|
||||
function destroy() {
|
||||
if (closed)
|
||||
return;
|
||||
closed = true;
|
||||
revision++;
|
||||
let failure;
|
||||
const actions = [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;
|
||||
}
|
||||
try {
|
||||
for (const entry of [["ready", update], ["restart", update], ["destroy", destroy]]) {
|
||||
if (closed)
|
||||
break;
|
||||
subscriptions.push(entry);
|
||||
art.on(...entry);
|
||||
}
|
||||
} catch (error) {
|
||||
try {
|
||||
destroy();
|
||||
} catch (cleanupError) {
|
||||
console.warn("ArtPlayer DASH subscription cleanup failed:", cleanupError);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
return { name: "artplayerPluginDashControl", update };
|
||||
};
|
||||
}
|
||||
export {
|
||||
|
||||
+2
-2
@@ -36,7 +36,7 @@
|
||||
"test:playback": "node --test test/playback.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-control": "node --test test/dash-control.test.js test/dash-contract.test.js test/dash-lifecycle.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}\" \"test/*\" \"docs/assets/ts/*\" \"types/*.d.ts\" \"playwright*.config.js\" --no-fix",
|
||||
@@ -50,7 +50,7 @@
|
||||
"ci:build": "yarn build:types && yarn build all && yarn build:i18n && yarn build:ts && yarn build:docs && yarn test:imports",
|
||||
"test:imports": "node --test test/esm.test.js test/i18n.test.js test/ssr.test.js",
|
||||
"typecheck": "node scripts/typecheck.mjs",
|
||||
"test:unit": "node --test test/playback.test.js test/dash-control.test.js test/dash-contract.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:unit": "node --test test/playback.test.js test/dash-control.test.js test/dash-contract.test.js test/dash-lifecycle.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:coverage": "node --test test/coverage.test.js && node scripts/coverage.mjs",
|
||||
"test": "yarn test:node && yarn test:baseline",
|
||||
"test:browser": "playwright test",
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
# DASH Control implementation
|
||||
|
||||
The factory remains `artplayerPluginDashControl(option = {})(art)`. Its result has
|
||||
`name` and synchronous `update(): void`. Installation does not require `art.dash`
|
||||
yet; `ready`, `restart`, or explicit `update()` reads and validates that instance.
|
||||
The caller creates, attaches, replaces, and destroys the SDK. This plugin never
|
||||
destroys it, changes its source, or adds a runtime dependency on dash.js.
|
||||
|
||||
## Module map
|
||||
|
||||
| File | Responsibility |
|
||||
| --- | --- |
|
||||
| `src/index.js` | Deferred installation, media identity, update revisions, event subscription and cleanup |
|
||||
| `src/sdk.js` | 4.x qualityIndex vs 5.x representation ID access and manual/Auto selection |
|
||||
| `src/mapping.js` | Names, current item matching, duplicate labels and selector ordering |
|
||||
| `src/menu.js` | Existing control/setting registries, menu ownership and guarded selection callbacks |
|
||||
| `src/audio.svg`, `src/quality.svg` | Existing setting icons |
|
||||
| `types/artplayer-plugin-dash-control.d.ts` | Existing public declarations; precise TS migration follows in PKG-DASH-04 |
|
||||
|
||||
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. This is the structural step, still in JavaScript.
|
||||
|
||||
## Selection and lifetime
|
||||
|
||||
An update validates the captured media element and starts a new revision. A callback
|
||||
from an older update, replaced SDK, or destroyed player returns its previous label
|
||||
without mutating the SDK or UI. This includes references retained by consumers.
|
||||
Every caller formatter and SDK operation is followed by a validity check before
|
||||
subsequent effects. A nested update wins over its older caller.
|
||||
|
||||
Manual quality first disables video ABR, then uses the stored SDK selection key.
|
||||
Auto only enables video ABR. Audio passes the original track object to the SDK.
|
||||
Successful selection retains the existing notice, controls.check, setting.check
|
||||
order and synchronous label return. SDK/getName exceptions remain synchronous with
|
||||
their original identity. Other ABR settings are not overwritten.
|
||||
|
||||
Duplicate labels still occupy one row. When a duplicate is current, that row keeps
|
||||
the selected item's key/object so its highlight and subsequent click agree. Numeric
|
||||
zero is a valid representation ID; a representation named `auto` is still manual
|
||||
because the synthetic Auto row is distinguished by its value. Audio prefers object
|
||||
identity, then a unique match on the current track's available id/index/lang fields.
|
||||
Ambiguous matches remain unselected instead of selecting an arbitrary track.
|
||||
|
||||
Only menus actually installed by this instance are removed on empty topology,
|
||||
disabled surfaces, failed rendering, or destruction. Where registry lookup exists,
|
||||
the saved callback identity protects an entry replaced by the caller. Cleanup takes
|
||||
a snapshot so a synchronous restart cannot cause old cleanup to remove new menus.
|
||||
Cleanup attempts all owned surfaces/subscriptions even if one removal fails.
|
||||
Installation failure rolls back subscriptions, including one installed by a throwing
|
||||
host. The primary error is preserved; secondary cleanup errors are reported.
|
||||
|
||||
Keep `dash-quality`/`dash-audio`, selector shape, right control placement/padding,
|
||||
setting width/icons, defaults and unbound getName calls compatible. Do not use a
|
||||
filtered representation array index or absoluteIndex as the SDK 5 selection key.
|
||||
|
||||
## Validation and next work
|
||||
|
||||
Run at the workspace root with pinned Node and Yarn:
|
||||
|
||||
```sh
|
||||
yarn test:dash-control
|
||||
yarn build artplayer-plugin-dash-control
|
||||
yarn test:browser test/browser/dash-control.spec.js --trace on
|
||||
yarn ci:check
|
||||
```
|
||||
|
||||
`ARTPLAYER_TEST_DASH` adds main/legacy/module files to both SDK generations of Node
|
||||
contracts and lifecycle tests; use the operating system's path delimiter.
|
||||
`ARTPLAYER_DASH_ARTIFACT` selects the browser plugin artifact. Archive browser report
|
||||
and result/trace directory before running another suite because the output is shared.
|
||||
|
||||
Tests use frozen npm 1.1.0, a frozen pre-refactor 5.x implementation, and candidates.
|
||||
Browser 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
|
||||
review SDK-driven refresh/source events and the existing demo lifecycle. Physical
|
||||
devices and installed package acceptance remain separate release gates.
|
||||
See [refactor validation](../../refactor/dash-validation.md) for baseline failures
|
||||
and [task plan](../../refactor/plan.md) for remaining type and release work.
|
||||
@@ -2,7 +2,9 @@
|
||||
|
||||
Dash control plugin for ArtPlayer
|
||||
|
||||
Requires dash.js 5.x. dash.js 4.x is not supported.
|
||||
Accepts a caller-owned dash.js instance at `art.dash`. Quality selection adapts to the
|
||||
dash.js 4.x quality methods or 5.x representation methods. Version 5 selection uses
|
||||
representation IDs so bitrate filtering does not change the selected quality.
|
||||
|
||||
## Demo
|
||||
|
||||
@@ -11,3 +13,8 @@ Requires dash.js 5.x. dash.js 4.x is not supported.
|
||||
## License
|
||||
|
||||
MIT © Harvey Zhao
|
||||
|
||||
## Maintenance
|
||||
|
||||
See [ARCHITECTURE.md](ARCHITECTURE.md) for module ownership, compatibility rules,
|
||||
test commands and the remaining real SDK playback validation scope.
|
||||
+1
-1
@@ -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,e){const o=new Map;return t.filter(t=>{const n=t[e];return void 0===n||!o.has(n)&&o.set(n,1)})}return function(e={}){return o=>{const{$video:n}=o.template,{errorHandle:i}=o.constructor.utils;function a(){i(o.dash.getVideoElement()===n,'Cannot find instance of DASH from "art.dash"'),function(n){const i=n.getRepresentationsByType("video");if(!i||!i.length)return;const a=e.quality||{},l=a.auto||"Auto",s=a.title||"Quality",r=a.getName||(t=>`${t.height}p`),c=n.getCurrentRepresentationForType("video"),u=c&&c.id,d=n.getSettings().streaming.abr.autoSwitchBitrate.video,h=!d&&c?r(c):l,g=t(i.map((t,e)=>({html:r(t),value:e,id:t.id,default:!d&&!!u&&t.id===u})),"html").sort((t,e)=>e.value-t.value);g.push({html:l,value:"auto",default:d});const p=t=>("auto"===t.value?n.updateSettings({streaming:{abr:{autoSwitchBitrate:{video:!0}}}}):(n.updateSettings({streaming:{abr:{autoSwitchBitrate:{video:!1}}}}),n.setRepresentationForTypeById("video",t.id)),o.notice.show=`${s}: ${t.html}`,a.control&&o.controls.check(t),a.setting&&o.setting.check(t),t.html);a.control&&o.controls.update({name:"dash-quality",position:"right",html:h,style:{padding:"0 10px"},selector:g,onSelect:p}),a.setting&&o.setting.update({name:"dash-quality",tooltip:h,html:s,icon:'<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>',width:200,selector:g,onSelect:p})}(o.dash),function(n){const i=n.getTracksFor("audio");if(!i||!i.length)return;const a=e.audio||{},l=a.auto||"Auto",s=a.title||"Audio",r=a.getName||(t=>t.lang||t.id),c=n.getCurrentTrackFor("audio")||i[0],u=c?r(c):l,d=t(i.map(t=>({html:r(t),value:t,default:c===t})),"html"),h=t=>(n.setCurrentTrack(t.value),o.notice.show=`${s}: ${t.html}`,a.control&&o.controls.check(t),a.setting&&o.setting.check(t),t.html);a.control&&o.controls.update({name:"dash-audio",position:"right",html:u,style:{padding:"0 10px"},selector:d,onSelect:h}),a.setting&&o.setting.update({name:"dash-audio",tooltip:u,html:s,icon:'<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>',width:200,selector:d,onSelect:h})}(o.dash)}return o.on("ready",a),o.on("restart",a),{name:"artplayerPluginDashControl",update:a}}}});
|
||||
!function(t,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"==typeof define&&define.amd?(t.artplayerPluginDashControl=e(),define(function(){return t.artplayerPluginDashControl})):(t="undefined"!==typeof globalThis?globalThis:t||self).artplayerPluginDashControl=e()}(this,function(){"use strict";function t(t){const e=new Map;return t.filter(t=>{if(void 0===t.html)return!0;const n=e.get(t.html);return n?(t.default&&Object.assign(n,t),!1):(e.set(t.html,t),!0)})}function e(t,e,n){let o;const r=new Map;function l(n,o=r.get(n)){if(!o||r.get(n)!==o)return;r.delete(n);const l=t[n];"controls"===n&&l.cache?.get&&l.cache.get(e)?.option?.onSelect!==o||"setting"===n&&l.find&&l.find(e)?.onSelect!==o||l.remove(e)}function i(){let t;o=void 0;for(const[n,o]of[...r])try{l(n,o)}catch(e){t||(t=e)}if(t)throw t}return{update:function(c,s,u){if(!s)return void i();const a={};o=a;const f=()=>a===o&&u(),d=e=>f()?(s.select(e,f),f()?(t.notice.show=`${s.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:s.html,style:{padding:"0 10px"},selector:s.selector,onSelect:d})):l("controls"),f()&&(c.setting?(r.set("setting",d),t.setting.update({name:e,tooltip:s.html,html:s.title,icon:n,width:200,selector:s.selector,onSelect:d})):l("setting"))},clear:i}}return function(n={}){return o=>{const{$video:r}=o.template,{errorHandle:l}=o.constructor.utils;let i=!1,c=0;const s=e(o,"dash-quality",'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" height="18"><path fill="#fff" d="M0 96C0 60.7 28.7 32 64 32l384 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 480c-35.3 0-64-28.7-64-64L0 96zM323.8 202.5c-4.5-6.6-11.9-10.5-19.8-10.5s-15.4 3.9-19.8 10.5l-87 127.6L170.7 297c-4.6-5.7-11.5-9-18.7-9s-14.2 3.3-18.7 9l-64 80c-5.8 7.2-6.9 17.1-2.9 25.4s12.4 13.6 21.6 13.6l96 0 32 0 208 0c8.9 0 17.1-4.9 21.2-12.8s3.6-17.4-1.4-24.7l-120-176zM112 192a48 48 0 1 0 0-96 48 48 0 1 0 0 96z"/></svg>'),u=e(o,"dash-audio",'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" height="18"><path fill="#fff" d="M256 80C149.9 80 62.4 159.4 49.6 262c9.4-3.8 19.6-6 30.4-6c26.5 0 48 21.5 48 48l0 128c0 26.5-21.5 48-48 48c-44.2 0-80-35.8-80-80l0-16 0-48 0-48C0 146.6 114.6 32 256 32s256 114.6 256 256l0 48 0 48 0 16c0 44.2-35.8 80-80 80c-26.5 0-48-21.5-48-48l0-128c0-26.5 21.5-48 48-48c10.8 0 21 2.1 30.4 6C449.6 159.4 362.1 80 256 80z"/></svg>'),a=[];function f(t=()=>!0){let e;for(const o of[s.clear,u.clear]){if(!t())break;try{o()}catch(n){e||(e=n)}}if(e)throw e}function d(){if(i||o.isDestroy)return;const e=++c,a=o.dash,d=()=>!i&&!o.isDestroy&&e===c&&o.dash===a,h=()=>d()&&a.getVideoElement()===r&&d();try{if(l(a.getVideoElement()===r,'Cannot find instance of DASH from "art.dash"'),!d())return;const e=n.quality||{},o=function(e,n,o){const r=function(t){const e="function"===typeof t.getRepresentationsByType;return{levels:()=>e?t.getRepresentationsByType("video"):t.getBitrateInfoListFor("video"),current(n){if(e)return t.getCurrentRepresentationForType("video");const o=t.getQualityFor("video");return n.find(t=>t.qualityIndex===o)},item:(t,n,o,r)=>e?{value:n,id:t.id,default:!r&&null!=o&&t.id===o.id}:{value:t.qualityIndex,default:!r&&null!=o&&t.qualityIndex===o.qualityIndex},select(n,o){const r="auto"===n.value;t.updateSettings({streaming:{abr:{autoSwitchBitrate:{video:r}}}}),!r&&o()&&(e?t.setRepresentationForTypeById("video",n.id):t.setQualityFor("video",n.value))}}}(e),l=r.levels();if(!o()||!l?.length)return null;const i=n.auto||"Auto",c=n.getName||(t=>`${t.height}p`),s=r.current(l);if(!o())return null;const u=e.getSettings().streaming.abr.autoSwitchBitrate.video;if(!o())return null;const a=!u&&s?c(s):i;if(!o())return null;const f=[];for(const[t,h]of l.entries()){const e=c(h);if(!o())return null;f.push({html:e,...r.item(h,t,s,u)})}const d=t(f).sort((t,e)=>e.value-t.value);return d.push({html:i,value:"auto",default:u}),{html:a,title:n.title||"Quality",selector:d,select:r.select}}(a,e,h);if(!h())return;if(s.update(e,o,h),!h())return;const i=n.audio||{},c=function(e,n,o){const r=e.getTracksFor("audio");if(!o()||!r?.length)return null;const l=n.auto||"Auto",i=n.getName||(t=>t.lang||t.id),c=e.getCurrentTrackFor("audio")||r[0];if(!o())return null;const s=c?i(c):l;if(!o())return null;const u=function(t,e){if(t.includes(e))return e;if(null==e.id&&null==e.index)return;const n=t.filter(t=>["id","index","lang"].every(n=>null==e[n]||t[n]===e[n]));return 1===n.length?n[0]:void 0}(r,c),a=[];for(const t of r){const e=i(t);if(!o())return null;a.push({html:e,value:t,default:t===u})}const f=t(a);return{html:s,title:n.title||"Audio",selector:f,select:t=>e.setCurrentTrack(t.value)}}(a,i,h);h()&&u.update(i,c,h)}catch(g){if(d()){const t=++c;try{f(()=>c===t)}catch(p){console.warn("ArtPlayer DASH cleanup failed:",p)}}throw g}}function h(){if(i)return;let t;i=!0,c++;const e=[f,...a.splice(0).map(([t,e])=>()=>o.off(t,e))Line truncated
|
||||
+1
-1
@@ -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,e){const o=new Map;return t.filter(t=>{const n=t[e];return void 0===n||!o.has(n)&&o.set(n,1)})}return function(e={}){return o=>{const{$video:n}=o.template,{errorHandle:i}=o.constructor.utils;function a(){i(o.dash.getVideoElement()===n,'Cannot find instance of DASH from "art.dash"'),function(n){const i=n.getRepresentationsByType("video");if(!i||!i.length)return;const a=e.quality||{},l=a.auto||"Auto",s=a.title||"Quality",r=a.getName||(t=>`${t.height}p`),c=n.getCurrentRepresentationForType("video"),u=c&&c.id,d=n.getSettings().streaming.abr.autoSwitchBitrate.video,h=!d&&c?r(c):l,g=t(i.map((t,e)=>({html:r(t),value:e,id:t.id,default:!d&&!!u&&t.id===u})),"html").sort((t,e)=>e.value-t.value);g.push({html:l,value:"auto",default:d});const p=t=>("auto"===t.value?n.updateSettings({streaming:{abr:{autoSwitchBitrate:{video:!0}}}}):(n.updateSettings({streaming:{abr:{autoSwitchBitrate:{video:!1}}}}),n.setRepresentationForTypeById("video",t.id)),o.notice.show=`${s}: ${t.html}`,a.control&&o.controls.check(t),a.setting&&o.setting.check(t),t.html);a.control&&o.controls.update({name:"dash-quality",position:"right",html:h,style:{padding:"0 10px"},selector:g,onSelect:p}),a.setting&&o.setting.update({name:"dash-quality",tooltip:h,html:s,icon:'<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>',width:200,selector:g,onSelect:p})}(o.dash),function(n){const i=n.getTracksFor("audio");if(!i||!i.length)return;const a=e.audio||{},l=a.auto||"Auto",s=a.title||"Audio",r=a.getName||(t=>t.lang||t.id),c=n.getCurrentTrackFor("audio")||i[0],u=c?r(c):l,d=t(i.map(t=>({html:r(t),value:t,default:c===t})),"html"),h=t=>(n.setCurrentTrack(t.value),o.notice.show=`${s}: ${t.html}`,a.control&&o.controls.check(t),a.setting&&o.setting.check(t),t.html);a.control&&o.controls.update({name:"dash-audio",position:"right",html:u,style:{padding:"0 10px"},selector:d,onSelect:h}),a.setting&&o.setting.update({name:"dash-audio",tooltip:u,html:s,icon:'<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>',width:200,selector:d,onSelect:h})}(o.dash)}return o.on("ready",a),o.on("restart",a),{name:"artplayerPluginDashControl",update:a}}}});
|
||||
!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,l=(t,l)=>{for(var i in l||(l={}))n.call(l,i)&&r(t,i,l[i]);if(e)for(var i of e(l))o.call(l,i)&&r(t,i,l[i]);return t};function i(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 l(n,o=r.get(n)){var l,i,c,u;if(!o||r.get(n)!==o)return;r.delete(n);const s=t[n];"controls"===n&&(null==(l=s.cache)?void 0:l.get)&&(null==(c=null==(i=s.cache.get(e))?void 0:i.option)?void 0:c.onSelect)!==o||"setting"===n&&s.find&&(null==(u=s.find(e))?void 0:u.onSelect)!==o||s.remove(e)}function i(){let t;o=void 0;for(const[n,o]of[...r])try{l(n,o)}catch(e){t||(t=e)}if(t)throw t}return{update:function(c,u,s){if(!u)return void i();const a={};o=a;const f=()=>a===o&&s(),d=e=>f()?(u.select(e,f),f()?(t.notice.show=`${u.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:u.html,style:{padding:"0 10px"},selector:u.selector,onSelect:d})):l("controls"),f()&&(c.setting?(r.set("setting",d),t.setting.update({name:e,tooltip:u.html,html:u.title,icon:n,width:200,selector:u.selector,onSelect:d})):l("setting"))},clear:i}}return function(t={}){return e=>{const{$video:n}=e.template,{errorHandle:o}=e.constructor.utils;let r=!1,u=0;const s=c(e,"dash-quality",'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" height="18"><path fill="#fff" d="M0 96C0 60.7 28.7 32 64 32l384 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 480c-35.3 0-64-28.7-64-64L0 96zM323.8 202.5c-4.5-6.6-11.9-10.5-19.8-10.5s-15.4 3.9-19.8 10.5l-87 127.6L170.7 297c-4.6-5.7-11.5-9-18.7-9s-14.2 3.3-18.7 9l-64 80c-5.8 7.2-6.9 17.1-2.9 25.4s12.4 13.6 21.6 13.6l96 0 32 0 208 0c8.9 0 17.1-4.9 21.2-12.8s3.6-17.4-1.4-24.7l-120-176zM112 192a48 48 0 1 0 0-96 48 48 0 1 0 0 96z"/></svg>'),a=c(e,"dash-audio",'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" height="18"><path fill="#fff" d="M256 80C149.9 80 62.4 159.4 49.6 262c9.4-3.8 19.6-6 30.4-6c26.5 0 48 21.5 48 48l0 128c0 26.5-21.5 48-48 48c-44.2 0-80-35.8-80-80l0-16 0-48 0-48C0 146.6 114.6 32 256 32s256 114.6 256 256l0 48 0 48 0 16c0 44.2-35.8 80-80 80c-26.5 0-48-21.5-48-48l0-128c0-26.5 21.5-48 48-48c10.8 0 21 2.1 30.4 6C449.6 159.4 362.1 80 256 80z"/></svg>'),f=[];function d(t=()=>!0){let e;for(const o of[s.clear,a.clear]){if(!t())break;try{o()}catch(n){e||(e=n)}}if(e)throw e}function h(){if(r||e.isDestroy)return;const c=++u,f=e.dash,h=()=>!r&&!e.isDestroy&&c===u&&e.dash===f,p=()=>h()&&f.getVideoElement()===n&&h();try{if(o(f.getVideoElement()===n,'Cannot find instance of DASH from "art.dash"'),!h())return;const e=t.quality||{},r=function(t,e,n){const o=function(t){const e="function"===typeof t.getRepresentationsByType;return{levels:()=>e?t.getRepresentationsByType("video"):t.getBitrateInfoListFor("video"),current(n){if(e)return t.getCurrentRepresentationForType("video");const o=t.getQualityFor("video");return n.find(t=>t.qualityIndex===o)},item:(t,n,o,r)=>e?{value:n,id:t.id,default:!r&&null!=o&&t.id===o.id}:{value:t.qualityIndex,default:!r&&null!=o&&t.qualityIndex===o.qualityIndex},select(n,o){const r="auto"===n.value;t.updateSettings({streaming:{abr:{autoSwitchBitrate:{video:r}}}}),!r&&o()&&(e?t.setRepresentationForTypeById("video",n.id):t.setQualityFor("video",n.value))}}}(t),r=o.levels();if(!n()||!(null==r?void 0:r.length))return null;const c=e.auto||"Auto",u=e.getName||(t=>`${t.height}p`),s=o.current(r);if(!n())return null;const a=t.getSettings().streaming.abr.autoSwitchBitrate.video;if(!n())return null;const f=!a&&s?u(s):c;if(!n())return null;const d=[];for(const[i,p]of r.entries()){const t=u(p);if(!n())return null;d.push(l({html:t},o.item(p,i,s,a)))}const h=i(d).sort((t,e)=>e.value-t.value);return h.push({html:c,value:"auto",default:a}),{html:f,title:e.title||"Quality",selector:h,select:o.select}}(f,e,p);if(!p())return;if(s.update(e,r,p),!p())return;const c=t.audio||{},u=function(t,e,n){const o=t.getTracksFor("audio");if(!n()||!(null==o?void 0:o.length))return null;const r=e.auto||"Auto",l=e.getName||(t=>t.lang||t.id),c=t.getCurrentTrackFor("audio")||o[0];if(!n())return null;const u=c?l(c):r;if(!n())return null;const s=function(t,e){if(t.includes(e))return e;if(null==e.id&&null==e.index)return;const n=t.filter(t=>["id","index","lang"].every(n=>null==e[n]||t[n]===e[n]));return 1===n.lLine truncated
|
||||
+242
-145
@@ -5,163 +5,260 @@
|
||||
* 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>';
|
||||
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 uniqBy(array, property) {
|
||||
const seen = /* @__PURE__ */ new Map();
|
||||
return array.filter((item) => {
|
||||
const key = item[property];
|
||||
if (key === void 0) {
|
||||
return true;
|
||||
function qualityAdapter(dash) {
|
||||
const modern = typeof dash.getRepresentationsByType === "function";
|
||||
return {
|
||||
levels: () => modern ? dash.getRepresentationsByType("video") : dash.getBitrateInfoListFor("video"),
|
||||
current(levels) {
|
||||
if (modern)
|
||||
return dash.getCurrentRepresentationForType("video");
|
||||
const quality = dash.getQualityFor("video");
|
||||
return levels.find((level) => level.qualityIndex === quality);
|
||||
},
|
||||
item(level, index, selected, automatic) {
|
||||
return modern ? { value: index, id: level.id, default: !automatic && selected != null && level.id === selected.id } : { value: level.qualityIndex, default: !automatic && selected != null && level.qualityIndex === selected.qualityIndex };
|
||||
},
|
||||
select(item, valid) {
|
||||
const automatic = item.value === "auto";
|
||||
dash.updateSettings({ streaming: { abr: { autoSwitchBitrate: { video: automatic } } } });
|
||||
if (!automatic && valid()) {
|
||||
if (modern)
|
||||
dash.setRepresentationForTypeById("video", item.id);
|
||||
else
|
||||
dash.setQualityFor("video", item.value);
|
||||
}
|
||||
}
|
||||
return !seen.has(key) && seen.set(key, 1);
|
||||
};
|
||||
}
|
||||
function uniqueLabels(items) {
|
||||
const seen = /* @__PURE__ */ new Map();
|
||||
return items.filter((item) => {
|
||||
if (item.html === void 0)
|
||||
return true;
|
||||
const first = seen.get(item.html);
|
||||
if (first) {
|
||||
if (item.default)
|
||||
Object.assign(first, item);
|
||||
return false;
|
||||
}
|
||||
seen.set(item.html, item);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
function qualityModel(dash, config, active) {
|
||||
const adapter = qualityAdapter(dash);
|
||||
const levels = adapter.levels();
|
||||
if (!active() || !levels?.length)
|
||||
return null;
|
||||
const auto = config.auto || "Auto";
|
||||
const getName = config.getName || ((level) => `${level.height}p`);
|
||||
const selected = adapter.current(levels);
|
||||
if (!active())
|
||||
return null;
|
||||
const automatic = dash.getSettings().streaming.abr.autoSwitchBitrate.video;
|
||||
if (!active())
|
||||
return null;
|
||||
const html = !automatic && selected ? getName(selected) : auto;
|
||||
if (!active())
|
||||
return null;
|
||||
const items = [];
|
||||
for (const [index, level] of levels.entries()) {
|
||||
const label = getName(level);
|
||||
if (!active())
|
||||
return null;
|
||||
items.push({ html: label, ...adapter.item(level, index, selected, automatic) });
|
||||
}
|
||||
const selector = uniqueLabels(items).sort((left, right) => right.value - left.value);
|
||||
selector.push({ html: auto, value: "auto", default: automatic });
|
||||
return { html, title: config.title || "Quality", selector, select: adapter.select };
|
||||
}
|
||||
function selectedTrack(tracks, current) {
|
||||
if (tracks.includes(current))
|
||||
return current;
|
||||
if (current.id == null && current.index == null)
|
||||
return void 0;
|
||||
const matching = tracks.filter((track) => ["id", "index", "lang"].every((key) => current[key] == null || track[key] === current[key]));
|
||||
return matching.length === 1 ? matching[0] : void 0;
|
||||
}
|
||||
function audioModel(dash, config, active) {
|
||||
const tracks = dash.getTracksFor("audio");
|
||||
if (!active() || !tracks?.length)
|
||||
return null;
|
||||
const auto = config.auto || "Auto";
|
||||
const getName = config.getName || ((track) => track.lang || track.id);
|
||||
const current = dash.getCurrentTrackFor("audio") || tracks[0];
|
||||
if (!active())
|
||||
return null;
|
||||
const html = current ? getName(current) : auto;
|
||||
if (!active())
|
||||
return null;
|
||||
const selected = selectedTrack(tracks, current);
|
||||
const items = [];
|
||||
for (const track of tracks) {
|
||||
const label = getName(track);
|
||||
if (!active())
|
||||
return null;
|
||||
items.push({ html: label, value: track, default: track === selected });
|
||||
}
|
||||
const selector = uniqueLabels(items);
|
||||
return { html, title: config.title || "Audio", selector, select: (item) => dash.setCurrentTrack(item.value) };
|
||||
}
|
||||
function createMenu(art, name, icon) {
|
||||
let current;
|
||||
const owned = /* @__PURE__ */ new Map();
|
||||
function remove(surface, callback = owned.get(surface)) {
|
||||
if (!callback || owned.get(surface) !== callback)
|
||||
return;
|
||||
owned.delete(surface);
|
||||
const registry = art[surface];
|
||||
if (surface === "controls" && registry.cache?.get && registry.cache.get(name)?.option?.onSelect !== callback)
|
||||
return;
|
||||
if (surface === "setting" && registry.find && registry.find(name)?.onSelect !== callback)
|
||||
return;
|
||||
registry.remove(name);
|
||||
}
|
||||
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;
|
||||
}
|
||||
function update(config, model, active) {
|
||||
if (!model) {
|
||||
clear();
|
||||
return;
|
||||
}
|
||||
const state = {};
|
||||
current = state;
|
||||
const valid = () => state === current && active();
|
||||
const onSelect = (item) => {
|
||||
if (!valid())
|
||||
return item.html;
|
||||
model.select(item, valid);
|
||||
if (!valid())
|
||||
return item.html;
|
||||
art.notice.show = `${model.title}: ${item.html}`;
|
||||
if (valid() && config.control)
|
||||
art.controls.check(item);
|
||||
if (valid() && config.setting)
|
||||
art.setting.check(item);
|
||||
return item.html;
|
||||
};
|
||||
if (config.control) {
|
||||
owned.set("controls", onSelect);
|
||||
art.controls.update({ name, position: "right", html: model.html, style: { padding: "0 10px" }, selector: model.selector, onSelect });
|
||||
} else {
|
||||
remove("controls");
|
||||
}
|
||||
if (!valid())
|
||||
return;
|
||||
if (config.setting) {
|
||||
owned.set("setting", onSelect);
|
||||
art.setting.update({ name, tooltip: model.html, html: model.title, icon, width: 200, selector: model.selector, onSelect });
|
||||
} else {
|
||||
remove("setting");
|
||||
}
|
||||
}
|
||||
return { update, clear };
|
||||
}
|
||||
const $quality = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" height="18"><path fill="#fff" d="M0 96C0 60.7 28.7 32 64 32l384 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 480c-35.3 0-64-28.7-64-64L0 96zM323.8 202.5c-4.5-6.6-11.9-10.5-19.8-10.5s-15.4 3.9-19.8 10.5l-87 127.6L170.7 297c-4.6-5.7-11.5-9-18.7-9s-14.2 3.3-18.7 9l-64 80c-5.8 7.2-6.9 17.1-2.9 25.4s12.4 13.6 21.6 13.6l96 0 32 0 208 0c8.9 0 17.1-4.9 21.2-12.8s3.6-17.4-1.4-24.7l-120-176zM112 192a48 48 0 1 0 0-96 48 48 0 1 0 0 96z"/></svg>';
|
||||
function artplayerPluginDashControl(option = {}) {
|
||||
return (art) => {
|
||||
const { $video } = art.template;
|
||||
const { errorHandle } = art.constructor.utils;
|
||||
function updateQuality(dash) {
|
||||
const qualities = dash.getRepresentationsByType("video");
|
||||
if (!qualities || !qualities.length)
|
||||
return;
|
||||
const config = option.quality || {};
|
||||
const auto = config.auto || "Auto";
|
||||
const title = config.title || "Quality";
|
||||
const getName = config.getName || ((level) => `${level.height}p`);
|
||||
const currentRepresentation = dash.getCurrentRepresentationForType("video");
|
||||
const currentId = currentRepresentation && currentRepresentation.id;
|
||||
const currentAuto = dash.getSettings().streaming.abr.autoSwitchBitrate.video;
|
||||
const defaultHtml = !currentAuto && currentRepresentation ? getName(currentRepresentation) : auto;
|
||||
const selector = uniqBy(
|
||||
qualities.map((item, index) => {
|
||||
return {
|
||||
html: getName(item),
|
||||
value: index,
|
||||
id: item.id,
|
||||
default: !currentAuto && !!currentId && item.id === currentId
|
||||
};
|
||||
}),
|
||||
"html"
|
||||
).sort((a, b) => b.value - a.value);
|
||||
selector.push({
|
||||
html: auto,
|
||||
value: "auto",
|
||||
default: currentAuto
|
||||
});
|
||||
const onSelect = (item) => {
|
||||
if (item.value === "auto") {
|
||||
dash.updateSettings({
|
||||
streaming: {
|
||||
abr: {
|
||||
autoSwitchBitrate: {
|
||||
video: true
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
dash.updateSettings({
|
||||
streaming: {
|
||||
abr: {
|
||||
autoSwitchBitrate: {
|
||||
video: false
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
dash.setRepresentationForTypeById("video", item.id);
|
||||
let closed = false;
|
||||
let revision = 0;
|
||||
const quality = createMenu(art, "dash-quality", $quality);
|
||||
const audio = createMenu(art, "dash-audio", $audio);
|
||||
const subscriptions = [];
|
||||
function clear(current = () => true) {
|
||||
let failure;
|
||||
for (const cleanup of [quality.clear, audio.clear]) {
|
||||
if (!current())
|
||||
break;
|
||||
try {
|
||||
cleanup();
|
||||
} catch (error) {
|
||||
failure || (failure = error);
|
||||
}
|
||||
art.notice.show = `${title}: ${item.html}`;
|
||||
if (config.control)
|
||||
art.controls.check(item);
|
||||
if (config.setting)
|
||||
art.setting.check(item);
|
||||
return item.html;
|
||||
};
|
||||
if (config.control) {
|
||||
art.controls.update({
|
||||
name: "dash-quality",
|
||||
position: "right",
|
||||
html: defaultHtml,
|
||||
style: { padding: "0 10px" },
|
||||
selector,
|
||||
onSelect
|
||||
});
|
||||
}
|
||||
if (config.setting) {
|
||||
art.setting.update({
|
||||
name: "dash-quality",
|
||||
tooltip: defaultHtml,
|
||||
html: title,
|
||||
icon: $quality,
|
||||
width: 200,
|
||||
selector,
|
||||
onSelect
|
||||
});
|
||||
}
|
||||
}
|
||||
function updateAudio(dash) {
|
||||
const audioTracks = dash.getTracksFor("audio");
|
||||
if (!audioTracks || !audioTracks.length)
|
||||
return;
|
||||
const config = option.audio || {};
|
||||
const auto = config.auto || "Auto";
|
||||
const title = config.title || "Audio";
|
||||
const getName = config.getName || ((track) => track.lang || track.id);
|
||||
const currentTrack = dash.getCurrentTrackFor("audio") || audioTracks[0];
|
||||
const defaultHtml = currentTrack ? getName(currentTrack) : auto;
|
||||
const selector = uniqBy(
|
||||
audioTracks.map((item) => {
|
||||
return {
|
||||
html: getName(item),
|
||||
value: item,
|
||||
default: currentTrack === item
|
||||
};
|
||||
}),
|
||||
"html"
|
||||
);
|
||||
const onSelect = (item) => {
|
||||
dash.setCurrentTrack(item.value);
|
||||
art.notice.show = `${title}: ${item.html}`;
|
||||
if (config.control)
|
||||
art.controls.check(item);
|
||||
if (config.setting)
|
||||
art.setting.check(item);
|
||||
return item.html;
|
||||
};
|
||||
if (config.control) {
|
||||
art.controls.update({
|
||||
name: "dash-audio",
|
||||
position: "right",
|
||||
html: defaultHtml,
|
||||
style: { padding: "0 10px" },
|
||||
selector,
|
||||
onSelect
|
||||
});
|
||||
}
|
||||
if (config.setting) {
|
||||
art.setting.update({
|
||||
name: "dash-audio",
|
||||
tooltip: defaultHtml,
|
||||
html: title,
|
||||
icon: $audio,
|
||||
width: 200,
|
||||
selector,
|
||||
onSelect
|
||||
});
|
||||
}
|
||||
if (failure)
|
||||
throw failure;
|
||||
}
|
||||
function update() {
|
||||
errorHandle(art.dash.getVideoElement() === $video, 'Cannot find instance of DASH from "art.dash"');
|
||||
updateQuality(art.dash);
|
||||
updateAudio(art.dash);
|
||||
if (closed || art.isDestroy)
|
||||
return;
|
||||
const version = ++revision;
|
||||
const dash = art.dash;
|
||||
const current = () => !closed && !art.isDestroy && version === revision && art.dash === dash;
|
||||
const valid = () => current() && dash.getVideoElement() === $video && current();
|
||||
try {
|
||||
errorHandle(dash.getVideoElement() === $video, 'Cannot find instance of DASH from "art.dash"');
|
||||
if (!current())
|
||||
return;
|
||||
const qualityConfig = option.quality || {};
|
||||
const qualities = qualityModel(dash, qualityConfig, valid);
|
||||
if (!valid())
|
||||
return;
|
||||
quality.update(qualityConfig, qualities, valid);
|
||||
if (!valid())
|
||||
return;
|
||||
const audioConfig = option.audio || {};
|
||||
const tracks = audioModel(dash, audioConfig, valid);
|
||||
if (valid())
|
||||
audio.update(audioConfig, tracks, valid);
|
||||
} catch (error) {
|
||||
if (current()) {
|
||||
const cleanupVersion = ++revision;
|
||||
try {
|
||||
clear(() => revision === cleanupVersion);
|
||||
} catch (cleanupError) {
|
||||
console.warn("ArtPlayer DASH cleanup failed:", cleanupError);
|
||||
}
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
art.on("ready", update);
|
||||
art.on("restart", update);
|
||||
return {
|
||||
name: "artplayerPluginDashControl",
|
||||
update
|
||||
};
|
||||
function destroy() {
|
||||
if (closed)
|
||||
return;
|
||||
closed = true;
|
||||
revision++;
|
||||
let failure;
|
||||
const actions = [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;
|
||||
}
|
||||
try {
|
||||
for (const entry of [["ready", update], ["restart", update], ["destroy", destroy]]) {
|
||||
if (closed)
|
||||
break;
|
||||
subscriptions.push(entry);
|
||||
art.on(...entry);
|
||||
}
|
||||
} catch (error) {
|
||||
try {
|
||||
destroy();
|
||||
} catch (cleanupError) {
|
||||
console.warn("ArtPlayer DASH subscription cleanup failed:", cleanupError);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
return { name: "artplayerPluginDashControl", update };
|
||||
};
|
||||
}
|
||||
export {
|
||||
|
||||
@@ -1,179 +1,107 @@
|
||||
import $audio from './audio.svg?raw'
|
||||
import { audioModel, qualityModel } from './mapping'
|
||||
import { createMenu } from './menu'
|
||||
import $quality from './quality.svg?raw'
|
||||
|
||||
function uniqBy(array, property) {
|
||||
const seen = new Map()
|
||||
return array.filter((item) => {
|
||||
const key = item[property]
|
||||
if (key === undefined) {
|
||||
return true
|
||||
}
|
||||
return !seen.has(key) && seen.set(key, 1)
|
||||
})
|
||||
}
|
||||
|
||||
export default function artplayerPluginDashControl(option = {}) {
|
||||
return (art) => {
|
||||
const { $video } = art.template
|
||||
const { errorHandle } = art.constructor.utils
|
||||
let closed = false
|
||||
let revision = 0
|
||||
const quality = createMenu(art, 'dash-quality', $quality)
|
||||
const audio = createMenu(art, 'dash-audio', $audio)
|
||||
const subscriptions = []
|
||||
|
||||
function updateQuality(dash) {
|
||||
const qualities = dash.getRepresentationsByType('video')
|
||||
if (!qualities || !qualities.length)
|
||||
return
|
||||
|
||||
const config = option.quality || {}
|
||||
const auto = config.auto || 'Auto'
|
||||
const title = config.title || 'Quality'
|
||||
const getName = config.getName || (level => `${level.height}p`)
|
||||
const currentRepresentation = dash.getCurrentRepresentationForType('video')
|
||||
const currentId = currentRepresentation && currentRepresentation.id
|
||||
const currentAuto = dash.getSettings().streaming.abr.autoSwitchBitrate.video
|
||||
const defaultHtml = !currentAuto && currentRepresentation ? getName(currentRepresentation) : auto
|
||||
|
||||
const selector = uniqBy(
|
||||
qualities.map((item, index) => {
|
||||
return {
|
||||
html: getName(item),
|
||||
value: index,
|
||||
id: item.id,
|
||||
default: !currentAuto && !!currentId && item.id === currentId,
|
||||
}
|
||||
}),
|
||||
'html',
|
||||
).sort((a, b) => b.value - a.value)
|
||||
|
||||
selector.push({
|
||||
html: auto,
|
||||
value: 'auto',
|
||||
default: currentAuto,
|
||||
})
|
||||
|
||||
const onSelect = (item) => {
|
||||
if (item.value === 'auto') {
|
||||
dash.updateSettings({
|
||||
streaming: {
|
||||
abr: {
|
||||
autoSwitchBitrate: {
|
||||
video: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
function clear(current = () => true) {
|
||||
let failure
|
||||
for (const cleanup of [quality.clear, audio.clear]) {
|
||||
if (!current())
|
||||
break
|
||||
try {
|
||||
cleanup()
|
||||
}
|
||||
else {
|
||||
dash.updateSettings({
|
||||
streaming: {
|
||||
abr: {
|
||||
autoSwitchBitrate: {
|
||||
video: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
dash.setRepresentationForTypeById('video', item.id)
|
||||
catch (error) {
|
||||
failure ||= error
|
||||
}
|
||||
art.notice.show = `${title}: ${item.html}`
|
||||
if (config.control)
|
||||
art.controls.check(item)
|
||||
if (config.setting)
|
||||
art.setting.check(item)
|
||||
return item.html
|
||||
}
|
||||
|
||||
if (config.control) {
|
||||
art.controls.update({
|
||||
name: 'dash-quality',
|
||||
position: 'right',
|
||||
html: defaultHtml,
|
||||
style: { padding: '0 10px' },
|
||||
selector,
|
||||
onSelect,
|
||||
})
|
||||
}
|
||||
|
||||
if (config.setting) {
|
||||
art.setting.update({
|
||||
name: 'dash-quality',
|
||||
tooltip: defaultHtml,
|
||||
html: title,
|
||||
icon: $quality,
|
||||
width: 200,
|
||||
selector,
|
||||
onSelect,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function updateAudio(dash) {
|
||||
const audioTracks = dash.getTracksFor('audio')
|
||||
if (!audioTracks || !audioTracks.length)
|
||||
return
|
||||
|
||||
const config = option.audio || {}
|
||||
const auto = config.auto || 'Auto'
|
||||
const title = config.title || 'Audio'
|
||||
const getName = config.getName || (track => track.lang || track.id)
|
||||
const currentTrack = dash.getCurrentTrackFor('audio') || audioTracks[0]
|
||||
const defaultHtml = currentTrack ? getName(currentTrack) : auto
|
||||
|
||||
const selector = uniqBy(
|
||||
audioTracks.map((item) => {
|
||||
return {
|
||||
html: getName(item),
|
||||
value: item,
|
||||
default: currentTrack === item,
|
||||
}
|
||||
}),
|
||||
'html',
|
||||
)
|
||||
|
||||
const onSelect = (item) => {
|
||||
dash.setCurrentTrack(item.value)
|
||||
art.notice.show = `${title}: ${item.html}`
|
||||
if (config.control)
|
||||
art.controls.check(item)
|
||||
if (config.setting)
|
||||
art.setting.check(item)
|
||||
return item.html
|
||||
}
|
||||
|
||||
if (config.control) {
|
||||
art.controls.update({
|
||||
name: 'dash-audio',
|
||||
position: 'right',
|
||||
html: defaultHtml,
|
||||
style: { padding: '0 10px' },
|
||||
selector,
|
||||
onSelect,
|
||||
})
|
||||
}
|
||||
|
||||
if (config.setting) {
|
||||
art.setting.update({
|
||||
name: 'dash-audio',
|
||||
tooltip: defaultHtml,
|
||||
html: title,
|
||||
icon: $audio,
|
||||
width: 200,
|
||||
selector,
|
||||
onSelect,
|
||||
})
|
||||
}
|
||||
if (failure)
|
||||
throw failure
|
||||
}
|
||||
|
||||
function update() {
|
||||
errorHandle(art.dash.getVideoElement() === $video, 'Cannot find instance of DASH from "art.dash"')
|
||||
updateQuality(art.dash)
|
||||
updateAudio(art.dash)
|
||||
if (closed || art.isDestroy)
|
||||
return
|
||||
const version = ++revision
|
||||
const dash = art.dash
|
||||
const current = () => !closed && !art.isDestroy && version === revision && art.dash === dash
|
||||
const valid = () => current() && dash.getVideoElement() === $video && current()
|
||||
try {
|
||||
errorHandle(dash.getVideoElement() === $video, 'Cannot find instance of DASH from "art.dash"')
|
||||
if (!current())
|
||||
return
|
||||
const qualityConfig = option.quality || {}
|
||||
const qualities = qualityModel(dash, qualityConfig, valid)
|
||||
if (!valid())
|
||||
return
|
||||
quality.update(qualityConfig, qualities, valid)
|
||||
if (!valid())
|
||||
return
|
||||
const audioConfig = option.audio || {}
|
||||
const tracks = audioModel(dash, audioConfig, valid)
|
||||
if (valid())
|
||||
audio.update(audioConfig, tracks, valid)
|
||||
}
|
||||
catch (error) {
|
||||
if (current()) {
|
||||
const cleanupVersion = ++revision
|
||||
try {
|
||||
clear(() => revision === cleanupVersion)
|
||||
}
|
||||
catch (cleanupError) {
|
||||
console.warn('ArtPlayer DASH cleanup failed:', cleanupError)
|
||||
}
|
||||
}
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
art.on('ready', update)
|
||||
art.on('restart', update)
|
||||
|
||||
return {
|
||||
name: 'artplayerPluginDashControl',
|
||||
update,
|
||||
function destroy() {
|
||||
if (closed)
|
||||
return
|
||||
closed = true
|
||||
revision++
|
||||
let failure
|
||||
const actions = [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
|
||||
}
|
||||
|
||||
try {
|
||||
for (const entry of [['ready', update], ['restart', update], ['destroy', destroy]]) {
|
||||
if (closed)
|
||||
break
|
||||
subscriptions.push(entry)
|
||||
art.on(...entry)
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
try {
|
||||
destroy()
|
||||
}
|
||||
catch (cleanupError) {
|
||||
console.warn('ArtPlayer DASH subscription cleanup failed:', cleanupError)
|
||||
}
|
||||
throw error
|
||||
}
|
||||
return { name: 'artplayerPluginDashControl', update }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
import { qualityAdapter } from './sdk'
|
||||
|
||||
function uniqueLabels(items) {
|
||||
const seen = new Map()
|
||||
return items.filter((item) => {
|
||||
if (item.html === undefined)
|
||||
return true
|
||||
const first = seen.get(item.html)
|
||||
if (first) {
|
||||
if (item.default)
|
||||
Object.assign(first, item)
|
||||
return false
|
||||
}
|
||||
seen.set(item.html, item)
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
export function qualityModel(dash, config, active) {
|
||||
const adapter = qualityAdapter(dash)
|
||||
const levels = adapter.levels()
|
||||
if (!active() || !levels?.length)
|
||||
return null
|
||||
const auto = config.auto || 'Auto'
|
||||
const getName = config.getName || (level => `${level.height}p`)
|
||||
const selected = adapter.current(levels)
|
||||
if (!active())
|
||||
return null
|
||||
const automatic = dash.getSettings().streaming.abr.autoSwitchBitrate.video
|
||||
if (!active())
|
||||
return null
|
||||
const html = !automatic && selected ? getName(selected) : auto
|
||||
if (!active())
|
||||
return null
|
||||
const items = []
|
||||
for (const [index, level] of levels.entries()) {
|
||||
const label = getName(level)
|
||||
if (!active())
|
||||
return null
|
||||
items.push({ html: label, ...adapter.item(level, index, selected, automatic) })
|
||||
}
|
||||
const selector = uniqueLabels(items)
|
||||
.sort((left, right) => right.value - left.value)
|
||||
selector.push({ html: auto, value: 'auto', default: automatic })
|
||||
return { html, title: config.title || 'Quality', selector, select: adapter.select }
|
||||
}
|
||||
|
||||
function selectedTrack(tracks, current) {
|
||||
if (tracks.includes(current))
|
||||
return current
|
||||
if (current.id == null && current.index == null)
|
||||
return undefined
|
||||
const matching = tracks.filter(track => ['id', 'index', 'lang'].every(key => current[key] == null || track[key] === current[key]))
|
||||
return matching.length === 1 ? matching[0] : undefined
|
||||
}
|
||||
|
||||
export function audioModel(dash, config, active) {
|
||||
const tracks = dash.getTracksFor('audio')
|
||||
if (!active() || !tracks?.length)
|
||||
return null
|
||||
const auto = config.auto || 'Auto'
|
||||
const getName = config.getName || (track => track.lang || track.id)
|
||||
const current = dash.getCurrentTrackFor('audio') || tracks[0]
|
||||
if (!active())
|
||||
return null
|
||||
const html = current ? getName(current) : auto
|
||||
if (!active())
|
||||
return null
|
||||
const selected = selectedTrack(tracks, current)
|
||||
const items = []
|
||||
for (const track of tracks) {
|
||||
const label = getName(track)
|
||||
if (!active())
|
||||
return null
|
||||
items.push({ html: label, value: track, default: track === selected })
|
||||
}
|
||||
const selector = uniqueLabels(items)
|
||||
return { html, title: config.title || 'Audio', selector, select: item => dash.setCurrentTrack(item.value) }
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
export function createMenu(art, name, icon) {
|
||||
let current
|
||||
const owned = new Map()
|
||||
|
||||
function remove(surface, callback = owned.get(surface)) {
|
||||
if (!callback || owned.get(surface) !== callback)
|
||||
return
|
||||
owned.delete(surface)
|
||||
const registry = art[surface]
|
||||
if (surface === 'controls' && registry.cache?.get && registry.cache.get(name)?.option?.onSelect !== callback)
|
||||
return
|
||||
if (surface === 'setting' && registry.find && registry.find(name)?.onSelect !== callback)
|
||||
return
|
||||
registry.remove(name)
|
||||
}
|
||||
|
||||
function clear() {
|
||||
current = undefined
|
||||
let failure
|
||||
for (const [surface, callback] of [...owned]) {
|
||||
try {
|
||||
remove(surface, callback)
|
||||
}
|
||||
catch (error) {
|
||||
failure ||= error
|
||||
}
|
||||
}
|
||||
if (failure)
|
||||
throw failure
|
||||
}
|
||||
|
||||
function update(config, model, active) {
|
||||
if (!model) {
|
||||
clear()
|
||||
return
|
||||
}
|
||||
const state = {}
|
||||
current = state
|
||||
const valid = () => state === current && active()
|
||||
const onSelect = (item) => {
|
||||
if (!valid())
|
||||
return item.html
|
||||
model.select(item, valid)
|
||||
if (!valid())
|
||||
return item.html
|
||||
art.notice.show = `${model.title}: ${item.html}`
|
||||
if (valid() && config.control)
|
||||
art.controls.check(item)
|
||||
if (valid() && config.setting)
|
||||
art.setting.check(item)
|
||||
return item.html
|
||||
}
|
||||
if (config.control) {
|
||||
owned.set('controls', onSelect)
|
||||
art.controls.update({ name, position: 'right', html: model.html, style: { padding: '0 10px' }, selector: model.selector, onSelect })
|
||||
}
|
||||
else {
|
||||
remove('controls')
|
||||
}
|
||||
if (!valid())
|
||||
return
|
||||
if (config.setting) {
|
||||
owned.set('setting', onSelect)
|
||||
art.setting.update({ name, tooltip: model.html, html: model.title, icon, width: 200, selector: model.selector, onSelect })
|
||||
}
|
||||
else {
|
||||
remove('setting')
|
||||
}
|
||||
}
|
||||
|
||||
return { update, clear }
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
export function qualityAdapter(dash) {
|
||||
const modern = typeof dash.getRepresentationsByType === 'function'
|
||||
return {
|
||||
levels: () => modern ? dash.getRepresentationsByType('video') : dash.getBitrateInfoListFor('video'),
|
||||
current(levels) {
|
||||
if (modern)
|
||||
return dash.getCurrentRepresentationForType('video')
|
||||
const quality = dash.getQualityFor('video')
|
||||
return levels.find(level => level.qualityIndex === quality)
|
||||
},
|
||||
item(level, index, selected, automatic) {
|
||||
return modern
|
||||
? { value: index, id: level.id, default: !automatic && selected != null && level.id === selected.id }
|
||||
: { value: level.qualityIndex, default: !automatic && selected != null && level.qualityIndex === selected.qualityIndex }
|
||||
},
|
||||
select(item, valid) {
|
||||
const automatic = item.value === 'auto'
|
||||
dash.updateSettings({ streaming: { abr: { autoSwitchBitrate: { video: automatic } } } })
|
||||
if (!automatic && valid()) {
|
||||
if (modern)
|
||||
dash.setRepresentationForTypeById('video', item.id)
|
||||
else
|
||||
dash.setQualityFor('video', item.value)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,854 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"task": "PKG-DASH-03",
|
||||
"baseCommit": "762cc634",
|
||||
"scope": "Runtime structure and compatible SDK call surfaces. Real old/new core DOM with formal plugin main/legacy and native MP4. SDK is controlled; actual dash.js, MPD/ABR, physical devices and installed tarball acceptance remain outstanding.",
|
||||
"inputs": [
|
||||
{
|
||||
"file": "packages/artplayer-plugin-dash-control/src/index.js",
|
||||
"sha256LF": "3d82930fa8da9f729235ed34e5b9ae732bde6a986124b0ca1016eabefae4fc70"
|
||||
},
|
||||
{
|
||||
"file": "packages/artplayer-plugin-dash-control/src/sdk.js",
|
||||
"sha256LF": "90f77e57fd9d8cae3c7876166b048285d18634a93eecfcf50a70f8d6c9da1050"
|
||||
},
|
||||
{
|
||||
"file": "packages/artplayer-plugin-dash-control/src/mapping.js",
|
||||
"sha256LF": "03c1b99e0869263778f05896b05d2fe4004cf657fd5168cb303926a90de35b34"
|
||||
},
|
||||
{
|
||||
"file": "packages/artplayer-plugin-dash-control/src/menu.js",
|
||||
"sha256LF": "e852cbdf07bd681594c95ee368834995d34c6d1314d06c1c6089a025b1d7b896"
|
||||
},
|
||||
{
|
||||
"file": "test/helpers/dash-control.js",
|
||||
"sha256LF": "a32852c960a25c27ae18a6910f2b6d9e4f5786c1339db91f414b70a1fec041db"
|
||||
},
|
||||
{
|
||||
"file": "test/dash-contract.test.js",
|
||||
"sha256LF": "4477b48fc53224890a7eb22ed5f1554b57fd6265c864fb4039393978e63ceb31"
|
||||
},
|
||||
{
|
||||
"file": "test/dash-lifecycle.test.js",
|
||||
"sha256LF": "1374d51d7952c44a63580a6b127bc53f626ba161a03982414299d807cfc1525c"
|
||||
},
|
||||
{
|
||||
"file": "test/browser/dash-control.spec.js",
|
||||
"sha256LF": "a770394af0f3291925734e116ab53f8c090349673aa14205bd9a1300c82ad154"
|
||||
}
|
||||
],
|
||||
"artifacts": [
|
||||
{
|
||||
"file": "packages/artplayer-plugin-dash-control/dist/artplayer-plugin-dash-control.js",
|
||||
"sha256": "c1c63bd0ec787a6f9eb445b53341dd5eaeaccccd1589455234be311fcf6ce5e6",
|
||||
"bytes": 5469
|
||||
},
|
||||
{
|
||||
"file": "packages/artplayer-plugin-dash-control/dist/artplayer-plugin-dash-control.legacy.js",
|
||||
"sha256": "1b6fba04c90ba19197213cf1868599dab13d430e8923d3081e37c43dc4d5d00e",
|
||||
"bytes": 5924
|
||||
},
|
||||
{
|
||||
"file": "packages/artplayer-plugin-dash-control/dist/artplayer-plugin-dash-control.mjs",
|
||||
"sha256": "84b50383a8d6e75ad27dad6b550826b0d690aaaec9b2831a65111ec4f4c277b0",
|
||||
"bytes": 9724
|
||||
}
|
||||
],
|
||||
"reports": [
|
||||
{
|
||||
"file": "refactor/.cache/dash03-main-first/report.json",
|
||||
"sha256": "b17971ffd22f20acd68b64bb4ba7bff62b65c6fcc9ce10836b1f16f2d1bc9bd3",
|
||||
"bytes": 15822733,
|
||||
"stats": {
|
||||
"startTime": "2026-09-11T20:51:00.164Z",
|
||||
"duration": 64825.979,
|
||||
"expected": 84,
|
||||
"skipped": 0,
|
||||
"unexpected": 0,
|
||||
"flaky": 0
|
||||
},
|
||||
"results": {
|
||||
"directory": "refactor/.cache/dash03-main-first/results",
|
||||
"files": 85,
|
||||
"sha256": "a92c517dd7be719052b8766b3d19c00619469c0da792a9b2eead751048893594",
|
||||
"algorithm": "SHA256 of JSON.stringify(sorted relative path and raw SHA256 pairs), UTF-8, slash paths"
|
||||
},
|
||||
"pluginInput": {
|
||||
"published": {
|
||||
"name": "artplayer-plugin-dash-control",
|
||||
"version": "1.1.0",
|
||||
"metadataUrl": "https://registry.npmjs.org/artplayer-plugin-dash-control/1.1.0",
|
||||
"tarball": "https://registry.npmjs.org/artplayer-plugin-dash-control/-/artplayer-plugin-dash-control-1.1.0.tgz",
|
||||
"integrity": "sha512-EssRkzYXWcln3a6nAzi4ESacBhzMih8s0bzdjXuvBBCh2UFmvK99ITD5E+0cuU8i5ZzIQGQSdce1/gANTrX0BQ==",
|
||||
"sha256": "f4047a569d99b6a7de57b3719b7ad40109f2a1d360566a840f50ab32aacefcd9",
|
||||
"archiveBytes": 3385,
|
||||
"registryGitHead": "daf133b22630b4a0eecfa3336bbddab0e9119d96",
|
||||
"files": {
|
||||
"package/README.md": "98982f1123ff9a11f5d3d9a52da6e8fea9cd0989f7465605c27b0f9a46f2dbcd",
|
||||
"package/dist/artplayer-plugin-dash-control.js": "f0cbbcf8badeaca13c05ae178932818cb52fbce37ed504ac0e62160b9c7e1145",
|
||||
"package/dist/artplayer-plugin-dash-control.legacy.js": "f0cbbcf8badeaca13c05ae178932818cb52fbce37ed504ac0e62160b9c7e1145",
|
||||
"package/dist/artplayer-plugin-dash-control.mjs": "e32bf08c33a481781883067dcc639f0495c9dcb8a88b91be13736370f4932fc4",
|
||||
"package/package.json": "556e2adc479bab36c5973de617c0b57c5d86aa8425643a1952ab04738e9ce927",
|
||||
"package/types/artplayer-plugin-dash-control.d.ts": "e15ef683e0c335d81531587828593325e037efe7ed29c022477cd14fe71aebba"
|
||||
},
|
||||
"manifest": {
|
||||
"name": "artplayer-plugin-dash-control",
|
||||
"version": "1.1.0",
|
||||
"description": "Dash control plugin for ArtPlayer",
|
||||
"author": "Harvey Zhao <laozhaochaguan@gmail.com>",
|
||||
"license": "MIT",
|
||||
"homepage": "https://artplayer.org",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/zhw2590582/ArtPlayer.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/zhw2590582/ArtPlayer/issues"
|
||||
},
|
||||
"keywords": [
|
||||
"html5",
|
||||
"video",
|
||||
"player"
|
||||
],
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./types/artplayer-plugin-dash-control.d.ts",
|
||||
"import": "./dist/artplayer-plugin-dash-control.mjs",
|
||||
"require": "./dist/artplayer-plugin-dash-control.js"
|
||||
},
|
||||
"./legacy": {
|
||||
"types": "./types/artplayer-plugin-dash-control.d.ts",
|
||||
"import": "./dist/artplayer-plugin-dash-control.legacy.js",
|
||||
"require": "./dist/artplayer-plugin-dash-control.legacy.js"
|
||||
}
|
||||
},
|
||||
"main": "./dist/artplayer-plugin-dash-control.js",
|
||||
"module": "./dist/artplayer-plugin-dash-control.mjs",
|
||||
"types": "./types/artplayer-plugin-dash-control.d.ts",
|
||||
"legacy": "./dist/artplayer-plugin-dash-control.legacy.js",
|
||||
"browserslist": "last 1 Chrome version"
|
||||
}
|
||||
},
|
||||
"sourceSHA256": "6c37d58c427aa688fe252127dbbc5bb131d0ef6e5141a3b22400f40e5fd177f9",
|
||||
"candidate": "packages/artplayer-plugin-dash-control/dist/artplayer-plugin-dash-control.js",
|
||||
"scope": "Real ArtPlayer DOM and native MP4 with controlled SDK methods. No dash.js instance, MPD, ABR or adaptive media validation."
|
||||
},
|
||||
"engines": [
|
||||
{
|
||||
"project": "chromium",
|
||||
"version": "153.0.8010.12",
|
||||
"platform": "win32"
|
||||
},
|
||||
{
|
||||
"project": "firefox",
|
||||
"version": "155.0",
|
||||
"platform": "win32"
|
||||
},
|
||||
{
|
||||
"project": "webkit",
|
||||
"version": "26.6",
|
||||
"platform": "win32"
|
||||
}
|
||||
],
|
||||
"manifest": {
|
||||
"kind": "artplayer-browser-test",
|
||||
"resources": {
|
||||
"/candidate/artplayer.js": {
|
||||
"source": {
|
||||
"kind": "workspace-build",
|
||||
"entry": "packages\\artplayer\\src\\index.ts"
|
||||
},
|
||||
"sha256": "df495dd386d32ea96be5006e1bbfda1ea91cfeb32aa21758197b46a155ebe0dc",
|
||||
"bytes": 378040
|
||||
},
|
||||
"/compiled/artplayer.js": {
|
||||
"source": {
|
||||
"kind": "workspace-build",
|
||||
"entry": "packages\\artplayer\\src\\index.ts"
|
||||
},
|
||||
"sha256": "df495dd386d32ea96be5006e1bbfda1ea91cfeb32aa21758197b46a155ebe0dc",
|
||||
"bytes": 378040,
|
||||
"aliasOf": "/candidate/artplayer.js"
|
||||
},
|
||||
"/uncompiled/artplayer/index.js": {
|
||||
"source": {
|
||||
"kind": "workspace-build",
|
||||
"entry": "packages\\artplayer\\src\\index.ts"
|
||||
},
|
||||
"sha256": "df495dd386d32ea96be5006e1bbfda1ea91cfeb32aa21758197b46a155ebe0dc",
|
||||
"bytes": 378040,
|
||||
"aliasOf": "/candidate/artplayer.js"
|
||||
},
|
||||
"/candidate/artplayer-plugin-chapter.js": {
|
||||
"source": {
|
||||
"kind": "workspace-build",
|
||||
"entry": "packages\\artplayer-plugin-chapter\\src\\index.ts"
|
||||
},
|
||||
"sha256": "1a8eb891d3690e2526e78a2e53183a25367b1d02a5fd349d821548edb731ee3a",
|
||||
"bytes": 8529
|
||||
},
|
||||
"/compiled/artplayer-plugin-chapter.js": {
|
||||
"source": {
|
||||
"kind": "workspace-build",
|
||||
"entry": "packages\\artplayer-plugin-chapter\\src\\index.ts"
|
||||
},
|
||||
"sha256": "1a8eb891d3690e2526e78a2e53183a25367b1d02a5fd349d821548edb731ee3a",
|
||||
"bytes": 8529,
|
||||
"aliasOf": "/candidate/artplayer-plugin-chapter.js"
|
||||
},
|
||||
"/uncompiled/artplayer-plugin-chapter/index.js": {
|
||||
"source": {
|
||||
"kind": "workspace-build",
|
||||
"entry": "packages\\artplayer-plugin-chapter\\src\\index.ts"
|
||||
},
|
||||
"sha256": "1a8eb891d3690e2526e78a2e53183a25367b1d02a5fd349d821548edb731ee3a",
|
||||
"bytes": 8529,
|
||||
"aliasOf": "/candidate/artplayer-plugin-chapter.js"
|
||||
},
|
||||
"/published/artplayer.js": {
|
||||
"source": {
|
||||
"kind": "npm-release",
|
||||
"version": "5.4.0",
|
||||
"integrity": "sha512-2B+plbx8N2yNsjK4nJU3+EOG8TULm1LRZk/QPkWRAMEX2Ee/MSnZG/WJYz8kcoZxZuLKcQ3uXifqLuPxZOH29A==",
|
||||
"member": "package/dist/artplayer.js"
|
||||
},
|
||||
"sha256": "f1db1fa3406aa6d41baa7c554749429a8e79f99d56a794bde23345eb9c07573f",
|
||||
"bytes": 134037
|
||||
},
|
||||
"/published/artplayer-plugin-chapter.js": {
|
||||
"source": {
|
||||
"kind": "npm-release",
|
||||
"version": "1.1.0",
|
||||
"integrity": "sha512-ji66Lr2K2S+OjFMuMby3yq+pefWlOC51Sbpop/DIr2N6w3F0smrF+YZbshN1Dy8/Tx1TYpT7N6WkkZWy5Jix1w==",
|
||||
"member": "package/dist/artplayer-plugin-chapter.js"
|
||||
},
|
||||
"sha256": "c2aaee11bf1db765433b01d9f8dd3978e41fa4d39655db00ebd03fc0be3e35e0",
|
||||
"bytes": 4973
|
||||
},
|
||||
"/test/pattern.mp4": {
|
||||
"source": {
|
||||
"kind": "generated-media",
|
||||
"file": "test/browser/media/pattern.mp4"
|
||||
},
|
||||
"sha256": "0ad1d7ad286aec6e0fa9e27fc1e487e7277df2e603e30040fb942365763d0233",
|
||||
"bytes": 251538
|
||||
},
|
||||
"/test/pattern.webm": {
|
||||
"source": {
|
||||
"kind": "generated-media",
|
||||
"file": "test/browser/media/pattern.webm"
|
||||
},
|
||||
"sha256": "9d3c2b3dec9581a599de2d6732992ef7846790e0d1368e6e131bd339b39e984f",
|
||||
"bytes": 162073
|
||||
},
|
||||
"/test/audio-tone.m4a": {
|
||||
"source": {
|
||||
"kind": "generated-media",
|
||||
"file": "test/browser/media/audio-tone.m4a"
|
||||
},
|
||||
"sha256": "83e6672b880b5d1c97093b1e26bdc40221c0b279615f40e99dbf9d2ade7b4d56",
|
||||
"bytes": 132436
|
||||
},
|
||||
"/test/declaration-cues.vtt": {
|
||||
"source": {
|
||||
"kind": "test-subtitles",
|
||||
"file": "test/browser/media/declaration-cues.vtt"
|
||||
},
|
||||
"sha256": "5d149d4e641ce41cbb83376835c03daf1fb89f3b9f568fa59e064d51e2b57704",
|
||||
"bytes": 54
|
||||
},
|
||||
"/test/thumbnail-grid.svg": {
|
||||
"source": {
|
||||
"kind": "test-thumbnail-grid",
|
||||
"file": "test/browser/media/thumbnail-grid.svg"
|
||||
},
|
||||
"sha256": "bb3f1ec9895d37def77ca4b192d3cd73577a2668558cc99de1588f511d65d3e6",
|
||||
"bytes": 14969
|
||||
},
|
||||
"/test/legacy-safe-area.js": {
|
||||
"source": {
|
||||
"kind": "frozen-own-source",
|
||||
"file": "test/helpers/legacy-safe-area.js",
|
||||
"commit": "ccf77c4e"
|
||||
},
|
||||
"sha256": "ce0368618ef6363b78271b8bcc7973abd0478e797cc464ddd912929e6dfa501a",
|
||||
"bytes": 795
|
||||
},
|
||||
"/assets/sample/video.mp4": {
|
||||
"source": {
|
||||
"kind": "docs-media",
|
||||
"file": "docs/assets/sample/video.mp4"
|
||||
},
|
||||
"sha256": "30a2de667ba267b6bc605edcee1c4bd728c048ed76f500ea0e73d1f1a6f8f980",
|
||||
"bytes": 5818706
|
||||
},
|
||||
"/test/performance.js": {
|
||||
"source": {
|
||||
"kind": "adapted-baseline-fixture",
|
||||
"file": "refactor/fixtures/performance.js"
|
||||
},
|
||||
"sha256": "2318f39bdb5f3a3348d755cb4335907ae4adc944be3a435839c1bba201e81500",
|
||||
"bytes": 8994
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/dash03-main-final/report.json",
|
||||
"sha256": "c3175930e53b8d4771c5393de015520f0dbcfb56362cd8017c1263ac1eba8758",
|
||||
"bytes": 15813200,
|
||||
"stats": {
|
||||
"startTime": "2026-09-11T20:53:02.032Z",
|
||||
"duration": 64490.32,
|
||||
"expected": 84,
|
||||
"skipped": 0,
|
||||
"unexpected": 0,
|
||||
"flaky": 0
|
||||
},
|
||||
"results": {
|
||||
"directory": "refactor/.cache/dash03-main-final/results",
|
||||
"files": 85,
|
||||
"sha256": "d3efa538c4ba0e698aa95b40b112caabf8324b5f3beec05f6845e7f5675de857",
|
||||
"algorithm": "SHA256 of JSON.stringify(sorted relative path and raw SHA256 pairs), UTF-8, slash paths"
|
||||
},
|
||||
"pluginInput": {
|
||||
"published": {
|
||||
"name": "artplayer-plugin-dash-control",
|
||||
"version": "1.1.0",
|
||||
"metadataUrl": "https://registry.npmjs.org/artplayer-plugin-dash-control/1.1.0",
|
||||
"tarball": "https://registry.npmjs.org/artplayer-plugin-dash-control/-/artplayer-plugin-dash-control-1.1.0.tgz",
|
||||
"integrity": "sha512-EssRkzYXWcln3a6nAzi4ESacBhzMih8s0bzdjXuvBBCh2UFmvK99ITD5E+0cuU8i5ZzIQGQSdce1/gANTrX0BQ==",
|
||||
"sha256": "f4047a569d99b6a7de57b3719b7ad40109f2a1d360566a840f50ab32aacefcd9",
|
||||
"archiveBytes": 3385,
|
||||
"registryGitHead": "daf133b22630b4a0eecfa3336bbddab0e9119d96",
|
||||
"files": {
|
||||
"package/README.md": "98982f1123ff9a11f5d3d9a52da6e8fea9cd0989f7465605c27b0f9a46f2dbcd",
|
||||
"package/dist/artplayer-plugin-dash-control.js": "f0cbbcf8badeaca13c05ae178932818cb52fbce37ed504ac0e62160b9c7e1145",
|
||||
"package/dist/artplayer-plugin-dash-control.legacy.js": "f0cbbcf8badeaca13c05ae178932818cb52fbce37ed504ac0e62160b9c7e1145",
|
||||
"package/dist/artplayer-plugin-dash-control.mjs": "e32bf08c33a481781883067dcc639f0495c9dcb8a88b91be13736370f4932fc4",
|
||||
"package/package.json": "556e2adc479bab36c5973de617c0b57c5d86aa8425643a1952ab04738e9ce927",
|
||||
"package/types/artplayer-plugin-dash-control.d.ts": "e15ef683e0c335d81531587828593325e037efe7ed29c022477cd14fe71aebba"
|
||||
},
|
||||
"manifest": {
|
||||
"name": "artplayer-plugin-dash-control",
|
||||
"version": "1.1.0",
|
||||
"description": "Dash control plugin for ArtPlayer",
|
||||
"author": "Harvey Zhao <laozhaochaguan@gmail.com>",
|
||||
"license": "MIT",
|
||||
"homepage": "https://artplayer.org",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/zhw2590582/ArtPlayer.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/zhw2590582/ArtPlayer/issues"
|
||||
},
|
||||
"keywords": [
|
||||
"html5",
|
||||
"video",
|
||||
"player"
|
||||
],
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./types/artplayer-plugin-dash-control.d.ts",
|
||||
"import": "./dist/artplayer-plugin-dash-control.mjs",
|
||||
"require": "./dist/artplayer-plugin-dash-control.js"
|
||||
},
|
||||
"./legacy": {
|
||||
"types": "./types/artplayer-plugin-dash-control.d.ts",
|
||||
"import": "./dist/artplayer-plugin-dash-control.legacy.js",
|
||||
"require": "./dist/artplayer-plugin-dash-control.legacy.js"
|
||||
}
|
||||
},
|
||||
"main": "./dist/artplayer-plugin-dash-control.js",
|
||||
"module": "./dist/artplayer-plugin-dash-control.mjs",
|
||||
"types": "./types/artplayer-plugin-dash-control.d.ts",
|
||||
"legacy": "./dist/artplayer-plugin-dash-control.legacy.js",
|
||||
"browserslist": "last 1 Chrome version"
|
||||
}
|
||||
},
|
||||
"sourceSHA256": "c1c63bd0ec787a6f9eb445b53341dd5eaeaccccd1589455234be311fcf6ce5e6",
|
||||
"candidate": "packages/artplayer-plugin-dash-control/dist/artplayer-plugin-dash-control.js",
|
||||
"scope": "Real ArtPlayer DOM and native MP4 with controlled SDK methods. No dash.js instance, MPD, ABR or adaptive media validation."
|
||||
},
|
||||
"engines": [
|
||||
{
|
||||
"project": "chromium",
|
||||
"version": "153.0.8010.12",
|
||||
"platform": "win32"
|
||||
},
|
||||
{
|
||||
"project": "firefox",
|
||||
"version": "155.0",
|
||||
"platform": "win32"
|
||||
},
|
||||
{
|
||||
"project": "webkit",
|
||||
"version": "26.6",
|
||||
"platform": "win32"
|
||||
}
|
||||
],
|
||||
"manifest": {
|
||||
"kind": "artplayer-browser-test",
|
||||
"resources": {
|
||||
"/candidate/artplayer.js": {
|
||||
"source": {
|
||||
"kind": "workspace-build",
|
||||
"entry": "packages\\artplayer\\src\\index.ts"
|
||||
},
|
||||
"sha256": "df495dd386d32ea96be5006e1bbfda1ea91cfeb32aa21758197b46a155ebe0dc",
|
||||
"bytes": 378040
|
||||
},
|
||||
"/compiled/artplayer.js": {
|
||||
"source": {
|
||||
"kind": "workspace-build",
|
||||
"entry": "packages\\artplayer\\src\\index.ts"
|
||||
},
|
||||
"sha256": "df495dd386d32ea96be5006e1bbfda1ea91cfeb32aa21758197b46a155ebe0dc",
|
||||
"bytes": 378040,
|
||||
"aliasOf": "/candidate/artplayer.js"
|
||||
},
|
||||
"/uncompiled/artplayer/index.js": {
|
||||
"source": {
|
||||
"kind": "workspace-build",
|
||||
"entry": "packages\\artplayer\\src\\index.ts"
|
||||
},
|
||||
"sha256": "df495dd386d32ea96be5006e1bbfda1ea91cfeb32aa21758197b46a155ebe0dc",
|
||||
"bytes": 378040,
|
||||
"aliasOf": "/candidate/artplayer.js"
|
||||
},
|
||||
"/candidate/artplayer-plugin-chapter.js": {
|
||||
"source": {
|
||||
"kind": "workspace-build",
|
||||
"entry": "packages\\artplayer-plugin-chapter\\src\\index.ts"
|
||||
},
|
||||
"sha256": "1a8eb891d3690e2526e78a2e53183a25367b1d02a5fd349d821548edb731ee3a",
|
||||
"bytes": 8529
|
||||
},
|
||||
"/compiled/artplayer-plugin-chapter.js": {
|
||||
"source": {
|
||||
"kind": "workspace-build",
|
||||
"entry": "packages\\artplayer-plugin-chapter\\src\\index.ts"
|
||||
},
|
||||
"sha256": "1a8eb891d3690e2526e78a2e53183a25367b1d02a5fd349d821548edb731ee3a",
|
||||
"bytes": 8529,
|
||||
"aliasOf": "/candidate/artplayer-plugin-chapter.js"
|
||||
},
|
||||
"/uncompiled/artplayer-plugin-chapter/index.js": {
|
||||
"source": {
|
||||
"kind": "workspace-build",
|
||||
"entry": "packages\\artplayer-plugin-chapter\\src\\index.ts"
|
||||
},
|
||||
"sha256": "1a8eb891d3690e2526e78a2e53183a25367b1d02a5fd349d821548edb731ee3a",
|
||||
"bytes": 8529,
|
||||
"aliasOf": "/candidate/artplayer-plugin-chapter.js"
|
||||
},
|
||||
"/published/artplayer.js": {
|
||||
"source": {
|
||||
"kind": "npm-release",
|
||||
"version": "5.4.0",
|
||||
"integrity": "sha512-2B+plbx8N2yNsjK4nJU3+EOG8TULm1LRZk/QPkWRAMEX2Ee/MSnZG/WJYz8kcoZxZuLKcQ3uXifqLuPxZOH29A==",
|
||||
"member": "package/dist/artplayer.js"
|
||||
},
|
||||
"sha256": "f1db1fa3406aa6d41baa7c554749429a8e79f99d56a794bde23345eb9c07573f",
|
||||
"bytes": 134037
|
||||
},
|
||||
"/published/artplayer-plugin-chapter.js": {
|
||||
"source": {
|
||||
"kind": "npm-release",
|
||||
"version": "1.1.0",
|
||||
"integrity": "sha512-ji66Lr2K2S+OjFMuMby3yq+pefWlOC51Sbpop/DIr2N6w3F0smrF+YZbshN1Dy8/Tx1TYpT7N6WkkZWy5Jix1w==",
|
||||
"member": "package/dist/artplayer-plugin-chapter.js"
|
||||
},
|
||||
"sha256": "c2aaee11bf1db765433b01d9f8dd3978e41fa4d39655db00ebd03fc0be3e35e0",
|
||||
"bytes": 4973
|
||||
},
|
||||
"/test/pattern.mp4": {
|
||||
"source": {
|
||||
"kind": "generated-media",
|
||||
"file": "test/browser/media/pattern.mp4"
|
||||
},
|
||||
"sha256": "0ad1d7ad286aec6e0fa9e27fc1e487e7277df2e603e30040fb942365763d0233",
|
||||
"bytes": 251538
|
||||
},
|
||||
"/test/pattern.webm": {
|
||||
"source": {
|
||||
"kind": "generated-media",
|
||||
"file": "test/browser/media/pattern.webm"
|
||||
},
|
||||
"sha256": "9d3c2b3dec9581a599de2d6732992ef7846790e0d1368e6e131bd339b39e984f",
|
||||
"bytes": 162073
|
||||
},
|
||||
"/test/audio-tone.m4a": {
|
||||
"source": {
|
||||
"kind": "generated-media",
|
||||
"file": "test/browser/media/audio-tone.m4a"
|
||||
},
|
||||
"sha256": "83e6672b880b5d1c97093b1e26bdc40221c0b279615f40e99dbf9d2ade7b4d56",
|
||||
"bytes": 132436
|
||||
},
|
||||
"/test/declaration-cues.vtt": {
|
||||
"source": {
|
||||
"kind": "test-subtitles",
|
||||
"file": "test/browser/media/declaration-cues.vtt"
|
||||
},
|
||||
"sha256": "5d149d4e641ce41cbb83376835c03daf1fb89f3b9f568fa59e064d51e2b57704",
|
||||
"bytes": 54
|
||||
},
|
||||
"/test/thumbnail-grid.svg": {
|
||||
"source": {
|
||||
"kind": "test-thumbnail-grid",
|
||||
"file": "test/browser/media/thumbnail-grid.svg"
|
||||
},
|
||||
"sha256": "bb3f1ec9895d37def77ca4b192d3cd73577a2668558cc99de1588f511d65d3e6",
|
||||
"bytes": 14969
|
||||
},
|
||||
"/test/legacy-safe-area.js": {
|
||||
"source": {
|
||||
"kind": "frozen-own-source",
|
||||
"file": "test/helpers/legacy-safe-area.js",
|
||||
"commit": "ccf77c4e"
|
||||
},
|
||||
"sha256": "ce0368618ef6363b78271b8bcc7973abd0478e797cc464ddd912929e6dfa501a",
|
||||
"bytes": 795
|
||||
},
|
||||
"/assets/sample/video.mp4": {
|
||||
"source": {
|
||||
"kind": "docs-media",
|
||||
"file": "docs/assets/sample/video.mp4"
|
||||
},
|
||||
"sha256": "30a2de667ba267b6bc605edcee1c4bd728c048ed76f500ea0e73d1f1a6f8f980",
|
||||
"bytes": 5818706
|
||||
},
|
||||
"/test/performance.js": {
|
||||
"source": {
|
||||
"kind": "adapted-baseline-fixture",
|
||||
"file": "refactor/fixtures/performance.js"
|
||||
},
|
||||
"sha256": "2318f39bdb5f3a3348d755cb4335907ae4adc944be3a435839c1bba201e81500",
|
||||
"bytes": 8994
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/dash03-legacy-final/report.json",
|
||||
"sha256": "5eaa45267a3f0e6a5c5922f974d46737a7d3c559c9a8e2dab84e8262269e6d83",
|
||||
"bytes": 15807757,
|
||||
"stats": {
|
||||
"startTime": "2026-09-11T20:55:36.976Z",
|
||||
"duration": 69224.891,
|
||||
"expected": 84,
|
||||
"skipped": 0,
|
||||
"unexpected": 0,
|
||||
"flaky": 0
|
||||
},
|
||||
"results": {
|
||||
"directory": "refactor/.cache/dash03-legacy-final/results",
|
||||
"files": 85,
|
||||
"sha256": "a7016f915efb43c45b31f9d25b755fba95c649ec7008bb397130cc840e78b7fe",
|
||||
"algorithm": "SHA256 of JSON.stringify(sorted relative path and raw SHA256 pairs), UTF-8, slash paths"
|
||||
},
|
||||
"pluginInput": {
|
||||
"published": {
|
||||
"name": "artplayer-plugin-dash-control",
|
||||
"version": "1.1.0",
|
||||
"metadataUrl": "https://registry.npmjs.org/artplayer-plugin-dash-control/1.1.0",
|
||||
"tarball": "https://registry.npmjs.org/artplayer-plugin-dash-control/-/artplayer-plugin-dash-control-1.1.0.tgz",
|
||||
"integrity": "sha512-EssRkzYXWcln3a6nAzi4ESacBhzMih8s0bzdjXuvBBCh2UFmvK99ITD5E+0cuU8i5ZzIQGQSdce1/gANTrX0BQ==",
|
||||
"sha256": "f4047a569d99b6a7de57b3719b7ad40109f2a1d360566a840f50ab32aacefcd9",
|
||||
"archiveBytes": 3385,
|
||||
"registryGitHead": "daf133b22630b4a0eecfa3336bbddab0e9119d96",
|
||||
"files": {
|
||||
"package/README.md": "98982f1123ff9a11f5d3d9a52da6e8fea9cd0989f7465605c27b0f9a46f2dbcd",
|
||||
"package/dist/artplayer-plugin-dash-control.js": "f0cbbcf8badeaca13c05ae178932818cb52fbce37ed504ac0e62160b9c7e1145",
|
||||
"package/dist/artplayer-plugin-dash-control.legacy.js": "f0cbbcf8badeaca13c05ae178932818cb52fbce37ed504ac0e62160b9c7e1145",
|
||||
"package/dist/artplayer-plugin-dash-control.mjs": "e32bf08c33a481781883067dcc639f0495c9dcb8a88b91be13736370f4932fc4",
|
||||
"package/package.json": "556e2adc479bab36c5973de617c0b57c5d86aa8425643a1952ab04738e9ce927",
|
||||
"package/types/artplayer-plugin-dash-control.d.ts": "e15ef683e0c335d81531587828593325e037efe7ed29c022477cd14fe71aebba"
|
||||
},
|
||||
"manifest": {
|
||||
"name": "artplayer-plugin-dash-control",
|
||||
"version": "1.1.0",
|
||||
"description": "Dash control plugin for ArtPlayer",
|
||||
"author": "Harvey Zhao <laozhaochaguan@gmail.com>",
|
||||
"license": "MIT",
|
||||
"homepage": "https://artplayer.org",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/zhw2590582/ArtPlayer.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/zhw2590582/ArtPlayer/issues"
|
||||
},
|
||||
"keywords": [
|
||||
"html5",
|
||||
"video",
|
||||
"player"
|
||||
],
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./types/artplayer-plugin-dash-control.d.ts",
|
||||
"import": "./dist/artplayer-plugin-dash-control.mjs",
|
||||
"require": "./dist/artplayer-plugin-dash-control.js"
|
||||
},
|
||||
"./legacy": {
|
||||
"types": "./types/artplayer-plugin-dash-control.d.ts",
|
||||
"import": "./dist/artplayer-plugin-dash-control.legacy.js",
|
||||
"require": "./dist/artplayer-plugin-dash-control.legacy.js"
|
||||
}
|
||||
},
|
||||
"main": "./dist/artplayer-plugin-dash-control.js",
|
||||
"module": "./dist/artplayer-plugin-dash-control.mjs",
|
||||
"types": "./types/artplayer-plugin-dash-control.d.ts",
|
||||
"legacy": "./dist/artplayer-plugin-dash-control.legacy.js",
|
||||
"browserslist": "last 1 Chrome version"
|
||||
}
|
||||
},
|
||||
"sourceSHA256": "1b6fba04c90ba19197213cf1868599dab13d430e8923d3081e37c43dc4d5d00e",
|
||||
"candidate": "packages/artplayer-plugin-dash-control/dist/artplayer-plugin-dash-control.legacy.js",
|
||||
"scope": "Real ArtPlayer DOM and native MP4 with controlled SDK methods. No dash.js instance, MPD, ABR or adaptive media validation."
|
||||
},
|
||||
"engines": [
|
||||
{
|
||||
"project": "chromium",
|
||||
"version": "153.0.8010.12",
|
||||
"platform": "win32"
|
||||
},
|
||||
{
|
||||
"project": "firefox",
|
||||
"version": "155.0",
|
||||
"platform": "win32"
|
||||
},
|
||||
{
|
||||
"project": "webkit",
|
||||
"version": "26.6",
|
||||
"platform": "win32"
|
||||
}
|
||||
],
|
||||
"manifest": {
|
||||
"kind": "artplayer-browser-test",
|
||||
"resources": {
|
||||
"/candidate/artplayer.js": {
|
||||
"source": {
|
||||
"kind": "workspace-build",
|
||||
"entry": "packages\\artplayer\\src\\index.ts"
|
||||
},
|
||||
"sha256": "df495dd386d32ea96be5006e1bbfda1ea91cfeb32aa21758197b46a155ebe0dc",
|
||||
"bytes": 378040
|
||||
},
|
||||
"/compiled/artplayer.js": {
|
||||
"source": {
|
||||
"kind": "workspace-build",
|
||||
"entry": "packages\\artplayer\\src\\index.ts"
|
||||
},
|
||||
"sha256": "df495dd386d32ea96be5006e1bbfda1ea91cfeb32aa21758197b46a155ebe0dc",
|
||||
"bytes": 378040,
|
||||
"aliasOf": "/candidate/artplayer.js"
|
||||
},
|
||||
"/uncompiled/artplayer/index.js": {
|
||||
"source": {
|
||||
"kind": "workspace-build",
|
||||
"entry": "packages\\artplayer\\src\\index.ts"
|
||||
},
|
||||
"sha256": "df495dd386d32ea96be5006e1bbfda1ea91cfeb32aa21758197b46a155ebe0dc",
|
||||
"bytes": 378040,
|
||||
"aliasOf": "/candidate/artplayer.js"
|
||||
},
|
||||
"/candidate/artplayer-plugin-chapter.js": {
|
||||
"source": {
|
||||
"kind": "workspace-build",
|
||||
"entry": "packages\\artplayer-plugin-chapter\\src\\index.ts"
|
||||
},
|
||||
"sha256": "1a8eb891d3690e2526e78a2e53183a25367b1d02a5fd349d821548edb731ee3a",
|
||||
"bytes": 8529
|
||||
},
|
||||
"/compiled/artplayer-plugin-chapter.js": {
|
||||
"source": {
|
||||
"kind": "workspace-build",
|
||||
"entry": "packages\\artplayer-plugin-chapter\\src\\index.ts"
|
||||
},
|
||||
"sha256": "1a8eb891d3690e2526e78a2e53183a25367b1d02a5fd349d821548edb731ee3a",
|
||||
"bytes": 8529,
|
||||
"aliasOf": "/candidate/artplayer-plugin-chapter.js"
|
||||
},
|
||||
"/uncompiled/artplayer-plugin-chapter/index.js": {
|
||||
"source": {
|
||||
"kind": "workspace-build",
|
||||
"entry": "packages\\artplayer-plugin-chapter\\src\\index.ts"
|
||||
},
|
||||
"sha256": "1a8eb891d3690e2526e78a2e53183a25367b1d02a5fd349d821548edb731ee3a",
|
||||
"bytes": 8529,
|
||||
"aliasOf": "/candidate/artplayer-plugin-chapter.js"
|
||||
},
|
||||
"/published/artplayer.js": {
|
||||
"source": {
|
||||
"kind": "npm-release",
|
||||
"version": "5.4.0",
|
||||
"integrity": "sha512-2B+plbx8N2yNsjK4nJU3+EOG8TULm1LRZk/QPkWRAMEX2Ee/MSnZG/WJYz8kcoZxZuLKcQ3uXifqLuPxZOH29A==",
|
||||
"member": "package/dist/artplayer.js"
|
||||
},
|
||||
"sha256": "f1db1fa3406aa6d41baa7c554749429a8e79f99d56a794bde23345eb9c07573f",
|
||||
"bytes": 134037
|
||||
},
|
||||
"/published/artplayer-plugin-chapter.js": {
|
||||
"source": {
|
||||
"kind": "npm-release",
|
||||
"version": "1.1.0",
|
||||
"integrity": "sha512-ji66Lr2K2S+OjFMuMby3yq+pefWlOC51Sbpop/DIr2N6w3F0smrF+YZbshN1Dy8/Tx1TYpT7N6WkkZWy5Jix1w==",
|
||||
"member": "package/dist/artplayer-plugin-chapter.js"
|
||||
},
|
||||
"sha256": "c2aaee11bf1db765433b01d9f8dd3978e41fa4d39655db00ebd03fc0be3e35e0",
|
||||
"bytes": 4973
|
||||
},
|
||||
"/test/pattern.mp4": {
|
||||
"source": {
|
||||
"kind": "generated-media",
|
||||
"file": "test/browser/media/pattern.mp4"
|
||||
},
|
||||
"sha256": "0ad1d7ad286aec6e0fa9e27fc1e487e7277df2e603e30040fb942365763d0233",
|
||||
"bytes": 251538
|
||||
},
|
||||
"/test/pattern.webm": {
|
||||
"source": {
|
||||
"kind": "generated-media",
|
||||
"file": "test/browser/media/pattern.webm"
|
||||
},
|
||||
"sha256": "9d3c2b3dec9581a599de2d6732992ef7846790e0d1368e6e131bd339b39e984f",
|
||||
"bytes": 162073
|
||||
},
|
||||
"/test/audio-tone.m4a": {
|
||||
"source": {
|
||||
"kind": "generated-media",
|
||||
"file": "test/browser/media/audio-tone.m4a"
|
||||
},
|
||||
"sha256": "83e6672b880b5d1c97093b1e26bdc40221c0b279615f40e99dbf9d2ade7b4d56",
|
||||
"bytes": 132436
|
||||
},
|
||||
"/test/declaration-cues.vtt": {
|
||||
"source": {
|
||||
"kind": "test-subtitles",
|
||||
"file": "test/browser/media/declaration-cues.vtt"
|
||||
},
|
||||
"sha256": "5d149d4e641ce41cbb83376835c03daf1fb89f3b9f568fa59e064d51e2b57704",
|
||||
"bytes": 54
|
||||
},
|
||||
"/test/thumbnail-grid.svg": {
|
||||
"source": {
|
||||
"kind": "test-thumbnail-grid",
|
||||
"file": "test/browser/media/thumbnail-grid.svg"
|
||||
},
|
||||
"sha256": "bb3f1ec9895d37def77ca4b192d3cd73577a2668558cc99de1588f511d65d3e6",
|
||||
"bytes": 14969
|
||||
},
|
||||
"/test/legacy-safe-area.js": {
|
||||
"source": {
|
||||
"kind": "frozen-own-source",
|
||||
"file": "test/helpers/legacy-safe-area.js",
|
||||
"commit": "ccf77c4e"
|
||||
},
|
||||
"sha256": "ce0368618ef6363b78271b8bcc7973abd0478e797cc464ddd912929e6dfa501a",
|
||||
"bytes": 795
|
||||
},
|
||||
"/assets/sample/video.mp4": {
|
||||
"source": {
|
||||
"kind": "docs-media",
|
||||
"file": "docs/assets/sample/video.mp4"
|
||||
},
|
||||
"sha256": "30a2de667ba267b6bc605edcee1c4bd728c048ed76f500ea0e73d1f1a6f8f980",
|
||||
"bytes": 5818706
|
||||
},
|
||||
"/test/performance.js": {
|
||||
"source": {
|
||||
"kind": "adapted-baseline-fixture",
|
||||
"file": "refactor/fixtures/performance.js"
|
||||
},
|
||||
"sha256": "2318f39bdb5f3a3348d755cb4335907ae4adc944be3a435839c1bba201e81500",
|
||||
"bytes": 8994
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"logs": [
|
||||
{
|
||||
"file": "refactor/.cache/dash03-before.log",
|
||||
"sha256": "5a4e92a1bcf6a8117ba6745d1d54ade25d58643200ffc1d18d0f09e8e1ba8e28",
|
||||
"bytes": 175508
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/dash03-reentry-before.log",
|
||||
"sha256": "bc04fcd6b8518f74718bcc732a26f88fb919a79f63a1dc073fe724849ea0c99d",
|
||||
"bytes": 4865
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/dash03-formatter-before.log",
|
||||
"sha256": "0799ccc96096116e75e83db65289102bde825f57796bfb620579927d982084d0",
|
||||
"bytes": 4840
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/dash03-node-final.log",
|
||||
"sha256": "4455e88c907ea0f2f335b67c020bf819e47b124fc3e766c1bb220b6135934f6d",
|
||||
"bytes": 11463
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/dash03-artifacts-realm-before.log",
|
||||
"sha256": "0a09f96f3457128982aa93d3d5c9a93f84900ea1bf09add729c47d048714b7be",
|
||||
"bytes": 36034
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/dash03-artifacts-final.log",
|
||||
"sha256": "c68ec1c1ab54f7e389ce210bc95276ed6484f499e3d2d90cb023bf92a2dcb76b",
|
||||
"bytes": 31662
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/dash03-build-final.log",
|
||||
"sha256": "90a8251da20f5ebe7cf2759e87284ab0265bc9db4b7a12dc14304f14019883c1",
|
||||
"bytes": 1472
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/dash03-main.log",
|
||||
"sha256": "9c55a02c2bc12d2128779677d9a02a4be3e3c57a50a5ad46631bc703b9d58a99",
|
||||
"bytes": 16163
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/dash03-main-final.log",
|
||||
"sha256": "896e9acf28c7b66998dd25ad9c39304242180d8e273f27f16a52a8c3d02fe162",
|
||||
"bytes": 16162
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/dash03-legacy-final.log",
|
||||
"sha256": "837a8ad81b4306231d44f9ca38f328afef8a57c1f604196bc64319b772049167",
|
||||
"bytes": 16160
|
||||
},
|
||||
{
|
||||
"file": "refactor/.cache/dash03-ci.log",
|
||||
"sha256": "4d88eba96b783e9e26ea2197c398160b2565e622a58ab5b80db44375bcd85722",
|
||||
"bytes": 74484
|
||||
}
|
||||
],
|
||||
"results": {
|
||||
"initialRegression": {
|
||||
"passed": 1,
|
||||
"failed": 21
|
||||
},
|
||||
"reentrantCleanupBefore": {
|
||||
"passed": 29,
|
||||
"failed": 2
|
||||
},
|
||||
"formatterShutdownBefore": {
|
||||
"passed": 29,
|
||||
"failed": 2
|
||||
},
|
||||
"nodeFinal": 116,
|
||||
"nodeWithThreeArtifacts": 266,
|
||||
"mainBrowser": 84,
|
||||
"legacyBrowser": 84,
|
||||
"ci": {
|
||||
"unit": 661,
|
||||
"engineering": 14,
|
||||
"baseline": 37
|
||||
}
|
||||
},
|
||||
"localEvidence": "Raw logs and browser reports/traces remain local cache. Committed hashes locate evidence but are not uploaded traces; rerun on a fresh machine."
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
# PKG-DASH-03:两代 SDK 适配、菜单与生命周期拆分
|
||||
|
||||
起点 `762cc634`。把单一入口拆为入口生命周期、SDK 方法适配、selector 模型、菜单所有权
|
||||
四个模块。保持 JavaScript 完成本步结构与行为修复,下一步 04 再迁移严格 TS 和公开声明。
|
||||
包内 [架构图](../../packages/artplayer-plugin-dash-control/ARCHITECTURE.md) 随实际实现维护。
|
||||
|
||||
## 行为修正
|
||||
|
||||
- 恢复发布版 dash.js 4.x 方法表,同时保留工作区 5.x 稳定 ID;不使用过滤后的下标选画质。
|
||||
- 空拓扑、禁用菜单、渲染失败清理自有 UI;换 SDK/重建/销毁后旧引用无副作用。
|
||||
- 销毁释放 ready/restart/destroy 订阅,安装异常回滚;不销毁调用方 SDK,不误删调用方替换的菜单。
|
||||
- 当前重复标签保留实际当前 key/track;零 ID、唯一克隆轨道高亮正确,歧义轨道不伪造选中。
|
||||
- SDK/getName 重入使旧 revision 失效;回调销毁后停止后续格式化、SDK 写入和提示。
|
||||
清理中同步重建时只释放旧所有权,不删除重建的新菜单。
|
||||
|
||||
公开工厂、同步 update/选中返回、getName 身份/参数、通知顺序、DOM/CSS hooks 与包入口保持。
|
||||
错误继续同步传播原对象,次要清理异常不覆盖原错误。上述历史缺陷作为有意修正记录,
|
||||
不把冻结旧实现的缺陷观察改成候选期望。README 更新 SDK 适配说明,没有改 demo 或增加依赖。
|
||||
|
||||
## 验证
|
||||
|
||||
最初 22 项新增回归先运行旧源码;后续重入和格式化关闭的两组红灯另保留。正式三格式加入
|
||||
生命周期回归时发现 4 项 Node VM 数组原型比较问题,实际内容相同;用本地 Array.from
|
||||
比较原有精确内容,未放宽断言。所有日志和输入指纹见 [执行证据](../baselines/dash-runtime.json)。
|
||||
|
||||
最终源码 116 项、包含三格式及两代 SDK 的 Node 266 项通过;正式 main/legacy 浏览器各
|
||||
84 项通过。完整 CI 712 项(661 单元、14 工程、37 基线)通过;包三格式构建成功,
|
||||
计划/风险/本地链接校验通过。没有重试或跳过。DASH-LIFE-01 关闭,SDK/实际轨道风险仍 open。
|
||||
当前 217 项:73 done、3 doing、141 todo。已完成结构步,不计为 TS 迁移或发布完成。
|
||||
|
||||
真实 DOM 浏览器测试扩充到两种候选 SDK、两代核心及空拓扑/销毁:每格式 84 项。
|
||||
仍是可控 SDK 方法与原生 MP4,不是实际 dash.js/MPD/ABR 验收。SDK/状态的真实媒体风险
|
||||
继续由 05 接续;04 精确类型、06 分发、真机和全局发布复盘没有被本步替代。
|
||||
|
||||
本步通过标准构建脚本生成 dist/docs compiled,未手改产物。独立本地提交;不推送、不发布。
|
||||
回退本提交同时撤回结构、行为修正、回归和产物,恢复重构前仅支持 5.x 的源码;该限制需知悉。
|
||||
+19
-15
@@ -1,30 +1,34 @@
|
||||
# DASH Control 验证与维护入口
|
||||
|
||||
PKG-DASH-02 保护真实 npm 1.1.0 的既有调用契约,以及重构前工作区的 SDK 5.x 稳定 ID
|
||||
修正。版本来源见 [发布契约](baselines/dash-control-contract.md)。本步没有更改生产实现。
|
||||
测试保护真实 npm 1.1.0 的既有调用契约,以及重构前工作区的 SDK 5.x 稳定 ID 修正。
|
||||
版本来源见 [发布契约](baselines/dash-control-contract.md)。02 建立基线,03 已改造生产实现;
|
||||
实际模块地图与生命周期规则见 [包内架构](../packages/artplayer-plugin-dash-control/ARCHITECTURE.md)。
|
||||
|
||||
## 可重跑的测试
|
||||
|
||||
- `yarn test:dash-control`:原有 5 项过滤/稳定 ID 回归和新增 69 项行为测试,共 74 项。
|
||||
- `yarn test:dash-control`:原有 5 项过滤/稳定 ID 回归、80 项行为/历史观察、31 项候选生命周期,共 116 项。
|
||||
该脚本和 `test:unit` 都保留旧测试,完整 CI 自动执行。
|
||||
- `yarn test:browser test/browser/dash-control.spec.js --trace on`:48 项,两个核心、两个插件、
|
||||
两类菜单、两种选择、Chromium/Firefox/WebKit。浏览器套件串行运行,避免共享端口和报告覆盖。
|
||||
- `yarn test:browser test/browser/dash-control.spec.js --trace on`:84 项,两个核心、发布插件 SDK 4/
|
||||
候选插件 SDK 4/5、两类菜单、两种选择及候选清理、Chromium/Firefox/WebKit。
|
||||
浏览器套件串行运行,避免共享端口和报告覆盖。
|
||||
- `yarn ci:check`:工具链、计划、lint、声明漂移、严格类型和 Node/基线测试。
|
||||
|
||||
Node helper 分别加载当前源码、固定 Git 提交的旧工作区 UMD、实际 npm main/legacy/module。
|
||||
Git 内容以 LF 指纹核对,发布归档与成员以原始字节指纹核对。`ARTPLAYER_TEST_DASH` 可指定
|
||||
待测产物,以系统路径分隔符连接;未设置时不会把源码测试冒充三种候选产物验证。
|
||||
待测产物,以系统路径分隔符连接;同时指定 main/legacy/module 时共 266 项,包含两代 SDK 的
|
||||
行为与生命周期。未设置时不会把源码测试冒充三种候选产物验证。
|
||||
|
||||
`test/helpers/dash-control.js` 只提供可控 SDK 方法和组件注册表,检查 this、类型参数、
|
||||
原始轨道身份、调用顺序、错误身份、ABR 配置保留、换 SDK、多实例和关闭后的引用。
|
||||
真实旧实现的缺陷观察保持冻结;03 修复时为候选实现新增正确行为断言,不把历史缺陷当作兼容要求。
|
||||
真实旧实现的缺陷观察保持冻结;候选生命周期新增正确行为断言,不把历史缺陷当作兼容要求。
|
||||
|
||||
浏览器使用真实发布/候选 ArtPlayer DOM、真实发布/候选插件和本地原生 MP4。SDK 方法可控,
|
||||
断言真实点击触发的参数及控制栏/设置同步,再通过真实播放按钮验证视频时钟推进、无媒体错误。
|
||||
**没有加载 dash.js,没有验证 MPD、ABR 自动切换或自适应流解码。** 这些仍是 05 必须完成的
|
||||
固定 dash.js 4.5.2 / 5.2.1 集成验证。`ARTPLAYER_DASH_ARTIFACT` 可替换插件脚本;本次没有使用。
|
||||
固定 dash.js 4.5.2 / 5.2.1 集成验证。`ARTPLAYER_DASH_ARTIFACT` 可替换插件脚本;02 未设置,
|
||||
03 分别使用正式 main 与 legacy 文件。核心仍按服务端 manifest 加载发布/候选源码构建。
|
||||
|
||||
## 首次失败与证据
|
||||
## 02 首次失败与证据
|
||||
|
||||
首次浏览器运行 24 通过、24 失败;全部失败位于设置选项文字定位。trace 中选项确实已渲染,
|
||||
整行文本包含图标 SVG 的换行,`^fr$`/`^720p$` 不能匹配。改为当前面板内专用
|
||||
@@ -35,11 +39,11 @@ Git 内容以 LF 指纹核对,发布归档与成员以原始字节指纹核对
|
||||
[本步执行记录](baselines/dash-validation.json)。缓存不入 Git,永久保存的是测试、来源和结果摘要;
|
||||
换机器应按上述命令重新取得执行证据,不能声称本地 trace 已上传。
|
||||
|
||||
## 下一步缺陷与边界
|
||||
## 03 修正与后续边界
|
||||
|
||||
- DASH-SDK-01:两代旧实现分别在另一代 SDK 方法表上失败。03 通过能力适配恢复旧用法。
|
||||
- DASH-LIFE-01:空列表/禁用后 UI 残留,旧回调仍写旧 SDK,销毁后 update 和订阅仍有效。
|
||||
- DASH-STATE-01:克隆当前轨道、重复标签、数字零 representation ID 会丢失高亮。
|
||||
- DASH-SDK-01:通过能力适配恢复两代方法表,实际 SDK/媒体仍由 05 验证。
|
||||
- DASH-LIFE-01:空列表/禁用后 UI、旧回调、销毁后 update/订阅已修正,补充重入和错误回归。
|
||||
- DASH-STATE-01:唯一克隆当前轨道、重复标签、数字零 ID 已修正;实际 SDK 轨道身份仍由 05 核验。
|
||||
|
||||
这些受控复现仍是 open,不能以本步绿色测试关闭。03 处理结构、清理与正确当前项,04 完成公开
|
||||
类型兼容,05 完成真实 SDK/媒体/组合验证,06 完成包分发验收。物理设备和 npm 发布准入仍待完成。
|
||||
状态以 risks.json 为准;受控修复不替代真实 SDK 验收。04 完成严格 TS/公开类型兼容,05 完成
|
||||
真实 SDK/媒体/组合验证,06 完成包分发验收。物理设备和 npm 发布准入仍待完成。
|
||||
+3
-2
@@ -4,7 +4,7 @@
|
||||
|
||||
基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 217 项,范围 22 个包及工作区/示例。
|
||||
|
||||
状态:todo 142 / doing 3 / blocked 0 / done 72 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
|
||||
状态:todo 141 / doing 3 / blocked 0 / done 73 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
|
||||
|
||||
前置依赖是启动条件;验收是完成条件。任务可以继续拆分,但不能复用或悄悄删除旧 ID。
|
||||
|
||||
@@ -203,7 +203,7 @@
|
||||
| --- | --- | --- | --- | --- | --- | --- |
|
||||
| PKG-DASH-01 | artplayer-plugin-dash-control<br>核对包契约与历史用法 | BASE-05 | quality/audio、representation ID/Auto、getName 和 update | 源码/声明/README/demo/发布包差异已登记;公开形状和版本范围冻结 | H | done |
|
||||
| PKG-DASH-02 | artplayer-plugin-dash-control<br>建立特有行为与错误测试 | PKG-DASH-01, ENG-03, ENG-05 | 保留已有 5 项稳定 ID 回归,补发布版 SDK 4.x/当前 5.x、音轨/空列表/过滤/换源与关闭引用 | 旧版本行为可重跑,成功/失败/切源/销毁有必要断言 | H | done |
|
||||
| PKG-DASH-03 | artplayer-plugin-dash-control<br>整理内部职责与资源 | PKG-DASH-02, CORE-11, CORE-14 | 能力适配保留 SDK 4.x 与 5.x,稳定 ID 映射、ABR 状态、UI 清理和生命周期职责分离 | 结构变化和缺陷修复分开记录;原 API/事件/资源生命周期通过 | H | todo |
|
||||
| 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 | todo |
|
||||
| PKG-DASH-05 | artplayer-plugin-dash-control<br>验证新旧核心和组合 | PKG-DASH-04, CORE-22 | 固定 dash.js 4.5.2/5.2.1 与本地 DASH 实际清晰度/音轨、高亮、Auto 和换源组合 | 最终核心与原支持范围核心分别通过;设备/SDK 缺证据不能标完成 | H | todo |
|
||||
| PKG-DASH-06 | artplayer-plugin-dash-control<br>验证分发并同步文档 | PKG-DASH-05, ENG-07 | dash.control.js、支持的 dash.js 版本与产物 | tarball 入口/资源、类型、8082 demo 和 README 一致,有回退记录 | H | todo |
|
||||
@@ -481,3 +481,4 @@
|
||||
- PKG-HLS-SDK-01: [记录](changes/2026-09-12-PKG-HLS-SDK-01-integration.md) [记录](baselines/hls-sdk-validation.json) [记录](baselines/hls-sdk-diagnostics.json)
|
||||
- PKG-DASH-01: [记录](changes/2026-09-12-PKG-DASH-01-contract.md) [记录](baselines/dash-control-contract.md) [记录](baselines/dash-control-release.json)
|
||||
- PKG-DASH-02: [记录](changes/2026-09-12-PKG-DASH-02-tests.md) [记录](dash-validation.md) [记录](baselines/dash-validation.json)
|
||||
- PKG-DASH-03: [记录](changes/2026-09-12-PKG-DASH-03-runtime.md) [记录](baselines/dash-runtime.json) [记录](dash-validation.md)
|
||||
@@ -1,5 +1,20 @@
|
||||
# 进度与证据
|
||||
|
||||
## 最新完成:DASH-03 SDK 适配与生命周期源码改造
|
||||
|
||||
DASH 源码已拆为入口生命周期、SDK 适配、selector 模型、菜单所有权四个 JS 模块。
|
||||
恢复发布版 4.x 方法表,保留 5.x 稳定 ID;修复空菜单残留、过期引用、销毁/安装异常清理、
|
||||
重复标签/零 ID/唯一克隆轨道高亮,以及清理重入误删新 UI 和销毁后继续格式化的问题。
|
||||
|
||||
源码 116 项、三格式两代 SDK Node 266 项通过;正式插件 main/legacy 三引擎、新旧核心
|
||||
DOM/原生 MP4 各 84 项通过。完整 CI 712 项通过,实际 SDK/MPD/ABR 与设备验证仍未完成。
|
||||
初始 21 项失败和两组重入/格式化失败、VM 数组比较修正证据均保留,未以重试跳过制造通过。
|
||||
DASH-LIFE-01 关闭;DASH-SDK-01/DASH-STATE-01 仍需 05 实际 SDK 验证。
|
||||
|
||||
当前 217 项:73 done、3 doing、141 todo。下一步 DASH-04 迁移这四个模块和公开类型到严格 TS。
|
||||
详见 [源码变更](changes/2026-09-12-PKG-DASH-03-runtime.md)、[执行证据](baselines/dash-runtime.json)
|
||||
及 [包内架构](../packages/artplayer-plugin-dash-control/ARCHITECTURE.md)。独立本地提交,不推送/发布。
|
||||
|
||||
## 最新完成:DASH-02 行为测试与真实 DOM 交互
|
||||
|
||||
保留原有 5 项稳定 ID 回归,新增发布三格式/冻结工作区/候选源码契约测试,定向 74 项通过。
|
||||
|
||||
@@ -136,7 +136,7 @@
|
||||
| CHAPTER-LAYOUT-01 | resolved / 已复现 | 长章节标题超出窄播放器进度条宽度 | PKG-CHAPTER-05 |
|
||||
| CHAPTER-TIMING-01 | open / 已复现 | Windows WebKit 清晰度切换曾在等待窗口内未见 restart,稍后状态恢复 | PKG-CHAPTER-05 |
|
||||
| DASH-SDK-01 | open / 已复现 | npm 发布版 4.x 与工作区仅 5.x 的 SDK 接口不兼容 | PKG-DASH-02, PKG-DASH-03, PKG-DASH-05 |
|
||||
| DASH-LIFE-01 | open / 已复现 | DASH 空拓扑残留 UI,监听和旧选择回调缺乏关闭守卫 | PKG-DASH-02, PKG-DASH-03 |
|
||||
| DASH-LIFE-01 | resolved / 已复现 | DASH 空拓扑残留 UI,监听和旧选择回调缺乏关闭守卫 | PKG-DASH-02, PKG-DASH-03 |
|
||||
| DASH-STATE-01 | open / 已复现 | DASH 当前项高亮使用真值 ID、音轨身份和标签去重,边界待核实 | PKG-DASH-02, PKG-DASH-03, PKG-DASH-05 |
|
||||
| DASH-TYPE-01 | open / 源码/产物事实 | DASH 类型未表达默认工厂参数、SDK 回调对象和现代模块入口 | PKG-DASH-04 |
|
||||
| DASH-DEMO-01 | open / 源码/产物事实 | DASH 示例仅新 SDK 且重复安装会累积销毁回调 | PKG-DASH-06 |
|
||||
+17
-5
@@ -2848,7 +2848,9 @@
|
||||
"packages/artplayer-plugin-dash-control/types/artplayer-plugin-dash-control.d.ts",
|
||||
"docs/assets/example/dash.control.js",
|
||||
"test/dash-contract.test.js",
|
||||
"refactor/dash-validation.md"
|
||||
"refactor/dash-validation.md",
|
||||
"test/dash-lifecycle.test.js",
|
||||
"refactor/baselines/dash-runtime.json"
|
||||
],
|
||||
"compatibleResolution": "按方法能力适配两代已存在用法,保留 v5 稳定 ID,不能要求旧消费者改用新 SDK。",
|
||||
"closureCriteria": "归档旧用法、v4/v5 受控及实际媒体矩阵通过,清晰度键不混用。"
|
||||
@@ -2857,7 +2859,7 @@
|
||||
"id": "DASH-LIFE-01",
|
||||
"title": "DASH 空拓扑残留 UI,监听和旧选择回调缺乏关闭守卫",
|
||||
"confirmation": "reproduced",
|
||||
"status": "open",
|
||||
"status": "resolved",
|
||||
"owners": [
|
||||
"PKG-DASH-02",
|
||||
"PKG-DASH-03"
|
||||
@@ -2869,10 +2871,18 @@
|
||||
"packages/artplayer-plugin-dash-control/types/artplayer-plugin-dash-control.d.ts",
|
||||
"docs/assets/example/dash.control.js",
|
||||
"test/dash-contract.test.js",
|
||||
"refactor/dash-validation.md"
|
||||
"refactor/dash-validation.md",
|
||||
"test/dash-lifecycle.test.js",
|
||||
"refactor/baselines/dash-runtime.json"
|
||||
],
|
||||
"compatibleResolution": "仅释放插件自有 ready/restart/destroy 订阅和 UI,SDK 仍由调用方拥有;替换后旧回调不写旧实例。",
|
||||
"closureCriteria": "空列表、换源/SDK、销毁后引用、安装异常有回归,其他实例与 SDK 不被误释放。"
|
||||
"closureCriteria": "空列表、换源/SDK、销毁后引用、安装异常有回归,其他实例与 SDK 不被误释放。",
|
||||
"resolutionEvidence": [
|
||||
"refactor/changes/2026-09-12-PKG-DASH-03-runtime.md",
|
||||
"refactor/baselines/dash-runtime.json",
|
||||
"packages/artplayer-plugin-dash-control/ARCHITECTURE.md"
|
||||
],
|
||||
"resolutionRationale": "空拓扑/禁用/失败/销毁清理自有 UI 与订阅,旧 revision/SDK 引用无副作用;先红后绿、两代方法表及三格式 Node 生命周期、新旧核心 main/legacy 各 84 项真实 DOM 通过。只关闭插件所有权和引用缺陷,不替代实际 SDK/MPD、demo 或设备验证。"
|
||||
},
|
||||
{
|
||||
"id": "DASH-STATE-01",
|
||||
@@ -2891,7 +2901,9 @@
|
||||
"packages/artplayer-plugin-dash-control/types/artplayer-plugin-dash-control.d.ts",
|
||||
"docs/assets/example/dash.control.js",
|
||||
"test/dash-contract.test.js",
|
||||
"refactor/dash-validation.md"
|
||||
"refactor/dash-validation.md",
|
||||
"test/dash-lifecycle.test.js",
|
||||
"refactor/baselines/dash-runtime.json"
|
||||
],
|
||||
"compatibleResolution": "验证零 ID、重复标签和克隆音轨当前项,在保持原展示语义时按实际可选项定位高亮。",
|
||||
"closureCriteria": "有受控和真实 SDK 证据说明选中的 representation/track 与 UI 一致。"
|
||||
|
||||
+6
-2
@@ -2014,11 +2014,15 @@
|
||||
"CORE-11",
|
||||
"CORE-14"
|
||||
],
|
||||
"status": "todo",
|
||||
"status": "done",
|
||||
"risk": "H",
|
||||
"deliverable": "能力适配保留 SDK 4.x 与 5.x,稳定 ID 映射、ABR 状态、UI 清理和生命周期职责分离",
|
||||
"acceptance": "结构变化和缺陷修复分开记录;原 API/事件/资源生命周期通过",
|
||||
"evidence": []
|
||||
"evidence": [
|
||||
"changes/2026-09-12-PKG-DASH-03-runtime.md",
|
||||
"baselines/dash-runtime.json",
|
||||
"dash-validation.md"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "PKG-DASH-04",
|
||||
|
||||
@@ -10,22 +10,14 @@ let sourceCode
|
||||
let evidence
|
||||
test.beforeAll(async () => {
|
||||
const { release } = JSON.parse(fs.readFileSync(new URL('../../refactor/baselines/dash-control-release.json', import.meta.url)))
|
||||
|
||||
const member = 'package/dist/artplayer-plugin-dash-control.js'
|
||||
|
||||
const bytes = readMember(await ensureArchive(release), member)
|
||||
|
||||
assert.equal(hash(bytes), release.files[member])
|
||||
|
||||
publishedCode = bytes.toString()
|
||||
|
||||
assert(!process.env.ARTPLAYER_BROWSER_ARTIFACTS, 'Controlled DASH source tests do not represent installed SDK/package acceptance')
|
||||
|
||||
sourceCode = process.env.ARTPLAYER_DASH_ARTIFACT ? fs.readFileSync(process.env.ARTPLAYER_DASH_ARTIFACT, 'utf8') : await compilePackage(release.name, 'umd')
|
||||
|
||||
evidence = { published: release, sourceSHA256: hash(sourceCode), candidate: process.env.ARTPLAYER_DASH_ARTIFACT || 'workspace source build', scope: 'Real ArtPlayer DOM and native MP4 with controlled SDK methods. No dash.js instance, MPD, ABR or adaptive media validation.' }
|
||||
})
|
||||
|
||||
test.afterEach(async ({ page }, testInfo) => {
|
||||
const state = await page.evaluate(() => ({
|
||||
calls: window.dashCalls,
|
||||
@@ -36,23 +28,20 @@ test.afterEach(async ({ page }, testInfo) => {
|
||||
audio: window.art?.controls['dash-audio']?.textContent,
|
||||
settingRows: [...document.querySelectorAll('.art-setting-panel.art-current .art-setting-item')].map(row => ({ text: row.textContent, label: row.querySelector('.art-setting-item-left-text')?.textContent })),
|
||||
})).catch(error => ({ error: error.message }))
|
||||
|
||||
await testInfo.attach('controlled-dash-state', { contentType: 'application/json', body: JSON.stringify(state) })
|
||||
|
||||
if (!page.isClosed())
|
||||
await page.evaluate(() => window.art?.destroy())
|
||||
if (!page.isClosed()) {
|
||||
await page.evaluate(() => {
|
||||
if (window.art && !window.art.isDestroy)
|
||||
window.art.destroy()
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
async function openDash(page, core, plugin, testInfo) {
|
||||
async function openDash(page, core, plugin, sdk, testInfo) {
|
||||
await testInfo.attach('controlled-dash-inputs', { contentType: 'application/json', body: JSON.stringify(evidence) })
|
||||
|
||||
await page.goto(`/test/player.html?core=${core}`)
|
||||
|
||||
await page.addScriptTag({ content: plugin === 'published' ? publishedCode : sourceCode })
|
||||
|
||||
await page.evaluate((sdk) => {
|
||||
window.dashCalls = []
|
||||
|
||||
window.art = new window.Artplayer({
|
||||
container: '.player',
|
||||
url: '/test/pattern.mp4',
|
||||
@@ -62,13 +51,9 @@ async function openDash(page, core, plugin, testInfo) {
|
||||
const levels = sdk === 4
|
||||
? [{ qualityIndex: 0, height: 360 }, { qualityIndex: 1, height: 720 }]
|
||||
: [{ id: 'low', height: 360 }, { id: 'high', height: 720 }]
|
||||
|
||||
const tracks = [{ id: 'en', lang: 'en' }, { id: 'fr', lang: 'fr' }]
|
||||
|
||||
window.dashState = { levels, tracks, track: tracks[0], current: levels[0], quality: 0, settings: { streaming: { abr: { autoSwitchBitrate: { video: true } } } } }
|
||||
|
||||
const state = window.dashState
|
||||
|
||||
art.dash = {
|
||||
getVideoElement: () => art.template.$video,
|
||||
getSettings: () => state.settings,
|
||||
@@ -83,7 +68,6 @@ async function openDash(page, core, plugin, testInfo) {
|
||||
window.dashCalls.push(['track', track.id, tracks.includes(track)])
|
||||
},
|
||||
}
|
||||
|
||||
if (sdk === 4) {
|
||||
Object.assign(art.dash, {
|
||||
getBitrateInfoListFor: () => levels,
|
||||
@@ -104,68 +88,83 @@ async function openDash(page, core, plugin, testInfo) {
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return window.artplayerPluginDashControl({ quality: { control: true, setting: true }, audio: { control: true, setting: true } })(art)
|
||||
}],
|
||||
})
|
||||
|
||||
document.querySelector('#play').onclick = () => window.art.play()
|
||||
|
||||
document.querySelector('#pause').onclick = () => window.art.pause()
|
||||
}, plugin === 'published' ? 4 : 5)
|
||||
|
||||
}, sdk)
|
||||
await expect.poll(() => page.evaluate(() => window.art.isReady)).toBe(true)
|
||||
|
||||
await expect(page.locator('.art-control-dash-quality .art-selector-value')).toHaveText('Auto')
|
||||
|
||||
await expect(page.locator('.art-control-dash-audio .art-selector-value')).toHaveText('en')
|
||||
}
|
||||
|
||||
for (const core of ['published', 'candidate']) {
|
||||
for (const plugin of ['published', 'candidate']) {
|
||||
for (const [plugin, sdk] of [['published', 4], ['candidate', 4], ['candidate', 5]]) {
|
||||
for (const surface of ['control', 'setting']) {
|
||||
for (const type of ['quality', 'audio']) {
|
||||
test(`${core} core + ${plugin} DASH: controlled SDK ${type} selection through real ${surface} DOM`, async ({ page }, testInfo) => {
|
||||
await openDash(page, core, plugin, testInfo)
|
||||
|
||||
test(`${core} core + ${plugin} DASH SDK ${sdk}: controlled SDK ${type} selection through real ${surface} DOM`, async ({ page }, testInfo) => {
|
||||
await openDash(page, core, plugin, sdk, testInfo)
|
||||
const text = type === 'quality' ? '720p' : 'fr'
|
||||
|
||||
if (surface === 'control') {
|
||||
const control = page.locator(`.art-control-dash-${type}`)
|
||||
|
||||
await control.hover()
|
||||
|
||||
await expect(control.locator('.art-selector-list')).toHaveCSS('opacity', '1')
|
||||
|
||||
await control.locator('.art-selector-item').filter({ hasText: new RegExp(`^${text}$`) }).click()
|
||||
}
|
||||
else {
|
||||
await page.locator('.art-control-setting').click()
|
||||
|
||||
await page.locator(`[data-name="dash-${type}"]`).click()
|
||||
|
||||
await page.locator('.art-setting-panel.art-current .art-setting-item-left-text').filter({ hasText: new RegExp(`^${text}$`) }).click()
|
||||
}
|
||||
|
||||
const expected = type === 'quality'
|
||||
? [['auto', false], plugin === 'published' ? ['qualityIndex', 'video', 1] : ['representationId', 'video', 'high']]
|
||||
? [['auto', false], sdk === 4 ? ['qualityIndex', 'video', 1] : ['representationId', 'video', 'high']]
|
||||
: [['track', 'fr', true]]
|
||||
|
||||
await expect.poll(() => page.evaluate(() => window.dashCalls)).toEqual(expected)
|
||||
|
||||
await expect(page.locator(`.art-control-dash-${type} .art-selector-value`)).toHaveText(text)
|
||||
|
||||
expect(await page.evaluate(type => window.art.setting.find(`dash-${type}`).tooltip, type)).toBe(text)
|
||||
|
||||
await testInfo.attach('controlled-dash-selection', { contentType: 'image/png', body: await page.screenshot() })
|
||||
|
||||
await page.locator('#play').click()
|
||||
|
||||
await expect.poll(() => page.evaluate(() => window.art.currentTime)).toBeGreaterThan(0.2)
|
||||
|
||||
expect(await page.evaluate(() => window.art.video.error)).toBeNull()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const core of ['published', 'candidate']) {
|
||||
for (const sdk of [4, 5]) {
|
||||
test(`${core} core + candidate DASH SDK ${sdk}: empty topology and destroy clear real menus and retained callbacks`, 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 quality = art.controls.cache.get('dash-quality').option
|
||||
const audio = art.setting.find('dash-audio')
|
||||
const state = window.dashState
|
||||
const levels = state.levels
|
||||
const tracks = state.tracks
|
||||
state.levels.splice(0)
|
||||
state.tracks = []
|
||||
plugin.update()
|
||||
const cleared = !document.querySelector('.art-control-dash-quality, .art-control-dash-audio') && !art.setting.find('dash-quality') && !art.setting.find('dash-audio')
|
||||
window.dashCalls.length = 0
|
||||
quality.onSelect(quality.selector[0])
|
||||
audio.onSelect(audio.selector[1])
|
||||
const staleWrites = window.dashCalls.length
|
||||
// Replace the lists after the empty-topology observation.
|
||||
state.levels.push(...(sdk === 4 ? [{ qualityIndex: 0, height: 360 }, { qualityIndex: 1, height: 720 }] : [{ id: 'low', height: 360 }, { id: 'high', height: 720 }]))
|
||||
state.tracks = tracks
|
||||
plugin.update()
|
||||
const restored = Boolean(art.controls['dash-quality'] && art.controls['dash-audio'])
|
||||
const retained = art.controls.cache.get('dash-quality').option
|
||||
art.destroy(false)
|
||||
window.dashCalls.length = 0
|
||||
retained.onSelect(retained.selector[0])
|
||||
plugin.update()
|
||||
return { cleared, staleWrites, restored, afterDestroyWrites: window.dashCalls.length, sameList: state.levels === levels }
|
||||
}, sdk)
|
||||
expect(result).toEqual({ cleared: true, staleWrites: 0, restored: true, afterDestroyWrites: 0, sameList: true })
|
||||
await testInfo.attach('controlled-dash-cleanup', { contentType: 'application/json', body: JSON.stringify(result) })
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,259 @@
|
||||
import assert from 'node:assert/strict'
|
||||
// eslint-disable-next-line test/no-import-node-test -- Runtime lifecycle contracts use Node's built-in runner.
|
||||
import test from 'node:test'
|
||||
import { bothMenus, dashHost, dashImplementations } from './helpers/dash-control.js'
|
||||
|
||||
const candidates = (await dashImplementations()).filter(item => item.name.startsWith('source') || item.name.startsWith('artifact'))
|
||||
const { factory } = candidates[0]
|
||||
for (const { sdk, name, factory } of candidates) {
|
||||
test(`DASH ${sdk} (${name}): supports the original SDK method generation and stable selection keys`, () => {
|
||||
const host = dashHost(sdk)
|
||||
factory(bothMenus())(host.art).update()
|
||||
const menu = host.controls.get('dash-quality')
|
||||
const selected = menu.selector.find(item => item.html === '720p')
|
||||
host.calls.length = 0
|
||||
assert.equal(menu.onSelect(selected), '720p')
|
||||
assert.deepEqual(host.calls[1], sdk === 4 ? ['setQualityFor', 'video', 1] : ['setRepresentationForTypeById', 'video', 'high'])
|
||||
})
|
||||
|
||||
test(`DASH ${sdk} (${name}): empty lists clear owned UI and invalidate retained selectors`, () => {
|
||||
const host = dashHost(sdk)
|
||||
const plugin = factory(bothMenus())(host.art)
|
||||
plugin.update()
|
||||
const menu = host.controls.get('dash-quality')
|
||||
const { levels, tracks } = host.state
|
||||
host.state.levels = []
|
||||
host.state.tracks = []
|
||||
plugin.update()
|
||||
assert.equal(host.controls.size + host.settings.size, 0)
|
||||
host.calls.length = 0
|
||||
assert.equal(menu.onSelect(menu.selector[0]), '1080p')
|
||||
assert.equal(host.calls.length, 0)
|
||||
host.state.levels = levels
|
||||
host.state.tracks = tracks
|
||||
host.art.emit('restart')
|
||||
assert.equal(host.controls.size + host.settings.size, 4)
|
||||
})
|
||||
|
||||
test(`DASH ${sdk} (${name}): disabling one surface removes only that surface`, () => {
|
||||
const host = dashHost(sdk)
|
||||
const options = bothMenus()
|
||||
const plugin = factory(options)(host.art)
|
||||
plugin.update()
|
||||
options.quality.control = false
|
||||
options.audio.setting = false
|
||||
plugin.update()
|
||||
assert.deepEqual([...host.controls.keys()], ['dash-audio'])
|
||||
assert.deepEqual([...host.settings.keys()], ['dash-quality'])
|
||||
})
|
||||
|
||||
test(`DASH ${sdk} (${name}): destroy unsubscribes and makes update/retained callbacks inert without owning SDK`, () => {
|
||||
const host = dashHost(sdk)
|
||||
const other = dashHost(sdk)
|
||||
factory(bothMenus())(other.art).update()
|
||||
const plugin = factory(bothMenus())(host.art)
|
||||
plugin.update()
|
||||
const menu = host.controls.get('dash-audio')
|
||||
host.art.destroy()
|
||||
assert.equal(host.controls.size + host.settings.size, 0)
|
||||
assert.equal([...host.listeners.values()].reduce((sum, set) => sum + set.size, 0), 0)
|
||||
host.calls.length = 0
|
||||
plugin.update()
|
||||
host.art.emit('ready')
|
||||
host.art.emit('restart')
|
||||
host.art.destroy()
|
||||
assert.equal(menu.onSelect(menu.selector[1]), 'fr')
|
||||
assert.equal(host.calls.length, 0)
|
||||
assert.equal(other.controls.size + other.settings.size, 4)
|
||||
})
|
||||
|
||||
test(`DASH ${sdk} (${name}): replacing SDK immediately silences old callbacks before and after update`, () => {
|
||||
const host = dashHost(sdk)
|
||||
const next = dashHost(sdk)
|
||||
const plugin = factory(bothMenus())(host.art)
|
||||
plugin.update()
|
||||
const menu = host.controls.get('dash-quality')
|
||||
next.state.video = host.video
|
||||
host.art.dash = next.dash
|
||||
host.calls.length = 0
|
||||
menu.onSelect(menu.selector[0])
|
||||
assert.equal(host.calls.length, 0)
|
||||
plugin.update()
|
||||
host.calls.length = 0
|
||||
menu.onSelect(menu.selector[0])
|
||||
assert.equal(host.calls.length, 0)
|
||||
assert.equal(next.calls.length, 0)
|
||||
})
|
||||
|
||||
test(`DASH ${sdk} (${name}): duplicate labels preserve the current selectable object`, () => {
|
||||
const host = dashHost(sdk)
|
||||
host.state.settings.streaming.abr.autoSwitchBitrate.video = false
|
||||
host.state.levels[1].height = 360
|
||||
host.state.tracks[1].lang = 'en'
|
||||
host.state.currentTrack = host.state.tracks[1]
|
||||
factory(bothMenus())(host.art).update()
|
||||
const quality = host.controls.get('dash-quality')
|
||||
const selected = quality.selector.filter(item => item.default)
|
||||
assert.equal(selected.length, 1)
|
||||
host.calls.length = 0
|
||||
quality.onSelect(selected[0])
|
||||
assert.deepEqual(host.calls[1], sdk === 4 ? ['setQualityFor', 'video', 1] : ['setRepresentationForTypeById', 'video', 'high'])
|
||||
const audio = host.controls.get('dash-audio').selector.filter(item => item.default)
|
||||
assert.equal(audio.length, 1)
|
||||
assert.equal(audio[0].value, host.state.tracks[1])
|
||||
})
|
||||
|
||||
test(`DASH ${sdk} (${name}): cloned current track matches a unique track but never fabricates ambiguous selection`, () => {
|
||||
const host = dashHost(sdk)
|
||||
const plugin = factory(bothMenus())(host.art)
|
||||
host.state.currentTrack = { ...host.state.tracks[1] }
|
||||
plugin.update()
|
||||
assert.equal(host.controls.get('dash-audio').selector.find(item => item.default).value, host.state.tracks[1])
|
||||
host.state.tracks.push({ ...host.state.tracks[1], lang: 'fr-duplicate' })
|
||||
host.state.currentTrack = { id: 'fr', index: 1 }
|
||||
plugin.update()
|
||||
assert.equal(host.controls.get('dash-audio').selector.some(item => item.default), false)
|
||||
})
|
||||
|
||||
test(`DASH ${sdk} (${name}): reentrant getName destruction cannot repopulate menus`, () => {
|
||||
const host = dashHost(sdk)
|
||||
const options = bothMenus()
|
||||
let calls = 0
|
||||
options.quality.getName = (level) => {
|
||||
calls++
|
||||
host.art.destroy()
|
||||
return `${level.height}p`
|
||||
}
|
||||
factory(options)(host.art).update()
|
||||
assert.equal(host.controls.size + host.settings.size, 0)
|
||||
assert.equal(host.calls.length, 0)
|
||||
assert.equal(calls, 1)
|
||||
})
|
||||
|
||||
test(`DASH ${sdk} (${name}): synchronous SDK destruction stops later selection writes and notifications`, () => {
|
||||
const host = dashHost(sdk)
|
||||
factory(bothMenus())(host.art).update()
|
||||
const menu = host.controls.get('dash-quality')
|
||||
const original = host.dash.updateSettings
|
||||
host.dash.updateSettings = (value) => {
|
||||
original.call(host.dash, value)
|
||||
host.art.destroy()
|
||||
}
|
||||
host.calls.length = 0
|
||||
assert.equal(menu.onSelect(menu.selector[0]), '1080p')
|
||||
assert.equal(host.calls.some(call => /^set(?:Quality|Representation)/.test(call[0])), false)
|
||||
assert.equal(host.calls.some(call => call[0] === 'notice' || call[0].endsWith('.check')), false)
|
||||
})
|
||||
|
||||
test(`DASH ${sdk} (${name}): rendering errors clear partial own menus and allow an explicit retry`, () => {
|
||||
const host = dashHost(sdk)
|
||||
const failure = new Error('setting rendering failed')
|
||||
const original = host.art.setting.update
|
||||
host.art.setting.update = (option) => {
|
||||
original(option)
|
||||
throw failure
|
||||
}
|
||||
const plugin = factory(bothMenus())(host.art)
|
||||
assert.throws(plugin.update, error => error === failure)
|
||||
assert.equal(host.controls.size + host.settings.size, 0)
|
||||
host.art.setting.update = original
|
||||
plugin.update()
|
||||
assert.equal(host.controls.size + host.settings.size, 4)
|
||||
})
|
||||
|
||||
test(`DASH ${sdk} (${name}): nested update from a formatter keeps the latest render`, () => {
|
||||
const host = dashHost(sdk)
|
||||
const options = bothMenus()
|
||||
const plugin = factory(options)(host.art)
|
||||
let nested = false
|
||||
options.quality.getName = (level) => {
|
||||
if (!nested) {
|
||||
nested = true
|
||||
host.state.levels = [host.state.levels[2]]
|
||||
plugin.update()
|
||||
}
|
||||
return `${level.height}p`
|
||||
}
|
||||
plugin.update()
|
||||
assert.deepEqual(Array.from(host.controls.get('dash-quality').selector, item => item.html), ['1080p', 'Auto'])
|
||||
assert.equal(host.controls.size + host.settings.size, 4)
|
||||
})
|
||||
|
||||
test(`DASH ${sdk} (${name}): a cleanup-triggered restart does not remove newly installed menus`, () => {
|
||||
const host = dashHost(sdk)
|
||||
const plugin = factory(bothMenus())(host.art)
|
||||
plugin.update()
|
||||
const { levels } = host.state
|
||||
host.state.levels = []
|
||||
const remove = host.art.controls.remove
|
||||
host.art.controls.remove = (name) => {
|
||||
remove(name)
|
||||
host.state.levels = levels
|
||||
plugin.update()
|
||||
}
|
||||
plugin.update()
|
||||
assert.equal(host.controls.size + host.settings.size, 4)
|
||||
})
|
||||
|
||||
test(`DASH ${sdk} (${name}): clearing a caller-replaced registry entry preserves the caller entry`, () => {
|
||||
const host = dashHost(sdk)
|
||||
host.art.controls.cache = { get: name => ({ option: host.controls.get(name) }) }
|
||||
host.art.setting.find = name => host.settings.get(name)
|
||||
factory(bothMenus())(host.art).update()
|
||||
const replacement = { onSelect() {} }
|
||||
host.controls.set('dash-quality', replacement)
|
||||
host.settings.set('dash-audio', replacement)
|
||||
host.art.destroy()
|
||||
assert.equal(host.controls.get('dash-quality'), replacement)
|
||||
assert.equal(host.settings.get('dash-audio'), replacement)
|
||||
assert.equal(host.controls.size + host.settings.size, 2)
|
||||
})
|
||||
|
||||
test(`DASH ${sdk} (${name}): cleanup failure does not retain other surfaces or listeners`, () => {
|
||||
const host = dashHost(sdk)
|
||||
factory(bothMenus())(host.art).update()
|
||||
const failure = new Error('control cleanup failed')
|
||||
const remove = host.art.controls.remove
|
||||
host.art.controls.remove = (name) => {
|
||||
remove(name)
|
||||
throw failure
|
||||
}
|
||||
assert.throws(() => host.art.destroy(), error => error === failure)
|
||||
assert.equal(host.controls.size + host.settings.size, 0)
|
||||
assert.equal([...host.listeners.values()].reduce((sum, set) => sum + set.size, 0), 0)
|
||||
host.art.destroy()
|
||||
})
|
||||
}
|
||||
|
||||
test('DASH v5: a numeric zero ID is a valid manually selected representation', () => {
|
||||
const host = dashHost(5)
|
||||
host.state.levels[1].id = 0
|
||||
host.state.settings.streaming.abr.autoSwitchBitrate.video = false
|
||||
factory(bothMenus())(host.art).update()
|
||||
assert.deepEqual(host.controls.get('dash-quality').selector.filter(item => item.default).map(item => item.id), [0])
|
||||
})
|
||||
|
||||
test('DASH installation failure removes even the listener registered by a throwing host', () => {
|
||||
const host = dashHost()
|
||||
const failure = new Error('subscription failed')
|
||||
const on = host.art.on
|
||||
host.art.on = (name, callback) => {
|
||||
on(name, callback)
|
||||
if (name === 'destroy')
|
||||
throw failure
|
||||
}
|
||||
assert.throws(() => factory(bothMenus())(host.art), error => error === failure)
|
||||
assert.equal([...host.listeners.values()].reduce((sum, set) => sum + set.size, 0), 0)
|
||||
})
|
||||
|
||||
test('DASH reentrant destruction during subscription cannot install later listeners', () => {
|
||||
const host = dashHost()
|
||||
const on = host.art.on
|
||||
host.art.on = (name, callback) => {
|
||||
on(name, callback)
|
||||
if (name === 'destroy')
|
||||
callback()
|
||||
}
|
||||
factory()(host.art)
|
||||
assert.equal([...host.listeners.values()].reduce((sum, set) => sum + set.size, 0), 0)
|
||||
})
|
||||
@@ -22,6 +22,7 @@ export async function dashImplementations() {
|
||||
const { release } = baseline
|
||||
const archive = await ensureArchive(release)
|
||||
const implementations = [{ name: 'source', sdk: 5, factory: (await loadPackage(release.name)).default }]
|
||||
implementations.push({ name: 'source-v4', sdk: 4, factory: implementations[0].factory })
|
||||
const historicalFile = `packages/${release.name}/dist/artplayer-plugin-dash-control.js`
|
||||
const historical = execFileSync('git', ['show', `${baseline.sourceCommit}:${historicalFile}`])
|
||||
assert.equal(hash(historical.toString().replace(/\r\n/g, '\n')), baseline.source[historicalFile])
|
||||
@@ -34,7 +35,9 @@ export async function dashImplementations() {
|
||||
}
|
||||
for (const filename of (process.env.ARTPLAYER_TEST_DASH || '').split(path.delimiter).filter(Boolean)) {
|
||||
const bytes = fs.readFileSync(filename)
|
||||
implementations.push({ name: `artifact-${path.basename(filename)}`, sdk: 5, factory: await factoryFromBytes(bytes, filename.endsWith('.mjs')) })
|
||||
const factory = await factoryFromBytes(bytes, filename.endsWith('.mjs'))
|
||||
for (const sdk of [4, 5])
|
||||
implementations.push({ name: `artifact-${path.basename(filename)}-v${sdk}`, sdk, factory })
|
||||
}
|
||||
return implementations
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user