SIP.js開発でつまずきやすいポイントまとめ|ブラウザ通話実装の落とし穴

JavaScript
この記事は約20分で読めます。

この記事の最終更新日: 2026年7月1日


はじめに

SIP.jsは、ブラウザ上でSIP通話を実装するためのJavaScriptライブラリです。
ブラウザからSIPサーバーへ接続し、WebRTCを使って音声通話やビデオ通話を実現できます。

ただし、SIP.js開発は単にライブラリをインストールすれば動くものではありません。

実際には、次のような複数の技術が関係します。

  • SIP
  • WebSocket
  • WebRTC
  • SIPサーバー
  • マイク権限
  • ICE / STUN / TURN
  • ブラウザの自動再生制限
  • 証明書
  • NAT越え

SIP自体は、通話などのセッションを作成・変更・終了するためのアプリケーション層プロトコルです。また、ブラウザでは一般的なUDP/TCPのSIPクライアントとは違い、SIP over WebSocketを使う構成になります。SIP over WebSocketはRFC 7118で定義されています。(IETF)

この記事では、SIP.js開発でつまずきやすいポイントを、実装時の落とし穴として整理します。


SIP.js開発で最初に理解すべきこと

SIP.jsは「ブラウザでSIPを扱うためのライブラリ」です。
しかし、SIP.js単体で通話サービスが完成するわけではありません。

SIP.jsを使ったブラウザ通話では、基本的に次のような構成になります。

ブラウザ
  ↓ SIP over WebSocket
SIPサーバー / PBX
  ↓
相手端末・内線・外線・別クライアント

SIP.jsの公式ガイドでも、接続先としてSIP WebSocketサーバーのURLを指定する必要があると説明されています。例として wss://example.com:8443 のようなWebSocket URLを transportOptions に設定します。(SIP.js)

つまり、SIP.jsを使う場合は、以下の3つを分けて考える必要があります。

SIP.js:ブラウザ側のSIPクライアント実装
SIPサーバー:通話の接続・認証・ルーティングを担当
WebRTC:ブラウザ間・ブラウザとサーバー間のメディア通信を担当

ここを混同すると、「SIP.jsを入れたのに通話できない」という状態になりやすいです。


つまずき1:SIP.jsだけで通話できると思ってしまう

最初につまずきやすいのが、SIP.jsを導入すればすぐに通話できると思ってしまうことです。

SIP.jsは、あくまでブラウザ側のSIPクライアントを実装するためのライブラリです。
通話を成立させるには、SIP over WebSocketに対応したSIPサーバーが必要です。

例えば、Asteriskを使う場合でも、WebRTC向けには暗号化、AVPF、ICE、DTLS、RTCP mux、WebSocket transportなどの設定が必要になります。SIP.js公式のAsterisk設定例でも、WebRTC client用の設定として encryption=yesavpf=yesicesupport=yesdtlsenable=yesrtcp_mux=yestransport=udp,ws,wss などが示されています。(SIP.js)

そのため、次のような考え方が必要です。

SIP.jsを入れる
↓
SIPサーバーへWebSocketで接続する
↓
SIP認証・REGISTERを行う
↓
INVITEで発信する
↓
WebRTCで音声メディアを流す

SIP.jsはこの全体のうち、主にブラウザ側の制御を担当します。


つまずき2:WebSocket接続とSIP登録を混同する

SIP.jsでは、WebSocket接続とSIP登録を分けて考える必要があります。

よくある誤解は、次のようなものです。

WebSocketが接続できた
= SIP登録できた
= 着信できる

これは正しくありません。

SIP.jsの公式ガイドでは、まず UserAgent を作成し、userAgent.start() でTransportへ接続し、その後 Registerer を使って register() を実行する流れが説明されています。匿名UserAgentは発信やメッセージ送信はできますが、着信はできないとも説明されています。(SIP.js)

つまり、状態は少なくとも次のように分かれます。

1. WebSocket接続できていない
2. WebSocket接続はできたがSIP登録できていない
3. SIP登録はできたが発信できない
4. 発信はできたが音声が流れない
5. 音声は流れているがブラウザで再生できていない

SIP.js開発では、エラーを一括りに「通話できない」と見るのではなく、どの段階で止まっているかを切り分けることが重要です。


つまずき3:SIP URI、認証ユーザー名、内線番号を混同する

SIP.jsの設定では、似たような値が複数出てきます。

例えば、次のような値です。

const aor = "sip:alice@example.com";
const authorizationUsername = "alice";
const authorizationPassword = "password";
const destination = "sip:bob@example.com";

ここで混同しやすいのが、以下の違いです。

項目役割
AORAddress of Record。自分のSIPアドレス
authorizationUsernameSIP認証に使うユーザー名
authorizationPasswordSIP認証に使うパスワード
destination発信先のSIP URI
WebSocket URLSIPサーバーへ接続するURL

特に、内線番号と認証ユーザー名が同じとは限りません。

例えば、画面上では「1001番」と表示していても、認証ユーザー名は別のIDになっているケースがあります。
この場合、SIP URIだけ正しくても、認証情報が違えばREGISTERに失敗します。

確認すべきポイントは以下です。

AORは正しいか
認証ユーザー名はSIPサーバー側の設定と一致しているか
パスワードは正しいか
発信先URIのドメインは正しいか
SIPサーバー側でその発信先へのルーティングが定義されているか


つまずき4:SimpleUserでどこまでできるかを誤解する

SIP.jsには、初心者が扱いやすい SimpleUser があります。

SimpleUser は、最小構成でSIP.jsを動かすには便利です。
しかし、実務で細かい制御をしたい場合は制限があります。

SIP.js公式ガイドでは、SimpleUser は素早く始めるためのシンプルなインターフェースであり、制限として「メディア制御が最小限」「同時通話は1つ」「通話転送なし」「DTMF制御が簡略化されている」ことが示されています。複雑な機能が必要ならFull APIを使うべきとされています。(SIP.js)

そのため、次のような用途なら SimpleUser で十分です。

1対1のシンプルな音声通話
最小限の発信・着信
検証用のプロトタイプ

一方で、次のような機能を作りたい場合はFull APIを検討した方がよいです。

保留
転送
複数同時通話
細かいメディア制御
通話状態の詳細管理
独自UIとの高度な連携

最初は SimpleUser で動作確認し、実務実装では UserAgentRegistererInviterInvitation などを使う構成に移行する流れが現実的です。


つまずき5:発信できたのに音声が聞こえない

SIP.js開発で非常に多いトラブルが、発信はできているのに音声が聞こえない問題です。

ここで重要なのは、SIPのシグナリングとWebRTCのメディア通信は別物だということです。

SIPシグナリング:呼び出し、応答、切断などを制御
WebRTCメディア:実際の音声・映像を送受信

つまり、INVITEが成功しても、音声が流れるとは限りません。

SIP.jsのMake a Callガイドでは、発信時に Inviter を作成し、invite() を呼び出してSIP INVITEを送る流れが説明されています。(SIP.js)
一方、メディアの再生については、セッションが Established になったタイミングでSession Description Handlerからトラックを取得し、MediaStream をaudio/video要素に設定する流れが説明されています。(SIP.js)

つまり、次のような状態が起きます。

INVITEは成功している
SIP上は通話中になっている
しかしremote audioをaudio要素に設定していない
そのため音声が聞こえない

実装では、通話状態が確立したタイミングで、リモートメディアを適切にHTMLのaudio要素へ接続する必要があります。

例:

function setupRemoteMedia(session: any, audioElement: HTMLAudioElement) {
  const remoteStream = new MediaStream();

  session.sessionDescriptionHandler.peerConnection
    .getReceivers()
    .forEach((receiver: RTCRtpReceiver) => {
      if (receiver.track) {
        remoteStream.addTrack(receiver.track);
      }
    });

  audioElement.srcObject = remoteStream;
  audioElement.play().catch((error) => {
    console.error("音声再生に失敗しました", error);
  });
}

