WebRTCをやりたくて(Access media devices)

Raspberry Piに付けたUSBカメラをコントロールしたくてAPIを探していたら”WebRTC”に関するHPを探していたら”WebRTC samples”を見つけました。HPの最初に、”This is a collection of small samples demonstrating various parts of the WebRTC APIs”と有るようにサンプルと共に”WebRTC”について説明しているHPです。興味が有ったのでそれらのサンプルを読んで見る事にしました。今回はRaspberry PIでは無く手持ちのNoteBookで実行しています。また、ブラウザーも関係するようで、FireFox 88.0.1 (64 ビット) を使用しました。

(1)Basic getUserMedia demo

最初のサンプルは”Basic getUserMedia demo”です。実行するとこんなWeb画面が表示されます。

”Open camera”ボタンを押すと、”カメラの使用を許可するか”と聞かれ、”Yes”と答えるとカメラの映像(動画)が表示されます。このHPのソースを見ると、


<!DOCTYPE html>
<!--
 *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree.
-->
<html>
<head>

    <meta charset="utf-8">
    <meta name="description" content="WebRTC code samples">
    <meta name="viewport" content="width=device-width, user-scalable=yes, initial-scale=1, maximum-scale=1">
    <meta itemprop="description" content="Client-side WebRTC code samples">
    <meta itemprop="image" content="../../../images/webrtc-icon-192x192.png">
    <meta itemprop="name" content="WebRTC code samples">
    <meta name="mobile-web-app-capable" content="yes">
    <meta id="theme-color" name="theme-color" content="#ffffff">

    <base target="_blank">

    <title>getUserMedia</title>

    <link rel="icon" sizes="192x192" href="../../../images/webrtc-icon-192x192.png">
    <link href="//fonts.googleapis.com/css?family=Roboto:300,400,500,700" rel="stylesheet" type="text/css">
    <link rel="stylesheet" href="../../../css/main.css">

</head>

<body>

<div id="container">
    <h1><a href="//webrtc.github.io/samples/" title="WebRTC samples homepage">WebRTC samples</a>
        <span>getUserMedia</span></h1>

    <video id="gum-local" autoplay playsinline></video>
    <button id="showVideo">Open camera</button>

    <div id="errorMsg"></div>

    <p class="warning"><strong>Warning:</strong> if you're not using headphones, pressing play will cause feedback.</p>

    <p>Display the video stream from <code>getUserMedia()</code> in a video element.</p>

    <p>The <code>MediaStream</code> object <code>stream</code> passed to the <code>getUserMedia()</code> callback is in
        global scope, so you can inspect it from the console.</p>

    <a href="https://github.com/webrtc/samples/tree/gh-pages/src/content/getusermedia/gum"
       title="View source for this page on GitHub" id="viewSource">View source on GitHub</a>
</div>

<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
<script src="js/main.js"></script>

<script src="../../../js/lib/ga.js"></script>

</body>
</html>

フォントの読み込み、CSS,Script Fileへのリンク等、色々盛りだくさんのソースコードです。コメントを削り、リンク先を取り込み、かつ機能をカメラの映像(動画)表示のみとして書き換えて見ました。File_Nameは、”samp01.html”とします。

