Enhanced readability of Display related codes
[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 class Player : IDisposable, IDisplayable<PlayerErrorCode>
42     {
43         private PlayerHandle _handle;
44
45         /// <summary>
46         /// Occurs when playback of a media is finished.
47         /// </summary>
48         public event EventHandler<EventArgs> PlaybackCompleted;
49         private NativePlayer.PlaybackCompletedCallback _playbackCompletedCallback;
50
51         /// <summary>
52         /// Occurs when playback of a media is interrupted.
53         /// </summary>
54         public event EventHandler<PlaybackInterruptedEventArgs> PlaybackInterrupted;
55         private NativePlayer.PlaybackInterruptedCallback _playbackInterruptedCallback;
56
57         /// <summary>
58         /// Occurs when any error occurs.
59         /// </summary>
60         /// <remarks>The event handler will be executed on an internal thread.</remarks>
61         public event EventHandler<PlayerErrorOccurredEventArgs> ErrorOccurred;
62         private NativePlayer.PlaybackErrorCallback _playbackErrorCallback;
63
64         /// <summary>
65         /// Occurs when the video stream changed.
66         /// </summary>
67         /// <remarks>The event handler will be executed on an internal thread.</remarks>
68         public event EventHandler<VideoStreamChangedEventArgs> VideoStreamChanged;
69         private NativePlayer.VideoStreamChangedCallback _videoStreamChangedCallback;
70
71         /// <summary>
72         /// Occurs when the subtitle is updated.
73         /// </summary>
74         /// <remarks>The event handler will be executed on an internal thread.</remarks>
75         public EventHandler<SubtitleUpdatedEventArgs> SubtitleUpdated;
76         private NativePlayer.SubtitleUpdatedCallback _subtitleUpdatedCallback;
77
78         /// <summary>
79         /// Occurs when there is a change in the buffering status of streaming.
80         /// </summary>
81         public event EventHandler<BufferingProgressChangedEventArgs> BufferingProgressChanged;
82         private NativePlayer.BufferingProgressCallback _bufferingProgressCallback;
83
84         internal event EventHandler<MediaStreamBufferStatusChangedEventArgs> MediaStreamAudioBufferStatusChanged;
85         private NativePlayer.MediaStreamBufferStatusCallback _mediaStreamAudioBufferStatusChangedCallback;
86
87         internal event EventHandler<MediaStreamBufferStatusChangedEventArgs> MediaStreamVideoBufferStatusChanged;
88         private NativePlayer.MediaStreamBufferStatusCallback _mediaStreamVideoBufferStatusChangedCallback;
89
90         internal event EventHandler<MediaStreamSeekingOccurredEventArgs> MediaStreamAudioSeekingOccurred;
91         private NativePlayer.MediaStreamSeekCallback _mediaStreamAudioSeekCallback;
92
93         internal event EventHandler<MediaStreamSeekingOccurredEventArgs> MediaStreamVideoSeekingOccurred;
94         private NativePlayer.MediaStreamSeekCallback _mediaStreamVideoSeekCallback;
95
96         /// <summary>
97         /// Initialize a new instance of the Player class.
98         /// </summary>
99         public Player()
100         {
101             Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
102
103             NativePlayer.Create(out _handle).ThrowIfFailed("Failed to create player");
104
105             Debug.Assert(_handle != null);
106
107             RetrieveProperties();
108
109             if (Features.IsSupported(Features.AudioEffect))
110             {
111                 _audioEffect = new AudioEffect(this);
112             }
113
114             if (Features.IsSupported(Features.RawVideo))
115             {
116                 RegisterVideoFrameDecodedCallback();
117             }
118         }
119
120         private void RetrieveProperties()
121         {
122             Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
123
124             NativePlayer.GetAudioLatencyMode(Handle, out _audioLatencyMode).
125                 ThrowIfFailed("Failed to initialize the player");
126
127             NativePlayer.IsLooping(Handle, out _isLooping).ThrowIfFailed("Failed to initialize the player");
128
129             Log.Debug(PlayerLog.Tag, PlayerLog.Leave);
130         }
131
132         private bool _callbackRegistered;
133
134         private void RegisterCallbacks()
135         {
136             Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
137
138             if (_callbackRegistered)
139             {
140                 return;
141             }
142             RegisterSubtitleUpdatedCallback();
143             RegisterErrorOccurredCallback();
144             RegisterPlaybackInterruptedCallback();
145             RegisterVideoStreamChangedCallback();
146             RegisterBufferingCallback();
147             RegisterMediaStreamBufferStatusCallback();
148             RegisterMediaStreamSeekCallback();
149             RegisterPlaybackCompletedCallback();
150
151             _callbackRegistered = true;
152         }
153
154         public IntPtr Handle
155         {
156             get
157             {
158                 ValidateNotDisposed();
159                 return _handle.DangerousGetHandle();
160             }
161         }
162
163         internal void ValidatePlayerState(params PlayerState[] desiredStates)
164         {
165             Debug.Assert(desiredStates.Length > 0);
166
167             ValidateNotDisposed();
168
169             var curState = State;
170             if (curState.IsAnyOf(desiredStates))
171             {
172                 return;
173             }
174
175             throw new InvalidOperationException($"The player is not in a valid state. " +
176                 $"Current State : { curState }, Valid State : { string.Join(", ", desiredStates) }.");
177         }
178
179         #region Properties
180         #region Network configuration
181         private string _cookie = "";
182         private string _userAgent = "";
183
184         /// <summary>
185         /// Gets or Sets the cookie for streaming playback.
186         /// </summary>
187         /// <remarks>To set, the player must be in the <see cref="PlayerState.Idle"/> state.</remarks>
188         /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
189         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
190         /// <exception cref="ArgumentNullException">The value to set is null.</exception>
191         public string Cookie
192         {
193             get
194             {
195                 Log.Info(PlayerLog.Tag, "get cookie : " + _cookie);
196                 return _cookie;
197             }
198             set
199             {
200                 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
201                 ValidatePlayerState(PlayerState.Idle);
202
203                 if (value == null)
204                 {
205                     Log.Error(PlayerLog.Tag, "cookie can't be null");
206                     throw new ArgumentNullException(nameof(value), "Cookie can't be null.");
207                 }
208
209                 NativePlayer.SetStreamingCookie(Handle, value, value.Length).
210                     ThrowIfFailed("Failed to set the cookie to the player");
211
212                 _cookie = value;
213                 Log.Debug(PlayerLog.Tag, PlayerLog.Leave);
214             }
215         }
216
217         /// <summary>
218         /// Gets or Sets the user agent for streaming playback.
219         /// </summary>
220         /// <remarks>To set, the player must be in the <see cref="PlayerState.Idle"/> state.</remarks>
221         /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
222         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
223         /// <exception cref="ArgumentNullException">The value to set is null.</exception>
224         public string UserAgent
225         {
226             get
227             {
228                 Log.Info(PlayerLog.Tag, "get useragent : " + _userAgent);
229                 return _userAgent;
230             }
231             set
232             {
233                 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
234                 ValidatePlayerState(PlayerState.Idle);
235
236                 if (value == null)
237                 {
238                     Log.Error(PlayerLog.Tag, "UserAgent can't be null");
239                     throw new ArgumentNullException(nameof(value), "UserAgent can't be null.");
240                 }
241
242                 NativePlayer.SetStreamingUserAgent(Handle, value, value.Length).
243                     ThrowIfFailed("Failed to set the user agent to the player");
244
245                 _userAgent = value;
246                 Log.Debug(PlayerLog.Tag, PlayerLog.Leave);
247             }
248         }
249         #endregion
250
251         /// <summary>
252         /// Gets the state of the player.
253         /// </summary>
254         /// <value>The current state of the player.</value>
255         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
256         public PlayerState State
257         {
258             get
259             {
260                 ValidateNotDisposed();
261
262                 //TODO is this needed?
263                 if (IsPreparing())
264                 {
265                     return PlayerState.Preparing;
266                 }
267
268                 int state = 0;
269                 NativePlayer.GetState(Handle, out state).ThrowIfFailed("Failed to retrieve the state of the player");
270
271                 Debug.Assert(Enum.IsDefined(typeof(PlayerState), state));
272
273                 return (PlayerState)state;
274             }
275         }
276
277         private AudioLatencyMode _audioLatencyMode;
278
279         /// <summary>
280         /// Gets or sets the audio latency mode.
281         /// </summary>
282         /// <value>A <see cref="AudioLatencyMode"/> that specifies the mode. The default is <see cref="AudioLatencyMode.Mid"/>.</value>
283         /// <remarks>
284         /// If the mode is <see cref="AudioLatencyMode.High"/>,
285         /// audio output interval can be increased so, it can keep more audio data to play.
286         /// But, state transition like pause or resume can be more slower than default(<see cref="AudioLatencyMode.Mid"/>).
287         /// </remarks>
288         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
289         /// <exception cref="ArgumentException">The value is not valid.</exception>
290         public AudioLatencyMode AudioLatencyMode
291         {
292             get
293             {
294                 Log.Info(PlayerLog.Tag, "get audio latency mode : " + _audioLatencyMode);
295                 return _audioLatencyMode;
296             }
297             set
298             {
299                 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
300                 ValidateNotDisposed();
301
302                 if (_audioLatencyMode == value)
303                 {
304                     return;
305                 }
306                 ValidationUtil.ValidateEnum(typeof(AudioLatencyMode), value);
307
308                 NativePlayer.SetAudioLatencyMode(Handle, value).
309                     ThrowIfFailed("Failed to set the audio latency mode of the player");
310
311                 _audioLatencyMode = value;
312             }
313         }
314
315         private bool _isLooping;
316
317         /// <summary>
318         /// Gets or sets the looping state.
319         /// </summary>
320         /// <value>true if the playback is looping; otherwise, false. The default value is false.</value>
321         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
322         public bool IsLooping
323         {
324             get
325             {
326                 Log.Info(PlayerLog.Tag, "get looping : " + _isLooping);
327                 return _isLooping;
328             }
329             set
330             {
331                 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
332                 ValidateNotDisposed();
333
334                 if (_isLooping == value)
335                 {
336                     return;
337                 }
338
339                 NativePlayer.SetLooping(Handle, value).ThrowIfFailed("Failed to set the looping state of the player");
340
341                 _isLooping = value;
342             }
343         }
344
345         #region Display methods
346         /// <summary>
347         /// Gets the display settings.
348         /// </summary>
349         /// <value>A <see cref="PlayerDisplaySettings"/> that specifies the display settings.</value>
350         public PlayerDisplaySettings DisplaySettings { get; }
351
352         private Display _display;
353
354         private PlayerErrorCode SetDisplay(Display display)
355         {
356             if (display == null)
357             {
358                 Log.Info(PlayerLog.Tag, "set display to none");
359                 return NativePlayer.SetDisplay(Handle, DisplayType.None, IntPtr.Zero);
360             }
361
362             return display.ApplyTo(this);
363         }
364
365         private void ReplaceDisplay(Display newDisplay)
366         {
367             _display?.SetOwner(null);
368             _display = newDisplay;
369             _display?.SetOwner(this);
370         }
371
372         /// <summary>
373         /// Gets or sets the display.
374         /// </summary>
375         /// <value>A <see cref="Multimedia.Display"/> that specifies the display.</value>
376         /// <remarks>The player must be in the <see cref="PlayerState.Idle"/> state.</remarks>
377         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
378         /// <exception cref="ArgumentException">The value has already been assigned to another player.</exception>
379         /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
380         public Display Display
381         {
382             get
383             {
384                 return _display;
385             }
386             set
387             {
388                 ValidatePlayerState(PlayerState.Idle);
389
390                 if (value?.Owner != null)
391                 {
392                     if (ReferenceEquals(this, value.Owner))
393                     {
394                         return;
395                     }
396
397                     throw new ArgumentException("The display has already been assigned to another.");
398                 }
399                 SetDisplay(value).ThrowIfFailed("Failed to set the display to the player");
400
401                 ReplaceDisplay(value);
402             }
403         }
404
405         PlayerErrorCode IDisplayable<PlayerErrorCode>.ApplyEvasDisplay(DisplayType type, ElmSharp.EvasObject evasObject)
406         {
407             Debug.Assert(IsDisposed == false);
408
409             Debug.Assert(Enum.IsDefined(typeof(DisplayType), type));
410
411             return NativePlayer.SetDisplay(Handle, type, evasObject);
412         }
413         #endregion
414
415         private PlayerTrackInfo _audioTrack;
416
417         /// <summary>
418         /// Gets the track info for audio.
419         /// </summary>
420         /// <value>A <see cref="PlayerTrackInfo"/> for audio.</value>
421         public PlayerTrackInfo AudioTrackInfo
422         {
423             get
424             {
425                 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
426                 if (_audioTrack == null)
427                 {
428                     _audioTrack = new PlayerTrackInfo(this, StreamType.Audio);
429                 }
430                 return _audioTrack;
431             }
432         }
433
434         private PlayerTrackInfo _subtitleTrackInfo;
435
436         /// <summary>
437         /// Gets the track info for subtitle.
438         /// </summary>
439         /// <value>A <see cref="PlayerTrackInfo"/> for subtitle.</value>
440         public PlayerTrackInfo SubtitleTrackInfo
441         {
442             get
443             {
444                 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
445                 if (_subtitleTrackInfo == null)
446                 {
447                     _subtitleTrackInfo = new PlayerTrackInfo(this, StreamType.Text);
448                 }
449                 return _subtitleTrackInfo;
450             }
451         }
452
453         private StreamInfo _streamInfo;
454
455         /// <summary>
456         /// Gets the stream information.
457         /// </summary>
458         /// <value>A <see cref="StreamInfo"/> for this player.</value>
459         public StreamInfo StreamInfo
460         {
461             get
462             {
463                 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
464                 if (_streamInfo == null)
465                 {
466                     _streamInfo = new StreamInfo(this);
467                 }
468                 return _streamInfo;
469             }
470         }
471
472         private readonly AudioEffect _audioEffect;
473
474         /// <summary>
475         /// Gets the audio effect.
476         /// </summary>
477         /// <feature>http://tizen.org/feature/multimedia.custom_audio_effect</feature>
478         /// <exception cref="NotSupportedException">The required feature is not supported.</exception>
479         public AudioEffect AudioEffect
480         {
481             get
482             {
483                 if (_audioEffect == null)
484                 {
485                     throw new NotSupportedException($"The feature({Features.AudioEffect}) is not supported.");
486                 }
487
488                 return _audioEffect;
489             }
490         }
491
492         #endregion
493
494         #region Dispose support
495         private bool _disposed;
496
497         public void Dispose()
498         {
499             Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
500             Dispose(true);
501         }
502
503         private void Dispose(bool disposing)
504         {
505             if (!_disposed)
506             {
507                 ReplaceDisplay(null);
508
509                 if (_source != null)
510                 {
511                     try
512                     {
513                         _source.DetachFrom(this);
514                     }
515                     catch (Exception e)
516                     {
517                         Log.Error(PlayerLog.Tag, e.ToString());
518                     }
519                 }
520                 _source = null;
521
522                 if (_handle != null)
523                 {
524                     _handle.Dispose();
525                 }
526                 _disposed = true;
527             }
528         }
529
530         internal void ValidateNotDisposed()
531         {
532             if (_disposed)
533             {
534                 Log.Warn(PlayerLog.Tag, "player was disposed");
535                 throw new ObjectDisposedException(nameof(Player));
536             }
537         }
538
539         internal bool IsDisposed => _disposed;
540         #endregion
541
542         #region Methods
543
544         /// <summary>
545         /// Gets or sets the mute state.
546         /// </summary>
547         /// <value>true if the player is muted; otherwise, false.</value>
548         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
549         public bool Muted
550         {
551             get
552             {
553                 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
554
555                 bool value = false;
556                 NativePlayer.IsMuted(Handle, out value).ThrowIfFailed("Failed to get the mute state of the player");
557
558                 Log.Info(PlayerLog.Tag, "get mute : " + value);
559
560                 return value;
561             }
562             set
563             {
564                 NativePlayer.SetMute(Handle, value).ThrowIfFailed("Failed to set the mute state of the player");
565             }
566         }
567
568         /// <summary>
569         /// Get Streaming download Progress.
570         /// </summary>
571         /// <remarks>The player must be in the <see cref="PlayerState.Playing"/> or <see cref="PlayerState.Paused"/> state.</remarks>
572         /// <exception cref="InvalidOperationException">
573         ///     The player is not streaming.\n
574         ///     -or-\n
575         ///     The player is not in the valid state.
576         ///     </exception>
577         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
578         public DownloadProgress GetDownloadProgress()
579         {
580             Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
581             ValidatePlayerState(PlayerState.Playing, PlayerState.Paused);
582
583             int start = 0;
584             int current = 0;
585             NativePlayer.GetStreamingDownloadProgress(Handle, out start, out current).
586                 ThrowIfFailed("Failed to get download progress");
587
588             Log.Info(PlayerLog.Tag, "get download progress : " + start + ", " + current);
589
590             return new DownloadProgress(start, current);
591         }
592
593         #region Volume
594         /// <summary>
595         /// Gets or sets the current volume.
596         /// </summary>
597         /// <remarks>Valid volume range is from 0 to 1.0, inclusive.</remarks>
598         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
599         /// <exception cref="ArgumentOutOfRangeException">
600         ///     <paramref name="value"/> is less than zero.\n
601         ///     -or-\n
602         ///     <paramref name="value"/> is greater than 1.0.
603         /// </exception>
604         public float Volume
605         {
606             get
607             {
608                 float value = 0.0F;
609                 NativePlayer.GetVolume(Handle, out value, out value).
610                     ThrowIfFailed("Failed to get the volume of the player");
611                 return value;
612             }
613             set
614             {
615                 if (value < 0F || 1.0F < value)
616                 {
617                     throw new ArgumentOutOfRangeException(nameof(value), value,
618                         $"Valid volume range is 0 <= value <= 1.0, but got { value }.");
619                 }
620
621                 NativePlayer.SetVolume(Handle, value, value).
622                     ThrowIfFailed("Failed to set the volume of the player");
623             }
624         }
625
626         #endregion
627
628         /// <summary>
629         /// Sets the subtitle path for playback.
630         /// </summary>
631         /// <remarks>Only MicroDVD/SubViewer(*.sub), SAMI(*.smi), and SubRip(*.srt) subtitle formats are supported.
632         ///     <para>The mediastorage privilege(http://tizen.org/privilege/mediastorage) must be added if any files are used to play located in the internal storage.
633         ///     The externalstorage privilege(http://tizen.org/privilege/externalstorage) must be added if any files are used to play located in the external storage.</para>
634         /// </remarks>
635         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
636         /// <exception cref="ArgumentException"><paramref name="path"/> is an empty string.</exception>
637         /// <exception cref="FileNotFoundException">The specified path does not exist.</exception>
638         /// <exception cref="ArgumentNullException">The path is null.</exception>
639         public void SetSubtitle(string path)
640         {
641             Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
642             ValidateNotDisposed();
643
644             if (path == null)
645             {
646                 throw new ArgumentNullException(nameof(path));
647             }
648
649             if (path.Length == 0)
650             {
651                 throw new ArgumentException("The path is empty.", nameof(path));
652             }
653
654             if (!File.Exists(path))
655             {
656                 throw new FileNotFoundException($"The specified file does not exist.", path);
657             }
658
659             NativePlayer.SetSubtitlePath(Handle, path).
660                 ThrowIfFailed("Failed to set the subtitle path to the player");
661
662             Log.Debug(PlayerLog.Tag, PlayerLog.Leave);
663         }
664
665         /// <summary>
666         /// Removes the subtitle path.
667         /// </summary>
668         /// <remarks>The player must be in the <see cref="PlayerState.Idle"/> state.</remarks>
669         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
670         /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
671         public void ClearSubtitle()
672         {
673             Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
674             ValidatePlayerState(PlayerState.Idle);
675
676             NativePlayer.SetSubtitlePath(Handle, null).
677                 ThrowIfFailed("Failed to clear the subtitle of the player");
678             Log.Debug(PlayerLog.Tag, PlayerLog.Leave);
679         }
680
681         /// <summary>
682         /// Sets the offset for the subtitle.
683         /// </summary>
684         /// <remarks>The player must be in the <see cref="PlayerState.Playing"/> or <see cref="PlayerState.Paused"/> state.</remarks>
685         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
686         /// <exception cref="InvalidOperationException">
687         ///     The player is not in the valid state.\n
688         ///     -or-\n
689         ///     No subtitle is set.
690         /// </exception>
691         /// <seealso cref="SetSubtitle(string)"/>
692         public void SetSubtitleOffset(int offset)
693         {
694             Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
695             ValidatePlayerState(PlayerState.Playing, PlayerState.Paused);
696
697             var err = NativePlayer.SetSubtitlePositionOffset(Handle, offset);
698
699             if (err == PlayerErrorCode.FeatureNotSupported)
700             {
701                 throw new InvalidOperationException("No subtitle set");
702             }
703
704             err.ThrowIfFailed("Failed to the subtitle offset of the player");
705             Log.Debug(PlayerLog.Tag, PlayerLog.Leave);
706         }
707
708         private void Prepare()
709         {
710             Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
711             NativePlayer.Prepare(Handle).ThrowIfFailed("Failed to prepare the player");
712         }
713
714         protected virtual void OnPreparing()
715         {
716             RegisterCallbacks();
717         }
718
719         /// <summary>
720         /// Prepares the media player for playback, asynchronously.
721         /// </summary>
722         /// <remarks>To prepare the player, the player must be in the <see cref="PlayerState.Idle"/> state,
723         ///     and a source must be set.</remarks>
724         /// <exception cref="InvalidOperationException">No source is set.</exception>
725         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
726         /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
727         public virtual Task PrepareAsync()
728         {
729             Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
730             if (_source == null)
731             {
732                 throw new InvalidOperationException("No source is set.");
733             }
734
735             ValidatePlayerState(PlayerState.Idle);
736
737             OnPreparing();
738
739             var completionSource = new TaskCompletionSource<bool>();
740
741             SetPreparing();
742
743             Task.Run(() =>
744             {
745                 try
746                 {
747                     Prepare();
748                     ClearPreparing();
749                     completionSource.SetResult(true);
750                 }
751                 catch (Exception e)
752                 {
753                     ClearPreparing();
754                     completionSource.TrySetException(e);
755                 }
756             });
757             Log.Debug(PlayerLog.Tag, PlayerLog.Leave);
758
759             return completionSource.Task;
760         }
761
762         /// <summary>
763         /// Unprepares the player.
764         /// </summary>
765         /// <remarks>
766         ///     The most recently used source is reset and no longer associated with the player. Playback is no longer possible.
767         ///     If you want to use the player again, you have to set a source and call <see cref="PrepareAsync"/> again.
768         ///     <para>
769         ///     The player must be in the <see cref="PlayerState.Ready"/>, <see cref="PlayerState.Playing"/> or <see cref="PlayerState.Paused"/> state.
770         ///     It has no effect if the player is already in the <see cref="PlayerState.Idle"/> state.
771         ///     </para>
772         /// </remarks>
773         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
774         /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
775         public virtual void Unprepare()
776         {
777             Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
778             if (State == PlayerState.Idle)
779             {
780                 Log.Warn(PlayerLog.Tag, "idle state already");
781                 return;
782             }
783             ValidatePlayerState(PlayerState.Ready, PlayerState.Paused, PlayerState.Playing);
784
785             NativePlayer.Unprepare(Handle).ThrowIfFailed("Failed to unprepare the player");
786
787             OnUnprepared();
788         }
789
790         protected virtual void OnUnprepared()
791         {
792             _source?.DetachFrom(this);
793             _source = null;
794         }
795
796         //TODO remarks needs to be updated. see the native reference.
797         /// <summary>
798         /// Starts or resumes playback.
799         /// </summary>
800         /// <remarks>
801         /// The player must be in the <see cref="PlayerState.Ready"/> or <see cref="PlayerState.Paused"/> state.
802         /// It has no effect if the player is already in the <see cref="PlayerState.Playing"/> state.
803         /// </remarks>
804         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
805         /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
806         /// <seealso cref="PrepareAsync"/>
807         /// <seealso cref="Stop"/>
808         /// <seealso cref="Pause"/>
809         /// <seealso cref="PlaybackCompleted"/>
810         public virtual void Start()
811         {
812             Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
813             if (State == PlayerState.Playing)
814             {
815                 Log.Warn(PlayerLog.Tag, "playing state already");
816                 return;
817             }
818             ValidatePlayerState(PlayerState.Ready, PlayerState.Paused);
819
820             NativePlayer.Start(Handle).ThrowIfFailed("Failed to start the player");
821             Log.Debug(PlayerLog.Tag, PlayerLog.Leave);
822         }
823
824         /// <summary>
825         /// Stops playing media content.
826         /// </summary>
827         /// <remarks>
828         /// The player must be in the <see cref="PlayerState.Playing"/> or <see cref="PlayerState.Paused"/> state.
829         /// It has no effect if the player is already in the <see cref="PlayerState.Ready"/> state.
830         /// </remarks>
831         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
832         /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
833         /// <seealso cref="Start"/>
834         /// <seealso cref="Pause"/>
835         public virtual void Stop()
836         {
837             Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
838             if (State == PlayerState.Ready)
839             {
840                 Log.Warn(PlayerLog.Tag, "ready state already");
841                 return;
842             }
843             ValidatePlayerState(PlayerState.Paused, PlayerState.Playing);
844
845             NativePlayer.Stop(Handle).ThrowIfFailed("Failed to stop the player");
846             Log.Debug(PlayerLog.Tag, PlayerLog.Leave);
847         }
848
849         /// <summary>
850         /// Pauses the player.
851         /// </summary>
852         /// <remarks>
853         /// The player must be in the <see cref="PlayerState.Playing"/> state.
854         /// It has no effect if the player is already in the <see cref="PlayerState.Paused"/> state.
855         /// </remarks>
856         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
857         /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
858         /// <seealso cref="Start"/>
859         public virtual void Pause()
860         {
861             Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
862             if (State == PlayerState.Paused)
863             {
864                 Log.Warn(PlayerLog.Tag, "pause state already");
865                 return;
866             }
867
868             ValidatePlayerState(PlayerState.Playing);
869
870             NativePlayer.Pause(Handle).ThrowIfFailed("Failed to pause the player");
871             Log.Debug(PlayerLog.Tag, PlayerLog.Leave);
872         }
873
874         private MediaSource _source;
875
876         /// <summary>
877         /// Sets a media source for the player.
878         /// </summary>
879         /// <param name="source">A <see cref="MediaSource"/> that specifies the source for playback.</param>
880         /// <remarks>The player must be in the <see cref="PlayerState.Idle"/> state.</remarks>
881         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
882         /// <exception cref="InvalidOperationException">
883         ///     The player is not in the valid state.\n
884         ///     -or-\n
885         ///     It is not able to assign the source to the player.
886         ///     </exception>
887         /// <seealso cref="PrepareAsync"/>
888         public void SetSource(MediaSource source)
889         {
890             Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
891             ValidatePlayerState(PlayerState.Idle);
892
893             if (source != null)
894             {
895                 source.AttachTo(this);
896             }
897
898             if (_source != null)
899             {
900                 _source.DetachFrom(this);
901             }
902
903             _source = source;
904             Log.Debug(PlayerLog.Tag, PlayerLog.Leave);
905         }
906
907         /// <summary>
908         /// Captures a video frame asynchronously.
909         /// </summary>
910         /// <feature>http://tizen.org/feature/multimedia.raw_video</feature>
911         /// <remarks>The player must be in the <see cref="PlayerState.Playing"/> or <see cref="PlayerState.Paused"/> state.</remarks>
912         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
913         /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
914         /// <exception cref="NotSupportedException">The required feature is not supported.</exception>
915         public async Task<CapturedFrame> CaptureVideoAsync()
916         {
917             Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
918
919             ValidationUtil.ValidateFeatureSupported(Features.RawVideo);
920
921             ValidatePlayerState(PlayerState.Playing, PlayerState.Paused);
922
923             TaskCompletionSource<CapturedFrame> t = new TaskCompletionSource<CapturedFrame>();
924
925             NativePlayer.VideoCaptureCallback cb = (data, width, height, size, _) =>
926             {
927                 Debug.Assert(size <= int.MaxValue);
928
929                 byte[] buf = new byte[size];
930                 Marshal.Copy(data, buf, 0, (int)size);
931
932                 t.TrySetResult(new CapturedFrame(buf, width, height));
933             };
934
935             using (var cbKeeper = ObjectKeeper.Get(cb))
936             {
937                 NativePlayer.CaptureVideo(Handle, cb)
938                     .ThrowIfFailed("Failed to capture the video");
939
940                 return await t.Task;
941             }
942         }
943
944         /// <summary>
945         /// Gets the play position in milliseconds.
946         /// </summary>
947         /// <remarks>The player must be in the <see cref="PlayerState.Ready"/>, <see cref="PlayerState.Playing"/> or <see cref="PlayerState.Paused"/> state.</remarks>
948         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
949         /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
950         /// <seealso cref="SetPlayPositionAsync(int, bool)"/>
951         public int GetPlayPosition()
952         {
953             Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
954             ValidatePlayerState(PlayerState.Ready, PlayerState.Paused, PlayerState.Playing);
955
956             int playPosition = 0;
957
958             NativePlayer.GetPlayPosition(Handle, out playPosition).
959                 ThrowIfFailed("Failed to get the play position of the player");
960
961             Log.Info(PlayerLog.Tag, "get play position : " + playPosition);
962
963             return playPosition;
964         }
965
966         private void SetPlayPosition(int milliseconds, bool accurate,
967             NativePlayer.SeekCompletedCallback cb)
968         {
969             Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
970             var ret = NativePlayer.SetPlayPosition(Handle, milliseconds, accurate, cb, IntPtr.Zero);
971
972             //Note that we assume invalid param error is returned only when the position value is invalid.
973             if (ret == PlayerErrorCode.InvalidArgument)
974             {
975                 throw new ArgumentOutOfRangeException(nameof(milliseconds), milliseconds,
976                     "The position is not valid.");
977             }
978             if (ret != PlayerErrorCode.None)
979             {
980                 Log.Error(PlayerLog.Tag, "Failed to set play position, " + (PlayerError)ret);
981             }
982             ret.ThrowIfFailed("Failed to set play position");
983         }
984
985         /// <summary>
986         /// Sets the seek position for playback, asynchronously.
987         /// </summary>
988         /// <param name="position">The value indicating a desired position in milliseconds.</param>
989         /// <param name="accurate">The value indicating whether the operation performs with accuracy.</param>
990         /// <remarks>
991         ///     <para>The player must be in the <see cref="PlayerState.Ready"/>, <see cref="PlayerState.Playing"/> or <see cref="PlayerState.Paused"/> state.</para>
992         ///     <para>If the <paramref name="accurate"/> is true, the play position will be adjusted as the specified <paramref name="position"/> value,
993         ///     but this might be considerably slow. If false, the play position will be a nearest keyframe position.</para>
994         ///     </remarks>
995         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
996         /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
997         /// <exception cref="ArgumentOutOfRangeException">The specified position is not valid.</exception>
998         /// <seealso cref="GetPlayPosition"/>
999         public async Task SetPlayPositionAsync(int position, bool accurate)
1000         {
1001             Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
1002             ValidatePlayerState(PlayerState.Ready, PlayerState.Playing, PlayerState.Paused);
1003
1004             var taskCompletionSource = new TaskCompletionSource<bool>();
1005
1006             bool immediateResult = _source is MediaStreamSource;
1007
1008             NativePlayer.SeekCompletedCallback cb = _ => taskCompletionSource.TrySetResult(true);
1009
1010             using (var cbKeeper = ObjectKeeper.Get(cb))
1011             {
1012                 SetPlayPosition(position, accurate, cb);
1013                 if (immediateResult)
1014                 {
1015                     taskCompletionSource.TrySetResult(true);
1016                 }
1017
1018                 await taskCompletionSource.Task;
1019             }
1020
1021             Log.Debug(PlayerLog.Tag, PlayerLog.Leave);
1022         }
1023
1024         /// <summary>
1025         /// Sets playback rate.
1026         /// </summary>
1027         /// <param name="rate">The value for the playback rate. Valid range is -5.0 to 5.0, inclusive.</param>
1028         /// <remarks>
1029         ///     <para>The player must be in the <see cref="PlayerState.Ready"/>, <see cref="PlayerState.Playing"/> or <see cref="PlayerState.Paused"/> state.</para>
1030         ///     <para>The sound will be muted, when the playback rate is under 0.0 or over 2.0.</para>
1031         /// </remarks>
1032         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
1033         /// <exception cref="InvalidOperationException">
1034         ///     The player is not in the valid state.\n
1035         ///     -or-\n
1036         ///     Streaming playback.
1037         /// </exception>
1038         /// <exception cref="ArgumentOutOfRangeException">
1039         ///     <paramref name="rate"/> is less than 5.0.\n
1040         ///     -or-\n
1041         ///     <paramref name="rate"/> is greater than 5.0.\n
1042         ///     -or-\n
1043         ///     <paramref name="rate"/> is zero.
1044         /// </exception>
1045         public void SetPlaybackRate(float rate)
1046         {
1047             Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
1048             if (rate < -5.0F || 5.0F < rate || rate == 0.0F)
1049             {
1050                 throw new ArgumentOutOfRangeException(nameof(rate), rate, "Valid range is -5.0 to 5.0 (except 0.0)");
1051             }
1052
1053             ValidatePlayerState(PlayerState.Ready, PlayerState.Playing, PlayerState.Paused);
1054
1055             NativePlayer.SetPlaybackRate(Handle, rate).ThrowIfFailed("Failed to set the playback rate.");
1056             Log.Debug(PlayerLog.Tag, PlayerLog.Leave);
1057         }
1058
1059         /// <summary>
1060         /// Applies the audio stream policy.
1061         /// </summary>
1062         /// <param name="policy">The <see cref="AudioStreamPolicy"/> to apply.</param>
1063         /// <remarks>The player must be in the <see cref="PlayerState.Idle"/> state.</remarks>
1064         /// <exception cref="ObjectDisposedException">
1065         ///     The player has already been disposed of.\n
1066         ///     -or-\n
1067         ///     <paramref name="policy"/> has already been disposed of.
1068         /// </exception>
1069         /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
1070         /// <exception cref="ArgumentNullException"><paramref name="policy"/> is null.</exception>
1071         public void ApplyAudioStreamPolicy(AudioStreamPolicy policy)
1072         {
1073             Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
1074             if (policy == null)
1075             {
1076                 throw new ArgumentNullException(nameof(policy));
1077             }
1078
1079             if (policy.Handle == IntPtr.Zero)
1080             {
1081                 throw new ObjectDisposedException(nameof(policy));
1082             }
1083
1084             ValidatePlayerState(PlayerState.Idle);
1085
1086             NativePlayer.SetAudioPolicyInfo(Handle, policy.Handle).
1087                 ThrowIfFailed("Failed to set the audio stream policy to the player");
1088         }
1089         #endregion
1090
1091         #region Callback registrations
1092         private void RegisterSubtitleUpdatedCallback()
1093         {
1094             _subtitleUpdatedCallback = (duration, text, _) =>
1095             {
1096                 Log.Debug(PlayerLog.Tag, "duration : " + duration + ", text : " + text);
1097                 SubtitleUpdated?.Invoke(this, new SubtitleUpdatedEventArgs(duration, text));
1098             };
1099
1100             NativePlayer.SetSubtitleUpdatedCb(Handle, _subtitleUpdatedCallback).
1101                 ThrowIfFailed("Failed to initialize the player");
1102         }
1103
1104         private void RegisterPlaybackCompletedCallback()
1105         {
1106             _playbackCompletedCallback = _ =>
1107             {
1108                 Log.Debug(PlayerLog.Tag, "completed callback");
1109                 PlaybackCompleted?.Invoke(this, EventArgs.Empty);
1110             };
1111             NativePlayer.SetCompletedCb(Handle, _playbackCompletedCallback).
1112                 ThrowIfFailed("Failed to set PlaybackCompleted");
1113         }
1114
1115         private void RegisterPlaybackInterruptedCallback()
1116         {
1117             _playbackInterruptedCallback = (code, _) =>
1118             {
1119                 if (!Enum.IsDefined(typeof(PlaybackInterruptionReason), code))
1120                 {
1121                     return;
1122                 }
1123
1124                 if (code == PlaybackInterruptionReason.ResourceConflict)
1125                 {
1126                     OnUnprepared();
1127                 }
1128
1129                 Log.Warn(PlayerLog.Tag, "interrupted reason : " + code);
1130                 PlaybackInterrupted?.Invoke(this, new PlaybackInterruptedEventArgs(code));
1131             };
1132
1133             NativePlayer.SetInterruptedCb(Handle, _playbackInterruptedCallback).
1134                 ThrowIfFailed("Failed to set PlaybackInterrupted");
1135         }
1136
1137         private void RegisterErrorOccurredCallback()
1138         {
1139             _playbackErrorCallback = (code, _) =>
1140             {
1141                 //TODO handle service disconnected error.
1142                 Log.Warn(PlayerLog.Tag, "error code : " + code);
1143                 ErrorOccurred?.Invoke(this, new PlayerErrorOccurredEventArgs((PlayerError)code));
1144             };
1145
1146             NativePlayer.SetErrorCb(Handle, _playbackErrorCallback).
1147                 ThrowIfFailed("Failed to set PlaybackError");
1148         }
1149
1150         #region VideoFrameDecoded event
1151
1152         private EventHandler<VideoFrameDecodedEventArgs> _videoFrameDecoded;
1153
1154         private NativePlayer.VideoFrameDecodedCallback _videoFrameDecodedCallback;
1155
1156         /// <summary>
1157         /// Occurs when a video frame is decoded.
1158         /// </summary>
1159         /// <remarks>
1160         ///     <para>The event handler will be executed on an internal thread.</para>
1161         ///     <para>The <see cref="VideoFrameDecodedEventArgs.Packet"/> in event args should be disposed after use.</para>
1162         /// </remarks>
1163         /// <feature>http://tizen.org/feature/multimedia.raw_video</feature>
1164         /// <exception cref="NotSupportedException">The required feature is not supported.</exception>
1165         /// <seealso cref="VideoFrameDecodedEventArgs.Packet"/>
1166         public event EventHandler<VideoFrameDecodedEventArgs> VideoFrameDecoded
1167         {
1168             add
1169             {
1170                 ValidationUtil.ValidateFeatureSupported(Features.RawVideo);
1171
1172                 _videoFrameDecoded += value;
1173             }
1174             remove
1175             {
1176                 ValidationUtil.ValidateFeatureSupported(Features.RawVideo);
1177
1178                 _videoFrameDecoded -= value;
1179             }
1180         }
1181
1182         private void RegisterVideoFrameDecodedCallback()
1183         {
1184             _videoFrameDecodedCallback = (packetHandle, _) =>
1185             {
1186                 var handler = _videoFrameDecoded;
1187                 if (handler != null)
1188                 {
1189                     Log.Debug(PlayerLog.Tag, "packet : " + packetHandle);
1190                     handler.Invoke(this,
1191                         new VideoFrameDecodedEventArgs(MediaPacket.From(packetHandle)));
1192                 }
1193                 else
1194                 {
1195                     MediaPacket.From(packetHandle).Dispose();
1196                 }
1197             };
1198
1199             NativePlayer.SetVideoFrameDecodedCb(Handle, _videoFrameDecodedCallback).
1200                 ThrowIfFailed("Failed to register the VideoFrameDecoded");
1201         }
1202         #endregion
1203
1204         private void RegisterVideoStreamChangedCallback()
1205         {
1206             ValidatePlayerState(PlayerState.Idle);
1207
1208             _videoStreamChangedCallback = (width, height, fps, bitrate, _) =>
1209             {
1210                 Log.Debug(PlayerLog.Tag, "height : " + height + ", width : " + width
1211                 + ", fps : " + fps + ", bitrate : " + bitrate);
1212
1213                 VideoStreamChanged?.Invoke(this, new VideoStreamChangedEventArgs(height, width, fps, bitrate));
1214             };
1215
1216             NativePlayer.SetVideoStreamChangedCb(Handle, _videoStreamChangedCallback).
1217                 ThrowIfFailed("Failed to set the video stream changed callback");
1218         }
1219
1220         private void RegisterBufferingCallback()
1221         {
1222             _bufferingProgressCallback = (percent, _) =>
1223             {
1224                 Log.Debug(PlayerLog.Tag, $"Buffering callback with percent { percent }");
1225                 BufferingProgressChanged?.Invoke(this, new BufferingProgressChangedEventArgs(percent));
1226             };
1227
1228             NativePlayer.SetBufferingCb(Handle, _bufferingProgressCallback).
1229                 ThrowIfFailed("Failed to set BufferingProgress");
1230         }
1231
1232         private void RegisterMediaStreamBufferStatusCallback()
1233         {
1234             _mediaStreamAudioBufferStatusChangedCallback = (status, _) =>
1235             {
1236                 Debug.Assert(Enum.IsDefined(typeof(MediaStreamBufferStatus), status));
1237                 Log.Debug(PlayerLog.Tag, "audio buffer status : " + status);
1238                 MediaStreamAudioBufferStatusChanged?.Invoke(this,
1239                     new MediaStreamBufferStatusChangedEventArgs(status));
1240             };
1241             _mediaStreamVideoBufferStatusChangedCallback = (status, _) =>
1242             {
1243                 Debug.Assert(Enum.IsDefined(typeof(MediaStreamBufferStatus), status));
1244                 Log.Debug(PlayerLog.Tag, "video buffer status : " + status);
1245                 MediaStreamVideoBufferStatusChanged?.Invoke(this,
1246                     new MediaStreamBufferStatusChangedEventArgs(status));
1247             };
1248
1249             RegisterMediaStreamBufferStatusCallback(StreamType.Audio, _mediaStreamAudioBufferStatusChangedCallback);
1250             RegisterMediaStreamBufferStatusCallback(StreamType.Video, _mediaStreamVideoBufferStatusChangedCallback);
1251         }
1252
1253         private void RegisterMediaStreamBufferStatusCallback(StreamType streamType,
1254             NativePlayer.MediaStreamBufferStatusCallback cb)
1255         {
1256             NativePlayer.SetMediaStreamBufferStatusCb(Handle, streamType, cb).
1257                 ThrowIfFailed("Failed to SetMediaStreamBufferStatus");
1258         }
1259
1260         private void RegisterMediaStreamSeekCallback()
1261         {
1262             _mediaStreamAudioSeekCallback = (offset, _) =>
1263             {
1264                 Log.Debug(PlayerLog.Tag, "audio seeking offset : " + offset);
1265                 MediaStreamAudioSeekingOccurred?.Invoke(this, new MediaStreamSeekingOccurredEventArgs(offset));
1266             };
1267             _mediaStreamVideoSeekCallback = (offset, _) =>
1268             {
1269                 Log.Debug(PlayerLog.Tag, "video seeking offset : " + offset);
1270                 MediaStreamVideoSeekingOccurred?.Invoke(this, new MediaStreamSeekingOccurredEventArgs(offset));
1271             };
1272
1273             RegisterMediaStreamSeekCallback(StreamType.Audio, _mediaStreamAudioSeekCallback);
1274             RegisterMediaStreamSeekCallback(StreamType.Video, _mediaStreamVideoSeekCallback);
1275         }
1276
1277         private void RegisterMediaStreamSeekCallback(StreamType streamType, NativePlayer.MediaStreamSeekCallback cb)
1278         {
1279             NativePlayer.SetMediaStreamSeekCb(Handle, streamType, cb).
1280                 ThrowIfFailed("Failed to SetMediaStreamSeek");
1281         }
1282         #endregion
1283
1284         #region Preparing state
1285
1286         private int _isPreparing;
1287
1288         private bool IsPreparing()
1289         {
1290             return Interlocked.CompareExchange(ref _isPreparing, 1, 1) == 1;
1291         }
1292
1293         private void SetPreparing()
1294         {
1295             Interlocked.Exchange(ref _isPreparing, 1);
1296         }
1297
1298         private void ClearPreparing()
1299         {
1300             Interlocked.Exchange(ref _isPreparing, 0);
1301         }
1302
1303         #endregion
1304     }
1305 }