「ストリーム処理」は“流れてくる映像・音を、そのまま扱う”考え方
グラフィック・メディアの世界でいう「ストリーム」は、
ざっくり言うと
「時間とともに流れてくるデータ(映像・音)の連続」
です。
カメラからの映像
マイクからの音
画面共有の映像
ネット越しに届く相手の映像・音
こういったものは、全部「ストリーム」として扱われます。
「ストリーム処理」は、その流れてくるデータを
どこから受け取り、どこへ流し、途中でどう加工するか、
という“流れ”を設計することです。
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();
});
}
JavaScriptgetUserMedia で受け取った stream に対してこれを呼ぶと、
カメラやマイクがオフになります。
ストリーム処理は、
いつ流し始めるか
どこに流すか
いつ止めるか
まで含めて設計するものだと考えてください。
初心者として「ストリーム処理」で本当に掴んでほしいこと
グラフィック・メディアにおけるストリーム処理の本質は、
「時間とともに流れてくる映像・音を、どこからどこへ流すかを設計すること」 です。
そのうえで、まず次の感覚をしっかり押さえてください。
getUserMedia が返す MediaStream は「カメラ・マイクからの生の流れ」だということ。
MediaStream を video.srcObject に渡すと、そのままリアルタイム映像になること。
同じ MediaStream を MediaRecorder や AudioContext にも流し込めること。
Canvas を使うと、ストリームの「今この瞬間」を静止画として切り出せること。
おすすめの練習は、次の順番です。
カメラストリームを video に映すだけのページを作る。
同じストリームを MediaRecorder に渡して「録画してダウンロード」までやってみる。
Canvas に「今のフレームをキャプチャ」するボタンを足してみる。
ここまでやると、
ストリーム処理はもう「難しそうな言葉」ではなく、
「現実世界の映像・音の流れを、自分のアプリの中でどう配線するかを考える作業」
として、かなり手触りよく感じられるはずです。
