Dela via


Snabbstart: Lägga till 1:1-videosamtal som Teams-användare i ditt program

Kom igång med Azure Communication Services med hjälp av Kommunikationstjänster som anropar SDK för att lägga till röst- och videosamtal 1:1 i din app. Du får lära dig hur du startar och svarar på ett samtal med hjälp av Azure Communication Services Calling SDK för JavaScript.

Exempelkod

Om du vill gå vidare till slutet kan du ladda ned den här snabbstarten som ett exempel på GitHub.

Förutsättningar

Konfigurera

Skapa ett nytt Node.js-program

Öppna terminalen eller kommandofönstret skapa en ny katalog för din app och navigera till katalogen.

mkdir calling-quickstart && cd calling-quickstart

Kör npm init -y för att skapa en package.json fil med standardinställningar.

npm init -y

Installera -paketet

npm install Använd kommandot för att installera Azure Communication Services Calling SDK för JavaScript.

Viktigt!

Den här snabbstarten använder den senaste versionen av Azure Communication Services Calling SDK.

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

Konfigurera appramverket

Den här snabbstarten använder webpack för att paketera programtillgångarna. Kör följande kommando för att installera , webpack-cli och webpack-dev-server npm-paketen webpackoch lista dem som utvecklingsberoenden i :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

Skapa en index.html fil i rotkatalogen för projektet. Vi använder den här filen för att konfigurera en grundläggande layout som gör att användaren kan ringa ett 1:1-videosamtal.

Här är koden:

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

Azure Communication Services Anropar web-SDK-objektmodell

Följande klasser och gränssnitt hanterar några av huvudfunktionerna i Azure Communication Services Calling SDK:

Name beskrivning
CallClient Huvudinmatningspunkten till anropande SDK.
AzureCommunicationTokenCredential Implementerar CommunicationTokenCredential gränssnittet, som används för att instansiera teamsCallAgent.
TeamsCallAgent Används för att starta och hantera Teams-anrop.
DeviceManager Används för att hantera medieenheter.
TeamsCall Används för att representera ett Teams-samtal
LocalVideoStream Används för att skapa en lokal videoström för en kameraenhet i det lokala systemet.
RemoteParticipant Används för att representera en fjärrdeltagare i samtalet
RemoteVideoStream Används för att representera en fjärrvideoström från en fjärrdeltagare.

Skapa en fil i rotkatalogen för projektet som anropas index.js för att innehålla programlogik för den här snabbstarten. Lägg till följande kod i index.js:

// Make sure to install the necessary dependencies
const { CallClient, VideoStreamRenderer, LocalVideoStream } = require('@azure/communication-calling');
const { AzureCommunicationTokenCredential } = require('@azure/communication-common');
const { AzureLogger, setLogLevel } = require("@azure/logger");
// Set the log level and output
setLogLevel('verbose');
AzureLogger.log = (...args) => {
    console.log(...args);
};
// Calling web sdk objects
let teamsCallAgent;
let deviceManager;
let call;
let incomingCall;
let localVideoStream;
let localVideoStreamRenderer;
// UI widgets
let userAccessToken = document.getElementById('user-access-token');
let calleeTeamsUserId = document.getElementById('callee-teams-user-id');
let initializeCallAgentButton = document.getElementById('initialize-teams-call-agent');
let startCallButton = document.getElementById('start-call-button');
let hangUpCallButton = document.getElementById('hangup-call-button');
let acceptCallButton = document.getElementById('accept-call-button');
let startVideoButton = document.getElementById('start-video-button');
let stopVideoButton = document.getElementById('stop-video-button');
let connectedLabel = document.getElementById('connectedLabel');
let remoteVideoContainer = document.getElementById('remoteVideoContainer');
let localVideoContainer = document.getElementById('localVideoContainer');
/**
 * Create an instance of CallClient. Initialize a TeamsCallAgent instance with a CommunicationUserCredential via created CallClient. TeamsCallAgent enables us to make outgoing calls and receive incoming calls. 
 * You can then use the CallClient.getDeviceManager() API instance to get the DeviceManager.
 */
