diff --git a/VoiceTest/VoiceTest.cs b/VoiceTest/VoiceTest.cs
new file mode 100644
index 00000000..7f733e25
--- /dev/null
+++ b/VoiceTest/VoiceTest.cs
@@ -0,0 +1,43 @@
+using System;
+using libsecondlife;
+using libsecondlife.Utilities;
+
+namespace VoiceTest
+{
+ class VoiceTest
+ {
+ static void Main(string[] args)
+ {
+ SecondLife client = new SecondLife();
+ VoiceManager voice = new VoiceManager(client);
+
+ if (voice.ConnectToDaemon())
+ {
+ voice.RequestCaptureDevices();
+ voice.RequestRenderDevices();
+
+ voice.RequestSetRenderDevice("Speakers (Realtek High Definiti");
+
+ voice.RequestSetSpeakerVolume(75);
+ voice.RequestSetCaptureVolume(75);
+
+ voice.RequestStartTuningMode(10000);
+
+ //voice.RequestCreateConnector();
+
+
+
+ voice.RequestRenderAudioStart("bugsbunny1.wav", true);
+ voice.RequestRenderAudioStart("bugsbunny1.wav", false);
+
+ }
+
+ Console.ReadKey();
+
+ voice.RequestRenderAudioStop();
+ voice.RequestStopTuningMode();
+
+ Console.ReadKey();
+ }
+ }
+}
diff --git a/VoiceTest/VoiceTest.csproj b/VoiceTest/VoiceTest.csproj
new file mode 100644
index 00000000..50e4515b
--- /dev/null
+++ b/VoiceTest/VoiceTest.csproj
@@ -0,0 +1,59 @@
+
+
+ Debug
+ AnyCPU
+ 8.0.50727
+ 2.0
+ {B69597A7-5DC5-4564-9089-727D0348EB4E}
+ Exe
+ Properties
+ VoiceTest
+ VoiceTest
+
+
+ true
+ full
+ false
+ ..\bin\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ pdbonly
+ true
+ ..\bin\
+ TRACE
+ prompt
+ 4
+
+
+
+
+
+
+
+
+
+
+
+ {D9CDEDFB-8169-4B03-B57F-0DF638F044EC}
+ libsecondlife
+
+
+ {CE5E06C2-2428-416B-ADC1-F1FE16A0FB27}
+ libsecondlife.Utilities
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/libsecondlife/MainAvatar.cs b/libsecondlife/MainAvatar.cs
index 08ae5c8c..85be013e 100644
--- a/libsecondlife/MainAvatar.cs
+++ b/libsecondlife/MainAvatar.cs
@@ -2034,6 +2034,8 @@ namespace libsecondlife
{
Client.Log("Got EstablishAgentCommunication for unknown sim " + ipAndPort,
Helpers.LogLevel.Error);
+
+ // FIXME: Should we use this opportunity to connect to the simulator?
}
else
{
diff --git a/libsecondlife/NetworkManager.cs b/libsecondlife/NetworkManager.cs
index d9422f30..3994e27a 100644
--- a/libsecondlife/NetworkManager.cs
+++ b/libsecondlife/NetworkManager.cs
@@ -664,7 +664,7 @@ namespace libsecondlife
Simulator oldSim = CurrentSim;
lock (Simulators) CurrentSim = simulator; // CurrentSim is synchronized against Simulators
- simulator.SetSeedCaps(seedcaps);
+ simulator.SetSeedCaps(seedcaps);
// If the current simulator changed fire the callback
if (OnCurrentSimChanged != null && simulator != oldSim)
diff --git a/libsecondlife/Simulator.cs b/libsecondlife/Simulator.cs
index b1b315f7..109564a4 100644
--- a/libsecondlife/Simulator.cs
+++ b/libsecondlife/Simulator.cs
@@ -423,7 +423,7 @@ namespace libsecondlife
SimCaps = null;
}
- if (Client.Settings.ENABLE_CAPS) // [TODO] Implement caps
+ if (Client.Settings.ENABLE_CAPS)
{
// Connect to the new CAPS system
if (!String.IsNullOrEmpty(seedcaps))
diff --git a/libsecondlife/libsecondlife.Utilities/TCPPipe.cs b/libsecondlife/libsecondlife.Utilities/TCPPipe.cs
new file mode 100644
index 00000000..665f37d9
--- /dev/null
+++ b/libsecondlife/libsecondlife.Utilities/TCPPipe.cs
@@ -0,0 +1,156 @@
+using System;
+using System.Net;
+using System.Net.Sockets;
+
+public class TCPPipe
+{
+ protected class SocketPacket
+ {
+ public System.Net.Sockets.Socket TCPSocket;
+ public byte[] DataBuffer = new byte[1];
+ }
+
+ public delegate void OnReceiveLineCallback(string line);
+ public delegate void OnDisconnectedCallback(SocketException se);
+
+ public event OnReceiveLineCallback OnReceiveLine;
+ public event OnDisconnectedCallback OnDisconnected;
+
+ protected Socket _TCPSocket;
+ protected IAsyncResult _Result;
+ protected AsyncCallback _Callback;
+ protected string _Buffer = String.Empty;
+
+ public bool Connected
+ {
+ get
+ {
+ if (_TCPSocket != null && _TCPSocket.Connected)
+ return true;
+ else
+ return false;
+ }
+ }
+
+ public TCPPipe()
+ {
+ }
+
+ public SocketException Connect(string address, int port)
+ {
+ if (_TCPSocket != null && _TCPSocket.Connected)
+ Disconnect();
+
+ try
+ {
+ IPAddress ip;
+ if (!IPAddress.TryParse(address, out ip))
+ {
+ IPAddress[] ips = Dns.GetHostAddresses(address);
+ ip = ips[0];
+ }
+ _TCPSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
+ IPEndPoint ipEndPoint = new IPEndPoint(ip, port);
+ _TCPSocket.Connect(ipEndPoint);
+ if (_TCPSocket.Connected)
+ {
+ WaitForData();
+ return null;
+ }
+ else
+ {
+ return new SocketException(10000);
+ }
+ }
+ catch (SocketException se)
+ {
+ return se;
+ }
+ }
+
+ public void Disconnect()
+ {
+ _TCPSocket.Disconnect(true);
+ }
+
+ public void SendData(byte[] data)
+ {
+ if (Connected)
+ _TCPSocket.Send(data);
+ else
+ throw new InvalidOperationException("socket is not connected");
+ }
+
+ public void SendLine(string message)
+ {
+ if (Connected)
+ {
+ byte[] byData = System.Text.Encoding.ASCII.GetBytes(message + "\n");
+ _TCPSocket.Send(byData);
+ }
+ else
+ {
+ throw new InvalidOperationException("socket is not connected");
+ }
+ }
+
+ void WaitForData()
+ {
+ try
+ {
+ if (_Callback == null) _Callback = new AsyncCallback(OnDataReceived);
+ SocketPacket packet = new SocketPacket();
+ packet.TCPSocket = _TCPSocket;
+ _Result = _TCPSocket.BeginReceive(packet.DataBuffer, 0, packet.DataBuffer.Length, SocketFlags.None, _Callback, packet);
+ }
+ catch (SocketException se)
+ {
+ Console.WriteLine(se.Message);
+ }
+ }
+
+ void ReceiveData(string data)
+ {
+ if (OnReceiveLine == null) return;
+
+ string[] splitNull = { "\0" };
+ string[] line = data.Split(splitNull, StringSplitOptions.None);
+ _Buffer += line[0];
+ string[] splitLines = { "\r\n", "\r", "\n" };
+ string[] lines = _Buffer.Split(splitLines, StringSplitOptions.None);
+
+ if (lines.Length > 1)
+ {
+ int i;
+ for (i = 0; i < lines.Length - 1; i++)
+ {
+ if (lines[i].Trim().Length > 0) OnReceiveLine(lines[i]);
+ }
+ _Buffer = lines[i];
+ }
+ }
+
+ void OnDataReceived(IAsyncResult asyn)
+ {
+ try
+ {
+ SocketPacket packet = (SocketPacket)asyn.AsyncState;
+ int end = packet.TCPSocket.EndReceive(asyn);
+ char[] chars = new char[end + 1];
+ System.Text.Decoder d = System.Text.Encoding.UTF8.GetDecoder();
+ int charLen = d.GetChars(packet.DataBuffer, 0, end, chars, 0);
+ System.String data = new System.String(chars);
+ ReceiveData(data);
+ WaitForData();
+ }
+ catch (ObjectDisposedException)
+ {
+ Console.WriteLine("WARNING: Socket closed unexpectedly");
+ }
+ catch (SocketException se)
+ {
+ if (!_TCPSocket.Connected) OnDisconnected(se);
+ }
+ }
+
+}
diff --git a/libsecondlife/libsecondlife.Utilities/Utilities.cs b/libsecondlife/libsecondlife.Utilities/Utilities.cs
index 3c358ce8..ff847886 100644
--- a/libsecondlife/libsecondlife.Utilities/Utilities.cs
+++ b/libsecondlife/libsecondlife.Utilities/Utilities.cs
@@ -22,6 +22,36 @@ namespace libsecondlife.Utilities
Underwater
}
+ public enum Platform
+ {
+ Unknown,
+ Windows,
+ Linux,
+ OSX
+ }
+
+ public static class SLPlatformTools
+ {
+ public const string OSX_CHECK_FILE = "/Library/Extensions.kextcache";
+
+ public static Platform GetRunningPlatform()
+ {
+ int plat = (int)Environment.OSVersion.Platform;
+
+ if ((plat != 4) && (plat != 128))
+ {
+ return Platform.Windows;
+ }
+ else
+ {
+ if (System.IO.File.Exists(OSX_CHECK_FILE))
+ return Platform.OSX;
+ else
+ return Platform.Linux;
+ }
+ }
+ }
+
public static class Realism
{
///
diff --git a/libsecondlife/libsecondlife.Utilities/VoiceManager.cs b/libsecondlife/libsecondlife.Utilities/VoiceManager.cs
new file mode 100644
index 00000000..9c4b5d84
--- /dev/null
+++ b/libsecondlife/libsecondlife.Utilities/VoiceManager.cs
@@ -0,0 +1,332 @@
+using System;
+using System.Net.Sockets;
+using System.Text;
+using libsecondlife;
+
+namespace libsecondlife.Utilities
+{
+ public enum VoiceStatus
+ {
+ StatusLoginRetry,
+ StatusLoggedIn,
+ StatusJoining,
+ StatusJoined,
+ StatusLeftChannel,
+ BeginErrorStatus,
+ ErrorChannelFull,
+ ErrorChannelLocked,
+ ErrorNotAvailable,
+ ErrorUnknown
+ }
+
+ public enum VoiceServiceType
+ {
+ /// Unknown voice service level
+ Unknown,
+ /// Spatialized local chat
+ TypeA,
+ /// Remote multi-party chat
+ TypeB,
+ /// One-to-one and small group chat
+ TypeC
+ }
+
+ public class VoiceManager
+ {
+ public const string DAEMON_ARGS = " -p tcp -h -c -ll ";
+ public const int DAEMON_LOG_LEVEL = 1;
+ public const int DAEMON_PORT = 44124;
+ public const string VOICE_RELEASE_SERVER = "bhr.vivox.com";
+ public const string VOICE_DEBUG_SERVER = "bhd.vivox.com";
+ public const string REQUEST_TERMINATOR = "\n\n\n";
+
+ public SecondLife Client;
+
+ protected TCPPipe _DaemonPipe;
+ protected VoiceStatus _Status;
+ protected int _CommandCookie = 0;
+ protected string _TuningSoundFile = String.Empty;
+ protected string _VoiceServer = VOICE_RELEASE_SERVER;
+
+ public VoiceManager(SecondLife client)
+ {
+ Client = client;
+ }
+
+ public bool IsDaemonRunning()
+ {
+ return false;
+ }
+
+ public bool StartDaemon()
+ {
+ return false;
+ }
+
+ public void StopDaemon()
+ {
+ }
+
+ public bool ConnectToDaemon()
+ {
+ return ConnectToDaemon("127.0.0.1", DAEMON_PORT);
+ }
+
+ public bool ConnectToDaemon(string address, int port)
+ {
+ _DaemonPipe = new TCPPipe();
+ _DaemonPipe.OnDisconnected += new TCPPipe.OnDisconnectedCallback(_DaemonPipe_OnDisconnected);
+ _DaemonPipe.OnReceiveLine += new TCPPipe.OnReceiveLineCallback(_DaemonPipe_OnReceiveLine);
+
+ SocketException se = _DaemonPipe.Connect(address, port);
+
+ if (se == null)
+ {
+ return true;
+ }
+ else
+ {
+ Console.WriteLine("Connection failed: " + se.Message);
+ return false;
+ }
+ }
+
+ public string VoiceAccountFromUUID(LLUUID id)
+ {
+ string result = "x" + Convert.ToBase64String(id.GetBytes());
+ return result.Replace('+', '-').Replace('/', '_');
+ }
+
+ public LLUUID UUIDFromVoiceAccount(string accountName)
+ {
+ if (accountName.Length == 25 && accountName[0] == 'x' && accountName[23] == '=' && accountName[24] == '=')
+ {
+ accountName = accountName.Replace('/', '_').Replace('+', '-');
+ byte[] idBytes = Convert.FromBase64String(accountName);
+
+ if (idBytes.Length == 16)
+ return new LLUUID(idBytes, 0);
+ else
+ return LLUUID.Zero;
+ }
+ else
+ {
+ return LLUUID.Zero;
+ }
+ }
+
+ public string SIPURIFromVoiceAccount(string account)
+ {
+ return String.Format("sip:{0}@{1}", account, _VoiceServer);
+ }
+
+ public void RequestCaptureDevices()
+ {
+ if (_DaemonPipe.Connected)
+ {
+ _DaemonPipe.SendData(Encoding.ASCII.GetBytes(String.Format(
+ "{1}",
+ _CommandCookie++,
+ REQUEST_TERMINATOR)));
+ }
+ else
+ {
+ Client.Log("VoiceManager.RequestCaptureDevices() called when the daemon pipe is disconnected", Helpers.LogLevel.Error);
+ }
+ }
+
+ public void RequestRenderDevices()
+ {
+ if (_DaemonPipe.Connected)
+ {
+ _DaemonPipe.SendData(Encoding.ASCII.GetBytes(String.Format(
+ "{1}",
+ _CommandCookie++,
+ REQUEST_TERMINATOR)));
+ }
+ else
+ {
+ Client.Log("VoiceManager.RequestRenderDevices() called when the daemon pipe is disconnected", Helpers.LogLevel.Error);
+ }
+ }
+
+ public void RequestCreateConnector()
+ {
+ RequestCreateConnector(VOICE_RELEASE_SERVER);
+ }
+
+ public void RequestCreateConnector(string voiceServer)
+ {
+ if (_DaemonPipe.Connected)
+ {
+ _VoiceServer = voiceServer;
+
+ string accountServer = String.Format("https://www.{0}/api2/", _VoiceServer);
+ string logPath = ".";
+
+ StringBuilder request = new StringBuilder();
+ request.Append(String.Format("", _CommandCookie++));
+ request.Append("V2 SDK");
+ request.Append(String.Format("{0}", accountServer));
+ request.Append("");
+ request.Append("false");
+ request.Append(String.Format("{0}", logPath));
+ request.Append("vivox-gateway");
+ request.Append(".log");
+ request.Append("0");
+ request.Append("");
+ request.Append("");
+ request.Append(REQUEST_TERMINATOR);
+
+ _DaemonPipe.SendData(Encoding.ASCII.GetBytes(request.ToString()));
+ }
+ else
+ {
+ Client.Log("VoiceManager.CreateConnector() called when the daemon pipe is disconnected", Helpers.LogLevel.Error);
+ }
+ }
+
+ public void RequestLogin(string accountName, string password, string connectorHandle)
+ {
+ if (_DaemonPipe.Connected)
+ {
+ StringBuilder request = new StringBuilder();
+ request.Append(String.Format("", _CommandCookie++));
+ request.Append(String.Format("{0}", connectorHandle));
+ request.Append(String.Format("{0}", accountName));
+ request.Append(String.Format("{0}", password));
+ request.Append("VerifyAnswer");
+ request.Append("");
+ request.Append(REQUEST_TERMINATOR);
+
+ _DaemonPipe.SendData(Encoding.ASCII.GetBytes(request.ToString()));
+ }
+ else
+ {
+ Client.Log("VoiceManager.Login() called when the daemon pipe is disconnected", Helpers.LogLevel.Error);
+ }
+ }
+
+ public void RequestSetRenderDevice(string deviceName)
+ {
+ if (_DaemonPipe.Connected)
+ {
+ _DaemonPipe.SendData(Encoding.ASCII.GetBytes(String.Format(
+ "{1}{2}",
+ _CommandCookie, deviceName, REQUEST_TERMINATOR)));
+ }
+ else
+ {
+ Client.Log("VoiceManager.RequestSetRenderDevice() called when the daemon pipe is disconnected", Helpers.LogLevel.Error);
+ }
+ }
+
+ public void RequestStartTuningMode(int duration)
+ {
+ if (_DaemonPipe.Connected)
+ {
+ _DaemonPipe.SendData(Encoding.ASCII.GetBytes(String.Format(
+ "{1}{2}",
+ _CommandCookie, duration, REQUEST_TERMINATOR)));
+ }
+ else
+ {
+ Client.Log("VoiceManager.RequestStartTuningMode() called when the daemon pipe is disconnected", Helpers.LogLevel.Error);
+ }
+ }
+
+ public void RequestStopTuningMode()
+ {
+ if (_DaemonPipe.Connected)
+ {
+ _DaemonPipe.SendData(Encoding.ASCII.GetBytes(String.Format(
+ "{1}",
+ _CommandCookie, REQUEST_TERMINATOR)));
+ }
+ else
+ {
+ Client.Log("VoiceManager.RequestStopTuningMode() called when the daemon pipe is disconnected", Helpers.LogLevel.Error);
+ }
+ }
+
+ public void RequestSetSpeakerVolume(int volume)
+ {
+ if (volume < 0 || volume > 100)
+ throw new ArgumentException("volume must be between 0 and 100", "volume");
+
+ if (_DaemonPipe.Connected)
+ {
+ _DaemonPipe.SendData(Encoding.ASCII.GetBytes(String.Format(
+ "{1}{2}",
+ _CommandCookie, volume, REQUEST_TERMINATOR)));
+ }
+ else
+ {
+ Client.Log("VoiceManager.RequestSetSpeakerVolume() called when the daemon pipe is disconnected", Helpers.LogLevel.Error);
+ }
+ }
+
+ public void RequestSetCaptureVolume(int volume)
+ {
+ if (volume < 0 || volume > 100)
+ throw new ArgumentException("volume must be between 0 and 100", "volume");
+
+ if (_DaemonPipe.Connected)
+ {
+ _DaemonPipe.SendData(Encoding.ASCII.GetBytes(String.Format(
+ "{1}{2}",
+ _CommandCookie, volume, REQUEST_TERMINATOR)));
+ }
+ else
+ {
+ Client.Log("VoiceManager.RequestSetCaptureVolume() called when the daemon pipe is disconnected", Helpers.LogLevel.Error);
+ }
+ }
+
+ ///
+ /// Does not appear to be working
+ ///
+ ///
+ ///
+ public void RequestRenderAudioStart(string fileName, bool loop)
+ {
+ if (_DaemonPipe.Connected)
+ {
+ _TuningSoundFile = fileName;
+
+ _DaemonPipe.SendData(Encoding.ASCII.GetBytes(String.Format(
+ "{1}{2}{3}",
+ _CommandCookie++, _TuningSoundFile, (loop ? "1" : "0"), REQUEST_TERMINATOR)));
+ }
+ else
+ {
+ Client.Log("VoiceManager.RequestRenderAudioStart() called when the daemon pipe is disconnected", Helpers.LogLevel.Error);
+ }
+ }
+
+ public void RequestRenderAudioStop()
+ {
+ if (_DaemonPipe.Connected)
+ {
+ _DaemonPipe.SendData(Encoding.ASCII.GetBytes(String.Format(
+ "{1}{2}",
+ _CommandCookie++, _TuningSoundFile, REQUEST_TERMINATOR)));
+ }
+ else
+ {
+ Client.Log("VoiceManager.RequestRenderAudioStop() called when the daemon pipe is disconnected", Helpers.LogLevel.Error);
+ }
+ }
+
+ private void _DaemonPipe_OnDisconnected(SocketException se)
+ {
+ if (se != null) Console.WriteLine("Disconnected! " + se.Message);
+ else Console.WriteLine("Disconnected!");
+ }
+
+ private void _DaemonPipe_OnReceiveLine(string line)
+ {
+ Client.DebugLog("VOICE: " + line);
+ }
+ }
+}
diff --git a/libsecondlife/libsecondlife.Utilities/libsecondlife.Utilities.csproj b/libsecondlife/libsecondlife.Utilities/libsecondlife.Utilities.csproj
index 2ad096ff..a52fa31d 100644
--- a/libsecondlife/libsecondlife.Utilities/libsecondlife.Utilities.csproj
+++ b/libsecondlife/libsecondlife.Utilities/libsecondlife.Utilities.csproj
@@ -37,8 +37,10 @@
Code
+
+
diff --git a/libsecondlife/libsecondlife.sln b/libsecondlife/libsecondlife.sln
index 716032a1..a91922e9 100644
--- a/libsecondlife/libsecondlife.sln
+++ b/libsecondlife/libsecondlife.sln
@@ -42,6 +42,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "importprimscript", "..\impo
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SLImageUpload", "..\SLImageUpload\SLImageUpload.csproj", "{F3BDC0BC-74EB-451E-8FFE-AA162474F2F2}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VoiceTest", "..\VoiceTest\VoiceTest.csproj", "{B69597A7-5DC5-4564-9089-727D0348EB4E}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -131,6 +133,10 @@ Global
{F3BDC0BC-74EB-451E-8FFE-AA162474F2F2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F3BDC0BC-74EB-451E-8FFE-AA162474F2F2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F3BDC0BC-74EB-451E-8FFE-AA162474F2F2}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B69597A7-5DC5-4564-9089-727D0348EB4E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B69597A7-5DC5-4564-9089-727D0348EB4E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B69597A7-5DC5-4564-9089-727D0348EB4E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B69597A7-5DC5-4564-9089-727D0348EB4E}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE