OSDN Git Service

Harmonize line endings
[radegast/radegast.git] / Radegast / Core / Media / BufferSound.cs
index 65a4065..747e79d 100644 (file)
-// \r
-// Radegast Metaverse Client\r
-// Copyright (c) 2009-2012, Radegast Development Team\r
-// All rights reserved.\r
-// \r
-// Redistribution and use in source and binary forms, with or without\r
-// modification, are permitted provided that the following conditions are met:\r
-// \r
-//     * Redistributions of source code must retain the above copyright notice,\r
-//       this list of conditions and the following disclaimer.\r
-//     * Redistributions in binary form must reproduce the above copyright\r
-//       notice, this list of conditions and the following disclaimer in the\r
-//       documentation and/or other materials provided with the distribution.\r
-//     * Neither the name of the application "Radegast", nor the names of its\r
-//       contributors may be used to endorse or promote products derived from\r
-//       this software without specific prior written permission.\r
-// \r
-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"\r
-// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\r
-// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\r
-// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\r
-// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\r
-// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\r
-// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\r
-// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\r
-// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\r
-// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r
-//\r
-// $Id: Sound.cs 502 2010-03-14 23:13:46Z latifer $\r
-//\r
-// Uncomment this to get lots more logging\r
-//#define TRACE_SOUND\r
-using System;\r
-using System.Runtime.InteropServices;\r
-using System.Collections.Generic;\r
-using FMOD;\r
-using OpenMetaverse;\r
-using OpenMetaverse.Assets;\r
-using System.Threading;\r
-\r
-namespace Radegast.Media\r
-{\r
-\r
-    public class BufferSound : MediaObject\r
-    {\r
-        private UUID Id;\r
-        private UUID ContainerId;\r
-        private Boolean prefetchOnly = false;\r
-        private FMOD.MODE mode;\r
-        public Sound Sound { get { return sound; } }\r
-        private Boolean loopSound = false;\r
-        /// <summary>\r
-        /// The individual volume setting for THIS object\r
-        /// </summary>\r
-        private float volumeSetting = 0.5f;\r
-\r
-        /// <summary>\r
-        /// Creates a new sound object\r
-        /// </summary>\r
-        /// <param name="system">Sound system</param>\r
-        public BufferSound(UUID objectId, UUID soundId, bool loop, bool global, Vector3 worldpos, float vol)\r
-            : base()\r
-        {\r
-            InitBuffer(objectId, soundId, loop, global, worldpos, vol);\r
-        }\r
-\r
-        public BufferSound(UUID objectId, UUID soundId, bool loop, bool global, Vector3d worldpos, float vol)\r
-            : base()\r
-        {\r
-            InitBuffer(objectId, soundId, loop, global, new Vector3(worldpos), vol);\r
-        }\r
-\r
-        private void InitBuffer(UUID objectId, UUID soundId, bool loop, bool global, Vector3 worldpos, float vol)\r
-        {\r
-            if (manager == null || !manager.SoundSystemAvailable) return;\r
-\r
-            // Do not let this get garbage-collected.\r
-            lock (allBuffers)\r
-                allBuffers[objectId] = this;\r
-\r
-            ContainerId = objectId;\r
-            Id = soundId;\r
-            position = FromOMVSpace(worldpos);\r
-            volumeSetting = vol;\r
-            loopSound = loop;\r
-\r
-            Logger.Log(\r
-                String.Format(\r
-                    "Playing sound at <{0:0.0},{1:0.0},{2:0.0}> ID {3}",\r
-                    position.x,\r
-                    position.y,\r
-                    position.z,\r
-                    Id.ToString()),\r
-                Helpers.LogLevel.Debug);\r
-\r
-            // Set flags to determine how it will be played.\r
-            mode = FMOD.MODE.SOFTWARE | // Need software processing for all the features\r
-                FMOD.MODE._3D |         // Need 3D effects for placement\r
-                FMOD.MODE.OPENMEMORY;   // Use sound data in memory\r
-\r
-            // Set coordinate space interpretation.\r
-            if (global)\r
-                mode |= FMOD.MODE._3D_WORLDRELATIVE;\r
-            else\r
-                mode |= FMOD.MODE._3D_HEADRELATIVE;\r
-\r
-            if (loopSound)\r
-                mode |= FMOD.MODE.LOOP_NORMAL;\r
-\r
-            // Fetch the sound data.\r
-            manager.Instance.Client.Assets.RequestAsset(\r
-                Id,\r
-                AssetType.Sound,\r
-                false,\r
-                new AssetManager.AssetReceivedCallback(Assets_OnSoundReceived));\r
-        }\r
-\r
-        public static void Kill(UUID id)\r
-        {\r
-            if (allBuffers.ContainsKey(id))\r
-            {\r
-                BufferSound bs = allBuffers[id];\r
-                bs.StopSound(true);\r
-            }\r
-        }\r
-\r
-        /// <summary>\r
-        /// Stop all playing sounds in the environment\r
-        /// </summary>\r
-        public static void KillAll()\r
-        {\r
-            // Make a list from the dictionary so we do not get a deadlock\r
-            // on it when removing entries.\r
-            List<BufferSound> list = new List<BufferSound>(allBuffers.Values);\r
-\r
-            foreach (BufferSound s in list)\r
-            {\r
-                s.StopSound();\r
-            }\r
-\r
-            List<MediaObject> objs = new List<MediaObject>(allChannels.Values);\r
-            foreach (MediaObject obj in objs)\r
-            {\r
-                if (obj is BufferSound)\r
-                    ((BufferSound)obj).StopSound();\r
-            }\r
-        }\r
-\r
-        /// <summary>\r
-        /// Adjust volumes of all playing sounds to observe the new global sound volume\r
-        /// </summary>\r
-        public static void AdjustVolumes()\r
-        {\r
-            // Make a list from the dictionary so we do not get a deadlock\r
-            List<BufferSound> list = new List<BufferSound>(allBuffers.Values);\r
-\r
-            foreach (BufferSound s in list)\r
-            {\r
-                s.AdjustVolume();\r
-            }\r
-        }\r
-\r
-        /// <summary>\r
-        /// Adjust the volume of THIS sound when all are being adjusted.\r
-        /// </summary>\r
-        private void AdjustVolume()\r
-        {\r
-            Volume = volumeSetting * AllObjectVolume;\r
-        }\r
-\r
-        // A simpler constructor used by PreFetchSound.\r
-        public BufferSound(UUID soundId)\r
-            : base()\r
-        {\r
-            prefetchOnly = true;\r
-            ContainerId = UUID.Zero;\r
-            Id = soundId;\r
-\r
-            manager.Instance.Client.Assets.RequestAsset(\r
-                Id,\r
-                AssetType.Sound,\r
-                false,\r
-                new AssetManager.AssetReceivedCallback(Assets_OnSoundReceived));\r
-        }\r
-\r
-        /// <summary>\r
-        /// Releases resources of this sound object\r
-        /// </summary>\r
-        public override void Dispose()\r
-        {\r
-            base.Dispose();\r
-        }\r
-\r
-        /**\r
-         * Handle arrival of a sound resource.\r
-         */\r
-        void Assets_OnSoundReceived(AssetDownload transfer, Asset asset)\r
-        {\r
-            if (transfer.Success)\r
-            {\r
-                // If this was a Prefetch, just stop here.\r
-                if (prefetchOnly)\r
-                {\r
-                    return;\r
-                }\r
-\r
-                //                Logger.Log("Opening sound " + Id.ToString(), Helpers.LogLevel.Debug);\r
-\r
-                // Decode the Ogg Vorbis buffer.\r
-                AssetSound s = asset as AssetSound;\r
-                s.Decode();\r
-                byte[] data = s.AssetData;\r
-\r
-                // Describe the data to FMOD\r
-                extraInfo.length = (uint)data.Length;\r
-                extraInfo.cbsize = Marshal.SizeOf(extraInfo);\r
-\r
-                invoke(new SoundDelegate(delegate\r
-                {\r
-                    try\r
-                    {\r
-                        // Create an FMOD sound of this Ogg data.\r
-                        FMODExec(system.createSound(\r
-                            data,\r
-                            mode,\r
-                            ref extraInfo,\r
-                            ref sound));\r
-\r
-                        // Register for callbacks.\r
-                        RegisterSound(sound);\r
-\r
-\r
-                        // If looping is requested, loop the entire thing.\r
-                        if (loopSound)\r
-                        {\r
-                            uint soundlen = 0;\r
-                            FMODExec(sound.getLength(ref soundlen, TIMEUNIT.PCM));\r
-                            FMODExec(sound.setLoopPoints(0, TIMEUNIT.PCM, soundlen - 1, TIMEUNIT.PCM));\r
-                            FMODExec(sound.setLoopCount(-1));\r
-                        }\r
-\r
-                        // Allocate a channel and set initial volume.  Initially paused.\r
-                        FMODExec(system.playSound(CHANNELINDEX.FREE, sound, true, ref channel));\r
-#if TRACE_SOUND\r
-                    Logger.Log(\r
-                        String.Format("Channel {0} for {1} assigned to {2}",\r
-                             channel.getRaw().ToString("X"),\r
-                             sound.getRaw().ToString("X"),\r
-                             Id),\r
-                        Helpers.LogLevel.Debug);\r
-#endif\r
-                        RegisterChannel(channel);\r
-\r
-                        FMODExec(channel.setVolume(volumeSetting * AllObjectVolume));\r
-\r
-                        // Take note of when the sound is finished playing.\r
-                        FMODExec(channel.setCallback(endCallback));\r
-\r
-                        // Set attenuation limits.\r
-                        FMODExec(sound.set3DMinMaxDistance(\r
-                                    1.2f,       // Any closer than this gets no louder\r
-                                    100.0f));     // Further than this gets no softer.\r
-\r
-                        // Set the sound point of origin.  This is in SIM coordinates.\r
-                        FMODExec(channel.set3DAttributes(ref position, ref ZeroVector));\r
-\r
-                        // Turn off pause mode.  The sound will start playing now.\r
-                        FMODExec(channel.setPaused(false));\r
-                    }\r
-                    catch (Exception ex)\r
-                    {\r
-                        Logger.Log("Error playing sound: ", Helpers.LogLevel.Error, ex);\r
-                    }\r
-                }));\r
-            }\r
-            else\r
-            {\r
-                Logger.Log("Failed to download sound: " + transfer.Status.ToString(),\r
-                                        Helpers.LogLevel.Error);\r
-            }\r
-        }\r
-\r
-        /// <summary>\r
-        /// Handles stop sound even from FMOD\r
-        /// </summary>\r
-        /// <returns>RESULT.OK</returns>\r
-        protected override RESULT EndCallbackHandler()\r
-        {\r
-            StopSound();\r
-            return RESULT.OK;\r
-        }\r
-\r
-        protected void StopSound()\r
-        {\r
-            StopSound(false);\r
-        }\r
-\r
-        protected void StopSound(bool blocking)\r
-        {\r
-            ManualResetEvent stopped = null;\r
-            if (blocking)\r
-                stopped = new ManualResetEvent(false);\r
-\r
-            finished = true;\r
-\r
-            invoke(new SoundDelegate(delegate\r
-            {\r
-                string chanStr = "none";\r
-                string soundStr = "none";\r
-\r
-                // Release the buffer to avoid a big memory leak.\r
-                if (channel != null)\r
-                {\r
-                    lock (allChannels)\r
-                        allChannels.Remove(channel.getRaw());\r
-                    chanStr = channel.getRaw().ToString("X");\r
-                    channel.stop();\r
-                    channel = null;\r
-                }\r
-\r
-                if (sound != null)\r
-                {\r
-                    soundStr = sound.getRaw().ToString("X");\r
-                    sound.release();\r
-                    sound = null;\r
-                }\r
-#if TRACE_SOUND\r
-                Logger.Log(String.Format("Removing channel {0} sound {1} ID {2}",\r
-                    chanStr,\r
-                    soundStr,\r
-                    Id.ToString()),\r
-                    Helpers.LogLevel.Debug);\r
-#endif\r
-                lock (allBuffers)\r
-                    allBuffers.Remove(ContainerId);\r
-\r
-                if (blocking)\r
-                    stopped.Set();\r
-            }));\r
-\r
-            if (blocking)\r
-                stopped.WaitOne();\r
-\r
-        }\r
-\r
-    }\r
-}\r
+// 
+// Radegast Metaverse Client
+// Copyright (c) 2009-2012, Radegast Development Team
+// 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.
+//     * Redistributions in binary form must reproduce the above copyright
+//       notice, this list of conditions and the following disclaimer in the
+//       documentation and/or other materials provided with the distribution.
+//     * Neither the name of the application "Radegast", 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 HOLDER 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.
+//
+// $Id: Sound.cs 502 2010-03-14 23:13:46Z latifer $
+//
+// Uncomment this to get lots more logging
+//#define TRACE_SOUND
+using System;
+using System.Runtime.InteropServices;
+using System.Collections.Generic;
+using FMOD;
+using OpenMetaverse;
+using OpenMetaverse.Assets;
+using System.Threading;
+
+namespace Radegast.Media
+{
+
+    public class BufferSound : MediaObject
+    {
+        private UUID Id;
+        private UUID ContainerId;
+        private Boolean prefetchOnly = false;
+        private FMOD.MODE mode;
+        public Sound Sound { get { return sound; } }
+        private Boolean loopSound = false;
+        /// <summary>
+        /// The individual volume setting for THIS object
+        /// </summary>
+        private float volumeSetting = 0.5f;
+
+        /// <summary>
+        /// Creates a new sound object
+        /// </summary>
+        /// <param name="system">Sound system</param>
+        public BufferSound(UUID objectId, UUID soundId, bool loop, bool global, Vector3 worldpos, float vol)
+            : base()
+        {
+            InitBuffer(objectId, soundId, loop, global, worldpos, vol);
+        }
+
+        public BufferSound(UUID objectId, UUID soundId, bool loop, bool global, Vector3d worldpos, float vol)
+            : base()
+        {
+            InitBuffer(objectId, soundId, loop, global, new Vector3(worldpos), vol);
+        }
+
+        private void InitBuffer(UUID objectId, UUID soundId, bool loop, bool global, Vector3 worldpos, float vol)
+        {
+            if (manager == null || !manager.SoundSystemAvailable) return;
+
+            // Do not let this get garbage-collected.
+            lock (allBuffers)
+                allBuffers[objectId] = this;
+
+            ContainerId = objectId;
+            Id = soundId;
+            position = FromOMVSpace(worldpos);
+            volumeSetting = vol;
+            loopSound = loop;
+
+            Logger.Log(
+                String.Format(
+                    "Playing sound at <{0:0.0},{1:0.0},{2:0.0}> ID {3}",
+                    position.x,
+                    position.y,
+                    position.z,
+                    Id.ToString()),
+                Helpers.LogLevel.Debug);
+
+            // Set flags to determine how it will be played.
+            mode = FMOD.MODE.SOFTWARE | // Need software processing for all the features
+                FMOD.MODE._3D |         // Need 3D effects for placement
+                FMOD.MODE.OPENMEMORY;   // Use sound data in memory
+
+            // Set coordinate space interpretation.
+            if (global)
+                mode |= FMOD.MODE._3D_WORLDRELATIVE;
+            else
+                mode |= FMOD.MODE._3D_HEADRELATIVE;
+
+            if (loopSound)
+                mode |= FMOD.MODE.LOOP_NORMAL;
+
+            // Fetch the sound data.
+            manager.Instance.Client.Assets.RequestAsset(
+                Id,
+                AssetType.Sound,
+                false,
+                new AssetManager.AssetReceivedCallback(Assets_OnSoundReceived));
+        }
+
+        public static void Kill(UUID id)
+        {
+            if (allBuffers.ContainsKey(id))
+            {
+                BufferSound bs = allBuffers[id];
+                bs.StopSound(true);
+            }
+        }
+
+        /// <summary>
+        /// Stop all playing sounds in the environment
+        /// </summary>
+        public static void KillAll()
+        {
+            // Make a list from the dictionary so we do not get a deadlock
+            // on it when removing entries.
+            List<BufferSound> list = new List<BufferSound>(allBuffers.Values);
+
+            foreach (BufferSound s in list)
+            {
+                s.StopSound();
+            }
+
+            List<MediaObject> objs = new List<MediaObject>(allChannels.Values);
+            foreach (MediaObject obj in objs)
+            {
+                if (obj is BufferSound)
+                    ((BufferSound)obj).StopSound();
+            }
+        }
+
+        /// <summary>
+        /// Adjust volumes of all playing sounds to observe the new global sound volume
+        /// </summary>
+        public static void AdjustVolumes()
+        {
+            // Make a list from the dictionary so we do not get a deadlock
+            List<BufferSound> list = new List<BufferSound>(allBuffers.Values);
+
+            foreach (BufferSound s in list)
+            {
+                s.AdjustVolume();
+            }
+        }
+
+        /// <summary>
+        /// Adjust the volume of THIS sound when all are being adjusted.
+        /// </summary>
+        private void AdjustVolume()
+        {
+            Volume = volumeSetting * AllObjectVolume;
+        }
+
+        // A simpler constructor used by PreFetchSound.
+        public BufferSound(UUID soundId)
+            : base()
+        {
+            prefetchOnly = true;
+            ContainerId = UUID.Zero;
+            Id = soundId;
+
+            manager.Instance.Client.Assets.RequestAsset(
+                Id,
+                AssetType.Sound,
+                false,
+                new AssetManager.AssetReceivedCallback(Assets_OnSoundReceived));
+        }
+
+        /// <summary>
+        /// Releases resources of this sound object
+        /// </summary>
+        public override void Dispose()
+        {
+            base.Dispose();
+        }
+
+        /**
+         * Handle arrival of a sound resource.
+         */
+        void Assets_OnSoundReceived(AssetDownload transfer, Asset asset)
+        {
+            if (transfer.Success)
+            {
+                // If this was a Prefetch, just stop here.
+                if (prefetchOnly)
+                {
+                    return;
+                }
+
+                //                Logger.Log("Opening sound " + Id.ToString(), Helpers.LogLevel.Debug);
+
+                // Decode the Ogg Vorbis buffer.
+                AssetSound s = asset as AssetSound;
+                s.Decode();
+                byte[] data = s.AssetData;
+
+                // Describe the data to FMOD
+                extraInfo.length = (uint)data.Length;
+                extraInfo.cbsize = Marshal.SizeOf(extraInfo);
+
+                invoke(new SoundDelegate(delegate
+                {
+                    try
+                    {
+                        // Create an FMOD sound of this Ogg data.
+                        FMODExec(system.createSound(
+                            data,
+                            mode,
+                            ref extraInfo,
+                            ref sound));
+
+                        // Register for callbacks.
+                        RegisterSound(sound);
+
+
+                        // If looping is requested, loop the entire thing.
+                        if (loopSound)
+                        {
+                            uint soundlen = 0;
+                            FMODExec(sound.getLength(ref soundlen, TIMEUNIT.PCM));
+                            FMODExec(sound.setLoopPoints(0, TIMEUNIT.PCM, soundlen - 1, TIMEUNIT.PCM));
+                            FMODExec(sound.setLoopCount(-1));
+                        }
+
+                        // Allocate a channel and set initial volume.  Initially paused.
+                        FMODExec(system.playSound(CHANNELINDEX.FREE, sound, true, ref channel));
+#if TRACE_SOUND
+                    Logger.Log(
+                        String.Format("Channel {0} for {1} assigned to {2}",
+                             channel.getRaw().ToString("X"),
+                             sound.getRaw().ToString("X"),
+                             Id),
+                        Helpers.LogLevel.Debug);
+#endif
+                        RegisterChannel(channel);
+
+                        FMODExec(channel.setVolume(volumeSetting * AllObjectVolume));
+
+                        // Take note of when the sound is finished playing.
+                        FMODExec(channel.setCallback(endCallback));
+
+                        // Set attenuation limits.
+                        FMODExec(sound.set3DMinMaxDistance(
+                                    1.2f,       // Any closer than this gets no louder
+                                    100.0f));     // Further than this gets no softer.
+
+                        // Set the sound point of origin.  This is in SIM coordinates.
+                        FMODExec(channel.set3DAttributes(ref position, ref ZeroVector));
+
+                        // Turn off pause mode.  The sound will start playing now.
+                        FMODExec(channel.setPaused(false));
+                    }
+                    catch (Exception ex)
+                    {
+                        Logger.Log("Error playing sound: ", Helpers.LogLevel.Error, ex);
+                    }
+                }));
+            }
+            else
+            {
+                Logger.Log("Failed to download sound: " + transfer.Status.ToString(),
+                                        Helpers.LogLevel.Error);
+            }
+        }
+
+        /// <summary>
+        /// Handles stop sound even from FMOD
+        /// </summary>
+        /// <returns>RESULT.OK</returns>
+        protected override RESULT EndCallbackHandler()
+        {
+            StopSound();
+            return RESULT.OK;
+        }
+
+        protected void StopSound()
+        {
+            StopSound(false);
+        }
+
+        protected void StopSound(bool blocking)
+        {
+            ManualResetEvent stopped = null;
+            if (blocking)
+                stopped = new ManualResetEvent(false);
+
+            finished = true;
+
+            invoke(new SoundDelegate(delegate
+            {
+                string chanStr = "none";
+                string soundStr = "none";
+
+                // Release the buffer to avoid a big memory leak.
+                if (channel != null)
+                {
+                    lock (allChannels)
+                        allChannels.Remove(channel.getRaw());
+                    chanStr = channel.getRaw().ToString("X");
+                    channel.stop();
+                    channel = null;
+                }
+
+                if (sound != null)
+                {
+                    soundStr = sound.getRaw().ToString("X");
+                    sound.release();
+                    sound = null;
+                }
+#if TRACE_SOUND
+                Logger.Log(String.Format("Removing channel {0} sound {1} ID {2}",
+                    chanStr,
+                    soundStr,
+                    Id.ToString()),
+                    Helpers.LogLevel.Debug);
+#endif
+                lock (allBuffers)
+                    allBuffers.Remove(ContainerId);
+
+                if (blocking)
+                    stopped.Set();
+            }));
+
+            if (blocking)
+                stopped.WaitOne();
+
+        }
+
+    }
+}