Communication Services 통화 SDK를 사용하여 앱에 1:1 음성 및 영상 통화를 추가하여 Azure Communication Services를 시작합니다. JavaScript용 Azure Communication Services Calling SDK를 사용하여 통화를 시작하고 답변하는 방법에 대해 알아봅니다.
프로젝트의 루트 디렉터리에 index.html 파일을 만듭니다. 이 파일을 사용하여 사용자가 1:1 영상 통화를 걸 수 있도록 하는 기본 레이아웃을 구성합니다.
코드는 다음과 같습니다.
<!-- 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>
웹 SDK 개체 모델을 호출하는 Azure Communication Services
Azure Communication Services Calling SDK의 주 기능 중 일부를 처리하는 클래스와 인터페이스는 다음과 같습니다.
이름
설명
CallClient
통화 SDK의 주 진입점입니다.
AzureCommunicationTokenCredential
teamsCallAgent를 인스턴스화하는 데 사용되는 CommunicationTokenCredential 인터페이스를 구현합니다.
TeamsCallAgent
Teams 통화를 시작하고 관리하는 데 사용됩니다.
DeviceManager
미디어 디바이스를 관리하는 데 사용됩니다.
TeamsCall
Teams 통화를 나타내는 데 사용
LocalVideoStream
로컬 시스템의 카메라 디바이스에 대한 로컬 비디오 스트림을 만드는 데 사용됩니다.
RemoteParticipant
호출에서 원격 참가자를 나타내는 데 사용됩니다.
RemoteVideoStream
원격 참가자로부터 원격 비디오 스트림을 나타내는 데 사용됩니다.
이 빠른 시작에 대한 애플리케이션 로직을 포함하기 위해 index.js라는 프로젝트의 루트 디렉터리에 파일을 만듭니다. 다음 코드를 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();
});
webpack 로컬 서버 코드 추가
이 빠른 시작에 대한 로컬 서버 논리를 포함하도록 webpack.config.js라는 프로젝트의 루트 디렉터리에 파일을 만듭니다. 다음 코드를 webpack.config.js에 추가합니다.
webpack-dev-server를 사용하여 앱을 빌드하고 실행합니다. 다음 명령을 실행하여 로컬 웹 서버에서 애플리케이션 호스트를 번들로 묶습니다.
`npx webpack serve --config webpack.config.js`
브라우저를 열고 두 개의 탭에서 http://localhost:8080/.으로 이동합니다. 탭은 다음 이미지와 유사한 결과를 표시해야 합니다.
첫 번째 탭에서 유효한 사용자 액세스 토큰을 입력합니다. 두 번째 탭에서 다른 유효한 사용자 액세스 토큰을 입력합니다. 사용할 수 있는 액세스 토큰이 아직 없는 경우 사용자 액세스 토큰 설명서를 참조하세요.
두 탭에서 "에이전트 호출 초기화" 단추를 클릭합니다. 탭은 다음 이미지와 유사한 결과를 표시해야 합니다.
첫 번째 탭에서 두 번째 탭의 Azure Communication Services 사용자 ID를 입력하고 "통화 시작" 단추를 선택합니다. 첫 번째 탭이 두 번째 탭으로 발신 전화를 걸기 시작하고 두 번째 탭의 "통화 수락" 단추가 사용하도록 설정됩니다.
두 번째 탭에서 "통화 수락" 단추를 선택합니다. 전화를 받고 연결됩니다. 탭은 다음 이미지와 유사한 결과를 표시해야 합니다.
이제 두 탭 모두 1:1 영상 통화에 성공했습니다. 두 사용자 모두 서로의 오디오를 듣고 서로의 동영상 스트림을 볼 수 있습니다.
Communication Services 통화 SDK를 사용하여 앱에 1:1 음성 및 영상 통화를 추가하여 Azure Communication Services를 시작합니다. Windows용 Azure Communication Services Calling SDK를 사용하여 통화를 시작하고 답변하는 방법에 대해 알아봅니다.
Visual Studio에서 비어 있는 앱(유니버설 Windows) 템플릿을 사용하여 단일 페이지 UWP(유니버설 Windows 플랫폼) 앱을 설정하는 새 프로젝트를 만듭니다.
패키지 설치
프로젝트를 마우스 오른쪽 단추로 선택하고 Manage Nuget Packages(으)로 이동하여 Azure.Communication.Calling.WindowsClient1.2.0-beta.1 이상을 설치합니다. 시험판 포함이 선택되어 있는지 확인합니다.
액세스 요청
Package.appxmanifest로 이동하여 Capabilities를 선택합니다.
인터넷에 대한 인바운드 및 아웃바운드 액세스 권한을 얻으려면 Internet (Client) 및 Internet (Client & Server)를 선택합니다.
Microphone을 선택하여 마이크의 오디오 피드에 액세스하고 Webcam을 선택하여 카메라의 비디오 피드에 액세스합니다.
앱 프레임워크 설정
논리를 연결하려면 기본 레이아웃을 구성해야 합니다. 아웃바운드 통화를 수행하려면 호출 수신자의 사용자 ID를 입력할 TextBox가 필요합니다. 또한 Start/Join call 단추와 Hang up 단추도 필요합니다. 오디오 상태 및 비디오 효과를 토글하는 기능을 보여 주는 Mute 및 BackgroundBlur 확인란도 이 샘플에 포함되어 있습니다.
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
}
}
개체 모델
다음 표에는 Azure Communication Services Calling SDK의 주요 기능 중 일부를 처리하는 클래스 및 인터페이스가 나열되어 있습니다.
이름
설명
CallClient
CallClient는 Calling SDK의 기본 진입점입니다.
TeamsCallAgent
TeamsCallAgent는 통화를 시작하고 관리하는 데 사용됩니다.
TeamsCommunicationCall
TeamsCommunicationCall는 진행 중인 통화를 관리하는 데 사용됩니다.
CallTokenCredential
CallTokenCredential은 TeamsCallAgent를 인스턴스화하기 위한 토큰 자격 증명으로 사용됩니다.
CallIdentifier
CallIdentifier는 사용자의 ID를 나타내는 데 사용되며 다음 옵션(MicrosoftTeamsUserCallIdentifier, UserCallIdentifier, PhoneNumberCallIdentifier 등) 중 하나일 수 있습니다.
클라이언트 인증
전화를 걸고 받을 수 있는 사용자 액세스 토큰을 사용하여 TeamsCallAgent 인스턴스를 초기화하고 필요에 따라 DeviceManager 인스턴스를 가져와 클라이언트 디바이스 구성을 쿼리합니다.
코드에서 <AUTHENTICATION_TOKEN>을 사용자 액세스 토큰으로 바꿉니다. 사용할 수 있는 토큰이 아직 없는 경우 사용자 액세스 토큰 설명서를 참조하세요.
SDK를 부트스트랩하는 InitCallAgentAndDeviceManagerAsync 함수를 추가합니다. 이 도우미는 애플리케이션의 요구 사항을 충족하도록 사용자 지정할 수 있습니다.
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;
}
통화 시작
구현을 CallButton_Click에 추가하여 만든 teamsCallAgent 개체를 사용하여 다양한 종류의 호출을 시작하고 TeamsCommunicationCall 개체에 RemoteParticipantsUpdated 및 StateChanged 이벤트 처리기를 연결합니다.
Mute 단추를 클릭하면 나가는 오디오를 음소거합니다. MuteLocal_Click에 구현을 추가하여 통화를 음소거합니다.
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
}
}
통화 시작
StartTeamsCallOptions 개체를 얻은 후에는 TeamsCallAgent를 사용하여 Teams 통화를 시작할 수 있습니다.
private async Task<TeamsCommunicationCall> StartCteCallAsync(string cteCallee)
{
var options = new StartTeamsCallOptions();
var teamsCall = await this.teamsCallAgent.StartCallAsync( new MicrosoftTeamsUserCallIdentifier(cteCallee), options);
return call;
}
수신 전화 받기
TeamsIncomingCallReceived 이벤트 싱크는 SDK 부트스트랩 도우미 InitCallAgentAndDeviceManagerAsync에서 설정됩니다.
Visual Studio에서 빈 앱, 패키지됨(데스크톱의 WinUI 3) 템플릿으로 새 프로젝트를 만들어 단일 페이지 WinUI 3 앱을 설정합니다.
패키지 설치
프로젝트를 마우스 오른쪽 단추로 선택하고 Manage Nuget Packages(으)로 이동하여 Azure.Communication.Calling.WindowsClient1.2.0-beta.1 이상을 설치합니다. 시험판 포함이 선택되어 있는지 확인합니다.
액세스 요청
앱 프레임워크 설정
논리를 연결하려면 기본 레이아웃을 구성해야 합니다. 아웃바운드 통화를 수행하려면 호출 수신자의 사용자 ID를 입력할 TextBox가 필요합니다. 또한 Start/Join call 단추와 Hang up 단추도 필요합니다. 오디오 상태 및 비디오 효과를 토글하는 기능을 보여 주는 Mute 및 BackgroundBlur 확인란도 이 샘플에 포함되어 있습니다.
프로젝트의 MainPage.xaml을 열고 Grid 노드를 Page에 추가합니다.
논리를 연결하려면 기본 레이아웃을 구성해야 합니다. 아웃바운드 통화를 수행하려면 호출 수신자의 사용자 ID를 입력할 TextBox가 필요합니다. 또한 Start Call 단추와 Hang Up 단추도 필요합니다.
프로젝트의 MainPage.xaml을 열고 Grid 노드를 Page에 추가합니다.
using Azure.Communication.Calling.WindowsClient;
using Microsoft.UI;
using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Navigation;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Windows.ApplicationModel;
using Windows.Media.Core;
using WinRT.Interop;
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
}
}
개체 모델
다음 표에는 Azure Communication Services Calling SDK의 주요 기능 중 일부를 처리하는 클래스 및 인터페이스가 나열되어 있습니다.
이름
설명
CallClient
CallClient는 Calling SDK의 기본 진입점입니다.
TeamsCallAgent
TeamsCallAgent는 통화를 시작하고 관리하는 데 사용됩니다.
TeamsCommunicationCall
TeamsCommunicationCall는 진행 중인 통화를 관리하는 데 사용됩니다.
CallTokenCredential
CallTokenCredential은 TeamsCallAgent를 인스턴스화하기 위한 토큰 자격 증명으로 사용됩니다.
CallIdentifier
CallIdentifier는 사용자의 ID를 나타내는 데 사용되며 다음 옵션(MicrosoftTeamsUserCallIdentifier, UserCallIdentifier, PhoneNumberCallIdentifier 등) 중 하나일 수 있습니다.
클라이언트 인증
전화를 걸고 받을 수 있는 사용자 액세스 토큰을 사용하여 TeamsCallAgent 인스턴스를 초기화하고 필요에 따라 DeviceManager 인스턴스를 가져와 클라이언트 디바이스 구성을 쿼리합니다.
코드에서 <AUTHENTICATION_TOKEN>을 사용자 액세스 토큰으로 바꿉니다. 사용할 수 있는 토큰이 아직 없는 경우 사용자 액세스 토큰 설명서를 참조하세요.
SDK를 부트스트랩하는 InitCallAgentAndDeviceManagerAsync 함수를 추가합니다. 이 도우미는 애플리케이션의 요구 사항을 충족하도록 사용자 지정할 수 있습니다.
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);
var teamsCallAgentOptions = new TeamsCallAgentOptions();
this.teamsCallAgent = await this.callClient.CreateTeamsCallAgentAsync(tokenCredential, teamsCallAgentOptions);
this.teamsCallAgent.IncomingCallReceived += OnIncomingCallAsync;
}
통화 시작
구현을 CallButton_Click에 추가하여 만든 teamsCallAgent 개체를 사용하여 다양한 종류의 호출을 시작하고 TeamsCommunicationCall 개체에 RemoteParticipantsUpdated 및 StateChanged 이벤트 처리기를 연결합니다.
Mute 단추를 클릭하면 나가는 오디오를 음소거합니다. MuteLocal_Click에 구현을 추가하여 통화를 음소거합니다.
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
}
}
통화 사용자 지정
StartTeamsCallOptions는 통화 시나리오의 요구 사항에 맞게 오디오 및 비디오를 구성하는 옵션을 제공합니다.
private StartTeamsCallOptions GetStartTeamsCallOptions()
{
var startTeamsCallOptions = new StartTeamsCallOptions() {
OutgoingAudioOptions = new OutgoingAudioOptions() { IsMuted = true, Stream = micStream },
OutgoingVideoOptions = new OutgoingVideoOptions() { Streams = new OutgoingVideoStream[] { cameraStream } }
};
return startTeamsCallOptions;
}
모임 참가 시나리오에서는 JoinCallOptions를 사용하여 오디오 및 화상 통화 환경을 사용자 지정할 수 있습니다.
private JoinCallOptions GetJoinCallOptions()
{
return new JoinCallOptions() {
OutgoingAudioOptions = new OutgoingAudioOptions() { IsMuted = true },
OutgoingVideoOptions = new OutgoingVideoOptions() { Streams = new OutgoingVideoStream[] { cameraStream } }
};
}
통화 시작
StartTeamsCallOptions 개체를 얻은 후에는 TeamsCallAgent를 사용하여 Teams 통화를 시작할 수 있습니다.
private async Task<TeamsCommunicationCall> StartCteCallAsync(string cteCallee)
{
var options = new StartTeamsCallOptions();
var teamsCall = await this.teamsCallAgent.StartCallAsync( new MicrosoftTeamsUserCallIdentifier(cteCallee), options);
return teamsCall;
}
수신 전화 받기
TeamsIncomingCallReceived 이벤트 싱크는 SDK 부트스트랩 도우미 InitCallAgentAndDeviceManagerAsync에서 설정됩니다.
애플리케이션은 비디오 및 오디오 스트림 종류와 같이 수신 전화를 수락하는 방법을 구성할 수 있습니다.
private async void OnIncomingCallAsync(object sender, TeamsIncomingCallReceivedEventArgs args)
{
var teamsIncomingCall = args.IncomingCall;
var acceptCallOptions = new AcceptCallOptions() { };
teamsCall = await teamsIncomingCall.AcceptAsync(acceptCallOptions);
teamsCall.StateChanged += OnStateChangedAsync;
}
통화 상태 변경 이벤트에 대한 모니터링 및 응답
진행 중인 통화가 한 상태에서 다른 상태로 트랜잭션될 때 TeamsCommunicationCall 개체의 StateChanged 이벤트가 발생합니다. 애플리케이션은 상태 변경을 UI에 반영하거나 비즈니스 논리를 삽입할 수 있는 기회를 제공합니다.
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;
}
}
}
코드 실행
Visual Studio에서 코드를 빌드하고 실행할 수 있습니다. 지원되는 솔루션 플랫폼은 ARM64, x64 및 x86입니다.
텍스트 필드에 사용자 ID를 지정하고 Start Call 단추를 클릭하여 아웃바운드 호출을 수행할 수 있습니다.
8:echo123에 전화를 걸면 에코 봇과 연결됩니다. 이 기능은 통화를 시작하고 오디오 디바이스가 작동하는지 확인하는 데 유용합니다.
Communication Services 통화 SDK를 사용하여 앱에 1:1 음성 및 영상 통화를 추가하여 Azure Communication Services를 시작합니다. Java용 Azure Communication Services Calling SDK를 사용하여 통화를 시작하고 답변하는 방법에 대해 알아봅니다.
두 개의 입력, 즉 수신자 ID에 대한 텍스트 입력 및 전화를 걸기 위한 단추가 필요합니다. 이러한 입력은 디자이너를 사용하거나 레이아웃 xml을 편집하여 추가할 수 있습니다. ID가 call_button이고 텍스트 입력이 callee_id인 단추를 만듭니다. (app/src/main/res/layout/activity_main.xml)로 이동하고 파일의 내용을 다음 코드로 바꿉니다.
레이아웃이 만들어지면 바인딩과 활동의 기본 스캐폴딩을 추가할 수 있습니다. 이 활동은 런타임 권한을 요청하고 Teams 통화 에이전트를 만들고 단추가 눌러지면 전화를 겁니다. 각각에 대해 해당 섹션에서 설명합니다.
onCreate 메서드는 getAllPermissions 및 createTeamsAgent를 호출하고 통화 단추에 대한 바인딩을 추가하도록 재정의됩니다. 이 이벤트는 활동이 만들어질 때 한 번만 발생합니다.
onCreate에 대한 자세한 내용은 활동 수명 주기에 관한 이해 가이드를 참조하세요.
MainActivity.java로 이동하고, 내용을 다음 코드로 바꿉니다.
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
}
}
런타임에 권한 요청
Android 6.0 이상(API 수준 23) 및 targetSdkVersion 23 이상에서는 앱이 설치될 때가 아니라 런타임에 권한이 부여됩니다. 이를 지원하기 위해 필요한 각 권한에 대해 ActivityCompat.checkSelfPermission 및 ActivityCompat.requestPermissions를 호출하도록 getAllPermissions를 구현할 수 있습니다.
/**
* 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);
}
}
참고
앱을 설계할 때 언제 이러한 권한을 요청할지 해당 시기를 고려하세요. 권한은 미리 요청하는 것이 아니라 필요에 따라 요청해야 합니다. 자세한 내용은 Android 권한 가이드를 참조하세요.
개체 모델
Azure Communication Services 통화 SDK의 주요 기능 중 일부를 처리하는 클래스와 인터페이스는 다음과 같습니다.
이름
설명
CallClient
CallClient는 Calling SDK의 기본 진입점입니다.
TeamsCallAgent
TeamsCallAgent는 통화를 시작하고 관리하는 데 사용됩니다.
TeamsCall
TeamsCall은 Teams 통화를 나타내는 데 사용됩니다.
CommunicationTokenCredential
CommunicationTokenCredential은 TeamsCallAgent를 인스턴스화하기 위한 토큰 자격 증명으로 사용됩니다.
CommunicationIdentifier
CommunicationIdentifier는 통화의 일부가 될 수 있는 다른 유형의 참가자로 사용됩니다.
사용자 액세스 토큰에서 에이전트 만들기
사용자 토큰을 사용하면 인증된 통화 에이전트를 인스턴스화할 수 있습니다. 일반적으로 이 토큰은 애플리케이션과 관련된 인증을 사용하는 서비스에서 생성됩니다. 사용자 액세스 토큰에 대한 자세한 내용은 사용자 액세스 토큰 가이드를 확인하세요.
빠른 시작에서는 <User_Access_Token>을 Azure Communication Services 리소스에 대해 생성된 사용자 액세스 토큰으로 바꿉니다.
/**
* 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();
}
}
통화 에이전트를 사용하여 통화 시작
전화를 거는 것은 Teams 통화 에이전트를 통해 수행할 수 있으며 수신자 ID 및 통화 옵션의 목록만 제공하면 됩니다. 빠른 시작에서는 비디오가 없는 기본 통화 옵션 및 텍스트 입력의 단일 수신자 ID가 사용됩니다.
/**
* 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);
}
통화에 응답
현재 컨텍스트에 대한 참조만 사용하여 팀 통화 에이전트를 사용하여 통화를 수락할 수 있습니다.
public void acceptACall(TeamsIncomingCall teamsIncomingCall){
teamsIncomingCall.accept(this);
}
Teams 통화에 참여
사용자는 링크를 전달하여 기존 통화에 참여할 수 있습니다.
/**
* Join a call using a teams meeting link.
*/
public TeamsCall joinTeamsCall(TeamsCallAgent teamsCallAgent){
TeamsMeetingLinkLocator link = new TeamsMeetingLinkLocator("meetingLink");
TeamsCall call = teamsCallAgent.join(this, link);
}
옵션을 사용하여 Teams 통화에 참여
음소거와 같은 사전 설정된 옵션을 사용하여 기존 통화에 참여할 수도 있습니다.
/**
* 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);
}
수신 통화 수신기 설정
수신 전화 및 이 사용자가 수행하지 않은 기타 작업을 검색하려면 수신기를 설정해야 합니다.
이제 도구 모음의 "앱 실행" 단추(Shift+F10)를 사용하여 앱을 시작할 수 있습니다.
8:echo123을 호출하여 전화를 걸 수 있는지 확인합니다. 미리 녹음된 메시지가 재생된 다음, 해당 메시지를 다시 반복합니다.
Communication Services Calling SDK를 사용하여 앱에 1:1 영상 통화를 추가하여 Azure Communication Services를 시작하세요. Teams ID를 사용하여 iOS용 Azure Communication Services Calling SDK를 사용하여 영상 통화를 시작하고 응답하는 방법에 대해 알아봅니다.
Xcode에서 새 iOS 프로젝트를 만들고 단일 보기 앱 템플릿을 선택합니다. 이 자습서에서는 SwiftUI 프레임워크를 사용하므로 언어를 Swift로, 사용자 인터페이스는 SwiftUI로 설정해야 합니다. 이 빠른 시작 중에는 테스트를 만들지 않습니다. 테스트 포함을 선택 취소합니다.
platform :ios, '13.0'
use_frameworks!
target 'VideoCallingQuickstart' do
pod 'AzureCommunicationCalling', '~> 2.10.0'
end
Pod 설치를 실행합니다.
Xcode로 .xcworkspace를 엽니다.
마이크, 카메라에 대한 액세스 요청
디바이스의 마이크 및 카메라에 액세스하려면 앱의 정보 속성 목록을 NSMicrophoneUsageDescription 및 NSCameraUsageDescription으로 업데이트해야 합니다. 시스템이 사용자의 액세스를 요청하는 데 사용하는 대화 상자에 포함될 문자열로 연결된 값을 설정합니다.
프로젝트 트리의 Info.plist 항목을 마우스 오른쪽 단추로 클릭하고 다음 형식으로 열기 > 소스 코드를 선택합니다. 최상위 <dict> 섹션에 다음 줄을 추가한 다음, 파일을 저장합니다.
<key>NSMicrophoneUsageDescription</key>
<string>Need microphone access for VOIP calling.</string>
<key>NSCameraUsageDescription</key>
<string>Need camera access for video calling</string>
앱 프레임워크 설정
프로젝트의 ContentView.swift 파일을 열고 파일 맨 위에 가져오기 선언을 추가하여 AzureCommunicationCalling 라이브러리 및 AVFoundation을 가져옵니다. AvFoundation은 코드에서 오디오 권한을 캡처하는 데 사용됩니다.
iOS용 Azure Communication Services Calling SDK의 주요 기능 중 일부를 처리하는 클래스와 인터페이스는 다음과 같습니다.
이름
설명
CallClient
CallClient는 Calling SDK의 기본 진입점입니다.
TeamsCallAgent
TeamsCallAgent는 통화를 시작하고 관리하는 데 사용됩니다.
TeamsIncomingCall
TeamsIncomingCall은 수신 Teams 통화를 수락하거나 거부하는 데 사용됩니다.
CommunicationTokenCredential
CommunicationTokenCredential은 TeamsCallAgent를 인스턴스화하기 위한 토큰 자격 증명으로 사용됩니다.
CommunicationIdentifier
CommunicationIdentifier는 사용자의 ID를 나타내는 데 사용되며 다음 옵션(CommunicationUserIdentifier, PhoneNumberIdentifier 또는 CallingApplication) 중 하나일 수 있습니다.
Teams 통화 에이전트 만들기
ContentView struct의 구현을 사용자가 통화를 시작하고 종료할 수 있는 몇 가지 간단한 UI 컨트롤로 대체합니다. 이 빠른 시작에서는 비즈니스 논리를 이러한 컨트롤에 추가합니다.
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()
}
}
클라이언트 인증
TeamsCallAgent 인스턴스를 초기화하려면 통화를 주고받을 사용자 액세스 토큰이 필요합니다. 사용할 수 있는 토큰이 아직 없는 경우 사용자 액세스 토큰 설명서를 참조하세요.
토큰이 있으면 ContentView.swift의 onAppear 콜백에 다음 코드를 추가합니다.
<USER ACCESS TOKEN>을 리소스에 대한 유효한 사용자 액세스 토큰으로 바꿔야 합니다.
var userCredential: CommunicationTokenCredential?
do {
userCredential = try CommunicationTokenCredential(token: "<USER ACCESS TOKEN>")
} catch {
print("ERROR: It was not possible to create user credential.")
return
}
Teams CallAgent 초기화 및 디바이스 관리자 액세스
CallClient에서 TeamsCallAgent 인스턴스를 만들려면 초기화된 후 비동기적으로 TeamsCallAgent 개체를 반환하는 callClient.createTeamsCallAgent 메서드를 사용합니다.
DeviceManager를 사용하여 오디오/비디오 스트림을 전송하는 호출에 사용 가능한 로컬 디바이스를 열거할 수 있습니다. 또한 사용자에게 마이크/카메라 액세스 권한을 요청할 수 있습니다.
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")
}
}
}
}
권한 요청
onAppear 콜백에 다음 코드를 추가하여 오디오 및 비디오에 대한 권한을 요청해야 합니다.
AVAudioSession.sharedInstance().requestRecordPermission { (granted) in
if granted {
AVCaptureDevice.requestAccess(for: .video) { (videoGranted) in
/* NO OPERATION */
}
}
}
발신 전화 걸기
startCall 메서드는 통화 시작 단추를 누르면 수행되는 작업으로 설정됩니다. 이 빠른 시작에서 발신 전화는 기본적으로 오디오 전용입니다. 영상 통화를 시작하려면 VideoOptions를 LocalVideoStream으로 설정하고 startCallOptions로 전달하여 통화 초기 옵션을 설정해야 합니다.
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.
}
Teams 모임 참여
join 메서드를 사용하면 사용자가 팀 모임에 참여할 수 있습니다.
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 및 RemoteParticipantObserver는 통화 중 이벤트 및 원격 참가자를 관리하는 데 사용됩니다.
setTeamsCallAndObserver 함수에서 관찰자를 설정합니다.
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")
}
}
수신 전화 응답
수신 전화에 응답하려면 TeamsIncomingCallHandler를 구현하여 통화에 응답하거나 거절할 수 있는 수신 전화 배너를 표시합니다.
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
}
}
}
다음 코드를 ContentView.swift의 onAppear 콜백에 추가하여 TeamsIncomingCallHandler의 인스턴스를 만들어야 합니다.
TeamsCallAgent가 성공적으로 생성된 후 TeamsCallAgent에 대리자를 설정합니다.