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 🙏

© 2025 – Pkg Stats / Ryan Hefner

tav-media

v0.3.2

Published

Cross platform media editing framework

Downloads

24

Readme

TAVMedia

TAVMedia 是一个跨平台的音视频编辑框架,支持在移动端、桌面端、浏览器端和服务器端使用。

Web 版使用指南

浏览器支持

TAVMedia 目前支持 Chrome 75+, Safari 12+.

安装

在 Web 工程中使用 TAVMedia,您可以直接使用 NPM 将 TAVMedia 安装到您的项目中

npm install tav-media

初始化 tav-media

import { initializeWasm, TAVWasmOptions } from 'tav-media';

const env: TAVWasmOptions = {
  /**
   * 可选,指定 wasm 文件在服务端的路径。如果不指定,则使用 `./tav-media-wasm.wasm`
   */
  locateFile: wasmFileName => `/node_modules/tav-media/bin/wasm/${wasmFileName}`,
  /**
   * 可选,后续资源的 path 为相对路径时,会自动添加此前缀
   */
  baseUrl: '/',
};

initializeWasm(env).then(async () => {
  // 调用业务初始化代码
});

License 指引

License 鉴权

在 TAVMedia SDK 中提供了鉴权的对应的 API ,可以参考使用:

/**
 * 验证授权信息
 */
async function authorize() {
  const authResult = await TAVLicense.Auth(
    // License url 或者 license 内容的 ArrayBuffer
    'assets/tav_media.license',
    // AppId
    'your_appid_here',
    // License secret
    'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
  );
  if (authResult !== OAuthErrorCode.OK) {
    // 根据验证结果进行进一步处理
    console.warn('授权失败,使用 TAVMedia 水印试用版');
  }
}

initializeWasm(env).then(async () => {
  // 加载 TAVMedia wasm 后验证 license
  await authorize();
});

基础功能

使用 TAVMedia 渲染视频

TAVMedia Web 版封装了 TAVMediaView 类, 封装了 TAVSurface, TAVVideoReader, TAVAudioReader 的创建和调用过程, 提供了适合 Web 播放使用的 API。视频图像渲染到指定的 canvas,音频会直接通过浏览器输出,暂时不支持读取每帧的音频合成数据

// 创建一个 MovieClip 对象
const ghost = await MovieAsset.MakeFromPath('assets/ghost.mp4');
const movie = await MovieClip.MakeFromAsset(ghost);
movie.duration = ghost.duration;

// 创建一个 TAVMediaView,将图像渲染到 id 为 stage 的 canvas
const view = TAVMediaView.MakeFromHtmlCanvas('#stage');

// 播放视频
await view.setMedia(movie);
view.play();

同时播放多个媒体

通过将多个媒体添加到 Composition 的方式,可以在场景中同时播放多个视频和特效。例如下面的视频转场的例子。

// 创建主时间轴
const root = new Composition();
root.width = 1280;
root.height = 720;
root.duration = 3_000_000;

// 添加第一个视频
const ghost = await MovieAsset.MakeFromPath('video-640x360.mp4');
const movie1 = await MovieClip.MakeFromAsset(ghost);
movie1.duration = ghost.duration;
movie1.startTime = 0;

// 添加第二个视频
const dog = await MovieAsset.MakeFromPath('video-640x360-2.mp4');
const movie2 = await MovieClip.MakeFromAsset(dog);
movie2.duration = dog.duration;
movie2.startTime = 800_000;

// 添加转场
const zcAsset = await PAGAsset.MakeFromPath('zc.pag');
const zc = await PAGEffect.MakeFromAsset(zcAsset);
zc.startTime = 800_000;
zc.duration = zcAsset.duration;
// 将两个视频作为转场效果的输入,在转场播放过程中,将根据设计师
// 指定的方式展示两个视频切换的效果。
zc.addInput(movie1);
zc.addInput(movie2);

// 添加到主时间轴
root.addClip(movie1);
root.addClip(movie2);
root.addClip(zc);

// 播放
await view.setMedia(root);
view.play();

层级结构

显示控制

Matrix 和 CropRect 控制位移缩放旋转裁切

const asset = await MovieAsset.MakeFromPath('video-640x360.mp4');
const clip = await MovieClip.MakeFromAsset(asset);
clip.duration = 3_000_000;

const matrix = new Matrix();
matrix.postTranslate(-asset.width / 2, -asset.height / 2);
matrix.postScale(2, 2);
matrix.postTranslate(asset.width / 2, asset.height / 2);

const cropRect = Rect.MakeXYWH(asset.width / 4, asset.height / 4, asset.width / 2, asset.height / 2);
clip.matrix = matrix;
clip.cropRect = cropRect;

Opacity

opacity是不透明度,通过设置opacity可以控制Movie的透明度。取值范围是0.0-1.0。0是完全透明,1是完全不透明。


const clip = await MovieClip.MakeFromAsset(asset);
clip.opacity = 0.5;

时间控制

通过 Clip 和 Composition 排布时间轴

const ghost = await MovieAsset.MakeFromPath('video-640x360.mp4');
const movie1 = await MovieClip.MakeFromAsset(ghost);
movie1.startTime = 1_000_000;
movie1.duration = 4_000_000;

// 创建一个分辨率为 720 * 1280 的Composition
// contentStartTime 为 1_000_000,表示从视频的第1秒开始播放
// contentDuration 为 10_000_000,表示播放 10 秒
const composition = await Composition.Make(720, 1280, 2_000_000, 10_000_000);
composition.duration = 10_000_000;
composition.addClip(movie1);

通过 MovieClip 裁剪资源片段

const ghost = await MovieAsset.MakeFromPath('video-640x360.mp4');
const movie1 = await MovieClip.MakeFromAsset(ghost, 1_000_000, 4_000_000);
movie1.startTime = 0;
movie1.duration = 4_000_000;

变速

// 通过 Movie 直接变速
const totalDuration = 10_000_000;
const asset = await MovieAsset.MakeFromPath('video-640x360.mp4');
const movie = await MovieClip.MakeFromAsset(asset, 0, totalDuration);
movie.startTime = 0;
movie.duration = totalDuration / 2;

// 通过 Composition 进行变速
const asset1 = await MovieAsset.MakeFromPath('video-640x360.mp4');
const movie1 = await MovieClip.MakeFromAsset(asset1, 0, totalDuration);
movie1.startTime = 0;
movie1.duration = totalDuration;
const composition = await Composition.Make(640, 360, 0, totalDuration);
composition.addClip(movie1);
composition.startTime = 0;
composition.duration = totalDuration / 2;

定格

// 通过 Movie 定格
const totalDuration = 10_000_000;
const asset = await MovieAsset.MakeFromPath('video-640x360.mp4');
const movie = await MovieClip.MakeFromAsset(asset, 1_000_000, 0);
movie.startTime = 0;
movie.duration = totalDuration;

// 通过 Composition 定格
const asset1 = await MovieAsset.MakeFromPath('video-640x360.mp4');
const movie1 = await MovieClip.MakeFromAsset(asset1, 0, totalDuration);
movie1.startTime = 0;
movie1.duration = totalDuration;
const composition = await Composition.Make(640, 360, 1_000_000, 0);
composition.addClip(movie1);
composition.startTime = 0;
composition.duration = totalDuration;

音频播放

TAVMedia的音频播放

const totalDuration = 10_000_000;
const path = 'hoaprox.mp3';
const asset = await AudioAsset.MakeFromPath(path);
const audio = await AudioClip.MakeFromAsset(asset, 0, totalDuration);
audio.duration = totalDuration;
const effect = await AudioVolumeEffect.MakeFIFOEffect(audio, 1.0, 3_000_000, 3_000_000);
effect.startTime = 0;
effect.duration = totalDuration;

添加效果

TransformEffect

const asset1 = await MovieAsset.MakeFromPath('video-640x360.mp4');
const movie1 = await MovieClip.MakeFromAsset(asset1);
movie1.startTime = 0;
movie1.duration = asset1.duration;

const transformEffect = TransformEffect.MakeTransformEffect();
const keyframes: NoBlankArray<Keyframe> = [
  TAVKeyframe.MakeLinear(0, 1_000_000, 0, 90),
];
transformEffect.transform2D = {
  rotation: TAVProperty.MakeAnimatableProperty(keyframes),
}
transformEffect.addInput(movie1);

