corrade-vassal – Blame information for rev 1

Subversion Repositories:
Rev:
Rev Author Line No. Line
1 vero 1 /*
2 * Copyright (c) 2006-2014, openmetaverse.org
3 * All rights reserved.
4 *
5 * - Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * - Redistributions of source code must retain the above copyright notice, this
9 * list of conditions and the following disclaimer.
10 * - Neither the name of the openmetaverse.org nor the names
11 * of its contributors may be used to endorse or promote products derived from
12 * this software without specific prior written permission.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
15 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
18 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
22 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
24 * POSSIBILITY OF SUCH DAMAGE.
25 */
26 //#define DEBUG_VOICE
27  
28 using System;
29 using System.Collections.Generic;
30 using System.Linq;
31 using System.Text;
32 using System.IO;
33 using System.Threading;
34  
35 using OpenMetaverse;
36 using OpenMetaverse.StructuredData;
37  
38 namespace OpenMetaverse.Voice
39 {
40 public partial class VoiceGateway : IDisposable
41 {
42 // These states should be in increasing order of 'completeness'
43 // so that the (int) values can drive a progress bar.
44 public enum ConnectionState
45 {
46 None = 0,
47 Provisioned,
48 DaemonStarted,
49 DaemonConnected,
50 ConnectorConnected,
51 AccountLogin,
52 RegionCapAvailable,
53 SessionRunning
54 }
55  
56 internal string sipServer = "";
57 private string acctServer = "https://www.bhr.vivox.com/api2/";
58 private string connectionHandle;
59 private string accountHandle;
60 private string sessionHandle;
61  
62 // Parameters to Vivox daemon
63 private string slvoicePath = "";
64 private string slvoiceArgs = "-ll 5";
65 private string daemonNode = "127.0.0.1";
66 private int daemonPort = 37331;
67  
68 private string voiceUser;
69 private string voicePassword;
70 private string spatialUri;
71 private string spatialCredentials;
72  
73 // Session management
74 private Dictionary<string, VoiceSession> sessions;
75 private VoiceSession spatialSession;
76 private Uri currentParcelCap;
77 private Uri nextParcelCap;
78 private string regionName;
79  
80 // Position update thread
81 private Thread posThread;
82 private ManualResetEvent posRestart;
83 public GridClient Client;
84 private VoicePosition position;
85 private Vector3d oldPosition;
86 private Vector3d oldAt;
87  
88 // Audio interfaces
89 private List<string> inputDevices;
90 /// <summary>
91 /// List of audio input devices
92 /// </summary>
93 public List<string> CaptureDevices { get { return inputDevices; } }
94 private List<string> outputDevices;
95 /// <summary>
96 /// List of audio output devices
97 /// </summary>
98 public List<string> PlaybackDevices { get { return outputDevices; } }
99 private string currentCaptureDevice;
100 private string currentPlaybackDevice;
101 private bool testing = false;
102  
103 public event EventHandler OnSessionCreate;
104 public event EventHandler OnSessionRemove;
105 public delegate void VoiceConnectionChangeCallback(ConnectionState state);
106 public event VoiceConnectionChangeCallback OnVoiceConnectionChange;
107 public delegate void VoiceMicTestCallback(float level);
108 public event VoiceMicTestCallback OnVoiceMicTest;
109  
110 public VoiceGateway(GridClient c)
111 {
112 Random rand = new Random();
113 daemonPort = rand.Next(34000, 44000);
114  
115 Client = c;
116  
117 sessions = new Dictionary<string, VoiceSession>();
118 position = new VoicePosition();
119 position.UpOrientation = new Vector3d(0.0, 1.0, 0.0);
120 position.Velocity = new Vector3d(0.0, 0.0, 0.0);
121 oldPosition = new Vector3d(0, 0, 0);
122 oldAt = new Vector3d(1, 0, 0);
123  
124 slvoiceArgs = " -ll -1"; // Min logging
125 slvoiceArgs += " -i 127.0.0.1:" + daemonPort.ToString();
126 // slvoiceArgs += " -lf " + control.instance.ClientDir;
127 }
128  
129 /// <summary>
130 /// Start up the Voice service.
131 /// </summary>
132 public void Start()
133 {
134 // Start the background thread
135 if (posThread != null && posThread.IsAlive)
136 posThread.Abort();
137 posThread = new Thread(new ThreadStart(PositionThreadBody));
138 posThread.Name = "VoicePositionUpdate";
139 posThread.IsBackground = true;
140 posRestart = new ManualResetEvent(false);
141 posThread.Start();
142  
143 Client.Network.EventQueueRunning += new EventHandler<EventQueueRunningEventArgs>(Network_EventQueueRunning);
144  
145 // Connection events
146 OnDaemonRunning +=
147 new VoiceGateway.DaemonRunningCallback(connector_OnDaemonRunning);
148 OnDaemonCouldntRun +=
149 new VoiceGateway.DaemonCouldntRunCallback(connector_OnDaemonCouldntRun);
150 OnConnectorCreateResponse +=
151 new EventHandler<VoiceGateway.VoiceConnectorEventArgs>(connector_OnConnectorCreateResponse);
152 OnDaemonConnected +=
153 new DaemonConnectedCallback(connector_OnDaemonConnected);
154 OnDaemonCouldntConnect +=
155 new DaemonCouldntConnectCallback(connector_OnDaemonCouldntConnect);
156 OnAuxAudioPropertiesEvent +=
157 new EventHandler<AudioPropertiesEventArgs>(connector_OnAuxAudioPropertiesEvent);
158  
159 // Session events
160 OnSessionStateChangeEvent +=
161 new EventHandler<SessionStateChangeEventArgs>(connector_OnSessionStateChangeEvent);
162 OnSessionAddedEvent +=
163 new EventHandler<SessionAddedEventArgs>(connector_OnSessionAddedEvent);
164  
165 // Session Participants events
166 OnSessionParticipantUpdatedEvent +=
167 new EventHandler<ParticipantUpdatedEventArgs>(connector_OnSessionParticipantUpdatedEvent);
168 OnSessionParticipantAddedEvent +=
169 new EventHandler<ParticipantAddedEventArgs>(connector_OnSessionParticipantAddedEvent);
170  
171 // Device events
172 OnAuxGetCaptureDevicesResponse +=
173 new EventHandler<VoiceDevicesEventArgs>(connector_OnAuxGetCaptureDevicesResponse);
174 OnAuxGetRenderDevicesResponse +=
175 new EventHandler<VoiceDevicesEventArgs>(connector_OnAuxGetRenderDevicesResponse);
176  
177 // Generic status response
178 OnVoiceResponse += new EventHandler<VoiceResponseEventArgs>(connector_OnVoiceResponse);
179  
180 // Account events
181 OnAccountLoginResponse +=
182 new EventHandler<VoiceAccountEventArgs>(connector_OnAccountLoginResponse);
183  
184 Logger.Log("Voice initialized", Helpers.LogLevel.Info);
185  
186 // If voice provisioning capability is already available,
187 // proceed with voice startup. Otherwise the EventQueueRunning
188 // event will do it.
189 System.Uri vCap =
190 Client.Network.CurrentSim.Caps.CapabilityURI("ProvisionVoiceAccountRequest");
191 if (vCap != null)
192 RequestVoiceProvision(vCap);
193  
194 }
195  
196 /// <summary>
197 /// Handle miscellaneous request status
198 /// </summary>
199 /// <param name="sender"></param>
200 /// <param name="e"></param>
201 /// ///<remarks>If something goes wrong, we log it.</remarks>
202 void connector_OnVoiceResponse(object sender, VoiceGateway.VoiceResponseEventArgs e)
203 {
204 if (e.StatusCode == 0)
205 return;
206  
207 Logger.Log(e.Message + " on " + sender as string, Helpers.LogLevel.Error);
208 }
209  
210 public void Stop()
211 {
212 Client.Network.EventQueueRunning -= new EventHandler<EventQueueRunningEventArgs>(Network_EventQueueRunning);
213  
214 // Connection events
215 OnDaemonRunning -=
216 new VoiceGateway.DaemonRunningCallback(connector_OnDaemonRunning);
217 OnDaemonCouldntRun -=
218 new VoiceGateway.DaemonCouldntRunCallback(connector_OnDaemonCouldntRun);
219 OnConnectorCreateResponse -=
220 new EventHandler<VoiceGateway.VoiceConnectorEventArgs>(connector_OnConnectorCreateResponse);
221 OnDaemonConnected -=
222 new VoiceGateway.DaemonConnectedCallback(connector_OnDaemonConnected);
223 OnDaemonCouldntConnect -=
224 new VoiceGateway.DaemonCouldntConnectCallback(connector_OnDaemonCouldntConnect);
225 OnAuxAudioPropertiesEvent -=
226 new EventHandler<AudioPropertiesEventArgs>(connector_OnAuxAudioPropertiesEvent);
227  
228 // Session events
229 OnSessionStateChangeEvent -=
230 new EventHandler<SessionStateChangeEventArgs>(connector_OnSessionStateChangeEvent);
231 OnSessionAddedEvent -=
232 new EventHandler<SessionAddedEventArgs>(connector_OnSessionAddedEvent);
233  
234 // Session Participants events
235 OnSessionParticipantUpdatedEvent -=
236 new EventHandler<ParticipantUpdatedEventArgs>(connector_OnSessionParticipantUpdatedEvent);
237 OnSessionParticipantAddedEvent -=
238 new EventHandler<ParticipantAddedEventArgs>(connector_OnSessionParticipantAddedEvent);
239 OnSessionParticipantRemovedEvent -=
240 new EventHandler<ParticipantRemovedEventArgs>(connector_OnSessionParticipantRemovedEvent);
241  
242 // Tuning events
243 OnAuxGetCaptureDevicesResponse -=
244 new EventHandler<VoiceGateway.VoiceDevicesEventArgs>(connector_OnAuxGetCaptureDevicesResponse);
245 OnAuxGetRenderDevicesResponse -=
246 new EventHandler<VoiceGateway.VoiceDevicesEventArgs>(connector_OnAuxGetRenderDevicesResponse);
247  
248 // Account events
249 OnAccountLoginResponse -=
250 new EventHandler<VoiceGateway.VoiceAccountEventArgs>(connector_OnAccountLoginResponse);
251  
252 // Stop the background thread
253 if (posThread != null)
254 {
255 PosUpdating(false);
256  
257 if (posThread.IsAlive)
258 posThread.Abort();
259 posThread = null;
260 }
261  
262 // Close all sessions
263 foreach (VoiceSession s in sessions.Values)
264 {
265 if (OnSessionRemove != null)
266 OnSessionRemove(s, EventArgs.Empty);
267 s.Close();
268 }
269  
270 // Clear out lots of state so in case of restart we begin at the beginning.
271 currentParcelCap = null;
272 sessions.Clear();
273 accountHandle = null;
274 voiceUser = null;
275 voicePassword = null;
276  
277 SessionTerminate(sessionHandle);
278 sessionHandle = null;
279 AccountLogout(accountHandle);
280 accountHandle = null;
281 ConnectorInitiateShutdown(connectionHandle);
282 connectionHandle = null;
283 StopDaemon();
284 }
285  
286 /// <summary>
287 /// Cleanup oject resources
288 /// </summary>
289 public void Dispose()
290 {
291 Stop();
292 }
293  
294 internal string GetVoiceDaemonPath()
295 {
296 string myDir =
297 Path.GetDirectoryName(
298 (System.Reflection.Assembly.GetEntryAssembly() ?? typeof (VoiceGateway).Assembly).Location);
299  
300 if (Environment.OSVersion.Platform != PlatformID.MacOSX &&
301 Environment.OSVersion.Platform != PlatformID.Unix)
302 {
303 string localDaemon = Path.Combine(myDir, Path.Combine("voice", "SLVoice.exe"));
304  
305 if (File.Exists(localDaemon))
306 return localDaemon;
307  
308 string progFiles;
309 if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("ProgramFiles(x86)")))
310 {
311 progFiles = Environment.GetEnvironmentVariable("ProgramFiles(x86)");
312 }
313 else
314 {
315 progFiles = Environment.GetEnvironmentVariable("ProgramFiles");
316 }
317  
318 if (System.IO.File.Exists(Path.Combine(progFiles, @"SecondLife" + Path.DirectorySeparatorChar + @"SLVoice.exe")))
319 {
320 return Path.Combine(progFiles, @"SecondLife" + Path.DirectorySeparatorChar + @"SLVoice.exe");
321 }
322  
323 return Path.Combine(myDir, @"SLVoice.exe");
324  
325 }
326 else
327 {
328 string localDaemon = Path.Combine(myDir, Path.Combine("voice", "SLVoice"));
329  
330 if (File.Exists(localDaemon))
331 return localDaemon;
332  
333 return Path.Combine(myDir,"SLVoice");
334 }
335 }
336  
337 void RequestVoiceProvision(System.Uri cap)
338 {
339 OpenMetaverse.Http.CapsClient capClient =
340 new OpenMetaverse.Http.CapsClient(cap);
341 capClient.OnComplete +=
342 new OpenMetaverse.Http.CapsClient.CompleteCallback(cClient_OnComplete);
343 OSD postData = new OSD();
344  
345 // STEP 0
346 Logger.Log("Requesting voice capability", Helpers.LogLevel.Info);
347 capClient.BeginGetResponse(postData, OSDFormat.Xml, 10000);
348 }
349  
350 /// <summary>
351 /// Request voice cap when changing regions
352 /// </summary>
353 void Network_EventQueueRunning(object sender, EventQueueRunningEventArgs e)
354 {
355 // We only care about the sim we are in.
356 if (e.Simulator != Client.Network.CurrentSim)
357 return;
358  
359 // Did we provision voice login info?
360 if (string.IsNullOrEmpty(voiceUser))
361 {
362 // The startup steps are
363 // 0. Get voice account info
364 // 1. Start Daemon
365 // 2. Create TCP connection
366 // 3. Create Connector
367 // 4. Account login
368 // 5. Create session
369  
370 // Get the voice provisioning data
371 System.Uri vCap =
372 Client.Network.CurrentSim.Caps.CapabilityURI("ProvisionVoiceAccountRequest");
373  
374 // Do we have voice capability?
375 if (vCap == null)
376 {
377 Logger.Log("Null voice capability after event queue running", Helpers.LogLevel.Warning);
378 }
379 else
380 {
381 RequestVoiceProvision(vCap);
382 }
383  
384 return;
385 }
386 else
387 {
388 // Change voice session for this region.
389 ParcelChanged();
390 }
391 }
392  
393  
394 #region Participants
395  
396 void connector_OnSessionParticipantUpdatedEvent(object sender, ParticipantUpdatedEventArgs e)
397 {
398 VoiceSession s = FindSession(e.SessionHandle, false);
399 if (s == null) return;
400 s.ParticipantUpdate(e.URI, e.IsMuted, e.IsSpeaking, e.Volume, e.Energy);
401 }
402  
403 public string SIPFromUUID(UUID id)
404 {
405 return "sip:" +
406 nameFromID(id) +
407 "@" +
408 sipServer;
409 }
410  
411 private static string nameFromID(UUID id)
412 {
413 string result = null;
414  
415 if (id == UUID.Zero)
416 return result;
417  
418 // Prepending this apparently prevents conflicts with reserved names inside the vivox and diamondware code.
419 result = "x";
420  
421 // Base64 encode and replace the pieces of base64 that are less compatible
422 // with e-mail local-parts.
423 // See RFC-4648 "Base 64 Encoding with URL and Filename Safe Alphabet"
424 byte[] encbuff = id.GetBytes();
425 result += Convert.ToBase64String(encbuff);
426 result = result.Replace('+', '-');
427 result = result.Replace('/', '_');
428  
429 return result;
430 }
431  
432 void connector_OnSessionParticipantAddedEvent(object sender, ParticipantAddedEventArgs e)
433 {
434 VoiceSession s = FindSession(e.SessionHandle, false);
435 if (s == null)
436 {
437 Logger.Log("Orphan participant", Helpers.LogLevel.Error);
438 return;
439 }
440 s.AddParticipant(e.URI);
441 }
442  
443 void connector_OnSessionParticipantRemovedEvent(object sender, ParticipantRemovedEventArgs e)
444 {
445 VoiceSession s = FindSession(e.SessionHandle, false);
446 if (s == null) return;
447 s.RemoveParticipant(e.URI);
448 }
449 #endregion
450  
451 #region Sessions
452 void connector_OnSessionAddedEvent(object sender, SessionAddedEventArgs e)
453 {
454 sessionHandle = e.SessionHandle;
455  
456 // Create our session context.
457 VoiceSession s = FindSession(sessionHandle, true);
458 s.RegionName = regionName;
459  
460 spatialSession = s;
461  
462 // Tell any user-facing code.
463 if (OnSessionCreate != null)
464 OnSessionCreate(s, null);
465  
466 Logger.Log("Added voice session in " + regionName, Helpers.LogLevel.Info);
467 }
468  
469 /// <summary>
470 /// Handle a change in session state
471 /// </summary>
472 void connector_OnSessionStateChangeEvent(object sender, SessionStateChangeEventArgs e)
473 {
474 VoiceSession s;
475  
476 switch (e.State)
477 {
478 case VoiceGateway.SessionState.Connected:
479 s = FindSession(e.SessionHandle, true);
480 sessionHandle = e.SessionHandle;
481 s.RegionName = regionName;
482 spatialSession = s;
483  
484 Logger.Log("Voice connected in " + regionName, Helpers.LogLevel.Info);
485 // Tell any user-facing code.
486 if (OnSessionCreate != null)
487 OnSessionCreate(s, null);
488 break;
489  
490 case VoiceGateway.SessionState.Disconnected:
491 s = FindSession(sessionHandle, false);
492 sessions.Remove(sessionHandle);
493  
494 if (s != null)
495 {
496 Logger.Log("Voice disconnected in " + s.RegionName, Helpers.LogLevel.Info);
497  
498 // Inform interested parties
499 if (OnSessionRemove != null)
500 OnSessionRemove(s, null);
501  
502 if (s == spatialSession)
503 spatialSession = null;
504 }
505  
506 // The previous session is now ended. Check for a new one and
507 // start it going.
508 if (nextParcelCap != null)
509 {
510 currentParcelCap = nextParcelCap;
511 nextParcelCap = null;
512 RequestParcelInfo(currentParcelCap);
513 }
514 break;
515 }
516  
517  
518 }
519  
520 /// <summary>
521 /// Close a voice session
522 /// </summary>
523 /// <param name="sessionHandle"></param>
524 internal void CloseSession(string sessionHandle)
525 {
526 if (!sessions.ContainsKey(sessionHandle))
527 return;
528  
529 PosUpdating(false);
530 ReportConnectionState(ConnectionState.AccountLogin);
531  
532 // Clean up spatial pointers.
533 VoiceSession s = sessions[sessionHandle];
534 if (s.IsSpatial)
535 {
536 spatialSession = null;
537 currentParcelCap = null;
538 }
539  
540 // Remove this session from the master session list
541 sessions.Remove(sessionHandle);
542  
543 // Let any user-facing code clean up.
544 if (OnSessionRemove != null)
545 OnSessionRemove(s, null);
546  
547 // Tell SLVoice to clean it up as well.
548 SessionTerminate(sessionHandle);
549 }
550  
551 /// <summary>
552 /// Locate a Session context from its handle
553 /// </summary>
554 /// <remarks>Creates the session context if it does not exist.</remarks>
555 VoiceSession FindSession(string sessionHandle, bool make)
556 {
557 if (sessions.ContainsKey(sessionHandle))
558 return sessions[sessionHandle];
559  
560 if (!make) return null;
561  
562 // Create a new session and add it to the sessions list.
563 VoiceSession s = new VoiceSession(this, sessionHandle);
564  
565 // Turn on position updating for spatial sessions
566 // (For now, only spatial sessions are supported)
567 if (s.IsSpatial)
568 PosUpdating(true);
569  
570 // Register the session by its handle
571 sessions.Add(sessionHandle, s);
572 return s;
573 }
574  
575 #endregion
576  
577 #region MinorResponses
578  
579 void connector_OnAuxAudioPropertiesEvent(object sender, AudioPropertiesEventArgs e)
580 {
581 if (OnVoiceMicTest != null)
582 OnVoiceMicTest(e.MicEnergy);
583 }
584  
585 #endregion
586  
587 private void ReportConnectionState(ConnectionState s)
588 {
589 if (OnVoiceConnectionChange == null) return;
590  
591 OnVoiceConnectionChange(s);
592 }
593  
594 /// <summary>
595 /// Handle completion of main voice cap request.
596 /// </summary>
597 /// <param name="client"></param>
598 /// <param name="result"></param>
599 /// <param name="error"></param>
600 void cClient_OnComplete(OpenMetaverse.Http.CapsClient client,
601 OpenMetaverse.StructuredData.OSD result,
602 Exception error)
603 {
604 if (error != null)
605 {
606 Logger.Log("Voice cap error " + error.Message, Helpers.LogLevel.Error);
607 return;
608 }
609  
610 Logger.Log("Voice provisioned", Helpers.LogLevel.Info);
611 ReportConnectionState(ConnectionState.Provisioned);
612  
613 OpenMetaverse.StructuredData.OSDMap pMap = result as OpenMetaverse.StructuredData.OSDMap;
614  
615 // We can get back 4 interesting values:
616 // voice_sip_uri_hostname
617 // voice_account_server_name (actually a full URI)
618 // username
619 // password
620 if (pMap.ContainsKey("voice_sip_uri_hostname"))
621 sipServer = pMap["voice_sip_uri_hostname"].AsString();
622 if (pMap.ContainsKey("voice_account_server_name"))
623 acctServer = pMap["voice_account_server_name"].AsString();
624 voiceUser = pMap["username"].AsString();
625 voicePassword = pMap["password"].AsString();
626  
627 // Start the SLVoice daemon
628 slvoicePath = GetVoiceDaemonPath();
629  
630 // Test if the executable exists
631 if (!System.IO.File.Exists(slvoicePath))
632 {
633 Logger.Log("SLVoice is missing", Helpers.LogLevel.Error);
634 return;
635 }
636  
637 // STEP 1
638 StartDaemon(slvoicePath, slvoiceArgs);
639 }
640  
641 #region Daemon
642 void connector_OnDaemonCouldntConnect()
643 {
644 Logger.Log("No voice daemon connect", Helpers.LogLevel.Error);
645 }
646  
647 void connector_OnDaemonCouldntRun()
648 {
649 Logger.Log("Daemon not started", Helpers.LogLevel.Error);
650 }
651  
652 /// <summary>
653 /// Daemon has started so connect to it.
654 /// </summary>
655 void connector_OnDaemonRunning()
656 {
657 OnDaemonRunning -=
658 new VoiceGateway.DaemonRunningCallback(connector_OnDaemonRunning);
659  
660 Logger.Log("Daemon started", Helpers.LogLevel.Info);
661 ReportConnectionState(ConnectionState.DaemonStarted);
662  
663 // STEP 2
664 ConnectToDaemon(daemonNode, daemonPort);
665  
666 }
667  
668 /// <summary>
669 /// The daemon TCP connection is open.
670 /// </summary>
671 void connector_OnDaemonConnected()
672 {
673 Logger.Log("Daemon connected", Helpers.LogLevel.Info);
674 ReportConnectionState(ConnectionState.DaemonConnected);
675  
676 // The connector is what does the logging.
677 VoiceGateway.VoiceLoggingSettings vLog =
678 new VoiceGateway.VoiceLoggingSettings();
679  
680 #if DEBUG_VOICE
681 vLog.Enabled = true;
682 vLog.FileNamePrefix = "OpenmetaverseVoice";
683 vLog.FileNameSuffix = ".log";
684 vLog.LogLevel = 4;
685 #endif
686 // STEP 3
687 int reqId = ConnectorCreate(
688 "V2 SDK", // Magic value keeps SLVoice happy
689 acctServer, // Account manager server
690 30000, 30099, // port range
691 vLog);
692 if (reqId < 0)
693 {
694 Logger.Log("No voice connector request", Helpers.LogLevel.Error);
695 }
696 }
697  
698 /// <summary>
699 /// Handle creation of the Connector.
700 /// </summary>
701 void connector_OnConnectorCreateResponse(
702 object sender,
703 VoiceGateway.VoiceConnectorEventArgs e)
704 {
705 Logger.Log("Voice daemon protocol started " + e.Message, Helpers.LogLevel.Info);
706  
707 connectionHandle = e.Handle;
708  
709 if (e.StatusCode != 0)
710 return;
711  
712 // STEP 4
713 AccountLogin(
714 connectionHandle,
715 voiceUser,
716 voicePassword,
717 "VerifyAnswer", // This can also be "AutoAnswer"
718 "", // Default account management server URI
719 10, // Throttle state changes
720 true); // Enable buddies and presence
721 }
722 #endregion
723  
724 void connector_OnAccountLoginResponse(
725 object sender,
726 VoiceGateway.VoiceAccountEventArgs e)
727 {
728 Logger.Log("Account Login " + e.Message, Helpers.LogLevel.Info);
729 accountHandle = e.AccountHandle;
730 ReportConnectionState(ConnectionState.AccountLogin);
731 ParcelChanged();
732 }
733  
734 #region Audio devices
735 /// <summary>
736 /// Handle response to audio output device query
737 /// </summary>
738 void connector_OnAuxGetRenderDevicesResponse(
739 object sender,
740 VoiceGateway.VoiceDevicesEventArgs e)
741 {
742 outputDevices = e.Devices;
743 currentPlaybackDevice = e.CurrentDevice;
744 }
745  
746 /// <summary>
747 /// Handle response to audio input device query
748 /// </summary>
749 void connector_OnAuxGetCaptureDevicesResponse(
750 object sender,
751 VoiceGateway.VoiceDevicesEventArgs e)
752 {
753 inputDevices = e.Devices;
754 currentCaptureDevice = e.CurrentDevice;
755 }
756  
757 public string CurrentCaptureDevice
758 {
759 get { return currentCaptureDevice; }
760 set
761 {
762 currentCaptureDevice = value;
763 AuxSetCaptureDevice(value);
764 }
765 }
766 public string PlaybackDevice
767 {
768 get { return currentPlaybackDevice; }
769 set
770 {
771 currentPlaybackDevice = value;
772 AuxSetRenderDevice(value);
773 }
774 }
775  
776 public int MicLevel
777 {
778 set
779 {
780 ConnectorSetLocalMicVolume(connectionHandle, value);
781 }
782 }
783 public int SpkrLevel
784 {
785 set
786 {
787 ConnectorSetLocalSpeakerVolume(connectionHandle, value);
788 }
789 }
790  
791 public bool MicMute
792 {
793 set
794 {
795 ConnectorMuteLocalMic(connectionHandle, value);
796 }
797 }
798  
799 public bool SpkrMute
800 {
801 set
802 {
803 ConnectorMuteLocalSpeaker(connectionHandle, value);
804 }
805 }
806  
807 /// <summary>
808 /// Set audio test mode
809 /// </summary>
810 public bool TestMode
811 {
812 get { return testing; }
813 set
814 {
815 testing = value;
816 if (testing)
817 {
818 if (spatialSession != null)
819 {
820 spatialSession.Close();
821 spatialSession = null;
822 }
823 AuxCaptureAudioStart(0);
824 }
825 else
826 {
827 AuxCaptureAudioStop();
828 ParcelChanged();
829 }
830 }
831 }
832 #endregion
833  
834  
835  
836  
837 /// <summary>
838 /// Set voice channel for new parcel
839 /// </summary>
840 ///
841 internal void ParcelChanged()
842 {
843 // Get the capability for this parcel.
844 Caps c = Client.Network.CurrentSim.Caps;
845 System.Uri pCap = c.CapabilityURI("ParcelVoiceInfoRequest");
846  
847 if (pCap == null)
848 {
849 Logger.Log("Null voice capability", Helpers.LogLevel.Error);
850 return;
851 }
852  
853 // Parcel has changed. If we were already in a spatial session, we have to close it first.
854 if (spatialSession != null)
855 {
856 nextParcelCap = pCap;
857 CloseSession(spatialSession.Handle);
858 }
859  
860 // Not already in a session, so can start the new one.
861 RequestParcelInfo(pCap);
862 }
863  
864 private OpenMetaverse.Http.CapsClient parcelCap;
865  
866 /// <summary>
867 /// Request info from a parcel capability Uri.
868 /// </summary>
869 /// <param name="cap"></param>
870  
871 void RequestParcelInfo(Uri cap)
872 {
873 Logger.Log("Requesting region voice info", Helpers.LogLevel.Info);
874  
875 parcelCap = new OpenMetaverse.Http.CapsClient(cap);
876 parcelCap.OnComplete +=
877 new OpenMetaverse.Http.CapsClient.CompleteCallback(pCap_OnComplete);
878 OSD postData = new OSD();
879  
880 currentParcelCap = cap;
881 parcelCap.BeginGetResponse(postData, OSDFormat.Xml, 10000);
882 }
883  
884 /// <summary>
885 /// Receive parcel voice cap
886 /// </summary>
887 /// <param name="client"></param>
888 /// <param name="result"></param>
889 /// <param name="error"></param>
890 void pCap_OnComplete(OpenMetaverse.Http.CapsClient client,
891 OpenMetaverse.StructuredData.OSD result,
892 Exception error)
893 {
894 parcelCap.OnComplete -=
895 new OpenMetaverse.Http.CapsClient.CompleteCallback(pCap_OnComplete);
896 parcelCap = null;
897  
898 if (error != null)
899 {
900 Logger.Log("Region voice cap " + error.Message, Helpers.LogLevel.Error);
901 return;
902 }
903  
904 OpenMetaverse.StructuredData.OSDMap pMap = result as OpenMetaverse.StructuredData.OSDMap;
905  
906 regionName = pMap["region_name"].AsString();
907 ReportConnectionState(ConnectionState.RegionCapAvailable);
908  
909 if (pMap.ContainsKey("voice_credentials"))
910 {
911 OpenMetaverse.StructuredData.OSDMap cred =
912 pMap["voice_credentials"] as OpenMetaverse.StructuredData.OSDMap;
913  
914 if (cred.ContainsKey("channel_uri"))
915 spatialUri = cred["channel_uri"].AsString();
916 if (cred.ContainsKey("channel_credentials"))
917 spatialCredentials = cred["channel_credentials"].AsString();
918 }
919  
920 if (spatialUri == null || spatialUri == "")
921 {
922 // "No voice chat allowed here");
923 return;
924 }
925  
926 Logger.Log("Voice connecting for region " + regionName, Helpers.LogLevel.Info);
927  
928 // STEP 5
929 int reqId = SessionCreate(
930 accountHandle,
931 spatialUri, // uri
932 "", // Channel name seems to be always null
933 spatialCredentials, // spatialCredentials, // session password
934 true, // Join Audio
935 false, // Join Text
936 "");
937 if (reqId < 0)
938 {
939 Logger.Log("Voice Session ReqID " + reqId.ToString(), Helpers.LogLevel.Error);
940 }
941 }
942  
943 #region Location Update
944 /// <summary>
945 /// Tell Vivox where we are standing
946 /// </summary>
947 /// <remarks>This has to be called when we move or turn.</remarks>
948 internal void UpdatePosition(AgentManager self)
949 {
950 // Get position in Global coordinates
951 Vector3d OMVpos = new Vector3d(self.GlobalPosition);
952  
953 // Do not send trivial updates.
954 if (OMVpos.ApproxEquals(oldPosition, 1.0))
955 return;
956  
957 oldPosition = OMVpos;
958  
959 // Convert to the coordinate space that Vivox uses
960 // OMV X is East, Y is North, Z is up
961 // VVX X is East, Y is up, Z is South
962 position.Position = new Vector3d(OMVpos.X, OMVpos.Z, -OMVpos.Y);
963  
964 // TODO Rotate these two vectors
965  
966 // Get azimuth from the facing Quaternion.
967 // By definition, facing.W = Cos( angle/2 )
968 double angle = 2.0 * Math.Acos(self.Movement.BodyRotation.W);
969  
970 position.LeftOrientation = new Vector3d(-1.0, 0.0, 0.0);
971 position.AtOrientation = new Vector3d((float)Math.Acos(angle), 0.0, -(float)Math.Asin(angle));
972  
973 SessionSet3DPosition(
974 sessionHandle,
975 position,
976 position);
977 }
978  
979 /// <summary>
980 /// Start and stop updating out position.
981 /// </summary>
982 /// <param name="go"></param>
983 internal void PosUpdating(bool go)
984 {
985 if (go)
986 posRestart.Set();
987 else
988 posRestart.Reset();
989 }
990  
991 private void PositionThreadBody()
992 {
993 while (true)
994 {
995 posRestart.WaitOne();
996 Thread.Sleep(1500);
997 UpdatePosition(Client.Self);
998 }
999 }
1000 #endregion
1001  
1002 }
1003 }