Mulai Cepat: Menambahkan panggilan video 1:1 sebagai pengguna Teams ke aplikasi Anda

Mulai menggunakan Azure Communication Services dengan menggunakan SDK panggilan Communication Services untuk menambahkan panggilan suara &video 1:1 ke aplikasi Anda. Anda akan mempelajari cara memulai dan menjawab panggilan menggunakan SDK Panggilan Azure Communication Services untuk JavaScript.

Kode Sampel

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

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

Jalankan npm init -y untuk membuat file package.json dengan pengaturan default.

npm init -y

Pasang paket

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

Penting

Mulai cepat ini menggunakan versi SDK Panggilan Azure Communication Services terbaru.

npm install @azure/communication-common --save
npm install @azure/communication-calling --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 - Teams Calling Web Application</title>
    </head>
    <body>
        <h4>Azure Communication Services - Teams Calling Web Application</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">Login</button>
        <br>
        <br>
        <input id="callee-teams-user-id"
            type="text"
            placeholder="Microsoft Teams callee's id (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)"
            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.
AzureCommunicationTokenCredential Menerapkan antarmuka CommunicationTokenCredential, yang digunakan untuk membuat instans teamsCallAgent.
TeamsCallAgent Digunakan untuk memulai dan mengelola panggilan Teams.
DeviceManager Digunakan untuk mengelola perangkat media.
TeamsCall Digunakan untuk mewakili Panggilan Teams
LocalVideoStream Digunakan untuk membuat aliran video lokal untuk perangkat kamera pada sistem lokal.
RemoteParticipant Digunakan untuk mewakili peserta jarak jauh di Panggilan
RemoteVideoStream Digunakan untuk mewakili aliran video jarak jauh dari Peserta Jarak Jauh.

Buat file di direktori akar proyek Anda yang bernama index.js guna memuat logika aplikasi untuk mulai cepat ini. Tambahkan kode berikut ke index.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 teamsCallAgent;
let deviceManager;
let call;
let incomingCall;
let localVideoStream;
let localVideoStreamRenderer;
// UI widgets
let userAccessToken = document.getElementById('user-access-token');
let calleeTeamsUserId = document.getElementById('callee-teams-user-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 TeamsCallAgent instance with a CommunicationUserCredential via created CallClient. TeamsCallAgent 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());
        teamsCallAgent = await callClient.createTeamsCallAgent(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.
        teamsCallAgent.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 a user
 * 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 = teamsCallAgent.startCall({ microsoftTeamsUserId: calleeTeamsUserId.value.trim() }, { 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 `TeamsCallAgent.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: './index.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`

Buka browser Anda dan pada dua tab navigasi ke http://localhost:8080/. Tab harus menampilkan hasil serupa seperti gambar berikut: Cuplikan layar memperlihatkan dua tab dalam tampilan default. Setiap tab akan digunakan untuk pengguna Teams yang berbeda.

Pada tab pertama, masukkan token akses pengguna yang valid. Pada tab kedua, masukkan token akses pengguna lain yang valid. Lihat dokumentasi token akses pengguna jika Anda belum memiliki token akses yang tersedia untuk digunakan. Pada kedua tab, klik tombol "Inisialisasi Agen Panggilan". Tab harus menampilkan hasil serupa seperti gambar berikut: Cuplikan layar memperlihatkan langkah-langkah untuk menginisialisasi setiap pengguna Teams di tab browser.

Pada tab pertama, masukkan identitas pengguna Azure Communication Services dari tab kedua, dan pilih tombol "Mulai Panggilan". Tab pertama akan memulai panggilan keluar ke tab kedua, dan tombol "Terima Panggilan" tab kedua akan diaktifkan: Cuplikan layar memperlihatkan pengalaman saat pengguna Teams menginisialisasi SDK dan memperlihatkan langkah-langkah untuk memulai panggilan ke pengguna kedua dan cara menerima panggilan.

Dari tab kedua, pilih tombol "Terima Panggilan". Panggilan akan dijawab dan dihubungkan. Tab harus menampilkan hasil serupa seperti gambar berikut: Cuplikan layar memperlihatkan dua tab, dengan panggilan berkelanjutan antara dua pengguna Teams, masing-masing dicatat di tab individual.

Kedua tab sekarang berhasil dalam panggilan video 1:1. Kedua pengguna dapat mendengar audio satu sama lain dan melihat aliran video lainnya.

Mulai menggunakan Azure Communication Services dengan menggunakan SDK panggilan Communication Services untuk menambahkan panggilan suara &video 1:1 ke aplikasi Anda. Anda mempelajari cara memulai dan menjawab panggilan menggunakan Azure Communication Services Calling SDK untuk Windows.

Kode Sampel

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

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.WindowsClient1.2.0-beta.1 atau lebih unggul. Pastikan Sertakan Yang Telah Dilepas telah dicentang.

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

    <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 TeamsCallAgent teamsCallAgent;
        private TeamsCommunicationCall teamsCall;

        private LocalOutgoingAudioStream micStream;
        private LocalOutgoingVideoStream cameraStream;

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

        protected override async void OnNavigatedTo(NavigationEventArgs e)
        {
            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, TeamsIncomingCallReceivedEventArgs args)
        {
            // Handle incoming call event
        }

        private async void OnStateChangedAsync(object sender, PropertyChangedEventArgs args)
        {
            // Handle connected and disconnected state change of a call
        }
        #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.
TeamsCallAgent TeamsCallAgent digunakan untuk memulai dan mengelola panggilan.
TeamsCommunicationCall TeamsCommunicationCall digunakan untuk mengelola panggilan yang sedang berlangsung.
CallTokenCredential CallTokenCredential digunakan sebagai kredensial token untuk membuat instans TeamsCallAgent.
CallIdentifier CallIdentifier digunakan untuk mewakili identitas pengguna, yang dapat menjadi salah satu opsi berikut: MicrosoftTeamsUserCallIdentifier, , UserCallIdentifierPhoneNumberCallIdentifier dll.

Mengautentikasi klien

Menginisialisasi TeamsCallAgent 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() { 
                    AppName = "CallingQuickstart",
                    AppVersion="1.0",
                    Tags = new[] { "Calling", "CTE", "Windows" }
                    }
                });

            // Set up local video stream using the first camera enumerated
            var deviceManager = await this.callClient.GetDeviceManagerAsync();
            var camera = deviceManager?.Cameras?.FirstOrDefault();
            var mic = deviceManager?.Microphones?.FirstOrDefault();
            micStream = new LocalOutgoingAudioStream();

            var tokenCredential = new CallTokenCredential(authToken, callTokenRefreshOptions);

            this.teamsCallAgent = await this.callClient.CreateTeamsCallAgentAsync(tokenCredential);
            this.teamsCallAgent.IncomingCallReceived += OnIncomingCallAsync;
        }

Memulai panggilan

Tambahkan implementasi ke CallButton_Click untuk memulai berbagai jenis panggilan dengan objek yang teamsCallAgent kami buat, dan menghubungkan RemoteParticipantsUpdated dan StateChanged penanganan aktivitas pada TeamsCommunicationCall objek.

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

            teamsCall = await StartCteCallAsync(callString);
            if (teamsCall != null)
            {
                teamsCall.StateChanged += OnStateChangedAsync;
            }
        }

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 teamsCall = this.teamsCallAgent?.Calls?.FirstOrDefault();
            if (teamsCall != null)
            {
                await teamsCall.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 teamsCall = this.teamsCallAgent?.Calls?.FirstOrDefault();
                if (teamsCall != null)
                {
                    if ((bool)muteCheckbox.IsChecked)
                    {
                        await teamsCall.MuteOutgoingAudioAsync();
                    }
                    else
                    {
                        await teamsCall.UnmuteOutgoingAudioAsync();
                    }
                }

                // Update the UI to reflect the state
            }
        }

Memulai panggilan

StartTeamsCallOptions Setelah objek diperoleh, TeamsCallAgent dapat digunakan untuk memulai panggilan Teams:

        private async Task<TeamsCommunicationCall> StartCteCallAsync(string cteCallee)
        {
            var options = new StartTeamsCallOptions();
            var teamsCall = await this.teamsCallAgent.StartCallAsync( new MicrosoftTeamsUserCallIdentifier(cteCallee), options);
            return call;
        }

Menerima panggilan masuk

TeamsIncomingCallReceived sink peristiwa disiapkan di pembantu bootstrap InitCallAgentAndDeviceManagerAsyncSDK .

    this.teamsCallAgent.IncomingCallReceived += OnIncomingCallAsync;

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

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

            var acceptteamsCallOptions = new AcceptTeamsCallOptions() { };

            teamsCall = await teamsIncomingCall.AcceptAsync(acceptteamsCallOptions);
            teamsCall.StateChanged += OnStateChangedAsync;
        }

Bergabung dengan Panggilan Teams

Pengguna juga dapat bergabung dengan panggilan yang ada dengan meneruskan tautan

TeamsMeetingLinkLocator link = new TeamsMeetingLinkLocator("meetingLink");
JoinTeamsCallOptions options = new JoinTeamsCallOptions();
TeamsCall call = await teamsCallAgent.JoinAsync(link, options);

Memantau dan merespons peristiwa perubahan status panggilan

StateChanged peristiwa pada TeamsCommunicationCall 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 teamsCall = sender as TeamsCommunicationCall;

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

                // Update the UI

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

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

                            teamsCall.Dispose();

                            break;
                        }
                    default: break;
                }
            }
        }

Menjalankan kode

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

Anda dapat melakukan panggilan keluar dengan memberikan ID pengguna di bidang teks dan mengklik tombol Start Call/Join. Memanggil 8:echo123 menghubungkan Anda dengan bot echo, fitur ini bagus untuk memulai dan memverifikasi perangkat audio Anda berfungsi.

Cuplikan layar memperlihatkan menjalankan aplikasi mulai cepat UWP

Mulai menggunakan Azure Communication Services dengan menggunakan SDK panggilan Communication Services untuk menambahkan panggilan suara &video 1:1 ke aplikasi Anda. Anda akan mempelajari cara memulai dan menjawab panggilan menggunakan SDK Panggilan Azure Communication Services untuk Java.

Kode Sampel

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

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 Kosong" di bagian "Ponsel 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 build.gradle tingkat proyek Anda dan pastikan untuk menambahkan mavenCentral() ke daftar repositori di bawah buildscript dan allprojects

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

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

android {
    ...
    packagingOptions {
        pickFirst  'META-INF/*'
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

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

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.ctequickstart">

    <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=".MainActivity">

    <Button
        android:id="@+id/call_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="16dp"
        android:text="Call"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

    <EditText
        android:id="@+id/callee_id"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:ems="10"
        android:hint="Callee Id"
        android:inputType="textPersonName"
        app:layout_constraintBottom_toTopOf="@+id/call_button"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="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 tim, dan melakukan panggilan saat tombol ditekan. Masing-masing tercakup dalam bagiannya sendiri. Metode onCreate ini ditimpa untuk memanggil getAllPermissions dan createTeamsAgent 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.ctequickstart;

import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import android.media.AudioManager;
import android.Manifest;
import android.content.pm.PackageManager;

import android.os.Bundle;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

import com.azure.android.communication.common.CommunicationUserIdentifier;
import com.azure.android.communication.common.CommunicationTokenCredential;
import com.azure.android.communication.calling.TeamsCallAgent;
import com.azure.android.communication.calling.CallClient;
import com.azure.android.communication.calling.StartTeamsCallOptions;


import java.util.ArrayList;

public class MainActivity extends AppCompatActivity {
    
    private TeamsCallAgent teamsCallAgent;

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

        getAllPermissions();
        createTeamsAgent();
        
        // Bind call button to call `startCall`
        Button callButton = findViewById(R.id.call_button);
        callButton.setOnClickListener(l -> startCall());
        
        setVolumeControlStream(AudioManager.STREAM_VOICE_CALL);
    }

    /**
     * Request each required permission if the app doesn't already have it.
     */
    private void getAllPermissions() {
        // See section on requesting permissions
    }

    /**
      * Create the call agent for placing calls
      */
    private void createTeamsAgent() {
        // See section on creating the call agent
    }

    /**
     * Place a call to the callee id provided in `callee_id` text input.
     */
    private void startCall() {
        // See section on starting the call
    }
}

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() {
    String[] requiredPermissions = new String[]{Manifest.permission.RECORD_AUDIO, Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_PHONE_STATE};
    ArrayList<String> permissionsToAskFor = new ArrayList<>();
    for (String permission : requiredPermissions) {
        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.
TeamsCallAgent TeamsCallAgent digunakan untuk memulai dan mengelola panggilan.
TeamsCall yang TeamsCall digunakan untuk mewakili Panggilan Teams.
CommunicationTokenCredential CommunicationTokenCredential digunakan sebagai kredensial token untuk membuat instans TeamsCallAgent.
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 teams call agent for placing calls
 */
private void createAgent() {
    String userToken = "<User_Access_Token>";

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

Memulai panggilan menggunakan agen panggilan

Menempatkan panggilan dapat dilakukan melalui agen panggilan tim, dan hanya perlu menyediakan daftar ID penerima panggilan dan opsi panggilan. Untuk mulai cepat, opsi panggilan default tanpa video dan SATU ID penerima panggilan dari input teks digunakan.

/**
 * Place a call to the callee id provided in `callee_id` text input.
 */
private void startCall() {
    EditText calleeIdView = findViewById(R.id.callee_id);
    String calleeId = calleeIdView.getText().toString();
    
    StartTeamsCallOptions options = new StartTeamsCallOptions();

    teamsCallAgent.startCall(
        getApplicationContext(),
        new MicrosoftTeamsUserCallIdentifier(calleeId),
        options);
}

Menjawab Panggilan

Menerima panggilan dapat dilakukan menggunakan agen panggilan teams hanya menggunakan referensi ke konteks saat ini.

public void acceptACall(TeamsIncomingCall teamsIncomingCall){
	teamsIncomingCall.accept(this);
}

Bergabung dengan Panggilan Teams

Pengguna dapat bergabung dengan panggilan yang ada dengan meneruskan tautan.

/**
 * Join a call using a teams meeting link.
 */
public TeamsCall joinTeamsCall(TeamsCallAgent teamsCallAgent){
	TeamsMeetingLinkLocator link = new TeamsMeetingLinkLocator("meetingLink");
	TeamsCall call = teamsCallAgent.join(this, link);
}

Bergabung dengan Panggilan Teams dengan opsi

Kita juga dapat bergabung dengan panggilan yang ada dengan opsi prasetel, seperti dibisukan.

/**
 * Join a call using a teams meeting link while muted.
 */
public TeamsCall joinTeamsCall(TeamsCallAgent teamsCallAgent){
	TeamsMeetingLinkLocator link = new TeamsMeetingLinkLocator("meetingLink");
	OutgoingAudioOptions audioOptions = new OutgoingAudioOptions().setMuted(true);
	JoinTeamsCallOptions options = new JoinTeamsCallOptions().setAudioOptions(audioOptions);
	TeamsCall call = teamsCallAgent.join(this, link, options);
}

Menyiapkan Pendengar Panggilan Masuk

Agar dapat mendeteksi panggilan masuk dan tindakan lain yang tidak dilakukan oleh pengguna ini, pendengar harus disiapkan.

private TeamsIncomingCall teamsincomingCall;
teamsCallAgent.addOnIncomingCallListener(this::handleIncomingCall);

private void handleIncomingCall(TeamsIncomingCall incomingCall) {
	this.teamsincomingCall = incomingCall;
}

Meluncurkan aplikasi dan memanggil bot echo

Aplikasi kini dapat diluncurkan menggunakan tombol "Jalankan Aplikasi" pada toolbar (Shift+F10). Verifikasi bahwa Anda dapat melakukan panggilan dengan memanggil 8:echo123. Pesan yang direkam sebelumnya diputar lalu ulangi pesan Anda kembali kepada Anda.

Cuplikan layar yang menunjukkan aplikasi yang sudah selesai.

Mulai menggunakan Azure Communication Services dengan menggunakan SDK panggilan Communication Services untuk menambahkan satu pada satu panggilan video ke aplikasi Anda. Anda mempelajari cara memulai dan menjawab panggilan video menggunakan Azure Communication Services Calling SDK untuk iOS menggunakan identitas Teams.

Kode Sampel

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

Prasyarat

Menyiapkan

Membuat proyek Xcode

Di Xcode, buat proyek iOS baru dan pilih templat Aplikasi Tampilan Tunggal. 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.

Menginstal CocoaPods

Gunakan panduan ini untuk menginstal CocoaPods di Mac Anda.

Memasang paket dan dependensi dengan CocoaPods

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

  2. Tambahkan kode berikut ke Podfile dan simpan. Lihat versi dukungan SDK.

platform :ios, '13.0'
use_frameworks!

target 'VideoCallingQuickstart' do
  pod 'AzureCommunicationCalling', '~> 2.10.0'
end
  1. Jalankan penginstalan pod.

  2. Buka .xcworkspace dengan Xcode.

Minta akses ke mikrofon dan kamera

Untuk mengakses mikrofon dan kamera perangkat, Anda perlu memperbarui aplikasi Daftar Properti Informasi denganNSMicrophoneUsageDescription dan NSCameraUsageDescription. Anda mengatur nilai terkait ke string yang menyertakan dialog yang digunakan sistem untuk meminta akses dari pengguna.

Info.plist Klik kanan 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>
<key>NSCameraUsageDescription</key>
<string>Need camera access for video calling</string>

Menyiapkan kerangka kerja aplikasi

Buka file proyek ContentView.swift Anda dan tambahkan deklarasi impor ke bagian atas file untuk mengimpor pustaka AzureCommunicationCalling dan AVFoundation. AVFoundation digunakan untuk mengambil izin akses audio dari kode.

import AzureCommunicationCalling
import AVFoundation

Model objek

Kelas dan antarmuka berikut menangani beberapa fitur utama Azure Communication Services Calling SDK for iOS.

Nama Deskripsi
CallClient CallClient adalah titik masuk utama ke SDK Panggilan.
TeamsCallAgent TeamsCallAgent digunakan untuk memulai dan mengelola panggilan.
TeamsIncomingCall TeamsIncomingCall digunakan untuk menerima atau menolak panggilan tim masuk.
CommunicationTokenCredential CommunicationTokenCredential digunakan sebagai kredensial token untuk membuat instans TeamsCallAgent.
CommunicationIdentifier CommunicationIdentifier digunakan untuk mewakili identitas pengguna, yang dapat menjadi salah satu opsi berikut: CommunicationUserIdentifier, PhoneNumberIdentifier atau CallingApplication.

Membuat Agen Panggilan Teams

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

struct ContentView: View {
    @State var callee: String = ""
    @State var callClient: CallClient?
    @State var teamsCallAgent: TeamsCallAgent?
    @State var teamsCall: TeamsCall?
    @State var deviceManager: DeviceManager?
    @State var localVideoStream:[LocalVideoStream]?
    @State var teamsIncomingCall: TeamsIncomingCall?
    @State var sendingVideo:Bool = false
    @State var errorMessage:String = "Unknown"

    @State var remoteVideoStreamData:[Int32:RemoteVideoStreamData] = [:]
    @State var previewRenderer:VideoStreamRenderer? = nil
    @State var previewView:RendererView? = nil
    @State var remoteRenderer:VideoStreamRenderer? = nil
    @State var remoteViews:[RendererView] = []
    @State var remoteParticipant: RemoteParticipant?
    @State var remoteVideoSize:String = "Unknown"
    @State var isIncomingCall:Bool = false
    
    @State var callObserver:CallObserver?
    @State var remoteParticipantObserver:RemoteParticipantObserver?

    var body: some View {
        NavigationView {
            ZStack{
                Form {
                    Section {
                        TextField("Who would you like to call?", text: $callee)
                        Button(action: startCall) {
                            Text("Start Teams Call")
                        }.disabled(teamsCallAgent == nil)
                        Button(action: endCall) {
                            Text("End Teams Call")
                        }.disabled(teamsCall == nil)
                        Button(action: toggleLocalVideo) {
                            HStack {
                                Text(sendingVideo ? "Turn Off Video" : "Turn On Video")
                            }
                        }
                    }
                }
                // Show incoming call banner
                if (isIncomingCall) {
                    HStack() {
                        VStack {
                            Text("Incoming call")
                                .padding(10)
                                .frame(maxWidth: .infinity, alignment: .topLeading)
                        }
                        Button(action: answerIncomingCall) {
                            HStack {
                                Text("Answer")
                            }
                            .frame(width:80)
                            .padding(.vertical, 10)
                            .background(Color(.green))
                        }
                        Button(action: declineIncomingCall) {
                            HStack {
                                Text("Decline")
                            }
                            .frame(width:80)
                            .padding(.vertical, 10)
                            .background(Color(.red))
                        }
                    }
                    .frame(maxWidth: .infinity, alignment: .topLeading)
                    .padding(10)
                    .background(Color.gray)
                }
                ZStack{
                    VStack{
                        ForEach(remoteViews, id:\.self) { renderer in
                            ZStack{
                                VStack{
                                    RemoteVideoView(view: renderer)
                                        .frame(width: .infinity, height: .infinity)
                                        .background(Color(.lightGray))
                                }
                            }
                            Button(action: endCall) {
                                Text("End Call")
                            }.disabled(teamsCall == nil)
                            Button(action: toggleLocalVideo) {
                                HStack {
                                    Text(sendingVideo ? "Turn Off Video" : "Turn On Video")
                                }
                            }
                        }
                        
                    }.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
                    VStack{
                        if(sendingVideo)
                        {
                            VStack{
                                PreviewVideoStream(view: previewView!)
                                    .frame(width: 135, height: 240)
                                    .background(Color(.lightGray))
                            }
                        }
                    }.frame(maxWidth:.infinity, maxHeight:.infinity,alignment: .bottomTrailing)
                }
            }
     .navigationBarTitle("Video Calling Quickstart")
        }.onAppear{
            // Authenticate the client
            
            // Initialize the TeamsCallAgent and access Device Manager
            
            // Ask for permissions
        }
    }
}

//Functions and Observers

struct PreviewVideoStream: UIViewRepresentable {
    let view:RendererView
    func makeUIView(context: Context) -> UIView {
        return view
    }
    func updateUIView(_ uiView: UIView, context: Context) {}
}

struct RemoteVideoView: UIViewRepresentable {
    let view:RendererView
    func makeUIView(context: Context) -> UIView {
        return view
    }
    func updateUIView(_ uiView: UIView, context: Context) {}
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

Mengautentikasi klien

Untuk menginisialisasi TeamsCallAgent instans, Anda memerlukan Token Akses Pengguna yang memungkinkannya melakukan, dan menerima panggilan. Lihat dokumentasi token akses pengguna, jika Anda tidak memiliki token yang tersedia.

Setelah Anda memiliki token, Tambahkan kode berikut ke onAppear panggilan balik di ContentView.swift. Anda perlu mengganti <USER ACCESS TOKEN> dengan token akses pengguna yang valid untuk sumber daya Anda:

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

Menginisialisasi CallAgent Teams dan mengakses Device Manager

Untuk membuat TeamsCallAgent instans dari CallClient, gunakan callClient.createTeamsCallAgent metode yang secara asinkron mengembalikan TeamsCallAgent objek setelah diinisialisasi. DeviceManager memungkinkan Anda menghitung perangkat lokal yang dapat digunakan dalam panggilan untuk mengirimkan aliran audio/video. DeviceManager juga memungkinkan Anda untuk meminta izin akses dari pengguna untuk mengakses mikrofon/kamera.

self.callClient = CallClient()
let options = TeamsCallAgentOptions()
// Enable CallKit in the SDK
options.callKitOptions = CallKitOptions(with: createCXProvideConfiguration())
self.callClient?.createTeamsCallAgent(userCredential: userCredential, options: options) { (agent, error) in
    if error != nil {
        print("ERROR: It was not possible to create a Teams call agent.")
        return
    } else {
        self.teamsCallAgent = agent
        print("Teams Call agent successfully created.")
        self.teamsCallAgent!.delegate = teamsIncomingCallHandler
        self.callClient?.getDeviceManager { (deviceManager, error) in
            if (error == nil) {
                print("Got device manager instance")
                self.deviceManager = deviceManager
            } else {
                print("Failed to get device manager instance")
            }
        }
    }
}

Meminta izin

Kita perlu menambahkan kode berikut ke panggilan balik onAppear guna meminta izin audio dan video.

AVAudioSession.sharedInstance().requestRecordPermission { (granted) in
    if granted {
        AVCaptureDevice.requestAccess(for: .video) { (videoGranted) in
            /* NO OPERATION */
        }
    }
}

Melakukan panggilan keluar

Metode startCall ini diatur sebagai tindakan yang dilakukan saat tombol Mulai Panggilan diketuk. Dalam mulai cepat ini, panggilan keluar hanya audio secara default. Untuk memulai panggilan dengan video, kita perlu mengatur VideoOptions dengan LocalVideoStream dan meneruskannya dengan startCallOptions untuk mengatur opsi awal untuk panggilan.

let startTeamsCallOptions = StartTeamsCallOptions()
if sendingVideo  {
    if self.localVideoStream == nil  {
        self.localVideoStream = [LocalVideoStream]()
    }
    let videoOptions = VideoOptions(localVideoStreams: localVideoStream!)
    startTeamsCallOptions.videoOptions = videoOptions
}
let callees: [CommunicationIdentifier] = [CommunicationUserIdentifier(self.callee)]
self.teamsCallAgent?.startCall(participants: callees, options: startTeamsCallOptions) { (call, error) in
    // Handle call object if successful or an error.
}

Bergabung dengan rapat Teams

Metode ini join memungkinkan pengguna untuk bergabung dalam rapat tim.

let joinTeamsCallOptions = JoinTeamsCallOptions()
if sendingVideo
{
    if self.localVideoStream == nil {
        self.localVideoStream = [LocalVideoStream]()
    }
    let videoOptions = VideoOptions(localVideoStreams: localVideoStream!)
    joinTeamsCallOptions.videoOptions = videoOptions
}

// Join the Teams meeting muted
if isMuted
{
    let outgoingAudioOptions = OutgoingAudioOptions()
    outgoingAudioOptions.muted = true
    joinTeamsCallOptions.outgoingAudioOptions = outgoingAudioOptions
}

let teamsMeetingLinkLocator = TeamsMeetingLinkLocator(meetingLink: "https://meeting_link")

self.teamsCallAgent?.join(with: teamsMeetingLinkLocator, options: joinTeamsCallOptions) { (call, error) in
    // Handle call object if successful or an error.
}

TeamsCallObserver dan RemotePariticipantObserver digunakan untuk mengelola peristiwa tengah panggilan dan peserta jarak jauh. Kami mengatur pengamat dalam setTeamsCallAndObserver fungsi .

func setTeamsCallAndObserver(call:TeamsCall, error:Error?) {
    if (error == nil) {
        self.teamsCall = call
        self.teamsCallObserver = TeamsCallObserver(self)
        self.teamsCall!.delegate = self.teamsCallObserver
        // Attach a RemoteParticipant observer
        self.remoteParticipantObserver = RemoteParticipantObserver(self)
    } else {
        print("Failed to get teams call object")
    }
}

Menjawab panggilan masuk

Untuk menjawab panggilan masuk, implementasikan TeamsIncomingCallHandler untuk menampilkan spanduk panggilan masuk untuk menjawab atau menolak panggilan. Masukkan implementasi berikut ke dalam TeamsIncomingCallHandler.swift.

final class TeamsIncomingCallHandler: NSObject, TeamsCallAgentDelegate, TeamsIncomingCallDelegate {
    public var contentView: ContentView?
    private var teamsIncomingCall: TeamsIncomingCall?

    private static var instance: TeamsIncomingCallHandler?
    static func getOrCreateInstance() -> TeamsIncomingCallHandler {
        if let c = instance {
            return c
        }
        instance = TeamsIncomingCallHandler()
        return instance!
    }

    private override init() {}
    
    func teamsCallAgent(_ teamsCallAgent: TeamsCallAgent, didRecieveIncomingCall incomingCall: TeamsIncomingCall) {
        self.teamsIncomingCall = incomingCall
        self.teamsIncomingCall.delegate = self
        contentView?.showIncomingCallBanner(self.teamsIncomingCall!)
    }
    
    func teamsCallAgent(_ teamsCallAgent: TeamsCallAgent, didUpdateCalls args: TeamsCallsUpdatedEventArgs) {
        if let removedCall = args.removedCalls.first {
            contentView?.callRemoved(removedCall)
            self.teamsIncomingCall = nil
        }
    }
}

Kita perlu membuat instans TeamsIncomingCallHandler dengan menambahkan kode berikut ke onAppear panggilan balik di ContentView.swift:

Atur delegasi ke TeamsCallAgent setelah TeamsCallAgent berhasil dibuat:

self.teamsCallAgent!.delegate = incomingCallHandler

Setelah ada panggilan masuk, TeamsIncomingCallHandler memanggil fungsi showIncomingCallBanner untuk ditampilkan answer dan decline tombol.

func showIncomingCallBanner(_ incomingCall: TeamsIncomingCall) {
    self.teamsIncomingCall = incomingCall
}

Tindakan yang dilampirkan answer dan decline diimplementasikan sebagai kode berikut. Untuk menjawab panggilan dengan video, kita perlu mengaktifkan video lokal dan mengatur opsi AcceptCallOptions dengan localVideoStream.

func answerIncomingCall() {
    let options = AcceptTeamsCallOptions()
    guard let teamsIncomingCall = self.teamsIncomingCall else {
      print("No active incoming call")
      return
    }

    guard let deviceManager = deviceManager else {
      print("No device manager instance")
      return
    }

    if self.localVideoStreams == nil {
        self.localVideoStreams = [LocalVideoStream]()
    }

    if sendingVideo
    {
        guard let camera = deviceManager.cameras.first else {
            // Handle failure
            return
        }
        self.localVideoStreams?.append( LocalVideoStream(camera: camera))
        let videoOptions = VideoOptions(localVideoStreams: localVideosStreams!)
        options.videoOptions = videoOptions
    }

    teamsIncomingCall.accept(options: options) { (call, error) in
        // Handle call object if successful or an error.
    }
}

func declineIncomingCall() {
    self.teamsIncomingCall?.reject { (error) in 
        // Handle if rejection was successfully or not.
    }
}

Berlangganan Peristiwa

Kita dapat menerapkan kelas TeamsCallObserver untuk berlangganan koleksi acara yang akan diberi tahu ketika nilai berubah selama panggilan.

public class TeamsCallObserver: NSObject, TeamsCallDelegate, TeamsIncomingCallDelegate {
    private var owner: ContentView
    init(_ view:ContentView) {
            owner = view
    }
        
    public func teamsCall(_ teamsCall: TeamsCall, didChangeState args: PropertyChangedEventArgs) {
        if(teamsCall.state == CallState.connected) {
            initialCallParticipant()
        }
    }

    // render remote video streams when remote participant changes
    public func teamsCall(_ teamsCall: TeamsCall, didUpdateRemoteParticipant args: ParticipantsUpdatedEventArgs) {
        for participant in args.addedParticipants {
            participant.delegate = self.remoteParticipantObserver
        }
    }

    // Handle remote video streams when the call is connected
    public func initialCallParticipant() {
        for participant in owner.teamsCall.remoteParticipants {
            participant.delegate = self.remoteParticipantObserver
            for stream in participant.videoStreams {
                renderRemoteStream(stream)
            }
            owner.remoteParticipant = participant
        }
    }
}

Menjalankan kode

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

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: