本記事はAsiaQuest Advent Calendarの20日目です。
WebRTCを用いたビデオ通話機能の実装を通じて、通信確立のプロセスを理解することを目的とします。
こんにちは!Web3課のコウです。
最近の技術勉強で、WebRTCについて勉強していました。
今回の記事は、WebRTCの基礎概念や知識を紹介せず、直でハンズオンを行い、WebRTCの通信を実践することを考えています。 WebRTCの通信プロセスのイメージを持った上で、改めてわからなかった単語などを調べてもらえれば効率的にWebRTCについて学習できると思います!
※記事中に出たSDPやICE Candidateなどの説明について、下記の記事をお勧めします。
手動でWebRTCの通信をつなげよう ーWebRTC入門2016
※ コードはReact JSXで書いています。
function Main() {
const localVideoRef = useRef(null)
const localStreamRef = useRef(null)
const getMediaDevices = () => {
// video:true, audio:true を指定すると、カメラとマイクをオンにします
navigator.mediaDevices.getUserMedia({ video: true, audio: true })
.then(stream => {
console.log('stream', stream)
localVideoRef.current.srcObject = stream // カメラ映像を画面で表示します。
localStreamRef.current = stream
})
}
return (
<div>
<button onClick={getMediaDevices}>カメラとマイクをオンにする</button><br />
<video ref={localVideoRef} style={{ width: '400px'}} autoPlay></video>
</div>
)
}
export default Main
function Main() {
const localVideoRef = useRef(null)
const localStreamRef = useRef(null)
/* 追加 */
const pc = useRef(null)
const getMediaDevices = () => {...}
/* 追加 */
const createRtcConnection = () => {
const _pc = new RTCPeerConnection({
// 二つのクライアント直接通信できない場合、STUNサーバーを介して中継通信
iceServers: [{ urls: 'stun:stun.l.google.com:19302' }]
})
pc.current = _pc
console.log('RTCコネクションを作成しました');
}
return (
<div>
<button onClick={getMediaDevices}>カメラとマイクをオンにする</button><br />
<video ref={localVideoRef} style={{ width: '400px'}} autoPlay></video>
/* 追加 */
<button onClick={createRtcConnection}>RTCコネクションを作成する</button><br />
</div>
)
}
export default Main
function Main() {
const localVideoRef = useRef(null)
const localStreamRef = useRef(null)
const pc = useRef(null)
const getMediaDevices = () => {...}
const createRtcConnection = () => {...}
/* 追加 */
const addLocalStreamToRtcConnection = () => {
const localStream = localStreamRef.current
localStream.getTracks().forEach(track => {
pc.current?.addTrack(track, localStream)
})
console.log('ローカルストリームをRTCコネクションに追加しました');
}
return (
<div>
<button onClick={getMediaDevices}>カメラとマイクをオンにする</button><br />
<video ref={localVideoRef} style={{ width: '400px'}} autoPlay></video>
<button onClick={createRtcConnection}>RTCコネクションを作成する</button><br />
/* 追加 */
<button onClick={addLocalStreamToRtcConnection}>ローカルストリームをRTCコネクションに追加</button><br />
</div>
)
}
export default Main
function Main() {
const localVideoRef = useRef(null)
const localStreamRef = useRef(null)
const pc = useRef(null)
/* 追加 */
const textRef = useRef(null)
const getMediaDevices = () => {...}
const createRtcConnection = () => {...}
const addLocalStreamToRtcConnection = () => {...}
/* 追加 */
const createOffer = () => {
pc.current?.createOffer({
offerToReceiveAudio: true,
offerToReceiveVideo: true
})
.then(sdp => {
console.log('offer', JSON.stringify(sdp));
// 作成したSDPをローカルデスクリプションにセット
pc.current?.setLocalDescription(sdp)
})
}
/* 追加 */
const createAnswer = () => {
pc.current?.createAnswer({
offerToReceiveAudio: true,
offerToReceiveVideo: true
})
.then(sdp => {
console.log('answer', JSON.stringify(sdp));
pc.current?.setLocalDescription(sdp)
})
}
return (
<div>
<button onClick={getMediaDevices}>カメラとマイクをオンにする</button><br />
<video ref={localVideoRef} style={{ width: '400px'}} autoPlay></video>
<button onClick={createRtcConnection}>RTCコネクションを作成する</button><br />
<button onClick={addLocalStreamToRtcConnection}>ローカルストリームをRTCコネクションに追加</button><br />
/* 追加 */
<button onClick={createOffer}>Offerを作成する</button>
<button onClick={createAnswer}>Answerを作成する</button><br />
<textarea ref={textRef}></textarea><br />
</div>
)
}
export default Main
function Main() {
const localVideoRef = useRef(null)
const localStreamRef = useRef(null)
const pc = useRef(null)
const textRef = useRef(null)
const getMediaDevices = () => {...}
const createRtcConnection = () => {...}
const addLocalStreamToRtcConnection = () => {...}
const createOffer = () => {...}
const createAnswer = () => {...}
/* 追加 */
const setRemoteDescription = () => {
const remoteSdp = JSON.parse(textRef.current.value)
pc.current?.setRemoteDescription(new RTCSessionDescription(remoteSdp))
console.log('リモートデスクリプション', remoteSdp);
}
return (
<div>
<button onClick={getMediaDevices}>カメラとマイクをオンにする</button><br />
<video ref={localVideoRef} style={{ width: '400px'}} autoPlay></video>
<button onClick={createRtcConnection}>RTCコネクションを作成する</button><br />
<button onClick={addLocalStreamToRtcConnection}>ローカルストリームをRTCコネクションに追加</button><br />
<button onClick={createOffer}>Offerを作成する</button>
<button onClick={createAnswer}>Answerを作成する</button><br />
<textarea ref={textRef}></textarea><br />
/* 追加 */
<button onClick={setRemoteDescription}>リモートデスクリプションを設定する</button><br />
</div>
)
}
export default Main
function Main() {
const localVideoRef = useRef(null)
const localStreamRef = useRef(null)
const pc = useRef(null)
const textRef = useRef(null)
const getMediaDevices = () => {...}
const createRtcConnection = () => {
const _pc = new RTCPeerConnection({
// 二つのクライアント直接通信できない場合、STUNサーバーを介して中継通信
iceServers: [{ urls: 'stun:stun.l.google.com:19302' }]
})
/* 追加 */
_pc.onicecandidate = event => {
if (event.candidate) {
console.log('candidate', JSON.stringify(event.candidate));
}
}
pc.current = _pc
console.log('RTCコネクションを作成しました');
}
const addLocalStreamToRtcConnection = () => {...}
const createOffer = () => {...}
const createAnswer = () => {...}
const setRemoteDescription = () => {...}
/* 追加 */
const addCandidate = () => {
const candidate = JSON.parse(textRef.current.value)
pc.current?.addIceCandidate(new RTCIceCandidate(candidate))
console.log('candiate', candidate);
}
return (
<div>
<button onClick={getMediaDevices}>カメラとマイクをオンにする</button><br />
<video ref={localVideoRef} style={{ width: '400px'}} autoPlay></video>
<button onClick={createRtcConnection}>RTCコネクションを作成する</button><br />
<button onClick={addLocalStreamToRtcConnection}>ローカルストリームをRTCコネクションに追加</button><br />
<button onClick={createOffer}>Offerを作成する</button>
<button onClick={createAnswer}>Answerを作成する</button><br />
<textarea ref={textRef}></textarea><br />
<button onClick={setRemoteDescription}>リモートデスクリプションを設定する</button>
/* 追加 */
<button onClick={addCandidate}>AddCandidate</button><br />
</div>
)
}
export default Main
function Main() {
const localVideoRef = useRef(null)
const localStreamRef = useRef(null)
const pc = useRef(null)
const textRef = useRef(null)
/* 追加 */
const remoteVideoRef = useRef(null)
const getMediaDevices = () => {...}
const createRtcConnection = () => {
const _pc = new RTCPeerConnection({...})
_pc.onicecandidate = event => {...}
/* 追加 */
_pc.ontrack = event => {
remoteVideoRef.current.srcObject = event.streams[0]
}
pc.current = _pc
console.log('RTCコネクションを作成しました');
}
const addLocalStreamToRtcConnection = () => {...}
const createOffer = () => {...}
const createAnswer = () => {...}
const setRemoteDescription = () => {...}
const addCandidate = () => {...}
return (
<div>
<button onClick={getMediaDevices}>カメラとマイクをオンにする</button><br />
<video ref={localVideoRef} style={{ width: '400px'}} autoPlay></video>
/* 追加 */
<video ref={remoteVideoRef} style={{ width: '400px'}} autoPlay></video><br />
<button onClick={createRtcConnection}>RTCコネクションを作成する</button><br />
<button onClick={addLocalStreamToRtcConnection}>ローカルストリームをRTCコネクションに追加</button><br />
<button onClick={createOffer}>Offerを作成する</button>
<button onClick={createAnswer}>Answerを作成する</button><br />
<textarea ref={textRef}></textarea><br />
<button onClick={setRemoteDescription}>リモートデスクリプションを設定する</button>
<button onClick={addCandidate}>AddCandidate</button><br />
</div>
)
}
export default Main
今回はWebRTCの通信確立プロセスを理解するために、SDPとICE Candidateの交換は手動で行いましたが、より実践的な方法としてはWebSocketを使ってシグナリングサーバーを作って、遠く離れたユーザーとも通信の確立ができます。
以上、多少WebRTCの入門の参考となったら嬉しいです。
最後までお読みいただき、ありがとうございました!