Sdílet prostřednictvím


Rychlý start: Přidání videohovorů 1:1 jako uživatele Teams do aplikace

Začněte se službou Azure Communication Services pomocí sady SDK pro volání komunikačních služeb a přidejte do aplikace hlasové volání 1:1 a videohovory. Dozvíte se, jak zahájit a přijmout volání pomocí sady SDK pro volání služeb Azure Communication Services pro JavaScript.

Ukázka kódu

Pokud chcete přeskočit na konec, můžete si tento rychlý start stáhnout jako ukázku na GitHubu.

Požadavky

Nastavení

Vytvoření nové aplikace Node.js

Otevřete terminál nebo příkazové okno, vytvořte pro aplikaci nový adresář a přejděte do adresáře.

mkdir calling-quickstart && cd calling-quickstart

Spuštěním příkazu npm init -y vytvořte soubor package.json s výchozím nastavením.

npm init -y

Nainstalujte balíček .

npm install Pomocí příkazu nainstalujte sadu SDK pro volání služeb Azure Communication Services pro JavaScript.

Důležité

V tomto rychlém startu se používá nejnovější verze sady SDK pro volání služeb Azure Communication Services.

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

Nastavení architektury aplikace

V tomto rychlém startu se ke sbalení prostředků aplikace používá webpack. Spuštěním následujícího příkazu nainstalujte webpackbalíčky a webpack-cli npm a webpack-dev-server uveďte je jako vývojové závislosti ve vaší package.json:

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

Vytvořte index.html soubor v kořenovém adresáři projektu. Tento soubor použijeme ke konfiguraci základního rozložení, které uživateli umožní umístit videohovor 1:1.

Tady je kód:

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

Objektový model webové sady SDK volání služeb Azure Communication Services

Následující třídy a rozhraní zpracovávají některé z hlavních funkcí sady SDK pro volání služeb Azure Communication Services:

Název Popis
CallClient Hlavní vstupní bod do volající sady SDK.
AzureCommunicationTokenCredential Implementuje CommunicationTokenCredential rozhraní, které se používá k vytvoření instance teamsCallAgent.
TeamsCallAgent Používá se ke spouštění a správě hovorů v Teams.
DeviceManager Slouží ke správě mediálních zařízení.
TeamsCall Používá se k reprezentaci hovoru v Teams.
LocalVideoStream Používá se k vytvoření místního videostreamu pro zařízení fotoaparátu v místním systému.
RemoteParticipant Používá se pro reprezentaci vzdáleného účastníka v hovoru.
RemoteVideoStream Používá se pro reprezentaci vzdáleného video streamu ze vzdáleného účastníka.

Vytvořte soubor v kořenovém adresáři projektu, který bude index.js obsahovat logiku aplikace pro účely tohoto rychlého startu. Do index.js přidejte následující kód:

// 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 updates.
subscribeToCall = (call) => {
    try {
        // Inspect the initial call.id value.
        console.log(`Call Id: ${call.id}`);
        //Subscribe 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 updates.
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 remoteParticipant 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();
});

Přidání kódu místního serveru webpacku

V kořenovém adresáři projektu vytvořte soubor s názvem webpack.config.js , který bude obsahovat logiku místního serveru pro účely tohoto rychlého startu. Do webpack.config.js přidejte následující kód:

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'
            ]
        }),
    ]
};

Spuštění kódu

Použijte k webpack-dev-server sestavení a spuštění aplikace. Spuštěním následujícího příkazu sbalte hostitele aplikace v místním webovém serveru:

`npx webpack serve --config webpack.config.js`

Otevřete prohlížeč a na dvou kartách přejděte na http://localhost:8080/. Karty by měly zobrazit podobný výsledek jako na následujícím obrázku: Snímek obrazovky znázorňující dvě karty ve výchozím zobrazení Každá karta se použije pro jiného uživatele Teams.

Na první kartě zadejte platný přístupový token uživatele. Na druhé kartě zadejte jiný platný přístupový token uživatele. Pokud ještě nemáte k dispozici přístupové tokeny, projděte si dokumentaci k přístupovým tokenům uživatele. Na oboukartách Karty by měly zobrazit podobný výsledek jako na následujícím obrázku: Snímek obrazovky ukazuje postup inicializace jednotlivých uživatelů Teams na kartě prohlížeče.

