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

@@ -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 }