mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-29 11:11:20 -08:00
refactor(mediabunny): [PKG-MB-08] checkpoint typed entry and compatible media declarations
This commit is contained in:
1 parent
8a2b56e829
commit
5e28dfee95
39 files changed
+3007
-287
No files matched your search
@@ -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
|
||||
|
||||
Reference in new issue
Block a user