Na první kartě zadejte identitu uživatele Azure Communication Services na druhé kartě a vyberte tlačítko Zahájit hovor. První karta spustí odchozí hovor na druhou kartu a tlačítko Přijmout hovor druhé karty se aktivuje: Snímek obrazovky znázorňující prostředí, kdy uživatelé Teams inicializují sadu SDK a zobrazují kroky pro zahájení volání druhému uživateli a způsob přijetí hovoru.

Na druhé kartě vyberte tlačítko Přijmout hovor. Hovor bude zodpovězen a připojen. Karty by měly zobrazit podobný výsledek jako na následujícím obrázku: Snímek obrazovky znázorňující dvě karty s průběžným voláním mezi dvěma uživateli Teams, každý přihlášený na jednotlivé kartě

Obě karty jsou nyní úspěšně ve videohovoru 1:1. Oba uživatelé můžou slyšet zvuk sebe navzájem a vidět si navzájem stream videa.

Začněte se službou Azure Communication Services pomocí sady SDK pro volání komunikačních služeb a přidejte do aplikace hlasové volání 1:1 a videohovory. Naučíte se, jak zahájit a přijmout volání pomocí sady SDK pro volání služeb Azure Communication Services pro Windows.

Ukázka kódu

Pokud chcete přeskočit na konec, můžete si tento rychlý start stáhnout jako ukázku na GitHubu.

Požadavky

Pro absolvování tohoto kurzu musí být splněné následující požadavky:

Nastavení

Vytvoření projektu

V sadě Visual Studio vytvořte nový projekt pomocí šablony Prázdná aplikace (Univerzální windows) pro nastavení jednostránka Univerzální platforma Windows (UPW).

Snímek obrazovky s oknem Nový projekt UPW v sadě Visual Studio

Nainstalujte balíček .

Vyberte projekt pravým tlačítkem a přejděte na Manage Nuget Packages instalaci Azure.Communication.Calling.WindowsClient verze 1.2.0-beta.1 nebo superior. Ujistěte se, že je zaškrtnuté políčko Zahrnout předběžné verze.

Vyžádat si přístup

Přejděte na Package.appxmanifest a vyberte Capabilities. Zkontrolujte a Internet (Client & Server) získejte Internet (Client) příchozí a odchozí přístup k internetu. Zkontrolujte Microphone , jestli chcete získat přístup ke zvukovému kanálu mikrofonu a Webcam získat přístup k videokamerě.

Snímek obrazovky s žádostí o přístup k internetu a mikrofonu v sadě Visual Studio

Nastavení architektury aplikace

Potřebujeme nakonfigurovat základní rozložení pro připojení naší logiky. Abychom mohli umístit odchozí hovor, musíme TextBox zadat ID uživatele volaného. Potřebujeme Start/Join call také tlačítko a Hang up tlačítko. Součástí Mute této ukázky jsou také zaškrtávací políčka, BackgroundBlur které demonstrují funkce přepínání zvukových stavů a efektů videa.

MainPage.xaml Otevřete projekt a přidejte uzel Grid do svéhoPage:

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

MainPage.xaml.cs Otevřete a nahraďte obsah následující implementací:

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

Objektový model

V další tabulce jsou uvedené třídy a rozhraní, které zpracovávají některé z hlavních funkcí sady SDK pro volání služeb Azure Communication Services:

Název Popis
CallClient Jedná se CallClient o hlavní vstupní bod volající sady SDK.
TeamsCallAgent Slouží TeamsCallAgent ke spouštění a správě hovorů.
TeamsCommunicationCall Slouží TeamsCommunicationCall ke správě probíhajícího hovoru.
CallTokenCredential Slouží CallTokenCredential jako přihlašovací údaje tokenu k vytvoření instance TeamsCallAgent.
CallIdentifier Slouží CallIdentifier k reprezentaci identity uživatele, což může být jedna z následujících možností: MicrosoftTeamsUserCallIdentifier, UserCallIdentifieratd PhoneNumberCallIdentifier .

Ověření klienta

Inicializace TeamsCallAgent instance pomocí přístupového tokenu uživatele, který nám umožňuje provádět a přijímat volání, a volitelně získat instanci DeviceManager pro dotazování na konfigurace klientských zařízení.

V kódu nahraďte <AUTHENTICATION_TOKEN> přístupovým tokenem uživatele. Pokud ještě token nemáte k dispozici, projděte si dokumentaci k přístupovým tokenům uživatele.

Přidejte InitCallAgentAndDeviceManagerAsync funkci, která spouští sadu SDK. Tento pomocník je možné přizpůsobit tak, aby splňoval požadavky vaší aplikace.

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

Zahájení hovoru

Přidejte implementaci ke CallButton_Click spuštění různých druhů volání s objektem teamsCallAgent , který jsme vytvořili, a připojte RemoteParticipantsUpdated StateChanged obslužné rutiny událostí k objektu TeamsCommunicationCall .

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

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

Ukončení hovoru

Ukončení aktuálního hovoru po kliknutí na Hang up tlačítko Přidejte implementaci do HangupButton_Click pro ukončení volání a zastavte streamy náhledu a videa.

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

Přepnutí ztlumení nebo zrušení ztlumení zvuku

Po kliknutí na Mute tlačítko ztlumte odchozí zvuk. Přidejte implementaci do MuteLocal_Click pro ztlumení volání.

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

Zahájení hovoru

Jakmile se StartTeamsCallOptions objekt získá, TeamsCallAgent můžete ho použít k zahájení volání Teams:

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

Přijetí příchozího hovoru

TeamsIncomingCallReceived Jímka událostí je nastavena v pomocné rutině InitCallAgentAndDeviceManagerAsyncsdk bootstrap .

    this.teamsCallAgent.IncomingCallReceived += OnIncomingCallAsync;

Aplikace má příležitost ke konfiguraci způsobu přijetí příchozího hovoru, jako jsou druhy video a zvukového streamu.

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

            var acceptteamsCallOptions = new AcceptTeamsCallOptions() { };

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

Připojení k hovoru v Teams

Uživatel se také může připojit k existujícímu volání předáním odkazu.

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

Monitorování a reakce na událost změny stavu volání

StateChanged událost objektu TeamsCommunicationCall se aktivuje, když probíhající volání transakcí z jednoho stavu do druhého. Aplikace nabízí příležitosti k vyjádření změn stavu v uživatelském rozhraní nebo vložení obchodní logiky.

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

Spuštění kódu

Kód můžete sestavit a spustit v sadě Visual Studio. Pro platformy řešení podporujeme ARM64x64 a x86.

Odchozí volání můžete provést tak, že do textového pole zadáte ID uživatele a kliknete na Start Call/Join tlačítko. Volání 8:echo123 vás spojí s robotem echo, tato funkce je skvělá pro začátek a ověření fungování zvukových zařízení.

Snímek obrazovky znázorňující spuštění aplikace pro rychlý start pro UPW

Začněte se službou Azure Communication Services pomocí sady SDK pro volání komunikačních služeb a přidejte do aplikace hlasové volání 1:1 a videohovory. Dozvíte se, jak zahájit a přijmout volání pomocí sady SDK pro volání služeb Azure Communication Services pro Javu.

Ukázka kódu

Pokud chcete přeskočit na konec, můžete si tento rychlý start stáhnout jako ukázku na GitHubu.

Požadavky

Nastavení

Vytvoření aplikace pro Android s prázdnou aktivitou

V Android Studiu vyberte Spustit nový projekt Android Studio.

Snímek obrazovky s tlačítkem Start a new Android Studio Project (Spustit nový projekt Android Studio) vybraným v android Studiu

V části Telefon a tablet vyberte šablonu projektu Prázdná aktivita.

Snímek obrazovky znázorňující možnost Prázdná aktivita vybraná na obrazovce Šablony projektu

Vyberte minimální sadu SDK rozhraní API 26: Android 8.0 (Oreo) nebo vyšší.

Snímek obrazovky znázorňující možnost Prázdná aktivita vybraná na obrazovce Šablona projektu 2

Nainstalujte balíček .

Vyhledejte soubor build.gradle na úrovni projektu a nezapomeňte ho přidat mavenCentral() do seznamu úložišť v části buildscript a allprojects

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

Potom na úrovni modulu build.gradle přidejte následující řádky do oddílů závislostí a androidu.

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

Přidání oprávnění k manifestu aplikace

Aby bylo možné požádat o oprávnění požadovaná k volání, musí být deklarována v manifestu aplikace (app/src/main/AndroidManifest.xml). Obsah souboru nahraďte následujícím kódem:

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

Nastavení rozložení aplikace

Jsou potřeba dva vstupy: textový vstup pro volané ID a tlačítko pro umístění hovoru. Tyto vstupy lze přidat prostřednictvím návrháře nebo úpravou xml rozložení. Vytvořte tlačítko s ID call_button a textovým vstupem callee_id. Přejděte na (app/src/main/res/layout/activity_main.xml) a nahraďte obsah souboru následujícím kódem:

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

Vytvoření generování uživatelského rozhraní a vazeb hlavní aktivity

S rozložením vytvořeným vazbami je možné přidat i základní generování aktivity. Aktivita zpracovává žádosti o oprávnění modulu runtime, vytvoření agenta volání týmů a umístění hovoru při stisknutí tlačítka. Každý z nich je popsaný ve své vlastní části. Metoda onCreate je přepsána k vyvolání getAllPermissions a createTeamsAgent přidání vazeb pro tlačítko volání. K této události dochází pouze jednou při vytvoření aktivity. Další informace onCreatenajdete v příručce Principy životního cyklu aktivity.

Přejděte na MainActivity.java a nahraďte obsah následujícím kódem:

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

Vyžádání oprávnění za běhu

Pro Android 6.0 a vyšší (úroveň rozhraní API 23) a targetSdkVersion 23 nebo vyšší jsou oprávnění udělena za běhu místo instalace aplikace. Aby bylo možné ho podporovat, getAllPermissions je možné implementovat volání ActivityCompat.checkSelfPermission a ActivityCompat.requestPermissions pro každé požadované oprávnění.

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

Poznámka:

Při návrhu aplikace zvažte, kdy by se tato oprávnění měla požadovat. Oprávnění by se měla vyžadovat podle potřeby, a ne předem. Další informace najdete v průvodci oprávněními androidu .

Objektový model

Následující třídy a rozhraní zpracovávají některé z hlavních funkcí sady SDK pro volání služeb Azure Communication Services:

Název Popis
CallClient Jedná se CallClient o hlavní vstupní bod volající sady SDK.
TeamsCallAgent Slouží TeamsCallAgent ke spouštění a správě hovorů.
TeamsCall Používá TeamsCall se k reprezentaci hovoru v Teams.
CommunicationTokenCredential Slouží CommunicationTokenCredential jako přihlašovací údaje tokenu k vytvoření instance TeamsCallAgent.
CommunicationIdentifier Používá CommunicationIdentifier se jako jiný typ účastníka, který může být součástí hovoru.

Vytvoření agenta z přístupového tokenu uživatele

Pomocí tokenu uživatele je možné vytvořit instanci ověřeného agenta volání. Obecně platí, že tento token se generuje ze služby s ověřováním specifickým pro aplikaci. Další informace o přístupových tokenech uživatelů najdete v průvodci uživatelskými přístupovými tokeny .

V tomto rychlém startu nahraďte <User_Access_Token> přístupovým tokenem uživatele vygenerovaným pro prostředek služby Azure Communication Service.


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

Spuštění hovoru pomocí agenta volání

Volání je možné provést přes agenta hovoru v teams a vyžaduje jenom zadání seznamu ID volaných a možností volání. Pro rychlý start se použijí výchozí možnosti volání bez videa a jedno volané ID z textového vstupu.

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

Přijmout hovor

Přijetí hovoru je možné provést pomocí agenta volání teams pouze pomocí odkazu na aktuální kontext.

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

Připojení k hovoru v Teams

Uživatel se může připojit k existujícímu volání předáním odkazu.

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

Připojení k hovoru v Teams s možnostmi

Můžeme se také připojit k existujícímu volání s přednastavenými možnostmi, jako je ztlumený.

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

Nastavení naslouchacího procesu příchozího volání

Aby bylo možné rozpoznat příchozí hovory a další akce, které tento uživatel neprováděje, musí být naslouchací procesy nastaveny.

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

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

Spusťte aplikaci a zavolejte robota echo.

Aplikaci teď můžete spustit pomocí tlačítka Spustit aplikaci na panelu nástrojů (Shift+F10). Ověřte, že můžete volat.8:echo123 Přehraje se předem zaznamenaná zpráva a pak zprávu znovu zopakuje.

Snímek obrazovky znázorňující dokončenou aplikaci

Začněte se službou Azure Communication Services tím, že pomocí sady SDK pro volání komunikačních služeb přidáte do své aplikace jednu službu pro videohovory. Naučíte se, jak zahájit a přijmout videohovor pomocí sady SDK pro volání služeb Azure Communication Services pro iOS pomocí identity Teams.

Ukázka kódu

Pokud chcete přeskočit na konec, můžete si tento rychlý start stáhnout jako ukázku na GitHubu.

Požadavky

Nastavení

Vytvoření projektu Xcode

V Xcode vytvořte nový projekt pro iOS a vyberte šablonu aplikace s jedním zobrazením. Tento kurz používá architekturu SwiftUI, takže byste měli nastavit jazyk na Swift a uživatelské rozhraní na SwiftUI. Během tohoto rychlého startu nebudete vytvářet testy. Nebojte se zrušit zaškrtnutí políčka Zahrnout testy.

Snímek obrazovky s oknem Nový projekt v Xcode

Instalace CocoaPods

Tento průvodce použijte k instalaci CocoaPods na Mac.

Instalace balíčku a závislostí pomocí CocoaPods

  1. Pokud chcete vytvořit Podfile aplikaci, otevřete terminál a přejděte do složky projektu a spusťte inicializaci podu.

  2. Do souboru a uložte následující kód Podfile . Viz verze podpory sady SDK.

platform :ios, '13.0'
use_frameworks!

target 'VideoCallingQuickstart' do
  pod 'AzureCommunicationCalling', '~> 2.10.0'
end
  1. Spusťte instalaci podu.

  2. Otevřete soubor .xcworkspace xcode.

Žádost o přístup k mikrofonu a fotoaparátu

Pokud chcete získat přístup k mikrofonu a fotoaparátu zařízení, musíte aktualizovat seznam vlastností informací aplikace pomocí a NSMicrophoneUsageDescription NSCameraUsageDescription. Přidruženou hodnotu nastavíte na řetězec, který obsahuje dialogové okno, které systém používá k vyžádání přístupu od uživatele.

Klikněte pravým tlačítkem myši na Info.plist položku stromu projektu a vyberte Otevřít jako > zdrojový kód. Přidejte následující řádky oddílu nejvyšší úrovně <dict> a pak soubor uložte.

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

Nastavení architektury aplikace

Otevřete soubor projektu ContentView.swift a přidejte deklaraci importu do horní části souboru pro import AzureCommunicationCalling knihovny a AVFoundation. AvFoundation se používá k zachycení zvukového oprávnění z kódu.

import AzureCommunicationCalling
import AVFoundation

Objektový model

Následující třídy a rozhraní zpracovávají některé z hlavních funkcí sady SDK pro volání služeb Azure Communication Services pro iOS.

Název Popis
CallClient Jedná se CallClient o hlavní vstupní bod volající sady SDK.
TeamsCallAgent Slouží TeamsCallAgent ke spouštění a správě hovorů.
TeamsIncomingCall Slouží TeamsIncomingCall k přijetí nebo odmítnutí příchozího hovoru v týmech.
CommunicationTokenCredential Slouží CommunicationTokenCredential jako přihlašovací údaje tokenu k vytvoření instance TeamsCallAgent.
CommunicationIdentifier Slouží CommunicationIdentifier k reprezentaci identity uživatele, což může být jedna z následujících možností: CommunicationUserIdentifiernebo PhoneNumberIdentifier CallingApplication.

Vytvoření agenta volání v Teams

Nahraďte implementaci ContentView struct některými jednoduchými ovládacími prvky uživatelského rozhraní, které uživateli umožňují zahájit a ukončit volání. Do těchto ovládacích prvků v tomto rychlém startu přidáme obchodní logiku.

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

Ověření klienta

K inicializaci TeamsCallAgent instance potřebujete přístupový token uživatele, který umožňuje provádět a přijímat volání. Pokud token nemáte k dispozici, projděte si dokumentaci k přístupovým tokenům uživatele.

Jakmile máte token, přidejte do zpětného onAppear volání následující kód .ContentView.swift Musíte nahradit <USER ACCESS TOKEN> platným přístupovým tokenem uživatele pro váš prostředek:

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

Inicializace agenta CallAgent teams a přístup k Správce zařízení

Chcete-li vytvořit instanci z objektu TeamsCallAgent CallClient, použijte metodu callClient.createTeamsCallAgent , která asynchronně vrátí TeamsCallAgent objekt po inicializaci. DeviceManager umožňuje vytvořit výčet místních zařízení, která se dají použít při volání k přenosu zvukových datových proudů nebo datových proudů videa. Umožňuje také požádat uživatele o oprávnění pro přístup k mikrofonu nebo fotoaparátu.

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

Požádat o oprávnění

Do zpětného onAppear volání musíme přidat následující kód, abychom požádali o oprávnění pro zvuk a video.

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

Umístění odchozího hovoru

Metoda startCall je nastavena jako akce, která se provádí při klepnutí na tlačítko Zahájit volání. V tomto rychlém startu jsou odchozí hovory ve výchozím nastavení jenom zvuk. Abychom mohli zahájit hovor s videem, musíme ho nastavit VideoOptions LocalVideoStream a předat ho startCallOptions , abychom pro hovor nastavili počáteční možnosti.

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

Připojení ke schůzce Teams

Metoda join umožňuje uživateli připojit se ke schůzce týmů.

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 a RemoteParticipantObserver slouží ke správě událostí uprostřed hovoru a vzdálených účastníků. Nastavíme pozorovatele ve setTeamsCallAndObserver funkci.

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

Přijetí příchozího hovoru

Příchozí hovor přijmete tak, že implementujete TeamsIncomingCallHandler zobrazení banneru příchozího hovoru, aby bylo možné hovor přijmout nebo odmítnout. Vložte následující implementaci do TeamsIncomingCallHandler.swiftsouboru .

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

Potřebujeme vytvořit instanci tak, že do zpětného TeamsIncomingCallHandler onAppear ContentView.swiftvolání přidáme následující kód:

Nastavte delegáta TeamsCallAgent na po TeamsCallAgent úspěšném vytvoření:

self.teamsCallAgent!.delegate = incomingCallHandler

Jakmile dojde k příchozímu hovoru, zavolá funkcishowIncomingCallBanner, TeamsIncomingCallHandler která se má zobrazit answer a decline tlačítko.

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

Akce připojené answer k následujícímu kódu a decline jsou implementovány jako následující kód. Abychom mohli hovor přijmout pomocí videa, musíme zapnout místní video a nastavit možnosti AcceptCallOptions s 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.
    }
}

Přihlášení k odběru událostí

Můžeme implementovat TeamsCallObserver třídu pro přihlášení k odběru kolekce událostí, které mají být upozorněny při změně hodnot během volání.

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

Spuštění kódu

Aplikaci můžete sestavit a spustit v simulátoru iOS tak, že vyberete Spuštění produktu > nebo pomocí klávesové zkratky (⌘-R).

Vyčištění prostředků

Pokud chcete vyčistit a odebrat předplatné služby Communication Services, můžete odstranit prostředek nebo skupinu prostředků. Odstraněním skupiny prostředků se odstraní také všechny ostatní prostředky, které jsou k ní přidružené. Přečtěte si další informace o čištění prostředků.

Další kroky

Další informace najdete v následujících článcích: