次の方法で共有


通話中にビデオを管理する

Azure Communication Services SDK を使用してビデオ通話を管理する方法について説明します。 通話内のビデオの受信と送信を管理する方法について説明します。

前提条件

SDK のインストール

npm install コマンドを使用して、JavaScript 用の Azure Communication Services の Common SDK と Calling SDK をインストールします。

npm install @azure/communication-common --save
npm install @azure/communication-calling --save

必要なオブジェクトを初期化する

CallClient インスタンスは、ほとんどの通話操作に必要です。 新しい CallClient インスタンスを作成する際に、Logger インスタンスなどのカスタム オプションを使用してこれを構成できます。

CallClient インスタンスでは、CallAgent を呼び出すことで createCallAgent インスタンスを作成できます。 このメソッドでは、非同期的に CallAgent インスタンス オブジェクトが返されます。

createCallAgent メソッドでは、CommunicationTokenCredential が引数として使用されます。 これは、ユーザー アクセス トークンを受け取ります。

getDeviceManager インスタンスで CallClient メソッドを使用して、deviceManager にアクセスできます。

const { CallClient } = require('@azure/communication-calling');
const { AzureCommunicationTokenCredential} = require('@azure/communication-common');
const { AzureLogger, setLogLevel } = require("@azure/logger");

// Set the logger's log level
setLogLevel('verbose');

// Redirect log output to console, file, buffer, REST API, or whatever ___location you want
AzureLogger.log = (...args) => {
    console.log(...args); // Redirect log output to console
};

const userToken = '<USER_TOKEN>';
callClient = new CallClient(options);
const tokenCredential = new AzureCommunicationTokenCredential(userToken);
const callAgent = await callClient.createCallAgent(tokenCredential, {displayName: 'optional Azure Communication Services user name'});
const deviceManager = await callClient.getDeviceManager()

Microsoft インフラストラクチャへの SDK 接続を管理する

Call Agent インスタンスは、(呼び出しを結合または開始するために) 呼び出しを管理するのに役立ちます。 呼び出しの SDK を機能させるには、Microsoft インフラストラクチャに接続して着信呼び出しの通知を取得し、他の呼び出しの詳細を調整する必要があります。 Call Agent には、次の 2 つの状態があります。

接続済み - Call AgentConnected connectionStatue 値は、クライアント SDK が接続されており、Microsoft インフラストラクチャから通知を受信できることを意味します。

切断済み - Call AgentDisconnected connectionStatue 値は、SDK の正常な接続を妨げる問題があることを示します。 Call Agent を再作成する必要があります。

  • invalidToken: トークンが有効期限切れであるか、無効な場合、Call Agent インスタンスがこのエラーで切断されます。
  • connectionIssue: クライアントが Microsoft インフラストラクチャに接続する際に問題が発生した場合、多数の再試行ののちに Call AgentconnectionIssue エラーが表示されます。

Call Agent プロパティの現在の値を調べて、ローカル connectionState が Microsoft インフラストラクチャに接続されているかどうかを確認できます。 アクティブな呼び出し中に、connectionStateChanged イベントをリッスンして、Call Agent の状態が接続済みから切断済みに変化するかどうかを判断できます。

const connectionState = callAgentInstance.connectionState;
console.log(connectionState); // it may return either of 'Connected' | 'Disconnected'

const connectionStateCallback = (args) => {
    console.log(args); // it will return an object with oldState and newState, each of having a value of either of 'Connected' | 'Disconnected'
    // it will also return reason, either of 'invalidToken' | 'connectionIssue'
}
callAgentInstance.on('connectionStateChanged', connectionStateCallback);

デバイス管理

Calling SDK でのビデオの使用を開始するには、デバイスを管理できることが必要になります。 デバイスを使用すると、音声とビデオを通話に送信する内容を制御できます。

deviceManagerを使用して、通話でオーディオ ストリームとビデオ ストリームを送信できるローカル デバイスを列挙します。 また、deviceManager を使用して、ローカル デバイスのマイクやカメラにアクセスするためのアクセス許可を要求することもできます。

deviceManager メソッドを呼び出すことによって callClient.getDeviceManager() にアクセスできます。

const deviceManager = await callClient.getDeviceManager();

ローカル デバイスを取得する

ローカル デバイスにアクセスするには、deviceManager 列挙メソッド getCameras()getMicrophones を使用します。 これらのメソッドは非同期アクションです。

//  Get a list of available video devices for use.
const localCameras = await deviceManager.getCameras(); // [VideoDeviceInfo, VideoDeviceInfo...]

// Get a list of available microphone devices for use.
const localMicrophones = await deviceManager.getMicrophones(); // [AudioDeviceInfo, AudioDeviceInfo...]

// Get a list of available speaker devices for use.
const localSpeakers = await deviceManager.getSpeakers(); // [AudioDeviceInfo, AudioDeviceInfo...]

既定のデバイスを設定する

使用可能なデバイスがわかったら、マイク、スピーカー、カメラの既定のデバイスを設定できます。 クライアントの既定値が設定されていない場合、Communication Services SDK ではオペレーティング システムの既定値が使用されます。

マイク

使用されているデバイスにアクセス

// Get the microphone device that is being used.
const defaultMicrophone = deviceManager.selectedMicrophone;

使用するデバイスを設定

// Set the microphone device to use.
await deviceManager.selectMicrophone(localMicrophones[0]);

スピーカー

使用されているデバイスにアクセス

// Get the speaker device that is being used.
const defaultSpeaker = deviceManager.selectedSpeaker;

使用するデバイスを設定

// Set the speaker device to use.
await deviceManager.selectSpeaker(localSpeakers[0]);

カメラ

使用されているデバイスにアクセス

// Get the camera device that is being used.
const defaultSpeaker = deviceManager.selectedSpeaker;

使用するデバイスを設定

// Set the speaker device to use.
await deviceManager.selectSpeaker(localCameras[0]);

それぞれの CallAgent が自分のマイクとスピーカーを、関連付けられた DeviceManager で選択することができます。 異なる CallAgents は異なるマイクとスピーカーを使用することをお勧めします。 同じマイクやスピーカーを共有するべきではありません。 共有が行われると、マイク ユーザー向け診断 (UFD) がトリガーされ、ブラウザーと OS によってはマイクの動作が停止する可能性があります。

ローカル ビデオ ストリーム

ユーザーが通話でビデオを送信するには、 LocalVideoStream オブジェクトを作成する必要があります。

const localVideoStream = new LocalVideoStream(camera);

パラメーターとして渡されるカメラは、VideoDeviceInfo メソッドによって返されるdeviceManager.getCameras() オブジェクトです。

LocalVideoStream には次のプロパティがあります。

  • source はデバイス情報です。

    const source = localVideoStream.source;
    
  • mediaStreamType は、VideoScreenSharing、または RawMedia のいずれかです。

    const type: MediaStreamType = localVideoStream.mediaStreamType;
    

ローカル カメラのプレビュー

deviceManagerVideoStreamRenderer を使用して、ローカル カメラからのストリームのレンダリングを開始できます。

LocalVideoStreamを作成した後、それを使用してVideoStreamRendererを設定します。 VideoStreamRendererを作成したら、そのcreateView() メソッドを呼び出して、子としてページに追加できるビューを取得します。

このストリームは他の参加者には送信されません。 これはローカル プレビュー フィードです。

// To start viewing local camera preview
const cameras = await deviceManager.getCameras();
const camera = cameras[0];
const localVideoStream = new LocalVideoStream(camera);
const videoStreamRenderer = new VideoStreamRenderer(localVideoStream);
const view = await videoStreamRenderer.createView();
htmlElement.appendChild(view.target);

ローカル プレビューを停止する

ローカル プレビュー通話を停止するには、VideoStreamRenderer から派生したビューを破棄します。 VideoStreamRenderer が破棄されたら、プレビューを含む DOM Node から removeChild() メソッドを呼び出して、html ツリーから ビューを削除します。

// To stop viewing local camera preview
view.dispose();
htmlElement.removeChild(view.target);

カメラとマイクへのアクセス許可を要求する

アプリケーションは、アクセス許可なしでカメラやマイクを使用できません。 deviceManager を使用して、カメラやマイクへのアクセス許可を付与するようにユーザーに求めることができます。

const result = await deviceManager.askDevicePermission({audio: true, video: true});

Promise が解決されたら、メソッドは、DeviceAccessaudio のアクセス許可が付与されたかどうかを示す video オブジェクトと共に返ります。

console.log(result.audio);
console.log(result.video);

メモ

  • ビデオ デバイスが接続されたり、取り外されたりすると、videoDevicesUpdated イベントが発生します。
  • オーディオ デバイスが接続されると、audioDevicesUpdated イベントが発生します。
  • DeviceManagerを初めて作成したとき、アクセス許可がまだ付与されていない場合、デバイスは認識されません。 最初は、そのデバイス名は空で、詳細なデバイス情報は含まれません。 ユーザーにデバイス アクセスを求める DeviceManager.askPermission()を呼び出す必要があります。 ユーザーがアクセスを許可すると、デバイス マネージャーはシステム上のデバイスについて学習し、デバイスリストを更新し、 audioDevicesUpdated イベントと videoDevicesUpdated イベントを送信します。 ユーザーがページを更新してデバイス マネージャーを作成した場合、デバイス マネージャーは、ユーザーが以前にアクセス権を付与したため、デバイスについて学習します。 デバイス リストが最初に入力され、 audioDevicesUpdated イベントや videoDevicesUpdated イベントは生成されません。
  • スピーカーの列挙や選択は、Android の Chrome、iOS の Safari、macOS の Safari のいずれでもサポートされていません。

ビデオ カメラを使用して呼び出しを行う

重要

現在、サポートされている発信ローカル動画ストリームは 1 つだけです。

ビデオ通話を行うには、getCameras()deviceManager メソッドを使ってローカル カメラを列挙する必要があります。

カメラを選択したら、それを使用して LocalVideoStream インスタンスを作成します。 videoOptions 内のそれを localVideoStream 配列内の項目として CallAgentstartCall メソッドに渡します。

const deviceManager = await callClient.getDeviceManager();
const cameras = await deviceManager.getCameras();
const camera = cameras[0]
const localVideoStream = new LocalVideoStream(camera);
const placeCallOptions = {videoOptions: {localVideoStreams:[localVideoStream]}};
const userCallee = { communicationUserId: '<ACS_USER_ID>' }
const call = callAgent.startCall([userCallee], placeCallOptions);
  • CallAgent.join()API を使用してビデオを使用して通話に参加し、Call.Accept()API を使用してビデオを受け入れて呼び出す方法も可能です。
  • 通話が接続されると、選択したカメラから他の参加者への動画ストリームの送信が自動的に開始されます。

通話中にローカル ビデオの送信を開始および停止する

ビデオを開始する

呼び出し中に動画を開始するには、getCameras オブジェクトで deviceManager メソッドを使用して、カメラを列挙する必要があります。 次に、目的のカメラを使用して LocalVideoStream の新しいインスタンスを作成し、その LocalVideoStream オブジェクトを既存の呼び出しオブジェクトの startVideo メソッドに渡します。

const deviceManager = await callClient.getDeviceManager();
const cameras = await deviceManager.getCameras();
const camera = cameras[0]
const localVideoStream = new LocalVideoStream(camera);
await call.startVideo(localVideoStream);

ビデオを停止

動画の送信が正常に開始されると、通話インスタンスの LocalVideoStream コレクションに種類が VideolocalVideoStreams インスタンスが追加されます。

Call オブジェクトでビデオ ストリームを見つける

const localVideoStream = call.localVideoStreams.find( (stream) => { return stream.mediaStreamType === 'Video'} );

ローカル ビデオを停止する 通話中にローカルビデオを停止するには、ビデオで使用されている localVideoStream インスタンスを Call の stopVideo メソッドに渡します。

await call.stopVideo(localVideoStream);

その switchSource インスタンスで LocalVideoStream を呼び出すことで、アクティブな LocalVideoStream を保持しつつ、別のカメラ デバイスに切り替えることができます。

const cameras = await callClient.getDeviceManager().getCameras();
const camera = cameras[1];
localVideoStream.switchSource(camera);

指定したビデオ デバイスが使用できない場合は、次のようにします。

  • 通話中にビデオがオフの状態で call.startVideo() を使用してビデオを開始すると、このメソッドは SourceUnavailableError をスローし、cameraStartFailed のユーザー向け診断が true に設定されます。
  • localVideoStream.switchSource() メソッドの呼び出しにより cameraStartFailed が true に設定されます。 通話診断ガイドによって、通話関連の問題を診断する方法に関する追加情報が提供されます。

ローカル ビデオのオン/オフを確認するには、Call メソッド isLocalVideoStarted を使用します。これは true または false を返します。

// Check if local video is on or off
call.isLocalVideoStarted;

ローカルビデオの変更をリッスンするには、isLocalVideoStartedChanged イベントに登録したり解除したりできます。

// Subscribe to local video event
call.on('isLocalVideoStartedChanged', () => {
    // Callback();
});
// Unsubscribe from local video event
call.off('isLocalVideoStartedChanged', () => {
    // Callback();
});

通話中に画面共有を開始および停止する

通話中に画面共有を開始するには、startScreenSharing() オブジェクトに対して非同期メソッド Call を使用します。

画面共有を開始する

// Start screen sharing
await call.startScreenSharing();

画面共有の送信は、デスクトップ ブラウザーでのみサポートされています。

LocalVideoStream のコレクションで画面共有を見つける

画面共有の送信が正常に開始されると、LocalVideoStream型のScreenSharing インスタンスが呼び出しインスタンスのlocalVideoStreams コレクションに追加されます。

const localVideoStream = call.localVideoStreams.find( (stream) => { return stream.mediaStreamType === 'ScreenSharing'} );

画面共有を停止する

通話中に画面共有を停止するには、非同期 API stoptScreenSharing を使用します。

// Stop screen sharing
await call.stopScreenSharing();

画面共有の状態を確認する

画面共有がオンかオフかを確認する際は、isScreenSharingOn API を使用すると、true または false が返されます。

// Check if screen sharing is on or off
call.isScreenSharingOn;

画面共有の変更を監視するには、isScreenSharingOnChanged イベントをサブスクライブし、解除することができます。

// Subscribe to screen share event
call.on('isScreenSharingOnChanged', () => {
    // Callback();
});
// Unsubscribe from screen share event
call.off('isScreenSharingOnChanged', () => {
    // Callback();
});

重要

Azure Communication Services のこの機能は、現在プレビュー段階にあります。 プレビュー段階の機能は一般公開されており、新規および既存の Microsoft のすべてのお客様が使用できます。

プレビューの API と SDK は、サービス レベル アグリーメントなしに提供されます。 運用環境のワークロードには使用しないことをお勧めします。 特定の機能がサポートされていないか、機能が制約されている可能性があります。

詳しくは、Microsoft Azure プレビューの追加使用条件に関するページをご覧ください。

ローカル画面共有プレビューはパブリック プレビューの段階であり、バージョン 1.15.1-beta.1+ の一部としてご利用になれます。

ローカル画面共有プレビュー

VideoStreamRenderer を使用してローカル画面共有からのストリーム レンダリングを開始できるので、画面共有ストリームとして何を送信しているのかを見ることができます。

// To start viewing local screen share preview
await call.startScreenSharing();
const localScreenSharingStream = call.localVideoStreams.find( (stream) => { return stream.mediaStreamType === 'ScreenSharing' });
const videoStreamRenderer = new VideoStreamRenderer(localScreenSharingStream);
const view = await videoStreamRenderer.createView();
htmlElement.appendChild(view.target);

// To stop viewing local screen share preview.
await call.stopScreenSharing();
view.dispose();
htmlElement.removeChild(view.target);

// Screen sharing can also be stopped by clicking on the native browser's "Stop sharing" button.
// The isScreenSharingOnChanged event will be triggered where you can check the value of call.isScreenSharingOn.
// If the value is false, then that means screen sharing is turned off and so we can go ahead and dispose the screen share preview.
// This event is also triggered for the case when stopping screen sharing via Call.stopScreenSharing() API.
call.on('isScreenSharingOnChanged', () => {
    if (!call.isScreenSharingOn) {
        view.dispose();
        htmlElement.removeChild(view.target);
    }
});

リモート参加者の動画/画面共有ストリームをレンダリングする

リモート参加者のビデオまたは画面共有をレンダリングするには、最初の手順として、レンダリングする RemoteVideoStream の参照を取得します。

リモート参加者をレンダリングできるのは、videoStreamsの配列またはビデオ ストリーム (RemoteParticipant) を経由することだけです。 リモート参加者コレクションには、Call オブジェクトを介してアクセスします。

const remoteVideoStream = call.remoteParticipants[0].videoStreams[0];
const streamType = remoteVideoStream.mediaStreamType;

RemoteVideoStreamをレンダリングするには、そのisAvailableChanged イベントをサブスクライブする必要があります。 isAvailable プロパティが true に変更される場合、リモート参加者はビデオ ストリームを送信しています。 その後、VideoStreamRendererの新しいインスタンスを作成し、非同期VideoStreamRendererView メソッドを使用して新しいcreateView インスタンスを作成します。 その後、任意の UI 要素に view.target を付加できます。

リモート ストリームの使用可否が変わるたびに、VideoStreamRenderer 全体を破棄することも、特定の VideoStreamRendererView を破棄することもできます。 それらを保持する場合、ビューには空のビデオ フレームが表示されます。

// Reference to the html's div where we would display a grid of all remote video stream from all participants.
let remoteVideosGallery = document.getElementById('remoteVideosGallery');

subscribeToRemoteVideoStream = async (remoteVideoStream) => {
    let renderer = new VideoStreamRenderer(remoteVideoStream);
    let view;
    let remoteVideoContainer = document.createElement('div');
    remoteVideoContainer.className = 'remote-video-container';

    let loadingSpinner = document.createElement('div');
    // See the css example below for styling the loading spinner.
    loadingSpinner.className = 'loading-spinner';
    remoteVideoStream.on('isReceivingChanged', () => {
        try {
            if (remoteVideoStream.isAvailable) {
                const isReceiving = remoteVideoStream.isReceiving;
                const isLoadingSpinnerActive = remoteVideoContainer.contains(loadingSpinner);
                if (!isReceiving && !isLoadingSpinnerActive) {
                    remoteVideoContainer.appendChild(loadingSpinner);
                } else if (isReceiving && isLoadingSpinnerActive) {
                    remoteVideoContainer.removeChild(loadingSpinner);
                }
            }
        } catch (e) {
            console.error(e);
        }
    });

    const createView = async () => {
        // Create a renderer view for the remote video stream.
        view = await renderer.createView();
        // Attach the renderer view to the UI.
        remoteVideoContainer.appendChild(view.target);
        remoteVideosGallery.appendChild(remoteVideoContainer);
    }

    // Remote participant has switched video on/off
    remoteVideoStream.on('isAvailableChanged', async () => {
        try {
            if (remoteVideoStream.isAvailable) {
                await createView();
            } else {
                view.dispose();
                remoteVideosGallery.removeChild(remoteVideoContainer);
            }
        } catch (e) {
            console.error(e);
        }
    });

    // Remote participant has video on initially.
    if (remoteVideoStream.isAvailable) {
        try {
            await createView();
        } catch (e) {
            console.error(e);
        }
    }
    
    console.log(`Initial stream size: height: ${remoteVideoStream.size.height}, width: ${remoteVideoStream.size.width}`);
    remoteVideoStream.on('sizeChanged', () => {
        console.log(`Remote video stream size changed: new height: ${remoteVideoStream.size.height}, new width: ${remoteVideoStream.size.width}`);
    });
}

読み込みスピナーをリモート ビデオ ストリームにスタイル設定するための CSS。

.remote-video-container {
   position: relative;
}
.loading-spinner {
   border: 12px solid #f3f3f3;
   border-radius: 50%;
   border-top: 12px solid #ca5010;
   width: 100px;
   height: 100px;
   -webkit-animation: spin 2s linear infinite; /* Safari */
   animation: spin 2s linear infinite;
   position: absolute;
   margin: auto;
   top: 0;
   bottom: 0;
   left: 0;
   right: 0;
   transform: translate(-50%, -50%);
}
@keyframes spin {
   0% { transform: rotate(0deg); }
   100% { transform: rotate(360deg); }
}
/* Safari */
@-webkit-keyframes spin {
   0% { -webkit-transform: rotate(0deg); }
   100% { -webkit-transform: rotate(360deg); }
}

リモート動画の画質

Azure Communication Services WebJS SDK には、バージョン 1.15.1 以降の最適なビデオ数 (OVC) と呼ばれる機能が用意されています。

この機能を使用して、2 人以上の参加者のグループ通話で、特定の時点で最適にレンダリングできるさまざまな参加者からの受信ビデオの数を実行時にアプリケーションに通知します。

この機能が公開するプロパティ optimalVideoCount は、ローカル エンドポイントのネットワークとハードウェアの機能に基づいて、呼び出し中に動的に変化します。 optimalVideoCount の値は、さまざまな参加者アプリケーションから送られるどれだけのビデオを特定の時点でレンダリングするかについて詳細に示します。 アプリケーションはこれらの変更に対処して、レンダリングされたビデオの数を、推奨事項に従って更新する必要があります。 各更新の間にはデバウンス期間 (約 10 秒) があります。

使用方法

optimalVideoCount機能は通話機能です。 OptimalVideoCount オブジェクトの feature メソッドを使用して、フィーチャー Call を参照する必要があります。

その後、onOptimalVideoCountCallFeature メソッドを使用してリスナーを設定し、optimalVideoCount が変更されたときに通知を受け取ることができます。 変更のサブスクライブを解除するには、off メソッドを呼び出すことができます。

レンダリングできる現在の最大着信ビデオ数は 16 です。 16 個の受信ビデオを適切にサポートするには、コンピューターには 16 GB 以上の RAM と、3 歳未満の 4 コア以上の CPU が必要です。

const optimalVideoCountFeature = call.feature(Features.OptimalVideoCount);
optimalVideoCountFeature.on('optimalVideoCountChanged', () => {
    const localOptimalVideoCountVariable = optimalVideoCountFeature.optimalVideoCount;
})

使用例: アプリケーションは、グループ呼び出しで最適なビデオ数の変更をサブスクライブします。 最適なビデオ数の変更は、新しいレンダラー createView メソッドを作成するか、ビュー dispose 破棄して、それに応じてアプリケーション レイアウトを更新することによって処理されます。

リモート動画ストリームのプロパティ

リモート動画ストリームには次のプロパティがあります。

const id: number = remoteVideoStream.id;
  • id: リモート動画ストリームの ID。
const type: MediaStreamType = remoteVideoStream.mediaStreamType;
  • mediaStreamType: Video または ScreenSharing を指定できます。
const isAvailable: boolean = remoteVideoStream.isAvailable;
  • isAvailable: リモート参加者のエンドポイントによりストリームがアクティブに送信されているかどうかを定義します。
const isReceiving: boolean = remoteVideoStream.isReceiving;
  • isReceiving:

    • リモート ビデオ ストリーム データを受信しているかどうかをアプリケーションに通知します。

    • 次のシナリオでは、フラグが false に移行します。

      • モバイル ブラウザーのリモート参加者が、ブラウザー アプリをバックグラウンドに移動する。
      • リモート参加者またはビデオを受信しているユーザーに、ビデオ品質に大きな影響を与えるネットワークの問題がある。
      • macOS/iOS Safari のリモート参加者が、アドレス バーから [一時停止] を選択する。
      • リモート参加者がネットワーク切断に遭っている。
      • モバイルのリモート参加者が、ブラウザーを強制的または通常どおり終了する。
      • モバイルまたはデスクトップのリモート参加者が、そのデバイスをロックする。 このシナリオは、リモート参加者がデスクトップ コンピューターで、スリープ状態になる場合にも適用されます。
    • 次のシナリオでは、フラグが true に移行します。

      • モバイル ブラウザーのリモート参加者が、バックグラウンドにしていたブラウザーをフォアグラウンドに戻す。
      • macOS/iOS Safari のリモート参加者は、ビデオを一時停止した後、アドレス バーから [再開 ] を選択します。
      • リモート参加者が、一時的な切断の後、ネットワークに再接続する。
      • モバイルのリモート参加者がデバイスのロックを解除し、モバイル ブラウザーで通話に戻る。
    • この機能により、リモート ビデオ ストリームのレンダリングに関するユーザー エクスペリエンスが向上します。

    • isReceiving フラグが false に変わると、読み込みスピナーをリモート ビデオ ストリーム上に表示できます。 読み込みスピナーの実装は必須ではありませんが、読み込みスピナーは、ユーザー エクスペリエンスの向上のために最も一般的に使用されているものです。

    const size: StreamSize = remoteVideoStream.size;
    
  • size: ビデオの幅と高さに関する情報を含むストリーム サイズ。

VideoStreamRenderer のメソッドとプロパティ

await videoStreamRenderer.createView();

リモート ビデオ ストリームをレンダリングするためにアプリケーション UI に付加できる VideoStreamRendererView インスタンスを作成し、非同期 createView() メソッドを使用します。これにより、ストリームをレンダリングする準備ができたときに解決され、DOM ツリー内のどこにでも挿入できる target 要素を表す video プロパティを含むオブジェクトが返されます。

videoStreamRenderer.dispose();

videoStreamRenderer と、それに関連付けられているすべての VideoStreamRendererView インスタンスを破棄します。

VideoStreamRendererView のメソッドとプロパティ

VideoStreamRendererView を作成するときに、scalingModeisMirrored プロパティを指定できます。 scalingMode は、StretchCrop、または Fit のいずれかです。 isMirrored を指定すると、レンダリングされたストリームは垂直方向に反転されます。

const videoStreamRendererView: VideoStreamRendererView = await videoStreamRenderer.createView({ scalingMode, isMirrored });

すべての VideoStreamRendererView インスタンスには、レンダリング サーフェイスを表す target プロパティがあります。 アプリケーション UI にこのプロパティを付加します。

htmlElement.appendChild(view.target);

scalingMode は、updateScalingMode メソッドを呼び出すことで更新できます。

view.updateScalingMode('Crop');

2 つの異なるカメラからの動画ストリームを、同じデスクトップ デバイスから同じ通話の中で送信します。

重要

Azure Communication Services のこの機能は、現在プレビュー段階にあります。 プレビュー段階の機能は一般公開されており、新規および既存の Microsoft のすべてのお客様が使用できます。

プレビューの API と SDK は、サービス レベル アグリーメントなしに提供されます。 運用環境のワークロードには使用しないことをお勧めします。 特定の機能がサポートされていないか、機能が制約されている可能性があります。

詳しくは、Microsoft Azure プレビューの追加使用条件に関するページをご覧ください。

同一の通話で 2 つの異なるカメラからビデオ ストリームを送信することは、デスクトップでサポートされているブラウザーでバージョン 1.17.1-beta.1 以降の一部としてサポートされています。

ひとつのデスクトップ ブラウザー タブ/アプリの 2 つの異なるカメラから動画ストリームを、同じ通話の中で、次のコード スニペットを使用して送信できます。

// Create your first CallAgent with identity A
const callClient1 = new CallClient();
const callAgent1 = await callClient1.createCallAgent(tokenCredentialA);
const deviceManager1 = await callClient1.getDeviceManager();

// Create your second CallAgent with identity B
const callClient2 = new CallClient();
const callAgent2 = await callClient2.createCallAgent(tokenCredentialB);
const deviceManager2 = await callClient2.getDeviceManager();

// Join the call with your first CallAgent
const camera1 = await deviceManager1.getCameras()[0];
const callObj1 = callAgent1.join({ groupId: ‘123’}, { videoOptions: { localVideoStreams: [new LocalVideoStream(camera1)] } });

// Join the same call with your second CallAgent and make it use a different camera
const camera2 = (await deviceManager2.getCameras()).filter((camera) => { return camera !== camera1 })[0];
const callObj2 = callAgent2.join({ groupId: '123' }, { videoOptions: { localVideoStreams: [new LocalVideoStream(camera2)] } });

//Mute the microphone and speakers of your second CallAgent’s Call, so that there is no echos/noises.
await callObj2.muteIncomingAudio();
await callObj2.mute();

制限事項:

  • ビデオ ストリームの送信は、異なる ID を使用する 2 つの異なる CallAgent インスタンスで行う必要があります。 このコード スニペットは、2 つの通話エージェントが使用されており、それぞれが独自の Call オブジェクトをもっている状態を示しています。
  • コード例では、両方の CallAgent が同じ呼び出し (同じ呼び出し ID) に参加しています。 また、エージェントごとに異なる通話に参加し、一方の通話では 1 つのビデオ、もう一方の通話では別のビデオを送信することもできます。
  • 両方の CallAgent で同じカメラを送信することはサポートされていません。 これらは 2 つの異なるカメラでなければなりません。
  • 2 つの異なるカメラをひとつの CallAgent で送信することは、現在はサポートされていません。
  • macOS Safari では、背景ぼかし動画エフェクト (@azure/communication-effects) から) を適用できるのはひとつのカメラだけであり、両方に同時に適用することはできません。

SDK のインストール

プロジェクト レベルの build.gradle ファイルを見つけて、mavenCentral()buildscript の下のリポジトリの一覧に allprojects を追加します。

buildscript {
    repositories {
    ...
        mavenCentral()
    ...
    }
}
allprojects {
    repositories {
    ...
        mavenCentral()
    ...
    }
}

次に、モジュール レベルの build.gradle ファイルで、次の行を dependencies セクションに追加します。

dependencies {
    ...
    implementation 'com.azure.android:azure-communication-calling:1.0.0'
    ...
}

必要なオブジェクトを初期化する

CallAgent インスタンスを作成するには、createCallAgent インスタンス上で CallClient メソッドを呼び出す必要があります。 この呼び出しは、CallAgent インスタンス オブジェクトを非同期に返します。

createCallAgent メソッドは、CommunicationUserCredentialをカプセル化する を引数として受け取ります。

DeviceManager にアクセスするには、まず callAgent インスタンスを作成する必要があります。 それから、CallClient.getDeviceManager メソッドを使用して DeviceManager を取得することができます。

String userToken = '<user token>';
CallClient callClient = new CallClient();
CommunicationTokenCredential tokenCredential = new CommunicationTokenCredential(userToken);
android.content.Context appContext = this.getApplicationContext(); // From within an activity, for instance
CallAgent callAgent = callClient.createCallAgent(appContext, tokenCredential).get();
DeviceManager deviceManager = callClient.getDeviceManager(appContext).get();

呼び出し元の表示名を設定するには、この代替メソッドを使用します。

String userToken = '<user token>';
CallClient callClient = new CallClient();
CommunicationTokenCredential tokenCredential = new CommunicationTokenCredential(userToken);
android.content.Context appContext = this.getApplicationContext(); // From within an activity, for instance
CallAgentOptions callAgentOptions = new CallAgentOptions();
callAgentOptions.setDisplayName("Alice Bob");
DeviceManager deviceManager = callClient.getDeviceManager(appContext).get();
CallAgent callAgent = callClient.createCallAgent(appContext, tokenCredential, callAgentOptions).get();

デバイス管理

通話でビデオを使用するには、デバイスを管理する必要があります。 デバイスを使用すると、音声とビデオを通話に送信する内容を制御できます。

DeviceManager オブジェクトを使用すると、オーディオ/ビデオ ストリームを送信する呼び出しで使用するローカル デバイスを列挙できます。 また、ネイティブ ブラウザー API を使用して、マイクとカメラにアクセスするアクセス許可をユーザーに要求することもできます。

deviceManagerにアクセスするには、callClient.getDeviceManager() メソッドを呼び出します。

Context appContext = this.getApplicationContext();
DeviceManager deviceManager = callClient.getDeviceManager(appContext).get();

ローカル デバイスを列挙する

ローカル デバイスにアクセスするには、デバイス マネージャーで列挙メソッドを使用します。 列挙は同期アクションです。

//  Get a list of available video devices for use.
List<VideoDeviceInfo> localCameras = deviceManager.getCameras(); // [VideoDeviceInfo, VideoDeviceInfo...]

ローカル カメラのプレビュー

DeviceManagerRenderer を使用して、ローカル カメラからのストリームのレンダリングを開始できます。 このストリームは他の参加者には送信されません。 これはローカル プレビュー フィードです。 ストリームのレンダリングは非同期アクションです。

VideoDeviceInfo videoDevice = <get-video-device>; // See the `Enumerate local devices` topic above
Context appContext = this.getApplicationContext();

LocalVideoStream currentVideoStream = new LocalVideoStream(videoDevice, appContext);

LocalVideoStream[] localVideoStreams = new LocalVideoStream[1];
localVideoStreams[0] = currentVideoStream;

VideoOptions videoOptions = new VideoOptions(localVideoStreams);

RenderingOptions renderingOptions = new RenderingOptions(ScalingMode.Fit);
VideoStreamRenderer previewRenderer = new VideoStreamRenderer(currentVideoStream, appContext);

VideoStreamRendererView uiView = previewRenderer.createView(renderingOptions);

// Attach the uiView to a viewable ___location on the app at this point
layout.addView(uiView);

ビデオ カメラを使用して 1:1 の通話を行う

警告

現在、サポートされている発信ローカル動画ストリームは 1 つだけです。 ビデオを使用して呼び出しを行うには、 deviceManagergetCameras API を使用してローカル カメラを列挙する必要があります。 カメラを選択したら、それを使用してLocalVideoStream インスタンスを構築し、videoOptions配列内の項目としてlocalVideoStreamcallメソッドに渡します。 通話が接続されると、選択したカメラから他の参加者へのビデオ ストリームの送信が自動的に開始されます。

プライバシーに関する懸念があるため、ビデオがローカルでプレビューされていない場合、ビデオは通話に共有されません。 詳細については、「 ローカル カメラのプレビュー」を参照してください。

VideoDeviceInfo desiredCamera = <get-video-device>; // See the `Enumerate local devices` topic above
Context appContext = this.getApplicationContext();

LocalVideoStream currentVideoStream = new LocalVideoStream(desiredCamera, appContext);

LocalVideoStream[] localVideoStreams = new LocalVideoStream[1];
localVideoStreams[0] = currentVideoStream;

VideoOptions videoOptions = new VideoOptions(localVideoStreams);

// Render a local preview of video so the user knows that their video is being shared
Renderer previewRenderer = new VideoStreamRenderer(currentVideoStream, appContext);
View uiView = previewRenderer.createView(new CreateViewOptions(ScalingMode.FIT));

// Attach the uiView to a viewable ___location on the app at this point
layout.addView(uiView);

CommunicationUserIdentifier[] participants = new CommunicationUserIdentifier[]{ new CommunicationUserIdentifier("<acs user id>") };

StartCallOptions startCallOptions = new StartCallOptions();
startCallOptions.setVideoOptions(videoOptions);

Call call = callAgent.startCall(context, participants, startCallOptions);

ローカル動画の送信を開始および停止する

ビデオを開始するには、オブジェクトに対する getCameraList 操作を使用してカメラ deviceManager 列挙する必要があります。 次に、目的のカメラを渡して LocalVideoStream の新しいインスタンスを作成し、それを引数として startVideo API に渡します。

VideoDeviceInfo desiredCamera = <get-video-device>; // See the `Enumerate local devices` topic above
Context appContext = this.getApplicationContext();

LocalVideoStream currentLocalVideoStream = new LocalVideoStream(desiredCamera, appContext);

VideoOptions videoOptions = new VideoOptions(currentLocalVideoStream);

Future startVideoFuture = call.startVideo(appContext, currentLocalVideoStream);
startVideoFuture.get();

ビデオの送信が正常に開始されると、 LocalVideoStream インスタンスが呼び出しインスタンスの localVideoStreams コレクションに追加されます。

List<LocalVideoStream> videoStreams = call.getLocalVideoStreams();
LocalVideoStream currentLocalVideoStream = videoStreams.get(0); // Please make sure there are VideoStreams in the list before calling get(0).

ローカル動画を停止するには、LocalVideoStream コレクションで使用可能な localVideoStreams インスタンスを渡します。

call.stopVideo(appContext, currentLocalVideoStream).get();

switchSource インスタンスで LocalVideoStream を呼び出すことにより、動画の送信中に別のカメラ デバイスに切り替えることができます。

currentLocalVideoStream.switchSource(source).get();

リモート参加者の動画ストリームをレンダリングする

リモート参加者の動画ストリームと画面共有ストリームの一覧を取得するには、videoStreams コレクションを調べます。

List<RemoteParticipant> remoteParticipants = call.getRemoteParticipants();
RemoteParticipant remoteParticipant = remoteParticipants.get(0); // Please make sure there are remote participants in the list before calling get(0).

List<RemoteVideoStream> remoteStreams = remoteParticipant.getVideoStreams();
RemoteVideoStream remoteParticipantStream = remoteStreams.get(0); // Please make sure there are video streams in the list before calling get(0).

MediaStreamType streamType = remoteParticipantStream.getType(); // of type MediaStreamType.Video or MediaStreamType.ScreenSharing

リモート参加者からの RemoteVideoStream をレンダリングするには、OnVideoStreamsUpdated イベントをサブスクライブする必要があります。

イベント内で isAvailable プロパティが true に変更された場合、リモート参加者が現在ストリームを送信していることを示します。 それが発生したら、Renderer の新しいインスタンスを作成し、非同期 RendererView API を使用して新しい createView を作成し、お使いのアプリケーションの UI の任意の場所に view.target をアタッチします。

リモート ストリームの可用性が変化するたびに、 Renderer全体を破棄するか、特定の RendererView を保持するかを選択できますが、空のビデオ フレームが表示されます。

VideoStreamRenderer remoteVideoRenderer = new VideoStreamRenderer(remoteParticipantStream, appContext);
VideoStreamRendererView uiView = remoteVideoRenderer.createView(new RenderingOptions(ScalingMode.FIT));
layout.addView(uiView);

remoteParticipant.addOnVideoStreamsUpdatedListener(e -> onRemoteParticipantVideoStreamsUpdated(p, e));

void onRemoteParticipantVideoStreamsUpdated(RemoteParticipant participant, RemoteVideoStreamsEvent args) {
    for(RemoteVideoStream stream : args.getAddedRemoteVideoStreams()) {
        if(stream.getIsAvailable()) {
            startRenderingVideo();
        } else {
            renderer.dispose();
        }
    }
}

リモート動画ストリームのプロパティ

リモート ビデオ ストリームには、次のプロパティがあります。

  • Id - リモート ビデオ ストリームの ID。

    int id = remoteVideoStream.getId();
    
  • MediaStreamType - Video または ScreenSharingできます。

    MediaStreamType type = remoteVideoStream.getMediaStreamType();
    
  • isAvailable - リモート参加者エンドポイントがストリームをアクティブに送信しているかどうかを示します。

    boolean availability = remoteVideoStream.isAvailable();
    

Renderer のメソッドとプロパティ

Renderer オブジェクトは、次のメソッドを使用します。

  • リモート ビデオ ストリームをレンダリングするには、後でアプリケーション UI にアタッチできる VideoStreamRendererView インスタンスを作成します。

    // Create a view for a video stream
    VideoStreamRendererView.createView()
    
  • レンダラーと、このレンダラーに関連付けられているすべての VideoStreamRendererView を破棄します。 関連付けられているすべてのビューを UI から削除した後に呼び出します。

    VideoStreamRenderer.dispose()
    
  • リモート ビデオ ストリームのサイズ (幅/高さ) を設定するには、 StreamSizeを使用します。

    StreamSize renderStreamSize = VideoStreamRenderer.getSize();
    int width = renderStreamSize.getWidth();
    int height = renderStreamSize.getHeight();
    

RendererView のメソッドとプロパティ

VideoStreamRendererViewを作成するときに、このビューに適用するScalingModeプロパティとmirroredプロパティを指定できます。

スケーリング モードには、 CROP または FITのいずれかを指定できます。

VideoStreamRenderer remoteVideoRenderer = new VideoStreamRenderer(remoteVideoStream, appContext);
VideoStreamRendererView rendererView = remoteVideoRenderer.createView(new CreateViewOptions(ScalingMode.Fit));

作成した RendererView は、次のスニペットを使用してアプリケーションの UI にアタッチできます。

layout.addView(rendererView);

後で、updateScalingModeまたはRendererViewの引数を使用して、ScalingMode.CROP オブジェクトに対するScalingMode.FIT操作を使用してスケーリング モードを更新できます。

// Update the scale mode for this view.
rendererView.updateScalingMode(ScalingMode.CROP)

システムを設定する

次の手順のようにして、システムを設定します。

Xcode プロジェクトを作成する

Xcode で、新しい iOS プロジェクトを作成し、[単一ビュー アプリ] テンプレートを選択します。 この記事では SwiftUI フレームワークを使うので、[言語][Swift] に、[インターフェイス][SwiftUI] に設定する必要があります。

この記事では、テストは作成しません。 [Include Tests] チェック ボックスはオフにしてもかまいません。

Xcode 内にプロジェクトを作成するためのウィンドウを示すスクリーンショット。

CocoaPods を使用してパッケージと依存関係をインストールする

  1. この例のように、アプリケーション用の Podfile を作成します。

    platform :ios, '13.0'
    use_frameworks!
    target 'AzureCommunicationCallingSample' do
        pod 'AzureCommunicationCalling', '~> 1.0.0'
    end
    
  2. pod install を実行します。

  3. Xcode を使用して .xcworkspace を開きます。

マイクへのアクセスを要求する

デバイスのマイクにアクセスするには、NSMicrophoneUsageDescription を使用してアプリの情報プロパティ一覧を更新する必要があります。 関連付けられる値には、システムがユーザーにアクセスを要求するために使うダイアログに含まれる文字列を設定します。

プロジェクト ツリーの [Info.plist] エントリを右クリックし、[Open As]> を選択します。 最上位の <dict> セクションに以下の行を追加してから、ファイルを保存します。

<key>NSMicrophoneUsageDescription</key>
<string>Need microphone access for VOIP calling.</string>

アプリのフレームワークを設定する

プロジェクトの ContentView.swift ファイルを開きます。 ファイルの先頭に import 宣言を追加して、AzureCommunicationCalling ライブラリをインポートします。 さらに、AVFoundation をインポートします。 これは、コードでのオーディオ アクセス許可の要求に必要です。

import AzureCommunicationCalling
import AVFoundation

CallAgent を初期化する

CallAgent から CallClient インスタンスを作成するには、初期化された後に callClient.createCallAgent オブジェクトを非同期に返す CallAgent メソッドを使用する必要があります。

通話クライアントを作成するには、CommunicationTokenCredential オブジェクトを渡します。

import AzureCommunication

let tokenString = "token_string"
var userCredential: CommunicationTokenCredential?
do {
    let options = CommunicationTokenRefreshOptions(initialToken: token, refreshProactively: true, tokenRefresher: self.fetchTokenSync)
    userCredential = try CommunicationTokenCredential(withOptions: options)
} catch {
    updates("Couldn't created Credential object", false)
    initializationDispatchGroup!.leave()
    return
}

// tokenProvider needs to be implemented by Contoso, which fetches a new token
public func fetchTokenSync(then onCompletion: TokenRefreshOnCompletion) {
    let newToken = self.tokenProvider!.fetchNewToken()
    onCompletion(newToken, nil)
}

作成した CommunicationTokenCredential オブジェクトを CallClient に渡し、表示名を設定します。

self.callClient = CallClient()
let callAgentOptions = CallAgentOptions()
options.displayName = " iOS Azure Communication Services User"

self.callClient!.createCallAgent(userCredential: userCredential!,
    options: callAgentOptions) { (callAgent, error) in
        if error == nil {
            print("Create agent succeeded")
            self.callAgent = callAgent
        } else {
            print("Create agent failed")
        }
})

デバイスの管理

通話でビデオの使用を開始するには、デバイスを管理する方法を知っている必要があります。 デバイスを使用すると、音声とビデオを通話に送信する内容を制御できます。

DeviceManager を使用すると、オーディオまたは動画のストリームを送信するために通話内で使用できるローカル デバイスを列挙できます。 また、マイクまたはカメラにアクセスするためのアクセス許可をユーザーに要求することもできます。 deviceManager オブジェクトの callClient にアクセスできます。

self.callClient!.getDeviceManager { (deviceManager, error) in
        if (error == nil) {
            print("Got device manager instance")
            self.deviceManager = deviceManager
        } else {
            print("Failed to get device manager instance")
        }
    }

ローカル デバイスを列挙する

ローカル デバイスにアクセスする場合は、デバイス マネージャーで列挙メソッドを使用できます。 列挙は同期アクションです。

// enumerate local cameras
var localCameras = deviceManager.cameras // [VideoDeviceInfo, VideoDeviceInfo...]

ローカル カメラのプレビューを取得する

Renderer を使用して、ローカル カメラからのストリームのレンダリングを開始できます。 このストリームは他の参加者には適用されません。これはローカル プレビュー フィードです。 これは、非同期アクションです。

let camera: VideoDeviceInfo = self.deviceManager!.cameras.first!
let localVideoStream = LocalVideoStream(camera: camera)
let localRenderer = try! VideoStreamRenderer(localVideoStream: localVideoStream)
self.view = try! localRenderer.createView()

ローカル カメラのプレビューのプロパティを取得する

レンダラーには、レンダリングを制御できる一連のプロパティとメソッドが含まれています。

// Constructor can take in LocalVideoStream or RemoteVideoStream
let localRenderer = VideoStreamRenderer(localVideoStream:localVideoStream)
let remoteRenderer = VideoStreamRenderer(remoteVideoStream:remoteVideoStream)

// [StreamSize] size of the rendering view
localRenderer.size

// [VideoStreamRendererDelegate] an object you provide to receive events from this Renderer instance
localRenderer.delegate

// [Synchronous] create view
try! localRenderer.createView()

// [Synchronous] create view with rendering options
try! localRenderer!.createView(withOptions: CreateViewOptions(scalingMode: ScalingMode.fit))

// [Synchronous] dispose rendering view
localRenderer.dispose()

動画を使用して 1:1 の通話を行う

デバイス マネージャーのインスタンスを取得する場合は、デバイスの管理に関するセクションを参照してください。

let firstCamera = self.deviceManager!.cameras.first
self.localVideoStreams = [LocalVideoStream]()
self.localVideoStreams!.append(LocalVideoStream(camera: firstCamera!))
let videoOptions = VideoOptions(localVideoStreams: self.localVideoStreams!)

let startCallOptions = StartCallOptions()
startCallOptions.videoOptions = videoOptions

let callee = CommunicationUserIdentifier('UserId')
self.callAgent?.startCall(participants: [callee], options: startCallOptions) { (call, error) in
    if error == nil {
        print("Successfully started outgoing video call")
        self.call = call
    } else {
        print("Failed to start outgoing video call")
    }
}

リモート参加者の動画ストリームをレンダリングする

リモート参加者は、通話中に動画または画面共有を開始できます。

リモート参加者の動画共有または画面共有ストリームを処理する

リモート参加者のストリームを一覧表示するには、videoStreams コレクションを調べます。

var remoteParticipantVideoStream = call.remoteParticipants[0].videoStreams[0]

リモート動画ストリームのプロパティを取得する

var type: MediaStreamType = remoteParticipantVideoStream.type // 'MediaStreamTypeVideo'
var isAvailable: Bool = remoteParticipantVideoStream.isAvailable // indicates if remote stream is available
var id: Int = remoteParticipantVideoStream.id // id of remoteParticipantStream

リモート参加者ストリームをレンダリングする

リモート参加者ストリームのレンダリングを開始するには、次のコードを使用します。

let renderer = VideoStreamRenderer(remoteVideoStream: remoteParticipantVideoStream)
let targetRemoteParticipantView = renderer?.createView(withOptions: CreateViewOptions(scalingMode: ScalingMode.crop))
// To update the scaling mode later
targetRemoteParticipantView.update(scalingMode: ScalingMode.fit)

リモート動画レンダラーのメソッドとプロパティを取得する

// [Synchronous] dispose() - dispose renderer and all `RendererView` associated with this renderer. To be called when you have removed all associated views from the UI.
remoteVideoRenderer.dispose()

システムを設定する

次の手順のようにして、システムを設定します。

Visual Studio プロジェクトの作成

ユニバーサル Windows プラットフォーム アプリを作成する場合は、Visual Studio 2022 で新しい空のアプリケーション (ユニバーサル Windows)プロジェクトを作成します。 プロジェクト名を入力した後、10.0.17763.0 より後の Windows SDK を自由に選択できます。

WinUI 3 アプリの場合、Blank App, Packaged (WinUI 3 in Desktop) テンプレートで新しいプロジェクトを作成し、シングルページの WinUI 3 アプリを設定します。 Windows App SDK バージョン 1.3 以降が必要です。

NuGet パッケージ マネージャーを使用してパッケージと依存関係をインストールする

Calling SDK の API とライブラリは、NuGet パッケージにより一般公開されています。

Calling SDK NuGet パッケージを検索、ダウンロード、インストールするには:

  1. [ツール]>[NuGet パッケージ マネージャー]>[ソリューションの NuGet パッケージの管理] を選んで、NuGet パッケージ マネージャーを開きます。
  2. [参照] を選んでから、検索ボックスに「Azure.Communication.Calling.WindowsClient」と入力します。
  3. [プレリリースを含める] チェックボックスがオンになっていることを確認します。
  4. Azure.Communication.Calling.WindowsClient パッケージを選び、Azure.Communication.Calling.WindowsClient1.4.0-beta.1 以降のバージョンを選びます。
  5. 右側のペインで、Azure Communication Services プロジェクトに対応するチェックボックスをオンにします。
  6. [インストール] を選択します。

マイクへのアクセスを要求する

アプリにはカメラへのアクセスが必要です。 ユニバーサル Windows プラットフォーム (UWP) アプリでは、アプリ マニフェスト ファイルでカメラ機能を宣言する必要があります。

  1. Visual Studio でプロジェクトを開きます。
  2. ソリューション エクスプローラー パネルで、拡張子が.appxmanifestファイルをダブルクリックします。
  3. [機能] タブ クリックします。
  4. 機能の一覧から [Camera] チェック ボックスをオンにします。

通話の発信と終了を行うための UI ボタンを作成する

このサンプル アプリには、2 つのボタンが含まれています。 1 つは通話を発信するためのもので、もう 1 つは発信した通話を終了するためのものです。

  1. ソリューション エクスプローラー パネルで、UWP の場合は MainPage.xaml、WinUI 3 の場合は MainWindows.xaml という名前のファイルをダブルクリックします。
  2. 中央のパネルで、UI プレビューの下にある XAML コードを探します。
  3. 次の抜粋を使用して XAML コードを変更します。
<TextBox x:Name="CalleeTextBox" PlaceholderText="Who would you like to call?" />
<StackPanel>
    <Button x:Name="CallButton" Content="Start/Join call" Click="CallButton_Click" />
    <Button x:Name="HangupButton" Content="Hang up" Click="HangupButton_Click" />
</StackPanel>

Calling SDK API を使用したアプリの設定

Calling SDK API は、2 つの異なる名前空間に含まれています。

次の手順を実行して、これらの名前空間について C# コンパイラに通知し、Visual Studio の Intellisense がコード開発を支援できるようにします。

  1. ソリューション エクスプローラー パネルで、UWP の場合は MainPage.xaml、WinUI 3 の場合は MainWindows.xaml という名前のファイルの左側にある矢印をクリックします。
  2. MainPage.xaml.cs または MainWindows.xaml.cs という名前のファイルをダブルクリックします。
  3. 現在の using ステートメントの下部に次のコマンドを追加します。
using Azure.Communication.Calling.WindowsClient;

MainPage.xaml.cs または MainWindows.xaml.cs は開いたままにします。 次の手順では、さらにコードを追加します。

アプリの操作を有効にする

追加した UI ボタンは、配置された CommunicationCallの上で操作する必要があります。 つまり、 CommunicationCall データ メンバーを MainPage または MainWindow クラスに追加する必要があります。 また、 CallAgent を作成する非同期操作を有効にして成功させる必要もあります。 CallAgent データ メンバーを同じクラスに追加します。

MainPage または MainWindow クラスに次のデータ メンバーを追加してください。

CallAgent callAgent;
CommunicationCall call;

ボタン ハンドラーを作成する

以前は、XAML コードに 2 つの UI ボタンを追加しました。 次のコードは、ユーザーがボタンを選択したときに実行するハンドラーを追加します。

前のセクションのデータ メンバーの後に次のコードを追加します。

private async void CallButton_Click(object sender, RoutedEventArgs e)
{
    // Start call
}

private async void HangupButton_Click(object sender, RoutedEventArgs e)
{
    // End the current call
}

オブジェクト モデル

UWP 用の Azure Communication Services Calling クライアント ライブラリが備える主な機能のいくつかは、以下のクラスとインターフェイスにより処理されます。

名前 説明
CallClient CallClient は、通話クライアント ライブラリへのメイン エントリ ポイントです。
CallAgent CallAgent は、通話を開始して参加するために使用します。
CommunicationCall CommunicationCall は、開始または参加した通話の管理に使用されます。
CommunicationTokenCredential CommunicationTokenCredential は、CallAgent をインスタンス化するためのトークン資格情報として使用されます。
CallAgentOptions CallAgentOptions には、呼び出し元を識別するための情報が含まれています。
HangupOptions HangupOptions は、呼び出しを終了する必要があるかどうかをすべての参加者に対して通知します。

ビデオ スキーマ ハンドラーの登録

XAML の MediaElementMediaPlayerElementなどの UI コンポーネントでは、ローカルおよびリモートのビデオ フィードをレンダリングするための構成を登録する必要があります。

PackagePackage.appxmanifest タグの間に次の内容を追加してください。

<Extensions>
    <Extension Category="windows.activatableClass.inProcessServer">
        <InProcessServer>
            <Path>RtmMvrUap.dll</Path>
            <ActivatableClass ActivatableClassId="VideoN.VideoSchemeHandler" ThreadingModel="both" />
        </InProcessServer>
    </Extension>
</Extensions>

CallAgent を初期化する

CallAgent から CallClient インスタンスを作成するには、初期化されると CallClient.CreateCallAgentAsync オブジェクトを非同期に返す CallAgent メソッドを使用する必要があります。

CallAgent を作成するには、CallTokenCredential オブジェクトと CallAgentOptions オブジェクトを渡す必要があります。 形式に誤りがあるトークンが渡されると、CallTokenCredential がスローされることに注意してください。

初期化中に実行されるように、次のコードを内部に追加し、ヘルパー関数を追加します。

var callClient = new CallClient();
this.deviceManager = await callClient.GetDeviceManagerAsync();

var tokenCredential = new CallTokenCredential("<AUTHENTICATION_TOKEN>");
var callAgentOptions = new CallAgentOptions()
{
    DisplayName = "<DISPLAY_NAME>"
};

this.callAgent = await callClient.CreateCallAgentAsync(tokenCredential, callAgentOptions);
this.callAgent.CallsUpdated += Agent_OnCallsUpdatedAsync;
this.callAgent.IncomingCallReceived += Agent_OnIncomingCallAsync;

リソース用の有効な資格情報トークンで <AUTHENTICATION_TOKEN> を変更します。 資格情報トークンのソーシングの詳細については、「 ユーザー アクセス トークン」を参照してください。

ビデオ カメラを使用して 1:1 の通話を行う

CallAgent を作成するために必要なオブジェクトの準備ができました。 次に、 CallAgent を非同期的に作成し、ビデオ通話を行います。

private async void CallButton_Click(object sender, RoutedEventArgs e)
{
    var callString = CalleeTextBox.Text.Trim();

    if (!string.IsNullOrEmpty(callString))
    {
        if (callString.StartsWith("8:")) // 1:1 Azure Communication Services call
        {
            this.call = await StartAcsCallAsync(callString);
        }
    }

    if (this.call != null)
    {
        this.call.RemoteParticipantsUpdated += OnRemoteParticipantsUpdatedAsync;
        this.call.StateChanged += OnStateChangedAsync;
    }
}

private async Task<CommunicationCall> StartAcsCallAsync(string acsCallee)
{
    var options = await GetStartCallOptionsAsync();
    var call = await this.callAgent.StartCallAsync( new [] { new UserCallIdentifier(acsCallee) }, options);
    return call;
}

var micStream = new LocalOutgoingAudioStream(); // Create a default local audio stream
var cameraStream = new LocalOutgoingVideoStream(this.viceManager.Cameras.FirstOrDefault() as VideoDeviceDetails); // Create a default video stream

private async Task<StartCallOptions> GetStartCallOptionsAsync()
{
    return new StartCallOptions() {
        OutgoingAudioOptions = new OutgoingAudioOptions() { IsMuted = true, Stream = micStream  },
        OutgoingVideoOptions = new OutgoingVideoOptions() { Streams = new OutgoingVideoStream[] { cameraStream } }
    };
}

ローカル カメラのプレビュー

必要に応じて、ローカル カメラのプレビューを設定できます。 次の MediaPlayerElementを使用してビデオをレンダリングできます。

<Grid>
    <MediaPlayerElement x:Name="LocalVideo" AutoPlay="True" />
    <MediaPlayerElement x:Name="RemoteVideo" AutoPlay="True" />
</Grid>

ローカル プレビュー MediaPlayerElement を初期化するには:

private async void CameraList_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    if (cameraStream != null)
    {
        await cameraStream?.StopPreviewAsync();
        if (this.call != null)
        {
            await this.call?.StopVideoAsync(cameraStream);
        }
    }
    var selectedCamera = CameraList.SelectedItem as VideoDeviceDetails;
    cameraStream = new LocalOutgoingVideoStream(selectedCamera);

    var localUri = await cameraStream.StartPreviewAsync();
    LocalVideo.Source = MediaSource.CreateFromUri(localUri);

    if (this.call != null) {
        await this.call?.StartVideoAsync(cameraStream);
    }
}

リモート カメラ ストリームをレンダリングする

OnCallsUpdated イベントに応答して偶数ハンドラーを設定します。

private async void OnCallsUpdatedAsync(object sender, CallsUpdatedEventArgs args)
{
    var removedParticipants = new List<RemoteParticipant>();
    var addedParticipants = new List<RemoteParticipant>();

    foreach(var call in args.RemovedCalls)
    {
        removedParticipants.AddRange(call.RemoteParticipants.ToList<RemoteParticipant>());
    }

    foreach (var call in args.AddedCalls)
    {
        addedParticipants.AddRange(call.RemoteParticipants.ToList<RemoteParticipant>());
    }

    await OnParticipantChangedAsync(removedParticipants, addedParticipants);
}

private async void OnRemoteParticipantsUpdatedAsync(object sender, ParticipantsUpdatedEventArgs args)
{
    await OnParticipantChangedAsync(
        args.RemovedParticipants.ToList<RemoteParticipant>(),
        args.AddedParticipants.ToList<RemoteParticipant>());
}

private async Task OnParticipantChangedAsync(IEnumerable<RemoteParticipant> removedParticipants, IEnumerable<RemoteParticipant> addedParticipants)
{
    foreach (var participant in removedParticipants)
    {
        foreach(var incomingVideoStream in  participant.IncomingVideoStreams)
        {
            var remoteVideoStream = incomingVideoStream as RemoteIncomingVideoStream;
            if (remoteVideoStream != null)
            {
                await remoteVideoStream.StopPreviewAsync();
            }
        }
        participant.VideoStreamStateChanged -= OnVideoStreamStateChanged;
    }

    foreach (var participant in addedParticipants)
    {
        participant.VideoStreamStateChanged += OnVideoStreamStateChanged;
    }
}

private void OnVideoStreamStateChanged(object sender, VideoStreamStateChangedEventArgs e)
{
    CallVideoStream callVideoStream = e.CallVideoStream;

    switch (callVideoStream.StreamDirection)
    {
        case StreamDirection.Outgoing:
            OnOutgoingVideoStreamStateChanged(callVideoStream as OutgoingVideoStream);
            break;
        case StreamDirection.Incoming:
            OnIncomingVideoStreamStateChanged(callVideoStream as IncomingVideoStream);
            break;
    }
}

MediaPlayerElement でリモート ビデオ ストリームのレンダリングを開始します:

private async void OnIncomingVideoStreamStateChanged(IncomingVideoStream incomingVideoStream)
{
    switch (incomingVideoStream.State)
    {
        case VideoStreamState.Available:
            {
                switch (incomingVideoStream.Kind)
                {
                    case VideoStreamKind.RemoteIncoming:
                        var remoteVideoStream = incomingVideoStream as RemoteIncomingVideoStream;
                        var uri = await remoteVideoStream.StartPreviewAsync();

                        await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
                        {
                            RemoteVideo.Source = MediaSource.CreateFromUri(uri);
                        });

                        /* Or WinUI 3
                        this.DispatcherQueue.TryEnqueue(() => {
                            RemoteVideo.Source = MediaSource.CreateFromUri(uri);
                            RemoteVideo.MediaPlayer.Play();
                        });
                        */

                        break;

                    case VideoStreamKind.RawIncoming:
                        break;
                }

                break;
            }
        case VideoStreamState.Started:
            break;
        case VideoStreamState.Stopping:
            break;
        case VideoStreamState.Stopped:
            if (incomingVideoStream.Kind == VideoStreamKind.RemoteIncoming)
            {
                var remoteVideoStream = incomingVideoStream as RemoteIncomingVideoStream;
                await remoteVideoStream.StopPreviewAsync();
            }
            break;
        case VideoStreamState.NotAvailable:
            break;
    }
}

通話を終了する

呼び出しが行われたら、HangupAsync オブジェクトの CommunicationCall メソッドを使用して呼び出しを切断します。

呼び出しを終了する必要があるかどうかを参加者に通知するには、 HangupOptions のインスタンスを使用します。

HangupButton_Click内に次のコードを追加します。

var call = this.callAgent?.Calls?.FirstOrDefault();
if (call != null)
{
    var call = this.callAgent?.Calls?.FirstOrDefault();
    if (call != null)
    {
        foreach (var localVideoStream in call.OutgoingVideoStreams)
        {
            await call.StopVideoAsync(localVideoStream);
        }

        try
        {
            if (cameraStream != null)
            {
                await cameraStream.StopPreviewAsync();
            }

            await call.HangUpAsync(new HangUpOptions() { ForEveryone = false });
        }
        catch(Exception ex) 
        { 
            var errorCode = unchecked((int)(0x0000FFFFU & ex.HResult));
            if (errorCode != 98) // Sample error code, sam_status_failed_to_hangup_for_everyone (98)
            {
                throw;
            }
        }
    }
}

コードの実行

  1. Visual Studio で、 x64x86、または ARM64用のアプリがビルドされていることを確認します。
  2. F5 キーを押してアプリの実行を開始します。
  3. [ CommunicationCall ] ボタンをクリックして、定義された受信者に通話を発信します。

アプリを初めて実行すると、マイクへのアクセスを許可するようにユーザーに求められます。

次のステップ