JavaScriptを用いた簡易ビデオチャット(その1 )


webrtc(web real time communication)は,ユーザがオーディオ/ビデオメディアを取得し,ピアツーピア通信でこの情報を伝送できるようにする機能セットである.また、この接続でテキストやファイルのような任意のデータを送信することも可能です.
このポストは、単純なビデオ共有を実装するチュートリアルを提供します.

プロジェクト構造


このプロジェクトは、クライアントへのアクセスポイントのように動作するサーバーで構成され、Web通信を開始します.WebSocketは、クライアントがお互いを知ることができるように使用されます.
クライアントは、ビデオ/オーディオストリームとチャットメッセージを送信する入力を取得する単純なHTMLです.webrtcコミュニケーションはこのHTMLによってインポートされたJavaScriptファイルで実装されます.

ウェブ資源

  • MediaStream:オーディオとビデオにトラックを持つメディアコンテンツのストリームを表します.ナビゲータを使用してMediaStreamオブジェクトを取得できます.Mediadevices関数.
  • RTCの接続:2つのピア間の接続を表します.これはクライアント間でストリームを送信するために使用されます.
  • rtcDataCannel :接続の2対の間の双方向データチャネルを表します.これはクライアント間のチャットメッセージを送信するために使用されます.
  • コードを見せてください


    サーバコードから始めましょう.最初に我々はnodejsプロジェクトを開始します.
    yarn init -y
    
    必要な依存関係をインストールします.Expressはサーバーとソケットを作成します.IOソケットの通信を有効にします.
    yarn add express socket.io
    
    サーバーの作成.サーバーを起動し、次のコードを実行します.
    const express = require('express');
    const socketio = require('socket.io');
    const cors = require('cors');
    const http = require('http');
    
    // Create server
    const app = express();
    const server = http.Server(app);
    
    // Enable Cors to Socket IO
    app.use(cors());
    
    // Init Socket IO Server
    const io = socketio(server);
    
    // Called whend a client start a socket connection
    io.on('connection', (socket) => {
    
    });
    
    // Start server in port 3000 or the port passed at "PORT" env variable
    server.listen(process.env.PORT || 3000,
      () => console.log('Server Listen On: *:', process.env.PORT || 3000));
    
    
    最初のプロジェクト構造は次のようになります.

    WebSocket構造体


    WebSocketの目的は、クライアントが互いのwebrtc接続を知っていることです.
    webrtc接続はいくつかのステップでbellow記述する確立されます.すべての手順をクライアントの実装セクションで説明します.
  • RTCの接続インスタンスを作成する
  • 接続へのオファーを作成します
  • 要求を申し出に答えを送ります;

  • クライアント間のシグナリング
  • そのためには、ソケットにいくつかのイベントを追加する必要があります.
    最初のステップは自分自身に送信され、他のユーザーは、それぞれのRTC接続を開始する接続します.その後、上記のすべてのステップとの接続を確立するイベントがあります.
    以下に、この実装への完全なコードがあります.
    // Array to map all clients connected in socket
    let connectedUsers = [];
    
    // Called whend a client start a socket connection
    io.on('connection', (socket) => {
      // It's necessary to socket knows all clients connected
      connectedUsers.push(socket.id);
    
      // Emit to myself the other users connected array to start a connection with each them
      const otherUsers = connectedUsers.filter(socketId => socketId !== socket.id);
      socket.emit('other-users', otherUsers);
    
      // Send Offer To Start Connection
      socket.on('offer', (socketId, description) => {
        socket.to(socketId).emit('offer', socket.id, description);
      });
    
      // Send Answer From Offer Request
      socket.on('answer', (socketId, description) => {
        socket.to(socketId).emit('answer', description);
      });
    
      // Send Signals to Establish the Communication Channel
      socket.on('candidate', (socketId, signal) => {
        socket.to(socketId).emit('candidate', signal);
      });
    
      // Remove client when socket is disconnected
      socket.on('disconnect', () => {
        connectedUsers = connectedUsers.filter(socketId => socketId !== socket.id);
      });
    });
    

    クライアントコード


    Fistはpublic publicフォルダを作成し、ファイルインデックスを追加します.HTMLとメイン.jsプロジェクトの最終的な構造は次のようになります.

  • HMLコード
  • <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <title>WebRTC Example</title>
    
      <style>
        #video-grid {
          display: none;
          grid-template-columns: repeat(auto-fill, 400px);
          grid-auto-rows: 400px;
        }
    
        video {
          width: 100%;
          height: 100%;
        }
      </style>
    
      <script src="/socket.io/socket.io.js"></script>
      <script src="/main.js" type="module"></script>
    </head>
    <body>
      <h1>Hello!</h1>
    
      <!-- My Video and Remote Video from connection -->
      <div id="video-grid">
        <video playsinline autoplay muted id="local-video"></video>
        <video playsinline autoplay id="remote-video"></video>
      </div>
    
      <!-- Input to send messages -->
      <div>
        <span style="font-weight: bold">Message: </span>
        <input type="text" id="message-input" title="Message to Send!">
        <button id="message-button">Send</button>
      </div>
    
      <!-- Area to Print Images -->
      <div class="messages"></div>
    </body>
    </html>
    
    メインで.JSファイル最初のステップは以下のようなmediastreamを起動します.
    console.log('Main JS!');
    
    // Map All HTML Elements
    const videoGrid = document.getElementById('video-grid');
    const messagesEl = document.querySelector('.messages');
    const messageInput = document.getElementById('message-input');
    const sendButton = document.getElementById('message-button');
    const localVideo = document.getElementById('local-video');
    const remoteVideo = document.getElementById('remote-video');
    
    // Open Camera To Capture Audio and Video
    navigator.mediaDevices.getUserMedia({ video: true, audio: true })
      .then(stream => {
        // Show My Video
        videoGrid.style.display = 'grid';
        localVideo.srcObject = stream;
    
        // Start a Peer Connection to Transmit Stream
        initConnection(stream);
      })
      .catch(error => console.log(error));
    
    この動画はお気に入りから削除されています.

    次のステップはソケット接続を開始し、接続されている他のユーザに対してinit rtcpeerconnectinを起動します.他のユーザーソケットイベントを受信すると、クライアントはそれぞれの接続を開始します.
    const initConnection = (stream) => {
      const socket = io('/');
      let localConnection;
      let remoteConnection;
    
      // Start a RTCPeerConnection to each client
      socket.on('other-users', (otherUsers) => {
        // Ignore when not exists other users connected
        if (!otherUsers || !otherUsers.length) return;
    
        const socketId = otherUsers[0];
    
        // Ininit peer connection
        localConnection = new RTCPeerConnection();
    
        // Add all tracks from stream to peer connection
        stream.getTracks().forEach(track => localConnection.addTrack(track, stream));
    
        // Send Candidtates to establish a channel communication to send stream and data
        localConnection.onicecandidate = ({ candidate }) => {
          candidate && socket.emit('candidate', socketId, candidate);
        };
    
        // Receive stream from remote client and add to remote video area
        localConnection.ontrack = ({ streams: [ stream ] }) => {
          remoteVideo.srcObject = stream;
        };
    
        // Create Offer, Set Local Description and Send Offer to other users connected
        localConnection
          .createOffer()
          .then(offer => localConnection.setLocalDescription(offer))
          .then(() => {
            socket.emit('offer', socketId, localConnection.localDescription);
          });
      });
    }
    
    重要:実際の世界では、rtcpeerconnectionはstunとターンサーバを備えたICEServerの設定でinitalizedしなければなりません、これは本当のIPをインターネット接続に得て、ネットワークのNATブロックを避けるために必要です.RTCPeerConnectionWebRTC in real worldでこれについて詳しく見てください
    私たちのチュートリアルを続行すると、今すぐに他のクライアントの申し出要求を受け取り、あなたの答えとRTCの接続を作成する必要があります.
    // Receive Offer From Other Client
    socket.on('offer', (socketId, description) => {
        // Ininit peer connection
        remoteConnection = new RTCPeerConnection();
    
        // Add all tracks from stream to peer connection
        stream.getTracks().forEach(track => remoteConnection.addTrack(track, stream));
    
        // Send Candidtates to establish a channel communication to send stream and data
        remoteConnection.onicecandidate = ({ candidate }) => {
          candidate && socket.emit('candidate', socketId, candidate);
        };
    
        // Receive stream from remote client and add to remote video area
        remoteConnection.ontrack = ({ streams: [ stream ] }) => {
          remoteVideo.srcObject = stream;
        };
    
        // Set Local And Remote description and create answer
        remoteConnection
          .setRemoteDescription(description)
          .then(() => remoteConnection.createAnswer())
          .then(answer => remoteConnection.setLocalDescription(answer))
          .then(() => {
            socket.emit('answer', socketId, remoteConnection.localDescription);
          });
      });
    
    最後に、最初のクライアントは、回答を受信し、リモートの説明を設定します.そこで、ストリームを送信する通信チャネルを作成するために送信候補を開始します.
    // Receive Answer to establish peer connection
    socket.on('answer', (description) => {
      localConnection.setRemoteDescription(description);
    });
    
    // Receive candidates and add to peer connection
    socket.on('candidate', (candidate) => {
      // GET Local or Remote Connection
      const conn = localConnection || remoteConnection;
      conn.addIceCandidate(new RTCIceCandidate(candidate));
    });
    
    最終的な結果は、ローカルとリモートのビデオを表示すると、以下のイメージのように見えるものです.

    リファレンス


    WebRTC API
    WebRTC in real world

    次の手順


    あなたはGitHubですべてのコードを見ることができます
    チャットメッセージを送信するには、このチュートリアルを完了するに従ってください.
    あなたの読書ありがとう.してください、あなたの貢献とコメントを残してください.