Udostępnij za pośrednictwem


Szybki start: dodawanie połączenia wideo 1:1 jako użytkownik usługi Teams do aplikacji

Rozpocznij pracę z usługami Azure Communication Services przy użyciu zestawu SDK wywołującego usługi Communication Services, aby dodać połączenie głosowe i wideo 1:1 do aplikacji. Dowiesz się, jak uruchomić i odpowiedzieć na połączenie przy użyciu zestawu SDK wywołującego usługi Azure Communication Services dla języka JavaScript.

Przykładowy kod

Jeśli chcesz przejść do końca, możesz pobrać ten przewodnik Szybki start jako przykład w usłudze GitHub.

Wymagania wstępne

Konfigurowanie

Tworzenie nowej aplikacji Node.js

Otwórz terminal lub okno polecenia utwórz nowy katalog dla aplikacji i przejdź do katalogu.

mkdir calling-quickstart && cd calling-quickstart

Uruchom polecenie npm init -y , aby utworzyć plik package.json z ustawieniami domyślnymi.

npm init -y

Instalowanie pakietu

npm install Użyj polecenia , aby zainstalować zestaw SDK wywołujący usługi Azure Communication Services dla języka JavaScript.

Ważne

W tym przewodniku Szybki start jest używana najnowsza wersja zestawu SDK wywołującego usługi Azure Communication Services.

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

Konfigurowanie struktury aplikacji

Ten przewodnik Szybki start używa pakietu webpack do tworzenia pakietów zawartości aplikacji. Uruchom następujące polecenie, aby zainstalować webpackpakiety i webpack-cli webpack-dev-server npm i wyświetlić je jako zależności programistyczne w pliku package.json:

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

index.html Utwórz plik w katalogu głównym projektu. Użyjemy tego pliku do skonfigurowania podstawowego układu, który umożliwi użytkownikowi umieszczenie połączenia wideo 1:1.

Oto kod:

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

Usługa Azure Communication Services wywołująca model obiektów zestawu WEB SDK

Następujące klasy i interfejsy obsługują niektóre główne funkcje zestawu AZURE Communication Services Calling SDK:

Nazwa/nazwisko opis
CallClient Główny punkt wejścia do zestawu SDK wywołującego.
AzureCommunicationTokenCredential Implementuje CommunicationTokenCredential interfejs używany do tworzenia wystąpienia teamsCallAgentelementu .
TeamsCallAgent Służy do uruchamiania wywołań usługi Teams i zarządzania nimi.
DeviceManager Służy do zarządzania urządzeniami multimedialnymi.
TeamsCall Służy do reprezentowania połączenia usługi Teams
LocalVideoStream Służy do tworzenia lokalnego strumienia wideo dla urządzenia aparatu w systemie lokalnym.
RemoteParticipant Służy do reprezentowania uczestnika zdalnego w wywołaniu
RemoteVideoStream Służy do reprezentowania zdalnego strumienia wideo z uczestnika zdalnego.

Utwórz plik w katalogu głównym projektu o nazwie index.js , aby zawierał logikę aplikacji na potrzeby tego przewodnika Szybki start. Dodaj następujący kod do 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 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();
});

Dodawanie kodu serwera lokalnego webpack

Utwórz plik w katalogu głównym projektu o nazwie webpack.config.js zawierający logikę serwera lokalnego dla tego przewodnika Szybki start. Dodaj następujący kod, aby 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'
            ]
        }),
    ]
};

Uruchamianie kodu

Użyj polecenia , webpack-dev-server aby skompilować i uruchomić aplikację. Uruchom następujące polecenie, aby utworzyć pakiet hosta aplikacji na lokalnym serwerze internetowym:

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

Otwórz przeglądarkę i na dwóch kartach przejdź do http://localhost:8080/. Karty powinny wyświetlać podobny wynik jak na poniższej ilustracji: Zrzut ekranu przedstawia dwie karty w widoku domyślnym. Każda karta będzie używana dla innego użytkownika usługi Teams.

Na pierwszej karcie wprowadź prawidłowy token dostępu użytkownika. Na drugiej karcie wprowadź inny prawidłowy token dostępu użytkownika. Zapoznaj się z dokumentacją tokenu dostępu użytkownika, jeśli nie masz jeszcze dostępnych tokenów dostępu do użycia. Na obu kartach kliknij przyciski "Inicjowanie agenta połączeń". Karty powinny wyświetlać podobny wynik jak na poniższej ilustracji: Zrzut ekranu przedstawia kroki inicjowania każdego użytkownika usługi Teams na karcie przeglądarki.

Na pierwszej karcie wprowadź tożsamość użytkownika usług Azure Communication Services na drugiej karcie, a następnie wybierz przycisk "Rozpocznij połączenie". Pierwsza karta spowoduje uruchomienie połączenia wychodzącego na drugą kartę, a przycisk "Akceptuj połączenie" drugiej karty zostanie włączony: Zrzut ekranu przedstawia środowisko, gdy użytkownicy usługi Teams inicjują zestaw SDK i pokazują kroki uruchamiania wywołania drugiego użytkownika i sposobu akceptowania wywołania.

Na drugiej karcie wybierz przycisk "Akceptuj połączenie". Połączenie zostanie odebrane i nawiązane. Karty powinny wyświetlać podobny wynik jak na poniższej ilustracji: Zrzut ekranu przedstawia dwie karty z trwającym połączeniem między dwoma użytkownikami usługi Teams, z których każdy jest zalogowany na poszczególnych kartach.

Obie karty są teraz pomyślnie w wywołaniu wideo 1:1. Obaj użytkownicy mogą usłyszeć dźwięk i zobaczyć się nawzajem strumieniowo wideo.

Rozpocznij pracę z usługami Azure Communication Services przy użyciu zestawu SDK wywołującego usługi Communication Services, aby dodać połączenie głosowe i wideo 1:1 do aplikacji. Dowiesz się, jak uruchomić i odpowiedzieć na połączenie przy użyciu zestawu SDK wywołującego usługi Azure Communication Services dla systemu Windows.

Przykładowy kod

Jeśli chcesz przejść do końca, możesz pobrać ten przewodnik Szybki start jako przykład w usłudze GitHub.

Wymagania wstępne

Do wykonania kroków tego samouczka niezbędne jest spełnienie następujących wymagań wstępnych:

  • Konto platformy Azure z aktywną subskrypcją. Utwórz konto bezpłatnie.
  • Zainstaluj program Visual Studio 2022 z pakietem roboczym programowania platforma uniwersalna systemu Windows.
  • Wdrożony zasób usług komunikacyjnych. Utwórz zasób usług komunikacyjnych. Musisz zarejestrować parametry połączenia na potrzeby tego przewodnika Szybki start.
  • Token dostępu użytkownika dla usługi Azure Communication Service.
  • Uzyskaj identyfikator wątku usługi Teams dla operacji wywołania przy użyciu Eksploratora programu Graph. Przeczytaj więcej na temat tworzenia identyfikatora wątku czatu.

Konfigurowanie

Tworzenie projektu

W programie Visual Studio utwórz nowy projekt przy użyciu szablonu Pusta aplikacja (uniwersalny system Windows), aby skonfigurować jednostronicową aplikację platforma uniwersalna systemu Windows (UWP).

Zrzut ekranu przedstawiający okno Nowy projekt platformy UWP w programie Visual Studio.

Instalowanie pakietu

Wybierz projekt prawym przyciskiem i przejdź do Manage Nuget Packages strony , aby zainstalować Azure.Communication.Calling.WindowsClient 1.2.0-beta.1 lub superior. Upewnij się, że zaznaczono opcję Uwzględnij wersję Preleased.

Żądanie dostępu

Przejdź do Package.appxmanifest strony i wybierz pozycję Capabilities. Sprawdź Internet (Client) i Internet (Client & Server) , aby uzyskać dostęp przychodzący i wychodzący do Internetu. Sprawdź Microphone , czy chcesz uzyskać dostęp do kanału audio mikrofonu i Webcam uzyskać dostęp do kanału informacyjnego wideo aparatu.

Zrzut ekranu przedstawiający żądanie dostępu do Internetu i mikrofonu w programie Visual Studio.

Konfigurowanie struktury aplikacji

Musimy skonfigurować podstawowy układ, aby dołączyć naszą logikę. Aby umieścić wywołanie wychodzące, musimy TextBox podać identyfikator użytkownika wywoływanego. Potrzebujemy Start/Join call również przycisku i Hang up przycisku. Pola Mute wyboru i BackgroundBlur są również uwzględnione w tym przykładzie, aby zademonstrować funkcje przełączania stanów dźwięku i efektów wideo.

MainPage.xaml Otwórz projekt i dodaj Grid węzeł do pliku 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>

Otwórz plik MainPage.xaml.cs i zastąp zawartość następującą implementacją:

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 obiektów

W następnej tabeli wymieniono klasy i interfejsy obsługujące niektóre główne funkcje zestawu SDK wywołującego usługi Azure Communication Services:

Nazwa/nazwisko opis
CallClient Jest CallClient to główny punkt wejścia do zestawu SDK wywołującego.
TeamsCallAgent Służy do uruchamiania TeamsCallAgent wywołań i zarządzania nimi.
TeamsCommunicationCall Służy TeamsCommunicationCall do zarządzania trwającym połączeniem.
CallTokenCredential Element CallTokenCredential jest używany jako poświadczenie tokenu w celu utworzenia wystąpienia elementu TeamsCallAgent.
CallIdentifier Element CallIdentifier służy do reprezentowania tożsamości użytkownika, która może być jedną z następujących opcji: MicrosoftTeamsUserCallIdentifier, UserCallIdentifier, PhoneNumberCallIdentifier itp.

Uwierzytelnianie użytkownika

Zainicjuj TeamsCallAgent wystąpienie przy użyciu tokenu dostępu użytkownika, które umożliwia nam wykonywanie i odbieranie wywołań, a opcjonalnie uzyskiwanie wystąpienia deviceManager w celu wykonywania zapytań dotyczących konfiguracji urządzeń klienckich.

W kodzie zastąp <AUTHENTICATION_TOKEN> element tokenem dostępu użytkownika. Jeśli nie masz jeszcze dostępnego tokenu dostępu, zapoznaj się z dokumentacją tokenu dostępu użytkownika.

Dodaj InitCallAgentAndDeviceManagerAsync funkcję , która uruchamia zestaw SDK. Ten pomocnik można dostosować w celu spełnienia wymagań aplikacji.

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

Rozpoczynanie rozmowy

Dodaj implementację do CallButton_Click elementu , aby uruchomić różne rodzaje wywołań za pomocą utworzonego teamsCallAgent obiektu oraz podłączyć RemoteParticipantsUpdated programy StateChanged obsługi zdarzeń w TeamsCommunicationCall obiekcie.

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

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

Kończ połączenie

Zakończ bieżące wywołanie po kliknięciu Hang up przycisku. Dodaj implementację do HangupButton_Click, aby zakończyć wywołanie, i zatrzymać podgląd i strumienie wideo.

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

Przełączanie wyciszenia/odłączania dźwięku

Wycisz wychodzący dźwięk po kliknięciu Mute przycisku. Dodaj implementację do MuteLocal_Click, aby wyciszyć wywołanie.

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

Uruchamianie połączenia

StartTeamsCallOptions Po uzyskaniu TeamsCallAgent obiektu można go użyć do zainicjowania wywołania usługi Teams:

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

Akceptowanie połączenia przychodzącego

TeamsIncomingCallReceived Ujście zdarzeń jest konfigurowane w pomocniku InitCallAgentAndDeviceManagerAsyncbootstrap zestawu SDK.

    this.teamsCallAgent.IncomingCallReceived += OnIncomingCallAsync;

Aplikacja ma możliwość skonfigurowania sposobu akceptowania połączenia przychodzącego, takiego jak rodzaje strumieni wideo i 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;
        }

Dołączanie do połączenia usługi Teams

Użytkownik może również dołączyć istniejące wywołanie, przekazując łącze

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

Monitorowanie zdarzenia zmiany stanu wywołania i reagowanie na nie

StateChanged zdarzenie na TeamsCommunicationCall obiekcie jest wyzwalane, gdy transakcje wywołania w toku z jednego stanu do innego. Aplikacja oferuje możliwości odzwierciedlenia zmian stanu w interfejsie użytkownika lub wstawiania logiki biznesowej.

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

Uruchamianie kodu

Możesz skompilować i uruchomić kod w programie Visual Studio. W przypadku platform rozwiązań obsługujemy ARM64platformy , x64 i x86.

Możesz wykonać wywołanie wychodzące, podając identyfikator użytkownika w polu tekstowym i klikając Start Call/Join przycisk. Wywoływanie 8:echo123 łączy Cię z botem echo. Ta funkcja doskonale nadaje się do rozpoczęcia pracy i sprawdzania, czy urządzenia audio działają.

Zrzut ekranu przedstawiający uruchamianie aplikacji Szybki start platformy UWP

Rozpocznij pracę z usługami Azure Communication Services przy użyciu zestawu SDK wywołującego usługi Communication Services, aby dodać połączenie głosowe i wideo 1:1 do aplikacji. Dowiesz się, jak uruchomić i odpowiedzieć na połączenie przy użyciu zestawu SDK wywołującego usługi Azure Communication Services dla języka Java.

Przykładowy kod

Jeśli chcesz przejść do końca, możesz pobrać ten przewodnik Szybki start jako przykład w usłudze GitHub.

Wymagania wstępne

  • Konto platformy Azure z aktywną subskrypcją. Utwórz konto bezpłatnie.
  • Program Android Studio do tworzenia aplikacji systemu Android.
  • Wdrożony zasób usług komunikacyjnych. Utwórz zasób usług komunikacyjnych. Musisz zarejestrować parametry połączenia na potrzeby tego przewodnika Szybki start.
  • Token dostępu użytkownika dla usługi Azure Communication Service.
  • Uzyskaj identyfikator wątku usługi Teams dla operacji wywołania przy użyciu Eksploratora programu Graph. Przeczytaj więcej na temat tworzenia identyfikatora wątku czatu.

Konfigurowanie

Tworzenie aplikacji systemu Android z pustym działaniem

W programie Android Studio wybierz pozycję Uruchom nowy projekt programu Android Studio.

Zrzut ekranu przedstawiający przycisk

Wybierz szablon projektu "Puste działanie" w obszarze "Telefon i tablet".

Zrzut ekranu przedstawiający opcję

Wybierz pozycję Minimalny zestaw SDK "API 26: Android 8.0 (Oreo)" lub nowszy.

Zrzut ekranu przedstawiający opcję

Instalowanie pakietu

Znajdź plik build.gradle na poziomie projektu i upewnij się, że dodano mavenCentral() je do listy repozytoriów w obszarze buildscript i allprojects

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

Następnie w kompilacji.gradle na poziomie modułu dodaj następujące wiersze do sekcji zależności i systemu 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'
    ...
}

Dodawanie uprawnień do manifestu aplikacji

Aby zażądać uprawnień wymaganych do wykonania wywołania, należy je zadeklarować w manifeście aplikacji (app/src/main/AndroidManifest.xml). Zastąp zawartość pliku następującym kodem:

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

Konfigurowanie układu aplikacji

Potrzebne są dwa dane wejściowe: wprowadzanie tekstu dla identyfikatora wywoływanego i przycisk do umieszczenia wywołania. Te dane wejściowe można dodawać za pośrednictwem projektanta lub edytując plik XML układu. Utwórz przycisk z identyfikatorem call_button i tekstem wejściowym callee_id. Przejdź do (app/src/main/res/layout/activity_main.xml) i zastąp zawartość pliku następującym kodem:

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

Tworzenie szkieletu i powiązań głównego działania

Po utworzeniu układu można dodać powiązania, a także podstawowe szkielety działania. Działanie obsługuje żądanie uprawnień środowiska uruchomieniowego, tworzenie agenta wywołań zespołów i umieszczanie wywołania po naciśnięciu przycisku. Każda z nich jest omówiona we własnej sekcji. Metoda onCreate jest zastępowana do wywoływania getAllPermissions i createTeamsAgent dodawania powiązań dla przycisku wywołania. To zdarzenie występuje tylko raz po utworzeniu działania. Aby uzyskać więcej informacji, onCreatezobacz przewodnik Understand the Activity Lifecycle (Omówienie cyklu życia działania).

Przejdź do MainActivity.java i zastąp zawartość następującym kodem:

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

Żądanie uprawnień w czasie wykonywania

W przypadku systemu Android w wersji 6.0 lub nowszej (poziom interfejsu API 23) i targetSdkVersion 23 lub nowszej uprawnienia są przyznawane w czasie wykonywania zamiast podczas instalowania aplikacji. Aby można było go obsługiwać, getAllPermissions można zaimplementować wywołanie ActivityCompat.checkSelfPermission i ActivityCompat.requestPermissions dla każdego wymaganego uprawnienia.

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

Uwaga

Podczas projektowania aplikacji należy wziąć pod uwagę, kiedy należy zażądać tych uprawnień. Uprawnienia powinny być wymagane, ponieważ są potrzebne, a nie przed upływem czasu. Aby uzyskać więcej informacji, zobacz Przewodnik po uprawnieniach systemu Android.

Model obiektów

Następujące klasy i interfejsy obsługują niektóre główne funkcje zestawu AZURE Communication Services Calling SDK:

Nazwa/nazwisko opis
CallClient Jest CallClient to główny punkt wejścia do zestawu SDK wywołującego.
TeamsCallAgent Służy do uruchamiania TeamsCallAgent wywołań i zarządzania nimi.
TeamsCall Element TeamsCall używany do reprezentowania wywołania usługi Teams.
CommunicationTokenCredential Element CommunicationTokenCredential jest używany jako poświadczenie tokenu w celu utworzenia wystąpienia elementu TeamsCallAgent.
CommunicationIdentifier Element CommunicationIdentifier jest używany jako inny typ uczestnika, który może być częścią połączenia.

Tworzenie agenta na podstawie tokenu dostępu użytkownika

Za pomocą tokenu użytkownika można utworzyć wystąpienie uwierzytelnionego agenta wywołania. Zazwyczaj ten token jest generowany na podstawie usługi z uwierzytelnianiem specyficznym dla aplikacji. Aby uzyskać więcej informacji na temat tokenów dostępu użytkowników, zapoznaj się z przewodnikiem Tokeny dostępu użytkowników.

W tym przewodniku Szybki start zastąp <User_Access_Token> ciąg tokenem dostępu użytkownika wygenerowanym dla zasobu usługi 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();
    }
}

Uruchamianie wywołania przy użyciu agenta wywołania

Umieszczenie połączenia można wykonać za pośrednictwem agenta połączeń zespołów i wymaga podania listy identyfikatorów wywoływanych i opcji wywołania. W przypadku przewodnika Szybki start są używane domyślne opcje wywołania bez wideo i jeden wywoływany identyfikator z wprowadzania tekstu.

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

Odpowiedz na połączenie

Akceptowanie połączenia można wykonać za pomocą agenta wywołania teams, używając tylko odwołania do bieżącego kontekstu.

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

Dołączanie do połączenia usługi Teams

Użytkownik może dołączyć do istniejącego wywołania, przekazując link.

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

Dołączanie do połączenia usługi Teams przy użyciu opcji

Możemy również dołączyć istniejące wywołanie z opcjami ustawień wstępnych, takimi jak wyciszenie.

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

Konfigurowanie odbiornika połączeń przychodzących

Aby można było wykrywać połączenia przychodzące i inne akcje, które nie są wykonywane przez tego użytkownika, należy skonfigurować odbiorniki.

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

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

Uruchamianie aplikacji i wywoływanie bota echo

Teraz można uruchomić aplikację przy użyciu przycisku "Uruchom aplikację" na pasku narzędzi (Shift+F10). Sprawdź, czy możesz umieścić wywołania, wywołując metodę 8:echo123. Wstępnie nagrany komunikat jest odtwarzany, a następnie powtarzać wiadomość z powrotem do Ciebie.

Zrzut ekranu przedstawiający ukończoną aplikację.

Rozpocznij pracę z usługami Azure Communication Services przy użyciu zestawu SDK wywołującego usługi Communication Services, aby dodać go do jednej usługi wideo wywołującej aplikację. Dowiesz się, jak rozpocząć i odpowiedzieć na połączenie wideo przy użyciu zestawu SDK wywołującego usługi Azure Communication Services dla systemu iOS przy użyciu tożsamości usługi Teams.

Przykładowy kod

Jeśli chcesz przejść do końca, możesz pobrać ten przewodnik Szybki start jako przykład w usłudze GitHub.

Wymagania wstępne

  • Uzyskaj konto platformy Azure z aktywną subskrypcją. Utwórz konto bezpłatnie.
  • Komputer Mac z uruchomionym programem Xcode wraz z prawidłowym certyfikatem dewelopera zainstalowanym w pęku kluczy.
  • Utwórz aktywny zasób usług komunikacyjnych. Utwórz zasób usług komunikacyjnych. Musisz zarejestrować parametry połączenia na potrzeby tego przewodnika Szybki start.
  • Token dostępu użytkownika dla usługi Azure Communication Service.
  • Uzyskaj identyfikator wątku usługi Teams dla operacji wywołania przy użyciu Eksploratora programu Graph. Dowiedz się więcej o sposobie tworzenia identyfikatora wątku czatu

Konfigurowanie

Tworzenie projektu Xcode

W programie Xcode utwórz nowy projekt systemu iOS i wybierz szablon Aplikacja z jednym widokiem. W tym samouczku jest używana struktura SwiftUI, dlatego należy ustawić język na swift i interfejs użytkownika na swiftUI. Podczas tego przewodnika Szybki start nie utworzysz testów. Możesz usunąć zaznaczenie pola wyboru Uwzględnij testy.

Zrzut ekranu przedstawiający okno Nowy projekt w programie Xcode.

Instalowanie platformy CocoaPods

Skorzystaj z tego przewodnika, aby zainstalować narzędzie CocoaPods na komputerze Mac.

Instalowanie pakietu i zależności za pomocą narzędzia CocoaPods

  1. Aby utworzyć aplikację Podfile dla aplikacji, otwórz terminal i przejdź do folderu projektu i uruchom init zasobnika.

  2. Dodaj następujący kod do pliku Podfile i zapisz. Zobacz Wersje obsługi zestawu SDK.

platform :ios, '13.0'
use_frameworks!

target 'VideoCallingQuickstart' do
  pod 'AzureCommunicationCalling', '~> 2.10.0'
end
  1. Uruchom instalację zasobnika.

  2. Otwórz plik za .xcworkspace pomocą programu Xcode.

Żądanie dostępu do mikrofonu i kamery

Aby uzyskać dostęp do mikrofonu i aparatu fotograficznego urządzenia, musisz zaktualizować listę właściwości informacji aplikacji za pomocą elementu NSMicrophoneUsageDescription i NSCameraUsageDescription. Skojarzona wartość jest ustawiana na ciąg zawierający okno dialogowe używane przez system do żądania dostępu od użytkownika.

Kliknij prawym przyciskiem myszy Info.plist wpis drzewa projektu i wybierz pozycję Otwórz jako > kod źródłowy. Dodaj następujące wiersze sekcji najwyższego poziomu <dict> , a następnie zapisz plik.

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

Konfigurowanie struktury aplikacji

Otwórz plik projektu ContentView.swift i dodaj deklarację importu na początku pliku, aby zaimportować bibliotekę AzureCommunicationCalling i AVFoundation. Funkcja AVFoundation służy do przechwytywania uprawnień dźwięku z kodu.

import AzureCommunicationCalling
import AVFoundation

Model obiektów

Następujące klasy i interfejsy obsługują niektóre główne funkcje zestawu SDK wywołującego usługi Azure Communication Services dla systemu iOS.

Nazwa/nazwisko opis
CallClient Jest CallClient to główny punkt wejścia do zestawu SDK wywołującego.
TeamsCallAgent Służy do uruchamiania TeamsCallAgent wywołań i zarządzania nimi.
TeamsIncomingCall Element TeamsIncomingCall służy do akceptowania lub odrzucania połączeń przychodzących zespołów.
CommunicationTokenCredential Element CommunicationTokenCredential jest używany jako poświadczenie tokenu w celu utworzenia wystąpienia elementu TeamsCallAgent.
CommunicationIdentifier Element CommunicationIdentifier służy do reprezentowania tożsamości użytkownika, która może być jedną z następujących opcji: CommunicationUserIdentifier, PhoneNumberIdentifier lub CallingApplication.

Tworzenie agenta połączeń usługi Teams

Zastąp implementację elementu ContentView struct kilkoma prostymi kontrolkami interfejsu użytkownika, które umożliwiają użytkownikowi zainicjowanie i zakończenie wywołania. W tym przewodniku Szybki start dodamy logikę biznesową do tych kontrolek.

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

Uwierzytelnianie użytkownika

Aby zainicjować TeamsCallAgent wystąpienie, potrzebny jest token dostępu użytkownika, który umożliwia wykonywanie i odbieranie wywołań. Jeśli nie masz dostępnego tokenu dostępu, zapoznaj się z dokumentacją tokenu dostępu użytkownika.

Po utworzeniu tokenu dodaj następujący kod do wywołania zwrotnego onAppear w pliku ContentView.swift. Musisz zastąpić <USER ACCESS TOKEN> prawidłowym tokenem dostępu użytkownika dla zasobu:

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

Inicjowanie aplikacji Teams CallAgent i uzyskiwanie dostępu do Menedżer urządzeń

Aby utworzyć TeamsCallAgent wystąpienie na podstawie CallClientklasy , użyj callClient.createTeamsCallAgent metody , która asynchronicznie zwraca TeamsCallAgent obiekt po zainicjowaniu. DeviceManager Umożliwia wyliczanie urządzeń lokalnych, które mogą być używane w wywołaniu do przesyłania strumieni audio/wideo. Umożliwia również zażądanie uprawnień od użytkownika w celu uzyskania dostępu do mikrofonu/kamery.

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

Poproś o uprawnienia

Musimy dodać następujący kod do wywołania zwrotnego onAppear , aby poprosić o uprawnienia do audio i wideo.

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

Umieszczanie połączenia wychodzącego

Metoda startCall jest ustawiana jako akcja wykonywana po naciśnięciu przycisku Uruchom wywołanie. W tym przewodniku Szybki start połączenia wychodzące są domyślnie tylko audio. Aby rozpocząć połączenie za pomocą wideo, musimy ustawić VideoOptions element i LocalVideoStream przekazać go za pomocą startCallOptions , aby ustawić początkowe opcje dla wywołania.

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

Dołącz do spotkania w Teams

Metoda join umożliwia użytkownikowi dołączanie do spotkania zespołów.

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 i RemoteParticipantObserver są używane do zarządzania zdarzeniami w połowie połączenia i uczestnikami zdalnymi. Ustawiamy obserwatorów w setTeamsCallAndObserver funkcji .

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

Odpowiedz na połączenie przychodzące

Aby odpowiedzieć na połączenie przychodzące, zaimplementuj element , TeamsIncomingCallHandler aby wyświetlić baner połączenia przychodzącego, aby odpowiedzieć lub odrzucić połączenie. Umieść następującą implementację w pliku 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, 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
        }
    }
}

Musimy utworzyć wystąpienie klasy TeamsIncomingCallHandler , dodając następujący kod do wywołania zwrotnego onAppear w pliku ContentView.swift:

Ustaw pełnomocnika na TeamsCallAgent wartość po pomyślnym utworzeniu TeamsCallAgent :

self.teamsCallAgent!.delegate = incomingCallHandler

Po wywołaniu przychodzącym funkcja wywołuje funkcję showIncomingCallBanner w TeamsIncomingCallHandler celu wyświetlenia answer i decline przycisku.

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

Akcje dołączone do answer i decline są implementowane jako następujący kod. Aby odpowiedzieć na połączenie wideo, musimy włączyć lokalne wideo i ustawić opcje za AcceptCallOptions localVideoStreampomocą polecenia .

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

Subskrybowanie zdarzeń

Możemy zaimplementować klasę TeamsCallObserver , aby subskrybować kolekcję zdarzeń, aby otrzymywać powiadomienia, gdy wartości zmieniają się podczas wywołania.

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

Uruchamianie kodu

Aplikację można skompilować i uruchomić w symulatorze systemu iOS, wybierając pozycję Uruchom produkt > lub za pomocą skrótu klawiaturowego (⌘-R).

Czyszczenie zasobów

Jeśli chcesz wyczyścić i usunąć subskrypcję usług Komunikacyjnych, możesz usunąć zasób lub grupę zasobów. Usunięcie grupy zasobów powoduje również usunięcie wszelkich innych skojarzonych z nią zasobów. Dowiedz się więcej o czyszczeniu zasobów.

Następne kroki

Aby uzyskać więcej informacji, zobacz następujące artykuły: