LoginSignup
13
14

More than 5 years have passed since last update.

ブラウザで映像と音声を自在に操ろう、そして録画もね(CanvasとWebAuidoを使って)

Last updated at Posted at 2017-10-05

はじめに

この記事は、「WebRTCを試すときにオッサンが映り続ける問題に対処する」「WebRTCオッサン問題を超えて。ローカルファイルを自在に操りたい」の続編です。

また、Web Music Demo Party #1 のLT用の記事でもあります。

自己紹介

デモ

環境

手順

  • ローカルにあらかじめ映像ファイルx2と、音声ファイルx2 を準備
  • ファイルを指定
    • ローカルにある映像ファイル1を、左半分の領域の、左から1番目のビデオ(Video)領域にドラッグ&ドロップ
    • ローカルにある映像ファイル2を、左半分の領域の、左から2番目のビデオ(Video2)領域にドラッグ&ドロップ
    • ローカルにある音声ファイル1を、右半分の領域の、左から1番目のオーディオ(Audio)領域にドラッグ&ドロップ
    • ローカルにある音声ファイル2を、右半分の領域の、左から2番目のオーディオ(Audio2)領域にドラッグ&ドロップ
    • 参考操作イメージ(音声1つだけのケース)
    • file select
  • ミックス開始
    • 上下中央あたりの [Start] ボタンをクリック
    • その上のスライダーを左右に動かすと、映像1/音声1 ←→ 映像2/音声2 を指定した割合で表示
    • swipe / stripe / dissolve で切り替え方法を選択可能
    • [Full Screen]ボタンをクリックすると、ミックした結果を全画面表示
    • 参考操作イメージ
    • change_video

仕組み

映像(video)ファイルの変換

基本的な流れは「WebRTCを試すときにオッサンが映り続ける問題に対処する」 と同じです。

  • ユーザにローカルの動画ファイルを指定してもらう(ファイル選択 or ドラッグ&ドロップ)
  • FileオブジェクトからURLを生成 (window.URL.createObjectURLを利用)
  • それをVideoタグで再生
  • 継続的にCanvasタグに転写 (window.requestAnimationFrameを利用)
    • 今回、映像が2つあり、スライダーの値によって描画を制御しています
  • 録画用に、CanvasタグからMediaStreamを取り出す (Canvas.captureStreamを利用)

switch_2_video_to_stream2.png

映像の合成

2つの映像をスライダーの値にしたがって合成しています。その方法は「WebRTCオッサン問題を超えて。ローカルファイルを自在に操りたい」の方法そのままです。

スワイプの場合

  • スライダーが0の時はvideo 1の内容を、100の時はvideo 2の内容をCanvasに転写しています
  • あいだの場合は、その割合に応じてvideoの一部を切り出して、 drawImage()で転写します
    • 30ならvideo 1が70%、video 2が30%の比率にしています

swipe30.png

swipe
      let video1Width = Math.round((100- faderValue)/100.0 * localVideo.videoWidth);
      let video2Width = Math.round(faderValue/100.0 * localVideo2.videoWidth);
      let canvasWidth1 = Math.round((100- faderValue)/100.0 * duplicateCanvas.width);
      let canvasWidth2 = Math.round(faderValue/100.0 * duplicateCanvas.width);

      if (canvasWidth1 > 0) {
        ctxDuplicate.drawImage(localVideo, 0, 0, video1Width, localVideo.videoHeight,
          0, 0, canvasWidth1 , duplicateCanvas.height
        );
      }

      if (canvasWidth2 > 0) {
        ctxDuplicate.drawImage(localVideo2, (localVideo2.videoWidth - video2Width), 0, video2Width, localVideo2.videoHeight,
          (duplicateCanvas.width - canvasWidth2), 0, canvasWidth2 , duplicateCanvas.height
        );
      }

ストライプの場合

全体を10個に分割し、その1列ごとにスワイプと同じ処理を行っています。
stripe30.png

ディゾルブの場合

片方を透けさせながらもう片方に切り替えるディゾルブの処理はについては、globalAlphaという指定を使って実現しています。
dissolve30.png

dissolve
      let video1Alpha = (100 - faderValue)/100.0;
      let video2Alpha = (faderValue)/100.0;

      ctxDuplicate.globalAlpha = video1Alpha;
      ctxDuplicate.drawImage(localVideo, 0, 0, localVideo.videoWidth, localVideo.videoHeight,
        0, 0, duplicateCanvas.width , duplicateCanvas.height
      );

      ctxDuplicate.globalAlpha = video2Alpha;
      ctxDuplicate.drawImage(localVideo2, 0, 0, localVideo2.videoWidth, localVideo2.videoHeight,
        0, 0, duplicateCanvas.width , duplicateCanvas.height
      );

変換したメディアストリームは、RTCPeerConnectionで通信相手に送ることができます。(※ new MediaStream() でオブジェクトを作っているため、現状ではFirefoxではエラーになってしまいます)

音声(audio)ファイルの変換

こちらも基本的な流れは「WebRTCを試すときにオッサンが映り続ける問題に対処する」 と同じです。

  • ユーザにローカルの音声ファイルを指定してもらう(ファイル選択 or ドラッグ&ドロップ)
  • FileReaderでファイルを読み込む
  • WebAudio API デコード、再生
    • 今回、音声が2つあり、スライダーの値によってゲインを制御しています
  • 録音用に、WebAudio API の createMediaStreamDestination でMediaStreamを取り出す

switch_2_audio_to_stream.png

録画/録音

  • Canvasから取り出した映像のMediaStreamから、videoTrackを取り出す
  • おなじく、WebAudioで作ったMediaStreamから、audioTrackを取り出す
  • 2つを新しいMediaStreamにまとめる
  • MediaRecorderで録画/録音する
  • 実際の録画処理はこちらを参考にしてください

 mix_rec.png

おまけ Leap Motion

今回のデモでは Leap Motion を使ってスライダーを操作してみました。久しぶりに使ってみましたが、ドライバが随分進化していて、安定して手の動きを取れるようになってと感じました。

  • Leap Motion のドライバを入れると、サービスプロセスが起動
  • そのプロセスが、USBからLeap Motion のデータを読み込む
  • プロセスは WebSocket サーバーになっている
  • ブラウザで利用するSDKを使うと、WebSocketでプロセスに接続し、データを取得する
  • Leap Motion 版のソースはこちら GitHub
LeapMotion
  // https://developer.leapmotion.com/documentation/javascript/devguide/Leap_Guides2.html
  // ======== LEAP MOTION =============
  // X: -left +righ
  // Y: -down +up
  // Z: -push +pull

  let controller = Leap.loop(function(frame){
    if(frame.hands.length > 0)
    {
        let hand = frame.hands[0];
        const position = hand.palmPosition;
        const velocity = hand.palmVelocity;
        const direction = hand.direction;
        const grab = hand.grabStrength;

        // --- handle X -----
        yourLeapFunction(grab, position[0], velocity[0], direction[0]);
    }
  });
13
14
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
14