mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-08 10:56:15 -08:00
refactor(mediabunny): [PKG-MB-08] finish typed media capability boundaries
This commit is contained in:
1 parent
5e28dfee95
commit
fb9fb23f53
25 files changed
+2698
-30
No files matched your search
@@ -4,4 +4,4 @@
|
||||
* (c) 2017-2026 Harvey Zhao
|
||||
* Released under the MIT License.
|
||||
*/
|
||||
!function(e,t){"object"===typeof exports&&"undefined"!==typeof module?module.exports=t():"function"==typeof define&&define.amd?(e.artplayerProxyMediabunny=t(),define(function(){return e.artplayerProxyMediabunny})):(e="undefined"!==typeof globalThis?globalThis:e||self).artplayerProxyMediabunny=t()}(this,function(){"use strict";var e,t,i,r,a,s,n,o,c,l,u,h,d,f,m,p,g,k,y,b,w,T,S,v,P,I,x,C,B,E,_,R,F,A=e=>{throw TypeError(e)},M=(e,t,i)=>t.has(e)||A("Cannot "+i),D=(e,t,i)=>(M(e,t,"read from private field"),i?i.call(e):t.get(e)),N=(e,t,i)=>t.has(e)?A("Cannot add the same private member more than once"):t instanceof WeakSet?t.add(e):t.set(e,i),O=(e,t,i,r)=>(M(e,t,"write to private field"),r?r.call(e,i):t.set(e,i),i),L=(e,t,i)=>(M(e,t,"access private method"),i),z=(e,t,i,r)=>({set _(r){O(e,t,r,i)},get _(){return D(e,t,r)}});function U(e){const t=[];for(const r of e)try{r()}catch(i){t.push(i)}if(t.length)throw t[0]}function q(e,t,i){const r={control:!1,setting:!1};let a=null;function s(i){r[i]&&(r[i]=!1,"control"===i?e.controls.cache.has(t)&&e.controls.remove(t):e.setting.find(t)&&e.setting.remove(t))}function n(){a=null,U([()=>s("control"),()=>s("setting")])}return{update:function(o,c,l,u){if(!o)return void n();a=o;const h=()=>a===o&&l(),d=async e=>h()&&o.selector.some(t=>t.value===e.value)?u(e,o.title):e.html;c.control?(r.control=!0,e.controls.update({name:t,position:"right",html:o.html,style:{padding:"0 10px"},selector:o.selector,onSelect:d})):s("control"),h()&&(c.setting?(r.setting=!0,e.setting.update({name:t,tooltip:o.html,html:o.title,icon:i,width:200,selector:o.selector,onSelect:d})):s("setting"))},clear:n}}function W(e){const t=new Map,i=[];for(const r of e){const e=t.get(r.html);void 0!==r.html&&void 0!==e?r.default&&(i[e]=r):(t.set(r.html,i.length),i.push(r))}return i}function H({art:e,shim:t,option:i}){if(!i.m3u8)return;let r=!1,a=0,s=0;const n=q(e,"mediabunny-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>'),o=q(e,"mediabunny-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>'),c=e=>!r&&!t.engine.destroyed&&t.engine.loadSeq===e;async function l(){if(r||t.engine.destroyed)return;const e=++a,s=t.engine.loadSeq,l=()=>e===a&&c(s);try{const e=await t.getM3u8State();if(!l())return;const r=i.m3u8?.quality||{},a=function(e,t,i){if(!t.control&&!t.setting||!e?.levels.length)return null;const r=t.auto||"Auto",a=t.getName||(e=>e.name||`${e.height}P`),s=e.currentLevel?a(e.currentLevel):r,n=[];for(const l of e.levels){if(!i())return null;n.push({html:a(l),value:l.id,default:e.currentLevel?.id===l.id})}const o=new Map(e.levels.map(e=>[e.id,e.height])),c=W(n).sort((e,t)=>("auto"===t.value?0:o.get(t.value)||0)-("auto"===e.value?0:o.get(e.value)||0));return c.push({html:r,value:"auto",default:!e.currentLevel}),{title:t.title||"Quality",html:s,selector:c}}(e,r,l);if(!l())return;if(n.update(a,r,()=>c(s),(e,t)=>u("quality",e,t)),!l())return;const h=i.m3u8?.audio||{},d=function(e,t,i){if(!t.control&&!t.setting||!e||e.audios.length<2)return null;const r=t.auto||"Auto",a=t.getName||(e=>e.name||e.lang||e.language),s=e.currentAudio?a(e.currentAudio):r,n=[];for(const c of e.audios){if(!i())return null;n.push({html:a(c),value:c.id,default:e.currentAudio?.id===c.id})}const o=W(n);return o.push({html:r,value:"auto",default:!e.currentAudio}),{title:t.title||"Audio",html:s,selector:o}}(e,h,l);l()&&o.update(d,h,()=>c(s),(e,t)=>u("audio",e,t))}catch(h){if(l())throw h}}async function u(i,r,n){const o=++s,u=t.engine.loadSeq;a++;const d=()=>o===s&&c(u);try{"quality"===i?await t.switchM3u8Quality(r.value):await t.switchM3u8Audio(r.value),d()&&(await l(),d()&&(e.notice.show=`${n}: ${r.html}`))}catch(f){if(d()&&(await h(),d()))throw f}return r.html}function h(){return l().catch(e=>console.warn("MediaBunny HLS menu update:",e))}function d(){a++,s++;try{U([n.clear,o.clear])}catch(e){console.warn("MediaBunny HLS menu cleanup:",e)}}function f(){if(!r){r=!0,d();try{U([()=>e.off("video:loadedmetadata",h),()=>e.off("restart",h),()=>e.off("video:loadstart",d),()=>e.off("video:error",d),()=>e.off("destroy",f)])}catch(t){console.warn("MediaBunny HLS listener cleanup:",t)}}}try{e.on("video:loadedmetadata",h),e.on("restarLine truncated
|
||||
!function(e,t){"object"===typeof exports&&"undefined"!==typeof module?module.exports=t():"function"==typeof define&&define.amd?(e.artplayerProxyMediabunny=t(),define(function(){return e.artplayerProxyMediabunny})):(e="undefined"!==typeof globalThis?globalThis:e||self).artplayerProxyMediabunny=t()}(this,function(){"use strict";var e,t,i,r,a,s,n,o,c,l,u,h,d,f,m,p,g,k,y,b,w,T,S,v,P,I,x,C,B,E,_,R,F,A=e=>{throw TypeError(e)},M=(e,t,i)=>t.has(e)||A("Cannot "+i),D=(e,t,i)=>(M(e,t,"read from private field"),i?i.call(e):t.get(e)),N=(e,t,i)=>t.has(e)?A("Cannot add the same private member more than once"):t instanceof WeakSet?t.add(e):t.set(e,i),O=(e,t,i,r)=>(M(e,t,"write to private field"),r?r.call(e,i):t.set(e,i),i),L=(e,t,i)=>(M(e,t,"access private method"),i),z=(e,t,i,r)=>({set _(r){O(e,t,r,i)},get _(){return D(e,t,r)}});function U(e){const t=[];for(const r of e)try{r()}catch(i){t.push(i)}if(t.length)throw t[0]}function q(e,t,i){const r={control:!1,setting:!1};let a=null;function s(i){r[i]&&(r[i]=!1,"control"===i?e.controls.cache.has(t)&&e.controls.remove(t):e.setting.find(t)&&e.setting.remove(t))}function n(){a=null,U([()=>s("control"),()=>s("setting")])}return{update:function(o,c,l,u){if(!o)return void n();a=o;const h=()=>a===o&&l(),d=async e=>h()&&o.selector.some(t=>t.value===e.value)?u(e,o.title):e.html;c.control?(r.control=!0,e.controls.update({name:t,position:"right",html:o.html,style:{padding:"0 10px"},selector:o.selector,onSelect:d})):s("control"),h()&&(c.setting?(r.setting=!0,e.setting.update({name:t,tooltip:o.html,html:o.title,icon:i,width:200,selector:o.selector,onSelect:d})):s("setting"))},clear:n}}function W(e){const t=new Map,i=[];for(const r of e){const e=t.get(r.html);void 0!==r.html&&void 0!==e?r.default&&(i[e]=r):(t.set(r.html,i.length),i.push(r))}return i}function H({art:e,shim:t,option:i}){if(!i.m3u8)return;let r=!1,a=0,s=0;const n=q(e,"mediabunny-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>'),o=q(e,"mediabunny-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>'),c=e=>!r&&!t.engine.destroyed&&t.engine.loadSeq===e;async function l(){if(r||t.engine.destroyed)return;const e=++a,s=t.engine.loadSeq,l=()=>e===a&&c(s);try{const e=await t.getM3u8State();if(!l())return;const r=i.m3u8?.quality||{},a=function(e,t,i){if(!t.control&&!t.setting||!e?.levels.length)return null;const r=t.auto||"Auto",a=t.getName||(e=>e.name||`${e.height}P`),s=e.currentLevel?a(e.currentLevel):r,n=[];for(const l of e.levels){if(!i())return null;n.push({html:a(l),value:l.id,default:e.currentLevel?.id===l.id})}const o=new Map(e.levels.map(e=>[e.id,e.height])),c=W(n).sort((e,t)=>("auto"===t.value?0:o.get(t.value)||0)-("auto"===e.value?0:o.get(e.value)||0));return c.push({html:r,value:"auto",default:!e.currentLevel}),{title:t.title||"Quality",html:s,selector:c}}(e,r,l);if(!l())return;if(n.update(a,r,()=>c(s),(e,t)=>u("quality",e,t)),!l())return;const h=i.m3u8?.audio||{},d=function(e,t,i){if(!t.control&&!t.setting||!e||e.audios.length<2)return null;const r=t.auto||"Auto",a=t.getName||(e=>e.name||e.lang||e.language),s=e.currentAudio?a(e.currentAudio):r,n=[];for(const c of e.audios){if(!i())return null;n.push({html:a(c),value:c.id,default:e.currentAudio?.id===c.id})}const o=W(n);return o.push({html:r,value:"auto",default:!e.currentAudio}),{title:t.title||"Audio",html:s,selector:o}}(e,h,l);l()&&o.update(d,h,()=>c(s),(e,t)=>u("audio",e,t))}catch(h){if(l())throw h}}async function u(i,r,n){const o=++s,u=t.engine.loadSeq;a++;const d=()=>o===s&&c(u);try{"quality"===i?await t.switchM3u8Quality(r.value):await t.switchM3u8Audio(r.value),d()&&(await l(),d()&&(e.notice.show=`${n}: ${r.html}`))}catch(f){if(d()&&(await h(),d()))throw f}return r.html}function h(){return l().catch(e=>console.warn("MediaBunny HLS menu update:",e))}function d(){a++,s++;try{U([n.clear,o.clear])}catch(e){console.warn("MediaBunny HLS menu cleanup:",e)}}function f(){if(!r){r=!0,d();try{U([()=>e.off("video:loadedmetadata",h),()=>e.off("restart",h),()=>e.off("video:loadstart",d),()=>e.off("video:error",d),()=>e.off("destroy",f)])}catch(t){console.warn("MediaBunny HLS listener cleanup:",t)}}}try{e.on("video:loadedmetadata",h),e.on("restarLine truncated
|
||||
@@ -4,4 +4,4 @@
|
||||
* (c) 2017-2026 Harvey Zhao
|
||||
* Released under the MIT License.
|
||||
*/
|
||||
!function(e,t){"object"===typeof exports&&"undefined"!==typeof module?module.exports=t():"function"==typeof define&&define.amd?(e.artplayerProxyMediabunny=t(),define(function(){return e.artplayerProxyMediabunny})):(e="undefined"!==typeof globalThis?globalThis:e||self).artplayerProxyMediabunny=t()}(this,function(){"use strict";var e,t,i,r,n,s,a,o,l,c,u,d,h,f,m,p,g,k,y,b,T,v,w,S,P,I,x,C,B,E,_,R,F,A=Object.defineProperty,M=Object.defineProperties,D=Object.getOwnPropertyDescriptors,N=Object.getOwnPropertySymbols,O=Object.prototype.hasOwnProperty,L=Object.prototype.propertyIsEnumerable,z=(e,t)=>(t=Symbol[e])?t:Symbol.for("Symbol."+e),U=e=>{throw TypeError(e)},q=Math.pow,W=(e,t,i)=>t in e?A(e,t,{enumerable:!0,configurable:!0,writable:!0,value:i}):e[t]=i,H=(e,t)=>{for(var i in t||(t={}))O.call(t,i)&&W(e,i,t[i]);if(N)for(var i of N(t))L.call(t,i)&&W(e,i,t[i]);return e},V=(e,t)=>M(e,D(t)),j=(e,t,i)=>t.has(e)||U("Cannot "+i),K=(e,t,i)=>(j(e,t,"read from private field"),i?i.call(e):t.get(e)),X=(e,t,i)=>t.has(e)?U("Cannot add the same private member more than once"):t instanceof WeakSet?t.add(e):t.set(e,i),$=(e,t,i,r)=>(j(e,t,"write to private field"),r?r.call(e,i):t.set(e,i),i),G=(e,t,i)=>(j(e,t,"access private method"),i),Q=(e,t,i,r)=>({set _(r){$(e,t,r,i)},get _(){return K(e,t,r)}}),Y=(e,t,i)=>new Promise((r,n)=>{var s=e=>{try{o(i.next(e))}catch(t){n(t)}},a=e=>{try{o(i.throw(e))}catch(t){n(t)}},o=e=>e.done?r(e.value):Promise.resolve(e.value).then(s,a);o((i=i.apply(e,t)).next())}),J=function(e,t){this[0]=e,this[1]=t},Z=(e,t,i)=>{var r,n=(e,t,r,s)=>{try{var a=i[e](t),o=(t=a.value)instanceof J,l=a.done;Promise.resolve(o?t[0]:t).then(i=>o?n("return"===e?e:"next",t[1]?{done:i.done,value:i.value}:i,r,s):r({value:i,done:l})).catch(e=>n("throw",e,r,s))}catch(c){s(c)}},s=(e,t,i,s)=>a[e]=a=>(t=new Promise((t,i,s)=>(s=()=>n(e,a,t,i),r?r.then(s):s())),s=()=>r===i&&(r=0),r=i=t.then(s,s),t),a={};return i=i.apply(e,t),a[z("asyncIterator")]=()=>a,s("next"),s("throw"),s("return"),a},ee=e=>{var t,i=e[z("asyncIterator")],r=!1,n={};return null==i?(i=e[z("iterator")](),t=e=>n[e]=t=>i[e](t)):(i=i.call(e),t=e=>n[e]=t=>{if(r){if(r=!1,"throw"===e)throw t;return t}return r=!0,{done:!1,value:new J(new Promise(r=>{var n=i[e](t);n instanceof Object||U("Object expected"),r(n)}),1)}}),n[z("iterator")]=()=>n,t("next"),"throw"in i?t("throw"):n.throw=e=>{throw e},"return"in i&&t("return"),n},te=(e,t,i)=>(t=e[z("asyncIterator")])?t.call(e):(e=e[z("iterator")](),t={},(i=(i,r)=>(r=e[i])&&(t[i]=t=>new Promise((i,n,s)=>(t=r.call(e,t),s=t.done,Promise.resolve(t.value).then(e=>i({value:e,done:s}),n)))))("next"),i("return"),t);function ie(e){const t=[];for(const r of e)try{r()}catch(i){t.push(i)}if(t.length)throw t[0]}function re(e,t,i){const r={control:!1,setting:!1};let n=null;function s(i){r[i]&&(r[i]=!1,"control"===i?e.controls.cache.has(t)&&e.controls.remove(t):e.setting.find(t)&&e.setting.remove(t))}function a(){n=null,ie([()=>s("control"),()=>s("setting")])}return{update:function(o,l,c,u){if(!o)return void a();n=o;const d=()=>n===o&&c(),h=e=>Y(null,null,function*(){return d()&&o.selector.some(t=>t.value===e.value)?u(e,o.title):e.html});l.control?(r.control=!0,e.controls.update({name:t,position:"right",html:o.html,style:{padding:"0 10px"},selector:o.selector,onSelect:h})):s("control"),d()&&(l.setting?(r.setting=!0,e.setting.update({name:t,tooltip:o.html,html:o.title,icon:i,width:200,selector:o.selector,onSelect:h})):s("setting"))},clear:a}}function ne(e){const t=new Map,i=[];for(const r of e){const e=t.get(r.html);void 0!==r.html&&void 0!==e?r.default&&(i[e]=r):(t.set(r.html,i.length),i.push(r))}return i}function se({art:e,shim:t,option:i}){if(!i.m3u8)return;let r=!1,n=0,s=0;const a=re(e,"mediabunny-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>'),o=re(e,"mediabunny-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>'),l=e=>!r&&!t.engine.destroyed&&t.engine.loadSeq===e;function c(){return Y(this,null,function*(){var e,s;if(r||t.engine.destroyed)return;const c=++n,d=t.engine.loadSeq,h=()=>c===n&&l(d);try{const r=yield t.getM3u8State();if(!h())return;const n=(null==(e=i.m3u8)?void 0:e.quality)||{},c=function(e,t,i){vaLine truncated
|
||||
!function(e,t){"object"===typeof exports&&"undefined"!==typeof module?module.exports=t():"function"==typeof define&&define.amd?(e.artplayerProxyMediabunny=t(),define(function(){return e.artplayerProxyMediabunny})):(e="undefined"!==typeof globalThis?globalThis:e||self).artplayerProxyMediabunny=t()}(this,function(){"use strict";var e,t,i,r,n,s,a,o,l,c,u,d,h,f,m,p,g,k,y,b,T,v,w,S,P,I,x,C,B,E,_,R,F,A=Object.defineProperty,M=Object.defineProperties,D=Object.getOwnPropertyDescriptors,N=Object.getOwnPropertySymbols,O=Object.prototype.hasOwnProperty,L=Object.prototype.propertyIsEnumerable,z=(e,t)=>(t=Symbol[e])?t:Symbol.for("Symbol."+e),U=e=>{throw TypeError(e)},q=Math.pow,W=(e,t,i)=>t in e?A(e,t,{enumerable:!0,configurable:!0,writable:!0,value:i}):e[t]=i,H=(e,t)=>{for(var i in t||(t={}))O.call(t,i)&&W(e,i,t[i]);if(N)for(var i of N(t))L.call(t,i)&&W(e,i,t[i]);return e},V=(e,t)=>M(e,D(t)),j=(e,t,i)=>t.has(e)||U("Cannot "+i),K=(e,t,i)=>(j(e,t,"read from private field"),i?i.call(e):t.get(e)),X=(e,t,i)=>t.has(e)?U("Cannot add the same private member more than once"):t instanceof WeakSet?t.add(e):t.set(e,i),$=(e,t,i,r)=>(j(e,t,"write to private field"),r?r.call(e,i):t.set(e,i),i),G=(e,t,i)=>(j(e,t,"access private method"),i),Q=(e,t,i,r)=>({set _(r){$(e,t,r,i)},get _(){return K(e,t,r)}}),Y=(e,t,i)=>new Promise((r,n)=>{var s=e=>{try{o(i.next(e))}catch(t){n(t)}},a=e=>{try{o(i.throw(e))}catch(t){n(t)}},o=e=>e.done?r(e.value):Promise.resolve(e.value).then(s,a);o((i=i.apply(e,t)).next())}),J=function(e,t){this[0]=e,this[1]=t},Z=(e,t,i)=>{var r,n=(e,t,r,s)=>{try{var a=i[e](t),o=(t=a.value)instanceof J,l=a.done;Promise.resolve(o?t[0]:t).then(i=>o?n("return"===e?e:"next",t[1]?{done:i.done,value:i.value}:i,r,s):r({value:i,done:l})).catch(e=>n("throw",e,r,s))}catch(c){s(c)}},s=(e,t,i,s)=>a[e]=a=>(t=new Promise((t,i,s)=>(s=()=>n(e,a,t,i),r?r.then(s):s())),s=()=>r===i&&(r=0),r=i=t.then(s,s),t),a={};return i=i.apply(e,t),a[z("asyncIterator")]=()=>a,s("next"),s("throw"),s("return"),a},ee=e=>{var t,i=e[z("asyncIterator")],r=!1,n={};return null==i?(i=e[z("iterator")](),t=e=>n[e]=t=>i[e](t)):(i=i.call(e),t=e=>n[e]=t=>{if(r){if(r=!1,"throw"===e)throw t;return t}return r=!0,{done:!1,value:new J(new Promise(r=>{var n=i[e](t);n instanceof Object||U("Object expected"),r(n)}),1)}}),n[z("iterator")]=()=>n,t("next"),"throw"in i?t("throw"):n.throw=e=>{throw e},"return"in i&&t("return"),n},te=(e,t,i)=>(t=e[z("asyncIterator")])?t.call(e):(e=e[z("iterator")](),t={},(i=(i,r)=>(r=e[i])&&(t[i]=t=>new Promise((i,n,s)=>(t=r.call(e,t),s=t.done,Promise.resolve(t.value).then(e=>i({value:e,done:s}),n)))))("next"),i("return"),t);function ie(e){const t=[];for(const r of e)try{r()}catch(i){t.push(i)}if(t.length)throw t[0]}function re(e,t,i){const r={control:!1,setting:!1};let n=null;function s(i){r[i]&&(r[i]=!1,"control"===i?e.controls.cache.has(t)&&e.controls.remove(t):e.setting.find(t)&&e.setting.remove(t))}function a(){n=null,ie([()=>s("control"),()=>s("setting")])}return{update:function(o,l,c,u){if(!o)return void a();n=o;const d=()=>n===o&&c(),h=e=>Y(null,null,function*(){return d()&&o.selector.some(t=>t.value===e.value)?u(e,o.title):e.html});l.control?(r.control=!0,e.controls.update({name:t,position:"right",html:o.html,style:{padding:"0 10px"},selector:o.selector,onSelect:h})):s("control"),d()&&(l.setting?(r.setting=!0,e.setting.update({name:t,tooltip:o.html,html:o.title,icon:i,width:200,selector:o.selector,onSelect:h})):s("setting"))},clear:a}}function ne(e){const t=new Map,i=[];for(const r of e){const e=t.get(r.html);void 0!==r.html&&void 0!==e?r.default&&(i[e]=r):(t.set(r.html,i.length),i.push(r))}return i}function se({art:e,shim:t,option:i}){if(!i.m3u8)return;let r=!1,n=0,s=0;const a=re(e,"mediabunny-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>'),o=re(e,"mediabunny-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>'),l=e=>!r&&!t.engine.destroyed&&t.engine.loadSeq===e;function c(){return Y(this,null,function*(){var e,s;if(r||t.engine.destroyed)return;const c=++n,d=t.engine.loadSeq,h=()=>c===n&&l(d);try{const r=yield t.getM3u8State();if(!h())return;const n=(null==(e=i.m3u8)?void 0:e.quality)||{},c=function(e,t,i){vaLine truncated
|
||||
@@ -24403,6 +24403,12 @@ class AudioTask {
|
||||
this.timers.clear();
|
||||
}
|
||||
}
|
||||
function requireDecoder(host) {
|
||||
if (host.audio.audioSink || host.video.videoSink)
|
||||
return;
|
||||
host.readyState = 0;
|
||||
throw new Error("Input has no decodable audio or video tracks.");
|
||||
}
|
||||
function publish(events, current, names) {
|
||||
for (const name of names) {
|
||||
if (!current())
|
||||
@@ -25127,6 +25133,7 @@ class Playback {
|
||||
await Promise.all([this.host.video.load(media), this.host.audio.load(media)]);
|
||||
if (!current())
|
||||
return;
|
||||
requireDecoder(this.host);
|
||||
await Promise.all([this.host.video.seek(time), this.host.audio.seek(time)]);
|
||||
if (current()) {
|
||||
this.host.readyState = 4;
|
||||
@@ -25815,6 +25822,7 @@ class MediaBunnyEngine {
|
||||
this.input = input;
|
||||
this.media = media;
|
||||
const metadata = metadataBarrier(() => {
|
||||
requireDecoder(this);
|
||||
this.readyState = 1;
|
||||
publish(this.events, current, ["loadedmetadata", "durationchange", "progress"]);
|
||||
}, current);
|
||||
@@ -25824,6 +25832,7 @@ class MediaBunnyEngine {
|
||||
await Promise.all([video, audio2]);
|
||||
if (!current())
|
||||
return;
|
||||
requireDecoder(this);
|
||||
this.readyState = 4;
|
||||
this.networkState = 1;
|
||||
publish(this.events, current, ["loadeddata", "canplay", "canplaythrough", "progress"]);
|
||||
|
||||
+2
-2
@@ -51,7 +51,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/mediabunny.test.js test/mediabunny-shim.test.js test/mediabunny-coordination.test.js test/mediabunny-video.test.js test/mediabunny-audio.test.js test/mediabunny-hls.test.js test/mediabunny-entry.test.js test/mediabunny-load.test.js test/mediabunny-input.test.js test/dpip.test.js test/dpip-lifecycle.test.js test/canvas.test.js test/canvas-lifecycle.test.js test/ambilight.test.js test/ambilight-lifecycle.test.js test/ambilight-proxy.test.js test/vast.test.js test/vast-lifecycle.test.js test/ads.test.js test/ads-lifecycle.test.js test/playback.test.js test/dash-control.test.js test/dash-contract.test.js test/dash-lifecycle.test.js test/dash-events.test.js test/hls-control.test.js test/audio-track.test.js test/public-behavior.test.js test/helpers.test.js test/chapter.test.js test/utils.test.js test/resource-scope.test.js test/instance-lifecycle.test.js test/options.test.js test/media-hosts.test.js test/plugins.test.js test/source.test.js test/playback-properties.test.js test/media-events.test.js test/template-resources.test.js test/core-vendor.test.js test/component-resources.test.js test/setting-model.test.js test/setting-layout.test.js test/setting-resources.test.js test/subtitle.test.js test/display-native.test.js test/display-video-fullscreen.test.js test/display-pip.test.js test/display-mini.test.js test/display-sizing.test.js test/display-orientation.test.js test/hotkey.test.js test/listener-registry.test.js test/global-events.test.js test/pointer-events.test.js test/gesture.test.js test/event-scheduling.test.js test/notice.test.js test/fast-forward.test.js test/auto-playback.test.js test/builtin-layers.test.js test/prompt-components.test.js test/screenshot.test.js test/thumbnails.test.js test/progress.test.js test/environment.test.js test/storage.test.js test/facade-properties.test.js test/dom-boundaries.test.js test/initialization.test.js test/entry.test.js test/accessibility-button.test.js test/accessibility-focus.test.js test/accessibility-slider.test.js",
|
||||
"test:unit": "node --test test/mediabunny.test.js test/mediabunny-shim.test.js test/mediabunny-coordination.test.js test/mediabunny-video.test.js test/mediabunny-audio.test.js test/mediabunny-hls.test.js test/mediabunny-entry.test.js test/mediabunny-capability.test.js test/mediabunny-load.test.js test/mediabunny-input.test.js test/dpip.test.js test/dpip-lifecycle.test.js test/canvas.test.js test/canvas-lifecycle.test.js test/ambilight.test.js test/ambilight-lifecycle.test.js test/ambilight-proxy.test.js test/vast.test.js test/vast-lifecycle.test.js test/ads.test.js test/ads-lifecycle.test.js test/playback.test.js test/dash-control.test.js test/dash-contract.test.js test/dash-lifecycle.test.js test/dash-events.test.js test/hls-control.test.js test/audio-track.test.js test/public-behavior.test.js test/helpers.test.js test/chapter.test.js test/utils.test.js test/resource-scope.test.js test/instance-lifecycle.test.js test/options.test.js test/media-hosts.test.js test/plugins.test.js test/source.test.js test/playback-properties.test.js test/media-events.test.js test/template-resources.test.js test/core-vendor.test.js test/component-resources.test.js test/setting-model.test.js test/setting-layout.test.js test/setting-resources.test.js test/subtitle.test.js test/display-native.test.js test/display-video-fullscreen.test.js test/display-pip.test.js test/display-mini.test.js test/display-sizing.test.js test/display-orientation.test.js test/hotkey.test.js test/listener-registry.test.js test/global-events.test.js test/pointer-events.test.js test/gesture.test.js test/event-scheduling.test.js test/notice.test.js test/fast-forward.test.js test/auto-playback.test.js test/builtin-layers.test.js test/prompt-components.test.js test/screenshot.test.js test/thumbnails.test.js test/progress.test.js test/environment.test.js test/storage.test.js test/facade-properties.test.js test/dom-boundaries.test.js test/initialization.test.js test/entry.test.js test/accessibility-button.test.js test/accessibility-focus.test.js test/accessibility-slider.test.js",
|
||||
"test:coverage": "node --test test/coverage.test.js && node scripts/coverage.mjs",
|
||||
"test": "yarn test:node && yarn test:baseline",
|
||||
"test:browser": "playwright test",
|
||||
@@ -76,7 +76,7 @@
|
||||
"test:canvas-types-package": "node refactor/scripts/canvas-package-types.mjs",
|
||||
"test:dpip": "node --test test/dpip.test.js test/dpip-lifecycle.test.js",
|
||||
"test:dpip-types-package": "node refactor/scripts/dpip-package-types.mjs",
|
||||
"test:mediabunny": "node --test test/mediabunny.test.js test/mediabunny-shim.test.js test/mediabunny-coordination.test.js test/mediabunny-video.test.js test/mediabunny-audio.test.js test/mediabunny-hls.test.js test/mediabunny-entry.test.js test/mediabunny-load.test.js test/mediabunny-input.test.js",
|
||||
"test:mediabunny": "node --test test/mediabunny.test.js test/mediabunny-shim.test.js test/mediabunny-coordination.test.js test/mediabunny-video.test.js test/mediabunny-audio.test.js test/mediabunny-hls.test.js test/mediabunny-entry.test.js test/mediabunny-capability.test.js test/mediabunny-load.test.js test/mediabunny-input.test.js",
|
||||
"test:mediabunny-types-package": "node refactor/scripts/mb-package-types.mjs"
|
||||
},
|
||||
"browserslist": "last 1 Chrome version",
|
||||
|
||||
@@ -46,8 +46,8 @@ view in `types/media.d.ts` is opt-in and checked against the real VideoShim clas
|
||||
The thirty-four production TypeScript modules use strict checking with `skipLibCheck: false`
|
||||
and no ambient Node types. The coordinator and both decoder engines are checked
|
||||
implementations; there are no remaining adjacent JavaScript declaration bridges.
|
||||
All owned production JavaScript has been migrated. This source milestone does not complete
|
||||
PKG-MB-08: unsupported decoder fallback/readiness still needs its remaining review.
|
||||
All owned production JavaScript has been migrated. PKG-MB-08 also checks public consumer
|
||||
compatibility and distinguishes supported partial media from wholly undecodable inputs.
|
||||
|
||||
The entry preserves native Canvas method priority, detached createElement invocation,
|
||||
two reads of enumerable native method accessors, direct-prototype forwarding, bound shim
|
||||
@@ -162,8 +162,18 @@ callbacks have source and request identity and are detached on replacement, draw
|
||||
Background decoder failure stops rendering and reports one media error through the coordinator,
|
||||
even if audio cleanup fails. Iterator cleanup failures are observed and warned; explicit
|
||||
stopIterator still rejects. Missing 2D context fails at use with a descriptive error, rather
|
||||
than becoming falsely ready. Capability handling for existing undecodable tracks remains
|
||||
under MB-CAP-01/MB-READY-01; the current no-video fallback is preserved for that separate review.
|
||||
than becoming falsely ready. Existing but undecodable tracks may use the historical
|
||||
no-video/no-audio fallback only when at least one selected decoder is usable.
|
||||
|
||||
`readiness.requireDecoder` checks the actual audio/video sinks after their existing SDK
|
||||
capability checks. It does not issue duplicate canDecode queries or change decoder load
|
||||
return values. The metadata barrier checks before publishing metadata, and the load
|
||||
completion checks before publishing data/canplay. Track replacement checks after decoder
|
||||
setup and before seeking or publishing its success sequence. Without either sink, readyState
|
||||
returns to zero and the existing code-4 error channel reports
|
||||
`Input has no decodable audio or video tracks.` Active SDK query errors retain their own
|
||||
messages. Valid audio-only/video-only and mixed inputs with one usable track retain normal
|
||||
readiness/playback; canceled queries cannot report against a newer source or destroyed player.
|
||||
|
||||
## Audio clock and resource ownership
|
||||
|
||||
@@ -266,9 +276,9 @@ consumers. Do not switch to export= or a required default member without those c
|
||||
`canPlayType()` still always returns maybe; buffered/played/seekable and RAF metadata remain
|
||||
synthetic. None is proof that a codec or browser can play the input. Missing Web Audio or
|
||||
WebCodecs in Windows WebKit remains an unsupported-capability control. Existing but
|
||||
undecodable tracks can still take historical clock-only fallback; remaining MB-08 work
|
||||
must distinguish valid audio-only/video-only playback from false readiness with no usable
|
||||
decoder, including selection replacement. Native/long-play/device acceptance remains MB-09.
|
||||
undecodable tracks no longer produce clock-only false readiness when all selected decoders
|
||||
are unusable. Partial playback retains the usable decoder. Native/long-play/device acceptance
|
||||
remains MB-09; no synthetic capability probe proves support on a physical Safari device.
|
||||
|
||||
## Validation and remaining work
|
||||
|
||||
@@ -283,6 +293,7 @@ yarn test:browser test/browser/mediabunny-video.spec.js
|
||||
yarn test:browser test/browser/mediabunny-audio.spec.js
|
||||
yarn test:browser test/browser/mediabunny-hls.spec.js
|
||||
yarn test:browser test/browser/mediabunny-entry.spec.js
|
||||
yarn test:browser test/browser/mediabunny-capability.spec.js
|
||||
```
|
||||
|
||||
Node tests use actual SDK parsing and controlled lifecycle interleavings. Browser tests
|
||||
|
||||
@@ -44,7 +44,9 @@ SDK track objects need explicit narrowing with your application's SDK types.
|
||||
The `canPlayType()` response remains `maybe` for compatibility; it does not test codec
|
||||
support. TimeRanges and frame callback metadata are synthetic. Actual playback requires
|
||||
supported codecs and browser media APIs; a `legacy` bundle does not polyfill those APIs.
|
||||
Decoder fallback and broader browser/device validation remain in progress.
|
||||
When every selected track is undecodable, loading or track replacement reports error code 4
|
||||
without a success readiness sequence. Partial playback keeps a usable audio or video track.
|
||||
Broader browser/device validation remains in progress.
|
||||
|
||||
ESM default imports and `/legacy` type resolution now support the tested TS 5.9 and
|
||||
TS 4.3 modes. CommonJS supports both direct calls and the old `require(...).default`
|
||||
|
||||
@@ -4,4 +4,4 @@
|
||||
* (c) 2017-2026 Harvey Zhao
|
||||
* Released under the MIT License.
|
||||
*/
|
||||
!function(e,t){"object"===typeof exports&&"undefined"!==typeof module?module.exports=t():"function"==typeof define&&define.amd?(e.artplayerProxyMediabunny=t(),define(function(){return e.artplayerProxyMediabunny})):(e="undefined"!==typeof globalThis?globalThis:e||self).artplayerProxyMediabunny=t()}(this,function(){"use strict";var e,t,i,r,a,s,n,o,c,l,u,h,d,f,m,p,g,k,y,b,w,T,S,v,P,I,x,C,B,E,_,R,F,A=e=>{throw TypeError(e)},M=(e,t,i)=>t.has(e)||A("Cannot "+i),D=(e,t,i)=>(M(e,t,"read from private field"),i?i.call(e):t.get(e)),N=(e,t,i)=>t.has(e)?A("Cannot add the same private member more than once"):t instanceof WeakSet?t.add(e):t.set(e,i),O=(e,t,i,r)=>(M(e,t,"write to private field"),r?r.call(e,i):t.set(e,i),i),L=(e,t,i)=>(M(e,t,"access private method"),i),z=(e,t,i,r)=>({set _(r){O(e,t,r,i)},get _(){return D(e,t,r)}});function U(e){const t=[];for(const r of e)try{r()}catch(i){t.push(i)}if(t.length)throw t[0]}function q(e,t,i){const r={control:!1,setting:!1};let a=null;function s(i){r[i]&&(r[i]=!1,"control"===i?e.controls.cache.has(t)&&e.controls.remove(t):e.setting.find(t)&&e.setting.remove(t))}function n(){a=null,U([()=>s("control"),()=>s("setting")])}return{update:function(o,c,l,u){if(!o)return void n();a=o;const h=()=>a===o&&l(),d=async e=>h()&&o.selector.some(t=>t.value===e.value)?u(e,o.title):e.html;c.control?(r.control=!0,e.controls.update({name:t,position:"right",html:o.html,style:{padding:"0 10px"},selector:o.selector,onSelect:d})):s("control"),h()&&(c.setting?(r.setting=!0,e.setting.update({name:t,tooltip:o.html,html:o.title,icon:i,width:200,selector:o.selector,onSelect:d})):s("setting"))},clear:n}}function W(e){const t=new Map,i=[];for(const r of e){const e=t.get(r.html);void 0!==r.html&&void 0!==e?r.default&&(i[e]=r):(t.set(r.html,i.length),i.push(r))}return i}function H({art:e,shim:t,option:i}){if(!i.m3u8)return;let r=!1,a=0,s=0;const n=q(e,"mediabunny-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>'),o=q(e,"mediabunny-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>'),c=e=>!r&&!t.engine.destroyed&&t.engine.loadSeq===e;async function l(){if(r||t.engine.destroyed)return;const e=++a,s=t.engine.loadSeq,l=()=>e===a&&c(s);try{const e=await t.getM3u8State();if(!l())return;const r=i.m3u8?.quality||{},a=function(e,t,i){if(!t.control&&!t.setting||!e?.levels.length)return null;const r=t.auto||"Auto",a=t.getName||(e=>e.name||`${e.height}P`),s=e.currentLevel?a(e.currentLevel):r,n=[];for(const l of e.levels){if(!i())return null;n.push({html:a(l),value:l.id,default:e.currentLevel?.id===l.id})}const o=new Map(e.levels.map(e=>[e.id,e.height])),c=W(n).sort((e,t)=>("auto"===t.value?0:o.get(t.value)||0)-("auto"===e.value?0:o.get(e.value)||0));return c.push({html:r,value:"auto",default:!e.currentLevel}),{title:t.title||"Quality",html:s,selector:c}}(e,r,l);if(!l())return;if(n.update(a,r,()=>c(s),(e,t)=>u("quality",e,t)),!l())return;const h=i.m3u8?.audio||{},d=function(e,t,i){if(!t.control&&!t.setting||!e||e.audios.length<2)return null;const r=t.auto||"Auto",a=t.getName||(e=>e.name||e.lang||e.language),s=e.currentAudio?a(e.currentAudio):r,n=[];for(const c of e.audios){if(!i())return null;n.push({html:a(c),value:c.id,default:e.currentAudio?.id===c.id})}const o=W(n);return o.push({html:r,value:"auto",default:!e.currentAudio}),{title:t.title||"Audio",html:s,selector:o}}(e,h,l);l()&&o.update(d,h,()=>c(s),(e,t)=>u("audio",e,t))}catch(h){if(l())throw h}}async function u(i,r,n){const o=++s,u=t.engine.loadSeq;a++;const d=()=>o===s&&c(u);try{"quality"===i?await t.switchM3u8Quality(r.value):await t.switchM3u8Audio(r.value),d()&&(await l(),d()&&(e.notice.show=`${n}: ${r.html}`))}catch(f){if(d()&&(await h(),d()))throw f}return r.html}function h(){return l().catch(e=>console.warn("MediaBunny HLS menu update:",e))}function d(){a++,s++;try{U([n.clear,o.clear])}catch(e){console.warn("MediaBunny HLS menu cleanup:",e)}}function f(){if(!r){r=!0,d();try{U([()=>e.off("video:loadedmetadata",h),()=>e.off("restart",h),()=>e.off("video:loadstart",d),()=>e.off("video:error",d),()=>e.off("destroy",f)])}catch(t){console.warn("MediaBunny HLS listener cleanup:",t)}}}try{e.on("video:loadedmetadata",h),e.on("restarLine truncated
|
||||
!function(e,t){"object"===typeof exports&&"undefined"!==typeof module?module.exports=t():"function"==typeof define&&define.amd?(e.artplayerProxyMediabunny=t(),define(function(){return e.artplayerProxyMediabunny})):(e="undefined"!==typeof globalThis?globalThis:e||self).artplayerProxyMediabunny=t()}(this,function(){"use strict";var e,t,i,r,a,s,n,o,c,l,u,h,d,f,m,p,g,k,y,b,w,T,S,v,P,I,x,C,B,E,_,R,F,A=e=>{throw TypeError(e)},M=(e,t,i)=>t.has(e)||A("Cannot "+i),D=(e,t,i)=>(M(e,t,"read from private field"),i?i.call(e):t.get(e)),N=(e,t,i)=>t.has(e)?A("Cannot add the same private member more than once"):t instanceof WeakSet?t.add(e):t.set(e,i),O=(e,t,i,r)=>(M(e,t,"write to private field"),r?r.call(e,i):t.set(e,i),i),L=(e,t,i)=>(M(e,t,"access private method"),i),z=(e,t,i,r)=>({set _(r){O(e,t,r,i)},get _(){return D(e,t,r)}});function U(e){const t=[];for(const r of e)try{r()}catch(i){t.push(i)}if(t.length)throw t[0]}function q(e,t,i){const r={control:!1,setting:!1};let a=null;function s(i){r[i]&&(r[i]=!1,"control"===i?e.controls.cache.has(t)&&e.controls.remove(t):e.setting.find(t)&&e.setting.remove(t))}function n(){a=null,U([()=>s("control"),()=>s("setting")])}return{update:function(o,c,l,u){if(!o)return void n();a=o;const h=()=>a===o&&l(),d=async e=>h()&&o.selector.some(t=>t.value===e.value)?u(e,o.title):e.html;c.control?(r.control=!0,e.controls.update({name:t,position:"right",html:o.html,style:{padding:"0 10px"},selector:o.selector,onSelect:d})):s("control"),h()&&(c.setting?(r.setting=!0,e.setting.update({name:t,tooltip:o.html,html:o.title,icon:i,width:200,selector:o.selector,onSelect:d})):s("setting"))},clear:n}}function W(e){const t=new Map,i=[];for(const r of e){const e=t.get(r.html);void 0!==r.html&&void 0!==e?r.default&&(i[e]=r):(t.set(r.html,i.length),i.push(r))}return i}function H({art:e,shim:t,option:i}){if(!i.m3u8)return;let r=!1,a=0,s=0;const n=q(e,"mediabunny-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>'),o=q(e,"mediabunny-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>'),c=e=>!r&&!t.engine.destroyed&&t.engine.loadSeq===e;async function l(){if(r||t.engine.destroyed)return;const e=++a,s=t.engine.loadSeq,l=()=>e===a&&c(s);try{const e=await t.getM3u8State();if(!l())return;const r=i.m3u8?.quality||{},a=function(e,t,i){if(!t.control&&!t.setting||!e?.levels.length)return null;const r=t.auto||"Auto",a=t.getName||(e=>e.name||`${e.height}P`),s=e.currentLevel?a(e.currentLevel):r,n=[];for(const l of e.levels){if(!i())return null;n.push({html:a(l),value:l.id,default:e.currentLevel?.id===l.id})}const o=new Map(e.levels.map(e=>[e.id,e.height])),c=W(n).sort((e,t)=>("auto"===t.value?0:o.get(t.value)||0)-("auto"===e.value?0:o.get(e.value)||0));return c.push({html:r,value:"auto",default:!e.currentLevel}),{title:t.title||"Quality",html:s,selector:c}}(e,r,l);if(!l())return;if(n.update(a,r,()=>c(s),(e,t)=>u("quality",e,t)),!l())return;const h=i.m3u8?.audio||{},d=function(e,t,i){if(!t.control&&!t.setting||!e||e.audios.length<2)return null;const r=t.auto||"Auto",a=t.getName||(e=>e.name||e.lang||e.language),s=e.currentAudio?a(e.currentAudio):r,n=[];for(const c of e.audios){if(!i())return null;n.push({html:a(c),value:c.id,default:e.currentAudio?.id===c.id})}const o=W(n);return o.push({html:r,value:"auto",default:!e.currentAudio}),{title:t.title||"Audio",html:s,selector:o}}(e,h,l);l()&&o.update(d,h,()=>c(s),(e,t)=>u("audio",e,t))}catch(h){if(l())throw h}}async function u(i,r,n){const o=++s,u=t.engine.loadSeq;a++;const d=()=>o===s&&c(u);try{"quality"===i?await t.switchM3u8Quality(r.value):await t.switchM3u8Audio(r.value),d()&&(await l(),d()&&(e.notice.show=`${n}: ${r.html}`))}catch(f){if(d()&&(await h(),d()))throw f}return r.html}function h(){return l().catch(e=>console.warn("MediaBunny HLS menu update:",e))}function d(){a++,s++;try{U([n.clear,o.clear])}catch(e){console.warn("MediaBunny HLS menu cleanup:",e)}}function f(){if(!r){r=!0,d();try{U([()=>e.off("video:loadedmetadata",h),()=>e.off("restart",h),()=>e.off("video:loadstart",d),()=>e.off("video:error",d),()=>e.off("destroy",f)])}catch(t){console.warn("MediaBunny HLS listener cleanup:",t)}}}try{e.on("video:loadedmetadata",h),e.on("restarLine truncated
|
||||
+1
-1
@@ -4,4 +4,4 @@
|
||||
* (c) 2017-2026 Harvey Zhao
|
||||
* Released under the MIT License.
|
||||
*/
|
||||
!function(e,t){"object"===typeof exports&&"undefined"!==typeof module?module.exports=t():"function"==typeof define&&define.amd?(e.artplayerProxyMediabunny=t(),define(function(){return e.artplayerProxyMediabunny})):(e="undefined"!==typeof globalThis?globalThis:e||self).artplayerProxyMediabunny=t()}(this,function(){"use strict";var e,t,i,r,n,s,a,o,l,c,u,d,h,f,m,p,g,k,y,b,T,v,w,S,P,I,x,C,B,E,_,R,F,A=Object.defineProperty,M=Object.defineProperties,D=Object.getOwnPropertyDescriptors,N=Object.getOwnPropertySymbols,O=Object.prototype.hasOwnProperty,L=Object.prototype.propertyIsEnumerable,z=(e,t)=>(t=Symbol[e])?t:Symbol.for("Symbol."+e),U=e=>{throw TypeError(e)},q=Math.pow,W=(e,t,i)=>t in e?A(e,t,{enumerable:!0,configurable:!0,writable:!0,value:i}):e[t]=i,H=(e,t)=>{for(var i in t||(t={}))O.call(t,i)&&W(e,i,t[i]);if(N)for(var i of N(t))L.call(t,i)&&W(e,i,t[i]);return e},V=(e,t)=>M(e,D(t)),j=(e,t,i)=>t.has(e)||U("Cannot "+i),K=(e,t,i)=>(j(e,t,"read from private field"),i?i.call(e):t.get(e)),X=(e,t,i)=>t.has(e)?U("Cannot add the same private member more than once"):t instanceof WeakSet?t.add(e):t.set(e,i),$=(e,t,i,r)=>(j(e,t,"write to private field"),r?r.call(e,i):t.set(e,i),i),G=(e,t,i)=>(j(e,t,"access private method"),i),Q=(e,t,i,r)=>({set _(r){$(e,t,r,i)},get _(){return K(e,t,r)}}),Y=(e,t,i)=>new Promise((r,n)=>{var s=e=>{try{o(i.next(e))}catch(t){n(t)}},a=e=>{try{o(i.throw(e))}catch(t){n(t)}},o=e=>e.done?r(e.value):Promise.resolve(e.value).then(s,a);o((i=i.apply(e,t)).next())}),J=function(e,t){this[0]=e,this[1]=t},Z=(e,t,i)=>{var r,n=(e,t,r,s)=>{try{var a=i[e](t),o=(t=a.value)instanceof J,l=a.done;Promise.resolve(o?t[0]:t).then(i=>o?n("return"===e?e:"next",t[1]?{done:i.done,value:i.value}:i,r,s):r({value:i,done:l})).catch(e=>n("throw",e,r,s))}catch(c){s(c)}},s=(e,t,i,s)=>a[e]=a=>(t=new Promise((t,i,s)=>(s=()=>n(e,a,t,i),r?r.then(s):s())),s=()=>r===i&&(r=0),r=i=t.then(s,s),t),a={};return i=i.apply(e,t),a[z("asyncIterator")]=()=>a,s("next"),s("throw"),s("return"),a},ee=e=>{var t,i=e[z("asyncIterator")],r=!1,n={};return null==i?(i=e[z("iterator")](),t=e=>n[e]=t=>i[e](t)):(i=i.call(e),t=e=>n[e]=t=>{if(r){if(r=!1,"throw"===e)throw t;return t}return r=!0,{done:!1,value:new J(new Promise(r=>{var n=i[e](t);n instanceof Object||U("Object expected"),r(n)}),1)}}),n[z("iterator")]=()=>n,t("next"),"throw"in i?t("throw"):n.throw=e=>{throw e},"return"in i&&t("return"),n},te=(e,t,i)=>(t=e[z("asyncIterator")])?t.call(e):(e=e[z("iterator")](),t={},(i=(i,r)=>(r=e[i])&&(t[i]=t=>new Promise((i,n,s)=>(t=r.call(e,t),s=t.done,Promise.resolve(t.value).then(e=>i({value:e,done:s}),n)))))("next"),i("return"),t);function ie(e){const t=[];for(const r of e)try{r()}catch(i){t.push(i)}if(t.length)throw t[0]}function re(e,t,i){const r={control:!1,setting:!1};let n=null;function s(i){r[i]&&(r[i]=!1,"control"===i?e.controls.cache.has(t)&&e.controls.remove(t):e.setting.find(t)&&e.setting.remove(t))}function a(){n=null,ie([()=>s("control"),()=>s("setting")])}return{update:function(o,l,c,u){if(!o)return void a();n=o;const d=()=>n===o&&c(),h=e=>Y(null,null,function*(){return d()&&o.selector.some(t=>t.value===e.value)?u(e,o.title):e.html});l.control?(r.control=!0,e.controls.update({name:t,position:"right",html:o.html,style:{padding:"0 10px"},selector:o.selector,onSelect:h})):s("control"),d()&&(l.setting?(r.setting=!0,e.setting.update({name:t,tooltip:o.html,html:o.title,icon:i,width:200,selector:o.selector,onSelect:h})):s("setting"))},clear:a}}function ne(e){const t=new Map,i=[];for(const r of e){const e=t.get(r.html);void 0!==r.html&&void 0!==e?r.default&&(i[e]=r):(t.set(r.html,i.length),i.push(r))}return i}function se({art:e,shim:t,option:i}){if(!i.m3u8)return;let r=!1,n=0,s=0;const a=re(e,"mediabunny-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>'),o=re(e,"mediabunny-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>'),l=e=>!r&&!t.engine.destroyed&&t.engine.loadSeq===e;function c(){return Y(this,null,function*(){var e,s;if(r||t.engine.destroyed)return;const c=++n,d=t.engine.loadSeq,h=()=>c===n&&l(d);try{const r=yield t.getM3u8State();if(!h())return;const n=(null==(e=i.m3u8)?void 0:e.quality)||{},c=function(e,t,i){vaLine truncated
|
||||
!function(e,t){"object"===typeof exports&&"undefined"!==typeof module?module.exports=t():"function"==typeof define&&define.amd?(e.artplayerProxyMediabunny=t(),define(function(){return e.artplayerProxyMediabunny})):(e="undefined"!==typeof globalThis?globalThis:e||self).artplayerProxyMediabunny=t()}(this,function(){"use strict";var e,t,i,r,n,s,a,o,l,c,u,d,h,f,m,p,g,k,y,b,T,v,w,S,P,I,x,C,B,E,_,R,F,A=Object.defineProperty,M=Object.defineProperties,D=Object.getOwnPropertyDescriptors,N=Object.getOwnPropertySymbols,O=Object.prototype.hasOwnProperty,L=Object.prototype.propertyIsEnumerable,z=(e,t)=>(t=Symbol[e])?t:Symbol.for("Symbol."+e),U=e=>{throw TypeError(e)},q=Math.pow,W=(e,t,i)=>t in e?A(e,t,{enumerable:!0,configurable:!0,writable:!0,value:i}):e[t]=i,H=(e,t)=>{for(var i in t||(t={}))O.call(t,i)&&W(e,i,t[i]);if(N)for(var i of N(t))L.call(t,i)&&W(e,i,t[i]);return e},V=(e,t)=>M(e,D(t)),j=(e,t,i)=>t.has(e)||U("Cannot "+i),K=(e,t,i)=>(j(e,t,"read from private field"),i?i.call(e):t.get(e)),X=(e,t,i)=>t.has(e)?U("Cannot add the same private member more than once"):t instanceof WeakSet?t.add(e):t.set(e,i),$=(e,t,i,r)=>(j(e,t,"write to private field"),r?r.call(e,i):t.set(e,i),i),G=(e,t,i)=>(j(e,t,"access private method"),i),Q=(e,t,i,r)=>({set _(r){$(e,t,r,i)},get _(){return K(e,t,r)}}),Y=(e,t,i)=>new Promise((r,n)=>{var s=e=>{try{o(i.next(e))}catch(t){n(t)}},a=e=>{try{o(i.throw(e))}catch(t){n(t)}},o=e=>e.done?r(e.value):Promise.resolve(e.value).then(s,a);o((i=i.apply(e,t)).next())}),J=function(e,t){this[0]=e,this[1]=t},Z=(e,t,i)=>{var r,n=(e,t,r,s)=>{try{var a=i[e](t),o=(t=a.value)instanceof J,l=a.done;Promise.resolve(o?t[0]:t).then(i=>o?n("return"===e?e:"next",t[1]?{done:i.done,value:i.value}:i,r,s):r({value:i,done:l})).catch(e=>n("throw",e,r,s))}catch(c){s(c)}},s=(e,t,i,s)=>a[e]=a=>(t=new Promise((t,i,s)=>(s=()=>n(e,a,t,i),r?r.then(s):s())),s=()=>r===i&&(r=0),r=i=t.then(s,s),t),a={};return i=i.apply(e,t),a[z("asyncIterator")]=()=>a,s("next"),s("throw"),s("return"),a},ee=e=>{var t,i=e[z("asyncIterator")],r=!1,n={};return null==i?(i=e[z("iterator")](),t=e=>n[e]=t=>i[e](t)):(i=i.call(e),t=e=>n[e]=t=>{if(r){if(r=!1,"throw"===e)throw t;return t}return r=!0,{done:!1,value:new J(new Promise(r=>{var n=i[e](t);n instanceof Object||U("Object expected"),r(n)}),1)}}),n[z("iterator")]=()=>n,t("next"),"throw"in i?t("throw"):n.throw=e=>{throw e},"return"in i&&t("return"),n},te=(e,t,i)=>(t=e[z("asyncIterator")])?t.call(e):(e=e[z("iterator")](),t={},(i=(i,r)=>(r=e[i])&&(t[i]=t=>new Promise((i,n,s)=>(t=r.call(e,t),s=t.done,Promise.resolve(t.value).then(e=>i({value:e,done:s}),n)))))("next"),i("return"),t);function ie(e){const t=[];for(const r of e)try{r()}catch(i){t.push(i)}if(t.length)throw t[0]}function re(e,t,i){const r={control:!1,setting:!1};let n=null;function s(i){r[i]&&(r[i]=!1,"control"===i?e.controls.cache.has(t)&&e.controls.remove(t):e.setting.find(t)&&e.setting.remove(t))}function a(){n=null,ie([()=>s("control"),()=>s("setting")])}return{update:function(o,l,c,u){if(!o)return void a();n=o;const d=()=>n===o&&c(),h=e=>Y(null,null,function*(){return d()&&o.selector.some(t=>t.value===e.value)?u(e,o.title):e.html});l.control?(r.control=!0,e.controls.update({name:t,position:"right",html:o.html,style:{padding:"0 10px"},selector:o.selector,onSelect:h})):s("control"),d()&&(l.setting?(r.setting=!0,e.setting.update({name:t,tooltip:o.html,html:o.title,icon:i,width:200,selector:o.selector,onSelect:h})):s("setting"))},clear:a}}function ne(e){const t=new Map,i=[];for(const r of e){const e=t.get(r.html);void 0!==r.html&&void 0!==e?r.default&&(i[e]=r):(t.set(r.html,i.length),i.push(r))}return i}function se({art:e,shim:t,option:i}){if(!i.m3u8)return;let r=!1,n=0,s=0;const a=re(e,"mediabunny-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>'),o=re(e,"mediabunny-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>'),l=e=>!r&&!t.engine.destroyed&&t.engine.loadSeq===e;function c(){return Y(this,null,function*(){var e,s;if(r||t.engine.destroyed)return;const c=++n,d=t.engine.loadSeq,h=()=>c===n&&l(d);try{const r=yield t.getM3u8State();if(!h())return;const n=(null==(e=i.m3u8)?void 0:e.quality)||{},c=function(e,t,i){vaLine truncated
|
||||
@@ -24403,6 +24403,12 @@ class AudioTask {
|
||||
this.timers.clear();
|
||||
}
|
||||
}
|
||||
function requireDecoder(host) {
|
||||
if (host.audio.audioSink || host.video.videoSink)
|
||||
return;
|
||||
host.readyState = 0;
|
||||
throw new Error("Input has no decodable audio or video tracks.");
|
||||
}
|
||||
function publish(events, current, names) {
|
||||
for (const name of names) {
|
||||
if (!current())
|
||||
@@ -25127,6 +25133,7 @@ class Playback {
|
||||
await Promise.all([this.host.video.load(media), this.host.audio.load(media)]);
|
||||
if (!current())
|
||||
return;
|
||||
requireDecoder(this.host);
|
||||
await Promise.all([this.host.video.seek(time), this.host.audio.seek(time)]);
|
||||
if (current()) {
|
||||
this.host.readyState = 4;
|
||||
@@ -25815,6 +25822,7 @@ class MediaBunnyEngine {
|
||||
this.input = input;
|
||||
this.media = media;
|
||||
const metadata = metadataBarrier(() => {
|
||||
requireDecoder(this);
|
||||
this.readyState = 1;
|
||||
publish(this.events, current, ["loadedmetadata", "durationchange", "progress"]);
|
||||
}, current);
|
||||
@@ -25824,6 +25832,7 @@ class MediaBunnyEngine {
|
||||
await Promise.all([video, audio2]);
|
||||
if (!current())
|
||||
return;
|
||||
requireDecoder(this);
|
||||
this.readyState = 4;
|
||||
this.networkState = 1;
|
||||
publish(this.events, current, ["loadeddata", "canplay", "canplaythrough", "progress"]);
|
||||
|
||||
@@ -10,7 +10,7 @@ import { getHlsState } from './hls-state'
|
||||
import { createInput } from './input'
|
||||
import LoadSession from './load-session'
|
||||
import Playback from './playback'
|
||||
import { metadataBarrier, publish } from './readiness'
|
||||
import { metadataBarrier, publish, requireDecoder } from './readiness'
|
||||
import { selectPlaybackTracks } from './tracks'
|
||||
import VideoEngine from './VideoEngine.js'
|
||||
|
||||
@@ -150,6 +150,7 @@ export default class MediaBunnyEngine implements EnginePort {
|
||||
this.input = input
|
||||
this.media = media
|
||||
const metadata = metadataBarrier(() => {
|
||||
requireDecoder(this)
|
||||
this.readyState = 1
|
||||
publish(this.events, current, ['loadedmetadata', 'durationchange', 'progress'])
|
||||
}, current)
|
||||
@@ -159,6 +160,7 @@ export default class MediaBunnyEngine implements EnginePort {
|
||||
await Promise.all([video, audio])
|
||||
if (!current())
|
||||
return
|
||||
requireDecoder(this)
|
||||
this.readyState = 4
|
||||
this.networkState = 1
|
||||
publish(this.events, current, ['loadeddata', 'canplay', 'canplaythrough', 'progress'])
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import type { Input } from 'mediabunny'
|
||||
import type { AudioBufferSink, CanvasSink, Input } from 'mediabunny'
|
||||
import type { ProxyOptions } from './engine-types'
|
||||
import type EventTarget from './EventTarget'
|
||||
import type { PlaybackMedia } from './media'
|
||||
|
||||
export interface AudioPort {
|
||||
readonly audioSink: AudioBufferSink | null
|
||||
cancelPending: () => void
|
||||
readonly currentTime: number
|
||||
duration: number
|
||||
@@ -19,6 +20,7 @@ export interface AudioPort {
|
||||
}
|
||||
|
||||
export interface VideoPort {
|
||||
readonly videoSink: CanvasSink | null
|
||||
cancelPending: () => void
|
||||
width: number
|
||||
height: number
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { PlaybackHost } from './engine-ports'
|
||||
import type { PlaybackMedia } from './media'
|
||||
import { publish } from './readiness'
|
||||
import { publish, requireDecoder } from './readiness'
|
||||
import { resolveDuration } from './tracks'
|
||||
|
||||
interface Completion {
|
||||
@@ -156,6 +156,7 @@ export default class Playback {
|
||||
await Promise.all([this.host.video.load(media), this.host.audio.load(media)])
|
||||
if (!current())
|
||||
return
|
||||
requireDecoder(this.host)
|
||||
await Promise.all([this.host.video.seek(time), this.host.audio.seek(time)])
|
||||
if (current()) {
|
||||
this.host.readyState = 4
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
import type { PlaybackHost } from './engine-ports'
|
||||
import type EventTarget from './EventTarget'
|
||||
|
||||
export function requireDecoder(host: Pick<PlaybackHost, 'audio' | 'video' | 'readyState'>): void {
|
||||
// Sinks reflect the decoder's completed capability check; do not query the SDK twice.
|
||||
if (host.audio.audioSink || host.video.videoSink)
|
||||
return
|
||||
host.readyState = 0
|
||||
throw new Error('Input has no decodable audio or video tracks.')
|
||||
}
|
||||
|
||||
export function publish(events: EventTarget, current: () => boolean, names: readonly string[]): void {
|
||||
for (const name of names) {
|
||||
if (!current())
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"task": "PKG-MB-08",
|
||||
"sourceCommit": "5e28dfee950a748f5a92dc0d26508815f23c6aec",
|
||||
"sourceSha256LF": {
|
||||
"packages/artplayer-proxy-mediabunny/src/readiness.ts": "9aa24b5e994006b45bda9eafad602785c3e0e334cef54ff45a4939e386c4aceb",
|
||||
"packages/artplayer-proxy-mediabunny/src/MediaBunnyEngine.ts": "f8c6d96d11878712a40571961d734d4baa4e6bb5317dd32b776ea7a4795ca951",
|
||||
"packages/artplayer-proxy-mediabunny/src/playback.ts": "e2a65782c89627198752ff1fe691d4eea0bef215d883a59f0bdcd3e904a4fb47",
|
||||
"packages/artplayer-proxy-mediabunny/src/engine-ports.ts": "c56fad4a8f9578aa37686972d96d925012df8160c62337a53200b23a4fc3b164",
|
||||
"packages/artplayer-proxy-mediabunny/dist/artplayer-proxy-mediabunny.js": "b7f4ff882a59cdff2b351d0ffcf4948dc9c06317c11dfaf6257f4865bf8c4134"
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,58 @@
|
||||
# PKG-MB-08:完成类型迁移与解码能力边界
|
||||
|
||||
入口与公开类型已在5e28dfee检查点落地,本轮完成剩余能力/就绪问题。
|
||||
实际接续源码及main冻结于[能力基线](../baselines/mb-capability-source.json)。
|
||||
完整CI与最终证据核对通过,任务标记done并立即专用本地提交。
|
||||
|
||||
## 问题与修复
|
||||
|
||||
实际SDK解析出的video-only/audio-only输入,在codec=null或canDecode=false时,旧引擎把
|
||||
两端都降级为空轨道,却仍发布loadedmetadata/data/canplay/through并进入readyState 4。
|
||||
HLS替换轨道也有相同问题。仅有音频时使用音频解码,或仅有视频时使用AudioContext作为时钟,
|
||||
本来是有效场景;不能一律要求音频和视频两条轨道都支持。
|
||||
|
||||
readiness.ts新增内部requireDecoder,根据两个引擎完成现有能力检查后实际建立的sink判断。
|
||||
不重复调用SDK canDecode,不改变decoder.load的Promise<void>返回,也不添加Canvas公开字段。
|
||||
metadata barrier在发布metadata前检查;load在发布data/canplay前检查;轨道替换在decoder
|
||||
加载后、seek及成功事件前检查。全部不可用时readyState归零,通过既有code 4错误通道报告
|
||||
`Input has no decodable audio or video tracks.`。原SDK查询拒绝保留原消息。
|
||||
|
||||
至少一条轨道可用时,原有部分播放、事件顺序和AudioContext时钟保留。暂停不取消仍有效的加载;
|
||||
过期能力查询不能对新source或已destroy实例发就绪/错误。后续有效source可正常恢复。
|
||||
HLS失败触发现有error清理,移除失效菜单;旧canPlayType恒maybe及synthetic ranges/RAF不变。
|
||||
内部AudioPort/VideoPort补上准确SDK sink类型,公共声明仍不引入这些SDK依赖。
|
||||
|
||||
## 验证
|
||||
|
||||
新增16项Node回归:两个单轨输入的false/null能力、轨道替换、正常音频、晚查询false/reject、
|
||||
暂停期间查询、活动SDK错误、有效source恢复。使用真实SDK输入/track与受控能力响应;正常替换
|
||||
夹具先模拟已工作的decoder并保留真实Input,再使用实际引擎执行失败替换。
|
||||
同断言旧main8通过/8失败;候选全部通过。原协调器两个正常夹具补齐其声明模拟的有效sink,
|
||||
事件/顺序断言保持原样,没有为错误实现放宽预期。候选包专项257、main/legacy各233通过;
|
||||
每个产物命令包含229候选断言与4冻结正常控制。
|
||||
|
||||
新增30浏览器用例涵盖发布版/候选核心与三个引擎。Chromium/Firefox中12项受控不可解码
|
||||
检查通过,8项混合媒体保留另一实际原生解码器并播放;Windows WebKit的10项API缺失为能力对照。
|
||||
canDecode=false是受控输入,不把它描述为浏览器自身拒绝该真实fixture的编码。
|
||||
旧main相同30项18通过/12失败;失败均为单轨假就绪和HLS替换假成功,正常部分播放仍通过。
|
||||
每轮report及整个results目录均保留。
|
||||
|
||||
最终整组浏览器、隔离安装、完整CI及哈希记录见[最终证据](../baselines/mb-capability-validation.json)。
|
||||
入口检查点的17组安装类型/导出矩阵在最终候选上重跑;两个冻结声明整工厂双向赋值、精确Canvas
|
||||
结果和旧Option保持,新的可选shim/Player/HLS/RAF类型仍由实际VideoShim实现约束。
|
||||
|
||||
## 维护与验收边界
|
||||
|
||||
原有8个JS模块已在前序步骤细分并全部迁为34个生产TS模块,无相邻声明桥替代实现检查。
|
||||
包内ARCHITECTURE.md记录模块、资源归属、能力/类型边界,README给出可选类型用法;
|
||||
test:unit/test:mediabunny接入本轮用例,三种构建及docs复制由标准build生成。本轮没有新依赖。
|
||||
|
||||
MB-08交付严格TS实现、兼容媒体声明与明确的不可解码错误处理。MB-CAP-01仍保留物理Safari/
|
||||
支持范围核验,MB-09负责长播放/音画同步/设备/组合,MB-10负责示例、完整分发与MPL/source notice。
|
||||
这些门槛不因本轮正常检查通过而视为完成。后续发现具体失败应修复并加入回归,不能只增加豁免。
|
||||
回退本轮commit可还原能力处理;入口/声明检查点单独回退5e28dfee,不改冻结历史输入。
|
||||
|
||||
最终9个MediaBunny浏览器文件150项通过,其中39项为Windows WebKit能力缺失对照;
|
||||
完整CI1418项(1263单元+14工程+141基线)与44重复契约通过,324生产TS。
|
||||
最终源码/声明/测试/产物/安装文件及报告哈希一致,三格式dist与docs复制一致。
|
||||
MB-TYPE-01和MB-READY-01按其声明/就绪验收条件关闭;MB-CAP-01、MB-LIFE-01与MB-LICENSE-01仍open。
|
||||
@@ -91,3 +91,13 @@ index实际迁移,入口/Canvas桥/生命周期/清理拆为四模块,全包
|
||||
见[变更](changes/2026-09-12-PKG-MB-08-entry-checkpoint.md)和[证据](baselines/mb-entry-checkpoint.json)。
|
||||
完整CI1402项及44重复契约通过,324生产TS。受控canDecode=false已复现旧/候选
|
||||
video-only输入及替换无sink仍readyState=4;任务保持doing,下一步修复该能力边界。
|
||||
|
||||
## PKG-MB-08 完成:能力与公开声明验收
|
||||
|
||||
入口检查点后补齐解码能力就绪门槛:全部无sink时通过原code 4通道拒绝,至少一条可用则
|
||||
保留正常部分播放。16新增Node断言旧main8通过/8失败;候选专项257、main/legacy各233通过。
|
||||
新增30浏览器中12受控拒绝、8原生部分播放及10能力对照;旧main相同30项18通过/12失败。
|
||||
最终150浏览器、17组实际安装类型/导出、完整CI1418与44重复契约通过,324生产TS。
|
||||
[变更](changes/2026-09-12-PKG-MB-08-capability.md)和[证据](baselines/mb-capability-validation.json)
|
||||
核对全部源码/产物/报告/安装文件哈希。MB-TYPE-01/MB-READY-01关闭;任务done并立即专用提交。
|
||||
39个WebKit能力对照不是播放或物理Safari支持,长播放/设备/组合及分发许可仍由09/10接续。
|
||||
+3
-3
@@ -4,7 +4,7 @@
|
||||
|
||||
基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 222 项,范围 22 个包及工作区/示例。
|
||||
|
||||
状态:todo 110 / doing 8 / blocked 0 / done 104 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
|
||||
状态:todo 110 / doing 7 / blocked 0 / done 105 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
|
||||
|
||||
前置依赖是启动条件;验收是完成条件。任务可以继续拆分,但不能复用或悄悄删除旧 ID。
|
||||
|
||||
@@ -336,7 +336,7 @@
|
||||
| PKG-MB-05 | artplayer-proxy-mediabunny<br>整理视频解码、seek 和帧释放 | PKG-MB-04 | VideoEngine 调度、队列、晚帧处理和资源释放 | 暂停/seek/切源无旧帧覆盖,默认丢帧策略不变 | H | done |
|
||||
| PKG-MB-06 | artplayer-proxy-mediabunny<br>整理音频解码、时钟与同步 | PKG-MB-04 | AudioEngine 时钟、缓冲和 AudioContext 归属 | AV sync、倍速、无音轨、静音/音量和暂停恢复通过 | H | done |
|
||||
| PKG-MB-07 | artplayer-proxy-mediabunny<br>整理 HLS 配对轨道与 selector | PKG-MB-05, PKG-MB-06, CORE-14 | m3u8 配对、质量/音频选择及拓扑清理 | 实际选择高亮、切到无轨道来源清理、无重复 readiness | H | done |
|
||||
| PKG-MB-08 | artplayer-proxy-mediabunny<br>完成 TS 与媒体能力声明 | PKG-MB-07, ENG-04, ENG-06 | 8 个自有 JS 模块迁移与 Result/shim 类型 | 解码器/Stream/DOM 类型清楚,旧 Option 和 art.mediabunny 使用保持 | H | doing |
|
||||
| PKG-MB-08 | artplayer-proxy-mediabunny<br>完成 TS 与媒体能力声明 | PKG-MB-07, ENG-04, ENG-06 | 8 个自有 JS 模块迁移与 Result/shim 类型 | 解码器/Stream/DOM 类型清楚,旧 Option 和 art.mediabunny 使用保持 | H | done |
|
||||
| PKG-MB-09 | artplayer-proxy-mediabunny<br>完成新旧核心和真实媒体组合 | PKG-MB-08, CORE-22 | 跨浏览器能力、长播放、DPiP、HLS 音轨/质量报告 | 资源释放、事件顺序、AV sync 与支持范围满足基线 | H | todo |
|
||||
| PKG-MB-10 | artplayer-proxy-mediabunny<br>完成分发和文档 | PKG-MB-09, ENG-07 | mediabunny.js、README、依赖版本、三产物和许可 | tarball 可消费、无意外依赖升级、旧调用与回退可用 | H | todo |
|
||||
|
||||
@@ -527,5 +527,5 @@
|
||||
- PKG-MB-05: [记录](baselines/mb-video-source.json) [记录](changes/2026-09-12-PKG-MB-05-video.md) [记录](baselines/mb-video-validation.json)
|
||||
- PKG-MB-06: [记录](baselines/mb-audio-source.json) [记录](changes/2026-09-12-PKG-MB-06-audio.md) [记录](baselines/mb-audio-validation.json)
|
||||
- PKG-MB-07: [记录](baselines/mb-hls-source.json) [记录](changes/2026-09-12-PKG-MB-07-hls.md) [记录](baselines/mb-hls-validation.json)
|
||||
- PKG-MB-08: [记录](baselines/mb-entry-source.json) [记录](changes/2026-09-12-PKG-MB-08-entry-checkpoint.md) [记录](baselines/mb-entry-checkpoint.json)
|
||||
- PKG-MB-08: [记录](baselines/mb-entry-source.json) [记录](changes/2026-09-12-PKG-MB-08-entry-checkpoint.md) [记录](baselines/mb-entry-checkpoint.json) [记录](baselines/mb-capability-source.json) [记录](changes/2026-09-12-PKG-MB-08-capability.md) [记录](baselines/mb-capability-validation.json)
|
||||
- PKG-FACTORY-01: [记录](baselines/factory-assignment-gaps.json) [记录](baselines/factory-compatibility-proposals.json) [记录](factory-compatibility-decision.md) [记录](changes/2026-09-12-PKG-FACTORY-01-decision.md)
|
||||
@@ -1,5 +1,17 @@
|
||||
# 进度与证据
|
||||
|
||||
## PKG-MB-08完成:严格TS、兼容声明与解码能力边界
|
||||
|
||||
全包34生产TS已迁移,默认可选工厂/精确Canvas兼容;显式媒体类型不泄漏SDK现代声明。
|
||||
本轮修复全部轨道不可解码时仍发布就绪和替换成功的问题;保留一条可用轨道的部分播放,
|
||||
不重复canDecode查询,SDK原错误和过期查询取消保持。16新断言旧main8通过/8失败,
|
||||
候选专项257及main/legacy各233通过。新增30浏览器含12受控拒绝、8实际部分解码播放、
|
||||
10能力对照;旧main相同30项18通过/12失败。最终150浏览器全部通过,39能力对照不计播放。
|
||||
最终实际安装17类型/导出矩阵、完整CI1418及44重复契约通过,324生产TS,全部哈希核对。
|
||||
见[变更](changes/2026-09-12-PKG-MB-08-capability.md)与[证据](baselines/mb-capability-validation.json)。
|
||||
222项:105 done、7 doing、110 todo;立即专用本地提交。MB-TYPE-01/MB-READY-01关闭。
|
||||
下一项MB-09处理长播放、AV sync、原生DPiP与新旧核心组合;能力/设备、完整分发与许可门槛仍open。
|
||||
|
||||
## PKG-MB-08检查点:入口TS与兼容媒体声明(doing)
|
||||
|
||||
全包34个自有生产模块已转TS,入口/Canvas桥/生命周期/清理分离。默认工厂仍可选Option、
|
||||
|
||||
@@ -170,6 +170,6 @@
|
||||
| FACTORY-TYPE-01 | open / 已复现 | Canvas and Ambilight default self property narrows assignability of historical factory replacements | PKG-FACTORY-01 |
|
||||
| MB-LICENSE-01 | open / 已复现 | Historical MediaBunny proxy bundles embed SDK code without MPL notice markers or separate license/source provenance | PKG-MB-10 |
|
||||
| MB-LIFE-01 | open / 已复现 | MediaBunny load, track replacement, timeout, RAF and audio callbacks lack one shared terminal owner | PKG-MB-02, PKG-MB-03, PKG-MB-04, PKG-MB-05, PKG-MB-06, PKG-MB-07, PKG-MB-09 |
|
||||
| MB-TYPE-01 | open / 已复现 | MediaBunny proxy declares only Canvas while exposing a shim and changes CommonJS export generation between releases | PKG-MB-08, PKG-MB-10 |
|
||||
| MB-TYPE-01 | resolved / 已复现 | MediaBunny proxy declares only Canvas while exposing a shim and changes CommonJS export generation between releases | PKG-MB-08, PKG-MB-10 |
|
||||
| MB-CAP-01 | open / 已复现 | Current Windows WebKit lacks WebCodecs and Web Audio constructors needed by historical MediaBunny proxies | PKG-MB-02, PKG-MB-09 |
|
||||
| MB-READY-01 | open / 已复现 | MediaBunny historical readiness can repeat for trackless input and occur after capability errors | PKG-MB-04, PKG-MB-09 |
|
||||
| MB-READY-01 | resolved / 已复现 | MediaBunny historical readiness can repeat for trackless input and occur after capability errors | PKG-MB-04, PKG-MB-09 |
|
||||
+10
-6
@@ -3679,15 +3679,17 @@
|
||||
"PKG-MB-08",
|
||||
"PKG-MB-10"
|
||||
],
|
||||
"compatibleResolution": "Preserve optional whole-factory and exact Canvas assignment, model explicit shim capabilities separately and verify historical default namespace and callable consumers. MB-08 entry checkpoint preserves exact optional factory/Canvas assignment, adds opt-in checked media types without SDK imports, and fixes classic legacy/NodeNext ESM resolution. Seventeen installed compiler cases plus runtime exports passed against two frozen releases and candidate; seven candidate cases reject fifteen invalid uses each. Type facade/runtime export part is verified; final MB-08 capability and MB-10 distribution gates remain open.",
|
||||
"compatibleResolution": "Preserved exact optional whole-factory and HTMLCanvasElement result; real VideoShim implements opt-in media/Player/HLS/synthetic-frame types without modern SDK declarations leaking to older compilers. Conditional ESM declarations and classic legacy resolution work. Runtime supports both historical default namespace and callable use without a required default property narrowing factory replacement. Final isolated installs of two frozen releases and candidate pass 17 compiler/runtime cases; seven candidate modes each reject fifteen invalid uses and check bidirectional assignments against actual frozen declaration bytes. Complete distribution/license/device gates remain separate.",
|
||||
"closureCriteria": "Actual installed old/candidate compiler and runtime export matrices pass with negative controls; any incompatible declaration generation requires an explicit decision.",
|
||||
"status": "open",
|
||||
"status": "resolved",
|
||||
"evidence": [
|
||||
"refactor/baselines/mb-release.json",
|
||||
"refactor/baselines/mb-contract.md",
|
||||
"refactor/changes/2026-09-12-PKG-MB-01-contract.md",
|
||||
"refactor/changes/2026-09-12-PKG-MB-08-entry-checkpoint.md",
|
||||
"refactor/baselines/mb-entry-checkpoint.json"
|
||||
"refactor/baselines/mb-entry-checkpoint.json",
|
||||
"refactor/changes/2026-09-12-PKG-MB-08-capability.md",
|
||||
"refactor/baselines/mb-capability-validation.json"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -3711,20 +3713,22 @@
|
||||
{
|
||||
"id": "MB-READY-01",
|
||||
"title": "MediaBunny historical readiness can repeat for trackless input and occur after capability errors",
|
||||
"status": "open",
|
||||
"status": "resolved",
|
||||
"confirmation": "reproduced",
|
||||
"owners": [
|
||||
"PKG-MB-04",
|
||||
"PKG-MB-09"
|
||||
],
|
||||
"compatibleResolution": "Use explicit coordinated readiness and failure state, preserving valid event order while preventing duplicate or stale callbacks; evaluate trackless behavior separately from normal media. MB-04 coordinates metadata once, suppresses stale readiness and rejects trackless input before decoder setup. Existing-track unsupported-codec behavior and full native combinations remain MB-05/06/09; risk stays open. MB-08 entry checkpoint reproduces remaining false readiness in both previous and candidate builds: actual SDK-parsed video-only MP4 with controlled canDecode=false and AudioContext yields null audio/video sinks but readyState 4 and all readiness events, including replaceTracks. Keep open for the next MB-08 capability implementation; this controlled case is not native decoding evidence.",
|
||||
"compatibleResolution": "Coordinated metadata publishes once, trackless input rejects before decoder setup, and stale callbacks cannot publish. MB-08 additionally rejects existing but wholly undecodable tracks before metadata/data/canplay and before replacement success, preserving one usable audio/video track, SDK query errors and late-operation cancellation. Sixteen controlled Node cases and thirty added browser cases distinguish twelve controlled rejection cases, eight native partial-decoder playback cases and ten missing-API controls; the final 150-case media suite passes. Physical Safari/support-range work remains MB-CAP-01 rather than being claimed from these controls.",
|
||||
"closureCriteria": "Candidate metadata/data/canplay ordering and counts pass normal, absent-track and failure interleavings in native browsers and controlled race tests.",
|
||||
"evidence": [
|
||||
"refactor/changes/2026-09-12-PKG-MB-02-baseline.md",
|
||||
"refactor/baselines/mb-behavior-validation.json",
|
||||
"refactor/changes/2026-09-12-PKG-MB-04-coordination.md",
|
||||
"refactor/baselines/mb-coordination-validation.json",
|
||||
"refactor/baselines/mb-entry-checkpoint.json"
|
||||
"refactor/baselines/mb-entry-checkpoint.json",
|
||||
"refactor/changes/2026-09-12-PKG-MB-08-capability.md",
|
||||
"refactor/baselines/mb-capability-validation.json"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
+5
-2
@@ -3546,14 +3546,17 @@
|
||||
"ENG-04",
|
||||
"ENG-06"
|
||||
],
|
||||
"status": "doing",
|
||||
"status": "done",
|
||||
"risk": "H",
|
||||
"deliverable": "8 个自有 JS 模块迁移与 Result/shim 类型",
|
||||
"acceptance": "解码器/Stream/DOM 类型清楚,旧 Option 和 art.mediabunny 使用保持",
|
||||
"evidence": [
|
||||
"baselines/mb-entry-source.json",
|
||||
"changes/2026-09-12-PKG-MB-08-entry-checkpoint.md",
|
||||
"baselines/mb-entry-checkpoint.json"
|
||||
"baselines/mb-entry-checkpoint.json",
|
||||
"baselines/mb-capability-source.json",
|
||||
"changes/2026-09-12-PKG-MB-08-capability.md",
|
||||
"baselines/mb-capability-validation.json"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import fs from 'node:fs'
|
||||
import { hash } from '../../refactor/scripts/releases.mjs'
|
||||
import { mbCandidate } from '../helpers/mediabunny.js'
|
||||
import { expect, test } from './fixtures.js'
|
||||
|
||||
const implementation = await mbCandidate()
|
||||
const manifest = JSON.parse(fs.readFileSync(new URL('./media/hls/manifest.json', import.meta.url)))
|
||||
const hls = new Map(Object.entries(manifest.files).map(([name, expected]) => {
|
||||
const bytes = fs.readFileSync(new URL(`./media/hls/${name}`, import.meta.url))
|
||||
assert.equal(hash(bytes), expected.sha256)
|
||||
return [name, bytes]
|
||||
}))
|
||||
|
||||
for (const core of ['published', 'candidate']) {
|
||||
for (const scenario of ['video-unsupported', 'audio-unsupported', 'mixed-video-unsupported', 'mixed-audio-unsupported', 'replacement']) {
|
||||
test(`MediaBunny ${implementation.name}: ${core} core decoder capability ${scenario}`, async ({ page, browserName }, testInfo) => {
|
||||
await page.route('**/mb-cap-hls/**', async (route) => {
|
||||
const name = new URL(route.request().url()).pathname.split('/').at(-1)
|
||||
const body = hls.get(name)
|
||||
await route.fulfill({ status: body ? 200 : 404, body: body || 'Missing fixture', contentType: name.endsWith('.m3u8') ? 'application/vnd.apple.mpegurl' : 'video/mp2t' })
|
||||
})
|
||||
await page.goto(`/test/player.html?core=${core}`)
|
||||
await page.addScriptTag({ content: `(() => { const module = { exports: {} }; const exports = module.exports;
|
||||
${implementation.code}; window.mbFactory = module.exports.default || module.exports; })();` })
|
||||
const capabilities = await page.evaluate(() => ({ AudioContext: typeof AudioContext, webkitAudioContext: typeof window.webkitAudioContext, AudioDecoder: typeof AudioDecoder, VideoDecoder: typeof VideoDecoder }))
|
||||
let outcome = 'unverified'
|
||||
let result
|
||||
try {
|
||||
await page.evaluate((scenario) => {
|
||||
window.mbEvents = []
|
||||
window.mbQueries = { video: 0, audio: 0 }
|
||||
window.mbBlock = scenario === 'replacement' ? [] : [scenario.includes('video-') ? 'video' : 'audio']
|
||||
window.art = new window.Artplayer({
|
||||
container: '.player',
|
||||
url: '',
|
||||
muted: true,
|
||||
setting: true,
|
||||
proxy: window.mbFactory({ m3u8: { quality: { control: true, setting: true }, audio: { control: true, setting: true } } }),
|
||||
})
|
||||
window.mbCanvas = window.art.video
|
||||
const engine = window.mbCanvas.engine
|
||||
for (const kind of ['video', 'audio']) {
|
||||
const load = engine[kind].load.bind(engine[kind])
|
||||
engine[kind].load = (media, ...args) => {
|
||||
const track = media[`${kind}Track`]
|
||||
if (track && window.mbBlock.includes(kind)) {
|
||||
track.canDecode = async () => {
|
||||
window.mbQueries[kind]++
|
||||
return false
|
||||
}
|
||||
}
|
||||
return load(media, ...args)
|
||||
}
|
||||
}
|
||||
for (const name of ['loadedmetadata', 'loadeddata', 'canplay', 'canplaythrough', 'seeked', 'error'])
|
||||
window.mbCanvas.events.addEventListener(name, () => window.mbEvents.push(name))
|
||||
const source = scenario === 'video-unsupported' ? '/test/pattern.mp4' : scenario === 'audio-unsupported' ? '/test/audio-tone.m4a' : '/mb-cap-hls/master.m3u8'
|
||||
window.mbLoading = engine.load(source)
|
||||
document.querySelector('#play').onclick = () => window.art.play()
|
||||
}, scenario)
|
||||
await page.evaluate(() => window.mbLoading)
|
||||
if (capabilities.AudioContext === 'undefined' && capabilities.webkitAudioContext === 'undefined') {
|
||||
expect(browserName).toBe('webkit')
|
||||
expect(await page.evaluate(() => window.mbCanvas.error?.code)).toBe(4)
|
||||
expect(await page.evaluate(() => window.art.isReady)).toBe(false)
|
||||
outcome = 'unsupported-capability-control'
|
||||
return
|
||||
}
|
||||
if (scenario === 'replacement') {
|
||||
expect(await page.evaluate(() => window.art.isReady)).toBe(true)
|
||||
await expect(page.locator('.art-control-mediabunny-quality')).toHaveCount(1)
|
||||
const error = await page.evaluate(async () => {
|
||||
window.mbEvents.length = 0
|
||||
window.mbBlock = ['video', 'audio']
|
||||
try {
|
||||
await window.mbCanvas.switchM3u8Quality('auto')
|
||||
return null
|
||||
}
|
||||
catch (error) { return error.message }
|
||||
})
|
||||
expect(error).toBe('Input has no decodable audio or video tracks.')
|
||||
await expect(page.locator('.art-control-mediabunny-quality')).toHaveCount(0)
|
||||
await expect(page.locator('.art-control-mediabunny-audio')).toHaveCount(0)
|
||||
}
|
||||
if (scenario.startsWith('mixed-')) {
|
||||
expect(await page.evaluate(() => window.art.isReady)).toBe(true)
|
||||
await page.click('#play')
|
||||
await expect.poll(() => page.evaluate(() => window.mbCanvas.currentTime)).toBeGreaterThan(0.2)
|
||||
result = await page.evaluate(() => ({ video: Boolean(window.mbCanvas.engine.video.videoSink), audio: Boolean(window.mbCanvas.engine.audio.audioSink), nodes: window.mbCanvas.engine.audio.queuedNodes.size, width: window.mbCanvas.videoWidth, events: window.mbEvents, error: window.mbCanvas.error, queries: window.mbQueries }))
|
||||
expect(result.error).toBe(null)
|
||||
expect(result.events).toEqual(['loadedmetadata', 'loadeddata', 'canplay', 'canplaythrough'])
|
||||
expect(result.video).toBe(scenario === 'mixed-audio-unsupported')
|
||||
expect(result.audio).toBe(scenario === 'mixed-video-unsupported')
|
||||
if (result.audio)
|
||||
expect(result.nodes).toBeGreaterThan(0)
|
||||
if (result.video)
|
||||
expect(result.width).toBeGreaterThan(0)
|
||||
outcome = 'native-partial-decoder-playback-with-controlled-unsupported-track'
|
||||
}
|
||||
else {
|
||||
result = await page.evaluate(() => ({ readyState: window.mbCanvas.readyState, networkState: window.mbCanvas.networkState, error: window.mbCanvas.error, events: window.mbEvents, queries: window.mbQueries }))
|
||||
expect(result.readyState).toBe(0)
|
||||
expect(result.networkState).toBe(3)
|
||||
expect(result.error).toEqual({ code: 4, message: 'Input has no decodable audio or video tracks.' })
|
||||
expect(result.events).toEqual(['error'])
|
||||
outcome = 'controlled-unsupported-track-rejected-before-readiness'
|
||||
}
|
||||
const kind = scenario.includes('video-') ? 'video' : 'audio'
|
||||
expect(result.queries).toEqual(scenario === 'replacement' ? { video: 1, audio: 1 } : { video: kind === 'video' ? 1 : 0, audio: kind === 'audio' ? 1 : 0 })
|
||||
}
|
||||
finally {
|
||||
await page.evaluate(() => window.art?.destroy(false))
|
||||
await testInfo.attach('mediabunny-capability', { contentType: 'application/json', body: JSON.stringify({ implementation: implementation.name, sha256: hash(implementation.code), core, scenario, capabilities, outcome, result, scope: 'Actual SDK parsing, Canvas, Web Audio and two cores. canDecode=false is deliberately controlled for selected tracks; partial cases play the remaining native decoder. Windows WebKit missing APIs is a separate control, not playback.' }) })
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import fs from 'node:fs'
|
||||
// eslint-disable-next-line test/no-import-node-test -- Actual SDK tracks and decoder/coordinator code with controlled capability responses.
|
||||
import test from 'node:test'
|
||||
import { mbCandidate, mbEnvironment } from './helpers/mediabunny.js'
|
||||
|
||||
const implementation = await mbCandidate()
|
||||
const sources = {
|
||||
video: new Blob([fs.readFileSync(new URL('./browser/media/pattern.mp4', import.meta.url))]),
|
||||
audio: new Blob([fs.readFileSync(new URL('./browser/media/audio-tone.m4a', import.meta.url))]),
|
||||
}
|
||||
const readiness = ['loadedmetadata', 'loadeddata', 'canplay', 'canplaythrough']
|
||||
const flush = () => new Promise(resolve => setImmediate(resolve))
|
||||
const message = 'Input has no decodable audio or video tracks.'
|
||||
function deferred() {
|
||||
let resolve, reject
|
||||
const promise = new Promise((yes, no) => {
|
||||
resolve = yes
|
||||
reject = no
|
||||
})
|
||||
return { promise, resolve, reject }
|
||||
}
|
||||
function environment(kind, response = false) {
|
||||
const env = mbEnvironment(implementation)
|
||||
env.globals.console = { ...console, error() {}, warn() {} }
|
||||
env.globals.window.AudioContext = class {
|
||||
currentTime = 0
|
||||
state = 'running'
|
||||
destination = {}
|
||||
createGain() { return { gain: { value: 0 }, connect() {}, disconnect() {} } }
|
||||
async close() { this.state = 'closed' }
|
||||
}
|
||||
const canvas = env.factory()(env.art)
|
||||
const engine = canvas.engine
|
||||
const events = []
|
||||
const queries = []
|
||||
for (const name of [...readiness, 'error', 'play', 'playing', 'seeked'])
|
||||
canvas.events.addEventListener(name, () => events.push(name))
|
||||
const decoder = engine[kind]
|
||||
const load = decoder.load.bind(decoder)
|
||||
decoder.load = (media, ...args) => {
|
||||
const track = media[`${kind}Track`]
|
||||
assert(track, 'The real input must contain the selected track')
|
||||
if (response === null) {
|
||||
Object.defineProperty(track, 'codec', { value: null, configurable: true })
|
||||
}
|
||||
else {
|
||||
track.canDecode = () => {
|
||||
queries.push(track)
|
||||
return typeof response === 'function' ? response() : Promise.resolve(response)
|
||||
}
|
||||
}
|
||||
return load(media, ...args)
|
||||
}
|
||||
return { ...env, canvas, engine, decoder, events, queries }
|
||||
}
|
||||
|
||||
for (const kind of ['video', 'audio']) {
|
||||
for (const response of [false, null]) {
|
||||
test(`MediaBunny ${kind}-only input with capability ${response} rejects before any readiness`, async () => {
|
||||
const env = environment(kind, response)
|
||||
await env.engine.load(sources[kind])
|
||||
assert.equal(env.canvas.readyState, 0)
|
||||
assert.deepEqual({ ...env.canvas.error }, { code: 4, message })
|
||||
assert.equal(env.canvas.networkState, 3)
|
||||
assert.deepEqual(env.events, ['error'])
|
||||
assert.equal(env.engine.input, null)
|
||||
assert.equal(env.engine.media, null)
|
||||
assert.equal(env.queries.length, response === null ? 0 : 1)
|
||||
assert.equal(env.canvas.canPlayType('video/mp4'), 'maybe')
|
||||
env.art.emit('destroy')
|
||||
})
|
||||
}
|
||||
test(`MediaBunny ${kind} track replacement cannot publish readiness without a decoder`, async () => {
|
||||
const env = environment(kind)
|
||||
let prepared = false
|
||||
const load = env.decoder.load
|
||||
env.decoder.load = (media, ...args) => {
|
||||
if (!prepared) {
|
||||
prepared = true
|
||||
// Prepare a supported decoder without native decoding in Node; keep the real Input alive.
|
||||
env.decoder[`${kind}Sink`] = {}
|
||||
args[0]?.()
|
||||
return Promise.resolve()
|
||||
}
|
||||
return load(media, ...args)
|
||||
}
|
||||
await env.engine.load(sources[kind])
|
||||
assert.equal(env.canvas.readyState, 4)
|
||||
assert.equal(env.canvas.error, null)
|
||||
env.events.length = 0
|
||||
await assert.rejects(env.engine.replaceTracks({}), error => error.message === message)
|
||||
assert.equal(env.canvas.readyState, 0)
|
||||
assert.equal(env.canvas.paused, true)
|
||||
assert.equal(env.canvas.seeking, false)
|
||||
assert.equal(env.canvas.networkState, 3)
|
||||
assert.deepEqual(env.events, ['error'])
|
||||
env.art.emit('destroy')
|
||||
})
|
||||
}
|
||||
|
||||
test('MediaBunny audio-only input preserves one readiness sequence when its decoder is supported', async () => {
|
||||
const env = environment('audio', true)
|
||||
await env.engine.load(sources.audio)
|
||||
assert.equal(env.canvas.readyState, 4)
|
||||
assert.equal(env.canvas.error, null)
|
||||
assert(env.engine.audio.audioSink)
|
||||
assert.equal(env.engine.video.videoSink, null)
|
||||
assert.deepEqual(env.events, readiness)
|
||||
assert.equal(env.queries.length, 1)
|
||||
env.art.emit('destroy')
|
||||
})
|
||||
|
||||
for (const ending of ['source', 'destroy']) {
|
||||
for (const result of ['false', 'reject']) {
|
||||
test(`MediaBunny late capability ${result} after ${ending} cannot publish readiness or errors`, async () => {
|
||||
const pending = deferred()
|
||||
const env = environment('video', () => pending.promise)
|
||||
const loading = env.engine.load(sources.video)
|
||||
await flush()
|
||||
assert.equal(env.queries.length, 1)
|
||||
if (ending === 'source') {
|
||||
env.engine.performLoad = async () => {}
|
||||
await env.engine.load('replacement.mp4')
|
||||
}
|
||||
else {
|
||||
env.art.emit('destroy')
|
||||
}
|
||||
await loading
|
||||
env.events.length = 0
|
||||
if (result === 'false')
|
||||
pending.resolve(false)
|
||||
else pending.reject(new Error('stale decoder query'))
|
||||
await flush()
|
||||
assert.deepEqual(env.events, [])
|
||||
assert.equal(env.canvas.error, null)
|
||||
env.art.emit('destroy')
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
for (const supported of [false, true]) {
|
||||
test(`MediaBunny pause during capability query preserves load result ${supported} without autoplay`, async () => {
|
||||
const pending = deferred()
|
||||
const env = environment('audio', () => pending.promise)
|
||||
const loading = env.engine.load(sources.audio)
|
||||
await flush()
|
||||
env.engine.pause()
|
||||
pending.resolve(supported)
|
||||
await loading
|
||||
assert.equal(env.canvas.paused, true)
|
||||
assert.deepEqual(env.events, supported ? readiness : ['error'])
|
||||
assert.equal(env.queries.length, 1)
|
||||
env.art.emit('destroy')
|
||||
})
|
||||
}
|
||||
|
||||
for (const kind of ['video', 'audio']) {
|
||||
test(`MediaBunny active ${kind} capability rejection keeps the SDK error instead of inventing unsupported status`, async () => {
|
||||
const env = environment(kind, () => Promise.reject(new Error('Decoder capability query failed')))
|
||||
await env.engine.load(sources[kind])
|
||||
assert.deepEqual({ ...env.canvas.error }, { code: 4, message: 'Decoder capability query failed' })
|
||||
assert.deepEqual(env.events, ['error'])
|
||||
assert.equal(env.canvas.readyState, 0)
|
||||
env.art.emit('destroy')
|
||||
})
|
||||
}
|
||||
|
||||
test('MediaBunny supported source can recover after an unsupported source without keeping its error', async () => {
|
||||
let supported = false
|
||||
const env = environment('audio', () => Promise.resolve(supported))
|
||||
await env.engine.load(sources.audio)
|
||||
assert.equal(env.canvas.error?.message, message)
|
||||
supported = true
|
||||
env.events.length = 0
|
||||
await env.engine.load(sources.audio)
|
||||
assert.equal(env.canvas.error, null)
|
||||
assert.equal(env.canvas.readyState, 4)
|
||||
assert.equal(env.canvas.networkState, 1)
|
||||
assert.deepEqual(env.events, readiness)
|
||||
assert.equal(env.queries.length, 2)
|
||||
env.art.emit('destroy')
|
||||
})
|
||||
@@ -363,6 +363,8 @@ async function loadingEnvironment() {
|
||||
const audio = deferred()
|
||||
const metadata = {}
|
||||
env.engine.video.load = (media, callback) => {
|
||||
// This fixture represents a supported decoder whose metadata/completion are delayed.
|
||||
env.engine.video.videoSink = {}
|
||||
metadata.video = callback
|
||||
return video.promise
|
||||
}
|
||||
@@ -493,6 +495,8 @@ function replacementEnvironment() {
|
||||
const track = { isLive: async () => false }
|
||||
const input = { getDurationFromMetadata: async () => 8 }
|
||||
env.engine.media = { input, videoTrack: track, audioTrack: null, isHls: true, videoMode: 'auto', audioMode: 'auto', duration: 8 }
|
||||
// The already selected media has a working video decoder before a replacement starts.
|
||||
env.engine.video.videoSink = {}
|
||||
env.engine.video.load = async () => env.calls.push('video:load')
|
||||
env.engine.audio.load = async () => env.calls.push('audio:load')
|
||||
return env
|
||||
|
||||
Reference in new issue
Block a user