Walkthrough: Start a Video Conversation (Lync 2010 SDK)
This topic demonstrates how to start a video conversation by using a locally-attached webcam. In this process, you create a conversation with a local and remote participant, and connect to the remote participant by using the participant’s audio/video (AV) modality. An instance of IVideoWindow is used to obtain the video stream from the webcam.
To complete this walkthrough, you must handle state change events on both the client’s ConversationManager instance and the conversation itself.
Tip
A video conversation can be held, forwarded, or transferred to another unified communications-enabled phone. You cannot transfer or forward the video channel of an audio/video conversation to a public switched telephone network (PSTN) telephone such as a mobile phone. The video channel is disconnected. You can park the audio channel of an A/V conversation on a call park orbit, but the video channel is disconnected.
Starting a Video Conversation
Figure 1 shows the classes, methods, and events that are used to start a video conversation.
Figure 1. Starting a video conversation
To start a video conversation
Create an event handler to handle the ConversationAdded event.
Create an event handler to handle the ParticipantAdded event.
Create an event handler to handle the ModalityStateChanged event.
Create an event handler to handle the StateChanged event.
Get the LyncClient instance. Verify that the client is signed in to the server. For information about signing in to Microsoft Lync Server 2010, see Walkthrough: Sign In to Lync (Lync 2010 SDK).
Get the ConversationManager instance by reading the ConversationManager property of the LyncClient instance.
Register for the ConversationAdded event on the ConversationManager instance.
Call the AddConversation method on the ConversationManager instance.
Handling Events
ConversationAdded Event
Check the modality state of the audio/video modality on the added conversation. If the modality is ModalityState.Notified, then conversation was not started by the local user. For this walkthrough, you handle the event for only the conversation that is added by the local user.
Tip
The ConversationAdded event is raised when either a local user starts a conversation or a remote user invites the local user to a new conversation.
Register for state change events on the conversation.
Call the CanInvoke method and pass ConversationAction. AddParticipant.
If you can add a participant, get a Contact instance to add to the conversation by calling into GetContactByUri(string). A contact that resolves to the passed URI is returned.
Call the AddParticipant method on the conversation. Pass the Contact instance from the prior step as the contact argument.
ParticipantAdded Event
Read the IsSelf property of the added participant exposed by the Participant property.
Note
You should not use steps 2 through 6 for the self-participant.
Get the AVModality from the Modalities property of the conversation in the source parameter of the callback method: source.Modalities[ModalityTypes.AudioVideoModality].
Cast the obtained Modality class instance to AVModality.
Register for the ModalityStateChanged event on the A/V modality to catch the ModalityState.Connected event that is raised after you connect to the modality.
Register for participant events on the remote participant.
Call the BeginConnect method on this AVModality instance to allow both the local and remote endpoints to accept the conversation. You must call EndConnect after calling BeginConnect. You can call EndConnect on your UI thread, or you can call it within a System.AsyncCallback method that you pass into BeginConnect.
ModalityStateChanged Event
Read NewState. If the new state is ModalityState.Connected, then proceed to the next step.
Obtain an instance of VideoChannel by casting the event handler source object to AVModality, and getting the VideoChannel property.
Register for the StateChanged event on the video channel obtained in the previous step.
Call into the CanInvoke method, passing ChannelAction.Start as the only argument. Proceed to the next step if true is returned.
Call BeginStart to start the video that you connected to in the ParticipantAdded event handler. You must call into EndStart to complete the channel start operation. Your thread is blocked until the channel is started. You avoid blocking your thread by passing a System.AsyncCallback method into BeginStart and then calling EndStart within the callback you define.
Channel StateChanged Event
Read the NewState property to determine the current state of the video channel you started in an earlier step.
If the new state of the video channel is ChannelState.Send or .Receive, then you obtain instances of VideoWindow by reading the CaptureVideoWindow and RenderVideoWindow properties of the source video channel.
Marshal the event data to your UI thread and then update your relevant form controls that are intended to host the VideoWindow instances.
Important
Marshaling is necessary only for Microsoft Windows Forms and Microsoft Windows Presentation Foundation (WPF) applications. Microsoft Silverlight applications execute the API logic on the UI thread.
Examples
This walkthrough assumes that you have created a Windows.Forms object and then populated it with a textbox for entry of a contact’s URI and a button that you use to start a conversation. If you create a WPF application and use this example code, you must marshal event data by using System.Windows.Threading.Dispatcher.BeginInvoke(Delegate, Object[]).
The following example creates a new conversation by calling into the AddConversation method on an instance of the ConversationManager.
Declarations
The following declarations provide this example with the ability to marshal event data to the UI thread from the Lync 2010 thread. UIUpdater is invoked by using the UpdateFormControlDelegate so that updates to controls on the UI can be made.
using System;
using System.Windows.Forms;
using Microsoft.Lync.Model;
using Microsoft.Lync.Model.Conversation;
using Microsoft.Lync.Model.Conversation.AudioVideo;
namespace VideoConversation
{
enum UIDelegateAction
{
SetButtonEnableState = 0,
SetAllButtonsEnableState = 1,
SetButtonText = 2,
SetLabelText = 3,
CloseForm = 4,
ShutdownForm = 5,
UpdateVideoControl = 6
}
public partial class MainForm : Form
{
/// <summary>
/// Updates a form control based on action.
/// </summary>
/// <param name="action">UIDelegateAction. The action to perform on a control.</param>
/// <param name="formControl">object. The form control to act on.</param>
/// <param name="updateValue">updateValue. The value to set on the control property.</param>
delegate void UpdateFormControlDelegate(UIDelegateAction action, object formControl, object updateValue);
UpdateFormControlDelegate UIUpdater;
private System.Windows.Forms.GroupBox Capture_GroupBox;
private System.Windows.Forms.GroupBox Render_GroupBox;
private string targetUri;
private Conversation _Conversation;
private LyncClient _LyncClient;
/// <summary>
/// The video channel of the audio/video modality of _Conversation.
/// </summary>
private VideoChannel _VideoChannel;
...
Start a Conversation
The following example registers for conversation manager events and starts the conversation. Assume the LyncClient was obtained by using the walkthrough steps appearing in Walkthrough: Sign In to Lync with UI Suppressed (Lync 2010 SDK).
public void StartConversation()
{
_LyncClient.ConversationManager.ConversationAdded += ConversationManager_ConversationAdded;
_Conversation = _LyncClient.ConversationsManager.AddConversation();
}
ConversationManager Events
The following example registers for conversation instance events and adds a participant to the new conversation after verifying that the added conversation is the one started in the previous example.
/// <summary>
/// Called on the LyncClient worker thread by the ConversationManager instance when a conversation has been
/// added to the Conversations collection on ConversationManager.
/// A conversation is added when ConversationManager.AddConversation() is called on the UI thread or when a remote user
/// is calling the local user.
/// </summary>
/// <param name="sender">object. A ConversationManager instance.</param>
/// <param name="e">ConversationManagerEventArgs. The event state data object.</param>
void ConversationManager_ConversationAdded(object sender, ConversationManagerEventArgs e)
{
//Conversation originated with remote SIP user
if (e.Conversation.Modalities[ModalityTypes.AudioVideo].State != ModalityState.Notified)
{
if (e.Conversation.CanInvoke(ConversationAction.AddParticipant)
{
e.Conversation.ParticipantAdded += Conversation_ParticipantAdded;
e.Conversation.AddParticipant(_LyncClient.ContactManager.GetContactByUri("bob@contoso.com"));
}
}
}
Conversation Events
The previous example added a participant. This example handles the event that is raised when the participant is added. It obtains the AVModality of the new conversation and begins to connect to the remote participant by using this modality. When the modality is connected, a state change event for the modality is raised.
Tip
A collection of modalities is also surfaced by the new participant but you do not connect to the remote participant with these modalities. The participant modality collection is only used for instant messaging.
/// <summary>
/// ParticipantAdded callback handles ParticipantAdded event raised by Conversation
/// </summary>
/// <param name="source">Conversation Source conversation.</param>
/// <param name="data">ParticpantCollectionEventArgs Event data</param>
void Conversation_ParticipantAdded(object source, ParticipantCollectionChangedEventArgs data)
{
if (data.Participant.IsSelf != true)
{
if (((Conversation)source).Modalities[ModalityTypes.AudioVideo].CanInvoke(ModalityAction.Connect))
{
object[] asyncState = { ((Conversation)source).Modalities[ModalityTypes.AudioVideo], "CONNECT" };
try
{
((Conversation)source).Modalities[ModalityTypes.AudioVideo].ModalityStateChanged += _AVModality_ModalityStateChanged;
((Conversation)source).Modalities[ModalityTypes.AudioVideo].BeginConnect(ModalityConnectOptions.None, ModalityCallback, asyncState);
}
catch (LyncPlatformException ce)
{
throw new Exception("Lync Platform Exception on BeginConnect: " + ce.Message);
}
}
}
}
Conversation Modality Operation Callback
The following example calls EndConnect to complete the connect operation. Because several different modality operations can be executed, it is important that your callback method determines which started operation triggers the asynchronous callback. The example uses the asynchronous state property of IAsyncResult to indicate that the operation was started on the UI thread.
Calling the EndConnect method in the callback instead of in the UI thread prevents the example application from blocking while the call is connected.
/// <summary>
/// Called on the LyncClient worker thread when an audio/video modality action completes.
/// </summary>
/// <param name="ar">IAsyncResult. The state of the asynchronous operation.</param>
private void ModalityCallback(IAsyncResult ar)
{
Object[] asyncState = (Object[])ar.AsyncState;
try
{
if (ar.IsCompleted == true)
{
if (asyncState[1].ToString() == "RETRIEVE")
{
((AVModality)asyncState[0]).EndRetrieve(ar);
}
if (asyncState[1].ToString() == "HOLD")
{
((AVModality)asyncState[0]).EndHold(ar);
}
if (asyncState[1].ToString() == "CONNECT")
{
((AVModality)asyncState[0]).EndConnect(ar);
}
if (asyncState[1].ToString() == "FORWARD")
{
((AVModality)asyncState[0]).EndForward(ar);
}
}
}
catch (LyncPlatformException)
{ }
}
Modality Events
This example handles the modality state change event that is raised when the conversation A/V modality is connected to the remote participant. A VideoChannel is obtained from the connected modality and then started.
/// <summary>
/// Handles the Modality state changed event for a Conversation
/// </summary>
/// <param name="source">Modality. Modality whose state has changed.</param>
/// <param name="data">ModalityStateChangedEventArgs. Old and new modality states.</param>
void _AVModality_ModalityStateChanged(object sender, ModalityStateChangedEventArgs e)
{
switch (e.NewState)
{
case ModalityState.Connected:
if (_VideoChannel == null)
{
_VideoChannel = ((AVModality)sender).VideoChannel;
_VideoChannel.StateChanged += new EventHandler<ChannelStateChangedEventArgs>(_VideoChannel_StateChanged);
}
if (_VideoChannel.CanInvoke(ChannelAction.Start))
{
_VideoChannel.BeginStart(MediaChannelCallback, _VideoChannel);
}
break;
}
}
/// <summary>
/// Called on the LyncClient worker thread when a media channel action completes.
/// </summary>
/// <param name="ar">IAsyncResult. The state of the asynchronous operation.</param>
private void MediaChannelCallback(IAsyncResult ar)
{
((VideoChannel)ar.AsyncState).EndStart(ar);
}
Video Channel Events
The following example handles the state change event on the video channel. When the new state of the video channel is either ChannelState. Send or .Receive, the event handler marshals the event data to the UI thread by calling Invoke on the class instance and passing relevant data.
void _VideoChannel_StateChanged(object sender, ChannelStateChangedEventArgs e)
{
if (e.NewState == ChannelState.Send || e.NewState == ChannelState.Receive)
{
UIUpdater = new UpdateFormControlDelegate(UpdateFormControl);
this.Invoke(UIUpdater,
new object[] {UIDelegateAction.UpdateVideoControl,
Capture_GroupBox,
(VideoChannel)sender });
this.Invoke(UIUpdater,
new object[] {UIDelegateAction.UpdateVideoControl,
Render_GroupBox,
(VideoChannel)sender });
}
}
UI Update Delegate
The following example is invoked by the video channel state changed event on the Lync 2010 thread. The invoked method updates a GroupBox on the UI form for the two video stream directions, capture and render. In addition, properties are set on the VideoWindow instances for each direction.
/// <summary>
/// Updates a form control based on action.
/// </summary>
/// <param name="action">UIDelegateAction. The action to perform on a control.</param>
/// <param name="formControl">object. The form control to act on.</param>
/// <param name="updateValue">updateValue. The value to set on the control property.</param>
private void UpdateFormControl(UIDelegateAction action, object formControl, object updateValue)
{
switch (action)
{
case UIDelegateAction.UpdateVideoControl:
int OATRUE = -1;
int OAFALSE = 0;
//from OC UI: long lNewWindowStyle = WS_CHILD | WS_CLIPCHILDREN | WS_CLIPSIBLINGS;
long lNewWindowStyle = 0x40000000L | 0x02000000L | 0x04000000L;
GroupBox boxToUpdate = (GroupBox)formControl;
VideoChannel video = (VideoChannel)updateValue;
if (boxToUpdate == Capture_GroupBox)
{
if (video.CaptureVideoWindow != null)
{
video.CaptureVideoWindow.AutoShow = OATRUE;
video.CaptureVideoWindow.WindowStyle = (int)lNewWindowStyle;
video.CaptureVideoWindow.SetWindowPosition(0, 0, boxToUpdate.Width, boxToUpdate.Height);
video.CaptureVideoWindow.Owner = boxToUpdate.Handle.ToInt32();
}
}
else
{
if (video.RenderVideoWindow != null)
{
video.RenderVideoWindow.AutoShow = OATRUE;
video.RenderVideoWindow.WindowStyle = (int)lNewWindowStyle;
video.RenderVideoWindow.SetWindowPosition(0, 0, boxToUpdate.Width, boxToUpdate.Height);
video.RenderVideoWindow.Owner = boxToUpdate.Handle.ToInt32();
}
}
boxToUpdate.Refresh();
break;
}
}