WebRTC に基づいたブラウザ側の音声およびビデオ チャット ルームの実装

I.はじめに
       

WebRTC (Web Real-Time Communication) はブラウザにリアルタイム通信機能を導入することを目的としており、ユーザーは他のソフトウェアやプラグインをインストールすることなくブラウザ間でリアルタイム通信機能を実行できます。この記事では、WebRTC をベースとした 1 対 1 の音声およびビデオのリアルタイム チャット ルーム機能の実装について紹介します。ブラウザは、HTTP 経由で Web サーバーのフロントエンド ページを実行する要素 (HTML、CSS、JS) を要求し、シグナリング サーバー シグナリング サービスはルーム管理を提供し、メッセージ転送などの機能では、メディア データは STUN/TURN サーバーを介して転送されます。​ 

2. 機能表示

     上の図に示すように、これは 1 対 1 のチャット ルームです。ルームに参加すると、ユーザーはルーム内の他のユーザーとテキスト チャットしたり、音声通話やビデオ通話をクリックしたりできます。 < a i=2> ボタンを使用して音声通話とビデオ通話を行います。

3. アーキテクチャの説明

 上記は 1 対 1 の音声およびビデオ チャット ルームのアーキテクチャの説明であり、左側は発信側のプロセス、右側は通話先のプロセスです。発信側はまずオーディオおよびビデオ機器を検出し、オーディオおよびビデオ機器のデータを収集し、次に STUN/TURN サーバーとの PeerConnection 接続 (ピアツーピア接続) を作成し、オファー SDP を生成して呼び出し先に送信します。イニシエータの SDP を受信した後、オーディオおよびビデオ機器の検出とオーディオおよびビデオ データの収集を開始し、応答 SDP を生成してイニシエータに送信します。

4. コードの実装

1. サーバー側の実装

a. Web+Signal Server の実装
        まず、フロントエンドの実行要素をプルするためにブラウザ用の Web サーバーを構築する必要があります。この記事では、nodejs Express モジュールを選択します。静的ファイルを簡単に構築できるリソース サーバーの場合、サーバーはフロントエンド コードが配置されているパスを公開するだけでよく、ブラウザーは HTTP 経由でそのパスを要求できます。

        次に、ルームのステータスを維持し、メッセージ転送などの機能を提供するシグナリング サーバー Signal Server を構築する必要があり、ユーザーのリクエストを HTTP 経由で受信して処理できます。

        以下は、Express を通じてパブリック ディレクトリを公開するサーバー (Web+Signal Server) コードの一部を示しています。ユーザーはフロントエンド コードを取得し、ルームへの参加リクエスト join_req などのユーザー操作と組み合わせてリクエスト メッセージをシグナリングに送信します。 、ルームからの退出要求、leave_req およびその他のニーズ シグナリング サーバーは、同じルーム内の他のユーザーに制御メッセージを転送します。

// 注意:如下代码不完整,仅提供关键部分
 
var logger = log4js.getLogger();
 
var app = express();
app.use(serveIndex('./public'));
app.use(express.static('./public'));
 
var options = {
    key  : fs.readFileSync('./certificate/server.key'),
    cert : fs.readFileSync('./certificate/server.pem')
}
 
var httpsServer = https.createServer(options, app);
httpsServer.listen(443, '0.0.0.0');
 
var httpsIO = socketIO.listen(httpsServer);
httpsIO.sockets.on('connection', (socket) => {
    socket.on('join_req', (roomId, userName) => {
        socket.join(roomId);
        var room = httpsIO.sockets.adapter.rooms[roomId];
        var memberNumInRoom = Object.keys(room.sockets).length;
        if (memberNumInRoom > MAX_NUMBER_IN_ROOM) {
            socket.emit('member_full', roomId, socket.id);
            socket.leave(roomId);
            return;
        }
        fetchUserInRoom(socket, roomId);
        addUserToRoom(roomId, userName);
        logger.info(`${userName} join room: (${roomId}), current number in room is ${memberNumInRoom+1}`);
        socket.emit('join_res', roomId, socket.id);
        socket.to(roomId).emit('other_joined', roomId, socket.id, userName);
    });
    socket.on('leave_req', (roomId, userName) => {
        socket.leave(roomId);
        var room = httpsIO.sockets.adapter.rooms[roomId];
        if (!room) {
            return;
        }
        var memberNumInRoom = Object.keys(room.sockets).length;       
        delUserFromRoom(roomId, userName);
        logger.info(`${userName} leave room: (${roomId}), current number in room is ${memberNumInRoom-1}`);
        socket.emit('leave_res', roomId, socket.id);
        socket.to(roomId).emit('other_leaved', roomId, socket.id, userName);
    });
    socket.on('ctrl_message', (roomId, data) => {
        logger.debug('recv a ctrl message from ' + socket.id);
        socket.to(roomId).emit('ctrl_message', roomId, socket.id, data);
    });
    socket.on('start_call', (roomId, data) => {
        logger.debug("recv start_call from " + socket.id);
        socket.to(roomId).emit('start_call', roomId, socket.id, data);
    });
    socket.on('message', (roomId, data) => {
        logger.debug('recv a message from ' + socket.id);
        socket.to(roomId).emit('message', roomId, socket.id, data);
    });
});

 

b. STUN/TURN サーバーを構築する
        上のアーキテクチャ図に示すように、STUN/TURN サーバーも構築する必要があります。STUN サーバーは主に通知機能を提供します。 NAT マップ アドレスのクライアント。クライアントは、自分の NAT マップ アドレスを知っており、それをピアに転送します。ピアも、NAT マップ アドレスをクライアントに送信します。両端は相互に通信を試みます。P2P 接続が失敗した場合は、 TURN サーバー 通常、異なるネットワーク環境にある 2 つのクライアント間で P2P が失敗した場合に、TURN サーバーを介してデータを転送できるようにするために、パブリック IP を持つマシンにデプロイされます。

        STUN/TURN の動作原理がよくわからない場合は、STUN の動作原理と TURN の動作原理を参照してください。coturn を使用して STUN/TURN サーバーを構築することもできます。このブログを参照してください。 2. クライアントの実装

a. RTCPeerConnection を作成する
        RTCPeerConnection は、ポイントツーポイント通信を実装する WebRTC のコンポーネントであり、ピアに接続してデータを通信する方法を提供します。ピアツーピア接続では、イベントをリッスンし、イベントが発生したときにコールバックを実行する必要があります。たとえば、icecandidate イベントを受信したとき、メディア トラック イベントを受信したときなど、ローカル候補をピアに送信する必要があります。
 

var ICE_CFG = {
    'iceServers': [{
        'url': 'stun:stun.l.google.com:19302'
    }, {
        'url': 'turn:xxx.xxx.xxx.xxx:3478',
        'username': 'xxx',
        'credential': 'xxx'
    }]
};
var pConn = new RTCPeerConnection(ICE_CFG);
 

 

 stun:stun.l.google.com:19302 は、Google が P2P トラバーサル用に提供する STUN サーバー アドレスです。TURN サーバーのセットアップが完了したら、TURN サービス アドレスを指定すると、P2P トラバーサルが失敗したときに TURN サービスを使用できるようになります。 . リレー転送を実行します。

b. ローカル ストリームを取得して PeerConnection に追加します
        WebRTC は getUserMedia API を提供するため、ユーザーはオーディオ デバイスやビデオ デバイスに簡単にアクセスしてストリーム データを取得できます。

        基本的な使用法: varpromise = navigator.mediaDevices.getUserMedia(constraints);

        getUserMedia API の使用方法については、このブログを参照してください。
 

function GetLocalMediaStream(mediaStream) {
    localStream = mediaStream;
    localAv.srcObject = localStream;
 
    localStream.getTracks().forEach((track) => {
        myPconn.addTrack(track, localStream);
    });
 
    if (isInitiator == true) {
        var offerOptions = {
            offerToReceiveAudio: 1,
            offerToReceiveVideo: 1
        };
        myPconn.createOffer(offerOptions)
            .then(GetOfferDesc)
            .catch(HandleError);
    }
}

c. メディア ネゴシエーション
        通話開始者の場合、オーディオ トラックとビデオ トラックを RTCPeerConnection に追加した後、createOffer メソッドを使用してローカル SDP 情報を生成し、setLocalDescription を使用してそれを RTCPeerConnection に設定できます。その後、SDP 情報がシグナリングを通じて呼び出し先バディに送信されます。オファー SDP を受信した後、バディは RTCPeerConnection setRemoteDescription を使用して SDP をリモート SDP に設定し、RTCPeerConnection createAnswer メソッドを通じてローカル SDP メッセージを生成および設定し、最後に送信します。シグナリングを通じてイニシエーターに送信されます。

        SDP の概要については、このブログを参照してください。createOffer の概要については、このブログを参照してください。
 

if (isInitiator == true) {
    var offerOptions = {
        offerToReceiveAudio: 1,
        offerToReceiveVideo: 1
    };
    myPconn.createOffer(offerOptions)
        .then(GetOfferDesc)
        .catch(HandleError);
}
 
function GetOfferDesc(desc) {
    myPconn.setLocalDescription(desc);
 
    socket.emit('ctrl_message', roomId.value, desc);
}
 
function CreateAnswerDesc() {
    myPconn.createAnswer().then(GetAnswerDesc).catch(HandleError);
}
 
function GetAnswerDesc(desc) {
    myPconn.setLocalDescription(desc);
 
    socket.emit('ctrl_message', roomId.value, desc);
}

socket.on('ctrl_message', (roomId, socketId, data) => {
    if (data) {
        if (data.hasOwnProperty('type') && data.type === 'offer') {
            HandleOfferDesc(data);
            CreateAnswerDesc();
        } else if (data.hasOwnProperty('type') && data.type === 'answer') {
            HandleAnswerDesc(data);
        } else if (data.hasOwnProperty('type') && data.type === 'candidate') {
            HandleCandidate(data);
        } else {
            console.log('Unknow ctrl message, ' + data);
        }
    }
});

d. 交換候補
        以下のように、PeerConnection が作成されると、onicecandidate コールバック関数が監視されます。たとえば、通話開始者の場合、呼び出し後に候補収集が開始されます。 setLocalDescription. 収集が完了すると、onicecandidate メソッドがコールバックされ、ローカル候補情報がピアに送信されます。ピアはローカル候補を受信した後、接続の作成を開始し、多くの接続から利用可能な接続を選択できます。呼び出します。

        候補の詳細については、ホスト候補、srflx候補、リレー候補などを紹介しているこのブログを参照してください。

        PeerConnection作成時にピアストリームデータを受信した後の処理であるオントラックコールバック関数も設定しており、ストリームを<video>タグに設定するデモです。
 

function CreatePeerConnection() {
    if (myPconn) {
        console.log('peer connection has already been created.');
        return;
    }
    myPconn = new RTCPeerConnection(ICE_CFG);
 
    myPconn.onicecandidate = (event) => {
        if (event.candidate) {
            socket.emit('ctrl_message', roomId.value, {
                type: 'candidate',
                label: event.candidate.sdpMLineIndex, 
                id: event.candidate.sdpMid, 
                candidate: event.candidate.candidate
            });
		}
	}
	myPconn.ontrack = GetRemoteMediaStream;
}
function HandleCandidate(data) {
    var candidate = new RTCIceCandidate({
        sdpMlineIndex: data.label,
        sdpMid: data.id,
        candidate: data.candidate
    });
    myPconn.addIceCandidate(candidate);
}

function GetRemoteMediaStream(event) {
    if(event && event.streams.length > 0) {
        remoteStream = event.streams[0];
        remoteAv.srcObject = remoteStream;
    }
}

原文   WebRTC_ice 候補交換構築に基づくブラウザ側の音声およびビデオ チャット ルームの実装 - CSDN ブログ

★記事末尾の名刺では、(FFmpeg、webRTC、rtmp、hls、rtsp、ffplay、srs)を含むオーディオおよびビデオ開発学習教材やオーディオおよびビデオ学習ロードマップなどを無料で受け取ることができます。

以下を参照してください! ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓

 

おすすめ

転載: blog.csdn.net/yinshipin007/article/details/134930396