ac1b78f64a1aa2ad67a94eb0becc0bbd52a861a3
[platform/core/csapi/tizenfx.git] / src / Tizen.Multimedia.MediaPlayer / Player / Player.cs
1 /*
2  * Copyright (c) 2018 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
17 using static Interop;
18 using System;
19 using System.ComponentModel;
20 using System.Diagnostics;
21 using System.IO;
22 using System.Runtime.InteropServices;
23 using System.Threading;
24 using System.Threading.Tasks;
25
26 namespace Tizen.Multimedia
27 {
28     internal static class PlayerLog
29     {
30         internal const string Tag = "Tizen.Multimedia.Player";
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 partial class Player : IDisposable, IDisplayable<PlayerErrorCode>
42     {
43         private readonly PlayerHandle _handle;
44
45         /// <summary>
46         /// Initializes a new instance of the <see cref="Player"/> class.
47         /// </summary>
48         /// <since_tizen> 3 </since_tizen>
49         public Player()
50         {
51             NativePlayer.Create(out _handle).ThrowIfFailed(null, "Failed to create player");
52
53             Debug.Assert(_handle != null);
54
55             Initialize();
56         }
57
58         /// <summary>
59         /// Initializes a new instance of the <see cref="Player"/> class with a native handle.
60         /// The class takes care of the life cycle of the handle.
61         /// Thus, it should not be closed/destroyed in another location.
62         /// </summary>
63         /// <param name="handle">The handle for the media player.</param>
64         /// <param name="errorHandler">The handle for occuring error.</param>
65         /// <remarks>
66         /// This supports the product infrastructure and is not intended to be used directly from application code.
67         /// </remarks>
68         [EditorBrowsable(EditorBrowsableState.Never)]
69         protected Player(IntPtr handle, Action<int, string> errorHandler)
70         {
71             // This constructor is to support TV product player.
72             // Be careful with 'handle'. It must be wrapped in safe handle, first.
73             _handle = handle != IntPtr.Zero ? new PlayerHandle(handle) :
74                 throw new ArgumentException("Handle is invalid.", nameof(handle));
75
76             _errorHandler = errorHandler;
77         }
78
79         private bool _initialized;
80
81         /// <summary>
82         /// This supports the product infrastructure and is not intended to be used directly from application code.
83         /// </summary>
84         [EditorBrowsable(EditorBrowsableState.Never)]
85         protected void Initialize()
86         {
87             if (_initialized)
88             {
89                 throw new InvalidOperationException("It has already been initialized.");
90             }
91
92             if (Features.IsSupported(PlayerFeatures.AudioEffect))
93             {
94                 _audioEffect = new AudioEffect(this);
95             }
96
97             if (Features.IsSupported(PlayerFeatures.RawVideo))
98             {
99                 RegisterVideoFrameDecodedCallback();
100             }
101
102             RegisterEvents();
103
104             _displaySettings = PlayerDisplaySettings.Create(this);
105
106             _initialized = true;
107         }
108
109         internal void ValidatePlayerState(params PlayerState[] desiredStates)
110         {
111             Debug.Assert(desiredStates.Length > 0);
112
113             ValidateNotDisposed();
114
115             var curState = State;
116             if (curState.IsAnyOf(desiredStates))
117             {
118                 return;
119             }
120
121             throw new InvalidOperationException($"The player is not in a valid state. " +
122                 $"Current State : { curState }, Valid State : { string.Join(", ", desiredStates) }.");
123         }
124
125         #region Dispose support
126         private bool _disposed;
127
128         /// <summary>
129         /// Releases all resources used by the current instance.
130         /// </summary>
131         /// <since_tizen> 3 </since_tizen>
132         public void Dispose()
133         {
134             Dispose(true);
135             GC.SuppressFinalize(this);
136         }
137
138         /// <summary>
139         /// Releases the unmanaged resources used by the <see cref="Player"/>.
140         /// </summary>
141         /// <param name="disposing">
142         /// true to release both managed and unmanaged resources; false to release only unmanaged resources.
143         /// </param>
144         [EditorBrowsable(EditorBrowsableState.Never)]
145         protected virtual void Dispose(bool disposing)
146         {
147             if (_disposed)
148             {
149                 return;
150             }
151
152             if (disposing)
153             {
154                 ReplaceDisplay(null);
155
156                 if (_source != null)
157                 {
158                     try
159                     {
160                         _source.DetachFrom(this);
161                         _source = null;
162                     }
163                     catch (Exception e)
164                     {
165                         Log.Error(PlayerLog.Tag, e.ToString());
166                     }
167                 }
168
169                 if (_handle != null)
170                 {
171                     _handle.Dispose();
172                     _disposed = true;
173                 }
174             }
175         }
176
177         internal void ValidateNotDisposed()
178         {
179             if (_disposed)
180             {
181                 Log.Warn(PlayerLog.Tag, "player was disposed");
182                 throw new ObjectDisposedException(nameof(Player));
183             }
184         }
185
186         internal bool IsDisposed => _disposed;
187         #endregion
188
189         #region Methods
190
191         /// <summary>
192         /// Gets the streaming download progress.
193         /// </summary>
194         /// <returns>The <see cref="DownloadProgress"/> containing current download progress.</returns>
195         /// <remarks>The player must be in the <see cref="PlayerState.Ready"/>, <see cref="PlayerState.Playing"/>,
196         /// or <see cref="PlayerState.Paused"/> state.</remarks>
197         /// <exception cref="InvalidOperationException">
198         ///     The player is not streaming.<br/>
199         ///     -or-<br/>
200         ///     The player is not in the valid state.
201         ///     </exception>
202         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
203         /// <since_tizen> 3 </since_tizen>
204         public DownloadProgress GetDownloadProgress()
205         {
206             ValidatePlayerState(PlayerState.Ready, PlayerState.Playing, PlayerState.Paused);
207
208             int start = 0;
209             int current = 0;
210             NativePlayer.GetStreamingDownloadProgress(Handle, out start, out current).
211                 ThrowIfFailed(this, "Failed to get download progress");
212
213             Log.Info(PlayerLog.Tag, $"get download progress : {start}, {current}");
214
215             return new DownloadProgress(start, current);
216         }
217
218         /// <summary>
219         /// Sets the subtitle path for playback.
220         /// </summary>
221         /// <param name="path">The absolute path of the subtitle file, it can be NULL in the <see cref="PlayerState.Idle"/> state.</param>
222         /// <remarks>Only MicroDVD/SubViewer(*.sub), SAMI(*.smi), and SubRip(*.srt) subtitle formats are supported.
223         ///     <para>The mediastorage privilege(http://tizen.org/privilege/mediastorage) must be added if any files are used to play located in the internal storage.
224         ///     The externalstorage privilege(http://tizen.org/privilege/externalstorage) must be added if any files are used to play located in the external storage.</para>
225         /// </remarks>
226         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
227         /// <exception cref="ArgumentException"><paramref name="path"/> is an empty string.</exception>
228         /// <exception cref="FileNotFoundException">The specified path does not exist.</exception>
229         /// <exception cref="ArgumentNullException"><paramref name="path"/> is null.</exception>
230         /// <since_tizen> 3 </since_tizen>
231         public void SetSubtitle(string path)
232         {
233             ValidateNotDisposed();
234
235             if (path == null)
236             {
237                 throw new ArgumentNullException(nameof(path));
238             }
239
240             if (path.Length == 0)
241             {
242                 throw new ArgumentException("The path is empty.", nameof(path));
243             }
244
245             if (!File.Exists(path))
246             {
247                 throw new FileNotFoundException($"The specified file does not exist.", path);
248             }
249
250             NativePlayer.SetSubtitlePath(Handle, path).
251                 ThrowIfFailed(this, "Failed to set the subtitle path to the player");
252         }
253
254         /// <summary>
255         /// Removes the subtitle path.
256         /// </summary>
257         /// <remarks>The player must be in the <see cref="PlayerState.Idle"/> state.</remarks>
258         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
259         /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
260         /// <since_tizen> 3 </since_tizen>
261         public void ClearSubtitle()
262         {
263             ValidatePlayerState(PlayerState.Idle);
264
265             NativePlayer.SetSubtitlePath(Handle, null).
266                 ThrowIfFailed(this, "Failed to clear the subtitle of the player");
267         }
268
269         /// <summary>
270         /// Sets the offset for the subtitle.
271         /// </summary>
272         /// <param name="offset">The value indicating a desired offset in milliseconds.</param>
273         /// <remarks>The player must be in the <see cref="PlayerState.Playing"/> or <see cref="PlayerState.Paused"/> state.</remarks>
274         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
275         /// <exception cref="InvalidOperationException">
276         ///     The player is not in the valid state.<br/>
277         ///     -or-<br/>
278         ///     No subtitle is set.
279         /// </exception>
280         /// <seealso cref="SetSubtitle(string)"/>
281         /// <since_tizen> 3 </since_tizen>
282         public void SetSubtitleOffset(int offset)
283         {
284             ValidatePlayerState(PlayerState.Playing, PlayerState.Paused);
285
286             var err = NativePlayer.SetSubtitlePositionOffset(Handle, offset);
287
288             if (err == PlayerErrorCode.FeatureNotSupported)
289             {
290                 throw new InvalidOperationException("No subtitle set");
291             }
292
293             err.ThrowIfFailed(this, "Failed to the subtitle offset of the player");
294         }
295
296         private void Prepare()
297         {
298             NativePlayer.Prepare(Handle).ThrowIfFailed(this, "Failed to prepare the player");
299         }
300
301         /// <summary>
302         /// Called when the <see cref="Prepare"/> is invoked.
303         /// </summary>
304         /// <since_tizen> 3 </since_tizen>
305         protected virtual void OnPreparing()
306         {
307         }
308
309         /// <summary>
310         /// Prepares the media player for playback, asynchronously.
311         /// </summary>
312         /// <returns>A task that represents the asynchronous prepare operation.</returns>
313         /// <remarks>To prepare the player, the player must be in the <see cref="PlayerState.Idle"/> state,
314         ///     and a source must be set.</remarks>
315         /// <exception cref="InvalidOperationException">No source is set.</exception>
316         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
317         /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
318         /// <seealso cref="PlayerState.Preparing"/>
319         /// <since_tizen> 3 </since_tizen>
320         public virtual Task PrepareAsync()
321         {
322             if (_source == null)
323             {
324                 throw new InvalidOperationException("No source is set.");
325             }
326
327             ValidatePlayerState(PlayerState.Idle);
328
329             OnPreparing();
330
331             SetPreparing();
332
333             return Task.Factory.StartNew(() =>
334             {
335                 try
336                 {
337                     Prepare();
338                 }
339                 finally
340                 {
341                     ClearPreparing();
342                 }
343             }, CancellationToken.None,
344                 TaskCreationOptions.DenyChildAttach | TaskCreationOptions.LongRunning,
345                 TaskScheduler.Default);
346         }
347
348         /// <summary>
349         /// Prepares the cancellable media player for playback, asynchronously.
350         /// </summary>
351         /// <param name="cancellationToken">The cancellation token to cancel preparing.</param>
352         /// <seealso cref="CancellationToken"/>
353         /// <returns>A task that represents the asynchronous prepare operation.</returns>
354         /// <remarks>To prepare the player, the player must be in the <see cref="PlayerState.Idle"/> state,
355         /// and a source must be set.
356         /// The state must be <see cref="PlayerState.Preparing"/> to cancel preparing.
357         /// When preparing is cancelled, a state will be changed to <see cref="PlayerState.Idle"/> from <see cref="PlayerState.Preparing"/>.</remarks>
358         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
359         /// <exception cref="InvalidOperationException">
360         ///     Operation failed; internal error.
361         ///     -or-<br/>
362         ///     The player is not in the valid state.
363         ///     </exception>
364         /// <seealso cref="PrepareAsync()"/>
365         /// <seealso cref="Unprepare()"/>
366         /// <since_tizen> 6 </since_tizen>
367         public virtual async Task PrepareAsync(CancellationToken cancellationToken)
368         {
369             ValidateNotDisposed();
370
371             var taskCompletionSource = new TaskCompletionSource<bool>();
372
373             if (_source == null)
374             {
375                 throw new InvalidOperationException("No source is set.");
376             }
377
378             ValidatePlayerState(PlayerState.Idle);
379
380             OnPreparing();
381
382             SetPreparing();
383
384             // register a callback to handle cancellation token anytime it occurs
385             cancellationToken.Register(() =>
386             {
387                 ValidatePlayerState(PlayerState.Preparing);
388
389                 // a user can get the state before finally block is called.
390                 ClearPreparing();
391
392                 Log.Warn(PlayerLog.Tag, $"preparing will be cancelled.");
393                 NativePlayer.Unprepare(Handle).ThrowIfFailed(this, "Failed to unprepare the player");
394
395                 taskCompletionSource.TrySetCanceled();
396             });
397
398             _prepareCallback = _ =>
399             {
400                 Log.Warn(PlayerLog.Tag, $"prepared callback is called.");
401                 taskCompletionSource.TrySetResult(true);
402             };
403
404             try
405             {
406                 NativePlayer.PrepareAsync(Handle, _prepareCallback, IntPtr.Zero).
407                     ThrowIfFailed(this, "Failed to prepare the player");
408
409                 await taskCompletionSource.Task.ConfigureAwait(false);
410             }
411             finally
412             {
413                 ClearPreparing();
414             }
415         }
416
417         /// <summary>
418         /// Unprepares the player.
419         /// </summary>
420         /// <remarks>
421         ///     The most recently used source is reset and is no longer associated with the player. Playback is no longer possible.
422         ///     If you want to use the player again, you have to set a source and call <see cref="PrepareAsync"/> again.
423         ///     <para>
424         ///     The player must be in the <see cref="PlayerState.Ready"/>, <see cref="PlayerState.Playing"/>, or <see cref="PlayerState.Paused"/> state.
425         ///     It has no effect if the player is already in the <see cref="PlayerState.Idle"/> state.
426         ///     </para>
427         /// </remarks>
428         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
429         /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
430         /// <since_tizen> 3 </since_tizen>
431         public virtual void Unprepare()
432         {
433             if (State == PlayerState.Idle)
434             {
435                 Log.Warn(PlayerLog.Tag, "idle state already");
436                 return;
437             }
438             ValidatePlayerState(PlayerState.Ready, PlayerState.Paused, PlayerState.Playing);
439
440             NativePlayer.Unprepare(Handle).ThrowIfFailed(this, "Failed to unprepare the player");
441
442             OnUnprepared();
443         }
444
445         /// <summary>
446         /// Called after the <see cref="Player"/> is unprepared.
447         /// </summary>
448         /// <seealso cref="Unprepare"/>
449         /// <since_tizen> 3 </since_tizen>
450         protected virtual void OnUnprepared()
451         {
452             _source?.DetachFrom(this);
453             _source = null;
454         }
455
456         /// <summary>
457         /// Starts or resumes playback.
458         /// </summary>
459         /// <remarks>
460         /// Sound can be mixed with other sounds if you don't control the stream focus using <see cref="ApplyAudioStreamPolicy"/>.<br/>
461         ///      <para>Before Tizen 5.0, The player must be in the <see cref="PlayerState.Ready"/> or <see cref="PlayerState.Paused"/> state.
462         ///      It has no effect if the player is already in the <see cref="PlayerState.Playing"/> state.</para>
463         ///      <para>Since Tizen 5.0, The player must be in the <see cref="PlayerState.Ready"/>, <see cref="PlayerState.Playing"/>,
464         ///      or <see cref="PlayerState.Paused"/> state.<br/>
465         ///      In case of HTTP streaming playback, the player could be internally paused for buffering.
466         ///      If the application calls this function during the buffering, the playback will be resumed by force
467         ///      and the buffering message posting by <see cref="BufferingProgressChanged"/> will be stopped.<br/>
468         ///      In other cases, the player will keep playing without returning error.</para>
469         /// </remarks>
470         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
471         /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
472         /// <seealso cref="PrepareAsync"/>
473         /// <seealso cref="Stop"/>
474         /// <seealso cref="Pause"/>
475         /// <seealso cref="PlaybackCompleted"/>
476         /// <seealso cref="ApplyAudioStreamPolicy"/>
477         /// <seealso cref="BufferingProgressChanged"/>
478         /// <since_tizen> 3 </since_tizen>
479         public virtual void Start()
480         {
481             ValidatePlayerState(PlayerState.Ready, PlayerState.Paused, PlayerState.Playing);
482
483             NativePlayer.Start(Handle).ThrowIfFailed(this, "Failed to start the player");
484         }
485
486         /// <summary>
487         /// Stops playing the media content.
488         /// </summary>
489         /// <remarks>
490         /// The player must be in the <see cref="PlayerState.Playing"/> or <see cref="PlayerState.Paused"/> state.
491         /// It has no effect if the player is already in the <see cref="PlayerState.Ready"/> state.
492         /// </remarks>
493         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
494         /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
495         /// <seealso cref="Start"/>
496         /// <seealso cref="Pause"/>
497         /// <since_tizen> 3 </since_tizen>
498         public virtual void Stop()
499         {
500             if (State == PlayerState.Ready)
501             {
502                 Log.Warn(PlayerLog.Tag, "ready state already");
503                 return;
504             }
505             ValidatePlayerState(PlayerState.Paused, PlayerState.Playing);
506
507             NativePlayer.Stop(Handle).ThrowIfFailed(this, "Failed to stop the player");
508         }
509
510         /// <summary>
511         /// Pauses the player.
512         /// </summary>
513         /// <remarks>
514         /// The player must be in the <see cref="PlayerState.Playing"/> state.
515         /// It has no effect if the player is already in the <see cref="PlayerState.Paused"/> state.
516         /// </remarks>
517         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
518         /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
519         /// <seealso cref="Start"/>
520         /// <since_tizen> 3 </since_tizen>
521         public virtual void Pause()
522         {
523             if (State == PlayerState.Paused)
524             {
525                 Log.Warn(PlayerLog.Tag, "pause state already");
526                 return;
527             }
528
529             ValidatePlayerState(PlayerState.Playing);
530
531             NativePlayer.Pause(Handle).ThrowIfFailed(this, "Failed to pause the player");
532         }
533
534         private MediaSource _source;
535
536         /// <summary>
537         /// Determines whether MediaSource has set.
538         /// This supports the product infrastructure and is not intended to be used directly from application code.
539         /// </summary>
540         [EditorBrowsable(EditorBrowsableState.Never)]
541         protected bool HasSource => _source != null;
542
543         /// <summary>
544         /// Sets a media source for the player.
545         /// </summary>
546         /// <param name="source">A <see cref="MediaSource"/> that specifies the source for playback.</param>
547         /// <remarks>The player must be in the <see cref="PlayerState.Idle"/> state.</remarks>
548         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
549         /// <exception cref="InvalidOperationException">
550         ///     The player is not in the valid state.<br/>
551         ///     -or-<br/>
552         ///     It is not able to assign the source to the player.
553         ///     </exception>
554         /// <seealso cref="PrepareAsync"/>
555         /// <since_tizen> 3 </since_tizen>
556         public void SetSource(MediaSource source)
557         {
558             ValidatePlayerState(PlayerState.Idle);
559
560             if (source != null)
561             {
562                 source.AttachTo(this);
563             }
564
565             if (_source != null)
566             {
567                 _source.DetachFrom(this);
568             }
569
570             _source = source;
571         }
572
573         /// <summary>
574         /// Captures a video frame, asynchronously.
575         /// </summary>
576         /// <returns>A task that represents the asynchronous capture operation.</returns>
577         /// <feature>http://tizen.org/feature/multimedia.raw_video</feature>
578         /// <remarks>The player must be in the <see cref="PlayerState.Playing"/> or <see cref="PlayerState.Paused"/> state.</remarks>
579         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
580         /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
581         /// <exception cref="NotSupportedException">The required feature is not supported.</exception>
582         /// <since_tizen> 3 </since_tizen>
583         public async Task<CapturedFrame> CaptureVideoAsync()
584         {
585             ValidationUtil.ValidateFeatureSupported(PlayerFeatures.RawVideo);
586
587             ValidatePlayerState(PlayerState.Playing, PlayerState.Paused);
588
589             TaskCompletionSource<CapturedFrame> t = new TaskCompletionSource<CapturedFrame>();
590
591             NativePlayer.VideoCaptureCallback cb = (data, width, height, size, _) =>
592             {
593                 Debug.Assert(size <= int.MaxValue);
594
595                 byte[] buf = new byte[size];
596                 Marshal.Copy(data, buf, 0, (int)size);
597
598                 t.TrySetResult(new CapturedFrame(buf, width, height));
599             };
600
601             using (var cbKeeper = ObjectKeeper.Get(cb))
602             {
603                 NativePlayer.CaptureVideo(Handle, cb)
604                     .ThrowIfFailed(this, "Failed to capture the video");
605
606                 return await t.Task;
607             }
608         }
609
610         /// <summary>
611         /// Gets the play position in milliseconds.
612         /// </summary>
613         /// <returns>The current position in milliseconds.</returns>
614         /// <remarks>The player must be in the <see cref="PlayerState.Ready"/>, <see cref="PlayerState.Playing"/>,
615         /// or <see cref="PlayerState.Paused"/> state.</remarks>
616         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
617         /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
618         /// <seealso cref="SetPlayPositionAsync(int, bool)"/>
619         /// <seealso cref="SetPlayPositionNanosecondsAsync(long, bool)"/>
620         /// <seealso cref="GetPlayPositionNanoseconds"/>
621         /// <since_tizen> 3 </since_tizen>
622         public int GetPlayPosition()
623         {
624             ValidatePlayerState(PlayerState.Ready, PlayerState.Paused, PlayerState.Playing);
625
626             int playPosition = 0;
627
628             NativePlayer.GetPlayPosition(Handle, out playPosition).
629                 ThrowIfFailed(this, "Failed to get the play position of the player");
630
631             Log.Info(PlayerLog.Tag, $"get play position : {playPosition}");
632
633             return playPosition;
634         }
635
636         private void NativeSetPlayPosition(long position, bool accurate, bool nanoseconds,
637             NativePlayer.SeekCompletedCallback cb)
638         {
639             //Check if it is nanoseconds or milliseconds.
640             var ret = !nanoseconds ? NativePlayer.SetPlayPosition(Handle, (int)position, accurate, cb, IntPtr.Zero) :
641                 NativePlayer.SetPlayPositionNanoseconds(Handle, position, accurate, cb, IntPtr.Zero);
642
643             //Note that we assume invalid param error is returned only when the position value is invalid.
644             if (ret == PlayerErrorCode.InvalidArgument)
645             {
646                 throw new ArgumentOutOfRangeException(nameof(position), position,
647                     "The position is not valid.");
648             }
649             if (ret != PlayerErrorCode.None)
650             {
651                 Log.Error(PlayerLog.Tag, "Failed to set play position, " + (PlayerError)ret);
652             }
653             ret.ThrowIfFailed(this, "Failed to set play position");
654         }
655
656         private async Task SetPlayPosition(long position, bool accurate, bool nanoseconds)
657         {
658             var taskCompletionSource = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
659
660             bool immediateResult = _source is MediaStreamSource;
661
662             NativePlayer.SeekCompletedCallback cb = _ => taskCompletionSource.TrySetResult(true);
663
664             using (var cbKeeper = ObjectKeeper.Get(cb))
665             {
666                 NativeSetPlayPosition(position, accurate, nanoseconds, immediateResult ? null : cb);
667
668                 if (immediateResult)
669                 {
670                     taskCompletionSource.TrySetResult(true);
671                 }
672                 await taskCompletionSource.Task;
673             }
674         }
675
676         /// <summary>
677         /// Sets the seek position for playback, asynchronously.
678         /// </summary>
679         /// <param name="position">The value indicating a desired position in milliseconds.</param>
680         /// <param name="accurate">The value indicating whether the operation performs with accuracy.</param>
681         /// <returns>A task that represents the asynchronous operation.</returns>
682         /// <remarks>
683         ///     <para>The player must be in the <see cref="PlayerState.Ready"/>, <see cref="PlayerState.Playing"/>,
684         ///     or <see cref="PlayerState.Paused"/> state.</para>
685         ///     <para>If the <paramref name="accurate"/> is true, the play position will be adjusted as the specified <paramref name="position"/> value,
686         ///     but this might be considerably slow. If false, the play position will be a nearest keyframe position.</para>
687         ///     </remarks>
688         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
689         /// <exception cref="InvalidOperationException">The player is not in the valid state.<br/>
690         ///     -or-<br/>
691         ///     In case of non-seekable content, the player will return error and keep playing without changing the play position.</exception>
692         /// <exception cref="ArgumentOutOfRangeException">The specified position is not valid.</exception>
693         /// <seealso cref="SetPlayPositionNanosecondsAsync(long, bool)"/>
694         /// <seealso cref="GetPlayPosition"/>
695         /// <seealso cref="GetPlayPositionNanoseconds"/>
696         /// <since_tizen> 3 </since_tizen>
697         public async Task SetPlayPositionAsync(int position, bool accurate)
698         {
699             ValidatePlayerState(PlayerState.Ready, PlayerState.Playing, PlayerState.Paused);
700
701             await SetPlayPosition(position, accurate, false);
702         }
703
704         /// <summary>
705         /// Gets the play position in nanoseconds.
706         /// </summary>
707         /// <returns>The current position in nanoseconds.</returns>
708         /// <remarks>The player must be in the <see cref="PlayerState.Ready"/>, <see cref="PlayerState.Playing"/>,
709         /// or <see cref="PlayerState.Paused"/> state.</remarks>
710         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
711         /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
712         /// <seealso cref="SetPlayPositionAsync(int, bool)"/>
713         /// <seealso cref="SetPlayPositionNanosecondsAsync(long, bool)"/>
714         /// <seealso cref="GetPlayPosition"/>
715         /// <since_tizen> 5 </since_tizen>
716         public long GetPlayPositionNanoseconds()
717         {
718             ValidatePlayerState(PlayerState.Ready, PlayerState.Paused, PlayerState.Playing);
719
720             NativePlayer.GetPlayPositionNanoseconds(Handle, out long playPosition).
721                 ThrowIfFailed(this, "Failed to get the play position(nsec) of the player");
722
723             Log.Info(PlayerLog.Tag, $"get play position(nsec) : {playPosition}");
724
725             return playPosition;
726         }
727
728         /// <summary>
729         /// Sets the seek position in nanoseconds for playback, asynchronously.
730         /// </summary>
731         /// <param name="position">The value indicating a desired position in nanoseconds.</param>
732         /// <param name="accurate">The value indicating whether the operation performs with accuracy.</param>
733         /// <returns>A task that represents the asynchronous operation.</returns>
734         /// <remarks>
735         ///     <para>The player must be in the <see cref="PlayerState.Ready"/>, <see cref="PlayerState.Playing"/>,
736         ///     or <see cref="PlayerState.Paused"/> state.</para>
737         ///     <para>If the <paramref name="accurate"/> is true, the play position will be adjusted as the specified <paramref name="position"/> value,
738         ///     but this might be considerably slow. If false, the play position will be a nearest keyframe position.</para>
739         ///     </remarks>
740         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
741         /// <exception cref="InvalidOperationException">The player is not in the valid state.<br/>
742         ///     -or-<br/>
743         ///     In case of non-seekable content, the player will return error and keep playing without changing the play position.</exception>
744         /// <exception cref="ArgumentOutOfRangeException">The specified position is not valid.</exception>
745         /// <seealso cref="SetPlayPositionAsync(int, bool)"/>
746         /// <seealso cref="GetPlayPosition"/>
747         /// <seealso cref="GetPlayPositionNanoseconds"/>
748         /// <since_tizen> 5 </since_tizen>
749         public async Task SetPlayPositionNanosecondsAsync(long position, bool accurate)
750         {
751             ValidatePlayerState(PlayerState.Ready, PlayerState.Playing, PlayerState.Paused);
752
753             await SetPlayPosition(position, accurate, true);
754         }
755
756         /// <summary>
757         /// Sets the playback rate.
758         /// </summary>
759         /// <param name="rate">The value for the playback rate. Valid range is -5.0 to 5.0, inclusive.</param>
760         /// <remarks>
761         ///     <para>The player must be in the <see cref="PlayerState.Ready"/>, <see cref="PlayerState.Playing"/>,
762         ///     or <see cref="PlayerState.Paused"/> state.</para>
763         ///     <para>The sound will be muted, when the playback rate is under 0.0 or over 2.0.</para>
764         /// </remarks>
765         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
766         /// <exception cref="InvalidOperationException">
767         ///     The player is not in the valid state.<br/>
768         ///     -or-<br/>
769         ///     Streaming playback.
770         /// </exception>
771         /// <exception cref="ArgumentOutOfRangeException">
772         ///     <paramref name="rate"/> is less than -5.0.<br/>
773         ///     -or-<br/>
774         ///     <paramref name="rate"/> is greater than 5.0.<br/>
775         ///     -or-<br/>
776         ///     <paramref name="rate"/> is zero.
777         /// </exception>
778         /// <since_tizen> 3 </since_tizen>
779         public void SetPlaybackRate(float rate)
780         {
781             if (rate < -5.0F || 5.0F < rate || rate == 0.0F)
782             {
783                 throw new ArgumentOutOfRangeException(nameof(rate), rate, "Valid range is -5.0 to 5.0 (except 0.0)");
784             }
785
786             ValidatePlayerState(PlayerState.Ready, PlayerState.Playing, PlayerState.Paused);
787
788             NativePlayer.SetPlaybackRate(Handle, rate).ThrowIfFailed(this, "Failed to set the playback rate.");
789         }
790
791         /// <summary>
792         /// Applies the audio stream policy.
793         /// </summary>
794         /// <param name="policy">The <see cref="AudioStreamPolicy"/> to apply.</param>
795         /// <remarks>
796         /// The player must be in the <see cref="PlayerState.Idle"/> state.<br/>
797         /// <br/>
798         /// <see cref="Player"/> does not support all <see cref="AudioStreamType"/>.<br/>
799         /// Supported types are <see cref="AudioStreamType.Media"/>, <see cref="AudioStreamType.System"/>,
800         /// <see cref="AudioStreamType.Alarm"/>, <see cref="AudioStreamType.Notification"/>,
801         /// <see cref="AudioStreamType.Emergency"/>, <see cref="AudioStreamType.VoiceInformation"/>,
802         /// <see cref="AudioStreamType.RingtoneVoip"/> and <see cref="AudioStreamType.MediaExternalOnly"/>.
803         /// </remarks>
804         /// <exception cref="ObjectDisposedException">
805         ///     The player has already been disposed of.<br/>
806         ///     -or-<br/>
807         ///     <paramref name="policy"/> has already been disposed of.
808         /// </exception>
809         /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
810         /// <exception cref="ArgumentNullException"><paramref name="policy"/> is null.</exception>
811         /// <exception cref="NotSupportedException">
812         ///     The required feature is not supported.<br/>
813         ///     -or-<br/>
814         ///     <see cref="AudioStreamType"/> of <paramref name="policy"/> is not supported on the current platform.
815         /// </exception>
816         /// <seealso cref="AudioStreamPolicy"/>
817         /// <feature>http://tizen.org/feature/multimedia.player.stream_info</feature>
818         /// <since_tizen> 3 </since_tizen>
819         public void ApplyAudioStreamPolicy(AudioStreamPolicy policy)
820         {
821             ValidationUtil.ValidateFeatureSupported("http://tizen.org/feature/multimedia.player.stream_info");
822
823             if (policy == null)
824             {
825                 throw new ArgumentNullException(nameof(policy));
826             }
827
828             ValidatePlayerState(PlayerState.Idle);
829
830             var ret = NativePlayer.SetAudioPolicyInfo(Handle, policy.Handle);
831
832             if (ret == PlayerErrorCode.InvalidArgument)
833             {
834                 throw new NotSupportedException("The specified policy is not supported on the current system.");
835             }
836
837             ret.ThrowIfFailed(this, "Failed to set the audio stream policy to the player");
838         }
839
840         /// <summary>
841         /// Set the relative ROI (Region Of Interest) area as a decimal fraction based on the video source.
842         /// It can be regarded as zooming operation because the specified video area will be rendered to fit to the display.
843         /// </summary>
844         /// <param name="scaleRectangle">The containing the ROI area information.</param>
845         /// <remarks>
846         /// This function requires the ratio of the each coordinate and size to the video resolution size
847         /// to guarantee of showing the same area for the dynamic resolution video content.
848         /// This function have to be called after setting <see cref="Display"/>
849         /// </remarks>
850         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
851         /// <exception cref="InvalidOperationException">
852         ///     Operation failed; internal error.
853         ///     -or-<br/>
854         ///     The <see cref="PlayerDisplayType"/> is not set to <see cref="PlayerDisplayType.Overlay"/>.
855         ///     </exception>
856         /// <exception cref="ArgumentOutOfRangeException">
857         ///     <paramref name="scaleRectangle.ScaleX"/> is less than 0.0 or greater than 1.0.<br/>
858         ///     -or-<br/>
859         ///     <paramref name="scaleRectangle.ScaleY"/> is less than 0.0 or greater than 1.0.<br/>
860         ///     -or-<br/>
861         ///     <paramref name="scaleRectangle.ScaleWidth"/> is less than or equal to 0.0 or greater than 1.0.<br/>
862         ///     -or-<br/>
863         ///     <paramref name="scaleRectangle.ScaleHeight"/> is less than or equal to 0.0 or greater than 1.0.
864         /// </exception>
865         /// <seealso cref="ScaleRectangle"/>
866         /// <seealso cref="Display"/>
867         /// <seealso cref="StreamInfo.GetVideoProperties"/>
868         /// <seealso cref="GetVideoRoi"/>
869         /// <since_tizen> 5 </since_tizen>
870         public void SetVideoRoi(ScaleRectangle scaleRectangle)
871         {
872             ValidateNotDisposed();
873
874             if (scaleRectangle.ScaleX < 0 || scaleRectangle.ScaleX > 1)
875             {
876                 throw new ArgumentOutOfRangeException(nameof(scaleRectangle.ScaleX), scaleRectangle.ScaleX, "Valid range is 0 to 1.0");
877             }
878             if (scaleRectangle.ScaleY < 0 || scaleRectangle.ScaleY > 1)
879             {
880                 throw new ArgumentOutOfRangeException(nameof(scaleRectangle.ScaleY), scaleRectangle.ScaleY, "Valid range is 0 to 1.0");
881             }
882             if (scaleRectangle.ScaleWidth <= 0 || scaleRectangle.ScaleWidth > 1)
883             {
884                 throw new ArgumentOutOfRangeException(nameof(scaleRectangle.ScaleWidth), scaleRectangle.ScaleWidth, "Valid range is 0 to 1.0 (except 0.0)");
885             }
886             if (scaleRectangle.ScaleHeight <= 0 || scaleRectangle.ScaleHeight > 1)
887             {
888                 throw new ArgumentOutOfRangeException(nameof(scaleRectangle.ScaleHeight), scaleRectangle.ScaleHeight, "Valid range is 0 to 1.0 (except 0.0)");
889             }
890
891             NativePlayer.SetVideoRoi(Handle, scaleRectangle.ScaleX, scaleRectangle.ScaleY, scaleRectangle.ScaleWidth, scaleRectangle.ScaleHeight).ThrowIfFailed(this, "Failed to set the video roi area.");
892         }
893
894         /// <summary>
895         /// Get the relative ROI (Region Of Interest) area as a decimal fraction based on the video source.
896         /// </summary>
897         /// <returns>The <see cref="ScaleRectangle"/> containing the ROI area information.</returns>
898         /// <remarks>The specified ROI area is valid only in <see cref="PlayerDisplayType.Overlay"/>.</remarks>
899         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
900         /// <exception cref="InvalidOperationException">
901         ///     Operation failed; internal error.
902         ///     </exception>
903         /// <seealso cref="Display"/>
904         /// <seealso cref="StreamInfo.GetVideoProperties"/>
905         /// <seealso cref="SetVideoRoi"/>
906         /// <since_tizen> 5 </since_tizen>
907         public ScaleRectangle GetVideoRoi()
908         {
909             ValidateNotDisposed();
910
911             NativePlayer.GetVideoRoi(Handle, out var scaleX, out var scaleY,
912                 out var scaleWidth, out var scaleHeight).ThrowIfFailed(this, "Failed to get the video roi area");
913
914             return new ScaleRectangle(scaleX, scaleY, scaleWidth, scaleHeight);
915         }
916
917         /// <summary>
918         /// This supports the product infrastructure and is not intended to be used directly from application code.
919         /// </summary>
920         [EditorBrowsable(EditorBrowsableState.Never)]
921         protected MediaPacket GetMediaPacket(IntPtr handle)
922         {
923             MediaPacket mediaPacket = handle != IntPtr.Zero ? MediaPacket.From(handle) :
924                 throw new ArgumentException("MediaPacket handle is invalid.", nameof(handle));
925
926             return mediaPacket;
927         }
928         #endregion
929
930         #region Preparing state
931
932         private int _isPreparing;
933
934         private bool IsPreparing()
935         {
936             return Interlocked.CompareExchange(ref _isPreparing, 1, 1) == 1;
937         }
938
939         /// <summary>
940         /// This supports the product infrastructure and is not intended to be used directly from application code.
941         /// </summary>
942         [EditorBrowsable(EditorBrowsableState.Never)]
943         protected void SetPreparing()
944         {
945             Interlocked.Exchange(ref _isPreparing, 1);
946         }
947
948         /// <summary>
949         /// This supports the product infrastructure and is not intended to be used directly from application code.
950         /// </summary>
951         [EditorBrowsable(EditorBrowsableState.Never)]
952         protected void ClearPreparing()
953         {
954             Interlocked.Exchange(ref _isPreparing, 0);
955         }
956         #endregion
957
958         /// <summary>
959         /// Enable to decode an audio data for exporting PCM from a data.
960         /// </summary>
961         /// <param name="format">The media format handle required to audio PCM specification.
962         /// The format has to include <see cref="AudioMediaFormat.MimeType"/>,
963         /// <see cref="AudioMediaFormat.Channel"/> and <see cref="AudioMediaFormat.SampleRate"/>.
964         /// If the format is NULL, the original PCM format or platform default PCM format will be applied.</param>
965         /// <param name="option">The audio extract option.</param>
966         /// <remarks><para>The player must be in the <see cref="PlayerState.Idle"/> state.</para>
967         /// <para>A <see cref="AudioDataDecoded"/> event is called in a separate thread(not in the main loop).</para>
968         /// <para>The audio PCM data can be retrieved using a <see cref="AudioDataDecoded"/> event as a media packet
969         /// and it is available until it's destroyed by <see cref="MediaPacket.Dispose()"/>.
970         /// The packet has to be destroyed as quickly as possible after rendering the data
971         /// and all the packets have to be destroyed before <see cref="Unprepare"/> is called.</para></remarks>
972         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
973         /// <exception cref="ArgumentException">The value is not valid.</exception>
974         /// <exception cref="InvalidOperationException">
975         ///     Operation failed; internal error.
976         ///     -or-<br/>
977         ///     The player is not in the valid state.
978         ///     </exception>
979         /// <seealso cref="PlayerAudioExtractOption"/>
980         /// <seealso cref="DisableExportingAudioData"/>
981         /// <since_tizen> 6 </since_tizen>
982         public void EnableExportingAudioData(AudioMediaFormat format, PlayerAudioExtractOption option)
983         {
984             ValidatePlayerState(PlayerState.Idle);
985             ValidationUtil.ValidateEnum(typeof(PlayerAudioExtractOption), option, nameof(option));
986
987             _audioFrameDecodedCallback = (IntPtr packetHandle, IntPtr userData) =>
988             {
989                 var handler = AudioDataDecoded;
990                 if (handler != null)
991                 {
992                     Log.Debug(PlayerLog.Tag, "packet : " + packetHandle.ToString());
993                     handler.Invoke(this,
994                         new AudioDataDecodedEventArgs(MediaPacket.From(packetHandle)));
995                 }
996                 else
997                 {
998                     MediaPacket.From(packetHandle).Dispose();
999                 }
1000             };
1001
1002             NativePlayer.SetAudioFrameDecodedCb(Handle, format == null ? IntPtr.Zero : format.AsNativeHandle(), option,
1003                 _audioFrameDecodedCallback, IntPtr.Zero).ThrowIfFailed(this, "Failed to register the _audioFrameDecoded");
1004         }
1005
1006         /// <summary>
1007         /// Disable to decode an audio data.
1008         /// </summary>
1009         /// <remarks>The player must be in the <see cref="PlayerState.Idle"/> or <see cref="PlayerState.Ready"/>
1010         /// state.</remarks>
1011         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
1012         /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
1013         /// <seealso cref="EnableExportingAudioData"/>
1014         /// <since_tizen> 6 </since_tizen>
1015         public void DisableExportingAudioData()
1016         {
1017             ValidatePlayerState(PlayerState.Idle, PlayerState.Ready);
1018
1019             NativePlayer.UnsetAudioFrameDecodedCb(Handle).
1020                 ThrowIfFailed(this, "Failed to unset the AudioFrameDecoded");
1021
1022             _audioFrameDecodedCallback = null;
1023         }
1024     }
1025 }