JavaScript API VideoEncoder/Decoderの使い方

最近(?)追加された動画用のエンコード・デコードを行うAPIを使ってみました。キャンバスの画像(2フレーム)をエンコード(録画)、デコード(再生)するプログラムです。

ソースコード

export async function main(){
    let canvas = document.getElementById("canvas");
    if(!(canvas instanceof HTMLCanvasElement))throw canvas;
    let ctx = canvas.getContext("2d");
    if(!(ctx instanceof CanvasRenderingContext2D))throw ctx;

    let width = canvas.width;
    let height = canvas.height;
    ctx.clearRect(0,0, width, height);
    ctx.fillStyle = "white";
    

    let encoder = new EncodeVideo();
    let decoder = new DecodeVideo();
    let vconfig = {
        codec: "vp09.00.10.08",//prof0 lv1.0 8bit
        // codec: "vp8",
        width: width,
        height: height,
        bitrate: 5_000_000, //bps
        framerate: 30,
    };
    encoder.configure(vconfig);
    decoder.configure(vconfig);
   
    // エンコード 録画
    ctx.fillRect(width/4, height/4, width/2, height/2);
    let keyChunk = await encoder.encode(0, canvas, true);//keyframe
    ctx.clearRect(0,0, width, height);
    let deltaChunk = await encoder.encode(16666, canvas, false);
    console.log(keyChunk, deltaChunk)

    // デコード 再生
    let keyFrame = await decoder.decode(keyChunk);
    let deltaFrame = await decoder.decode(deltaChunk);
    console.log(keyFrame, deltaFrame)
    for(let i=0;i<10;++i){
        await new Promise<void>(rs=>setTimeout(rs,1000));
        ctx.drawImage(keyFrame, 0, 0);
        await new Promise<void>(rs=>setTimeout(rs,1000));
        ctx.drawImage(deltaFrame, 0, 0);
    }
    deltaFrame.close();
    keyFrame.close();
   
}

//古いTypeScriptは、VideoEncoderでエラー(定義未対応)
// 5.2.2を使用

export class EncodeVideo{
    encoder : VideoEncoder;
    signal : SignalWait;
    constructor(){
        let init : VideoEncoderInit = {
            output: (chunk, metadata) => {
                this.signal.signal(chunk);
            },
            error: (e) => {
                console.log(e.message);
            },
        };
        this.encoder = new VideoEncoder(init);
        this.signal = new SignalWait();
    }
    
    // config
    configure(config : VideoEncoderConfig){
        this.encoder.configure(config);
    }

    // エンコード簡易版 終了まで待つ
    async encode(timeUS : number, image : HTMLCanvasElement, keyframe : boolean){
        let frame = new VideoFrame(image,{ timestamp: timeUS});
        this.encoder.encode(frame, {keyFrame : keyframe});
        let chunk = await this.signal.wait();   
        frame.close();
        return chunk;
    }
}

export class DecodeVideo{
    decoder : VideoDecoder;
    signal : SignalWait;
    constructor(){
        let init : VideoDecoderInit = {
            output: (frame) => {
                this.signal.signal(frame);
            },
            error: (e) => {
                console.log(e.message);
            },
        };
        this.decoder = new VideoDecoder(init);
        this.signal = new SignalWait();
    }
    
    // config
    configure(config : VideoEncoderConfig){
        this.decoder.configure(config);
    }

    // デコード 簡易版 終了まで待つ
    // 使い終わったVideoFrameはclose()を忘れずに
    async decode(chunk : EncodedVideoChunk){
        this.decoder.decode(chunk);
        let frame = await this.signal.wait();   
        return frame;
    }
}

export class SignalWait<T=any>{
    resolve : any[];
    constructor(){
        this.resolve = [];
    }
    
    async wait() : Promise<T>{
        return new Promise((rs)=>{this.resolve.push(rs);});
    }

    signal(val : T){
        if(this.resolve !== undefined){
            for(let rs of this.resolve){
                rs(val);
            }
            this.resolve = [];
        }
    }
}

この記事が役に立ったという方は、サポートお願いします。今後の製作の励みになります。