次の方法で共有


クイックスタート: 通話アプリを Teams 自動応答に参加させる

このクイックスタートでは、Azure Communication Services ユーザーから Teams 自動応答への通話を始める方法について説明します。 これを実現するには、次の手順を実行します。

  1. Teams テナントでの Azure Communication Services リソースのフェデレーションを有効にします。
  2. Teams 管理センターを使用して、Teams 自動応答を選択または作成します。
  3. Teams 管理センターを介して自動応答のメール アドレスを取得します。
  4. Graph API を使用して自動応答のオブジェクト ID を取得します。
  5. Azure Communication Services Calling SDK を使用して通話を開始します。

最後までスキップしたい場合は、GitHub のサンプルとしてこのクイックスタートをダウンロードできます。

Teams テナントで相互運用性を有効にする

Teams 管理者ロールを持つ Microsoft Entra ユーザーは、MicrosoftTeams モジュールを使って PowerShell コマンドレットを実行し、テナント内の Communication Services リソースを有効にすることができます。

1.Microsoft Teams モジュールを準備する

まず、PowerShell を開き、次のコマンドを使って Teams モジュールの存在を検証します。

Get-module *teams* 

MicrosoftTeams モジュールが表示されない場合は、最初にインストールします。 モジュールをインストールするには、管理者として PowerShell を実行する必要があります。 次に、次のコマンドを実行します。

	Install-Module -Name MicrosoftTeams

インストールされるモジュールが表示されるので、Y または A で応答して確認します。 モジュールがインストールされていても古くなっている場合は、次のコマンドを実行してモジュールを更新できます。

	Update-Module MicrosoftTeams

2.Microsoft Teams モジュールに接続する

モジュールがインストールされ準備ができたら、次のコマンドを使って MicrosftTeams モジュールに接続できます。 ログインするための対話型ウィンドウが表示されます。 使用するユーザー アカウントには、Teams 管理者のアクセス許可が必要です。 それ以外の場合は、次の手順で access denied 応答が返される可能性があります。

Connect-MicrosoftTeams

3.テナント構成を有効にする

Communication Services リソースとの相互運用性は、テナント構成と割り当てられたポリシーによって制御されます。 Teams テナントには 1 つのテナント構成があり、Teams ユーザーにはグローバル ポリシーまたはカスタム ポリシーが割り当てられます。 詳細については、「Teams でポリシーを割り当てる」を参照してください。

ログインに成功したら、コマンドレット Set-CsTeamsAcsFederationConfiguration を実行して、テナントで Communication Services リソースを有効にすることができます。 テキスト IMMUTABLE_RESOURCE_ID を、コミュニケーション リソース内の不変リソース ID に置き換えます。 この情報を取得する方法の詳細については、こちらを参照してください。

$allowlist = @('IMMUTABLE_RESOURCE_ID')
Set-CsTeamsAcsFederationConfiguration -EnableAcsUsers $True -AllowedAcsResources $allowlist

4.テナント ポリシーを有効にする

各 Teams ユーザーには、Communication Services ユーザーがこの Teams ユーザーを呼び出すことができるかどうかを決定する External Access Policy が割り当てられます。 コマンドレット Set-CsExternalAccessPolicy を使って、Teams ユーザーに割り当てられたポリシーで EnableAcsFederationAccess$true に設定されていることを確認します

Set-CsExternalAccessPolicy -Identity Global -EnableAcsFederationAccess $true

Teams 自動応答を作成または選択する

Teams 自動応答は、着信通話用の自動通話処理システムを提供するシステムです。 仮想の受付担当として機能し、人間のオペレーターを必要とせずに、適切な担当者または部門に発信者を自動的にルーティングできます。 Teams 管理センターを使用して、既存の自動応答を選択するか、新しく作成できます。

Teams 管理センターを使用して自動応答を作成する方法の詳細については、こちらを参照してください。

自動応答のオブジェクト ID を見つける

自動応答を作成したら、後で通話に使用するために、関連づけられたオブジェクト ID を見つける必要があります。 オブジェクト ID は、自動応答にアタッチされたリソース アカウントに関連付けられています。Teams 管理の [リソース アカウント] タブを開き、アカウントのメールを検索します。 Teams 管理ポータルの [リソース アカウント] のスクリーンショット。 リソース アカウントに必要なすべての情報は、検索でこのメールを使用して、Microsoft Graph Explorer で確認できます。

https://graph.microsoft.com/v1.0/users/lab-test2-cq-@contoso.com

結果で、"ID" フィールドが見つかります。

    "userPrincipalName": "lab-test2-cq@contoso.com",
    "id": "31a011c2-2672-4dd0-b6f9-9334ef4999db"

前提条件

設定

新しい Node.js アプリケーションを作成する

ターミナルまたはコマンド ウィンドウを開き、自分のアプリ用に新しいディレクトリを作成して、そのディレクトリに移動します。

mkdir calling-quickstart && cd calling-quickstart

パッケージをインストールする

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

重要

このクイックスタートでは、Azure Communication Services Calling SDK バージョン next を使用します。

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

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

このクイックスタートでは、webpack を使用してアプリケーション資産をバンドルします。 次のコマンドを実行して webpackwebpack-cliwebpack-dev-server の 3 つの npm パッケージをインストールし、開発の依存関係として package.json 内でリスト化します。

npm install copy-webpack-plugin@^11.0.0 webpack@^5.88.2 webpack-cli@^5.1.4 webpack-dev-server@^4.15.1 --save-dev

自分のプロジェクトのルート ディレクトリに、index.html ファイルを作成します。 このファイルを使用して、ユーザーが 1 対 1 のビデオ通話を開始できるようにする基本的なレイアウトを構成します。

コードは次のとおりです。

<!-- index.html -->
<!DOCTYPE html>
<html>
    <head>
        <title>Azure Communication Services - Calling Web SDK</title>
    </head>
    <body>
        <h4>Azure Communication Services - Calling Web SDK</h4>
        <input id="user-access-token"
            type="text"
            placeholder="User access token"
            style="margin-bottom:1em; width: 500px;"/>
        <button id="initialize-teams-call-agent" type="button">Initialize Call Agent</button>
        <br>
        <br>
        <input id="application-object-id"
            type="text"
            placeholder="Enter application objectId identity in format: 'APP_GUID'"
            style="margin-bottom:1em; width: 500px; display: block;"/>
        <button id="start-call-button" type="button" disabled="true">Start Call</button>
        <button id="hangup-call-button" type="button" disabled="true">Hang up Call</button>
        <button id="accept-call-button" type="button" disabled="true">Accept Call</button>
        <button id="start-video-button" type="button" disabled="true">Start Video</button>
        <button id="stop-video-button" type="button" disabled="true">Stop Video</button>
        <br>
        <br>
        <div id="connectedLabel" style="color: #13bb13;" hidden>Call is connected!</div>
        <br>
        <div id="remoteVideoContainer" style="width: 40%;" hidden>Remote participants' video streams:</div>
        <br>
        <div id="localVideoContainer" style="width: 30%;" hidden>Local video stream:</div>
        <!-- points to the bundle generated from client.js -->
        <script src="./main.js"></script>
    </body>
</html>

Azure Communication Services Calling Web SDK オブジェクト モデル

Azure Communication Services Calling SDK の主な機能のいくつかは、次のクラスとインターフェイスによって処理されます。

名前 説明
CallClient Calling SDK へのメイン エントリ ポイント。
CallAgent 通話を開始および管理するために使用されます。
DeviceManager メディア デバイスを管理するために使用されます。
Call 通話を表すために使用します。
LocalVideoStream ローカル システム上のカメラ デバイスのローカル ビデオ ストリームを作成するために使用します。
RemoteParticipant 通話のリモート参加者を表すために使用します。
RemoteVideoStream リモート参加者からのリモート ビデオ ストリームを表すために使用します。

このクイックスタートのアプリケーション ロジックを格納するために、client.js という名前のファイルを自分のプロジェクトのルート ディレクトリに作成します。 次のコードを client.js に追加します。

// Make sure to install the necessary dependencies
const { CallClient, VideoStreamRenderer, LocalVideoStream } = require('@azure/communication-calling');
const { AzureCommunicationTokenCredential } = require('@azure/communication-common');
const { AzureLogger, setLogLevel } = require("@azure/logger");
// Set the log level and output
setLogLevel('verbose');
AzureLogger.log = (...args) => {
    console.log(...args);
};
// Calling web sdk objects
let callAgent;
let deviceManager;
let call;
let incomingCall;
let localVideoStream;
let localVideoStreamRenderer;
// UI widgets
let userAccessToken = document.getElementById('user-access-token');
let applicationObjectId = document.getElementById('application-object-id');
let initializeCallAgentButton = document.getElementById('initialize-teams-call-agent');
let startCallButton = document.getElementById('start-call-button');
let hangUpCallButton = document.getElementById('hangup-call-button');
let acceptCallButton = document.getElementById('accept-call-button');
let startVideoButton = document.getElementById('start-video-button');
let stopVideoButton = document.getElementById('stop-video-button');
let connectedLabel = document.getElementById('connectedLabel');
let remoteVideoContainer = document.getElementById('remoteVideoContainer');
let localVideoContainer = document.getElementById('localVideoContainer');
/**
 * Create an instance of CallClient. Initialize a CallAgent instance with a AzureCommunicationTokenCredential via created CallClient. CallAgent enables us to make outgoing calls and receive incoming calls. 
 * You can then use the CallClient.getDeviceManager() API instance to get the DeviceManager.
 */
initializeCallAgentButton.onclick = async () => {
    try {
        const callClient = new CallClient(); 
        tokenCredential = new AzureCommunicationTokenCredential(userAccessToken.value.trim());
        callAgent = await callClient.createCallAgent(tokenCredential)
        // Set up a camera device to use.
        deviceManager = await callClient.getDeviceManager();
        await deviceManager.askDevicePermission({ video: true });
        await deviceManager.askDevicePermission({ audio: true });
        // Listen for an incoming call to accept.
        callAgent.on('incomingCall', async (args) => {
            try {
                incomingCall = args.incomingCall;
                acceptCallButton.disabled = false;
                startCallButton.disabled = true;
            } catch (error) {
                console.error(error);
            }
        });
        startCallButton.disabled = false;
        initializeCallAgentButton.disabled = true;
    } catch(error) {
        console.error(error);
    }
}
/**
 * Place a 1:1 outgoing video call to an Teams Auto attendant
 * Add an event listener to initiate a call when the `startCallButton` is selected.
 * Enumerate local cameras using the deviceManager `getCameraList` API.
 * In this quickstart, we're using the first camera in the collection. Once the desired camera is selected, a
 * LocalVideoStream instance will be constructed and passed within `videoOptions` as an item within the
 * localVideoStream array to the call method. When the call connects, your application will be sending a video stream to the other participant. 
 */
startCallButton.onclick = async () => {
    try {
        const localVideoStream = await createLocalVideoStream();
        const videoOptions = localVideoStream ? { localVideoStreams: [localVideoStream] } : undefined;
        call = callAgent.startCall([{ teamsAppId: applicationObjectId.value.trim(), cloud:"public" }], { videoOptions: videoOptions });
        // Subscribe to the call's properties and events.
        subscribeToCall(call);
    } catch (error) {
        console.error(error);
    }
}
/**
 * Accepting an incoming call with a video
 * Add an event listener to accept a call when the `acceptCallButton` is selected.
 * You can accept incoming calls after subscribing to the `CallAgent.on('incomingCall')` event.
 * You can pass the local video stream to accept the call with the following code.
 */
acceptCallButton.onclick = async () => {
    try {
        const localVideoStream = await createLocalVideoStream();
        const videoOptions = localVideoStream ? { localVideoStreams: [localVideoStream] } : undefined;
        call = await incomingCall.accept({ videoOptions });
        // Subscribe to the call's properties and events.
        subscribeToCall(call);
    } catch (error) {
        console.error(error);
    }
}
// Subscribe to a call obj.
// Listen for property changes and collection udpates.
subscribeToCall = (call) => {
    try {
        // Inspect the initial call.id value.
        console.log(`Call Id: ${call.id}`);
        //Subsribe to call's 'idChanged' event for value changes.
        call.on('idChanged', () => {
            console.log(`Call ID changed: ${call.id}`); 
        });
        // Inspect the initial call.state value.
        console.log(`Call state: ${call.state}`);
        // Subscribe to call's 'stateChanged' event for value changes.
        call.on('stateChanged', async () => {
            console.log(`Call state changed: ${call.state}`);
            if(call.state === 'Connected') {
                connectedLabel.hidden = false;
                acceptCallButton.disabled = true;
                startCallButton.disabled = true;
                hangUpCallButton.disabled = false;
                startVideoButton.disabled = false;
                stopVideoButton.disabled = false;
            } else if (call.state === 'Disconnected') {
                connectedLabel.hidden = true;
                startCallButton.disabled = false;
                hangUpCallButton.disabled = true;
                startVideoButton.disabled = true;
                stopVideoButton.disabled = true;
                console.log(`Call ended, call end reason={code=${call.callEndReason.code}, subCode=${call.callEndReason.subCode}}`);
            }   
        });

        call.on('isLocalVideoStartedChanged', () => {
            console.log(`isLocalVideoStarted changed: ${call.isLocalVideoStarted}`);
        });
        console.log(`isLocalVideoStarted: ${call.isLocalVideoStarted}`);
        call.localVideoStreams.forEach(async (lvs) => {
            localVideoStream = lvs;
            await displayLocalVideoStream();
        });
        call.on('localVideoStreamsUpdated', e => {
            e.added.forEach(async (lvs) => {
                localVideoStream = lvs;
                await displayLocalVideoStream();
            });
            e.removed.forEach(lvs => {
               removeLocalVideoStream();
            });
        });
        
        // Inspect the call's current remote participants and subscribe to them.
        call.remoteParticipants.forEach(remoteParticipant => {
            subscribeToRemoteParticipant(remoteParticipant);
        });
        // Subscribe to the call's 'remoteParticipantsUpdated' event to be
        // notified when new participants are added to the call or removed from the call.
        call.on('remoteParticipantsUpdated', e => {
            // Subscribe to new remote participants that are added to the call.
            e.added.forEach(remoteParticipant => {
                subscribeToRemoteParticipant(remoteParticipant)
            });
            // Unsubscribe from participants that are removed from the call
            e.removed.forEach(remoteParticipant => {
                console.log('Remote participant removed from the call.');
            });
        });
    } catch (error) {
        console.error(error);
    }
}
// Subscribe to a remote participant obj.
// Listen for property changes and collection udpates.
subscribeToRemoteParticipant = (remoteParticipant) => {
    try {
        // Inspect the initial remoteParticipant.state value.
        console.log(`Remote participant state: ${remoteParticipant.state}`);
        // Subscribe to remoteParticipant's 'stateChanged' event for value changes.
        remoteParticipant.on('stateChanged', () => {
            console.log(`Remote participant state changed: ${remoteParticipant.state}`);
        });
        // Inspect the remoteParticipants's current videoStreams and subscribe to them.
        remoteParticipant.videoStreams.forEach(remoteVideoStream => {
            subscribeToRemoteVideoStream(remoteVideoStream)
        });
        // Subscribe to the remoteParticipant's 'videoStreamsUpdated' event to be
        // notified when the remoteParticiapant adds new videoStreams and removes video streams.
        remoteParticipant.on('videoStreamsUpdated', e => {
            // Subscribe to newly added remote participant's video streams.
            e.added.forEach(remoteVideoStream => {
                subscribeToRemoteVideoStream(remoteVideoStream)
            });
            // Unsubscribe from newly removed remote participants' video streams.
            e.removed.forEach(remoteVideoStream => {
                console.log('Remote participant video stream was removed.');
            })
        });
    } catch (error) {
        console.error(error);
    }
}
/**
 * Subscribe to a remote participant's remote video stream obj.
 * You have to subscribe to the 'isAvailableChanged' event to render the remoteVideoStream. If the 'isAvailable' property
 * changes to 'true' a remote participant is sending a stream. Whenever the availability of a remote stream changes
 * you can choose to destroy the whole 'Renderer' a specific 'RendererView' or keep them. Displaying RendererView without a video stream will result in a blank video frame. 
 */
subscribeToRemoteVideoStream = async (remoteVideoStream) => {
    // Create a video stream renderer for the remote video stream.
    let videoStreamRenderer = new VideoStreamRenderer(remoteVideoStream);
    let view;
    const renderVideo = async () => {
        try {
            // Create a renderer view for the remote video stream.
            view = await videoStreamRenderer.createView();
            // Attach the renderer view to the UI.
            remoteVideoContainer.hidden = false;
            remoteVideoContainer.appendChild(view.target);
        } catch (e) {
            console.warn(`Failed to createView, reason=${e.message}, code=${e.code}`);
        }	
    }
    
    remoteVideoStream.on('isAvailableChanged', async () => {
        // Participant has switched video on.
        if (remoteVideoStream.isAvailable) {
            await renderVideo();
        // Participant has switched video off.
        } else {
            if (view) {
                view.dispose();
                view = undefined;
            }
        }
    });
    // Participant has video on initially.
    if (remoteVideoStream.isAvailable) {
        await renderVideo();
    }
}
// Start your local video stream.
// This will send your local video stream to remote participants so they can view it.
startVideoButton.onclick = async () => {
    try {
        const localVideoStream = await createLocalVideoStream();
        await call.startVideo(localVideoStream);
    } catch (error) {
        console.error(error);
    }
}
// Stop your local video stream.
// This will stop your local video stream from being sent to remote participants.
stopVideoButton.onclick = async () => {
    try {
        await call.stopVideo(localVideoStream);
    } catch (error) {
        console.error(error);
    }
}
/**
 * To render a LocalVideoStream, you need to create a new instance of VideoStreamRenderer, and then
 * create a new VideoStreamRendererView instance using the asynchronous createView() method.
 * You may then attach view.target to any UI element. 
 */
// Create a local video stream for your camera device
createLocalVideoStream = async () => {
    const camera = (await deviceManager.getCameras())[0];
    if (camera) {
        return new LocalVideoStream(camera);
    } else {
        console.error(`No camera device found on the system`);
    }
}
// Display your local video stream preview in your UI
displayLocalVideoStream = async () => {
    try {
        localVideoStreamRenderer = new VideoStreamRenderer(localVideoStream);
        const view = await localVideoStreamRenderer.createView();
        localVideoContainer.hidden = false;
        localVideoContainer.appendChild(view.target);
    } catch (error) {
        console.error(error);
    } 
}
// Remove your local video stream preview from your UI
removeLocalVideoStream = async() => {
    try {
        localVideoStreamRenderer.dispose();
        localVideoContainer.hidden = true;
    } catch (error) {
        console.error(error);
    } 
}
// End the current call
hangUpCallButton.addEventListener("click", async () => {
    // end the current call
    await call.hangUp();
});

webpack ローカル サーバー コードを追加する

このクイックスタートのローカル サーバー ロジックを格納するために、webpack.config.js という名前のファイルを自分のプロジェクトのルート ディレクトリに作成します。 次のコードを webpack.config.js ファイルに追加します。

const path = require('path');
const CopyPlugin = require("copy-webpack-plugin");

module.exports = {
    mode: 'development',
    entry: './client.js',
    output: {
        filename: 'main.js',
        path: path.resolve(__dirname, 'dist'),
    },
    devServer: {
        static: {
            directory: path.join(__dirname, './')
        },
    },
    plugins: [
        new CopyPlugin({
            patterns: [
                './index.html'
            ]
        }),
    ]
};

コードの実行

アプリをビルドして実行するには、webpack-dev-server を使用します。 次のコマンドを実行して、ローカルの Web サーバーにアプリケーション ホストをバンドルします。

npx webpack serve --config webpack.config.js

手動で通話を設定する手順:

  1. ブラウザーを開き、http://localhost:8080/. に移動します
  2. 有効なユーザー アクセス トークンを入力します。 使用可能なトークンをまだ入手していない場合は、ユーザー アクセス トークンに関するドキュメントを参照してください。
  3. 両方のタブで、[Initialize Call Agent] (通話エージェントの初期化) ボタンをクリックします。
  4. 自動応答のオブジェクト ID を入力し、[Start Call] (通話を開始) ボタンを選択します。 アプリケーションにより、指定したオブジェクト ID を使って自動応答への発信通話が開始されます。
  5. 通話は自動応答に接続されます。
  6. Communication Services ユーザーは、その構成に基づいて自動応答を介してルーティングされます。

重要

Azure Communication Services のこの機能は、現在プレビュー段階にあります。

プレビューの API と SDK は、サービス レベル アグリーメントなしに提供されます。 運用環境のワークロードには使用しないことをお勧めします。 一部の機能はサポート対象ではなく、機能が制限されることがあります。

詳細については、「Microsoft Azure プレビューの追加利用規約」を確認してください。

このクイックスタートでは、Azure Communication Services ユーザーから Teams 自動応答への通話を開始する方法について説明します。 これを実現するには、次の手順を実行します。

  1. Teams テナントでの Azure Communication Services リソースのフェデレーションを有効にします。
  2. Teams 管理センターを使用して、Teams 自動応答を選択または作成します。
  3. Teams 管理センターを介して自動応答のメール アドレスを取得します。
  4. Graph API を使用して自動応答のオブジェクト ID を取得します。
  5. Azure Communication Services Calling SDK を使用して通話を開始します。

最後までスキップしたい場合は、GitHub のサンプルとしてこのクイックスタートをダウンロードできます。

Teams テナントで相互運用性を有効にする

Teams 管理者ロールを持つ Microsoft Entra ユーザーは、MicrosoftTeams モジュールを使って PowerShell コマンドレットを実行し、テナント内の Communication Services リソースを有効にすることができます。

1.Microsoft Teams モジュールを準備する

まず、PowerShell を開き、次のコマンドを使って Teams モジュールの存在を検証します。

Get-module *teams* 

MicrosoftTeams モジュールが表示されない場合は、最初にインストールします。 モジュールをインストールするには、管理者として PowerShell を実行する必要があります。 次に、次のコマンドを実行します。

	Install-Module -Name MicrosoftTeams

インストールされるモジュールが表示されるので、Y または A で応答して確認します。 モジュールがインストールされていても古くなっている場合は、次のコマンドを実行してモジュールを更新できます。

	Update-Module MicrosoftTeams

2.Microsoft Teams モジュールに接続する

モジュールがインストールされ準備ができたら、次のコマンドを使って MicrosftTeams モジュールに接続できます。 ログインするための対話型ウィンドウが表示されます。 使用するユーザー アカウントには、Teams 管理者のアクセス許可が必要です。 それ以外の場合は、次の手順で access denied 応答が返される可能性があります。

Connect-MicrosoftTeams

3.テナント構成を有効にする

Communication Services リソースとの相互運用性は、テナント構成と割り当てられたポリシーによって制御されます。 Teams テナントには 1 つのテナント構成があり、Teams ユーザーにはグローバル ポリシーまたはカスタム ポリシーが割り当てられます。 詳細については、「Teams でポリシーを割り当てる」を参照してください。

ログインに成功したら、コマンドレット Set-CsTeamsAcsFederationConfiguration を実行して、テナントで Communication Services リソースを有効にすることができます。 テキスト IMMUTABLE_RESOURCE_ID を、コミュニケーション リソース内の不変リソース ID に置き換えます。 この情報を取得する方法の詳細については、こちらを参照してください。

$allowlist = @('IMMUTABLE_RESOURCE_ID')
Set-CsTeamsAcsFederationConfiguration -EnableAcsUsers $True -AllowedAcsResources $allowlist

4.テナント ポリシーを有効にする

各 Teams ユーザーには、Communication Services ユーザーがこの Teams ユーザーを呼び出すことができるかどうかを決定する External Access Policy が割り当てられます。 コマンドレット Set-CsExternalAccessPolicy を使って、Teams ユーザーに割り当てられたポリシーで EnableAcsFederationAccess$true に設定されていることを確認します

Set-CsExternalAccessPolicy -Identity Global -EnableAcsFederationAccess $true

Teams 自動応答を作成または選択する

