# @banou/media-player 中文文档

规范地址：https://npmcn.com/package/%40banou/media-player/

版本：0\.10\.37
npm 元数据更新时间：2026-11-05T23:46:13.848Z
中文译文更新时间：2026-11-07T16:38:11.402Z
软件包许可：未声明

本文由上游 README 翻译，可能存在偏差，请结合原文核对 API、命令与配置。版权与许可归原作者所有。

原始 npm 页面：https://www.npmjs.com/package/%40banou/media-player
原文对照：https://npmcn.com/package/%40banou/media-player/?lang=original
项目主页：https://github.com/banou26/media-player#readme
源码仓库：https://github.com/banou26/media-player

## 关键信息（来自 npm 元数据）

- 包体积（解压后）：3.4 MB，166 个文件
- 模块格式：ES Module
- TypeScript 类型：包内自带
- 运行依赖：10 个
- peer 依赖：react ^19\.0\.0，@emotion/react ^11\.14\.0
- 首次发布：2023-12-13T03:06:33.853Z
- 最新版本发布：2026-11-05T23:46:13.445Z
- 近 30 天下载量：5647（2026-10-08 至 2026-11-06，npm 官方下载统计）
- 已发布版本数：85（近 12 个月 60 个）

## 中文简介

用于容器和可解码器的视频播放器,浏览器无法在苍蝇上进行本地播放

## 安装

```sh
npm install @banou/media-player
```

也可以使用：`pnpm add @banou/media-player`、`yarn add @banou/media-player`、`bun add @banou/media-player`

## README

# @ banou/ 媒体玩家

