[MediaPlayer] add API to set zoom with field of view for spherical video (#329)
[platform/core/csapi/tizenfx.git] / src / Tizen.Multimedia.MediaPlayer / Player / Player.cs
1 /*
2  * Copyright (c) 2016 Samsung Electronics Co., Ltd All Rights Reserved
3  *
4  * Licensed under the Apache License, Version 2.0 (the License);
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an AS IS BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
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         /// <remarks>
64         /// This supports the product infrastructure and is not intended to be used directly from application code.
65         /// </remarks>
66         [EditorBrowsable(EditorBrowsableState.Never)]
67         protected Player(IntPtr handle, Action<int, string> errorHandler)
68         {
69             // This constructor is to support TV product player.
70             // Be careful with 'handle'. It must be wrapped in safe handle, first.
71             _handle = handle != IntPtr.Zero ? new PlayerHandle(handle) :
72                 throw new ArgumentException("Handle is invalid.", nameof(handle));
73
74             _errorHandler = errorHandler;
75         }
76
77         private bool _initialized;
78
79         /// <summary>
80         /// This supports the product infrastructure and is not intended to be used directly from application code.
81         /// </summary>
82         [EditorBrowsable(EditorBrowsableState.Never)]
83         protected void Initialize()
84         {
85             if (_initialized)
86             {
87                 throw new InvalidOperationException("It has already been initialized.");
88             }
89
90             if (Features.IsSupported(PlayerFeatures.AudioEffect))
91             {
92                 _audioEffect = new AudioEffect(this);
93             }
94
95             if (Features.IsSupported(PlayerFeatures.RawVideo))
96             {
97                 RegisterVideoFrameDecodedCallback();
98             }
99
100             RegisterEvents();
101
102             _displaySettings = PlayerDisplaySettings.Create(this);
103
104             _initialized = true;
105         }
106
107         internal void ValidatePlayerState(params PlayerState[] desiredStates)
108         {
109             Debug.Assert(desiredStates.Length > 0);
110
111             ValidateNotDisposed();
112
113             var curState = State;
114             if (curState.IsAnyOf(desiredStates))
115             {
116                 return;
117             }
118
119             throw new InvalidOperationException($"The player is not in a valid state. " +
120                 $"Current State : { curState }, Valid State : { string.Join(", ", desiredStates) }.");
121         }
122
123         #region Dispose support
124         private bool _disposed;
125
126         /// <summary>
127         /// Releases all resources used by the current instance.
128         /// </summary>
129         /// <since_tizen> 3 </since_tizen>
130         public void Dispose()
131         {
132             Dispose(true);
133         }
134
135         /// <summary>
136         /// Releases the unmanaged resources used by the <see cref="Player"/>.
137         /// </summary>
138         /// <param name="disposing">
139         /// true to release both managed and unmanaged resources; false to release only unmanaged resources.
140         /// </param>
141         [EditorBrowsable(EditorBrowsableState.Never)]
142         protected virtual void Dispose(bool disposing)
143         {
144             if (!_disposed)
145             {
146                 ReplaceDisplay(null);
147
148                 if (_source != null)
149                 {
150                     try
151                     {
152                         _source.DetachFrom(this);
153                     }
154                     catch (Exception e)
155                     {
156                         Log.Error(PlayerLog.Tag, e.ToString());
157                     }
158                 }
159                 _source = null;
160
161                 if (_handle != null)
162                 {
163                     _handle.Dispose();
164                 }
165                 _disposed = true;
166             }
167         }
168
169         internal void ValidateNotDisposed()
170         {
171             if (_disposed)
172             {
173                 Log.Warn(PlayerLog.Tag, "player was disposed");
174                 throw new ObjectDisposedException(nameof(Player));
175             }
176         }
177
178         internal bool IsDisposed => _disposed;
179         #endregion
180
181         #region Methods
182
183         /// <summary>
184         /// Gets the streaming download progress.
185         /// </summary>
186         /// <returns>The <see cref="DownloadProgress"/> containing current download progress.</returns>
187         /// <remarks>The player must be in the <see cref="PlayerState.Playing"/> or <see cref="PlayerState.Paused"/> state.</remarks>
188         /// <exception cref="InvalidOperationException">
189         ///     The player is not streaming.<br/>
190         ///     -or-<br/>
191         ///     The player is not in the valid state.
192         ///     </exception>
193         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
194         /// <since_tizen> 3 </since_tizen>
195         public DownloadProgress GetDownloadProgress()
196         {
197             ValidatePlayerState(PlayerState.Playing, PlayerState.Paused);
198
199             int start = 0;
200             int current = 0;
201             NativePlayer.GetStreamingDownloadProgress(Handle, out start, out current).
202                 ThrowIfFailed(this, "Failed to get download progress");
203
204             Log.Info(PlayerLog.Tag, "get download progress : " + start + ", " + current);
205
206             return new DownloadProgress(start, current);
207         }
208
209         /// <summary>
210         /// Sets the subtitle path for playback.
211         /// </summary>
212         /// <remarks>Only MicroDVD/SubViewer(*.sub), SAMI(*.smi), and SubRip(*.srt) subtitle formats are supported.
213         ///     <para>The mediastorage privilege(http://tizen.org/privilege/mediastorage) must be added if any files are used to play located in the internal storage.
214         ///     The externalstorage privilege(http://tizen.org/privilege/externalstorage) must be added if any files are used to play located in the external storage.</para>
215         /// </remarks>
216         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
217         /// <exception cref="ArgumentException"><paramref name="path"/> is an empty string.</exception>
218         /// <exception cref="FileNotFoundException">The specified path does not exist.</exception>
219         /// <exception cref="ArgumentNullException"><paramref name="path"/> is null.</exception>
220         /// <since_tizen> 3 </since_tizen>
221         public void SetSubtitle(string path)
222         {
223             ValidateNotDisposed();
224
225             if (path == null)
226             {
227                 throw new ArgumentNullException(nameof(path));
228             }
229
230             if (path.Length == 0)
231             {
232                 throw new ArgumentException("The path is empty.", nameof(path));
233             }
234
235             if (!File.Exists(path))
236             {
237                 throw new FileNotFoundException($"The specified file does not exist.", path);
238             }
239
240             NativePlayer.SetSubtitlePath(Handle, path).
241                 ThrowIfFailed(this, "Failed to set the subtitle path to the player");
242         }
243
244         /// <summary>
245         /// Removes the subtitle path.
246         /// </summary>
247         /// <remarks>The player must be in the <see cref="PlayerState.Idle"/> state.</remarks>
248         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
249         /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
250         /// <since_tizen> 3 </since_tizen>
251         public void ClearSubtitle()
252         {
253             ValidatePlayerState(PlayerState.Idle);
254
255             NativePlayer.SetSubtitlePath(Handle, null).
256                 ThrowIfFailed(this, "Failed to clear the subtitle of the player");
257         }
258
259         /// <summary>
260         /// Sets the offset for the subtitle.
261         /// </summary>
262         /// <param name="offset">The value indicating a desired offset in milliseconds.</param>
263         /// <remarks>The player must be in the <see cref="PlayerState.Playing"/> or <see cref="PlayerState.Paused"/> state.</remarks>
264         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
265         /// <exception cref="InvalidOperationException">
266         ///     The player is not in the valid state.<br/>
267         ///     -or-<br/>
268         ///     No subtitle is set.
269         /// </exception>
270         /// <seealso cref="SetSubtitle(string)"/>
271         /// <since_tizen> 3 </since_tizen>
272         public void SetSubtitleOffset(int offset)
273         {
274             ValidatePlayerState(PlayerState.Playing, PlayerState.Paused);
275
276             var err = NativePlayer.SetSubtitlePositionOffset(Handle, offset);
277
278             if (err == PlayerErrorCode.FeatureNotSupported)
279             {
280                 throw new InvalidOperationException("No subtitle set");
281             }
282
283             err.ThrowIfFailed(this, "Failed to the subtitle offset of the player");
284         }
285
286         private void Prepare()
287         {
288             NativePlayer.Prepare(Handle).ThrowIfFailed(this, "Failed to prepare the player");
289         }
290
291         /// <summary>
292         /// Called when the <see cref="Prepare"/> is invoked.
293         /// </summary>
294         /// <since_tizen> 3 </since_tizen>
295         protected virtual void OnPreparing()
296         {
297         }
298
299         /// <summary>
300         /// Prepares the media player for playback, asynchronously.
301         /// </summary>
302         /// <returns>A task that represents the asynchronous prepare operation.</returns>
303         /// <remarks>To prepare the player, the player must be in the <see cref="PlayerState.Idle"/> state,
304         ///     and a source must be set.</remarks>
305         /// <exception cref="InvalidOperationException">No source is set.</exception>
306         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
307         /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
308         /// <since_tizen> 3 </since_tizen>
309         public virtual Task PrepareAsync()
310         {
311             if (_source == null)
312             {
313                 throw new InvalidOperationException("No source is set.");
314             }
315
316             ValidatePlayerState(PlayerState.Idle);
317
318             OnPreparing();
319
320             SetPreparing();
321
322             return Task.Factory.StartNew(() =>
323             {
324                 try
325                 {
326                     Prepare();
327                 }
328                 finally
329                 {
330                     ClearPreparing();
331                 }
332             }, CancellationToken.None,
333                 TaskCreationOptions.DenyChildAttach | TaskCreationOptions.LongRunning,
334                 TaskScheduler.Default);
335         }
336
337         /// <summary>
338         /// Unprepares the player.
339         /// </summary>
340         /// <remarks>
341         ///     The most recently used source is reset and is no longer associated with the player. Playback is no longer possible.
342         ///     If you want to use the player again, you have to set a source and call <see cref="PrepareAsync"/> again.
343         ///     <para>
344         ///     The player must be in the <see cref="PlayerState.Ready"/>, <see cref="PlayerState.Playing"/>, or <see cref="PlayerState.Paused"/> state.
345         ///     It has no effect if the player is already in the <see cref="PlayerState.Idle"/> state.
346         ///     </para>
347         /// </remarks>
348         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
349         /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
350         /// <since_tizen> 3 </since_tizen>
351         public virtual void Unprepare()
352         {
353             if (State == PlayerState.Idle)
354             {
355                 Log.Warn(PlayerLog.Tag, "idle state already");
356                 return;
357             }
358             ValidatePlayerState(PlayerState.Ready, PlayerState.Paused, PlayerState.Playing);
359
360             NativePlayer.Unprepare(Handle).ThrowIfFailed(this, "Failed to unprepare the player");
361
362             OnUnprepared();
363         }
364
365         /// <summary>
366         /// Called after the <see cref="Player"/> is unprepared.
367         /// </summary>
368         /// <seealso cref="Unprepare"/>
369         /// <since_tizen> 3 </since_tizen>
370         protected virtual void OnUnprepared()
371         {
372             _source?.DetachFrom(this);
373             _source = null;
374         }
375
376         /// <summary>
377         /// Starts or resumes playback.
378         /// </summary>
379         /// <remarks>
380         /// The player must be in the <see cref="PlayerState.Ready"/> or <see cref="PlayerState.Paused"/> state.
381         /// It has no effect if the player is already in the <see cref="PlayerState.Playing"/> state.<br/>
382         /// <br/>
383         /// Sound can be mixed with other sounds if you don't control the stream focus using <see cref="ApplyAudioStreamPolicy"/>.
384         /// </remarks>
385         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
386         /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
387         /// <seealso cref="PrepareAsync"/>
388         /// <seealso cref="Stop"/>
389         /// <seealso cref="Pause"/>
390         /// <seealso cref="PlaybackCompleted"/>
391         /// <seealso cref="ApplyAudioStreamPolicy"/>
392         /// <since_tizen> 3 </since_tizen>
393         public virtual void Start()
394         {
395             if (State == PlayerState.Playing)
396             {
397                 Log.Warn(PlayerLog.Tag, "playing state already");
398                 return;
399             }
400             ValidatePlayerState(PlayerState.Ready, PlayerState.Paused);
401
402             NativePlayer.Start(Handle).ThrowIfFailed(this, "Failed to start the player");
403         }
404
405         /// <summary>
406         /// Stops playing the media content.
407         /// </summary>
408         /// <remarks>
409         /// The player must be in the <see cref="PlayerState.Playing"/> or <see cref="PlayerState.Paused"/> state.
410         /// It has no effect if the player is already in the <see cref="PlayerState.Ready"/> state.
411         /// </remarks>
412         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
413         /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
414         /// <seealso cref="Start"/>
415         /// <seealso cref="Pause"/>
416         /// <since_tizen> 3 </since_tizen>
417         public virtual void Stop()
418         {
419             if (State == PlayerState.Ready)
420             {
421                 Log.Warn(PlayerLog.Tag, "ready state already");
422                 return;
423             }
424             ValidatePlayerState(PlayerState.Paused, PlayerState.Playing);
425
426             NativePlayer.Stop(Handle).ThrowIfFailed(this, "Failed to stop the player");
427         }
428
429         /// <summary>
430         /// Pauses the player.
431         /// </summary>
432         /// <remarks>
433         /// The player must be in the <see cref="PlayerState.Playing"/> state.
434         /// It has no effect if the player is already in the <see cref="PlayerState.Paused"/> state.
435         /// </remarks>
436         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
437         /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
438         /// <seealso cref="Start"/>
439         /// <since_tizen> 3 </since_tizen>
440         public virtual void Pause()
441         {
442             if (State == PlayerState.Paused)
443             {
444                 Log.Warn(PlayerLog.Tag, "pause state already");
445                 return;
446             }
447
448             ValidatePlayerState(PlayerState.Playing);
449
450             NativePlayer.Pause(Handle).ThrowIfFailed(this, "Failed to pause the player");
451         }
452
453         private MediaSource _source;
454
455         /// <summary>
456         /// Determines whether MediaSource has set.
457         /// This supports the product infrastructure and is not intended to be used directly from application code.
458         /// </summary>
459         [EditorBrowsable(EditorBrowsableState.Never)]
460         protected bool HasSource => _source != null;
461
462         /// <summary>
463         /// Sets a media source for the player.
464         /// </summary>
465         /// <param name="source">A <see cref="MediaSource"/> that specifies the source for playback.</param>
466         /// <remarks>The player must be in the <see cref="PlayerState.Idle"/> state.</remarks>
467         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
468         /// <exception cref="InvalidOperationException">
469         ///     The player is not in the valid state.<br/>
470         ///     -or-<br/>
471         ///     It is not able to assign the source to the player.
472         ///     </exception>
473         /// <seealso cref="PrepareAsync"/>
474         /// <since_tizen> 3 </since_tizen>
475         public void SetSource(MediaSource source)
476         {
477             ValidatePlayerState(PlayerState.Idle);
478
479             if (source != null)
480             {
481                 source.AttachTo(this);
482             }
483
484             if (_source != null)
485             {
486                 _source.DetachFrom(this);
487             }
488
489             _source = source;
490         }
491
492         /// <summary>
493         /// Captures a video frame, asynchronously.
494         /// </summary>
495         /// <returns>A task that represents the asynchronous capture operation.</returns>
496         /// <feature>http://tizen.org/feature/multimedia.raw_video</feature>
497         /// <remarks>The player must be in the <see cref="PlayerState.Playing"/> or <see cref="PlayerState.Paused"/> state.</remarks>
498         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
499         /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
500         /// <exception cref="NotSupportedException">The required feature is not supported.</exception>
501         /// <since_tizen> 3 </since_tizen>
502         public async Task<CapturedFrame> CaptureVideoAsync()
503         {
504             ValidationUtil.ValidateFeatureSupported(PlayerFeatures.RawVideo);
505
506             ValidatePlayerState(PlayerState.Playing, PlayerState.Paused);
507
508             TaskCompletionSource<CapturedFrame> t = new TaskCompletionSource<CapturedFrame>();
509
510             NativePlayer.VideoCaptureCallback cb = (data, width, height, size, _) =>
511             {
512                 Debug.Assert(size <= int.MaxValue);
513
514                 byte[] buf = new byte[size];
515                 Marshal.Copy(data, buf, 0, (int)size);
516
517                 t.TrySetResult(new CapturedFrame(buf, width, height));
518             };
519
520             using (var cbKeeper = ObjectKeeper.Get(cb))
521             {
522                 NativePlayer.CaptureVideo(Handle, cb)
523                     .ThrowIfFailed(this, "Failed to capture the video");
524
525                 return await t.Task;
526             }
527         }
528
529         /// <summary>
530         /// Gets the play position in milliseconds.
531         /// </summary>
532         /// <remarks>The player must be in the <see cref="PlayerState.Ready"/>, <see cref="PlayerState.Playing"/>,
533         /// or <see cref="PlayerState.Paused"/> state.</remarks>
534         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
535         /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
536         /// <seealso cref="SetPlayPositionAsync(int, bool)"/>
537         /// <since_tizen> 3 </since_tizen>
538         public int GetPlayPosition()
539         {
540             ValidatePlayerState(PlayerState.Ready, PlayerState.Paused, PlayerState.Playing);
541
542             int playPosition = 0;
543
544             NativePlayer.GetPlayPosition(Handle, out playPosition).
545                 ThrowIfFailed(this, "Failed to get the play position of the player");
546
547             Log.Info(PlayerLog.Tag, "get play position : " + playPosition);
548
549             return playPosition;
550         }
551
552         private void SetPlayPosition(int milliseconds, bool accurate,
553             NativePlayer.SeekCompletedCallback cb)
554         {
555             var ret = NativePlayer.SetPlayPosition(Handle, milliseconds, accurate, cb, IntPtr.Zero);
556
557             //Note that we assume invalid param error is returned only when the position value is invalid.
558             if (ret == PlayerErrorCode.InvalidArgument)
559             {
560                 throw new ArgumentOutOfRangeException(nameof(milliseconds), milliseconds,
561                     "The position is not valid.");
562             }
563             if (ret != PlayerErrorCode.None)
564             {
565                 Log.Error(PlayerLog.Tag, "Failed to set play position, " + (PlayerError)ret);
566             }
567             ret.ThrowIfFailed(this, "Failed to set play position");
568         }
569
570         /// <summary>
571         /// Sets the seek position for playback, asynchronously.
572         /// </summary>
573         /// <param name="position">The value indicating a desired position in milliseconds.</param>
574         /// <param name="accurate">The value indicating whether the operation performs with accuracy.</param>
575         /// <remarks>
576         ///     <para>The player must be in the <see cref="PlayerState.Ready"/>, <see cref="PlayerState.Playing"/>,
577         ///     or <see cref="PlayerState.Paused"/> state.</para>
578         ///     <para>If the <paramref name="accurate"/> is true, the play position will be adjusted as the specified <paramref name="position"/> value,
579         ///     but this might be considerably slow. If false, the play position will be a nearest keyframe position.</para>
580         ///     </remarks>
581         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
582         /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
583         /// <exception cref="ArgumentOutOfRangeException">The specified position is not valid.</exception>
584         /// <seealso cref="GetPlayPosition"/>
585         /// <since_tizen> 3 </since_tizen>
586         public async Task SetPlayPositionAsync(int position, bool accurate)
587         {
588             ValidatePlayerState(PlayerState.Ready, PlayerState.Playing, PlayerState.Paused);
589
590             var taskCompletionSource = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
591
592             bool immediateResult = _source is MediaStreamSource;
593
594             NativePlayer.SeekCompletedCallback cb = _ => taskCompletionSource.TrySetResult(true);
595
596             using (var cbKeeper = ObjectKeeper.Get(cb))
597             {
598                 SetPlayPosition(position, accurate, cb);
599                 if (immediateResult)
600                 {
601                     taskCompletionSource.TrySetResult(true);
602                 }
603
604                 await taskCompletionSource.Task;
605             }
606         }
607
608         /// <summary>
609         /// Sets the playback rate.
610         /// </summary>
611         /// <param name="rate">The value for the playback rate. Valid range is -5.0 to 5.0, inclusive.</param>
612         /// <remarks>
613         ///     <para>The player must be in the <see cref="PlayerState.Ready"/>, <see cref="PlayerState.Playing"/>,
614         ///     or <see cref="PlayerState.Paused"/> state.</para>
615         ///     <para>The sound will be muted, when the playback rate is under 0.0 or over 2.0.</para>
616         /// </remarks>
617         /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
618         /// <exception cref="InvalidOperationException">
619         ///     The player is not in the valid state.<br/>
620         ///     -or-<br/>
621         ///     Streaming playback.
622         /// </exception>
623         /// <exception cref="ArgumentOutOfRangeException">
624         ///     <paramref name="rate"/> is less than -5.0.<br/>
625         ///     -or-<br/>
626         ///     <paramref name="rate"/> is greater than 5.0.<br/>
627         ///     -or-<br/>
628         ///     <paramref name="rate"/> is zero.
629         /// </exception>
630         /// <since_tizen> 3 </since_tizen>
631         public void SetPlaybackRate(float rate)
632         {
633             if (rate < -5.0F || 5.0F < rate || rate == 0.0F)
634             {
635                 throw new ArgumentOutOfRangeException(nameof(rate), rate, "Valid range is -5.0 to 5.0 (except 0.0)");
636             }
637
638             ValidatePlayerState(PlayerState.Ready, PlayerState.Playing, PlayerState.Paused);
639
640             NativePlayer.SetPlaybackRate(Handle, rate).ThrowIfFailed(this, "Failed to set the playback rate.");
641         }
642
643         /// <summary>
644         /// Applies the audio stream policy.
645         /// </summary>
646         /// <param name="policy">The <see cref="AudioStreamPolicy"/> to apply.</param>
647         /// <remarks>
648         /// The player must be in the <see cref="PlayerState.Idle"/> state.<br/>
649         /// <br/>
650         /// <see cref="Player"/> does not support all <see cref="AudioStreamType"/>.<br/>
651         /// Supported types are <see cref="AudioStreamType.Media"/>, <see cref="AudioStreamType.System"/>,
652         /// <see cref="AudioStreamType.Alarm"/>, <see cref="AudioStreamType.Notification"/>,
653         /// <see cref="AudioStreamType.Emergency"/>, <see cref="AudioStreamType.VoiceInformation"/>,
654         /// <see cref="AudioStreamType.RingtoneVoip"/> and <see cref="AudioStreamType.MediaExternalOnly"/>.
655         /// </remarks>
656         /// <exception cref="ObjectDisposedException">
657         ///     The player has already been disposed of.<br/>
658         ///     -or-<br/>
659         ///     <paramref name="policy"/> has already been disposed of.
660         /// </exception>
661         /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
662         /// <exception cref="ArgumentNullException"><paramref name="policy"/> is null.</exception>
663         /// <exception cref="NotSupportedException">
664         ///     The required feature is not supported.<br/>
665         ///     -or-<br/>
666         ///     <see cref="AudioStreamType"/> of <paramref name="policy"/> is not supported on the current platform.
667         /// </exception>
668         /// <seealso cref="AudioStreamPolicy"/>
669         /// <feature>http://tizen.org/feature/multimedia.player.stream_info</feature>
670         /// <since_tizen> 3 </since_tizen>
671         public void ApplyAudioStreamPolicy(AudioStreamPolicy policy)
672         {
673             ValidationUtil.ValidateFeatureSupported("http://tizen.org/feature/multimedia.player.stream_info");
674
675             if (policy == null)
676             {
677                 throw new ArgumentNullException(nameof(policy));
678             }
679
680             ValidatePlayerState(PlayerState.Idle);
681
682             var ret = NativePlayer.SetAudioPolicyInfo(Handle, policy.Handle);
683
684             if (ret == PlayerErrorCode.InvalidArgument)
685             {
686                 throw new NotSupportedException("The specified policy is not supported on the current system.");
687             }
688
689             ret.ThrowIfFailed(this, "Failed to set the audio stream policy to the player");
690         }
691         #endregion
692
693         #region Preparing state
694
695         private int _isPreparing;
696
697         private bool IsPreparing()
698         {
699             return Interlocked.CompareExchange(ref _isPreparing, 1, 1) == 1;
700         }
701
702         /// <summary>
703         /// This supports the product infrastructure and is not intended to be used directly from application code.
704         /// </summary>
705         [EditorBrowsable(EditorBrowsableState.Never)]
706         protected void SetPreparing()
707         {
708             Interlocked.Exchange(ref _isPreparing, 1);
709         }
710
711         /// <summary>
712         /// This supports the product infrastructure and is not intended to be used directly from application code.
713         /// </summary>
714         [EditorBrowsable(EditorBrowsableState.Never)]
715         protected void ClearPreparing()
716         {
717             Interlocked.Exchange(ref _isPreparing, 0);
718         }
719
720         #endregion
721     }
722 }