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