refactor(mediabunny): [PKG-MB-08] finish typed media capability boundaries

This commit is contained in:
Harvey Zhao committed 2026-09-12 21:51:47 +08:00
1 parent 5e28dfee95
commit fb9fb23f53
25 files changed
+2698 -30

No files matched your search

@@ -10,7 +10,7 @@ import { getHlsState } from './hls-state'
import { createInput } from './input'
import LoadSession from './load-session'
import Playback from './playback'
import { metadataBarrier, publish } from './readiness'
import { metadataBarrier, publish, requireDecoder } from './readiness'
import { selectPlaybackTracks } from './tracks'
import VideoEngine from './VideoEngine.js'
@@ -150,6 +150,7 @@ export default class MediaBunnyEngine implements EnginePort {
this.input = input
this.media = media
const metadata = metadataBarrier(() => {
requireDecoder(this)
this.readyState = 1
publish(this.events, current, ['loadedmetadata', 'durationchange', 'progress'])
}, current)
@@ -159,6 +160,7 @@ export default class MediaBunnyEngine implements EnginePort {
await Promise.all([video, audio])
if (!current())
return
requireDecoder(this)
this.readyState = 4
this.networkState = 1
publish(this.events, current, ['loadeddata', 'canplay', 'canplaythrough', 'progress'])
@@ -1,9 +1,10 @@
import type { Input } from 'mediabunny'
import type { AudioBufferSink, CanvasSink, Input } from 'mediabunny'
import type { ProxyOptions } from './engine-types'
import type EventTarget from './EventTarget'
import type { PlaybackMedia } from './media'
export interface AudioPort {
readonly audioSink: AudioBufferSink | null
cancelPending: () => void
readonly currentTime: number
duration: number
@@ -19,6 +20,7 @@ export interface AudioPort {
}
export interface VideoPort {
readonly videoSink: CanvasSink | null
cancelPending: () => void
width: number
height: number
@@ -1,6 +1,6 @@
import type { PlaybackHost } from './engine-ports'
import type { PlaybackMedia } from './media'
import { publish } from './readiness'
import { publish, requireDecoder } from './readiness'
import { resolveDuration } from './tracks'
interface Completion {
@@ -156,6 +156,7 @@ export default class Playback {
await Promise.all([this.host.video.load(media), this.host.audio.load(media)])
if (!current())
return
requireDecoder(this.host)
await Promise.all([this.host.video.seek(time), this.host.audio.seek(time)])
if (current()) {
this.host.readyState = 4
@@ -1,5 +1,14 @@
import type { PlaybackHost } from './engine-ports'
import type EventTarget from './EventTarget'
export function requireDecoder(host: Pick<PlaybackHost, 'audio' | 'video' | 'readyState'>): void {
// Sinks reflect the decoder's completed capability check; do not query the SDK twice.
if (host.audio.audioSink || host.video.videoSink)
return
host.readyState = 0
throw new Error('Input has no decodable audio or video tracks.')
}
export function publish(events: EventTarget, current: () => boolean, names: readonly string[]): void {
for (const name of names) {
if (!current())