initializeCallAgentButton.onclick = async () => {
    try {
        const callClient = new CallClient(); 
        tokenCredential = new AzureCommunicationTokenCredential(userAccessToken.value.trim());
        teamsCallAgent = await callClient.createTeamsCallAgent(tokenCredential)
        // Set up a camera device to use.
        deviceManager = await callClient.getDeviceManager();
        await deviceManager.askDevicePermission({ video: true });
        await deviceManager.askDevicePermission({ audio: true });
        // Listen for an incoming call to accept.
        teamsCallAgent.on('incomingCall', async (args) => {
            try {
                incomingCall = args.incomingCall;
                acceptCallButton.disabled = false;
                startCallButton.disabled = true;
            } catch (error) {
                console.error(error);
            }
        });
        startCallButton.disabled = false;
        initializeCallAgentButton.disabled = true;
    } catch(error) {
        console.error(error);
    }
}
/**
 * Place a 1:1 outgoing video call to a user
 * Add an event listener to initiate a call when the `startCallButton` is selected.
 * Enumerate local cameras using the deviceManager `getCameraList` API.
 * In this quickstart, we're using the first camera in the collection. Once the desired camera is selected, a
 * LocalVideoStream instance will be constructed and passed within `videoOptions` as an item within the
 * localVideoStream array to the call method. When the call connects, your application will be sending a video stream to the other participant. 
 */
startCallButton.onclick = async () => {
    try {
        const localVideoStream = await createLocalVideoStream();
        const videoOptions = localVideoStream ? { localVideoStreams: [localVideoStream] } : undefined;
        call = teamsCallAgent.startCall({ microsoftTeamsUserId: calleeTeamsUserId.value.trim() }, { videoOptions: videoOptions });
        // Subscribe to the call's properties and events.
        subscribeToCall(call);
    } catch (error) {
        console.error(error);
    }
}
/**
 * Accepting an incoming call with a video
 * Add an event listener to accept a call when the `acceptCallButton` is selected.
 * You can accept incoming calls after subscribing to the `TeamsCallAgent.on('incomingCall')` event.
 * You can pass the local video stream to accept the call with the following code.
 */
acceptCallButton.onclick = async () => {
    try {
        const localVideoStream = await createLocalVideoStream();
        const videoOptions = localVideoStream ? { localVideoStreams: [localVideoStream] } : undefined;
        call = await incomingCall.accept({ videoOptions });
        // Subscribe to the call's properties and events.
        subscribeToCall(call);
    } catch (error) {
        console.error(error);
    }
}
// Subscribe to a call obj.
// Listen for property changes and collection udpates.
subscribeToCall = (call) => {
    try {
        // Inspect the initial call.id value.
        console.log(`Call Id: ${call.id}`);
        //Subsribe to call's 'idChanged' event for value changes.
        call.on('idChanged', () => {
            console.log(`Call ID changed: ${call.id}`); 
        });
        // Inspect the initial call.state value.
        console.log(`Call state: ${call.state}`);
        // Subscribe to call's 'stateChanged' event for value changes.
        call.on('stateChanged', async () => {
            console.log(`Call state changed: ${call.state}`);
            if(call.state === 'Connected') {
                connectedLabel.hidden = false;
                acceptCallButton.disabled = true;
                startCallButton.disabled = true;
                hangUpCallButton.disabled = false;
                startVideoButton.disabled = false;
                stopVideoButton.disabled = false;
            } else if (call.state === 'Disconnected') {
                connectedLabel.hidden = true;
                startCallButton.disabled = false;
                hangUpCallButton.disabled = true;
                startVideoButton.disabled = true;
                stopVideoButton.disabled = true;
                console.log(`Call ended, call end reason={code=${call.callEndReason.code}, subCode=${call.callEndReason.subCode}}`);
            }   
        });
        call.on('isLocalVideoStartedChanged', () => {
            console.log(`isLocalVideoStarted changed: ${call.isLocalVideoStarted}`);
        });
        console.log(`isLocalVideoStarted: ${call.isLocalVideoStarted}`);
        call.localVideoStreams.forEach(async (lvs) => {
            localVideoStream = lvs;
            await displayLocalVideoStream();
        });
        call.on('localVideoStreamsUpdated', e => {
            e.added.forEach(async (lvs) => {
                localVideoStream = lvs;
                await displayLocalVideoStream();
            });
            e.removed.forEach(lvs => {
               removeLocalVideoStream();
            });
        });
        
        // Inspect the call's current remote participants and subscribe to them.
        call.remoteParticipants.forEach(remoteParticipant => {
            subscribeToRemoteParticipant(remoteParticipant);
        });
        // Subscribe to the call's 'remoteParticipantsUpdated' event to be
        // notified when new participants are added to the call or removed from the call.
        call.on('remoteParticipantsUpdated', e => {
            // Subscribe to new remote participants that are added to the call.
            e.added.forEach(remoteParticipant => {
                subscribeToRemoteParticipant(remoteParticipant)
            });
            // Unsubscribe from participants that are removed from the call
            e.removed.forEach(remoteParticipant => {
                console.log('Remote participant removed from the call.');
            });
        });
    } catch (error) {
        console.error(error);
    }
}
// Subscribe to a remote participant obj.
// Listen for property changes and collection udpates.
subscribeToRemoteParticipant = (remoteParticipant) => {
    try {
        // Inspect the initial remoteParticipant.state value.
        console.log(`Remote participant state: ${remoteParticipant.state}`);
        // Subscribe to remoteParticipant's 'stateChanged' event for value changes.
        remoteParticipant.on('stateChanged', () => {
            console.log(`Remote participant state changed: ${remoteParticipant.state}`);
        });
        // Inspect the remoteParticipants's current videoStreams and subscribe to them.
        remoteParticipant.videoStreams.forEach(remoteVideoStream => {
            subscribeToRemoteVideoStream(remoteVideoStream)
        });
        // Subscribe to the remoteParticipant's 'videoStreamsUpdated' event to be
        // notified when the remoteParticiapant adds new videoStreams and removes video streams.
        remoteParticipant.on('videoStreamsUpdated', e => {
            // Subscribe to newly added remote participant's video streams.
            e.added.forEach(remoteVideoStream => {
                subscribeToRemoteVideoStream(remoteVideoStream)
            });
            // Unsubscribe from newly removed remote participants' video streams.
            e.removed.forEach(remoteVideoStream => {
                console.log('Remote participant video stream was removed.');
            })
        });
    } catch (error) {
        console.error(error);
    }
}
/**
 * Subscribe to a remote participant's remote video stream obj.
 * You have to subscribe to the 'isAvailableChanged' event to render the remoteVideoStream. If the 'isAvailable' property
 * changes to 'true' a remote participant is sending a stream. Whenever the availability of a remote stream changes
 * you can choose to destroy the whole 'Renderer' a specific 'RendererView' or keep them. Displaying RendererView without a video stream will result in a blank video frame. 
 */
subscribeToRemoteVideoStream = async (remoteVideoStream) => {
    // Create a video stream renderer for the remote video stream.
    let videoStreamRenderer = new VideoStreamRenderer(remoteVideoStream);
    let view;
    const renderVideo = async () => {
        try {
            // Create a renderer view for the remote video stream.
            view = await videoStreamRenderer.createView();
            // Attach the renderer view to the UI.
            remoteVideoContainer.hidden = false;
            remoteVideoContainer.appendChild(view.target);
        } catch (e) {
            console.warn(`Failed to createView, reason=${e.message}, code=${e.code}`);
        }	
    }
    
    remoteVideoStream.on('isAvailableChanged', async () => {
        // Participant has switched video on.
        if (remoteVideoStream.isAvailable) {
            await renderVideo();
        // Participant has switched video off.
        } else {
            if (view) {
                view.dispose();
                view = undefined;
            }
        }
    });
    // Participant has video on initially.
    if (remoteVideoStream.isAvailable) {
        await renderVideo();
    }
}
// Start your local video stream.
// This will send your local video stream to remote participants so they can view it.
startVideoButton.onclick = async () => {
    try {
        const localVideoStream = await createLocalVideoStream();
        await call.startVideo(localVideoStream);
    } catch (error) {
        console.error(error);
    }
}
// Stop your local video stream.
// This will stop your local video stream from being sent to remote participants.
stopVideoButton.onclick = async () => {
    try {
        await call.stopVideo(localVideoStream);
    } catch (error) {
        console.error(error);
    }
}
/**
 * To render a LocalVideoStream, you need to create a new instance of VideoStreamRenderer, and then
 * create a new VideoStreamRendererView instance using the asynchronous createView() method.
 * You may then attach view.target to any UI element. 
 */
// Create a local video stream for your camera device
createLocalVideoStream = async () => {
    const camera = (await deviceManager.getCameras())[0];
    if (camera) {
        return new LocalVideoStream(camera);
    } else {
        console.error(`No camera device found on the system`);
    }
}
// Display your local video stream preview in your UI
displayLocalVideoStream = async () => {
    try {
        localVideoStreamRenderer = new VideoStreamRenderer(localVideoStream);
        const view = await localVideoStreamRenderer.createView();
        localVideoContainer.hidden = false;
        localVideoContainer.appendChild(view.target);
    } catch (error) {
        console.error(error);
    } 
}
// Remove your local video stream preview from your UI
removeLocalVideoStream = async() => {
    try {
        localVideoStreamRenderer.dispose();
        localVideoContainer.hidden = true;
    } catch (error) {
        console.error(error);
    } 
}
// End the current call
hangUpCallButton.addEventListener("click", async () => {
    // end the current call
    await call.hangUp();
});

Lägg till den lokala serverkoden för webpack

Skapa en fil i rotkatalogen i projektet med namnet webpack.config.js för att innehålla den lokala serverlogik som finns i den här snabbstarten. Lägg till följande kod i 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'
            ]
        }),
    ]
};

Kör koden

Använd för webpack-dev-server att skapa och köra din app. Kör följande kommando för att paketa programvärden i en lokal webbserver:

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

Öppna webbläsaren och gå till två flikar http://localhost:8080/. Flikar bör visa liknande resultat som i följande bild: Skärmbild som visar två flikar i standardvyn. Varje flik används för olika Teams-användare.

På den första fliken anger du en giltig användaråtkomsttoken. På den andra fliken anger du en annan giltig användaråtkomsttoken. Se dokumentationen för användaråtkomsttoken om du inte redan har åtkomsttoken att använda. På båda flikarna klickar du på knapparna "Initiera samtalsagent". Flikar bör visa liknande resultat som i följande bild: Skärmbild som visar steg för att initiera varje Teams-användare på webbläsarfliken.

På den första fliken anger du användaridentiteten för Azure Communication Services på den andra fliken och väljer knappen "Starta samtal". Den första fliken startar det utgående samtalet till den andra fliken och den andra flikens "Acceptera samtal"-knappen aktiveras: Skärmbild som visar upplevelsen när Teams-användare initierar SDK och visar steg för att starta ett anrop till en andra användare och hur man accepterar samtalet.

På den andra fliken väljer du knappen "Acceptera samtal". Samtalet besvaras och ansluts. Flikar bör visa liknande resultat som i följande bild: Skärmbild som visar två flikar, med pågående anrop mellan två Teams-användare, som var och en loggas på den enskilda fliken.

Båda flikarna är nu i ett 1:1-videosamtal. Båda användarna kan höra varandras ljud och se varandras videoström.

Kom igång med Azure Communication Services med hjälp av Kommunikationstjänster som anropar SDK för att lägga till röst- och videosamtal 1:1 i din app. Du lär dig hur du startar och svarar på ett samtal med hjälp av Azure Communication Services Calling SDK för Windows.

Exempelkod

Om du vill gå vidare till slutet kan du ladda ned den här snabbstarten som ett exempel på GitHub.

Förutsättningar

För att slutföra den här självstudien, finns följande förhandskrav:

Konfigurera

Skapa projektet

I Visual Studio skapar du ett nytt projekt med mallen Tom app (Universell Windows) för att konfigurera en enkelsidig Universell Windows-plattform-app (UWP).

Skärmbild som visar fönstret Nytt UWP-projekt i Visual Studio.

Installera -paketet

Välj projektet och gå till för att Manage Nuget Packages installera Azure.Communication.Calling.WindowsClient1.2.0-beta.1 eller superior. Kontrollera att Inkludera förhyrd är markerat.

Begär åtkomst

Gå till Package.appxmanifest och välj Capabilities. Kontrollera Internet (Client) och Internet (Client & Server) för att få inkommande och utgående åtkomst till Internet. Kontrollera Microphone om du vill komma åt mikrofonens ljudflöde och Webcam få åtkomst till kamerans videoflöde.

Skärmbild som visar begäran om åtkomst till Internet och mikrofon i Visual Studio.

Konfigurera appramverket

Vi måste konfigurera en grundläggande layout för att koppla vår logik. För att kunna ringa ett utgående samtal behöver vi ett TextBox för att ange användar-ID:t för den anropade. Vi behöver också en Start/Join call knapp och en Hang up knapp. En Mute och en BackgroundBlur kryssrutor ingår också i det här exemplet för att demonstrera funktionerna i att växla ljudtillstånd och videoeffekter.

MainPage.xaml Öppna projektet och lägg till noden i Grid :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>

MainPage.xaml.cs Öppna och ersätt innehållet med följande implementering:

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

Objektmodell

I nästa tabell visas klasserna och gränssnitten som hanterar några av de viktigaste funktionerna i Azure Communication Services Calling SDK:

Name beskrivning
CallClient CallClient är den viktigaste startpunkten för anropande SDK.
TeamsCallAgent TeamsCallAgent Används för att starta och hantera anrop.
TeamsCommunicationCall TeamsCommunicationCall Används för att hantera ett pågående anrop.
CallTokenCredential CallTokenCredential Används som tokenautentiseringsuppgifter för att instansiera TeamsCallAgent.
CallIdentifier CallIdentifier Används för att representera användarens identitet, vilket kan vara något av följande alternativ: MicrosoftTeamsUserCallIdentifier, UserCallIdentifierosvPhoneNumberCallIdentifier.

Autentisera klienten

Initiera en TeamsCallAgent instans med en användaråtkomsttoken som gör att vi kan göra och ta emot anrop och eventuellt hämta en DeviceManager-instans för att fråga efter klientenhetskonfigurationer.

I koden ersätter du <AUTHENTICATION_TOKEN> med en användaråtkomsttoken. Se dokumentationen för användaråtkomsttoken om du inte redan har en tillgänglig token.

Lägg till InitCallAgentAndDeviceManagerAsync funktion, som startar SDK:t. Den här hjälpen kan anpassas för att uppfylla kraven för ditt program.

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

Starta ett samtal

Lägg till implementeringen i CallButton_Click för att starta olika typer av anrop med objektet teamsCallAgent vi skapade och koppla in RemoteParticipantsUpdated och StateChanged händelsehanterare för TeamsCommunicationCall objektet.

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

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

Avsluta samtal

Avsluta det aktuella samtalet när Hang up knappen klickas. Lägg till implementeringen i HangupButton_Click för att avsluta ett anrop och stoppa förhandsgranskningen och videoströmmarna.

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

Växla ljudavstängning/ljudavstängning

Stäng av det utgående ljudet när Mute knappen klickas. Lägg till implementeringen i MuteLocal_Click för att stänga av anropet.

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

Starta samtalet

När ett StartTeamsCallOptions objekt har hämtats TeamsCallAgent kan användas för att initiera Teams-anropet:

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

Acceptera ett inkommande samtal

TeamsIncomingCallReceived händelsemottagaren har konfigurerats i SDK bootstrap-hjälpen InitCallAgentAndDeviceManagerAsync.

    this.teamsCallAgent.IncomingCallReceived += OnIncomingCallAsync;

Programmet har möjlighet att konfigurera hur det inkommande samtalet ska godkännas, till exempel video- och ljudströmstyper.

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

            var acceptteamsCallOptions = new AcceptTeamsCallOptions() { };

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

Ansluta till ett Teams-samtal

Användaren kan också ansluta till ett befintligt anrop genom att skicka en länk

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

Övervaka och svara på ändringshändelse för samtalstillstånd

StateChanged händelsen på TeamsCommunicationCall objektet utlöses när en pågående anropstransaktioner från ett tillstånd till ett annat. Programmet erbjuds möjligheter att återspegla tillståndsändringarna i användargränssnittet eller infoga affärslogik.

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

Kör koden

Du kan skapa och köra koden i Visual Studio. För lösningsplattformar stöder ARM64vi , x64 och x86.

Du kan ringa ett utgående anrop genom att ange ett användar-ID i textfältet och klicka på Start Call/Join knappen. Samtal 8:echo123 ansluter dig till en ekorobot. Den här funktionen är bra för att komma igång och verifiera att ljudenheterna fungerar.

Skärmbild som visar hur du kör UWP-snabbstartsappen

Kom igång med Azure Communication Services med hjälp av Kommunikationstjänster som anropar SDK för att lägga till röst- och videosamtal 1:1 i din app. Du får lära dig hur du startar och svarar på ett samtal med hjälp av Azure Communication Services Calling SDK för Java.

Exempelkod

Om du vill gå vidare till slutet kan du ladda ned den här snabbstarten som ett exempel på GitHub.

Förutsättningar

Konfigurera

Skapa en Android-app med en tom aktivitet

Från Android Studio väljer du Starta ett nytt Android Studio-projekt.

Skärmbild som visar knappen Starta ett nytt Android Studio-projekt valt i Android Studio.

Välj projektmallen "Tom aktivitet" under "Telefon och surfplatta".

Skärmbild som visar alternativet

Välj Minsta SDK för "API 26: Android 8.0 (Oreo)" eller senare.

Skärmbild som visar alternativet

Installera -paketet

Leta upp din projektnivå build.gradle och se till att lägga mavenCentral() till i listan över lagringsplatser under buildscript och allprojects

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

I modulnivån build.gradle lägger du sedan till följande rader i avsnitten beroenden och 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'
    ...
}

Lägga till behörigheter i programmanifestet

För att kunna begära behörigheter som krävs för att göra ett anrop måste de deklareras i programmanifestet (app/src/main/AndroidManifest.xml). Ersätt innehållet i filen med följande kod:

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

Konfigurera layouten för appen

Två indata behövs: en textinmatning för samtals-ID:t och en knapp för att ringa samtalet. Dessa indata kan läggas till via designern eller genom att redigera xml-layouten. Skapa en knapp med ett ID call_button för och en textinmatning på callee_id. Gå till (app/src/main/res/layout/activity_main.xml) och ersätt innehållet i filen med följande kod:

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

Skapa huvudaktivitetens byggnadsställningar och bindningar

När layouten har skapats kan bindningarna läggas till samt den grundläggande scaffoldingen för aktiviteten. Aktiviteten hanterar begäran om körningsbehörigheter, skapar teams-samtalsagenten och anropar när knappen trycks ned. Var och en omfattas av sitt eget avsnitt. Metoden onCreate åsidosättas för att anropa getAllPermissions och createTeamsAgent lägga till bindningar för anropsknappen. Den här händelsen inträffar bara en gång när aktiviteten skapas. Mer information onCreatefinns i guiden Förstå aktivitetslivscykeln.

Gå till MainActivity.java och ersätt innehållet med följande kod:

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

Begär behörigheter vid körning

För Android 6.0 och senare (API-nivå 23) och targetSdkVersion 23 eller senare beviljas behörigheter vid körning i stället för när appen installeras. För att stödja det getAllPermissions kan implementeras för att anropa ActivityCompat.checkSelfPermission och ActivityCompat.requestPermissions för varje nödvändig behörighet.

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

Kommentar

När du utformar din app bör du tänka på när dessa behörigheter ska begäras. Behörigheter bör begäras när de behövs, inte i förväg. Mer information finns i Android-behörighetsguiden.

Objektmodell

Följande klasser och gränssnitt hanterar några av de viktigaste funktionerna i Azure Communication Services Calling SDK:

Name beskrivning
CallClient CallClient är den viktigaste startpunkten för anropande SDK.
TeamsCallAgent TeamsCallAgent Används för att starta och hantera anrop.
TeamsCall Används TeamsCall för att representera ett Teams-samtal.
CommunicationTokenCredential CommunicationTokenCredential Används som tokenautentiseringsuppgifter för att instansiera TeamsCallAgent.
CommunicationIdentifier CommunicationIdentifier Används som en annan typ av deltagare som kan ingå i ett anrop.

Skapa en agent från användarens åtkomsttoken

Med en användartoken kan en autentiserad samtalsagent instansieras. Vanligtvis genereras denna token från en tjänst med autentisering som är specifik för programmet. Mer information om token för användaråtkomst finns i guiden Användaråtkomsttoken .

För snabbstarten ersätter du <User_Access_Token> med en användaråtkomsttoken som genererats för din Azure Communication Service-resurs.


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

Starta ett samtal med hjälp av samtalsagenten

Att ringa samtalet kan göras via teamens samtalsagent och kräver bara att du anger en lista över nummermottagarens ID:t och samtalsalternativen. För snabbstarten används standardalternativen för samtal utan video och ett enda samtals-ID från textinmatningen.

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

Besvara ett samtal

Att acceptera ett anrop kan göras med hjälp av teams-samtalsagenten med endast en referens till den aktuella kontexten.

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

Ansluta till ett Teams-samtal

En användare kan ansluta till ett befintligt anrop genom att skicka en länk.

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

Ansluta till ett Teams-samtal med alternativ

Vi kan också ansluta till ett befintligt anrop med förinställda alternativ, till exempel att vara avstängda.

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

Konfigurera inkommande samtalslyssnare

För att kunna identifiera inkommande samtal och andra åtgärder som inte utförs av den här användaren måste lyssnarna konfigureras.

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

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

Starta appen och anropa ekoroboten

Appen kan nu startas med knappen "Kör app" i verktygsfältet (Skift+F10). Kontrollera att du kan ringa samtal genom att anropa 8:echo123. Ett förinspelat meddelande spelas upp och upprepar sedan meddelandet tillbaka till dig.

Skärmbild som visar det slutförda programmet.

Kom igång med Azure Communication Services med hjälp av Communication Services anropande SDK för att lägga till ett på ett videosamtal i din app. Du lär dig hur du startar och svarar på ett videosamtal med hjälp av Azure Communication Services Calling SDK för iOS med hjälp av Teams-identitet.

Exempelkod

Om du vill gå vidare till slutet kan du ladda ned den här snabbstarten som ett exempel på GitHub.

Förutsättningar

Konfigurera

Skapa Xcode-projektet

I Xcode skapar du ett nytt iOS-projekt och väljer mallen Enkel vyapp. I den här självstudien används SwiftUI-ramverket, så du bör ange Språket till Swift och användargränssnittet till SwiftUI. Du kommer inte att skapa tester under den här snabbstarten. Avmarkera Inkludera tester.

Skärmbild som visar fönstret Nytt projekt i Xcode.

Installera CocoaPods

Använd den här guiden för att installera CocoaPods på din Mac.

Installera paketet och beroenden med CocoaPods

  1. Om du vill skapa en Podfile för ditt program öppnar du terminalen och navigerar till projektmappen och kör podd-init.

  2. Lägg till följande kod i Podfile och spara. Se SDK-supportversioner.

platform :ios, '13.0'
use_frameworks!

target 'VideoCallingQuickstart' do
  pod 'AzureCommunicationCalling', '~> 2.10.0'
end
  1. Kör poddinstallation.

  2. .xcworkspace Öppna med Xcode.

Begär åtkomst till mikrofonen och kameran

För att få åtkomst till enhetens mikrofon och kamera måste du uppdatera appens informationsegenskapslista med en NSMicrophoneUsageDescription och NSCameraUsageDescription. Du anger det associerade värdet till en sträng som innehåller den dialogruta som systemet använder för att begära åtkomst från användaren.

Högerklicka på posten i Info.plist projektträdet och välj Öppna som > källkod. Lägg till följande rader i avsnittet på den översta nivån <dict> och spara sedan filen.

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

Konfigurera appramverket

Öppna projektets ContentView.swift fil och lägg till en importdeklaration överst i filen för att importera AzureCommunicationCalling biblioteket och AVFoundation. AVFoundation används för att samla in ljudbehörighet från kod.

import AzureCommunicationCalling
import AVFoundation

Objektmodell

Följande klasser och gränssnitt hanterar några av de viktigaste funktionerna i Azure Communication Services Calling SDK för iOS.

Name beskrivning
CallClient CallClient är den viktigaste startpunkten för anropande SDK.
TeamsCallAgent TeamsCallAgent Används för att starta och hantera anrop.
TeamsIncomingCall TeamsIncomingCall Används för att acceptera eller avvisa inkommande teamsamtal.
CommunicationTokenCredential CommunicationTokenCredential Används som tokenautentiseringsuppgifter för att instansiera TeamsCallAgent.
CommunicationIdentifier CommunicationIdentifier Används för att representera användarens identitet, vilket kan vara något av följande alternativ: CommunicationUserIdentifier, PhoneNumberIdentifier eller CallingApplication.

Skapa Teams-samtalsagenten

Ersätt implementeringen av ContentView struct med några enkla användargränssnittskontroller som gör att en användare kan initiera och avsluta ett anrop. Vi lägger till affärslogik i dessa kontroller i den här snabbstarten.

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

Autentisera klienten

För att initiera en TeamsCallAgent instans behöver du en användaråtkomsttoken som gör att den kan göra och ta emot anrop. Se dokumentationen för användaråtkomsttoken om du inte har någon tillgänglig token.

När du har en token lägger du till följande kod i återanropet onAppear i ContentView.swift. Du måste ersätta <USER ACCESS TOKEN> med en giltig användaråtkomsttoken för resursen:

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

Initiera Teams CallAgent och åtkomst Enhetshanteraren

Om du vill skapa en TeamsCallAgent instans från en CallClientanvänder du metoden callClient.createTeamsCallAgent som asynkront returnerar ett TeamsCallAgent objekt när det har initierats. DeviceManager låter dig räkna upp lokala enheter som kan användas i ett anrop för att överföra ljud-/videoströmmar. Du kan också begära behörighet från en användare för att få åtkomst till mikrofon/kamera.

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

Be om behörigheter

Vi måste lägga till följande kod i återanropet onAppear för att be om behörigheter för ljud och video.

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

Ringa ett utgående samtal

Metoden startCall anges som den åtgärd som utförs när knappen Starta samtal knackas. I den här snabbstarten är utgående samtal endast ljud som standard. För att starta ett samtal med video måste vi ställa in VideoOptions med LocalVideoStream och skicka det med startCallOptions för att ange inledande alternativ för samtalet.

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

Ansluta till Teams-möte

Med join metoden kan användaren ansluta till ett teams-möte.

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 och RemotePariticipantObserver används för att hantera mellansamtalshändelser och fjärranslutna deltagare. Vi ställer in observatörerna setTeamsCallAndObserver i funktionen.

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

Besvara ett inkommande samtal

För att besvara ett inkommande samtal implementerar du en TeamsIncomingCallHandler för att visa den inkommande samtalsbanderollen för att besvara eller avvisa samtalet. Placera följande implementering i TeamsIncomingCallHandler.swift.

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

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

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

Vi måste skapa en instans av TeamsIncomingCallHandler genom att lägga till följande kod i återanropet onAppear i ContentView.swift:

Ange ett ombud till TeamsCallAgent efter att ha TeamsCallAgent skapats:

self.teamsCallAgent!.delegate = incomingCallHandler

När det finns ett inkommande samtal TeamsIncomingCallHandler anropar funktionen showIncomingCallBanner för att visa answer och decline knapp.

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

Åtgärderna som är kopplade till answer och decline implementeras som följande kod. För att besvara samtalet med video måste vi aktivera den lokala videon och ange alternativen AcceptCallOptions för med 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.
    }
}

Prenumerera på händelser

Vi kan implementera en TeamsCallObserver klass för att prenumerera på en samling händelser som ska meddelas när värden ändras under anropet.

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

Kör koden

Du kan skapa och köra appen i iOS-simulatorn genom att välja Produktkörning > eller med hjälp av kortkommandot (⌘-R).

Rensa resurser

Om du vill rensa och ta bort en Communication Services-prenumeration kan du ta bort resursen eller resursgruppen. Om du tar bort resursgruppen tas även alla andra resurser som är associerade med den bort. Läs mer om att rensa resurser.

Nästa steg

Mer information finns i följande artiklar: