diff --git a/LibreMetaverse.Voice/LibreMetaverse.Voice.csproj b/LibreMetaverse.Voice/LibreMetaverse.Voice.csproj new file mode 100644 index 00000000..f7be7be8 --- /dev/null +++ b/LibreMetaverse.Voice/LibreMetaverse.Voice.csproj @@ -0,0 +1,52 @@ + + + LibreMetaverse.Voice + LibreMetaverse.Voice + Library interface for Vivox voice client + Library + LibreMetaverse + true + true + 0419,1574,1591 + netstandard2.0;netstandard2.1;net5.0 + x64;x86;AnyCPU + ..\bin\ + + + TRACE;DEBUG + + + TRACE;DEBUG + + + TRACE;DEBUG + + + true + pdbonly + TRACE + True + + + true + pdbonly + TRACE + True + + + true + pdbonly + TRACE + True + + + + + + + + + + + + diff --git a/LibreMetaverse/Voice/TCPPipe.cs b/LibreMetaverse.Voice/TCPPipe.cs similarity index 99% rename from LibreMetaverse/Voice/TCPPipe.cs rename to LibreMetaverse.Voice/TCPPipe.cs index 840fc9cd..4273548a 100644 --- a/LibreMetaverse/Voice/TCPPipe.cs +++ b/LibreMetaverse.Voice/TCPPipe.cs @@ -29,7 +29,7 @@ using System; using System.Net; using System.Net.Sockets; -namespace OpenMetaverse.Voice +namespace LibreMetaverse.Voice { public class TCPPipe { diff --git a/LibreMetaverse/Voice/VoiceAccount.cs b/LibreMetaverse.Voice/VoiceAccount.cs similarity index 97% rename from LibreMetaverse/Voice/VoiceAccount.cs rename to LibreMetaverse.Voice/VoiceAccount.cs index a9aa96f3..4eaff06d 100644 --- a/LibreMetaverse/Voice/VoiceAccount.cs +++ b/LibreMetaverse.Voice/VoiceAccount.cs @@ -1,80 +1,80 @@ -/* - * Copyright (c) 2006-2016, openmetaverse.co - * Copyright (c) 2021-2022, Sjofn LLC. - * 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.co 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. - */ - -using System.Text; - -namespace OpenMetaverse.Voice -{ - public partial class VoiceGateway - { - /// - /// This is used to login a specific user account(s). It may only be called after - /// Connector initialization has completed successfully - /// - /// Handle returned from successful Connector ‘create’ request - /// User's account name - /// User's account password - /// Values may be “AutoAnswer” or “VerifyAnswer” - /// "" - /// This is an integer that specifies how often - /// the daemon will send participant property events while in a channel. If this is not set - /// the default will be “on state change”, which means that the events will be sent when - /// the participant starts talking, stops talking, is muted, is unmuted. - /// The valid values are: - /// 0 – Never - /// 5 – 10 times per second - /// 10 – 5 times per second - /// 50 – 1 time per second - /// 100 – on participant state change (this is the default) - /// false - /// - public int AccountLogin(string ConnectorHandle, string AccountName, string AccountPassword, string AudioSessionAnswerMode, string AccountURI, int ParticipantPropertyFrequency, bool EnableBuddiesAndPresence) - { - StringBuilder sb = new StringBuilder(); - sb.Append(VoiceGateway.MakeXML("ConnectorHandle", ConnectorHandle)); - sb.Append(VoiceGateway.MakeXML("AccountName", AccountName)); - sb.Append(VoiceGateway.MakeXML("AccountPassword", AccountPassword)); - sb.Append(VoiceGateway.MakeXML("AudioSessionAnswerMode", AudioSessionAnswerMode)); - sb.Append(VoiceGateway.MakeXML("AccountURI", AccountURI)); - sb.Append(VoiceGateway.MakeXML("ParticipantPropertyFrequency", ParticipantPropertyFrequency.ToString())); - sb.Append(VoiceGateway.MakeXML("EnableBuddiesAndPresence", EnableBuddiesAndPresence ? "true" : "false")); - sb.Append(VoiceGateway.MakeXML("BuddyManagementMode", "Application")); - return Request("Account.Login.1", sb.ToString()); - } - - /// - /// This is used to logout a user session. It should only be called with a valid AccountHandle. - /// - /// Handle returned from successful Connector ‘login’ request - /// - public int AccountLogout(string AccountHandle) - { - string RequestXML = VoiceGateway.MakeXML("AccountHandle", AccountHandle); - return Request("Account.Logout.1", RequestXML); - } - } -} +/* + * Copyright (c) 2006-2016, openmetaverse.co + * Copyright (c) 2021-2022, Sjofn LLC. + * 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.co 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. + */ + +using System.Text; + +namespace LibreMetaverse.Voice +{ + public partial class VoiceGateway + { + /// + /// This is used to login a specific user account(s). It may only be called after + /// Connector initialization has completed successfully + /// + /// Handle returned from successful Connector ‘create’ request + /// User's account name + /// User's account password + /// Values may be “AutoAnswer” or “VerifyAnswer” + /// "" + /// This is an integer that specifies how often + /// the daemon will send participant property events while in a channel. If this is not set + /// the default will be “on state change”, which means that the events will be sent when + /// the participant starts talking, stops talking, is muted, is unmuted. + /// The valid values are: + /// 0 – Never + /// 5 – 10 times per second + /// 10 – 5 times per second + /// 50 – 1 time per second + /// 100 – on participant state change (this is the default) + /// false + /// + public int AccountLogin(string ConnectorHandle, string AccountName, string AccountPassword, string AudioSessionAnswerMode, string AccountURI, int ParticipantPropertyFrequency, bool EnableBuddiesAndPresence) + { + StringBuilder sb = new StringBuilder(); + sb.Append(VoiceGateway.MakeXML("ConnectorHandle", ConnectorHandle)); + sb.Append(VoiceGateway.MakeXML("AccountName", AccountName)); + sb.Append(VoiceGateway.MakeXML("AccountPassword", AccountPassword)); + sb.Append(VoiceGateway.MakeXML("AudioSessionAnswerMode", AudioSessionAnswerMode)); + sb.Append(VoiceGateway.MakeXML("AccountURI", AccountURI)); + sb.Append(VoiceGateway.MakeXML("ParticipantPropertyFrequency", ParticipantPropertyFrequency.ToString())); + sb.Append(VoiceGateway.MakeXML("EnableBuddiesAndPresence", EnableBuddiesAndPresence ? "true" : "false")); + sb.Append(VoiceGateway.MakeXML("BuddyManagementMode", "Application")); + return Request("Account.Login.1", sb.ToString()); + } + + /// + /// This is used to logout a user session. It should only be called with a valid AccountHandle. + /// + /// Handle returned from successful Connector ‘login’ request + /// + public int AccountLogout(string AccountHandle) + { + string RequestXML = VoiceGateway.MakeXML("AccountHandle", AccountHandle); + return Request("Account.Logout.1", RequestXML); + } + } +} diff --git a/LibreMetaverse/Voice/VoiceAux.cs b/LibreMetaverse.Voice/VoiceAux.cs similarity index 97% rename from LibreMetaverse/Voice/VoiceAux.cs rename to LibreMetaverse.Voice/VoiceAux.cs index 7412d9c7..02c781e1 100644 --- a/LibreMetaverse/Voice/VoiceAux.cs +++ b/LibreMetaverse.Voice/VoiceAux.cs @@ -1,119 +1,120 @@ -/* - * Copyright (c) 2006-2016, openmetaverse.co - * 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.co 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. - */ - -namespace OpenMetaverse.Voice -{ - public partial class VoiceGateway - { - /// - /// This is used to get a list of audio devices that can be used for capture (input) of voice. - /// - /// - public int AuxGetCaptureDevices() - { - return Request("Aux.GetCaptureDevices.1"); - } - - /// - /// This is used to get a list of audio devices that can be used for render (playback) of voice. - /// - public int AuxGetRenderDevices() - { - return Request("Aux.GetRenderDevices.1"); - } - - /// - /// This command is used to select the render device. - /// - /// The name of the device as returned by the Aux.GetRenderDevices command. - public int AuxSetRenderDevice(string RenderDeviceSpecifier) - { - string RequestXML = VoiceGateway.MakeXML("RenderDeviceSpecifier", RenderDeviceSpecifier); - return Request("Aux.SetRenderDevice.1", RequestXML); - } - - /// - /// This command is used to select the capture device. - /// - /// The name of the device as returned by the Aux.GetCaptureDevices command. - public int AuxSetCaptureDevice(string CaptureDeviceSpecifier) - { - string RequestXML = VoiceGateway.MakeXML("CaptureDeviceSpecifier", CaptureDeviceSpecifier); - return Request("Aux.SetCaptureDevice.1", RequestXML); - } - - /// - /// This command is used to start the audio capture process which will cause - /// AuxAudioProperty Events to be raised. These events can be used to display a - /// microphone VU meter for the currently selected capture device. This command - /// should not be issued if the user is on a call. - /// - /// (unused but required) - /// - public int AuxCaptureAudioStart(int Duration) - { - string RequestXML = VoiceGateway.MakeXML("Duration", Duration.ToString()); - return Request("Aux.CaptureAudioStart.1", RequestXML); - } - - /// - /// This command is used to stop the audio capture process. - /// - /// - public int AuxCaptureAudioStop() - { - return Request("Aux.CaptureAudioStop.1"); - } - - /// - /// This command is used to set the mic volume while in the audio tuning process. - /// Once an acceptable mic level is attained, the application must issue a - /// connector set mic volume command to have that level be used while on voice - /// calls. - /// - /// the microphone volume (-100 to 100 inclusive) - /// - public int AuxSetMicLevel(int Level) - { - string RequestXML = VoiceGateway.MakeXML("Level", Level.ToString()); - return Request("Aux.SetMicLevel.1", RequestXML); - } - - /// - /// This command is used to set the speaker volume while in the audio tuning - /// process. Once an acceptable speaker level is attained, the application must - /// issue a connector set speaker volume command to have that level be used while - /// on voice calls. - /// - /// the speaker volume (-100 to 100 inclusive) - /// - public int AuxSetSpeakerLevel(int Level) - { - string RequestXML = VoiceGateway.MakeXML("Level", Level.ToString()); - return Request("Aux.SetSpeakerLevel.1", RequestXML); - } - } -} +/* + * Copyright (c) 2006-2016, openmetaverse.co + * Copyright (c) 2021-2022, Sjofn LLC. + * 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.co 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. + */ + +namespace LibreMetaverse.Voice +{ + public partial class VoiceGateway + { + /// + /// This is used to get a list of audio devices that can be used for capture (input) of voice. + /// + /// + public int AuxGetCaptureDevices() + { + return Request("Aux.GetCaptureDevices.1"); + } + + /// + /// This is used to get a list of audio devices that can be used for render (playback) of voice. + /// + public int AuxGetRenderDevices() + { + return Request("Aux.GetRenderDevices.1"); + } + + /// + /// This command is used to select the render device. + /// + /// The name of the device as returned by the Aux.GetRenderDevices command. + public int AuxSetRenderDevice(string RenderDeviceSpecifier) + { + string RequestXML = VoiceGateway.MakeXML("RenderDeviceSpecifier", RenderDeviceSpecifier); + return Request("Aux.SetRenderDevice.1", RequestXML); + } + + /// + /// This command is used to select the capture device. + /// + /// The name of the device as returned by the Aux.GetCaptureDevices command. + public int AuxSetCaptureDevice(string CaptureDeviceSpecifier) + { + string RequestXML = VoiceGateway.MakeXML("CaptureDeviceSpecifier", CaptureDeviceSpecifier); + return Request("Aux.SetCaptureDevice.1", RequestXML); + } + + /// + /// This command is used to start the audio capture process which will cause + /// AuxAudioProperty Events to be raised. These events can be used to display a + /// microphone VU meter for the currently selected capture device. This command + /// should not be issued if the user is on a call. + /// + /// (unused but required) + /// + public int AuxCaptureAudioStart(int Duration) + { + string RequestXML = VoiceGateway.MakeXML("Duration", Duration.ToString()); + return Request("Aux.CaptureAudioStart.1", RequestXML); + } + + /// + /// This command is used to stop the audio capture process. + /// + /// + public int AuxCaptureAudioStop() + { + return Request("Aux.CaptureAudioStop.1"); + } + + /// + /// This command is used to set the mic volume while in the audio tuning process. + /// Once an acceptable mic level is attained, the application must issue a + /// connector set mic volume command to have that level be used while on voice + /// calls. + /// + /// the microphone volume (-100 to 100 inclusive) + /// + public int AuxSetMicLevel(int Level) + { + string RequestXML = VoiceGateway.MakeXML("Level", Level.ToString()); + return Request("Aux.SetMicLevel.1", RequestXML); + } + + /// + /// This command is used to set the speaker volume while in the audio tuning + /// process. Once an acceptable speaker level is attained, the application must + /// issue a connector set speaker volume command to have that level be used while + /// on voice calls. + /// + /// the speaker volume (-100 to 100 inclusive) + /// + public int AuxSetSpeakerLevel(int Level) + { + string RequestXML = VoiceGateway.MakeXML("Level", Level.ToString()); + return Request("Aux.SetSpeakerLevel.1", RequestXML); + } + } +} diff --git a/LibreMetaverse/Voice/VoiceConnector.cs b/LibreMetaverse.Voice/VoiceConnector.cs similarity index 97% rename from LibreMetaverse/Voice/VoiceConnector.cs rename to LibreMetaverse.Voice/VoiceConnector.cs index b4fd8566..77241667 100644 --- a/LibreMetaverse/Voice/VoiceConnector.cs +++ b/LibreMetaverse.Voice/VoiceConnector.cs @@ -1,128 +1,129 @@ -/* - * Copyright (c) 2006-2016, openmetaverse.co - * 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.co 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. - */ - -using System.Text; - -namespace OpenMetaverse.Voice -{ - public partial class VoiceGateway - { - /// - /// This is used to initialize and stop the Connector as a whole. The Connector - /// Create call must be completed successfully before any other requests are made - /// (typically during application initialization). The shutdown should be called - /// when the application is shutting down to gracefully release resources - /// - /// A string value indicting the Application name - /// URL for the management server - /// LoggingSettings - /// - /// - public int ConnectorCreate(string ClientName, string AccountManagementServer, ushort MinimumPort, - ushort MaximumPort, VoiceLoggingSettings Logging) - { - StringBuilder sb = new StringBuilder(); - sb.Append(VoiceGateway.MakeXML("ClientName", ClientName)); - sb.Append(VoiceGateway.MakeXML("AccountManagementServer", AccountManagementServer)); - sb.Append(VoiceGateway.MakeXML("MinimumPort", MinimumPort.ToString())); - sb.Append(VoiceGateway.MakeXML("MaximumPort", MaximumPort.ToString())); - sb.Append(VoiceGateway.MakeXML("Mode", "Normal")); - sb.Append(""); - sb.Append(VoiceGateway.MakeXML("Enabled", Logging.Enabled ? "true" : "false")); - sb.Append(VoiceGateway.MakeXML("Folder", Logging.Folder)); - sb.Append(VoiceGateway.MakeXML("FileNamePrefix", Logging.FileNamePrefix)); - sb.Append(VoiceGateway.MakeXML("FileNameSuffix", Logging.FileNameSuffix)); - sb.Append(VoiceGateway.MakeXML("LogLevel", Logging.LogLevel.ToString())); - sb.Append(""); - return Request("Connector.Create.1", sb.ToString()); - } - - /// - /// Shutdown Connector -- Should be called when the application is shutting down - /// to gracefully release resources - /// - /// Handle returned from successful Connector ‘create’ request - public int ConnectorInitiateShutdown(string ConnectorHandle) - { - string RequestXML = VoiceGateway.MakeXML("ConnectorHandle", ConnectorHandle); - return Request("Connector.InitiateShutdown.1", RequestXML); - } - - /// - /// Mute or unmute the microphone - /// - /// Handle returned from successful Connector ‘create’ request - /// true (mute) or false (unmute) - public int ConnectorMuteLocalMic(string ConnectorHandle, bool Mute) - { - StringBuilder sb = new StringBuilder(); - sb.Append(VoiceGateway.MakeXML("ConnectorHandle", ConnectorHandle)); - sb.Append(VoiceGateway.MakeXML("Value", Mute ? "true" : "false")); - return Request("Connector.MuteLocalMic.1", sb.ToString()); - } - - /// - /// Mute or unmute the speaker - /// - /// Handle returned from successful Connector ‘create’ request - /// true (mute) or false (unmute) - public int ConnectorMuteLocalSpeaker(string ConnectorHandle, bool Mute) - { - StringBuilder sb = new StringBuilder(); - sb.Append(VoiceGateway.MakeXML("ConnectorHandle", ConnectorHandle)); - sb.Append(VoiceGateway.MakeXML("Value", Mute ? "true" : "false")); - return Request("Connector.MuteLocalSpeaker.1", sb.ToString()); - } - - /// - /// Set microphone volume - /// - /// Handle returned from successful Connector ‘create’ request - /// The level of the audio, a number between -100 and 100 where - /// 0 represents ‘normal’ speaking volume - public int ConnectorSetLocalMicVolume(string ConnectorHandle, int Value) - { - StringBuilder sb = new StringBuilder(); - sb.Append(VoiceGateway.MakeXML("ConnectorHandle", ConnectorHandle)); - sb.Append(VoiceGateway.MakeXML("Value", Value.ToString())); - return Request("Connector.SetLocalMicVolume.1", sb.ToString()); - } - - /// - /// Set local speaker volume - /// - /// Handle returned from successful Connector ‘create’ request - /// The level of the audio, a number between -100 and 100 where - /// 0 represents ‘normal’ speaking volume - public int ConnectorSetLocalSpeakerVolume(string ConnectorHandle, int Value) - { - StringBuilder sb = new StringBuilder(); - sb.Append(VoiceGateway.MakeXML("ConnectorHandle", ConnectorHandle)); - sb.Append(VoiceGateway.MakeXML("Value", Value.ToString())); - return Request("Connector.SetLocalSpeakerVolume.1", sb.ToString()); - } - } -} +/* + * Copyright (c) 2006-2016, openmetaverse.co + * Copyright (c) 2021-2022, Sjofn LLC. + * 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.co 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. + */ + +using System.Text; + +namespace LibreMetaverse.Voice +{ + public partial class VoiceGateway + { + /// + /// This is used to initialize and stop the Connector as a whole. The Connector + /// Create call must be completed successfully before any other requests are made + /// (typically during application initialization). The shutdown should be called + /// when the application is shutting down to gracefully release resources + /// + /// A string value indicting the Application name + /// URL for the management server + /// LoggingSettings + /// + /// + public int ConnectorCreate(string ClientName, string AccountManagementServer, ushort MinimumPort, + ushort MaximumPort, VoiceLoggingSettings Logging) + { + StringBuilder sb = new StringBuilder(); + sb.Append(VoiceGateway.MakeXML("ClientName", ClientName)); + sb.Append(VoiceGateway.MakeXML("AccountManagementServer", AccountManagementServer)); + sb.Append(VoiceGateway.MakeXML("MinimumPort", MinimumPort.ToString())); + sb.Append(VoiceGateway.MakeXML("MaximumPort", MaximumPort.ToString())); + sb.Append(VoiceGateway.MakeXML("Mode", "Normal")); + sb.Append(""); + sb.Append(VoiceGateway.MakeXML("Enabled", Logging.Enabled ? "true" : "false")); + sb.Append(VoiceGateway.MakeXML("Folder", Logging.Folder)); + sb.Append(VoiceGateway.MakeXML("FileNamePrefix", Logging.FileNamePrefix)); + sb.Append(VoiceGateway.MakeXML("FileNameSuffix", Logging.FileNameSuffix)); + sb.Append(VoiceGateway.MakeXML("LogLevel", Logging.LogLevel.ToString())); + sb.Append(""); + return Request("Connector.Create.1", sb.ToString()); + } + + /// + /// Shutdown Connector -- Should be called when the application is shutting down + /// to gracefully release resources + /// + /// Handle returned from successful Connector ‘create’ request + public int ConnectorInitiateShutdown(string ConnectorHandle) + { + string RequestXML = VoiceGateway.MakeXML("ConnectorHandle", ConnectorHandle); + return Request("Connector.InitiateShutdown.1", RequestXML); + } + + /// + /// Mute or unmute the microphone + /// + /// Handle returned from successful Connector ‘create’ request + /// true (mute) or false (unmute) + public int ConnectorMuteLocalMic(string ConnectorHandle, bool Mute) + { + StringBuilder sb = new StringBuilder(); + sb.Append(VoiceGateway.MakeXML("ConnectorHandle", ConnectorHandle)); + sb.Append(VoiceGateway.MakeXML("Value", Mute ? "true" : "false")); + return Request("Connector.MuteLocalMic.1", sb.ToString()); + } + + /// + /// Mute or unmute the speaker + /// + /// Handle returned from successful Connector ‘create’ request + /// true (mute) or false (unmute) + public int ConnectorMuteLocalSpeaker(string ConnectorHandle, bool Mute) + { + StringBuilder sb = new StringBuilder(); + sb.Append(VoiceGateway.MakeXML("ConnectorHandle", ConnectorHandle)); + sb.Append(VoiceGateway.MakeXML("Value", Mute ? "true" : "false")); + return Request("Connector.MuteLocalSpeaker.1", sb.ToString()); + } + + /// + /// Set microphone volume + /// + /// Handle returned from successful Connector ‘create’ request + /// The level of the audio, a number between -100 and 100 where + /// 0 represents ‘normal’ speaking volume + public int ConnectorSetLocalMicVolume(string ConnectorHandle, int Value) + { + StringBuilder sb = new StringBuilder(); + sb.Append(VoiceGateway.MakeXML("ConnectorHandle", ConnectorHandle)); + sb.Append(VoiceGateway.MakeXML("Value", Value.ToString())); + return Request("Connector.SetLocalMicVolume.1", sb.ToString()); + } + + /// + /// Set local speaker volume + /// + /// Handle returned from successful Connector ‘create’ request + /// The level of the audio, a number between -100 and 100 where + /// 0 represents ‘normal’ speaking volume + public int ConnectorSetLocalSpeakerVolume(string ConnectorHandle, int Value) + { + StringBuilder sb = new StringBuilder(); + sb.Append(VoiceGateway.MakeXML("ConnectorHandle", ConnectorHandle)); + sb.Append(VoiceGateway.MakeXML("Value", Value.ToString())); + return Request("Connector.SetLocalSpeakerVolume.1", sb.ToString()); + } + } +} diff --git a/LibreMetaverse/Voice/VoiceControl.cs b/LibreMetaverse.Voice/VoiceControl.cs similarity index 99% rename from LibreMetaverse/Voice/VoiceControl.cs rename to LibreMetaverse.Voice/VoiceControl.cs index c9cc1515..a8af6e35 100644 --- a/LibreMetaverse/Voice/VoiceControl.cs +++ b/LibreMetaverse.Voice/VoiceControl.cs @@ -31,10 +31,11 @@ using System.Collections.Generic; using System.IO; using System.Runtime.InteropServices; using System.Threading; +using OpenMetaverse; using OpenMetaverse.StructuredData; using OpenMetaverse.Http; -namespace OpenMetaverse.Voice +namespace LibreMetaverse.Voice { public partial class VoiceGateway : IDisposable { diff --git a/LibreMetaverse/Voice/VoiceDefinitions.cs b/LibreMetaverse.Voice/VoiceDefinitions.cs similarity index 99% rename from LibreMetaverse/Voice/VoiceDefinitions.cs rename to LibreMetaverse.Voice/VoiceDefinitions.cs index 552eb585..861f39fd 100644 --- a/LibreMetaverse/Voice/VoiceDefinitions.cs +++ b/LibreMetaverse.Voice/VoiceDefinitions.cs @@ -28,8 +28,9 @@ using System; using System.Collections.Generic; using System.Xml.Serialization; +using OpenMetaverse; -namespace OpenMetaverse.Voice +namespace LibreMetaverse.Voice { public partial class VoiceGateway { diff --git a/LibreMetaverse/Voice/VoiceGateway.cs b/LibreMetaverse.Voice/VoiceGateway.cs similarity index 99% rename from LibreMetaverse/Voice/VoiceGateway.cs rename to LibreMetaverse.Voice/VoiceGateway.cs index 8e53218c..f7d81ddb 100644 --- a/LibreMetaverse/Voice/VoiceGateway.cs +++ b/LibreMetaverse.Voice/VoiceGateway.cs @@ -32,8 +32,9 @@ using System.Net.Sockets; using System.Diagnostics; using System.Threading; using System.Text; +using OpenMetaverse; -namespace OpenMetaverse.Voice +namespace LibreMetaverse.Voice { public partial class VoiceGateway { diff --git a/LibreMetaverse.Utilities/VoiceManager.cs b/LibreMetaverse.Voice/VoiceManager.cs similarity index 97% rename from LibreMetaverse.Utilities/VoiceManager.cs rename to LibreMetaverse.Voice/VoiceManager.cs index f058c026..0e11b2d1 100644 --- a/LibreMetaverse.Utilities/VoiceManager.cs +++ b/LibreMetaverse.Voice/VoiceManager.cs @@ -1,813 +1,815 @@ -/* - * Copyright (c) 2006-2016, openmetaverse.co - * 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.co 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. - */ - -using System; -using System.Collections.Generic; -using System.Net.Sockets; -using System.Text; -using System.IO; -using System.Xml; -using OpenMetaverse.StructuredData; -using OpenMetaverse.Http; -using OpenMetaverse.Interfaces; -using OpenMetaverse.Messages.Linden; - -namespace OpenMetaverse.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 partial class VoiceManager - { - public const int VOICE_MAJOR_VERSION = 1; - 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 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 delegate void ParcelVoiceInfoCallback(string regionName, int localID, string channelURI); - - 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 event ParcelVoiceInfoCallback OnParcelVoiceInfo; - - public GridClient Client; - public string VoiceServer = VOICE_RELEASE_SERVER; - public bool Enabled; - - protected Voice.TCPPipe _DaemonPipe; - protected VoiceStatus _Status; - protected int _CommandCookie = 0; - protected string _TuningSoundFile = string.Empty; - protected Dictionary _ChannelMap = new Dictionary(); - protected List _CaptureDevices = new List(); - protected List _RenderDevices = new List(); - - #region Response Processing Variables - - private bool isEvent; - private bool isChannel; - private bool isLocallyMuted; - private bool isModeratorMuted; - private bool isSpeaking; - private int cookie; - //private int returnCode; - private int statusCode; - private int volume; - private int state; - private int participantType; - private float energy; - 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(GridClient client) - { - Client = client; - Client.Network.RegisterEventCallback("RequiredVoiceVersion", RequiredVoiceVersionEventHandler); - - // Register callback handlers for the blocking functions - RegisterCallbacks(); - - Enabled = true; - } - - public bool IsDaemonRunning() - { - throw new NotImplementedException(); - } - - public bool StartDaemon() - { - throw new NotImplementedException(); - } - - public void StopDaemon() - { - throw new NotImplementedException(); - } - - public bool ConnectToDaemon() - { - if (!Enabled) return false; - - return ConnectToDaemon("127.0.0.1", DAEMON_PORT); - } - - public bool ConnectToDaemon(string address, int port) - { - if (!Enabled) return false; - - _DaemonPipe = new Voice.TCPPipe(); - _DaemonPipe.OnDisconnected += _DaemonPipe_OnDisconnected; - _DaemonPipe.OnReceiveLine += _DaemonPipe_OnReceiveLine; - - var se = _DaemonPipe.Connect(address, port); - - if (se == null) - { - return true; - } - Console.WriteLine("Connection failed: " + se.Message); - return false; - } - - public Dictionary GetChannelMap() - { - return new Dictionary(_ChannelMap); - } - - public List CurrentCaptureDevices() - { - return new List(_CaptureDevices); - } - - public List CurrentRenderDevices() - { - return new List(_RenderDevices); - } - - public string VoiceAccountFromUUID(UUID id) - { - var result = "x" + Convert.ToBase64String(id.GetBytes()); - return result.Replace('+', '-').Replace('/', '_'); - } - - public UUID UUIDFromVoiceAccount(string accountName) - { - if (accountName.Length == 25 && accountName[0] == 'x' && accountName[23] == '=' && accountName[24] == '=') - { - accountName = accountName.Replace('/', '_').Replace('+', '-'); - var idBytes = Convert.FromBase64String(accountName); - - return idBytes.Length == 16 ? new UUID(idBytes, 0) : UUID.Zero; - } - return UUID.Zero; - } - - public string SIPURIFromVoiceAccount(string account) - { - return $"sip:{account}@{VoiceServer}"; - } - - public int RequestCaptureDevices() - { - if (_DaemonPipe.Connected) - { - _DaemonPipe.SendData(Encoding.ASCII.GetBytes( - $"{REQUEST_TERMINATOR}")); - - return _CommandCookie - 1; - } - Logger.Log("VoiceManager.RequestCaptureDevices() called when the daemon pipe is disconnected", Helpers.LogLevel.Error, Client); - return -1; - } - - public int RequestRenderDevices() - { - if (_DaemonPipe.Connected) - { - _DaemonPipe.SendData(Encoding.ASCII.GetBytes( - $"{REQUEST_TERMINATOR}")); - - return _CommandCookie - 1; - } - Logger.Log("VoiceManager.RequestRenderDevices() called when the daemon pipe is disconnected", Helpers.LogLevel.Error, Client); - return -1; - } - - public int RequestCreateConnector() - { - return RequestCreateConnector(VoiceServer); - } - - public int RequestCreateConnector(string voiceServer) - { - if (_DaemonPipe.Connected) - { - VoiceServer = voiceServer; - - var accountServer = $"https://www.{VoiceServer}/api2/"; - var logPath = "."; - - var request = new StringBuilder(); - request.Append($""); - request.Append("V2 SDK"); - request.Append($"{accountServer}"); - request.Append(""); - request.Append("false"); - request.Append($"{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())); - - return _CommandCookie - 1; - } - else - { - Logger.Log("VoiceManager.CreateConnector() called when the daemon pipe is disconnected", Helpers.LogLevel.Error, Client); - return -1; - } - } - - private bool RequestVoiceInternal(string me, CapsClient.CompleteCallback callback, string capsName) - { - if (Enabled && Client.Network.Connected) - { - if (Client.Network.CurrentSim != null && Client.Network.CurrentSim.Caps != null) - { - var url = Client.Network.CurrentSim.Caps.CapabilityURI(capsName); - - if (url != null) - { - var request = new CapsClient(url); - var body = new OSDMap(); - request.OnComplete += callback; - request.PostRequestAsync(body, OSDFormat.Xml, Client.Settings.CAPS_TIMEOUT); - - return true; - } - Logger.Log("VoiceManager." + me + "(): " + capsName + " capability is missing", - Helpers.LogLevel.Info, Client); - return false; - } - } - - Logger.Log("VoiceManager.RequestVoiceInternal(): Voice system is currently disabled", - Helpers.LogLevel.Info, Client); - return false; - - } - - public bool RequestProvisionAccount() - { - return RequestVoiceInternal("RequestProvisionAccount", ProvisionCapsResponse, "ProvisionVoiceAccountRequest"); - } - - public bool RequestParcelVoiceInfo() - { - return RequestVoiceInternal("RequestParcelVoiceInfo", ParcelVoiceInfoResponse, "ParcelVoiceInfoRequest"); - } - - public int RequestLogin(string accountName, string password, string connectorHandle) - { - if (_DaemonPipe.Connected) - { - var request = new StringBuilder(); - request.Append($""); - request.Append($"{connectorHandle}"); - request.Append($"{accountName}"); - request.Append($"{password}"); - request.Append("VerifyAnswer"); - request.Append(""); - request.Append("10"); - request.Append("false"); - request.Append(""); - request.Append(REQUEST_TERMINATOR); - - _DaemonPipe.SendData(Encoding.ASCII.GetBytes(request.ToString())); - - return _CommandCookie - 1; - } - else - { - Logger.Log("VoiceManager.Login() called when the daemon pipe is disconnected", Helpers.LogLevel.Error, Client); - return -1; - } - } - - public int RequestSetRenderDevice(string deviceName) - { - if (_DaemonPipe.Connected) - { - _DaemonPipe.SendData(Encoding.ASCII.GetBytes( - $"{deviceName}{REQUEST_TERMINATOR}")); - - return _CommandCookie - 1; - } - else - { - Logger.Log("VoiceManager.RequestSetRenderDevice() called when the daemon pipe is disconnected", Helpers.LogLevel.Error, Client); - return -1; - } - } - - public int RequestStartTuningMode(int duration) - { - if (_DaemonPipe.Connected) - { - _DaemonPipe.SendData(Encoding.ASCII.GetBytes( - $"{duration}{REQUEST_TERMINATOR}")); - - return _CommandCookie - 1; - } - else - { - Logger.Log("VoiceManager.RequestStartTuningMode() called when the daemon pipe is disconnected", Helpers.LogLevel.Error, Client); - return -1; - } - } - - public int RequestStopTuningMode() - { - if (_DaemonPipe.Connected) - { - _DaemonPipe.SendData(Encoding.ASCII.GetBytes( - $"{REQUEST_TERMINATOR}")); - - return _CommandCookie - 1; - } - else - { - Logger.Log("VoiceManager.RequestStopTuningMode() called when the daemon pipe is disconnected", Helpers.LogLevel.Error, Client); - return _CommandCookie - 1; - } - } - - public int RequestSetSpeakerVolume(int volume_) - { - if (volume_ < 0 || volume_ > 100) - throw new ArgumentException("volume must be between 0 and 100", nameof(volume_)); - - if (_DaemonPipe.Connected) - { - _DaemonPipe.SendData(Encoding.ASCII.GetBytes( - $"{volume_}{REQUEST_TERMINATOR}")); - - return _CommandCookie - 1; - } - else - { - Logger.Log("VoiceManager.RequestSetSpeakerVolume() called when the daemon pipe is disconnected", Helpers.LogLevel.Error, Client); - return -1; - } - } - - public int RequestSetCaptureVolume(int volume_) - { - if (volume_ < 0 || volume_ > 100) - throw new ArgumentException("volume must be between 0 and 100", nameof(volume_)); - - if (_DaemonPipe.Connected) - { - _DaemonPipe.SendData(Encoding.ASCII.GetBytes( - $"{volume_}{REQUEST_TERMINATOR}")); - - return _CommandCookie - 1; - } - else - { - Logger.Log("VoiceManager.RequestSetCaptureVolume() called when the daemon pipe is disconnected", Helpers.LogLevel.Error, Client); - return -1; - } - } - - /// - /// Does not appear to be working - /// - /// - /// - public int RequestRenderAudioStart(string fileName, bool loop) - { - if (_DaemonPipe.Connected) - { - _TuningSoundFile = fileName; - - _DaemonPipe.SendData(Encoding.ASCII.GetBytes( - $"{_TuningSoundFile}{(loop ? "1" : "0")}{REQUEST_TERMINATOR}")); - - return _CommandCookie - 1; - } - else - { - Logger.Log("VoiceManager.RequestRenderAudioStart() called when the daemon pipe is disconnected", Helpers.LogLevel.Error, Client); - return -1; - } - } - - public int RequestRenderAudioStop() - { - if (_DaemonPipe.Connected) - { - _DaemonPipe.SendData(Encoding.ASCII.GetBytes( - $"{_TuningSoundFile}{REQUEST_TERMINATOR}")); - - return _CommandCookie - 1; - } - else - { - Logger.Log("VoiceManager.RequestRenderAudioStop() called when the daemon pipe is disconnected", Helpers.LogLevel.Error, Client); - return -1; - } - } - - #region Callbacks - - private void RequiredVoiceVersionEventHandler(string capsKey, IMessage message, Simulator simulator) - { - var msg = (RequiredVoiceVersionMessage)message; - - if (VOICE_MAJOR_VERSION != msg.MajorVersion) - { - Logger.Log( - $"Voice version mismatch! Got {msg.MajorVersion}, expecting {VOICE_MAJOR_VERSION}. Disabling the voice manager", Helpers.LogLevel.Error, Client); - Enabled = false; - } - else - { - Logger.DebugLog("Voice version " + msg.MajorVersion + " verified", Client); - } - } - - private void ProvisionCapsResponse(CapsClient client, OSD response, Exception error) - { - if (!(response is OSDMap respMap)) return; - - if (OnProvisionAccount == null) return; - try { OnProvisionAccount(respMap["username"].AsString(), respMap["password"].AsString()); } - catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); } - } - - private void ParcelVoiceInfoResponse(CapsClient client, OSD response, Exception error) - { - if (!(response is OSDMap respMap)) return; - - var regionName = respMap["region_name"].AsString(); - var localID = respMap["parcel_local_id"].AsInteger(); - - string channelURI = null; - if (respMap["voice_credentials"] is OSDMap) - { - var creds = (OSDMap)respMap["voice_credentials"]; - channelURI = creds["channel_uri"].AsString(); - } - - OnParcelVoiceInfo?.Invoke(regionName, localID, channelURI); - } - - private static void _DaemonPipe_OnDisconnected(SocketException se) - { - if (se != null) Console.WriteLine("Disconnected! " + se.Message); - else Console.WriteLine("Disconnected!"); - } - - private void _DaemonPipe_OnReceiveLine(string line) - { - var 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 (var i = 0; i < reader.AttributeCount; i++) - { - reader.MoveToAttribute(i); - - if (reader.Name == "action") - actionString = reader.Value; - else if (reader.Name == "type") - eventTypeString = reader.Value; - } - } - } - else - { - switch (reader.Name) - { - case "InputXml": - cookie = -1; - - // Parse through here to get the cookie value - reader.Read(); - if (reader.Name == "Request") - { - for (var i = 0; i < reader.AttributeCount; i++) - { - reader.MoveToAttribute(i); - - if (reader.Name != "requestId") continue; - int.TryParse(reader.Value, out cookie); - break; - } - } - - if (cookie == -1) - { - Logger.Log( - "VoiceManager._DaemonPipe_OnReceiveLine(): Failed to parse InputXml for the cookie", - Helpers.LogLevel.Warning, Client); - } - 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; - case XmlNodeType.None: - break; - case XmlNodeType.Attribute: - break; - case XmlNodeType.Text: - break; - case XmlNodeType.CDATA: - break; - case XmlNodeType.EntityReference: - break; - case XmlNodeType.Entity: - break; - case XmlNodeType.ProcessingInstruction: - break; - case XmlNodeType.Comment: - break; - case XmlNodeType.Document: - break; - case XmlNodeType.DocumentType: - break; - case XmlNodeType.DocumentFragment: - break; - case XmlNodeType.Notation: - break; - case XmlNodeType.Whitespace: - break; - case XmlNodeType.SignificantWhitespace: - break; - case XmlNodeType.EndEntity: - break; - case XmlNodeType.XmlDeclaration: - break; - default: - throw new ArgumentOutOfRangeException(); - } - } - - if (isEvent) - { - } - - //Client.DebugLog("VOICE: " + line); - } - - private void ProcessEvent() - { - if (isEvent) - { - switch (eventTypeString) - { - case "LoginStateChangeEvent": - OnLoginStateChange?.Invoke(cookie, accountHandle, statusCode, statusString, state); - break; - case "SessionNewEvent": - OnNewSession?.Invoke(cookie, accountHandle, eventSessionHandle, state, nameString, uriString); - break; - case "SessionStateChangeEvent": - OnSessionStateChange?.Invoke(cookie, uriString, statusCode, statusString, eventSessionHandle, state, isChannel, nameString); - break; - case "ParticipantStateChangeEvent": - OnParticipantStateChange?.Invoke(cookie, uriString, statusCode, statusString, state, nameString, displayNameString, participantType); - break; - case "ParticipantPropertiesEvent": - OnParticipantProperties?.Invoke(cookie, uriString, statusCode, statusString, isLocallyMuted, isModeratorMuted, isSpeaking, volume, energy); - break; - case "AuxAudioPropertiesEvent": - OnAuxAudioProperties?.Invoke(cookie, energy); - break; - } - } - else - { - switch (actionString) - { - case "Connector.Create.1": - OnConnectorCreated?.Invoke(cookie, statusCode, statusString, connectorHandle); - break; - case "Account.Login.1": - OnLogin?.Invoke(cookie, statusCode, statusString, accountHandle); - break; - case "Session.Create.1": - OnSessionCreated?.Invoke(cookie, statusCode, statusString, sessionHandle); - break; - case "Session.Connect.1": - OnSessionConnected?.Invoke(cookie, statusCode, statusString); - break; - case "Session.Terminate.1": - OnSessionTerminated?.Invoke(cookie, statusCode, statusString); - break; - case "Account.Logout.1": - OnAccountLogout?.Invoke(cookie, statusCode, statusString); - break; - case "Connector.InitiateShutdown.1": - OnConnectorInitiateShutdown?.Invoke(cookie, statusCode, statusString); - break; - case "Account.ChannelGetList.1": - OnAccountChannelGetList?.Invoke(cookie, statusCode, statusString); - break; - case "Aux.GetCaptureDevices.1": - OnCaptureDevices?.Invoke(cookie, statusCode, statusString, nameString); - break; - case "Aux.GetRenderDevices.1": - OnRenderDevices?.Invoke(cookie, statusCode, statusString, nameString); - break; - } - } - } - - #endregion Callbacks - } -} +/* + * Copyright (c) 2006-2016, openmetaverse.co + * Copyright (c) 2021-2022, Sjofn LLC. + * 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.co 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. + */ + +using System; +using System.Collections.Generic; +using System.Net.Sockets; +using System.Text; +using System.IO; +using System.Xml; +using OpenMetaverse.StructuredData; +using OpenMetaverse; +using OpenMetaverse.Http; +using OpenMetaverse.Interfaces; +using OpenMetaverse.Messages.Linden; + +namespace LibreMetaverse.Voice +{ + 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 partial class VoiceManager + { + public const int VOICE_MAJOR_VERSION = 1; + 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 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 delegate void ParcelVoiceInfoCallback(string regionName, int localID, string channelURI); + + 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 event ParcelVoiceInfoCallback OnParcelVoiceInfo; + + public GridClient Client; + public string VoiceServer = VOICE_RELEASE_SERVER; + public bool Enabled; + + protected Voice.TCPPipe _DaemonPipe; + protected VoiceStatus _Status; + protected int _CommandCookie = 0; + protected string _TuningSoundFile = string.Empty; + protected Dictionary _ChannelMap = new Dictionary(); + protected List _CaptureDevices = new List(); + protected List _RenderDevices = new List(); + + #region Response Processing Variables + + private bool isEvent; + private bool isChannel; + private bool isLocallyMuted; + private bool isModeratorMuted; + private bool isSpeaking; + private int cookie; + //private int returnCode; + private int statusCode; + private int volume; + private int state; + private int participantType; + private float energy; + 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(GridClient client) + { + Client = client; + Client.Network.RegisterEventCallback("RequiredVoiceVersion", RequiredVoiceVersionEventHandler); + + // Register callback handlers for the blocking functions + RegisterCallbacks(); + + Enabled = true; + } + + public bool IsDaemonRunning() + { + throw new NotImplementedException(); + } + + public bool StartDaemon() + { + throw new NotImplementedException(); + } + + public void StopDaemon() + { + throw new NotImplementedException(); + } + + public bool ConnectToDaemon() + { + if (!Enabled) return false; + + return ConnectToDaemon("127.0.0.1", DAEMON_PORT); + } + + public bool ConnectToDaemon(string address, int port) + { + if (!Enabled) return false; + + _DaemonPipe = new Voice.TCPPipe(); + _DaemonPipe.OnDisconnected += _DaemonPipe_OnDisconnected; + _DaemonPipe.OnReceiveLine += _DaemonPipe_OnReceiveLine; + + var se = _DaemonPipe.Connect(address, port); + + if (se == null) + { + return true; + } + Console.WriteLine("Connection failed: " + se.Message); + return false; + } + + public Dictionary GetChannelMap() + { + return new Dictionary(_ChannelMap); + } + + public List CurrentCaptureDevices() + { + return new List(_CaptureDevices); + } + + public List CurrentRenderDevices() + { + return new List(_RenderDevices); + } + + public string VoiceAccountFromUUID(UUID id) + { + var result = "x" + Convert.ToBase64String(id.GetBytes()); + return result.Replace('+', '-').Replace('/', '_'); + } + + public UUID UUIDFromVoiceAccount(string accountName) + { + if (accountName.Length == 25 && accountName[0] == 'x' && accountName[23] == '=' && accountName[24] == '=') + { + accountName = accountName.Replace('/', '_').Replace('+', '-'); + var idBytes = Convert.FromBase64String(accountName); + + return idBytes.Length == 16 ? new UUID(idBytes, 0) : UUID.Zero; + } + return UUID.Zero; + } + + public string SIPURIFromVoiceAccount(string account) + { + return $"sip:{account}@{VoiceServer}"; + } + + public int RequestCaptureDevices() + { + if (_DaemonPipe.Connected) + { + _DaemonPipe.SendData(Encoding.ASCII.GetBytes( + $"{REQUEST_TERMINATOR}")); + + return _CommandCookie - 1; + } + Logger.Log("VoiceManager.RequestCaptureDevices() called when the daemon pipe is disconnected", Helpers.LogLevel.Error, Client); + return -1; + } + + public int RequestRenderDevices() + { + if (_DaemonPipe.Connected) + { + _DaemonPipe.SendData(Encoding.ASCII.GetBytes( + $"{REQUEST_TERMINATOR}")); + + return _CommandCookie - 1; + } + Logger.Log("VoiceManager.RequestRenderDevices() called when the daemon pipe is disconnected", Helpers.LogLevel.Error, Client); + return -1; + } + + public int RequestCreateConnector() + { + return RequestCreateConnector(VoiceServer); + } + + public int RequestCreateConnector(string voiceServer) + { + if (_DaemonPipe.Connected) + { + VoiceServer = voiceServer; + + var accountServer = $"https://www.{VoiceServer}/api2/"; + var logPath = "."; + + var request = new StringBuilder(); + request.Append($""); + request.Append("V2 SDK"); + request.Append($"{accountServer}"); + request.Append(""); + request.Append("false"); + request.Append($"{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())); + + return _CommandCookie - 1; + } + else + { + Logger.Log("VoiceManager.CreateConnector() called when the daemon pipe is disconnected", Helpers.LogLevel.Error, Client); + return -1; + } + } + + private bool RequestVoiceInternal(string me, CapsClient.CompleteCallback callback, string capsName) + { + if (Enabled && Client.Network.Connected) + { + if (Client.Network.CurrentSim != null && Client.Network.CurrentSim.Caps != null) + { + var url = Client.Network.CurrentSim.Caps.CapabilityURI(capsName); + + if (url != null) + { + var request = new CapsClient(url); + var body = new OSDMap(); + request.OnComplete += callback; + request.PostRequestAsync(body, OSDFormat.Xml, Client.Settings.CAPS_TIMEOUT); + + return true; + } + Logger.Log("VoiceManager." + me + "(): " + capsName + " capability is missing", + Helpers.LogLevel.Info, Client); + return false; + } + } + + Logger.Log("VoiceManager.RequestVoiceInternal(): Voice system is currently disabled", + Helpers.LogLevel.Info, Client); + return false; + + } + + public bool RequestProvisionAccount() + { + return RequestVoiceInternal("RequestProvisionAccount", ProvisionCapsResponse, "ProvisionVoiceAccountRequest"); + } + + public bool RequestParcelVoiceInfo() + { + return RequestVoiceInternal("RequestParcelVoiceInfo", ParcelVoiceInfoResponse, "ParcelVoiceInfoRequest"); + } + + public int RequestLogin(string accountName, string password, string connectorHandle) + { + if (_DaemonPipe.Connected) + { + var request = new StringBuilder(); + request.Append($""); + request.Append($"{connectorHandle}"); + request.Append($"{accountName}"); + request.Append($"{password}"); + request.Append("VerifyAnswer"); + request.Append(""); + request.Append("10"); + request.Append("false"); + request.Append(""); + request.Append(REQUEST_TERMINATOR); + + _DaemonPipe.SendData(Encoding.ASCII.GetBytes(request.ToString())); + + return _CommandCookie - 1; + } + else + { + Logger.Log("VoiceManager.Login() called when the daemon pipe is disconnected", Helpers.LogLevel.Error, Client); + return -1; + } + } + + public int RequestSetRenderDevice(string deviceName) + { + if (_DaemonPipe.Connected) + { + _DaemonPipe.SendData(Encoding.ASCII.GetBytes( + $"{deviceName}{REQUEST_TERMINATOR}")); + + return _CommandCookie - 1; + } + else + { + Logger.Log("VoiceManager.RequestSetRenderDevice() called when the daemon pipe is disconnected", Helpers.LogLevel.Error, Client); + return -1; + } + } + + public int RequestStartTuningMode(int duration) + { + if (_DaemonPipe.Connected) + { + _DaemonPipe.SendData(Encoding.ASCII.GetBytes( + $"{duration}{REQUEST_TERMINATOR}")); + + return _CommandCookie - 1; + } + else + { + Logger.Log("VoiceManager.RequestStartTuningMode() called when the daemon pipe is disconnected", Helpers.LogLevel.Error, Client); + return -1; + } + } + + public int RequestStopTuningMode() + { + if (_DaemonPipe.Connected) + { + _DaemonPipe.SendData(Encoding.ASCII.GetBytes( + $"{REQUEST_TERMINATOR}")); + + return _CommandCookie - 1; + } + else + { + Logger.Log("VoiceManager.RequestStopTuningMode() called when the daemon pipe is disconnected", Helpers.LogLevel.Error, Client); + return _CommandCookie - 1; + } + } + + public int RequestSetSpeakerVolume(int volume_) + { + if (volume_ < 0 || volume_ > 100) + throw new ArgumentException("volume must be between 0 and 100", nameof(volume_)); + + if (_DaemonPipe.Connected) + { + _DaemonPipe.SendData(Encoding.ASCII.GetBytes( + $"{volume_}{REQUEST_TERMINATOR}")); + + return _CommandCookie - 1; + } + else + { + Logger.Log("VoiceManager.RequestSetSpeakerVolume() called when the daemon pipe is disconnected", Helpers.LogLevel.Error, Client); + return -1; + } + } + + public int RequestSetCaptureVolume(int volume_) + { + if (volume_ < 0 || volume_ > 100) + throw new ArgumentException("volume must be between 0 and 100", nameof(volume_)); + + if (_DaemonPipe.Connected) + { + _DaemonPipe.SendData(Encoding.ASCII.GetBytes( + $"{volume_}{REQUEST_TERMINATOR}")); + + return _CommandCookie - 1; + } + else + { + Logger.Log("VoiceManager.RequestSetCaptureVolume() called when the daemon pipe is disconnected", Helpers.LogLevel.Error, Client); + return -1; + } + } + + /// + /// Does not appear to be working + /// + /// + /// + public int RequestRenderAudioStart(string fileName, bool loop) + { + if (_DaemonPipe.Connected) + { + _TuningSoundFile = fileName; + + _DaemonPipe.SendData(Encoding.ASCII.GetBytes( + $"{_TuningSoundFile}{(loop ? "1" : "0")}{REQUEST_TERMINATOR}")); + + return _CommandCookie - 1; + } + else + { + Logger.Log("VoiceManager.RequestRenderAudioStart() called when the daemon pipe is disconnected", Helpers.LogLevel.Error, Client); + return -1; + } + } + + public int RequestRenderAudioStop() + { + if (_DaemonPipe.Connected) + { + _DaemonPipe.SendData(Encoding.ASCII.GetBytes( + $"{_TuningSoundFile}{REQUEST_TERMINATOR}")); + + return _CommandCookie - 1; + } + else + { + Logger.Log("VoiceManager.RequestRenderAudioStop() called when the daemon pipe is disconnected", Helpers.LogLevel.Error, Client); + return -1; + } + } + + #region Callbacks + + private void RequiredVoiceVersionEventHandler(string capsKey, IMessage message, Simulator simulator) + { + var msg = (RequiredVoiceVersionMessage)message; + + if (VOICE_MAJOR_VERSION != msg.MajorVersion) + { + Logger.Log( + $"Voice version mismatch! Got {msg.MajorVersion}, expecting {VOICE_MAJOR_VERSION}. Disabling the voice manager", Helpers.LogLevel.Error, Client); + Enabled = false; + } + else + { + Logger.DebugLog("Voice version " + msg.MajorVersion + " verified", Client); + } + } + + private void ProvisionCapsResponse(CapsClient client, OSD response, Exception error) + { + if (!(response is OSDMap respMap)) return; + + if (OnProvisionAccount == null) return; + try { OnProvisionAccount(respMap["username"].AsString(), respMap["password"].AsString()); } + catch (Exception e) { Logger.Log(e.Message, Helpers.LogLevel.Error, Client, e); } + } + + private void ParcelVoiceInfoResponse(CapsClient client, OSD response, Exception error) + { + if (!(response is OSDMap respMap)) return; + + var regionName = respMap["region_name"].AsString(); + var localID = respMap["parcel_local_id"].AsInteger(); + + string channelURI = null; + if (respMap["voice_credentials"] is OSDMap) + { + var creds = (OSDMap)respMap["voice_credentials"]; + channelURI = creds["channel_uri"].AsString(); + } + + OnParcelVoiceInfo?.Invoke(regionName, localID, channelURI); + } + + private static void _DaemonPipe_OnDisconnected(SocketException se) + { + if (se != null) Console.WriteLine("Disconnected! " + se.Message); + else Console.WriteLine("Disconnected!"); + } + + private void _DaemonPipe_OnReceiveLine(string line) + { + var 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 (var i = 0; i < reader.AttributeCount; i++) + { + reader.MoveToAttribute(i); + + if (reader.Name == "action") + actionString = reader.Value; + else if (reader.Name == "type") + eventTypeString = reader.Value; + } + } + } + else + { + switch (reader.Name) + { + case "InputXml": + cookie = -1; + + // Parse through here to get the cookie value + reader.Read(); + if (reader.Name == "Request") + { + for (var i = 0; i < reader.AttributeCount; i++) + { + reader.MoveToAttribute(i); + + if (reader.Name != "requestId") continue; + int.TryParse(reader.Value, out cookie); + break; + } + } + + if (cookie == -1) + { + Logger.Log( + "VoiceManager._DaemonPipe_OnReceiveLine(): Failed to parse InputXml for the cookie", + Helpers.LogLevel.Warning, Client); + } + 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; + case XmlNodeType.None: + break; + case XmlNodeType.Attribute: + break; + case XmlNodeType.Text: + break; + case XmlNodeType.CDATA: + break; + case XmlNodeType.EntityReference: + break; + case XmlNodeType.Entity: + break; + case XmlNodeType.ProcessingInstruction: + break; + case XmlNodeType.Comment: + break; + case XmlNodeType.Document: + break; + case XmlNodeType.DocumentType: + break; + case XmlNodeType.DocumentFragment: + break; + case XmlNodeType.Notation: + break; + case XmlNodeType.Whitespace: + break; + case XmlNodeType.SignificantWhitespace: + break; + case XmlNodeType.EndEntity: + break; + case XmlNodeType.XmlDeclaration: + break; + default: + throw new ArgumentOutOfRangeException(); + } + } + + if (isEvent) + { + } + + //Client.DebugLog("VOICE: " + line); + } + + private void ProcessEvent() + { + if (isEvent) + { + switch (eventTypeString) + { + case "LoginStateChangeEvent": + OnLoginStateChange?.Invoke(cookie, accountHandle, statusCode, statusString, state); + break; + case "SessionNewEvent": + OnNewSession?.Invoke(cookie, accountHandle, eventSessionHandle, state, nameString, uriString); + break; + case "SessionStateChangeEvent": + OnSessionStateChange?.Invoke(cookie, uriString, statusCode, statusString, eventSessionHandle, state, isChannel, nameString); + break; + case "ParticipantStateChangeEvent": + OnParticipantStateChange?.Invoke(cookie, uriString, statusCode, statusString, state, nameString, displayNameString, participantType); + break; + case "ParticipantPropertiesEvent": + OnParticipantProperties?.Invoke(cookie, uriString, statusCode, statusString, isLocallyMuted, isModeratorMuted, isSpeaking, volume, energy); + break; + case "AuxAudioPropertiesEvent": + OnAuxAudioProperties?.Invoke(cookie, energy); + break; + } + } + else + { + switch (actionString) + { + case "Connector.Create.1": + OnConnectorCreated?.Invoke(cookie, statusCode, statusString, connectorHandle); + break; + case "Account.Login.1": + OnLogin?.Invoke(cookie, statusCode, statusString, accountHandle); + break; + case "Session.Create.1": + OnSessionCreated?.Invoke(cookie, statusCode, statusString, sessionHandle); + break; + case "Session.Connect.1": + OnSessionConnected?.Invoke(cookie, statusCode, statusString); + break; + case "Session.Terminate.1": + OnSessionTerminated?.Invoke(cookie, statusCode, statusString); + break; + case "Account.Logout.1": + OnAccountLogout?.Invoke(cookie, statusCode, statusString); + break; + case "Connector.InitiateShutdown.1": + OnConnectorInitiateShutdown?.Invoke(cookie, statusCode, statusString); + break; + case "Account.ChannelGetList.1": + OnAccountChannelGetList?.Invoke(cookie, statusCode, statusString); + break; + case "Aux.GetCaptureDevices.1": + OnCaptureDevices?.Invoke(cookie, statusCode, statusString, nameString); + break; + case "Aux.GetRenderDevices.1": + OnRenderDevices?.Invoke(cookie, statusCode, statusString, nameString); + break; + } + } + } + + #endregion Callbacks + } +} diff --git a/LibreMetaverse.Utilities/VoiceManagerBlocking.cs b/LibreMetaverse.Voice/VoiceManagerBlocking.cs similarity index 96% rename from LibreMetaverse.Utilities/VoiceManagerBlocking.cs rename to LibreMetaverse.Voice/VoiceManagerBlocking.cs index b6eca486..e066f607 100644 --- a/LibreMetaverse.Utilities/VoiceManagerBlocking.cs +++ b/LibreMetaverse.Voice/VoiceManagerBlocking.cs @@ -1,143 +1,144 @@ -/* - * Copyright (c) 2006-2016, openmetaverse.co - * 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.co 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. - */ - -using System.Collections.Generic; -using System.Threading; - -namespace OpenMetaverse.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() - { - var evt = new AutoResetEvent(false); - Events[_CommandCookie] = evt; - - if (RequestCaptureDevices() == -1) - { - Events.Remove(_CommandCookie); - return new List(); - } - - return evt.WaitOne(BlockingTimeout, false) ? CurrentCaptureDevices() : new List(); - } - - public List RenderDevices() - { - var evt = new AutoResetEvent(false); - Events[_CommandCookie] = evt; - - if (RequestRenderDevices() == -1) - { - Events.Remove(_CommandCookie); - return new List(); - } - - return evt.WaitOne(BlockingTimeout, false) ? CurrentRenderDevices() : new List(); - } - - public string CreateConnector(out int status) - { - status = 0; - - var evt = new AutoResetEvent(false); - Events[_CommandCookie] = evt; - - if (RequestCreateConnector() == -1) - { - Events.Remove(_CommandCookie); - return string.Empty; - } - - var success = evt.WaitOne(BlockingTimeout, false); - status = statusCode; - - return success && statusCode == 0 ? connectorHandle : string.Empty; - } - - public string Login(string accountName, string password, string connectorHandle, out int status) - { - status = 0; - - var evt = new AutoResetEvent(false); - Events[_CommandCookie] = evt; - - if (RequestLogin(accountName, password, connectorHandle) == -1) - { - Events.Remove(_CommandCookie); - return string.Empty; - } - - var success = evt.WaitOne(BlockingTimeout, false); - status = statusCode; - - return success && statusCode == 0 ? accountHandle : string.Empty; - } - - protected void RegisterCallbacks() - { - OnCaptureDevices += VoiceManager_OnCaptureDevices; - OnRenderDevices += VoiceManager_OnRenderDevices; - OnConnectorCreated += VoiceManager_OnConnectorCreated; - OnLogin += 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 - } +/* + * Copyright (c) 2006-2016, openmetaverse.co + * Copyright (c) 2021-2022, Sjofn LLC. + * 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.co 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. + */ + +using System.Collections.Generic; +using System.Threading; + +namespace LibreMetaverse.Voice +{ + 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() + { + var evt = new AutoResetEvent(false); + Events[_CommandCookie] = evt; + + if (RequestCaptureDevices() == -1) + { + Events.Remove(_CommandCookie); + return new List(); + } + + return evt.WaitOne(BlockingTimeout, false) ? CurrentCaptureDevices() : new List(); + } + + public List RenderDevices() + { + var evt = new AutoResetEvent(false); + Events[_CommandCookie] = evt; + + if (RequestRenderDevices() == -1) + { + Events.Remove(_CommandCookie); + return new List(); + } + + return evt.WaitOne(BlockingTimeout, false) ? CurrentRenderDevices() : new List(); + } + + public string CreateConnector(out int status) + { + status = 0; + + var evt = new AutoResetEvent(false); + Events[_CommandCookie] = evt; + + if (RequestCreateConnector() == -1) + { + Events.Remove(_CommandCookie); + return string.Empty; + } + + var success = evt.WaitOne(BlockingTimeout, false); + status = statusCode; + + return success && statusCode == 0 ? connectorHandle : string.Empty; + } + + public string Login(string accountName, string password, string connectorHandle, out int status) + { + status = 0; + + var evt = new AutoResetEvent(false); + Events[_CommandCookie] = evt; + + if (RequestLogin(accountName, password, connectorHandle) == -1) + { + Events.Remove(_CommandCookie); + return string.Empty; + } + + var success = evt.WaitOne(BlockingTimeout, false); + status = statusCode; + + return success && statusCode == 0 ? accountHandle : string.Empty; + } + + protected void RegisterCallbacks() + { + OnCaptureDevices += VoiceManager_OnCaptureDevices; + OnRenderDevices += VoiceManager_OnRenderDevices; + OnConnectorCreated += VoiceManager_OnConnectorCreated; + OnLogin += 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/LibreMetaverse/Voice/VoiceParticipant.cs b/LibreMetaverse.Voice/VoiceParticipant.cs similarity index 99% rename from LibreMetaverse/Voice/VoiceParticipant.cs rename to LibreMetaverse.Voice/VoiceParticipant.cs index aefc989a..6a8a56e0 100644 --- a/LibreMetaverse/Voice/VoiceParticipant.cs +++ b/LibreMetaverse.Voice/VoiceParticipant.cs @@ -28,8 +28,9 @@ using System; using System.Text; using System.Text.RegularExpressions; +using OpenMetaverse; -namespace OpenMetaverse.Voice +namespace LibreMetaverse.Voice { public class VoiceParticipant { diff --git a/LibreMetaverse/Voice/VoiceSession.cs b/LibreMetaverse.Voice/VoiceSession.cs similarity index 99% rename from LibreMetaverse/Voice/VoiceSession.cs rename to LibreMetaverse.Voice/VoiceSession.cs index fcede9e4..72392751 100644 --- a/LibreMetaverse/Voice/VoiceSession.cs +++ b/LibreMetaverse.Voice/VoiceSession.cs @@ -29,7 +29,7 @@ using System.Collections.Generic; using System.Globalization; using System.Text; -namespace OpenMetaverse.Voice +namespace LibreMetaverse.Voice { /// /// Represents a single Voice Session to the Vivox service. diff --git a/LibreMetaverse.sln b/LibreMetaverse.sln index 7bac22b7..a35df357 100644 --- a/LibreMetaverse.sln +++ b/LibreMetaverse.sln @@ -56,6 +56,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GridAccountant", "Programs\ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LibreMetaverse.LslTools", "LibreMetaverse.LslTools\LibreMetaverse.LslTools.csproj", "{989E5E15-D99B-4CF1-AF64-90C568FC979A}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LibreMetaverse.Voice", "LibreMetaverse.Voice\LibreMetaverse.Voice.csproj", "{FB07C6DE-F791-4336-B6E2-B32EEAC34792}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -381,6 +383,24 @@ Global {989E5E15-D99B-4CF1-AF64-90C568FC979A}.ReleaseNoGui|x64.Build.0 = Release|Any CPU {989E5E15-D99B-4CF1-AF64-90C568FC979A}.ReleaseNoGui|x86.ActiveCfg = Release|Any CPU {989E5E15-D99B-4CF1-AF64-90C568FC979A}.ReleaseNoGui|x86.Build.0 = Release|Any CPU + {FB07C6DE-F791-4336-B6E2-B32EEAC34792}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FB07C6DE-F791-4336-B6E2-B32EEAC34792}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FB07C6DE-F791-4336-B6E2-B32EEAC34792}.Debug|x64.ActiveCfg = Debug|Any CPU + {FB07C6DE-F791-4336-B6E2-B32EEAC34792}.Debug|x64.Build.0 = Debug|Any CPU + {FB07C6DE-F791-4336-B6E2-B32EEAC34792}.Debug|x86.ActiveCfg = Debug|Any CPU + {FB07C6DE-F791-4336-B6E2-B32EEAC34792}.Debug|x86.Build.0 = Debug|Any CPU + {FB07C6DE-F791-4336-B6E2-B32EEAC34792}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FB07C6DE-F791-4336-B6E2-B32EEAC34792}.Release|Any CPU.Build.0 = Release|Any CPU + {FB07C6DE-F791-4336-B6E2-B32EEAC34792}.Release|x64.ActiveCfg = Release|Any CPU + {FB07C6DE-F791-4336-B6E2-B32EEAC34792}.Release|x64.Build.0 = Release|Any CPU + {FB07C6DE-F791-4336-B6E2-B32EEAC34792}.Release|x86.ActiveCfg = Release|Any CPU + {FB07C6DE-F791-4336-B6E2-B32EEAC34792}.Release|x86.Build.0 = Release|Any CPU + {FB07C6DE-F791-4336-B6E2-B32EEAC34792}.ReleaseNoGui|Any CPU.ActiveCfg = Release|Any CPU + {FB07C6DE-F791-4336-B6E2-B32EEAC34792}.ReleaseNoGui|Any CPU.Build.0 = Release|Any CPU + {FB07C6DE-F791-4336-B6E2-B32EEAC34792}.ReleaseNoGui|x64.ActiveCfg = Release|Any CPU + {FB07C6DE-F791-4336-B6E2-B32EEAC34792}.ReleaseNoGui|x64.Build.0 = Release|Any CPU + {FB07C6DE-F791-4336-B6E2-B32EEAC34792}.ReleaseNoGui|x86.ActiveCfg = Release|Any CPU + {FB07C6DE-F791-4336-B6E2-B32EEAC34792}.ReleaseNoGui|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Programs/VoiceTest/VoiceTest.cs b/Programs/VoiceTest/VoiceTest.cs index bc16a3ea..1e54c33a 100644 --- a/Programs/VoiceTest/VoiceTest.cs +++ b/Programs/VoiceTest/VoiceTest.cs @@ -28,7 +28,7 @@ using System; using System.Collections.Generic; using System.Threading; using OpenMetaverse; -using OpenMetaverse.Utilities; +using LibreMetaverse.Voice; namespace VoiceTest { diff --git a/Programs/VoiceTest/VoiceTest.csproj b/Programs/VoiceTest/VoiceTest.csproj index ec9bb129..644935e1 100644 --- a/Programs/VoiceTest/VoiceTest.csproj +++ b/Programs/VoiceTest/VoiceTest.csproj @@ -14,7 +14,7 @@ - + diff --git a/Programs/examples/TestClient/TestClient.cs b/Programs/examples/TestClient/TestClient.cs index 7a54911f..4819b46e 100644 --- a/Programs/examples/TestClient/TestClient.cs +++ b/Programs/examples/TestClient/TestClient.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using System.Threading; using System.Reflection; using OpenMetaverse.Packets; -using OpenMetaverse.Utilities; +using LibreMetaverse.Voice; namespace OpenMetaverse.TestClient { diff --git a/Programs/examples/TestClient/TestClient.csproj b/Programs/examples/TestClient/TestClient.csproj index 2d3aaed5..b3c5e3bf 100644 --- a/Programs/examples/TestClient/TestClient.csproj +++ b/Programs/examples/TestClient/TestClient.csproj @@ -12,7 +12,7 @@ - +