@banou/media-player

中文文档
0.10.37发布于 2026/11/06TypeScriptES Module构建来源已验证近 30 天下载 5647

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

中文使用文档
文档目录14 节
  1. 使用量
  2. 你的应用软件必须服务的资产
  3. jassub 的资产是导入的, 没有复制
  4. 它会做什么
  5. 章节
  6. 跳过打开并跳过结束
  7. 图片中保存字幕
  8. 图片为您不拥有的媒体
  9. 主机页面保存的全屏
  10. 音量正常化器
  11. 首选语言
  12. 观众语言中的标签
  13. 版式
  14. 发展

@ banou/ 媒体玩家

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

回放状态运行于 视频.js v10。其皮肤没有被使用: 这里的铬是它自己的.

使用量

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(显示其长度在一定范围内) 或您自己的阅读器 :

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状态和这个玩家自己的源状态(音轨,缩略图, 索引,准备) 活在一个商店上,所以没有第二个上下文可以到达.

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.wasmlibav-wasm/build/没有 JSPI 的浏览器: Safari, 以及 iOS 上的每个浏览器
libav-jspi.wasmlibav-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;见 音量常态器。 。 。 .

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

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

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.lib110,525字节0 个

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

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

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

选项文件当使用时
jassubWasmUrljassub/dist/wasm/jassub-worker-modern.wasm在有RELEXED SIMD的地方
jassubLegacyWasmUrljassub/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 ; 设置在那里以调整整个大小 铬一次.

它会做什么

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

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

章节

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

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

<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>,所以一个 远程媒体只有在主机选择接受该点击时才能获得控制 :

// `.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数字 :

<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>照镜子来要求的.

音量正常化器

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

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就会拒绝:在那之前,开关读作 从那时起,它读取“无法获取”,元素从未像往常一样经过路由和播放.

首选语言

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

<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标签,例如: 又一个主机页面显示 :

<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 文档驱动的玩家, 结束 奥斯拉。 。 。 . 发表于 @banou/media-player/remote。 。 。 .

    在使玩家成为玩家的文档中,一个道具为谁提供文件的帧, 只有他们,或一个有名的出身:

    <MediaPlayer expose read={read} size={size} publicPath="/" {...workers} />
    <MediaPlayer expose={{ origin: 'https://anime.fkn.app' }} media={media} />
    

    一个服务于一个以上玩家的文档会给每个玩家一个ID,一个嵌入者会要求它的一个 想要吗 :

    <MediaPlayer expose={{ id: 'left' }} {...left} />
    <MediaPlayer expose={{ id: 'right' }} {...right} />
    
    const left = mediaPlayer(iframe, { id: 'left', origin })
    const right = mediaPlayer(iframe, { id: 'right', origin })
    

    双方都默认为一个未命名的玩家,所以一个有一个从未说过的文档. 不管伯爵是谁, 每个文档每个方向都有一个连接: 上面的 IDs multilex , 服务两次 ID 替换该玩家( 源开关是如何被跟踪的), 并询问无人服务的 id 而只是等待 直到有人这样做.

    在嵌入器中, mediaPlayer 手拿回它自己的媒介,一样 PlayerMedia 形状 播放器驱动,所以它读取,移动,听取,甚至可以交给秒 <MediaPlayer>数字 :

    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() 给全屏回放.

    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 出版.

发展

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 共发布 85 个版本,首次发布于 2023/12/13;过去 12 个月发布了 60 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.10.37latest2026/11/061 天前
0.10.362026/11/044 天前
0.10.352026/11/025 天前
0.10.342026/11/016 天前
0.10.332026/10/318 天前
0.10.322026/10/308 天前

关于 @banou/media-player 的常见问题

@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 和提交记录判断。