2 * Copyright (c) 2016 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.
17 using System.Threading.Tasks;
18 using System.Runtime.InteropServices;
19 using System.Diagnostics;
21 using System.Threading;
24 namespace Tizen.Multimedia
26 static internal class PlayerLog
28 internal const string Tag = "Tizen.Multimedia.Player";
29 internal const string Enter = "[ENTER]";
30 internal const string Leave = "[LEAVE]";
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 class Player : IDisposable, IDisplayable<PlayerErrorCode>
43 private PlayerHandle _handle;
46 /// Occurs when playback of a media is finished.
48 public event EventHandler<EventArgs> PlaybackCompleted;
49 private NativePlayer.PlaybackCompletedCallback _playbackCompletedCallback;
52 /// Occurs when playback of a media is interrupted.
54 public event EventHandler<PlaybackInterruptedEventArgs> PlaybackInterrupted;
55 private NativePlayer.PlaybackInterruptedCallback _playbackInterruptedCallback;
58 /// Occurs when any error occurs.
60 /// <remarks>The event handler will be executed on an internal thread.</remarks>
61 public event EventHandler<PlayerErrorOccurredEventArgs> ErrorOccurred;
62 private NativePlayer.PlaybackErrorCallback _playbackErrorCallback;
65 /// Occurs when the video stream changed.
67 /// <remarks>The event handler will be executed on an internal thread.</remarks>
68 public event EventHandler<VideoStreamChangedEventArgs> VideoStreamChanged;
69 private NativePlayer.VideoStreamChangedCallback _videoStreamChangedCallback;
72 /// Occurs when the subtitle is updated.
74 /// <remarks>The event handler will be executed on an internal thread.</remarks>
75 public EventHandler<SubtitleUpdatedEventArgs> SubtitleUpdated;
76 private NativePlayer.SubtitleUpdatedCallback _subtitleUpdatedCallback;
79 /// Occurs when there is a change in the buffering status of streaming.
81 public event EventHandler<BufferingProgressChangedEventArgs> BufferingProgressChanged;
82 private NativePlayer.BufferingProgressCallback _bufferingProgressCallback;
84 internal event EventHandler<MediaStreamBufferStatusChangedEventArgs> MediaStreamAudioBufferStatusChanged;
85 private NativePlayer.MediaStreamBufferStatusCallback _mediaStreamAudioBufferStatusChangedCallback;
87 internal event EventHandler<MediaStreamBufferStatusChangedEventArgs> MediaStreamVideoBufferStatusChanged;
88 private NativePlayer.MediaStreamBufferStatusCallback _mediaStreamVideoBufferStatusChangedCallback;
90 internal event EventHandler<MediaStreamSeekingOccurredEventArgs> MediaStreamAudioSeekingOccurred;
91 private NativePlayer.MediaStreamSeekCallback _mediaStreamAudioSeekCallback;
93 internal event EventHandler<MediaStreamSeekingOccurredEventArgs> MediaStreamVideoSeekingOccurred;
94 private NativePlayer.MediaStreamSeekCallback _mediaStreamVideoSeekCallback;
97 /// Initialize a new instance of the Player class.
101 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
103 NativePlayer.Create(out _handle).ThrowIfFailed("Failed to create player");
105 Debug.Assert(_handle != null);
107 RetrieveProperties();
109 if (Features.IsSupported(Features.AudioEffect))
111 _audioEffect = new AudioEffect(this);
114 if (Features.IsSupported(Features.RawVideo))
116 RegisterVideoFrameDecodedCallback();
119 DisplaySettings = PlayerDisplaySettings.Create(this);
122 private void RetrieveProperties()
124 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
126 NativePlayer.GetAudioLatencyMode(Handle, out _audioLatencyMode).
127 ThrowIfFailed("Failed to initialize the player");
129 NativePlayer.IsLooping(Handle, out _isLooping).ThrowIfFailed("Failed to initialize the player");
131 Log.Debug(PlayerLog.Tag, PlayerLog.Leave);
134 private bool _callbackRegistered;
136 private void RegisterCallbacks()
138 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
140 if (_callbackRegistered)
144 RegisterSubtitleUpdatedCallback();
145 RegisterErrorOccurredCallback();
146 RegisterPlaybackInterruptedCallback();
147 RegisterVideoStreamChangedCallback();
148 RegisterBufferingCallback();
149 RegisterMediaStreamBufferStatusCallback();
150 RegisterMediaStreamSeekCallback();
151 RegisterPlaybackCompletedCallback();
153 _callbackRegistered = true;
157 /// Gets the native handle of the player.
159 /// <value>An IntPtr that contains the native handle of the player.</value>
160 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
165 ValidateNotDisposed();
166 return _handle.DangerousGetHandle();
170 internal void ValidatePlayerState(params PlayerState[] desiredStates)
172 Debug.Assert(desiredStates.Length > 0);
174 ValidateNotDisposed();
176 var curState = State;
177 if (curState.IsAnyOf(desiredStates))
182 throw new InvalidOperationException($"The player is not in a valid state. " +
183 $"Current State : { curState }, Valid State : { string.Join(", ", desiredStates) }.");
187 #region Network configuration
188 private string _cookie = "";
189 private string _userAgent = "";
192 /// Gets or Sets the cookie for streaming playback.
194 /// <remarks>To set, the player must be in the <see cref="PlayerState.Idle"/> state.</remarks>
195 /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
196 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
197 /// <exception cref="ArgumentNullException">The value to set is null.</exception>
202 Log.Info(PlayerLog.Tag, "get cookie : " + _cookie);
207 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
208 ValidatePlayerState(PlayerState.Idle);
212 Log.Error(PlayerLog.Tag, "cookie can't be null");
213 throw new ArgumentNullException(nameof(value), "Cookie can't be null.");
216 NativePlayer.SetStreamingCookie(Handle, value, value.Length).
217 ThrowIfFailed("Failed to set the cookie to the player");
220 Log.Debug(PlayerLog.Tag, PlayerLog.Leave);
225 /// Gets or Sets the user agent for streaming playback.
227 /// <remarks>To set, the player must be in the <see cref="PlayerState.Idle"/> state.</remarks>
228 /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
229 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
230 /// <exception cref="ArgumentNullException">The value to set is null.</exception>
231 public string UserAgent
235 Log.Info(PlayerLog.Tag, "get useragent : " + _userAgent);
240 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
241 ValidatePlayerState(PlayerState.Idle);
245 Log.Error(PlayerLog.Tag, "UserAgent can't be null");
246 throw new ArgumentNullException(nameof(value), "UserAgent can't be null.");
249 NativePlayer.SetStreamingUserAgent(Handle, value, value.Length).
250 ThrowIfFailed("Failed to set the user agent to the player");
253 Log.Debug(PlayerLog.Tag, PlayerLog.Leave);
259 /// Gets the state of the player.
261 /// <value>The current state of the player.</value>
262 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
263 public PlayerState State
267 ValidateNotDisposed();
271 return PlayerState.Preparing;
275 NativePlayer.GetState(Handle, out state).ThrowIfFailed("Failed to retrieve the state of the player");
277 Debug.Assert(Enum.IsDefined(typeof(PlayerState), state));
279 return (PlayerState)state;
283 private AudioLatencyMode _audioLatencyMode;
286 /// Gets or sets the audio latency mode.
288 /// <value>A <see cref="AudioLatencyMode"/> that specifies the mode. The default is <see cref="AudioLatencyMode.Mid"/>.</value>
290 /// If the mode is <see cref="AudioLatencyMode.High"/>,
291 /// audio output interval can be increased so, it can keep more audio data to play.
292 /// But, state transition like pause or resume can be more slower than default(<see cref="AudioLatencyMode.Mid"/>).
294 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
295 /// <exception cref="ArgumentException">The value is not valid.</exception>
296 public AudioLatencyMode AudioLatencyMode
300 Log.Info(PlayerLog.Tag, "get audio latency mode : " + _audioLatencyMode);
301 return _audioLatencyMode;
305 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
306 ValidateNotDisposed();
308 if (_audioLatencyMode == value)
312 ValidationUtil.ValidateEnum(typeof(AudioLatencyMode), value);
314 NativePlayer.SetAudioLatencyMode(Handle, value).
315 ThrowIfFailed("Failed to set the audio latency mode of the player");
317 _audioLatencyMode = value;
321 private bool _isLooping;
324 /// Gets or sets the looping state.
326 /// <value>true if the playback is looping; otherwise, false. The default value is false.</value>
327 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
328 public bool IsLooping
332 Log.Info(PlayerLog.Tag, "get looping : " + _isLooping);
337 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
338 ValidateNotDisposed();
340 if (_isLooping == value)
345 NativePlayer.SetLooping(Handle, value).ThrowIfFailed("Failed to set the looping state of the player");
351 #region Display methods
353 /// Gets the display settings.
355 /// <value>A <see cref="PlayerDisplaySettings"/> that specifies the display settings.</value>
356 public PlayerDisplaySettings DisplaySettings { get; }
358 private Display _display;
360 private PlayerErrorCode SetDisplay(Display display)
364 Log.Info(PlayerLog.Tag, "set display to none");
365 return NativePlayer.SetDisplay(Handle, DisplayType.None, IntPtr.Zero);
368 return display.ApplyTo(this);
371 private void ReplaceDisplay(Display newDisplay)
373 _display?.SetOwner(null);
374 _display = newDisplay;
375 _display?.SetOwner(this);
379 /// Gets or sets the display.
381 /// <value>A <see cref="Multimedia.Display"/> that specifies the display.</value>
382 /// <remarks>The player must be in the <see cref="PlayerState.Idle"/> state.</remarks>
383 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
384 /// <exception cref="ArgumentException">The value has already been assigned to another player.</exception>
385 /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
386 public Display Display
394 ValidatePlayerState(PlayerState.Idle);
396 if (value?.Owner != null)
398 if (ReferenceEquals(this, value.Owner))
403 throw new ArgumentException("The display has already been assigned to another.");
405 SetDisplay(value).ThrowIfFailed("Failed to set the display to the player");
407 ReplaceDisplay(value);
411 PlayerErrorCode IDisplayable<PlayerErrorCode>.ApplyEvasDisplay(DisplayType type, ElmSharp.EvasObject evasObject)
413 Debug.Assert(IsDisposed == false);
415 Debug.Assert(Enum.IsDefined(typeof(DisplayType), type));
417 return NativePlayer.SetDisplay(Handle, type, evasObject);
421 private PlayerTrackInfo _audioTrack;
424 /// Gets the track info for audio.
426 /// <value>A <see cref="PlayerTrackInfo"/> for audio.</value>
427 public PlayerTrackInfo AudioTrackInfo
431 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
432 if (_audioTrack == null)
434 _audioTrack = new PlayerTrackInfo(this, StreamType.Audio);
440 private PlayerTrackInfo _subtitleTrackInfo;
443 /// Gets the track info for subtitle.
445 /// <value>A <see cref="PlayerTrackInfo"/> for subtitle.</value>
446 public PlayerTrackInfo SubtitleTrackInfo
450 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
451 if (_subtitleTrackInfo == null)
453 _subtitleTrackInfo = new PlayerTrackInfo(this, StreamType.Text);
455 return _subtitleTrackInfo;
459 private StreamInfo _streamInfo;
462 /// Gets the stream information.
464 /// <value>A <see cref="StreamInfo"/> for this player.</value>
465 public StreamInfo StreamInfo
469 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
470 if (_streamInfo == null)
472 _streamInfo = new StreamInfo(this);
478 private readonly AudioEffect _audioEffect;
481 /// Gets the audio effect.
483 /// <feature>http://tizen.org/feature/multimedia.custom_audio_effect</feature>
484 /// <exception cref="NotSupportedException">The required feature is not supported.</exception>
485 public AudioEffect AudioEffect
489 if (_audioEffect == null)
491 throw new NotSupportedException($"The feature({Features.AudioEffect}) is not supported.");
500 #region Dispose support
501 private bool _disposed;
504 /// Releases all resources used by the current instance.
506 public void Dispose()
508 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
512 private void Dispose(bool disposing)
516 ReplaceDisplay(null);
522 _source.DetachFrom(this);
526 Log.Error(PlayerLog.Tag, e.ToString());
539 internal void ValidateNotDisposed()
543 Log.Warn(PlayerLog.Tag, "player was disposed");
544 throw new ObjectDisposedException(nameof(Player));
548 internal bool IsDisposed => _disposed;
554 /// Gets or sets the mute state.
556 /// <value>true if the player is muted; otherwise, false.</value>
557 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
562 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
565 NativePlayer.IsMuted(Handle, out value).ThrowIfFailed("Failed to get the mute state of the player");
567 Log.Info(PlayerLog.Tag, "get mute : " + value);
573 NativePlayer.SetMute(Handle, value).ThrowIfFailed("Failed to set the mute state of the player");
578 /// Gets the streaming download Progress.
580 /// <returns>The <see cref="DownloadProgress"/> containing current download progress.</returns>
581 /// <remarks>The player must be in the <see cref="PlayerState.Playing"/> or <see cref="PlayerState.Paused"/> state.</remarks>
582 /// <exception cref="InvalidOperationException">
583 /// The player is not streaming.\n
585 /// The player is not in the valid state.
587 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
588 public DownloadProgress GetDownloadProgress()
590 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
591 ValidatePlayerState(PlayerState.Playing, PlayerState.Paused);
595 NativePlayer.GetStreamingDownloadProgress(Handle, out start, out current).
596 ThrowIfFailed("Failed to get download progress");
598 Log.Info(PlayerLog.Tag, "get download progress : " + start + ", " + current);
600 return new DownloadProgress(start, current);
605 /// Gets or sets the current volume.
607 /// <remarks>Valid volume range is from 0 to 1.0, inclusive.</remarks>
608 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
609 /// <exception cref="ArgumentOutOfRangeException">
610 /// <paramref name="value"/> is less than zero.\n
612 /// <paramref name="value"/> is greater than 1.0.
619 NativePlayer.GetVolume(Handle, out value, out value).
620 ThrowIfFailed("Failed to get the volume of the player");
625 if (value < 0F || 1.0F < value)
627 throw new ArgumentOutOfRangeException(nameof(value), value,
628 $"Valid volume range is 0 <= value <= 1.0, but got { value }.");
631 NativePlayer.SetVolume(Handle, value, value).
632 ThrowIfFailed("Failed to set the volume of the player");
639 /// Sets the subtitle path for playback.
641 /// <remarks>Only MicroDVD/SubViewer(*.sub), SAMI(*.smi), and SubRip(*.srt) subtitle formats are supported.
642 /// <para>The mediastorage privilege(http://tizen.org/privilege/mediastorage) must be added if any files are used to play located in the internal storage.
643 /// The externalstorage privilege(http://tizen.org/privilege/externalstorage) must be added if any files are used to play located in the external storage.</para>
645 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
646 /// <exception cref="ArgumentException"><paramref name="path"/> is an empty string.</exception>
647 /// <exception cref="FileNotFoundException">The specified path does not exist.</exception>
648 /// <exception cref="ArgumentNullException">The path is null.</exception>
649 public void SetSubtitle(string path)
651 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
652 ValidateNotDisposed();
656 throw new ArgumentNullException(nameof(path));
659 if (path.Length == 0)
661 throw new ArgumentException("The path is empty.", nameof(path));
664 if (!File.Exists(path))
666 throw new FileNotFoundException($"The specified file does not exist.", path);
669 NativePlayer.SetSubtitlePath(Handle, path).
670 ThrowIfFailed("Failed to set the subtitle path to the player");
672 Log.Debug(PlayerLog.Tag, PlayerLog.Leave);
676 /// Removes the subtitle path.
678 /// <remarks>The player must be in the <see cref="PlayerState.Idle"/> state.</remarks>
679 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
680 /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
681 public void ClearSubtitle()
683 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
684 ValidatePlayerState(PlayerState.Idle);
686 NativePlayer.SetSubtitlePath(Handle, null).
687 ThrowIfFailed("Failed to clear the subtitle of the player");
688 Log.Debug(PlayerLog.Tag, PlayerLog.Leave);
692 /// Sets the offset for the subtitle.
694 /// <param name="offset">The value indicating a desired offset in milliseconds.</param>
695 /// <remarks>The player must be in the <see cref="PlayerState.Playing"/> or <see cref="PlayerState.Paused"/> state.</remarks>
696 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
697 /// <exception cref="InvalidOperationException">
698 /// The player is not in the valid state.\n
700 /// No subtitle is set.
702 /// <seealso cref="SetSubtitle(string)"/>
703 public void SetSubtitleOffset(int offset)
705 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
706 ValidatePlayerState(PlayerState.Playing, PlayerState.Paused);
708 var err = NativePlayer.SetSubtitlePositionOffset(Handle, offset);
710 if (err == PlayerErrorCode.FeatureNotSupported)
712 throw new InvalidOperationException("No subtitle set");
715 err.ThrowIfFailed("Failed to the subtitle offset of the player");
716 Log.Debug(PlayerLog.Tag, PlayerLog.Leave);
719 private void Prepare()
721 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
722 NativePlayer.Prepare(Handle).ThrowIfFailed("Failed to prepare the player");
725 protected virtual void OnPreparing()
731 /// Prepares the media player for playback, asynchronously.
733 /// <returns>A task that represents the asynchronous prepare operation.</returns>
734 /// <remarks>To prepare the player, the player must be in the <see cref="PlayerState.Idle"/> state,
735 /// and a source must be set.</remarks>
736 /// <exception cref="InvalidOperationException">No source is set.</exception>
737 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
738 /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
739 public virtual Task PrepareAsync()
741 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
744 throw new InvalidOperationException("No source is set.");
747 ValidatePlayerState(PlayerState.Idle);
751 var completionSource = new TaskCompletionSource<bool>();
761 completionSource.SetResult(true);
766 completionSource.TrySetException(e);
769 Log.Debug(PlayerLog.Tag, PlayerLog.Leave);
771 return completionSource.Task;
775 /// Unprepares the player.
778 /// The most recently used source is reset and no longer associated with the player. Playback is no longer possible.
779 /// If you want to use the player again, you have to set a source and call <see cref="PrepareAsync"/> again.
781 /// The player must be in the <see cref="PlayerState.Ready"/>, <see cref="PlayerState.Playing"/> or <see cref="PlayerState.Paused"/> state.
782 /// It has no effect if the player is already in the <see cref="PlayerState.Idle"/> state.
785 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
786 /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
787 public virtual void Unprepare()
789 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
790 if (State == PlayerState.Idle)
792 Log.Warn(PlayerLog.Tag, "idle state already");
795 ValidatePlayerState(PlayerState.Ready, PlayerState.Paused, PlayerState.Playing);
797 NativePlayer.Unprepare(Handle).ThrowIfFailed("Failed to unprepare the player");
802 protected virtual void OnUnprepared()
804 _source?.DetachFrom(this);
809 /// Starts or resumes playback.
812 /// The player must be in the <see cref="PlayerState.Ready"/> or <see cref="PlayerState.Paused"/> state.
813 /// It has no effect if the player is already in the <see cref="PlayerState.Playing"/> state.\n
815 /// Sound can be mixed with other sounds if you don't control the stream focus using <see cref="ApplyAudioStreamPolicy"/>.
817 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
818 /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
819 /// <seealso cref="PrepareAsync"/>
820 /// <seealso cref="Stop"/>
821 /// <seealso cref="Pause"/>
822 /// <seealso cref="PlaybackCompleted"/>
823 /// <seealso cref="ApplyAudioStreamPolicy"/>
824 public virtual void Start()
826 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
827 if (State == PlayerState.Playing)
829 Log.Warn(PlayerLog.Tag, "playing state already");
832 ValidatePlayerState(PlayerState.Ready, PlayerState.Paused);
834 NativePlayer.Start(Handle).ThrowIfFailed("Failed to start the player");
835 Log.Debug(PlayerLog.Tag, PlayerLog.Leave);
839 /// Stops playing media content.
842 /// The player must be in the <see cref="PlayerState.Playing"/> or <see cref="PlayerState.Paused"/> state.
843 /// It has no effect if the player is already in the <see cref="PlayerState.Ready"/> state.
845 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
846 /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
847 /// <seealso cref="Start"/>
848 /// <seealso cref="Pause"/>
849 public virtual void Stop()
851 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
852 if (State == PlayerState.Ready)
854 Log.Warn(PlayerLog.Tag, "ready state already");
857 ValidatePlayerState(PlayerState.Paused, PlayerState.Playing);
859 NativePlayer.Stop(Handle).ThrowIfFailed("Failed to stop the player");
860 Log.Debug(PlayerLog.Tag, PlayerLog.Leave);
864 /// Pauses the player.
867 /// The player must be in the <see cref="PlayerState.Playing"/> state.
868 /// It has no effect if the player is already in the <see cref="PlayerState.Paused"/> state.
870 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
871 /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
872 /// <seealso cref="Start"/>
873 public virtual void Pause()
875 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
876 if (State == PlayerState.Paused)
878 Log.Warn(PlayerLog.Tag, "pause state already");
882 ValidatePlayerState(PlayerState.Playing);
884 NativePlayer.Pause(Handle).ThrowIfFailed("Failed to pause the player");
885 Log.Debug(PlayerLog.Tag, PlayerLog.Leave);
888 private MediaSource _source;
891 /// Sets a media source for the player.
893 /// <param name="source">A <see cref="MediaSource"/> that specifies the source for playback.</param>
894 /// <remarks>The player must be in the <see cref="PlayerState.Idle"/> state.</remarks>
895 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
896 /// <exception cref="InvalidOperationException">
897 /// The player is not in the valid state.\n
899 /// It is not able to assign the source to the player.
901 /// <seealso cref="PrepareAsync"/>
902 public void SetSource(MediaSource source)
904 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
905 ValidatePlayerState(PlayerState.Idle);
909 source.AttachTo(this);
914 _source.DetachFrom(this);
918 Log.Debug(PlayerLog.Tag, PlayerLog.Leave);
922 /// Captures a video frame asynchronously.
924 /// <returns>A task that represents the asynchronous capture operation.</returns>
925 /// <feature>http://tizen.org/feature/multimedia.raw_video</feature>
926 /// <remarks>The player must be in the <see cref="PlayerState.Playing"/> or <see cref="PlayerState.Paused"/> state.</remarks>
927 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
928 /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
929 /// <exception cref="NotSupportedException">The required feature is not supported.</exception>
930 public async Task<CapturedFrame> CaptureVideoAsync()
932 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
934 ValidationUtil.ValidateFeatureSupported(Features.RawVideo);
936 ValidatePlayerState(PlayerState.Playing, PlayerState.Paused);
938 TaskCompletionSource<CapturedFrame> t = new TaskCompletionSource<CapturedFrame>();
940 NativePlayer.VideoCaptureCallback cb = (data, width, height, size, _) =>
942 Debug.Assert(size <= int.MaxValue);
944 byte[] buf = new byte[size];
945 Marshal.Copy(data, buf, 0, (int)size);
947 t.TrySetResult(new CapturedFrame(buf, width, height));
950 using (var cbKeeper = ObjectKeeper.Get(cb))
952 NativePlayer.CaptureVideo(Handle, cb)
953 .ThrowIfFailed("Failed to capture the video");
960 /// Gets the play position in milliseconds.
962 /// <remarks>The player must be in the <see cref="PlayerState.Ready"/>, <see cref="PlayerState.Playing"/> or <see cref="PlayerState.Paused"/> state.</remarks>
963 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
964 /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
965 /// <seealso cref="SetPlayPositionAsync(int, bool)"/>
966 public int GetPlayPosition()
968 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
969 ValidatePlayerState(PlayerState.Ready, PlayerState.Paused, PlayerState.Playing);
971 int playPosition = 0;
973 NativePlayer.GetPlayPosition(Handle, out playPosition).
974 ThrowIfFailed("Failed to get the play position of the player");
976 Log.Info(PlayerLog.Tag, "get play position : " + playPosition);
981 private void SetPlayPosition(int milliseconds, bool accurate,
982 NativePlayer.SeekCompletedCallback cb)
984 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
985 var ret = NativePlayer.SetPlayPosition(Handle, milliseconds, accurate, cb, IntPtr.Zero);
987 //Note that we assume invalid param error is returned only when the position value is invalid.
988 if (ret == PlayerErrorCode.InvalidArgument)
990 throw new ArgumentOutOfRangeException(nameof(milliseconds), milliseconds,
991 "The position is not valid.");
993 if (ret != PlayerErrorCode.None)
995 Log.Error(PlayerLog.Tag, "Failed to set play position, " + (PlayerError)ret);
997 ret.ThrowIfFailed("Failed to set play position");
1001 /// Sets the seek position for playback, asynchronously.
1003 /// <param name="position">The value indicating a desired position in milliseconds.</param>
1004 /// <param name="accurate">The value indicating whether the operation performs with accuracy.</param>
1006 /// <para>The player must be in the <see cref="PlayerState.Ready"/>, <see cref="PlayerState.Playing"/> or <see cref="PlayerState.Paused"/> state.</para>
1007 /// <para>If the <paramref name="accurate"/> is true, the play position will be adjusted as the specified <paramref name="position"/> value,
1008 /// but this might be considerably slow. If false, the play position will be a nearest keyframe position.</para>
1010 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
1011 /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
1012 /// <exception cref="ArgumentOutOfRangeException">The specified position is not valid.</exception>
1013 /// <seealso cref="GetPlayPosition"/>
1014 public async Task SetPlayPositionAsync(int position, bool accurate)
1016 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
1017 ValidatePlayerState(PlayerState.Ready, PlayerState.Playing, PlayerState.Paused);
1019 var taskCompletionSource = new TaskCompletionSource<bool>();
1021 bool immediateResult = _source is MediaStreamSource;
1023 NativePlayer.SeekCompletedCallback cb = _ => taskCompletionSource.TrySetResult(true);
1025 using (var cbKeeper = ObjectKeeper.Get(cb))
1027 SetPlayPosition(position, accurate, cb);
1028 if (immediateResult)
1030 taskCompletionSource.TrySetResult(true);
1033 await taskCompletionSource.Task;
1036 Log.Debug(PlayerLog.Tag, PlayerLog.Leave);
1040 /// Sets playback rate.
1042 /// <param name="rate">The value for the playback rate. Valid range is -5.0 to 5.0, inclusive.</param>
1044 /// <para>The player must be in the <see cref="PlayerState.Ready"/>, <see cref="PlayerState.Playing"/> or <see cref="PlayerState.Paused"/> state.</para>
1045 /// <para>The sound will be muted, when the playback rate is under 0.0 or over 2.0.</para>
1047 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
1048 /// <exception cref="InvalidOperationException">
1049 /// The player is not in the valid state.\n
1051 /// Streaming playback.
1053 /// <exception cref="ArgumentOutOfRangeException">
1054 /// <paramref name="rate"/> is less than 5.0.\n
1056 /// <paramref name="rate"/> is greater than 5.0.\n
1058 /// <paramref name="rate"/> is zero.
1060 public void SetPlaybackRate(float rate)
1062 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
1063 if (rate < -5.0F || 5.0F < rate || rate == 0.0F)
1065 throw new ArgumentOutOfRangeException(nameof(rate), rate, "Valid range is -5.0 to 5.0 (except 0.0)");
1068 ValidatePlayerState(PlayerState.Ready, PlayerState.Playing, PlayerState.Paused);
1070 NativePlayer.SetPlaybackRate(Handle, rate).ThrowIfFailed("Failed to set the playback rate.");
1071 Log.Debug(PlayerLog.Tag, PlayerLog.Leave);
1075 /// Applies the audio stream policy.
1077 /// <param name="policy">The <see cref="AudioStreamPolicy"/> to apply.</param>
1078 /// <remarks>The player must be in the <see cref="PlayerState.Idle"/> state.</remarks>
1079 /// <exception cref="ObjectDisposedException">
1080 /// The player has already been disposed of.\n
1082 /// <paramref name="policy"/> has already been disposed of.
1084 /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
1085 /// <exception cref="ArgumentNullException"><paramref name="policy"/> is null.</exception>
1086 public void ApplyAudioStreamPolicy(AudioStreamPolicy policy)
1088 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
1091 throw new ArgumentNullException(nameof(policy));
1094 if (policy.Handle == IntPtr.Zero)
1096 throw new ObjectDisposedException(nameof(policy));
1099 ValidatePlayerState(PlayerState.Idle);
1101 NativePlayer.SetAudioPolicyInfo(Handle, policy.Handle).
1102 ThrowIfFailed("Failed to set the audio stream policy to the player");
1106 #region Callback registrations
1107 private void RegisterSubtitleUpdatedCallback()
1109 _subtitleUpdatedCallback = (duration, text, _) =>
1111 Log.Debug(PlayerLog.Tag, "duration : " + duration + ", text : " + text);
1112 SubtitleUpdated?.Invoke(this, new SubtitleUpdatedEventArgs(duration, text));
1115 NativePlayer.SetSubtitleUpdatedCb(Handle, _subtitleUpdatedCallback).
1116 ThrowIfFailed("Failed to initialize the player");
1119 private void RegisterPlaybackCompletedCallback()
1121 _playbackCompletedCallback = _ =>
1123 Log.Debug(PlayerLog.Tag, "completed callback");
1124 PlaybackCompleted?.Invoke(this, EventArgs.Empty);
1126 NativePlayer.SetCompletedCb(Handle, _playbackCompletedCallback).
1127 ThrowIfFailed("Failed to set PlaybackCompleted");
1130 private void RegisterPlaybackInterruptedCallback()
1132 _playbackInterruptedCallback = (code, _) =>
1134 if (!Enum.IsDefined(typeof(PlaybackInterruptionReason), code))
1139 if (code == PlaybackInterruptionReason.ResourceConflict)
1144 Log.Warn(PlayerLog.Tag, "interrupted reason : " + code);
1145 PlaybackInterrupted?.Invoke(this, new PlaybackInterruptedEventArgs(code));
1148 NativePlayer.SetInterruptedCb(Handle, _playbackInterruptedCallback).
1149 ThrowIfFailed("Failed to set PlaybackInterrupted");
1152 private void RegisterErrorOccurredCallback()
1154 _playbackErrorCallback = (code, _) =>
1156 //TODO handle service disconnected error.
1157 Log.Warn(PlayerLog.Tag, "error code : " + code);
1158 ErrorOccurred?.Invoke(this, new PlayerErrorOccurredEventArgs((PlayerError)code));
1161 NativePlayer.SetErrorCb(Handle, _playbackErrorCallback).
1162 ThrowIfFailed("Failed to set PlaybackError");
1165 #region VideoFrameDecoded event
1167 private EventHandler<VideoFrameDecodedEventArgs> _videoFrameDecoded;
1169 private NativePlayer.VideoFrameDecodedCallback _videoFrameDecodedCallback;
1172 /// Occurs when a video frame is decoded.
1175 /// <para>The event handler will be executed on an internal thread.</para>
1176 /// <para>The <see cref="VideoFrameDecodedEventArgs.Packet"/> in event args should be disposed after use.</para>
1178 /// <feature>http://tizen.org/feature/multimedia.raw_video</feature>
1179 /// <exception cref="NotSupportedException">The required feature is not supported.</exception>
1180 /// <seealso cref="VideoFrameDecodedEventArgs.Packet"/>
1181 public event EventHandler<VideoFrameDecodedEventArgs> VideoFrameDecoded
1185 ValidationUtil.ValidateFeatureSupported(Features.RawVideo);
1187 _videoFrameDecoded += value;
1191 ValidationUtil.ValidateFeatureSupported(Features.RawVideo);
1193 _videoFrameDecoded -= value;
1197 private void RegisterVideoFrameDecodedCallback()
1199 _videoFrameDecodedCallback = (packetHandle, _) =>
1201 var handler = _videoFrameDecoded;
1202 if (handler != null)
1204 Log.Debug(PlayerLog.Tag, "packet : " + packetHandle);
1205 handler.Invoke(this,
1206 new VideoFrameDecodedEventArgs(MediaPacket.From(packetHandle)));
1210 MediaPacket.From(packetHandle).Dispose();
1214 NativePlayer.SetVideoFrameDecodedCb(Handle, _videoFrameDecodedCallback).
1215 ThrowIfFailed("Failed to register the VideoFrameDecoded");
1219 private void RegisterVideoStreamChangedCallback()
1221 ValidatePlayerState(PlayerState.Idle);
1223 _videoStreamChangedCallback = (width, height, fps, bitrate, _) =>
1225 Log.Debug(PlayerLog.Tag, "height : " + height + ", width : " + width
1226 + ", fps : " + fps + ", bitrate : " + bitrate);
1228 VideoStreamChanged?.Invoke(this, new VideoStreamChangedEventArgs(height, width, fps, bitrate));
1231 NativePlayer.SetVideoStreamChangedCb(Handle, _videoStreamChangedCallback).
1232 ThrowIfFailed("Failed to set the video stream changed callback");
1235 private void RegisterBufferingCallback()
1237 _bufferingProgressCallback = (percent, _) =>
1239 Log.Debug(PlayerLog.Tag, $"Buffering callback with percent { percent }");
1240 BufferingProgressChanged?.Invoke(this, new BufferingProgressChangedEventArgs(percent));
1243 NativePlayer.SetBufferingCb(Handle, _bufferingProgressCallback).
1244 ThrowIfFailed("Failed to set BufferingProgress");
1247 private void RegisterMediaStreamBufferStatusCallback()
1249 _mediaStreamAudioBufferStatusChangedCallback = (status, _) =>
1251 Debug.Assert(Enum.IsDefined(typeof(MediaStreamBufferStatus), status));
1252 Log.Debug(PlayerLog.Tag, "audio buffer status : " + status);
1253 MediaStreamAudioBufferStatusChanged?.Invoke(this,
1254 new MediaStreamBufferStatusChangedEventArgs(status));
1256 _mediaStreamVideoBufferStatusChangedCallback = (status, _) =>
1258 Debug.Assert(Enum.IsDefined(typeof(MediaStreamBufferStatus), status));
1259 Log.Debug(PlayerLog.Tag, "video buffer status : " + status);
1260 MediaStreamVideoBufferStatusChanged?.Invoke(this,
1261 new MediaStreamBufferStatusChangedEventArgs(status));
1264 RegisterMediaStreamBufferStatusCallback(StreamType.Audio, _mediaStreamAudioBufferStatusChangedCallback);
1265 RegisterMediaStreamBufferStatusCallback(StreamType.Video, _mediaStreamVideoBufferStatusChangedCallback);
1268 private void RegisterMediaStreamBufferStatusCallback(StreamType streamType,
1269 NativePlayer.MediaStreamBufferStatusCallback cb)
1271 NativePlayer.SetMediaStreamBufferStatusCb(Handle, streamType, cb).
1272 ThrowIfFailed("Failed to SetMediaStreamBufferStatus");
1275 private void RegisterMediaStreamSeekCallback()
1277 _mediaStreamAudioSeekCallback = (offset, _) =>
1279 Log.Debug(PlayerLog.Tag, "audio seeking offset : " + offset);
1280 MediaStreamAudioSeekingOccurred?.Invoke(this, new MediaStreamSeekingOccurredEventArgs(offset));
1282 _mediaStreamVideoSeekCallback = (offset, _) =>
1284 Log.Debug(PlayerLog.Tag, "video seeking offset : " + offset);
1285 MediaStreamVideoSeekingOccurred?.Invoke(this, new MediaStreamSeekingOccurredEventArgs(offset));
1288 RegisterMediaStreamSeekCallback(StreamType.Audio, _mediaStreamAudioSeekCallback);
1289 RegisterMediaStreamSeekCallback(StreamType.Video, _mediaStreamVideoSeekCallback);
1292 private void RegisterMediaStreamSeekCallback(StreamType streamType, NativePlayer.MediaStreamSeekCallback cb)
1294 NativePlayer.SetMediaStreamSeekCb(Handle, streamType, cb).
1295 ThrowIfFailed("Failed to SetMediaStreamSeek");
1299 #region Preparing state
1301 private int _isPreparing;
1303 private bool IsPreparing()
1305 return Interlocked.CompareExchange(ref _isPreparing, 1, 1) == 1;
1308 private void SetPreparing()
1310 Interlocked.Exchange(ref _isPreparing, 1);
1313 private void ClearPreparing()
1315 Interlocked.Exchange(ref _isPreparing, 0);