Agora.ioを使用したライブ配信機能の実装チュートリアル
こんにちは、チャリセです!
最近のブログではビデオ通話機能の実装について説明しましたが、今回はAgora.ioを使用したライブ配信機能の実装方法について解説します。ライブ配信は1人の配信者と多数の視聴者という構成で、教育、エンターテインメント、イベント配信など様々な用途に活用できます。
1. ライブ配信とビデオ通話の違い
ライブ配信とビデオ通話には以下のような主な違いがあります:
- 通信モード
- ライブ配信:Live-streaming mode
- ビデオ通話:RTC (Real-time Communication) mode
- レイテンシー
- ライブ配信:2-3秒程度の遅延
- ビデオ通話:400-800ミリ秒の遅延
- 参加者の役割
- ライブ配信:配信者(host)と視聴者(audience)の役割が明確
- ビデオ通話:全ての参加者が対等
2. 準備
- Agora.ioのアカウントとApp IDが必要です
- プロジェクトで以下の2つのファイルを作成します:
index.html
:ユーザーインターフェースstreaming.js
:ライブ配信機能の実装
3. HTML (index.html)
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Agora ライブ配信デモ</title>
<style>
.stream-container {
display: flex;
flex-wrap: wrap;
gap: 20px;
padding: 20px;
}
.stream-player {
width: 640px;
height: 480px;
background-color: #f0f0f0;
border: 1px solid #ccc;
}
.controls {
margin: 20px;
padding: 10px;
border: 1px solid #ddd;
border-radius: 5px;
}
.role-selector {
margin-bottom: 10px;
}
button {
margin: 5px;
padding: 8px 16px;
cursor: pointer;
}
.status {
margin: 10px;
padding: 5px;
background-color: #f8f9fa;
border-radius: 3px;
}
</style>
</head>
<body>
<h1>Agora ライブ配信デモ</h1>
<div class="controls">
<div class="role-selector">
<input type="radio" id="host" name="role" value="host" checked>
<label for="host">配信者</label>
<input type="radio" id="audience" name="role" value="audience">
<label for="audience">視聴者</label>
</div>
<button id="join">配信に参加</button>
<button id="leave">配信を終了</button>
<button id="toggleAudio">音声ミュート切替</button>
<button id="toggleVideo">ビデオミュート切替</button>
<div id="status" class="status">ステータス: 未接続</div>
</div>
<div id="stream-container" class="stream-container"></div>
<script src="https://download.agora.io/sdk/release/AgoraRTC_N-4.17.2.js"></script>
<script src="streaming.js"></script>
</body>
</html>
4. JavaScript (streaming.js)
// グローバル変数の定義
const rtc = {
client: null,
localAudioTrack: null,
localVideoTrack: null
};
const options = {
appId: "あなたのApp ID",
channel: "livestream",
token: null, // 本番環境では適切なトークンを使用してください
uid: Math.floor(Math.random() * 100000),
role: "host",
audioMuted: false,
videoMuted: false
};
// ステータス表示の更新関数
function updateStatus(message) {
document.getElementById("status").textContent = `ステータス: ${message}`;
}
// Agoraクライアントの初期化
async function initializeAgoraClient() {
rtc.client = AgoraRTC.createClient({
mode: "live",
codec: "vp8"
});
// ユーザーが配信を公開した時のイベントハンドラ
rtc.client.on("user-published", async (user, mediaType) => {
await rtc.client.subscribe(user, mediaType);
if (mediaType === "video") {
const playerContainer = document.createElement("div");
playerContainer.id = `player-${user.uid}`;
playerContainer.className = "stream-player";
document.getElementById("stream-container").append(playerContainer);
user.videoTrack.play(playerContainer);
updateStatus(`${user.uid}の配信を受信中`);
}
if (mediaType === "audio") {
user.audioTrack.play();
}
});
// ユーザーが配信を停止した時のイベントハンドラ
rtc.client.on("user-unpublished", (user) => {
const playerContainer = document.getElementById(`player-${user.uid}`);
playerContainer?.remove();
updateStatus(`${user.uid}の配信が終了`);
});
// 接続状態の監視
rtc.client.on("connection-state-change", (curState, prevState) => {
updateStatus(`接続状態: ${curState}`);
});
}
// 配信開始処理
async function startStreaming() {
try {
// ラジオボタンから選択された役割を取得
options.role = document.querySelector('input[name="role"]:checked').value;
// クライアントの役割を設定
await rtc.client.setClientRole(options.role);
updateStatus(`役割を${options.role}に設定`);
// チャンネルに参加
await rtc.client.join(options.appId, options.channel, options.token, options.uid);
updateStatus("チャンネルに参加しました");
// 配信者の場合のみ、トラックを作成して配信
if (options.role === "host") {
try {
rtc.localAudioTrack = await AgoraRTC.createMicrophoneAudioTrack();
rtc.localVideoTrack = await AgoraRTC.createCameraVideoTrack({
encoderConfig: {
width: 640,
height: 360,
frameRate: 15,
bitrateMin: 400,
bitrateMax: 1000
}
});
// ローカルビデオの表示
const localContainer = document.createElement("div");
localContainer.id = `player-${options.uid}`;
localContainer.className = "stream-player";
document.getElementById("stream-container").append(localContainer);
rtc.localVideoTrack.play(localContainer);
// トラックの公開
await rtc.client.publish([rtc.localAudioTrack, rtc.localVideoTrack]);
updateStatus("配信を開始しました");
// UI更新
document.getElementById("join").disabled = true;
document.getElementById("leave").disabled = false;
document.getElementById("toggleAudio").disabled = false;
document.getElementById("toggleVideo").disabled = false;
} catch (error) {
updateStatus(`デバイスのアクセスに失敗: ${error.message}`);
await rtc.client.leave();
return;
}
} else {
updateStatus("視聴者として参加しました");
document.getElementById("join").disabled = true;
document.getElementById("leave").disabled = false;
}
} catch (error) {
updateStatus(`エラーが発生しました: ${error.message}`);
}
}
// 配信終了処理
async function stopStreaming() {
try {
if (options.role === "host") {
// 配信者の場合、トラックを解放
rtc.localAudioTrack?.close();
rtc.localVideoTrack?.close();
}
// すべての視聴者のプレイヤーを削除
const container = document.getElementById("stream-container");
container.innerHTML = "";
// チャンネルから退出
await rtc.client.leave();
// UI更新
document.getElementById("join").disabled = false;
document.getElementById("leave").disabled = true;
document.getElementById("toggleAudio").disabled = true;
document.getElementById("toggleVideo").disabled = true;
updateStatus("配信を終了しました");
} catch (error) {
updateStatus(`エラーが発生しました: ${error.message}`);
}
}
// 音声ミュート切替
function toggleAudio() {
if (rtc.localAudioTrack) {
if (options.audioMuted) {
rtc.localAudioTrack.setEnabled(true);
options.audioMuted = false;
this.textContent = "音声ミュート";
updateStatus("音声をオンにしました");
} else {
rtc.localAudioTrack.setEnabled(false);
options.audioMuted = true;
this.textContent = "音声ミュート解除";
updateStatus("音声をミュートにしました");
}
}
}
// ビデオミュート切替
function toggleVideo() {
if (rtc.localVideoTrack) {
if (options.videoMuted) {
rtc.localVideoTrack.setEnabled(true);
options.videoMuted = false;
this.textContent = "ビデオミュート";
updateStatus("ビデオをオンにしました");
} else {
rtc.localVideoTrack.setEnabled(false);
options.videoMuted = true;
this.textContent = "ビデオミュート解除";
updateStatus("ビデオをミュートにしました");
}
}
}
// 初期化とイベントリスナーの設定
window.onload = async function() {
// Agoraクライアントの初期化
await initializeAgoraClient();
// ボタンのイベントリスナー設定
document.getElementById("join").onclick = startStreaming;
document.getElementById("leave").onclick = stopStreaming;
document.getElementById("toggleAudio").onclick = toggleAudio;
document.getElementById("toggleVideo").onclick = toggleVideo;
// 初期状態のUIセットアップ
document.getElementById("leave").disabled = true;
document.getElementById("toggleAudio").disabled = true;
document.getElementById("toggleVideo").disabled = true;
};
// ページ遷移時のクリーンアップ
window.onbeforeunload = async function() {
await stopStreaming();
};
5. 主な機能の説明
- 役割の選択
- 配信者(host)と視聴者(audience)の役割を切り替え可能
- 配信者のみがオーディオ・ビデオトラックを公開可能
- 配信の制御
- 配信の開始/終了
- 音声・ビデオのミュート制御
- 視聴者数の表示(実装可能)
- エラーハンドリング
- 配信開始/終了時のエラー処理
- ネットワーク接続の監視
6. セキュリティ対策
- トークンベースの認証
- App IDとトークンはサーバーサイドで管理
- 一時トークンは定期的に更新
- 役割の制御
- 配信者の権限を適切に管理
- なりすまし防止の実装
7. パフォーマンス最適化
- ビデオ品質の設定
// ビデオプロファイルの設定例
rtc.localVideoTrack.setEncoderConfiguration({
width: 640,
height: 360,
frameRate: 15,
bitrateMin: 400,
bitrateMax: 1000,
});
- 帯域幅の管理
1. 視聴者数に応じた品質調整
2. ネットワーク状況に応じた動的な品質制御
まとめ
Agora.ioを使用したライブ配信機能の実装について、基本的な実装から発展的な機能まで解説しました。このコードをベースに、プロジェクトの要件に応じてカスタマイズしていくことで、様々なユースケースに対応できます。
セキュリティ、パフォーマンス、ユーザビリティなど、考慮すべき点は多岐にわたりますが、Agora.ioの充実したSDKを活用することで、安定した配信システムを構築できます。
HappyCoding!!
現在
株式会社チョモランマ
株式会社シェルパ
3Dmodeljapan株式会社
ではスタッフを大募集しております!!
Unreal Engine4、AI、プログラミングや建築パースに興味がある方!
ぜひご応募下さい!!
初心者の方、未経験の方やインターンを受けてみたい方々でも大歓迎です!!