1992c8158184b65319b3014821b689315e1c020c
[platform/core/csapi/tizenfx.git] / src / Tizen.Multimedia.MediaPlayer / Player / Player.cs
1 /*
2  * Copyright (c) 2016 Samsung Electronics Co., Ltd All Rights Reserved
3  *
4  * Licensed under the Apache License, Version 2.0 (the License);
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an AS IS BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 using System;
17 using System.Threading.Tasks;
18 using System.Runtime.InteropServices;
19 using System.Diagnostics;
20 using System.IO;
21 using System.Threading;
22 using static Interop;
23
24 namespace Tizen.Multimedia
25 {
26     static internal class PlayerLog
27     {
28         internal const string Tag = "Tizen.Multimedia.Player";
29         internal const string Enter = "[ENTER]";
30         internal const string Leave = "[LEAVE]";
31     }
32
33     /// <summary>
34     /// Provides the ability to control media playback.
35     /// </summary>
36     /// <remarks>
37     /// The Player provides functions to play a media content.
38     /// It also provides functions to adjust the configurations of the player such as playback rate, volume, looping etc.
39     /// Note that only one video player can be played at one time.
40     /// </remarks>
41     public partial class Player : IDisposable, IDisplayable<PlayerErrorCode>
42     {
43         private PlayerHandle _handle;
44
45         /// <summary>
46         /// Initialize a new instance of the Player class.
47         /// </summary>
48         public Player()
49         {
50             NativePlayer.Create(out _handle).ThrowIfFailed("Failed to create player");
51
52             Debug.Assert(_handle != null);
53
54             RetrieveProperties();
55
56             if (Features.IsSupported(Features.AudioEffect))
57             {
58                 _audioEffect = new AudioEffect(this);
59             }
60
61             if (Features.IsSupported(Features.RawVideo))
62             {
63                 RegisterVideoFrameDecodedCallback();
64             }
65
66             DisplaySettings = PlayerDisplaySettings.Create(this);
67         }
68
69         internal void ValidatePlayerState(params PlayerState[] desiredStates)
70         {
71             Debug.Assert(desiredStates.Length > 0);
72
73             ValidateNotDisposed();
74
75             var curState = State;
76             if (curState.IsAnyOf(desiredStates))
77             {
78                 return;
79             }
80
81             throw new InvalidOperationException($"The player is not in a valid state. " +
82                 $"Current State : { curState }, Valid State : { string.Join(", ", desiredStates) }.");
83         }
84
85         #region Dispose support
86         private bool _disposed;
87
88         /// <summary>
89         /// Releases all resources used by the current instance.
90         /// </summary>
91         public void Dispose()
92         {
93             Dispose(true);
94         }
95
96         private void Dispose(bool disposing)
97         {
98             if (!_disposed)
99             {
100                 ReplaceDisplay(null);
101
102                 if (_source != null)
103                 {
104                     try
105                     {
106                         _source.DetachFrom(this);
107                     }
108                     catch (Exception e)
109                     {
110                         Log.Error(PlayerLog.Tag, e.ToString());
111                     }
112                 }
113                 _source = null;
114
115                 if (_handle != null)
116                 {
117                     _handle.Dispose();
118                 }
119                 _disposed = true;
120             }
121         }
122
123         internal void ValidateNotDisposed()
124         {
125             if (_disposed)
126             {
127                 Log.Warn(PlayerLog.Tag, "player was disposed");
128                 throw new ObjectDisposedException(nameof(Player));
129             }
130         }
131
132         internal bool IsDisposed => _disposed;
133         #endregion
134
135         #region Methods
136
137         /// <summary>
138         /// Gets the streaming download Progress.
139         /// </summary>
140         /// <returns>The <see cref="DownloadProgress"/> containing current download progress.</returns>
141         /// <remarks>The player must be in the <see cref="PlayerState.Playing"/> or <see cref="PlayerState.Paused"/> state.</remarks>
142         /// <exception cref="InvalidOperationException">
143         ///     The player is not streaming.\n
144         ///     -or-\n
145         ///     The player is not in the valid state.
146         ///     </exception>
147         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
148         public DownloadProgress GetDownloadProgress()
149         {
150             Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
151             ValidatePlayerState(PlayerState.Playing, PlayerState.Paused);
152
153             int start = 0;
154             int current = 0;
155             NativePlayer.GetStreamingDownloadProgress(Handle, out start, out current).
156                 ThrowIfFailed("Failed to get download progress");
157
158             Log.Info(PlayerLog.Tag, "get download progress : " + start + ", " + current);
159
160             return new DownloadProgress(start, current);
161         }
162
163         /// <summary>
164         /// Sets the subtitle path for playback.
165         /// </summary>
166         /// <remarks>Only MicroDVD/SubViewer(*.sub), SAMI(*.smi), and SubRip(*.srt) subtitle formats are supported.
167         ///     <para>The mediastorage privilege(http://tizen.org/privilege/mediastorage) must be added if any files are used to play located in the internal storage.
168         ///     The externalstorage privilege(http://tizen.org/privilege/externalstorage) must be added if any files are used to play located in the external storage.</para>
169         /// </remarks>
170         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
171         /// <exception cref="ArgumentException"><paramref name="path"/> is an empty string.</exception>
172         /// <exception cref="FileNotFoundException">The specified path does not exist.</exception>
173         /// <exception cref="ArgumentNullException">The path is null.</exception>
174         public void SetSubtitle(string path)
175         {
176             Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
177             ValidateNotDisposed();
178
179             if (path == null)
180             {
181                 throw new ArgumentNullException(nameof(path));
182             }
183
184             if (path.Length == 0)
185             {
186                 throw new ArgumentException("The path is empty.", nameof(path));
187             }
188
189             if (!File.Exists(path))
190             {
191                 throw new FileNotFoundException($"The specified file does not exist.", path);
192             }
193
194             NativePlayer.SetSubtitlePath(Handle, path).
195                 ThrowIfFailed("Failed to set the subtitle path to the player");
196
197             Log.Debug(PlayerLog.Tag, PlayerLog.Leave);
198         }
199
200         /// <summary>
201         /// Removes the subtitle path.
202         /// </summary>
203         /// <remarks>The player must be in the <see cref="PlayerState.Idle"/> state.</remarks>
204         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
205         /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
206         public void ClearSubtitle()
207         {
208             Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
209             ValidatePlayerState(PlayerState.Idle);
210
211             NativePlayer.SetSubtitlePath(Handle, null).
212                 ThrowIfFailed("Failed to clear the subtitle of the player");
213             Log.Debug(PlayerLog.Tag, PlayerLog.Leave);
214         }
215
216         /// <summary>
217         /// Sets the offset for the subtitle.
218         /// </summary>
219         /// <param name="offset">The value indicating a desired offset in milliseconds.</param>
220         /// <remarks>The player must be in the <see cref="PlayerState.Playing"/> or <see cref="PlayerState.Paused"/> state.</remarks>
221         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
222         /// <exception cref="InvalidOperationException">
223         ///     The player is not in the valid state.\n
224         ///     -or-\n
225         ///     No subtitle is set.
226         /// </exception>
227         /// <seealso cref="SetSubtitle(string)"/>
228         public void SetSubtitleOffset(int offset)
229         {
230             ValidatePlayerState(PlayerState.Playing, PlayerState.Paused);
231
232             var err = NativePlayer.SetSubtitlePositionOffset(Handle, offset);
233
234             if (err == PlayerErrorCode.FeatureNotSupported)
235             {
236                 throw new InvalidOperationException("No subtitle set");
237             }
238
239             err.ThrowIfFailed("Failed to the subtitle offset of the player");
240             Log.Debug(PlayerLog.Tag, PlayerLog.Leave);
241         }
242
243         private void Prepare()
244         {
245             NativePlayer.Prepare(Handle).ThrowIfFailed("Failed to prepare the player");
246         }
247
248         /// <summary>
249         /// Called when the <see cref="Prepare"/> is invoked.
250         /// </summary>
251         protected virtual void OnPreparing()
252         {
253             RegisterEvents();
254         }
255
256         /// <summary>
257         /// Prepares the media player for playback, asynchronously.
258         /// </summary>
259         /// <returns>A task that represents the asynchronous prepare operation.</returns>
260         /// <remarks>To prepare the player, the player must be in the <see cref="PlayerState.Idle"/> state,
261         ///     and a source must be set.</remarks>
262         /// <exception cref="InvalidOperationException">No source is set.</exception>
263         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
264         /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
265         public virtual Task PrepareAsync()
266         {
267             Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
268             if (_source == null)
269             {
270                 throw new InvalidOperationException("No source is set.");
271             }
272
273             ValidatePlayerState(PlayerState.Idle);
274
275             OnPreparing();
276
277             var completionSource = new TaskCompletionSource<bool>();
278
279             SetPreparing();
280
281             Task.Run(() =>
282             {
283                 try
284                 {
285                     Prepare();
286                     ClearPreparing();
287                     completionSource.SetResult(true);
288                 }
289                 catch (Exception e)
290                 {
291                     ClearPreparing();
292                     completionSource.TrySetException(e);
293                 }
294             });
295             Log.Debug(PlayerLog.Tag, PlayerLog.Leave);
296
297             return completionSource.Task;
298         }
299
300         /// <summary>
301         /// Unprepares the player.
302         /// </summary>
303         /// <remarks>
304         ///     The most recently used source is reset and no longer associated with the player. Playback is no longer possible.
305         ///     If you want to use the player again, you have to set a source and call <see cref="PrepareAsync"/> again.
306         ///     <para>
307         ///     The player must be in the <see cref="PlayerState.Ready"/>, <see cref="PlayerState.Playing"/> or <see cref="PlayerState.Paused"/> state.
308         ///     It has no effect if the player is already in the <see cref="PlayerState.Idle"/> state.
309         ///     </para>
310         /// </remarks>
311         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
312         /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
313         public virtual void Unprepare()
314         {
315             Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
316             if (State == PlayerState.Idle)
317             {
318                 Log.Warn(PlayerLog.Tag, "idle state already");
319                 return;
320             }
321             ValidatePlayerState(PlayerState.Ready, PlayerState.Paused, PlayerState.Playing);
322
323             NativePlayer.Unprepare(Handle).ThrowIfFailed("Failed to unprepare the player");
324
325             OnUnprepared();
326         }
327
328         /// <summary>
329         /// Called after the <see cref="Player"/> is unprepared.
330         /// </summary>
331         /// <seealso cref="Unprepare"/>
332         protected virtual void OnUnprepared()
333         {
334             _source?.DetachFrom(this);
335             _source = null;
336         }
337
338         /// <summary>
339         /// Starts or resumes playback.
340         /// </summary>
341         /// <remarks>
342         /// The player must be in the <see cref="PlayerState.Ready"/> or <see cref="PlayerState.Paused"/> state.
343         /// It has no effect if the player is already in the <see cref="PlayerState.Playing"/> state.\n
344         /// \n
345         /// Sound can be mixed with other sounds if you don't control the stream focus using <see cref="ApplyAudioStreamPolicy"/>.
346         /// </remarks>
347         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
348         /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
349         /// <seealso cref="PrepareAsync"/>
350         /// <seealso cref="Stop"/>
351         /// <seealso cref="Pause"/>
352         /// <seealso cref="PlaybackCompleted"/>
353         /// <seealso cref="ApplyAudioStreamPolicy"/>
354         public virtual void Start()
355         {
356             Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
357             if (State == PlayerState.Playing)
358             {
359                 Log.Warn(PlayerLog.Tag, "playing state already");
360                 return;
361             }
362             ValidatePlayerState(PlayerState.Ready, PlayerState.Paused);
363
364             NativePlayer.Start(Handle).ThrowIfFailed("Failed to start the player");
365             Log.Debug(PlayerLog.Tag, PlayerLog.Leave);
366         }
367
368         /// <summary>
369         /// Stops playing media content.
370         /// </summary>
371         /// <remarks>
372         /// The player must be in the <see cref="PlayerState.Playing"/> or <see cref="PlayerState.Paused"/> state.
373         /// It has no effect if the player is already in the <see cref="PlayerState.Ready"/> state.
374         /// </remarks>
375         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
376         /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
377         /// <seealso cref="Start"/>
378         /// <seealso cref="Pause"/>
379         public virtual void Stop()
380         {
381             Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
382             if (State == PlayerState.Ready)
383             {
384                 Log.Warn(PlayerLog.Tag, "ready state already");
385                 return;
386             }
387             ValidatePlayerState(PlayerState.Paused, PlayerState.Playing);
388
389             NativePlayer.Stop(Handle).ThrowIfFailed("Failed to stop the player");
390             Log.Debug(PlayerLog.Tag, PlayerLog.Leave);
391         }
392
393         /// <summary>
394         /// Pauses the player.
395         /// </summary>
396         /// <remarks>
397         /// The player must be in the <see cref="PlayerState.Playing"/> state.
398         /// It has no effect if the player is already in the <see cref="PlayerState.Paused"/> state.
399         /// </remarks>
400         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
401         /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
402         /// <seealso cref="Start"/>
403         public virtual void Pause()
404         {
405             Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
406             if (State == PlayerState.Paused)
407             {
408                 Log.Warn(PlayerLog.Tag, "pause state already");
409                 return;
410             }
411
412             ValidatePlayerState(PlayerState.Playing);
413
414             NativePlayer.Pause(Handle).ThrowIfFailed("Failed to pause the player");
415             Log.Debug(PlayerLog.Tag, PlayerLog.Leave);
416         }
417
418         private MediaSource _source;
419
420         /// <summary>
421         /// Sets a media source for the player.
422         /// </summary>
423         /// <param name="source">A <see cref="MediaSource"/> that specifies the source for playback.</param>
424         /// <remarks>The player must be in the <see cref="PlayerState.Idle"/> state.</remarks>
425         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
426         /// <exception cref="InvalidOperationException">
427         ///     The player is not in the valid state.\n
428         ///     -or-\n
429         ///     It is not able to assign the source to the player.
430         ///     </exception>
431         /// <seealso cref="PrepareAsync"/>
432         public void SetSource(MediaSource source)
433         {
434             Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
435             ValidatePlayerState(PlayerState.Idle);
436
437             if (source != null)
438             {
439                 source.AttachTo(this);
440             }
441
442             if (_source != null)
443             {
444                 _source.DetachFrom(this);
445             }
446
447             _source = source;
448             Log.Debug(PlayerLog.Tag, PlayerLog.Leave);
449         }
450
451         /// <summary>
452         /// Captures a video frame asynchronously.
453         /// </summary>
454         /// <returns>A task that represents the asynchronous capture operation.</returns>
455         /// <feature>http://tizen.org/feature/multimedia.raw_video</feature>
456         /// <remarks>The player must be in the <see cref="PlayerState.Playing"/> or <see cref="PlayerState.Paused"/> state.</remarks>
457         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
458         /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
459         /// <exception cref="NotSupportedException">The required feature is not supported.</exception>
460         public async Task<CapturedFrame> CaptureVideoAsync()
461         {
462             Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
463
464             ValidationUtil.ValidateFeatureSupported(Features.RawVideo);
465
466             ValidatePlayerState(PlayerState.Playing, PlayerState.Paused);
467
468             TaskCompletionSource<CapturedFrame> t = new TaskCompletionSource<CapturedFrame>();
469
470             NativePlayer.VideoCaptureCallback cb = (data, width, height, size, _) =>
471             {
472                 Debug.Assert(size <= int.MaxValue);
473
474                 byte[] buf = new byte[size];
475                 Marshal.Copy(data, buf, 0, (int)size);
476
477                 t.TrySetResult(new CapturedFrame(buf, width, height));
478             };
479
480             using (var cbKeeper = ObjectKeeper.Get(cb))
481             {
482                 NativePlayer.CaptureVideo(Handle, cb)
483                     .ThrowIfFailed("Failed to capture the video");
484
485                 return await t.Task;
486             }
487         }
488
489         /// <summary>
490         /// Gets the play position in milliseconds.
491         /// </summary>
492         /// <remarks>The player must be in the <see cref="PlayerState.Ready"/>, <see cref="PlayerState.Playing"/> or <see cref="PlayerState.Paused"/> state.</remarks>
493         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
494         /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
495         /// <seealso cref="SetPlayPositionAsync(int, bool)"/>
496         public int GetPlayPosition()
497         {
498             Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
499             ValidatePlayerState(PlayerState.Ready, PlayerState.Paused, PlayerState.Playing);
500
501             int playPosition = 0;
502
503             NativePlayer.GetPlayPosition(Handle, out playPosition).
504                 ThrowIfFailed("Failed to get the play position of the player");
505
506             Log.Info(PlayerLog.Tag, "get play position : " + playPosition);
507
508             return playPosition;
509         }
510
511         private void SetPlayPosition(int milliseconds, bool accurate,
512             NativePlayer.SeekCompletedCallback cb)
513         {
514             Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
515             var ret = NativePlayer.SetPlayPosition(Handle, milliseconds, accurate, cb, IntPtr.Zero);
516
517             //Note that we assume invalid param error is returned only when the position value is invalid.
518             if (ret == PlayerErrorCode.InvalidArgument)
519             {
520                 throw new ArgumentOutOfRangeException(nameof(milliseconds), milliseconds,
521                     "The position is not valid.");
522             }
523             if (ret != PlayerErrorCode.None)
524             {
525                 Log.Error(PlayerLog.Tag, "Failed to set play position, " + (PlayerError)ret);
526             }
527             ret.ThrowIfFailed("Failed to set play position");
528         }
529
530         /// <summary>
531         /// Sets the seek position for playback, asynchronously.
532         /// </summary>
533         /// <param name="position">The value indicating a desired position in milliseconds.</param>
534         /// <param name="accurate">The value indicating whether the operation performs with accuracy.</param>
535         /// <remarks>
536         ///     <para>The player must be in the <see cref="PlayerState.Ready"/>, <see cref="PlayerState.Playing"/> or <see cref="PlayerState.Paused"/> state.</para>
537         ///     <para>If the <paramref name="accurate"/> is true, the play position will be adjusted as the specified <paramref name="position"/> value,
538         ///     but this might be considerably slow. If false, the play position will be a nearest keyframe position.</para>
539         ///     </remarks>
540         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
541         /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
542         /// <exception cref="ArgumentOutOfRangeException">The specified position is not valid.</exception>
543         /// <seealso cref="GetPlayPosition"/>
544         public async Task SetPlayPositionAsync(int position, bool accurate)
545         {
546             Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
547             ValidatePlayerState(PlayerState.Ready, PlayerState.Playing, PlayerState.Paused);
548
549             var taskCompletionSource = new TaskCompletionSource<bool>();
550
551             bool immediateResult = _source is MediaStreamSource;
552
553             NativePlayer.SeekCompletedCallback cb = _ => taskCompletionSource.TrySetResult(true);
554
555             using (var cbKeeper = ObjectKeeper.Get(cb))
556             {
557                 SetPlayPosition(position, accurate, cb);
558                 if (immediateResult)
559                 {
560                     taskCompletionSource.TrySetResult(true);
561                 }
562
563                 await taskCompletionSource.Task;
564             }
565
566             Log.Debug(PlayerLog.Tag, PlayerLog.Leave);
567         }
568
569         /// <summary>
570         /// Sets playback rate.
571         /// </summary>
572         /// <param name="rate">The value for the playback rate. Valid range is -5.0 to 5.0, inclusive.</param>
573         /// <remarks>
574         ///     <para>The player must be in the <see cref="PlayerState.Ready"/>, <see cref="PlayerState.Playing"/> or <see cref="PlayerState.Paused"/> state.</para>
575         ///     <para>The sound will be muted, when the playback rate is under 0.0 or over 2.0.</para>
576         /// </remarks>
577         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
578         /// <exception cref="InvalidOperationException">
579         ///     The player is not in the valid state.\n
580         ///     -or-\n
581         ///     Streaming playback.
582         /// </exception>
583         /// <exception cref="ArgumentOutOfRangeException">
584         ///     <paramref name="rate"/> is less than 5.0.\n
585         ///     -or-\n
586         ///     <paramref name="rate"/> is greater than 5.0.\n
587         ///     -or-\n
588         ///     <paramref name="rate"/> is zero.
589         /// </exception>
590         public void SetPlaybackRate(float rate)
591         {
592             Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
593             if (rate < -5.0F || 5.0F < rate || rate == 0.0F)
594             {
595                 throw new ArgumentOutOfRangeException(nameof(rate), rate, "Valid range is -5.0 to 5.0 (except 0.0)");
596             }
597
598             ValidatePlayerState(PlayerState.Ready, PlayerState.Playing, PlayerState.Paused);
599
600             NativePlayer.SetPlaybackRate(Handle, rate).ThrowIfFailed("Failed to set the playback rate.");
601             Log.Debug(PlayerLog.Tag, PlayerLog.Leave);
602         }
603
604         /// <summary>
605         /// Applies the audio stream policy.
606         /// </summary>
607         /// <param name="policy">The <see cref="AudioStreamPolicy"/> to apply.</param>
608         /// <remarks>
609         /// The player must be in the <see cref="PlayerState.Idle"/> state.\n
610         /// \n
611         /// <see cref="Player"/> does not support all <see cref="AudioStreamType"/>.\n
612         /// Supported types are <see cref="AudioStreamType.Media"/>, <see cref="AudioStreamType.System"/>,
613         /// <see cref="AudioStreamType.Alarm"/>, <see cref="AudioStreamType.Notification"/>,
614         /// <see cref="AudioStreamType.Emergency"/>, <see cref="AudioStreamType.VoiceInformation"/>,
615         /// <see cref="AudioStreamType.RingtoneVoip"/> and <see cref="AudioStreamType.MediaExternalOnly"/>.
616         /// </remarks>
617         /// <exception cref="ObjectDisposedException">
618         ///     The player has already been disposed of.\n
619         ///     -or-\n
620         ///     <paramref name="policy"/> has already been disposed of.
621         /// </exception>
622         /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
623         /// <exception cref="ArgumentNullException"><paramref name="policy"/> is null.</exception>
624         /// <exception cref="NotSupportedException">
625         ///     <see cref="AudioStreamType"/> of <paramref name="policy"/> is not supported by <see cref="Player"/>.
626         /// </exception>
627         /// <seealso cref="AudioStreamPolicy"/>
628         public void ApplyAudioStreamPolicy(AudioStreamPolicy policy)
629         {
630             if (policy == null)
631             {
632                 throw new ArgumentNullException(nameof(policy));
633             }
634
635             ValidatePlayerState(PlayerState.Idle);
636
637             NativePlayer.SetAudioPolicyInfo(Handle, policy.Handle).
638                 ThrowIfFailed("Failed to set the audio stream policy to the player");
639         }
640         #endregion
641
642         #region Preparing state
643
644         private int _isPreparing;
645
646         private bool IsPreparing()
647         {
648             return Interlocked.CompareExchange(ref _isPreparing, 1, 1) == 1;
649         }
650
651         private void SetPreparing()
652         {
653             Interlocked.Exchange(ref _isPreparing, 1);
654         }
655
656         private void ClearPreparing()
657         {
658             Interlocked.Exchange(ref _isPreparing, 0);
659         }
660
661         #endregion
662
663         /// <summary>
664         /// This method supports the product infrastructure and is not intended to be used directly from application code.
665         /// </summary>
666         protected static Exception GetException(int errorCode, string message) =>
667             ((PlayerErrorCode)errorCode).GetException(message);
668     }
669 }