npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@flow-player/browser-runtime

v0.14.2

Published

Rust/WASM-powered browser video player runtime with FLV/HLS/MP4 demux, WebCodecs decode, WebGL2 render, and HEVC software decode fallback.

Readme

@flow-player/browser-runtime

ESM browser runtime for H.264 / H.265 FLV live and VOD playback with WebCodecs, WebGL2, and a Rust-powered WASM demux core. Zero npm dependencies.

0.4.0 新增(@beta):HEVC 软件解码兜底。decodeMode: "auto" 在浏览器 WebCodecs HEVC 不可用时自动软解;默认 decodeMode: "webcodecs" 零回归。详见 HEVC 软解(@beta)

Quick Start (Live)

import { flowLivePlayer } from "@flow-player/browser-runtime";
import PlayerWorker from "@flow-player/browser-runtime/worker?worker";
import wasmUrl from "@flow-player/browser-runtime/wasm/flow_player_wasm.wasm?url";

const player = new flowLivePlayer({
  canvas: document.getElementById("player-canvas") as HTMLCanvasElement,
  workerFactory: () => new PlayerWorker(),
  wasmUrl, // 生产构建必须显式声明(见下方说明)
  useWCS: true,
});

player.on("play", () => console.log("playing"));
player.on("stats", (stats) => console.log(stats.fps, "fps"));
player.on("diagnostic", (detail) => console.warn("normalization:", detail));

await player.play("https://example.com/live.flv");

接入说明wasmUrlworkerFactory 在生产构建中必须显式声明new URL(variable, import.meta.url) 自动推断在 dev 模式可用,但生产构建因 bundler 资产检测只识别字面量而失效。详见 Consumer 接入体验优化 PRD?worker / ?url 需要 /// <reference types="vite/client" /> 类型声明(vite 模板默认已有)。

Quick Start (VOD)

import { flowVodPlayer } from "@flow-player/browser-runtime";
import PlayerWorker from "@flow-player/browser-runtime/worker?worker";
import wasmUrl from "@flow-player/browser-runtime/wasm/flow_player_wasm.wasm?url";

const player = flowVodPlayer({
  canvas: document.getElementById("vod-canvas") as HTMLCanvasElement,
  workerFactory: () => new PlayerWorker(),
  wasmUrl,
});

player.playList([
  {
    url: "/vod/1.mp4",
    startTimestamp: 0,
    endTimestamp: 60000,
    timeLen: 60,
    fileSize: 1024000,
    channelID: 1,
    RecordType: 0,
  },
]);

Worker Setup

The module worker is at @flow-player/browser-runtime/worker.

| Bundler | Import | | ------------- | ------------------------------------------------------------------------------------------------- | | Vite | import PlayerWorker from "@flow-player/browser-runtime/worker?worker" | | webpack 5 | new Worker(new URL("@flow-player/browser-runtime/worker", import.meta.url)) | | esbuild | new Worker(new URL("@flow-player/browser-runtime/worker", import.meta.url), { type: "module" }) |

WASM 部署

.wasm 产物(~147 KB release)包含在 npm 包的 wasm/flow_player_wasm.wasm,通过 ./wasm/* 子路径暴露。

加载机制

提供两种互斥的 wasm 来源(按优先级):

| 选项 | 模式 | 生产构建 | 适用场景 | | ----------- | -------------------- | --------------------------------------- | ------------------------ | | wasmUrl | URL 字符串 | consumer ?url 导入 | 推荐(Vite/Webpack) | | wasmBytes | BufferSource | consumer ?arraybuffer 导入 | 需绕过 URL(如内联) | | 不传 | new URL() 自动推断 | 不可用(非字面量不被 bundler 识别) | 仅 dev |

另有独立并行通道 wasmModuleWebAssembly.Module):作为 init payload 独立字段 postMessage 到 worker,与上述选项可共存(Safari fallback:wasmModule 失败时回落到 wasm config)。不是互斥选项的第三选

Vite / webpack 5(推荐)

?url 导入让 bundler 自动拷贝:

import wasmUrl from "@flow-player/browser-runtime/wasm/flow_player_wasm.wasm?url";

new flowLivePlayer({ wasmUrl /* ... */ });

手动部署(其他 bundler)

拷贝 wasm/flow_player_wasm.wasm 到静态资源并传 URL:

new flowLivePlayer({
  wasmUrl: "https://cdn.example.com/flow_player_wasm.wasm",
  // ...
});

API Reference

flowLivePlayer

