Add VideoSampleColorSpace

This commit is contained in:
Vanilagy
2025-12-16 18:47:50 +01:00
parent ea9ba96ae3
commit 228721d68f
3 changed files with 66 additions and 4 deletions
+1
View File
@@ -168,6 +168,7 @@ export {
AudioSampleCopyToOptions,
VideoSample,
VideoSampleInit,
VideoSampleColorSpace,
CropRectangle,
} from './sample';
export {
+38 -4
View File
@@ -125,7 +125,7 @@ export class VideoSample implements Disposable {
/** The duration of the frame in seconds. */
readonly duration!: number;
/** The color space of the frame. */
readonly colorSpace!: VideoColorSpace;
readonly colorSpace!: VideoSampleColorSpace;
/** The width of the frame in pixels after rotation. */
get displayWidth() {
@@ -216,7 +216,7 @@ export class VideoSample implements Disposable {
this.rotation = init.rotation ?? 0;
this.timestamp = init.timestamp!;
this.duration = init.duration ?? 0;
this.colorSpace = new VideoColorSpace(init.colorSpace);
this.colorSpace = new VideoSampleColorSpace(init.colorSpace);
} else if (typeof VideoFrame !== 'undefined' && data instanceof VideoFrame) {
if (init?.rotation !== undefined && ![0, 90, 180, 270].includes(init.rotation)) {
throw new TypeError('init.rotation, when provided, must be 0, 90, 180, or 270.');
@@ -239,7 +239,7 @@ export class VideoSample implements Disposable {
this.rotation = init?.rotation ?? 0;
this.timestamp = init?.timestamp ?? data.timestamp / 1e6;
this.duration = init?.duration ?? (data.duration ?? 0) / 1e6;
this.colorSpace = data.colorSpace;
this.colorSpace = new VideoSampleColorSpace(data.colorSpace);
} else if (
(typeof HTMLImageElement !== 'undefined' && data instanceof HTMLImageElement)
|| (typeof SVGImageElement !== 'undefined' && data instanceof SVGImageElement)
@@ -308,7 +308,7 @@ export class VideoSample implements Disposable {
this.rotation = init.rotation ?? 0;
this.timestamp = init.timestamp!;
this.duration = init.duration ?? 0;
this.colorSpace = new VideoColorSpace({
this.colorSpace = new VideoSampleColorSpace({
matrix: 'rgb',
primaries: 'bt709',
transfer: 'iec61966-2-1',
@@ -805,6 +805,40 @@ export class VideoSample implements Disposable {
}
}
/**
* Describes the color space of a {@link VideoSample}. Corresponds to the WebCodecs API's VideoColorSpace.
* @group Samples
* @public
*/
export class VideoSampleColorSpace {
/** The color primaries standard used. */
readonly primaries: VideoColorPrimaries | null;
/** The transfer characteristics used. */
readonly transfer: VideoTransferCharacteristics | null;
/** The color matrix coefficients used. */
readonly matrix: VideoMatrixCoefficients | null;
/** Whether the color values use the full range or limited range. */
readonly fullRange: boolean | null;
/** Creates a new VideoSampleColorSpace. */
constructor(init?: VideoColorSpaceInit) {
this.primaries = init?.primaries ?? null;
this.transfer = init?.transfer ?? null;
this.matrix = init?.matrix ?? null;
this.fullRange = init?.fullRange ?? null;
}
/** Serializes the color space to a JSON object. */
toJSON(): VideoColorSpaceInit {
return {
primaries: this.primaries,
transfer: this.transfer,
matrix: this.matrix,
fullRange: this.fullRange,
};
}
}
const isVideoFrame = (x: unknown): x is VideoFrame => {
return typeof VideoFrame !== 'undefined' && x instanceof VideoFrame;
};
+27
View File
@@ -0,0 +1,27 @@
import { test } from 'vitest';
import { AudioSample, VideoSample } from '../../src/sample.js';
test('VideoSample creation from bytes', async () => {
const bytes = new Uint8Array(1024);
const sample = new VideoSample(bytes, {
codedWidth: 1280,
codedHeight: 720,
format: 'RGBA',
timestamp: 0,
});
sample.close();
});
test('AudioSample creation from bytes', async () => {
const bytes = new Uint8Array(1024);
const sample = new AudioSample({
data: bytes,
numberOfChannels: 2,
format: 's16',
sampleRate: 48000,
timestamp: 0,
});
sample.close();
});