https://d226lax1qjow5r.cloudfront.net/blog/blogposts/create-a-multiparty-video-app-with-the-new-vonage-video-express/react-native_video-express_1200x600.png

新しいVonage Video Expressでマルチパーティビデオアプリを作成する

最終更新日 September 24, 2021

所要時間:1 分

この記事は、ハビエル・モリーナ・サンスとの共同執筆によるものである。 ハビエル・モリーナ・サンス

このブログ記事では、ReactJSと新しいVonage Video Expressをベースにしたマルチパーティビデオアプリケーションを開発する方法を紹介します。Video Expressは、デフォルトで以下の機能を提供します:

  • ルーム&パーティシパント・マネージャーパブリッシング、サブスクライブ、ストリーム管理ロジックの簡素化

  • レイアウト管理カスタマイズ可能なコンポーネントを備えた、レスポンシブUIとレイアウトマネージャ。

  • ビデオ品質の最適化(参加者数、レンダリングサイズ、CPU、ネットワーク状況に応じて、フレームレートと解像度を最適化。

  • ネットワークの最適化:参加者のビデオや音声を自動的に削除し、帯域幅リソースを最適化します。

  • 使いやすさ:パブリッシング、サブスクライブ、ストリームをルームと参加者に置き換えることで、より自然なインタラクションを提供します。

最後まで読み飛ばしたいですか?このチュートリアルのソースコードはすべて GitHub.

アプリ・アーキテクチャ

アプリは、サーバーサイドとクライアントサイドの2つの主要セクションに分かれている:サーバーサイド:クレデンシャル生成とアーカイブ管理を担当するシンプルなNodeJSサーバー クライアントサイド:React Hooksを使用したReact SPA(シングル・ページ・アプリケーション)。

クライアントサイドは、実際に動作する場所です。Video Express を使用することで、応答性と拡張性に優れたマルチパーティ ビデオ会議アプリを実装することができました。

クライアント

Reactアプリケーションは vonage/video-expressモジュールを利用している。Video Expressは、HTMLスクリプトタグでも使用できます。 Video Express ドキュメントをご覧ください。

このアプリは、React 16.8に付属するReact Hooksをベースにしている。次に、このアプリケーションの主なフックを詳しく見てみよう。

ユーズルーム

利用室 UseRoomフックは、Video Room のライフサイクルを処理するものです。Video Express のおかげで、Session、Publisher、Subscriber のライフサイクルを管理する必要はありません。代わりに ルームオブジェクトをインスタンス化し room.join()メソッドを使用するだけです。

まず、Roomオブジェクトを初期化し、呼び出しに参加する関数を作成する必要があります。認証 (apiKey, sessionIdtokenなど) と、パブリッシャー設定として使用するオプションのパラメータを指定します。 userNameまた、Room を表示するコンテナや、オプションのパブリッシャー設定も必要です。

Video Expressが提供するデフォルトのレイアウトマネージャを使用するため、いくつかのレイアウトパラメータを渡します。 ここで.

const createCall = useCallback(
    (
      { apikey, sessionId, token },
      roomContainer,
      userName,
      publisherOptions
    ) => {
      if (!apikey || !sessionId || !token) {
        throw new Error('Check your credentials');
      }

      roomRef.current = new MP.Room({
        apiKey: apikey,
        sessionId: sessionId,
        token: token,
        roomContainer: 'roomContainer',
        participantName: userName,
        managedLayoutOptions: {
          layoutMode: 'grid',
          screenPublisherContainer: 'screenSharingContainer'
        }
      })
       startRoomListeners();

       roomRef.current
        .join({ publisherProperties: finalPublisherOptions })
        .then(() => {
          setConnected(true);
          setCamera(roomRef.current.camera);
          setScreen(roomRef.current.screen);
          addLocalParticipant({ room: roomRef.current });
        })
        .catch(e => console.log(e));
    },
    [ ]
  );

オブジェクトが初期化されると Roomオブジェクトが初期化されたら startRoomListeners関数を呼び出して Room関数を呼び出します。次に room.join()メソッドを呼び出します。 publisherSettingsメソッドを呼び出します。新しい参加者の参加、新しい画面共有ストリームの作成、ユーザーが通話に再接続するなどのイベントを通知するために、イベントリスナーが必要です。

 
const startRoomListeners = () => {
    if (roomRef.current) {
      roomRef.current.on('connected', () => {
        console.log('Room: connected');
      });
      roomRef.current.on('disconnected', () => {
        setNetworkStatus('disconnected');
        console.log('Room: disconnected');
      });
      roomRef.current.camera.on('created', () => {
        setCameraPublishing(true);
        console.log('camera publishing now');
      });
      roomRef.current.on('reconnected', () => {
        setNetworkStatus('reconnected');
        console.log('Room: reconnected');
      });
      roomRef.current.on('reconnecting', () => {
        setNetworkStatus('reconnecting');
        console.log('Room: reconnecting');
      });
      roomRef.current.on('participantJoined', participant => {
        console.log(participant);
        addParticipants({ participant: participant });
        console.log('Room: participant joined: ', participant);
      });
      roomRef.current.on('participantLeft', (participant, reason) => {
        removeParticipants({ participant: participant });
        console.log('Room: participant left', participant, reason);
      });
    }
  };

参加者のリストを表示できるように、セッションの参加者も記録していることに注意してください。参加者が入室または退室すると更新されるステート変数を作成します。

もうひとつの便利な機能は ネットワーク・ステータス・コンポーネント.この機能は、ユーザーの切断/再接続時にUIを更新し、ユーザーにネットワークの問題を知らせるのに役立つだろう。

使用デバイス

現在では、複数のオーディオ/ビデオ・デバイスを利用できるのが一般的です。イヤホンを使いたがるユーザーもいれば、外付けのウェブカメラをコンピュータに接続したがるユーザーもいます。Videoアプリケーションでは、ユーザーが様々なデバイスから選択できるようにすることが不可欠です。そのため useDevicesフックは、利用可能なデバイスのリストを取得する方法を説明します。

  useEffect(() => {
    navigator.mediaDevices.addEventListener('devicechange', getDevices);
    getDevices();

    return () => {
      navigator.mediaDevices.removeEventListener('devicechange', getDevices);
    };
  }, [getDevices]);

メディア・デバイスの変化を検知するイベント・リスナーをセットアップしました。 getDevices()関数をトリガーします。

const getDevices = useCallback(async () => {
    if (!navigator.mediaDevices || !navigator.mediaDevices.enumerateDevices) {
      console.log('enumerateDevices() not supported.');
      return;
    }
    try {
      const devices = await MP.getDevices();
      const audioInputDevices = devices.filter(
        (d) => d.kind.toLowerCase() === 'audioinput'
      );
      const audioOutputDevices = devices.filter(
        (d) => d.kind.toLowerCase() === 'audiooutput'
      );
      const videoInputDevices = devices.filter(
        (d) => d.kind.toLowerCase() === 'videoinput'
      );
      setDeviceInfo({
        audioInputDevices,
        videoInputDevices,
        audioOutputDevices
      });
      // });
    } catch (err) {
      console.log('[loadDevices] - ', err);
    }
  }, []);

私たちの getDevices()関数は MP.geDevices()メソッドを呼び出します。このメソッドは、ユーザがデバイスへのアクセスを許可すると、使用可能なデバイスのリストを返します。そして、デバイスをフィルタリングし、利用可能なさまざまなデバイスをステートに入力します。

const [deviceInfo, setDeviceInfo] = useState({
    audioInputDevices: [],
    videoInputDevices: [],
    audioOutputDevices: []
  });

プレビュー・パブリッシャーを使う

Video Expressは、通話前のユーザー体験の実装にも役立ちます。実際、Video Expressは プレビュー発行者クラスを実装しています。この PreviewPublisherクラスのアイデアは、開発者が簡単にメディアをプレビューできるようにすることです。 Roomオブジェクトを作成する必要がありません。

ユーザーが正しいデバイスを選択できるようにプレビューを作成し(複数ある場合)、マイクが音声を拾い、カメラが問題なく動作することを確認します。完全な実装は GitHub.

まず、UseDevicesフックから利用可能なデバイスを取得する。

const { deviceInfo, getDevices } = useDevices();

プレビューパブリッシャーをターゲット要素で初期化したら previewMediaメソッドを呼び出してメディアを可視化します。また、デバイスのアクセスやイベントを処理するためにイベントリスナーを設定します。 audioLevelイベントリスナーを設定します。見ての通り getDevices()関数を呼び出すことはありません。 accessAllowedイベント)

const createPreview = useCallback(
    async (targetEl, publisherOptions) => {
      try {
        const publisherProperties = Object.assign({}, publisherOptions);
        console.log('[createPreview]', publisherProperties);
        previewPublisher.current = new MP.PreviewPublisher(targetEl);
        previewPublisher.current.on('audioLevelUpdated', (audioLevel) => {
          calculateAudioLevel(audioLevel);
        });
        previewPublisher.current.on('accessAllowed', (audioLevel) => {
          console.log('[createPreview] - accessAllowed');
          setAccessAllowed(DEVICE_ACCESS_STATUS.ACCEPTED);
          getDevices();
        });
        previewPublisher.current.on('accessDenied', (audioLevel) => {
          console.log('[createPreview] - accessDenied');
          setAccessAllowed(DEVICE_ACCESS_STATUS.REJECTED);
        });
        await previewPublisher.current.previewMedia({
          targetElement: targetEl,
          publisherProperties
        });

        setPreviewMediaCreated(true);
        console.log(
          '[Preview Created] - ',
          previewPublisher.current.getVideoDevice()
        );
      } catch (err) {
        console.log('[createPreview]', err);
      }
    },
    [calculateAudioLevel, getDevices]
  );

ユーザーがデバイスへのアクセスを許可したかどうかを知るためにSDKからいくつかのイベントをサブスクライブし、UIを更新してマイクが音声を拾っていることをユーザーに知らせるためにオーディオ・レベル・イベントをサブスクライブしています。また、オーディオ/ビデオ・デバイスへのアクセスが拒否された場合は、ユーザーにアラートを表示します ( 実装).

控室

このアプリケーションの最も重要なコンポーネントのひとつが WaitingRoomコンポーネントです。 useDevicesusePreviewPublisherフックを使用するからです。WaitingRoomは電話をかける前のページで、ユーザーは正しいオーディオとビデオのデバイスを選び、マイクとカメラが動作するかどうかを確認し、名前を選ぶことができます。

待合室はこんな感じ:

Screenshot of waiting room on mobile deviceScreenshot of waiting room on mobile device

ユーザーの選択を保持するいくつかのステート変数があります。これは、ユーザーがオーディオまたはビデオをオフにした状態でルームに参加したり、名前を設定したり、オーディオデバイスを変更したりできるようにするために行われます:

const roomToJoin = location?.state?.room || '';
const [roomName, setRoomName] = useState(roomToJoin);
const [userName, setUserName] = useState('');
const [isRoomNameInvalid, setIsRoomNameInvalid] = useState(false);
const [isUserNameInvalid, setIsUserNameInvalid] = useState(false);
const [localAudio, setLocalAudio] = useState(
    user.defaultSettings.publishAudio
  );
const [localVideo, setLocalVideo] = useState(
    user.defaultSettings.publishVideo
);
const [localVideoSource, setLocalVideoSource] = useState(undefined); const [localAudioSource, setLocalAudioSource] = useState(undefined);
let [audioDevice, setAudioDevice] = useState('');
let [videoDevice, setVideoDevice] = useState('');

私たちは UserContextを作成しました。この usePreviewPublisherフックを使って待合室のパブリッシャープレビューを作成したり破棄したりします。

const {
    createPreview,
    destroyPreview,
    previewPublisher,
    logLevel,
    previewMediaCreated,
    deviceInfo,
    accessAllowed
  } = usePreviewPublisher();

コンポーネントがマウントされ、待合室のコンテナが用意されたら、ロジックを開始します。パブリッシャープレビューを作成します。

useEffect(() => {
    if (waitingRoomVideoContainer.current) {
      createPreview(waitingRoomVideoContainer.current);
    }

    return () => {
      destroyPreview();
    };
  }, [createPreview, destroyPreview]);

プレビューが作成されると useEffectフックがあり、プレビューが作成されると実行され、現在使用中のデバイスでデバイスのリストを初期化します。への呼び出しに注意してください。 getAudioDevice()getVideoDevice()の呼び出しに注目してください。前者はプロミスで、後者は同期メソッドです。

useEffect(() => {
    if (previewPublisher && previewMediaCreated && deviceInfo) {
      console.log('useEffect - preview', deviceInfo);
      previewPublisher.getAudioDevice().then(currentAudioDevice => {
        setAudioDevice(currentAudioDevice.deviceId);
      });
      const currentVideoDevice = previewPublisher.getVideoDevice();
      console.log('currentVideoDevice', currentVideoDevice);
      setVideoDevice(currentVideoDevice.deviceId);
    }
  }, [
    deviceInfo,
    previewPublisher,
    setAudioDevice,
    setVideoDevice,
    previewMediaCreated
  ]);

デバイスを変更するロジックは、VideoでもAudioでもほとんど同じです。ここではオーディオについて説明しますが、オーディオの場合は WaitingRoomコンポーネントの実装を確認してください。

useEffect(() => {
    if (previewPublisher) {
      if (localVideo && !previewPublisher.isVideoEnabled()) {
        previewPublisher.enableVideo();
      } else if (!localVideo && previewPublisher.isVideoEnabled()) {
        previewPublisher.disableVideo();
      }
    }
  }, [localVideo, previewPublisher]);

ユーザーが使用中のVideoデバイスを変更したときに発火するイベントリスナーがあります:

const handleVideoSource = React.useCallback(
    e => {
      const videoDeviceId = e.target.value;
      setVideoDevice(e.target.value);
      previewPublisher.setVideoDevice(videoDeviceId);
      setLocalVideoSource(videoDeviceId);
    },
    [previewPublisher, setVideoDevice, setLocalVideoSource]
  );

結論

この投稿では、まったく新しい Video Express を React Applications と統合する方法を紹介します。このアプリケーションは、待合室、デバイスの選択、ネットワーク状態の検出、画面共有、チャットなど、ビデオアプリケーションに関連する主な機能を実装しています。

をクローンしてください。 Githubレポをクローンして、あなたのアプリケーションで使い始めてください。

シェア:

https://a.storyblok.com/f/270183/400x266/5bd495df3c/enrico-portolan.png
Enrico Portolanゲスト執筆者

エンリコはVonageの元チームメンバーです。ソリューション・エンジニアとして、技術的な専門知識で営業チームをサポートした。 クラウド、スタートアップ、新技術に情熱を注ぐ。イタリアのWebRTCスタートアップの共同設立者。仕事以外では、旅行とできるだけ多くの奇妙な食べ物を味わうのが好き。