| Method | Description | | --------------------------------------------------------------------- | ---------------------------------------- | | play(url) | Start streaming | | pause() | Pause | | close() / destroy() | Stop and release resources | | screenshot(filename?, format?, quality?, type?) | Capture frame → string \| Blob \| void | | startRecord(name?, type?) / stopRecordAndSave() / isRecording() | Recording | | mute() / cancelMute() / setVolume(v) / isMute() | Audio control | | setScaleMode(m) / setRotate(deg) / resize(w, h) / clearView() | Display | | setFullscreen(f) / toggleControlBar(b) / getControlBarShow() | Fullscreen | | setKeepScreenOn() | Prevent screen sleep |

| Property | Type | | -------------------------------------- | ------------------------------------------------------ | | metrics | RuntimeMetrics (chunksPushed, firstFrameAt, ...) | | loaded, playing, muted, volume | State booleans/numbers | | scaleMode, fullscreen, rotate | Display state |

| Key Events | Payload | | ------------------------------------------- | --------------------------------------------------------------- | | play, pause, destroy | void | | start | void (play request started; not a rendered-frame signal) | | stats | FlowLivePlayerStats {buf, fps, abps, vbps, ts} | | videoInfo | FlowLivePlayerVideoInfo {width, height, codec, codecString} | | error | unknown | | diagnostic | unknown (stream normalization detail) | | timeout | FlowLivePlayerTimeoutDetail | | resize | FlowLivePlayerResizeDetail | | clearView | FlowLivePlayerClearViewDetail | | mute / fullscreen | boolean | | volume | number | | scaleMode | FlowLivePlayerScaleMode | | rotate | number | | recordStart / recordEnd / recordError | unknown | | recordingTimestamp | number (seconds) |

Live 事件与恢复契约

ononceoff 共用 FlowLivePlayerEventMap,回调参数可自动推导。 off(event, handler) 可用原回调取消普通订阅及一次性订阅;off(event) 清除此事件全部订阅。 事件同步派发,once 在回调重入触发同名事件时也最多执行一次。

const onTime = (timestampMs: number) => console.log(timestampMs);
player.on("timeUpdate", onTime);
player.off("timeUpdate", onTime);

play() 返回的 Promise、startplay 表示播放请求已启动,不保证首帧。 首帧使用 render 事件或 metrics.firstFrameAt !== null 判断。 timeUpdate 单位是毫秒;recordingTimestamp 单位是秒。

同一 URL 再次 play() 或自动重连会复用已保存的 headersformatretryreadTimeoutMs 快照;显式传 {} 可清空配置。切换到不同 URL 且未传 options 时 使用空配置,避免旧源的鉴权信息泄漏到新源。主动播放会取消旧退避任务, 在 retry 回调内主动换流同样生效。双层重试限流和错误触发矩阵见 Live 重连契约

HEVC 软解(@beta)

⚠️ 商用前置:HEVC/H.265 涉及标准必要专利(HEVC Advance、MPEG LA、独立持有人),无论用哪种 codec 实现,只要发生 HEVC 解码就触发专利授权义务。商用前必须完成专利授权确认。详见 HEVC 专利授权与商用合规。默认 decodeMode: "webcodecs" 不加载软解;启用 auto/software 前请确认授权。

flowLivePlayer 支持 HEVC 软件解码兜底,软解后端由独立 npm 包 @flow-player/software-decoder-ffmpeg(FFmpeg→WASM / LGPL v2.1+)交付,不进入本包(包隔离 release gate 强约束)。接入需额外安装该包。

| decodeMode | 行为 | | --------------------- | ------------------------------------------------------------------------------------------------------- | | "webcodecs"(默认) | 仅 WebCodecs 硬解;不加载软解资源,零回归 | | "auto" | WebCodecs 不可用时自动软解兜底(仅 decoder-capability 错误触发,排除 keyframe/seek/network 暂态错误) | | "software" | 强制软解(HEVC);不支持的 codec(如 H.264)自动回退 WebCodecs |

import { flowLivePlayer } from "@flow-player/browser-runtime";
import PlayerWorker from "@flow-player/browser-runtime/worker?worker";
import wasmUrl from "@flow-player/browser-runtime/wasm/flow_player_wasm.wasm?url";
import { ffmpegSoftwareDecoderOptions } from "@flow-player/software-decoder-ffmpeg";

const player = new flowLivePlayer({
  canvas,
  workerFactory: () => new PlayerWorker(),
  wasmUrl,
  decodeMode: "auto", // @beta
  ...ffmpegSoftwareDecoderOptions(), // 提供 softwareDecoderUrl + softwareDecoderWorkerUrl
});

安装:

npm i @flow-player/browser-runtime @flow-player/software-decoder-ffmpeg

软解触发时 metrics.decoderLoadingState 流转:idleloadingdecoder-ready / decoder-errormetrics.decoderWasmMemoryPeakMb 记录 WASM 内存峰值。decode-error 事件携带 classifyWebCodecsError 分类(decoder-capability / keyframe-required / keyframe-wait / seek-flush / network)。

⚠️ 软解包运行时产物内含 FFmpeg 9.0.1 WASM 构建(LGPL v2.1+),分发含软解的产物时请确认 LGPL 合规义务(提供对应源代码 / 重新链接机制)。HEVC 标准必要专利授权独立于软件 license(详见 专利文档)。API 标注 @beta,后续版本可能调整。

flowVodPlayer

| Method | Description | | ----------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | | playList(segments: FlowVodRecordSegment[]) | Load and start VOD playback | | seek(timestampMs) | Seek to absolute millisecond timestamp | | pause() / resume() | Playback control | | nextSegment() / previousSegment() | Segment navigation | | nextFrame() / previousFrame() | Frame-stepping | | setRate(rate) | Playback speed. rate > 0 = forward (e.g. 2 = 2×), rate < 0 = reverse (e.g. -4 = backward 4×, \|rate\| must be 1/2/4/8). 0 rejects. | | setPlaybackDirection("forward"\|"backward", speed?) | Reverse playback control. Equivalent to setRate(-speed) for backward. | | setGapPolicy(policy: FlowVodGapPolicy) | Gap handling ("jump" or "hold") | | syncTo(targetMs, options?) | Multi-player sync. Default smooth trim uses an adaptive absolute delta: max(baseRate × 0.4, 1×); pass smoothRates to override. |

seek() / hard syncTo() targets in a gap or outside the playlist use the configured gapPolicy consistently instead of entering the pipeline error state. Every seek settles exactly once via seek-settled with { targetMs, actualMs, errorMs, latencyMs, direction, generation, cancelled?, reason? }: either a real arrival, or a cancelled: true settlement (reason: "superseded" when a newer seek on the same player took over, "direction-change" when a true direction flip voided the in-flight seek, "reset" when playList() replaced the list), so multi-player consumers can stop polling getPrecisePosition() to detect settlement and never stall on a superseded seek.

方向切换(setPlaybackDirection 真翻转 / setRate 负数)立即下发 worker:directionchange 即代表解码方向开始切换,不再等待在途 seek 落定。在途 seek 被翻转作废时以 seek-settled { cancelled: true, reason: "direction-change" } 结算;同方向仅切档(speed-only) 遇在途 seek 仍延后到落定后生效。

MP4 分片加载、解码或渲染失败时,播放器在保留既有 error 事件的同时派发 segment-load-error。其 detail 包含脱敏后的 segmentUrl / nextSegmentUrlsegmentIndexhttpStatus、从 0 开始的 retryCountisLastSegmentlayercausemessage。同一播放列表内同一分片连续失败达到 segmentLoadFailureThreshold(默认 3)后,额外派发一次 unrecoverable-error;该阈值只 用于错误升级观测,播放器不会自动重试或跳过分片。成功出帧或重新 playList() 会清空 连续失败序列和 snapshot.lastLoadError

import {
  flowVodPlayer,
} from "@flow-player/browser-runtime";

const player = flowVodPlayer({
  segmentLoadFailureThreshold: 3,
});

player.addEventListener("segment-load-error", (event) => {
  const detail = event.detail;
  console.warn(detail.cause, detail.httpStatus, detail.retryCount);
});

player.addEventListener("unrecoverable-error", (event) => {
  const detail = event.detail;
  console.error(detail.segmentUrl, detail.totalRetries, detail.lastError);
});

| Property | Type | | -------------- | ----------------------------------------------------- | | state | "idle" \| "loading" \| "playing" \| "paused" \| ... | | rate | number | | snapshot | FlowVodPlayerSnapshot | | effectiveSpeed | number(正放为正,倒放为负) |

VOD API 与事件契约

根入口和 /vod 子路径均导出 FlowVodPlayUrlOptionsFlowVodSeekMappingFlowVodPlayerEventMapnew FlowVodPlayer(options)flowVodPlayer(options) 等价。

| 操作 / 事件 | 返回值或载荷与含义 | | --- | --- | | playList / playUrl / pause / resume / setRate / setPlaybackSpeed | 同步返回 FlowVodPlayerSnapshot,不等待网络或目标帧 | | seek / nextSegment / previousSegment / nextFrame / previousFrame | 返回 FlowVodSeekMappingnull(缺口 hold 或无相邻片段) | | statechange | 完整快照;UI 状态以此为准 | | seek | 请求已映射到片段;尚未保证目标帧呈现 | | seek-settled | 每个 seek 恰好一次:真实落定含 target/actual/error/latency/generation;被顶替/方向切换/列表重置/素材末端时 cancelled: true + reason(actual/error/latency 为占位值) | | stream-end | 播放列表素材末端到达(state 进入 ended)时一次性派发:{ finalPositionMs, segmentIndex, direction, source }。消费方据此渲染"录像结束"占位,与网络卡顿(error/buffering)区分 | | timeupdate | { positionMs, rate };位置为绝对时间轴毫秒,rate 为有符号倍率(direction 模式下倒放为负、档位切换立即可见),与 snapshot.rate/effectiveSpeed 同口径 | | buffering | { buffering } 后端 stall 通知;loading/paused 等状态下可能不改变播放状态 | | destroy | 销毁完成时的快照;现有契约不额外发送 destroyed 的 statechange |

addEventListener / removeEventListener 根据事件名推导 CustomEvent.detail, 保留标准 EventTarget 的 { once, signal }、对象监听器及自定义事件支持。 异步播放错误通过 error 观测;同步参数错误通过异常反馈。 无效 playList 不改变旧列表、pending seek 或暂停意图。 当前 runtime/worker 通过消息 envelope 的 seekRequestId 隔离旧进度和迟到超时, 旧 worker 缺少该字段时保持原有兼容行为。停止时钟确认不能结算 pending seek; runtime 的一次 progress 消息只派发一次载荷一致的事件。

seek-error 恢复契约(自动恢复与 recover())

seek 超时以 error { source: "seek", code: "seek-error", targetMs, generation } 派发,失败目标保留在播放器内。恢复配额关系如下:

  • 上游自动恢复:每次 seek-error 事故自动以重启管线路径重发同一目标一次 (300ms 退避)。settle 成功或重新 playList() 后配额刷新;底层原因不消除时 不会形成重试风暴。
  • recover():显式恢复入口,每次调用重发最后一次失败目标(无失败记录时 仅重建当前段管线)。与自动恢复配额相互独立——自动恢复已消耗后仍可调用。
  • 消费方指引:收到 seek-error 后推荐调用一次 recover() 而非重建会话; 同一目标连续第 3 次 seek-error(自动恢复与 recover() 均已失败)说明会话 已僵死,建议重建会话(重新 playList() 或新建实例)。多路同步场景不要在 syncTo 失败路径上再自行包一层重试——与 recover() 叠加会形成双层重试。
  • 慢供给预算:decoder 重建挂起(等目标段数据到位才能开始重配)的超时预算 与数据供给节奏解耦:供给仍在推进(字节持续到达/请求在途)时挂起等待自动 顺延(每次续期派发 diagnostic"Seek suspension extended",总上限 30s); 只有数据已到位而重配仍未完成,才在一个预算窗(8s)内判 seek-error。 NVR 夜间高峰等慢供给场景的初始 seek 不再被 8s 预算误杀。
player.addEventListener("error", (event) => {
  const detail = (event as CustomEvent).detail;
  if (detail?.code === "seek-error") {
    // 推荐动作:一次 recover() 而非重建会话;连续第 3 次失败再重建。
    player.recover();
  }
});
player.addEventListener("seek-settled", (event) => {
  console.log(event.detail.actualMs, event.detail.errorMs);
}, { once: true });

参数与事件质量门禁:新增或变更公共 API 必须同时通过源码、workspace 声明、 发布生成声明三种入口的 consumer 编译测试,并对事件次数、取消与重入行为补回归。 现有用例为 test/public-api-types.test.tstest/player-events.test.ts,随包单测执行。

FlowVodRecordSegment

interface FlowVodRecordSegment {
  url: string;
  startTimestamp: number;
  endTimestamp: number;
  timeLen: number;
  fileSize: number;
  channelID: number | string;
  RecordType: number | string;
}

Tree Shaking

Package is "sideEffects": false with per-module output. Modern bundlers (Vite, webpack 5, Rollup, esbuild) will remove unused code automatically. Importing only flowLivePlayer excludes VOD from your bundle.

License

MIT

Development

# Build WASM (requires Rust wasm32-unknown-unknown target)
npm run build:wasm

# Build npm-grade package
npm run build:package

# Verify before publish
npm run publish:check

# Full test suite
npm run test:smoke

# Type check
npm run typecheck