Skip to content

非公式本サイトは非公式の日本語ドキュメントであり、Cloudflare 公式サイトではありません。最新情報はdevelopers.cloudflare.comをご確認ください。

ブラウザーで最初の WebRTC 配信

ネイティブのブラウザー WebRTC だけを使い、サードパーティライブラリなしで、WHIP によりウェブカメラを Cloudflare Stream へ配信し、WHEP で再生します。

最終更新 Markdown で表示Agent セットアップ

このチュートリアルでは、WHIP を使ってブラウザーから Cloudflare Stream へ超低遅延のライブ動画を配信し、WHEP でブラウザー再生する方法を説明します。配信側と再生側の両方で、ブラウザー組み込みの WebRTC API だけを使います。インストールするライブラリも、外部アプリケーションも不要です。

完了すると、カメラとマイクを取得してライブ入力へ配信し、同じストリームをサブ秒の遅延で再生する、基本的な HTML ページができます。所要時間は 15 分未満です。

WHIP と WHEP は、WebRTC 向けのシンプルな HTTP ベースのシグナリングプロトコルです。どちらも、このコードは RTCPeerConnection を作り、ローカルのセッション記述(SDP offer)を生成し、HTTP の POST 1 回で Cloudflare の URL へ送り、返ってきた SDP answer を適用します。やり取りはリクエストと応答の 1 往復だけなので、独自のシグナリングサーバーは不要です。

始める前に

このチュートリアルには次が必要です。

  • ライブ入力を作成できる、次のいずれか。
    • 有料の Stream サブスクリプション。
    • Pro または Business のゾーンプラン。動画ストレージ 100 分と、動画配信 10,000 分が含まれます。
    • Stream が有効な Enterprise 契約。
  • カメラとマイク付きの、現行のブラウザー。
  • ページを https または localhost で配信すること。

1. ライブ入力を作成する

配信先は、必ずライブ入力です。次のいずれかで作成します。

応答には、このチュートリアルで使う 2 つの URL が含まれます。

API response from a POST request to /live_inputsjson
{
  "uid": "1a553f11a88915d093d45eda660d2f8c",
  ...
  "webRTC": {
    "url": "https://customer-<CODE>.cloudflarestream.com/<SECRET>/webRTC/publish"
  },
  "webRTCPlayback": {
    "url": "https://customer-<CODE>.cloudflarestream.com/<INPUT_UID>/webRTC/play"
  },
  ...
}
  • webRTC.url は配信先の WHIP エンドポイントです。配信用シークレットはこの URL に含まれます。 ストリームキーと同様に扱い、配信する人にだけ共有してください。
  • webRTCPlayback.url は視聴者が再生する WHEP エンドポイントです。ただし、入力で署名付き URL を有効にしている場合は除きます(ここでは扱いません)。

両方の URL をコピーし、次のコードへ貼り付けます。

2. WHIP で配信する

この配信スクリプトはローカルメディアを取得し、RTCPeerConnection に send-only トラックとして追加し、できた SDP offer を WHIP URL へ POST します。

基本的な HTML ページから始め、ローカルカメラのプレビュー用に <video> 要素を追加します。

<video id="broadcast-preview" autoplay muted playsinline></video>

次に、配信用のスクリプトを追加します。

broadcast.jsjavascript
// Paste the webRTC.url value from your live input.
const WHIP_URL = "<WHIP_URL_FROM_YOUR_LIVE_INPUT>";

async function startBroadcast() {
	// 1. Capture the camera and microphone.
	const media = await navigator.mediaDevices.getUserMedia({
		video: true,
		audio: true,
	});
	document.getElementById("broadcast-preview").srcObject = media;

	// 2. Create the peer connection and add each track as send-only.
	const pc = new RTCPeerConnection();
	media.getTracks().forEach((track) => {
		pc.addTransceiver(track, { direction: "sendonly" });
	});

	// 3. Create the SDP offer and set it as the local description.
	const offer = await pc.createOffer();
	await pc.setLocalDescription(offer);

	// 4. POST the offer to the WHIP endpoint.
	const response = await fetch(WHIP_URL, {
		method: "POST",
		headers: { "Content-Type": "application/sdp" },
		body: offer.sdp,
	});
	if (!response.ok) {
		throw new Error(`WHIP request failed: ${response.status}`);
	}

	// 5. Apply the SDP answer returned by Cloudflare.
	const answer = await response.text();
	await pc.setRemoteDescription({ type: "answer", sdp: answer });

	// The Location header identifies this session, used to stop it later.
	const sessionUrl = new URL(
		response.headers.get("Location"),
		WHIP_URL,
	).toString();

	return { pc, sessionUrl };
}

startBroadcast().catch(console.error);

startBroadcast() を呼び出し、カメラとマイクの許可を与えると、ブラウザーは接続をネゴシエートし、WebRTC でライブの動画と音声を Cloudflare へ送り始めます。コーデックを選ぶ必要はありません。ブラウザーが 対応コーデック を自動でネゴシエートします。

このスクリプトは、複数のカメラや音声ソースの切り替えは扱いません。ブラウザーが提供するデフォルトを使います。

3. WHEP で再生する

再生スクリプトは配信側の逆です。ローカルトラックを追加する代わりに、receive-only の transceiver を追加し、WHEP URL へ offer を POST し、受信メディアを <video> 要素へ接続します。

基本的な HTML ページから始め、再生用の <video> 要素を追加します。

<video id="playback-video" autoplay playsinline controls></video>

次に、再生用のスクリプトを追加します。

playback.jsjavascript
// Paste the webRTCPlayback.url value from your live input.
const WHEP_URL = "<WHEP_URL_FROM_YOUR_LIVE_INPUT>";

async function startPlayback() {
	const pc = new RTCPeerConnection();

	// 1. Ask to receive one audio track and one video track.
	pc.addTransceiver("video", { direction: "recvonly" });
	pc.addTransceiver("audio", { direction: "recvonly" });

	// 2. Attach incoming media to the video element as it arrives.
	const stream = new MediaStream();
	document.getElementById("playback-video").srcObject = stream;
	pc.ontrack = (event) => stream.addTrack(event.track);

	// 3. Create the SDP offer and set it as the local description.
	const offer = await pc.createOffer();
	await pc.setLocalDescription(offer);

	// 4. POST the offer to the WHEP endpoint.
	const response = await fetch(WHEP_URL, {
		method: "POST",
		headers: { "Content-Type": "application/sdp" },
		body: offer.sdp,
	});
	if (!response.ok) {
		throw new Error(`WHEP request failed: ${response.status}`);
	}

	// 5. Apply the SDP answer returned by Cloudflare.
	const answer = await response.text();
	await pc.setRemoteDescription({ type: "answer", sdp: answer });

	const sessionUrl = new URL(
		response.headers.get("Location"),
		WHEP_URL,
	).toString();

	return { pc, sessionUrl };
}

startPlayback().catch(console.error);

配信側がライブのあいだ、再生側は接続し、遅延 500 ミリ秒未満でストリームを表示します。

4. 配信を停止する

WebRTC セッションは、ページを閉じるか接続が切れると自動で終了します。ただし、ユーザーが終了したら明示的に止めてください。Location ヘッダーのセッション URL へ HTTP DELETE を送り、そのあとピア接続を閉じます。

stop.jsjavascript
async function stop({ pc, sessionUrl }) {
	if (sessionUrl) {
		await fetch(sessionUrl, { method: "DELETE" });
	}
	pc.close();
}

これは WHIP と WHEP の両方のセッションに当てはまります。startBroadcast() または startPlayback() が返すオブジェクトを渡してください。

5. 動作する完全な例

次の 1 ファイルは、ここまでの内容をまとめたものです。2 つのプレースホルダー URL を、ライブ入力の webRTC.urlwebRTCPlayback.url に置き換えます。そのあと、Workers または Pages で https 配信するか、localhost で配信してブラウザーで開きます。

index.htmlhtml
<!doctype html>
<html lang="en">
	<head>
		<meta charset="utf-8" />
		<title>Cloudflare Stream WHIP/WHEP example</title>
	</head>
	<body>
		<h2>Broadcast (WHIP)</h2>
		<video id="broadcast-preview" autoplay muted playsinline></video>
		<button id="broadcast-btn">Start broadcasting</button>

		<h2>Playback (WHEP)</h2>
		<video id="playback-video" autoplay playsinline controls></video>
		<button id="playback-btn">Start playback</button>

		<script type="module">
			const WHIP_URL = "<WHIP_URL_FROM_YOUR_LIVE_INPUT>";
			const WHEP_URL = "<WHEP_URL_FROM_YOUR_LIVE_INPUT>";

			async function negotiate(pc, url) {
				const offer = await pc.createOffer();
				await pc.setLocalDescription(offer);

				const response = await fetch(url, {
					method: "POST",
					headers: { "Content-Type": "application/sdp" },
					body: offer.sdp,
				});
				if (!response.ok) {
					throw new Error(`Request failed: ${response.status}`);
				}

				const answer = await response.text();
				await pc.setRemoteDescription({ type: "answer", sdp: answer });
				return new URL(response.headers.get("Location"), url).toString();
			}

			document
				.getElementById("broadcast-btn")
				.addEventListener("click", async () => {
					const media = await navigator.mediaDevices.getUserMedia({
						video: true,
						audio: true,
					});
					document.getElementById("broadcast-preview").srcObject = media;

					const pc = new RTCPeerConnection();
					media
						.getTracks()
						.forEach((track) =>
							pc.addTransceiver(track, { direction: "sendonly" }),
						);

					await negotiate(pc, WHIP_URL);
				});

			document
				.getElementById("playback-btn")
				.addEventListener("click", async () => {
					const pc = new RTCPeerConnection();
					pc.addTransceiver("video", { direction: "recvonly" });
					pc.addTransceiver("audio", { direction: "recvonly" });

					const stream = new MediaStream();
					document.getElementById("playback-video").srcObject = stream;
					pc.ontrack = (event) => stream.addTrack(event.track);

					await negotiate(pc, WHEP_URL);
				});
		</script>
	</body>
</html>

デバッグ

配信または再生のセッションが接続しない場合は、ブラウザー組み込みの WebRTC ツールで SDP のやり取りと ICE 接続状態を確認できます。

  • Chrome: chrome://webrtc-internals を開き、詳細ログとグラフを確認します。
  • Firefox: about:webrtc を開き、WebRTC セッションの情報を確認します。
  • Safari: インスペクターから設定タブ(歯車アイコン)を開き、ドロップダウンで WebRTC ログを "Verbose" にします。

よくある問題:

  • getUserMedia がエラーになる、または何も返さない — ページがセキュアに配信されていることと、カメラとマイクの許可を与えたことを確認します。
  • POST が失敗する — 正しい URL を貼り付けたことを確認します。配信には webRTC.url、再生には webRTCPlayback.url を使います。
  • 再生が黒いまま — 同じ入力で配信側がライブであることと、署名付き URL が有効になっていないことを確認します。

次のステップ

役に立ちましたか?