From d9e2b40e3eae12aec6dc94b204acda5870c0ab71 Mon Sep 17 00:00:00 2001 From: John Hurliman Date: Fri, 3 Aug 2007 16:00:50 +0000 Subject: [PATCH] * Massive amount of work on VoiceManager and VoiceTest, we are almost logging in * Fixed a few lingering bugs with the CAPS event queue wanting to time out * Forced CapsRequest to always use POST * Removed more debugging spew from HttpBase git-svn-id: http://libopenmetaverse.googlecode.com/svn/trunk@1346 52acb1d6-8a22-11de-b505-999d5b087335 --- VoiceTest/VoiceTest.cs | 105 ++++- libsecondlife/Capabilities.cs | 13 +- libsecondlife/CapsEventQueue.cs | 92 +++- libsecondlife/CapsRequest.cs | 5 + libsecondlife/HttpBase.cs | 12 +- .../libsecondlife.Utilities/TCPPipe.cs | 8 +- .../libsecondlife.Utilities/VoiceManager.cs | 429 +++++++++++++++++- .../VoiceManagerBlocking.cs | 131 ++++++ .../libsecondlife.Utilities.csproj | 1 + 9 files changed, 750 insertions(+), 46 deletions(-) create mode 100644 libsecondlife/libsecondlife.Utilities/VoiceManagerBlocking.cs diff --git a/VoiceTest/VoiceTest.cs b/VoiceTest/VoiceTest.cs index 7f733e25..86c4824f 100644 --- a/VoiceTest/VoiceTest.cs +++ b/VoiceTest/VoiceTest.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Threading; using libsecondlife; using libsecondlife.Utilities; @@ -6,38 +8,113 @@ namespace VoiceTest { class VoiceTest { + static AutoResetEvent ProvisionEvent = new AutoResetEvent(false); + static string VoiceAccount = String.Empty; + static string VoicePassword = String.Empty; + static void Main(string[] args) { + if (args.Length != 3) + { + Console.WriteLine("Usage: VoiceTest.exe [firstname] [lastname] [password]"); + return; + } + + string firstName = args[0]; + string lastName = args[1]; + string password = args[2]; + SecondLife client = new SecondLife(); + client.OnLogMessage += new SecondLife.LogCallback(client_OnLogMessage); VoiceManager voice = new VoiceManager(client); + voice.OnProvisionAccount += new VoiceManager.ProvisionAccountCallback(voice_OnProvisionAccount); if (voice.ConnectToDaemon()) { - voice.RequestCaptureDevices(); - voice.RequestRenderDevices(); + List captureDevices = voice.CaptureDevices(); - voice.RequestSetRenderDevice("Speakers (Realtek High Definiti"); + Console.WriteLine("Capture Devices:"); + for (int i = 0; i < captureDevices.Count; i++) + Console.WriteLine(String.Format("{0}. \"{1}\"", i, captureDevices[i])); + Console.WriteLine(); - voice.RequestSetSpeakerVolume(75); - voice.RequestSetCaptureVolume(75); + List renderDevices = voice.RenderDevices(); - voice.RequestStartTuningMode(10000); + Console.WriteLine("Render Devices:"); + for (int i = 0; i < renderDevices.Count; i++) + Console.WriteLine(String.Format("{0}. \"{1}\"", i, renderDevices[i])); + Console.WriteLine(); - //voice.RequestCreateConnector(); + // Login to SL + if (client.Network.Login(firstName, lastName, password, "Voice Test", "Metaverse Industries LLC ")) + { + Console.WriteLine("Creating voice connector..."); - + int status; + string connectorHandle = voice.CreateConnector(out status); - voice.RequestRenderAudioStart("bugsbunny1.wav", true); - voice.RequestRenderAudioStart("bugsbunny1.wav", false); - + if (connectorHandle != String.Empty) + { + Console.WriteLine("Voice connector handle: " + connectorHandle); + Console.WriteLine("Asking the current simulator to create a provisional account..."); + + voice.RequestProvisionAccount(); + + if (ProvisionEvent.WaitOne(45 * 1000, false)) + { + Console.WriteLine("Provisional account created. Username: " + VoiceAccount + ", Password: " + VoicePassword); + Console.WriteLine("Logging in to voice server " + voice.VoiceServer); + + string accountHandle = voice.Login(VoiceAccount, VoicePassword, connectorHandle, out status); + + if (accountHandle != String.Empty) + { + Console.WriteLine("Login succeeded, account handle: " + accountHandle); + } + else + { + Console.WriteLine("Login failed, error code: " + status); + client.Network.Logout(); + } + } + else + { + Console.WriteLine("Failed to create a provisional account"); + client.Network.Logout(); + } + } + else + { + Console.WriteLine("Failed to create a voice connector, error code: " + status); + client.Network.Logout(); + } + } + else + { + Console.WriteLine("Login to SL failed: " + client.Network.LoginMessage); + } + } + else + { + Console.WriteLine("Failed to connect to the voice daemon"); } + Console.WriteLine("Press any key to continue..."); Console.ReadKey(); + } - voice.RequestRenderAudioStop(); - voice.RequestStopTuningMode(); + static void client_OnLogMessage(string message, Helpers.LogLevel level) + { + if (level == Helpers.LogLevel.Warning || level == Helpers.LogLevel.Error) + Console.WriteLine(level.ToString() + ": " + message); + } - Console.ReadKey(); + static void voice_OnProvisionAccount(string username, string password) + { + VoiceAccount = username; + VoicePassword = password; + + ProvisionEvent.Set(); } } } diff --git a/libsecondlife/Capabilities.cs b/libsecondlife/Capabilities.cs index e9464040..201b6c37 100644 --- a/libsecondlife/Capabilities.cs +++ b/libsecondlife/Capabilities.cs @@ -26,7 +26,7 @@ using System; using System.Collections; -using System.Collections.Specialized; +using System.Collections.Generic; using System.Text; namespace libsecondlife @@ -58,7 +58,7 @@ namespace libsecondlife public Simulator Simulator; internal string _Seedcaps; - internal StringDictionary _Caps = new StringDictionary(); + internal Dictionary _Caps = new Dictionary(); private CapsRequest _SeedRequest; private CapsEventQueue _EventQueueCap = null; @@ -109,6 +109,14 @@ namespace libsecondlife } } + public string CapabilityURI(string capability) + { + if (_Caps.ContainsKey(capability)) + return _Caps[capability]; + else + return String.Empty; + } + private void MakeSeedRequest() { if (Simulator == null || !Simulator.Client.Network.Connected) @@ -135,6 +143,7 @@ namespace libsecondlife req.Add("UntrustedSimulatorMessage"); req.Add("ParcelVoiceInfoRequest"); req.Add("ChatSessionRequest"); + req.Add("ProvisionVoiceAccountRequest"); byte[] postData = LLSD.LLSDSerialize(req); diff --git a/libsecondlife/CapsEventQueue.cs b/libsecondlife/CapsEventQueue.cs index 20df6b71..11a8e24f 100644 --- a/libsecondlife/CapsEventQueue.cs +++ b/libsecondlife/CapsEventQueue.cs @@ -28,6 +28,7 @@ using System; using System.Collections; using System.Net; using System.Text; +using System.IO; using System.Threading; namespace libsecondlife @@ -96,6 +97,59 @@ namespace libsecondlife new AsyncCallback(EventRequestStreamCallback), _RequestState); } + public new void MakeRequest(byte[] postData) + { + // Create a new HttpWebRequest + HttpWebRequest httpRequest = (HttpWebRequest)WebRequest.Create(_RequestURL); + _RequestState = new HttpRequestState(httpRequest); + + if (_ProxyURL != String.Empty) + { + // Create a proxy object + WebProxy proxy = new WebProxy(); + + // Associate a new Uri object to the _wProxy object, using the proxy address + // selected by the user + proxy.Address = new Uri(_ProxyURL); + + // Finally, initialize the Web request object proxy property with the _wProxy + // object + httpRequest.Proxy = proxy; + } + + // Always disable keep-alive for our purposes + httpRequest.KeepAlive = false; + + try + { + if (postData != null) + { + // POST request + _RequestState.WebRequest.Method = "POST"; + _RequestState.WebRequest.ContentLength = postData.Length; + _RequestState.RequestData = postData; + + IAsyncResult result = (IAsyncResult)_RequestState.WebRequest.BeginGetRequestStream( + new AsyncCallback(EventRequestStreamCallback), _RequestState); + } + else + { + throw new ArgumentException("postData cannot be null for the event queue", "postData"); + } + } + catch (WebException e) + { + Abort(false, e); + } + catch (Exception e) + { + Log(String.Format("CapsEventQueue.MakeRequest(): {0} (Source: {1})", e.Message, e.Source), + Helpers.LogLevel.Warning); + + Abort(false, null); + } + } + public new void Abort() { Disconnect(true); @@ -132,7 +186,41 @@ namespace libsecondlife Simulator.Client.Log("Capabilities event queue connected for " + Simulator.ToString(), Helpers.LogLevel.Info); } - base.RequestStreamCallback(result); + try + { + _RequestState = (HttpRequestState)result.AsyncState; + Stream reqStream = _RequestState.WebRequest.EndGetRequestStream(result); + + reqStream.Write(_RequestState.RequestData, 0, _RequestState.RequestData.Length); + reqStream.Close(); + + IAsyncResult newResult = _RequestState.WebRequest.BeginGetResponse(new AsyncCallback(EventResponseCallback), _RequestState); + } + catch (WebException e) + { + Abort(false, e); + } + } + + protected void EventResponseCallback(IAsyncResult result) + { + try + { + _RequestState = (HttpRequestState)result.AsyncState; + _RequestState.WebResponse = (HttpWebResponse)_RequestState.WebRequest.EndGetResponse(result); + + // Read the response into a Stream object + Stream responseStream = _RequestState.WebResponse.GetResponseStream(); + _RequestState.ResponseStream = responseStream; + + // Begin reading of the contents of the response + IAsyncResult asynchronousInputRead = responseStream.BeginRead(_RequestState.BufferRead, 0, BUFFER_SIZE, + new AsyncCallback(ReadCallback), _RequestState); + } + catch (WebException e) + { + Abort(false, e); + } } protected override void RequestReply(HttpRequestState state, bool success, WebException exception) @@ -153,7 +241,7 @@ namespace libsecondlife _Running = false; _Dead = true; } - else if (!exception.Message.Contains("aborted") && !exception.Message.Contains("502")) + else if (!exception.Message.ToLower().Contains("aborted") && !exception.Message.Contains("502")) { Simulator.Client.Log(String.Format("Unrecognized caps exception for {0}: {1}", Simulator, exception.Message), Helpers.LogLevel.Warning); diff --git a/libsecondlife/CapsRequest.cs b/libsecondlife/CapsRequest.cs index 4a5f1135..3a8e2b55 100644 --- a/libsecondlife/CapsRequest.cs +++ b/libsecondlife/CapsRequest.cs @@ -60,6 +60,11 @@ namespace libsecondlife Simulator = simulator; } + public new void MakeRequest() + { + base.MakeRequest(new byte[0]); + } + protected override void Log(string message, Helpers.LogLevel level) { if (Simulator != null) diff --git a/libsecondlife/HttpBase.cs b/libsecondlife/HttpBase.cs index e5036362..7104df45 100644 --- a/libsecondlife/HttpBase.cs +++ b/libsecondlife/HttpBase.cs @@ -113,7 +113,7 @@ namespace libsecondlife try { - if (postData != null && postData.Length > 0) + if (postData != null) { // POST request _RequestState.WebRequest.Method = "POST"; @@ -175,19 +175,23 @@ namespace libsecondlife if (exception == null) { _Aborted = true; - Log("HttpBase.Abort(): HTTP request aborted", Helpers.LogLevel.Debug); + //Log("HttpBase.Abort(): HTTP request aborted", Helpers.LogLevel.Debug); } else if (exception.Message.Contains("404") || exception.Message.Contains("410")) { _Aborted = true; Log("HttpBase.Abort(): HTTP request target is missing", Helpers.LogLevel.Debug); } - else if (exception.Message.Contains("aborted")) + else if (exception.Message.Contains("Aborted") || exception.Message.Contains("aborted")) { // A callback threw an exception because the request is aborting, return to // avoid circular problems return; } + else if (exception.Message.Contains("502")) + { + // Don't log anything since 502 errors are so common + } else { Log(String.Format("HttpBase.Abort(): {0} (Status: {1})", exception.Message, exception.Status), @@ -258,7 +262,7 @@ namespace libsecondlife } } - private void ReadCallback(IAsyncResult result) + protected void ReadCallback(IAsyncResult result) { try { diff --git a/libsecondlife/libsecondlife.Utilities/TCPPipe.cs b/libsecondlife/libsecondlife.Utilities/TCPPipe.cs index 665f37d9..24379730 100644 --- a/libsecondlife/libsecondlife.Utilities/TCPPipe.cs +++ b/libsecondlife/libsecondlife.Utilities/TCPPipe.cs @@ -109,16 +109,18 @@ public class TCPPipe } } + static char[] splitNull = { '\0' }; + static string[] splitLines = { "\r", "\n", "\r\n" }; + void ReceiveData(string data) { if (OnReceiveLine == null) return; - string[] splitNull = { "\0" }; + //string[] splitNull = { "\0" }; string[] line = data.Split(splitNull, StringSplitOptions.None); _Buffer += line[0]; - string[] splitLines = { "\r\n", "\r", "\n" }; + //string[] splitLines = { "\r\n", "\r", "\n" }; string[] lines = _Buffer.Split(splitLines, StringSplitOptions.None); - if (lines.Length > 1) { int i; diff --git a/libsecondlife/libsecondlife.Utilities/VoiceManager.cs b/libsecondlife/libsecondlife.Utilities/VoiceManager.cs index 9c4b5d84..ca53a2e6 100644 --- a/libsecondlife/libsecondlife.Utilities/VoiceManager.cs +++ b/libsecondlife/libsecondlife.Utilities/VoiceManager.cs @@ -1,6 +1,10 @@ using System; +using System.Collections.Generic; using System.Net.Sockets; using System.Text; +using System.IO; +using System.Xml; +using System.Threading; using libsecondlife; namespace libsecondlife.Utilities @@ -31,7 +35,7 @@ namespace libsecondlife.Utilities TypeC } - public class VoiceManager + public partial class VoiceManager { public const string DAEMON_ARGS = " -p tcp -h -c -ll "; public const int DAEMON_LOG_LEVEL = 1; @@ -40,31 +44,98 @@ namespace libsecondlife.Utilities public const string VOICE_DEBUG_SERVER = "bhd.vivox.com"; public const string REQUEST_TERMINATOR = "\n\n\n"; + public delegate void LoginStateChangeCallback(int cookie, string accountHandle, int statusCode, string statusString, int state); + public delegate void NewSessionCallback(int cookie, string accountHandle, string eventSessionHandle, int state, string nameString, string uriString); + public delegate void SessionStateChangeCallback(int cookie, string uriString, int statusCode, string statusString, string eventSessionHandle, int state, bool isChannel, string nameString); + public delegate void ParticipantStateChangeCallback(int cookie, string uriString, int statusCode, string statusString, int state, string nameString, string displayNameString, int participantType); + public delegate void ParticipantPropertiesCallback(int cookie, string uriString, int statusCode, string statusString, bool isLocallyMuted, bool isModeratorMuted, bool isSpeaking, int volume, float energy); + public delegate void AuxAudioPropertiesCallback(int cookie, float energy); + public delegate void BasicActionCallback(int cookie, int statusCode, string statusString); + public delegate void ConnectorCreatedCallback(int cookie, int statusCode, string statusString, string connectorHandle); + public delegate void LoginCallback(int cookie, int statusCode, string statusString, string accountHandle); + public delegate void SessionCreatedCallback(int cookie, int statusCode, string statusString, string sessionHandle); + public delegate void DevicesCallback(int cookie, int statusCode, string statusString, string currentDevice); + public delegate void ProvisionAccountCallback(string username, string password); + + public event LoginStateChangeCallback OnLoginStateChange; + public event NewSessionCallback OnNewSession; + public event SessionStateChangeCallback OnSessionStateChange; + public event ParticipantStateChangeCallback OnParticipantStateChange; + public event ParticipantPropertiesCallback OnParticipantProperties; + public event AuxAudioPropertiesCallback OnAuxAudioProperties; + public event ConnectorCreatedCallback OnConnectorCreated; + public event LoginCallback OnLogin; + public event SessionCreatedCallback OnSessionCreated; + public event BasicActionCallback OnSessionConnected; + public event BasicActionCallback OnAccountLogout; + public event BasicActionCallback OnConnectorInitiateShutdown; + public event BasicActionCallback OnAccountChannelGetList; + public event BasicActionCallback OnSessionTerminated; + public event DevicesCallback OnCaptureDevices; + public event DevicesCallback OnRenderDevices; + public event ProvisionAccountCallback OnProvisionAccount; + public SecondLife Client; + public string VoiceServer = VOICE_RELEASE_SERVER; protected TCPPipe _DaemonPipe; protected VoiceStatus _Status; protected int _CommandCookie = 0; protected string _TuningSoundFile = String.Empty; - protected string _VoiceServer = VOICE_RELEASE_SERVER; + protected Dictionary _ChannelMap = new Dictionary(); + protected List _CaptureDevices = new List(); + protected List _RenderDevices = new List(); + + #region Response Processing Variables + + private bool isEvent = false; + private bool isChannel = false; + private bool isLocallyMuted = false; + private bool isModeratorMuted = false; + private bool isSpeaking = false; + private int cookie = 0; + private int returnCode = 0; + private int statusCode = 0; + private int volume = 0; + private int state = 0; + private int participantType = 0; + private float energy = 0f; + private string statusString = String.Empty; + private string uuidString = String.Empty; + private string actionString = String.Empty; + private string connectorHandle = String.Empty; + private string accountHandle = String.Empty; + private string sessionHandle = String.Empty; + private string eventSessionHandle = String.Empty; + private string eventTypeString = String.Empty; + private string uriString = String.Empty; + private string nameString = String.Empty; + private string audioMediaString = String.Empty; + private string displayNameString = String.Empty; + + #endregion Response Processing Variables public VoiceManager(SecondLife client) { Client = client; + + // Register callback handlers for the blocking functions + RegisterCallbacks(); } public bool IsDaemonRunning() { - return false; + throw new NotImplementedException(); } public bool StartDaemon() { - return false; + throw new NotImplementedException(); } public void StopDaemon() { + throw new NotImplementedException(); } public bool ConnectToDaemon() @@ -91,6 +162,21 @@ namespace libsecondlife.Utilities } } + public Dictionary GetChannelMap() + { + return new Dictionary(_ChannelMap); + } + + public List CurrentCaptureDevices() + { + return new List(_CaptureDevices); + } + + public List CurrentRenderDevices() + { + return new List(_RenderDevices); + } + public string VoiceAccountFromUUID(LLUUID id) { string result = "x" + Convert.ToBase64String(id.GetBytes()); @@ -117,10 +203,10 @@ namespace libsecondlife.Utilities public string SIPURIFromVoiceAccount(string account) { - return String.Format("sip:{0}@{1}", account, _VoiceServer); + return String.Format("sip:{0}@{1}", account, VoiceServer); } - public void RequestCaptureDevices() + public int RequestCaptureDevices() { if (_DaemonPipe.Connected) { @@ -128,14 +214,17 @@ namespace libsecondlife.Utilities "{1}", _CommandCookie++, REQUEST_TERMINATOR))); + + return _CommandCookie - 1; } else { Client.Log("VoiceManager.RequestCaptureDevices() called when the daemon pipe is disconnected", Helpers.LogLevel.Error); + return -1; } } - public void RequestRenderDevices() + public int RequestRenderDevices() { if (_DaemonPipe.Connected) { @@ -143,25 +232,28 @@ namespace libsecondlife.Utilities "{1}", _CommandCookie++, REQUEST_TERMINATOR))); + + return _CommandCookie - 1; } else { Client.Log("VoiceManager.RequestRenderDevices() called when the daemon pipe is disconnected", Helpers.LogLevel.Error); + return -1; } } - public void RequestCreateConnector() + public int RequestCreateConnector() { - RequestCreateConnector(VOICE_RELEASE_SERVER); + return RequestCreateConnector(VoiceServer); } - public void RequestCreateConnector(string voiceServer) + public int RequestCreateConnector(string voiceServer) { if (_DaemonPipe.Connected) { - _VoiceServer = voiceServer; + VoiceServer = voiceServer; - string accountServer = String.Format("https://www.{0}/api2/", _VoiceServer); + string accountServer = String.Format("https://www.{0}/api2/", VoiceServer); string logPath = "."; StringBuilder request = new StringBuilder(); @@ -179,14 +271,35 @@ namespace libsecondlife.Utilities request.Append(REQUEST_TERMINATOR); _DaemonPipe.SendData(Encoding.ASCII.GetBytes(request.ToString())); + + return _CommandCookie - 1; } else { Client.Log("VoiceManager.CreateConnector() called when the daemon pipe is disconnected", Helpers.LogLevel.Error); + return -1; } } - public void RequestLogin(string accountName, string password, string connectorHandle) + public void RequestProvisionAccount() + { + if (Client.Network.Connected) + { + if (Client.Network.CurrentSim != null && Client.Network.CurrentSim.SimCaps != null) + { + string requestURI = Client.Network.CurrentSim.SimCaps.CapabilityURI("ProvisionVoiceAccountRequest"); + + if (requestURI != String.Empty) + { + CapsRequest request = new CapsRequest(requestURI, Client.Network.CurrentSim); + request.OnCapsResponse += new CapsRequest.CapsResponseCallback(ProvisionCapsResponse); + request.MakeRequest(); + } + } + } + } + + public int RequestLogin(string accountName, string password, string connectorHandle) { if (_DaemonPipe.Connected) { @@ -200,56 +313,68 @@ namespace libsecondlife.Utilities request.Append(REQUEST_TERMINATOR); _DaemonPipe.SendData(Encoding.ASCII.GetBytes(request.ToString())); + + return _CommandCookie - 1; } else { Client.Log("VoiceManager.Login() called when the daemon pipe is disconnected", Helpers.LogLevel.Error); + return -1; } } - public void RequestSetRenderDevice(string deviceName) + public int RequestSetRenderDevice(string deviceName) { if (_DaemonPipe.Connected) { _DaemonPipe.SendData(Encoding.ASCII.GetBytes(String.Format( "{1}{2}", _CommandCookie, deviceName, REQUEST_TERMINATOR))); + + return _CommandCookie - 1; } else { Client.Log("VoiceManager.RequestSetRenderDevice() called when the daemon pipe is disconnected", Helpers.LogLevel.Error); + return -1; } } - public void RequestStartTuningMode(int duration) + public int RequestStartTuningMode(int duration) { if (_DaemonPipe.Connected) { _DaemonPipe.SendData(Encoding.ASCII.GetBytes(String.Format( "{1}{2}", _CommandCookie, duration, REQUEST_TERMINATOR))); + + return _CommandCookie - 1; } else { Client.Log("VoiceManager.RequestStartTuningMode() called when the daemon pipe is disconnected", Helpers.LogLevel.Error); + return -1; } } - public void RequestStopTuningMode() + public int RequestStopTuningMode() { if (_DaemonPipe.Connected) { _DaemonPipe.SendData(Encoding.ASCII.GetBytes(String.Format( "{1}", _CommandCookie, REQUEST_TERMINATOR))); + + return _CommandCookie - 1; } else { Client.Log("VoiceManager.RequestStopTuningMode() called when the daemon pipe is disconnected", Helpers.LogLevel.Error); + return _CommandCookie - 1; } } - public void RequestSetSpeakerVolume(int volume) + public int RequestSetSpeakerVolume(int volume) { if (volume < 0 || volume > 100) throw new ArgumentException("volume must be between 0 and 100", "volume"); @@ -259,14 +384,17 @@ namespace libsecondlife.Utilities _DaemonPipe.SendData(Encoding.ASCII.GetBytes(String.Format( "{1}{2}", _CommandCookie, volume, REQUEST_TERMINATOR))); + + return _CommandCookie - 1; } else { Client.Log("VoiceManager.RequestSetSpeakerVolume() called when the daemon pipe is disconnected", Helpers.LogLevel.Error); + return -1; } } - public void RequestSetCaptureVolume(int volume) + public int RequestSetCaptureVolume(int volume) { if (volume < 0 || volume > 100) throw new ArgumentException("volume must be between 0 and 100", "volume"); @@ -276,10 +404,13 @@ namespace libsecondlife.Utilities _DaemonPipe.SendData(Encoding.ASCII.GetBytes(String.Format( "{1}{2}", _CommandCookie, volume, REQUEST_TERMINATOR))); + + return _CommandCookie - 1; } else { Client.Log("VoiceManager.RequestSetCaptureVolume() called when the daemon pipe is disconnected", Helpers.LogLevel.Error); + return -1; } } @@ -288,7 +419,7 @@ namespace libsecondlife.Utilities /// /// /// - public void RequestRenderAudioStart(string fileName, bool loop) + public int RequestRenderAudioStart(string fileName, bool loop) { if (_DaemonPipe.Connected) { @@ -297,24 +428,42 @@ namespace libsecondlife.Utilities _DaemonPipe.SendData(Encoding.ASCII.GetBytes(String.Format( "{1}{2}{3}", _CommandCookie++, _TuningSoundFile, (loop ? "1" : "0"), REQUEST_TERMINATOR))); + + return _CommandCookie - 1; } else { Client.Log("VoiceManager.RequestRenderAudioStart() called when the daemon pipe is disconnected", Helpers.LogLevel.Error); + return -1; } } - public void RequestRenderAudioStop() + public int RequestRenderAudioStop() { if (_DaemonPipe.Connected) { _DaemonPipe.SendData(Encoding.ASCII.GetBytes(String.Format( "{1}{2}", _CommandCookie++, _TuningSoundFile, REQUEST_TERMINATOR))); + + return _CommandCookie - 1; } else { Client.Log("VoiceManager.RequestRenderAudioStop() called when the daemon pipe is disconnected", Helpers.LogLevel.Error); + return -1; + } + } + + #region Callbacks + + private void ProvisionCapsResponse(object response, HttpRequestState state) + { + if (response is System.Collections.Hashtable) + { + System.Collections.Hashtable respTable = (System.Collections.Hashtable)response; + + if (OnProvisionAccount != null) OnProvisionAccount((string)respTable["username"], (string)respTable["password"]); } } @@ -326,7 +475,245 @@ namespace libsecondlife.Utilities private void _DaemonPipe_OnReceiveLine(string line) { - Client.DebugLog("VOICE: " + line); + XmlTextReader reader = new XmlTextReader(new StringReader(line)); + + while (reader.Read()) + { + switch (reader.NodeType) + { + case XmlNodeType.Element: + { + if (reader.Depth == 0) + { + isEvent = (reader.Name == "Event"); + + if (isEvent || reader.Name == "Response") + { + for (int i = 0; i < reader.AttributeCount; i++) + { + reader.MoveToAttribute(i); + + switch (reader.Name) + { + case "requestId": + uuidString = reader.Value; + break; + case "action": + actionString = reader.Value; + break; + case "type": + eventTypeString = reader.Value; + break; + } + } + } + } + else + { + switch (reader.Name) + { + case "InputXml": + cookie = -1; + + // Parse through here to get the cookie value + reader.Read(); + if (reader.Name == "Request") + { + for (int i = 0; i < reader.AttributeCount; i++) + { + reader.MoveToAttribute(i); + + if (reader.Name == "requestId") + { + Int32.TryParse(reader.Value, out cookie); + break; + } + } + } + + if (cookie == -1) + { + Client.Log("VoiceManager._DaemonPipe_OnReceiveLine(): Failed to parse InputXml for the cookie", + Helpers.LogLevel.Warning); + } + break; + case "CaptureDevices": + _CaptureDevices.Clear(); + break; + case "RenderDevices": + _RenderDevices.Clear(); + break; + case "ReturnCode": + returnCode = reader.ReadElementContentAsInt(); + break; + case "StatusCode": + statusCode = reader.ReadElementContentAsInt(); + break; + case "StatusString": + statusString = reader.ReadElementContentAsString(); + break; + case "State": + state = reader.ReadElementContentAsInt(); + break; + case "ConnectorHandle": + connectorHandle = reader.ReadElementContentAsString(); + break; + case "AccountHandle": + accountHandle = reader.ReadElementContentAsString(); + break; + case "SessionHandle": + sessionHandle = reader.ReadElementContentAsString(); + break; + case "URI": + uriString = reader.ReadElementContentAsString(); + break; + case "IsChannel": + isChannel = reader.ReadElementContentAsBoolean(); + break; + case "Name": + nameString = reader.ReadElementContentAsString(); + break; + case "AudioMedia": + audioMediaString = reader.ReadElementContentAsString(); + break; + case "ChannelName": + nameString = reader.ReadElementContentAsString(); + break; + case "ParticipantURI": + uriString = reader.ReadElementContentAsString(); + break; + case "DisplayName": + displayNameString = reader.ReadElementContentAsString(); + break; + case "AccountName": + nameString = reader.ReadElementContentAsString(); + break; + case "ParticipantType": + participantType = reader.ReadElementContentAsInt(); + break; + case "IsLocallyMuted": + isLocallyMuted = reader.ReadElementContentAsBoolean(); + break; + case "IsModeratorMuted": + isModeratorMuted = reader.ReadElementContentAsBoolean(); + break; + case "IsSpeaking": + isSpeaking = reader.ReadElementContentAsBoolean(); + break; + case "Volume": + volume = reader.ReadElementContentAsInt(); + break; + case "Energy": + energy = reader.ReadElementContentAsFloat(); + break; + case "MicEnergy": + energy = reader.ReadElementContentAsFloat(); + break; + case "ChannelURI": + uriString = reader.ReadElementContentAsString(); + break; + case "ChannelListResult": + _ChannelMap[nameString] = uriString; + break; + case "CaptureDevice": + reader.Read(); + _CaptureDevices.Add(reader.ReadElementContentAsString()); + break; + case "CurrentCaptureDevice": + reader.Read(); + nameString = reader.ReadElementContentAsString(); + break; + case "RenderDevice": + reader.Read(); + _RenderDevices.Add(reader.ReadElementContentAsString()); + break; + case "CurrentRenderDevice": + reader.Read(); + nameString = reader.ReadElementContentAsString(); + break; + } + } + + break; + } + case XmlNodeType.EndElement: + if (reader.Depth == 0) + ProcessEvent(); + break; + } + } + + if (isEvent) + { + } + + //Client.DebugLog("VOICE: " + line); } + + private void ProcessEvent() + { + if (isEvent) + { + switch (eventTypeString) + { + case "LoginStateChangeEvent": + if (OnLoginStateChange != null) OnLoginStateChange(cookie, accountHandle, statusCode, statusString, state); + break; + case "SessionNewEvent": + if (OnNewSession != null) OnNewSession(cookie, accountHandle, eventSessionHandle, state, nameString, uriString); + break; + case "SessionStateChangeEvent": + if (OnSessionStateChange != null) OnSessionStateChange(cookie, uriString, statusCode, statusString, eventSessionHandle, state, isChannel, nameString); + break; + case "ParticipantStateChangeEvent": + if (OnParticipantStateChange != null) OnParticipantStateChange(cookie, uriString, statusCode, statusString, state, nameString, displayNameString, participantType); + break; + case "ParticipantPropertiesEvent": + if (OnParticipantProperties != null) OnParticipantProperties(cookie, uriString, statusCode, statusString, isLocallyMuted, isModeratorMuted, isSpeaking, volume, energy); + break; + case "AuxAudioPropertiesEvent": + if (OnAuxAudioProperties != null) OnAuxAudioProperties(cookie, energy); + break; + } + } + else + { + switch (actionString) + { + case "Connector.Create.1": + if (OnConnectorCreated != null) OnConnectorCreated(cookie, statusCode, statusString, connectorHandle); + break; + case "Account.Login.1": + if (OnLogin != null) OnLogin(cookie, statusCode, statusString, accountHandle); + break; + case "Session.Create.1": + if (OnSessionCreated != null) OnSessionCreated(cookie, statusCode, statusString, sessionHandle); + break; + case "Session.Connect.1": + if (OnSessionConnected != null) OnSessionConnected(cookie, statusCode, statusString); + break; + case "Session.Terminate.1": + if (OnSessionTerminated != null) OnSessionTerminated(cookie, statusCode, statusString); + break; + case "Account.Logout.1": + if (OnAccountLogout != null) OnAccountLogout(cookie, statusCode, statusString); + break; + case "Connector.InitiateShutdown.1": + if (OnConnectorInitiateShutdown != null) OnConnectorInitiateShutdown(cookie, statusCode, statusString); + break; + case "Account.ChannelGetList.1": + if (OnAccountChannelGetList != null) OnAccountChannelGetList(cookie, statusCode, statusString); + break; + case "Aux.GetCaptureDevices.1": + if (OnCaptureDevices != null) OnCaptureDevices(cookie, statusCode, statusString, nameString); + break; + case "Aux.GetRenderDevices.1": + if (OnRenderDevices != null) OnRenderDevices(cookie, statusCode, statusString, nameString); + break; + } + } + } + + #endregion Callbacks } } diff --git a/libsecondlife/libsecondlife.Utilities/VoiceManagerBlocking.cs b/libsecondlife/libsecondlife.Utilities/VoiceManagerBlocking.cs new file mode 100644 index 00000000..e654e4f9 --- /dev/null +++ b/libsecondlife/libsecondlife.Utilities/VoiceManagerBlocking.cs @@ -0,0 +1,131 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using libsecondlife; + +namespace libsecondlife.Utilities +{ + public partial class VoiceManager + { + /// Amount of time to wait for the voice daemon to respond. + /// The value needs to stay relatively high because some of the calls + /// require the voice daemon to make remote queries before replying + public int BlockingTimeout = 30 * 1000; + + protected Dictionary Events = new Dictionary(); + + public List CaptureDevices() + { + AutoResetEvent evt = new AutoResetEvent(false); + Events[_CommandCookie] = evt; + + if (RequestCaptureDevices() == -1) + { + Events.Remove(_CommandCookie); + return new List(); + } + + if (evt.WaitOne(BlockingTimeout, false)) + return CurrentCaptureDevices(); + else + return new List(); + } + + public List RenderDevices() + { + AutoResetEvent evt = new AutoResetEvent(false); + Events[_CommandCookie] = evt; + + if (RequestRenderDevices() == -1) + { + Events.Remove(_CommandCookie); + return new List(); + } + + if (evt.WaitOne(BlockingTimeout, false)) + return CurrentRenderDevices(); + else + return new List(); + } + + public string CreateConnector(out int status) + { + status = 0; + + AutoResetEvent evt = new AutoResetEvent(false); + Events[_CommandCookie] = evt; + + if (RequestCreateConnector() == -1) + { + Events.Remove(_CommandCookie); + return String.Empty; + } + + bool success = evt.WaitOne(BlockingTimeout, false); + status = statusCode; + + if (success && statusCode == 0) + return connectorHandle; + else + return String.Empty; + } + + public string Login(string accountName, string password, string connectorHandle, out int status) + { + status = 0; + + AutoResetEvent evt = new AutoResetEvent(false); + Events[_CommandCookie] = evt; + + if (RequestLogin(accountHandle, password, connectorHandle) == -1) + { + Events.Remove(_CommandCookie); + return String.Empty; + } + + bool success = evt.WaitOne(BlockingTimeout, false); + status = statusCode; + + if (success && statusCode == 0) + return accountHandle; + else + return String.Empty; + } + + protected void RegisterCallbacks() + { + OnCaptureDevices += new DevicesCallback(VoiceManager_OnCaptureDevices); + OnRenderDevices += new DevicesCallback(VoiceManager_OnRenderDevices); + OnConnectorCreated += new ConnectorCreatedCallback(VoiceManager_OnConnectorCreated); + OnLogin += new LoginCallback(VoiceManager_OnLogin); + } + + #region Callbacks + + private void VoiceManager_OnCaptureDevices(int cookie, int statusCode, string statusString, string currentDevice) + { + if (Events.ContainsKey(cookie)) + Events[cookie].Set(); + } + + private void VoiceManager_OnRenderDevices(int cookie, int statusCode, string statusString, string currentDevice) + { + if (Events.ContainsKey(cookie)) + Events[cookie].Set(); + } + + private void VoiceManager_OnConnectorCreated(int cookie, int statusCode, string statusString, string connectorHandle) + { + if (Events.ContainsKey(cookie)) + Events[cookie].Set(); + } + + private void VoiceManager_OnLogin(int cookie, int statusCode, string statusString, string accountHandle) + { + if (Events.ContainsKey(cookie)) + Events[cookie].Set(); + } + + #endregion Callbacks + } +} \ No newline at end of file diff --git a/libsecondlife/libsecondlife.Utilities/libsecondlife.Utilities.csproj b/libsecondlife/libsecondlife.Utilities/libsecondlife.Utilities.csproj index a52fa31d..6d21c19c 100644 --- a/libsecondlife/libsecondlife.Utilities/libsecondlife.Utilities.csproj +++ b/libsecondlife/libsecondlife.Utilities/libsecondlife.Utilities.csproj @@ -41,6 +41,7 @@ +