Teams 自動応答は、着信通話用の自動通話処理システムを提供するシステムです。 仮想の受付担当として機能し、人間のオペレーターを必要とせずに、適切な担当者または部門に発信者を自動的にルーティングできます。 Teams 管理センターを使用して、既存の自動応答を選択するか、新しく作成できます。

Teams 管理センターを使用して自動応答を作成する方法の詳細については、こちらを参照してください。

自動応答のオブジェクト ID を見つける

自動応答を作成したら、後で通話に使用するために、関連づけられたオブジェクト ID を見つける必要があります。 オブジェクト ID は、自動応答にアタッチされたリソース アカウントに関連付けられています。Teams 管理の [リソース アカウント] タブを開き、アカウントのメールを検索します。 Teams 管理ポータルの [リソース アカウント] のスクリーンショット。 リソース アカウントに必要なすべての情報は、検索でこのメールを使用して、Microsoft Graph Explorer で確認できます。

https://graph.microsoft.com/v1.0/users/lab-test2-cq-@contoso.com

結果で、"ID" フィールドが見つかります。

    "userPrincipalName": "lab-test2-cq@contoso.com",
    "id": "31a011c2-2672-4dd0-b6f9-9334ef4999db"

通話アプリで使うには、この ID にプレフィックスを追加する必要があります。 現在は、次のものがサポートされています。

  • パブリック クラウド自動応答: 28:orgid:<id>
  • 政府クラウド自動応答: 28:gcch:<id>

前提条件

設定

空のアクティビティで Android アプリを作成する

Android Studio で、[Start a new Android Studio project](新しい Android Studio プロジェクトを開始する) を選択します。

Android Studio で [Start a new Android Studio project]\(新しい Android Studio プロジェクトを開始する\) ボタンが選択された状態を示すスクリーンショット。

[電話とタブレット] の [空のビュー アクティビティ] を選択します。

プロジェクト テンプレート画面で [Empty Activity]\(空のアクティビティ\) オプションが選択された状態を示すスクリーンショット。

[Minimum SDK](最小 SDK) として "API 26: Android 8.0 (Oreo)" 以上を選択します。

プロジェクト テンプレート画面 2 で [Empty Activity]\(空のアクティビティ\) オプションが選択された状態を示すスクリーンショット。

パッケージをインストールする

プロジェクト settings.gradle.kts を見つけて、pluginManagementdependencyResolutionManagement で、リポジトリの一覧に mavenCentral() が表示されていることを確認します。

pluginManagement {
    repositories {
    ...
        mavenCentral()
    ...
    }
}

dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
    ...
        mavenCentral()
    }
}

次に、モジュール レベルの build.gradle で、次の行を dependencies および android セクションに追加します

android {
    ...
    
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

dependencies {
    ...
    implementation ("com.azure.android:azure-communication-calling:2.6.0")
    ...
}

アプリケーション マニフェストにアクセス許可を追加する

通話を行うために必要なアクセス許可を要求するには、アプリケーション マニフェスト (app/src/main/AndroidManifest.xml) でそれを宣言する必要があります。 ファイルの内容を次のコードに置き換えます。

    <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.contoso.acsquickstart">

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.RECORD_AUDIO" />
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <!--Our Calling SDK depends on the Apache HTTP SDK.
When targeting Android SDK 28+, this library needs to be explicitly referenced.
See https://developer.android.com/about/versions/pie/android-9.0-changes-28#apache-p-->
        <uses-library android:name="org.apache.http.legacy" android:required="false"/>
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>
    

アプリのレイアウトを設定する

必要な入力は、通話先 ID のテキスト入力と電話を掛けるためのボタンの 2 つです。 これらの入力は、デザイナーを使用するかレイアウトの xml を編集して追加できます。 ID に call_button、テキスト入力に callee_id を使用してボタンを作成します。 (app/src/main/res/layout/activity_main.xml) に移動して、ファイルの内容を以下のコードに置き換えます。

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="${launchApp}">

    <EditText
        android:id="@+id/callee_id"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:ems="10"
        android:hint="Callee Id"
        android:inputType="textPersonName"
        android:layout_marginTop="100dp"
        android:layout_marginHorizontal="20dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="46dp"
        android:gravity="center"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent">

        <Button
            android:id="@+id/call_button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Call" />

        <Button
            android:id="@+id/hangup_button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Hangup" />

    </LinearLayout>

    <TextView
        android:id="@+id/status_bar"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="16dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

メイン アクティビティのスキャフォールディングとバインドを作成する

レイアウトを作成したら、バインドと、アクティビティの基本的なスキャフォールディングを追加できます。 このアクティビティを使用して、実行時のアクセス許可の要求、通話エージェントの作成、ボタンが押されたときの通話の発信を行います。 onCreate メソッドがオーバーライドされて、getAllPermissionscreateAgent が呼び出され、通話ボタンのバインドが追加されます。 このイベントは、アクティビティの作成時に 1 回だけ行われます。 onCreate の詳細については、ガイド「アクティビティのライフサイクルについて」を参照してください。

MainActivity.java に移動し、内容を次のコードに置き換えます。

package com.contoso.acsquickstart;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;

import android.Manifest;
import android.content.pm.PackageManager;
import android.media.AudioManager;
import android.os.Bundle;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;

import java.util.ArrayList;
import java.util.concurrent.ExecutionException;

import com.azure.android.communication.common.CommunicationIdentifier;
import com.azure.android.communication.common.CommunicationUserIdentifier;
import com.azure.android.communication.calling.Call;
import com.azure.android.communication.calling.CallAgent;
import com.azure.android.communication.calling.CallClient;
import com.azure.android.communication.calling.HangUpOptions;
import com.azure.android.communication.common.CommunicationTokenCredential;
import com.azure.android.communication.calling.StartCallOptions;

public class MainActivity extends AppCompatActivity {
    private static final String[] allPermissions = new String[] { Manifest.permission.RECORD_AUDIO, Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_PHONE_STATE };
    private static final String UserToken = "<User_Access_Token>";

    TextView statusBar;

    private CallAgent agent;
    private Call call;
    private Button callButton;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        callButton = findViewById(R.id.call_button);

        getAllPermissions();
        createAgent();
        callButton.setOnClickListener(l -> startCall());

        Button hangupButton = findViewById(R.id.hangup_button);
        hangupButton.setOnClickListener(l -> endCall());

        statusBar = findViewById(R.id.status_bar);
        
        setVolumeControlStream(AudioManager.STREAM_VOICE_CALL);
    }

    /**
     * Start a call
     */
    private void startCall() {
        if (UserToken.startsWith("<")) {
            Toast.makeText(this, "Please enter token in source code", Toast.LENGTH_SHORT).show();
            return;
        }

        EditText calleeIdView = findViewById(R.id.callee_id);
        String calleeId = calleeIdView.getText().toString();
        if (calleeId.isEmpty()) {
            Toast.makeText(this, "Please enter callee", Toast.LENGTH_SHORT).show();
            return;
        }
        ArrayList<CommunicationIdentifier> participants = new ArrayList<>();
        participants.add(new MicrosoftTeamsAppIdentifier(calleeId));
        StartCallOptions options = new StartCallOptions();
        call = agent.startCall(
                getApplicationContext(),
                participants,
                options);
        call.addOnStateChangedListener(p -> setStatus(call.getState().toString()));
    }

    /**
     * Ends the call previously started
     */
    private void endCall() {
        try {
            call.hangUp(new HangUpOptions()).get();
        } catch (ExecutionException | InterruptedException e) {
            Toast.makeText(this, "Unable to hang up call", Toast.LENGTH_SHORT).show();
        }
    }

    /**
     * Create the call agent
     */
    private void createAgent() {
        try {
            CommunicationTokenCredential credential = new CommunicationTokenCredential(UserToken);
            agent = new CallClient().createCallAgent(getApplicationContext(), credential).get();
        } catch (Exception ex) {
            Toast.makeText(getApplicationContext(), "Failed to create call agent.", Toast.LENGTH_SHORT).show();
        }
    }

    /**
     * Ensure all permissions were granted, otherwise inform the user permissions are missing.
     */
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, int[] grantResults) {
        boolean allPermissionsGranted = true;
        for (int result : grantResults) {
            allPermissionsGranted &= (result == PackageManager.PERMISSION_GRANTED);
        }
        if (!allPermissionsGranted) {
            Toast.makeText(this, "All permissions are needed to make the call.", Toast.LENGTH_LONG).show();
            finish();
        }
    }

    /**
     * Shows message in the status bar
     */
    private void setStatus(String status) {
        runOnUiThread(() -> statusBar.setText(status));
    }
}

実行時にアクセス許可を要求する

Android 6.0 以上 (API レベル 23) と targetSdkVersion 23 以上では、アプリのインストール時ではなく、実行時にアクセス許可が付与されます。 これをサポートするために、getAllPermissions を実装して、必要なアクセス許可ごとに ActivityCompat.checkSelfPermissionActivityCompat.requestPermissions を呼び出すことができます。

/**
 * Request each required permission if the app doesn't already have it.
 */
private void getAllPermissions() {
    ArrayList<String> permissionsToAskFor = new ArrayList<>();
    for (String permission : allPermissions) {
        if (ActivityCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
            permissionsToAskFor.add(permission);
        }
    }
    if (!permissionsToAskFor.isEmpty()) {
        ActivityCompat.requestPermissions(this, permissionsToAskFor.toArray(new String[0]), 1);
    }
}

Note

アプリを設計するときは、これらのアクセス許可を要求するタイミングを検討してください。 アクセス許可は、事前に要求するのではなく、必要が生じたときに要求する必要があります。 詳細については、Android のアクセス許可のガイドを参照してください。

オブジェクト モデル

Azure Communication Services Calling SDK の主な機能のいくつかは、次のクラスとインターフェイスによって処理されます。

名前 説明
CallClient CallClient は、Calling SDK へのメイン エントリ ポイントです。
CallAgent CallAgent は、通話を開始および管理するために使用されます。
CommunicationTokenCredential CommunicationTokenCredential は、CallAgent をインスタンス化するためのトークン資格情報として使用されます。
CommunicationIdentifier CommunicationIdentifier は、通話の一部となる可能性があるさまざまな種類の参加者として使用されます。

ユーザー アクセス トークンからエージェントを作成する

認証された通話エージェントは、ユーザー トークンを使用してインスタンス化できます。 通常、このトークンは、アプリケーション固有の認証を使用してサービスから生成されます。 ユーザー アクセス トークンの詳細については、ユーザー アクセス トークンのガイドを参照してください。

クイック スタートでは、<User_Access_Token> を Azure Communication Service リソース用に生成されたユーザー アクセス トークンに置き換えます。


/**
 * Create the call agent for placing calls
 */
private void createAgent() {
    String userToken = "<User_Access_Token>";

    try {
            CommunicationTokenCredential credential = new CommunicationTokenCredential(userToken);
            callAgent = new CallClient().createCallAgent(getApplicationContext(), credential).get();
    } catch (Exception ex) {
        Toast.makeText(getApplicationContext(), "Failed to create call agent.", Toast.LENGTH_SHORT).show();
    }
}

コードの実行

ツール バーの [アプリの実行] ボタンを使って、アプリを起動できるようになりました。

手動で通話を設定する手順:

  1. Android Studio を使ってアプリを起動します。
  2. 自動応答のオブジェクト ID (プレフィックス付き) を入力して、[通話の開始] ボタンを選びます。 アプリケーションにより、指定したオブジェクト ID を使って自動応答への発信通話が開始されます。
  3. 通話は自動応答に接続されます。
  4. Communication Services ユーザーは、その構成に基づいて自動応答を介してルーティングされます。

重要

Azure Communication Services のこの機能は、現在プレビュー段階にあります。

プレビューの API と SDK は、サービス レベル アグリーメントなしに提供されます。 運用環境のワークロードには使用しないことをお勧めします。 一部の機能はサポート対象ではなく、機能が制限されることがあります。

詳細については、「Microsoft Azure プレビューの追加利用規約」を確認してください。

このクイックスタートでは、Azure Communication Services ユーザーから Teams 自動応答への通話を開始する方法について説明します。 これを実現するには、次の手順を実行します。

  1. Teams テナントでの Azure Communication Services リソースのフェデレーションを有効にします。
  2. Teams 管理センターを使用して、Teams 自動応答を選択または作成します。
  3. Teams 管理センターを介して自動応答のメール アドレスを取得します。
  4. Graph API を使用して自動応答のオブジェクト ID を取得します。
  5. Azure Communication Services Calling SDK を使用して通話を開始します。

最後までスキップしたい場合は、GitHub のサンプルとしてこのクイックスタートをダウンロードできます。

Teams テナントで相互運用性を有効にする

Teams 管理者ロールを持つ Microsoft Entra ユーザーは、MicrosoftTeams モジュールを使って PowerShell コマンドレットを実行し、テナント内の Communication Services リソースを有効にすることができます。

1.Microsoft Teams モジュールを準備する

まず、PowerShell を開き、次のコマンドを使って Teams モジュールの存在を検証します。

Get-module *teams* 

MicrosoftTeams モジュールが表示されない場合は、最初にインストールします。 モジュールをインストールするには、管理者として PowerShell を実行する必要があります。 次に、次のコマンドを実行します。

	Install-Module -Name MicrosoftTeams

インストールされるモジュールが表示されるので、Y または A で応答して確認します。 モジュールがインストールされていても古くなっている場合は、次のコマンドを実行してモジュールを更新できます。

	Update-Module MicrosoftTeams

2.Microsoft Teams モジュールに接続する

モジュールがインストールされ準備ができたら、次のコマンドを使って MicrosftTeams モジュールに接続できます。 ログインするための対話型ウィンドウが表示されます。 使用するユーザー アカウントには、Teams 管理者のアクセス許可が必要です。 それ以外の場合は、次の手順で access denied 応答が返される可能性があります。

Connect-MicrosoftTeams

3.テナント構成を有効にする

Communication Services リソースとの相互運用性は、テナント構成と割り当てられたポリシーによって制御されます。 Teams テナントには 1 つのテナント構成があり、Teams ユーザーにはグローバル ポリシーまたはカスタム ポリシーが割り当てられます。 詳細については、「Teams でポリシーを割り当てる」を参照してください。

ログインに成功したら、コマンドレット Set-CsTeamsAcsFederationConfiguration を実行して、テナントで Communication Services リソースを有効にすることができます。 テキスト IMMUTABLE_RESOURCE_ID を、コミュニケーション リソース内の不変リソース ID に置き換えます。 この情報を取得する方法の詳細については、こちらを参照してください。

$allowlist = @('IMMUTABLE_RESOURCE_ID')
Set-CsTeamsAcsFederationConfiguration -EnableAcsUsers $True -AllowedAcsResources $allowlist

4.テナント ポリシーを有効にする

各 Teams ユーザーには、Communication Services ユーザーがこの Teams ユーザーを呼び出すことができるかどうかを決定する External Access Policy が割り当てられます。 コマンドレット Set-CsExternalAccessPolicy を使って、Teams ユーザーに割り当てられたポリシーで EnableAcsFederationAccess$true に設定されていることを確認します

Set-CsExternalAccessPolicy -Identity Global -EnableAcsFederationAccess $true

Teams 自動応答を作成または選択する

Teams 自動応答は、着信通話用の自動通話処理システムを提供するシステムです。 仮想の受付担当として機能し、人間のオペレーターを必要とせずに、適切な担当者または部門に発信者を自動的にルーティングできます。 Teams 管理センターを使用して、既存の自動応答を選択するか、新しく作成できます。

Teams 管理センターを使用して自動応答を作成する方法の詳細については、こちらを参照してください。

自動応答のオブジェクト ID を見つける

自動応答を作成したら、後で通話に使用するために、関連づけられたオブジェクト ID を見つける必要があります。 オブジェクト ID は、自動応答にアタッチされたリソース アカウントに関連付けられています。Teams 管理の [リソース アカウント] タブを開き、アカウントのメールを検索します。 Teams 管理ポータルの [リソース アカウント] のスクリーンショット。 リソース アカウントに必要なすべての情報は、検索でこのメールを使用して、Microsoft Graph Explorer で確認できます。

https://graph.microsoft.com/v1.0/users/lab-test2-cq-@contoso.com

結果で、"ID" フィールドが見つかります。

    "userPrincipalName": "lab-test2-cq@contoso.com",
    "id": "31a011c2-2672-4dd0-b6f9-9334ef4999db"

通話アプリで使うには、この ID にプレフィックスを追加する必要があります。 現在は、次のものがサポートされています。

  • パブリック クラウド自動応答: 28:orgid:<id>
  • 政府クラウド自動応答: 28:gcch:<id>

前提条件

設定

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

Xcode で、新しい iOS プロジェクトを作成し、 [App](アプリ) テンプレートを選択します。 このチュートリアルでは SwiftUI フレームワークを使用します。そのため、 [言語][Swift] に設定し、 [ユーザー インターフェイス][SwiftUI] に設定する必要があります。 このクイック スタートでは、テストは作成しません。 [Include Tests](テストを含める) チェック ボックスはオフにしてかまいません。

Xcode 内で新しいプロジェクトを作成するウィンドウのスクリーンショット。

CocoaPods でパッケージと依存関係をインストールする

  1. アプリケーションのポッドファイルを作成するために、ターミナルを開いてプロジェクト フォルダーに移動し、次を実行します。

    pod init

  2. ポッドファイルに次のコードを追加して保存します ("target" がプロジェクトの名前と一致していることを確認してください)。

    platform :ios, '13.0'
    use_frameworks!
    
    target 'AzureCommunicationCallingSample' do
      pod 'AzureCommunicationCalling', '~> 2.14.0-beta.1'
    end
    
  3. pod install を実行します。

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

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

デバイスのマイクにアクセスするには、アプリの情報プロパティ リストを NSMicrophoneUsageDescription によって更新する必要があります。 関連する値を、ユーザーからのアクセスを要求するためにシステムによって使用されるダイアログに含まれていた string に設定します。

プロジェクト ツリーの Info.plist のエントリを右クリックし、 [Open As](形式を指定して開く)>[Source Code](ソース コード) の順に選択します。 最上位の <dict> セクションに以下の行を追加してから、ファイルを保存します。

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

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

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

import AzureCommunicationCalling
import AVFoundation

ContentView 構造体の実装を、ユーザーが通話を開始して終了できるようにする、いくつかの単純な UI コントロールに置き換えます。 このクイック スタートでは、これらのコントロールにビジネス ロジックをアタッチします。

struct ContentView: View {
    @State var callee: String = ""
    @State var callClient: CallClient?
    @State var callAgent: CallAgent?
    @State var call: Call?

    var body: some View {
        NavigationView {
            Form {
                Section {
                    TextField("Who would you like to call?", text: $callee)
                    Button(action: startCall) {
                        Text("Start Call")
                    }.disabled(callAgent == nil)
                    Button(action: endCall) {
                        Text("End Call")
                    }.disabled(call == nil)
                }
            }
            .navigationBarTitle("Calling Quickstart")
        }.onAppear {
            // Initialize call agent
        }
    }

    func startCall() {
        // Ask permissions
        AVAudioSession.sharedInstance().requestRecordPermission { (granted) in
            if granted {
                // Add start call logic
            }
        }
    }

    func endCall() {
        // Add end call logic
    }
}

オブジェクト モデル

Azure Communication Services Calling SDK の主な機能のいくつかは、次のクラスとインターフェイスによって処理されます。

名前 説明
CallClient CallClient は、Calling SDK へのメイン エントリ ポイントです。
CallAgent CallAgent は、通話を開始および管理するために使用されます。
CommunicationTokenCredential CommunicationTokenCredential は、CallAgent をインスタンス化するためのトークン資格情報として使用されます。
CommunicationUserIdentifier CommunicationUserIdentifier はユーザーの ID を表すために使用されます。これは、CommunicationUserIdentifierPhoneNumberIdentifierCallingApplication. のいずれかのオプションになります

クライアントを認証する

ユーザー アクセス トークンを使用して CallAgent インスタンスを初期化します。これにより、電話をかけたり受けたりすることができるようになります。

次のコードでは、<USER ACCESS TOKEN> をリソース用の有効なユーザー アクセス トークンに置き換える必要があります。 まだトークンを入手していない場合は、ユーザー アクセス トークンに関するドキュメントを参照してください。

ContentView.swiftonAppear コールバックに次のコードを追加します。

var userCredential: CommunicationTokenCredential?
do {
    userCredential = try CommunicationTokenCredential(token: "<USER ACCESS TOKEN>")
} catch {
    print("ERROR: It was not possible to create user credential.")
    return
}

self.callClient = CallClient()

// Creates the call agent
self.callClient?.createCallAgent(userCredential: userCredential!) { (agent, error) in
    if error != nil {
        print("ERROR: It was not possible to create a call agent.")
        return
    }
    else {
        self.callAgent = agent
        print("Call agent successfully created.")
    }
}

通話を開始する

startCall メソッドは、[通話を開始] ボタンがタップされたときに実行されるアクションとして設定されます。 ASACallAgent を使用して通話を開始するように実装を更新します。

func startCall()
{
    // Ask permissions
    AVAudioSession.sharedInstance().requestRecordPermission { (granted) in
        if granted {
            // start call logic
            let callees:[CommunicationIdentifier] = [MicrosoftTeamsAppIdentifier(self.callee)]
            self.callAgent?.startCall(participants: callees, options: StartCallOptions()) { (call, error) in
                if (error == nil) {
                    self.call = call
                } else {
                    print("Failed to get call object")
                }
            }
        }
    }
}

StartCallOptions のプロパティを使用して、通話の初期オプションを設定することもできます (つまり、マイクをミュートした状態で通話を開始できます)。

通話を終了する

endCall メソッドを実装して、 [通話の終了] ボタンがタップされたときに現在の通話を終了します。

func endCall()
{    
    self.call!.hangUp(options: HangUpOptions()) { (error) in
        if (error != nil) {
            print("ERROR: It was not possible to hangup the call.")
        }
    }
}

コードの実行

iOS シミュレーターでアプリをビルドして実行するには、[製品]>[実行] の順に選択するか、(⌘-R) キーボード ショートカットを使用します。

Note

初めて通話を行うときに、マイクへのアクセスを求めるメッセージが表示されます。 運用環境のアプリケーションでは、AVAudioSession API を使用してアクセス許可の状態を確認し、アクセス許可が付与されていない場合はアプリケーションの動作を適切に更新する必要があります。

手動で通話を設定する手順:

  1. Xcode を使ってアプリを起動します
  2. 自動応答のオブジェクト ID (プレフィックス付き) を入力して、[通話の開始] ボタンを選びます。 アプリケーションにより、指定したオブジェクト ID を使って自動応答への発信通話が開始されます。
  3. 通話は自動応答に接続されます。
  4. Communication Services ユーザーは、その構成に基づいて自動応答を介してルーティングされます。

重要

Azure Communication Services のこの機能は、現在プレビュー段階にあります。

プレビューの API と SDK は、サービス レベル アグリーメントなしに提供されます。 運用環境のワークロードには使用しないことをお勧めします。 一部の機能はサポート対象ではなく、機能が制限されることがあります。

詳細については、「Microsoft Azure プレビューの追加利用規約」を確認してください。

このクイックスタートでは、Azure Communication Services ユーザーから Teams 自動応答への通話を開始する方法について説明します。 これを実現するには、次の手順を実行します。

  1. Teams テナントでの Azure Communication Services リソースのフェデレーションを有効にします。
  2. Teams 管理センターを使用して、Teams 自動応答を選択または作成します。
  3. Teams 管理センターを介して自動応答のメール アドレスを取得します。
  4. Graph API を使用して自動応答のオブジェクト ID を取得します。
  5. Azure Communication Services Calling SDK を使用して通話を開始します。

最後までスキップしたい場合は、GitHub のサンプルとしてこのクイックスタートをダウンロードできます。

Teams テナントで相互運用性を有効にする

Teams 管理者ロールを持つ Microsoft Entra ユーザーは、MicrosoftTeams モジュールを使って PowerShell コマンドレットを実行し、テナント内の Communication Services リソースを有効にすることができます。

1.Microsoft Teams モジュールを準備する

まず、PowerShell を開き、次のコマンドを使って Teams モジュールの存在を検証します。

Get-module *teams* 

