2 * Copyright (c) 2018 Samsung Electronics Co., Ltd All Rights Reserved
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
19 using System.ComponentModel;
20 using System.Diagnostics;
22 using System.Runtime.InteropServices;
23 using System.Threading;
24 using System.Threading.Tasks;
26 namespace Tizen.Multimedia
28 internal static class PlayerLog
30 internal const string Tag = "Tizen.Multimedia.Player";
34 /// Provides the ability to control media playback.
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.
41 public partial class Player : IDisposable, IDisplayable<PlayerErrorCode>
43 private readonly PlayerHandle _handle;
46 /// Initializes a new instance of the <see cref="Player"/> class.
48 /// <since_tizen> 3 </since_tizen>
51 NativePlayer.Create(out _handle).ThrowIfFailed(null, "Failed to create player");
53 Debug.Assert(_handle != null);
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.
63 /// <param name="handle">The handle for the media player.</param>
64 /// <param name="errorHandler">The handle for occuring error.</param>
66 /// This supports the product infrastructure and is not intended to be used directly from application code.
68 [EditorBrowsable(EditorBrowsableState.Never)]
69 protected Player(IntPtr handle, Action<int, string> errorHandler)
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));
76 _errorHandler = errorHandler;
79 private bool _initialized;
82 /// This supports the product infrastructure and is not intended to be used directly from application code.
84 [EditorBrowsable(EditorBrowsableState.Never)]
85 protected void Initialize()
89 throw new InvalidOperationException("It has already been initialized.");
92 if (Features.IsSupported(PlayerFeatures.AudioEffect))
94 _audioEffect = new AudioEffect(this);
97 if (Features.IsSupported(PlayerFeatures.RawVideo))
99 RegisterVideoFrameDecodedCallback();
104 _displaySettings = PlayerDisplaySettings.Create(this);
109 internal void ValidatePlayerState(params PlayerState[] desiredStates)
111 Debug.Assert(desiredStates.Length > 0);
113 ValidateNotDisposed();
115 var curState = State;
116 if (curState.IsAnyOf(desiredStates))
121 throw new InvalidOperationException($"The player is not in a valid state. " +
122 $"Current State : { curState }, Valid State : { string.Join(", ", desiredStates) }.");
125 #region Dispose support
126 private bool _disposed;
129 /// Releases all resources used by the current instance.
131 /// <since_tizen> 3 </since_tizen>
132 public void Dispose()
135 GC.SuppressFinalize(this);
139 /// Releases the unmanaged resources used by the <see cref="Player"/>.
141 /// <param name="disposing">
142 /// true to release both managed and unmanaged resources; false to release only unmanaged resources.
144 [EditorBrowsable(EditorBrowsableState.Never)]
145 protected virtual void Dispose(bool disposing)
154 ReplaceDisplay(null);
160 _source.DetachFrom(this);
165 Log.Error(PlayerLog.Tag, e.ToString());
177 internal void ValidateNotDisposed()
181 Log.Warn(PlayerLog.Tag, "player was disposed");
182 throw new ObjectDisposedException(nameof(Player));
186 internal bool IsDisposed => _disposed;
192 /// Gets the streaming download progress.
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/>
200 /// The player is not in the valid state.
202 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
203 /// <since_tizen> 3 </since_tizen>
204 public DownloadProgress GetDownloadProgress()
206 ValidatePlayerState(PlayerState.Ready, PlayerState.Playing, PlayerState.Paused);
210 NativePlayer.GetStreamingDownloadProgress(Handle, out start, out current).
211 ThrowIfFailed(this, "Failed to get download progress");
213 Log.Info(PlayerLog.Tag, $"get download progress : {start}, {current}");
215 return new DownloadProgress(start, current);
219 /// Sets the subtitle path for playback.
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>
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)
233 ValidateNotDisposed();
237 throw new ArgumentNullException(nameof(path));
240 if (path.Length == 0)
242 throw new ArgumentException("The path is empty.", nameof(path));
245 if (!File.Exists(path))
247 throw new FileNotFoundException($"The specified file does not exist.", path);
250 NativePlayer.SetSubtitlePath(Handle, path).
251 ThrowIfFailed(this, "Failed to set the subtitle path to the player");
255 /// Removes the subtitle path.
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()
263 ValidatePlayerState(PlayerState.Idle);
265 NativePlayer.SetSubtitlePath(Handle, null).
266 ThrowIfFailed(this, "Failed to clear the subtitle of the player");
270 /// Sets the offset for the subtitle.
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/>
278 /// No subtitle is set.
280 /// <seealso cref="SetSubtitle(string)"/>
281 /// <since_tizen> 3 </since_tizen>
282 public void SetSubtitleOffset(int offset)
284 ValidatePlayerState(PlayerState.Playing, PlayerState.Paused);
286 var err = NativePlayer.SetSubtitlePositionOffset(Handle, offset);
288 if (err == PlayerErrorCode.FeatureNotSupported)
290 throw new InvalidOperationException("No subtitle set");
293 err.ThrowIfFailed(this, "Failed to the subtitle offset of the player");
296 private void Prepare()
298 NativePlayer.Prepare(Handle).ThrowIfFailed(this, "Failed to prepare the player");
302 /// Called when the <see cref="Prepare"/> is invoked.
304 /// <since_tizen> 3 </since_tizen>
305 protected virtual void OnPreparing()
310 /// Prepares the media player for playback, asynchronously.
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()
324 throw new InvalidOperationException("No source is set.");
327 ValidatePlayerState(PlayerState.Idle);
333 return Task.Factory.StartNew(() =>
343 }, CancellationToken.None,
344 TaskCreationOptions.DenyChildAttach | TaskCreationOptions.LongRunning,
345 TaskScheduler.Default);
349 /// Prepares the cancellable media player for playback, asynchronously.
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.
362 /// The player is not in the valid state.
364 /// <seealso cref="PrepareAsync()"/>
365 /// <seealso cref="Unprepare()"/>
366 /// <since_tizen> 6 </since_tizen>
367 public virtual async Task PrepareAsync(CancellationToken cancellationToken)
369 ValidateNotDisposed();
371 var taskCompletionSource = new TaskCompletionSource<bool>();
375 throw new InvalidOperationException("No source is set.");
378 ValidatePlayerState(PlayerState.Idle);
384 // register a callback to handle cancellation token anytime it occurs
385 cancellationToken.Register(() =>
387 ValidatePlayerState(PlayerState.Preparing);
389 // a user can get the state before finally block is called.
392 Log.Warn(PlayerLog.Tag, $"preparing will be cancelled.");
393 NativePlayer.Unprepare(Handle).ThrowIfFailed(this, "Failed to unprepare the player");
395 taskCompletionSource.TrySetCanceled();
398 _prepareCallback = _ =>
400 Log.Warn(PlayerLog.Tag, $"prepared callback is called.");
401 taskCompletionSource.TrySetResult(true);
406 NativePlayer.PrepareAsync(Handle, _prepareCallback, IntPtr.Zero).
407 ThrowIfFailed(this, "Failed to prepare the player");
409 await taskCompletionSource.Task.ConfigureAwait(false);
418 /// Unprepares the player.
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.
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.
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()
433 if (State == PlayerState.Idle)
435 Log.Warn(PlayerLog.Tag, "idle state already");
438 ValidatePlayerState(PlayerState.Ready, PlayerState.Paused, PlayerState.Playing);
440 NativePlayer.Unprepare(Handle).ThrowIfFailed(this, "Failed to unprepare the player");
446 /// Called after the <see cref="Player"/> is unprepared.
448 /// <seealso cref="Unprepare"/>
449 /// <since_tizen> 3 </since_tizen>
450 protected virtual void OnUnprepared()
452 _source?.DetachFrom(this);
457 /// Starts or resumes playback.
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>
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()
481 ValidatePlayerState(PlayerState.Ready, PlayerState.Paused, PlayerState.Playing);
483 NativePlayer.Start(Handle).ThrowIfFailed(this, "Failed to start the player");
487 /// Stops playing the media content.
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.
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()
500 if (State == PlayerState.Ready)
502 Log.Warn(PlayerLog.Tag, "ready state already");
505 ValidatePlayerState(PlayerState.Paused, PlayerState.Playing);
507 NativePlayer.Stop(Handle).ThrowIfFailed(this, "Failed to stop the player");
511 /// Pauses the player.
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.
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()
523 if (State == PlayerState.Paused)
525 Log.Warn(PlayerLog.Tag, "pause state already");
529 ValidatePlayerState(PlayerState.Playing);
531 NativePlayer.Pause(Handle).ThrowIfFailed(this, "Failed to pause the player");
534 private MediaSource _source;
537 /// Determines whether MediaSource has set.
538 /// This supports the product infrastructure and is not intended to be used directly from application code.
540 [EditorBrowsable(EditorBrowsableState.Never)]
541 protected bool HasSource => _source != null;
544 /// Sets a media source for the player.
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/>
552 /// It is not able to assign the source to the player.
554 /// <seealso cref="PrepareAsync"/>
555 /// <since_tizen> 3 </since_tizen>
556 public void SetSource(MediaSource source)
558 ValidatePlayerState(PlayerState.Idle);
562 source.AttachTo(this);
567 _source.DetachFrom(this);
574 /// Captures a video frame, asynchronously.
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()
585 ValidationUtil.ValidateFeatureSupported(PlayerFeatures.RawVideo);
587 ValidatePlayerState(PlayerState.Playing, PlayerState.Paused);
589 TaskCompletionSource<CapturedFrame> t = new TaskCompletionSource<CapturedFrame>();
591 NativePlayer.VideoCaptureCallback cb = (data, width, height, size, _) =>
593 Debug.Assert(size <= int.MaxValue);
595 byte[] buf = new byte[size];
596 Marshal.Copy(data, buf, 0, (int)size);
598 t.TrySetResult(new CapturedFrame(buf, width, height));
601 using (var cbKeeper = ObjectKeeper.Get(cb))
603 NativePlayer.CaptureVideo(Handle, cb)
604 .ThrowIfFailed(this, "Failed to capture the video");
611 /// Gets the play position in milliseconds.
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()
624 ValidatePlayerState(PlayerState.Ready, PlayerState.Paused, PlayerState.Playing);
626 int playPosition = 0;
628 NativePlayer.GetPlayPosition(Handle, out playPosition).
629 ThrowIfFailed(this, "Failed to get the play position of the player");
631 Log.Info(PlayerLog.Tag, $"get play position : {playPosition}");
636 private void NativeSetPlayPosition(long position, bool accurate, bool nanoseconds,
637 NativePlayer.SeekCompletedCallback cb)
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);
643 //Note that we assume invalid param error is returned only when the position value is invalid.
644 if (ret == PlayerErrorCode.InvalidArgument)
646 throw new ArgumentOutOfRangeException(nameof(position), position,
647 "The position is not valid.");
649 if (ret != PlayerErrorCode.None)
651 Log.Error(PlayerLog.Tag, "Failed to set play position, " + (PlayerError)ret);
653 ret.ThrowIfFailed(this, "Failed to set play position");
656 private async Task SetPlayPosition(long position, bool accurate, bool nanoseconds)
658 var taskCompletionSource = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
660 bool immediateResult = _source is MediaStreamSource;
662 NativePlayer.SeekCompletedCallback cb = _ => taskCompletionSource.TrySetResult(true);
664 using (var cbKeeper = ObjectKeeper.Get(cb))
666 NativeSetPlayPosition(position, accurate, nanoseconds, immediateResult ? null : cb);
670 taskCompletionSource.TrySetResult(true);
672 await taskCompletionSource.Task;
677 /// Sets the seek position for playback, asynchronously.
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>
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>
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/>
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)
699 ValidatePlayerState(PlayerState.Ready, PlayerState.Playing, PlayerState.Paused);
701 await SetPlayPosition(position, accurate, false);
705 /// Gets the play position in nanoseconds.
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()
718 ValidatePlayerState(PlayerState.Ready, PlayerState.Paused, PlayerState.Playing);
720 NativePlayer.GetPlayPositionNanoseconds(Handle, out long playPosition).
721 ThrowIfFailed(this, "Failed to get the play position(nsec) of the player");
723 Log.Info(PlayerLog.Tag, $"get play position(nsec) : {playPosition}");
729 /// Sets the seek position in nanoseconds for playback, asynchronously.
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>
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>
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/>
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)
751 ValidatePlayerState(PlayerState.Ready, PlayerState.Playing, PlayerState.Paused);
753 await SetPlayPosition(position, accurate, true);
757 /// Sets the playback rate.
759 /// <param name="rate">The value for the playback rate. Valid range is -5.0 to 5.0, inclusive.</param>
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>
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/>
769 /// Streaming playback.
771 /// <exception cref="ArgumentOutOfRangeException">
772 /// <paramref name="rate"/> is less than -5.0.<br/>
774 /// <paramref name="rate"/> is greater than 5.0.<br/>
776 /// <paramref name="rate"/> is zero.
778 /// <since_tizen> 3 </since_tizen>
779 public void SetPlaybackRate(float rate)
781 if (rate < -5.0F || 5.0F < rate || rate == 0.0F)
783 throw new ArgumentOutOfRangeException(nameof(rate), rate, "Valid range is -5.0 to 5.0 (except 0.0)");
786 ValidatePlayerState(PlayerState.Ready, PlayerState.Playing, PlayerState.Paused);
788 NativePlayer.SetPlaybackRate(Handle, rate).ThrowIfFailed(this, "Failed to set the playback rate.");
792 /// Applies the audio stream policy.
794 /// <param name="policy">The <see cref="AudioStreamPolicy"/> to apply.</param>
796 /// The player must be in the <see cref="PlayerState.Idle"/> state.<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"/>.
804 /// <exception cref="ObjectDisposedException">
805 /// The player has already been disposed of.<br/>
807 /// <paramref name="policy"/> has already been disposed of.
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/>
814 /// <see cref="AudioStreamType"/> of <paramref name="policy"/> is not supported on the current platform.
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)
821 ValidationUtil.ValidateFeatureSupported("http://tizen.org/feature/multimedia.player.stream_info");
825 throw new ArgumentNullException(nameof(policy));
828 ValidatePlayerState(PlayerState.Idle);
830 var ret = NativePlayer.SetAudioPolicyInfo(Handle, policy.Handle);
832 if (ret == PlayerErrorCode.InvalidArgument)
834 throw new NotSupportedException("The specified policy is not supported on the current system.");
837 ret.ThrowIfFailed(this, "Failed to set the audio stream policy to the player");
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.
844 /// <param name="scaleRectangle">The containing the ROI area information.</param>
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"/>
850 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
851 /// <exception cref="InvalidOperationException">
852 /// Operation failed; internal error.
854 /// The <see cref="PlayerDisplayType"/> is not set to <see cref="PlayerDisplayType.Overlay"/>.
856 /// <exception cref="ArgumentOutOfRangeException">
857 /// <paramref name="scaleRectangle.ScaleX"/> is less than 0.0 or greater than 1.0.<br/>
859 /// <paramref name="scaleRectangle.ScaleY"/> is less than 0.0 or greater than 1.0.<br/>
861 /// <paramref name="scaleRectangle.ScaleWidth"/> is less than or equal to 0.0 or greater than 1.0.<br/>
863 /// <paramref name="scaleRectangle.ScaleHeight"/> is less than or equal to 0.0 or greater than 1.0.
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)
872 ValidateNotDisposed();
874 if (scaleRectangle.ScaleX < 0 || scaleRectangle.ScaleX > 1)
876 throw new ArgumentOutOfRangeException(nameof(scaleRectangle.ScaleX), scaleRectangle.ScaleX, "Valid range is 0 to 1.0");
878 if (scaleRectangle.ScaleY < 0 || scaleRectangle.ScaleY > 1)
880 throw new ArgumentOutOfRangeException(nameof(scaleRectangle.ScaleY), scaleRectangle.ScaleY, "Valid range is 0 to 1.0");
882 if (scaleRectangle.ScaleWidth <= 0 || scaleRectangle.ScaleWidth > 1)
884 throw new ArgumentOutOfRangeException(nameof(scaleRectangle.ScaleWidth), scaleRectangle.ScaleWidth, "Valid range is 0 to 1.0 (except 0.0)");
886 if (scaleRectangle.ScaleHeight <= 0 || scaleRectangle.ScaleHeight > 1)
888 throw new ArgumentOutOfRangeException(nameof(scaleRectangle.ScaleHeight), scaleRectangle.ScaleHeight, "Valid range is 0 to 1.0 (except 0.0)");
891 NativePlayer.SetVideoRoi(Handle, scaleRectangle.ScaleX, scaleRectangle.ScaleY, scaleRectangle.ScaleWidth, scaleRectangle.ScaleHeight).ThrowIfFailed(this, "Failed to set the video roi area.");
895 /// Get the relative ROI (Region Of Interest) area as a decimal fraction based on the video source.
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.
903 /// <seealso cref="Display"/>
904 /// <seealso cref="StreamInfo.GetVideoProperties"/>
905 /// <seealso cref="SetVideoRoi"/>
906 /// <since_tizen> 5 </since_tizen>
907 public ScaleRectangle GetVideoRoi()
909 ValidateNotDisposed();
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");
914 return new ScaleRectangle(scaleX, scaleY, scaleWidth, scaleHeight);
918 /// This supports the product infrastructure and is not intended to be used directly from application code.
920 [EditorBrowsable(EditorBrowsableState.Never)]
921 protected MediaPacket GetMediaPacket(IntPtr handle)
923 MediaPacket mediaPacket = handle != IntPtr.Zero ? MediaPacket.From(handle) :
924 throw new ArgumentException("MediaPacket handle is invalid.", nameof(handle));
930 #region Preparing state
932 private int _isPreparing;
934 private bool IsPreparing()
936 return Interlocked.CompareExchange(ref _isPreparing, 1, 1) == 1;
940 /// This supports the product infrastructure and is not intended to be used directly from application code.
942 [EditorBrowsable(EditorBrowsableState.Never)]
943 protected void SetPreparing()
945 Interlocked.Exchange(ref _isPreparing, 1);
949 /// This supports the product infrastructure and is not intended to be used directly from application code.
951 [EditorBrowsable(EditorBrowsableState.Never)]
952 protected void ClearPreparing()
954 Interlocked.Exchange(ref _isPreparing, 0);
959 /// Enable to decode an audio data for exporting PCM from a data.
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.
977 /// The player is not in the valid state.
979 /// <seealso cref="PlayerAudioExtractOption"/>
980 /// <seealso cref="DisableExportingAudioData"/>
981 /// <since_tizen> 6 </since_tizen>
982 public void EnableExportingAudioData(AudioMediaFormat format, PlayerAudioExtractOption option)
984 ValidatePlayerState(PlayerState.Idle);
985 ValidationUtil.ValidateEnum(typeof(PlayerAudioExtractOption), option, nameof(option));
987 _audioFrameDecodedCallback = (IntPtr packetHandle, IntPtr userData) =>
989 var handler = AudioDataDecoded;
992 Log.Debug(PlayerLog.Tag, "packet : " + packetHandle.ToString());
994 new AudioDataDecodedEventArgs(MediaPacket.From(packetHandle)));
998 MediaPacket.From(packetHandle).Dispose();
1002 NativePlayer.SetAudioFrameDecodedCb(Handle, format == null ? IntPtr.Zero : format.AsNativeHandle(), option,
1003 _audioFrameDecodedCallback, IntPtr.Zero).ThrowIfFailed(this, "Failed to register the _audioFrameDecoded");
1007 /// Disable to decode an audio data.
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()
1017 ValidatePlayerState(PlayerState.Idle, PlayerState.Ready);
1019 NativePlayer.UnsetAudioFrameDecodedCb(Handle).
1020 ThrowIfFailed(this, "Failed to unset the AudioFrameDecoded");
1022 _audioFrameDecodedCallback = null;