refactor(mediabunny): [PKG-MB-08] checkpoint typed entry and compatible media declarations

This commit is contained in:
Harvey Zhao committed 2026-09-12 21:31:34 +08:00
1 parent 8a2b56e829
commit 5e28dfee95
39 files changed
+3007 -287

No files matched your search

+1 -1
View File
@@ -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)}}return e.on("video:loadedmetadata",h),e.on("restart",h),e.on("video:loadstart",d),e.on("video:error",d),e.on("destroy",function t(){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("destLine 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
+93 -64
View File
@@ -21,8 +21,38 @@ var __privateWrapper = (obj, member, setter, getter) => ({
}
});
var _closed, _closed2, _generation, _contexts, _nodes, _pump, _playTask, _playing, _loading, _onError, _AudioEngine_instances, load_fn, startPump_fn, play_fn, _generation2, _iteration, _loading2, _createSink, _usedSink, _destroyed, _preparing, _poster, _renderer, _onError2, _VideoEngine_instances, current_fn, context_fn, report_fn, release_fn, draw_fn, load_fn2, reset_fn, _playback;
const $audio = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" height="18"><path fill="#fff" d="M256 80C149.9 80 62.4 159.4 49.6 262c9.4-3.8 19.6-6 30.4-6c26.5 0 48 21.5 48 48l0 128c0 26.5-21.5 48-48 48c-44.2 0-80-35.8-80-80l0-16 0-48 0-48C0 146.6 114.6 32 256 32s256 114.6 256 256l0 48 0 48 0 16c0 44.2-35.8 80-80 80c-26.5 0-48-21.5-48-48l0-128c0-26.5 21.5-48 48-48c10.8 0 21 2.1 30.4 6C449.6 159.4 362.1 80 256 80z"/></svg>';
const $quality = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" height="18"><path fill="#fff" d="M0 96C0 60.7 28.7 32 64 32l384 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 480c-35.3 0-64-28.7-64-64L0 96zM323.8 202.5c-4.5-6.6-11.9-10.5-19.8-10.5s-15.4 3.9-19.8 10.5l-87 127.6L170.7 297c-4.6-5.7-11.5-9-18.7-9s-14.2 3.3-18.7 9l-64 80c-5.8 7.2-6.9 17.1-2.9 25.4s12.4 13.6 21.6 13.6l96 0 32 0 208 0c8.9 0 17.1-4.9 21.2-12.8s3.6-17.4-1.4-24.7l-120-176zM112 192a48 48 0 1 0 0-96 48 48 0 1 0 0 96z"/></svg>';
function bridgeCanvas(canvas, shim) {
const methods = /* @__PURE__ */ new Map();
const native = canvas;
const members = shim;
for (const name in canvas) {
if (typeof native[name] === "function") {
const method = native[name];
methods.set(name, method.bind(canvas));
}
}
const names = /* @__PURE__ */ new Set([
...Object.getOwnPropertyNames(shim),
...Object.getOwnPropertyNames(Object.getPrototypeOf(shim))
]);
for (const name of names) {
if (name === "constructor" || name in canvas)
continue;
Object.defineProperty(canvas, name, {
get() {
const value = members[name];
return typeof value === "function" ? value.bind(shim) : value;
},
set(value) {
members[name] = value;
},
configurable: true,
enumerable: true
});
}
for (const [name, method] of methods)
native[name] = (...args) => method(...args);
}
function releaseAll(actions) {
const failures = [];
for (const action of actions) {
@@ -35,6 +65,8 @@ function releaseAll(actions) {
if (failures.length)
throw failures[0];
}
const $audio = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" height="18"><path fill="#fff" d="M256 80C149.9 80 62.4 159.4 49.6 262c9.4-3.8 19.6-6 30.4-6c26.5 0 48 21.5 48 48l0 128c0 26.5-21.5 48-48 48c-44.2 0-80-35.8-80-80l0-16 0-48 0-48C0 146.6 114.6 32 256 32s256 114.6 256 256l0 48 0 48 0 16c0 44.2-35.8 80-80 80c-26.5 0-48-21.5-48-48l0-128c0-26.5 21.5-48 48-48c10.8 0 21 2.1 30.4 6C449.6 159.4 362.1 80 256 80z"/></svg>';
const $quality = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" height="18"><path fill="#fff" d="M0 96C0 60.7 28.7 32 64 32l384 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 480c-35.3 0-64-28.7-64-64L0 96zM323.8 202.5c-4.5-6.6-11.9-10.5-19.8-10.5s-15.4 3.9-19.8 10.5l-87 127.6L170.7 297c-4.6-5.7-11.5-9-18.7-9s-14.2 3.3-18.7 9l-64 80c-5.8 7.2-6.9 17.1-2.9 25.4s12.4 13.6 21.6 13.6l96 0 32 0 208 0c8.9 0 17.1-4.9 21.2-12.8s3.6-17.4-1.4-24.7l-120-176zM112 192a48 48 0 1 0 0-96 48 48 0 1 0 0 96z"/></svg>';
function createMenu(art, name, icon) {
const owned = { control: false, setting: false };
let model = null;
@@ -218,12 +250,49 @@ function setupM3u8Controls({ art, shim, option }) {
console.warn("MediaBunny HLS listener cleanup:", error);
}
}
art.on("video:loadedmetadata", refresh);
art.on("restart", refresh);
art.on("video:loadstart", invalidate);
art.on("video:error", invalidate);
art.on("destroy", destroy);
return { update };
try {
art.on("video:loadedmetadata", refresh);
art.on("restart", refresh);
art.on("video:loadstart", invalidate);
art.on("video:error", invalidate);
art.on("destroy", destroy);
} catch (error) {
destroy();
throw error;
}
return { update, destroy };
}
function entryLifecycle(art, canvas, shim, option) {
let closed = false;
let controls;
function resize() {
if (closed || !art.template?.$player || art.option.autoSize)
return;
Object.assign(canvas.style, { width: "100%", height: "100%", objectFit: "contain" });
}
function destroy() {
if (closed)
return;
closed = true;
releaseAll([
() => controls?.destroy(),
() => {
if (art.mediabunny === shim)
delete art.mediabunny;
},
() => shim.destroy(),
() => art.off?.("resize", resize),
() => art.off?.("video:loadedmetadata", resize),
() => art.off?.("destroy", destroy)
]);
}
function install() {
art.on("resize", resize);
art.on("video:loadedmetadata", resize);
controls = setupM3u8Controls({ art, shim, option });
art.on("destroy", destroy);
}
return { install, destroy };
}
class EventTarget {
constructor() {
@@ -26143,68 +26212,28 @@ class VideoShim {
}
}
function artplayerProxyMediabunny(option = {}) {
return (art) => {
const { constructor } = art;
const { createElement } = constructor.utils;
return (player) => {
const art = player;
const { createElement } = art.constructor.utils;
const canvas = createElement("canvas");
const ctx = canvas.getContext("2d");
const shim = new VideoShim({
art,
canvas,
ctx,
option
});
art.mediabunny = shim;
const originalCanvasMethods = {};
for (const prop in canvas) {
if (typeof canvas[prop] === "function") {
originalCanvasMethods[prop] = canvas[prop].bind(canvas);
const shim = new VideoShim({ art, canvas, ctx: canvas.getContext("2d"), option });
const lifecycle = entryLifecycle(art, canvas, shim, option);
try {
art.mediabunny = shim;
bridgeCanvas(canvas, shim);
lifecycle.install();
} catch (error) {
try {
lifecycle.destroy();
} catch (failure) {
console.warn("MediaBunny entry setup cleanup:", failure);
}
throw error;
}
const propertyNames = /* @__PURE__ */ new Set([
...Object.getOwnPropertyNames(shim),
...Object.getOwnPropertyNames(Object.getPrototypeOf(shim))
]);
for (const prop of propertyNames) {
if (prop === "constructor")
continue;
if (!(prop in canvas)) {
Object.defineProperty(canvas, prop, {
get() {
const value = shim[prop];
return typeof value === "function" ? value.bind(shim) : value;
},
set(v) {
shim[prop] = v;
},
configurable: true,
enumerable: true
});
}
}
for (const prop in originalCanvasMethods) {
canvas[prop] = (...args) => originalCanvasMethods[prop](...args);
}
function resize() {
const player = art.template?.$player;
if (!player || art.option.autoSize)
return;
Object.assign(canvas.style, {
width: "100%",
height: "100%",
objectFit: "contain"
});
}
art.on("resize", resize);
art.on("video:loadedmetadata", resize);
setupM3u8Controls({ art, shim, option });
art.on("destroy", () => {
delete art.mediabunny;
shim.destroy();
});
return canvas;
};
}
Object.defineProperty(artplayerProxyMediabunny, "default", { value: artplayerProxyMediabunny, writable: true, configurable: true });
export {
artplayerProxyMediabunny as default
};
+3 -2
View File
@@ -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-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-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,8 @@
"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-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-load.test.js test/mediabunny-input.test.js",
"test:mediabunny-types-package": "node refactor/scripts/mb-package-types.mjs"
},
"browserslist": "last 1 Chrome version",
"devDependencies": {
@@ -2,4 +2,5 @@ node_modules
.DS_Store
yarn.lock
package-lock.json
src
src
tsconfig.json
@@ -1,14 +1,19 @@
# MediaBunny proxy maintenance
The public entry is `src/index.js`: an optional-options factory returns the existing
The public entry is `src/index.ts`: an optional-options factory returns the existing
ArtPlayer proxy callback and an HTMLCanvasElement. Public declarations remain in
`types/artplayer-proxy-mediabunny.d.ts`. The input migration does not change those
declarations, the factory signature, canvas forwarding, or package entrypoints.
`types/artplayer-proxy-mediabunny.d.ts`, with a separate ESM declaration entry.
The exact optional factory and HTMLCanvasElement result remain unchanged. The media
view in `types/media.d.ts` is opt-in and checked against the real VideoShim class.
## Current module map
| Module | Responsibility |
| --- | --- |
| `index.ts` | Optional factory, Canvas construction, alias installation and setup rollback |
| `canvas-bridge.ts` | Historical own/direct-prototype forwarding and native Canvas receiver binding |
| `entry-lifecycle.ts` | Entry resize/metadata/destroy subscriptions, alias ownership and shim cleanup |
| `cleanup.ts` | Attempt every release and preserve the first failure |
| `input.ts` | String/Blob/ReadableStream normalization, SDK Source/SourceRef pass-through, HLS detection and Input construction |
| `media.ts` | Internal SDK-backed media and track-mode types; not public consumer types |
| `tracks.ts` | Primary video and pairable audio selection, metadata/computed/live duration |
@@ -38,11 +43,20 @@ declarations, the factory signature, canvas forwarding, or package entrypoints.
| `m3u8-menu.ts` | Owned control/setting surfaces and stale callback guards |
| `m3u8-types.ts` | Narrow internal UI/shim contracts without changing public factory declarations |
The thirty production TypeScript modules use strict checking with `skipLibCheck: false`
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.
Only the public factory remains JavaScript, scheduled in PKG-MB-08.
Do not describe the whole package as migrated yet.
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.
The entry preserves native Canvas method priority, detached createElement invocation,
two reads of enumerable native method accessors, direct-prototype forwarding, bound shim
methods and strict assignment errors for getter-only properties. Avoid replacing these
with generic Reflect forwarding or a broad HTMLVideoElement cast. Entry bookkeeping stays
off the canvas surface. Destroy unregisters its callbacks and only removes art.mediabunny
when the alias still belongs to that entry. A custom historical host without off keeps inert
callbacks; a real Artplayer host removes them. Partial setup attempts all cleanup while
preserving the original setup error. HLS cleanup cannot strand the remaining entry resources.
The shim keeps its existing own-property order and direct prototype surface because
the entry copies those descriptors onto the canvas. Resource bookkeeping lives in a
@@ -228,13 +242,39 @@ resolution selects `@types/dom-webcodecs@0.1.19` for both dependency paths; the
itself is unchanged. This is a build-time declaration correction, not a browser polyfill.
The package typecheck adds ESNext.Disposable for SDK declarations while keeping the
ES2020 target; production source does not use `using` or require Symbol.dispose.
Public consumer declarations do not import these SDK types; preserve that boundary
until the separate consumer-compatibility task verifies any proposed change.
Public declarations do not import SDK types or modern DOM VideoFrame callback names.
The optional media view names the synthetic frame metadata directly; SDK track objects
are unknown until consumers narrow them using their own SDK version. `VideoShim implements
MediaBunnyShim` checks that this view matches implementation rather than masking JS with
an adjacent declaration. Explicit getter return types retain coercive unknown setters
internally while giving consumers numeric/boolean reads.
Default Result remains exactly HTMLCanvasElement, allowing callers to substitute a plain
canvas initializer or an entire historical factory. MediaBunnyCanvas combines the opt-in
media view with native Canvas methods winning collisions. MediaBunnyPlayer has an optional
mediabunny alias because it is absent before initialization and after destruction; there
is no global augmentation of Artplayer. Named exports are types only.
The .d.mts wrapper fixes NodeNext ESM default resolution. CommonJS keeps the historical
default declaration facade, and typesVersions resolves /legacy in TS 4.3/classic Node
resolution. Runtime remains directly callable and adds a nonenumerable, writable default
self alias for the 1.0.0 CommonJS usage. That property is deliberately not mandatory on
the default factory type. Whole-factory assignments are tested bidirectionally against
both frozen published declarations, in an actual isolated install as well as workspace
consumers. Do not switch to export= or a required default member without those checks.
`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.
## Validation and remaining work
```sh
yarn test:mediabunny
yarn test:mediabunny-types-package
node node_modules/typescript/bin/tsc -p packages/artplayer-proxy-mediabunny/tsconfig.json
yarn build artplayer-proxy-mediabunny
yarn test:browser test/browser/mediabunny-inputs.spec.js test/browser/mediabunny.spec.js test/browser/mediabunny-load.spec.js
@@ -242,6 +282,7 @@ yarn test:browser test/browser/mediabunny-shim.spec.js
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
```
Node tests use actual SDK parsing and controlled lifecycle interleavings. Browser tests
@@ -10,6 +10,46 @@ mediabunny proxy for ArtPlayer, including `m3u8` playback through Mediabunny's H
MIT © Harvey Zhao
## TypeScript
The default factory still accepts optional options and returns an initializer whose
result is exactly `HTMLCanvasElement`. Existing factory replacements remain assignable.
Use the optional media view when accessing the proxy's media methods:
```ts
import type { MediaBunnyPlayer } from 'artplayer-proxy-mediabunny'
import Artplayer from 'artplayer'
import mediabunny from 'artplayer-proxy-mediabunny'
const art: MediaBunnyPlayer = new Artplayer({
container: '#player',
url: 'movie.mp4',
proxy: mediabunny({ volume: 0.7 }),
})
art.on('ready', async () => {
const shim = art.mediabunny
if (!shim)
return
const state = await shim.getM3u8State()
console.log(state?.currentLevel?.height)
})
```
`MediaBunnyShim`, `MediaBunnyCanvas`, HLS track/state and synthetic frame callback types
are also available as type-only exports. The player alias is optional and removed on
destruction. Native Canvas event/attribute methods take precedence over shim methods.
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.
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`
runtime path. The default factory type keeps its historical callable signature.
## Maintenance
See [ARCHITECTURE.md](ARCHITECTURE.md) for current modules, resource ownership, compatibility boundaries and validation commands.
@@ -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)}}return e.on("video:loadedmetadata",h),e.on("restart",h),e.on("video:loadstart",d),e.on("video:error",d),e.on("destroy",function t(){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("destLine 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
@@ -21,8 +21,38 @@ var __privateWrapper = (obj, member, setter, getter) => ({
}
});
var _closed, _closed2, _generation, _contexts, _nodes, _pump, _playTask, _playing, _loading, _onError, _AudioEngine_instances, load_fn, startPump_fn, play_fn, _generation2, _iteration, _loading2, _createSink, _usedSink, _destroyed, _preparing, _poster, _renderer, _onError2, _VideoEngine_instances, current_fn, context_fn, report_fn, release_fn, draw_fn, load_fn2, reset_fn, _playback;
const $audio = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" height="18"><path fill="#fff" d="M256 80C149.9 80 62.4 159.4 49.6 262c9.4-3.8 19.6-6 30.4-6c26.5 0 48 21.5 48 48l0 128c0 26.5-21.5 48-48 48c-44.2 0-80-35.8-80-80l0-16 0-48 0-48C0 146.6 114.6 32 256 32s256 114.6 256 256l0 48 0 48 0 16c0 44.2-35.8 80-80 80c-26.5 0-48-21.5-48-48l0-128c0-26.5 21.5-48 48-48c10.8 0 21 2.1 30.4 6C449.6 159.4 362.1 80 256 80z"/></svg>';
const $quality = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" height="18"><path fill="#fff" d="M0 96C0 60.7 28.7 32 64 32l384 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 480c-35.3 0-64-28.7-64-64L0 96zM323.8 202.5c-4.5-6.6-11.9-10.5-19.8-10.5s-15.4 3.9-19.8 10.5l-87 127.6L170.7 297c-4.6-5.7-11.5-9-18.7-9s-14.2 3.3-18.7 9l-64 80c-5.8 7.2-6.9 17.1-2.9 25.4s12.4 13.6 21.6 13.6l96 0 32 0 208 0c8.9 0 17.1-4.9 21.2-12.8s3.6-17.4-1.4-24.7l-120-176zM112 192a48 48 0 1 0 0-96 48 48 0 1 0 0 96z"/></svg>';
function bridgeCanvas(canvas, shim) {
const methods = /* @__PURE__ */ new Map();
const native = canvas;
const members = shim;
for (const name in canvas) {
if (typeof native[name] === "function") {
const method = native[name];
methods.set(name, method.bind(canvas));
}
}
const names = /* @__PURE__ */ new Set([
...Object.getOwnPropertyNames(shim),
...Object.getOwnPropertyNames(Object.getPrototypeOf(shim))
]);
for (const name of names) {
if (name === "constructor" || name in canvas)
continue;
Object.defineProperty(canvas, name, {
get() {
const value = members[name];
return typeof value === "function" ? value.bind(shim) : value;
},
set(value) {
members[name] = value;
},
configurable: true,
enumerable: true
});
}
for (const [name, method] of methods)
native[name] = (...args) => method(...args);
}
function releaseAll(actions) {
const failures = [];
for (const action of actions) {
@@ -35,6 +65,8 @@ function releaseAll(actions) {
if (failures.length)
throw failures[0];
}
const $audio = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" height="18"><path fill="#fff" d="M256 80C149.9 80 62.4 159.4 49.6 262c9.4-3.8 19.6-6 30.4-6c26.5 0 48 21.5 48 48l0 128c0 26.5-21.5 48-48 48c-44.2 0-80-35.8-80-80l0-16 0-48 0-48C0 146.6 114.6 32 256 32s256 114.6 256 256l0 48 0 48 0 16c0 44.2-35.8 80-80 80c-26.5 0-48-21.5-48-48l0-128c0-26.5 21.5-48 48-48c10.8 0 21 2.1 30.4 6C449.6 159.4 362.1 80 256 80z"/></svg>';
const $quality = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" height="18"><path fill="#fff" d="M0 96C0 60.7 28.7 32 64 32l384 0c35.3 0 64 28.7 64 64l0 320c0 35.3-28.7 64-64 64L64 480c-35.3 0-64-28.7-64-64L0 96zM323.8 202.5c-4.5-6.6-11.9-10.5-19.8-10.5s-15.4 3.9-19.8 10.5l-87 127.6L170.7 297c-4.6-5.7-11.5-9-18.7-9s-14.2 3.3-18.7 9l-64 80c-5.8 7.2-6.9 17.1-2.9 25.4s12.4 13.6 21.6 13.6l96 0 32 0 208 0c8.9 0 17.1-4.9 21.2-12.8s3.6-17.4-1.4-24.7l-120-176zM112 192a48 48 0 1 0 0-96 48 48 0 1 0 0 96z"/></svg>';
function createMenu(art, name, icon) {
const owned = { control: false, setting: false };
let model = null;
@@ -218,12 +250,49 @@ function setupM3u8Controls({ art, shim, option }) {
console.warn("MediaBunny HLS listener cleanup:", error);
}
}
art.on("video:loadedmetadata", refresh);
art.on("restart", refresh);
art.on("video:loadstart", invalidate);
art.on("video:error", invalidate);
art.on("destroy", destroy);
return { update };
try {
art.on("video:loadedmetadata", refresh);
art.on("restart", refresh);
art.on("video:loadstart", invalidate);
art.on("video:error", invalidate);
art.on("destroy", destroy);
} catch (error) {
destroy();
throw error;
}
return { update, destroy };
}
function entryLifecycle(art, canvas, shim, option) {
let closed = false;
let controls;
function resize() {
if (closed || !art.template?.$player || art.option.autoSize)
return;
Object.assign(canvas.style, { width: "100%", height: "100%", objectFit: "contain" });
}
function destroy() {
if (closed)
return;
closed = true;
releaseAll([
() => controls?.destroy(),
() => {
if (art.mediabunny === shim)
delete art.mediabunny;
},
() => shim.destroy(),
() => art.off?.("resize", resize),
() => art.off?.("video:loadedmetadata", resize),
() => art.off?.("destroy", destroy)
]);
}
function install() {
art.on("resize", resize);
art.on("video:loadedmetadata", resize);
controls = setupM3u8Controls({ art, shim, option });
art.on("destroy", destroy);
}
return { install, destroy };
}
class EventTarget {
constructor() {
@@ -26143,68 +26212,28 @@ class VideoShim {
}
}
function artplayerProxyMediabunny(option = {}) {
return (art) => {
const { constructor } = art;
const { createElement } = constructor.utils;
return (player) => {
const art = player;
const { createElement } = art.constructor.utils;
const canvas = createElement("canvas");
const ctx = canvas.getContext("2d");
const shim = new VideoShim({
art,
canvas,
ctx,
option
});
art.mediabunny = shim;
const originalCanvasMethods = {};
for (const prop in canvas) {
if (typeof canvas[prop] === "function") {
originalCanvasMethods[prop] = canvas[prop].bind(canvas);
const shim = new VideoShim({ art, canvas, ctx: canvas.getContext("2d"), option });
const lifecycle = entryLifecycle(art, canvas, shim, option);
try {
art.mediabunny = shim;
bridgeCanvas(canvas, shim);
lifecycle.install();
} catch (error) {
try {
lifecycle.destroy();
} catch (failure) {
console.warn("MediaBunny entry setup cleanup:", failure);
}
throw error;
}
const propertyNames = /* @__PURE__ */ new Set([
...Object.getOwnPropertyNames(shim),
...Object.getOwnPropertyNames(Object.getPrototypeOf(shim))
]);
for (const prop of propertyNames) {
if (prop === "constructor")
continue;
if (!(prop in canvas)) {
Object.defineProperty(canvas, prop, {
get() {
const value = shim[prop];
return typeof value === "function" ? value.bind(shim) : value;
},
set(v) {
shim[prop] = v;
},
configurable: true,
enumerable: true
});
}
}
for (const prop in originalCanvasMethods) {
canvas[prop] = (...args) => originalCanvasMethods[prop](...args);
}
function resize() {
const player = art.template?.$player;
if (!player || art.option.autoSize)
return;
Object.assign(canvas.style, {
width: "100%",
height: "100%",
objectFit: "contain"
});
}
art.on("resize", resize);
art.on("video:loadedmetadata", resize);
setupM3u8Controls({ art, shim, option });
art.on("destroy", () => {
delete art.mediabunny;
shim.destroy();
});
return canvas;
};
}
Object.defineProperty(artplayerProxyMediabunny, "default", { value: artplayerProxyMediabunny, writable: true, configurable: true });
export {
artplayerProxyMediabunny as default
};
@@ -19,14 +19,24 @@
],
"exports": {
".": {
"types": "./types/artplayer-proxy-mediabunny.d.ts",
"import": "./dist/artplayer-proxy-mediabunny.mjs",
"require": "./dist/artplayer-proxy-mediabunny.js"
"import": {
"types": "./types/artplayer-proxy-mediabunny.d.mts",
"default": "./dist/artplayer-proxy-mediabunny.mjs"
},
"require": {
"types": "./types/artplayer-proxy-mediabunny.d.ts",
"default": "./dist/artplayer-proxy-mediabunny.js"
}
},
"./legacy": {
"types": "./types/artplayer-proxy-mediabunny.d.ts",
"import": "./dist/artplayer-proxy-mediabunny.legacy.js",
"require": "./dist/artplayer-proxy-mediabunny.legacy.js"
"import": {
"types": "./types/artplayer-proxy-mediabunny.d.mts",
"default": "./dist/artplayer-proxy-mediabunny.legacy.js"
},
"require": {
"types": "./types/artplayer-proxy-mediabunny.d.ts",
"default": "./dist/artplayer-proxy-mediabunny.legacy.js"
}
}
},
"main": "./dist/artplayer-proxy-mediabunny.js",
@@ -34,6 +44,11 @@
"types": "./types/artplayer-proxy-mediabunny.d.ts",
"legacy": "./dist/artplayer-proxy-mediabunny.legacy.js",
"browserslist": "last 1 Chrome version",
"typesVersions": {
"*": {
"legacy": ["types/artplayer-proxy-mediabunny.d.ts"]
}
},
"dependencies": {
"mediabunny": "^1.43.1"
}
@@ -1,3 +1,4 @@
import type { MediaBunnyShim, SyntheticFrameCallback } from '../types/artplayer-proxy-mediabunny'
import type { EngineOptions, EnginePort, ProxyOptions, ShimHost } from './engine-types'
import type { MediaListener } from './EventTarget'
import EventTarget from './EventTarget'
@@ -5,7 +6,7 @@ import MediaBunnyEngine from './MediaBunnyEngine.js'
import { cancelFrame, closeFrames, requestFrame } from './shim-frames'
import { clampVolume, timeRanges } from './shim-values'
export default class VideoShim {
export default class VideoShim implements MediaBunnyShim {
declare art: ShimHost
declare canvas: HTMLCanvasElement
declare option: ProxyOptions
@@ -82,7 +83,7 @@ export default class VideoShim {
}
// Time
get currentTime() {
get currentTime(): number {
return this.engine.currentTime
}
@@ -143,7 +144,7 @@ export default class VideoShim {
}
// Playback rate
get playbackRate() {
get playbackRate(): number {
return this._playbackRate
}
@@ -158,7 +159,7 @@ export default class VideoShim {
}
// Volume
get volume() {
get volume(): number {
return this._volume
}
@@ -169,7 +170,7 @@ export default class VideoShim {
this.events.emit('volumechange')
}
get muted() {
get muted(): boolean {
return this._muted
}
@@ -223,56 +224,56 @@ export default class VideoShim {
this.option.poster = v
}
get autoplay() {
get autoplay(): boolean {
return this.option.autoplay || false
}
set autoplay(v: unknown) {}
get loop() {
get loop(): boolean {
return this.option.loop || false
}
set loop(v: unknown) {}
get controls() {
get controls(): boolean {
return false
}
set controls(v: unknown) {}
get playsInline() {
get playsInline(): boolean {
return true
}
set playsInline(v: unknown) {}
get crossOrigin() {
get crossOrigin(): string {
return this.option.crossOrigin || ''
}
set crossOrigin(v: unknown) {}
get preload() {
get preload(): string {
return 'auto'
}
set preload(v: unknown) {}
get defaultMuted() {
get defaultMuted(): boolean {
return false
}
set defaultMuted(v: unknown) {}
get defaultPlaybackRate() {
get defaultPlaybackRate(): number {
return 1
}
set defaultPlaybackRate(v: unknown) {}
// Methods
canPlayType(_type: string) {
canPlayType(_type: string): 'maybe' {
return 'maybe'
}
@@ -280,7 +281,7 @@ export default class VideoShim {
return this.canvas.getBoundingClientRect()
}
requestVideoFrameCallback(callback: VideoFrameRequestCallback): number {
requestVideoFrameCallback(callback: SyntheticFrameCallback): number {
return requestFrame(this, callback)
}
@@ -0,0 +1,36 @@
import type VideoShim from './VideoShim'
export function bridgeCanvas(canvas: HTMLCanvasElement, shim: VideoShim): void {
const methods = new Map<string, (...args: unknown[]) => unknown>()
// Dynamic forwarding is intentional: own and direct-prototype members are historical API.
const native = canvas as unknown as Record<string, unknown>
const members = shim as unknown as Record<string, unknown>
for (const name in canvas) {
if (typeof native[name] === 'function') {
// Preserve the historical second read, including accessor side effects and binding errors.
const method = native[name] as (...args: unknown[]) => unknown
methods.set(name, method.bind(canvas))
}
}
const names = new Set([
...Object.getOwnPropertyNames(shim),
...Object.getOwnPropertyNames(Object.getPrototypeOf(shim)),
])
for (const name of names) {
if (name === 'constructor' || name in canvas)
continue
Object.defineProperty(canvas, name, {
get() {
const value = members[name]
return typeof value === 'function' ? value.bind(shim) : value
},
set(value: unknown) {
members[name] = value
},
configurable: true,
enumerable: true,
})
}
for (const [name, method] of methods)
native[name] = (...args: unknown[]) => method(...args)
}
@@ -0,0 +1,11 @@
export function releaseAll(actions: (() => void)[]): void {
const failures: unknown[] = []
for (const action of actions) {
try {
action()
}
catch (error) { failures.push(error) }
}
if (failures.length)
throw failures[0]
}
@@ -0,0 +1,45 @@
import type { ProxyOptions, ShimHost } from './engine-types'
import type { MenuHost } from './m3u8-types'
import type VideoShim from './VideoShim'
import { releaseAll } from './cleanup'
import { setupM3u8Controls } from './m3u8'
export interface EntryHost extends ShimHost, MenuHost {
constructor: ShimHost['constructor'] & { utils: { createElement: (tag: 'canvas') => HTMLCanvasElement } }
option: ShimHost['option'] & { autoSize?: boolean }
template?: { $player?: HTMLElement }
mediabunny?: unknown
}
export function entryLifecycle(art: EntryHost, canvas: HTMLCanvasElement, shim: VideoShim, option: ProxyOptions) {
let closed = false
let controls: ReturnType<typeof setupM3u8Controls>
function resize(): void {
if (closed || !art.template?.$player || art.option.autoSize)
return
Object.assign(canvas.style, { width: '100%', height: '100%', objectFit: 'contain' })
}
function destroy(): void {
if (closed)
return
closed = true
releaseAll([
() => controls?.destroy(),
() => {
if (art.mediabunny === shim)
delete art.mediabunny
},
() => shim.destroy(),
() => art.off?.('resize', resize),
() => art.off?.('video:loadedmetadata', resize),
() => art.off?.('destroy', destroy),
])
}
function install(): void {
art.on('resize', resize)
art.on('video:loadedmetadata', resize)
controls = setupM3u8Controls({ art, shim, option })
art.on('destroy', destroy)
}
return { install, destroy }
}
@@ -1,89 +0,0 @@
import { setupM3u8Controls } from './m3u8.js'
/**
* ArtPlayer MediaBunny Proxy
* Main entry point
*/
import VideoShim from './VideoShim.js'
export default function artplayerProxyMediabunny(option = {}) {
return (art) => {
const { constructor } = art
const { createElement } = constructor.utils
// Create canvas element
const canvas = createElement('canvas')
const ctx = canvas.getContext('2d')
// Create video shim
const shim = new VideoShim({
art,
canvas,
ctx,
option,
})
art.mediabunny = shim
// Proxy canvas methods to shim
const originalCanvasMethods = {}
for (const prop in canvas) {
if (typeof canvas[prop] === 'function') {
originalCanvasMethods[prop] = canvas[prop].bind(canvas)
}
}
// Get all properties from shim instance and prototype
const propertyNames = new Set([
...Object.getOwnPropertyNames(shim),
...Object.getOwnPropertyNames(Object.getPrototypeOf(shim)),
])
// Add shim properties to canvas
for (const prop of propertyNames) {
if (prop === 'constructor')
continue
if (!(prop in canvas)) {
Object.defineProperty(canvas, prop, {
get() {
const value = shim[prop]
return typeof value === 'function' ? value.bind(shim) : value
},
set(v) {
shim[prop] = v
},
configurable: true,
enumerable: true,
})
}
}
// Restore original canvas methods
for (const prop in originalCanvasMethods) {
canvas[prop] = (...args) => originalCanvasMethods[prop](...args)
}
// Handle resize
function resize() {
const player = art.template?.$player
if (!player || art.option.autoSize)
return
Object.assign(canvas.style, {
width: '100%',
height: '100%',
objectFit: 'contain',
})
}
art.on('resize', resize)
art.on('video:loadedmetadata', resize)
setupM3u8Controls({ art, shim, option })
// Cleanup on destroy
art.on('destroy', () => {
delete art.mediabunny
shim.destroy()
})
return canvas
}
}
@@ -0,0 +1,33 @@
import type Artplayer from 'artplayer'
import type { ProxyOptions } from './engine-types'
import type { EntryHost } from './entry-lifecycle'
import { bridgeCanvas } from './canvas-bridge'
import { entryLifecycle } from './entry-lifecycle'
import VideoShim from './VideoShim'
export default function artplayerProxyMediabunny(option: ProxyOptions = {}) {
return (player: Artplayer): HTMLCanvasElement => {
// Runtime constructor utilities and the owned alias exceed the historical consumer facade.
const art = player as unknown as EntryHost
const { createElement } = art.constructor.utils
const canvas = createElement('canvas')
const shim = new VideoShim({ art, canvas, ctx: canvas.getContext('2d'), option })
const lifecycle = entryLifecycle(art, canvas, shim, option)
try {
art.mediabunny = shim
bridgeCanvas(canvas, shim)
lifecycle.install()
}
catch (error) {
try {
lifecycle.destroy()
}
catch (failure) { console.warn('MediaBunny entry setup cleanup:', failure) }
throw error
}
return canvas
}
}
// Restore the 1.0.0 CommonJS namespace path without changing the callable factory type.
Object.defineProperty(artplayerProxyMediabunny, 'default', { value: artplayerProxyMediabunny, writable: true, configurable: true })
@@ -1,16 +1,5 @@
import type { DisplayConfig, Label, MenuHost, MenuModel, SelectorItem } from './m3u8-types'
export function releaseAll(actions: (() => void)[]): void {
const failures: unknown[] = []
for (const action of actions) {
try {
action()
}
catch (error) { failures.push(error) }
}
if (failures.length)
throw failures[0]
}
import { releaseAll } from './cleanup'
export default function createMenu(art: MenuHost, name: string, icon: string) {
const owned = { control: false, setting: false }
@@ -1,7 +1,8 @@
import type { Label, SelectorItem, SetupOptions } from './m3u8-types'
import $audio from '../../artplayer-plugin-hls-control/src/audio.svg?raw'
import $quality from '../../artplayer-plugin-hls-control/src/quality.svg?raw'
import createMenu, { releaseAll } from './m3u8-menu'
import { releaseAll } from './cleanup'
import createMenu from './m3u8-menu'
import { audioModel, qualityModel } from './m3u8-model'
export function setupM3u8Controls({ art, shim, option }: SetupOptions) {
@@ -95,10 +96,16 @@ export function setupM3u8Controls({ art, shim, option }: SetupOptions) {
}
catch (error) { console.warn('MediaBunny HLS listener cleanup:', error) }
}
art.on('video:loadedmetadata', refresh)
art.on('restart', refresh)
art.on('video:loadstart', invalidate)
art.on('video:error', invalidate)
art.on('destroy', destroy)
return { update }
try {
art.on('video:loadedmetadata', refresh)
art.on('restart', refresh)
art.on('video:loadstart', invalidate)
art.on('video:error', invalidate)
art.on('destroy', destroy)
}
catch (error) {
destroy()
throw error
}
return { update, destroy }
}
@@ -1,3 +1,4 @@
import type { SyntheticFrameCallback } from '../types/artplayer-proxy-mediabunny'
import type { EnginePort } from './engine-types'
interface FrameOwner {
@@ -20,7 +21,7 @@ function state(owner: FrameOwner): FrameState {
return value
}
export function requestFrame(owner: FrameOwner, callback: VideoFrameRequestCallback): number {
export function requestFrame(owner: FrameOwner, callback: SyntheticFrameCallback): number {
const resource = state(owner)
if (resource.closed)
return 0
@@ -0,0 +1,6 @@
import type Artplayer from 'artplayer'
import type { Option, Result } from './artplayer-proxy-mediabunny.js'
declare const artplayerProxyMediabunny: (option?: Option) => (art: Artplayer) => Result
export default artplayerProxyMediabunny
export type { HlsAudio, HlsLevel, HlsState, MediaBunnyCanvas, MediaBunnyPlayer, MediaBunnyShim, MediaListener, Option, Result, SyntheticFrameCallback, SyntheticFrameMetadata } from './artplayer-proxy-mediabunny.js'
@@ -1,6 +1,6 @@
import type Artplayer from 'artplayer'
interface Option {
export interface Option {
m3u8?: {
quality?: {
control?: boolean
@@ -101,7 +101,9 @@ interface Option {
crossOrigin?: string
}
type Result = HTMLCanvasElement
export type Result = HTMLCanvasElement
export type { HlsAudio, HlsLevel, HlsState, MediaBunnyCanvas, MediaBunnyPlayer, MediaBunnyShim, MediaListener, SyntheticFrameCallback, SyntheticFrameMetadata } from './media'
declare const artplayerProxyMediabunny: (option?: Option) => (art: Artplayer) => Result
+107
View File
@@ -0,0 +1,107 @@
import type Artplayer from 'artplayer'
export interface HlsLevel {
id: number
index: number
name: string | null
height: number
bitrate: number
/** SDK object; narrow with the SDK version used by your application. */
track: unknown
}
export interface HlsAudio {
id: number
index: number
name: string | null
lang: string
language: string
bitrate: number
/** SDK object; narrow with the SDK version used by your application. */
track: unknown
}
export interface HlsState {
levels: HlsLevel[]
audios: HlsAudio[]
currentLevel: HlsLevel | null
currentAudio: HlsAudio | null
videoMode: 'auto' | 'manual'
audioMode: 'auto' | 'manual'
}
/** Historical RAF estimates, not decoded-frame or network timing measurements. */
export interface SyntheticFrameMetadata {
/** Historical media time in seconds, unlike the native video API. */
presentationTime: number
expectedDisplayTime: number
width: number
height: number
mediaTime: number
presentedFrames: number
processingDuration: number
captureTime: number
receiveTime: number
rtpTimestamp: number
}
export type SyntheticFrameCallback = (now: number, metadata: SyntheticFrameMetadata) => void
export type MediaListener = (event: Event & { detail: unknown }) => unknown
/** Opt-in media surface of art.mediabunny; decoder internals are not part of this view. */
export interface MediaBunnyShim {
canvas: HTMLCanvasElement
/** Runtime also accepts SDK sources; the default Option keeps its historical input union. */
src: unknown
readonly currentSrc: unknown
currentTime: number
readonly duration: number
/** Synthetic full-duration/current-time ranges, not measured network buffers. */
readonly buffered: TimeRanges
readonly played: TimeRanges
readonly seekable: TimeRanges
readonly paused: boolean
readonly playing: boolean
readonly ended: boolean
readonly seeking: boolean
readonly readyState: number
readonly networkState: number
readonly error: { code: number, message: string } | null
volume: number
muted: boolean
playbackRate: number
readonly videoWidth: number
readonly videoHeight: number
poster: string
/** The following setters are inert; use Option for autoplay/loop/crossOrigin. */
autoplay: boolean
loop: boolean
controls: boolean
playsInline: boolean
crossOrigin: string
preload: string
defaultMuted: boolean
defaultPlaybackRate: number
play: () => Promise<void>
pause: () => void
load: () => void
/** Always returns "maybe"; it does not probe codec/browser support. */
canPlayType: (type: string) => 'maybe'
getM3u8State: () => Promise<HlsState | null>
switchM3u8Quality: (value: unknown) => Promise<void>
switchM3u8Audio: (value: unknown) => Promise<void>
createTimeRanges: (start: number, end: number) => TimeRanges
requestVideoFrameCallback: (callback: SyntheticFrameCallback) => number
cancelVideoFrameCallback: (id: number) => void
addEventListener: (type: string, listener: MediaListener) => void
removeEventListener: (type: string, listener: MediaListener) => void
getBoundingClientRect: () => DOMRect
setAttribute: (name: string, value: unknown) => void
destroy: () => void
}
/** Native Canvas methods win collisions, including DOM event and attribute methods. */
export type MediaBunnyCanvas = HTMLCanvasElement & Omit<MediaBunnyShim, keyof HTMLCanvasElement>
/** The alias is installed by the proxy and removed on player destruction. */
export type MediaBunnyPlayer = Artplayer & { mediabunny?: MediaBunnyShim }
File diff suppressed because it is too large. Load diff
+12
View File
@@ -0,0 +1,12 @@
{
"task": "PKG-MB-08",
"sourceCommit": "8a2b56e829a3fa9af5e440a11cede8d1775ded10",
"sourceSha256LF": {
"packages/artplayer-proxy-mediabunny/src/index.js": "4e1be8937908ff6e6ef8a4e863e22b4468312cf31d66ac299322e475757773bb",
"packages/artplayer-proxy-mediabunny/src/VideoShim.ts": "1fef3028a3937182c31503129655efdb88118ea0f6d3be96e9be99dcbcc5fdc7",
"packages/artplayer-proxy-mediabunny/src/engine-types.ts": "6714070f954f7345f33cd93655facc6aa94fd34fa1be0375361962d54fa6e180",
"packages/artplayer-proxy-mediabunny/types/artplayer-proxy-mediabunny.d.ts": "af2083224d0760752d83eb2ac86b1dea5586455b81d47e4ba70f9f6a874a75d8",
"packages/artplayer-proxy-mediabunny/package.json": "96b09ce19ea7914485aa9780329a0cfc43b18e38232ea1dd92d2e6dc27310055",
"packages/artplayer-proxy-mediabunny/dist/artplayer-proxy-mediabunny.js": "303b1a5d71b661c7d973c52850f5e68766990c86c59fe438f0070bd38edbfb0e"
}
}
@@ -0,0 +1,66 @@
# PKG-MB-08 检查点:入口、Canvas 转发与公开声明
任务保持doing。本包自有源码已全部迁移为34个strict TS模块,但不可解码轨道的回退与
就绪能力审查尚未完成;不能据此把整个MB-08标done。进入本轮的源码/产物冻结于
[入口基线](../baselines/mb-entry-source.json),对应8a2b56e8。
## 实现与兼容边界
入口从index.js迁至index.ts,Canvas转发、入口生命周期和通用清理分别进入
canvas-bridge.ts、entry-lifecycle.ts、cleanup.ts。后者从HLS菜单层下移,避免入口依赖
UI层的通用释放实现。HLS初始化支持部分注册失败时撤销,返回内部destroy供入口回滚。
保留createElement脱离receiver调用、原生可枚举方法getter读取两次、Canvas原生方法优先、
shim own/直接prototype转发、每次读取绑定方法、getter-only写入TypeError等旧行为。
资源状态不增加Canvas公开字段。销毁取消入口resize/metadata/destroy监听,已保存回调失效;
只删除仍属于当前shim的art.mediabunny,保留调用方替换的别名。一个清理步骤抛错仍尝试其余步骤,
setup失败保留原始异常。缺少off的历史自定义host仍可用,残留回调在销毁后保持无副作用。
公开默认工厂继续是可选Option到同步initializer、精确HTMLCanvasElement的签名;不将Result
缩窄成Canvas与shim交叉类型,不增加强制default属性,也不全局增强Artplayer。
新增type-only Option/Result、MediaBunnyShim、MediaBunnyCanvas、MediaBunnyPlayer、HLS状态和
SyntheticFrameCallback等。MediaBunnyPlayer的别名可缺省;Canvas视图明确原生DOM方法优先。
VideoShim实际implements媒体视图,getter显式返回类型防止unknown setter污染读取推导。
声明不引入MediaBunny SDK的现代DOM/Disposable依赖;track对象保持unknown,由消费者按
自用SDK缩窄。SyntheticFrameMetadata保留历史估算语义,尤其presentationTime是媒体秒数。
canPlayType仍恒为maybe,TimeRanges不代表真实网络缓冲;新类型不制造能力承诺。
新增.d.mts及条件types路由修正NodeNext ESM不可调用问题,typesVersions补齐经典解析的legacy。
CommonJS继续保留旧default声明门面;运行时直接工厂增加不可枚举、可写、可配置的default自身别名,
同时支持1.0.0的namespace.default和1.2.0的直接调用。整工厂双向赋值使用实际两个npm冻结声明检查。
## 验证与迭代
15条入口断言在旧main上6通过/9失败;候选包专项241通过,main/legacy各217通过,其中
各213条候选断言和4条冻结正常控制。HLS测试将清理断言限定为其自己拥有的5个listener;
新增入口断言单独检查额外3个listener,不再错误假设resize回调应一直残留。
新增12浏览器用例使用真实Canvas与发布版/候选核心,覆盖方法receiver、DOM事件/属性优先、
清理和替换别名。旧main同12项全部复现缺陷;候选通过。首轮夹具错误地把后续核心尺寸处理后的
inline style当作入口独占结果,现单独调用已注册入口resize callback验证其行为;首轮18份失败
report/results保留,并合并了两组重复场景。未增加重试或放宽超时。最终8个MediaBunny浏览器文件
共120通过:新增12项为Canvas/生命周期检查,原有29项为Windows WebKit能力对照,均不冒充播放。
新增yarn test:mediabunny-types-package:实际pack核心与代理,在工作区外安装两个冻结发布包及
候选,offline/frozen重装、锁文件和安装文件哈希检查。17组编译/导出矩阵通过;候选7组各拒绝
15条非法用法,并验证原始两份声明的整工厂双向赋值。保留旧版NodeNext ESM的准确诊断作为负例。
最初pack发现tsconfig泄漏,补入.npmignore后修复。旧TS不读取SDK声明,所有解析文件限于安装
目录或编译器标准库。该检查并非MB-10完整分发/许可验收。
最终源码、打包安装、构建与浏览器证据见[检查点记录](../baselines/mb-entry-checkpoint.json)。
完整CI1402项(1247单元+14工程+141基线)通过,另44重复契约,324生产TS。
首轮全局noUncheckedIndexedAccess发现消费者夹具直接读取空数组首项,改为可选访问后
完整CI和17组实际安装矩阵重跑通过;运行时产物未变化。本轮未添加依赖,Yarn和SDK锁版本不变。
## 立即接续
继续检查已有track但codec/canDecode不可用时,VideoEngine/AudioEngine的历史空轨道回退。
必须保留有效audio-only/video-only播放,阻止没有可用解码器时假就绪,并覆盖加载、HLS替换、
晚能力查询、pause/source/destroy和错误事件顺序。不要仅凭新增接口或现有WebKit负例宣布修复。
长播放/AV sync/设备及组合由MB-09,示例、安装分发及MPL/source notice由MB-10继续。
本检查点独立本地提交,回退该提交即可恢复本轮前状态;冻结历史输入保持不变,不推送或发布。
接续缺陷已用实际SDK解析的video-only MP4复现:受控canDecode=false与AudioContext下,
旧/候选均为videoSink=null、audioSink=null,却readyState=4并发loadedmetadata/data/canplay/through;
replaceTracks后同样假就绪。该受控复现见检查点记录,不能当作真实浏览器解码结果。
@@ -0,0 +1,11 @@
import Artplayer from 'artplayer'
import factory from 'artplayer-proxy-mediabunny'
type Option = Parameters<typeof factory>[0]
const art = new Artplayer({ container: '#player', url: 'movie.mp4', proxy: factory() })
const replacement: typeof factory = (_option?: Option) => (_art: Artplayer) => document.createElement('canvas')
const canvas: ReturnType<ReturnType<typeof factory>> = document.createElement('canvas')
factory()
factory(undefined)
factory({ volume: 0.7, source: new Blob([]), loadTimeout: 1000 })(art)
void [replacement, canvas]
@@ -0,0 +1,34 @@
import Artplayer from 'artplayer'
import factory from 'artplayer-proxy-mediabunny'
const art = new Artplayer({ container: '#player', url: 'movie.mp4', proxy: factory() })
type Option = NonNullable<Parameters<typeof factory>[0]>
type HistoricalFactory = (option?: Option) => (art: Artplayer) => HTMLCanvasElement
const assignOld: HistoricalFactory = factory
const assignNew: typeof factory = (_option?: Option) => (_art: Artplayer) => document.createElement('canvas')
const initializer: ReturnType<typeof factory> = () => document.createElement('canvas')
const canvas: ReturnType<ReturnType<typeof factory>> = document.createElement('canvas')
const options: Option = {
source: new ReadableStream<Uint8Array>(),
loadTimeout: 1000,
timeupdateInterval: 250,
avSyncTolerance: 0.12,
dropLateFrames: false,
poster: 'poster.jpg',
preflightRange: false,
volume: 0.7,
muted: false,
autoplay: false,
loop: false,
crossOrigin: 'anonymous',
m3u8: {
quality: { control: true, setting: true, title: 'Quality', auto: 'Auto', getName: level => `${level.id}/${level.index}/${level.name}/${level.height}/${level.bitrate}` },
audio: { control: true, setting: true, title: 'Audio', auto: 'Auto', getName: track => `${track.id}/${track.index}/${track.name}/${track.lang}/${track.language}/${track.bitrate}` },
},
}
factory()
factory(undefined)
factory(options)(art).getContext('2d')
factory({ source: new Blob([]) })
factory({ source: 'movie.mp4' })
void [assignOld, assignNew, initializer, canvas]
+11
View File
@@ -80,3 +80,14 @@ main/legacy各159通过。最终78浏览器含16新增原生音频场景及8新
11通过/32失败,候选专项226及main/legacy各202通过。最终108浏览器含20新增原生HLS
新旧核心/UI/SDK场景与10新增能力对照。旧main同30项14通过/16失败,未把能力缺失计作播放。
全部报告/results已归档;完整CI1387项及44重复契约通过,320生产TS;完成后立即专用提交,MB-LIFE-01剩余组合/长播范围仍open。
## PKG-MB-08 入口与声明检查点(doing)
index实际迁移,入口/Canvas桥/生命周期/清理拆为四模块,全包34生产TS。
精确默认Canvas和整工厂保留;可选shim/HLS/RAF视图、ESM声明及旧legacy解析已验证。
15条新断言旧main6通过/9失败,候选专项241及main/legacy各217通过。新增12真实Canvas/
新旧核心场景通过,旧main同12项失败;最终8文件120浏览器通过,其中29能力对照不计作播放。
实际打包安装17类型矩阵通过,候选7组各拒绝15非法用法;旧NodeNext失败保留负例。
见[变更](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,下一步修复该能力边界。
+3 -2
View File
@@ -4,7 +4,7 @@
基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 222 项,范围 22 个包及工作区/示例。
状态:todo 111 / doing 7 / blocked 0 / done 104 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
状态:todo 110 / doing 8 / blocked 0 / done 104 / 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 | todo |
| 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-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,4 +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-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)
+13
View File
@@ -1,5 +1,18 @@
# 进度与证据
## PKG-MB-08检查点:入口TS与兼容媒体声明(doing)
全包34个自有生产模块已转TS,入口/Canvas桥/生命周期/清理分离。默认工厂仍可选Option、
同步初始化与精确Canvas结果;新增可选媒体/HLS/RAF类型,修复旧legacy及NodeNext ESM解析。
15条入口断言旧main6通过/9失败,候选专项241及main/legacy各217通过。最终120浏览器
全部通过,含12原生Canvas/新旧核心场景及29能力对照;旧main相同12入口场景全部失败。
实际安装17组类型/导出矩阵通过,候选7组各拒绝15非法用法;整工厂按冻结声明双向赋值。
完整CI1402项及44重复契约通过,324生产TS。源码/产物/浏览器/安装包哈希已核验。
见[变更](changes/2026-09-12-PKG-MB-08-entry-checkpoint.md)与[证据](baselines/mb-entry-checkpoint.json)。
222项:104 done、8 doing、110 todo。本地检查点提交,不计任务完成。
下一步:修复受控canDecode=false时video-only输入及替换无可用sink却发布就绪;
该问题旧/候选均已复现。MB-08、能力风险及后续分发/许可/设备门槛仍未关闭。
## PKG-MB-07完成:HLS配对、选择意图与UI拓扑
m3u8实际迁移为四个TS模块,包内30生产TS,仅剩index.js待MB-08。
+7 -4
View File
@@ -3679,13 +3679,15 @@
"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.",
"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.",
"closureCriteria": "Actual installed old/candidate compiler and runtime export matrices pass with negative controls; any incompatible declaration generation requires an explicit decision.",
"status": "open",
"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-01-contract.md",
"refactor/changes/2026-09-12-PKG-MB-08-entry-checkpoint.md",
"refactor/baselines/mb-entry-checkpoint.json"
]
},
{
@@ -3715,13 +3717,14 @@
"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.",
"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.",
"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-coordination-validation.json",
"refactor/baselines/mb-entry-checkpoint.json"
]
}
]
+138
View File
@@ -0,0 +1,138 @@
import assert from 'node:assert/strict'
import fs from 'node:fs'
import path from 'node:path'
import process from 'node:process'
import ts from 'typescript'
import compat from 'typescript-compat'
import { checkFiles, packedFiles } from '../../scripts/package-check.mjs'
import { consumerDirectory, removeConsumer, run, workspace, writeJson } from '../../scripts/package-consumer.mjs'
import { verifyMbContract } from './mb-contract.mjs'
import { hash, readMember } from './releases.mjs'
async function main() {
assert.equal(process.env.npm_config_user_agent?.split(' ')[0], 'yarn/1.22.22', 'Use yarn test:mediabunny-types-package')
const yarn = process.env.npm_execpath
assert(yarn && fs.existsSync(yarn))
const output = fs.mkdtempSync(path.join(workspace, 'refactor/.cache/mb-package-types-'))
console.log(`MediaBunny isolated package evidence: ${output}`)
const contract = await verifyMbContract()
const packages = []
for (const name of ['artplayer', 'artplayer-proxy-mediabunny']) {
const archive = path.join(output, `${name}.tgz`)
fs.writeFileSync(path.join(output, `${name}-pack.log`), run([yarn, 'pack', '--filename', archive], path.join(workspace, 'packages', name)))
const manifest = JSON.parse(readMember(archive, 'package/package.json'))
const files = packedFiles(archive)
checkFiles(manifest, files, name === contract.baseline.release.name ? Object.keys(contract.baseline.release.files) : [])
packages.push({ name, archive, sha256: hash(fs.readFileSync(archive)), files: Object.fromEntries(files.map(member => [member, hash(readMember(archive, member))])) })
}
const matrix = []
const core = packages[0]
const candidates = [
...contract.baseline.previous.concat(contract.baseline.release).map(release => ({ ...release, archive: contract.archives.get(release.version), label: `published-${release.version}` })),
{ ...packages[1], label: 'candidate' },
]
for (const plugin of candidates) {
const consumer = consumerDirectory()
try {
writeJson(path.join(consumer, 'package.json'), { name: 'mediabunny-isolated-consumer', private: true, dependencies: Object.fromEntries([core, plugin].map(pkg => [pkg.name, `file:${pkg.archive.replaceAll('\\', '/')}`])) })
fs.copyFileSync(path.join(workspace, 'yarn.lock'), path.join(consumer, 'yarn.lock'))
fs.writeFileSync(path.join(output, `${plugin.label}-install.log`), run([yarn, 'install', '--offline', '--ignore-scripts', '--non-interactive'], consumer))
const lock = fs.readFileSync(path.join(consumer, 'yarn.lock'))
fs.writeFileSync(path.join(output, `${plugin.label}-frozen-install.log`), run([yarn, 'install', '--offline', '--frozen-lockfile', '--ignore-scripts', '--non-interactive'], consumer))
assert.deepEqual(fs.readFileSync(path.join(consumer, 'yarn.lock')), lock)
fs.writeFileSync(path.join(output, `${plugin.label}-yarn.lock`), lock)
for (const pkg of [core, plugin]) {
const root = path.join(consumer, 'node_modules', pkg.name)
assert.equal(fs.realpathSync(root), root, 'Installed package must not be a workspace link')
for (const [member, expected] of Object.entries(pkg.files))
assert.equal(hash(fs.readFileSync(path.join(root, member.slice(8)))), expected, `Installed bytes differ: ${member}`)
}
const runtimeFile = path.join(consumer, 'exports.cjs')
fs.writeFileSync(runtimeFile, `const assert = require('node:assert/strict');
for (const name of ['artplayer-proxy-mediabunny', 'artplayer-proxy-mediabunny/legacy']) {
const value = require(name);
assert.equal(typeof value, '${plugin.version === '1.0.0' ? 'object' : 'function'}');
assert.equal(typeof (value.default || value)(), 'function');
${plugin.label === 'candidate' ? 'assert.equal(value.default, value); assert.equal(typeof value.default(), "function"); assert.deepEqual(Object.keys(value), []);' : plugin.version === '1.2.0' ? 'assert.equal(value.default, undefined);' : 'assert.equal(typeof value.default, "function");'}
}
Promise.all(['artplayer-proxy-mediabunny', 'artplayer-proxy-mediabunny/legacy'].map(async name => {
const module = await import(name);
const value = module.default;
assert.equal(typeof (value.default || value)(), 'function');
if (name === 'artplayer-proxy-mediabunny') assert.deepEqual(Object.keys(module), ['default']);
})).catch(error => { console.error(error); process.exitCode = 1; });`)
fs.writeFileSync(path.join(output, `${plugin.label}-runtime.log`), run([runtimeFile], consumer))
const modes = [[ts, 'node10-commonjs'], [ts, 'nodenext-cjs'], [ts, 'nodenext-esm'], [ts, 'bundler-esm'], [compat, 'node10-commonjs']]
if (plugin.label === 'candidate')
modes.push([ts, 'node10-commonjs-no-interop'], [compat, 'node10-commonjs-no-interop'])
for (const [compiler, mode] of modes) {
const next = mode.startsWith('nodenext')
const extension = next ? mode.endsWith('-cjs') ? 'cts' : 'mts' : 'ts'
const fixture = plugin.label === 'candidate' ? 'test/types/mediabunny.ts' : plugin.version === '1.0.0' ? 'refactor/fixtures/consumers/mediabunny-1.0.ts' : 'refactor/fixtures/consumers/mediabunny-published.ts'
const source = fs.readFileSync(path.join(workspace, fixture), 'utf8')
const filename = path.join(consumer, `consumer.${extension}`)
const files = [filename]
if (plugin.label === 'candidate') {
for (const generation of ['1.0', 'published']) {
const historical = path.join(consumer, `historical-${generation}.${extension}`)
fs.copyFileSync(path.join(workspace, `refactor/fixtures/consumers/mediabunny-${generation}.ts`), historical)
files.push(historical)
}
// Compare complete factories against the actual frozen declaration bytes.
for (const release of contract.baseline.previous.concat(contract.baseline.release)) {
const name = `frozen-${release.version}`
fs.writeFileSync(path.join(consumer, `${name}.d.ts`), readMember(contract.archives.get(release.version), `package/${release.manifest.types.replace(/^\.\//, '')}`))
const assignment = path.join(consumer, `${name}-assignment.cts`)
fs.writeFileSync(assignment, `import current from 'artplayer-proxy-mediabunny'; import frozen from './${name}'; const forward: typeof frozen = current; const backward: typeof current = frozen; void [forward, backward];`)
// TS 4.3 predates .cts. Use an ordinary .ts for classic resolution.
const actual = next ? assignment : assignment.replace(/\.cts$/, '.ts')
if (actual !== assignment)
fs.renameSync(assignment, actual)
files.push(actual)
}
if (mode === 'nodenext-cjs') {
const commonjs = path.join(consumer, 'commonjs.cts')
fs.writeFileSync(commonjs, `import factory = require('artplayer-proxy-mediabunny'); import legacy = require('artplayer-proxy-mediabunny/legacy'); factory.default(); legacy.default({ volume: 0.5 });`)
files.push(commonjs)
}
}
const options = { strict: true, noEmit: true, skipLibCheck: false, types: [], esModuleInterop: !mode.endsWith('-no-interop'), target: compiler.ScriptTarget.ES2020, lib: ['lib.es2020.d.ts', 'lib.dom.d.ts'], module: next ? compiler.ModuleKind.NodeNext : mode === 'bundler-esm' ? compiler.ModuleKind.ESNext : compiler.ModuleKind.CommonJS, moduleResolution: next ? compiler.ModuleResolutionKind.NodeNext : mode === 'bundler-esm' ? compiler.ModuleResolutionKind.Bundler : compiler.ModuleResolutionKind.NodeJs }
function compile(code) {
fs.writeFileSync(filename, code)
const program = compiler.createProgram(files, options)
for (const file of program.getSourceFiles()) {
const actual = fs.realpathSync(file.fileName)
assert(actual.startsWith(fs.realpathSync(consumer) + path.sep) || (program.isSourceFileDefaultLibrary(file) && path.dirname(actual) === fs.realpathSync(path.dirname(compiler.sys.getExecutingFilePath()))), `Types escaped installed consumer: ${actual}`)
assert(!/[\\/]node_modules[\\/]mediabunny[\\/]/.test(actual), 'Public declarations must not pull modern SDK types into older compilers')
}
return compiler.getPreEmitDiagnostics(program).map(item => ({ code: item.code, message: compiler.flattenDiagnosticMessageText(item.messageText, '\n') }))
}
const diagnostics = compile(source)
const historicalFailure = plugin.label !== 'candidate' && mode === 'nodenext-esm'
writeJson(path.join(output, `${plugin.label}-${compiler.version}-${mode}-diagnostics.json`), { diagnostics, historicalFailure })
if (historicalFailure) {
assert.deepEqual(diagnostics.map(item => item.code), plugin.version === '1.0.0'
? [2344, 2349, 2322, 2344, 2349, 2349, 2349]
: [2349, 2344, 2322, 2322, 2344, 2344, 2322, 7006, 7006, 2349, 2349, 2349, 2349, 2349], 'Unexpected historical NodeNext failure')
}
else {
assert.deepEqual(diagnostics, [], `${plugin.label} ${compiler.version} ${mode}`)
}
const invalid = plugin.label === 'candidate' ? compile(source.replaceAll(/\/\/ @ts-expect-error[^\n]*\n/g, '')) : []
if (plugin.label === 'candidate')
assert.equal(invalid.length, 15, 'Installed declarations must reject all invalid uses')
matrix.push({ plugin: plugin.label, compiler: compiler.version, mode, historicalFailure, diagnostics, invalid })
}
}
finally {
removeConsumer(consumer)
}
}
writeJson(path.join(output, 'report.json'), { suite: 'mediabunny-isolated-package-types', introducedBy: 'PKG-MB-08', scope: 'Two frozen published packages and packed candidate with packed candidate core, installed outside workspace; offline/frozen reinstall, exact bytes, default/legacy CJS/ESM runtime and compiler matrices. Exact optional whole-factory assignments checked against actual historical declaration bytes. New shim types are opt-in, without SDK declaration leakage. Historical NodeNext ESM failures remain explicit negative controls. Not native decoding, old-core runtime combinations or full distribution/license acceptance.', packages, published: candidates.slice(0, 2).map(pkg => ({ label: pkg.label, archive: pkg.archive, sha256: hash(fs.readFileSync(pkg.archive)) })), matrix })
console.log(`MediaBunny installed types passed: ${matrix.length} cases; ${output}`)
}
main().catch((error) => {
console.error(error)
process.exitCode = 1
})
+6 -2
View File
@@ -3546,11 +3546,15 @@
"ENG-04",
"ENG-06"
],
"status": "todo",
"status": "doing",
"risk": "H",
"deliverable": "8 个自有 JS 模块迁移与 Result/shim 类型",
"acceptance": "解码器/Stream/DOM 类型清楚,旧 Option 和 art.mediabunny 使用保持",
"evidence": []
"evidence": [
"baselines/mb-entry-source.json",
"changes/2026-09-12-PKG-MB-08-entry-checkpoint.md",
"baselines/mb-entry-checkpoint.json"
]
},
{
"id": "PKG-MB-09",
+1 -1
View File
@@ -76,7 +76,7 @@ export function runTypechecks() {
for (const fixture of ['test/types/public.ts', 'test/types/declaration-inputs.ts', 'test/types/declaration-legacy.ts', 'test/types/plugins-public.ts', 'test/types/playback-public.ts', 'test/types/chapter-options.ts', 'test/types/chapter-exports.ts', 'test/types/hls-control.ts', 'test/types/dash-control.ts', 'test/types/ads.ts', 'refactor/fixtures/consumers/ads-published.ts', 'refactor/fixtures/consumers/ads-workspace.ts', 'refactor/fixtures/consumers/dash-control-legacy.ts', 'test/types/audio-track.ts', 'test/types/language-value.ts', 'refactor/fixtures/consumers/language.ts', 'refactor/fixtures/consumers/legacy-plugin.ts']) {
assert.deepEqual(checkConsumer(compiler, mode, fs.readFileSync(path.join(root, fixture), 'utf8')), [], `Consumer failed: TS ${compiler.version} ${mode} ${fixture}`)
}
for (const fixture of ['test/types/ambilight.ts', 'refactor/fixtures/consumers/ambilight-published.ts', 'test/types/canvas.ts', 'refactor/fixtures/consumers/canvas-published.ts', 'test/types/dpip.ts', 'refactor/fixtures/consumers/dpip-published.ts'])
for (const fixture of ['test/types/ambilight.ts', 'refactor/fixtures/consumers/ambilight-published.ts', 'test/types/canvas.ts', 'refactor/fixtures/consumers/canvas-published.ts', 'test/types/mediabunny.ts', 'refactor/fixtures/consumers/mediabunny-1.0.ts', 'refactor/fixtures/consumers/mediabunny-published.ts', 'test/types/dpip.ts', 'refactor/fixtures/consumers/dpip-published.ts'])
assert.deepEqual(checkConsumer(compiler, mode, fs.readFileSync(path.join(root, fixture), 'utf8')), [], `Consumer failed: TS ${compiler.version} ${mode} ${fixture}`)
if (mode === 'nodenext-cjs')
assert.deepEqual(checkConsumer(compiler, mode, fs.readFileSync(path.join(root, 'test/types/commonjs.cts'), 'utf8')), [], 'CommonJS export assignment consumer failed')
+72
View File
@@ -0,0 +1,72 @@
import { hash } from '../../refactor/scripts/releases.mjs'
import { mbCandidate } from '../helpers/mediabunny.js'
import { expect, test } from './fixtures.js'
const implementation = await mbCandidate()
for (const core of ['published', 'candidate']) {
for (const scenario of ['native-cleanup', 'replacement']) {
test(`MediaBunny ${implementation.name}: ${core} core native Canvas entry ${scenario}`, async ({ page }, testInfo) => {
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 result = await page.evaluate((scenario) => {
const registered = []
const removed = []
const art = new window.Artplayer({
container: '.player',
url: '',
proxy(player) {
const on = player.on
const off = player.off
player.on = function (name, callback) {
registered.push({ name, callback })
return on.call(this, name, callback)
}
player.off = function (name, callback) {
removed.push({ name, callback })
return off.call(this, name, callback)
}
return window.mbFactory()(player)
},
})
const canvas = art.video
const shim = art.mediabunny
const resize = registered.find(item => item.name === 'resize')
const metadata = registered.find(item => item.name === 'video:loadedmetadata')
const destroy = registered.find(item => item.name === 'destroy')
const receiver = canvas.getContext
const bounds = canvas.getBoundingClientRect
const attributes = canvas.setAttribute
const identity = canvas instanceof HTMLCanvasElement && shim.canvas === canvas
const context = receiver('2d')?.canvas === canvas
const width = bounds().width
let clickCount = 0
canvas.addEventListener('click', () => clickCount++)
canvas.dispatchEvent(new MouseEvent('click'))
attributes('src', 'native-only.mp4')
canvas.volume = 0.35
art.emit('resize')
// Core sizing listeners may reset inline styles after proxy resize; isolate its callback.
resize.callback()
const before = { identity, context, width, clickCount, source: shim.src, attribute: canvas.getAttribute('src'), volume: shim.volume, style: canvas.style.width, maybe: canvas.canPlayType('video/unknown') }
const marker = { owner: 'replacement' }
if (scenario === 'replacement')
art.mediabunny = marker
art.destroy(false)
canvas.style.width = '43px'
resize.callback()
metadata.callback()
const cleanup = {
released: [resize, metadata, destroy].map(item => removed.some(entry => entry.name === item.name && entry.callback === item.callback)),
destroyed: shim.engine.destroyed,
styleAfter: canvas.style.width,
alias: scenario === 'replacement' ? art.mediabunny === marker : !Object.hasOwn(art, 'mediabunny'),
}
return { before, cleanup, coreVersion: window.Artplayer.version }
}, scenario)
expect(result.before).toEqual({ identity: true, context: true, width: 640, clickCount: 1, source: null, attribute: 'native-only.mp4', volume: 0.35, style: '100%', maybe: 'maybe' })
expect(result.cleanup).toEqual({ released: [true, true, true], destroyed: true, styleAfter: '43px', alias: true })
await testInfo.attach('mediabunny-entry', { contentType: 'application/json', body: JSON.stringify({ implementation: implementation.name, sha256: hash(implementation.code), core, scenario, result, scope: 'Actual browser Canvas descriptors and published/candidate core lifecycle. Empty source deliberately excludes decoding; existing media suites cover playback separately.' }) })
})
}
}
+229
View File
@@ -0,0 +1,229 @@
import assert from 'node:assert/strict'
// eslint-disable-next-line test/no-import-node-test -- Actual entry bridge and lifecycle with controlled native-like Canvas descriptors.
import test from 'node:test'
import { mbCandidate, mbEnvironment } from './helpers/mediabunny.js'
const implementation = await mbCandidate()
function environment() {
const env = mbEnvironment(implementation)
env.art.off = (name, callback) => {
env.handlers.set(name, (env.handlers.get(name) || []).filter(item => item !== callback))
}
return env
}
test('MediaBunny entry keeps a callable factory and a writable historical default alias', () => {
const env = environment()
assert.equal(typeof env.exported, 'function')
assert.equal(env.exported.default, env.exported)
assert.equal(typeof env.exported.default(), 'function')
assert.deepEqual(Object.keys(env.exported), [])
const replacement = () => {}
env.exported.default = replacement
assert.equal(env.exported.default, replacement)
assert.equal(typeof env.exported(), 'function')
})
test('MediaBunny entry preserves canvas identity, native receiver and shim binding', async () => {
const env = environment()
const canvas = env.factory()(env.art)
assert.equal(canvas, env.canvas)
assert.equal(canvas.canvas, canvas)
assert.equal(canvas.art, env.art)
assert.equal(env.art.mediabunny.engine, canvas.engine)
assert.notEqual(canvas.play, canvas.play)
const pause = canvas.pause
assert.equal(pause(), undefined)
canvas.getContext('2d')
assert.equal(env.nativeCalls.at(-1)[2], true)
const pending = Promise.resolve()
canvas.engine.play = () => pending
assert.equal(canvas.play(), pending)
env.art.emit('destroy')
})
test('MediaBunny entry preserves enumerable forwarding and readonly setter failure', () => {
const env = environment()
const canvas = env.factory()(env.art)
const descriptor = Object.getOwnPropertyDescriptor(canvas, 'duration')
assert.equal(descriptor.enumerable, true)
assert.equal(descriptor.configurable, true)
assert.equal(typeof descriptor.get, 'function')
assert.equal(typeof descriptor.set, 'function')
assert.throws(() => {
canvas.duration = 4
}, { name: 'TypeError' })
env.art.emit('destroy')
})
test('MediaBunny entry preserves native collisions and direct-prototype forwarding boundary', () => {
const env = environment()
env.canvas.currentTime = 13
const native = env.canvas.setAttribute
const canvas = env.factory()(env.art)
assert.equal(canvas.currentTime, 13)
assert.notEqual(canvas.setAttribute, native)
canvas.setAttribute('src', 'native-only.mp4')
assert.equal(env.art.mediabunny.src, null)
assert.equal(Object.hasOwn(canvas, 'toString'), false)
assert.equal(Object.hasOwn(canvas, 'constructor'), false)
env.art.emit('destroy')
})
test('MediaBunny resize preserves default styling and autoSize behavior', () => {
const env = environment()
const canvas = env.factory()(env.art)
assert.deepEqual(canvas.style, {})
env.art.emit('resize')
assert.deepEqual(canvas.style, { width: '100%', height: '100%', objectFit: 'contain' })
canvas.style.width = '42px'
env.art.option.autoSize = true
env.art.emit('video:loadedmetadata')
assert.equal(canvas.style.width, '42px')
env.art.emit('destroy')
})
test('MediaBunny entry destruction unregisters its resize and metadata callbacks', () => {
const env = environment()
const canvas = env.factory()(env.art)
env.art.emit('destroy')
assert.equal(env.handlers.get('resize')?.length || 0, 0)
assert.equal(env.handlers.get('video:loadedmetadata')?.length || 0, 0)
assert.equal(env.handlers.get('destroy')?.length || 0, 0)
assert.equal(canvas.engine.destroyed, true)
})
test('MediaBunny saved resize callback cannot write after destruction', () => {
const env = environment()
const canvas = env.factory()(env.art)
const callback = env.handlers.get('resize')[0]
env.art.emit('destroy')
canvas.style.width = '42px'
callback()
assert.equal(canvas.style.width, '42px')
})
test('MediaBunny destruction preserves a replacement art.mediabunny alias', () => {
const env = environment()
const canvas = env.factory()(env.art)
const replacement = {}
env.art.mediabunny = replacement
env.art.emit('destroy')
assert.equal(env.art.mediabunny, replacement)
assert.equal(canvas.engine.destroyed, true)
})
test('MediaBunny entry attempts listener cleanup even when engine destroy throws', () => {
const env = environment()
const canvas = env.factory()(env.art)
const failure = new Error('engine cleanup failed')
canvas.engine.destroy = () => {
throw failure
}
assert.throws(() => env.art.emit('destroy'), error => error === failure)
assert.equal(env.art.mediabunny, undefined)
assert.equal(env.handlers.get('resize')?.length || 0, 0)
assert.equal(env.handlers.get('destroy')?.length || 0, 0)
assert.doesNotThrow(() => env.art.emit('destroy'))
})
test('MediaBunny entry attempts all cleanup after one listener removal fails', () => {
const env = environment()
const canvas = env.factory()(env.art)
const off = env.art.off
const removed = []
const failure = new Error('off failed')
env.art.off = (name, callback) => {
removed.push(name)
off(name, callback)
if (name === 'resize')
throw failure
}
assert.throws(() => env.art.emit('destroy'), error => error === failure)
assert.deepEqual(removed, ['resize', 'video:loadedmetadata', 'destroy'])
assert.equal(canvas.engine.destroyed, true)
assert.equal(env.art.mediabunny, undefined)
})
test('MediaBunny entry keeps legacy host adapters without off usable and callbacks inert after destroy', () => {
const env = environment()
delete env.art.off
const canvas = env.factory()(env.art)
env.art.emit('destroy')
canvas.style.width = '42px'
env.art.emit('resize')
assert.equal(canvas.style.width, '42px')
})
test('MediaBunny bridge setup failure releases the assigned shim and preserves the original failure', () => {
const env = environment()
Object.preventExtensions(env.canvas)
let captured
Object.defineProperty(env.art, 'mediabunny', {
configurable: true,
get() { return captured },
set(value) { captured = value },
})
assert.throws(() => env.factory()(env.art), { name: 'TypeError' })
assert.equal(captured.engine.destroyed, true)
assert.equal(env.art.mediabunny, undefined)
assert.equal(env.handlers.get('resize')?.length || 0, 0)
})
test('MediaBunny entry calls createElement with the historical detached receiver', () => {
const env = environment()
let receiver = 'unset'
env.art.constructor.utils.createElement = function () {
receiver = this
return env.canvas
}
env.factory()(env.art)
assert.equal(receiver, undefined)
env.art.emit('destroy')
})
test('MediaBunny bridge preserves the two native method getter reads and bound invocation', () => {
const env = environment()
let reads = 0
const receivers = []
Object.defineProperty(env.canvas, 'probe', {
enumerable: true,
configurable: true,
get() {
reads++
return function () {
receivers.push(this)
}
},
set(value) {
Object.defineProperty(env.canvas, 'probe', { value, configurable: true })
},
})
env.factory()(env.art)
assert.equal(reads, 2)
const probe = env.canvas.probe
probe()
assert.equal(receivers[0], env.canvas)
env.art.emit('destroy')
})
test('MediaBunny partial HLS registration failure cleans the entry, shim and installed callbacks', () => {
const env = environment()
const failure = new Error('registration failed')
const on = env.art.on
env.art.on = (name, callback) => {
on(name, callback)
if (name === 'restart')
throw failure
}
let shim
Object.defineProperty(env.art, 'mediabunny', {
configurable: true,
get() { return shim },
set(value) { shim = value },
})
assert.throws(() => env.factory({ m3u8: {} })(env.art), error => error === failure)
assert.equal(shim.engine.destroyed, true)
assert.equal(env.art.mediabunny, undefined)
assert.equal([...env.handlers.values()].flat().length, 0)
})
+3 -3
View File
@@ -257,11 +257,11 @@ test('MediaBunny HLS control update reentry cannot write later settings or audio
test('MediaBunny HLS destruction removes its refresh listeners and preserves proxy teardown', async () => {
const env = environment()
await env.refresh()
const metadataBefore = env.handlers.get('video:loadedmetadata').length
const metadataListener = env.handlers.get('video:loadedmetadata').at(-1)
env.art.emit('destroy')
assert.equal(env.canvas.engine.destroyed, true)
assert.equal(env.art.mediabunny, undefined)
assert.equal(env.handlers.get('video:loadedmetadata').length, metadataBefore - 1)
assert.equal(env.handlers.get('video:loadedmetadata').includes(metadataListener), false)
assert.equal(env.handlers.get('restart')?.length || 0, 0)
})
@@ -402,7 +402,7 @@ test('MediaBunny HLS failing listener removal still attempts remaining removals
throw new Error('off failed')
}
env.art.emit('destroy')
assert.equal(removed.length, 5)
assert.deepEqual(removed.slice(0, 5), ['video:loadedmetadata', 'restart', 'video:loadstart', 'video:error', 'destroy'])
assert.equal(env.canvas.engine.destroyed, true)
assert.equal(env.warnings.length, 1)
})
+82
View File
@@ -0,0 +1,82 @@
import type { HlsState, MediaBunnyCanvas, MediaBunnyPlayer, MediaBunnyShim, Option, Result, SyntheticFrameCallback } from 'artplayer-proxy-mediabunny'
import Artplayer from 'artplayer'
import factory from 'artplayer-proxy-mediabunny'
import legacy from 'artplayer-proxy-mediabunny/legacy'
type Equal<A, B> = (<T>() => T extends A ? 1 : 2) extends (<T>() => T extends B ? 1 : 2) ? true : false
type Assert<T extends true> = T
type ExactFactory = Assert<Equal<typeof factory, (option?: Option) => (art: Artplayer) => HTMLCanvasElement>>
type ExactParameters = Assert<Equal<Parameters<typeof factory>, [option?: Option]>>
type ExactResult = Assert<Equal<ReturnType<ReturnType<typeof factory>>, HTMLCanvasElement>>
type ExactResultAlias = Assert<Equal<Result, HTMLCanvasElement>>
type NativeAttributes = Assert<Equal<MediaBunnyCanvas['setAttribute'], HTMLCanvasElement['setAttribute']>>
type NativeEvents = Assert<Equal<MediaBunnyCanvas['addEventListener'], HTMLCanvasElement['addEventListener']>>
const art: MediaBunnyPlayer = new Artplayer({ container: '#player', url: 'movie.mp4', proxy: factory() })
const replacement: typeof factory = (_option?: Option) => (_art: Artplayer) => document.createElement('canvas')
const canvas: Result = factory(undefined)(art)
const fromLegacy: typeof factory = legacy
const toLegacy: typeof legacy = factory
declare const shim: MediaBunnyShim
const playing: Promise<void> = shim.play()
const paused: void = shim.pause()
const loading: void = shim.load()
const state: Promise<HlsState | null> = shim.getM3u8State()
const source: unknown = shim.currentSrc
shim.src = new Blob([])
shim.currentTime = 2
shim.volume = 0.5
shim.muted = false
const callback: SyntheticFrameCallback = (now, metadata) => {
const values: number[] = [now, metadata.presentationTime, metadata.expectedDisplayTime, metadata.width, metadata.height, metadata.mediaTime, metadata.presentedFrames, metadata.processingDuration, metadata.captureTime, metadata.receiveTime, metadata.rtpTimestamp]
void values
}
shim.cancelVideoFrameCallback(shim.requestVideoFrameCallback(callback))
const maybe: 'maybe' = shim.canPlayType('video/mp4')
art.mediabunny?.pause()
delete art.mediabunny
declare const extended: MediaBunnyCanvas
const native: HTMLCanvasElement = extended
extended.play()
extended.addEventListener('click', event => event.clientX)
extended.setAttribute('src', 'native-attribute-only')
state.then((value) => {
if (!value)
return
const height: number | undefined = value.currentLevel?.height
const language: string | undefined = value.currentAudio?.language
const track: unknown = value.levels[0]?.track
// @ts-expect-error SDK internals require explicit narrowing, not ambient SDK declarations.
track.decode()
void [height, language]
})
// @ts-expect-error The default factory does not promise shim members on a replacement canvas.
canvas.play()
// @ts-expect-error The default result is the historical exact DOM Canvas.
const requiredShim: MediaBunnyCanvas = canvas
// @ts-expect-error Alias does not exist before initialization or after destroy.
art.mediabunny.play()
// @ts-expect-error Factory options are optional but not null.
factory(null)
// @ts-expect-error Volume remains numeric.
factory({ volume: '0.7' })
// @ts-expect-error Historical Option does not widen arbitrary SDK objects.
factory({ source: {} })
// @ts-expect-error Option stream remains byte-oriented.
factory({ source: new ReadableStream<string>() })
// @ts-expect-error Unknown options remain errors.
factory({ arbitrary: true })
// @ts-expect-error The initializer still requires its host.
factory()()
// @ts-expect-error Synchronous registration remains synchronous.
const promise: Promise<Result> = factory()(art)
// @ts-expect-error Computed duration has no writable setter.
shim.duration = 10
// @ts-expect-error Synchronous load does not falsely promise awaitable completion.
const loaded: Promise<void> = shim.load()
// @ts-expect-error Canvas retains its native string attribute API.
extended.setAttribute('src', new Blob([]))
// @ts-expect-error Manual/auto are the only track modes.
const mode: NonNullable<HlsState>['videoMode'] = 'adaptive'
void [replacement, fromLegacy, toLegacy, playing, paused, loading, state, source, maybe, native, requiredShim, promise, loaded, mode]
export type { ExactFactory, ExactParameters, ExactResult, ExactResultAlias, NativeAttributes, NativeEvents }