JavaScript | Web API:グラフィック・メディア - ストリーム処理

JavaScript JavaScript
スポンサーリンク

「ストリーム処理」は“流れてくる映像・音を、そのまま扱う”考え方

グラフィック・メディアの世界でいう「ストリーム」は、
ざっくり言うと

「時間とともに流れてくるデータ(映像・音)の連続」

です。

カメラからの映像
マイクからの音
画面共有の映像
ネット越しに届く相手の映像・音

こういったものは、全部「ストリーム」として扱われます。

「ストリーム処理」は、その流れてくるデータを
どこから受け取り、どこへ流し、途中でどう加工するか、
という“流れ”を設計することです。


MediaStream を「ホースの中を流れる水」としてイメージする

getUserMedia が返すのは「MediaStream」というホース

カメラ・マイクのところで出てきた getUserMedia は、
実は「MediaStream」というストリームを返しています。

const stream = await navigator.mediaDevices.getUserMedia({
  video: true,
  audio: true
});
JavaScript

この stream は、
カメラ映像とマイク音声が「時間とともに流れてくるホース」だと思ってください。

このホースを

video 要素につなぐ
MediaRecorder につなぐ
WebRTC(ピア接続)につなぐ
AudioContext につなぐ

といった感じで、いろんな“蛇口”に接続していくのがストリーム処理です。

video.srcObject に MediaStream をそのままつなぐ

一番シンプルなストリーム処理は、
MediaStream を <video> にそのまま流し込むことです。

HTML:

<video id="camera" autoplay playsinline style="width:300px; border:1px solid #ccc;"></video>
<button id="startBtn">カメラ開始</button>
<div id="status"></div>

JavaScript:

const videoEl = document.querySelector("#camera");
const startBtn = document.querySelector("#startBtn");
const statusEl = document.querySelector("#status");

startBtn.addEventListener("click", async () => {
  try {
    const stream = await navigator.mediaDevices.getUserMedia({
      video: true,
      audio: false
    });

    videoEl.srcObject = stream;
    statusEl.textContent = "カメラストリームを再生中";
  } catch (err) {
    statusEl.textContent = "カメラを利用できません: " + err.name;
  }
});
JavaScript

ここでやっていることはとてもシンプルです。

getUserMedia で「映像のストリーム(MediaStream)」をもらう
それを video.srcObject にそのままつなぐ

これだけで、「カメラ → MediaStream → video」というストリームの流れが完成します。


ストリームを「別の場所に流す」:録画というストリーム処理

MediaRecorder にストリームを渡して録画する

同じ MediaStream を、今度は録画に使ってみます。

HTML:

<video id="camera" autoplay playsinline style="width:300px; border:1px solid #ccc;"></video>
<button id="startRecBtn">録画開始</button>
<button id="stopRecBtn" disabled>録画停止</button>
<a id="downloadLink" style="display:none;">ダウンロード</a>
<div id="status"></div>

JavaScript:

const videoEl = document.querySelector("#camera");
const startRecBtn = document.querySelector("#startRecBtn");
const stopRecBtn = document.querySelector("#stopRecBtn");
const downloadLink = document.querySelector("#downloadLink");
const statusEl = document.querySelector("#status");

let stream;
let recorder;
let chunks = [];

async function startCamera() {
  stream = await navigator.mediaDevices.getUserMedia({
    video: true,
    audio: true
  });
  videoEl.srcObject = stream;
}

startCamera().catch(err => {
  statusEl.textContent = "カメラ・マイクを利用できません: " + err.name;
});

startRecBtn.addEventListener("click", () => {
  if (!stream) return;

  recorder = new MediaRecorder(stream);
  chunks = [];

  recorder.addEventListener("dataavailable", (e) => {
    chunks.push(e.data);
  });

  recorder.addEventListener("stop", () => {
    const blob = new Blob(chunks, { type: recorder.mimeType });
    const url = URL.createObjectURL(blob);
    downloadLink.href = url;
    downloadLink.download = "recorded.webm";
    downloadLink.textContent = "録画をダウンロード";
    downloadLink.style.display = "inline-block";
    statusEl.textContent = "録画完了";
  });

  recorder.start();
  statusEl.textContent = "録画中…";
  startRecBtn.disabled = true;
  stopRecBtn.disabled = false;
});

stopRecBtn.addEventListener("click", () => {
  if (!recorder) return;
  recorder.stop();
  startRecBtn.disabled = false;
  stopRecBtn.disabled = true;
});
JavaScript

ここでのストリームの流れはこうです。

カメラ・マイク → MediaStream
MediaStream → video(リアルタイム表示)
同じ MediaStream → MediaRecorder(録画)

つまり、1 本のストリームを
「リアルタイム表示」と「録画」の 2 方向に分岐させています。

これがストリーム処理の典型的なパターンです。


ストリームを「加工する」:AudioContext との組み合わせ

マイクストリームを Web Audio API に流し込む

ストリーム処理の面白いところは、
途中に「加工」を挟めることです。

例えば、マイクの音を Web Audio API に流し込んで、
音量を調整したり、エフェクトをかけたりできます。

const audioCtx = new AudioContext();

async function startMic() {
  const stream = await navigator.mediaDevices.getUserMedia({
    audio: true,
    video: false
  });

  const source = audioCtx.createMediaStreamSource(stream);
  const gain = audioCtx.createGain();

  gain.gain.value = 0.5; // 音量 50%

  source.connect(gain);
  gain.connect(audioCtx.destination);
}
JavaScript

ここでのストリームの流れはこうです。

マイク → MediaStream
MediaStream → MediaStreamSourceNode(Web Audio の入口)
MediaStreamSourceNode → GainNode(音量調整)
GainNode → destination(スピーカー)

「ストリームをノードに流し込み、ノード同士をつないで加工する」
というのが、Web Audio API におけるストリーム処理です。


ストリームを「フレーム単位で切り出す」:Canvas との連携

カメラストリームから静止画を取り出す

ストリームは「連続したデータ」ですが、
その一瞬を切り取って静止画にすることもできます。

これは、カメラストリームと Canvas を組み合わせると実現できます。

HTML:

<video id="camera" autoplay playsinline style="width:300px; border:1px solid #ccc;"></video>
<canvas id="snapshot" width="300" height="200" style="border:1px solid #ccc;"></canvas>
<button id="captureBtn">今のフレームをキャプチャ</button>

JavaScript:

const videoEl = document.querySelector("#camera");
const canvasEl = document.querySelector("#snapshot");
const ctx = canvasEl.getContext("2d");
const captureBtn = document.querySelector("#captureBtn");

async function startCamera() {
  const stream = await navigator.mediaDevices.getUserMedia({
    video: true,
    audio: false
  });
  videoEl.srcObject = stream;
}

captureBtn.addEventListener("click", () => {
  ctx.drawImage(videoEl, 0, 0, canvasEl.width, canvasEl.height);
});

startCamera();
JavaScript

ここでのストリーム処理はこうです。

カメラ → MediaStream → video(連続した映像)
ボタンが押された瞬間だけ、video の現在フレームを Canvas にコピー

ストリームそのものをいじるのではなく、
「流れている中から一瞬を切り出す」という形の処理です。


ストリーム処理で必ず意識してほしい「時間」と「継続」の感覚

ストリームは「一度きりのデータ」ではなく「流れ続けるもの」

普通の変数や配列は、「その瞬間の値」を扱います。

一方、ストリームは
「時間とともに変化し続けるデータ」です。

カメラ映像は毎秒 30 フレームなどで更新され続ける
マイク音声は毎ミリ秒ごとに新しいサンプルが届く

ストリーム処理を書くときは、
「一回値を取って終わり」ではなく、
「流れ続けているものを、どこにどうつなぐか」を考えます。

MediaStream を video につなぐ
MediaStream を MediaRecorder につなぐ
MediaStream を AudioContext につなぐ

というのは、全部「流れの配管工事」です。

止めることもストリーム処理の一部

ストリームは「流しっぱなし」にもできますが、
カメラやマイクの場合は、ちゃんと止めることも大事です。

function stopStream(stream) {
  stream.getTracks().forEach((track) => {
    track.stop();
  });
}
JavaScript

getUserMedia で受け取った stream に対してこれを呼ぶと、
カメラやマイクがオフになります。

ストリーム処理は、

いつ流し始めるか
どこに流すか
いつ止めるか

まで含めて設計するものだと考えてください。


初心者として「ストリーム処理」で本当に掴んでほしいこと

グラフィック・メディアにおけるストリーム処理の本質は、
「時間とともに流れてくる映像・音を、どこからどこへ流すかを設計すること」 です。

そのうえで、まず次の感覚をしっかり押さえてください。

getUserMedia が返す MediaStream は「カメラ・マイクからの生の流れ」だということ。
MediaStream を video.srcObject に渡すと、そのままリアルタイム映像になること。
同じ MediaStream を MediaRecorder や AudioContext にも流し込めること。
Canvas を使うと、ストリームの「今この瞬間」を静止画として切り出せること。

おすすめの練習は、次の順番です。

カメラストリームを video に映すだけのページを作る。
同じストリームを MediaRecorder に渡して「録画してダウンロード」までやってみる。
Canvas に「今のフレームをキャプチャ」するボタンを足してみる。

ここまでやると、
ストリーム処理はもう「難しそうな言葉」ではなく、
「現実世界の映像・音の流れを、自分のアプリの中でどう配線するかを考える作業」
として、かなり手触りよく感じられるはずです。

タイトルとURLをコピーしました