[Multimedia] Modified a constructor of the Display class not to check the raw video...
[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("Failed to create player");
51
52             Debug.Assert(_handle != null);
53
54             RetrieveProperties();
55
56             if (Features.IsSupported(PlayerFeatures.AudioEffect))
57             {
58                 _audioEffect = new AudioEffect(this);
59             }
60
61             if (Features.IsSupported(PlayerFeatures.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         /// <since_tizen> 3 </since_tizen>
92         public void Dispose()
93         {
94             Dispose(true);
95         }
96
97         private void Dispose(bool disposing)
98         {
99             if (!_disposed)
100             {
101                 ReplaceDisplay(null);
102
103                 if (_source != null)
104                 {
105                     try
106                     {
107                         _source.DetachFrom(this);
108                     }
109                     catch (Exception e)
110                     {
111                         Log.Error(PlayerLog.Tag, e.ToString());
112                     }
113                 }
114                 _source = null;
115
116                 if (_handle != null)
117                 {
118                     _handle.Dispose();
119                 }
120                 _disposed = true;
121             }
122         }
123
124         internal void ValidateNotDisposed()
125         {
126             if (_disposed)
127             {
128                 Log.Warn(PlayerLog.Tag, "player was disposed");
129                 throw new ObjectDisposedException(nameof(Player));
130             }
131         }
132
133         internal bool IsDisposed => _disposed;
134         #endregion
135
136         #region Methods
137
138         /// <summary>
139         /// Gets the streaming download progress.
140         /// </summary>
141         /// <returns>The <see cref="DownloadProgress"/> containing current download progress.</returns>
142         /// <remarks>The player must be in the <see cref="PlayerState.Playing"/> or <see cref="PlayerState.Paused"/> state.</remarks>
143         /// <exception cref="InvalidOperationException">
144         ///     The player is not streaming.<br/>
145         ///     -or-<br/>
146         ///     The player is not in the valid state.
147         ///     </exception>
148         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
149         /// <since_tizen> 3 </since_tizen>
150         public DownloadProgress GetDownloadProgress()
151         {
152             ValidatePlayerState(PlayerState.Playing, PlayerState.Paused);
153
154             int start = 0;
155             int current = 0;
156             NativePlayer.GetStreamingDownloadProgress(Handle, out start, out current).
157                 ThrowIfFailed("Failed to get download progress");
158
159             Log.Info(PlayerLog.Tag, "get download progress : " + start + ", " + current);
160
161             return new DownloadProgress(start, current);
162         }
163
164         /// <summary>
165         /// Sets the subtitle path for playback.
166         /// </summary>
167         /// <remarks>Only MicroDVD/SubViewer(*.sub), SAMI(*.smi), and SubRip(*.srt) subtitle formats are supported.
168         ///     <para>The mediastorage privilege(http://tizen.org/privilege/mediastorage) must be added if any files are used to play located in the internal storage.
169         ///     The externalstorage privilege(http://tizen.org/privilege/externalstorage) must be added if any files are used to play located in the external storage.</para>
170         /// </remarks>
171         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
172         /// <exception cref="ArgumentException"><paramref name="path"/> is an empty string.</exception>
173         /// <exception cref="FileNotFoundException">The specified path does not exist.</exception>
174         /// <exception cref="ArgumentNullException"><paramref name="path"/> is null.</exception>
175         /// <since_tizen> 3 </since_tizen>
176         public void SetSubtitle(string path)
177         {
178             ValidateNotDisposed();
179
180             if (path == null)
181             {
182                 throw new ArgumentNullException(nameof(path));
183             }
184
185             if (path.Length == 0)
186             {
187                 throw new ArgumentException("The path is empty.", nameof(path));
188             }
189
190             if (!File.Exists(path))
191             {
192                 throw new FileNotFoundException($"The specified file does not exist.", path);
193             }
194
195             NativePlayer.SetSubtitlePath(Handle, path).
196                 ThrowIfFailed("Failed to set the subtitle path to the player");
197         }
198
199         /// <summary>
200         /// Removes the subtitle path.
201         /// </summary>
202         /// <remarks>The player must be in the <see cref="PlayerState.Idle"/> state.</remarks>
203         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
204         /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
205         /// <since_tizen> 3 </since_tizen>
206         public void ClearSubtitle()
207         {
208             ValidatePlayerState(PlayerState.Idle);
209
210             NativePlayer.SetSubtitlePath(Handle, null).
211                 ThrowIfFailed("Failed to clear the subtitle of the player");
212         }
213
214         /// <summary>
215         /// Sets the offset for the subtitle.
216         /// </summary>
217         /// <param name="offset">The value indicating a desired offset in milliseconds.</param>
218         /// <remarks>The player must be in the <see cref="PlayerState.Playing"/> or <see cref="PlayerState.Paused"/> state.</remarks>
219         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
220         /// <exception cref="InvalidOperationException">
221         ///     The player is not in the valid state.<br/>
222         ///     -or-<br/>
223         ///     No subtitle is set.
224         /// </exception>
225         /// <seealso cref="SetSubtitle(string)"/>
226         /// <since_tizen> 3 </since_tizen>
227         public void SetSubtitleOffset(int offset)
228         {
229             ValidatePlayerState(PlayerState.Playing, PlayerState.Paused);
230
231             var err = NativePlayer.SetSubtitlePositionOffset(Handle, offset);
232
233             if (err == PlayerErrorCode.FeatureNotSupported)
234             {
235                 throw new InvalidOperationException("No subtitle set");
236             }
237
238             err.ThrowIfFailed("Failed to the subtitle offset of the player");
239         }
240
241         private void Prepare()
242         {
243             NativePlayer.Prepare(Handle).ThrowIfFailed("Failed to prepare the player");
244         }
245
246         /// <summary>
247         /// Called when the <see cref="Prepare"/> is invoked.
248         /// </summary>
249         /// <since_tizen> 3 </since_tizen>
250         protected virtual void OnPreparing()
251         {
252             RegisterEvents();
253         }
254
255         /// <summary>
256         /// Prepares the media player for playback, asynchronously.
257         /// </summary>
258         /// <returns>A task that represents the asynchronous prepare operation.</returns>
259         /// <remarks>To prepare the player, the player must be in the <see cref="PlayerState.Idle"/> state,
260         ///     and a source must be set.</remarks>
261         /// <exception cref="InvalidOperationException">No source is set.</exception>
262         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
263         /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
264         /// <since_tizen> 3 </since_tizen>
265         public virtual Task PrepareAsync()
266         {
267             if (_source == null)
268             {
269                 throw new InvalidOperationException("No source is set.");
270             }
271
272             ValidatePlayerState(PlayerState.Idle);
273
274             SetDisplay(_display).ThrowIfFailed("Failed to configure display of the player");
275
276             OnPreparing();
277
278             var completionSource = new TaskCompletionSource<bool>();
279
280             SetPreparing();
281
282             Task.Run(() =>
283             {
284                 try
285                 {
286                     Prepare();
287                     ClearPreparing();
288                     completionSource.SetResult(true);
289                 }
290                 catch (Exception e)
291                 {
292                     ClearPreparing();
293                     completionSource.TrySetException(e);
294                 }
295             });
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 is 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         /// <since_tizen> 3 </since_tizen>
314         public virtual void Unprepare()
315         {
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         /// <since_tizen> 3 </since_tizen>
333         protected virtual void OnUnprepared()
334         {
335             _source?.DetachFrom(this);
336             _source = null;
337         }
338
339         /// <summary>
340         /// Starts or resumes playback.
341         /// </summary>
342         /// <remarks>
343         /// The player must be in the <see cref="PlayerState.Ready"/> or <see cref="PlayerState.Paused"/> state.
344         /// It has no effect if the player is already in the <see cref="PlayerState.Playing"/> state.<br/>
345         /// <br/>
346         /// Sound can be mixed with other sounds if you don't control the stream focus using <see cref="ApplyAudioStreamPolicy"/>.
347         /// </remarks>
348         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
349         /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
350         /// <seealso cref="PrepareAsync"/>
351         /// <seealso cref="Stop"/>
352         /// <seealso cref="Pause"/>
353         /// <seealso cref="PlaybackCompleted"/>
354         /// <seealso cref="ApplyAudioStreamPolicy"/>
355         /// <since_tizen> 3 </since_tizen>
356         public virtual void Start()
357         {
358             if (State == PlayerState.Playing)
359             {
360                 Log.Warn(PlayerLog.Tag, "playing state already");
361                 return;
362             }
363             ValidatePlayerState(PlayerState.Ready, PlayerState.Paused);
364
365             NativePlayer.Start(Handle).ThrowIfFailed("Failed to start the player");
366         }
367
368         /// <summary>
369         /// Stops playing the 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         /// <since_tizen> 3 </since_tizen>
380         public virtual void Stop()
381         {
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         }
391
392         /// <summary>
393         /// Pauses the player.
394         /// </summary>
395         /// <remarks>
396         /// The player must be in the <see cref="PlayerState.Playing"/> state.
397         /// It has no effect if the player is already in the <see cref="PlayerState.Paused"/> state.
398         /// </remarks>
399         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
400         /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
401         /// <seealso cref="Start"/>
402         /// <since_tizen> 3 </since_tizen>
403         public virtual void Pause()
404         {
405             if (State == PlayerState.Paused)
406             {
407                 Log.Warn(PlayerLog.Tag, "pause state already");
408                 return;
409             }
410
411             ValidatePlayerState(PlayerState.Playing);
412
413             NativePlayer.Pause(Handle).ThrowIfFailed("Failed to pause the player");
414         }
415
416         private MediaSource _source;
417
418         /// <summary>
419         /// Sets a media source for the player.
420         /// </summary>
421         /// <param name="source">A <see cref="MediaSource"/> that specifies the source for playback.</param>
422         /// <remarks>The player must be in the <see cref="PlayerState.Idle"/> state.</remarks>
423         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
424         /// <exception cref="InvalidOperationException">
425         ///     The player is not in the valid state.<br/>
426         ///     -or-<br/>
427         ///     It is not able to assign the source to the player.
428         ///     </exception>
429         /// <seealso cref="PrepareAsync"/>
430         /// <since_tizen> 3 </since_tizen>
431         public void SetSource(MediaSource source)
432         {
433             ValidatePlayerState(PlayerState.Idle);
434
435             if (source != null)
436             {
437                 source.AttachTo(this);
438             }
439
440             if (_source != null)
441             {
442                 _source.DetachFrom(this);
443             }
444
445             _source = source;
446         }
447
448         /// <summary>
449         /// Captures a video frame, asynchronously.
450         /// </summary>
451         /// <returns>A task that represents the asynchronous capture operation.</returns>
452         /// <feature>http://tizen.org/feature/multimedia.raw_video</feature>
453         /// <remarks>The player must be in the <see cref="PlayerState.Playing"/> or <see cref="PlayerState.Paused"/> state.</remarks>
454         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
455         /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
456         /// <exception cref="NotSupportedException">The required feature is not supported.</exception>
457         /// <since_tizen> 3 </since_tizen>
458         public async Task<CapturedFrame> CaptureVideoAsync()
459         {
460             ValidationUtil.ValidateFeatureSupported(PlayerFeatures.RawVideo);
461
462             ValidatePlayerState(PlayerState.Playing, PlayerState.Paused);
463
464             TaskCompletionSource<CapturedFrame> t = new TaskCompletionSource<CapturedFrame>();
465
466             NativePlayer.VideoCaptureCallback cb = (data, width, height, size, _) =>
467             {
468                 Debug.Assert(size <= int.MaxValue);
469
470                 byte[] buf = new byte[size];
471                 Marshal.Copy(data, buf, 0, (int)size);
472
473                 t.TrySetResult(new CapturedFrame(buf, width, height));
474             };
475
476             using (var cbKeeper = ObjectKeeper.Get(cb))
477             {
478                 NativePlayer.CaptureVideo(Handle, cb)
479                     .ThrowIfFailed("Failed to capture the video");
480
481                 return await t.Task;
482             }
483         }
484
485         /// <summary>
486         /// Gets the play position in milliseconds.
487         /// </summary>
488         /// <remarks>The player must be in the <see cref="PlayerState.Ready"/>, <see cref="PlayerState.Playing"/>,
489         /// or <see cref="PlayerState.Paused"/> state.</remarks>
490         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
491         /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
492         /// <seealso cref="SetPlayPositionAsync(int, bool)"/>
493         /// <since_tizen> 3 </since_tizen>
494         public int GetPlayPosition()
495         {
496             ValidatePlayerState(PlayerState.Ready, PlayerState.Paused, PlayerState.Playing);
497
498             int playPosition = 0;
499
500             NativePlayer.GetPlayPosition(Handle, out playPosition).
501                 ThrowIfFailed("Failed to get the play position of the player");
502
503             Log.Info(PlayerLog.Tag, "get play position : " + playPosition);
504
505             return playPosition;
506         }
507
508         private void SetPlayPosition(int milliseconds, bool accurate,
509             NativePlayer.SeekCompletedCallback cb)
510         {
511             var ret = NativePlayer.SetPlayPosition(Handle, milliseconds, accurate, cb, IntPtr.Zero);
512
513             //Note that we assume invalid param error is returned only when the position value is invalid.
514             if (ret == PlayerErrorCode.InvalidArgument)
515             {
516                 throw new ArgumentOutOfRangeException(nameof(milliseconds), milliseconds,
517                     "The position is not valid.");
518             }
519             if (ret != PlayerErrorCode.None)
520             {
521                 Log.Error(PlayerLog.Tag, "Failed to set play position, " + (PlayerError)ret);
522             }
523             ret.ThrowIfFailed("Failed to set play position");
524         }
525
526         /// <summary>
527         /// Sets the seek position for playback, asynchronously.
528         /// </summary>
529         /// <param name="position">The value indicating a desired position in milliseconds.</param>
530         /// <param name="accurate">The value indicating whether the operation performs with accuracy.</param>
531         /// <remarks>
532         ///     <para>The player must be in the <see cref="PlayerState.Ready"/>, <see cref="PlayerState.Playing"/>,
533         ///     or <see cref="PlayerState.Paused"/> state.</para>
534         ///     <para>If the <paramref name="accurate"/> is true, the play position will be adjusted as the specified <paramref name="position"/> value,
535         ///     but this might be considerably slow. If false, the play position will be a nearest keyframe position.</para>
536         ///     </remarks>
537         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
538         /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
539         /// <exception cref="ArgumentOutOfRangeException">The specified position is not valid.</exception>
540         /// <seealso cref="GetPlayPosition"/>
541         /// <since_tizen> 3 </since_tizen>
542         public async Task SetPlayPositionAsync(int position, bool accurate)
543         {
544             ValidatePlayerState(PlayerState.Ready, PlayerState.Playing, PlayerState.Paused);
545
546             var taskCompletionSource = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
547
548             bool immediateResult = _source is MediaStreamSource;
549
550             NativePlayer.SeekCompletedCallback cb = _ => taskCompletionSource.TrySetResult(true);
551
552             using (var cbKeeper = ObjectKeeper.Get(cb))
553             {
554                 SetPlayPosition(position, accurate, cb);
555                 if (immediateResult)
556                 {
557                     taskCompletionSource.TrySetResult(true);
558                 }
559
560                 await taskCompletionSource.Task;
561             }
562         }
563
564         /// <summary>
565         /// Sets the playback rate.
566         /// </summary>
567         /// <param name="rate">The value for the playback rate. Valid range is -5.0 to 5.0, inclusive.</param>
568         /// <remarks>
569         ///     <para>The player must be in the <see cref="PlayerState.Ready"/>, <see cref="PlayerState.Playing"/>,
570         ///     or <see cref="PlayerState.Paused"/> state.</para>
571         ///     <para>The sound will be muted, when the playback rate is under 0.0 or over 2.0.</para>
572         /// </remarks>
573         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
574         /// <exception cref="InvalidOperationException">
575         ///     The player is not in the valid state.<br/>
576         ///     -or-<br/>
577         ///     Streaming playback.
578         /// </exception>
579         /// <exception cref="ArgumentOutOfRangeException">
580         ///     <paramref name="rate"/> is less than 5.0.<br/>
581         ///     -or-<br/>
582         ///     <paramref name="rate"/> is greater than 5.0.<br/>
583         ///     -or-<br/>
584         ///     <paramref name="rate"/> is zero.
585         /// </exception>
586         /// <since_tizen> 3 </since_tizen>
587         public void SetPlaybackRate(float rate)
588         {
589             if (rate < -5.0F || 5.0F < rate || rate == 0.0F)
590             {
591                 throw new ArgumentOutOfRangeException(nameof(rate), rate, "Valid range is -5.0 to 5.0 (except 0.0)");
592             }
593
594             ValidatePlayerState(PlayerState.Ready, PlayerState.Playing, PlayerState.Paused);
595
596             NativePlayer.SetPlaybackRate(Handle, rate).ThrowIfFailed("Failed to set the playback rate.");
597         }
598
599         /// <summary>
600         /// Applies the audio stream policy.
601         /// </summary>
602         /// <param name="policy">The <see cref="AudioStreamPolicy"/> to apply.</param>
603         /// <remarks>
604         /// The player must be in the <see cref="PlayerState.Idle"/> state.<br/>
605         /// <br/>
606         /// <see cref="Player"/> does not support all <see cref="AudioStreamType"/>.<br/>
607         /// Supported types are <see cref="AudioStreamType.Media"/>, <see cref="AudioStreamType.System"/>,
608         /// <see cref="AudioStreamType.Alarm"/>, <see cref="AudioStreamType.Notification"/>,
609         /// <see cref="AudioStreamType.Emergency"/>, <see cref="AudioStreamType.VoiceInformation"/>,
610         /// <see cref="AudioStreamType.RingtoneVoip"/> and <see cref="AudioStreamType.MediaExternalOnly"/>.
611         /// </remarks>
612         /// <exception cref="ObjectDisposedException">
613         ///     The player has already been disposed of.<br/>
614         ///     -or-<br/>
615         ///     <paramref name="policy"/> has already been disposed of.
616         /// </exception>
617         /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
618         /// <exception cref="ArgumentNullException"><paramref name="policy"/> is null.</exception>
619         /// <exception cref="NotSupportedException">
620         ///     <see cref="AudioStreamType"/> of <paramref name="policy"/> is not supported by <see cref="Player"/>.
621         /// </exception>
622         /// <seealso cref="AudioStreamPolicy"/>
623         /// <since_tizen> 3 </since_tizen>
624         public void ApplyAudioStreamPolicy(AudioStreamPolicy policy)
625         {
626             if (policy == null)
627             {
628                 throw new ArgumentNullException(nameof(policy));
629             }
630
631             ValidatePlayerState(PlayerState.Idle);
632
633             NativePlayer.SetAudioPolicyInfo(Handle, policy.Handle).
634                 ThrowIfFailed("Failed to set the audio stream policy to the player");
635         }
636         #endregion
637
638         #region Preparing state
639
640         private int _isPreparing;
641
642         private bool IsPreparing()
643         {
644             return Interlocked.CompareExchange(ref _isPreparing, 1, 1) == 1;
645         }
646
647         private void SetPreparing()
648         {
649             Interlocked.Exchange(ref _isPreparing, 1);
650         }
651
652         private void ClearPreparing()
653         {
654             Interlocked.Exchange(ref _isPreparing, 0);
655         }
656
657         #endregion
658
659         /// <summary>
660         /// This method supports the product infrastructure and is not intended to be used directly from application code.
661         /// </summary>
662         /// <since_tizen> 4 </since_tizen>
663         [EditorBrowsable(EditorBrowsableState.Never)]
664         protected static Exception GetException(int errorCode, string message) =>
665             ((PlayerErrorCode)errorCode).GetException(message);
666     }
667 }