はじめに
この記事は、「WebRTCを試すときにオッサンが映り続ける問題に対処する」、「WebRTCオッサン問題を超えて。ローカルファイルを自在に操りたい」の続編です。
また、Web Music Demo Party #1 のLT用の記事でもあります。
自己紹介
- @massie_g (まっしー)
- WebRTC Meetup Tokyo の主催者の一人です
デモ
環境
- GitHub pages https://mganeko.github.io/webrtcexpjp/tool/switch_to_stream_rec.html
- ソースコード https://github.com/mganeko/webrtcexpjp/blob/master/tool/switch_to_stream_rec.html
- デモに使った映像は http://動画素材.com/ のものを利用させていただきました
- デモに使った音楽は、GarageBand のループを適当に繋ぎました
手順
- ローカルにあらかじめ映像ファイルx2と、音声ファイルx2 を準備
- ファイルを指定
- ミックス開始
仕組み
映像(video)ファイルの変換
基本的な流れは「WebRTCを試すときにオッサンが映り続ける問題に対処する」 と同じです。
- ユーザにローカルの動画ファイルを指定してもらう(ファイル選択 or ドラッグ&ドロップ)
- FileオブジェクトからURLを生成 (window.URL.createObjectURLを利用)
- それをVideoタグで再生
- 継続的にCanvasタグに転写 (window.requestAnimationFrameを利用)
- 今回、映像が2つあり、スライダーの値によって描画を制御しています
- 録画用に、CanvasタグからMediaStreamを取り出す (Canvas.captureStreamを利用)
映像の合成
2つの映像をスライダーの値にしたがって合成しています。その方法は「WebRTCオッサン問題を超えて。ローカルファイルを自在に操りたい」の方法そのままです。
スワイプの場合
- スライダーが0の時はvideo 1の内容を、100の時はvideo 2の内容をCanvasに転写しています
- あいだの場合は、その割合に応じてvideoの一部を切り出して、 drawImage()で転写します
- 30ならvideo 1が70%、video 2が30%の比率にしています

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列ごとにスワイプと同じ処理を行っています。
ディゾルブの場合
片方を透けさせながらもう片方に切り替えるディゾルブの処理はについては、globalAlphaという指定を使って実現しています。
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を取り出す
録画/録音
- Canvasから取り出した映像のMediaStreamから、videoTrackを取り出す
- おなじく、WebAudioで作ったMediaStreamから、audioTrackを取り出す
- 2つを新しいMediaStreamにまとめる
- MediaRecorderで録画/録音する
- 実際の録画処理はこちらを参考にしてください

おまけ Leap Motion
今回のデモでは Leap Motion を使ってスライダーを操作してみました。久しぶりに使ってみましたが、ドライバが随分進化していて、安定して手の動きを取れるようになってと感じました。
- Leap Motion のドライバを入れると、サービスプロセスが起動
- そのプロセスが、USBからLeap Motion のデータを読み込む
- プロセスは WebSocket サーバーになっている
- ブラウザで利用するSDKを使うと、WebSocketでプロセスに接続し、データを取得する
- Leap Motion 版のソースはこちら GitHub
// 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]);
}
});