samp01.html

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>getUserMedia</title>
        <link rel="icon" sizes="192x192" href="./webrtc-icon-192x192.png">
        <link href="//fonts.googleapis.com/css?family=Roboto:300,400,500,700" rel="stylesheet" type="text/css">

        <style>
        body {
            font-family: 'Roboto', sans-serif;
            font-weight: 300;
            margin: 0;
            padding: 1em;
            word-break: break-word;
        }
    
        div#container {
            margin: 0 auto 0 auto;
            max-width: 60em;
            padding: 1em 1.5em 1.3em 1.5em;
        }

        h1 {
            border-bottom: 1px solid #ccc;
            font-family: 'Roboto', sans-serif;
            font-weight: 500;
            margin: 0 0 0.8em 0;
            padding: 0 0 0.2em 0;
        }

        video {
            background: #222;
            margin: 0 0 20px 0;
            --width: 100%;
            width: var(--width);
            height: calc(var(--width) * 0.75);
        }

        button {
            background-color: #d84a38;
            border: none;
            border-radius: 2px;
            color: white;
            font-family: 'Roboto', sans-serif;
            font-size: 0.8em;
            margin: 0 0 1em 0;
            padding: 0.5em 0.7em 0.6em 0.7em;
        }

        </style>
    </head>

    <body>
        <div id="container">
            <h1>WebRTC samples getUserMedia</h1>
            <video autoplay playsinline></video>
            <button id="showVideo">Open camera</button>
        </div>

        <script>
            'use strict';
    
            const constraints = {
              audio: false,
              video: true
            };
    
            document.querySelector('#showVideo').addEventListener('click', e => init(e));

            async function init(e) {
                  try {
                    const stream = await navigator.mediaDevices.getUserMedia(constraints);
                    handleSuccess(stream);
                  } catch (e) {
                    console.error("Error");
                  }
            }

            function handleSuccess(stream) {
                  const video = document.querySelector('video');
                  video.srcObject = stream;
            }
        </script>

    </body>

</html>

このコードを実行するとこんな感じになり、殆ど同じに動作します。

HTMLコードについて

説明
 ファビコン用の画像。ファイル名、”webrtc-icon-192×192.png”。
 リンク先のファイルをHTMLファイルと同じフォルダーに保存。
 フォントの指定。GoogleのRobotoを使用。
9から51  CSSの指定。リンク先から必要な部分を取り出しました。
55から59  HPの本体。56,7,8の3行のみ。
56  HPのタイトル
57  カメラの映像を表示するエリア。
 autoplay: 自動再生  playinline: インライン再生(表示を枠に合わせる)
58  Streaming開始ボタン
61から84  JaveScript。
62  use strict。厳格モード宣言
64から67  getUserMedia() (73行)で使用する引数
 音声をaudioで、映像をvideoで指定する。
 共に、ON->true Off->false を指定。 今回は映像のみ使用。
69  イベントを追加。”Open camera”ボタンを押すと、init()を実行。
71  init()関数定義。 非同期(async)を宣言。
73  カメラの使用許可とMediaStreamの取得
 mediaDevices.getUserMedia()は非同期関数。awaitで処理の終了を待つ。
 正常終了、handleSuccess(stream);を、エラー終了、console.error(“Error”);を実行
80  handleSuccess()関数定義。videoタグの部分に映像を表示する。
81  ’video’が示す、HTML要素の取得
82  その要素のsrcObject属性に73行で取得したMediaStreamを代入。
 代入された時点からStreaming開始。

下記は。コードを更に簡素化したものです。ファイル名を”sample1a.html”とします。

samp01a.html

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>getUserMedia</title>
    </head>
    <body>
        <div id="container">
            <h1>WebRTC samples getUserMedia</h1>
            <video autoplay playsinline></video><br>
            <button id="showVideo" onclick="init()">Open camera</button>
        </div>
        <script>
            function init() {
                navigator.mediaDevices.getUserMedia({audio: false, video: true})
                .then(function(stream) { 
                    document.querySelector('video').srcObject = stream; })
                .catch(function(err) { 
                    console.error(err); } );
            }
        </script>
    </body>
</html>

このHTMLの要点は、

  • HTMLファイルでvideoタグ要素を用いて映像表示箇所を作成。
  • この時必要に応じて、autoplay、 playsinline等を指定する。
  • navigator.mediaDevices.getUserMedia()関数で音と映像の許可を依頼。
  • 戻り値(stream)を、video要素のsrcObject属性に代入すれば、Streamingが開始する。

(2)Use getUserMedia with canvas

次は、”Use getUserMedia with canvas”。 カメラからの動画を表示し、ボタンを押した瞬間の映像を表示する(写真に撮る)コードです。これもコードを簡単にして見ました。FileNameは、”samp02.html”。

