Add longer variant to VideoSample.draw

This commit is contained in:
Vanilagy
2025-07-15 11:49:33 +02:00
parent c5d299c4ec
commit cf0bbe896a
6 changed files with 143 additions and 14 deletions
+13 -6
View File
@@ -16,13 +16,20 @@
source
});
const videoTrack = await input.getPrimaryVideoTrack();
const canvas = document.createElement('canvas');
canvas.width = 1920;
canvas.height = 1080;
canvas.style.background = 'ghostwhite';
const ctx = canvas.getContext('2d');
document.body.append(canvas);
const sink = new Mediabunny.EncodedPacketSink(videoTrack);
for await (const packet of sink.packets()) {
if (packet.timestamp >= 2) break;
console.log(packet);
}
const videoTrack = await input.getPrimaryVideoTrack();
const sink = new Mediabunny.VideoSampleSink(videoTrack);
const sample = await sink.getSample(3);
console.log(sample);
sample.draw(ctx, 1500, 500, 50, 50, 0, 0);
/*
let timestamps = [];
+16 -4
View File
@@ -309,17 +309,29 @@ The `VideoFrame` returned by this method **must** be closed separately from the
---
It's also common to draw video samples to a `<canvas>` element or an `OffscreenCanvas`. For this, you can use the following method:
It's also common to draw video samples to a `<canvas>` element or an `OffscreenCanvas`. For this, you can use the following methods:
```ts
draw(
context: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D,
dx: number,
dy: number,
dWidth?: number,
dHeight?: number,
dWidth?: number, // defaults to displayWidth
dHeight?: number, // defaults to displayHeight
): void;
draw(
context: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D,
sx: number,
sy: number,
sWidth: number,
sHeight: number,
dx: number,
dy: number,
dWidth?: number, // defaults to sWidth
dHeight?: number, // defaults to sHeight
): void;
```
This method is similar to [drawImage](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/drawImage) and paints the video frame at the given position with the given dimensions. If the dimensions aren't specified, they will default to the display dimensions. This method will automatically draw the frame with the correct rotation based on its `rotation` property.
These methods behave like [drawImage](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/drawImage) and paint the video frame at the given position with the given dimensions. This method will automatically draw the frame with the correct rotation based on its `rotation` property.
If you want to draw the raw underlying image to a canvas directly (without respecting the rotation metadata), then you can use the following method:
```ts
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "mediabunny",
"version": "1.0.6",
"version": "1.1.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "mediabunny",
"version": "1.0.6",
"version": "1.1.0",
"license": "MPL-2.0",
"dependencies": {
"@types/dom-mediacapture-transform": "^0.1.11",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "mediabunny",
"author": "Vanilagy",
"version": "1.0.6",
"version": "1.1.0",
"description": "Pure TypeScript media toolkit for reading, writing, and converting media files, directly in the browser.",
"type": "module",
"main": "./dist/bundles/mediabunny.cjs",
+1
View File
@@ -737,6 +737,7 @@ class VideoDecoderWrapper extends DecoderWrapper<VideoSample> {
// Round the timestamps to the time resolution
sample.setTimestamp(Math.round(sample.timestamp * this.timeResolution) / this.timeResolution);
sample.setDuration(Math.round(sample.duration * this.timeResolution) / this.timeResolution);
sample.setRotation(this.rotation);
this.onSample(sample);
}
+111 -2
View File
@@ -366,9 +366,79 @@ export class VideoSample {
context: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D,
dx: number,
dy: number,
dWidth: number = this.displayWidth,
dHeight: number = this.displayHeight,
dWidth?: number,
dHeight?: number,
): void;
/**
* Draws the video sample to a 2D canvas context. Rotation metadata will be taken into account.
*
* @param sx - The x-coordinate of the top left corner of the sub-rectangle of the source image to draw into the
* destination context.
* @param sy - The y-coordinate of the top left corner of the sub-rectangle of the source image to draw into the
* destination context.
* @param sWidth - The width of the sub-rectangle of the source image to draw into the destination context.
* @param sHeight - The height of the sub-rectangle of the source image to draw into the destination context.
* @param dx - The x-coordinate in the destination canvas at which to place the top-left corner of the source image.
* @param dy - The y-coordinate in the destination canvas at which to place the top-left corner of the source image.
* @param dWidth - The width in pixels with which to draw the image in the destination canvas.
* @param dHeight - The height in pixels with which to draw the image in the destination canvas.
*/
draw(
context: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D,
sx: number,
sy: number,
sWidth: number,
sHeight: number,
dx: number,
dy: number,
dWidth?: number,
dHeight?: number,
): void;
draw(
context: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D,
arg1: number,
arg2: number,
arg3?: number,
arg4?: number,
arg5?: number,
arg6?: number,
arg7?: number,
arg8?: number,
) {
let sx = 0;
let sy = 0;
let sWidth = this.displayWidth;
let sHeight = this.displayHeight;
let dx = 0;
let dy = 0;
let dWidth = this.displayWidth;
let dHeight = this.displayHeight;
if (arg5 !== undefined) {
sx = arg1!;
sy = arg2!;
sWidth = arg3!;
sHeight = arg4!;
dx = arg5;
dy = arg6!;
if (arg7 !== undefined) {
dWidth = arg7;
dHeight = arg8!;
} else {
dWidth = sWidth;
dHeight = sHeight;
}
} else {
dx = arg1;
dy = arg2;
if (arg3 !== undefined) {
dWidth = arg3;
dHeight = arg4!;
}
}
if (!(
(typeof CanvasRenderingContext2D !== 'undefined' && context instanceof CanvasRenderingContext2D)
|| (
@@ -378,6 +448,18 @@ export class VideoSample {
)) {
throw new TypeError('context must be a CanvasRenderingContext2D or OffscreenCanvasRenderingContext2D.');
}
if (!Number.isFinite(sx)) {
throw new TypeError('sx must be a number.');
}
if (!Number.isFinite(sy)) {
throw new TypeError('sy must be a number.');
}
if (!Number.isFinite(sWidth) || sWidth < 0) {
throw new TypeError('sWidth must be a non-negative number.');
}
if (!Number.isFinite(sHeight) || sHeight < 0) {
throw new TypeError('sHeight must be a non-negative number.');
}
if (!Number.isFinite(dx)) {
throw new TypeError('dx must be a number.');
}
@@ -395,6 +477,29 @@ export class VideoSample {
throw new Error('VideoSample is closed.');
}
// The provided sx,sy,sWidth,sHeight refer to the final rotated image, but that's not actually how the image is
// stored. Therefore, we must map these back onto the original, pre-rotation image.
if (this.rotation === 90) {
[sx, sy, sWidth, sHeight] = [
sy,
this.codedHeight - sx - sWidth,
sHeight,
sWidth,
];
} else if (this.rotation === 180) {
[sx, sy] = [
this.codedWidth - sx - sWidth,
this.codedHeight - sy - sHeight,
];
} else if (this.rotation === 270) {
[sx, sy, sWidth, sHeight] = [
this.codedWidth - sy - sHeight,
sx,
sHeight,
sWidth,
];
}
const source = this.toCanvasImageSource();
context.save();
@@ -412,6 +517,10 @@ export class VideoSample {
context.drawImage(
source,
sx,
sy,
sWidth,
sHeight,
-dWidth / 2,
-dHeight / 2,
dWidth,