浏览器无法自行打开文件的回放视频播放器 。 它需要一个 `read(offset, size)` 财务报告和审定财务报表 a 字节长度, 重置成零碎的 MP4, 因为它通过 [利巴夫 - 瓦斯姆语](https://github.com/banou26/libav-wasm), (中文(简体) ), 并制作ASS/SSA字幕与jasub. 没有下载到前面,所以它播放了4GB MKV HTTP 范围请求, 出自种子, 出自本地磁盘, 出自其他任何可以回答一个 字节范围 .

回放状态运行于 [视频.js v10](https://github.com/videojs/v10)。其皮肤没有被使用: 这里的铬是它自己的.

## 使用量

```tsx
import MediaPlayer from '@banou/media-player'

<MediaPlayer
  read={(offset, size) => Promise<ArrayBuffer>}
  size={fileByteLength}
  publicPath="/"
  libavWorkerUrl="/libav-worker.js"
  jassubWorkerUrl={jassubWorkerUrl}
  jassubWasmUrl={jassubWasmUrl}
  jassubLegacyWasmUrl={jassubLegacyWasmUrl}
  defaultFontUrl={defaultFontUrl}
  cjkFontUrl={cjkFontUrl}
  title="episode.mkv"
  autoplay
/>
```

`read` 财务报告和审定财务报表 `size` 一起出行:通过两次或两次。 玩家用两者都做成铬 在一个黑色的相框和等待, 这是空的状态.

`inputToRemuxerInput` 从 a 构建对 `Blob`编号 :`File`,一个URL(显示其长度在一定范围内) 或您自己的阅读器 :

```ts
import { inputToRemuxerInput } from '@banou/media-player'

const source = await inputToRemuxerInput({ blob: file })
const source = await inputToRemuxerInput({ url: 'https://example.com/episode.mkv' })
const source = await inputToRemuxerInput({ length, read })
```

URL 的源头会随着 `readsDownload: true`,玩家将选择旁边 `read` 财务报告和审定财务报表 `size`: 每一页 它是一个下载,所以音量常态化者然后分析所获取的音频播放而不是取取文件 再来一次.

`usePlayer` 读取并驱动播放状态从内侧任意位置 `MediaPlayer`这是唯一的钩子 染色体使用:内置视频.js状态和这个玩家自己的源状态(音轨,缩略图, 索引,准备) 活在一个商店上,所以没有第二个上下文可以到达.

```tsx
import { usePlayer } from '@banou/media-player'

const paused = usePlayer((state) => state.paused)          // subscribes to that field
const player = usePlayer()                                  // no selector: the store, no subscription
player.play()
```

`useSeekThumbnails` 财务报告和审定财务报表 `usePictureInPicture` 用于在捆绑的铬外再利用.

`useSeekThumbnails` 返回时 `{ thumbnails, requestThumbnail }`预览从开始生成 ,直到有人指向搜索栏为止: `requestThumbnail(time)` 移动覆盖的预览 `time` 走到队列的前面,走在后面 无论它得到。 过 `undefined` 当指针去。 捆绑的搜索栏叫它 所以这只是你画的铬 它不能打断已经在飞行的解码器, 所以等待是那个的尾巴 而不是整个积压.

`downloadedRanges` 绘画字节横跨您已经持有的搜索栏,通过键框映射 索引而非百分比, 因为文件的下载进度不是它的回放进度 : 集装箱上装有标题、字体和附件,完全没有时间.

### 你的应用软件必须服务的资产

工人们和园丁都是从你们提供的东西中 在跑道上找到的,从不从这些东西中找到 软件包自行解决。 libav的简单文件要复制出来 `node_modules`,这就是什么 `copy-assets` 脚本有 jassub 无法复制, 必须由您的捆绑者构建; 见下文 . `src/asset-urls.ts` 二者的榜样.

`publicPath` 是目录 **libav的两个主题文件** 两者必须在场:

| 文件 | 从 | 当使用时 |
| --- | --- | --- |
| `libav.wasm` | `libav-wasm/build/` | 没有 JSPI 的浏览器: Safari, 以及 iOS 上的每个浏览器 |
| `libav-jspi.wasm` | `libav-wasm/build/` | Chrome和边缘137+,火狐153+ |

{\\fn黑体\\fs22\\bord1\\shad0\\3ahbe\\4ah00\\fscx67\\fscy66\\2chffffff\\3ch808080}libav -wasm在跑步时在他们中间挑 `typeof WebAssembly.Suspending === 'function'`因此服务 只有一种不会在任何地方失败: 它在选择缺失文件的浏览器上失败了, 因为它 读取为浏览器错误而不是缺失资产。 既服务.

`libavWorkerUrl` (单位:千美元)`libav-wasm/build/worker.js`)被单独命名,并且仍然可以复制.

复制所有三个 从libav -wasm 这个包取决于。 在Libav -wasm 0.9.0之前的工人又写了一篇 时间表和报告 `timestampOffset`,所以重播失败.

`cjkFontUrl` 这个软件包是自己的 `noto-sans-jp.otf` (单位:千美元)`import cjkFontUrl from '@banou/media-player/noto-sans-jp.otf?no-inline&url'`),野多·桑斯JP切入JIS X 0208干地. Jassub的利布斯没有 系统字体回落,所以没有它,日文的样式,其字体没有日文,这就是如何放出歌曲 ,以空框形式绘制。 它只在选中的音轨显示 CJK 文本时获取, 仅次于 `defaultFontUrl`, (中文(简体) ), 只画面部缺乏的格言.

