見出し画像

JavaScript VideoDecoderを使った高機能動画再生プログラム

標準の<video>動画再生ではできない機能を実装した動画再生プログラムです。プレーヤーアプリではないので簡易的なUIのみの実装です。
高速&安定したシーク、複数動画の同期再生、オーディオ遅延補正、コマ送り戻し(ステップ再生)に対応、ストリーミング再生には対応していません(動画ファイルの再生のみ)。
ソースコードはすべて自作(オリジナル)、オープンソース、生成AIは使っていません。

対応ファイルとコーデック

動作環境

Windows Edge/Chrome
まだ一般的ではないAPIを使用しているため、これだけです。(MacOSのEdge/Chromeは未確認)

webmファイル

 映像 : VP8 VP9
 音声 : OPUS

mp4 movファイル

 映像 : avc1
 音声 : mp4a

※出力アプリやエンコーダーによって再生できないことがあります。

確認済みファイル

mp4 AVUTL 拡張x264出力
mp4 Win+G画面録画
mov iMovie ※2年前ぐらいに作成したもの
mov iPad画面収録 ※
webm JavaScript MediaRecorder

Win+G画面録画MP4 avc1
AVUTL 拡張x264出力 MP4 avc1
iPad画面収録 MOV avc1

機能説明

高速&安定したシーク

<video>の動画再生はストリーミング用のためか、シークを連続して行うと停止したりシークに時間が掛かることがあります(edge chrome safari)。
シークを連続発行しても、処理が渋滞しないように古いシーク処理を素早く中断させることで、高速&安定したシーク処理を実現しています。

同期再生

<video>では時間(時刻)を指定した再生はできません。同時にplayしてもずれます(ランダム)。
再生時に時刻を指定することで複数の動画/音声を同期再生可能です。
同期するための時計はWeb Audio APIの時刻を利用しています。
ブラウザ(JavaScript)の仕様により、精度はms単位

同期再生
<video>同時再生

オーディオ遅延補正

映像と音のずれを補正できます。
音声だけ早く再生を開始することで実現しています。

コマ送り戻し

意外と実装されない機能、音ゲーの練習のときに欲しかったので作成しました。進む方向は高速、戻りは低速(毎回シーク処理)。

使用例(ソースコード)

動画再生クラスの提供です。簡易的なUIのみ。DIY。
処理の流れ
 ファイルの部分アクセスを行うインターフェイスを用意
 動画クラスの作成(new)
 load処理 引数に読み込みインターフェイス
 seek / play / pause
 画像は指定したキャンバスに出力
 音声はWeb Audio APIで再生

export async function main(){
  
    let canvas = <HTMLCanvasElement>document.getElementById("canvas");
    let ctx = <CanvasRenderingContext2D>canvas.getContext("2d");
    let canvas2 = <HTMLCanvasElement>document.getElementById("canvas2");
    let ctx2 = <CanvasRenderingContext2D>canvas2.getContext("2d");
    let seek = <HTMLInputElement>document.getElementById("seek");
    let inc = <HTMLButtonElement>document.getElementById("inc");
    let dec = <HTMLButtonElement>document.getElementById("dec");
    let btnPlay = <HTMLButtonElement>document.getElementById("btn-play");
    let video = <HTMLVideoElement>document.getElementById("video");
    let video2 = <HTMLVideoElement>document.getElementById("video2");

    // ローカルファイル
    let sigFile = new SignalWait<File>();
    let inputFile = <HTMLInputElement>document.getElementById("file");
    inputFile.oninput = ()=>{
        if(inputFile.files && inputFile.files[0]){
            sigFile.signal(inputFile.files[0])
        }
        inputFile.oninput = null;
        inputFile.remove();
    }
    let blob = await sigFile.wait();
    let file = new BlobBinaryReadIF(blob);
    
    // // fetchで部分アクセス
    // let file = new FetchFile();
    // await file.open("./video.webm");
   
    // // fetch>blob
    // let blob = await (await fetch("./video.webm")).blob();
    // let readeIF = new BlobBinaryReadIF(blob);
    

    let player  = new VideoPlayer();
    
    // オーディオ遅延補正
    player.latencyAudio = player.latencyAudioOutput;

    btnPlay.onclick = ()=>{
        player.resume();//やらなくてもいいような UI操作したらオーディオ出力有効?
       
        if(player.playing){
            player.pause();
            player.waitTask().then(()=>{
                //console.log("pause")
            })
        }else{
            player.play();
            player.waitTask().then(()=>{
                //console.log("play")
            })
        }
    }

    player.setTarget(ctx);
    await player.load(file);
    player.volume = 0.7;//音量

    inc.onclick = ()=>{
        player.nextFrame();
    }
    dec.onclick = ()=>{
        player.prevFrame();
    }
  
    seek.min = "0";
    seek.max = "" + player.duration;
    seek.step = "0.1";
    player.onTimeUpdate = (timeSec)=>{
        seek.value = "" + timeSec;
    }
    seek.oninput = ()=>{
        let time = parseFloat(seek.value);
        player.seek(time);
    }
}

2つの動画を同期再生

 // 同じAudioContextを使用する(同じ時計を参照)
 let ctxAudio = new WebAudioContext;
        
 let file1 = new FetchFile();
 await file1.open("./video.webm");
 let player1  = new VideoPlayer(ctxAudio);
 player1.setTarget(ctx);
 await player1.load(file1);

 let file2 = new FetchFile();
 await file2.open("./video.webm");
 let player2  = new VideoPlayer(ctxAudio);
 player2.setTarget(ctx2);
 await player2.load(file2);

 await player1.waitSeek();
 await player2.waitSeek();

 // 0.5秒後に再生開始
 let time = 0.5 + player1.getClockTimeSec(); 
 player1.play(time)
 player2.play(time)

ソースコード

すぐに利用できるjsファイルとTypeScriptのプロジェクトをまとめたzipファイルです。VIdeoDecoderを使用するためには、HTTPSのセキュア通信かlocalhost接続が必要になります。

ダウンロード警告が
ブラウザのセキュリティー設定によってダウンロードできない場合があるようです。他のブラウザを使用するか、一時的にセキュリティー設定を変更してダウンロードしてください。
localhost接続(オフライン)を前提にしたプログラムなので問題はないと思いますが、念のため実行前にソースコードの内容を確認をしてください。

ここから先は

0字 / 1ファイル

¥ 1,500

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