MicrosoftTeams モジュールが表示されない場合は、最初にインストールします。 モジュールをインストールするには、管理者として PowerShell を実行する必要があります。 次に、次のコマンドを実行します。

	Install-Module -Name MicrosoftTeams

インストールされるモジュールが表示されるので、Y または A で応答して確認します。 モジュールがインストールされていても古くなっている場合は、次のコマンドを実行してモジュールを更新できます。

	Update-Module MicrosoftTeams

2.Microsoft Teams モジュールに接続する

モジュールがインストールされ準備ができたら、次のコマンドを使って MicrosftTeams モジュールに接続できます。 ログインするための対話型ウィンドウが表示されます。 使用するユーザー アカウントには、Teams 管理者のアクセス許可が必要です。 それ以外の場合は、次の手順で access denied 応答が返される可能性があります。

Connect-MicrosoftTeams

3.テナント構成を有効にする

Communication Services リソースとの相互運用性は、テナント構成と割り当てられたポリシーによって制御されます。 Teams テナントには 1 つのテナント構成があり、Teams ユーザーにはグローバル ポリシーまたはカスタム ポリシーが割り当てられます。 詳細については、「Teams でポリシーを割り当てる」を参照してください。

ログインに成功したら、コマンドレット Set-CsTeamsAcsFederationConfiguration を実行して、テナントで Communication Services リソースを有効にすることができます。 テキスト IMMUTABLE_RESOURCE_ID を、コミュニケーション リソース内の不変リソース ID に置き換えます。 この情報を取得する方法の詳細については、こちらを参照してください。

$allowlist = @('IMMUTABLE_RESOURCE_ID')
Set-CsTeamsAcsFederationConfiguration -EnableAcsUsers $True -AllowedAcsResources $allowlist

4.テナント ポリシーを有効にする

各 Teams ユーザーには、Communication Services ユーザーがこの Teams ユーザーを呼び出すことができるかどうかを決定する External Access Policy が割り当てられます。 コマンドレット Set-CsExternalAccessPolicy を使って、Teams ユーザーに割り当てられたポリシーで EnableAcsFederationAccess$true に設定されていることを確認します

Set-CsExternalAccessPolicy -Identity Global -EnableAcsFederationAccess $true

Teams 自動応答を作成または選択する

Teams 自動応答は、着信通話用の自動通話処理システムを提供するシステムです。 仮想の受付担当として機能し、人間のオペレーターを必要とせずに、適切な担当者または部門に発信者を自動的にルーティングできます。 Teams 管理センターを使用して、既存の自動応答を選択するか、新しく作成できます。

Teams 管理センターを使用して自動応答を作成する方法の詳細については、こちらを参照してください。

自動応答のオブジェクト ID を見つける

自動応答を作成したら、後で通話に使用するために、関連づけられたオブジェクト ID を見つける必要があります。 オブジェクト ID は、自動応答にアタッチされたリソース アカウントに関連付けられています。Teams 管理の [リソース アカウント] タブを開き、アカウントのメールを検索します。 Teams 管理ポータルの [リソース アカウント] のスクリーンショット。 リソース アカウントに必要なすべての情報は、検索でこのメールを使用して、Microsoft Graph Explorer で確認できます。

https://graph.microsoft.com/v1.0/users/lab-test2-cq-@contoso.com

結果で、"ID" フィールドが見つかります。

    "userPrincipalName": "lab-test2-cq@contoso.com",
    "id": "31a011c2-2672-4dd0-b6f9-9334ef4999db"

通話アプリで使うには、この ID にプレフィックスを追加する必要があります。 現在は、次のものがサポートされています。

  • パブリック クラウド自動応答: 28:orgid:<id>
  • 政府クラウド自動応答: 28:gcch:<id>

前提条件

このチュートリアルを完了するには、次の前提条件を用意しておく必要があります。

設定

プロジェクトの作成

Visual Studio の [空白のアプリ (ユニバーサル Windows)] テンプレートを使用して新しいプロジェクトを作成し、シングルページ ユニバーサル Windows プラットフォーム (UWP) アプリをセットアップします。

Visual Studio 内の新しい UWP プロジェクト ウィンドウのスクリーンショット。

パッケージをインストールする

プロジェクトを右クリックして選択し、Manage Nuget Packages に移動して、Azure.Communication.Calling.WindowsClient 1.4.0 以上をインストールします。 パブリック プレビューのバージョンを表示する場合は、Include Prerelease がオンになっていることを確認します。

アクセスの要求

Package.appxmanifest に移動し、Capabilities を選択します。 インターネットへの着信と発信のアクセスを取得するためめに Internet (Client)Internet (Client & Server) をオンにします。 マイクのオーディオ フィードにアクセスするために Microphone を、カメラのビデオ フィードにアクセスするために Webcam をオンにします。

Visual Studio でインターネットとマイクへのアクセスを要求する画面のスクリーンショット。

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

ロジックをアタッチするには、基本的なレイアウトを構成する必要があります。 発信通話を行うには、呼び出し先のユーザー ID を指定するための TextBox が必要です。 また、[Start/Join call](通話を開始) ボタンと [Hang up](通話終了) ボタンも必要となります。 オーディオの状態とビデオ効果を切り替える機能を示すために、このサンプルには MuteBackgroundBlur チェック ボックスも含まれています。

プロジェクトの MainPage.xaml を開き、PageGrid ノードを追加します。

<Page
    x:Class="CallingQuickstart.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:CallingQuickstart"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" Width="800" Height="600">

        <!-- Don't forget to replace ‘CallingQuickstart’ with your project’s name -->


    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="16*"/>
            <RowDefinition Height="30*"/>
            <RowDefinition Height="200*"/>
            <RowDefinition Height="60*"/>
            <RowDefinition Height="16*"/>
        </Grid.RowDefinitions>
        <TextBox Grid.Row="1" x:Name="CalleeTextBox" PlaceholderText="Who would you like to call?" TextWrapping="Wrap" VerticalAlignment="Center" Height="30" Margin="10,10,10,10" />

        <Grid x:Name="AppTitleBar" Background="LightSeaGreen">
            <TextBlock x:Name="QuickstartTitle" Text="Calling Quickstart sample title bar" Style="{StaticResource CaptionTextBlockStyle}" Padding="7,7,0,0"/>
        </Grid>

        <Grid Grid.Row="2">
            <Grid.RowDefinitions>
                <RowDefinition/>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="*"/>
            </Grid.ColumnDefinitions>
            <MediaPlayerElement x:Name="LocalVideo" HorizontalAlignment="Center" Stretch="UniformToFill" Grid.Column="0" VerticalAlignment="Center" AutoPlay="True" />
            <MediaPlayerElement x:Name="RemoteVideo" HorizontalAlignment="Center" Stretch="UniformToFill" Grid.Column="1" VerticalAlignment="Center" AutoPlay="True" />
        </Grid>
        <StackPanel Grid.Row="3" Orientation="Vertical" Grid.RowSpan="2">
            <StackPanel Orientation="Horizontal">
                <Button x:Name="CallButton" Content="Start/Join call" Click="CallButton_Click" VerticalAlignment="Center" Margin="10,0,0,0" Height="40" Width="123"/>
                <Button x:Name="HangupButton" Content="Hang up" Click="HangupButton_Click" VerticalAlignment="Center" Margin="10,0,0,0" Height="40" Width="123"/>
                <CheckBox x:Name="MuteLocal" Content="Mute" Margin="10,0,0,0" Click="MuteLocal_Click" Width="74"/>
            </StackPanel>
        </StackPanel>
        <TextBox Grid.Row="5" x:Name="Stats" Text="" TextWrapping="Wrap" VerticalAlignment="Center" Height="30" Margin="0,2,0,0" BorderThickness="2" IsReadOnly="True" Foreground="LightSlateGray" />
    </Grid>
</Page>

MainPage.xaml.cs を開き、その内容を次の実装に置き換えます。

using Azure.Communication.Calling.WindowsClient;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Threading.Tasks;
using Windows.ApplicationModel;
using Windows.ApplicationModel.Core;
using Windows.Media.Core;
using Windows.Networking.PushNotifications;
using Windows.UI;
using Windows.UI.ViewManagement;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;

namespace CallingQuickstart
{
    public sealed partial class MainPage : Page
    {
        private const string authToken = "<AUTHENTICATION_TOKEN>";

        private CallClient callClient;
        private CallTokenRefreshOptions callTokenRefreshOptions = new CallTokenRefreshOptions(false);
        private CallAgent callAgent;
        private CommunicationCall call;

        private LocalOutgoingAudioStream micStream;

        #region Page initialization
        public MainPage()
        {
            this.InitializeComponent();
            // Additional UI customization code goes here
        }

        protected override async void OnNavigatedTo(NavigationEventArgs e)
        {
            await InitCallAgentAndDeviceManagerAsync();

            base.OnNavigatedTo(e);
        }
        #endregion

        #region UI event handlers
        private async void CallButton_Click(object sender, RoutedEventArgs e)
        {
            // Start a call
        }

        private async void HangupButton_Click(object sender, RoutedEventArgs e)
        {
            // Hang up a call
        }

        private async void MuteLocal_Click(object sender, RoutedEventArgs e)
        {
            // Toggle mute/unmute audio state of a call
        }
        #endregion

        #region API event handlers
        private async void OnIncomingCallAsync(object sender, IncomingCallReceivedEventArgs args)
        {
            // Handle incoming call event
        }

        private async void OnStateChangedAsync(object sender, PropertyChangedEventArgs args)
        {
            // Handle connected and disconnected state change of a call
        }
        #endregion

        #region Helper methods

        private async Task InitCallAgentAndDeviceManagerAsync()
        {
            //Initialize the call agent and search for devices
        }


        private async Task<CommunicationCall> StartCallAsync(string acsCallee)
        {
            // Start a call to an Azure Communication Services user using the CallAgent and the callee id
        }

        #endregion
    }
}

オブジェクト モデル

次の表に、Azure Communication Services Calling SDK の主要な機能の一部を処理するクラスとインターフェイスを示します。

名前 説明
CallClient CallClient は、Calling SDK へのメイン エントリ ポイントです。
CallAgent CallAgent は、通話を開始および管理するために使用されます。
CommunicationCall CommunicationCall は、進行中の通話を管理するために使用されます。
CallTokenCredential CallTokenCredential は、CallAgent をインスタンス化するためのトークン資格情報として使用されます。
CallIdentifier CallIdentifier はユーザーの ID を表すために使用されます。これは、UserCallIdentifierPhoneNumberCallIdentifier などのオプションになります。

クライアントを認証する

通話を発信および受信できるようにするユーザー アクセス トークンで CallAgent インスタンスを初期化し、必要に応じてクライアント デバイス構成のクエリを実行するための DeviceManager インスタンスを取得します。

コードで、<AUTHENTICATION_TOKEN> をユーザー アクセス トークンに置き換えます。 まだトークンを入手していない場合は、ユーザー アクセス トークンに関するドキュメントを参照してください。

SDK をブートストラップする InitCallAgentAndDeviceManagerAsync 関数を追加します。 このヘルパーは、アプリケーションの要件を満たすようにカスタマイズできます。

        private async Task InitCallAgentAndDeviceManagerAsync()
        {
            this.callClient = new CallClient(new CallClientOptions() {
                Diagnostics = new CallDiagnosticsOptions() { 
                    
                    // make sure to put your project AppName
                    AppName = "CallingQuickstart",

                    AppVersion="1.0",

                    Tags = new[] { "Calling", "ACS", "Windows" }
                    }

                });

            // Set up local audio stream using the first mic enumerated
            var deviceManager = await this.callClient.GetDeviceManagerAsync();
            var mic = deviceManager?.Microphones?.FirstOrDefault();

            micStream = new LocalOutgoingAudioStream();

            var tokenCredential = new CallTokenCredential(authToken, callTokenRefreshOptions);

            var callAgentOptions = new CallAgentOptions()
            {
                DisplayName = $"{Environment.MachineName}/{Environment.UserName}",
            };

            this.callAgent = await this.callClient.CreateCallAgentAsync(tokenCredential, callAgentOptions);

            this.callAgent.IncomingCallReceived += OnIncomingCallAsync;
        }

通話を開始する

StartCallOptions オブジェクトが取得されたら、CallAgent を使用して Azure Communication Services 通話を開始できます。

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

通話を終了する

[Hang up](通話終了) ボタンがクリックされたら、現在の通話を終了します。 通話を終了し、プレビューとビデオのストリームを停止する実装を HangupButton_Click に追加します。

        private async void HangupButton_Click(object sender, RoutedEventArgs e)
        {
            var call = this.callAgent?.Calls?.FirstOrDefault();
            if (call != null)
            {
                await call.HangUpAsync(new HangUpOptions() { ForEveryone = false });
            }
        }

オーディオのミュート/ミュート解除を切り替える

Mute ボタンがクリックされたときに発信オーディオをミュートします。 通話をミュートする実装を MuteLocal_Click に追加します。

        private async void MuteLocal_Click(object sender, RoutedEventArgs e)
        {
            var muteCheckbox = sender as CheckBox;

            if (muteCheckbox != null)
            {
                var call = this.callAgent?.Calls?.FirstOrDefault();

                if (call != null)
                {
                    if ((bool)muteCheckbox.IsChecked)
                    {
                        await call.MuteOutgoingAudioAsync();
                    }
                    else
                    {
                        await call.UnmuteOutgoingAudioAsync();
                    }
                }

                // Update the UI to reflect the state
            }
        }

電話の着信を受け入れる

IncomingCallReceived イベント シンクは、SDK ブートストラップ ヘルパー InitCallAgentAndDeviceManagerAsync で設定されます。

    this.callAgent.IncomingCallReceived += OnIncomingCallAsync;

ビデオやオーディオのストリームの種類などの、着信通話を受け入れる方法をアプリケーションに構成できます。

        private async void OnIncomingCallAsync(object sender, IncomingCallReceivedEventArgs args)
        {
            var incomingCall = args.IncomingCall;

            var acceptCallOptions = new AcceptCallOptions() { };

            call = await incomingCall.AcceptAsync(acceptCallOptions);
            call.StateChanged += OnStateChangedAsync;
        }

通話状態変更イベントを監視して応答する

CommunicationCall オブジェクトの StateChanged イベントは、進行中の通話トランザクションがある状態から別の状態に変化したときに発生します。 アプリケーションには、UI に状態の変更を反映したり、ビジネス ロジックを挿入したりする機会が用意されています。

        private async void OnStateChangedAsync(object sender, PropertyChangedEventArgs args)
        {
            var call = sender as CommunicationCall;

            if (call != null)
            {
                var state = call.State;

                // Update the UI

                switch (state)
                {
                    case CallState.Connected:
                        {
                            await call.StartAudioAsync(micStream);

                            break;
                        }
                    case CallState.Disconnected:
                        {
                            call.StateChanged -= OnStateChangedAsync;

                            call.Dispose();

                            break;
                        }
                    default: break;
                }
            }
        }

通話ボタンが機能するようにする

Callee ID が null ではない、または空ではない場合、通話を開始できます。

通話状態は、OnStateChangedAsync アクションを使用して変更する必要があります。


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

        if (!string.IsNullOrEmpty(callString))
        {
            call = await StartCallAsync(callString);

            call.StateChanged += OnStateChangedAsync;
        }
    
        
    }

コードの実行

コードは、Visual Studio でビルドして実行できます。 ソリューション プラットフォームについては、ARM64x64x86 をサポートしています。

手動で通話を設定する手順:

  1. Visual Studio を使ってアプリを起動します。
  2. 自動応答のオブジェクト ID (プレフィックス付き) を入力して、[通話の開始] ボタンを選びます。 アプリケーションにより、指定したオブジェクト ID を使って自動応答への発信通話が開始されます。
  3. 通話は自動応答に接続されます。
  4. Communication Services ユーザーは、その構成に基づいて自動応答を介してルーティングされます。

リソースをクリーンアップする

Communication Services サブスクリプションをクリーンアップして解除する場合は、リソースまたはリソース グループを削除できます。 リソース グループを削除すると、それに関連付けられている他のリソースも削除されます。 詳細については、リソースのクリーンアップに関する記事を参照してください。

次の手順

詳細については、次の記事を参照してください。