Expose System.Media.SoundPlayer in System.Windows.Extensions (dotnet/corefx#34649)
authorMaryam Ariyan <maryam.ariyan@microsoft.com>
Thu, 24 Jan 2019 01:19:21 +0000 (17:19 -0800)
committerGitHub <noreply@github.com>
Thu, 24 Jan 2019 01:19:21 +0000 (17:19 -0800)
* Port System.Media members
* Add System.Media tests

Commit migrated from https://github.com/dotnet/corefx/commit/438e6af0b9d69610b25417da41ea00b2830d1b0d

22 files changed:
src/libraries/Common/src/Interop/Windows/Interop.Libraries.cs
src/libraries/Common/src/Interop/Windows/user32/Interop.MessageBeep.cs [new file with mode: 0644]
src/libraries/Common/src/Interop/Windows/winmm/Interop.MMCKINFO.cs [new file with mode: 0644]
src/libraries/Common/src/Interop/Windows/winmm/Interop.PlaySound.cs [new file with mode: 0644]
src/libraries/Common/src/Interop/Windows/winmm/Interop.mmioAscend.cs [new file with mode: 0644]
src/libraries/Common/src/Interop/Windows/winmm/Interop.mmioClose.cs [new file with mode: 0644]
src/libraries/Common/src/Interop/Windows/winmm/Interop.mmioDescend.cs [new file with mode: 0644]
src/libraries/Common/src/Interop/Windows/winmm/Interop.mmioOpen.cs [new file with mode: 0644]
src/libraries/Common/src/Interop/Windows/winmm/Interop.mmioRead.cs [new file with mode: 0644]
src/libraries/System.Windows.Extensions/ref/System.Windows.Extensions.cs
src/libraries/System.Windows.Extensions/ref/System.Windows.Extensions.csproj
src/libraries/System.Windows.Extensions/src/PinvokeAnalyzerExceptionList.analyzerdata
src/libraries/System.Windows.Extensions/src/Resources/Strings.resx
src/libraries/System.Windows.Extensions/src/System.Windows.Extensions.csproj
src/libraries/System.Windows.Extensions/src/System/Media/SoundPlayer.cs [new file with mode: 0644]
src/libraries/System.Windows.Extensions/src/System/Media/SystemSound.cs [new file with mode: 0644]
src/libraries/System.Windows.Extensions/src/System/Media/SystemSounds.cs [new file with mode: 0644]
src/libraries/System.Windows.Extensions/tests/System.Windows.Extensions.Tests.csproj
src/libraries/System.Windows.Extensions/tests/System/Media/SoundPlayerTests.cs [new file with mode: 0644]
src/libraries/System.Windows.Extensions/tests/System/Media/SystemSoundTests.cs [new file with mode: 0644]
src/libraries/System.Windows.Extensions/tests/System/Media/SystemSoundsTests.cs [new file with mode: 0644]
src/libraries/external/test-runtime/XUnit.Runtime.depproj

index f130aa1..5392784 100644 (file)
@@ -32,6 +32,7 @@ internal static partial class Interop
         internal const string Version = "version.dll";
         internal const string WebSocket = "websocket.dll";
         internal const string WinHttp = "winhttp.dll";
+        internal const string WinMM = "winmm.dll";
         internal const string Ws2_32 = "ws2_32.dll";
         internal const string Wtsapi32 = "wtsapi32.dll";
         internal const string CompressionNative = "clrcompression.dll";
diff --git a/src/libraries/Common/src/Interop/Windows/user32/Interop.MessageBeep.cs b/src/libraries/Common/src/Interop/Windows/user32/Interop.MessageBeep.cs
new file mode 100644 (file)
index 0000000..b1f2095
--- /dev/null
@@ -0,0 +1,20 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.InteropServices;
+
+internal partial class Interop
+{
+    internal partial class User32
+    {
+        internal const int MB_OK = 0;
+        internal const int MB_ICONHAND = 0x10;
+        internal const int MB_ICONQUESTION = 0x20;
+        internal const int MB_ICONEXCLAMATION = 0x30;
+        internal const int MB_ICONASTERISK = 0x40;
+
+        [DllImport(Libraries.User32, ExactSpelling = true)]
+        internal static extern bool MessageBeep(int type);
+    }
+}
diff --git a/src/libraries/Common/src/Interop/Windows/winmm/Interop.MMCKINFO.cs b/src/libraries/Common/src/Interop/Windows/winmm/Interop.MMCKINFO.cs
new file mode 100644 (file)
index 0000000..63ef27e
--- /dev/null
@@ -0,0 +1,21 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.InteropServices;
+
+internal static partial class Interop
+{
+    internal static partial class WinMM
+    {
+        [StructLayout(LayoutKind.Sequential)]
+        internal class MMCKINFO
+        {
+            internal int ckID;
+            internal int cksize;
+            internal int fccType;
+            internal int dwDataOffset;
+            internal int dwFlags;
+        }
+    }
+}
diff --git a/src/libraries/Common/src/Interop/Windows/winmm/Interop.PlaySound.cs b/src/libraries/Common/src/Interop/Windows/winmm/Interop.PlaySound.cs
new file mode 100644 (file)
index 0000000..7e8f8be
--- /dev/null
@@ -0,0 +1,27 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Runtime.InteropServices;
+
+internal static partial class Interop
+{
+    internal static partial class WinMM
+    {
+        internal const int SND_SYNC = 0x0;
+        internal const int SND_ASYNC = 0x1;
+        internal const int SND_NODEFAULT = 0x2;
+        internal const int SND_MEMORY = 0x4;
+        internal const int SND_LOOP = 0x8;
+        internal const int SND_PURGE = 0x40;
+        internal const int SND_FILENAME = 0x20000;
+        internal const int SND_NOSTOP = 0x10;
+
+        [DllImport(Libraries.WinMM, ExactSpelling = true, CharSet = CharSet.Unicode, EntryPoint = "PlaySoundW")]
+        internal static extern bool PlaySound(string soundName, IntPtr hmod, int soundFlags);
+
+        [DllImport(Libraries.WinMM, ExactSpelling = true, EntryPoint = "PlaySoundW")]
+        internal static extern bool PlaySound(byte[] soundName, IntPtr hmod, int soundFlags);
+    }
+}
diff --git a/src/libraries/Common/src/Interop/Windows/winmm/Interop.mmioAscend.cs b/src/libraries/Common/src/Interop/Windows/winmm/Interop.mmioAscend.cs
new file mode 100644 (file)
index 0000000..6567e6c
--- /dev/null
@@ -0,0 +1,15 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Runtime.InteropServices;
+
+internal static partial class Interop
+{
+    internal static partial class WinMM
+    {
+        [DllImport(Libraries.WinMM)]
+        internal static extern int mmioAscend(IntPtr hMIO, MMCKINFO lpck, int flags);
+    }
+}
diff --git a/src/libraries/Common/src/Interop/Windows/winmm/Interop.mmioClose.cs b/src/libraries/Common/src/Interop/Windows/winmm/Interop.mmioClose.cs
new file mode 100644 (file)
index 0000000..e61dea0
--- /dev/null
@@ -0,0 +1,15 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Runtime.InteropServices;
+
+internal static partial class Interop
+{
+    internal static partial class WinMM
+    {
+        [DllImport(Interop.Libraries.WinMM)]
+        internal static extern int mmioClose(IntPtr hMIO, int flags);
+    }
+}
diff --git a/src/libraries/Common/src/Interop/Windows/winmm/Interop.mmioDescend.cs b/src/libraries/Common/src/Interop/Windows/winmm/Interop.mmioDescend.cs
new file mode 100644 (file)
index 0000000..931d984
--- /dev/null
@@ -0,0 +1,20 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Runtime.InteropServices;
+
+internal static partial class Interop
+{
+    internal static partial class WinMM
+    {
+        internal const int MMIO_FINDRIFF = 0x00000020;
+
+        [DllImport(Libraries.WinMM)]
+        internal static extern int mmioDescend(IntPtr hMIO,
+                                               [MarshalAs(UnmanagedType.LPStruct)] MMCKINFO lpck,
+                                               [MarshalAs(UnmanagedType.LPStruct)] MMCKINFO lcpkParent,
+                                               int flags);
+    }
+}
diff --git a/src/libraries/Common/src/Interop/Windows/winmm/Interop.mmioOpen.cs b/src/libraries/Common/src/Interop/Windows/winmm/Interop.mmioOpen.cs
new file mode 100644 (file)
index 0000000..653ae73
--- /dev/null
@@ -0,0 +1,18 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Runtime.InteropServices;
+
+internal static partial class Interop
+{
+    internal static partial class WinMM
+    {
+        internal const int MMIO_READ = 0x00000000;
+        internal const int MMIO_ALLOCBUF = 0x00010000;
+
+        [DllImport(Libraries.WinMM, CharSet = CharSet.Auto)]
+        internal static extern IntPtr mmioOpen(string fileName, IntPtr not_used, int flags);
+    }
+}
diff --git a/src/libraries/Common/src/Interop/Windows/winmm/Interop.mmioRead.cs b/src/libraries/Common/src/Interop/Windows/winmm/Interop.mmioRead.cs
new file mode 100644 (file)
index 0000000..1ddf4db
--- /dev/null
@@ -0,0 +1,31 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Runtime.InteropServices;
+
+internal static partial class Interop
+{
+    internal static partial class WinMM
+    {
+        [StructLayout(LayoutKind.Sequential)]
+        internal class WAVEFORMATEX
+        {
+            internal short wFormatTag;
+            internal short nChannels;
+            internal int nSamplesPerSec;
+            internal int nAvgBytesPerSec;
+            internal short nBlockAlign;
+            internal short wBitsPerSample;
+            internal short cbSize;
+        }
+
+        internal const int WAVE_FORMAT_PCM = 0x0001;
+        internal const int WAVE_FORMAT_ADPCM = 0x0002;
+        internal const int WAVE_FORMAT_IEEE_FLOAT = 0x0003;
+
+        [DllImport(Libraries.WinMM)]
+        internal static extern int mmioRead(IntPtr hMIO, [MarshalAs(UnmanagedType.LPArray)] byte[] wf, int cch);
+    }
+}
index 2dbfe6d..e766acb 100644 (file)
@@ -1,6 +1,9 @@
 // Licensed to the .NET Foundation under one or more agreements.
 // The .NET Foundation licenses this file to you under the MIT license.
 // See the LICENSE file in the project root for more information.
+// ------------------------------------------------------------------------------
+// Changes to this file must follow the http://aka.ms/api-review process.
+// ------------------------------------------------------------------------------
 
 namespace System.Drawing
 {
@@ -75,19 +78,60 @@ namespace System.Drawing.Printing
         public override bool GetCreateInstanceSupported(System.ComponentModel.ITypeDescriptorContext context) { throw null; }
     }
 }
-namespace System.Security.Cryptography.X509Certificates
+namespace System.Media
 {
-    public enum X509SelectionFlag
+    public partial class SoundPlayer : System.ComponentModel.Component, System.Runtime.Serialization.ISerializable
     {
-        SingleSelection = 0x00,
-        MultiSelection = 0x01
+        public SoundPlayer() { }
+        public SoundPlayer(System.IO.Stream stream) { }
+        protected SoundPlayer(System.Runtime.Serialization.SerializationInfo serializationInfo, System.Runtime.Serialization.StreamingContext context) { }
+        public SoundPlayer(string soundLocation) { }
+        public bool IsLoadCompleted { get { throw null; } }
+        public int LoadTimeout { get { throw null; } set { } }
+        public string SoundLocation { get { throw null; } set { } }
+        public System.IO.Stream Stream { get { throw null; } set { } }
+        public object Tag { get { throw null; } set { } }
+        public event System.ComponentModel.AsyncCompletedEventHandler LoadCompleted { add { } remove { } }
+        public event System.EventHandler SoundLocationChanged { add { } remove { } }
+        public event System.EventHandler StreamChanged { add { } remove { } }
+        public void Load() { }
+        public void LoadAsync() { }
+        protected virtual void OnLoadCompleted(System.ComponentModel.AsyncCompletedEventArgs e) { }
+        protected virtual void OnSoundLocationChanged(System.EventArgs e) { }
+        protected virtual void OnStreamChanged(System.EventArgs e) { }
+        public void Play() { }
+        public void PlayLooping() { }
+        public void PlaySync() { }
+        public void Stop() { }
+        void System.Runtime.Serialization.ISerializable.GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { }
     }
-
+    public partial class SystemSound
+    {
+        internal SystemSound() { }
+        public void Play() { }
+    }
+    public static partial class SystemSounds
+    {
+        public static System.Media.SystemSound Asterisk { get { throw null; } }
+        public static System.Media.SystemSound Beep { get { throw null; } }
+        public static System.Media.SystemSound Exclamation { get { throw null; } }
+        public static System.Media.SystemSound Hand { get { throw null; } }
+        public static System.Media.SystemSound Question { get { throw null; } }
+    }
+}
+namespace System.Security.Cryptography.X509Certificates
+{
     public sealed partial class X509Certificate2UI
-    { 
-        public static void DisplayCertificate(X509Certificates.X509Certificate2 certificate) { throw null; }
-        public static void DisplayCertificate(X509Certificates.X509Certificate2 certificate, IntPtr hwndParent) { throw null; }
-        public static X509Certificates.X509Certificate2Collection SelectFromCollection(X509Certificates.X509Certificate2Collection certificates, string title, string message, X509SelectionFlag selectionFlag) { throw null; }
-        public static X509Certificates.X509Certificate2Collection SelectFromCollection(X509Certificates.X509Certificate2Collection certificates, string title, string message, X509SelectionFlag selectionFlag, IntPtr hwndParent) { throw null; }
+    {
+        public X509Certificate2UI() { }
+        public static void DisplayCertificate(System.Security.Cryptography.X509Certificates.X509Certificate2 certificate) { }
+        public static void DisplayCertificate(System.Security.Cryptography.X509Certificates.X509Certificate2 certificate, System.IntPtr hwndParent) { }
+        public static System.Security.Cryptography.X509Certificates.X509Certificate2Collection SelectFromCollection(System.Security.Cryptography.X509Certificates.X509Certificate2Collection certificates, string title, string message, System.Security.Cryptography.X509Certificates.X509SelectionFlag selectionFlag) { throw null; }
+        public static System.Security.Cryptography.X509Certificates.X509Certificate2Collection SelectFromCollection(System.Security.Cryptography.X509Certificates.X509Certificate2Collection certificates, string title, string message, System.Security.Cryptography.X509Certificates.X509SelectionFlag selectionFlag, System.IntPtr hwndParent) { throw null; }
+    }
+    public enum X509SelectionFlag
+    {
+        MultiSelection = 1,
+        SingleSelection = 0,
     }
 }
index 6d0f235..75704bf 100644 (file)
@@ -9,6 +9,8 @@
   <ItemGroup>
     <ProjectReference Include="..\..\System.ComponentModel.TypeConverter\ref\System.ComponentModel.TypeConverter.csproj" />
     <ProjectReference Include="..\..\System.Runtime\ref\System.Runtime.csproj" />
+    <ProjectReference Include="..\..\System.ComponentModel.EventBasedAsync\ref\System.ComponentModel.EventBasedAsync.csproj" />
+    <ProjectReference Include="..\..\System.ComponentModel.Primitives\ref\System.ComponentModel.Primitives.csproj" />
     <ProjectReference Include="..\..\System.Security.Cryptography.X509Certificates\ref\System.Security.Cryptography.X509Certificates.csproj" />
   </ItemGroup>
-</Project>
\ No newline at end of file
+</Project>
index be9bf16..2e702d3 100644 (file)
@@ -1,2 +1,9 @@
 cryptui.dll!CryptUIDlgViewCertificateW
-cryptui.dll!CryptUIDlgSelectCertificateW
\ No newline at end of file
+cryptui.dll!CryptUIDlgSelectCertificateW
+user32.dll!MessageBeep
+winmm.dll!mmioAscend
+winmm.dll!mmioDescend
+winmm.dll!mmioClose
+winmm.dll!mmioOpen
+winmm.dll!mmioRead
+winmm.dll!PlaySoundW
index a1d739b..6ad886a 100644 (file)
   <data name="PlatformNotSupported_X509Certificate2UI" xml:space="preserve">
     <value>X509Certificate2UI is not supported on this platform.</value>
   </data>
+  <data name="SoundAPIBadSoundLocation" xml:space="preserve">
+    <value>Could not determine a universal resource identifier for the sound location.</value>
+  </data>
+  <data name="SoundAPIFileDoesNotExist" xml:space="preserve">
+    <value>Please be sure a sound file exists at the specified location.</value>
+  </data>
+  <data name="SoundAPIFormatNotSupported" xml:space="preserve">
+    <value>Sound API only supports playing PCM wave files.</value>
+  </data>
+  <data name="SoundAPIInvalidWaveFile" xml:space="preserve">
+    <value>The file located at {0} is not a valid wave file.</value>
+  </data>
+  <data name="SoundAPIInvalidWaveHeader" xml:space="preserve">
+    <value>The wave header is corrupt.</value>
+  </data>
+  <data name="SoundAPILoadTimedOut" xml:space="preserve">
+    <value>The request to load the wave file in memory timed out.</value>
+  </data>
+  <data name="SoundAPILoadTimeout" xml:space="preserve">
+    <value>The LoadTimeout property of a SoundPlayer cannot be negative.</value>
+  </data>
+  <data name="SoundAPIReadError" xml:space="preserve">
+    <value>=There was an error reading the file located at {0}. Please make sure that a valid wave file exists at the specified location.</value>
+  </data>
   <data name="TextParseFailedFormat" xml:space="preserve">
     <value>Text "{0}" cannot be parsed. The expected text format is "{1}".</value>
   </data>
   <data name="PropertyValueInvalidEntry" xml:space="preserve">
     <value>IDictionary parameter contains at least one entry that is not valid. Ensure all values are consistent with the object's properties.</value>
   </data>
-</root>
\ No newline at end of file
+</root>
index ce50fb1..2f93a70 100644 (file)
     <Compile Include="$(CommonPath)\Interop\Windows\CryptUI\Interop.CryptUIDlgCertificate.cs">
       <Link>Common\Interop\Windows\Crypt32\Interop.CryptUIDlgCertificate.cs</Link>
     </Compile>
+    <Compile Include="$(CommonPath)\Interop\Windows\User32\Interop.MessageBeep.cs">
+      <Link>Common\Interop\Windows\User32\Interop.MessageBeep.cs</Link>
+    </Compile>
+    <Compile Include="$(CommonPath)\Interop\Windows\winmm\Interop.MMCKINFO.cs">
+      <Link>Common\Interop\Windows\winmm\Interop.MMCKINFO.cs</Link>
+    </Compile>
+    <Compile Include="$(CommonPath)\Interop\Windows\winmm\Interop.mmioAscend.cs">
+      <Link>Common\Interop\Windows\winmm\Interop.mmioAscend.cs</Link>
+    </Compile>
+    <Compile Include="$(CommonPath)\Interop\Windows\winmm\Interop.mmioClose.cs">
+      <Link>Common\Interop\Windows\winmm\Interop.mmioClose.cs</Link>
+    </Compile>
+    <Compile Include="$(CommonPath)\Interop\Windows\winmm\Interop.mmioDescend.cs">
+      <Link>Common\Interop\Windows\winmm\Interop.mmioDescend.cs</Link>
+    </Compile>
+    <Compile Include="$(CommonPath)\Interop\Windows\winmm\Interop.mmioRead.cs">
+      <Link>Common\Interop\Windows\winmm\Interop.mmioRead.cs</Link>
+    </Compile>
+    <Compile Include="$(CommonPath)\Interop\Windows\winmm\Interop.mmioOpen.cs">
+      <Link>Common\Interop\Windows\winmm\Interop.mmioOpen.cs</Link>
+    </Compile>
+    <Compile Include="$(CommonPath)\Interop\Windows\winmm\Interop.PlaySound.cs">
+      <Link>Common\Interop\Windows\winmm\Interop.PlaySound.cs</Link>
+    </Compile>
     <Compile Include="$(CommonPath)\Interop\Windows\Interop.Libraries.cs">
       <Link>Common\Interop\Windows\Interop.Libraries.cs</Link>
     </Compile>
     <Compile Include="System\Drawing\ImageConverter.cs" />
     <Compile Include="System\Drawing\ImageFormatConverter.cs" />
     <Compile Include="System\Drawing\Printing\MarginsConverter.cs" />
+    <Compile Include="System\Media\SoundPlayer.cs" />
+    <Compile Include="System\Media\SystemSound.cs" />
+    <Compile Include="System\Media\SystemSounds.cs" />
   </ItemGroup>
   <ItemGroup Condition="'$(TargetGroup)' == 'netcoreapp'">
     <Reference Include="System.Buffers" />
     <Reference Include="System.Collections.NonGeneric" />
+    <Reference Include="System.ComponentModel.EventBasedAsync" />
+    <Reference Include="System.ComponentModel.Primitives" />
     <Reference Include="System.ComponentModel.TypeConverter" />
     <Reference Include="System.Diagnostics.Debug" />
     <Reference Include="System.Diagnostics.Tools" />
     <Reference Include="System.Drawing.Common" />
+    <Reference Include="System.IO.FileSystem" />
     <Reference Include="System.Memory" />
+    <Reference Include="System.Net.Requests" />
     <Reference Include="System.Resources.ResourceManager" />
-    <Reference Include="System.Runtime" />
     <Reference Include="System.Runtime.Extensions" />
     <Reference Include="System.Runtime.InteropServices" />
+    <Reference Include="System.Runtime" />
     <Reference Include="System.Security.Cryptography.X509Certificates" />
     <Reference Include="System.Text.RegularExpressions" />
+    <Reference Include="System.Threading.Thread" />
     <Reference Include="System.Threading" />
   </ItemGroup>
-</Project>
\ No newline at end of file
+</Project>
diff --git a/src/libraries/System.Windows.Extensions/src/System/Media/SoundPlayer.cs b/src/libraries/System.Windows.Extensions/src/System/Media/SoundPlayer.cs
new file mode 100644 (file)
index 0000000..97a9ef4
--- /dev/null
@@ -0,0 +1,706 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.ComponentModel;
+using System.Diagnostics;
+using System.IO;
+using System.Net;
+using System.Runtime.InteropServices;
+using System.Runtime.Serialization;
+using System.Threading;
+
+namespace System.Media
+{
+    public class SoundPlayer : Component, ISerializable
+    {
+        private const int BlockSize = 1024;
+        private const int DefaultLoadTimeout = 10000; // 10 secs
+
+        private Uri _uri = null;
+        private string _soundLocation = string.Empty;
+        private int _loadTimeout = DefaultLoadTimeout;
+
+        // used to lock all synchronous calls to the SoundPlayer object
+        private readonly ManualResetEvent _semaphore = new ManualResetEvent(true);
+
+        // the worker copyThread
+        // we start the worker copyThread ONLY from entry points in the SoundPlayer API
+        // we also set the tread to null only from the entry points in the SoundPlayer API
+        private Thread _copyThread = null;
+
+        // local buffer information
+        private int _currentPos = 0;
+        private Stream _stream = null;
+        private Exception _lastLoadException = null;
+        private bool _doesLoadAppearSynchronous = false;
+        private byte[] _streamData = null;
+        private AsyncOperation _asyncOperation = null;
+        private readonly SendOrPostCallback _loadAsyncOperationCompleted;
+
+        // event
+        private static readonly object s_eventLoadCompleted = new object();
+        private static readonly object s_eventSoundLocationChanged = new object();
+        private static readonly object s_eventStreamChanged = new object();
+
+        public SoundPlayer()
+        {
+            _loadAsyncOperationCompleted = new SendOrPostCallback(LoadAsyncOperationCompleted);
+        }
+
+        public SoundPlayer(string soundLocation) : this()
+        {
+            SetupSoundLocation(soundLocation ?? string.Empty);
+        }
+
+        public SoundPlayer(Stream stream) : this()
+        {
+            _stream = stream;
+        }
+
+        protected SoundPlayer(SerializationInfo serializationInfo, StreamingContext context)
+        {
+            throw new PlatformNotSupportedException();
+        }
+        
+        public int LoadTimeout
+        {
+            get => _loadTimeout;
+            set
+            {
+                if (value < 0)
+                {
+                    throw new ArgumentOutOfRangeException("LoadTimeout", value, SR.SoundAPILoadTimeout);
+                }
+
+                _loadTimeout = value;
+            }
+        }
+
+        public string SoundLocation
+        {
+            get => _soundLocation;
+            set
+            {
+                if (value == null)
+                {
+                    value = string.Empty;
+                }
+
+                if (_soundLocation.Equals(value))
+                {
+                    return;
+                }
+
+                SetupSoundLocation(value);
+                OnSoundLocationChanged(EventArgs.Empty);
+            }
+        }
+
+        public Stream Stream
+        {
+            get
+            {
+                // if the path is set, we should return null
+                // Path and Stream are mutually exclusive
+                if (_uri != null)
+                {
+                    return null;
+                }
+
+                return _stream;
+            }
+            set
+            {
+                if (_stream == value)
+                {
+                    return;
+                }
+
+                SetupStream(value);
+                OnStreamChanged(EventArgs.Empty);
+            }
+        }
+
+        public bool IsLoadCompleted { get; private set; } = false;
+
+        public object Tag { get; set; } = null;
+
+        public void LoadAsync()
+        {
+            // if we have a file there is nothing to load - we just pass the file to the PlaySound function
+            // if we have a stream, then we start loading the stream async
+            if (_uri != null && _uri.IsFile)
+            {
+                Debug.Assert(_stream == null, "we can't have a stream and a path at the same time");
+                IsLoadCompleted = true;
+
+                FileInfo fi = new FileInfo(_uri.LocalPath);
+                if (!fi.Exists)
+                {
+                    throw new FileNotFoundException(SR.SoundAPIFileDoesNotExist, _soundLocation);
+                }
+
+                OnLoadCompleted(new AsyncCompletedEventArgs(null, false, null));
+                return;
+            }
+
+            // if we are actively loading, keep it running
+            if (_copyThread != null && _copyThread.ThreadState == ThreadState.Running)
+            {
+                return;
+            }
+            IsLoadCompleted = false;
+            _streamData = null;
+            _currentPos = 0;
+
+            _asyncOperation = AsyncOperationManager.CreateOperation(null);
+
+            LoadStream(false);
+        }
+
+        private void LoadAsyncOperationCompleted(object arg)
+        {
+            OnLoadCompleted((AsyncCompletedEventArgs)arg);
+        }
+
+        // called for loading a stream synchronously
+        // called either when the user is setting the path/stream and we are loading
+        // or when loading took more time than the time out
+        private void CleanupStreamData()
+        {
+            _currentPos = 0;
+            _streamData = null;
+            IsLoadCompleted = false;
+            _lastLoadException = null;
+            _doesLoadAppearSynchronous = false;
+            _copyThread = null;
+            _semaphore.Set();
+        }
+
+        public void Load()
+        {
+            // if we have a file there is nothing to load - we just pass the file to the PlaySound function
+            // if we have a stream, then we start loading the stream sync
+            if (_uri != null && _uri.IsFile)
+            {
+                Debug.Assert(_stream == null, "we can't have a stream and a path at the same time");
+                FileInfo fi = new FileInfo(_uri.LocalPath);
+                if (!fi.Exists)
+                {
+                    throw new FileNotFoundException(SR.SoundAPIFileDoesNotExist, _soundLocation);
+                }
+                IsLoadCompleted = true;
+                OnLoadCompleted(new AsyncCompletedEventArgs(null, false, null));
+                return;
+            }
+
+            LoadSync();
+        }
+
+        private void LoadAndPlay(int flags)
+        {
+            // When the user does not specify a sound location nor a stream, play Beep
+            if (string.IsNullOrEmpty(_soundLocation) && _stream == null)
+            {
+                SystemSounds.Beep.Play();
+                return;
+            }
+
+            if (_uri != null && _uri.IsFile)
+            {
+                // Someone can call SoundPlayer::set_Location between the time
+                // LoadAndPlay validates the sound file and the time it calls PlaySound.
+                // The SoundPlayer will end up playing an un-validated sound file.
+                // The solution is to store the uri.LocalPath on a local variable
+                string localPath = _uri.LocalPath;
+
+                // Play the path - don't use uri.AbsolutePath because that gives problems
+                // when there are whitespaces in file names
+                IsLoadCompleted = true;
+
+                ValidateSoundFile(localPath);
+                Interop.WinMM.PlaySound(localPath, IntPtr.Zero, Interop.WinMM.SND_NODEFAULT | flags);
+            }
+            else
+            {
+                LoadSync();
+                ValidateSoundData(_streamData);
+                Interop.WinMM.PlaySound(_streamData, IntPtr.Zero, Interop.WinMM.SND_MEMORY | Interop.WinMM.SND_NODEFAULT | flags);
+            }
+        }
+
+        private void LoadSync()
+        {
+            Debug.Assert((_uri == null || !_uri.IsFile), "we only load streams");
+
+            // first make sure that any possible download ended
+            if (!_semaphore.WaitOne(LoadTimeout, false))
+            {
+                _copyThread?.Abort();
+                CleanupStreamData();
+                throw new TimeoutException(SR.SoundAPILoadTimedOut);
+            }
+
+            // if we have data, then we are done
+            if (_streamData != null)
+            {
+                return;
+            }
+
+            // setup the http stream
+            if (_uri != null && !_uri.IsFile && _stream == null)
+            {
+                WebRequest webRequest = WebRequest.Create(_uri);
+                webRequest.Timeout = LoadTimeout;
+
+                WebResponse webResponse;
+                webResponse = webRequest.GetResponse();
+
+                // now get the stream
+                _stream = webResponse.GetResponseStream();
+            }
+
+            if (_stream.CanSeek)
+            {
+                // if we can get data synchronously, then get it
+                LoadStream(true);
+            }
+            else
+            {
+                // the data can't be loaded synchronously
+                // load it async, then wait for it to finish
+                _doesLoadAppearSynchronous = true; // to avoid OnFailed call.
+                LoadStream(false);
+
+                if (!_semaphore.WaitOne(LoadTimeout, false))
+                {
+                    _copyThread?.Abort();
+                    CleanupStreamData();
+                    throw new TimeoutException(SR.SoundAPILoadTimedOut);
+                }
+
+                _doesLoadAppearSynchronous = false;
+
+                if (_lastLoadException != null)
+                {
+                    throw _lastLoadException;
+                }
+            }
+
+            // we don't need the worker copyThread anymore
+            _copyThread = null;
+        }
+
+        private void LoadStream(bool loadSync)
+        {
+            if (loadSync && _stream.CanSeek)
+            {
+                int streamLen = (int)_stream.Length;
+                _currentPos = 0;
+                _streamData = new byte[streamLen];
+                _stream.Read(_streamData, 0, streamLen);
+                IsLoadCompleted = true;
+                OnLoadCompleted(new AsyncCompletedEventArgs(null, false, null));
+            }
+            else
+            {
+                // lock any synchronous calls on the Sound object
+                _semaphore.Reset();
+                // start loading
+                _copyThread = new Thread(new ThreadStart(WorkerThread));
+                _copyThread.Start();
+            }
+        }
+
+        public void Play()
+        {
+            LoadAndPlay(Interop.WinMM.SND_ASYNC);
+        }
+
+        public void PlaySync()
+        {
+            LoadAndPlay(Interop.WinMM.SND_SYNC);
+        }
+
+        public void PlayLooping()
+        {
+            LoadAndPlay(Interop.WinMM.SND_LOOP | Interop.WinMM.SND_ASYNC);
+        }
+
+        private static Uri ResolveUri(string partialUri)
+        {
+            Uri result = null;
+            try
+            {
+                result = new Uri(partialUri);
+            }
+            catch (UriFormatException)
+            {
+                // eat URI parse exceptions
+            }
+
+            if (result == null)
+            {
+                // try relative to appbase
+                try
+                {
+                    result = new Uri(Path.GetFullPath(partialUri));
+                }
+                catch (UriFormatException)
+                {
+                    // eat URI parse exceptions
+                }
+            }
+
+            return result;
+        }
+
+        private void SetupSoundLocation(string soundLocation)
+        {
+            // if we are loading a file, stop it right now
+            if (_copyThread != null)
+            {
+                _copyThread.Abort();
+                CleanupStreamData();
+            }
+
+            _uri = ResolveUri(soundLocation);
+            _soundLocation = soundLocation;
+            _stream = null;
+            if (_uri == null)
+            {
+                if (!string.IsNullOrEmpty(soundLocation))
+                {
+                    throw new UriFormatException(SR.SoundAPIBadSoundLocation);
+                }
+            }
+            else
+            {
+                if (!_uri.IsFile)
+                {
+                    // we are referencing a web resource ...
+                    // we treat it as a stream...
+                    _streamData = null;
+                    _currentPos = 0;
+                    IsLoadCompleted = false;
+                }
+            }
+        }
+
+        private void SetupStream(Stream stream)
+        {
+            if (_copyThread != null)
+            {
+                _copyThread.Abort();
+                CleanupStreamData();
+            }
+
+            _stream = stream;
+            _soundLocation = string.Empty;
+            _streamData = null;
+            _currentPos = 0;
+            IsLoadCompleted = false;
+            if (stream != null)
+            {
+                _uri = null;
+            }
+        }
+
+        public void Stop()
+        {
+            Interop.WinMM.PlaySound((byte[])null, IntPtr.Zero, Interop.WinMM.SND_PURGE);
+        }
+
+        public event AsyncCompletedEventHandler LoadCompleted
+        {
+            add
+            {
+                Events.AddHandler(s_eventLoadCompleted, value);
+            }
+            remove
+            {
+                Events.RemoveHandler(s_eventLoadCompleted, value);
+            }
+        }
+
+        public event EventHandler SoundLocationChanged
+        {
+            add
+            {
+                Events.AddHandler(s_eventSoundLocationChanged, value);
+            }
+            remove
+            {
+                Events.RemoveHandler(s_eventSoundLocationChanged, value);
+            }
+        }
+
+        public event EventHandler StreamChanged
+        {
+            add
+            {
+                Events.AddHandler(s_eventStreamChanged, value);
+            }
+            remove
+            {
+                Events.RemoveHandler(s_eventStreamChanged, value);
+            }
+        }
+
+        protected virtual void OnLoadCompleted(AsyncCompletedEventArgs e)
+        {
+            ((AsyncCompletedEventHandler)Events[s_eventLoadCompleted])?.Invoke(this, e);
+        }
+
+        protected virtual void OnSoundLocationChanged(EventArgs e)
+        {
+            ((EventHandler)Events[s_eventSoundLocationChanged])?.Invoke(this, e);
+        }
+
+        protected virtual void OnStreamChanged(EventArgs e)
+        {
+            ((EventHandler)Events[s_eventStreamChanged])?.Invoke(this, e);
+        }
+
+        private void WorkerThread()
+        {
+            try
+            {
+                // setup the http stream
+                if (_uri != null && !_uri.IsFile && _stream == null)
+                {
+                    WebRequest webRequest = WebRequest.Create(_uri);
+
+                    WebResponse webResponse = webRequest.GetResponse();
+
+                    _stream = webResponse.GetResponseStream();
+                }
+
+                _streamData = new byte[BlockSize];
+
+                int readBytes = _stream.Read(_streamData, _currentPos, BlockSize);
+                int totalBytes = readBytes;
+
+                while (readBytes > 0)
+                {
+                    _currentPos += readBytes;
+                    if (_streamData.Length < _currentPos + BlockSize)
+                    {
+                        byte[] newData = new byte[_streamData.Length * 2];
+                        Array.Copy(_streamData, newData, _streamData.Length);
+                        _streamData = newData;
+                    }
+                    readBytes = _stream.Read(_streamData, _currentPos, BlockSize);
+                    totalBytes += readBytes;
+                }
+
+                _lastLoadException = null;
+            }
+            catch (Exception exception)
+            {
+                _lastLoadException = exception;
+            }
+
+            if (!_doesLoadAppearSynchronous)
+            {
+                // Post notification back to the UI thread.
+                _asyncOperation.PostOperationCompleted(
+                    _loadAsyncOperationCompleted,
+                    new AsyncCompletedEventArgs(_lastLoadException, false, null));
+            }
+            IsLoadCompleted = true;
+            _semaphore.Set();
+        }
+
+        private unsafe void ValidateSoundFile(string fileName)
+        {
+            IntPtr hMIO = Interop.WinMM.mmioOpen(fileName, IntPtr.Zero, Interop.WinMM.MMIO_READ | Interop.WinMM.MMIO_ALLOCBUF);
+            if (hMIO == IntPtr.Zero)
+            {
+                throw new FileNotFoundException(SR.SoundAPIFileDoesNotExist, _soundLocation);
+            }
+
+            try
+            {
+                Interop.WinMM.WAVEFORMATEX waveFormat = null;
+                var ckRIFF = new Interop.WinMM.MMCKINFO()
+                {
+                    fccType = mmioFOURCC('W', 'A', 'V', 'E')
+                };
+                var ck = new Interop.WinMM.MMCKINFO();
+                if (Interop.WinMM.mmioDescend(hMIO, ckRIFF, null, Interop.WinMM.MMIO_FINDRIFF) != 0)
+                {
+                    throw new InvalidOperationException(SR.Format(SR.SoundAPIInvalidWaveFile, _soundLocation));
+                }
+
+                while (Interop.WinMM.mmioDescend(hMIO, ck, ckRIFF, 0) == 0)
+                {
+                    if (ck.dwDataOffset + ck.cksize > ckRIFF.dwDataOffset + ckRIFF.cksize)
+                    {
+                        throw new InvalidOperationException(SR.SoundAPIInvalidWaveHeader);
+                    }
+
+                    if (ck.ckID == mmioFOURCC('f', 'm', 't', ' '))
+                    {
+                        if (waveFormat == null)
+                        {
+                            int dw = ck.cksize;
+                            if (dw < Marshal.SizeOf(typeof(Interop.WinMM.WAVEFORMATEX)))
+                            {
+                                dw = Marshal.SizeOf(typeof(Interop.WinMM.WAVEFORMATEX));
+                            }
+
+                            waveFormat = new Interop.WinMM.WAVEFORMATEX();
+                            var data = new byte[dw];
+                            if (Interop.WinMM.mmioRead(hMIO, data, dw) != dw)
+                            {
+                                throw new InvalidOperationException(SR.Format(SR.SoundAPIReadError, _soundLocation));
+                            }
+
+                            fixed (byte* pdata = data)
+                            {
+                                Marshal.PtrToStructure((IntPtr)pdata, waveFormat);
+                            }
+                        }
+                        else
+                        {
+                            // multiple formats?
+                        }
+                    }
+                    Interop.WinMM.mmioAscend(hMIO, ck, 0);
+                }
+
+                if (waveFormat == null)
+                {
+                    throw new InvalidOperationException(SR.SoundAPIInvalidWaveHeader);
+                }
+                if (waveFormat.wFormatTag != Interop.WinMM.WAVE_FORMAT_PCM &&
+                    waveFormat.wFormatTag != Interop.WinMM.WAVE_FORMAT_ADPCM &&
+                    waveFormat.wFormatTag != Interop.WinMM.WAVE_FORMAT_IEEE_FLOAT)
+                {
+                    throw new InvalidOperationException(SR.SoundAPIFormatNotSupported);
+                }
+
+            }
+            finally
+            {
+                if (hMIO != IntPtr.Zero)
+                {
+                    Interop.WinMM.mmioClose(hMIO, 0);
+                }
+            }
+        }
+
+        private static void ValidateSoundData(byte[] data)
+        {
+            int position = 0;
+            short wFormatTag = -1;
+            bool fmtChunkFound = false;
+
+            // the RIFF header should be at least 12 bytes long.
+            if (data.Length < 12)
+            {
+                throw new InvalidOperationException(SR.SoundAPIInvalidWaveHeader);
+            }
+
+            // validate the RIFF header
+            if (data[0] != 'R' || data[1] != 'I' || data[2] != 'F' || data[3] != 'F')
+            {
+                throw new InvalidOperationException(SR.SoundAPIInvalidWaveHeader);
+            }
+            if (data[8] != 'W' || data[9] != 'A' || data[10] != 'V' || data[11] != 'E')
+            {
+                throw new InvalidOperationException(SR.SoundAPIInvalidWaveHeader);
+            }
+
+            // we only care about "fmt " chunk
+            position = 12;
+            int len = data.Length;
+            while (!fmtChunkFound && position < len - 8)
+            {
+                if (data[position] == (byte)'f' && data[position + 1] == (byte)'m' && data[position + 2] == (byte)'t' && data[position + 3] == (byte)' ')
+                {
+                    // fmt chunk
+                    fmtChunkFound = true;
+                    int chunkSize = BytesToInt(data[position + 7], data[position + 6], data[position + 5], data[position + 4]);
+
+                    // get the cbSize from the WAVEFORMATEX
+                    int sizeOfWAVEFORMAT = 16;
+                    if (chunkSize != sizeOfWAVEFORMAT)
+                    {
+                        // we are dealing w/ WAVEFORMATEX
+                        // do extra validation
+                        int sizeOfWAVEFORMATEX = 18;
+
+                        // make sure the buffer is big enough to store a short
+                        if (len < position + 8 + sizeOfWAVEFORMATEX - 1)
+                        {
+                            throw new InvalidOperationException(SR.SoundAPIInvalidWaveHeader);
+                        }
+
+                        short cbSize = BytesToInt16(data[position + 8 + sizeOfWAVEFORMATEX - 1],
+                                                    data[position + 8 + sizeOfWAVEFORMATEX - 2]);
+                        if (cbSize + sizeOfWAVEFORMATEX != chunkSize)
+                        {
+                            throw new InvalidOperationException(SR.SoundAPIInvalidWaveHeader);
+                        }
+                    }
+
+                    // make sure the buffer passed in is big enough to store a short
+                    if (len < position + 9)
+                    {
+                        throw new InvalidOperationException(SR.SoundAPIInvalidWaveHeader);
+                    }
+                    wFormatTag = BytesToInt16(data[position + 9], data[position + 8]);
+
+                    position += chunkSize + 8;
+                }
+                else
+                {
+                    position += 8 + BytesToInt(data[position + 7], data[position + 6], data[position + 5], data[position + 4]);
+                }
+            }
+
+            if (!fmtChunkFound)
+            {
+                throw new InvalidOperationException(SR.SoundAPIInvalidWaveHeader);
+            }
+
+            if (wFormatTag != Interop.WinMM.WAVE_FORMAT_PCM &&
+                wFormatTag != Interop.WinMM.WAVE_FORMAT_ADPCM &&
+                wFormatTag != Interop.WinMM.WAVE_FORMAT_IEEE_FLOAT)
+            {
+                throw new InvalidOperationException(SR.SoundAPIFormatNotSupported);
+            }
+        }
+
+        private static short BytesToInt16(byte ch0, byte ch1)
+        {
+            int res;
+            res = ch1;
+            res |= ch0 << 8;
+            return (short)res;
+        }
+
+        private static int BytesToInt(byte ch0, byte ch1, byte ch2, byte ch3)
+        {
+            return mmioFOURCC((char)ch3, (char)ch2, (char)ch1, (char)ch0);
+        }
+
+        private static int mmioFOURCC(char ch0, char ch1, char ch2, char ch3)
+        {
+            int result = 0;
+            result |= ch0;
+            result |= ch1 << 8;
+            result |= ch2 << 16;
+            result |= ch3 << 24;
+            return result;
+        }
+
+        void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
+        {
+            throw new PlatformNotSupportedException();
+        }
+    }
+}
diff --git a/src/libraries/System.Windows.Extensions/src/System/Media/SystemSound.cs b/src/libraries/System.Windows.Extensions/src/System/Media/SystemSound.cs
new file mode 100644 (file)
index 0000000..0b5fb84
--- /dev/null
@@ -0,0 +1,21 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Media
+{
+    public class SystemSound
+    {
+        private readonly int _soundType;
+
+        internal SystemSound(int soundType)
+        {
+            _soundType = soundType;
+        }
+
+        public void Play()
+        {
+            Interop.User32.MessageBeep(_soundType);
+        }
+    }
+}
diff --git a/src/libraries/System.Windows.Extensions/src/System/Media/SystemSounds.cs b/src/libraries/System.Windows.Extensions/src/System/Media/SystemSounds.cs
new file mode 100644 (file)
index 0000000..5fcda3c
--- /dev/null
@@ -0,0 +1,40 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Media
+{
+    public static class SystemSounds
+    {
+        private static volatile SystemSound s_asterisk;
+        private static volatile SystemSound s_beep;
+        private static volatile SystemSound s_exclamation;
+        private static volatile SystemSound s_hand;
+        private static volatile SystemSound s_question;
+
+        public static SystemSound Asterisk
+        {
+            get => s_asterisk ?? (s_asterisk = new SystemSound(Interop.User32.MB_ICONASTERISK));
+        }
+        
+        public static SystemSound Beep
+        {
+            get => s_beep ?? (s_beep = new SystemSound(Interop.User32.MB_OK));
+        }
+
+        public static SystemSound Exclamation
+        {
+            get => s_exclamation ?? (s_exclamation = new SystemSound(Interop.User32.MB_ICONEXCLAMATION));
+        }
+        
+        public static SystemSound Hand
+        {
+            get => s_hand ?? (s_hand = new SystemSound(Interop.User32.MB_ICONHAND));
+        }
+
+        public static SystemSound Question
+        {
+            get => s_question ?? (s_question = new SystemSound(Interop.User32.MB_ICONQUESTION));
+        }
+    }
+}
index e492283..6e9dff8 100644 (file)
     <Compile Include="System\Drawing\ImageConverterTests.cs" />
     <Compile Include="System\Drawing\ImageFormatConverterTests.cs" />
     <Compile Include="System\Drawing\Printing\MarginsConverterTests.cs" />
+    <Compile Include="System\Media\SoundPlayerTests.cs" />
+    <Compile Include="System\Media\SystemSoundTests.cs" />
+    <Compile Include="System\Media\SystemSoundsTests.cs" />
   </ItemGroup>
   <ItemGroup>
     <SupplementalTestData Include="$(PackagesDir)system.componentmodel.typeconverter.testdata\$(TestDataPackageVersion)\content\**\*.*">
       <Link>%(RecursiveDir)%(Filename)%(Extension)</Link>
     </SupplementalTestData>
+    <SupplementalTestData Include="$(PackagesDir)system.windows.extensions.testdata\$(TestDataPackageVersion)\content\**\*.*">
+      <Link>%(RecursiveDir)%(Filename)%(Extension)</Link>
+    </SupplementalTestData>
   </ItemGroup>
-</Project>
\ No newline at end of file
+</Project>
diff --git a/src/libraries/System.Windows.Extensions/tests/System/Media/SoundPlayerTests.cs b/src/libraries/System.Windows.Extensions/tests/System/Media/SoundPlayerTests.cs
new file mode 100644 (file)
index 0000000..0efb92a
--- /dev/null
@@ -0,0 +1,398 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using System.IO;
+using System.Runtime.Serialization.Formatters.Binary;
+using Xunit;
+
+namespace System.Media.Test
+{
+    public class SoundPlayerTests
+    {
+        [Fact]
+        public void Ctor_Default()
+        {
+            var player = new SoundPlayer();
+            Assert.Null(player.Container);
+            Assert.False(player.IsLoadCompleted);
+            Assert.Equal(10000, player.LoadTimeout);
+            Assert.Null(player.Site);
+            Assert.Empty(player.SoundLocation);
+            Assert.Null(player.Stream);
+            Assert.Null(player.Tag);
+        }
+
+        public static IEnumerable<object[]> Stream_TestData()
+        {
+            yield return new object[] { null };
+            yield return new object[] { new MemoryStream() };
+        }
+
+        [Theory]
+        [MemberData(nameof(Stream_TestData))]
+        public void Ctor_Stream(Stream stream)
+        {
+            var player = new SoundPlayer(stream);
+            Assert.Null(player.Container);
+            Assert.False(player.IsLoadCompleted);
+            Assert.Equal(10000, player.LoadTimeout);
+            Assert.Null(player.Site);
+            Assert.Empty(player.SoundLocation);
+            Assert.Same(stream, player.Stream);
+            Assert.Null(player.Tag);
+        }
+
+        [Theory]
+        [InlineData("http://google.com")]
+        [InlineData("invalid")]
+        [InlineData("/file")]
+        public void Ctor_String(string soundLocation)
+        {
+            var player = new SoundPlayer(soundLocation);
+            Assert.Null(player.Container);
+            Assert.False(player.IsLoadCompleted);
+            Assert.Equal(10000, player.LoadTimeout);
+            Assert.Null(player.Site);
+            Assert.Equal(soundLocation ?? "", player.SoundLocation);
+            Assert.Null(player.Stream);
+            Assert.Null(player.Tag);
+        }
+
+        [Theory]
+        [InlineData(null)]
+        [InlineData("")]
+        public void Ctor_NullOrEmptyString_ThrowsArgumentException(string soundLocation)
+        {
+            AssertExtensions.Throws<ArgumentException>("path", null, () => new SoundPlayer(soundLocation));
+        }
+
+        public static IEnumerable<object[]> Play_String_TestData()
+        {
+            yield return new object[] { "adpcm.wav" };
+            yield return new object[] { "pcm.wav" };
+        }
+
+        public static IEnumerable<object[]> Play_InvalidString_TestData()
+        {
+            yield return new object[] { "ccitt.wav" };
+            yield return new object[] { "ima.wav" };
+        }
+
+        [Theory]
+        [MemberData(nameof(Play_String_TestData))]
+        [OuterLoop]
+        public void Load_SourceLocation_Success(string sourceLocation)
+        {
+            var soundPlayer = new SoundPlayer(sourceLocation);
+            soundPlayer.Load();
+
+            // Load again.
+            soundPlayer.Load();
+
+            // Play.
+            soundPlayer.Play();
+        }
+
+        [Theory]
+        [MemberData(nameof(Play_String_TestData))]
+        [OuterLoop]
+        public void Load_Stream_Success(string sourceLocation)
+        {
+            using (FileStream stream = File.OpenRead(sourceLocation.Replace("file://", "")))
+            {
+                var soundPlayer = new SoundPlayer(stream);
+                soundPlayer.Load();
+
+                // Load again.
+                soundPlayer.Load();
+
+                // Play.
+                soundPlayer.Play();
+            }
+        }
+
+        [Fact]
+        public void Load_NoSuchFile_ThrowsFileNotFoundException()
+        {
+            var soundPlayer = new SoundPlayer("noSuchFile");
+            Assert.Throws<FileNotFoundException>(() => soundPlayer.Load());
+        }
+
+        [Fact]
+        public void Load_NullStream_ThrowsNullReferenceException()
+        {
+            var player = new SoundPlayer();
+            Assert.Throws<NullReferenceException>(() => player.Load());
+
+            player = new SoundPlayer((Stream)null);
+            Assert.Throws<NullReferenceException>(() => player.Load());
+        }
+
+        [Theory]
+        [MemberData(nameof(Play_InvalidString_TestData))]
+        public void Load_InvalidSourceLocation_Success(string sourceLocation)
+        {
+            var soundPlayer = new SoundPlayer(sourceLocation);
+            soundPlayer.Load();
+        }
+
+        [Theory]
+        [MemberData(nameof(Play_InvalidString_TestData))]
+        public void Load_InvalidStream_Success(string sourceLocation)
+        {
+            using (FileStream stream = File.OpenRead(sourceLocation))
+            {
+                var soundPlayer = new SoundPlayer(stream);
+                soundPlayer.Load();
+            }
+        }
+
+        [Theory]
+        [MemberData(nameof(Play_String_TestData))]
+        [OuterLoop]
+        public void Play_SourceLocation_Success(string sourceLocation)
+        {
+            var soundPlayer = new SoundPlayer(sourceLocation);
+            soundPlayer.Play();
+
+            // Play again.
+            soundPlayer.Play();
+
+            // Load.
+            soundPlayer.Load();
+        }
+
+        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindowsNanoServer))]
+        public void Play_NoSuchFile_ThrowsFileNotFoundException()
+        {
+            var soundPlayer = new SoundPlayer("noSuchFile");
+            Assert.Throws<FileNotFoundException>(() => soundPlayer.Play());
+        }
+
+        [Theory]
+        [MemberData(nameof(Play_String_TestData))]
+        [OuterLoop]
+        public void Play_Stream_Success(string sourceLocation)
+        {
+            using (FileStream stream = File.OpenRead(sourceLocation.Replace("file://", "")))
+            {
+                var soundPlayer = new SoundPlayer(stream);
+                soundPlayer.Play();
+
+                // Play again.
+                soundPlayer.Play();
+
+                // Load.
+                soundPlayer.Load();
+            }
+        }
+
+        [Fact]
+        [OuterLoop]
+        public void Play_NullStream_Success()
+        {
+            var player = new SoundPlayer();
+            player.Play();
+
+            player = new SoundPlayer((Stream)null);
+            player.Play();
+        }
+
+        [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindowsNanoServer))]
+        [MemberData(nameof(Play_InvalidString_TestData))]
+        [InlineData("http://google.com")]
+        public void Play_InvalidFile_ThrowsInvalidOperationException(string sourceLocation)
+        {
+            var soundPlayer = new SoundPlayer(sourceLocation);
+            Assert.Throws<InvalidOperationException>(() => soundPlayer.Play());
+        }
+
+        [Theory]
+        [MemberData(nameof(Play_InvalidString_TestData))]
+        public void Play_InvalidStream_ThrowsInvalidOperationException(string sourceLocation)
+        {
+            using (FileStream stream = File.OpenRead(sourceLocation.Replace("file://", "")))
+            {
+                var soundPlayer = new SoundPlayer(stream);
+                Assert.Throws<InvalidOperationException>(() => soundPlayer.Play());
+            }
+        }
+
+        [Fact]
+        [OuterLoop]
+        public void PlayLooping_NullStream_Success()
+        {
+            var player = new SoundPlayer();
+            player.PlayLooping();
+
+            player = new SoundPlayer((Stream)null);
+            player.PlayLooping();
+        }
+
+        [Fact]
+        [OuterLoop]
+        public void PlaySync_NullStream_Success()
+        {
+            var player = new SoundPlayer();
+            player.PlaySync();
+
+            player = new SoundPlayer((Stream)null);
+            player.PlaySync();
+        }
+
+        [Theory]
+        [InlineData(0)]
+        [InlineData(1)]
+        public void LoadTimeout_SetValid_GetReturnsExpected(int value)
+        {
+            var player = new SoundPlayer { LoadTimeout = value };
+            Assert.Equal(value, player.LoadTimeout);
+        }
+
+        [Fact]
+        public void LoadTimeout_SetNegative_ThrowsArgumentOutOfRangeException()
+        {
+            var player = new SoundPlayer();
+            AssertExtensions.Throws<ArgumentOutOfRangeException>("LoadTimeout", () => player.LoadTimeout = -1);
+        }
+
+        [Theory]
+        [InlineData("http://google.com")]
+        [InlineData("invalid")]
+        [InlineData("/file")]
+        [InlineData("file:///name")]
+        public void SoundLocation_SetValid_Success(string soundLocation)
+        {
+            var player = new SoundPlayer() { SoundLocation = soundLocation };
+            Assert.Equal(soundLocation, player.SoundLocation);
+
+            bool calledHandler = false;
+            player.SoundLocationChanged += (args, sender) => calledHandler = true;
+
+            // Set the same.
+            player.SoundLocation = soundLocation;
+            Assert.Equal(soundLocation, player.SoundLocation);
+            Assert.False(calledHandler);
+
+            // Set different.
+            player.SoundLocation = soundLocation + "a";
+            Assert.Equal(soundLocation + "a", player.SoundLocation);
+            Assert.True(calledHandler);
+
+            player = new SoundPlayer("location") { SoundLocation = soundLocation };
+            Assert.Equal(soundLocation, player.SoundLocation);
+
+            using (var stream = new MemoryStream())
+            {
+                player = new SoundPlayer(stream) { SoundLocation = soundLocation };
+                Assert.Equal(soundLocation, player.SoundLocation);
+            }
+        }
+
+        [Theory]
+        [InlineData(null)]
+        [InlineData("")]
+        public void SoundLocation_SetNullOrEmpty_ThrowsArgumentException(string soundLocation)
+        {
+            var player = new SoundPlayer() { SoundLocation = soundLocation };
+            Assert.Equal("", player.SoundLocation);
+
+            player = new SoundPlayer("location");
+            AssertExtensions.Throws<ArgumentException>("path", null, () => player.SoundLocation = soundLocation);
+
+            using (var stream = new MemoryStream())
+            {
+                player = new SoundPlayer(stream) { SoundLocation = soundLocation };
+                Assert.Equal("", player.SoundLocation);
+            }
+        }
+
+        [Theory]
+        [MemberData(nameof(Stream_TestData))]
+        public void Stream_SetValid_Success(Stream stream)
+        {
+            var player = new SoundPlayer() { Stream = stream };
+            Assert.Equal(stream, player.Stream);
+
+            bool calledHandler = false;
+            player.StreamChanged += (args, sender) => calledHandler = true;
+
+            // Set the same.
+            player.Stream = stream;
+            Assert.Equal(stream, player.Stream);
+            Assert.False(calledHandler);
+
+            // Set different.
+            using (var other = new MemoryStream())
+            {
+                player.Stream = other;
+                Assert.Equal(other, player.Stream);
+                Assert.True(calledHandler);
+            }
+
+            player = new SoundPlayer("location") { Stream = stream };
+            Assert.Equal(stream, player.Stream);
+
+            using (var other = new MemoryStream())
+            {
+                player = new SoundPlayer(other) { Stream = stream };
+                Assert.Equal(stream, player.Stream);
+            }
+        }
+
+        [Theory]
+        [InlineData(null)]
+        [InlineData("tag")]
+        public void Tag_Set_GetReturnsExpected(object value)
+        {
+            var player = new SoundPlayer { Tag = value };
+            Assert.Equal(value, player.Tag);
+        }
+
+        [Fact]
+        public void LoadCompleted_AddRemove_Success()
+        {
+            bool calledHandler = false;
+            void handler(object args, EventArgs sender) => calledHandler = true;
+
+            var player = new SoundPlayer();
+            player.LoadCompleted += handler;
+            player.LoadCompleted -= handler;
+
+            Assert.False(calledHandler);
+        }
+
+        [Fact]
+        public void SoundLocationChanged_AddRemove_Success()
+        {
+            bool calledHandler = false;
+            void handler(object args, EventArgs sender) => calledHandler = true;
+
+            var player = new SoundPlayer();
+            player.SoundLocationChanged += handler;
+            player.SoundLocationChanged -= handler;
+
+            player.SoundLocation = "location";
+            Assert.False(calledHandler);
+        }
+
+        [Fact]
+        public void StreamChanged_AddRemove_Success()
+        {
+            bool calledHandler = false;
+            void handler(object args, EventArgs sender) => calledHandler = true;
+
+            var player = new SoundPlayer();
+            player.StreamChanged += handler;
+            player.StreamChanged -= handler;
+
+            using (var stream = new MemoryStream())
+            {
+                player.Stream = stream;
+                Assert.False(calledHandler);
+            }
+        }
+    }
+}
diff --git a/src/libraries/System.Windows.Extensions/tests/System/Media/SystemSoundTests.cs b/src/libraries/System.Windows.Extensions/tests/System/Media/SystemSoundTests.cs
new file mode 100644 (file)
index 0000000..8535f10
--- /dev/null
@@ -0,0 +1,29 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using Xunit;
+
+namespace System.Media.Test
+{
+    [OuterLoop]
+    public class SystemSoundTests
+    {
+        public static IEnumerable<object[]> Play_TestData()
+        {
+            yield return new object[] { SystemSounds.Asterisk };
+            yield return new object[] { SystemSounds.Beep };
+            yield return new object[] { SystemSounds.Exclamation };
+            yield return new object[] { SystemSounds.Hand };
+            yield return new object[] { SystemSounds.Question };
+        }
+
+        [Theory]
+        [MemberData(nameof(Play_TestData))]
+        public void Question_Play_Success(SystemSound sound)
+        {
+            sound.Play();
+        }
+    }
+}
diff --git a/src/libraries/System.Windows.Extensions/tests/System/Media/SystemSoundsTests.cs b/src/libraries/System.Windows.Extensions/tests/System/Media/SystemSoundsTests.cs
new file mode 100644 (file)
index 0000000..f65ea44
--- /dev/null
@@ -0,0 +1,51 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Xunit;
+
+namespace System.Media.Test
+{
+    public class SystemSoundsTests
+    {
+        [Fact]
+        public void Asterisk_Get_ReturnsExpected()
+        {
+            SystemSound sound = SystemSounds.Asterisk;
+            Assert.NotNull(sound);
+            Assert.Same(sound, SystemSounds.Asterisk);
+        }
+
+        [Fact]
+        public void Beep_Get_ReturnsExpected()
+        {
+            SystemSound sound = SystemSounds.Beep;
+            Assert.NotNull(sound);
+            Assert.Same(sound, SystemSounds.Beep);
+        }
+
+        [Fact]
+        public void Exclamation_Get_ReturnsExpected()
+        {
+            SystemSound sound = SystemSounds.Exclamation;
+            Assert.NotNull(sound);
+            Assert.Same(sound, SystemSounds.Exclamation);
+        }
+
+        [Fact]
+        public void Hand_Get_ReturnsExpected()
+        {
+            SystemSound sound = SystemSounds.Hand;
+            Assert.NotNull(sound);
+            Assert.Same(sound, SystemSounds.Hand);
+        }
+
+        [Fact]
+        public void Question_Get_ReturnsExpected()
+        {
+            SystemSound sound = SystemSounds.Question;
+            Assert.NotNull(sound);
+            Assert.Same(sound, SystemSounds.Question);
+        }
+    }
+}
index f0724eb..c79e5fe 100644 (file)
@@ -49,6 +49,7 @@
     <PackageReference Include="System.ComponentModel.TypeConverter.TestData" Version="1.0.0" />
     <PackageReference Include="System.Drawing.Common.TestData" Version="1.0.7" />
     <PackageReference Include="System.Text.RegularExpressions.TestData" Version="1.0.2" />
+    <PackageReference Include="System.Windows.Extensions.TestData" Version="1.0.0" />
 
     <PackageToInclude Include="xunit.abstractions" />
     <PackageToInclude Include="xunit.assert" />