samp02.html

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>getUserMedia to canvas</title>
    </head>

    <body>
        <center>
            <h1>WebRTC samples getUserMedia ⇒ canvas</h1>
            <video playsinline autoplay></video><br>
            <button>Take snapshot</button><br>
            <canvas></canvas>
        </center>

        <script>
            'use strict';

            const video = document.querySelector('video');
            const canvas = document.querySelector('canvas');
            const button = document.querySelector('button');
            const constraints = {
                  audio: false,
                  video: true
            };

            navigator.mediaDevices.getUserMedia(constraints).then(handleSuccess).catch(handleError);

            function handleSuccess(stream) {
                  video.srcObject = stream;
            }

            function handleError(error) {
                  console.log('navigator.MediaDevices.getUserMedia error: ', error.message, error.name);
            }

            button.onclick = function() {
                  canvas.width = video.videoWidth;
                  canvas.height = video.videoHeight;
                  canvas.getContext('2d').drawImage(video, 0, 0, canvas.width, canvas.height);
            };

        </script>
    </body>
</html>
  • 13行: 写真表示用に、canvas 要素を追加。
  • 27行: コード起動時にこの部分(カメラ使用許可)が実行される。
  • 29行: 許可が出ると、この関数が実行される。video要素srcObject属性にstreamを代入。
         Streamが開始される。
  • 37行: ボタンがおされるとここが実行。
         縦、横を動画のサイズに合わせてcanvas.getContext(‘2d’).drawImage()関数で写真を表示。
         canvas.getContext(‘2d’).drawImage()の引数は、
    • 第1引数:video要素
    • 第2,3引数:画像左上の座標
    • 第4,5引数:画像の幅と高さ

動画を表示しているvideo要素を持って、canvas要素に、getContext(‘2d’).drawImage()関数を使って容易に写真を表示出来るって事の様です。

(3)Use getUserMedia with canvas and CSS filters

次は、”Use getUserMedia with canvas and CSS filters”です。これは動画、写真に映像効果(Filter)をかけるサンプルの様です。先ずは簡略化したHTMLコード。FileNameは、”samp03.html”。

samp03.html

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>getUserMedia + CSS filters</title>
        <style>
            .none {
                -webkit-filter: none;
                filter: none;
            }

            .blur {
                -webkit-filter: blur(3px);
                filter: blur(3px);
            }

            .grayscale {
                -webkit-filter: grayscale(1);
                filter: grayscale(1);
            }
    
            .invert {
                -webkit-filter: invert(1);
                filter: invert(1);
            }
    
            .sepia {
                -webkit-filter: sepia(1);
                filter: sepia(1);
            }

            .opacity {
                -webkit-filter: opacity(40%);
                filter: opacity(40%);
            }

            button#snapshot {
                margin: 0 10px 25px 0;
                width: 110px;
            }
    
            video {
                object-fit: cover;
            }
        </style>
    </head>
    <body>
        <center>
            <h1>WebRTC samples getUserMedia + CSS filters</h1>
            <video playsinline autoplay></video><br>

            <label for="filter">Filter: </label>
            <select id="filter">
                <option value="none">None</option>
                <option value="blur">Blur</option>
                <option value="grayscale">Grayscale</option>
                <option value="invert">Invert</option>
                <option value="sepia">Sepia</option>
                <option value="opacity">opacity</option>
            </select>

            <button id="snapshot">Take snapshot</button><br>

            <canvas></canvas>
        </center>

        <script>
            'use strict';

            const snapshotButton = document.querySelector('button#snapshot');
            const filterSelect = document.querySelector('select#filter');
            const video = document.querySelector('video');
            const canvas = document.querySelector('canvas');
            const constraints = {
                  audio: false,
                  video: true
            };

            navigator.mediaDevices.getUserMedia(constraints).then(handleSuccess).catch(handleError);

            function handleSuccess(stream) {
                  window.stream = stream; // make stream available to browser console
                  video.srcObject = stream;
            }

            function handleError(error) {
                  console.log('navigator.MediaDevices.getUserMedia error: ', error.message, error.name);
            }

            snapshotButton.onclick = function() {
                  canvas.className = filterSelect.value;
                  canvas.width = video.videoWidth;
                  canvas.height = video.videoHeight;
                  canvas.getContext('2d').drawImage(video, 0, 0, canvas.width, canvas.height);
            };

            filterSelect.onchange = function() {
                  video.className = filterSelect.value;
            };

        </script>
    </body>
</html>
  • ”Use getUserMedia with canvas”のサンプルにプルダウンメニューがついた様なHP
  • Filterをプルダウンメニューから選択し画像と動画にFilterを掛ける。
  • 6から45行: CSSの指定。Filterの指定が主です。
  • 48から65行: HPの本体。52から60行がプルダウンメニュー
  • 97行: プルダウンメニューが変更されるとこの関数が実行する。
  • 98行: この部分でFilterを掛けている。video要素のclassName属性にFilterの値(filterSelect.value;)を代入しているのみ

98行の、”video.className = filterSelect.value;”のみでFilter効果が得られる様です。

(4)Use getUserMedia with canvas and CSS filters

Use getUserMedia with canvas and CSS filters”は画像の解像度を指定するサンプルです。今回は、QVGA,VGA,HDの3解像度に変更してコードを簡略化しました。FileNameは”samp04.html”。

samp04.html

<html>
    <head>
        <meta charset="utf-8">
        <title>getUserMedia: select resolution</title>
    </head>

    <body>

        <center>
            <h1>getUserMedia: select resolution</h1>
            <p></p>
            <p>Click a button to call <code>getUserMedia()</code> with appropriate resolution.</p>

            <div id="buttons">
                <button id="qvga">QVGA</button>
                <button id="vga">VGA</button>
                <button id="hd">HD</button>
            </div>

            <div id="videoblock">
                <p id="dimensions"></p>
                <video playsinline autoplay></video>
           </div>
        </center>

        <script>
            'use strict';
    
            const videoblock = document.querySelector('#videoblock');
            const dimensions = document.querySelector('#dimensions');
            const video = document.querySelector('video');
            const vgaButton = document.querySelector('#vga');
            const qvgaButton = document.querySelector('#qvga');
            const hdButton = document.querySelector('#hd');
            const qvgaConstraints = {
                    video: {width: {exact: 320}, height: {exact: 240}}
            };

            const vgaConstraints = {
                    video: {width: {exact: 640}, height: {exact: 480}}
            };

            const hdConstraints = {
                    video: {width: {exact: 1280}, height: {exact: 720}}
            };

            vgaButton.onclick = () => {
                    getMedia(vgaConstraints);
            };

            qvgaButton.onclick = () => {
                    getMedia(qvgaConstraints);
            };

            hdButton.onclick = () => {
                    getMedia(hdConstraints);
            };
    
            function getMedia(constraints) {
                    videoblock.style.display = 'none';
                    navigator.mediaDevices.getUserMedia(constraints)
                        .then(gotStream)
                        .catch(err => {
                            videoblock.style.display = 'block';
                            dimensions.innerText = 'Video not ready';
                        });
            }

            function gotStream(mediaStream) {
                    video.srcObject = mediaStream;
                    videoblock.style.display = 'block';
            }

            video.onloadedmetadata = () => {
                    displayVideoDimensions('loadedmetadata');
            };
    
            function displayVideoDimensions(whereSeen) {
                    if (video.videoWidth) {
                        dimensions.innerText = 'Actual video dimensions: ' + video.videoWidth +
                        'x' + video.videoHeight + 'px.';
                    } else {
                        dimensions.innerText = 'Video not ready';
                    }
            }

        </script>
    </body>
</html>
  • navigator.mediaDevices.getUserMedia()の引数で映像の解像度の指定が出来る。
  • QVGAを指定する場合の引数は、qvgaConstraints = {video: {width: {exact: 320}, height: {exact: 240}}}; の様に高さと幅の情報を持って、navigator.mediaDevices.getUserMedia() を実行すれば良い。

(5)Audio-only getUserMedia() output to local audio element

Audio-only getUserMedia() output to local audio element”はnavigator.mediaDevices.getUserMedia()の引数で、映像をオフに音声をオンにしたバージョンです。引数の定義は、23から25行で行っています。FileNameは、”samp05.html”。

samp05.html

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>gUM audio</title>
    </head>

    <body>

        <center>
            <h1>WebRTC samples getUserMedia, audio only</h1>
            <audio controls autoplay></audio>
            <div>
                <span id="errorMsg"></span>
            </div>
        </center>

        <script>
            'use strict';

            // Put variables in global scope to make them available to the browser console.
            const audio = document.querySelector('audio');
            const constraints = window.constraints = {
                  audio: true,
                  video: false
            };

            function handleSuccess(stream) {
                  audio.srcObject = stream;
            }

            function handleError(error) {
                  const errorMessage = 'navigator.MediaDevices.getUserMedia error: ' + error.message + ' ' + error.name;
                  document.getElementById('errorMsg').innerText = errorMessage;
                  console.log(errorMessage);
            }

            navigator.mediaDevices.getUserMedia(constraints).then(handleSuccess).catch(handleError);
        </script>

    </body>
</html>

(6)Audio-only getUserMedia() displaying volume

Audio-only getUserMedia() displaying volume”は、音の大きさをメータに表示するサンプルですが、内容がちょっと理解出来ませんでした。これはスキップします。

(7)Record stream

Record stream”は、HPに2画面表示し、左側の画面にカメラからのSreamを表示。右側に録画した映像を表示するサンプルです。動画の録画、再生、保存の3つ機能についてのサンプルです。HTMLを簡略してと思いましたが、あまり省略出来ませんでした。FileNameは、”samp07.html”。

samp07.html

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>MediaStream Recording</title>
    </head>

    <style>
        video {
              background: #222;
              vertical-align: top;
              --width: 25vw;
              width: var(--width);
              height: calc(var(--width) * 0.5625);
       }
    </style>

    <body>
        <center>

            <h1>WebRTC samples MediaRecorder</h1>
            <video id="gum" playsinline autoplay muted></video>
            <video id="recorded" playsinline loop></video>

            <div>
                <button id="start">Start camera</button>
                <button id="record" disabled>Start Recording</button>
                <button id="play" disabled>Play</button>
                <button id="download" disabled>Download</button>
            </div>

            <div>
                Recording format: <select id="codecPreferences" disabled></select>
            </div>
            <div>
                <h4>Media Stream Constraints options</h4>
                <p>Echo cancellation: <input type="checkbox" id="echoCancellation"></p>
            </div>

            <div>
                <span id="errorMsg"></span>
            </div>

        </center>

        <script>
            'use strict';

            /* globals MediaRecorder */

            let mediaRecorder;
            let recordedBlobs;

            const codecPreferences = document.querySelector('#codecPreferences');

            const errorMsgElement = document.querySelector('span#errorMsg');
            const recordedVideo = document.querySelector('video#recorded');
            const recordButton = document.querySelector('button#record');
            recordButton.addEventListener('click', () => {
                      if (recordButton.textContent === 'Start Recording') {
                        startRecording();
                      } else {
                        stopRecording();
                        recordButton.textContent = 'Start Recording';
                        playButton.disabled = false;
                        downloadButton.disabled = false;
                        codecPreferences.disabled = false;
                      }
            });

            const playButton = document.querySelector('button#play');
            playButton.addEventListener('click', () => {
                      const mimeType = codecPreferences.options[codecPreferences.selectedIndex].value.split(';', 1)[0];
                      const superBuffer = new Blob(recordedBlobs, {type: mimeType});
                      recordedVideo.src = null;
                      recordedVideo.srcObject = null;
                      recordedVideo.src = window.URL.createObjectURL(superBuffer);
                      recordedVideo.controls = true;
                      recordedVideo.play();
            });

            const downloadButton = document.querySelector('button#download');
            downloadButton.addEventListener('click', () => {
                      const blob = new Blob(recordedBlobs, {type: 'video/webm'});
                      const url = window.URL.createObjectURL(blob);
                      const a = document.createElement('a');
                      a.style.display = 'none';
                      a.href = url;
                      a.download = 'test.webm';
                      document.body.appendChild(a);
                      a.click();
                      setTimeout(() => {
                        document.body.removeChild(a);
                        window.URL.revokeObjectURL(url);
                      }, 100);
            });

            function handleDataAvailable(event) {
                      console.log('handleDataAvailable', event);
                      if (event.data && event.data.size > 0) {
                        recordedBlobs.push(event.data);
                      }
            }

            function getSupportedMimeTypes() {
                     const possibleTypes = [
                            'video/webm;codecs=vp9,opus',
                            'video/webm;codecs=vp8,opus',
                            'video/webm;codecs=h264,opus',
                            'video/mp4;codecs=h264,aac',
                    ];
                  return possibleTypes.filter(mimeType => {
                        return MediaRecorder.isTypeSupported(mimeType);
                    });
            }

            function startRecording() {
                      recordedBlobs = [];
                      const mimeType = codecPreferences.options[codecPreferences.selectedIndex].value;
                      const options = {mimeType};

                      try {
                        mediaRecorder = new MediaRecorder(window.stream, options);
                      } catch (e) {
                        console.error('Exception while creating MediaRecorder:', e);
                        errorMsgElement.innerHTML = `Exception while creating MediaRecorder: ${JSON.stringify(e)}`;
                        return;
                        }

                      console.log('Created MediaRecorder', mediaRecorder, 'with options', options);
                      recordButton.textContent = 'Stop Recording';
                      playButton.disabled = true;
                      downloadButton.disabled = true;
                      codecPreferences.disabled = true;
                      mediaRecorder.onstop = (event) => {
                                console.log('Recorder stopped: ', event);
                                console.log('Recorded Blobs: ', recordedBlobs);
                      };
                      mediaRecorder.ondataavailable = handleDataAvailable;
                      mediaRecorder.start();
                      console.log('MediaRecorder started', mediaRecorder);
            }

            function stopRecording() {
                      mediaRecorder.stop();
            }

            function handleSuccess(stream) {
                      recordButton.disabled = false;
                      console.log('getUserMedia() got stream:', stream);
                      window.stream = stream;

                      const gumVideo = document.querySelector('video#gum');
                      gumVideo.srcObject = stream;

                      getSupportedMimeTypes().forEach(mimeType => {
                        const option = document.createElement('option');
                        option.value = mimeType;
                        option.innerText = option.value;
                        codecPreferences.appendChild(option);
                      });
                      codecPreferences.disabled = false;
            }

            async function init(constraints) {
                      try {
                        const stream = await navigator.mediaDevices.getUserMedia(constraints);
                        handleSuccess(stream);
                      } catch (e) {
                        console.error('navigator.getUserMedia error:', e);
                        errorMsgElement.innerHTML = `navigator.getUserMedia error:${e.toString()}`;
                      }
            }

            document.querySelector('button#start').addEventListener('click', async () => {
                      document.querySelector('button#start').disabled = true;
                      const hasEchoCancellation = document.querySelector('#echoCancellation').checked;
                      const constraints = {
                        audio: {
                          echoCancellation: {exact: hasEchoCancellation}
                        },
                        video: {
                          width: 1280, height: 720
                        }
                      };
                      console.log('Using media constraints:', constraints);
                      await init(constraints);
            });
        </script>
    </body>
</html>
  • ========= ”Start camera”ボタンが押されと以下が実行される ==========
  • 175行: カメラ使用許可の初期設定。
    • ”Start camera”の無効化
    • ”echoCancellation”チェックボックスのチェックの有無
    • navigator.mediaDevices.getUserMedia()の引数の設定
    • 上記の操作後、165行の ”init()” へ飛びます
  • 165行: カメラ使用許可依頼
    • navigator.mediaDevices.getUserMedia()を実行してカメラ使用許可依頼
    • OKなら148行、handleSuccess()へ
    • Errorならエラー処理を行います。
  • 148行: Streamingの開始
    • ”Start Recording”ボタンを有効にする。
    • 151行: window.stream = stream;
      •  window要素のstream属性にnavigator.mediaDevices.getUserMedia()からのstreamを代入。
      • これは、この後でStreamingを録画する時に使用します。
    • 156から161行: 対応するフォーマットの確認
      • 対応フォーマットが、プルダウンメニュー、”Recording format”に追加されます。
      • プルダウンメニュー、”Recording format”を有効にする。
  • ========= ”Start Recording”ボタンが押されと以下が実行される ==========
  • 59行: ”record”ボタンクリックイベントの追加
    • ボタンが、’Start Recording’ならstartRecording(); 117行を実行
    • それ以外は、stopRecording();を実行
  • 117行: startRecording() 録画開始
    • 118行: 録画用配列の宣言
    • 120行: 画像タイプの取得
    • 123行: MediaRecorder()の定義。引数は、window.streamとフォーマット
    • 135行: ストップイベント処理の登録
    • 139行: 画像データレディーイベントの登録。イベントで、handleDataAvailable()が実行
    • 140行: 録画の開始
    • 98行: handleDataAvailable()
      • 100行: データの有無を確認して
      • 101行: データが有れば、録画用配列(recordedBlobs)に書き込む。
  • 144行: stopRecording() 録画終了。
    • この時、135行のストップイベント(コンソールに表示)が実行される。
  • ========= ”Play”ボタンが押されと以下が実行される ==========
  • 72行: ”play”ボタンクリックイベントの追加
    • 73行: データのタイプ取得
    • 74行: 録画したデータとタイプで新しい配列を宣言
    • 77行: 録画表示用の画面(recordedVideo)要素のsrc属性に配列データを代入
    • 78行: ビデオコントロールをオンに設定
    • 79行: 再生開始
    • 23行: videoタグで、”loop”と定義されているので再生は繰り返される。
  • ========= ”download”ボタンが押されと以下が実行される ==========
  • 83行: ”download”ボタンクリックイベントの追加
    • 84行: 録画データとデータタイプを合わせてダウンロード用配列を定義
    • 85行: 配列のURLを取得
    • 86行: ”a”タグ要素の製作
    • 90行: 要素をbodyに追加
    • 91行: ダウンロードの開始
    • 92から95行: ダウンロード後、100ミリセックで下記を行う
      • ダウンロード要素の削除
      • ダウンロード用配列の削除

あまり省略出来なかったので、このをHTMLを以下の動作を行う様にかき変えました。

  1. 起動と同時にカメラ使用許可を要請。
  2. 承認後、”Stop Recording”ボタンが押されるまで、Streamingの表示と録画を開始。
  3. ”Stop Recording”ボタンが押されると、録画したデータを再生用画面の表示。
  4. ”Download”ボタンを押すと、録画したデータのダウンロード開始。
samp07a.html

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>MediaStream Recording</title>
    </head>

    <style>
        video {
              background: #222;
              vertical-align: top;
              --width: 25vw;
              width: var(--width);
              height: calc(var(--width) * 0.5625);
       }
    </style>

    <body>
        <center>
            <h1>WebRTC samples MediaRecorder</h1>
            <video id="gum" playsinline autoplay muted></video>
            <video id="recorded" playsinline loop></video>

            <div>
                <button id="record" disabled >Stop Recording</button>
                <button id="download" disabled >Download</button>
            </div>
        </center>

        <script>
            'use strict';
            let mediaRecorder;
            let recordedBlobs;

            const recordedVideo = document.querySelector('video#recorded');
            const recordButton = document.querySelector('button#record');
            recordButton.addEventListener('click', () => {
                      mediaRecorder.stop();
                      recordButton.disabled = true;
                      downloadButton.disabled = false;
            });

            const downloadButton = document.querySelector('button#download');
            downloadButton.addEventListener('click', () => {
                      const blob = new Blob(recordedBlobs, {type: 'video/webm'});
                      const url = window.URL.createObjectURL(blob);
                      const a = document.createElement('a');
                      a.style.display = 'none';
                      a.href = url;
                      a.download = 'test.webm';
                      document.body.appendChild(a);
                      a.click();
                      setTimeout(() => {
                        document.body.removeChild(a);
                        window.URL.revokeObjectURL(url);
                      }, 100);
            });

            navigator.mediaDevices.getUserMedia({audio: true, video: true})
            .then(function(stream) {
                const gumVideo = document.querySelector('video#gum');
                gumVideo.srcObject = stream;

                window.stream = stream;
                recordedBlobs = [];
                const mimeType = "video/webm; codecs=vp8,opus";
                mediaRecorder = new MediaRecorder(window.stream, {mimeType});
                recordButton.disabled = false;

                mediaRecorder.onstop = 
                    (event) => {
                            const superBuffer = new Blob(recordedBlobs, {type : 'video/webm'});
                            recordedVideo.src = null;
                            recordedVideo.srcObject = null;
                            recordedVideo.src = window.URL.createObjectURL(superBuffer);
                            recordedVideo.controls = true;
                            recordedVideo.play();
                    };

                mediaRecorder.ondataavailable = 
                    function(event) {
                            if (event.data && event.data.size > 0) 
                                    recordedBlobs.push(event.data); 
                    }

                mediaRecorder.start();
            })
            .catch(function(err) { 
                  console.log("err"); } );
        </script>

    </body>
</html>

下記の様に起動した時点でカメラ使用許可要請の画面が上がります。本来のHPの画面から今回使用しないコメント、ボタン等は省いています。先ずはこの画面でポップアップの”許可する(A)”を押して下さい。

  • 59行: navigator.mediaDevices.getUserMedia({audio: true, video: true})
    • ここで、カメラの使用許可を申請しています。
    • 引数にエコー処理、画面の大きさの情報が有りましたが、今回は、”true”のみにしています。
  • この状態が、上記のHPの画面です。 ここで、”許可する(A)”が押された前提で説明します。
    • 61−62行: カメラのデータをHP画面に表示する為の処理。
    • 64−89行: 録画の準備と録画開始まで。
      • 64行: 録画用のStreamの宣言
      • 65行: 録画データ保存用の配列の宣言
      • 66行: 録画データのタイプ。これは本来のHPで、”Recording format”に表示されます。
             今回は、音声、画像がオンで、”video/webm; codecs=vp8,opus”でした。
      • 67行: ここでレコーダーの宣言。
      • 70行: 録画がストップした時のイベントを追加。詳細は後で
      • 80行: データが有効になった時のイベントの追加。動作は録画用の配列へのデータを書き込み。
      • 86行: 録画開始。
  • この時点で、左側の画面にカメラの画像が表示され、”Stop Recording”ボタンが有効になります。
    • 録画は開始しているので、適当な所で”Stop Recording”ボタンを押して録画を停止します。
  • ”Stop Recording”ボタンを押すと以下の手順で、自動で再生処理に入ります。
    • 38行: mediaRecorder.stop();。録画の停止。
      • 停止により、”ondataavailable”(80行)、”onstop”(70行)の順でイベントが発生。 
      • ”ondataavailable”(80行)
        • 録画終了を受けて、バッファーに有るデータを保存用配列に追加します。
      • ”onstop”(70行)
        • 終了後の処理が必要な場合は、このイベント後に行います。
        • 本来のHTMLでは、録画終了メッセージをコンソールに出力しています。
        • 今回は、ここに再生用処理を追加しています。
          • 72行: 録画用配列から表示用配列を作成
          • 75行: 表示用配列の位置を、再生用HTML要素のsrc属性に代入。
          • 76行: 再生用HTML要素のcontrols属性をオン。
          • 77行: 再生開始。
    • 39,40行: ストップボタンを無効、”Download”ボタンを有効にして処理完了。
  • ”Download”ボタンを押すと以下が実行されます。
    • 本来のプログラムそのままです。
      • 45行: 録画データからダウンロード用配列を定義
      • 46行: その位置を変数、”url”に代入
      • 47−52行: ”a”タグ要素をbodyに追加し、ダウンロードを実行。
      • 53−56行: 100msec後に、bodyに追加した、”a”要素とURLを削除。

以上がHTMLの概要です。

カメラの使い方、画像の再生、録画のやり方が理解出来ました。”WebRTC samples”ではこれ以降、

と続きます。今後これらにつてもHTMLを見て行く予定です。