`normalizerWorkletUrl` 这个软件包是自己的 `dist/volume-normalizer-worklet.js`,一个没有导入的普通模块 复制罚款或输入为url;见 [音量常态器](https://npmcn.com/package/%40banou/media-player/#readme-the-volume-normalizer)。 。 。 .

### jassub 的资产是导入的, 没有复制

jassub 2的工人是进口的ES模块 `abslink` 财务报告和审定财务报表 `lfa-ponyfill` 光标加 5个亲戚的兄弟姐妹, 它产下一个自己的被窝住的工人。 复制一个文件出局 `node_modules` 生成浏览器无法加载的东西,所以捆绑者必须构建出该图. 与 在您自己的来源中, 输入一个查询 :

```ts
import jassubWorkerUrl from 'jassub/dist/worker/worker.js?worker&url'
import jassubWasmUrl from 'jassub/dist/wasm/jassub-worker-modern.wasm?no-inline&url'
import jassubLegacyWasmUrl from 'jassub/dist/wasm/jassub-worker.wasm?no-inline&url'
import defaultFontUrl from 'jassub/dist/default.woff2?no-inline&url'
```

`?no-inline` 用于任何构造的负载 `build.lib` 设置: 将每个资产内置为 base64 库模式下的数据url并忽略 `assetsInlineLimit` 在做的时候,没有它,那两个 以5.9 MB为基数64. `src/asset-urls.ts` 内 此 repo 是相同的四行, 意在被复制 .

**如果你建造与 `build.lib` 设定,将这四个建在一张单独的通行证中.** `?no-inline` 覆盖您命名的资产, 它不能覆盖那些 jassub 名称: 它的标记胶水 以 `new URL('./wasm/...', import.meta.url)` 在工人体内,和自由模式内线 这些也是。 根据这个版本,一个条目完全输入上述四行:

| 构建 | 工人块 | 基数64为blobs |
| --- | --- | --- |
| `build.lib` 设定 | 2 922 207 字节 | 2个 |
| 没有 `build.lib` | 110,525字节 | 0 个 |

`assetsInlineLimit: 0` 和函数表无关,因为 lib 模式决定 在协商之前先排好队 与JS单项的第二个虚拟配置,没有 `lib`,同样 `outDir` 生产110 KB 工人,30 KB 线粒体胶水以及两个相干文件作为真实文件.

`?no-inline&url` 需要类型声明,因为 vite 本身的环境 `*?url` 只匹配一个描述符 结束于 `?url`。见 `src/vite-env.d.ts`。 。 。 .

这两座建筑在运行时被选中:

| 选项 | 文件 | 当使用时 |
| --- | --- | --- |
| `jassubWasmUrl` | `jassub/dist/wasm/jassub-worker-modern.wasm` | 在有RELEXED SIMD的地方 |
| `jassubLegacyWasmUrl` | `jassub/dist/wasm/jassub-worker.wasm` | 到处都是 |

测试是 RELEXED simd, 不是基线 simd: jassub 验证一个模块 `i8x16.relaxed_swizzle`。 。 。 . 这比普通的SIMD更晚 所以第二个文件不是古老的博物馆作品 萨法里在Jassub 1下的样子 既服务.

装作是 `application/wasm`: jassub 将其与 `instantiateStreaming`,哪个 拒绝其它内容类型.

不需要因地而异的隔离。 Jassub 分配共享 `WebAssembly.Memory` 无条件, 在两台发动机(Chrome 152和Firefox 154测量)的平地上建造 自有线程计数已锁定 `crossOriginIsolated`因此,没有COOP和COEP 它只是运行单 线起了.

染色体带来自己的尺度,不需要主机页面的根字型. 所有东西都是大小的 反对 `--mp-unit`,默认为 `10px` ; 设置在那里以调整整个大小 铬一次.

## 它会做什么

播放和暂停, 以预览缩略图和密钥框精确的擦除来寻找, 在搜索栏上的章节, 提议跳过打开或结尾、日志曲线上的音量、哑音、回放速度、音轨 选择、字幕音轨选择、图片、全屏和键盘快捷键。 没什么 持续 : 音量和速度从默认的每个负载开始,音轨从 [首选语言](https://npmcn.com/package/%40banou/media-player/#readme-preferred-languages) 或者文件的默认值.

快捷键( 空格或 `k`, (中文(简体) ), `f`, (中文(简体) ), `m`,箭头键,和 `<` 财务报告和审定财务报表 `>` 暂停,然后退后或向前一步) 听整个页面,然后将 Ctrl, Cmd 和 Alt 组合留在浏览器中. 框架步骤需要媒体 与 `requestVideoFrameCallback`,哪个a `media` 在另一个文档中播放没有;其主机可以通过 `stepFrame` 在该文件中迈出一步。 过 `shortcuts="player"` 对于玩家及其他内容: 键然后只从玩家内部进行( 点击图片即可突出焦点) 或当它是全屏的, 空间和箭头 继续滚动页面到其它地方.

### 章节

分会来自容器,搜索栏将分会画成分会,每分会断 边界。 在预览片中.

过 `chapters` 他们赢得了所有文件:

```tsx
<MediaPlayer {...source} chapters={[{ start: 0, end: 88, title: 'Opening' }]} />
```

他们是 `{ start, end, title }` 在秒,命令和不重叠, 他们不需要覆盖 整个时间。 本地文件自动从 libav 获得, 所以这是给一个知道 章节,容器不.

### 跳过打开并跳过结束

当一个章节看起来像一个开口或结尾时,一个按钮提议跳过它,出现一个 在章节之前的第二秒 并停留六秒。 它从不跳过自己, 所以错误的猜测成本 没有人会按键而不是跳出剧集.

该猜测取自章节标题,取自实际使用的名称(`OP`, (中文(简体) ), `Opening`, (中文(简体) ), `ED`, (中文(简体) ), `Ending`, (中文(简体) ), `Credits`,并带有一首歌曲的同名词),并在整个文件中延后: `Intro` 是没有其他东西声称的开口,是冷开的开口 `OP` 跟着它. 其章节中没有任何可用名称的文件会回落到形状,其中一章大约 每半跑出时间90秒是开场和结束. 没有比 15秒是永远提供, 和一张碟片 大多是主题没有带来什么, 因为在那里 主题是人们所看到的.

同样的猜测是单独发布的, 对于一个必须知道章节是什么而不加载玩家的页面: `classifyChapters(chapters)` 从 `@banou/media-player/chapters`,它不导入玩家的其他东西,但给出 `'opening'`, (中文(简体) ), `'ending'` 或各章未定义.

### 图片中保存字幕

字幕由Jassub画在视频上方的画布上 图片里的照片是视频 元素和别的东西,所以浏览器无法将两者组合: 平面 `requestPictureInPicture()` 将裸露的视频放入窗口并放入页面字幕.

该帆布属于Jassub,从第二版开始: 建筑师把它转移给工人 元素完全接受一次,并且 `destroy()` 从文档中删除。 所以,玩家使一个 字幕LAYER和引擎在它内部挂上一个帆布 每一个管道建设。 任何能达到的 表面必须逐层查看 而不是按住.

所以玩家会自己合成它们. 每个显示的相框被绘制到屏幕外的画布上 上面的字幕画布,和 `captureStream()` 把它变成一个MediaStream 支持隐藏的视频 元素,是进入窗口的元素。 原始元素继续播放并保留 因为帆布流没有音轨.

镜像元素永远不会被暂停 。 暂停的视频停止渲染它的"媒体史特兰",所以让它失望 反映真实元素冻结窗口: 在屏幕上暂停时查找旧场景 。 这个 运输状态则由媒体会话载出,而这正是窗口读取的内容 播放/暂停按钮.

窗外活过玩家. 玩家在打开窗口时卸载, 打开一个 第二,下一个被安装在同一文档中的玩家在其中播放,形状为 它自己的视频。 因此,一个为下集挂载新玩家的应用程序保持窗口没有 单击,需要新建窗口。 如果没有玩家在那一秒内取出窗口,则会关闭.

如果路径不可用, 玩家将不提供图片控制 .

### 图片为您不拥有的媒体

浏览器在图片中打开图片只用于单击拥有该图片的文档 `<video>`,所以一个 远程媒体只有在主机选择接受该点击时才能获得控制 :

```tsx
// `.frame` sets `pointer-events: none`, so clicks reach the chrome everywhere else
<MediaPlayer
  media={media}
  pictureInPicture={{ onArmedChange: (armed) => { frame.current!.style.pointerEvents = armed ? 'auto' : '' } }}
>
  <iframe ref={frame} className='frame' />
</MediaPlayer>
```

当指针在控件上方时,铬会让控制器的盒子和 主机被告知它携带了武器,所以它的帧会用这个点击,从里面输入图片. 控制跟随媒体 `enterpictureinpicture` 财务报告和审定财务报表 `leavepictureinpicture` 活动,以及 图片中的图片 点击它叫媒体 `exitPictureInPicture`。 。 。 .

一根手指从不靠在控制器上,然后按住,所以水龙头不能武装它。 提供控制权 只在主指针徘徊时(`(hover: hover) and (pointer: fine)`),一触就隐藏了它 直到一个再次徘徊的指针.

### 主机页面保存的全屏

在玩家周围全屏显示自己元素的一页, 这样全屏可以超过其中的玩家, 通过 `fullscreen`数字 :

```tsx
<div ref={stage}>
  <MediaPlayer
    {...source}
    fullscreen={{
      active: stageIsFullscreen,
      request: () => document.fullscreenElement ? document.exitFullscreen() : stage.current!.requestFullscreen(),
    }}
  />
</div>
```

玩家然后叫不 `requestFullscreen`按钮, `f` 并双击每个呼叫 `request` 一次,期间 媒体自己的活动,所以页面 `requestFullscreen` 有媒体的激活, 和按钮 `shortcuts="player"` 跟着 `active`,该页面自其 `fullscreenchange`没有道具 玩家自行全屏显示。 另一文档中的玩家将其全屏通过 [`<MediaPlayer expose>`](https://npmcn.com/package/%40banou/media-player/#readme-layout)照镜子来要求的.

### 音量正常化器

设置菜单中的“ 正常音量” 切换会保持文件本身的正常级别, 并关闭运行良好的内容 因此,突然在对话附近爆炸,对话如出一辙。 你一通过就提供 工作室的url,它打开,除非 观众关闭它或你通过 `normalizeVolume={false}`数字 :

```tsx
import normalizerWorkletUrl from '@banou/media-player/volume-normalizer-worklet.js?no-inline&url'

<MediaPlayer
  {...source}
  normalizerWorkletUrl={normalizerWorkletUrl}
  normalizeVolume={saved ?? true}
  onNormalizeVolumeChange={save}
/>
```

玩家一无所存,所以一个想要选择坚持的应用程序可以保存什么 `onNormalizeVolumeChange` 财务报告和审定财务报表 传回去.

它分两个阶段运作. 一个工人第二次读文件,通过 `thumbnailRead` 当你通过一个,而且唯一的 在内部 `downloadedRanges` (对于一个来源 `readsDownload` 它没有读到任何内容, 并取回音频回放, 在播放头前面约30 s),用WebCodecs解码其音频,学习文件的正常关卡(中位400 ms) 并用无限的外观来计算切口,所以切口开始于突起前:输出的每10 ms是 在正常水平上保持为10个LU,每50至8个LU。 它首先分析玩家附近的东西 然后分析 其余的文件,只保留 1 ms 高音读取,从不录音。 如果分析没有达到 playhead, 对于一个音轨来说, 这个浏览器的 WebCodecs 无法解码, 一个 AudioWorklet 播放一个现场舞台: 一个100ms的探测器 将现在所听到的 5LU的音量 削减到10LU的音量. 两个舞台都无法提升 声音在画面后方播放出10ms,上或下,一旦路由.

Url 命名一个文件,它既是工作人,又是工人的脚本,所以它必须是你的出处服务的一个脚本: 一个CSP说 `script-src 'self'` 拒绝工人或工人 `blob:` 或者说 `data:` url,还有 `build.lib` 内入资产为 `data:` 无 `?no-inline`工人也必须是同源的,因此从 页面的自有来源 .

第一次开机时 `<video>` 被通过Web Audio发送到它的后半生,因为元素不能 ; 关闭是绕行。 运行等待音频 Context 运行: 一个自动播放规则会阻碍恢复 在页面的下一次点击, 点击或按键, 和视频播放 一如既往,直到那时。 WebKit 被拒绝 (单位:千美元)`createMediaElementSource` 内 `@banou/ponyfill/web-audio`),因为路由元素会失去体积并在那里变哑. 一旦开通了开关,音频Context就会拒绝:在那之前,开关读作 从那时起,它读取“无法获取”,元素从未像往常一样经过路由和播放.

### 首选语言

传递语言以启动文件,并保存查看者选择的内容:

```tsx
<MediaPlayer
  {...source}
  preferredAudioLanguage={saved.audio}
  preferredSubtitleLanguage={saved.subtitles}
  onPreferredAudioLanguageChange={(audio) => save({ ...saved, audio })}
  onPreferredSubtitleLanguageChange={(subtitles) => save({ ...saved, subtitles })}
/>
```

首选是标签或名称(`ja`, (中文(简体) ), `ja-JP`, (中文(简体) ), `jpn`, (中文(简体) ), `Japanese`, (中文(简体) ), `English (US)`并报告一个选择的方式 音轨名称,其语言、标记或其他标题,或字幕禁用。 文件开始于全音轨 优先使用的语言,否则默认使用:其中的评论或音频描述不计数。 开始字幕 在全音轨上,在语言, 否则在标语和歌曲 其中之一, 否则默认。 观众选择的胜出 为那个文件。 一个不报告语言的字幕音轨 在标题下,并且只有在字幕打开时,所以下一个文件开始于标题,如果 它有一个.

以观众自己的语言开始,通过它作为两个偏好 `alwaysShowSubtitles={false}`: 有音频的文件 里面播放有字幕的音频,任何其他人播放里面有字幕的原声. 字幕为 只有在音频用他们的语言播放时才使用,所以除了他们的语言之外,还有一种精选的音频语言保留着这些语言。 这个 原始文件的音轨在 `originalLanguage` 当源头知道的时候`ja` 对于动漫),否则文件的默认值, 这是它的第一个音轨: 在一个双音轨文件中,它的第一个音轨是哑弹,只有 `originalLanguage` 寻出正本. `onAlwaysShowSubtitlesChange` 当收视者打开音频所播放语言的字幕时,报告真实性 返回为 `alwaysShowSubtitles`规则是 `startingTracks(audio, subtitles, preferences)`从 `@banou/media-player/language`。 。 。 .

玩家自有的媒体选择不同方式 `select` 解析,其主机将首选项应用到自己的音轨: `preferredSelection(kind, selections, preferences)`, (中文(简体) ), 从 `@banou/media-player/language`,命名要按照同一规则选择的选项,选择的音频为 源代码默认, 并且不导入玩家的其他任何信息 . `languageTag(pick)` 从同一个模块中得出一个选取的 BCP 47 标记 (`jpn` 给 `ja`, (中文(简体) ), `Português (Brasil)` 给 `pt-BR`),或未定义用于命名任何语言,用于读取的代码 标签.

### 观众语言中的标签

控件,工具提示,菜单,跳出要约和失败都使用英语. 过 `language`,一个BCP 47标签,例如: 又一个主机页面显示 :

```tsx
<MediaPlayer {...source} language="fr" />
```

它们以玩家最接近的语言显示: 拉丁美洲西班牙语(`es-419`每张 `es-*`),巴西语 葡萄牙语`pt-BR`每张 `pt-*`),法语,德语,意大利语,俄语,日语,朝鲜语,简化语和传统语 中文( 英文)`zh-Hans`, (中文(简体) ), `zh-Hant`土耳其语、波兰语、阿拉伯语、希伯来语、印度尼西亚语、越南语、泰语、印地语、乌克兰语、荷兰语、, 瑞典语,捷克语,希腊语,罗马尼亚语,匈牙利语和马来语,其他为英语. 里面的号码是用标记写的 (单位:千美元)`es-AR` 给 `0,5x`, (中文(简体) ), `es-419` 本身 `0.5x`),并且跟踪语言在其中被命名,即使标签是英语 (单位:千美元)`en-DE` 给 `0,5x`) (中文(简体) ). 一个语言的文本是玩家第一次显示时获取的,并带有FormatJS(`@formatjs/intl`, (中文(简体) ), 标签是英文,直到到达为止。 玩家给予否 `language`或者它没有的 也拿不到. 每种语言的栏和搜索栏都左到右:右到左写主机页 `dir="ltr"` 编辑 玩家的家长.

每种语言都是 `src/lib/locales/<tag>.json`,按英语键 `src/lib/labels.ts`; 单位测试每个 到格式JS的文件 `verify` 对抗那个英国人.

## 版式

一个包裹. `src/` 是演示应用程序, `src/lib` 是它出版的图书馆, 和应用程序导入它 它的出版名称 所以它是一个诚实的消费者.

-   `src/` 应用程序 : `main.tsx`, (中文(简体) ), `routes/home.tsx`。 。 。 . 由 `vite.config.ts` 输入 `build/`。 。 。 .
    
-   `src/lib/engine/` 管道,其中没有反应: Media Source feeding, remux, jassub, 缩略图. 发表于 `@banou/media-player/engine`。 。 。 .
    
-   `src/lib/remote` 是从 ANOR 文档驱动的玩家, 结束 [奥斯拉](https://osra.banou.dev/)。 。 。 . 发表于 `@banou/media-player/remote`。 。 。 .
    
    在使玩家成为玩家的文档中,一个道具为谁提供文件的帧, 只有他们,或一个有名的出身:
    
    ```tsx
    <MediaPlayer expose read={read} size={size} publicPath="/" {...workers} />
    <MediaPlayer expose={{ origin: 'https://anime.fkn.app' }} media={media} />
    ```
    
    一个服务于一个以上玩家的文档会给每个玩家一个ID,一个嵌入者会要求它的一个 想要吗 :
    
    ```tsx
    <MediaPlayer expose={{ id: 'left' }} {...left} />
    <MediaPlayer expose={{ id: 'right' }} {...right} />
    ```
    
    ```ts
    const left = mediaPlayer(iframe, { id: 'left', origin })
    const right = mediaPlayer(iframe, { id: 'right', origin })
    ```
    
    双方都默认为一个未命名的玩家,所以一个有一个从未说过的文档. 不管伯爵是谁, 每个文档每个方向都有一个连接: 上面的 IDs multilex , 服务两次 ID 替换该玩家( 源开关是如何被跟踪的), 并询问无人服务的 id 而只是等待 直到有人这样做.
    
    在嵌入器中, `mediaPlayer` 手拿回它自己的媒介,一样 `PlayerMedia` 形状 播放器驱动,所以它读取,移动,听取,甚至可以交给秒 `<MediaPlayer>`数字 :
    
    ```ts
    import { mediaPlayer } from '@banou/media-player/remote'
    
    const player = mediaPlayer(iframe, { origin: 'https://torrent.fkn.app' })
    await player.ready
    await player.play()
    player.currentTime = 30
    player.addEventListener('seeked', () => console.log(player.currentTime))
    player.destroy()
    ```
    
    `play()` 以远方元素自己的答案和解,如果镜像在被破坏时被拒绝 在飞行中,一个元素的拒绝 在一个中断的游戏, 所以等待它或抓住它; `pause()` 财务报告和审定财务报表 `load()` 吞下他们.
    
    读取是同步的,因为最远的一面是镜像: 那边的每一个事件都有一个 快照,一写一动一动一动镜像,然后才能被应用出去. `play()` 结算 远方元素自己的答案, 所以自动播放拒绝在这里. `ready` 待定 没有人回答和拒绝 `destroy()` 或中止 `signal`页:1 `MessagePort` 谁的同伴 没有发生任何事件, 所以没有什么能分辨死亡的运输 从慢的,等待必须 被束缚而不是等待出来。 使用 `signal` 玩家的一生,或 `Promise.race([player.ready, timeout])` 独自等待. 切换来源的玩家或重新装入的玩家文档被跟踪:镜像报告 `emptied` 然后是新状态 一个元素的方式 将远程玩家递至一秒钟 `<MediaPlayer media={player}>` 之后 `ready`一份没有框架的文件 没有人; `exposePlayer(media)` 做同样的不反应.
    
    A 类 `<MediaPlayer expose>` 还载有其各章,因为 `player.chapters` 带有 `chapterschange` 每个事件之后 更改(一个赤色的元素没有),并将其全屏放入用 `fullscreen: true`: 远处 玩家的按钮, `f` 双击然后发射一发 `fullscreenrequest` 每个镜子,在媒体内部 激活并显示 `player.fullscreen` 如本页面所设置,直到 `destroy()` 给全屏回放.
    
    ```ts
    const player = mediaPlayer(iframe, { origin, fullscreen: true })
    player.addEventListener('fullscreenrequest', () => document.fullscreenElement ? document.exitFullscreen() : stage.requestFullscreen())
    document.addEventListener('fullscreenchange', () => { player.fullscreen = !!document.fullscreenElement })
    player.addEventListener('chapterschange', () => console.log(player.chapters))
    ```
    
    在玩家跳过要约集的一角有自己东西的一页 `player.endingOffered = false` 改为 接受最后的结局 读取是远方玩家自己的单词, 从它的渲染后,有一个 `chapterschange` 每次更改后的事件 : 等待错误,然后绘制出角, 和一个玩家 无法接受它(一个赤裸的元素,一个更古老的版本) .
    
-   `src/lib/react/` 玩家组件、其铬和钩子.
    
-   `src/lib/labels.ts` 铬的英文文本,和 `src/lib/locales/` 其他语言.
    

由 `vite.lib.config.ts` 输入 `dist/`,这是 npm 出版.

## 发展

```sh
npm install
npm run dev        # the demo app on port 4560
npm run build      # the app, into build/
npm run build-lib  # the library, into dist/
```

应用打开空玩家:黑色,有铬等. 随处投放文件,单击 选择一个,或粘贴一个 URL.

## 常见问题

### @banou/media-player 是什么？

用于容器和可解码器的视频播放器,浏览器无法在苍蝇上进行本地播放

### 如何安装 @banou/media-player？

在项目目录运行 npm install @banou/media-player，或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖：react（^19\.0\.0）、@emotion/react（^11\.14\.0）。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖，Yarn 需要手动安装。

### @banou/media-player 支持 TypeScript 吗？

支持。包内自带 TypeScript 类型声明（package\.json 声明了 types 入口），安装后即可获得类型提示，无需再安装 @types 包。

### @banou/media-player 支持 ESM 还是 CommonJS？

以 ES Module 形式发布，推荐使用 import 引入。在 CommonJS 项目中，Node\.js 20\.19\+ / 22\.12\+ 可以直接 require\(\) 不含顶层 await 的 ES Module，更早的版本需要改用动态 import\(\)。

### @banou/media-player 可以商用吗？

@banou/media-player 使用 未声明 许可。package\.json 没有声明许可证。没有许可证的代码默认保留全部权利，商用前请联系作者或查看仓库中的 LICENSE 文件。以上为概括，不构成法律意见，请以许可原文为准。

### @banou/media-player 的下载量有多少？

根据 npm 官方下载统计，@banou/media-player 在 2026/10/08 – 2026/11/06 这 30 天内被下载 5,647 次，日均约 188 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入，适合横向比较热度，不能直接等同于使用人数。

### @banou/media-player 还在维护吗？

最新版本 0\.10\.37 发布于 2026/11/06（1 天前）；至今共发布 85 个版本，其中过去 12 个月发布了 60 个；首次发布于 2023/12/13。发布频率只是参考信号：功能稳定的工具可能很少需要更新，建议结合源码仓库的 issue 和提交记录判断。

