Bagikan melalui


Mulai Cepat: Bergabung dengan aplikasi panggilan Anda ke Penjawab Otomatis Teams

Dalam mulai cepat ini, Anda akan mempelajari cara memulai panggilan dari pengguna Azure Communication Services ke Teams Auto Attendant. Anda akan mencapainya dengan langkah-langkah berikut:

  1. Aktifkan federasi sumber daya Azure Communication Services dengan Penyewa Teams.
  2. Pilih atau buat Penjawab Otomatis Teams melalui Pusat Admin Teams.
  3. Dapatkan alamat email Penjawab Otomatis melalui Pusat Admin Teams.
  4. Dapatkan ID Objek penjawab otomatis melalui Graph API.
  5. Mulai panggilan dengan Azure Communication Services Calling SDK.

Jika Anda ingin melompati ke bagian akhir, Anda dapat mengunduh mulai cepat ini sebagai sampel di GitHub.

Mengaktifkan interoperabilitas di penyewa Teams Anda

Pengguna Microsoft Entra dengan peran administrator Teams dapat menjalankan cmdlet PowerShell dengan modul MicrosoftTeams untuk mengaktifkan sumber daya Communication Services di penyewa.

1. Siapkan modul Microsoft Teams

Pertama, buka PowerShell dan validasi keberadaan modul Teams dengan perintah berikut:

Get-module *teams* 

Jika Anda tidak melihat MicrosoftTeams modul, instal terlebih dahulu. Untuk menginstal modul, Anda perlu menjalankan PowerShell sebagai administrator. Kemudian, jalankan perintah berikut:

	Install-Module -Name MicrosoftTeams

Anda akan diberi tahu tentang modul yang akan diinstal, yang dapat Dikonfirmasi dengan Y jawaban atau A . Jika modul diinstal tetapi sudah kedaluarsa, Anda dapat menjalankan perintah berikut untuk memperbarui modul:

	Update-Module MicrosoftTeams

2. Sambungkan ke modul Microsoft Teams

Ketika modul diinstal dan siap, Anda dapat terhubung ke modul MicrosftTeams dengan perintah berikut. Anda akan diminta dengan jendela interaktif untuk masuk. Akun pengguna yang akan Anda gunakan harus memiliki izin administrator Teams. Jika tidak, Anda mungkin mendapatkan access denied respons di langkah berikutnya.

Connect-MicrosoftTeams

3. Aktifkan konfigurasi penyewa

Interoperabilitas dengan sumber daya Communication Services dikontrol melalui konfigurasi penyewa dan kebijakan yang ditetapkan. Penyewa Teams memiliki konfigurasi penyewa tunggal, dan pengguna Teams telah menetapkan kebijakan global atau kebijakan kustom. Untuk informasi selengkapnya, lihat Menetapkan Kebijakan di Teams.

Setelah berhasil masuk, Anda dapat menjalankan cmdlet Set-CsTeamsAcsFederationConfiguration untuk mengaktifkan sumber daya Communication Services di penyewa Anda. Ganti teks IMMUTABLE_RESOURCE_ID dengan ID sumber daya yang tidak dapat diubah di sumber daya komunikasi Anda. Anda dapat menemukan detail selengkapnya tentang cara mendapatkan informasi ini di sini.

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

4. Aktifkan kebijakan penyewa

Setiap pengguna Teams telah menetapkan External Access Policy yang menentukan apakah pengguna Communication Services dapat memanggil pengguna Teams ini. Gunakan cmdlet Set-CsExternalAccessPolicy untuk memastikan bahwa kebijakan yang ditetapkan ke pengguna Teams telah diatur EnableAcsFederationAccess ke $true

Set-CsExternalAccessPolicy -Identity Global -EnableAcsFederationAccess $true

Membuat atau memilih Penjawab Otomatis Teams

Teams Auto Attendant adalah sistem yang menyediakan sistem penanganan panggilan otomatis untuk panggilan masuk. Ini berfungsi sebagai resepsionis virtual, memungkinkan penelepon untuk secara otomatis dirutekan ke orang atau departemen yang sesuai tanpa perlu operator manusia. Anda dapat memilih yang sudah ada atau membuat Penjawab Otomatis baru melalui Pusat Admin Teams.

Pelajari selengkapnya tentang cara membuat Penjawab Otomatis menggunakan Pusat Admin Teams di sini.

Temukan ID Objek untuk Penjawab Otomatis

Setelah Penjawab Otomatis dibuat, kita perlu menemukan ID Objek yang berkorelasi untuk menggunakannya nanti untuk panggilan. ID Objek tersambung ke Akun Sumber Daya yang dilampirkan ke Penjawab Otomatis - buka tab Akun Sumber Daya di Admin Teams dan temukan email akun. Cuplikan layar Akun Sumber Daya di Portal Admin Teams. Semua informasi yang diperlukan untuk Akun Sumber Daya dapat ditemukan di Microsoft Graph Explorer menggunakan email ini dalam pencarian.

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

Dalam hasil kita akan dapat menemukan bidang "ID"

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

Prasyarat

Menyiapkan

Membuat aplikasi Node.js baru

Buka terminal atau jendela perintah Anda, buat direktori baru untuk aplikasi Anda, dan navigasikan ke direktori.

mkdir calling-quickstart && cd calling-quickstart

Pasang paket

Gunakan perintah npm install untuk memasang Azure Communication Services Calling SDK untuk JavaScript.

Penting

Mulai cepat ini menggunakan Azure Communication Services Calling SDK versi next.

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

Menyiapkan kerangka kerja aplikasi

Mulai cepat ini menggunakan webpack untuk memaketkan aset aplikasi. Jalankan perintah berikut untuk menginstal paket npm webpack, webpack-cli, dan webpack-dev-server, serta cantumkan sebagai dependensi pengembangan di package.json Anda:

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

Buat file index.html di direktori akar proyek Anda. Kami akan menggunakan file ini untuk mengonfigurasi tata letak dasar yang akan memungkinkan pengguna melakukan panggilan video pribadi (1:1).

Berikut kodenya:

<!-- 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>

Model Objek Web SDK Panggilan Azure Communication Services

Kelas dan antarmuka berikut menangani beberapa fitur utama SDK Panggilan Azure Communication Services:

Nama Deskripsi
CallClient Titik masuk utama ke Calling SDK.
CallAgent Digunakan untuk memulai dan mengelola panggilan.
DeviceManager Digunakan untuk mengelola perangkat media.
Call Digunakan untuk mewakili Panggilan.
LocalVideoStream Digunakan untuk membuat aliran video lokal untuk perangkat kamera pada sistem lokal.
RemoteParticipant Digunakan untuk mewakili peserta jarak jauh dalam Panggilan.
RemoteVideoStream Digunakan untuk mewakili aliran video jarak jauh dari Peserta Jarak Jauh.

Buat file di direktori akar proyek Anda yang bernama client.js guna memuat logika aplikasi untuk mulai cepat ini. Tambahkan kode berikut ke 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();
});

Menambahkan kode server lokal webpack

Buat file di direktori akar proyek Anda yang disebut webpack.config.js untuk memuat logika server lokal untuk mulai cepat ini. Tambahkan kode berikut ke 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'
            ]
        }),
    ]
};

Menjalankan kode

Gunakan webpack-dev-server untuk membuat dan menjalankan aplikasi Anda. Jalankan perintah berikut untuk membundel host aplikasi di webserver lokal:

npx webpack serve --config webpack.config.js

Langkah-langkah manual untuk menyiapkan panggilan:

  1. Buka browser Anda dan navigasi ke http://localhost:8080/.
  2. Masukkan token akses pengguna yang valid. Lihat dokumentasi token akses pengguna jika Anda belum memiliki token akses yang tersedia untuk digunakan.
  3. Klik tombol "Inisialisasi Agen Panggilan".
  4. Masukkan ID Objek Penjawab Otomatis, dan pilih tombol "Mulai Panggilan". Aplikasi akan memulai panggilan keluar ke Penjawab Otomatis dengan ID objek tertentu.
  5. Panggilan tersambung ke Penjawab Otomatis.
  6. Pengguna Communication Services dirutekan melalui Penjawab Otomatis berdasarkan konfigurasinya.

Penting

Fitur Azure Communication Services ini saat ini dalam pratinjau.

API pratinjau dan SDK disediakan tanpa perjanjian tingkat layanan. Kami menyarankan agar Anda tidak menggunakannya untuk beban kerja produksi. Beberapa fitur mungkin tidak didukung, atau mungkin memiliki kemampuan yang dibatasi.

Untuk informasi selengkapnya, tinjau Ketentuan Penggunaan Tambahan untuk Pratinjau Microsoft Azure.

Dalam mulai cepat ini, Anda akan mempelajari cara memulai panggilan dari pengguna Azure Communication Services ke Teams Auto Attendant. Anda akan mencapainya dengan langkah-langkah berikut:

  1. Aktifkan federasi sumber daya Azure Communication Services dengan Penyewa Teams.
  2. Pilih atau buat Penjawab Otomatis Teams melalui Pusat Admin Teams.
  3. Dapatkan alamat email Penjawab Otomatis melalui Pusat Admin Teams.
  4. Dapatkan ID Objek penjawab otomatis melalui Graph API.
  5. Mulai panggilan dengan Azure Communication Services Calling SDK.

Jika Anda ingin melompati ke bagian akhir, Anda dapat mengunduh mulai cepat ini sebagai sampel di GitHub.

Mengaktifkan interoperabilitas di penyewa Teams Anda

Pengguna Microsoft Entra dengan peran administrator Teams dapat menjalankan cmdlet PowerShell dengan modul MicrosoftTeams untuk mengaktifkan sumber daya Communication Services di penyewa.

1. Siapkan modul Microsoft Teams

Pertama, buka PowerShell dan validasi keberadaan modul Teams dengan perintah berikut:

Get-module *teams* 

Jika Anda tidak melihat MicrosoftTeams modul, instal terlebih dahulu. Untuk menginstal modul, Anda perlu menjalankan PowerShell sebagai administrator. Kemudian, jalankan perintah berikut:

	Install-Module -Name MicrosoftTeams

Anda akan diberi tahu tentang modul yang akan diinstal, yang dapat Dikonfirmasi dengan Y jawaban atau A . Jika modul diinstal tetapi sudah kedaluarsa, Anda dapat menjalankan perintah berikut untuk memperbarui modul:

	Update-Module MicrosoftTeams

2. Sambungkan ke modul Microsoft Teams

Ketika modul diinstal dan siap, Anda dapat terhubung ke modul MicrosftTeams dengan perintah berikut. Anda akan diminta dengan jendela interaktif untuk masuk. Akun pengguna yang akan Anda gunakan harus memiliki izin administrator Teams. Jika tidak, Anda mungkin mendapatkan access denied respons di langkah berikutnya.

Connect-MicrosoftTeams

3. Aktifkan konfigurasi penyewa

Interoperabilitas dengan sumber daya Communication Services dikontrol melalui konfigurasi penyewa dan kebijakan yang ditetapkan. Penyewa Teams memiliki konfigurasi penyewa tunggal, dan pengguna Teams telah menetapkan kebijakan global atau kebijakan kustom. Untuk informasi selengkapnya, lihat Menetapkan Kebijakan di Teams.

Setelah berhasil masuk, Anda dapat menjalankan cmdlet Set-CsTeamsAcsFederationConfiguration untuk mengaktifkan sumber daya Communication Services di penyewa Anda. Ganti teks IMMUTABLE_RESOURCE_ID dengan ID sumber daya yang tidak dapat diubah di sumber daya komunikasi Anda. Anda dapat menemukan detail selengkapnya tentang cara mendapatkan informasi ini di sini.

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

4. Aktifkan kebijakan penyewa

Setiap pengguna Teams telah menetapkan External Access Policy yang menentukan apakah pengguna Communication Services dapat memanggil pengguna Teams ini. Gunakan cmdlet Set-CsExternalAccessPolicy untuk memastikan bahwa kebijakan yang ditetapkan ke pengguna Teams telah diatur EnableAcsFederationAccess ke $true

Set-CsExternalAccessPolicy -Identity Global -EnableAcsFederationAccess $true

Membuat atau memilih Penjawab Otomatis Teams

Teams Auto Attendant adalah sistem yang menyediakan sistem penanganan panggilan otomatis untuk panggilan masuk. Ini berfungsi sebagai resepsionis virtual, memungkinkan penelepon untuk secara otomatis dirutekan ke orang atau departemen yang sesuai tanpa perlu operator manusia. Anda dapat memilih yang sudah ada atau membuat Penjawab Otomatis baru melalui Pusat Admin Teams.

Pelajari selengkapnya tentang cara membuat Penjawab Otomatis menggunakan Pusat Admin Teams di sini.

Temukan ID Objek untuk Penjawab Otomatis

Setelah Penjawab Otomatis dibuat, kita perlu menemukan ID Objek yang berkorelasi untuk menggunakannya nanti untuk panggilan. ID Objek tersambung ke Akun Sumber Daya yang dilampirkan ke Penjawab Otomatis - buka tab Akun Sumber Daya di Admin Teams dan temukan email akun. Cuplikan layar Akun Sumber Daya di Portal Admin Teams. Semua informasi yang diperlukan untuk Akun Sumber Daya dapat ditemukan di Microsoft Graph Explorer menggunakan email ini dalam pencarian.

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

Dalam hasil kita akan dapat menemukan bidang "ID"

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

Untuk digunakan dalam Aplikasi panggilan, kita perlu menambahkan awalan ke ID ini. Saat ini, berikut ini didukung:

  • Penjawab Otomatis cloud publik: 28:orgid:<id>
  • Penjawab Otomatis cloud pemerintah: 28:gcch:<id>

Prasyarat

Menyiapkan

Membuat aplikasi Android dengan aktivitas kosong

Dari Android Studio, pilih Mulai proyek Android Studio baru.

Cuplikan layar yang menunjukkan tombol 'Mulai Proyek Android Studio baru' yang dipilih di Android Studio.

Pilih templat proyek "Aktivitas Tampilan Kosong" di bagian "Telepon dan Tablet".

Cuplikan layar menunjukkan opsi 'Aktivitas Kosong' yang dipilih di Layar Templat Proyek.

Pilih SDK Minimum "API 26: Android 8.0 (Oreo)" atau yang lebih tinggi.

Cuplikan layar menunjukkan opsi 'Aktivitas Kosong' yang dipilih di Layar Templat Proyek 2.

Pasang paket

Temukan proyek settings.gradle.kts Anda dan pastikan untuk melihat mavenCentral() daftar repositori di bawah pluginManagement dan dependencyResolutionManagement

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

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

Kemudian, di build.gradle tingkat modul Anda, tambahkan baris berikut ke dependensi dan bagian android

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

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

Menambahkan izin ke manifes aplikasi

Untuk meminta izin yang diperlukan untuk melakukan panggilan, mereka harus dideklarasikan dalam Manifes Aplikasi (app/src/main/AndroidManifest.xml). Ganti konten file dengan kode berikut:

    <?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>
    

Menyiapkan tata letak untuk aplikasi

Dua input diperlukan: input teks untuk ID penerima panggilan, dan tombol untuk melakukan panggilan. Input ini dapat ditambahkan melalui perancang atau dengan mengedit xml tata letak. Buat tombol dengan ID call_button dan input teks callee_id. Navigasi ke (app/src/main/res/layout/activity_main.xml) dan ganti konten file dengan kode berikut:

<?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>

Membuat perancah dan pengikatan aktivitas utama

