corrade-vassal – Rev 1
?pathlinks?
/*
* Copyright (c) 2006-2014, openmetaverse.org
* All rights reserved.
*
* - Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* - Neither the name of the openmetaverse.org nor the names
* of its contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
//#define DEBUG_VOICE
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Threading;
using OpenMetaverse;
using OpenMetaverse.StructuredData;
namespace OpenMetaverse.Voice
{
public partial class VoiceGateway : IDisposable
{
// These states should be in increasing order of 'completeness'
// so that the (int) values can drive a progress bar.
public enum ConnectionState
{
None = 0,
Provisioned,
DaemonStarted,
DaemonConnected,
ConnectorConnected,
AccountLogin,
RegionCapAvailable,
SessionRunning
}
internal string sipServer = "";
private string acctServer = "https://www.bhr.vivox.com/api2/";
private string connectionHandle;
private string accountHandle;
private string sessionHandle;
// Parameters to Vivox daemon
private string slvoicePath = "";
private string slvoiceArgs = "-ll 5";
private string daemonNode = "127.0.0.1";
private int daemonPort = 37331;
private string voiceUser;
private string voicePassword;
private string spatialUri;
private string spatialCredentials;
// Session management
private Dictionary<string, VoiceSession> sessions;
private VoiceSession spatialSession;
private Uri currentParcelCap;
private Uri nextParcelCap;
private string regionName;
// Position update thread
private Thread posThread;
private ManualResetEvent posRestart;
public GridClient Client;
private VoicePosition position;
private Vector3d oldPosition;
private Vector3d oldAt;
// Audio interfaces
private List<string> inputDevices;
/// <summary>
/// List of audio input devices
/// </summary>
public List<string> CaptureDevices { get { return inputDevices; } }
private List<string> outputDevices;
/// <summary>
/// List of audio output devices
/// </summary>
public List<string> PlaybackDevices { get { return outputDevices; } }
private string currentCaptureDevice;
private string currentPlaybackDevice;
private bool testing = false;
public event EventHandler OnSessionCreate;
public event EventHandler OnSessionRemove;
public delegate void VoiceConnectionChangeCallback(ConnectionState state);
public event VoiceConnectionChangeCallback OnVoiceConnectionChange;
public delegate void VoiceMicTestCallback(float level);
public event VoiceMicTestCallback OnVoiceMicTest;
public VoiceGateway(GridClient c)
{
Random rand = new Random();
daemonPort = rand.Next(34000, 44000);
Client = c;
sessions = new Dictionary<string, VoiceSession>();
position = new VoicePosition();
position.UpOrientation = new Vector3d(0.0, 1.0, 0.0);
position.Velocity = new Vector3d(0.0, 0.0, 0.0);
oldPosition = new Vector3d(0, 0, 0);
oldAt = new Vector3d(1, 0, 0);
slvoiceArgs = " -ll -1"; // Min logging
slvoiceArgs += " -i 127.0.0.1:" + daemonPort.ToString();
// slvoiceArgs += " -lf " + control.instance.ClientDir;
}
/// <summary>
/// Start up the Voice service.
/// </summary>
public void Start()
{
// Start the background thread
if (posThread != null && posThread.IsAlive)
posThread.Abort();
posThread = new Thread(new ThreadStart(PositionThreadBody));
posThread.Name = "VoicePositionUpdate";
posThread.IsBackground = true;
posRestart = new ManualResetEvent(false);
posThread.Start();
Client.Network.EventQueueRunning += new EventHandler<EventQueueRunningEventArgs>(Network_EventQueueRunning);
// Connection events
OnDaemonRunning +=
new VoiceGateway.DaemonRunningCallback(connector_OnDaemonRunning);
OnDaemonCouldntRun +=
new VoiceGateway.DaemonCouldntRunCallback(connector_OnDaemonCouldntRun);
OnConnectorCreateResponse +=
new EventHandler<VoiceGateway.VoiceConnectorEventArgs>(connector_OnConnectorCreateResponse);
OnDaemonConnected +=
new DaemonConnectedCallback(connector_OnDaemonConnected);
OnDaemonCouldntConnect +=
new DaemonCouldntConnectCallback(connector_OnDaemonCouldntConnect);
OnAuxAudioPropertiesEvent +=
new EventHandler<AudioPropertiesEventArgs>(connector_OnAuxAudioPropertiesEvent);
// Session events
OnSessionStateChangeEvent +=
new EventHandler<SessionStateChangeEventArgs>(connector_OnSessionStateChangeEvent);
OnSessionAddedEvent +=
new EventHandler<SessionAddedEventArgs>(connector_OnSessionAddedEvent);
// Session Participants events
OnSessionParticipantUpdatedEvent +=
new EventHandler<ParticipantUpdatedEventArgs>(connector_OnSessionParticipantUpdatedEvent);
OnSessionParticipantAddedEvent +=
new EventHandler<ParticipantAddedEventArgs>(connector_OnSessionParticipantAddedEvent);
// Device events
OnAuxGetCaptureDevicesResponse +=
new EventHandler<VoiceDevicesEventArgs>(connector_OnAuxGetCaptureDevicesResponse);
OnAuxGetRenderDevicesResponse +=
new EventHandler<VoiceDevicesEventArgs>(connector_OnAuxGetRenderDevicesResponse);
// Generic status response
OnVoiceResponse += new EventHandler<VoiceResponseEventArgs>(connector_OnVoiceResponse);
// Account events
OnAccountLoginResponse +=
new EventHandler<VoiceAccountEventArgs>(connector_OnAccountLoginResponse);
Logger.Log("Voice initialized", Helpers.LogLevel.Info);
// If voice provisioning capability is already available,
// proceed with voice startup. Otherwise the EventQueueRunning
// event will do it.
System.Uri vCap =
Client.Network.CurrentSim.Caps.CapabilityURI("ProvisionVoiceAccountRequest");
if (vCap != null)
RequestVoiceProvision(vCap);
}
/// <summary>
/// Handle miscellaneous request status
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
/// ///<remarks>If something goes wrong, we log it.</remarks>
void connector_OnVoiceResponse(object sender, VoiceGateway.VoiceResponseEventArgs e)
{
if (e.StatusCode == 0)
return;
Logger.Log(e.Message + " on " + sender as string, Helpers.LogLevel.Error);
}
public void Stop()
{
Client.Network.EventQueueRunning -= new EventHandler<EventQueueRunningEventArgs>(Network_EventQueueRunning);
// Connection events
OnDaemonRunning -=
new VoiceGateway.DaemonRunningCallback(connector_OnDaemonRunning);
OnDaemonCouldntRun -=
new VoiceGateway.DaemonCouldntRunCallback(connector_OnDaemonCouldntRun);
OnConnectorCreateResponse -=
new EventHandler<VoiceGateway.VoiceConnectorEventArgs>(connector_OnConnectorCreateResponse);
OnDaemonConnected -=
new VoiceGateway.DaemonConnectedCallback(connector_OnDaemonConnected);
OnDaemonCouldntConnect -=
new VoiceGateway.DaemonCouldntConnectCallback(connector_OnDaemonCouldntConnect);
OnAuxAudioPropertiesEvent -=
new EventHandler<AudioPropertiesEventArgs>(connector_OnAuxAudioPropertiesEvent);
// Session events
OnSessionStateChangeEvent -=
new EventHandler<SessionStateChangeEventArgs>(connector_OnSessionStateChangeEvent);
OnSessionAddedEvent -=
new EventHandler<SessionAddedEventArgs>(connector_OnSessionAddedEvent);
// Session Participants events
OnSessionParticipantUpdatedEvent -=
new EventHandler<ParticipantUpdatedEventArgs>(connector_OnSessionParticipantUpdatedEvent);
OnSessionParticipantAddedEvent -=
new EventHandler<ParticipantAddedEventArgs>(connector_OnSessionParticipantAddedEvent);
OnSessionParticipantRemovedEvent -=
new EventHandler<ParticipantRemovedEventArgs>(connector_OnSessionParticipantRemovedEvent);
// Tuning events
OnAuxGetCaptureDevicesResponse -=
new EventHandler<VoiceGateway.VoiceDevicesEventArgs>(connector_OnAuxGetCaptureDevicesResponse);
OnAuxGetRenderDevicesResponse -=
new EventHandler<VoiceGateway.VoiceDevicesEventArgs>(connector_OnAuxGetRenderDevicesResponse);
// Account events
OnAccountLoginResponse -=
new EventHandler<VoiceGateway.VoiceAccountEventArgs>(connector_OnAccountLoginResponse);
// Stop the background thread
if (posThread != null)
{
PosUpdating(false);
if (posThread.IsAlive)
posThread.Abort();
posThread = null;
}
// Close all sessions
foreach (VoiceSession s in sessions.Values)
{
if (OnSessionRemove != null)
OnSessionRemove(s, EventArgs.Empty);
s.Close();
}
// Clear out lots of state so in case of restart we begin at the beginning.
currentParcelCap = null;
sessions.Clear();
accountHandle = null;
voiceUser = null;
voicePassword = null;
SessionTerminate(sessionHandle);
sessionHandle = null;
AccountLogout(accountHandle);
accountHandle = null;
ConnectorInitiateShutdown(connectionHandle);
connectionHandle = null;
StopDaemon();
}
/// <summary>
/// Cleanup oject resources
/// </summary>
public void Dispose()
{
Stop();
}
internal string GetVoiceDaemonPath()
{
string myDir =
Path.GetDirectoryName(
(System.Reflection.Assembly.GetEntryAssembly() ?? typeof (VoiceGateway).Assembly).Location);
if (Environment.OSVersion.Platform != PlatformID.MacOSX &&
Environment.OSVersion.Platform != PlatformID.Unix)
{
string localDaemon = Path.Combine(myDir, Path.Combine("voice", "SLVoice.exe"));
if (File.Exists(localDaemon))
return localDaemon;
string progFiles;
if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("ProgramFiles(x86)")))
{
progFiles = Environment.GetEnvironmentVariable("ProgramFiles(x86)");
}
else
{
progFiles = Environment.GetEnvironmentVariable("ProgramFiles");
}
if (System.IO.File.Exists(Path.Combine(progFiles, @"SecondLife" + Path.DirectorySeparatorChar + @"SLVoice.exe")))
{
return Path.Combine(progFiles, @"SecondLife" + Path.DirectorySeparatorChar + @"SLVoice.exe");
}
return Path.Combine(myDir, @"SLVoice.exe");
}
else
{
string localDaemon = Path.Combine(myDir, Path.Combine("voice", "SLVoice"));
if (File.Exists(localDaemon))
return localDaemon;
return Path.Combine(myDir,"SLVoice");
}
}
void RequestVoiceProvision(System.Uri cap)
{
OpenMetaverse.Http.CapsClient capClient =
new OpenMetaverse.Http.CapsClient(cap);
capClient.OnComplete +=
new OpenMetaverse.Http.CapsClient.CompleteCallback(cClient_OnComplete);
OSD postData = new OSD();
// STEP 0
Logger.Log("Requesting voice capability", Helpers.LogLevel.Info);
capClient.BeginGetResponse(postData, OSDFormat.Xml, 10000);
}
/// <summary>
/// Request voice cap when changing regions
/// </summary>
void Network_EventQueueRunning(object sender, EventQueueRunningEventArgs e)
{
// We only care about the sim we are in.
if (e.Simulator != Client.Network.CurrentSim)
return;
// Did we provision voice login info?
if (string.IsNullOrEmpty(voiceUser))
{
// The startup steps are
// 0. Get voice account info
// 1. Start Daemon
// 2. Create TCP connection
// 3. Create Connector
// 4. Account login
// 5. Create session
// Get the voice provisioning data
System.Uri vCap =
Client.Network.CurrentSim.Caps.CapabilityURI("ProvisionVoiceAccountRequest");
// Do we have voice capability?
if (vCap == null)
{
Logger.Log("Null voice capability after event queue running", Helpers.LogLevel.Warning);
}
else
{
RequestVoiceProvision(vCap);
}
return;
}
else
{
// Change voice session for this region.
ParcelChanged();
}
}
#region Participants
void connector_OnSessionParticipantUpdatedEvent(object sender, ParticipantUpdatedEventArgs e)
{
VoiceSession s = FindSession(e.SessionHandle, false);
if (s == null) return;
s.ParticipantUpdate(e.URI, e.IsMuted, e.IsSpeaking, e.Volume, e.Energy);
}
public string SIPFromUUID(UUID id)
{
return "sip:" +
nameFromID(id) +
"@" +
sipServer;
}
private static string nameFromID(UUID id)
{
string result = null;
if (id == UUID.Zero)
return result;
// Prepending this apparently prevents conflicts with reserved names inside the vivox and diamondware code.
result = "x";
// Base64 encode and replace the pieces of base64 that are less compatible
// with e-mail local-parts.
// See RFC-4648 "Base 64 Encoding with URL and Filename Safe Alphabet"
byte[] encbuff = id.GetBytes();
result += Convert.ToBase64String(encbuff);
result = result.Replace('+', '-');
result = result.Replace('/', '_');
return result;
}
void connector_OnSessionParticipantAddedEvent(object sender, ParticipantAddedEventArgs e)
{
VoiceSession s = FindSession(e.SessionHandle, false);
if (s == null)
{
Logger.Log("Orphan participant", Helpers.LogLevel.Error);
return;
}
s.AddParticipant(e.URI);
}
void connector_OnSessionParticipantRemovedEvent(object sender, ParticipantRemovedEventArgs e)
{
VoiceSession s = FindSession(e.SessionHandle, false);
if (s == null) return;
s.RemoveParticipant(e.URI);
}
#endregion
#region Sessions
void connector_OnSessionAddedEvent(object sender, SessionAddedEventArgs e)
{
sessionHandle = e.SessionHandle;
// Create our session context.
VoiceSession s = FindSession(sessionHandle, true);
s.RegionName = regionName;
spatialSession = s;
// Tell any user-facing code.
if (OnSessionCreate != null)
OnSessionCreate(s, null);
Logger.Log("Added voice session in " + regionName, Helpers.LogLevel.Info);
}
/// <summary>
/// Handle a change in session state
/// </summary>
void connector_OnSessionStateChangeEvent(object sender, SessionStateChangeEventArgs e)
{
VoiceSession s;
switch (e.State)
{
case VoiceGateway.SessionState.Connected:
s = FindSession(e.SessionHandle, true);
sessionHandle = e.SessionHandle;
s.RegionName = regionName;
spatialSession = s;
Logger.Log("Voice connected in " + regionName, Helpers.LogLevel.Info);
// Tell any user-facing code.
if (OnSessionCreate != null)
OnSessionCreate(s, null);
break;
case VoiceGateway.SessionState.Disconnected:
s = FindSession(sessionHandle, false);
sessions.Remove(sessionHandle);
if (s != null)
{
Logger.Log("Voice disconnected in " + s.RegionName, Helpers.LogLevel.Info);
// Inform interested parties
if (OnSessionRemove != null)
OnSessionRemove(s, null);
if (s == spatialSession)
spatialSession = null;
}
// The previous session is now ended. Check for a new one and
// start it going.
if (nextParcelCap != null)
{
currentParcelCap = nextParcelCap;
nextParcelCap = null;
RequestParcelInfo(currentParcelCap);
}
break;
}
}
/// <summary>
/// Close a voice session
/// </summary>
/// <param name="sessionHandle"></param>
internal void CloseSession(string sessionHandle)
{
if (!sessions.ContainsKey(sessionHandle))
return;
PosUpdating(false);
ReportConnectionState(ConnectionState.AccountLogin);
// Clean up spatial pointers.
VoiceSession s = sessions[sessionHandle];
if (s.IsSpatial)
{
spatialSession = null;
currentParcelCap = null;
}
// Remove this session from the master session list
sessions.Remove(sessionHandle);
// Let any user-facing code clean up.
if (OnSessionRemove != null)
OnSessionRemove(s, null);
// Tell SLVoice to clean it up as well.
SessionTerminate(sessionHandle);
}
/// <summary>
/// Locate a Session context from its handle
/// </summary>
/// <remarks>Creates the session context if it does not exist.</remarks>
VoiceSession FindSession(string sessionHandle, bool make)
{
if (sessions.ContainsKey(sessionHandle))
return sessions[sessionHandle];
if (!make) return null;
// Create a new session and add it to the sessions list.
VoiceSession s = new VoiceSession(this, sessionHandle);
// Turn on position updating for spatial sessions
// (For now, only spatial sessions are supported)
if (s.IsSpatial)
PosUpdating(true);
// Register the session by its handle
sessions.Add(sessionHandle, s);
return s;
}
#endregion
#region MinorResponses
void connector_OnAuxAudioPropertiesEvent(object sender, AudioPropertiesEventArgs e)
{
if (OnVoiceMicTest != null)
OnVoiceMicTest(e.MicEnergy);
}
#endregion
private void ReportConnectionState(ConnectionState s)
{
if (OnVoiceConnectionChange == null) return;
OnVoiceConnectionChange(s);
}
/// <summary>
/// Handle completion of main voice cap request.
/// </summary>
/// <param name="client"></param>
/// <param name="result"></param>
/// <param name="error"></param>
void cClient_OnComplete(OpenMetaverse.Http.CapsClient client,
OpenMetaverse.StructuredData.OSD result,
Exception error)
{
if (error != null)
{
Logger.Log("Voice cap error " + error.Message, Helpers.LogLevel.Error);
return;
}
Logger.Log("Voice provisioned", Helpers.LogLevel.Info);
ReportConnectionState(ConnectionState.Provisioned);
OpenMetaverse.StructuredData.OSDMap pMap = result as OpenMetaverse.StructuredData.OSDMap;
// We can get back 4 interesting values:
// voice_sip_uri_hostname
// voice_account_server_name (actually a full URI)
// username
// password
if (pMap.ContainsKey("voice_sip_uri_hostname"))
sipServer = pMap["voice_sip_uri_hostname"].AsString();
if (pMap.ContainsKey("voice_account_server_name"))
acctServer = pMap["voice_account_server_name"].AsString();
voiceUser = pMap["username"].AsString();
voicePassword = pMap["password"].AsString();
// Start the SLVoice daemon
slvoicePath = GetVoiceDaemonPath();
// Test if the executable exists
if (!System.IO.File.Exists(slvoicePath))
{
Logger.Log("SLVoice is missing", Helpers.LogLevel.Error);
return;
}
// STEP 1
StartDaemon(slvoicePath, slvoiceArgs);
}
#region Daemon
void connector_OnDaemonCouldntConnect()
{
Logger.Log("No voice daemon connect", Helpers.LogLevel.Error);
}
void connector_OnDaemonCouldntRun()
{
Logger.Log("Daemon not started", Helpers.LogLevel.Error);
}
/// <summary>
/// Daemon has started so connect to it.
/// </summary>
void connector_OnDaemonRunning()
{
OnDaemonRunning -=
new VoiceGateway.DaemonRunningCallback(connector_OnDaemonRunning);
Logger.Log("Daemon started", Helpers.LogLevel.Info);
ReportConnectionState(ConnectionState.DaemonStarted);
// STEP 2
ConnectToDaemon(daemonNode, daemonPort);
}
/// <summary>
/// The daemon TCP connection is open.
/// </summary>
void connector_OnDaemonConnected()
{
Logger.Log("Daemon connected", Helpers.LogLevel.Info);
ReportConnectionState(ConnectionState.DaemonConnected);
// The connector is what does the logging.
VoiceGateway.VoiceLoggingSettings vLog =
new VoiceGateway.VoiceLoggingSettings();
#if DEBUG_VOICE
vLog.Enabled = true;
vLog.FileNamePrefix = "OpenmetaverseVoice";
vLog.FileNameSuffix = ".log";
vLog.LogLevel = 4;
#endif
// STEP 3
int reqId = ConnectorCreate(
"V2 SDK", // Magic value keeps SLVoice happy
acctServer, // Account manager server
30000, 30099, // port range
vLog);
if (reqId < 0)
{
Logger.Log("No voice connector request", Helpers.LogLevel.Error);
}
}
/// <summary>
/// Handle creation of the Connector.
/// </summary>
void connector_OnConnectorCreateResponse(
object sender,
VoiceGateway.VoiceConnectorEventArgs e)
{
Logger.Log("Voice daemon protocol started " + e.Message, Helpers.LogLevel.Info);
connectionHandle = e.Handle;
if (e.StatusCode != 0)
return;
// STEP 4
AccountLogin(
connectionHandle,
voiceUser,
voicePassword,
"VerifyAnswer", // This can also be "AutoAnswer"
"", // Default account management server URI
10, // Throttle state changes
true); // Enable buddies and presence
}
#endregion
void connector_OnAccountLoginResponse(
object sender,
VoiceGateway.VoiceAccountEventArgs e)
{
Logger.Log("Account Login " + e.Message, Helpers.LogLevel.Info);
accountHandle = e.AccountHandle;
ReportConnectionState(ConnectionState.AccountLogin);
ParcelChanged();
}
#region Audio devices
/// <summary>
/// Handle response to audio output device query
/// </summary>
void connector_OnAuxGetRenderDevicesResponse(
object sender,
VoiceGateway.VoiceDevicesEventArgs e)
{
outputDevices = e.Devices;
currentPlaybackDevice = e.CurrentDevice;
}
/// <summary>
/// Handle response to audio input device query
/// </summary>
void connector_OnAuxGetCaptureDevicesResponse(
object sender,
VoiceGateway.VoiceDevicesEventArgs e)
{
inputDevices = e.Devices;
currentCaptureDevice = e.CurrentDevice;
}
public string CurrentCaptureDevice
{
get { return currentCaptureDevice; }
set
{
currentCaptureDevice = value;
AuxSetCaptureDevice(value);
}
}
public string PlaybackDevice
{
get { return currentPlaybackDevice; }
set
{
currentPlaybackDevice = value;
AuxSetRenderDevice(value);
}
}
public int MicLevel
{
set
{
ConnectorSetLocalMicVolume(connectionHandle, value);
}
}
public int SpkrLevel
{
set
{
ConnectorSetLocalSpeakerVolume(connectionHandle, value);
}
}
public bool MicMute
{
set
{
ConnectorMuteLocalMic(connectionHandle, value);
}
}
public bool SpkrMute
{
set
{
ConnectorMuteLocalSpeaker(connectionHandle, value);
}
}
/// <summary>
/// Set audio test mode
/// </summary>
public bool TestMode
{
get { return testing; }
set
{
testing = value;
if (testing)
{
if (spatialSession != null)
{
spatialSession.Close();
spatialSession = null;
}
AuxCaptureAudioStart(0);
}
else
{
AuxCaptureAudioStop();
ParcelChanged();
}
}
}
#endregion
/// <summary>
/// Set voice channel for new parcel
/// </summary>
///
internal void ParcelChanged()
{
// Get the capability for this parcel.
Caps c = Client.Network.CurrentSim.Caps;
System.Uri pCap = c.CapabilityURI("ParcelVoiceInfoRequest");
if (pCap == null)
{
Logger.Log("Null voice capability", Helpers.LogLevel.Error);
return;
}
// Parcel has changed. If we were already in a spatial session, we have to close it first.
if (spatialSession != null)
{
nextParcelCap = pCap;
CloseSession(spatialSession.Handle);
}
// Not already in a session, so can start the new one.
RequestParcelInfo(pCap);
}
private OpenMetaverse.Http.CapsClient parcelCap;
/// <summary>
/// Request info from a parcel capability Uri.
/// </summary>
/// <param name="cap"></param>
void RequestParcelInfo(Uri cap)
{
Logger.Log("Requesting region voice info", Helpers.LogLevel.Info);
parcelCap = new OpenMetaverse.Http.CapsClient(cap);
parcelCap.OnComplete +=
new OpenMetaverse.Http.CapsClient.CompleteCallback(pCap_OnComplete);
OSD postData = new OSD();
currentParcelCap = cap;
parcelCap.BeginGetResponse(postData, OSDFormat.Xml, 10000);
}
/// <summary>
/// Receive parcel voice cap
/// </summary>
/// <param name="client"></param>
/// <param name="result"></param>
/// <param name="error"></param>
void pCap_OnComplete(OpenMetaverse.Http.CapsClient client,
OpenMetaverse.StructuredData.OSD result,
Exception error)
{
parcelCap.OnComplete -=
new OpenMetaverse.Http.CapsClient.CompleteCallback(pCap_OnComplete);
parcelCap = null;
if (error != null)
{
Logger.Log("Region voice cap " + error.Message, Helpers.LogLevel.Error);
return;
}
OpenMetaverse.StructuredData.OSDMap pMap = result as OpenMetaverse.StructuredData.OSDMap;
regionName = pMap["region_name"].AsString();
ReportConnectionState(ConnectionState.RegionCapAvailable);
if (pMap.ContainsKey("voice_credentials"))
{
OpenMetaverse.StructuredData.OSDMap cred =
pMap["voice_credentials"] as OpenMetaverse.StructuredData.OSDMap;
if (cred.ContainsKey("channel_uri"))
spatialUri = cred["channel_uri"].AsString();
if (cred.ContainsKey("channel_credentials"))
spatialCredentials = cred["channel_credentials"].AsString();
}
if (spatialUri == null || spatialUri == "")
{
// "No voice chat allowed here");
return;
}
Logger.Log("Voice connecting for region " + regionName, Helpers.LogLevel.Info);
// STEP 5
int reqId = SessionCreate(
accountHandle,
spatialUri, // uri
"", // Channel name seems to be always null
spatialCredentials, // spatialCredentials, // session password
true, // Join Audio
false, // Join Text
"");
if (reqId < 0)
{
Logger.Log("Voice Session ReqID " + reqId.ToString(), Helpers.LogLevel.Error);
}
}
#region Location Update
/// <summary>
/// Tell Vivox where we are standing
/// </summary>
/// <remarks>This has to be called when we move or turn.</remarks>
internal void UpdatePosition(AgentManager self)
{
// Get position in Global coordinates
Vector3d OMVpos = new Vector3d(self.GlobalPosition);
// Do not send trivial updates.
if (OMVpos.ApproxEquals(oldPosition, 1.0))
return;
oldPosition = OMVpos;
// Convert to the coordinate space that Vivox uses
// OMV X is East, Y is North, Z is up
// VVX X is East, Y is up, Z is South
position.Position = new Vector3d(OMVpos.X, OMVpos.Z, -OMVpos.Y);
// TODO Rotate these two vectors
// Get azimuth from the facing Quaternion.
// By definition, facing.W = Cos( angle/2 )
double angle = 2.0 * Math.Acos(self.Movement.BodyRotation.W);
position.LeftOrientation = new Vector3d(-1.0, 0.0, 0.0);
position.AtOrientation = new Vector3d((float)Math.Acos(angle), 0.0, -(float)Math.Asin(angle));
SessionSet3DPosition(
sessionHandle,
position,
position);
}
/// <summary>
/// Start and stop updating out position.
/// </summary>
/// <param name="go"></param>
internal void PosUpdating(bool go)
{
if (go)
posRestart.Set();
else
posRestart.Reset();
}
private void PositionThreadBody()
{
while (true)
{
posRestart.WaitOne();
Thread.Sleep(1500);
UpdatePosition(Client.Self);
}
}
#endregion
}
}