此页面收集了展示本库常用操作的一些简短代码片段。
读取文件元数据
import { Input, ALL_FORMATS, BlobSource } from 'mediabunny';
const input = new Input({
formats: ALL_FORMATS, // 支持所有文件格式
source: new BlobSource(file), // 假设是一个 File 实例
});
const duration = await input.computeDuration(); // 单位:秒
const allTracks = await input.getTracks(); // 所有轨道列表
// 提取视频元数据
const videoTrack = await input.getPrimaryVideoTrack();
if (videoTrack) {
await videoTrack.getDisplayWidth(); // 单位:像素
await videoTrack.getDisplayHeight(); // 单位:像素
await videoTrack.getRotation(); // 单位:顺时针角度
// 估算帧率(FPS)
const frameRateMetrics = await videoTrack.computeFrameRateMetrics();
const frameRate = frameRateMetrics.bestGuessFrameRate;
}
// 提取音频元数据
const audioTrack = await input.getPrimaryAudioTrack();
if (audioTrack) {
await audioTrack.getNumberOfChannels();
await audioTrack.getSampleRate(); // 单位:Hz
}
// 提取元数据标签
const tags = await input.getMetadataTags();
tags.title; // 标题
tags.date; // 发布日期
tags.images[0]; // 封面图
tags.raw['TBPM']; // 自定义标签
// ...读取媒体数据
import {
Input,
ALL_FORMATS,
BlobSource,
VideoSampleSink,
AudioSampleSink,
} from 'mediabunny';
const input = new Input({
formats: ALL_FORMATS,
source: new BlobSource(file),
});
// 读取视频帧
const videoTrack = await input.getPrimaryVideoTrack();
if (videoTrack) {
const decodable = await videoTrack.canDecode();
if (decodable) {
const sink = new VideoSampleSink(videoTrack);
// 获取时间戳为 5s 的视频帧
const videoSample = await sink.getSample(5);
videoSample.timestamp; // 单位:秒
videoSample.duration; // 单位:秒
// 将帧绘制到 canvas 上
videoSample.draw(ctx, 0, 0);
// 遍历视频前 30s 的所有帧
for await (const sample of sink.samples(0, 30)) {
// ...
}
}
}
// 读取音频块
const audioTrack = await input.getPrimaryAudioTrack();
if (audioTrack) {
const decodable = await audioTrack.canDecode();
if (decodable) {
const sink = new AudioSampleSink(audioTrack);
// 获取时间戳为 5s 的音频块;一段短小的音频
const audioSample = await sink.getSample(5);
audioSample.timestamp; // 单位:秒
audioSample.duration; // 单位:秒
audioSample.numberOfFrames;
// 转换为 AudioBuffer 以便与 Web Audio API 配合使用
const audioBuffer = audioSample.toAudioBuffer();
// 遍历音频前 30s 的所有样本
for await (const sample of sink.samples(0, 30)) {
// ...
}
}
}提取视频缩略图
import {
Input,
ALL_FORMATS,
BlobSource,
CanvasSink,
} from 'mediabunny';
const input = new Input({
formats: ALL_FORMATS,
source: new BlobSource(file),
});
const videoTrack = await input.getPrimaryVideoTrack();
if (videoTrack) {
const decodable = await videoTrack.canDecode();
if (decodable) {
const sink = new CanvasSink(videoTrack, {
width: 320, // 自动调整缩略图尺寸
});
// 获取时间戳为 10s 的缩略图
const result = await sink.getCanvas(10);
result.canvas; // HTMLCanvasElement | OffscreenCanvas
result.timestamp; // 单位:秒
result.duration; // 单位:秒
// 生成五个均匀分布的缩略图贯穿整个视频
const startTimestamp = await videoTrack.getFirstTimestamp();
const endTimestamp = await videoTrack.computeDuration();
const timestamps = [0, 0.2, 0.4, 0.6, 0.8].map(
(t) => startTimestamp + t * (endTimestamp - startTimestamp)
);
// 遍历这些时间戳
for await (const result of sink.canvasesAtTimestamps(timestamps)) {
// ...
}
}
}- 查看 缩略图生成示例 以运行此代码。
- 您可以进一步配置
CanvasSink。
提取编码包
import {
Input,
ALL_FORMATS,
BlobSource,
EncodedPacketSink,
} from 'mediabunny';
const input = new Input({
formats: ALL_FORMATS,
source: new BlobSource(file),
});
const videoTrack = await input.getPrimaryVideoTrack();
if (videoTrack) {
const sink = new EncodedPacketSink(videoTrack);
// 获取时间戳为 10s 的数据包
const packet = await sink.getPacket(10);
packet.data; // Uint8Array
packet.type; // 'key' | 'delta'
packet.timestamp; // 单位:秒
packet.duration; // 单位:秒
// 获取最接近时间戳 10s 的关键帧数据包
const keyPacket = await sink.getKeyPacket(10);
// 获取后续数据包
const nextPacket = await sink.getNextPacket(keyPacket);
// 设置手动解码器
const decoderConfig = await videoTrack.getDecoderConfig();
const videoDecoder = new VideoDecoder({
output: console.log,
error: console.error,
});
videoDecoder.configure(decoderConfig);
// 按解码顺序遍历所有数据包
for await (const packet of sink.packets()) {
videoDecoder.decode(packet.toEncodedVideoChunk());
}
await videoDecoder.flush();
}查看 EncodedPacketSink 以获取完整文档。
创建新的媒体文件
import {
AudioBufferSource,
BufferTarget,
CanvasSource,
Mp4OutputFormat,
Output,
Quality,
} from 'mediabunny';
// Output 代表一个新的媒体文件
const output = new Output({
format: new Mp4OutputFormat(), // 文件格式
target: new BufferTarget(), // 写入位置(此处为内存)
});
// 示例:添加由 Canvas 驱动的音视频轨道
const videoSource = new CanvasSource(canvas, {
codec: 'avc',
quality: new Quality('high'),
});
output.addVideoTrack(videoSource);
// 示例:添加由 AudioBuffers 驱动的音频轨道
const audioSource = new AudioBufferSource({
codec: 'aac',
quality: new Quality('high'),
});
output.addAudioTrack(audioSource);
// 设置一些元数据标签
output.setMetadataTags({
title: 'My Movie',
artist: 'Me',
});
await output.start();
// 添加一些视频帧
for (let frame = 0; ...) {
await videoSource.add(frame / 30, 1 / 30);
}
// 添加一些音频数据
await audioSource.add(audioBuffer1);
await audioSource.add(audioBuffer2);
await output.finalize();
const buffer = output.target.buffer; // 包含最终 MP4 文件的 ArrayBuffer直接写入磁盘
import {
Output,
StreamTarget,
} from 'mediabunny';
// File System API
const handle = await window.showSaveFilePicker();
const writableStream = await handle.createWritable();
const output = new Output({
// `chunked: true` 用于批量处理磁盘操作
target: new StreamTarget(writableStream, { chunked: true }),
// ...
});
// ...
await output.finalize();
// 文件已完全写入磁盘通过网络流式传输
import {
Output,
AppendOnlyStreamTarget,
Mp4OutputFormat,
} from 'mediabunny';
const { writable, readable } = new TransformStream<Uint8Array, Uint8Array>();
const output = new Output({
target: new AppendOnlyStreamTarget(writable),
// 我们必须在此使用仅追加格式,例如分片 MP4
format: new Mp4OutputFormat({ fastStart: 'fragmented' }),
});
const uploadComplete = fetch('https://example.com/upload', {
method: 'POST',
body: readable,
duplex: 'half',
headers: {
'Content-Type': output.format.mimeType,
},
});
await output.start();
// ...
await output.finalize();
await uploadComplete;- 此代码会自动处理慢速网络施加的反压(backpressure)。
- 阅读有关 仅追加格式 的更多信息,这是此模式的要求。
录制实时媒体
import {
BufferTarget,
MediaStreamAudioTrackSource,
MediaStreamVideoTrackSource,
Output,
Quality,
WebMOutputFormat,
} from 'mediabunny';
const userMedia = await navigator.mediaDevices.getUserMedia({
video: true,
audio: true,
});
const videoTrack = userMedia.getVideoTracks()[0];
const audioTrack = userMedia.getAudioTracks()[0];
const output = new Output({
format: new WebMOutputFormat(),
target: new BufferTarget(),
});
if (videoTrack) {
const source = new MediaStreamVideoTrackSource(videoTrack, {
codec: 'vp9',
quality: new Quality('medium'),
});
output.addVideoTrack(source);
}
if (audioTrack) {
const source = new MediaStreamAudioTrackSource(audioTrack, {
codec: 'opus',
quality: new Quality('medium'),
});
output.addAudioTrack(source);
}
await output.start();
// 等待...
await output.finalize();- 查看 直播录制演示 以查看此代码的运行效果。
- 这基本上就是
MediaRecorder,但更优秀。
创建透明视频
import {
BufferTarget,
CanvasSource,
Output,
Quality,
WebMOutputFormat,
} from 'mediabunny';
const output = new Output({
// 使用支持透明度的格式:
format: new WebMOutputFormat(),
target: new BufferTarget(),
});
const canvas = new OffscreenCanvas(1280, 720);
const context = canvas.getContext('2d', { alpha: true })!;
const source = new CanvasSource(canvas, {
codec: 'vp9',
quality: new Quality('medium'),
alpha: 'keep', // => 同时编码 Alpha 通道数据
});
output.addVideoTrack(source);
await output.start();
// 添加数据...
await source.add(0, 1 / 30);
// ...
await output.finalize();检查编码支持情况
import {
MovOutputFormat,
getFirstEncodableVideoCodec,
getFirstEncodableAudioCodec,
getEncodableVideoCodecs,
getEncodableAudioCodecs,
} from 'mediabunny';
const outputFormat = new MovOutputFormat();
// 查找给定容器格式的最佳受支持的编解码器
const bestVideoCodec = await getFirstEncodableVideoCodec(
outputFormat.getSupportedVideoCodecs(),
// 可选地,通过这些参数进行限制:
{ width: 1920, height: 1080 },
);
const bestAudioCodec = await getFirstEncodableAudioCodec(
outputFormat.getSupportedAudioCodecs(),
);
// 查找所有受支持的编解码器
const supportedVideoCodecs = await getEncodableVideoCodecs();
const supportedAudioCodecs = await getEncodableAudioCodecs();转换文件
import {
Input,
Output,
Conversion,
ALL_FORMATS,
BlobSource,
Mp4OutputFormat,
} from 'mediabunny';
// 查看上面的代码片段以获取更多 Input 和 Output 的示例
const input = new Input({
formats: ALL_FORMATS,
source: new BlobSource(file),
});
const output = new Output({
format: new Mp4OutputFormat(),
target: new BufferTarget(),
});
const conversion = await Conversion.init({ input, output });
if (!conversion.isValid) {
// 转换不可行,执行时会报错。
// 请检查 `discardedTracks` 以获取原因。
return;
}
// 不会出现在输出中的轨道列表:
conversion.discardedTracks;
conversion.onProgress = (progress) => {
progress; // 0 到 1 之间的数字(包含两端)
};
await conversion.execute();
// 转换完成
const buffer = output.target.buffer; // 包含最终 MP4 文件的 ArrayBuffer- 此代码在可能时会自动进行转复用(复制媒体数据),在必要时进行转码(重新编码媒体数据)。
- 完整文档请参阅 转换媒体文件。
提取音频
import {
Input,
Output,
Conversion,
WavOutputFormat,
} from 'mediabunny';
const input = new Input(...);
const output = new Output({
// 写入 .wav 文件,仅保留音频轨道
format: new WavOutputFormat(),
// ...
});
const conversion = await Conversion.init({
input,
output,
audio: {
sampleRate: 16000, // 重采样至 16 kHz
},
});
await conversion.execute();
// 转换完成- 您可以将音频提取到其他纯音频格式,例如 .mp3、.ogg 甚至 .m4a。请参阅 输出格式。
压缩媒体
import {
Conversion,
Input,
Output,
Quality,
} from 'mediabunny';
const input = new Input(...);
const output = new Output(...);
const conversion = await Conversion.init({
input,
output,
tracks: 'primary', // 仅保留每种类型的第一个轨道
video: {
width: 480, // 调整大小至 480p
quality: new Quality('low'),
},
audio: {
numberOfChannels: 1, // 重采样为单声道
quality: new Quality('low'),
},
trim: {
// 我们只保留前 60 秒
start: 0,
end: 60,
},
tags: {}, // 移除所有元数据标签
});
await conversion.execute();
// 转换完成- 查看 文件压缩示例 以了解此代码的实际运行效果。
添加视频叠加层
import {
Input,
Output,
Conversion,
} from 'mediabunny';
// 例如,让我们加载一个水印图片
const watermark = new Image();
watermark.src = '/watermark.jpg';
await new Promise(resolve => watermark.onload = resolve);
const input = new Input(...);
const output = new Output(...);
let ctx: CanvasRenderingContext2D | null = null;
const conversion = await Conversion.init({
input,
output,
video: {
process: (sample) => {
if (!ctx) {
// 创建一个用于图像合成的画布
const canvas = new OffscreenCanvas(
sample.displayWidth,
sample.displayHeight,
);
ctx = canvas.getContext('2d')!;
}
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
sample.draw(ctx, 0, 0);
ctx.drawImage(watermark, 32, 32);
return ctx.canvas;
},
},
});
await conversion.execute();
// 转换完成合并多个文件为一个
import {
Input,
Output,
Conversion,
} from 'mediabunny';
// 让我们从一个文件中获取视频轨道...
const videoInput = new Input(...);
// ...以及从另一个文件中获取音频轨道
const audioInput = new Input(...);
const output = new Output(...);
const videoConversion = await Conversion.init({
input: videoInput,
output,
composable: true, // 确保转换不拥有输出对象
audio: { discard: true },
});
const audioConversion = await Conversion.init({
input: audioInput,
output,
composable: true,
video: { discard: true },
});
await output.start();
await Promise.all([
videoConversion.execute(),
audioConversion.execute(),
]);
await output.finalize();
// 转换完成完整文档请参阅 可组合转换。
读取 HLS 播放列表
import { Input, UrlSource, HLS_FORMATS, desc } from 'mediabunny';
const input = new Input({
source: new UrlSource('https://example.com/master.m3u8'),
formats: HLS_FORMATS,
});
// 获取所有轨道
const tracks = await input.getTracks();
// 按质量获取视频轨道
const sortedVideoTracks = await input.getVideoTracks({
sortBy: async track => desc(await track.getDisplayHeight()),
});
// 选择一个质量
const bestVideoTrack = sortedVideoTracks[0]!;
// 获取匹配的音频轨道
const matchingAudioTrack = await bestVideoTrack.getPrimaryPairableAudioTrack();
// HLS 轨道可以像其他任何 Mediabunny InputTrack 一样读取
// ...
const isLive = await bestVideoTrack.isLive();
if (isLive) {
// 使用刷新间隔轮询一些数据,例如持续时间
let currentDuration: number | null = null;
const poll = async () => {
currentDuration = await bestVideoTrack.getDurationFromMetadata({
skipLiveWait: true,
});
const refreshInterval = await bestVideoTrack.getLiveRefreshInterval();
if (refreshInterval === null) {
return; // 不再是直播
}
setTimeout(poll, 1000 * refreshInterval);
};
await poll();
}深入指南请参阅 读取 HLS。