diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml
index 9b3c119ba..a5b7e438c 100644
--- a/.github/workflows/nodejs.yml
+++ b/.github/workflows/nodejs.yml
@@ -25,6 +25,7 @@ jobs:
yarn
yarn lint
yarn build:all
+ yarn test:dash-control
env:
CI: true
diff --git a/docs/compiled/artplayer-plugin-dash-control.js b/docs/compiled/artplayer-plugin-dash-control.js
index 5f92ccef8..97ce60f03 100644
--- a/docs/compiled/artplayer-plugin-dash-control.js
+++ b/docs/compiled/artplayer-plugin-dash-control.js
@@ -4,4 +4,4 @@
* (c) 2017-2026 Harvey Zhao
* Released under the MIT License.
*/
-!function(t,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"==typeof define&&define.amd?(t.artplayerPluginDashControl=e(),define(function(){return t.artplayerPluginDashControl})):(t="undefined"!==typeof globalThis?globalThis:t||self).artplayerPluginDashControl=e()}(this,function(){"use strict";function t(t,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,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.setRepresentationForTypeByIndex("video",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-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:'',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:'',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,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:'',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:'',width:200,selector:d,onSelect:h})}(o.dash)}return o.on("ready",a),o.on("restart",a),{name:"artplayerPluginDashControl",update:a}}}});
diff --git a/docs/compiled/artplayer-plugin-dash-control.legacy.js b/docs/compiled/artplayer-plugin-dash-control.legacy.js
index 5f92ccef8..97ce60f03 100644
--- a/docs/compiled/artplayer-plugin-dash-control.legacy.js
+++ b/docs/compiled/artplayer-plugin-dash-control.legacy.js
@@ -4,4 +4,4 @@
* (c) 2017-2026 Harvey Zhao
* Released under the MIT License.
*/
-!function(t,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"==typeof define&&define.amd?(t.artplayerPluginDashControl=e(),define(function(){return t.artplayerPluginDashControl})):(t="undefined"!==typeof globalThis?globalThis:t||self).artplayerPluginDashControl=e()}(this,function(){"use strict";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,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.setRepresentationForTypeByIndex("video",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-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:'',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:'',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,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:'',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:'',width:200,selector:d,onSelect:h})}(o.dash)}return o.on("ready",a),o.on("restart",a),{name:"artplayerPluginDashControl",update:a}}}});
diff --git a/docs/compiled/artplayer-plugin-dash-control.mjs b/docs/compiled/artplayer-plugin-dash-control.mjs
index 8e5e16c16..4ce15acf9 100644
--- a/docs/compiled/artplayer-plugin-dash-control.mjs
+++ b/docs/compiled/artplayer-plugin-dash-control.mjs
@@ -37,6 +37,7 @@ function artplayerPluginDashControl(option = {}) {
return {
html: getName(item),
value: index,
+ id: item.id,
default: !currentAuto && !!currentId && item.id === currentId
};
}),
@@ -68,7 +69,7 @@ function artplayerPluginDashControl(option = {}) {
}
}
});
- dash.setRepresentationForTypeByIndex("video", item.value);
+ dash.setRepresentationForTypeById("video", item.id);
}
art.notice.show = `${title}: ${item.html}`;
if (config.control)
diff --git a/docs/uncompiled/artplayer-plugin-dash-control/index.js b/docs/uncompiled/artplayer-plugin-dash-control/index.js
index d99cb1f47..54f9a0b21 100644
--- a/docs/uncompiled/artplayer-plugin-dash-control/index.js
+++ b/docs/uncompiled/artplayer-plugin-dash-control/index.js
@@ -33,6 +33,7 @@ var artplayerPluginDashControl = (function() {
return {
html: getName(item),
value: index,
+ id: item.id,
default: !currentAuto && !!currentId && item.id === currentId
};
}),
@@ -64,7 +65,7 @@ var artplayerPluginDashControl = (function() {
}
}
});
- dash.setRepresentationForTypeByIndex("video", item.value);
+ dash.setRepresentationForTypeById("video", item.id);
}
art.notice.show = `${title}: ${item.html}`;
if (config.control)
diff --git a/package.json b/package.json
index 62a95f68a..535ded096 100644
--- a/package.json
+++ b/package.json
@@ -34,6 +34,7 @@
"build:test": "node ./scripts/build-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",
"dev": "npx cross-env NODE_ENV=development node ./scripts/dev.js",
"build": "npx cross-env NODE_ENV=production node ./scripts/build.js",
"lint": "npx eslint packages/*/{src,types,package.json} scripts/*.js test/* docs/assets/ts/* --fix",
diff --git a/packages/artplayer-plugin-dash-control/README.md b/packages/artplayer-plugin-dash-control/README.md
index 90b0e4e76..a7b9176bd 100644
--- a/packages/artplayer-plugin-dash-control/README.md
+++ b/packages/artplayer-plugin-dash-control/README.md
@@ -2,6 +2,8 @@
Dash control plugin for ArtPlayer
+Requires dash.js 5.x. dash.js 4.x is not supported.
+
## Demo
[https://artplayer.org](https://artplayer.org/?libs=https://cdnjs.cloudflare.com/ajax/libs/dashjs/5.2.1/modern/umd/dash.all.min.js%0A./uncompiled/artplayer-plugin-dash-control/index.js&example=dash.control)
diff --git a/packages/artplayer-plugin-dash-control/dist/artplayer-plugin-dash-control.js b/packages/artplayer-plugin-dash-control/dist/artplayer-plugin-dash-control.js
index 5f92ccef8..97ce60f03 100644
--- a/packages/artplayer-plugin-dash-control/dist/artplayer-plugin-dash-control.js
+++ b/packages/artplayer-plugin-dash-control/dist/artplayer-plugin-dash-control.js
@@ -4,4 +4,4 @@
* (c) 2017-2026 Harvey Zhao
* Released under the MIT License.
*/
-!function(t,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"==typeof define&&define.amd?(t.artplayerPluginDashControl=e(),define(function(){return t.artplayerPluginDashControl})):(t="undefined"!==typeof globalThis?globalThis:t||self).artplayerPluginDashControl=e()}(this,function(){"use strict";function t(t,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,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.setRepresentationForTypeByIndex("video",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-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:'',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:'',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,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:'',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:'',width:200,selector:d,onSelect:h})}(o.dash)}return o.on("ready",a),o.on("restart",a),{name:"artplayerPluginDashControl",update:a}}}});
diff --git a/packages/artplayer-plugin-dash-control/dist/artplayer-plugin-dash-control.legacy.js b/packages/artplayer-plugin-dash-control/dist/artplayer-plugin-dash-control.legacy.js
index 5f92ccef8..97ce60f03 100644
--- a/packages/artplayer-plugin-dash-control/dist/artplayer-plugin-dash-control.legacy.js
+++ b/packages/artplayer-plugin-dash-control/dist/artplayer-plugin-dash-control.legacy.js
@@ -4,4 +4,4 @@
* (c) 2017-2026 Harvey Zhao
* Released under the MIT License.
*/
-!function(t,e){"object"===typeof exports&&"undefined"!==typeof module?module.exports=e():"function"==typeof define&&define.amd?(t.artplayerPluginDashControl=e(),define(function(){return t.artplayerPluginDashControl})):(t="undefined"!==typeof globalThis?globalThis:t||self).artplayerPluginDashControl=e()}(this,function(){"use strict";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,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.setRepresentationForTypeByIndex("video",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-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:'',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:'',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,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:'',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:'',width:200,selector:d,onSelect:h})}(o.dash)}return o.on("ready",a),o.on("restart",a),{name:"artplayerPluginDashControl",update:a}}}});
diff --git a/packages/artplayer-plugin-dash-control/dist/artplayer-plugin-dash-control.mjs b/packages/artplayer-plugin-dash-control/dist/artplayer-plugin-dash-control.mjs
index 8e5e16c16..4ce15acf9 100644
--- a/packages/artplayer-plugin-dash-control/dist/artplayer-plugin-dash-control.mjs
+++ b/packages/artplayer-plugin-dash-control/dist/artplayer-plugin-dash-control.mjs
@@ -37,6 +37,7 @@ function artplayerPluginDashControl(option = {}) {
return {
html: getName(item),
value: index,
+ id: item.id,
default: !currentAuto && !!currentId && item.id === currentId
};
}),
@@ -68,7 +69,7 @@ function artplayerPluginDashControl(option = {}) {
}
}
});
- dash.setRepresentationForTypeByIndex("video", item.value);
+ dash.setRepresentationForTypeById("video", item.id);
}
art.notice.show = `${title}: ${item.html}`;
if (config.control)
diff --git a/packages/artplayer-plugin-dash-control/src/index.js b/packages/artplayer-plugin-dash-control/src/index.js
index 73ac79360..04de6a6d5 100644
--- a/packages/artplayer-plugin-dash-control/src/index.js
+++ b/packages/artplayer-plugin-dash-control/src/index.js
@@ -36,6 +36,7 @@ export default function artplayerPluginDashControl(option = {}) {
return {
html: getName(item),
value: index,
+ id: item.id,
default: !currentAuto && !!currentId && item.id === currentId,
}
}),
@@ -70,7 +71,7 @@ export default function artplayerPluginDashControl(option = {}) {
},
},
})
- dash.setRepresentationForTypeByIndex('video', item.value)
+ dash.setRepresentationForTypeById('video', item.id)
}
art.notice.show = `${title}: ${item.html}`
if (config.control)
diff --git a/test/dash-control.test.js b/test/dash-control.test.js
new file mode 100644
index 000000000..2c0df066b
--- /dev/null
+++ b/test/dash-control.test.js
@@ -0,0 +1,117 @@
+import assert from 'node:assert/strict'
+import { Buffer } from 'node:buffer'
+// eslint-disable-next-line test/no-import-node-test -- Use the built-in runner without adding a test framework.
+import { mock, test } from 'node:test'
+import { fileURLToPath } from 'node:url'
+import { build } from 'vite'
+import { getViteBuildConfig } from '../scripts/utils.js'
+
+const config = getViteBuildConfig({
+ entry: fileURLToPath(new URL('../packages/artplayer-plugin-dash-control/src/index.js', import.meta.url)),
+ name: 'artplayerPluginDashControl',
+ format: 'es',
+ fileName: 'index.js',
+ minify: false,
+})
+config.build.write = false
+const result = await build(config)
+const outputs = (Array.isArray(result) ? result : [result]).flatMap(item => item.output)
+const { default: dashControl } = await import(
+ `data:text/javascript;base64,${Buffer.from(outputs.find(item => item.type === 'chunk').code).toString('base64')}`,
+)
+
+function createPlayer() {
+ const representations = [
+ { id: 'low', height: 288, bitrateInKbit: 395 },
+ { id: 'medium', height: 360, bitrateInKbit: 689 },
+ { id: 'target', height: 480, bitrateInKbit: 885 },
+ { id: 'high', height: 720, bitrateInKbit: 1474 },
+ { id: 'highest', height: 1080, bitrateInKbit: 1967 },
+ ]
+ const settings = { streaming: { abr: { minBitrate: { video: -1 }, autoSwitchBitrate: { video: true } } } }
+ const $video = {}
+ let current = representations[2]
+ const dash = {
+ getVideoElement: () => $video,
+ getSettings: () => settings,
+ updateSettings: ({ streaming: { abr } }) => Object.assign(settings.streaming.abr, abr),
+ getRepresentationsByType: () => representations.filter(item => item.bitrateInKbit >= settings.streaming.abr.minBitrate.video),
+ getCurrentRepresentationForType: () => current,
+ setRepresentationForTypeById: mock.fn((type, id) => {
+ current = representations.find(item => item.id === id)
+ }),
+ setRepresentationForTypeByIndex: mock.fn((type, index) => {
+ current = dash.getRepresentationsByType(type)[index]
+ }),
+ getTracksFor: () => [],
+ }
+ const controls = new Map()
+ const setting = new Map()
+ const art = {
+ dash,
+ template: { $video },
+ constructor: { utils: { errorHandle: assert.ok } },
+ notice: { show: '' },
+ on: mock.fn(),
+ controls: { update: option => controls.set(option.name, option), check: mock.fn() },
+ setting: { update: option => setting.set(option.name, option), check: mock.fn() },
+ }
+ const plugin = dashControl({ quality: { control: true, setting: true } })(art)
+ plugin.update()
+ return { art, dash, plugin, representations, controls, setting }
+}
+
+for (const surface of ['controls', 'setting']) {
+ test(`${surface}: bitrate filtering does not change the selected representation`, () => {
+ const player = createPlayer()
+ const { dash } = player
+ const option = player[surface].get('dash-quality')
+ const target = option.selector.find(item => item.html === '480p')
+ dash.updateSettings({ streaming: { abr: { minBitrate: { video: 500 } } } })
+ assert.equal(dash.getRepresentationsByType('video')[target.value].height, 720)
+
+ option.onSelect(target)
+
+ assert.equal(dash.getCurrentRepresentationForType('video').height, 480)
+ assert.deepEqual(dash.setRepresentationForTypeById.mock.calls[0].arguments, ['video', 'target'])
+ assert.equal(dash.setRepresentationForTypeByIndex.mock.callCount(), 0)
+ assert.equal(dash.getSettings().streaming.abr.autoSwitchBitrate.video, false)
+ assert.equal(player.art.notice.show, 'Quality: 480p')
+ assert.equal(player.art.controls.check.mock.calls[0].arguments[0], target)
+ assert.equal(player.art.setting.check.mock.calls[0].arguments[0], target)
+ })
+}
+
+test('quality ordering, automatic selection and return to Auto are preserved', () => {
+ const { controls, dash } = createPlayer()
+ const option = controls.get('dash-quality')
+ assert.deepEqual(option.selector.map(item => item.html), ['1080p', '720p', '480p', '360p', '288p', 'Auto'])
+ assert.deepEqual(option.selector.filter(item => item.default).map(item => item.value), ['auto'])
+ option.onSelect(option.selector[0])
+ assert.equal(dash.getCurrentRepresentationForType('video').height, 1080)
+ option.onSelect(option.selector.at(-1))
+ assert.equal(dash.getSettings().streaming.abr.autoSwitchBitrate.video, true)
+ assert.equal(dash.setRepresentationForTypeById.mock.callCount(), 1)
+})
+
+test('updating a filtered list preserves the manual selection by ID', () => {
+ const { controls, dash, plugin } = createPlayer()
+ const option = controls.get('dash-quality')
+ option.onSelect(option.selector.find(item => item.html === '480p'))
+ dash.updateSettings({ streaming: { abr: { minBitrate: { video: 500 } } } })
+ plugin.update()
+ const updated = controls.get('dash-quality')
+ assert.equal(updated.html, '480p')
+ assert.deepEqual(updated.selector.filter(item => item.default).map(item => item.id), ['target'])
+})
+
+test('a representation whose ID is auto remains a manual quality option', () => {
+ const { controls, dash, plugin, representations } = createPlayer()
+ representations[2].id = 'auto'
+ plugin.update()
+ const option = controls.get('dash-quality')
+ option.onSelect(option.selector.find(item => item.html === '480p'))
+ assert.equal(dash.getCurrentRepresentationForType('video').height, 480)
+ assert.equal(dash.getSettings().streaming.abr.autoSwitchBitrate.video, false)
+ assert.deepEqual(dash.setRepresentationForTypeById.mock.calls[0].arguments, ['video', 'auto'])
+})