Fixed typos.
[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             Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
357             if (display == null)
358             {
359                 Log.Info(PlayerLog.Tag, "set display to none");
360                 return NativePlayer.SetDisplay(Handle, DisplayType.None, IntPtr.Zero);
361             }
362
363             return display.ApplyTo(this);
364         }
365
366         private void ReplaceDisplay(Display newDisplay)
367         {
368             if (_display != null)
369             {
370                 _display.Owner = null;
371             }
372             _display = newDisplay;
373             if (_display != null)
374             {
375                 _display.Owner = this;
376             }
377         }
378
379         /// <summary>
380         /// Gets or sets the display.
381         /// </summary>
382         /// <value>A <see cref="Multimedia.Display"/> that specifies the display.</value>
383         /// <remarks>The player must be in the <see cref="PlayerState.Idle"/> state.</remarks>
384         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
385         /// <exception cref="ArgumentException">The value has already been assigned to another player.</exception>
386         /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
387         public Display Display
388         {
389             get
390             {
391                 return _display;
392             }
393             set
394             {
395                 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
396                 ValidatePlayerState(PlayerState.Idle);
397
398                 if (value != null && value.Owner != null)
399                 {
400                     if (ReferenceEquals(this, value.Owner))
401                     {
402                         return;
403                     }
404                     else
405                     {
406                         throw new ArgumentException("The display has already been assigned to another.");
407                     }
408                 }
409
410                 SetDisplay(value).ThrowIfFailed("Failed to set the display to the player");
411
412                 ReplaceDisplay(value);
413                 Log.Debug(PlayerLog.Tag, PlayerLog.Leave);
414             }
415         }
416
417         PlayerErrorCode IDisplayable<PlayerErrorCode>.ApplyEvasDisplay(DisplayType type, ElmSharp.EvasObject evasObject)
418         {
419             Debug.Assert(IsDisposed == false);
420
421             Debug.Assert(Enum.IsDefined(typeof(DisplayType), type));
422
423             return NativePlayer.SetDisplay(Handle, type, evasObject);
424         }
425         #endregion
426
427         private PlayerTrackInfo _audioTrack;
428
429         /// <summary>
430         /// Gets the track info for audio.
431         /// </summary>
432         /// <value>A <see cref="PlayerTrackInfo"/> for audio.</value>
433         public PlayerTrackInfo AudioTrackInfo
434         {
435             get
436             {
437                 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
438                 if (_audioTrack == null)
439                 {
440                     _audioTrack = new PlayerTrackInfo(this, StreamType.Audio);
441                 }
442                 return _audioTrack;
443             }
444         }
445
446         private PlayerTrackInfo _subtitleTrackInfo;
447
448         /// <summary>
449         /// Gets the track info for subtitle.
450         /// </summary>
451         /// <value>A <see cref="PlayerTrackInfo"/> for subtitle.</value>
452         public PlayerTrackInfo SubtitleTrackInfo
453         {
454             get
455             {
456                 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
457                 if (_subtitleTrackInfo == null)
458                 {
459                     _subtitleTrackInfo = new PlayerTrackInfo(this, StreamType.Text);
460                 }
461                 return _subtitleTrackInfo;
462             }
463         }
464
465         private StreamInfo _streamInfo;
466
467         /// <summary>
468         /// Gets the stream information.
469         /// </summary>
470         /// <value>A <see cref="StreamInfo"/> for this player.</value>
471         public StreamInfo StreamInfo
472         {
473             get
474             {
475                 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
476                 if (_streamInfo == null)
477                 {
478                     _streamInfo = new StreamInfo(this);
479                 }
480                 return _streamInfo;
481             }
482         }
483
484         private readonly AudioEffect _audioEffect;
485
486         /// <summary>
487         /// Gets the audio effect.
488         /// </summary>
489         /// <feature>http://tizen.org/feature/multimedia.custom_audio_effect</feature>
490         /// <exception cref="NotSupportedException">The required feature is not supported.</exception>
491         public AudioEffect AudioEffect
492         {
493             get
494             {
495                 if (_audioEffect == null)
496                 {
497                     throw new NotSupportedException($"The feature({Features.AudioEffect}) is not supported.");
498                 }
499
500                 return _audioEffect;
501             }
502         }
503
504         #endregion
505
506         #region Dispose support
507         private bool _disposed;
508
509         public void Dispose()
510         {
511             Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
512             Dispose(true);
513         }
514
515         private void Dispose(bool disposing)
516         {
517             if (!_disposed)
518             {
519                 if (_source != null)
520                 {
521                     try
522                     {
523                         _source.DetachFrom(this);
524                     }
525                     catch (Exception e)
526                     {
527                         Log.Error(PlayerLog.Tag, e.ToString());
528                     }
529                 }
530                 _source = null;
531
532                 if (_handle != null)
533                 {
534                     _handle.Dispose();
535                 }
536                 _disposed = true;
537             }
538         }
539
540         internal void ValidateNotDisposed()
541         {
542             if (_disposed)
543             {
544                 Log.Warn(PlayerLog.Tag, "player was disposed");
545                 throw new ObjectDisposedException(nameof(Player));
546             }
547         }
548
549         internal bool IsDisposed => _disposed;
550         #endregion
551
552         #region Methods
553
554         /// <summary>
555         /// Gets or sets the mute state.
556         /// </summary>
557         /// <value>true if the player is muted; otherwise, false.</value>
558         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
559         public bool Muted
560         {
561             get
562             {
563                 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
564
565                 bool value = false;
566                 NativePlayer.IsMuted(Handle, out value).ThrowIfFailed("Failed to get the mute state of the player");
567
568                 Log.Info(PlayerLog.Tag, "get mute : " + value);
569
570                 return value;
571             }
572             set
573             {
574                 NativePlayer.SetMute(Handle, value).ThrowIfFailed("Failed to set the mute state of the player");
575             }
576         }
577
578         /// <summary>
579         /// Get Streaming download Progress.
580         /// </summary>
581         /// <remarks>The player must be in the <see cref="PlayerState.Playing"/> or <see cref="PlayerState.Paused"/> state.</remarks>
582         /// <exception cref="InvalidOperationException">
583         ///     The player is not streaming.\n
584         ///     -or-\n
585         ///     The player is not in the valid state.
586         ///     </exception>
587         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
588         public DownloadProgress GetDownloadProgress()
589         {
590             Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
591             ValidatePlayerState(PlayerState.Playing, PlayerState.Paused);
592
593             int start = 0;
594             int current = 0;
595             NativePlayer.GetStreamingDownloadProgress(Handle, out start, out current).
596                 ThrowIfFailed("Failed to get download progress");
597
598             Log.Info(PlayerLog.Tag, "get download progress : " + start + ", " + current);
599
600             return new DownloadProgress(start, current);
601         }
602
603         #region Volume
604         /// <summary>
605         /// Gets or sets the current volume.
606         /// </summary>
607         /// <remarks>Valid volume range is from 0 to 1.0, inclusive.</remarks>
608         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
609         /// <exception cref="ArgumentOutOfRangeException">
610         ///     <paramref name="value"/> is less than zero.\n
611         ///     -or-\n
612         ///     <paramref name="value"/> is greater than 1.0.
613         /// </exception>
614         public float Volume
615         {
616             get
617             {
618                 float value = 0.0F;
619                 NativePlayer.GetVolume(Handle, out value, out value).
620                     ThrowIfFailed("Failed to get the volume of the player");
621                 return value;
622             }
623             set
624             {
625                 if (value < 0F || 1.0F < value)
626                 {
627                     throw new ArgumentOutOfRangeException(nameof(value), value,
628                         $"Valid volume range is 0 <= value <= 1.0, but got { value }.");
629                 }
630
631                 NativePlayer.SetVolume(Handle, value, value).
632                     ThrowIfFailed("Failed to set the volume of the player");
633             }
634         }
635
636         #endregion
637
638         /// <summary>
639         /// Sets the subtitle path for playback.
640         /// </summary>
641         /// <remarks>Only MicroDVD/SubViewer(*.sub), SAMI(*.smi), and SubRip(*.srt) subtitle formats are supported.
642         ///     <para>The mediastorage privilege(http://tizen.org/privilege/mediastorage) must be added if any files are used to play located in the internal storage.
643         ///     The externalstorage privilege(http://tizen.org/privilege/externalstorage) must be added if any files are used to play located in the external storage.</para>
644         /// </remarks>
645         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
646         /// <exception cref="ArgumentException"><paramref name="path"/> is an empty string.</exception>
647         /// <exception cref="FileNotFoundException">The specified path does not exist.</exception>
648         /// <exception cref="ArgumentNullException">The path is null.</exception>
649         public void SetSubtitle(string path)
650         {
651             Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
652             ValidateNotDisposed();
653
654             if (path == null)
655             {
656                 throw new ArgumentNullException(nameof(path));
657             }
658
659             if (path.Length == 0)
660             {
661                 throw new ArgumentException("The path is empty.", nameof(path));
662             }
663
664             if (!File.Exists(path))
665             {
666                 throw new FileNotFoundException($"The specified file does not exist.", path);
667             }
668
669             NativePlayer.SetSubtitlePath(Handle, path).
670                 ThrowIfFailed("Failed to set the subtitle path to the player");
671
672             Log.Debug(PlayerLog.Tag, PlayerLog.Leave);
673         }
674
675         /// <summary>
676         /// Removes the subtitle path.
677         /// </summary>
678         /// <remarks>The player must be in the <see cref="PlayerState.Idle"/> state.</remarks>
679         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
680         /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
681         public void ClearSubtitle()
682         {
683             Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
684             ValidatePlayerState(PlayerState.Idle);
685
686             NativePlayer.SetSubtitlePath(Handle, null).
687                 ThrowIfFailed("Failed to clear the subtitle of the player");
688             Log.Debug(PlayerLog.Tag, PlayerLog.Leave);
689         }
690
691         /// <summary>
692         /// Sets the offset for the subtitle.
693         /// </summary>
694         /// <remarks>The player must be in the <see cref="PlayerState.Playing"/> or <see cref="PlayerState.Paused"/> state.</remarks>
695         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
696         /// <exception cref="InvalidOperationException">
697         ///     The player is not in the valid state.\n
698         ///     -or-\n
699         ///     No subtitle is set.
700         /// </exception>
701         /// <seealso cref="SetSubtitle(string)"/>
702         public void SetSubtitleOffset(int offset)
703         {
704             Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
705             ValidatePlayerState(PlayerState.Playing, PlayerState.Paused);
706
707             var err = NativePlayer.SetSubtitlePositionOffset(Handle, offset);
708
709             if (err == PlayerErrorCode.FeatureNotSupported)
710             {
711                 throw new InvalidOperationException("No subtitle set");
712             }
713
714             err.ThrowIfFailed("Failed to the subtitle offset of the player");
715             Log.Debug(PlayerLog.Tag, PlayerLog.Leave);
716         }
717
718         private void Prepare()
719         {
720             Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
721             NativePlayer.Prepare(Handle).ThrowIfFailed("Failed to prepare the player");
722         }
723
724         protected virtual void OnPreparing()
725         {
726             RegisterCallbacks();
727         }
728
729         /// <summary>
730         /// Prepares the media player for playback, asynchronously.
731         /// </summary>
732         /// <remarks>To prepare the player, the player must be in the <see cref="PlayerState.Idle"/> state,
733         ///     and a source must be set.</remarks>
734         /// <exception cref="InvalidOperationException">No source is set.</exception>
735         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
736         /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
737         public virtual Task PrepareAsync()
738         {
739             Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
740             if (_source == null)
741             {
742                 throw new InvalidOperationException("No source is set.");
743             }
744
745             ValidatePlayerState(PlayerState.Idle);
746
747             OnPreparing();
748
749             var completionSource = new TaskCompletionSource<bool>();
750
751             SetPreparing();
752
753             Task.Run(() =>
754             {
755                 try
756                 {
757                     Prepare();
758                     ClearPreparing();
759                     completionSource.SetResult(true);
760                 }
761                 catch (Exception e)
762                 {
763                     ClearPreparing();
764                     completionSource.TrySetException(e);
765                 }
766             });
767             Log.Debug(PlayerLog.Tag, PlayerLog.Leave);
768
769             return completionSource.Task;
770         }
771
772         /// <summary>
773         /// Unprepares the player.
774         /// </summary>
775         /// <remarks>
776         ///     The most recently used source is reset and no longer associated with the player. Playback is no longer possible.
777         ///     If you want to use the player again, you have to set a source and call <see cref="PrepareAsync"/> again.
778         ///     <para>
779         ///     The player must be in the <see cref="PlayerState.Ready"/>, <see cref="PlayerState.Playing"/> or <see cref="PlayerState.Paused"/> state.
780         ///     It has no effect if the player is already in the <see cref="PlayerState.Idle"/> state.
781         ///     </para>
782         /// </remarks>
783         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
784         /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
785         public virtual void Unprepare()
786         {
787             Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
788             if (State == PlayerState.Idle)
789             {
790                 Log.Warn(PlayerLog.Tag, "idle state already");
791                 return;
792             }
793             ValidatePlayerState(PlayerState.Ready, PlayerState.Paused, PlayerState.Playing);
794
795             NativePlayer.Unprepare(Handle).ThrowIfFailed("Failed to unprepare the player");
796
797             OnUnprepared();
798         }
799
800         protected virtual void OnUnprepared()
801         {
802             _source?.DetachFrom(this);
803             _source = null;
804         }
805
806         //TODO remarks needs to be updated. see the native reference.
807         /// <summary>
808         /// Starts or resumes playback.
809         /// </summary>
810         /// <remarks>
811         /// The player must be in the <see cref="PlayerState.Ready"/> or <see cref="PlayerState.Paused"/> state.
812         /// It has no effect if the player is already in the <see cref="PlayerState.Playing"/> state.
813         /// </remarks>
814         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
815         /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
816         /// <seealso cref="PrepareAsync"/>
817         /// <seealso cref="Stop"/>
818         /// <seealso cref="Pause"/>
819         /// <seealso cref="PlaybackCompleted"/>
820         public virtual void Start()
821         {
822             Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
823             if (State == PlayerState.Playing)
824             {
825                 Log.Warn(PlayerLog.Tag, "playing state already");
826                 return;
827             }
828             ValidatePlayerState(PlayerState.Ready, PlayerState.Paused);
829
830             NativePlayer.Start(Handle).ThrowIfFailed("Failed to start the player");
831             Log.Debug(PlayerLog.Tag, PlayerLog.Leave);
832         }
833
834         /// <summary>
835         /// Stops playing media content.
836         /// </summary>
837         /// <remarks>
838         /// The player must be in the <see cref="PlayerState.Playing"/> or <see cref="PlayerState.Paused"/> state.
839         /// It has no effect if the player is already in the <see cref="PlayerState.Ready"/> state.
840         /// </remarks>
841         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
842         /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
843         /// <seealso cref="Start"/>
844         /// <seealso cref="Pause"/>
845         public virtual void Stop()
846         {
847             Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
848             if (State == PlayerState.Ready)
849             {
850                 Log.Warn(PlayerLog.Tag, "ready state already");
851                 return;
852             }
853             ValidatePlayerState(PlayerState.Paused, PlayerState.Playing);
854
855             NativePlayer.Stop(Handle).ThrowIfFailed("Failed to stop the player");
856             Log.Debug(PlayerLog.Tag, PlayerLog.Leave);
857         }
858
859         /// <summary>
860         /// Pauses the player.
861         /// </summary>
862         /// <remarks>
863         /// The player must be in the <see cref="PlayerState.Playing"/> state.
864         /// It has no effect if the player is already in the <see cref="PlayerState.Paused"/> state.
865         /// </remarks>
866         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
867         /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
868         /// <seealso cref="Start"/>
869         public virtual void Pause()
870         {
871             Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
872             if (State == PlayerState.Paused)
873             {
874                 Log.Warn(PlayerLog.Tag, "pause state already");
875                 return;
876             }
877
878             ValidatePlayerState(PlayerState.Playing);
879
880             NativePlayer.Pause(Handle).ThrowIfFailed("Failed to pause the player");
881             Log.Debug(PlayerLog.Tag, PlayerLog.Leave);
882         }
883
884         private MediaSource _source;
885
886         /// <summary>
887         /// Sets a media source for the player.
888         /// </summary>
889         /// <param name="source">A <see cref="MediaSource"/> that specifies the source for playback.</param>
890         /// <remarks>The player must be in the <see cref="PlayerState.Idle"/> state.</remarks>
891         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
892         /// <exception cref="InvalidOperationException">
893         ///     The player is not in the valid state.\n
894         ///     -or-\n
895         ///     It is not able to assign the source to the player.
896         ///     </exception>
897         /// <seealso cref="PrepareAsync"/>
898         public void SetSource(MediaSource source)
899         {
900             Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
901             ValidatePlayerState(PlayerState.Idle);
902
903             if (source != null)
904             {
905                 source.AttachTo(this);
906             }
907
908             if (_source != null)
909             {
910                 _source.DetachFrom(this);
911             }
912
913             _source = source;
914             Log.Debug(PlayerLog.Tag, PlayerLog.Leave);
915         }
916
917         /// <summary>
918         /// Captures a video frame asynchronously.
919         /// </summary>
920         /// <feature>http://tizen.org/feature/multimedia.raw_video</feature>
921         /// <remarks>The player must be in the <see cref="PlayerState.Playing"/> or <see cref="PlayerState.Paused"/> state.</remarks>
922         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
923         /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
924         /// <exception cref="NotSupportedException">The required feature is not supported.</exception>
925         public async Task<CapturedFrame> CaptureVideoAsync()
926         {
927             Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
928
929             ValidationUtil.ValidateFeatureSupported(Features.RawVideo);
930
931             ValidatePlayerState(PlayerState.Playing, PlayerState.Paused);
932
933             TaskCompletionSource<CapturedFrame> t = new TaskCompletionSource<CapturedFrame>();
934
935             NativePlayer.VideoCaptureCallback cb = (data, width, height, size, _) =>
936             {
937                 Debug.Assert(size <= int.MaxValue);
938
939                 byte[] buf = new byte[size];
940                 Marshal.Copy(data, buf, 0, (int)size);
941
942                 t.TrySetResult(new CapturedFrame(buf, width, height));
943             };
944
945             using (var cbKeeper = ObjectKeeper.Get(cb))
946             {
947                 NativePlayer.CaptureVideo(Handle, cb)
948                     .ThrowIfFailed("Failed to capture the video");
949
950                 return await t.Task;
951             }
952         }
953
954         /// <summary>
955         /// Gets the play position in milliseconds.
956         /// </summary>
957         /// <remarks>The player must be in the <see cref="PlayerState.Ready"/>, <see cref="PlayerState.Playing"/> or <see cref="PlayerState.Paused"/> state.</remarks>
958         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
959         /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
960         /// <seealso cref="SetPlayPositionAsync(int, bool)"/>
961         public int GetPlayPosition()
962         {
963             Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
964             ValidatePlayerState(PlayerState.Ready, PlayerState.Paused, PlayerState.Playing);
965
966             int playPosition = 0;
967
968             NativePlayer.GetPlayPosition(Handle, out playPosition).
969                 ThrowIfFailed("Failed to get the play position of the player");
970
971             Log.Info(PlayerLog.Tag, "get play position : " + playPosition);
972
973             return playPosition;
974         }
975
976         private void SetPlayPosition(int milliseconds, bool accurate,
977             NativePlayer.SeekCompletedCallback cb)
978         {
979             Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
980             var ret = NativePlayer.SetPlayPosition(Handle, milliseconds, accurate, cb, IntPtr.Zero);
981
982             //Note that we assume invalid param error is returned only when the position value is invalid.
983             if (ret == PlayerErrorCode.InvalidArgument)
984             {
985                 throw new ArgumentOutOfRangeException(nameof(milliseconds), milliseconds,
986                     "The position is not valid.");
987             }
988             if (ret != PlayerErrorCode.None)
989             {
990                 Log.Error(PlayerLog.Tag, "Failed to set play position, " + (PlayerError)ret);
991             }
992             ret.ThrowIfFailed("Failed to set play position");
993         }
994
995         /// <summary>
996         /// Sets the seek position for playback, asynchronously.
997         /// </summary>
998         /// <param name="position">The value indicating a desired position in milliseconds.</param>
999         /// <param name="accurate">The value indicating whether the operation performs with accuracy.</param>
1000         /// <remarks>
1001         ///     <para>The player must be in the <see cref="PlayerState.Ready"/>, <see cref="PlayerState.Playing"/> or <see cref="PlayerState.Paused"/> state.</para>
1002         ///     <para>If the <paramref name="accurate"/> is true, the play position will be adjusted as the specified <paramref name="position"/> value,
1003         ///     but this might be considerably slow. If false, the play position will be a nearest keyframe position.</para>
1004         ///     </remarks>
1005         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
1006         /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
1007         /// <exception cref="ArgumentOutOfRangeException">The specified position is not valid.</exception>
1008         /// <seealso cref="GetPlayPosition"/>
1009         public async Task SetPlayPositionAsync(int position, bool accurate)
1010         {
1011             Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
1012             ValidatePlayerState(PlayerState.Ready, PlayerState.Playing, PlayerState.Paused);
1013
1014             var taskCompletionSource = new TaskCompletionSource<bool>();
1015
1016             bool immediateResult = _source is MediaStreamSource;
1017
1018             NativePlayer.SeekCompletedCallback cb = _ => taskCompletionSource.TrySetResult(true);
1019
1020             using (var cbKeeper = ObjectKeeper.Get(cb))
1021             {
1022                 SetPlayPosition(position, accurate, cb);
1023                 if (immediateResult)
1024                 {
1025                     taskCompletionSource.TrySetResult(true);
1026                 }
1027
1028                 await taskCompletionSource.Task;
1029             }
1030
1031             Log.Debug(PlayerLog.Tag, PlayerLog.Leave);
1032         }
1033
1034         /// <summary>
1035         /// Sets playback rate.
1036         /// </summary>
1037         /// <param name="rate">The value for the playback rate. Valid range is -5.0 to 5.0, inclusive.</param>
1038         /// <remarks>
1039         ///     <para>The player must be in the <see cref="PlayerState.Ready"/>, <see cref="PlayerState.Playing"/> or <see cref="PlayerState.Paused"/> state.</para>
1040         ///     <para>The sound will be muted, when the playback rate is under 0.0 or over 2.0.</para>
1041         /// </remarks>
1042         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
1043         /// <exception cref="InvalidOperationException">
1044         ///     The player is not in the valid state.\n
1045         ///     -or-\n
1046         ///     Streaming playback.
1047         /// </exception>
1048         /// <exception cref="ArgumentOutOfRangeException">
1049         ///     <paramref name="rate"/> is less than 5.0.\n
1050         ///     -or-\n
1051         ///     <paramref name="rate"/> is greater than 5.0.\n
1052         ///     -or-\n
1053         ///     <paramref name="rate"/> is zero.
1054         /// </exception>
1055         public void SetPlaybackRate(float rate)
1056         {
1057             Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
1058             if (rate < -5.0F || 5.0F < rate || rate == 0.0F)
1059             {
1060                 throw new ArgumentOutOfRangeException(nameof(rate), rate, "Valid range is -5.0 to 5.0 (except 0.0)");
1061             }
1062
1063             ValidatePlayerState(PlayerState.Ready, PlayerState.Playing, PlayerState.Paused);
1064
1065             NativePlayer.SetPlaybackRate(Handle, rate).ThrowIfFailed("Failed to set the playback rate.");
1066             Log.Debug(PlayerLog.Tag, PlayerLog.Leave);
1067         }
1068
1069         /// <summary>
1070         /// Applies the audio stream policy.
1071         /// </summary>
1072         /// <param name="policy">The <see cref="AudioStreamPolicy"/> to apply.</param>
1073         /// <remarks>The player must be in the <see cref="PlayerState.Idle"/> state.</remarks>
1074         /// <exception cref="ObjectDisposedException">
1075         ///     The player has already been disposed of.\n
1076         ///     -or-\n
1077         ///     <paramref name="policy"/> has already been disposed of.
1078         /// </exception>
1079         /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
1080         /// <exception cref="ArgumentNullException"><paramref name="policy"/> is null.</exception>
1081         public void ApplyAudioStreamPolicy(AudioStreamPolicy policy)
1082         {
1083             Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
1084             if (policy == null)
1085             {
1086                 throw new ArgumentNullException(nameof(policy));
1087             }
1088
1089             if (policy.Handle == IntPtr.Zero)
1090             {
1091                 throw new ObjectDisposedException(nameof(policy));
1092             }
1093
1094             ValidatePlayerState(PlayerState.Idle);
1095
1096             NativePlayer.SetAudioPolicyInfo(Handle, policy.Handle).
1097                 ThrowIfFailed("Failed to set the audio stream policy to the player");
1098         }
1099         #endregion
1100
1101         #region Callback registrations
1102         private void RegisterSubtitleUpdatedCallback()
1103         {
1104             _subtitleUpdatedCallback = (duration, text, _) =>
1105             {
1106                 Log.Debug(PlayerLog.Tag, "duration : " + duration + ", text : " + text);
1107                 SubtitleUpdated?.Invoke(this, new SubtitleUpdatedEventArgs(duration, text));
1108             };
1109
1110             NativePlayer.SetSubtitleUpdatedCb(Handle, _subtitleUpdatedCallback).
1111                 ThrowIfFailed("Failed to initialize the player");
1112         }
1113
1114         private void RegisterPlaybackCompletedCallback()
1115         {
1116             _playbackCompletedCallback = _ =>
1117             {
1118                 Log.Debug(PlayerLog.Tag, "completed callback");
1119                 PlaybackCompleted?.Invoke(this, EventArgs.Empty);
1120             };
1121             NativePlayer.SetCompletedCb(Handle, _playbackCompletedCallback).
1122                 ThrowIfFailed("Failed to set PlaybackCompleted");
1123         }
1124
1125         private void RegisterPlaybackInterruptedCallback()
1126         {
1127             _playbackInterruptedCallback = (code, _) =>
1128             {
1129                 if (!Enum.IsDefined(typeof(PlaybackInterruptionReason), code))
1130                 {
1131                     return;
1132                 }
1133
1134                 if (code == PlaybackInterruptionReason.ResourceConflict)
1135                 {
1136                     OnUnprepared();
1137                 }
1138
1139                 Log.Warn(PlayerLog.Tag, "interrupted reason : " + code);
1140                 PlaybackInterrupted?.Invoke(this, new PlaybackInterruptedEventArgs(code));
1141             };
1142
1143             NativePlayer.SetInterruptedCb(Handle, _playbackInterruptedCallback).
1144                 ThrowIfFailed("Failed to set PlaybackInterrupted");
1145         }
1146
1147         private void RegisterErrorOccurredCallback()
1148         {
1149             _playbackErrorCallback = (code, _) =>
1150             {
1151                 //TODO handle service disconnected error.
1152                 Log.Warn(PlayerLog.Tag, "error code : " + code);
1153                 ErrorOccurred?.Invoke(this, new PlayerErrorOccurredEventArgs((PlayerError)code));
1154             };
1155
1156             NativePlayer.SetErrorCb(Handle, _playbackErrorCallback).
1157                 ThrowIfFailed("Failed to set PlaybackError");
1158         }
1159
1160         #region VideoFrameDecoded event
1161
1162         private EventHandler<VideoFrameDecodedEventArgs> _videoFrameDecoded;
1163
1164         private NativePlayer.VideoFrameDecodedCallback _videoFrameDecodedCallback;
1165
1166         /// <summary>
1167         /// Occurs when a video frame is decoded.
1168         /// </summary>
1169         /// <remarks>
1170         ///     <para>The event handler will be executed on an internal thread.</para>
1171         ///     <para>The <see cref="VideoFrameDecodedEventArgs.Packet"/> in event args should be disposed after use.</para>
1172         /// </remarks>
1173         /// <feature>http://tizen.org/feature/multimedia.raw_video</feature>
1174         /// <exception cref="NotSupportedException">The required feature is not supported.</exception>
1175         /// <seealso cref="VideoFrameDecodedEventArgs.Packet"/>
1176         public event EventHandler<VideoFrameDecodedEventArgs> VideoFrameDecoded
1177         {
1178             add
1179             {
1180                 ValidationUtil.ValidateFeatureSupported(Features.RawVideo);
1181
1182                 _videoFrameDecoded += value;
1183             }
1184             remove
1185             {
1186                 ValidationUtil.ValidateFeatureSupported(Features.RawVideo);
1187
1188                 _videoFrameDecoded -= value;
1189             }
1190         }
1191
1192         private void RegisterVideoFrameDecodedCallback()
1193         {
1194             _videoFrameDecodedCallback = (packetHandle, _) =>
1195             {
1196                 var handler = _videoFrameDecoded;
1197                 if (handler != null)
1198                 {
1199                     Log.Debug(PlayerLog.Tag, "packet : " + packetHandle);
1200                     handler.Invoke(this,
1201                         new VideoFrameDecodedEventArgs(MediaPacket.From(packetHandle)));
1202                 }
1203                 else
1204                 {
1205                     MediaPacket.From(packetHandle).Dispose();
1206                 }
1207             };
1208
1209             NativePlayer.SetVideoFrameDecodedCb(Handle, _videoFrameDecodedCallback).
1210                 ThrowIfFailed("Failed to register the VideoFrameDecoded");
1211         }
1212         #endregion
1213
1214         private void RegisterVideoStreamChangedCallback()
1215         {
1216             ValidatePlayerState(PlayerState.Idle);
1217
1218             _videoStreamChangedCallback = (width, height, fps, bitrate, _) =>
1219             {
1220                 Log.Debug(PlayerLog.Tag, "height : " + height + ", width : " + width
1221                 + ", fps : " + fps + ", bitrate : " + bitrate);
1222
1223                 VideoStreamChanged?.Invoke(this, new VideoStreamChangedEventArgs(height, width, fps, bitrate));
1224             };
1225
1226             NativePlayer.SetVideoStreamChangedCb(Handle, _videoStreamChangedCallback).
1227                 ThrowIfFailed("Failed to set the video stream changed callback");
1228         }
1229
1230         private void RegisterBufferingCallback()
1231         {
1232             _bufferingProgressCallback = (percent, _) =>
1233             {
1234                 Log.Debug(PlayerLog.Tag, $"Buffering callback with percent { percent }");
1235                 BufferingProgressChanged?.Invoke(this, new BufferingProgressChangedEventArgs(percent));
1236             };
1237
1238             NativePlayer.SetBufferingCb(Handle, _bufferingProgressCallback).
1239                 ThrowIfFailed("Failed to set BufferingProgress");
1240         }
1241
1242         private void RegisterMediaStreamBufferStatusCallback()
1243         {
1244             _mediaStreamAudioBufferStatusChangedCallback = (status, _) =>
1245             {
1246                 Debug.Assert(Enum.IsDefined(typeof(MediaStreamBufferStatus), status));
1247                 Log.Debug(PlayerLog.Tag, "audio buffer status : " + status);
1248                 MediaStreamAudioBufferStatusChanged?.Invoke(this,
1249                     new MediaStreamBufferStatusChangedEventArgs(status));
1250             };
1251             _mediaStreamVideoBufferStatusChangedCallback = (status, _) =>
1252             {
1253                 Debug.Assert(Enum.IsDefined(typeof(MediaStreamBufferStatus), status));
1254                 Log.Debug(PlayerLog.Tag, "video buffer status : " + status);
1255                 MediaStreamVideoBufferStatusChanged?.Invoke(this,
1256                     new MediaStreamBufferStatusChangedEventArgs(status));
1257             };
1258
1259             RegisterMediaStreamBufferStatusCallback(StreamType.Audio, _mediaStreamAudioBufferStatusChangedCallback);
1260             RegisterMediaStreamBufferStatusCallback(StreamType.Video, _mediaStreamVideoBufferStatusChangedCallback);
1261         }
1262
1263         private void RegisterMediaStreamBufferStatusCallback(StreamType streamType,
1264             NativePlayer.MediaStreamBufferStatusCallback cb)
1265         {
1266             NativePlayer.SetMediaStreamBufferStatusCb(Handle, streamType, cb).
1267                 ThrowIfFailed("Failed to SetMediaStreamBufferStatus");
1268         }
1269
1270         private void RegisterMediaStreamSeekCallback()
1271         {
1272             _mediaStreamAudioSeekCallback = (offset, _) =>
1273             {
1274                 Log.Debug(PlayerLog.Tag, "audio seeking offset : " + offset);
1275                 MediaStreamAudioSeekingOccurred?.Invoke(this, new MediaStreamSeekingOccurredEventArgs(offset));
1276             };
1277             _mediaStreamVideoSeekCallback = (offset, _) =>
1278             {
1279                 Log.Debug(PlayerLog.Tag, "video seeking offset : " + offset);
1280                 MediaStreamVideoSeekingOccurred?.Invoke(this, new MediaStreamSeekingOccurredEventArgs(offset));
1281             };
1282
1283             RegisterMediaStreamSeekCallback(StreamType.Audio, _mediaStreamAudioSeekCallback);
1284             RegisterMediaStreamSeekCallback(StreamType.Video, _mediaStreamVideoSeekCallback);
1285         }
1286
1287         private void RegisterMediaStreamSeekCallback(StreamType streamType, NativePlayer.MediaStreamSeekCallback cb)
1288         {
1289             NativePlayer.SetMediaStreamSeekCb(Handle, streamType, cb).
1290                 ThrowIfFailed("Failed to SetMediaStreamSeek");
1291         }
1292         #endregion
1293
1294         #region Preparing state
1295
1296         private int _isPreparing;
1297
1298         private bool IsPreparing()
1299         {
1300             return Interlocked.CompareExchange(ref _isPreparing, 1, 1) == 1;
1301         }
1302
1303         private void SetPreparing()
1304         {
1305             Interlocked.Exchange(ref _isPreparing, 1);
1306         }
1307
1308         private void ClearPreparing()
1309         {
1310             Interlocked.Exchange(ref _isPreparing, 0);
1311         }
1312
1313         #endregion
1314     }
1315 }