Add CanvasDrain & simplify player

This commit is contained in:
David Payr
2024-12-27 18:45:45 +01:00
parent d597f215d4
commit 792d9bd396
8 changed files with 194 additions and 73 deletions
+1
View File
@@ -42,6 +42,7 @@ export {
EncodedVideoChunkDrain,
VideoFrameDrain,
EncodedAudioChunkDrain,
CanvasDrain,
AudioDataDrain,
AudioBufferDrain,
} from './media-drain';
+10
View File
@@ -75,6 +75,16 @@ export class InputVideoTrack extends InputTrack {
return this._backing.getRotation();
}
async getRotatedWidth() {
const rotation = await this._backing.getRotation();
return rotation % 180 === 0 ? this._backing.getWidth() : this._backing.getHeight();
}
async getRotatedHeight() {
const rotation = await this._backing.getRotation();
return rotation % 180 === 0 ? this._backing.getHeight() : this._backing.getWidth();
}
getDecoderConfig() {
return this._backing.getDecoderConfig();
}
+62 -11
View File
@@ -333,6 +333,61 @@ export class VideoFrameDrain extends BaseMediaFrameDrain<EncodedVideoChunk, Vide
}
}
type WrappedCanvas = {
canvas: HTMLCanvasElement;
timestamp: number;
duration: number;
};
export class CanvasDrain {
videoFrameDrain: VideoFrameDrain;
constructor(public videoTrack: InputVideoTrack, public dimensions?: { width: number; height: number }) {
this.videoFrameDrain = new VideoFrameDrain(videoTrack);
}
private async videoFrameToWrappedCanvas(frame: VideoFrame): Promise<WrappedCanvas> {
const width = this.dimensions?.width ?? await this.videoTrack.getRotatedWidth();
const height = this.dimensions?.height ?? await this.videoTrack.getRotatedHeight();
const rotation = await this.videoTrack.getRotation();
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const context = canvas.getContext('2d', { alpha: false });
assert(context);
context.translate(width / 2, height / 2);
context.rotate(rotation * Math.PI / 180);
context.translate(-width / 2, -height / 2);
const [imageWidth, imageHeight] = rotation % 180 === 0 ? [width, height] : [height, width];
context.drawImage(frame, (width - imageWidth) / 2, (height - imageHeight) / 2, imageWidth, imageHeight);
const result = {
canvas,
timestamp: frame.timestamp / 1e6,
duration: (frame.duration ?? 0) / 1e6,
};
frame.close();
return result;
}
async getCanvas(timestamp: number) {
const frame = await this.videoFrameDrain.getFrame(timestamp);
return frame && this.videoFrameToWrappedCanvas(frame);
}
async* canvases(startTimestamp = 0, endTimestamp = Infinity) {
for await (const frame of this.videoFrameDrain.frames(startTimestamp, endTimestamp)) {
yield this.videoFrameToWrappedCanvas(frame);
}
}
}
export class EncodedAudioChunkDrain extends BaseChunkDrain<EncodedAudioChunk> {
constructor(public audioTrack: InputAudioTrack) {
super();
@@ -407,11 +462,7 @@ export class AudioBufferDrain {
this.audioDataDrain = new AudioDataDrain(audioTrack);
}
private audioDataToWrappedArrayBuffer(data: AudioData | null): WrappedAudioBuffer | null {
if (!data) {
return null;
}
private audioDataToWrappedArrayBuffer(data: AudioData): WrappedAudioBuffer {
const audioBuffer = new AudioBuffer({
numberOfChannels: data.numberOfChannels,
length: data.numberOfFrames,
@@ -428,24 +479,24 @@ export class AudioBufferDrain {
const sampleDuration = 1 / data.sampleRate;
return {
const result = {
buffer: audioBuffer,
// Rounding the timestamp based on the sample duration removes audio playback artifacts
timestamp: Math.round(data.timestamp / 1e6 / sampleDuration) * sampleDuration,
};
data.close();
return result;
}
async getBuffer(timestamp: number) {
const data = await this.audioDataDrain.getData(timestamp);
return this.audioDataToWrappedArrayBuffer(data);
return data && this.audioDataToWrappedArrayBuffer(data);
}
async* buffers(startTimestamp = 0, endTimestamp = Infinity) {
for await (const data of this.audioDataDrain.data(startTimestamp, endTimestamp)) {
const result = this.audioDataToWrappedArrayBuffer(data);
data.close();
yield result;
yield this.audioDataToWrappedArrayBuffer(data);
}
}
}