WebRTC が「connecting」のまま進まない:ICE 失敗を 5 分で切り分ける手順
WebRTC でビデオ通話やリアルタイム通信を実装した際、接続が「connecting」のまま止まってしまうことはありませんか?
この記事では、WebRTC の ICE(Interactive Connectivity Establishment)プロセスの失敗を素早く特定し、解決するための実践的な手順を解説します。開発現場で遭遇しやすいトラブルに焦点を当て、5 分で原因を切り分けられる方法をお伝えしましょう。
背景
WebRTC 接続の仕組み
WebRTC は、ブラウザ間でリアルタイムに音声・映像・データを送受信するための技術です。しかし、直接接続するためには、NAT やファイアウォールを越える必要があります。
以下の図は、WebRTC 接続における主要なコンポーネントの関係性を示しています。
mermaidflowchart TB
clientA["クライアント A<br/>(ブラウザ)"]
clientB["クライアント B<br/>(ブラウザ)"]
signaling["シグナリングサーバー<br/>(WebSocket/HTTP)"]
stun["STUN サーバー<br/>(NAT 越え支援)"]
turn["TURN サーバー<br/>(リレー中継)"]
clientA -->|"1. SDP Offer/Answer"| signaling
signaling -->|"1. SDP Offer/Answer"| clientB
clientA -.->|"2. STUN リクエスト"| stun
clientB -.->|"2. STUN リクエスト"| stun
stun -.->|"Public IP 返却"| clientA
stun -.->|"Public IP 返却"| clientB
clientA -->|"3a. P2P 接続試行"| clientB
clientA -.->|"3b. TURN 経由<br/>(P2P 失敗時)"| turn
turn -.->|"3b. TURN 経由<br/>(P2P 失敗時)"| clientB
図の要点:WebRTC 接続は、シグナリング → STUN による NAT 越え → P2P または TURN 経由という段階を経ます。
ICE の役割
ICE(Interactive Connectivity Establishment)は、ピア間の最適な通信経路を見つけるためのプロトコルです。複数の候補(Candidate)を収集し、優先順位をつけて接続を試みます。
mermaidstateDiagram-v2
[*] --> new: RTCPeerConnection 作成
new --> checking: ICE Candidate 収集開始
checking --> connected: 接続成功
checking --> failed: 全候補失敗
connected --> completed: 最適経路確定
connected --> disconnected: 一時的切断
disconnected --> connected: 再接続成功
disconnected --> failed: 再接続失敗
failed --> [*]
completed --> [*]
図の要点:ICE は複数の状態を遷移し、「checking」から「connected」に進めない場合、ICE 失敗と判断されます。
ICE が失敗すると、接続状態が「connecting」や「checking」のまま止まってしまい、通信が確立されません。
課題
「connecting」で止まる主な原因
WebRTC の接続が進まない場合、以下のような課題が考えられます。
| # | 原因カテゴリ | 具体的な問題 | 頻度 |
|---|---|---|---|
| 1 | ICE Candidate の不足 | STUN/TURN サーバーの設定ミス | ★★★ |
| 2 | ファイアウォール | UDP ポートがブロックされている | ★★★ |
| 3 | シグナリングの不備 | ICE Candidate が相手に届いていない | ★★☆ |
| 4 | ネットワーク環境 | Symmetric NAT で P2P 不可 | ★★☆ |
| 5 | ブラウザ制限 | セキュアコンテキスト (HTTPS) 未対応 | ★☆☆ |
これらの課題を素早く切り分けることが、トラブルシューティングの鍵となります。
以下の図は、ICE 接続失敗の主な原因と、それぞれが発生するポイントを示しています。
mermaidflowchart TD
start["ICE 接続失敗"]
check1{"ICE Candidate<br/>収集できた?"}
check2{"STUN/TURN<br/>設定は正しい?"}
check3{"Candidate<br/>交換できた?"}
check4{"ファイアウォール<br/>は開いている?"}
result1["原因 1:<br/>STUN/TURN 設定ミス"]
result2["原因 2:<br/>シグナリング不備"]
result3["原因 3:<br/>ファイアウォールブロック"]
result4["原因 4:<br/>Symmetric NAT"]
start --> check1
check1 -->|NO| check2
check2 -->|NO| result1
check2 -->|YES| result1
check1 -->|YES| check3
check3 -->|NO| result2
check3 -->|YES| check4
check4 -->|NO| result3
check4 -->|YES| result4
図で理解できる要点:
- ICE 失敗は段階的に切り分けられる
- Candidate 収集とシグナリング、ネットワーク設定の 3 つが主要チェックポイント
解決策
5 分で実践できる切り分け手順
ここでは、実際の開発現場で使える診断手順を順番に紹介します。各ステップは 1 分程度で確認できますので、焦らず進めていきましょう。
ステップ 1:ICE 状態のログを取得
まずは、現在の ICE 接続状態を確認するためのログを仕込みます。
以下のコードは、RTCPeerConnection のイベントリスナーを設定し、接続状態を監視するものです。
typescript// RTCPeerConnection のインスタンスを作成
const peerConnection = new RTCPeerConnection({
iceServers: [{ urls: 'stun:stun.l.google.com:19302' }],
});
次に、ICE 接続状態の変化を監視するリスナーを追加しましょう。
typescript// ICE 接続状態の変化を監視
peerConnection.addEventListener(
'iceconnectionstatechange',
() => {
console.log(
'ICE Connection State:',
peerConnection.iceConnectionState
);
// 各状態の意味
// new: まだ候補を収集していない
// checking: 候補をチェック中
// connected: 少なくとも 1 つの候補で接続成功
// completed: すべての候補をチェック完了
// failed: すべての候補で接続失敗
// disconnected: 接続が切れた
// closed: 接続が閉じられた
}
);
さらに、ICE 収集状態も確認します。
typescript// ICE 収集状態の変化を監視
peerConnection.addEventListener(
'icegatheringstatechange',
() => {
console.log(
'ICE Gathering State:',
peerConnection.iceGatheringState
);
// gathering: 候補を収集中
// complete: 収集完了
}
);
確認ポイント: iceConnectionState が「checking」のまま「connected」に進まない場合、ICE 失敗の可能性が高いです。
ステップ 2:ICE Candidate の収集確認
ICE Candidate が正しく収集されているかを確認します。これにより、STUN/TURN サーバーの設定ミスを検出できるでしょう。
typescript// ICE Candidate の収集を監視
peerConnection.addEventListener('icecandidate', (event) => {
if (event.candidate) {
console.log('ICE Candidate:', event.candidate);
// Candidate のタイプを確認
// host: ローカルネットワークアドレス
// srflx: STUN で取得した外部アドレス
// relay: TURN サーバー経由のアドレス
console.log('Candidate Type:', event.candidate.type);
console.log(
'Candidate Address:',
event.candidate.address
);
} else {
// null の場合、すべての候補収集が完了
console.log('ICE Candidate 収集完了');
}
});
収集された Candidate の種類を分類して表示する関数を作成しましょう。
typescript// Candidate の種類別に集計する関数
function analyzeCandidates(candidates: RTCIceCandidate[]) {
const types = {
host: 0,
srflx: 0,
relay: 0,
};
candidates.forEach((candidate) => {
if (candidate.type === 'host') types.host++;
if (candidate.type === 'srflx') types.srflx++;
if (candidate.type === 'relay') types.relay++;
});
console.log('Candidate 集計:', types);
return types;
}
診断ポイント:
hostのみ:STUN/TURN サーバーが機能していないsrflxがある:STUN は動作しているrelayがある:TURN も動作している
ステップ 3:STUN/TURN サーバーの設定確認
STUN/TURN サーバーの設定が正しいか確認します。設定ミスは最も頻繁に発生する問題です。
typescript// 正しい STUN/TURN 設定の例
const configuration: RTCConfiguration = {
iceServers: [
// Google の公開 STUN サーバー
{
urls: 'stun:stun.l.google.com:19302',
},
// Cloudflare の公開 STUN サーバー
{
urls: 'stun:stun.cloudflare.com:3478',
},
],
};
TURN サーバーを使う場合は、認証情報も必要になります。
typescript// TURN サーバーの設定(認証あり)
const configurationWithTurn: RTCConfiguration = {
iceServers: [
{
urls: 'stun:stun.l.google.com:19302',
},
{
urls: 'turn:your-turn-server.com:3478',
username: 'your-username',
credential: 'your-password',
},
],
};
const peerConnectionWithTurn = new RTCPeerConnection(
configurationWithTurn
);
設定が正しく読み込まれているか確認する関数も用意しましょう。
typescript// ICE サーバー設定の確認関数
function verifyIceServers(pc: RTCPeerConnection) {
const config = pc.getConfiguration();
console.log('設定されている ICE サーバー:');
config.iceServers?.forEach((server, index) => {
console.log(`Server ${index + 1}:`, {
urls: server.urls,
username: server.username ? '設定あり' : '設定なし',
credential: server.credential
? '設定あり'
: '設定なし',
});
});
}
// 実行例
verifyIceServers(peerConnection);
よくある設定ミス:
- URL のスペルミス(
stunをsturnと誤記) - TURN サーバーの認証情報が古い
- ポート番号の間違い
ステップ 4:シグナリングの確認
ICE Candidate が相手に正しく送信されているか確認します。シグナリングサーバーの実装ミスを検出できるでしょう。
以下は、WebSocket を使ったシグナリングの実装例です。
typescript// WebSocket 接続の確立
const signalingSocket = new WebSocket(
'wss://your-signaling-server.com'
);
signalingSocket.addEventListener('open', () => {
console.log('シグナリングサーバーに接続しました');
});
ICE Candidate を相手に送信する処理を実装します。
typescript// ICE Candidate を相手に送信
peerConnection.addEventListener('icecandidate', (event) => {
if (event.candidate) {
// Candidate をシグナリングサーバー経由で送信
const message = {
type: 'ice-candidate',
candidate: event.candidate.toJSON(),
};
signalingSocket.send(JSON.stringify(message));
console.log(
'ICE Candidate を送信:',
event.candidate.type
);
}
});
相手から受信した ICE Candidate を追加する処理も必要です。
typescript// 相手からの ICE Candidate を受信
signalingSocket.addEventListener(
'message',
async (event) => {
const message = JSON.parse(event.data);
if (
message.type === 'ice-candidate' &&
message.candidate
) {
try {
await peerConnection.addIceCandidate(
new RTCIceCandidate(message.candidate)
);
console.log('相手の ICE Candidate を追加しました');
} catch (error) {
console.error('ICE Candidate 追加エラー:', error);
}
}
}
);
送受信の整合性を確認するカウンター機能を追加しましょう。
typescript// シグナリングの統計情報
const signalingStats = {
candidatesSent: 0,
candidatesReceived: 0,
};
// 送信時にカウント
peerConnection.addEventListener('icecandidate', (event) => {
if (event.candidate) {
signalingStats.candidatesSent++;
console.log(
`送信: ${signalingStats.candidatesSent} 個目の Candidate`
);
}
});
// 受信時にカウント
signalingSocket.addEventListener('message', (event) => {
const message = JSON.parse(event.data);
if (message.type === 'ice-candidate') {
signalingStats.candidatesReceived++;
console.log(
`受信: ${signalingStats.candidatesReceived} 個目の Candidate`
);
}
});
診断ポイント:
- 送信カウントが 0:自分の Candidate が収集されていない
- 受信カウントが 0:相手の Candidate が届いていない(シグナリングの問題)
ステップ 5:ファイアウォール・ネットワーク確認
最後に、ネットワークレベルの問題を確認します。企業ネットワークや厳しい NAT 環境では、この問題が頻発するでしょう。
typescript// ネットワーク診断用の関数
async function diagnoseNetworkConnectivity() {
const results = {
stunReachable: false,
turnReachable: false,
p2pPossible: false,
};
// 診断用の PeerConnection を作成
const testPc = new RTCPeerConnection({
iceServers: [{ urls: 'stun:stun.l.google.com:19302' }],
});
return new Promise<typeof results>((resolve) => {
const candidateTypes = new Set<string>();
testPc.addEventListener('icecandidate', (event) => {
if (event.candidate) {
candidateTypes.add(event.candidate.type);
} else {
// 収集完了時に結果を判定
results.stunReachable = candidateTypes.has('srflx');
results.p2pPossible =
candidateTypes.has('srflx') ||
candidateTypes.has('host');
testPc.close();
resolve(results);
}
});
// ダミーのデータチャネルを作成して ICE 収集を開始
testPc.createDataChannel('test');
testPc
.createOffer()
.then((offer) => testPc.setLocalDescription(offer));
});
}
診断結果を見やすく表示する関数を作成します。
typescript// 診断実行と結果表示
async function runNetworkDiagnostics() {
console.log('ネットワーク診断を開始します...');
const results = await diagnoseNetworkConnectivity();
console.log('=== 診断結果 ===');
console.log(
`STUN 到達性: ${
results.stunReachable ? '✓ OK' : '✗ NG'
}`
);
console.log(
`P2P 接続可能性: ${
results.p2pPossible ? '✓ OK' : '✗ NG'
}`
);
// 推奨アクション
if (!results.stunReachable) {
console.warn(
'推奨: TURN サーバーの導入を検討してください'
);
}
if (!results.p2pPossible) {
console.error(
'警告: 現在のネットワークでは P2P 接続ができません'
);
console.log('対策: TURN サーバーが必須です');
}
return results;
}
// 実行
runNetworkDiagnostics();
ネットワーク問題のチェックリスト:
| # | 確認項目 | 確認方法 | 対処法 |
|---|---|---|---|
| 1 | UDP ポートが開いているか | ブラウザの開発者ツールで確認 | ファイアウォール設定を変更 |
| 2 | HTTPS で接続しているか | URL を確認 | HTTPS 化する |
| 3 | Symmetric NAT か | srflx Candidate の有無 | TURN サーバーを使用 |
| 4 | VPN を使用しているか | ネットワーク設定を確認 | VPN を無効化してテスト |
具体例
実践:診断ツールの実装
これまでの手順を統合した、包括的な診断ツールを実装してみましょう。このツールは、WebRTC の接続問題を自動的に診断し、レポートを生成します。
typescript// 診断結果の型定義
interface DiagnosticReport {
timestamp: string;
iceConnectionState: RTCIceConnectionState;
iceGatheringState: RTCIceGatheringState;
candidates: {
host: number;
srflx: number;
relay: number;
};
issues: string[];
recommendations: string[];
}
診断ツールのメインクラスを定義します。
typescript// WebRTC 診断ツールクラス
class WebRTCDiagnostics {
private peerConnection: RTCPeerConnection;
private collectedCandidates: RTCIceCandidate[] = [];
private report: DiagnosticReport;
constructor(configuration: RTCConfiguration) {
this.peerConnection = new RTCPeerConnection(
configuration
);
this.report = this.initializeReport();
this.setupEventListeners();
}
private initializeReport(): DiagnosticReport {
return {
timestamp: new Date().toISOString(),
iceConnectionState: 'new',
iceGatheringState: 'new',
candidates: { host: 0, srflx: 0, relay: 0 },
issues: [],
recommendations: [],
};
}
private setupEventListeners(): void {
// 次のコードブロックで実装
}
}
イベントリスナーを設定し、状態変化を記録します。
typescript// イベントリスナーの設定
private setupEventListeners(): void {
// ICE 接続状態の監視
this.peerConnection.addEventListener('iceconnectionstatechange', () => {
this.report.iceConnectionState = this.peerConnection.iceConnectionState;
console.log('ICE Connection State:', this.report.iceConnectionState);
});
// ICE 収集状態の監視
this.peerConnection.addEventListener('icegatheringstatechange', () => {
this.report.iceGatheringState = this.peerConnection.iceGatheringState;
console.log('ICE Gathering State:', this.report.iceGatheringState);
});
// ICE Candidate の収集
this.peerConnection.addEventListener('icecandidate', (event) => {
if (event.candidate) {
this.collectedCandidates.push(event.candidate);
this.categorizeCandidate(event.candidate);
}
});
}
収集した Candidate を分類する処理を実装します。
typescript// Candidate の分類
private categorizeCandidate(candidate: RTCIceCandidate): void {
switch (candidate.type) {
case 'host':
this.report.candidates.host++;
break;
case 'srflx':
this.report.candidates.srflx++;
break;
case 'relay':
this.report.candidates.relay++;
break;
}
console.log(`Candidate 追加 [${candidate.type}]:`, candidate.address);
}
診断を実行し、問題を検出するメソッドを実装しましょう。
typescript// 診断の実行
async runDiagnostics(): Promise<DiagnosticReport> {
console.log('診断を開始します...');
// ダミーのデータチャネルを作成
this.peerConnection.createDataChannel('diagnostics');
// Offer を作成して ICE 収集を開始
const offer = await this.peerConnection.createOffer();
await this.peerConnection.setLocalDescription(offer);
// ICE 収集完了を待つ(最大 5 秒)
await this.waitForIceGathering(5000);
// 問題の検出
this.detectIssues();
// レポートを返す
return this.report;
}
ICE 収集の完了を待つヘルパーメソッドを作成します。
typescript// ICE 収集完了を待つ
private waitForIceGathering(timeout: number): Promise<void> {
return new Promise((resolve) => {
const checkComplete = () => {
if (this.report.iceGatheringState === 'complete') {
resolve();
}
};
this.peerConnection.addEventListener('icegatheringstatechange', checkComplete);
// タイムアウト設定
setTimeout(() => {
console.warn('ICE 収集がタイムアウトしました');
resolve();
}, timeout);
});
}
検出された問題を分析し、推奨事項を生成します。
typescript// 問題の検出と推奨事項の生成
private detectIssues(): void {
const { candidates } = this.report;
// STUN サーバーの問題
if (candidates.host > 0 && candidates.srflx === 0) {
this.report.issues.push(
'STUN サーバーから srflx Candidate を取得できませんでした'
);
this.report.recommendations.push(
'STUN サーバーの設定を確認してください'
);
this.report.recommendations.push(
'ファイアウォールで UDP ポートがブロックされていないか確認してください'
);
}
// TURN サーバーの問題
if (candidates.relay === 0) {
this.report.issues.push(
'TURN サーバーから relay Candidate を取得できませんでした'
);
this.report.recommendations.push(
'Symmetric NAT 環境では TURN サーバーが必須です'
);
}
// Candidate が全く収集されない
if (candidates.host === 0) {
this.report.issues.push(
'ICE Candidate が全く収集されませんでした'
);
this.report.recommendations.push(
'ブラウザの WebRTC 機能が有効か確認してください'
);
this.report.recommendations.push(
'HTTPS で接続しているか確認してください'
);
}
}
診断レポートを見やすく表示するメソッドを追加します。
typescript// レポートの表示
displayReport(): void {
console.log('\n=== WebRTC 診断レポート ===');
console.log(`実行時刻: ${this.report.timestamp}`);
console.log(`ICE 接続状態: ${this.report.iceConnectionState}`);
console.log(`ICE 収集状態: ${this.report.iceGatheringState}`);
console.log('\n--- 収集された Candidate ---');
console.log(`Host: ${this.report.candidates.host} 個`);
console.log(`Server Reflexive (srflx): ${this.report.candidates.srflx} 個`);
console.log(`Relay: ${this.report.candidates.relay} 個`);
if (this.report.issues.length > 0) {
console.log('\n--- 検出された問題 ---');
this.report.issues.forEach((issue, index) => {
console.log(`${index + 1}. ${issue}`);
});
}
if (this.report.recommendations.length > 0) {
console.log('\n--- 推奨事項 ---');
this.report.recommendations.forEach((rec, index) => {
console.log(`${index + 1}. ${rec}`);
});
}
console.log('========================\n');
}
診断ツールの使用例を示します。
typescript// 使用例
async function runWebRTCDiagnostics() {
// 診断ツールのインスタンス作成
const diagnostics = new WebRTCDiagnostics({
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' },
{ urls: 'stun:stun.cloudflare.com:3478' },
],
});
// 診断実行
const report = await diagnostics.runDiagnostics();
// 結果表示
diagnostics.displayReport();
return report;
}
// 実行
runWebRTCDiagnostics();
トラブルシューティングフローチャート
以下の図は、実際のトラブルシューティングで使える判断フローを示しています。
mermaidflowchart TD
start["接続が connecting で止まる"]
step1{"ICE Candidate<br/>が収集されている?"}
step2{"srflx Candidate<br/>がある?"}
step3{"相手から Candidate<br/>を受信している?"}
step4{"ファイアウォールで<br/>UDP が開いている?"}
step5{"TURN サーバーを<br/>使用している?"}
fix1["STUN/TURN 設定を確認<br/>診断ツールを実行"]
fix2["STUN サーバーを追加<br/>UDP ポート開放を依頼"]
fix3["シグナリングサーバーを確認<br/>Candidate 送受信をログ出力"]
fix4["ファイアウォール設定を変更<br/>ネットワーク管理者に相談"]
fix5["TURN サーバーを導入<br/>Symmetric NAT 対策"]
success["接続成功"]
start --> step1
step1 -->|NO| fix1
step1 -->|YES| step2
step2 -->|NO| step4
step2 -->|YES| step3
step3 -->|NO| fix3
step3 -->|YES| success
step4 -->|NO| fix4
step4 -->|YES| step5
step5 -->|NO| fix5
step5 -->|YES| success
fix1 --> success
fix2 --> success
fix3 --> success
fix4 --> success
fix5 --> success
フローチャートの活用ポイント:
- 上から順に確認することで、5 分以内に原因を特定できる
- 各判断ノードで診断ツールのログを参照する
- 修正後は必ず接続テストを実行する
よくあるエラーコードと対処法
WebRTC の接続トラブルで遭遇しやすいエラーと、その解決方法を整理しましょう。
エラー 1:ICE failed, add a STUN server
エラーメッセージ:
textError: ICE failed, add a STUN server and see about:webrtc for more details
発生条件:
- STUN サーバーが設定されていない
- ICE Candidate が 1 つも収集されない
解決方法:
- RTCConfiguration に STUN サーバーを追加する
typescriptconst configuration: RTCConfiguration = {
iceServers: [{ urls: 'stun:stun.l.google.com:19302' }],
};
-
ブラウザの
about:webrtcまたはchrome://webrtc-internals/で詳細を確認する -
複数の STUN サーバーを設定して冗長化する
typescriptconst configuration: RTCConfiguration = {
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' },
{ urls: 'stun:stun1.l.google.com:19302' },
{ urls: 'stun:stun.cloudflare.com:3478' },
],
};
エラー 2:Failed to execute 'addIceCandidate'
エラーメッセージ:
textDOMException: Failed to execute 'addIceCandidate' on 'RTCPeerConnection':
Error processing ICE candidate
発生条件:
- リモート記述(Remote Description)が設定される前に ICE Candidate を追加しようとした
- 無効な Candidate フォーマット
解決方法:
- リモート記述の設定後に Candidate を追加する
typescript// 正しい順序
await peerConnection.setRemoteDescription(
remoteDescription
);
// その後に Candidate を追加
await peerConnection.addIceCandidate(candidate);
- Candidate を一時的にキューに保存する実装
typescript// Candidate キューの実装
class CandidateQueue {
private queue: RTCIceCandidate[] = [];
private remoteDescriptionSet = false;
constructor(private pc: RTCPeerConnection) {}
async setRemoteDescription(
desc: RTCSessionDescriptionInit
) {
await this.pc.setRemoteDescription(desc);
this.remoteDescriptionSet = true;
// キューに溜まった Candidate を追加
await this.flushQueue();
}
async addIceCandidate(candidate: RTCIceCandidate) {
if (this.remoteDescriptionSet) {
// すぐに追加
await this.pc.addIceCandidate(candidate);
} else {
// キューに保存
this.queue.push(candidate);
}
}
private async flushQueue() {
for (const candidate of this.queue) {
await this.pc.addIceCandidate(candidate);
}
this.queue = [];
}
}
使用例を示します。
typescript// 使用例
const queue = new CandidateQueue(peerConnection);
// シグナリングから受信した Candidate を追加
signalingSocket.addEventListener(
'message',
async (event) => {
const message = JSON.parse(event.data);
if (message.type === 'answer') {
await queue.setRemoteDescription(message.answer);
}
if (
message.type === 'ice-candidate' &&
message.candidate
) {
await queue.addIceCandidate(
new RTCIceCandidate(message.candidate)
);
}
}
);
エラー 3:NetworkError when attempting to fetch resource
エラーメッセージ:
textNetworkError: A network error occurred while attempting to fetch resource
発生条件:
- HTTPS ではないページで WebRTC を使用している(一部ブラウザ)
- シグナリングサーバーに接続できない
解決方法:
- HTTPS でページを提供する
javascript// 開発環境で HTTPS を有効化(Next.js の例)
// package.json
{
"scripts": {
"dev": "next dev --experimental-https"
}
}
-
ローカル開発では
localhostを使用する(HTTP でも許可される) -
シグナリングサーバーの接続確認
typescript// WebSocket 接続のエラーハンドリング
const socket = new WebSocket('wss://your-server.com');
socket.addEventListener('error', (event) => {
console.error('WebSocket エラー:', event);
console.log(
'対策: サーバーの URL とポートを確認してください'
);
});
socket.addEventListener('close', (event) => {
console.warn('WebSocket 切断:', event.code, event.reason);
});
エラーコード対応表:
| エラーコード | 意味 | 主な原因 | 対処法 |
|---|---|---|---|
| 1000 | 正常切断 | 正常な接続終了 | 対応不要 |
| 1006 | 異常切断 | ネットワーク障害 | 再接続処理を実装 |
| 1008 | ポリシー違反 | 認証失敗など | 認証情報を確認 |
| 1011 | サーバーエラー | サーバー側の問題 | サーバーログを確認 |
まとめ
WebRTC の「connecting」状態から進まない問題は、ICE プロセスの失敗が主な原因です。本記事では、5 分で原因を切り分けるための実践的な手順をご紹介しました。
重要なポイントを振り返りましょう:
- ICE 状態のログ取得:
iceConnectionStateとiceGatheringStateを監視する - Candidate の収集確認:
host、srflx、relayの 3 種類が収集されているか確認する - STUN/TURN 設定確認:URL のスペルミスや認証情報の誤りをチェックする
- シグナリングの確認:Candidate の送受信が正しく行われているか検証する
- ネットワーク確認:ファイアウォールや NAT の種類を確認し、必要なら TURN サーバーを導入する
本記事で紹介した診断ツールを活用すれば、問題の原因を素早く特定できるでしょう。特に Symmetric NAT 環境では、TURN サーバーの導入が必須となるケースが多いことを覚えておいてください。
WebRTC のトラブルシューティングは慣れるまで難しく感じるかもしれませんが、段階的に確認していけば必ず原因にたどり着けますよ。
関連リンク
articleWebRTC が「connecting」のまま進まない:ICE 失敗を 5 分で切り分ける手順
articleWebRTC AV1/VP9/H.264 ベンチ比較 2025:画質・CPU/GPU 負荷・互換性を実測
articleWebRTC で遠隔支援:画面注釈・ポインタ共有・低遅延音声の実装事例
articleWebRTC で E2EE ビデオ会議:Insertable Streams と鍵交換を実装する手順
articleWebRTC 完全メッシュ vs SFU 設計比較:同時接続数と帯域コストを数式で見積もる
articleWebRTC 開発環境セットアップ完全版:ローカル NAT/HTTPS/証明書を最短で通す手順
articleWebRTC が「connecting」のまま進まない:ICE 失敗を 5 分で切り分ける手順
articleWeb Components が “is not a constructor” で落ちる時:定義順序と複数登録の衝突を解決
articleVitest モジュールモック技術の基礎と応用:`vi.mock` / `vi.spyOn` を極める
articleVue.js リアクティビティ内部解剖:Proxy/ref/computed を図で読み解く
articleVite CSS HMR が反映されない時のチェックリスト:PostCSS/Modules/Cache 編
articleTailwind CSS 2025 年ロードマップ総ざらい:新機能・互換性・移行の見取り図
blogiPhone 17シリーズの発表!全モデルiPhone 16から進化したポイントを見やすく整理
blogGoogleストアから訂正案内!Pixel 10ポイント有効期限「1年」表示は誤りだった
blog【2025年8月】Googleストア「ストアポイント」は1年表記はミス?2年ルールとの整合性を検証
blogGoogleストアの注文キャンセルはなぜ起きる?Pixel 10購入前に知るべき注意点
blogPixcel 10シリーズの発表!全モデル Pixcel 9 から進化したポイントを見やすく整理
blogフロントエンドエンジニアの成長戦略:コーチングで最速スキルアップする方法
review今の自分に満足していますか?『持たざる者の逆襲 まだ何者でもない君へ』溝口勇児
reviewついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
review愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
review週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
review新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
review科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来