ただし、ここでもブラウザの自動再生制限に注意が必要です。


つまずき6:ブラウザの自動再生制限に引っかかる

音声トラックが届いているのに、ユーザーに音が聞こえないことがあります。

原因の一つが、ブラウザの自動再生制限です。

MDNでは、ユーザーが再生開始を明示的に要求していない状態で音声や動画を再生する動作は自動再生とみなされ、ブラウザの自動再生ブロックの対象になる可能性があると説明されています。音声がミュートされている、ユーザーがサイトを操作済み、サイトが許可されている、権限ポリシーで許可されている、といった条件を満たさない場合、再生がブロックされる可能性があります。(MDNウェブドキュメント)

SIP.jsの通話アプリでは、着信時に自動で音声を流したくなります。
しかし、ブラウザから見ると、これは「ユーザー操作なしで音声を再生しようとしている」動作に見えることがあります。

対策としては、次のような設計が有効です。

発信ボタン・応答ボタンなど、ユーザー操作の直後にaudio.play()を呼ぶ
audio.play()のPromise rejectionを必ずcatchする
再生に失敗した場合は「音声を有効にする」ボタンを表示する
アプリ起動時にユーザー操作を挟んで音声再生の準備をする

特に着信UIでは、以下のような設計が現実的です。

着信を受ける
↓
ユーザーが「応答」ボタンを押す
↓
accept()する
↓
remote streamをaudio要素に設定
↓
audio.play()を実行

「自動で応答して、自動で音声再生」するような設計は、ブラウザ制約に引っかかりやすいです。


つまずき7:マイク権限・HTTPS要件を見落とす

SIP.jsで音声通話をする場合、ブラウザからマイクへアクセスする必要があります。

WebRTCのマイク取得には getUserMedia() が使われます。MDNでは、getUserMedia() はユーザーにカメラやマイクなどのメディア入力の利用許可を求めるAPIであり、安全なコンテキスト、つまりHTTPSなどでのみ利用できると説明されています。(MDNウェブドキュメント)

そのため、次のような環境では失敗することがあります。

HTTPでアクセスしている
証明書が不正
iframe内で権限が許可されていない
ユーザーがマイク権限を拒否した
OS側でブラウザのマイク権限が無効
別アプリがマイクを占有している

開発中は localhost では動くのに、検証環境やスマホ実機では動かないことがあります。
その場合、まずHTTPS・証明書・マイク権限を確認しましょう。

確認ポイントは以下です。

URLはHTTPSか
ブラウザのマイク権限は許可されているか
OSのプライバシー設定でマイクが許可されているか
getUserMedia()のエラー内容をログ出力しているか
スマホブラウザで同じ挙動か


つまずき8:ICE / STUN / TURNを軽視する

ローカル環境や同じネットワーク内では通話できるのに、外部ネットワークやスマホ回線では音声がつながらないことがあります。

この場合、ICE、STUN、TURNまわりの問題である可能性があります。

WebRTCでは、ブラウザ同士、またはブラウザとメディアサーバーが直接通信できるとは限りません。NATやファイアウォールの影響を受けるためです。MDNでは、ICEはブラウザをピアと接続するためのフレームワークであり、STUNやTURNサーバーを使って接続を成立させると説明されています。STUNは自分のパブリックアドレスやNATの制限を調べるために使われます。(MDNウェブドキュメント)

よくある症状は以下です。

社内LANでは通話できる
自宅Wi-Fiでは片方向音声になる
スマホ回線では無音になる
発信はできるが数秒で切れる
SIP上は通話中だがRTPが流れない

このような場合、SIP.jsのコードだけを見ても原因は分かりません。

確認すべきポイントは以下です。

iceServersにSTUN/TURNを設定しているか
SIPサーバー側でWebRTC用のICE設定が有効か
NAT配下で正しい外部IPが広告されているか
片方向音声ではないか
TURN経由での通信が必要なネットワークではないか

特に実務では、STUNだけでは不十分なケースがあります。
企業ネットワーク、モバイル回線、厳しいNAT環境では、TURNサーバーが必要になることがあります。


つまずき9:SIPサーバー側のWebRTC対応を甘く見る

SIP.jsのコードが正しくても、SIPサーバー側がWebRTCに適切に対応していないと通話は成立しません。

特にAsteriskやFreeSWITCHなどを使う場合、通常のSIPクライアント向け設定と、ブラウザWebRTC向け設定は異なります。

AsteriskのSIP.js公式ガイドでは、WebRTC peerには暗号化、AVPF、ICE、DTLS、RTCP mux、WebSocket transportなどの設定が必要であり、これらはpeer設定側に置く必要があると説明されています。(SIP.js)

通常のSIPクライアントでは動くのに、SIP.jsでは動かない場合、以下を確認しましょう。

SIP over WebSocketに対応しているか
WebSocketのエンドポイントが有効か
WSSの証明書が正しいか
WebRTC用の暗号化設定が有効か
ICE supportが有効か
DTLS-SRTPに対応しているか
コーデックが一致しているか

「既存のSIPサーバーがあるからSIP.jsもすぐ接続できる」と考えると、ここで詰まりやすいです。


つまずき10:着信処理のdelegateを設定していない

発信はできるのに着信できない場合、UserAgentのdelegate設定を確認しましょう。

SIP.jsでは、INVITEを受け取ったときに UserAgent のdelegate経由でアプリケーション側へ通知されます。公式のReceive a Callガイドでも、着信INVITEを処理するために delegateonInvite を設定する例が示されています。(SIP.js)

典型的な流れは以下です。

const userAgentOptions = {
  uri,
  transportOptions,
  authorizationUsername,
  authorizationPassword,
  delegate: {
    onInvite(invitation) {
      console.log("着信しました", invitation);
    },
  },
};

着信できない場合は、次の観点で確認します。

REGISTERは成功しているか
SIPサーバー側で該当ユーザーに着信ルーティングされているか
delegate.onInviteを設定しているか
UserAgent.start()より前にdelegateを設定しているか
複数のUserAgentを作って状態が混線していないか

ReactなどのSPAで実装している場合、コンポーネントの再レンダリングによりUserAgentが複数生成されてしまうこともあります。
UserAgentはアプリ全体で1つに管理する設計にした方が安全です。


つまずき11:Reactのライフサイクルで接続が重複する

ReactやVueなどのフロントエンドフレームワークでSIP.jsを使う場合、ライフサイクル管理も重要です。

よくある問題は以下です。

画面表示のたびにUserAgentを作成している
useEffectが複数回実行されてWebSocket接続が増える
画面遷移時にunregisterやstopをしていない
古いsessionのイベントリスナーが残っている
通話終了後にmedia streamを解放していない

Reactでありがちな悪い例は、画面コンポーネントの中で毎回UserAgentを作ることです。

function CallPage() {
  const userAgent = new UserAgent(options); // 再レンダリングで危険

  return <button>Call</button>;
}

より安全なのは、SIPクライアント管理用のサービスやContextを作り、ライフサイクルを明確にすることです。

アプリ起動時にUserAgentを作成
ログイン後に接続・REGISTER
ログアウト時にUNREGISTER・STOP
通話開始時にSessionを作成
通話終了時にSessionとMediaStreamを破棄

SIP.jsは状態を持つライブラリなので、通常のAPIリクエストのように毎回使い捨てる設計には向きません。


つまずき12:ログを見る場所を間違える

SIP.js開発では、ブラウザのconsoleだけ見ても原因が分からないことがあります。

確認すべきログは複数あります。

ブラウザconsole
ブラウザNetworkタブのWebSocketフレーム
SIP.jsのログ
SIPサーバーのログ
Asterisk / FreeSWITCHのSIPログ
WebRTC internals
STUN/TURNサーバーのログ

特に切り分けでは、以下の順番で見ると分かりやすいです。

1. WebSocketは接続できているか
2. REGISTERは成功しているか
3. INVITEは送信されているか
4. 180 Ringing / 200 OK は返っているか
5. ACKは送れているか
6. SDPの交換は正常か
7. ICE candidateは出ているか
8. remote trackは届いているか
9. audio要素で再生できているか

「通話できない」と言っても、原因はSIP、WebSocket、WebRTC、ブラウザ制約、サーバー設定のどこにでもあります。

そのため、ログは層ごとに見る必要があります。


つまずき13:音声コーデックの不一致

SIP.jsで通話できない、または音声が流れない場合、コーデックの不一致も疑うべきです。

ブラウザWebRTCでは、利用できる音声コーデックが決まっています。
一方、SIPサーバーや相手端末側では、G.711、Opus、G.729など、異なるコーデック設定になっている場合があります。

よくある問題は以下です。

ブラウザ側はOpusを使いたい
SIPサーバー側はPCMU/PCMAのみ許可
相手端末側が特定のコーデックのみ対応
トランスコード設定がない
SDP上では合意できていない

確認するには、SDPを見ます。

m=audio ...
a=rtpmap:111 opus/48000/2
a=rtpmap:0 PCMU/8000
a=rtpmap:8 PCMA/8000

SIP.js側だけでなく、SIPサーバー側のコーデック許可設定も確認しましょう。


つまずき14:片方向音声をSIP.jsのバグだと思ってしまう

片方向音声は、SIP.js開発でよくあるトラブルです。

例えば、次のような状態です。

こちらの声は相手に届くが、相手の声が聞こえない
相手の声は聞こえるが、こちらの声が届かない
最初の数秒だけ聞こえて、その後無音になる

この場合、SIP.jsのバグではなく、NAT、ICE、RTP、ファイアウォール、サーバー設定の問題であることが多いです。

確認ポイントは以下です。

SDPにプライベートIPが出ていないか
ICE candidateに適切な候補が出ているか
TURNを経由すべき環境ではないか
SIPサーバーがmedia relayしているか
direct media設定が有効になっていないか
RTPポート範囲がファイアウォールで開いているか

Asterisk構成では、WebRTC peerに対して directmedia=no のようにメディアをAsterisk経由にする設定例も示されています。(SIP.js)
実務では、ブラウザと相手端末を直接つなぐより、PBXやメディアサーバーを経由した方が安定するケースがあります。


つまずき15:ローカルでは動くが本番で動かない

SIP.js開発では、ローカルでは動くのに本番環境で動かないことがあります。

主な原因は以下です。

HTTPSではない
WSSではない
証明書が不正
SIPサーバーのWebSocket URLが環境ごとに違う
NAT配下で外部IPが正しく設定されていない
本番ネットワークでWebSocketがブロックされている
STUN/TURN設定がない
マイク権限が本番ドメインで未許可

特に、ブラウザ通話ではHTTPSとWSSの組み合わせが重要です。

フロントエンド:HTTPS
SIP WebSocket:WSS
マイク権限:許可
証明書:ブラウザが信頼できるもの

ローカル検証だけで安心せず、スマホ回線、別Wi-Fi、社内ネットワークなど複数環境でテストすることが重要です。


つまずき16:通話終了時の後片付けを忘れる

通話が終わった後の処理も重要です。

後片付けを忘れると、次のような問題が起きます。

次の通話で音声が出ない
マイク使用中のままになる
古いremote streamが残る
イベントリスナーが重複する
通話終了済みsessionに対して操作してエラーになる

SIP.js公式のAttach Mediaガイドでも、通話完了後にはmedia要素の srcObjectnull にし、pauseする例が示されています。(SIP.js)

通話終了時には、最低限以下を行うとよいです。

function cleanupMedia(audioElement: HTMLAudioElement) {
  const stream = audioElement.srcObject as MediaStream | null;

  if (stream) {
    stream.getTracks().forEach((track) => track.stop());
  }

  audioElement.pause();
  audioElement.srcObject = null;
}

また、sessionの状態が Terminated になったタイミングでクリーンアップする設計にしておくと安全です。