Dengan tata letak yang sudah dibuat, pengikatan beserta perancah dasar aktivitas dapat ditambahkan. Aktivitas menangani permintaan izin runtime, membuat agen panggilan, dan melakukan panggilan saat tombol ditekan. Metode onCreate ini ditimpa untuk memanggil getAllPermissions dan createAgent dan menambahkan pengikatan untuk tombol panggilan. Peristiwa ini hanya terjadi sekali ketika aktivitas dibuat. Untuk informasi selengkapnya, pada onCreate, lihat panduan Memahami Siklus Hidup Aktivitas.

Buka MainActivity.java dan ganti konten dengan kode berikut:

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));
    }
}

Meminta izin saat runtime

Untuk Android 6.0 dan yang lebih tinggi (API level 23) dan targetSdkVersion 23 atau yang lebih tinggi, izin diberikan saat runtime, bukan saat aplikasi diinstal. Untuk mendukungnya, getAllPermissions dapat diimplementasikan untuk memanggil ActivityCompat.checkSelfPermission dan ActivityCompat.requestPermissions untuk setiap izin yang diperlukan.

/**
 * 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);
    }
}

Catatan

Saat merancang aplikasi Anda, pertimbangkan kapan izin ini harus diminta. Izin harus diminta saat dibutuhkan, bukan sebelumnya. Untuk informasi selengkapnya, lihat, Panduan Izin Android.

Model objek

Kelas dan antarmuka berikut menghandel beberapa fitur utama Azure Communication Services Calling SDK:

Nama Deskripsi
CallClient CallClient adalah titik masuk utama ke SDK Panggilan.
CallAgent CallAgent digunakan untuk memulai dan mengelola panggilan.
CommunicationTokenCredential CommunicationTokenCredential digunakan sebagai kredensial token untuk membuat instans CallAgent.
CommunicationIdentifier CommunicationIdentifier digunakan sebagai jenis peserta yang berbeda yang dapat menjadi bagian dari panggilan.

Membuat agen dari token akses pengguna

Dengan token pengguna, agen panggilan terautentikasi dapat diinstansiasi. Umumnya token ini dihasilkan dari layanan dengan autentikasi khusus untuk aplikasi. Untuk informasi selengkapnya tentang token akses pengguna, periksa panduan Token Akses Pengguna.

Untuk mulai cepat, ganti <User_Access_Token> dengan token akses pengguna yang dihasilkan untuk sumber daya Azure Communication Service Anda.


/**
 * 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();
    }
}

Menjalankan kode

Aplikasi sekarang dapat diluncurkan menggunakan tombol "Jalankan Aplikasi" pada toolbar.

Langkah-langkah manual untuk menyiapkan panggilan:

  1. Luncurkan aplikasi menggunakan Android Studio.
  2. Masukkan ID Objek Penjawab Otomatis (dengan awalan), dan pilih tombol "Mulai Panggilan". Aplikasi akan memulai panggilan keluar ke Penjawab Otomatis dengan ID objek tertentu.
  3. Panggilan tersambung ke Penjawab Otomatis.
  4. Pengguna Communication Services dirutekan melalui Penjawab Otomatis berdasarkan konfigurasinya.

Penting

Fitur Azure Communication Services ini saat ini dalam pratinjau.

API pratinjau dan SDK disediakan tanpa perjanjian tingkat layanan. Kami menyarankan agar Anda tidak menggunakannya untuk beban kerja produksi. Beberapa fitur mungkin tidak didukung, atau mungkin memiliki kemampuan yang dibatasi.

Untuk informasi selengkapnya, tinjau Ketentuan Penggunaan Tambahan untuk Pratinjau Microsoft Azure.

Dalam mulai cepat ini, Anda akan mempelajari cara memulai panggilan dari pengguna Azure Communication Services ke Teams Auto Attendant. Anda akan mencapainya dengan langkah-langkah berikut:

  1. Aktifkan federasi sumber daya Azure Communication Services dengan Penyewa Teams.
  2. Pilih atau buat Penjawab Otomatis Teams melalui Pusat Admin Teams.
  3. Dapatkan alamat email Penjawab Otomatis melalui Pusat Admin Teams.
  4. Dapatkan ID Objek penjawab otomatis melalui Graph API.
  5. Mulai panggilan dengan Azure Communication Services Calling SDK.

Jika Anda ingin melompati ke bagian akhir, Anda dapat mengunduh mulai cepat ini sebagai sampel di GitHub.

Mengaktifkan interoperabilitas di penyewa Teams Anda

Pengguna Microsoft Entra dengan peran administrator Teams dapat menjalankan cmdlet PowerShell dengan modul MicrosoftTeams untuk mengaktifkan sumber daya Communication Services di penyewa.

1. Siapkan modul Microsoft Teams

Pertama, buka PowerShell dan validasi keberadaan modul Teams dengan perintah berikut:

Get-module *teams* 

Jika Anda tidak melihat MicrosoftTeams modul, instal terlebih dahulu. Untuk menginstal modul, Anda perlu menjalankan PowerShell sebagai administrator. Kemudian, jalankan perintah berikut:

	Install-Module -Name MicrosoftTeams

Anda akan diberi tahu tentang modul yang akan diinstal, yang dapat Dikonfirmasi dengan Y jawaban atau A . Jika modul diinstal tetapi sudah kedaluarsa, Anda dapat menjalankan perintah berikut untuk memperbarui modul:

	Update-Module MicrosoftTeams

2. Sambungkan ke modul Microsoft Teams

Ketika modul diinstal dan siap, Anda dapat terhubung ke modul MicrosftTeams dengan perintah berikut. Anda akan diminta dengan jendela interaktif untuk masuk. Akun pengguna yang akan Anda gunakan harus memiliki izin administrator Teams. Jika tidak, Anda mungkin mendapatkan access denied respons di langkah berikutnya.

Connect-MicrosoftTeams

3. Aktifkan konfigurasi penyewa

Interoperabilitas dengan sumber daya Communication Services dikontrol melalui konfigurasi penyewa dan kebijakan yang ditetapkan. Penyewa Teams memiliki konfigurasi penyewa tunggal, dan pengguna Teams telah menetapkan kebijakan global atau kebijakan kustom. Untuk informasi selengkapnya, lihat Menetapkan Kebijakan di Teams.

Setelah berhasil masuk, Anda dapat menjalankan cmdlet Set-CsTeamsAcsFederationConfiguration untuk mengaktifkan sumber daya Communication Services di penyewa Anda. Ganti teks IMMUTABLE_RESOURCE_ID dengan ID sumber daya yang tidak dapat diubah di sumber daya komunikasi Anda. Anda dapat menemukan detail selengkapnya tentang cara mendapatkan informasi ini di sini.

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

4. Aktifkan kebijakan penyewa

Setiap pengguna Teams telah menetapkan External Access Policy yang menentukan apakah pengguna Communication Services dapat memanggil pengguna Teams ini. Gunakan cmdlet Set-CsExternalAccessPolicy untuk memastikan bahwa kebijakan yang ditetapkan ke pengguna Teams telah diatur EnableAcsFederationAccess ke $true

Set-CsExternalAccessPolicy -Identity Global -EnableAcsFederationAccess $true

Membuat atau memilih Penjawab Otomatis Teams

Teams Auto Attendant adalah sistem yang menyediakan sistem penanganan panggilan otomatis untuk panggilan masuk. Ini berfungsi sebagai resepsionis virtual, memungkinkan penelepon untuk secara otomatis dirutekan ke orang atau departemen yang sesuai tanpa perlu operator manusia. Anda dapat memilih yang sudah ada atau membuat Penjawab Otomatis baru melalui Pusat Admin Teams.

Pelajari selengkapnya tentang cara membuat Penjawab Otomatis menggunakan Pusat Admin Teams di sini.

Temukan ID Objek untuk Penjawab Otomatis

Setelah Penjawab Otomatis dibuat, kita perlu menemukan ID Objek yang berkorelasi untuk menggunakannya nanti untuk panggilan. ID Objek tersambung ke Akun Sumber Daya yang dilampirkan ke Penjawab Otomatis - buka tab Akun Sumber Daya di Admin Teams dan temukan email akun. Cuplikan layar Akun Sumber Daya di Portal Admin Teams. Semua informasi yang diperlukan untuk Akun Sumber Daya dapat ditemukan di Microsoft Graph Explorer menggunakan email ini dalam pencarian.

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

Dalam hasil kita akan dapat menemukan bidang "ID"

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

Untuk digunakan dalam Aplikasi panggilan, kita perlu menambahkan awalan ke ID ini. Saat ini, berikut ini didukung:

  • Penjawab Otomatis cloud publik: 28:orgid:<id>
  • Penjawab Otomatis cloud pemerintah: 28:gcch:<id>

Prasyarat

  • Dapatkan akun Azure dengan langganan aktif. Buat akun secara gratis.

  • Mac yang menjalankan Xcode, bersama dengan sertifikat pengembang valid yang diinstal ke rantai kunci Anda.

  • Sumber daya Communication Services yang disebarkan. Buat sumber daya Azure Communication Services. Anda perlu merekam string koneksi Anda untuk mulai cepat ini.

  • Token Akses Pengguna untuk Azure Communication Service. Anda juga dapat menggunakan Azure CLI dan menjalankan perintah dengan string koneksi Anda untuk membuat pengguna dan token akses.

    az communication identity token issue --scope voip --connection-string "yourConnectionString"
    

    Untuk detailnya, lihat Menggunakan Azure CLI untuk Membuat dan Mengelola Token Akses.

  • Dukungan minimum untuk aplikasi panggilan Teams: 2.14.0-beta.1

Menyiapkan

Membuat proyek Xcode

Di Xcode, buat proyek iOS baru dan pilih templat Aplikasi. Tutorial ini menggunakan kerangka kerja SwiftUI, jadi Anda harus mengatur Bahasa ke Swift dan Antarmuka Pengguna ke SwiftUI. Anda tidak akan membuat tes selama mulai cepat ini. Jangan ragu untuk menghapus centang Sertakan Pengujian.

Cuplikan layar yang menunjukkan jendela Proyek Baru dalam Xcode.

Memasang paket dan dependensi dengan CocoaPods

  1. Untuk membuat Podfile untuk aplikasi Anda, buka terminal dan navigasikan ke folder proyek dan jalankan:

    pod init

  2. Tambahkan kode berikut ke Podfile dan simpan (pastikan "target" cocok dengan nama proyek Anda):

    platform :ios, '13.0'
    use_frameworks!
    
    target 'AzureCommunicationCallingSample' do
      pod 'AzureCommunicationCalling', '~> 2.14.0-beta.1'
    end
    
  3. Jalankan pod install.

  4. Buka .xcworkspace dengan Xcode.

Minta akses ke mikrofon

Untuk mengakses mikrofon perangkat, Anda perlu memperbarui Daftar Properti Informasi aplikasi Anda dengan NSMicrophoneUsageDescription. Anda mengatur nilai terkait ke string yang disertakan dalam dialog yang digunakan sistem untuk meminta akses dari pengguna.

Klik kanan Info.plist entri pohon proyek dan pilih Buka Sebagai>Kode Sumber. Tambahkan baris berikut ke bagian <dict> tingkat atas, lalu simpan file.

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

Menyiapkan kerangka kerja aplikasi

Buka file ContentView.swift proyek Anda dan tambahkan deklarasi import ke bagian atas file untuk mengimpor AzureCommunicationCalling library. Selain itu, impor AVFoundation, kita memerlukan kode ini untuk permintaan izin audio dalam kode.

import AzureCommunicationCalling
import AVFoundation

Ganti implementasi struct ContentView dengan beberapa kontrol antarmuka pengguna sederhana yang memungkinkan pengguna memulai dan mengakhiri panggilan. Kami melampirkan logika bisnis ke kontrol ini dalam mulai cepat ini.

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
    }
}

Model objek

Kelas dan antarmuka berikut menghandel beberapa fitur utama Azure Communication Services Calling SDK:

Nama Deskripsi
CallClient CallClient adalah titik masuk utama ke SDK Panggilan.
CallAgent CallAgent digunakan untuk memulai dan mengelola panggilan.
CommunicationTokenCredential CommunicationTokenCredential digunakan sebagai kredensial token untuk membuat instans CallAgent.
CommunicationUserIdentifier CommunicationUserIdentifier digunakan untuk mewakili identitas pengguna, yang dapat menjadi salah satu opsi berikut: CommunicationUserIdentifier,PhoneNumberIdentifier atauCallingApplication.

Mengautentikasi klien

Menginisialisasi CallAgent instans dengan Token Akses Pengguna, yang memungkinkan kami melakukan dan menerima panggilan.

Dalam kode berikut, Anda perlu mengganti <USER ACCESS TOKEN> dengan token akses pengguna yang valid untuk sumber daya Anda. Lihat dokumentasi token akses pengguna jika Anda belum memiliki token yang tersedia.

Tambahkan kode berikut ke panggilan balik onAppear di ContentView.swift:

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.")
    }
}

Memulai panggilan

Metode startCall ini diatur sebagai tindakan yang dilakukan saat tombol Mulai Panggilan diketuk. Perbarui implementasi untuk memulai panggilan dengan 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")
                }
            }
        }
    }
}

Anda juga dapat menggunakan properti di StartCallOptions untuk mengatur opsi awal untuk panggilan (yaitu, memungkinkan memulai panggilan dengan mikrofon dibisukan).

Mengakhiri panggilan

Terapkan metode endCall untuk mengakhiri panggilan saat ini saat tombol Akhiri Panggilan diketuk.

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

Menjalankan kode

Anda dapat membuat dan menjalankan aplikasi di simulator iOS dengan memilih Eksekusi Produk>atau dengan menggunakan pintasan keyboard (⌘-R).

Catatan

Saat Anda melakukan panggilan untuk pertama kalinya, sistem akan meminta akses ke mikrofon kepada Anda. Dalam aplikasi produksi, Anda harus menggunakan API AVAudioSession untuk memeriksa status izin dan memperbarui perilaku aplikasi dengan baik saat izin tidak diberikan.

Langkah-langkah manual untuk menyiapkan panggilan:

  1. Luncurkan aplikasi menggunakan Xcode
  2. Masukkan ID Objek Penjawab Otomatis (dengan awalan), dan pilih tombol "Mulai Panggilan". Aplikasi akan memulai panggilan keluar ke Penjawab Otomatis dengan ID objek tertentu.
  3. Panggilan tersambung ke Penjawab Otomatis.
  4. Pengguna Communication Services dirutekan melalui Penjawab Otomatis berdasarkan konfigurasinya.

Penting

Fitur Azure Communication Services ini saat ini dalam pratinjau.

API pratinjau dan SDK disediakan tanpa perjanjian tingkat layanan. Kami menyarankan agar Anda tidak menggunakannya untuk beban kerja produksi. Beberapa fitur mungkin tidak didukung, atau mungkin memiliki kemampuan yang dibatasi.

Untuk informasi selengkapnya, tinjau Ketentuan Penggunaan Tambahan untuk Pratinjau Microsoft Azure.

Dalam mulai cepat ini, Anda akan mempelajari cara memulai panggilan dari pengguna Azure Communication Services ke Teams Auto Attendant. Anda akan mencapainya dengan langkah-langkah berikut:

  1. Aktifkan federasi sumber daya Azure Communication Services dengan Penyewa Teams.
  2. Pilih atau buat Penjawab Otomatis Teams melalui Pusat Admin Teams.
  3. Dapatkan alamat email Penjawab Otomatis melalui Pusat Admin Teams.
  4. Dapatkan ID Objek penjawab otomatis melalui Graph API.
  5. Mulai panggilan dengan Azure Communication Services Calling SDK.

Jika Anda ingin melompati ke bagian akhir, Anda dapat mengunduh mulai cepat ini sebagai sampel di GitHub.

Mengaktifkan interoperabilitas di penyewa Teams Anda

Pengguna Microsoft Entra dengan peran administrator Teams dapat menjalankan cmdlet PowerShell dengan modul MicrosoftTeams untuk mengaktifkan sumber daya Communication Services di penyewa.

1. Siapkan modul Microsoft Teams

Pertama, buka PowerShell dan validasi keberadaan modul Teams dengan perintah berikut:

Get-module *teams* 

Jika Anda tidak melihat MicrosoftTeams modul, instal terlebih dahulu. Untuk menginstal modul, Anda perlu menjalankan PowerShell sebagai administrator. Kemudian, jalankan perintah berikut:

	Install-Module -Name MicrosoftTeams

Anda akan diberi tahu tentang modul yang akan diinstal, yang dapat Dikonfirmasi dengan Y jawaban atau A . Jika modul diinstal tetapi sudah kedaluarsa, Anda dapat menjalankan perintah berikut untuk memperbarui modul:

	Update-Module MicrosoftTeams

2. Sambungkan ke modul Microsoft Teams

Ketika modul diinstal dan siap, Anda dapat terhubung ke modul MicrosftTeams dengan perintah berikut. Anda akan diminta dengan jendela interaktif untuk masuk. Akun pengguna yang akan Anda gunakan harus memiliki izin administrator Teams. Jika tidak, Anda mungkin mendapatkan access denied respons di langkah berikutnya.

Connect-MicrosoftTeams

3. Aktifkan konfigurasi penyewa

Interoperabilitas dengan sumber daya Communication Services dikontrol melalui konfigurasi penyewa dan kebijakan yang ditetapkan. Penyewa Teams memiliki konfigurasi penyewa tunggal, dan pengguna Teams telah menetapkan kebijakan global atau kebijakan kustom. Untuk informasi selengkapnya, lihat Menetapkan Kebijakan di Teams.

Setelah berhasil masuk, Anda dapat menjalankan cmdlet Set-CsTeamsAcsFederationConfiguration untuk mengaktifkan sumber daya Communication Services di penyewa Anda. Ganti teks IMMUTABLE_RESOURCE_ID dengan ID sumber daya yang tidak dapat diubah di sumber daya komunikasi Anda. Anda dapat menemukan detail selengkapnya tentang cara mendapatkan informasi ini di sini.

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

4. Aktifkan kebijakan penyewa

Setiap pengguna Teams telah menetapkan External Access Policy yang menentukan apakah pengguna Communication Services dapat memanggil pengguna Teams ini. Gunakan cmdlet Set-CsExternalAccessPolicy untuk memastikan bahwa kebijakan yang ditetapkan ke pengguna Teams telah diatur EnableAcsFederationAccess ke $true

Set-CsExternalAccessPolicy -Identity Global -EnableAcsFederationAccess $true

Membuat atau memilih Penjawab Otomatis Teams

Teams Auto Attendant adalah sistem yang menyediakan sistem penanganan panggilan otomatis untuk panggilan masuk. Ini berfungsi sebagai resepsionis virtual, memungkinkan penelepon untuk secara otomatis dirutekan ke orang atau departemen yang sesuai tanpa perlu operator manusia. Anda dapat memilih yang sudah ada atau membuat Penjawab Otomatis baru melalui Pusat Admin Teams.

Pelajari selengkapnya tentang cara membuat Penjawab Otomatis menggunakan Pusat Admin Teams di sini.

Temukan ID Objek untuk Penjawab Otomatis

Setelah Penjawab Otomatis dibuat, kita perlu menemukan ID Objek yang berkorelasi untuk menggunakannya nanti untuk panggilan. ID Objek tersambung ke Akun Sumber Daya yang dilampirkan ke Penjawab Otomatis - buka tab Akun Sumber Daya di Admin Teams dan temukan email akun. Cuplikan layar Akun Sumber Daya di Portal Admin Teams. Semua informasi yang diperlukan untuk Akun Sumber Daya dapat ditemukan di Microsoft Graph Explorer menggunakan email ini dalam pencarian.

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

Dalam hasil kita akan dapat menemukan bidang "ID"

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

Untuk digunakan dalam Aplikasi panggilan, kita perlu menambahkan awalan ke ID ini. Saat ini, berikut ini didukung:

  • Penjawab Otomatis cloud publik: 28:orgid:<id>
  • Penjawab Otomatis cloud pemerintah: 28:gcch:<id>

Prasyarat

Untuk menyelesaikan tutorial ini, Anda memerlukan prasyarat berikut:

Menyiapkan

Membuat proyek

Di Visual Studio, buat proyek baru dengan templat Blank App (Universal Windows) untuk menyiapkan aplikasi Universal Windows Platform (UWP) satu halaman.

Cuplikan layar memperlihatkan jendela Proyek UWP Baru dalam Visual Studio.

Pasang paket

Pilih kanan proyek Anda dan buka Manage Nuget Packages untuk menginstal Azure.Communication.Calling.WindowsClient 1.4.0 atau lebih unggul. Pastikan Include Prerelease dicentang jika Anda ingin melihat versi untuk pratinjau publik.

Meminta akses

Package.appxmanifest Buka dan pilih Capabilities. Periksa Internet (Client) dan Internet (Client & Server) untuk mendapatkan akses masuk dan keluar ke Internet. Periksa Microphone untuk mengakses umpan audio mikrofon, dan Webcam untuk mengakses umpan video kamera.

Cuplikan layar menampilkan permintaan akses ke Internet dan Mikrofon di Visual Studio.

Menyiapkan kerangka kerja aplikasi

Kita perlu mengonfigurasi tata letak dasar untuk melampirkan logika. Untuk melakukan panggilan keluar, kita perlu TextBox memberikan ID Pengguna dari penerima panggilan. Kita juga butuh tombol Start/Join call dan tombol Hang up. Mute Kotak centang dan BackgroundBlur juga disertakan dalam sampel ini untuk menunjukkan fitur mengubah status audio dan efek video.

Buka MainPage.xaml proyek Anda dan tambahkan node Grid ke Page:

<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>

Buka MainPage.xaml.cs dan ganti konten dengan implementasi berikut:

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
    }
}

Model objek

Tabel berikutnya mencantumkan kelas dan antarmuka menangani beberapa fitur utama SDK Panggilan Azure Communication Services:

Nama Deskripsi
CallClient CallClient adalah titik masuk utama ke SDK Panggilan.
CallAgent CallAgent digunakan untuk memulai dan mengelola panggilan.
CommunicationCall CommunicationCall digunakan untuk mengelola panggilan yang sedang berlangsung.
CallTokenCredential CallTokenCredential digunakan sebagai kredensial token untuk membuat instans CallAgent.
CallIdentifier CallIdentifier digunakan untuk mewakili identitas pengguna, yang dapat menjadi salah satu opsi berikut: UserCallIdentifier, PhoneNumberCallIdentifier dll.

Mengautentikasi klien

Menginisialisasi CallAgent instans dengan Token Akses Pengguna yang memungkinkan kami melakukan dan menerima panggilan, dan secara opsional mendapatkan instans DeviceManager untuk mengkueri konfigurasi perangkat klien.

Dalam kode, ganti <AUTHENTICATION_TOKEN> dengan Token Akses Pengguna. Lihat dokumentasi token akses pengguna jika Anda belum memiliki token yang tersedia.

Tambahkan InitCallAgentAndDeviceManagerAsync fungsi, yang mem-bootstrap SDK. Pembantu ini dapat disesuaikan untuk memenuhi persyaratan aplikasi Anda.

        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;
        }

Memulai panggilan

StartCallOptions Setelah objek diperoleh, CallAgent dapat digunakan untuk memulai panggilan 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;
        }

Mengakhiri panggilan

Akhiri panggilan saat ini ketika tombol Hang up diklik. Tambahkan implementasi ke HangupButton_Click untuk mengakhiri panggilan, dan hentikan pratinjau dan streaming video.

        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 });
            }
        }

Matikan suara/nyalakan suara pada audio

Matikan suara audio keluar saat tombol Mute diklik. Tambahkan implementasi ke MuteLocal_Click untuk mematikan suara panggilan.

        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
            }
        }

Menerima panggilan masuk

IncomingCallReceived sink peristiwa disiapkan di pembantu bootstrap InitCallAgentAndDeviceManagerAsyncSDK .

    this.callAgent.IncomingCallReceived += OnIncomingCallAsync;

Aplikasi memiliki kesempatan untuk mengonfigurasi bagaimana panggilan masuk harus diterima, seperti jenis streaming video dan audio.

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

            var acceptCallOptions = new AcceptCallOptions() { };

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

Memantau dan merespons peristiwa perubahan status panggilan

StateChanged peristiwa pada CommunicationCall objek diaktifkan ketika transaksi panggilan yang sedang berlangsung dari satu status ke status lainnya. Aplikasi ditawarkan peluang untuk mencerminkan perubahan status pada UI atau menyisipkan logika bisnis.

        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;
                }
            }
        }

Membuat tombol panggilan berfungsi

Callee ID Setelah tidak null atau kosong, Anda dapat memulai panggilan.

Status panggilan harus diubah menggunakan OnStateChangedAsync tindakan .


    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;
        }
    
        
    }

Menjalankan kode

Anda dapat membangun dan menjalankan kode di Visual Studio. Untuk platform solusi, kami mendukung ARM64, x64, dan x86.

Langkah-langkah manual untuk menyiapkan panggilan:

  1. Luncurkan aplikasi menggunakan Visual Studio.
  2. Masukkan ID Objek Penjawab Otomatis (dengan awalan), dan pilih tombol "Mulai Panggilan". Aplikasi akan memulai panggilan keluar ke Penjawab Otomatis dengan ID objek tertentu.
  3. Panggilan tersambung ke Penjawab Otomatis.
  4. Pengguna Communication Services dirutekan melalui Penjawab Otomatis berdasarkan konfigurasinya.

Membersihkan sumber daya

Jika ingin membersihkan dan menghapus langganan Azure Communication Services, Anda bisa menghapus sumber daya atau grup sumber daya. Menghapus grup sumber daya juga menghapus sumber daya apa pun yang terkait dengannya. Pelajari selengkapnya tentang membersihkan sumber daya.

Langkah berikutnya

Untuk informasi lebih lanjut, baca artikel berikut: