Updates referencesource to .NET 4.7
[mono.git] / mcs / class / referencesource / System / sys / system / Media / SoundPlayer.cs
1 namespace System.Media {
2     using System;
3     using System.IO;
4     using System.ComponentModel;
5     using System.Runtime.InteropServices;
6     using System.Runtime.Serialization;
7     using System.Runtime.Versioning;
8     using System.Diagnostics;
9     using System.Threading;
10     using System.Net;
11     using System.Globalization;
12     using System.Security.Permissions;
13     using System.Security;
14     using System.Diagnostics.CodeAnalysis;
15
16     /// <include file='doc\SoundPlayer.uex' path='docs/doc[@for="SoundPlayer"]/*' />
17     [
18     Serializable,
19     ToolboxItem(false),
20     SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes"), // This is the first class added to System.Media namespace.
21     SuppressMessage("Microsoft.Usage", "CA2240:ImplementISerializableCorrectly"), // vsw 427356
22     HostProtection(UI = true)
23     ]
24     public class SoundPlayer : Component, ISerializable {
25
26         const int blockSize = 1024;
27         const int defaultLoadTimeout = 10000;// 10 secs
28         private Uri uri = null;
29         private string soundLocation = String.Empty;
30         private int loadTimeout = defaultLoadTimeout;
31
32         private object tag = null;
33
34         // used to lock all synchronous calls to the SoundPlayer object
35         private ManualResetEvent semaphore = new ManualResetEvent(true);
36
37         // the worker copyThread
38         // we start the worker copyThread ONLY from entry points in the SoundPlayer API
39         // we also set the tread to null only from the entry points in the SoundPlayer API
40         private Thread copyThread = null;
41
42         // local buffer information
43         int currentPos = 0;
44         private Stream stream = null;
45         private bool isLoadCompleted = false;
46         private Exception lastLoadException = null;
47         private bool doesLoadAppearSynchronous = false;
48         private byte[] streamData = null;
49         private AsyncOperation asyncOperation = null;
50         private readonly SendOrPostCallback loadAsyncOperationCompleted;
51
52         // event
53         private static readonly object EventLoadCompleted = new object();
54         private static readonly object EventSoundLocationChanged = new object();
55         private static readonly object EventStreamChanged = new object();
56
57         /// <include file='doc\SoundPlayer.uex' path='docs/doc[@for="SoundPlayer.SoundPlayer"]/*' />
58         public SoundPlayer() {
59             loadAsyncOperationCompleted = 
60                 new SendOrPostCallback(LoadAsyncOperationCompleted);
61         }
62
63         /// <include file='doc\SoundPlayer.uex' path='docs/doc[@for="SoundPlayer.SoundPlayer1"]/*' />
64         public SoundPlayer(string soundLocation) : this() {
65             if(soundLocation == null) {
66                 soundLocation = String.Empty;
67             }
68             SetupSoundLocation(soundLocation);
69         }
70
71         /// <include file='doc\SoundPlayer.uex' path='docs/doc[@for="SoundPlayer.SoundPlayer2"]/*' />
72         public SoundPlayer(Stream stream) : this() {
73             this.stream = stream;
74         }
75
76         /**
77          * Constructor used in deserialization
78          */
79         /// <include file='doc\SoundPlayer.uex' path='docs/doc[@for="SoundPlayer.SoundPlayer4"]/*' />
80         [
81             SuppressMessage("Microsoft.Performance", "CA1808:AvoidCallsThatBoxValueTypes"), // SerializationInfo stores LoadTimeout as an object.
82             SuppressMessage("Microsoft.Performance", "CA1801:AvoidUnusedParameters")        // Serialization constructor needs a Context parameter.
83         ]
84         protected SoundPlayer(SerializationInfo serializationInfo, StreamingContext context) {
85             foreach(SerializationEntry entry in serializationInfo) {
86                 switch (entry.Name) {
87                     case "SoundLocation" :
88                         SetupSoundLocation((string) entry.Value);
89                         break;
90                     case "Stream" :
91                         stream = (Stream) entry.Value;
92                         // when we deserialize a stream we have to reset its seek position
93                         // vsWhidbey 180361
94                         if (stream.CanSeek) {
95                             stream.Seek(0, SeekOrigin.Begin);
96                         }
97                         break;
98                     case "LoadTimeout" :
99                         this.LoadTimeout = (int) entry.Value;
100                         break;
101                 }
102             }
103         }
104
105         /// <include file='doc\SoundPlayer.uex' path='docs/doc[@for="SoundPlayer.LoadTimeout"]/*' />
106         public int LoadTimeout {
107             get {
108                 return loadTimeout;
109             }
110             set {
111                 if (value < 0) {
112                     throw new ArgumentOutOfRangeException("LoadTimeout", value, SR.GetString(SR.SoundAPILoadTimeout));
113                 }
114
115                 loadTimeout = value;
116             }
117         }
118
119         /// <include file='doc\SoundPlayer.uex' path='docs/doc[@for="SoundPlayer.Path"]/*' />
120         public string SoundLocation {
121             get {
122                 if (uri != null && uri.IsFile) {
123                     FileIOPermission fiop = new FileIOPermission(PermissionState.None);
124                     fiop.AllFiles = FileIOPermissionAccess.PathDiscovery;
125                     fiop.Demand();
126                 }
127                 return soundLocation;
128             }
129             set {
130                 if (value == null)
131                     value = String.Empty;
132
133                 if (soundLocation.Equals(value))
134                     return;
135
136                 SetupSoundLocation(value);
137
138                 OnSoundLocationChanged(EventArgs.Empty);
139             }
140         }
141
142         /// <include file='doc\SoundPlayer.uex' path='docs/doc[@for="SoundPlayer.Stream"]/*' />
143         public Stream Stream {
144             get {
145                 // if the path is set, we should return null
146                 // Path and Stream are mutually exclusive
147                 if (uri != null)
148                     return null;
149                 return this.stream;
150             }
151             set {
152                 if (stream == value)
153                     return;
154
155                 SetupStream(value);
156
157                 OnStreamChanged(EventArgs.Empty);
158             }
159         }
160
161         /// <include file='doc\SoundPlayer.uex' path='docs/doc[@for="SoundPlayer.IsLoadCompleted"]/*' />
162         public bool IsLoadCompleted {
163             get {
164                 return isLoadCompleted;
165             }
166         }
167
168         /// <include file='doc\SoundPlayer.uex' path='docs/doc[@for="SoundPlayer.Tag"]/*' />
169         public object Tag {
170             get {
171                 return tag;
172             }
173             set {
174                 tag = value;
175             }
176         }
177
178         /// <include file='doc\SoundPlayer.uex' path='docs/doc[@for="SoundPlayer.LoadAsync"]/*' />
179         public void LoadAsync() {
180             // if we have a file there is nothing to load - we just pass the file to the PlaySound function
181             // if we have a stream, then we start loading the stream async
182             //
183             if (uri!= null && uri.IsFile){
184                 Debug.Assert(stream == null, "we can't have a stream and a path at the same time");
185                 isLoadCompleted = true;
186
187                 FileInfo fi = new FileInfo(uri.LocalPath);
188                 if (!fi.Exists) {
189                     throw new FileNotFoundException(SR.GetString(SR.SoundAPIFileDoesNotExist), this.soundLocation);
190                 }
191
192                 OnLoadCompleted(new AsyncCompletedEventArgs(null, false, null));
193                 return;
194             }
195
196             // if we are actively loading, keep it running
197             if (copyThread != null && copyThread.ThreadState == System.Threading.ThreadState.Running) {
198                 return;
199             }
200             isLoadCompleted = false;
201             streamData = null;
202             currentPos = 0;
203
204             asyncOperation = AsyncOperationManager.CreateOperation(null);
205             
206             LoadStream(false);
207         }
208
209         private void LoadAsyncOperationCompleted(object arg)
210         {
211             OnLoadCompleted((AsyncCompletedEventArgs)arg);
212         }
213
214         // called for loading a stream synchronously
215         // called either when the user is setting the path/stream and we are loading
216         // or when loading took more time than the time out
217         private void CleanupStreamData() {
218             this.currentPos = 0;
219             this.streamData = null;
220             this.isLoadCompleted = false;
221             this.lastLoadException = null;
222             this.doesLoadAppearSynchronous = false;
223             this.copyThread = null;
224             this.semaphore.Set();
225         }
226
227         /// <include file='doc\SoundPlayer.uex' path='docs/doc[@for="SoundPlayer.Load"]/*' />
228         public void Load() {
229             // if we have a file there is nothing to load - we just pass the file to the PlaySound function
230             // if we have a stream, then we start loading the stream sync
231             //
232             if (uri != null && uri.IsFile){
233                 Debug.Assert(stream == null, "we can't have a stream and a path at the same time");
234                 FileInfo fi = new FileInfo(uri.LocalPath);
235                 if (!fi.Exists) {
236                     throw new FileNotFoundException(SR.GetString(SR.SoundAPIFileDoesNotExist), this.soundLocation);
237                 }
238                 isLoadCompleted = true;
239                 OnLoadCompleted(new AsyncCompletedEventArgs(null, false, null));
240                 return;
241             }
242
243             LoadSync();
244         }
245
246         [SuppressMessage("Microsoft.Security", "CA2103:ReviewImperativeSecurity")] // FileIOPermission based on URI path, but path isn't gonna change during scope of Demand
247         private void LoadAndPlay(int flags) {
248             // bug 16794: when the user does not specify a sound location nor a stream, play Beep
249             if (String.IsNullOrEmpty(soundLocation) && stream == null) {
250                 SystemSounds.Beep.Play();
251                 return;
252             }
253
254             if (uri != null && uri.IsFile) {
255                 // VSW 580992: With more than one thread, someone could call SoundPlayer::set_Location
256                 // between the time LoadAndPlay demands FileIO and the time it calls PlaySound under elevation.
257                 // 
258                 // Another scenario is someone calling SoundPlayer::set_Location between the time
259                 // LoadAndPlay validates the sound file and the time it calls PlaySound.
260                 // The SoundPlayer will end up playing an un-validated sound file.
261                 // The solution is to store the uri.LocalPath on a local variable
262                 string localPath = uri.LocalPath;
263
264                 // request permission to read the file:
265                 // pass the full path to the FileIOPermission
266                 FileIOPermission perm = new FileIOPermission(FileIOPermissionAccess.Read, localPath);
267                 perm.Demand();
268
269                 // play the path
270                 isLoadCompleted = true;
271                 System.Media.SoundPlayer.IntSecurity.SafeSubWindows.Demand();
272
273                 System.ComponentModel.IntSecurity.UnmanagedCode.Assert();
274                 // ValidateSoundFile calls into the MMIO API so we need UnmanagedCode permissions to do that.
275                 // And of course we need UnmanagedCode permissions to all Win32::PlaySound method.
276                 try {
277                     // don't use uri.AbsolutePath because that gives problems when there are whitespaces in file names
278                     ValidateSoundFile(localPath);
279                     UnsafeNativeMethods.PlaySound(localPath, IntPtr.Zero, NativeMethods.SND_NODEFAULT | flags);
280                 } finally {
281                     System.Security.CodeAccessPermission.RevertAssert();
282                 }
283             } else {
284                 LoadSync();
285                 ValidateSoundData(streamData);
286                 System.Media.SoundPlayer.IntSecurity.SafeSubWindows.Demand();
287
288                 System.ComponentModel.IntSecurity.UnmanagedCode.Assert();
289                 try {
290                     UnsafeNativeMethods.PlaySound(streamData, IntPtr.Zero, NativeMethods.SND_MEMORY | NativeMethods.SND_NODEFAULT | flags);
291                 } finally {
292                     System.Security.CodeAccessPermission.RevertAssert();
293                 }
294             }
295         }
296
297         [SuppressMessage("Microsoft.Security", "CA2103:ReviewImperativeSecurity")] // WebPermission based on URI path, but path isn't gonna change during scope of Demand
298         private void LoadSync() {
299             
300             Debug.Assert((uri == null || !uri.IsFile), "we only load streams");
301
302             // first make sure that any possible download ended
303             if (!semaphore.WaitOne(LoadTimeout, false)) {
304                 if (copyThread != null)
305                     copyThread.Abort();
306                 CleanupStreamData();
307                 throw new TimeoutException(SR.GetString(SR.SoundAPILoadTimedOut));
308             }
309
310             // if we have data, then we are done
311             if (streamData != null)
312                 return;
313
314             // setup the http stream
315             if (uri != null && !uri.IsFile && stream == null) {
316                 WebPermission webPerm = new WebPermission(NetworkAccess.Connect, uri.AbsolutePath);
317                 webPerm.Demand();
318                 WebRequest webRequest = WebRequest.Create(uri);
319                 webRequest.Timeout = LoadTimeout;
320
321                 WebResponse webResponse;
322                 webResponse = webRequest.GetResponse();
323
324                 // now get the stream
325                 stream = webResponse.GetResponseStream();
326             }
327
328             if (stream.CanSeek) {
329                 // if we can get data synchronously, then get it
330                 LoadStream(true);
331             } else {
332                 // the data can't be loaded synchronously
333                 // load it async, then wait for it to finish
334                 doesLoadAppearSynchronous = true; // to avoid OnFailed call.
335                 LoadStream(false);
336
337                 if(!semaphore.WaitOne(LoadTimeout, false)) {
338                     if (copyThread != null)
339                         copyThread.Abort();
340                     CleanupStreamData();
341                     throw new TimeoutException(SR.GetString(SR.SoundAPILoadTimedOut));
342                 }
343
344                 doesLoadAppearSynchronous = false;
345                 
346                 if (lastLoadException != null)
347                 {
348                     throw lastLoadException;
349                 }
350             }
351
352             // we don't need the worker copyThread anymore
353             this.copyThread = null;
354         }
355
356         private void LoadStream(bool loadSync) {
357             if (loadSync && stream.CanSeek) {
358                 int streamLen = (int) stream.Length;
359                 currentPos = 0;
360                 streamData = new byte[streamLen];
361                 stream.Read(streamData, 0, streamLen);
362                 isLoadCompleted = true;
363                 OnLoadCompleted(new AsyncCompletedEventArgs(null, false, null));
364             } else {
365                 // lock any synchronous calls on the Sound object
366                 semaphore.Reset();
367                 // start loading
368                 copyThread = new Thread(new ThreadStart(this.WorkerThread));
369                 copyThread.Start();
370             }
371         }
372
373         /// <include file='doc\SoundPlayer.uex' path='docs/doc[@for="SoundPlayer.Play"]/*' />
374         public void Play() {
375             LoadAndPlay(NativeMethods.SND_ASYNC);
376         }
377
378         /// <include file='doc\SoundPlayer.uex' path='docs/doc[@for="SoundPlayer.PlaySync"]/*' />
379         public void PlaySync() {
380             LoadAndPlay(NativeMethods.SND_SYNC);
381         }
382
383         /// <include file='doc\SoundPlayer.uex' path='docs/doc[@for="SoundPlayer.PlayLooping"]/*' />
384         public void PlayLooping() {
385             LoadAndPlay(NativeMethods.SND_LOOP | NativeMethods.SND_ASYNC);
386         }
387
388         private static Uri ResolveUri(string partialUri) {
389             Uri result = null;
390             try {
391                 result = new Uri(partialUri);
392             } catch (UriFormatException) {
393                 // eat URI parse exceptions
394             }
395
396             if (result == null) {
397                 // try relative to appbase
398                 try {
399                     result = new Uri(Path.GetFullPath(partialUri));
400                 } catch (UriFormatException) {
401                     // eat URI parse exceptions
402                 }
403             }
404             return result;
405         }
406
407         private void SetupSoundLocation(string soundLocation) {
408             // if we are loading a file, stop it right now
409             //
410             if (copyThread != null) {
411                 copyThread.Abort();
412                 CleanupStreamData();
413             }
414
415             uri = ResolveUri(soundLocation);
416
417             this.soundLocation = soundLocation;
418             stream = null;
419             if (uri == null) {
420                 if (!String.IsNullOrEmpty(soundLocation))
421                     throw new UriFormatException(SR.GetString(SR.SoundAPIBadSoundLocation));
422             } else {
423                 if (!uri.IsFile) {
424                     // we are referencing a web resource ...
425                     //
426
427                     // we treat it as a stream...
428                     //
429                     streamData = null;
430                     currentPos = 0;
431                     isLoadCompleted = false;
432                 }
433             }
434         }
435
436         private void SetupStream(Stream stream) {
437             if (this.copyThread != null) {
438                 copyThread.Abort();
439                 CleanupStreamData();
440             }
441
442             this.stream = stream;
443             this.soundLocation = String.Empty;
444             this.streamData = null;
445             this.currentPos = 0;
446             isLoadCompleted = false;
447             if (stream != null) {
448                 uri = null;
449             }
450         }
451
452         /// <include file='doc\SoundPlayer.uex' path='docs/doc[@for="SoundPlayer.Stop"]/*' />
453         [ResourceExposure(ResourceScope.None)]
454         [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
455         public void Stop() {
456             IntSecurity.SafeSubWindows.Demand();
457             UnsafeNativeMethods.PlaySound((byte[]) null, IntPtr.Zero, NativeMethods.SND_PURGE);
458         }
459
460         /// <include file='doc\SoundPlayer.uex' path='docs/doc[@for="SoundPlayer.LoadCompleted"]/*' />
461         public event AsyncCompletedEventHandler LoadCompleted {
462             add {
463                 Events.AddHandler(EventLoadCompleted, value);
464             }
465             remove {
466                 Events.RemoveHandler(EventLoadCompleted, value);
467             }
468         }
469
470         /// <include file='doc\SoundPlayer.uex' path='docs/doc[@for="SoundPlayer.SoundLocationChanged"]/*' />
471         public event EventHandler SoundLocationChanged {
472             add {
473                 Events.AddHandler(EventSoundLocationChanged, value);
474             }
475             remove {
476                 Events.RemoveHandler(EventSoundLocationChanged, value);
477             }
478         }
479
480         /// <include file='doc\SoundPlayer.uex' path='docs/doc[@for="SoundPlayer.StreamChanged"]/*' />
481         public event EventHandler StreamChanged {
482             add {
483                 Events.AddHandler(EventStreamChanged, value);
484             }
485             remove {
486                 Events.RemoveHandler(EventStreamChanged, value);
487             }
488         }
489
490         /// <include file='doc\SoundPlayer.uex' path='docs/doc[@for="SoundPlayer.OnLoadCompleted"]/*' />
491         protected virtual void OnLoadCompleted(AsyncCompletedEventArgs e) {
492             AsyncCompletedEventHandler eh = (AsyncCompletedEventHandler) Events[EventLoadCompleted];
493             if (eh != null)
494             {
495                 eh(this, e);
496             }
497         }
498
499         /// <include file='doc\SoundPlayer.uex' path='docs/doc[@for="SoundPlayer.OnSoundLocationChanged"]/*' />
500         protected virtual void OnSoundLocationChanged(EventArgs e) {
501             EventHandler eh = (EventHandler) Events[EventSoundLocationChanged];
502             if (eh != null)
503             {
504                 eh(this, e);
505             }
506         }
507
508         /// <include file='doc\SoundPlayer.uex' path='docs/doc[@for="SoundPlayer.OnStreamChanged"]/*' />
509         protected virtual void OnStreamChanged(EventArgs e) {
510             EventHandler eh = (EventHandler) Events[EventStreamChanged];
511             if (eh != null)
512             {
513                 eh(this, e);
514             }
515         }
516
517         [
518             SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")   // The set of reasons why WorkerThread should fail is not finite
519         ]
520         private void WorkerThread() {
521             try
522             {
523                 // setup the http stream
524                 if (uri != null && !uri.IsFile && stream == null) {
525                     WebRequest webRequest = WebRequest.Create(uri);
526
527                     WebResponse webResponse = webRequest.GetResponse();
528
529                     stream = webResponse.GetResponseStream();
530                 }
531
532                 this.streamData = new byte[blockSize];
533
534                 int readBytes = stream.Read(streamData, currentPos, blockSize);
535                 int totalBytes = readBytes;
536
537                 while (readBytes > 0) {
538                     currentPos += readBytes;
539                     if (streamData.Length < currentPos + blockSize) {
540                         byte[] newData = new byte[streamData.Length * 2];
541                         Array.Copy(streamData, newData, streamData.Length);
542                         streamData = newData;
543                     }
544                     readBytes = stream.Read(streamData, currentPos, blockSize);
545                     totalBytes += readBytes;
546                 }
547
548                 lastLoadException = null;
549             }
550             catch (Exception exception)
551             {
552                 lastLoadException = exception;
553             }
554
555             if (!doesLoadAppearSynchronous)
556             {
557                 // Post notification back to the UI thread.
558                 asyncOperation.PostOperationCompleted(
559                     loadAsyncOperationCompleted,
560                     new AsyncCompletedEventArgs(lastLoadException, false, null));
561             }
562             isLoadCompleted = true;
563             semaphore.Set();
564         }
565
566         private unsafe void ValidateSoundFile(string fileName) {
567             NativeMethods.MMCKINFO ckRIFF = new NativeMethods.MMCKINFO();
568             NativeMethods.MMCKINFO ck = new NativeMethods.MMCKINFO();
569             NativeMethods.WAVEFORMATEX waveFormat = null;
570             int dw;
571
572             IntPtr hMIO = UnsafeNativeMethods.mmioOpen(fileName, IntPtr.Zero, NativeMethods.MMIO_READ | NativeMethods.MMIO_ALLOCBUF);
573
574             if (hMIO == IntPtr.Zero)
575                 throw new FileNotFoundException(SR.GetString(SR.SoundAPIFileDoesNotExist), this.soundLocation);
576
577             try {
578                 ckRIFF.fccType = mmioFOURCC('W', 'A','V','E');
579                 if (UnsafeNativeMethods.mmioDescend(hMIO, ckRIFF, null, NativeMethods.MMIO_FINDRIFF) != 0)
580                     throw new InvalidOperationException(SR.GetString(SR.SoundAPIInvalidWaveFile, this.soundLocation));
581
582                 while (UnsafeNativeMethods.mmioDescend(hMIO, ck, ckRIFF, 0) == 0) {
583                     if (ck.dwDataOffset + ck.cksize > ckRIFF.dwDataOffset + ckRIFF.cksize)
584                         throw new InvalidOperationException(SR.GetString(SR.SoundAPIInvalidWaveHeader));
585
586                     if (ck.ckID == mmioFOURCC('f','m','t',' ')) {
587                             if (waveFormat == null) {
588                                 dw = ck.cksize;
589                                 if (dw < Marshal.SizeOf(typeof(NativeMethods.WAVEFORMATEX)))
590                                     dw =  Marshal.SizeOf(typeof(NativeMethods.WAVEFORMATEX));
591
592                                 waveFormat = new NativeMethods.WAVEFORMATEX();
593                                 byte[] data = new byte[dw];
594                                 if (UnsafeNativeMethods.mmioRead(hMIO, data, dw) != dw)
595                                     throw new InvalidOperationException(SR.GetString(SR.SoundAPIReadError, this.soundLocation));
596                                 fixed(byte* pdata = data) {
597                                     Marshal.PtrToStructure((IntPtr) pdata, waveFormat);
598                                 }
599                             } else {
600                                 //
601                                 // multiple formats?
602                                 //
603                             }
604                     }
605                     UnsafeNativeMethods.mmioAscend(hMIO, ck, 0);
606                 }
607
608                 if (waveFormat == null)
609                     throw new InvalidOperationException(SR.GetString(SR.SoundAPIInvalidWaveHeader));
610
611                 if (waveFormat.wFormatTag != NativeMethods.WAVE_FORMAT_PCM &&
612                     waveFormat.wFormatTag != NativeMethods.WAVE_FORMAT_ADPCM &&
613                     waveFormat.wFormatTag != NativeMethods.WAVE_FORMAT_IEEE_FLOAT)
614                     throw new InvalidOperationException(SR.GetString(SR.SoundAPIFormatNotSupported));
615
616             } finally {
617                     if (hMIO != IntPtr.Zero)
618                         UnsafeNativeMethods.mmioClose(hMIO, 0);
619             }
620         }
621
622         private static void ValidateSoundData(byte[] data) {
623             int position = 0;
624             Int16 wFormatTag = -1;
625             bool fmtChunkFound = false;
626
627             // the RIFF header should be at least 12 bytes long.
628             if (data.Length < 12)
629                 throw new System.InvalidOperationException(SR.GetString(SR.SoundAPIInvalidWaveHeader));
630
631             // validate the RIFF header
632             if (data[0] != 'R' || data[1] != 'I' || data[2] != 'F' || data[3] != 'F')
633                 throw new System.InvalidOperationException(SR.GetString(SR.SoundAPIInvalidWaveHeader));
634             if (data[8] != 'W' || data[9] != 'A' || data[10] != 'V' || data[11] != 'E')
635                 throw new System.InvalidOperationException(SR.GetString(SR.SoundAPIInvalidWaveHeader));
636
637             // we only care about "fmt " chunk
638             position = 12;
639             int len = data.Length;
640             while (!fmtChunkFound && position < len - 8) {
641                 if (data[position] == (byte)'f' && data[position + 1] == (byte)'m' && data[position + 2] == (byte)'t' && data[position+3] == (byte)' ') {
642                     //
643                     // fmt chunk
644                     //
645                     fmtChunkFound = true;
646                     int chunkSize = BytesToInt(data[position+7], data[position+6], data[position+5], data[position+4]);
647                     //
648                     // get the cbSize from the WAVEFORMATEX
649                     //
650
651                     int sizeOfWAVEFORMAT = 16;
652                     if (chunkSize != sizeOfWAVEFORMAT) {
653                         // we are dealing w/ WAVEFORMATEX
654                         // do extra validation
655                         int sizeOfWAVEFORMATEX = 18;
656
657                         // make sure the buffer is big enough to store a short
658                         if (len < position + 8 + sizeOfWAVEFORMATEX - 1)
659                             throw new System.InvalidOperationException(SR.GetString(SR.SoundAPIInvalidWaveHeader));
660
661                         Int16 cbSize = BytesToInt16(data[position+8 + sizeOfWAVEFORMATEX - 1],
662                                                     data[position+8 + sizeOfWAVEFORMATEX-2]);
663                         if (cbSize + sizeOfWAVEFORMATEX != chunkSize)
664                             throw new System.InvalidOperationException(SR.GetString(SR.SoundAPIInvalidWaveHeader));
665                     }
666
667                     // make sure the buffer passed in is big enough to store a short
668                     if(len < position + 9)
669                         throw new System.InvalidOperationException(SR.GetString(SR.SoundAPIInvalidWaveHeader));
670                     wFormatTag = BytesToInt16(data[position+9], data[position+8]);
671
672                     position += chunkSize + 8;
673                 } else {
674                     position += 8 + BytesToInt(data[position+7], data[position+6], data[position+5], data[position+4]);
675                 }
676             }
677
678             if (!fmtChunkFound)
679                 throw new System.InvalidOperationException(SR.GetString(SR.SoundAPIInvalidWaveHeader));
680
681             if (wFormatTag != NativeMethods.WAVE_FORMAT_PCM &&
682                 wFormatTag != NativeMethods.WAVE_FORMAT_ADPCM &&
683                 wFormatTag != NativeMethods.WAVE_FORMAT_IEEE_FLOAT)
684                 throw new System.InvalidOperationException(SR.GetString(SR.SoundAPIFormatNotSupported));
685         }
686
687         private static Int16 BytesToInt16(byte ch0, byte ch1) {
688             int res;
689             res = (int) ch1;
690             res |= (int) (((int)ch0) << 8);
691             return (Int16) res;
692         }
693         private static int BytesToInt(byte ch0, byte ch1, byte ch2, byte ch3) {
694             return mmioFOURCC((char) ch3, (char)ch2, (char) ch1, (char)ch0);
695         }
696
697         private static int mmioFOURCC(char ch0, char ch1, char ch2, char ch3) {
698             int result = 0;
699             result |= ((int) ch0);
700             result |= ((int) ch1) << 8;
701             result |= ((int) ch2) << 16;
702             result |= ((int) ch3) << 24;
703             return result;
704         }
705
706         /// <include file='doc\SoundPlayer.uex' path='docs/doc[@for="SoundPlayer.GetObjectData"]/*' />
707         [SecurityPermission(SecurityAction.LinkDemand, Flags=SecurityPermissionFlag.SerializationFormatter)]                
708         [SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes")] // vsw 427356
709         [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase", Justification = "System.dll is still using pre-v4 security model and needs this demand")]
710          void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) {
711             if (!String.IsNullOrEmpty(this.soundLocation)) {
712                 info.AddValue("SoundLocation", this.soundLocation);
713             }
714
715             if (this.stream != null) {
716                 info.AddValue("Stream", this.stream);
717             }
718
719             info.AddValue("LoadTimeout", this.loadTimeout);
720         }
721
722         private class IntSecurity {
723             // Constructor added because of FxCop rules
724             private IntSecurity() {}
725
726             private static volatile CodeAccessPermission safeSubWindows;
727
728             internal static CodeAccessPermission SafeSubWindows {
729                 get {
730                     if (safeSubWindows == null) {
731                         safeSubWindows = new UIPermission(UIPermissionWindow.SafeSubWindows);
732                     }
733
734                     return safeSubWindows;
735                 }
736             }
737         }
738
739         private class NativeMethods {
740             // Constructor added because of FxCop rules
741             private NativeMethods() {}
742
743             internal const int WAVE_FORMAT_PCM        = 0x0001,
744             WAVE_FORMAT_ADPCM                       = 0x0002,
745             WAVE_FORMAT_IEEE_FLOAT                  = 0x0003;
746
747             internal const int MMIO_READ              = 0x00000000,
748             MMIO_ALLOCBUF                           = 0x00010000,
749             MMIO_FINDRIFF                           = 0x00000020;
750
751             internal const int SND_SYNC = 0000,
752             SND_ASYNC = 0x0001,
753             SND_NODEFAULT = 0x0002,
754             SND_MEMORY = 0x0004,
755             SND_LOOP = 0x0008,
756             SND_PURGE = 0x0040,
757             SND_FILENAME = 0x00020000,
758             SND_NOSTOP = 0x0010;
759
760             [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Auto)]
761             internal class MMCKINFO {
762                 internal int      ckID;
763                 internal int      cksize;
764                 internal int      fccType;
765                 internal int      dwDataOffset;
766                 internal int      dwFlags;
767             }
768
769             [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Auto)]
770             internal class WAVEFORMATEX {
771                 internal System.Int16     wFormatTag;
772                 internal System.Int16     nChannels;
773                 internal int              nSamplesPerSec;
774                 internal int              nAvgBytesPerSec;
775                 internal System.Int16     nBlockAlign;
776                 internal System.Int16     wBitsPerSample;
777                 internal System.Int16     cbSize;
778             }
779         }
780
781         private class UnsafeNativeMethods {
782             // Constructor added because of FxCop rules
783             private UnsafeNativeMethods() {}
784
785             [DllImport(ExternDll.WinMM, CharSet=CharSet.Auto)]
786             [ResourceExposure(ResourceScope.Machine)]
787             internal static extern bool PlaySound([MarshalAs(UnmanagedType.LPWStr)] string soundName, IntPtr hmod, int soundFlags);
788         
789             [DllImport(ExternDll.WinMM, ExactSpelling=true, CharSet=CharSet.Auto)]
790             [ResourceExposure(ResourceScope.Machine)]
791             internal static extern bool PlaySound(byte[] soundName, IntPtr hmod, int soundFlags);
792        
793             [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2101:SpecifyMarshalingForPInvokeStringArguments")]
794             [DllImport(ExternDll.WinMM, CharSet=CharSet.Auto)]
795             [ResourceExposure(ResourceScope.Machine)]
796             internal static extern IntPtr mmioOpen(string fileName, IntPtr not_used, int flags);
797         
798             [DllImport(ExternDll.WinMM, CharSet=CharSet.Auto)]
799             [ResourceExposure(ResourceScope.None)]
800             internal static extern int mmioAscend(IntPtr hMIO, NativeMethods.MMCKINFO lpck, int flags);
801         
802             [DllImport(ExternDll.WinMM, CharSet=CharSet.Auto)]
803             [ResourceExposure(ResourceScope.None)]
804             internal static extern int mmioDescend(IntPtr hMIO,
805                                                    [MarshalAs(UnmanagedType.LPStruct)] NativeMethods.MMCKINFO lpck,
806                                                    [MarshalAs(UnmanagedType.LPStruct)] NativeMethods.MMCKINFO lcpkParent,
807                                                    int flags);
808             [DllImport(ExternDll.WinMM, CharSet=CharSet.Auto)]
809             [ResourceExposure(ResourceScope.None)]
810             internal static extern int mmioRead(IntPtr hMIO, [MarshalAs(UnmanagedType.LPArray)] byte[] wf, int cch);
811        
812             [DllImport(ExternDll.WinMM, CharSet=CharSet.Auto)]
813             [ResourceExposure(ResourceScope.None)]
814             internal static extern int mmioClose(IntPtr hMIO, int flags);
815         }
816     }
817 }