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