I'm a little bit confused on how to use IoT Edge Offline mode. I though it was out-of-box!
The location of my IoT Hub is in West US. When I disconnect my Edge device from the network nothing happen. The datas is not saved or resend after reconnecting it online.
I got only one module that send data to the IoT Hub, I can see the datas flowing with Device Explorer Twin app and I saved the data in a database.
After disconnecting, wait 5 minutes and reconnecting, I don't see the datas that I was trying to send during offline mode in the database.
All messages while offline are missing (I'm sequencing the message with datetime stamp).
Did I missed a configuration?
Any idea why the offline mode doesn't work for me?
I'm using Iot Edge Runtime v1.0.6 and Windows Containers.
Here the source code of my testing module:
using Microsoft.Azure.Devices.Client;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Runtime.Loader;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
class Program
{
static int monitoringInterval { get; set; } = 60;// 60 seconds
static System.Timers.Timer testTimer;
static ModuleClient ioTHubModuleClient;
static void Main(string[] args)
{
Init().Wait();
StartTestTimer();
// Wait until the app unloads or is cancelled
var cts = new CancellationTokenSource();
AssemblyLoadContext.Default.Unloading += (ctx) => cts.Cancel();
Console.CancelKeyPress += (sender, cpe) => cts.Cancel();
WhenCancelled(cts.Token).Wait();
}
/// <summary>
/// Handles cleanup operations when app is cancelled or unloads
/// </summary>
public static Task WhenCancelled(CancellationToken cancellationToken)
{
var tcs = new TaskCompletionSource<bool>();
cancellationToken.Register(s => ((TaskCompletionSource<bool>)s).SetResult(true), tcs);
return tcs.Task;
}
/// <summary>
/// Initializes the ModuleClient and sets up the callback to receive
/// messages containing temperature information
/// </summary>
static async Task Init()
{
AmqpTransportSettings amqpSetting = new AmqpTransportSettings(TransportType.Amqp_Tcp_Only);
ITransportSettings[] settings = { amqpSetting };
// Open a connection to the Edge runtime
ioTHubModuleClient = await ModuleClient.CreateFromEnvironmentAsync(settings);
await ioTHubModuleClient.OpenAsync();
Console.WriteLine("IoT Hub module client initialized.");
}
static void StartTestTimer()
{
Console.WriteLine("Start Monitoring Timer: " + monitoringInterval + " seconds");
// Set up a timer that triggers every minute.
testTimer = new System.Timers.Timer();
testTimer.Interval = monitoringInterval * 1000; // 60 seconds
testTimer.Elapsed += new System.Timers.ElapsedEventHandler(SendEvent);
testTimer.Start();
SendEvent(null, null);
}
async static void SendEvent(object sender, System.Timers.ElapsedEventArgs args)
{
DateTime today = DateTime.Now;
Console.WriteLine("[" + today + "] Send Data has started...");
try
{
//IoT device connection string
string connectionString = "HostName=xxxxxx.azure-devices.net;DeviceId=IOT-Device1;SharedAccessKey=ett8xxxxxxxxx";
// Connect to the IoT hub using the MQTT protocol
DeviceClient _DeviceClient = DeviceClient.CreateFromConnectionString(connectionString, TransportType.Mqtt);
_DeviceClient.OperationTimeoutInMilliseconds = 10000;
Dictionary<string, Object> telemetryDataPoint = new Dictionary<string, Object>();
string dateTime = DateTime.Now.ToLongDateString() + " " + DateTime.Now.ToLongTimeString();
telemetryDataPoint.Add("DateTime", dateTime);
string messageString = JsonConvert.SerializeObject(telemetryDataPoint);
Message message = new Message(Encoding.ASCII.GetBytes(messageString));
// Send the telemetry message
Console.WriteLine("\n*> Sending message: {0}", messageString);
await _DeviceClient.SendEventAsync(message).ConfigureAwait(false);
Console.WriteLine("Message sent!");
}
catch (Exception e)
{
Console.WriteLine("Message not sent. Connection error to Iot Hub:" + e.Message);
}
}
}
Why is the code creating a moduleClient in Init(), but then attempting to send an a message directly to IoT Hub using a deviceClient in SendEvent()? This bypasses the edge runtime (specifically edgeHub) completely which is what facilitates offline store and forward.
Here is an example of the right way to do this: https://github.com/Azure/iotedge/blob/ad41fec507bb91a2e57a07cd32e287ada0ca08d8/edge-modules/SimulatedTemperatureSensor/src/Program.cs#L95
Related
When invoking a direct method on a specific module I just receive the result [object Object] in the azure portal and I don't know what I'm doing wrong.
Note that when I did exactly the same using the azure IoT SDK for c# (without running the azure iot runtime), I properly received the JSON object and it was not just shown as [object Object].
Note that I'm developing this in c# and the docker containers (used for IoT edge runtime and it's modules) is running Linux as OS.
I have the following sample method that I've registered as a direct method.
In the iot edge runtime Init() function I do the following:
await ioTHubModuleClient.SetMethodHandlerAsync("Sample1", Sample1, null);
The sample method looks like:
private static Task<MethodResponse> Sample1(MethodRequest methodRequest, object userContext)
{
// Get data but don't do anything with it... Works fine!
var data = Encoding.UTF8.GetString(methodRequest.Data);
var methodResponse = new MethodResponse(Encoding.UTF8.GetBytes("{\"status\": \"ok\"}"), 200);
return Task.FromResult(methodResponse);
}
I can monitor this module in the debug mode by setting breakpoints in the Sample1 method. I can't find what I'm doing wrong? Why is the response returned from this Sample1 method just shown as [object Object] and why don't I see the JSON-object {"status": "ok"} as I did when not using the Azure IoT Edge runtime?
The callback result for the Direct Method is object Task< MethodResponse >.It does not serialize to Json string to show in the Azure Portal. But you can use the Service Client Sdk to get the callback response and then serialize to JSON string.
The latest Microsoft Azure IoT Hub SDK for C# supports Modules and IoT Edge. You can refer to this sample with using the SDK.
Update:
In the latest Azure IoT Hub SDK(Microsoft.Azure.Devices.Client 1.18), please use ModuleClinet instead of DeviceClient. You can refer to the following code in module.
namespace SampleModuleA
{
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Runtime.Loader;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Azure.Devices.Client.Transport.Mqtt;
using Microsoft.Azure.Devices.Client;
using Newtonsoft.Json;
class Program
{
static int counter;
static void Main(string[] args)
{
Init().Wait();
// Wait until the app unloads or is cancelled
var cts = new CancellationTokenSource();
AssemblyLoadContext.Default.Unloading += (ctx) => cts.Cancel();
Console.CancelKeyPress += (sender, cpe) => cts.Cancel();
WhenCancelled(cts.Token).Wait();
}
/// <summary>
/// Handles cleanup operations when app is cancelled or unloads
/// </summary>
public static Task WhenCancelled(CancellationToken cancellationToken)
{
var tcs = new TaskCompletionSource<bool>();
cancellationToken.Register(s => ((TaskCompletionSource<bool>)s).SetResult(true), tcs);
return tcs.Task;
}
/// <summary>
/// Initializes the ModuleClient and sets up the callback to receive
/// messages containing temperature information
/// </summary>
static async Task Init()
{
MqttTransportSettings mqttSetting = new MqttTransportSettings(TransportType.Mqtt_WebSocket_Only);
ITransportSettings[] settings = { mqttSetting };
// Open a connection to the Edge runtime
ModuleClient ioTHubModuleClient = await ModuleClient.CreateFromEnvironmentAsync(settings);
await ioTHubModuleClient.OpenAsync();
Console.WriteLine("[{0:HH:mm:ss ffff}]IoT Hub SampleModuleA client initialized.", DateTime.Now);
await ioTHubModuleClient.SetMethodHandlerAsync("DirectMethod1", DirectMethod1, ioTHubModuleClient);
// Register callback to be called when a message is received by the module
await ioTHubModuleClient.SetInputMessageHandlerAsync("input1", PipeMessage, ioTHubModuleClient);
}
static async Task<MethodResponse> DirectMethod1(MethodRequest methodRequest, object moduleClient)
{
Console.WriteLine("Call DirectMethod1.");
MethodResponse resp = null;
//to do Something
return resp;
}
/// <summary>
/// This method is called whenever the module is sent a message from the EdgeHub.
/// It just pipe the messages without any change.
/// It prints all the incoming messages.
/// </summary>
static async Task<MessageResponse> PipeMessage(Message message, object userContext)
{
int counterValue = Interlocked.Increment(ref counter);
var moduleClient = userContext as ModuleClient;
if (moduleClient == null)
{
throw new InvalidOperationException("UserContext doesn't contain " + "expected values");
}
byte[] messageBytes = message.GetBytes();
string messageString = Encoding.UTF8.GetString(messageBytes);
Console.WriteLine($"Received message: {counterValue}, Body: [{messageString}]");
if (!string.IsNullOrEmpty(messageString))
{
var pipeMessage = new Message(messageBytes);
foreach (var prop in message.Properties)
{
pipeMessage.Properties.Add(prop.Key, prop.Value);
}
await moduleClient.SendEventAsync("output1", pipeMessage);
Console.WriteLine("Received message sent");
}
return MessageResponse.Completed;
}
}
}
I am new to ActiveMQ, but I tried and am able to create a durable publisher, but I am not able to set Client Id, because I am not finding any properties with client Id and am even unable to find in Google. It will be great help if I will get some sample code.
Note:
Not with the NMS protocol. I am using AMQP.Net Lite with ActiveMQ in the .NET Core Web API for creating a durable publisher/subscriber with ClientId.
In order to create a durable subscription to ActiveMQ or ActiveMQ Artemis your client needs to do a couple things.
Set a unique "client-id" for the client using the AMQP 'ContainerId' property which can be seen in the code below. The client must use that same container ID every time it connects and recovers it's durable subscription.
Create a new Session.
Create a new Receiver for the address (in this case Topic) that you want to subscribe to. The Source of a durable subscription need to have the address set to a Topic address (in ActiveMQ this is topic://name). The Source also needs the expiray policy set to NEVER, the Source must also have the terminus durability state set to UNSETTLED_STATE, and the distribution mode set to COPY.
Once the Receiver is created then you can either set an onMessage handler in start or call receive to consume messages (assuming you've granted credit for the broker to send you any).
using System;
using Amqp;
using Amqp.Framing;
using Amqp.Types;
using Amqp.Sasl;
using System.Threading;
namespace aorg.apache.activemq.examples
{
class Program
{
private static string DEFAULT_BROKER_URI = "amqp://localhost:5672";
private static string DEFAULT_CONTAINER_ID = "client-1";
private static string DEFAULT_SUBSCRIPTION_NAME = "test-subscription";
private static string DEFAULT_TOPIC_NAME = "test-topic";
static void Main(string[] args)
{
Console.WriteLine("Starting AMQP durable consumer example.");
Console.WriteLine("Creating a Durable Subscription");
CreateDurableSubscription();
Console.WriteLine("Attempting to recover a Durable Subscription");
RecoverDurableSubscription();
Console.WriteLine("Unsubscribe a durable subscription");
UnsubscribeDurableSubscription();
Console.WriteLine("Attempting to recover a non-existent durable subscription");
try
{
RecoverDurableSubscription();
throw new Exception("Subscription was not deleted.");
}
catch (AmqpException)
{
Console.WriteLine("Recover failed as expected");
}
Console.WriteLine("Example Complete.");
}
// Creating a durable subscription involves creating a Receiver with a Source that
// has the address set to the Topic name where the client wants to subscribe along
// with an expiry policy of 'never', Terminus Durability set to 'unsettled' and the
// Distribution Mode set to 'Copy'. The link name of the Receiver represents the
// desired name of the Subscription and of course the Connection must carry a container
// ID uniqure to the client that is creating the subscription.
private static void CreateDurableSubscription()
{
Connection connection = new Connection(new Address(DEFAULT_BROKER_URI),
SaslProfile.Anonymous,
new Open() { ContainerId = DEFAULT_CONTAINER_ID }, null);
try
{
Session session = new Session(connection);
Source source = CreateBasicSource();
// Create a Durable Consumer Source.
source.Address = DEFAULT_TOPIC_NAME;
source.ExpiryPolicy = new Symbol("never");
source.Durable = 2;
source.DistributionMode = new Symbol("copy");
ReceiverLink receiver = new ReceiverLink(session, DEFAULT_SUBSCRIPTION_NAME, source, null);
session.Close();
}
finally
{
connection.Close();
}
}
// Recovering an existing subscription allows the client to ask the remote
// peer if a subscription with the given name for the current 'Container ID'
// exists. The process involves the client attaching a receiver with a null
// Source on a link with the desired subscription name as the link name and
// the broker will then return a Source instance if this current container
// has a subscription registered with that subscription (link) name.
private static void RecoverDurableSubscription()
{
Connection connection = new Connection(new Address(DEFAULT_BROKER_URI),
SaslProfile.Anonymous,
new Open() { ContainerId = DEFAULT_CONTAINER_ID }, null);
try
{
Session session = new Session(connection);
Source recoveredSource = null;
ManualResetEvent attached = new ManualResetEvent(false);
OnAttached onAttached = (link, attach) =>
{
recoveredSource = (Source) attach.Source;
attached.Set();
};
ReceiverLink receiver = new ReceiverLink(session, DEFAULT_SUBSCRIPTION_NAME, (Source) null, onAttached);
attached.WaitOne(10000);
if (recoveredSource == null)
{
// The remote had no subscription matching what we asked for.
throw new AmqpException(new Error());
}
else
{
Console.WriteLine(" Receovered subscription for address: " + recoveredSource.Address);
Console.WriteLine(" Recovered Source Expiry Policy = " + recoveredSource.ExpiryPolicy);
Console.WriteLine(" Recovered Source Durability = " + recoveredSource.Durable);
Console.WriteLine(" Recovered Source Distribution Mode = " + recoveredSource.DistributionMode);
}
session.Close();
}
finally
{
connection.Close();
}
}
// Unsubscribing a durable subscription involves recovering an existing
// subscription and then closing the receiver link explicitly or in AMQP
// terms the close value of the Detach frame should be 'true'
private static void UnsubscribeDurableSubscription()
{
Connection connection = new Connection(new Address(DEFAULT_BROKER_URI),
SaslProfile.Anonymous,
new Open() { ContainerId = DEFAULT_CONTAINER_ID }, null);
try
{
Session session = new Session(connection);
Source recoveredSource = null;
ManualResetEvent attached = new ManualResetEvent(false);
OnAttached onAttached = (link, attach) =>
{
recoveredSource = (Source) attach.Source;
attached.Set();
};
ReceiverLink receiver = new ReceiverLink(session, DEFAULT_SUBSCRIPTION_NAME, (Source) null, onAttached);
attached.WaitOne(10000);
if (recoveredSource == null)
{
// The remote had no subscription matching what we asked for.
throw new AmqpException(new Error());
}
else
{
Console.WriteLine(" Receovered subscription for address: " + recoveredSource.Address);
Console.WriteLine(" Recovered Source Expiry Policy = " + recoveredSource.ExpiryPolicy);
Console.WriteLine(" Recovered Source Durability = " + recoveredSource.Durable);
Console.WriteLine(" Recovered Source Distribution Mode = " + recoveredSource.DistributionMode);
}
// Closing the Receiver vs. detaching it will unsubscribe
receiver.Close();
session.Close();
}
finally
{
connection.Close();
}
}
// Creates a basic Source type that contains common attributes needed
// to describe to the remote peer the features and expectations of the
// Source of the Receiver link.
private static Source CreateBasicSource()
{
Source source = new Source();
// These are the outcomes this link will accept.
Symbol[] outcomes = new Symbol[] {new Symbol("amqp:accepted:list"),
new Symbol("amqp:rejected:list"),
new Symbol("amqp:released:list"),
new Symbol("amqp:modified:list") };
// Default Outcome for deliveries not settled on this link
Modified defaultOutcome = new Modified();
defaultOutcome.DeliveryFailed = true;
defaultOutcome.UndeliverableHere = false;
// Configure Source.
source.DefaultOutcome = defaultOutcome;
source.Outcomes = outcomes;
return source;
}
}
}
I'm making a small web application which is built on ASP.NET Core. My application is for streaming video from clients to clients through service.
I've followed this post :
http://www.strathweb.com/2013/01/asynchronously-streaming-video-with-asp-net-web-api/
I've implemented the application of tutorial successfully, but, that was for streaming Video from server to clients.
What I wanna do now is :
Clients register to service for streaming. (using video or audio tag)
Service receives client submitted data (submit through POSTMAN)
Service broadcast the data to its every registered clients.
Here is what I've implemented:
(Index.cshtml)
<div>
<video width="480"
height="320"
controls="controls"
autoplay="autoplay">
<source src="/api/video/initiate"
type="video/mp4">
</source>
</video>
</div>
StreamingService
public class StreamingService: IStreamingService
{
public IList<Stream> Connections {get;set;}
public StreamingService()
{
Connections = new List<Stream>();
}
public byte[] AnalyzeStream(Stream stream)
{
long originalPosititon = 0;
if (stream.CanSeek)
{
originalPosititon = stream.Position;
stream.Position = 0;
}
try
{
var readBuffer = new byte[4096];
int bytesReader;
while ((byteRead = stream.Read(readBuffer, totalBytesRead, readBuffer.Length - totalBytesRead)) > 0)
{
totalBytesRead += byteRead;
if (totalBytesRead == readBuffer.Length)
{
var nextByte = stream.ReadByte();
if (nextByte != -1)
{
var temp = new byte[readBuffer * 2];
Buffer.BlockCopy(readBuffer, 0, temp, 0, readBuffer.Length);
Buffer.SetByte(temp, totalBytesRead, (byte)nextByte);
readBuffer = temp;
totalBytesRead++;
}
}
}
var buffer = readBuffer;
if (readBuffer.Length != totalBytesRead)
{
buffer = new byte[totalBytesRead];
Buffer.BlockCopy(readBuffer, 0, buffer, 0, totalBytesRead);
}
return buffer;
}
finally
{
if (stream.CanSeek)
stream.Position = originalPosititon;
}
}
}
VideoController
public class VideoController: Controller
{
private readonly IStreamingService _streamingService;
private readonly IHostingEnvironment _hostingEnvironment;
public VideoController(IStreamingService streamingService, IHostingEnvironment hostingEnvironment)
{
_streamingService = streamingService;
_hostingEnvironment = hostingEnvironment;
}
[HttpGet("initiate")]
public IActionResult Initiate()
{
_streamingService.Connections.Add(Response.Body);
}
[HttpPost("broadcast")]
public async Task<IActionResult> Broadcast()
{
// Retrieve data submitted from POSTMAN.
var data = _streamingService.AnalyzeStream(Request.Body);
foreach (var stream in _streamingService.Connections)
{
try
{
await stream.WriteAsync(data, 0, data.Length);
}
catch (Exception exception)
{
stream.Dispose();
_streamingService.Connections.Remove(stream);
}
}
}
}
When I send data from POSTMAN through api/video/broadcast . For loop ran and I got an exception said the stream has been disposed.
My question is:
How can I keep the stream alive for streaming ?
(Stream created in api/video/initiate is kept alive and when a client calls api/video/broadcast , all initiated stream will update its date without having disposed)
Thank you,
Is it an option to keep the stream in cache?
You can read more about it here. The simplest way it to add the cache services to the dependency injection container and the request the concrete implementation of IMemoryCache through constructor injection in your VideoController (as you've done with IStreamingService and IHostingEnvironment).
Just add the stream to the cache and use the cached stream the next time api/video/broadcast is hit.
Be aware though that if you are on a webfarm or hosted in the cloud it is recommended to use Distributed Cache like Redis Cache, or else your cache could disapear unexpected. I use Azure Redis Cache for instance which works great!
I am new to UCMA and I am learning as I go through examples. I am trying to build 2 Lync clients A and B with the scenario as follows,
A calls B
B answers
A plays audio
B records it using Recorder.
I am stuck at trying to record the call at B. For B its an incoming call. I need to attach the audiovideoflow to the recorder, but I am not sure on how to do it. I will appreciate any help.
Apologies on the unformatted code, I am not sure how to format it properly, I tried.
Thanks.
Kris
Client B Code:
Accepts an incoming call
Records the media received in the incoming call. ***This is the part I have trouble
using System;
using System.Threading;
using Microsoft.Rtc.Collaboration;
using Microsoft.Rtc.Collaboration.AudioVideo;
using Microsoft.Rtc.Signaling;
using Microsoft.Rtc.Collaboration.Lync;
namespace Microsoft.Rtc.Collaboration.LyncUAS
{
public class LyncUAS
{
#region Locals
private LyncUASConfigurationHelper _helper;
private UserEndpoint _userEndpoint;
private AudioVideoCall _audioVideoCall;
private AudioVideoFlow _audioVideoFlow;
private Conversation _incomingConversation;
//Wait handles are only present to keep things synchronous and easy to read.
private AutoResetEvent _autoResetEvent = new AutoResetEvent(false);
private EventHandler<AudioVideoFlowConfigurationRequestedEventArgs> _audioVideoFlowConfigurationRequestedEventHandler;
private EventHandler<MediaFlowStateChangedEventArgs> _audioVideoFlowStateChangedEventHandler;
private AutoResetEvent _waitForAudioVideoCallEstablishCompleted = new AutoResetEvent(false);
private AutoResetEvent _waitForAudioVideoFlowStateChangedToActiveCompleted = new AutoResetEvent(false);
private AutoResetEvent _waitForPrepareSourceCompleted = new AutoResetEvent(false);
#endregion
#region Methods
/// <summary>
/// Instantiate and run the DeclineIncomingCall quickstart.
/// </summary>
/// <param name="args">unused</param>
public static void Main(string[] args)
{
LyncUAS lyncUAS = new LyncUAS();
lyncUAS.Run();
}
private void Run()
{
string filename = "received.wma";
_helper = new LyncUASConfigurationHelper();
// Create a user endpoint, using the network credential object
// defined above.
_userEndpoint = _helper.CreateEstablishedUserEndpoint("Lync UAS" /*endpointFriendlyName*/);
_userEndpoint.RegisterForIncomingCall<AudioVideoCall>(On_AudioVideoCall_Received);
Console.WriteLine("Waiting for incoming call...");
_autoResetEvent.WaitOne();
Console.WriteLine("came after call is connected");
//start recording for audio.
Recorder recorder = new Recorder();
recorder.StateChanged += new EventHandler<RecorderStateChangedEventArgs>(recorder_StateChanged);
recorder.VoiceActivityChanged += new EventHandler<VoiceActivityChangedEventArgs>(recorder_VoiceActivityChanged);
//**********This is the issue, currently _audioVideoFlow is null, it is not attached to the flow
//So this will fail, how to attach _audioVideoFlow to an incoming call ?? HELP !!!
// recorder.AttachFlow(_audioVideoFlow); ------------> HELP!
WmaFileSink sink = new WmaFileSink(filename);
recorder.SetSink(sink);
recorder.Start();
Console.WriteLine("Started Recording ...");
_autoResetEvent.WaitOne();
recorder.Stop();
Console.WriteLine("Stopped Recording ...");
recorder.DetachFlow();
Console.WriteLine("Exiting");
Thread.Sleep(2000);
}
private void audioVideoFlow_StateChanged(object sender, MediaFlowStateChangedEventArgs e)
{
Console.WriteLine("Flow state changed from " + e.PreviousState + " to " + e.State);
//When flow is active, media operations can begin
if (e.State == MediaFlowState.Active)
{
// Flow-related media operations normally begin here.
_waitForAudioVideoFlowStateChangedToActiveCompleted.Set();
}
// call sample event handler
if (_audioVideoFlowStateChangedEventHandler != null)
{
_audioVideoFlowStateChangedEventHandler(sender, e);
}
}
void recorder_VoiceActivityChanged(object sender, VoiceActivityChangedEventArgs e)
{
Console.WriteLine("Recorder detected " + (e.IsVoice ? "voice" : "silence") + " at " + e.TimeStamp);
}
void recorder_StateChanged(object sender, RecorderStateChangedEventArgs e)
{
Console.WriteLine("Recorder state changed from " + e.PreviousState + " to " + e.State);
}
void On_AudioVideoCall_Received(object sender, CallReceivedEventArgs<AudioVideoCall> e)
{
//Type checking was done by the platform; no risk of this being any
// type other than the type expected.
_audioVideoCall = e.Call;
// Call: StateChanged: Only hooked up for logging, to show the call
// state transitions.
_audioVideoCall.StateChanged += new
EventHandler<CallStateChangedEventArgs>(_audioVideoCall_StateChanged);
_incomingConversation = new Conversation(_userEndpoint);
Console.WriteLine("Call Received! From: " + e.RemoteParticipant.Uri + " Toast is: " +e.ToastMessage.Message);
_audioVideoCall.BeginAccept(
ar =>
{
try {
_audioVideoCall.EndAccept(ar);
Console.WriteLine("Call must be connected at this point. "+_audioVideoCall.State);
_autoResetEvent.Set();
} catch (RealTimeException ex) { Console.WriteLine(ex); }
}, null);
}
//Just to record the state transitions in the console.
void _audioVideoCall_StateChanged(object sender, CallStateChangedEventArgs e)
{
Console.WriteLine("Call has changed state. The previous call state was: " + e.PreviousState +
" and the current state is: " + e.State);
if (e.State == CallState.Terminated)
{
Console.WriteLine("Shutting down");
_autoResetEvent.Set();
_helper.ShutdownPlatform();
}
}
#endregion
}
}
I think I have figured out what's not quite right here.
Your Code
// Create a user endpoint, using the network credential object
// defined above.
_userEndpoint = _helper.CreateEstablishedUserEndpoint("Lync UAS" /*endpointFriendlyName*/);
_userEndpoint.RegisterForIncomingCall<AudioVideoCall>(On_AudioVideoCall_Received);
Console.WriteLine("Waiting for incoming call...");
_autoResetEvent.WaitOne();
Console.WriteLine("came after call is connected");
//start recording for audio.
Recorder recorder = new Recorder();
recorder.StateChanged += new EventHandler<RecorderStateChangedEventArgs>(recorder_StateChanged);
recorder.VoiceActivityChanged += new EventHandler<VoiceActivityChangedEventArgs>(recorder_VoiceActivityChanged);
//**********This is the issue, currently _audioVideoFlow is null, it is not attached to the flow //So this will fail, how to attach _audioVideoFlow to an incoming call ?? HELP !!!
// recorder.AttachFlow(_audioVideoFlow); ------------> HELP!
Looking good so far. I'm assuming you're establishing and such in your CreateEstablishedUserEndpoint method, but I'm not seeing where you're getting the value for _audioVideoFlow.
I'm guessing you might be doing it elsewhere, but on the off chance that's actually where you're running into problems, here's that bit:
Simplest pattern to get AVFlow
public static void RegisterForIncomingCall(LocalEndpoint localEndpoint)
{
localEndpoint.RegisterForIncomingCall
<AudioVideoCall>(IncomingCallDelegate);
}
private static void IncomingCallDelegate(object sender, CallReceivedEventArgs<AudioVideoCall> e)
{
e.Call.AudioVideoFlowConfigurationRequested += IncomingCallOnAudioVideoFlowConfigurationRequested;
}
private static void IncomingCallOnAudioVideoFlowConfigurationRequested(object sender, AudioVideoFlowConfigurationRequestedEventArgs e)
{
AudioVideoFlow audioVideoFlow = e.Flow; // <--- There's your flow, gentleman.
}
Now, instead of registering for your incoming call, just call RegisterForIncomingCall(_userEndpoint);.
Your AVFlow will be hanging off e.Flow above, you could then pass that into your recorder: recorder.AttachFlow(e.Flow) or simply assign the flow to a field in your class and autoResetEvent.WaitOne(); and set that up where you're setting that up now.
Obviously this is a pretty naive implementation. A lot can go wrong in those few lines of code (exception handling/static event handler memory leak comes immediately to mind); don't forget to wire up events related to status changes on the conversation/call and endpoints, as well as any of the recovery related items.
I have developed a small console application to test EWS StreamingSubscriptions / Notifications. In the past we used Push Notifications but ,in theory, when using StreamingNotifications I should be able to avoid creating a listener http endpoint and all the trouble with it (firewall, etc.).
So, from my local machine; I'm doing this:
static void Main(string[] args)
{
if (String.IsNullOrEmpty(ConfigurationManager.AppSettings["PrimaryLabUserId"]))
{
throw new ArgumentNullException("Please provide a value for PrimaryLabUserId in app.config");
}
_primaryLabUserId = ConfigurationManager.AppSettings["PrimaryLabUserId"];
string ServiceAccountName = ConfigurationManager.AppSettings["ExchangeServiceAccountName"];
string ServiceAccountPassword = ConfigurationManager.AppSettings["ExchangeServiceAccountPassword"];
_service = new ExchangeService(ExchangeVersion.Exchange2010_SP2);
_service.Credentials = new WebCredentials(ServiceAccountName, ServiceAccountPassword);
_service.AutodiscoverUrl(_primaryLabUserId, (x) => true);
_ewsUrl = _service.Url.AbsoluteUri;
var _connection = new StreamingSubscriptionConnection(_service, 30);
var sub = SubscribeForStreamingNotifications();
_connection.AddSubscription(sub);
_connection.OnDisconnect +=
new StreamingSubscriptionConnection.SubscriptionErrorDelegate(OnDisconnect);
// set up subscriptions here.
_connection.OnNotificationEvent +=
new StreamingSubscriptionConnection.NotificationEventDelegate(OnNewMail);
_connection.Open();
Console.WriteLine("Listening streaming...");
Console.ReadLine();
}
public static StreamingSubscription SubscribeForStreamingNotifications()
{
var folderIds = new List<FolderId>()
{
WellKnownFolderName.Inbox,
WellKnownFolderName.Calendar
};
var eventTypes = new List<EventType>();
eventTypes.Add(EventType.NewMail);
eventTypes.Add(EventType.Deleted);
eventTypes.Add(EventType.Moved);
eventTypes.Add(EventType.Created);
eventTypes.Add(EventType.Modified);
return _service.SubscribeToStreamingNotifications(folderIds, eventTypes.ToArray());
}
private static void OnNewMail(object sender, NotificationEventArgs args)
{
var test = args;
Console.WriteLine("Incoming");
}
The Subscription initializes OK, but when I send a new mail to the LabUser nothing happens. The Notification Event never fires. I tried the same with pushnotifications and it was working (on another server with a public http endpoint for exchange to call back).
I was wondering if this might have anything to do with my local machine.
How very stupid of me. I forgot to impersonate. Since I'm calling into EWS with a service account it is of course listening on the mailbox of that account unless you specify:
_service.ImpersonatedUserId = new ImpersonatedUserId(ConnectingIdType.SmtpAddress, _primaryLabUserId);