Graph Core and Communications SDK Concepts
The Graph signaling SDK is quite flexible and can run in multiple environments, and support both stateful and stateless architectures. It can run on Azure Cloud Service, Azure Service Fabric, and Azure App Service. Furthermore, because the SDK supports both .net framework 4.6.1+ and netstandard 2.0 it is cross platform.
This article describes the key concepts in order to effectively utilize the Graph Core SDK and the Graph Communications SDK including the Calling SDK.
Graph Communications Core SDK
The GraphServiceClient object is the entry point to the Graph Core SDK. The majority of the Graph Core SDK is automatically generated by the Graph SDK generator using the OData resource model. It reflects the REST-ful wire protocol provided by the Graph Communications service.
Graph Communications Stateful SDK and Client Builder
The ICommunicationsClient object is the entry point to the Graph Communications SDK and the Calling SDK. This SDK is designed for stateful services and provides additional support on top of the Graph Core SDK for resource state management and media management. The ICommunicationsClientBuilder is the object used to construct a new ICommunicationsClient
with the desired settings.
Building media bots has additional considerations which are further outlined in the media section.
Notification Dispatching
Notifications for root collections, such as the ICallCollection
generated by ICommunicationsClient.Calls()
, are handled in a single queue. Any root resource, such as the ICall
, that has been added to the root collection is given it's own queue. Any child resources, of the root resource, such as the ICallParticipant
use the same queue as the root resource.
Each queue will delivered event callbacks to the developer sequentially. The SDK will wait to deliver the next event callbacks until all the callbacks for the current event have been processed. This has been done as to help developers avoid concurrency issues and will in the future support resource versioning and jitter buffers.
Important
It is important for all event handlers to be non-blocking, and any long running operations need to be offloaded to another thread. Long running operations in any event will block further operations from being raised for the given resource.
Example Outgoing Calls
To illustrate functionality of the Graph Communications Calling SDK, the below examples demonstrate 2 common scenarios: how to make an outbound call to a Microsoft Teams user and how to join an existing Microsoft Teams meeting.
Making an Outbound Call
Assuming a bot has been properly registered and deployed, the ICommunicationsClient
configured and built.
The bot needs to create the Call object with the corresponding parameters and pass the object to the CallCollectionExtensions.AddAsync method as follows:
Call callResource = new Call
{
Subject = "**Subject**",
Targets = new List<InvitationParticipantInfo>
{
new InvitationParticipantInfo
{
Identity = new IdentitySet
{
User = new Identity
{
Id = "**Target's AAD ObjectId GUID**"
},
},
}
},
TenantId = "**The id of the tenant that will host the meeting**"
};
IMediaSession mediaSession = this.Client.CreateMediaSession(**media session settings**);
ICall call = await this.Client.Calls().AddAsync(callResource, mediaSession);
SDK will store the state of the call in memory after calling AddAsync
. The returned call
object above contains the call Id set by the service.
Making an Outbound Call to Join an Existing Microsoft Teams Meeting
The above example shows how to create an outbound call to single or multiple participants and create a new conversation. If the bot needs to join an existing conversation, the SDK provides an overload of AddAsync that takes JoinMeetingParameters as input.
The bot needs to create the JoinMeetingParameters object with the corresponding meeting parameters and pass the object to the CallCollectionExtensions.AddAsync method as follows:
ChatInfo chatInfo = new ChatInfo
{
MessageId = "**Message Id**",
ThreadId = "**Thread Id**",
ReplyChainMessageId = "**Reply Chain Message Id**"
};
OrganizerMeetingInfo meetingInfo = new OrganizerMeetingInfo
{
Organizer = new IdentitySet
{
User = new Identity
{
Id = "**Meeting Organizer's AAD ObjectId GUID**"
},
}
};
meetingInfo.Organizer.User.SetTenantId("**TenantId Guid**");
IMediaSession mediaSession = this.Client.CreateMediaSession(**media session settings**);
JoinMeetingParameters joinCallParameters = new JoinMeetingParameters(
chatInfo,
meetingInfo,
mediaSession);
ICall call = await this.Client.Calls().AddAsync(joinCallParameters);
SDK will store the state of the call in memory after calling AddAsync
. The returned call
object above contains the callId set by the service.
Example Incoming Calls
Any time another user or bot places a call to your bot, you will receive a notification to the global application endpoint specified when registering your bot. Incoming call scenarios are supported with both Service Hosted Media and App Hosted Media configurations.
Important
For all incoming call scenarios, the initial incoming call notification will still be received using the BotBuilder protocol. The Graph SDK automatically returns 204 No Content to the initial notification to invoke the new Graph protocol.
Answering incoming call with application hosted media
First, subscribe to incoming calls.
this.Client.Calls().OnIncoming += this.CallsOnIncoming;
When incoming call comes, the bot needs to answer with an IMediaSession
. This can be a media session created using the ICommunicationsClient.CreateMediaSession() extension or a custom IMediaSession
.
private void CallsOnIncoming(ICallCollection sender, CollectionEventArgs<ICall> collectionEventArgs)
{
IMediaSession mediaSession = this.Client.CreateMediaSession(
new AudioSocketSettings
{
StreamDirections = StreamDirection.Recvonly,
SupportedAudioFormat = AudioFormat.Pcm16K
},
new VideoSocketSettings
{
StreamDirections = StreamDirection.Sendrecv,
ReceiveColorFormat = VideoColorFormat.NV12,
SupportedSendVideoFormats = new List<VideoFormat>
{
VideoFormat.NV12_720x1280_30Fps,
VideoFormat.NV12_1280x720_30Fps
}
}
);
// Run async as not to block subsequent notifications.
Task.Run(async () =>
{
await collectionEventArgs
.AddedResources
.FirstOrDefault()
.AnswerAsync(mediaSession)
.ConfigureAwait(false);
});
}
Answer incoming call with service hosted media
First subscribe to incoming calls.
this.Client.Calls().OnIncoming += this.CallsOnIncoming;
When incoming call comes, the bot will answer with a ServiceHostedMediaConfig
.
private void CallsOnIncoming(ICallCollection sender, CollectionEventArgs<ICall> collectionEventArgs)
{
// Run async as not to block subsequent notifications.
Task.Run(async () =>
{
await collectionEventArgs
.AddedResources
.FirstOrDefault()
.AnswerAsync(new Modality[] { Modality.Audio })
.ConfigureAwait(false);
});
}
State Management
Graph Communications SDK can be used to store state of all resources in memory. This has 2 implications:
- The instance hosting the call needs to be up throughout the lifetime of a call.
- Any subsequent asynchronous notifications delivered by the service need to be redirected to the instance hosting the call.
Any bot that hosts its own media stack should to be built using the Graph Communications SDK given that the media stream has a requirement that it needs to persist in memory throughout the lifetime of the call.
More details can be found in the State Management article.