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();
120 private void RetrieveProperties()
122 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
124 NativePlayer.GetAudioLatencyMode(Handle, out _audioLatencyMode).
125 ThrowIfFailed("Failed to initialize the player");
127 NativePlayer.IsLooping(Handle, out _isLooping).ThrowIfFailed("Failed to initialize the player");
129 Log.Debug(PlayerLog.Tag, PlayerLog.Leave);
132 private bool _callbackRegistered;
134 private void RegisterCallbacks()
136 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
138 if (_callbackRegistered)
142 RegisterSubtitleUpdatedCallback();
143 RegisterErrorOccurredCallback();
144 RegisterPlaybackInterruptedCallback();
145 RegisterVideoStreamChangedCallback();
146 RegisterBufferingCallback();
147 RegisterMediaStreamBufferStatusCallback();
148 RegisterMediaStreamSeekCallback();
149 RegisterPlaybackCompletedCallback();
151 _callbackRegistered = true;
158 ValidateNotDisposed();
159 return _handle.DangerousGetHandle();
163 internal void ValidatePlayerState(params PlayerState[] desiredStates)
165 Debug.Assert(desiredStates.Length > 0);
167 ValidateNotDisposed();
169 var curState = State;
170 if (curState.IsAnyOf(desiredStates))
175 throw new InvalidOperationException($"The player is not in a valid state. " +
176 $"Current State : { curState }, Valid State : { string.Join(", ", desiredStates) }.");
180 #region Network configuration
181 private string _cookie = "";
182 private string _userAgent = "";
185 /// Gets or Sets the cookie for streaming playback.
187 /// <remarks>To set, the player must be in the <see cref="PlayerState.Idle"/> state.</remarks>
188 /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
189 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
190 /// <exception cref="ArgumentNullException">The value to set is null.</exception>
195 Log.Info(PlayerLog.Tag, "get cookie : " + _cookie);
200 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
201 ValidatePlayerState(PlayerState.Idle);
205 Log.Error(PlayerLog.Tag, "cookie can't be null");
206 throw new ArgumentNullException(nameof(value), "Cookie can't be null.");
209 NativePlayer.SetStreamingCookie(Handle, value, value.Length).
210 ThrowIfFailed("Failed to set the cookie to the player");
213 Log.Debug(PlayerLog.Tag, PlayerLog.Leave);
218 /// Gets or Sets the user agent for streaming playback.
220 /// <remarks>To set, the player must be in the <see cref="PlayerState.Idle"/> state.</remarks>
221 /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
222 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
223 /// <exception cref="ArgumentNullException">The value to set is null.</exception>
224 public string UserAgent
228 Log.Info(PlayerLog.Tag, "get useragent : " + _userAgent);
233 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
234 ValidatePlayerState(PlayerState.Idle);
238 Log.Error(PlayerLog.Tag, "UserAgent can't be null");
239 throw new ArgumentNullException(nameof(value), "UserAgent can't be null.");
242 NativePlayer.SetStreamingUserAgent(Handle, value, value.Length).
243 ThrowIfFailed("Failed to set the user agent to the player");
246 Log.Debug(PlayerLog.Tag, PlayerLog.Leave);
252 /// Gets the state of the player.
254 /// <value>The current state of the player.</value>
255 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
256 public PlayerState State
260 ValidateNotDisposed();
262 //TODO is this needed?
265 return PlayerState.Preparing;
269 NativePlayer.GetState(Handle, out state).ThrowIfFailed("Failed to retrieve the state of the player");
271 Debug.Assert(Enum.IsDefined(typeof(PlayerState), state));
273 return (PlayerState)state;
277 private AudioLatencyMode _audioLatencyMode;
280 /// Gets or sets the audio latency mode.
282 /// <value>A <see cref="AudioLatencyMode"/> that specifies the mode. The default is <see cref="AudioLatencyMode.Mid"/>.</value>
284 /// If the mode is <see cref="AudioLatencyMode.High"/>,
285 /// audio output interval can be increased so, it can keep more audio data to play.
286 /// But, state transition like pause or resume can be more slower than default(<see cref="AudioLatencyMode.Mid"/>).
288 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
289 /// <exception cref="ArgumentException">The value is not valid.</exception>
290 public AudioLatencyMode AudioLatencyMode
294 Log.Info(PlayerLog.Tag, "get audio latency mode : " + _audioLatencyMode);
295 return _audioLatencyMode;
299 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
300 ValidateNotDisposed();
302 if (_audioLatencyMode == value)
306 ValidationUtil.ValidateEnum(typeof(AudioLatencyMode), value);
308 NativePlayer.SetAudioLatencyMode(Handle, value).
309 ThrowIfFailed("Failed to set the audio latency mode of the player");
311 _audioLatencyMode = value;
315 private bool _isLooping;
318 /// Gets or sets the looping state.
320 /// <value>true if the playback is looping; otherwise, false. The default value is false.</value>
321 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
322 public bool IsLooping
326 Log.Info(PlayerLog.Tag, "get looping : " + _isLooping);
331 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
332 ValidateNotDisposed();
334 if (_isLooping == value)
339 NativePlayer.SetLooping(Handle, value).ThrowIfFailed("Failed to set the looping state of the player");
345 #region Display methods
347 /// Gets the display settings.
349 /// <value>A <see cref="PlayerDisplaySettings"/> that specifies the display settings.</value>
350 public PlayerDisplaySettings DisplaySettings { get; }
352 private Display _display;
354 private PlayerErrorCode SetDisplay(Display display)
356 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
359 Log.Info(PlayerLog.Tag, "set display to none");
360 return NativePlayer.SetDisplay(Handle, DisplayType.None, IntPtr.Zero);
363 return display.ApplyTo(this);
366 private void ReplaceDisplay(Display newDisplay)
368 if (_display != null)
370 _display.Owner = null;
372 _display = newDisplay;
373 if (_display != null)
375 _display.Owner = this;
380 /// Gets or sets the display.
382 /// <value>A <see cref="Multimedia.Display"/> that specifies the display.</value>
383 /// <remarks>The player must be in the <see cref="PlayerState.Idle"/> state.</remarks>
384 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
385 /// <exception cref="ArgumentException">The value has already been assigned to another player.</exception>
386 /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
387 public Display Display
395 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
396 ValidatePlayerState(PlayerState.Idle);
398 if (value != null && value.Owner != null)
400 if (ReferenceEquals(this, value.Owner))
406 throw new ArgumentException("The display has already been assigned to another.");
410 SetDisplay(value).ThrowIfFailed("Failed to set the display to the player");
412 ReplaceDisplay(value);
413 Log.Debug(PlayerLog.Tag, PlayerLog.Leave);
417 PlayerErrorCode IDisplayable<PlayerErrorCode>.ApplyEvasDisplay(DisplayType type, ElmSharp.EvasObject evasObject)
419 Debug.Assert(IsDisposed == false);
421 Debug.Assert(Enum.IsDefined(typeof(DisplayType), type));
423 return NativePlayer.SetDisplay(Handle, type, evasObject);
427 private PlayerTrackInfo _audioTrack;
430 /// Gets the track info for audio.
432 /// <value>A <see cref="PlayerTrackInfo"/> for audio.</value>
433 public PlayerTrackInfo AudioTrackInfo
437 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
438 if (_audioTrack == null)
440 _audioTrack = new PlayerTrackInfo(this, StreamType.Audio);
446 private PlayerTrackInfo _subtitleTrackInfo;
449 /// Gets the track info for subtitle.
451 /// <value>A <see cref="PlayerTrackInfo"/> for subtitle.</value>
452 public PlayerTrackInfo SubtitleTrackInfo
456 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
457 if (_subtitleTrackInfo == null)
459 _subtitleTrackInfo = new PlayerTrackInfo(this, StreamType.Text);
461 return _subtitleTrackInfo;
465 private StreamInfo _streamInfo;
468 /// Gets the stream information.
470 /// <value>A <see cref="StreamInfo"/> for this player.</value>
471 public StreamInfo StreamInfo
475 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
476 if (_streamInfo == null)
478 _streamInfo = new StreamInfo(this);
484 private readonly AudioEffect _audioEffect;
487 /// Gets the audio effect.
489 /// <feature>http://tizen.org/feature/multimedia.custom_audio_effect</feature>
490 /// <exception cref="NotSupportedException">The required feature is not supported.</exception>
491 public AudioEffect AudioEffect
495 if (_audioEffect == null)
497 throw new NotSupportedException($"The feature({Features.AudioEffect}) is not supported.");
506 #region Dispose support
507 private bool _disposed;
509 public void Dispose()
511 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
515 private void Dispose(bool disposing)
523 _source.DetachFrom(this);
527 Log.Error(PlayerLog.Tag, e.ToString());
540 internal void ValidateNotDisposed()
544 Log.Warn(PlayerLog.Tag, "player was disposed");
545 throw new ObjectDisposedException(nameof(Player));
549 internal bool IsDisposed => _disposed;
555 /// Gets or sets the mute state.
557 /// <value>true if the player is muted; otherwise, false.</value>
558 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
563 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
566 NativePlayer.IsMuted(Handle, out value).ThrowIfFailed("Failed to get the mute state of the player");
568 Log.Info(PlayerLog.Tag, "get mute : " + value);
574 NativePlayer.SetMute(Handle, value).ThrowIfFailed("Failed to set the mute state of the player");
579 /// Get Streaming download Progress.
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 /// <remarks>The player must be in the <see cref="PlayerState.Playing"/> or <see cref="PlayerState.Paused"/> state.</remarks>
695 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
696 /// <exception cref="InvalidOperationException">
697 /// The player is not in the valid state.\n
699 /// No subtitle is set.
701 /// <seealso cref="SetSubtitle(string)"/>
702 public void SetSubtitleOffset(int offset)
704 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
705 ValidatePlayerState(PlayerState.Playing, PlayerState.Paused);
707 var err = NativePlayer.SetSubtitlePositionOffset(Handle, offset);
709 if (err == PlayerErrorCode.FeatureNotSupported)
711 throw new InvalidOperationException("No subtitle set");
714 err.ThrowIfFailed("Failed to the subtitle offset of the player");
715 Log.Debug(PlayerLog.Tag, PlayerLog.Leave);
718 private void Prepare()
720 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
721 NativePlayer.Prepare(Handle).ThrowIfFailed("Failed to prepare the player");
724 protected virtual void OnPreparing()
730 /// Prepares the media player for playback, asynchronously.
732 /// <remarks>To prepare the player, the player must be in the <see cref="PlayerState.Idle"/> state,
733 /// and a source must be set.</remarks>
734 /// <exception cref="InvalidOperationException">No source is set.</exception>
735 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
736 /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
737 public virtual Task PrepareAsync()
739 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
742 throw new InvalidOperationException("No source is set.");
745 ValidatePlayerState(PlayerState.Idle);
749 var completionSource = new TaskCompletionSource<bool>();
759 completionSource.SetResult(true);
764 completionSource.TrySetException(e);
767 Log.Debug(PlayerLog.Tag, PlayerLog.Leave);
769 return completionSource.Task;
773 /// Unprepares the player.
776 /// The most recently used source is reset and no longer associated with the player. Playback is no longer possible.
777 /// If you want to use the player again, you have to set a source and call <see cref="PrepareAsync"/> again.
779 /// The player must be in the <see cref="PlayerState.Ready"/>, <see cref="PlayerState.Playing"/> or <see cref="PlayerState.Paused"/> state.
780 /// It has no effect if the player is already in the <see cref="PlayerState.Idle"/> state.
783 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
784 /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
785 public virtual void Unprepare()
787 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
788 if (State == PlayerState.Idle)
790 Log.Warn(PlayerLog.Tag, "idle state already");
793 ValidatePlayerState(PlayerState.Ready, PlayerState.Paused, PlayerState.Playing);
795 NativePlayer.Unprepare(Handle).ThrowIfFailed("Failed to unprepare the player");
800 protected virtual void OnUnprepared()
802 _source?.DetachFrom(this);
806 //TODO remarks needs to be updated. see the native reference.
808 /// Starts or resumes playback.
811 /// The player must be in the <see cref="PlayerState.Ready"/> or <see cref="PlayerState.Paused"/> state.
812 /// It has no effect if the player is already in the <see cref="PlayerState.Playing"/> state.
814 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
815 /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
816 /// <seealso cref="PrepareAsync"/>
817 /// <seealso cref="Stop"/>
818 /// <seealso cref="Pause"/>
819 /// <seealso cref="PlaybackCompleted"/>
820 public virtual void Start()
822 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
823 if (State == PlayerState.Playing)
825 Log.Warn(PlayerLog.Tag, "playing state already");
828 ValidatePlayerState(PlayerState.Ready, PlayerState.Paused);
830 NativePlayer.Start(Handle).ThrowIfFailed("Failed to start the player");
831 Log.Debug(PlayerLog.Tag, PlayerLog.Leave);
835 /// Stops playing media content.
838 /// The player must be in the <see cref="PlayerState.Playing"/> or <see cref="PlayerState.Paused"/> state.
839 /// It has no effect if the player is already in the <see cref="PlayerState.Ready"/> state.
841 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
842 /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
843 /// <seealso cref="Start"/>
844 /// <seealso cref="Pause"/>
845 public virtual void Stop()
847 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
848 if (State == PlayerState.Ready)
850 Log.Warn(PlayerLog.Tag, "ready state already");
853 ValidatePlayerState(PlayerState.Paused, PlayerState.Playing);
855 NativePlayer.Stop(Handle).ThrowIfFailed("Failed to stop the player");
856 Log.Debug(PlayerLog.Tag, PlayerLog.Leave);
860 /// Pauses the player.
863 /// The player must be in the <see cref="PlayerState.Playing"/> state.
864 /// It has no effect if the player is already in the <see cref="PlayerState.Paused"/> state.
866 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
867 /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
868 /// <seealso cref="Start"/>
869 public virtual void Pause()
871 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
872 if (State == PlayerState.Paused)
874 Log.Warn(PlayerLog.Tag, "pause state already");
878 ValidatePlayerState(PlayerState.Playing);
880 NativePlayer.Pause(Handle).ThrowIfFailed("Failed to pause the player");
881 Log.Debug(PlayerLog.Tag, PlayerLog.Leave);
884 private MediaSource _source;
887 /// Sets a media source for the player.
889 /// <param name="source">A <see cref="MediaSource"/> that specifies the source for playback.</param>
890 /// <remarks>The player must be in the <see cref="PlayerState.Idle"/> state.</remarks>
891 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
892 /// <exception cref="InvalidOperationException">
893 /// The player is not in the valid state.\n
895 /// It is not able to assign the source to the player.
897 /// <seealso cref="PrepareAsync"/>
898 public void SetSource(MediaSource source)
900 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
901 ValidatePlayerState(PlayerState.Idle);
905 source.AttachTo(this);
910 _source.DetachFrom(this);
914 Log.Debug(PlayerLog.Tag, PlayerLog.Leave);
918 /// Captures a video frame asynchronously.
920 /// <feature>http://tizen.org/feature/multimedia.raw_video</feature>
921 /// <remarks>The player must be in the <see cref="PlayerState.Playing"/> or <see cref="PlayerState.Paused"/> state.</remarks>
922 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
923 /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
924 /// <exception cref="NotSupportedException">The required feature is not supported.</exception>
925 public async Task<CapturedFrame> CaptureVideoAsync()
927 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
929 ValidationUtil.ValidateFeatureSupported(Features.RawVideo);
931 ValidatePlayerState(PlayerState.Playing, PlayerState.Paused);
933 TaskCompletionSource<CapturedFrame> t = new TaskCompletionSource<CapturedFrame>();
935 NativePlayer.VideoCaptureCallback cb = (data, width, height, size, _) =>
937 Debug.Assert(size <= int.MaxValue);
939 byte[] buf = new byte[size];
940 Marshal.Copy(data, buf, 0, (int)size);
942 t.TrySetResult(new CapturedFrame(buf, width, height));
945 using (var cbKeeper = ObjectKeeper.Get(cb))
947 NativePlayer.CaptureVideo(Handle, cb)
948 .ThrowIfFailed("Failed to capture the video");
955 /// Gets the play position in milliseconds.
957 /// <remarks>The player must be in the <see cref="PlayerState.Ready"/>, <see cref="PlayerState.Playing"/> or <see cref="PlayerState.Paused"/> state.</remarks>
958 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
959 /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
960 /// <seealso cref="SetPlayPositionAsync(int, bool)"/>
961 public int GetPlayPosition()
963 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
964 ValidatePlayerState(PlayerState.Ready, PlayerState.Paused, PlayerState.Playing);
966 int playPosition = 0;
968 NativePlayer.GetPlayPosition(Handle, out playPosition).
969 ThrowIfFailed("Failed to get the play position of the player");
971 Log.Info(PlayerLog.Tag, "get play position : " + playPosition);
976 private void SetPlayPosition(int milliseconds, bool accurate,
977 NativePlayer.SeekCompletedCallback cb)
979 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
980 var ret = NativePlayer.SetPlayPosition(Handle, milliseconds, accurate, cb, IntPtr.Zero);
982 //Note that we assume invalid param error is returned only when the position value is invalid.
983 if (ret == PlayerErrorCode.InvalidArgument)
985 throw new ArgumentOutOfRangeException(nameof(milliseconds), milliseconds,
986 "The position is not valid.");
988 if (ret != PlayerErrorCode.None)
990 Log.Error(PlayerLog.Tag, "Failed to set play position, " + (PlayerError)ret);
992 ret.ThrowIfFailed("Failed to set play position");
996 /// Sets the seek position for playback, asynchronously.
998 /// <param name="position">The value indicating a desired position in milliseconds.</param>
999 /// <param name="accurate">The value indicating whether the operation performs with accuracy.</param>
1001 /// <para>The player must be in the <see cref="PlayerState.Ready"/>, <see cref="PlayerState.Playing"/> or <see cref="PlayerState.Paused"/> state.</para>
1002 /// <para>If the <paramref name="accurate"/> is true, the play position will be adjusted as the specified <paramref name="position"/> value,
1003 /// but this might be considerably slow. If false, the play position will be a nearest keyframe position.</para>
1005 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
1006 /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
1007 /// <exception cref="ArgumentOutOfRangeException">The specified position is not valid.</exception>
1008 /// <seealso cref="GetPlayPosition"/>
1009 public async Task SetPlayPositionAsync(int position, bool accurate)
1011 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
1012 ValidatePlayerState(PlayerState.Ready, PlayerState.Playing, PlayerState.Paused);
1014 var taskCompletionSource = new TaskCompletionSource<bool>();
1016 bool immediateResult = _source is MediaStreamSource;
1018 NativePlayer.SeekCompletedCallback cb = _ => taskCompletionSource.TrySetResult(true);
1020 using (var cbKeeper = ObjectKeeper.Get(cb))
1022 SetPlayPosition(position, accurate, cb);
1023 if (immediateResult)
1025 taskCompletionSource.TrySetResult(true);
1028 await taskCompletionSource.Task;
1031 Log.Debug(PlayerLog.Tag, PlayerLog.Leave);
1035 /// Sets playback rate.
1037 /// <param name="rate">The value for the playback rate. Valid range is -5.0 to 5.0, inclusive.</param>
1039 /// <para>The player must be in the <see cref="PlayerState.Ready"/>, <see cref="PlayerState.Playing"/> or <see cref="PlayerState.Paused"/> state.</para>
1040 /// <para>The sound will be muted, when the playback rate is under 0.0 or over 2.0.</para>
1042 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
1043 /// <exception cref="InvalidOperationException">
1044 /// The player is not in the valid state.\n
1046 /// Streaming playback.
1048 /// <exception cref="ArgumentOutOfRangeException">
1049 /// <paramref name="rate"/> is less than 5.0.\n
1051 /// <paramref name="rate"/> is greater than 5.0.\n
1053 /// <paramref name="rate"/> is zero.
1055 public void SetPlaybackRate(float rate)
1057 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
1058 if (rate < -5.0F || 5.0F < rate || rate == 0.0F)
1060 throw new ArgumentOutOfRangeException(nameof(rate), rate, "Valid range is -5.0 to 5.0 (except 0.0)");
1063 ValidatePlayerState(PlayerState.Ready, PlayerState.Playing, PlayerState.Paused);
1065 NativePlayer.SetPlaybackRate(Handle, rate).ThrowIfFailed("Failed to set the playback rate.");
1066 Log.Debug(PlayerLog.Tag, PlayerLog.Leave);
1070 /// Applies the audio stream policy.
1072 /// <param name="policy">The <see cref="AudioStreamPolicy"/> to apply.</param>
1073 /// <remarks>The player must be in the <see cref="PlayerState.Idle"/> state.</remarks>
1074 /// <exception cref="ObjectDisposedException">
1075 /// The player has already been disposed of.\n
1077 /// <paramref name="poilcy"/> has already been disposed of.
1079 /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
1080 /// <exception cref="ArgumentNullException"><paramref name="policy"/> is null.</exception>
1081 public void ApplyAudioStreamPolicy(AudioStreamPolicy policy)
1083 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
1086 throw new ArgumentNullException(nameof(policy));
1089 if (policy.Handle == IntPtr.Zero)
1091 throw new ObjectDisposedException(nameof(policy));
1094 ValidatePlayerState(PlayerState.Idle);
1096 NativePlayer.SetAudioPolicyInfo(Handle, policy.Handle).
1097 ThrowIfFailed("Failed to set the audio stream policy to the player");
1101 #region Callback registrations
1102 private void RegisterSubtitleUpdatedCallback()
1104 _subtitleUpdatedCallback = (duration, text, _) =>
1106 Log.Debug(PlayerLog.Tag, "duration : " + duration + ", text : " + text);
1107 SubtitleUpdated?.Invoke(this, new SubtitleUpdatedEventArgs(duration, text));
1110 NativePlayer.SetSubtitleUpdatedCb(Handle, _subtitleUpdatedCallback).
1111 ThrowIfFailed("Failed to initialize the player");
1114 private void RegisterPlaybackCompletedCallback()
1116 _playbackCompletedCallback = _ =>
1118 Log.Debug(PlayerLog.Tag, "completed callback");
1119 PlaybackCompleted?.Invoke(this, EventArgs.Empty);
1121 NativePlayer.SetCompletedCb(Handle, _playbackCompletedCallback).
1122 ThrowIfFailed("Failed to set PlaybackCompleted");
1125 private void RegisterPlaybackInterruptedCallback()
1127 _playbackInterruptedCallback = (code, _) =>
1129 if (!Enum.IsDefined(typeof(PlaybackInterruptionReason), code))
1134 if (code == PlaybackInterruptionReason.ResourceConflict)
1139 Log.Warn(PlayerLog.Tag, "interrupted reason : " + code);
1140 PlaybackInterrupted?.Invoke(this, new PlaybackInterruptedEventArgs(code));
1143 NativePlayer.SetInterruptedCb(Handle, _playbackInterruptedCallback).
1144 ThrowIfFailed("Failed to set PlaybackInterrupted");
1147 private void RegisterErrorOccurredCallback()
1149 _playbackErrorCallback = (code, _) =>
1151 //TODO handle service disconnected error.
1152 Log.Warn(PlayerLog.Tag, "error code : " + code);
1153 ErrorOccurred?.Invoke(this, new PlayerErrorOccurredEventArgs((PlayerError)code));
1156 NativePlayer.SetErrorCb(Handle, _playbackErrorCallback).
1157 ThrowIfFailed("Failed to set PlaybackError");
1160 #region VideoFrameDecoded event
1162 private EventHandler<VideoFrameDecodedEventArgs> _videoFrameDecoded;
1164 private NativePlayer.VideoFrameDecodedCallback _videoFrameDecodedCallback;
1167 /// Occurs when a video frame is decoded.
1170 /// <para>The event handler will be executed on an internal thread.</para>
1171 /// <para>The <see cref="VideoFrameDecodedEventArgs.Packet"/> in event args should be disposed after use.</para>
1173 /// <feature>http://tizen.org/feature/multimedia.raw_video</feature>
1174 /// <exception cref="NotSupportedException">The required feature is not supported.</exception>
1175 /// <seealso cref="VideoFrameDecodedEventArgs.Packet"/>
1176 public event EventHandler<VideoFrameDecodedEventArgs> VideoFrameDecoded
1180 ValidationUtil.ValidateFeatureSupported(Features.RawVideo);
1182 _videoFrameDecoded += value;
1186 ValidationUtil.ValidateFeatureSupported(Features.RawVideo);
1188 _videoFrameDecoded -= value;
1192 private void RegisterVideoFrameDecodedCallback()
1194 _videoFrameDecodedCallback = (packetHandle, _) =>
1196 var handler = _videoFrameDecoded;
1197 if (handler != null)
1199 Log.Debug(PlayerLog.Tag, "packet : " + packetHandle);
1200 handler.Invoke(this,
1201 new VideoFrameDecodedEventArgs(MediaPacket.From(packetHandle)));
1205 MediaPacket.From(packetHandle).Dispose();
1209 NativePlayer.SetVideoFrameDecodedCb(Handle, _videoFrameDecodedCallback).
1210 ThrowIfFailed("Failed to register the VideoFrameDecoded");
1214 private void RegisterVideoStreamChangedCallback()
1216 ValidatePlayerState(PlayerState.Idle);
1218 _videoStreamChangedCallback = (width, height, fps, bitrate, _) =>
1220 Log.Debug(PlayerLog.Tag, "height : " + height + ", width : " + width
1221 + ", fps : " + fps + ", bitrate : " + bitrate);
1223 VideoStreamChanged?.Invoke(this, new VideoStreamChangedEventArgs(height, width, fps, bitrate));
1226 NativePlayer.SetVideoStreamChangedCb(Handle, _videoStreamChangedCallback).
1227 ThrowIfFailed("Failed to set the video stream changed callback");
1230 private void RegisterBufferingCallback()
1232 _bufferingProgressCallback = (percent, _) =>
1234 Log.Debug(PlayerLog.Tag, $"Buffering callback with percent { percent }");
1235 BufferingProgressChanged?.Invoke(this, new BufferingProgressChangedEventArgs(percent));
1238 NativePlayer.SetBufferingCb(Handle, _bufferingProgressCallback).
1239 ThrowIfFailed("Failed to set BufferingProgress");
1242 private void RegisterMediaStreamBufferStatusCallback()
1244 _mediaStreamAudioBufferStatusChangedCallback = (status, _) =>
1246 Debug.Assert(Enum.IsDefined(typeof(MediaStreamBufferStatus), status));
1247 Log.Debug(PlayerLog.Tag, "audio buffer status : " + status);
1248 MediaStreamAudioBufferStatusChanged?.Invoke(this,
1249 new MediaStreamBufferStatusChangedEventArgs(status));
1251 _mediaStreamVideoBufferStatusChangedCallback = (status, _) =>
1253 Debug.Assert(Enum.IsDefined(typeof(MediaStreamBufferStatus), status));
1254 Log.Debug(PlayerLog.Tag, "video buffer status : " + status);
1255 MediaStreamVideoBufferStatusChanged?.Invoke(this,
1256 new MediaStreamBufferStatusChangedEventArgs(status));
1259 RegisterMediaStreamBufferStatusCallback(StreamType.Audio, _mediaStreamAudioBufferStatusChangedCallback);
1260 RegisterMediaStreamBufferStatusCallback(StreamType.Video, _mediaStreamVideoBufferStatusChangedCallback);
1263 private void RegisterMediaStreamBufferStatusCallback(StreamType streamType,
1264 NativePlayer.MediaStreamBufferStatusCallback cb)
1266 NativePlayer.SetMediaStreamBufferStatusCb(Handle, streamType, cb).
1267 ThrowIfFailed("Failed to SetMediaStreamBufferStatus");
1270 private void RegisterMediaStreamSeekCallback()
1272 _mediaStreamAudioSeekCallback = (offset, _) =>
1274 Log.Debug(PlayerLog.Tag, "audio seeking offset : " + offset);
1275 MediaStreamAudioSeekingOccurred?.Invoke(this, new MediaStreamSeekingOccurredEventArgs(offset));
1277 _mediaStreamVideoSeekCallback = (offset, _) =>
1279 Log.Debug(PlayerLog.Tag, "video seeking offset : " + offset);
1280 MediaStreamVideoSeekingOccurred?.Invoke(this, new MediaStreamSeekingOccurredEventArgs(offset));
1283 RegisterMediaStreamSeekCallback(StreamType.Audio, _mediaStreamAudioSeekCallback);
1284 RegisterMediaStreamSeekCallback(StreamType.Video, _mediaStreamVideoSeekCallback);
1287 private void RegisterMediaStreamSeekCallback(StreamType streamType, NativePlayer.MediaStreamSeekCallback cb)
1289 NativePlayer.SetMediaStreamSeekCb(Handle, streamType, cb).
1290 ThrowIfFailed("Failed to SetMediaStreamSeek");
1294 #region Preparing state
1296 private int _isPreparing;
1298 private bool IsPreparing()
1300 return Interlocked.CompareExchange(ref _isPreparing, 1, 1) == 1;
1303 private void SetPreparing()
1305 Interlocked.Exchange(ref _isPreparing, 1);
1308 private void ClearPreparing()
1310 Interlocked.Exchange(ref _isPreparing, 0);