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
- Uzyskaj konto platformy Azure z aktywną subskrypcją. Utwórz konto bezpłatnie.
- Musisz mieć Node.js 18. Aby go zainstalować, możesz użyć instalatora msi.
- Utwórz aktywny zasób usług komunikacyjnych. Utwórz zasób usług komunikacyjnych.
- Utwórz token dostępu użytkownika, aby utworzyć wystąpienie klienta wywołania. Dowiedz się, jak tworzyć tokeny dostępu użytkowników i zarządzać nimi.
- 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 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ć webpack
pakiety 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 teamsCallAgent elementu . |
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:
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:
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:
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:
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).
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.
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 InitCallAgentAndDeviceManagerAsync
bootstrap 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 ARM64
platformy , 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ą.
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.
Wybierz szablon projektu "Puste działanie" w obszarze "Telefon i tablet".
Wybierz pozycję Minimalny zestaw SDK "API 26: Android 8.0 (Oreo)" lub nowszy.
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, onCreate
zobacz 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.
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.
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
Aby utworzyć aplikację
Podfile
dla aplikacji, otwórz terminal i przejdź do folderu projektu i uruchom init zasobnika.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
Uruchom instalację zasobnika.
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 CallClient
klasy , 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
localVideoStream
pomocą 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:
- Zapoznaj się z naszym przykładowym wywołaniem internetowym
- Dowiedz się więcej o możliwościach wywoływania zestawu SDK
- Dowiedz się więcej o tym, jak działa wywołanie