Skip to content

yuv-render

npm package

NPM versionNPM Downloadsjsdelivr

一个基于 WebGL 的 YUV 视频帧渲染器,提供对 I420 格式视频数据的渲染能力,包含纹理管理、画布控制和资源释放功能。

功能特性

  • 支持 HTMLCanvasElement 容器绑定
  • 自动初始化 WebGL 渲染环境
  • 支持动态尺寸调整
  • 实现 YUV 三通道纹理分离渲染
  • 提供资源清理接口

类结构

typescript
class YUVRender {
  // 属性
  el: HTMLCanvasElement
  private webglContext: WebGLRenderingContext | null
  private yTexture: WebGLTexture | null
  private uTexture: WebGLTexture | null
  private vTexture: WebGLTexture | null

  // 构造方法
  constructor(el: HTMLCanvasElement)

  // 访问器
  get height(): number
  get width(): number

  // 方法
  setDimension(width: number, height: number): void
  render(data: Uint8Array): void
  clear(): void
  dispose(): void
}

API 文档

constructor(el: HTMLCanvasElement)

初始化渲染器,创建 WebGL 上下文并配置着色器程序

  • 参数:
    • el: 目标 canvas 元素
  • 初始化流程:
    1. 获取 WebGL 上下文
    2. 创建着色器程序
    3. 初始化缓冲区
    4. 创建 Y/U/V 三个纹理通道

get width(): number

获取画布当前宽度

  • 返回值:number 类型的 canvas 宽度值

get height(): number

获取画布当前高度

  • 返回值:number 类型的 canvas 高度值

setDimension(width: number, height: number): void

设置画布显示尺寸

  • 参数:
    • width: 画布宽度(像素)
    • height: 画布高度(像素)
  • 行为说明:
    • 直接修改 canvas.width/canvas.height 属性
    • 会触发 WebGL viewport 的重置

render(data: Uint8Array): void

渲染 YUV 数据到 canvas

  • 参数:
    • data: I420 格式视频数据(Uint8Array)
  • 数据处理流程:
  • 注意事项:
    • 数据必须符合 I420 格式规范
    • 宽高需预先通过 setDimension 设置
    • 每次调用会重新上传纹理数据

clear(): void

清除画布内容

  • 行为说明:
    • 调用 WebGL 的 clear(COLOR_BUFFER_BIT) 方法
    • 用于清除当前帧的渲染结果

dispose(): void

资源清理方法

  • 执行动作:
    1. 清除画布内容(调用 clear())
    2. 主动释放 WebGL 上下文(WEBGL_lose_context)
    3. 移除 canvas 元素
    4. 置空所有引用
  • 注意事项:
    • 应在组件销毁时调用
    • 调用后实例不可复用

使用示例

点击查看代码
vue
<script setup lang="ts">
  import { useTemplateRef, onMounted, shallowReactive } from 'vue';
  import { VPButton } from 'vitepress/theme';
  import { YUVRender } from 'yuv-render';

  const scope = shallowReactive({
    waiting: false
  });
  const canvas = useTemplateRef<HTMLCanvasElement>('canvas');
  let yuv: YUVRender;

  onMounted(()=> {
    const el = canvas.value!;
    yuv = new YUVRender(el);
    yuv.setDimension(768, 320);
  });

  const start = () => {
    scope.waiting = true;
    fetch(`${import.meta.env.BASE_URL}frame.json`)
      .then((res: Response) => res.json())
      .then((frameArray: ArrayBuffer) => {
        scope.waiting = false;
        yuv.render(new Uint8Array(frameArray));
      });
  };
</script>
<template>
  <div class="buttons">
    <VPButton
      text="Render"
      :disabled="scope.waiting"
      @click="start()"
    />
  </div>
  <div :class="{ 'yuv-render': true, 'v-waiting': scope.waiting }"
  >
    <canvas ref="canvas" />
    <div className="v-loading-spinner" />
  </div>
</template>
<style scoped lang="stylus">
  .buttons {
    margin-bottom: 16px;
  }
  .yuv-render
    display block
    box-sizing border-box
    background-color #000
    position relative
    padding 0
    width 768px
    height 320px

    canvas
      position absolute
      width 100%
      height 100%
      top 0
      left 0

  $primary-background-color = #2B333F
  $primary-background-transparency = 0.7
  $secondary-background-color = lighten($primary-background-color, 33%)
  $secondary-background-transparency = 0.5

  .v-loading-spinner
    display none
    position absolute
    top 50%
    left 50%
    margin -25px 0 0 -25px
    opacity 0.85
    // Need to fix centered page layouts
    text-align left
    border 6px solid rgba($primary-background-color, $primary-background-transparency)
    // border: 6px solid rgba(43, 51, 63, 0.5);
    box-sizing border-box
    background-clip padding-box
    width 50px
    height 50px
    border-radius 25px
    visibility hidden

  .v-seeking .v-loading-spinner, .v-waiting .v-loading-spinner
    display block
    // add a delay before actual show the spinner
    animation v-spinner-show 0s linear 0.3s forwards

  .v-loading-spinner:before, .v-loading-spinner:after
    content ''
    position absolute
    margin -6px
    box-sizing inherit
    width inherit
    height inherit
    border-radius inherit
    // Keep 100% opacity so they don't show through each other
    opacity 1
    border inherit
    border-color transparent
    border-top-color white

  // only animate when showing because it can be processor heavy
  .v-seeking .v-loading-spinner:before, .v-seeking .v-loading-spinner:after, .v-waiting .v-loading-spinner:before, .v-waiting .v-loading-spinner:after
    -webkit-animation v-spinner-spin 1.1s cubic-bezier(0.6, 0.2, 0, 0.8) infinite, v-spinner-fade 1.1s linear infinite
    animation v-spinner-spin 1.1s cubic-bezier(0.6, 0.2, 0, 0.8) infinite, v-spinner-fade 1.1s linear infinite

  .v-seeking .v-loading-spinner:before, .v-waiting .v-loading-spinner:before
    border-top-color rgb(255, 255, 255)

  .v-seeking .v-loading-spinner:after, .v-waiting .v-loading-spinner:after
    border-top-color rgb(255, 255, 255)
    -webkit-animation-delay 0.44s
    animation-delay 0.44s

  @keyframes v-spinner-show
    to
      visibility visible

  @keyframes v-spinner-show
    to
      visibility visible

  @keyframes v-spinner-spin
    100%
      transform rotate(360deg)

  @keyframes v-spinner-spin
    100%
      -webkit-transform rotate(360deg)

  @keyframes v-spinner-fade
    0%
      border-top-color $secondary-background-color

    20%
      border-top-color $secondary-background-color

    35%
      border-top-color white

    60%
      border-top-color $secondary-background-color

    100%
      border-top-color $secondary-background-color

  @keyframes v-spinner-fade
    0%
      border-top-color $secondary-background-color

    20%
      border-top-color $secondary-background-color

    35%
      border-top-color white

    60%
      border-top-color $secondary-background-color

    100%
      border-top-color $secondary-background-color
</style>

Last updated: