This topic demonstrates how to start a video conversation using locally-attached webcam. This process involves creating a conversation with a local and remote participant, and connecting to the remote participant using the participant's Audio/Video (AV) modality. An instance of IVideoWindowis 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 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

The following figure illustrates the classes, methods, and events used in the process of starting an audio conversation.

Start Video Conversation

Start a Video Conversation

  1. Create an event handler to handle the ConversationAdded event.

  2. Create an event handler to handle the ParticipantAdded event.

  3. Create an event handler to handle the ModalityStateChanged event.

  4. Create an event handler to handle the StateChanged event.

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

  6. Get the ConversationManager instance by reading the ConversationManager property of the LyncClient instance.

  7. Register for the ConversationAdded event on the ConversationManager instance.

  8. Call the AddConversation method on the ConversationManager instance.

Handling Events

ConversationAdded Event

  1. Check the modality state of the audio/video modality on the added conversation. If the modality is ModalityState . Notifiedthen conversation was not started by the local user. For this walkthrough, you handle the event for only the conversation added by the local user.

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

  2. Register for state change events on the conversation.

  3. Call the CanInvoke method and pass ConversationAction . AddParticipant.

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

  5. Call the AddParticipant method on the conversation. Pass the Contact instance from the prior step as the contact argument.

ParticipantAdded Event

  1. Read the IsSelf property of the added participant exposed by Participant property. The following steps should not be executed for the self-participant.

  2. Get the AVModality from the Modalities property of the conversation in the sourceparameter of the callback method: source.Modalities[ModalityTypes.AudioVideoModality]

  3. Cast the obtained Modality class instance to AVModality .

  4. Register for ModalityStateChanged on the audio/video modality to catch the ModalityState . Connectedevent that is raised after you connect to the modality.

  5. Register for participant events on the remote participant.

  6. 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 EndConnecton your UI thread or you can call it within a System.AsyncCallbackmethod you pass into BeginConnect.

ModalityStateChanged Event

  1. Read NewState . If the new state is ModalityState . Connected, then proceed to the next step.

  2. Obtain an instance of VideoChannel by casting the event handler sourceobject to AVModality and getting the VideoChannel property.

  3. Register for the StateChanged event on the video channel obtained in the previous step.

  4. Call into CanInvoke , passing ChannelAction . Startas the only argument. Proceed to the next step if trueis returned.

  5. Call BeginStart to start the video you connected to in the ParticipantAddedevent 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.AsyncCallbackmethod into BeginStartand then calling EndStartwithin the callback you define.

Channel StateChanged Event

  1. Read the NewState property to determine the current state of the video channel you started in an earlier step.

  2. If the new state of the video channel is ChannelState . Sendor . Receive, then you obtain instances of VideoWindow by reading the CaptureVideoWindow and RenderVideoWindow properties of the source video channel.

  3. Marshal the event data to your UI thread and update your relevant form controls intended to host the VideoWindowinstances.

    Important note Important

    Marshaling is necessary only for Windows Forms and WPF applications. SilverLight applications execute the API logic on the UI thread.

Examples

This walkthrough assumes that you have created a Windows.Forms object and 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 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. UIUpdateris invoked using the UpdateFormControlDelegateso that updates to controls on the UI can be made.

C#  Copy imageCopy Code
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 using the walkthrough steps found in Walkthrough: Sign In to Lync with UI Suppressed .

C#  Copy imageCopy Code


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.

C#  Copy imageCopy Code
		/// <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 raised when the participant is added. It obtains the AVModality of the new conversation and begins to connect to the remote participant using this modality. When the modality is connected, a state change event for the modality is raised.

Tip Tip

A collection of modalities is also surfaced by the new participant but you do not connect to the remote participant using these. The participant modality collection is only used for instant messaging.

C#  Copy imageCopy Code
		/// <summary>
		/// ParticipantAdded callback handles ParticpantAdded 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 IAsyncResultto indicate the operation that 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.

C#  Copy imageCopy Code
		/// <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 audio/video modality has connected to the remote participant. A VideoChannel is obtained from the connected modality and then started.

C#  Copy imageCopy Code
		/// <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 . Sendor . Receive, the event handler marshals the event data to the UI thread by calling Invokeon the class instance and passing relevant data.

C#  Copy imageCopy Code
		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 GroupBoxon the UI form for the two video stream directions, capture and render. In addition, properties are set on the VideoWindow instances for each direction.

C#  Copy imageCopy Code


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

See Also