TAVColorTuningEffect

const asset1 = await MovieAsset.MakeFromPath('video-640x360.mp4');
const movie1 = await MovieClip.MakeFromAsset(asset1);
movie1.startTime = 0;
movie1.duration = asset1.duration;

const colorTuningEffect = ColorTuningEffect.MakeColorTuningEffect();
const keyframes: NoBlankArray<Keyframe> = [
  TAVKeyframe.MakeLinear(0, 1_000_000, -50, 50),
];

// 设置饱和度
colorTuningEffect.colorTuning = {
  saturation: TAVProperty.MakeAnimatableProperty(keyframes),
}
colorTuningEffect.addInput(movie1);

LUTEffect

const asset1 = await MovieAsset.MakeFromPath('video-640x360.mp4');
const movie1 = await MovieClip.MakeFromAsset(asset1);
movie1.startTime = 0;
movie1.duration = asset1.duration;

const lutEffect = await LUTEffect.MakeFromPath('lut.png');
lutEffect.startTime = 0;
lutEffect.duration = 1_000_000;
lutEffect.strength = 0.5;
lutEffect.addInput(movie1);

ChromaMatting

const asset1 = await MovieAsset.MakeFromPath('video-640x360.mp4');
const movie1 = await MovieClip.MakeFromAsset(asset1);
movie1.startTime = 0;
movie1.duration = asset1.duration;

const chromaMattingEffect = ChromaMattingEffect.Make();
chromaMattingEffect.chromaMattingConfig = {
  intensity: 0.2,
  shadow: 0.5,
  selectedColor: {
    red: 0,
    green: 255,
    blue: 0,
    alpha: 255,
  },
}
chromaMattingEffect.startTime = 0;
chromaMattingEffect.duration = 1_000_000;
chromaMattingEffect.addInput(movie1);

PAGEffect

const asset1 = await MovieAsset.MakeFromPath('video-640x360.mp4');
const movie1 = await MovieClip.MakeFromAsset(asset1);
movie1.startTime = 0;
movie1.duration = asset1.duration;

const pagAsset = await PAGAsset.MakeFromPath('fw.pag');
const pagEffect = await PAGEffect.MakeFromAsset(pagAsset);
// numImgs 表示该 PAG File 最大支持的替换图层数量
const numImgs = pagEffect.numImages;

// 添加 movie1 作为 effect 的输入, 
// scaleMode 可以设置如何缩放 movie1 以适应 PAG 图层的大小
// editableIndex 表示 PAG File 中的第几个可替换图层
pagEffect.addInput(movie1, ScaleMode.LetterBox, 0);
pagEffect.startTime = 0;
// 这边可以根据需求设置成任意时长,例子中设置成文件时长
pagEffect.duration = pagAsset.duration;
const movieDuration = 3_000_000;
const asset1 = await MovieAsset.MakeFromPath('1.mp4');
const movie1 = await MovieClip.MakeFromAsset(asset1);
movie1.duration = movieDuration;

const asset2 = await MovieAsset.MakeFromPath('2.mp4');
const movie2 = await MovieClip.MakeFromAsset(asset2);
movie2.duration = movieDuration;

const pagAsset = await PAGAsset.MakeFromPath('zc.pag');
const pagEffect = await PAGEffect.MakeFromAsset(pagAsset);
const numImgs = pagEffect.numImages;
// 分别设置 movie1 和 movie2 替换 PAG file 中的第一个替换图层和第二个替换图层
// 替换哪两个替换图层需要根据 PAG 文件来决定,通常情况下 input0 对应 editableIndex0,input1 对应 editableIndex1
// 也可以通过 `addInput` 的 `editableIndex` 参数来指定
pagEffect.addInput(movie1);
pagEffect.addInput(movie2);
// 这边可以根据需求设置成任意时长,例子中设置成文件时长
pagEffect.duration = pagAsset.duration;
// 设置转场和 movie2 的开始时间为 movie1 的时长减去 PAG File 的时长,
// 这样 PAG Effect 能够将效果作用到 movie1 和 movie2 之间
movie2.startTime = movie1.duration - pagEffect.duration;
pagEffect.startTime = movie2.startTime;