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