SIP.js開発でのおすすめ切り分け手順

SIP.jsで通話できない場合は、次の順番で確認すると原因を絞りやすいです。

1. WebSocket接続を確認する

wss://... に接続できているか
証明書エラーがないか
NetworkタブでWebSocketが101 Switching Protocolsになっているか

2. SIP REGISTERを確認する

REGISTERを送っているか
401認証チャレンジ後に再REGISTERしているか
200 OKが返っているか
SIPサーバー側で登録状態になっているか

3. 発信INVITEを確認する

INVITEが送られているか
発信先URIが正しいか
100 Trying / 180 Ringing / 200 OK が返っているか
ACKが送れているか

4. WebRTCメディアを確認する

SDPが交換されているか
ICE candidateが出ているか
remote trackが届いているか
audio要素にsrcObjectが設定されているか
audio.play()が成功しているか

5. ネットワーク環境を変えて確認する

同一LAN
自宅Wi-Fi
スマホ回線
社内ネットワーク
VPNあり・なし

ローカルで動くことと、本番で安定して動くことは別です。


実装時に意識したい設計方針

SIP.jsを実務で使うなら、次のような設計にしておくと保守しやすくなります。

SIP接続管理をUIコンポーネントから分離する
UserAgentはアプリ全体で一元管理する
REGISTER状態を画面に表示できるようにする
通話状態をstate machineとして扱う
SIPログとWebRTCログを確認しやすくする
audio.play()失敗時のUIを用意する
STUN/TURN設定を環境変数化する
通話終了時のcleanupを必ず行う

特に、通話状態は曖昧に扱わない方がよいです。

idle
connecting
registered
calling
ringing
establishing
inCall
terminating
terminated
error

このように状態を明確にすると、UI制御やエラー表示がかなり楽になります。


SIP.js開発でよくあるチェックリスト

最後に、SIP.js開発でつまずいたときのチェックリストをまとめます。

□ SIPサーバーはSIP over WebSocketに対応しているか
□ WebSocket URLはwsではなくwssになっているか
□ 証明書はブラウザで信頼されているか
□ UserAgent.start()は成功しているか
□ REGISTERは成功しているか
□ AORと認証ユーザー名を混同していないか
□ 発信先SIP URIは正しいか
□ INVITEに対して200 OKが返っているか
□ ACKは送れているか
□ SDPは正常に交換されているか
□ ICE candidateは出ているか
□ STUN/TURN設定はあるか
□ マイク権限は許可されているか
□ HTTPS環境で動かしているか
□ remote streamをaudio要素に設定しているか
□ audio.play()の失敗をcatchしているか
□ ブラウザの自動再生制限に対応しているか
□ 通話終了時にMediaStreamを解放しているか
□ ReactなどでUserAgentが重複生成されていないか
□ SIPサーバー側ログを確認しているか
□ WebRTC internalsを確認しているか


まとめ

SIP.js開発でつまずきやすい理由は、関係する技術が多いからです。

SIP.jsは便利なライブラリですが、実際のブラウザ通話では以下のような領域をすべて意識する必要があります。

SIP.jsのAPI
SIP over WebSocket
SIP認証
SIPサーバー設定
WebRTCメディア
マイク権限
ICE / STUN / TURN
ブラウザの自動再生制限
HTTPS / WSS / 証明書
NAT・ファイアウォール

特に重要なのは、SIPの接続状態とWebRTCの音声状態を分けて考えることです。

SIP登録できているか
発信・着信できているか
メディアが届いているか
ブラウザで再生できているか

この4段階を分けて確認できるようになると、SIP.js開発のトラブルシューティングはかなり楽になります。

SIP.jsは、ブラウザで通話機能を実装できる強力なライブラリです。
ただし、Webアプリ開発の感覚だけで進めると、SIP・WebRTC・ネットワークまわりで詰まりやすいです。

最初はシンプルな1対1通話から始め、WebSocket接続、REGISTER、INVITE、メディア再生、ICE接続を一つずつ確認していくのがおすすめです。

コメント

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