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;
155 /// Gets the native handle of the player.
157 /// <value>An IntPtr that contains the native handle of the player.</value>
158 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
163 ValidateNotDisposed();
164 return _handle.DangerousGetHandle();
168 internal void ValidatePlayerState(params PlayerState[] desiredStates)
170 Debug.Assert(desiredStates.Length > 0);
172 ValidateNotDisposed();
174 var curState = State;
175 if (curState.IsAnyOf(desiredStates))
180 throw new InvalidOperationException($"The player is not in a valid state. " +
181 $"Current State : { curState }, Valid State : { string.Join(", ", desiredStates) }.");
185 #region Network configuration
186 private string _cookie = "";
187 private string _userAgent = "";
190 /// Gets or Sets the cookie for streaming playback.
192 /// <remarks>To set, the player must be in the <see cref="PlayerState.Idle"/> state.</remarks>
193 /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
194 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
195 /// <exception cref="ArgumentNullException">The value to set is null.</exception>
200 Log.Info(PlayerLog.Tag, "get cookie : " + _cookie);
205 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
206 ValidatePlayerState(PlayerState.Idle);
210 Log.Error(PlayerLog.Tag, "cookie can't be null");
211 throw new ArgumentNullException(nameof(value), "Cookie can't be null.");
214 NativePlayer.SetStreamingCookie(Handle, value, value.Length).
215 ThrowIfFailed("Failed to set the cookie to the player");
218 Log.Debug(PlayerLog.Tag, PlayerLog.Leave);
223 /// Gets or Sets the user agent for streaming playback.
225 /// <remarks>To set, the player must be in the <see cref="PlayerState.Idle"/> state.</remarks>
226 /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
227 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
228 /// <exception cref="ArgumentNullException">The value to set is null.</exception>
229 public string UserAgent
233 Log.Info(PlayerLog.Tag, "get useragent : " + _userAgent);
238 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
239 ValidatePlayerState(PlayerState.Idle);
243 Log.Error(PlayerLog.Tag, "UserAgent can't be null");
244 throw new ArgumentNullException(nameof(value), "UserAgent can't be null.");
247 NativePlayer.SetStreamingUserAgent(Handle, value, value.Length).
248 ThrowIfFailed("Failed to set the user agent to the player");
251 Log.Debug(PlayerLog.Tag, PlayerLog.Leave);
257 /// Gets the state of the player.
259 /// <value>The current state of the player.</value>
260 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
261 public PlayerState State
265 ValidateNotDisposed();
269 return PlayerState.Preparing;
273 NativePlayer.GetState(Handle, out state).ThrowIfFailed("Failed to retrieve the state of the player");
275 Debug.Assert(Enum.IsDefined(typeof(PlayerState), state));
277 return (PlayerState)state;
281 private AudioLatencyMode _audioLatencyMode;
284 /// Gets or sets the audio latency mode.
286 /// <value>A <see cref="AudioLatencyMode"/> that specifies the mode. The default is <see cref="AudioLatencyMode.Mid"/>.</value>
288 /// If the mode is <see cref="AudioLatencyMode.High"/>,
289 /// audio output interval can be increased so, it can keep more audio data to play.
290 /// But, state transition like pause or resume can be more slower than default(<see cref="AudioLatencyMode.Mid"/>).
292 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
293 /// <exception cref="ArgumentException">The value is not valid.</exception>
294 public AudioLatencyMode AudioLatencyMode
298 Log.Info(PlayerLog.Tag, "get audio latency mode : " + _audioLatencyMode);
299 return _audioLatencyMode;
303 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
304 ValidateNotDisposed();
306 if (_audioLatencyMode == value)
310 ValidationUtil.ValidateEnum(typeof(AudioLatencyMode), value);
312 NativePlayer.SetAudioLatencyMode(Handle, value).
313 ThrowIfFailed("Failed to set the audio latency mode of the player");
315 _audioLatencyMode = value;
319 private bool _isLooping;
322 /// Gets or sets the looping state.
324 /// <value>true if the playback is looping; otherwise, false. The default value is false.</value>
325 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
326 public bool IsLooping
330 Log.Info(PlayerLog.Tag, "get looping : " + _isLooping);
335 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
336 ValidateNotDisposed();
338 if (_isLooping == value)
343 NativePlayer.SetLooping(Handle, value).ThrowIfFailed("Failed to set the looping state of the player");
349 #region Display methods
351 /// Gets the display settings.
353 /// <value>A <see cref="PlayerDisplaySettings"/> that specifies the display settings.</value>
354 public PlayerDisplaySettings DisplaySettings { get; }
356 private Display _display;
358 private PlayerErrorCode SetDisplay(Display display)
362 Log.Info(PlayerLog.Tag, "set display to none");
363 return NativePlayer.SetDisplay(Handle, DisplayType.None, IntPtr.Zero);
366 return display.ApplyTo(this);
369 private void ReplaceDisplay(Display newDisplay)
371 _display?.SetOwner(null);
372 _display = newDisplay;
373 _display?.SetOwner(this);
377 /// Gets or sets the display.
379 /// <value>A <see cref="Multimedia.Display"/> that specifies the display.</value>
380 /// <remarks>The player must be in the <see cref="PlayerState.Idle"/> state.</remarks>
381 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
382 /// <exception cref="ArgumentException">The value has already been assigned to another player.</exception>
383 /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
384 public Display Display
392 ValidatePlayerState(PlayerState.Idle);
394 if (value?.Owner != null)
396 if (ReferenceEquals(this, value.Owner))
401 throw new ArgumentException("The display has already been assigned to another.");
403 SetDisplay(value).ThrowIfFailed("Failed to set the display to the player");
405 ReplaceDisplay(value);
409 PlayerErrorCode IDisplayable<PlayerErrorCode>.ApplyEvasDisplay(DisplayType type, ElmSharp.EvasObject evasObject)
411 Debug.Assert(IsDisposed == false);
413 Debug.Assert(Enum.IsDefined(typeof(DisplayType), type));
415 return NativePlayer.SetDisplay(Handle, type, evasObject);
419 private PlayerTrackInfo _audioTrack;
422 /// Gets the track info for audio.
424 /// <value>A <see cref="PlayerTrackInfo"/> for audio.</value>
425 public PlayerTrackInfo AudioTrackInfo
429 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
430 if (_audioTrack == null)
432 _audioTrack = new PlayerTrackInfo(this, StreamType.Audio);
438 private PlayerTrackInfo _subtitleTrackInfo;
441 /// Gets the track info for subtitle.
443 /// <value>A <see cref="PlayerTrackInfo"/> for subtitle.</value>
444 public PlayerTrackInfo SubtitleTrackInfo
448 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
449 if (_subtitleTrackInfo == null)
451 _subtitleTrackInfo = new PlayerTrackInfo(this, StreamType.Text);
453 return _subtitleTrackInfo;
457 private StreamInfo _streamInfo;
460 /// Gets the stream information.
462 /// <value>A <see cref="StreamInfo"/> for this player.</value>
463 public StreamInfo StreamInfo
467 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
468 if (_streamInfo == null)
470 _streamInfo = new StreamInfo(this);
476 private readonly AudioEffect _audioEffect;
479 /// Gets the audio effect.
481 /// <feature>http://tizen.org/feature/multimedia.custom_audio_effect</feature>
482 /// <exception cref="NotSupportedException">The required feature is not supported.</exception>
483 public AudioEffect AudioEffect
487 if (_audioEffect == null)
489 throw new NotSupportedException($"The feature({Features.AudioEffect}) is not supported.");
498 #region Dispose support
499 private bool _disposed;
502 /// Releases all resources used by the current instance.
504 public void Dispose()
506 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
510 private void Dispose(bool disposing)
514 ReplaceDisplay(null);
520 _source.DetachFrom(this);
524 Log.Error(PlayerLog.Tag, e.ToString());
537 internal void ValidateNotDisposed()
541 Log.Warn(PlayerLog.Tag, "player was disposed");
542 throw new ObjectDisposedException(nameof(Player));
546 internal bool IsDisposed => _disposed;
552 /// Gets or sets the mute state.
554 /// <value>true if the player is muted; otherwise, false.</value>
555 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
560 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
563 NativePlayer.IsMuted(Handle, out value).ThrowIfFailed("Failed to get the mute state of the player");
565 Log.Info(PlayerLog.Tag, "get mute : " + value);
571 NativePlayer.SetMute(Handle, value).ThrowIfFailed("Failed to set the mute state of the player");
576 /// Gets the streaming download Progress.
578 /// <returns>The <see cref="DownloadProgress"/> containing current download progress.</returns>
579 /// <remarks>The player must be in the <see cref="PlayerState.Playing"/> or <see cref="PlayerState.Paused"/> state.</remarks>
580 /// <exception cref="InvalidOperationException">
581 /// The player is not streaming.\n
583 /// The player is not in the valid state.
585 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
586 public DownloadProgress GetDownloadProgress()
588 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
589 ValidatePlayerState(PlayerState.Playing, PlayerState.Paused);
593 NativePlayer.GetStreamingDownloadProgress(Handle, out start, out current).
594 ThrowIfFailed("Failed to get download progress");
596 Log.Info(PlayerLog.Tag, "get download progress : " + start + ", " + current);
598 return new DownloadProgress(start, current);
603 /// Gets or sets the current volume.
605 /// <remarks>Valid volume range is from 0 to 1.0, inclusive.</remarks>
606 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
607 /// <exception cref="ArgumentOutOfRangeException">
608 /// <paramref name="value"/> is less than zero.\n
610 /// <paramref name="value"/> is greater than 1.0.
617 NativePlayer.GetVolume(Handle, out value, out value).
618 ThrowIfFailed("Failed to get the volume of the player");
623 if (value < 0F || 1.0F < value)
625 throw new ArgumentOutOfRangeException(nameof(value), value,
626 $"Valid volume range is 0 <= value <= 1.0, but got { value }.");
629 NativePlayer.SetVolume(Handle, value, value).
630 ThrowIfFailed("Failed to set the volume of the player");
637 /// Sets the subtitle path for playback.
639 /// <remarks>Only MicroDVD/SubViewer(*.sub), SAMI(*.smi), and SubRip(*.srt) subtitle formats are supported.
640 /// <para>The mediastorage privilege(http://tizen.org/privilege/mediastorage) must be added if any files are used to play located in the internal storage.
641 /// The externalstorage privilege(http://tizen.org/privilege/externalstorage) must be added if any files are used to play located in the external storage.</para>
643 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
644 /// <exception cref="ArgumentException"><paramref name="path"/> is an empty string.</exception>
645 /// <exception cref="FileNotFoundException">The specified path does not exist.</exception>
646 /// <exception cref="ArgumentNullException">The path is null.</exception>
647 public void SetSubtitle(string path)
649 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
650 ValidateNotDisposed();
654 throw new ArgumentNullException(nameof(path));
657 if (path.Length == 0)
659 throw new ArgumentException("The path is empty.", nameof(path));
662 if (!File.Exists(path))
664 throw new FileNotFoundException($"The specified file does not exist.", path);
667 NativePlayer.SetSubtitlePath(Handle, path).
668 ThrowIfFailed("Failed to set the subtitle path to the player");
670 Log.Debug(PlayerLog.Tag, PlayerLog.Leave);
674 /// Removes the subtitle path.
676 /// <remarks>The player must be in the <see cref="PlayerState.Idle"/> state.</remarks>
677 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
678 /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
679 public void ClearSubtitle()
681 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
682 ValidatePlayerState(PlayerState.Idle);
684 NativePlayer.SetSubtitlePath(Handle, null).
685 ThrowIfFailed("Failed to clear the subtitle of the player");
686 Log.Debug(PlayerLog.Tag, PlayerLog.Leave);
690 /// Sets the offset for the subtitle.
692 /// <param name="offset">The value indicating a desired offset in milliseconds.</param>
693 /// <remarks>The player must be in the <see cref="PlayerState.Playing"/> or <see cref="PlayerState.Paused"/> state.</remarks>
694 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
695 /// <exception cref="InvalidOperationException">
696 /// The player is not in the valid state.\n
698 /// No subtitle is set.
700 /// <seealso cref="SetSubtitle(string)"/>
701 public void SetSubtitleOffset(int offset)
703 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
704 ValidatePlayerState(PlayerState.Playing, PlayerState.Paused);
706 var err = NativePlayer.SetSubtitlePositionOffset(Handle, offset);
708 if (err == PlayerErrorCode.FeatureNotSupported)
710 throw new InvalidOperationException("No subtitle set");
713 err.ThrowIfFailed("Failed to the subtitle offset of the player");
714 Log.Debug(PlayerLog.Tag, PlayerLog.Leave);
717 private void Prepare()
719 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
720 NativePlayer.Prepare(Handle).ThrowIfFailed("Failed to prepare the player");
723 protected virtual void OnPreparing()
729 /// Prepares the media player for playback, asynchronously.
731 /// <returns>A task that represents the asynchronous prepare operation.</returns>
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);
807 /// Starts or resumes playback.
810 /// The player must be in the <see cref="PlayerState.Ready"/> or <see cref="PlayerState.Paused"/> state.
811 /// It has no effect if the player is already in the <see cref="PlayerState.Playing"/> state.\n
813 /// Sound can be mixed with other sounds if you don't control the stream focus using <see cref="ApplyAudioStreamPolicy"/>.
815 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
816 /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
817 /// <seealso cref="PrepareAsync"/>
818 /// <seealso cref="Stop"/>
819 /// <seealso cref="Pause"/>
820 /// <seealso cref="PlaybackCompleted"/>
821 /// <seealso cref="ApplyAudioStreamPolicy"/>
822 public virtual void Start()
824 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
825 if (State == PlayerState.Playing)
827 Log.Warn(PlayerLog.Tag, "playing state already");
830 ValidatePlayerState(PlayerState.Ready, PlayerState.Paused);
832 NativePlayer.Start(Handle).ThrowIfFailed("Failed to start the player");
833 Log.Debug(PlayerLog.Tag, PlayerLog.Leave);
837 /// Stops playing media content.
840 /// The player must be in the <see cref="PlayerState.Playing"/> or <see cref="PlayerState.Paused"/> state.
841 /// It has no effect if the player is already in the <see cref="PlayerState.Ready"/> state.
843 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
844 /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
845 /// <seealso cref="Start"/>
846 /// <seealso cref="Pause"/>
847 public virtual void Stop()
849 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
850 if (State == PlayerState.Ready)
852 Log.Warn(PlayerLog.Tag, "ready state already");
855 ValidatePlayerState(PlayerState.Paused, PlayerState.Playing);
857 NativePlayer.Stop(Handle).ThrowIfFailed("Failed to stop the player");
858 Log.Debug(PlayerLog.Tag, PlayerLog.Leave);
862 /// Pauses the player.
865 /// The player must be in the <see cref="PlayerState.Playing"/> state.
866 /// It has no effect if the player is already in the <see cref="PlayerState.Paused"/> state.
868 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
869 /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
870 /// <seealso cref="Start"/>
871 public virtual void Pause()
873 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
874 if (State == PlayerState.Paused)
876 Log.Warn(PlayerLog.Tag, "pause state already");
880 ValidatePlayerState(PlayerState.Playing);
882 NativePlayer.Pause(Handle).ThrowIfFailed("Failed to pause the player");
883 Log.Debug(PlayerLog.Tag, PlayerLog.Leave);
886 private MediaSource _source;
889 /// Sets a media source for the player.
891 /// <param name="source">A <see cref="MediaSource"/> that specifies the source for playback.</param>
892 /// <remarks>The player must be in the <see cref="PlayerState.Idle"/> state.</remarks>
893 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
894 /// <exception cref="InvalidOperationException">
895 /// The player is not in the valid state.\n
897 /// It is not able to assign the source to the player.
899 /// <seealso cref="PrepareAsync"/>
900 public void SetSource(MediaSource source)
902 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
903 ValidatePlayerState(PlayerState.Idle);
907 source.AttachTo(this);
912 _source.DetachFrom(this);
916 Log.Debug(PlayerLog.Tag, PlayerLog.Leave);
920 /// Captures a video frame asynchronously.
922 /// <returns>A task that represents the asynchronous capture operation.</returns>
923 /// <feature>http://tizen.org/feature/multimedia.raw_video</feature>
924 /// <remarks>The player must be in the <see cref="PlayerState.Playing"/> or <see cref="PlayerState.Paused"/> state.</remarks>
925 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
926 /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
927 /// <exception cref="NotSupportedException">The required feature is not supported.</exception>
928 public async Task<CapturedFrame> CaptureVideoAsync()
930 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
932 ValidationUtil.ValidateFeatureSupported(Features.RawVideo);
934 ValidatePlayerState(PlayerState.Playing, PlayerState.Paused);
936 TaskCompletionSource<CapturedFrame> t = new TaskCompletionSource<CapturedFrame>();
938 NativePlayer.VideoCaptureCallback cb = (data, width, height, size, _) =>
940 Debug.Assert(size <= int.MaxValue);
942 byte[] buf = new byte[size];
943 Marshal.Copy(data, buf, 0, (int)size);
945 t.TrySetResult(new CapturedFrame(buf, width, height));
948 using (var cbKeeper = ObjectKeeper.Get(cb))
950 NativePlayer.CaptureVideo(Handle, cb)
951 .ThrowIfFailed("Failed to capture the video");
958 /// Gets the play position in milliseconds.
960 /// <remarks>The player must be in the <see cref="PlayerState.Ready"/>, <see cref="PlayerState.Playing"/> or <see cref="PlayerState.Paused"/> state.</remarks>
961 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
962 /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
963 /// <seealso cref="SetPlayPositionAsync(int, bool)"/>
964 public int GetPlayPosition()
966 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
967 ValidatePlayerState(PlayerState.Ready, PlayerState.Paused, PlayerState.Playing);
969 int playPosition = 0;
971 NativePlayer.GetPlayPosition(Handle, out playPosition).
972 ThrowIfFailed("Failed to get the play position of the player");
974 Log.Info(PlayerLog.Tag, "get play position : " + playPosition);
979 private void SetPlayPosition(int milliseconds, bool accurate,
980 NativePlayer.SeekCompletedCallback cb)
982 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
983 var ret = NativePlayer.SetPlayPosition(Handle, milliseconds, accurate, cb, IntPtr.Zero);
985 //Note that we assume invalid param error is returned only when the position value is invalid.
986 if (ret == PlayerErrorCode.InvalidArgument)
988 throw new ArgumentOutOfRangeException(nameof(milliseconds), milliseconds,
989 "The position is not valid.");
991 if (ret != PlayerErrorCode.None)
993 Log.Error(PlayerLog.Tag, "Failed to set play position, " + (PlayerError)ret);
995 ret.ThrowIfFailed("Failed to set play position");
999 /// Sets the seek position for playback, asynchronously.
1001 /// <param name="position">The value indicating a desired position in milliseconds.</param>
1002 /// <param name="accurate">The value indicating whether the operation performs with accuracy.</param>
1004 /// <para>The player must be in the <see cref="PlayerState.Ready"/>, <see cref="PlayerState.Playing"/> or <see cref="PlayerState.Paused"/> state.</para>
1005 /// <para>If the <paramref name="accurate"/> is true, the play position will be adjusted as the specified <paramref name="position"/> value,
1006 /// but this might be considerably slow. If false, the play position will be a nearest keyframe position.</para>
1008 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
1009 /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
1010 /// <exception cref="ArgumentOutOfRangeException">The specified position is not valid.</exception>
1011 /// <seealso cref="GetPlayPosition"/>
1012 public async Task SetPlayPositionAsync(int position, bool accurate)
1014 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
1015 ValidatePlayerState(PlayerState.Ready, PlayerState.Playing, PlayerState.Paused);
1017 var taskCompletionSource = new TaskCompletionSource<bool>();
1019 bool immediateResult = _source is MediaStreamSource;
1021 NativePlayer.SeekCompletedCallback cb = _ => taskCompletionSource.TrySetResult(true);
1023 using (var cbKeeper = ObjectKeeper.Get(cb))
1025 SetPlayPosition(position, accurate, cb);
1026 if (immediateResult)
1028 taskCompletionSource.TrySetResult(true);
1031 await taskCompletionSource.Task;
1034 Log.Debug(PlayerLog.Tag, PlayerLog.Leave);
1038 /// Sets playback rate.
1040 /// <param name="rate">The value for the playback rate. Valid range is -5.0 to 5.0, inclusive.</param>
1042 /// <para>The player must be in the <see cref="PlayerState.Ready"/>, <see cref="PlayerState.Playing"/> or <see cref="PlayerState.Paused"/> state.</para>
1043 /// <para>The sound will be muted, when the playback rate is under 0.0 or over 2.0.</para>
1045 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
1046 /// <exception cref="InvalidOperationException">
1047 /// The player is not in the valid state.\n
1049 /// Streaming playback.
1051 /// <exception cref="ArgumentOutOfRangeException">
1052 /// <paramref name="rate"/> is less than 5.0.\n
1054 /// <paramref name="rate"/> is greater than 5.0.\n
1056 /// <paramref name="rate"/> is zero.
1058 public void SetPlaybackRate(float rate)
1060 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
1061 if (rate < -5.0F || 5.0F < rate || rate == 0.0F)
1063 throw new ArgumentOutOfRangeException(nameof(rate), rate, "Valid range is -5.0 to 5.0 (except 0.0)");
1066 ValidatePlayerState(PlayerState.Ready, PlayerState.Playing, PlayerState.Paused);
1068 NativePlayer.SetPlaybackRate(Handle, rate).ThrowIfFailed("Failed to set the playback rate.");
1069 Log.Debug(PlayerLog.Tag, PlayerLog.Leave);
1073 /// Applies the audio stream policy.
1075 /// <param name="policy">The <see cref="AudioStreamPolicy"/> to apply.</param>
1076 /// <remarks>The player must be in the <see cref="PlayerState.Idle"/> state.</remarks>
1077 /// <exception cref="ObjectDisposedException">
1078 /// The player has already been disposed of.\n
1080 /// <paramref name="policy"/> has already been disposed of.
1082 /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
1083 /// <exception cref="ArgumentNullException"><paramref name="policy"/> is null.</exception>
1084 public void ApplyAudioStreamPolicy(AudioStreamPolicy policy)
1086 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
1089 throw new ArgumentNullException(nameof(policy));
1092 if (policy.Handle == IntPtr.Zero)
1094 throw new ObjectDisposedException(nameof(policy));
1097 ValidatePlayerState(PlayerState.Idle);
1099 NativePlayer.SetAudioPolicyInfo(Handle, policy.Handle).
1100 ThrowIfFailed("Failed to set the audio stream policy to the player");
1104 #region Callback registrations
1105 private void RegisterSubtitleUpdatedCallback()
1107 _subtitleUpdatedCallback = (duration, text, _) =>
1109 Log.Debug(PlayerLog.Tag, "duration : " + duration + ", text : " + text);
1110 SubtitleUpdated?.Invoke(this, new SubtitleUpdatedEventArgs(duration, text));
1113 NativePlayer.SetSubtitleUpdatedCb(Handle, _subtitleUpdatedCallback).
1114 ThrowIfFailed("Failed to initialize the player");
1117 private void RegisterPlaybackCompletedCallback()
1119 _playbackCompletedCallback = _ =>
1121 Log.Debug(PlayerLog.Tag, "completed callback");
1122 PlaybackCompleted?.Invoke(this, EventArgs.Empty);
1124 NativePlayer.SetCompletedCb(Handle, _playbackCompletedCallback).
1125 ThrowIfFailed("Failed to set PlaybackCompleted");
1128 private void RegisterPlaybackInterruptedCallback()
1130 _playbackInterruptedCallback = (code, _) =>
1132 if (!Enum.IsDefined(typeof(PlaybackInterruptionReason), code))
1137 if (code == PlaybackInterruptionReason.ResourceConflict)
1142 Log.Warn(PlayerLog.Tag, "interrupted reason : " + code);
1143 PlaybackInterrupted?.Invoke(this, new PlaybackInterruptedEventArgs(code));
1146 NativePlayer.SetInterruptedCb(Handle, _playbackInterruptedCallback).
1147 ThrowIfFailed("Failed to set PlaybackInterrupted");
1150 private void RegisterErrorOccurredCallback()
1152 _playbackErrorCallback = (code, _) =>
1154 //TODO handle service disconnected error.
1155 Log.Warn(PlayerLog.Tag, "error code : " + code);
1156 ErrorOccurred?.Invoke(this, new PlayerErrorOccurredEventArgs((PlayerError)code));
1159 NativePlayer.SetErrorCb(Handle, _playbackErrorCallback).
1160 ThrowIfFailed("Failed to set PlaybackError");
1163 #region VideoFrameDecoded event
1165 private EventHandler<VideoFrameDecodedEventArgs> _videoFrameDecoded;
1167 private NativePlayer.VideoFrameDecodedCallback _videoFrameDecodedCallback;
1170 /// Occurs when a video frame is decoded.
1173 /// <para>The event handler will be executed on an internal thread.</para>
1174 /// <para>The <see cref="VideoFrameDecodedEventArgs.Packet"/> in event args should be disposed after use.</para>
1176 /// <feature>http://tizen.org/feature/multimedia.raw_video</feature>
1177 /// <exception cref="NotSupportedException">The required feature is not supported.</exception>
1178 /// <seealso cref="VideoFrameDecodedEventArgs.Packet"/>
1179 public event EventHandler<VideoFrameDecodedEventArgs> VideoFrameDecoded
1183 ValidationUtil.ValidateFeatureSupported(Features.RawVideo);
1185 _videoFrameDecoded += value;
1189 ValidationUtil.ValidateFeatureSupported(Features.RawVideo);
1191 _videoFrameDecoded -= value;
1195 private void RegisterVideoFrameDecodedCallback()
1197 _videoFrameDecodedCallback = (packetHandle, _) =>
1199 var handler = _videoFrameDecoded;
1200 if (handler != null)
1202 Log.Debug(PlayerLog.Tag, "packet : " + packetHandle);
1203 handler.Invoke(this,
1204 new VideoFrameDecodedEventArgs(MediaPacket.From(packetHandle)));
1208 MediaPacket.From(packetHandle).Dispose();
1212 NativePlayer.SetVideoFrameDecodedCb(Handle, _videoFrameDecodedCallback).
1213 ThrowIfFailed("Failed to register the VideoFrameDecoded");
1217 private void RegisterVideoStreamChangedCallback()
1219 ValidatePlayerState(PlayerState.Idle);
1221 _videoStreamChangedCallback = (width, height, fps, bitrate, _) =>
1223 Log.Debug(PlayerLog.Tag, "height : " + height + ", width : " + width
1224 + ", fps : " + fps + ", bitrate : " + bitrate);
1226 VideoStreamChanged?.Invoke(this, new VideoStreamChangedEventArgs(height, width, fps, bitrate));
1229 NativePlayer.SetVideoStreamChangedCb(Handle, _videoStreamChangedCallback).
1230 ThrowIfFailed("Failed to set the video stream changed callback");
1233 private void RegisterBufferingCallback()
1235 _bufferingProgressCallback = (percent, _) =>
1237 Log.Debug(PlayerLog.Tag, $"Buffering callback with percent { percent }");
1238 BufferingProgressChanged?.Invoke(this, new BufferingProgressChangedEventArgs(percent));
1241 NativePlayer.SetBufferingCb(Handle, _bufferingProgressCallback).
1242 ThrowIfFailed("Failed to set BufferingProgress");
1245 private void RegisterMediaStreamBufferStatusCallback()
1247 _mediaStreamAudioBufferStatusChangedCallback = (status, _) =>
1249 Debug.Assert(Enum.IsDefined(typeof(MediaStreamBufferStatus), status));
1250 Log.Debug(PlayerLog.Tag, "audio buffer status : " + status);
1251 MediaStreamAudioBufferStatusChanged?.Invoke(this,
1252 new MediaStreamBufferStatusChangedEventArgs(status));
1254 _mediaStreamVideoBufferStatusChangedCallback = (status, _) =>
1256 Debug.Assert(Enum.IsDefined(typeof(MediaStreamBufferStatus), status));
1257 Log.Debug(PlayerLog.Tag, "video buffer status : " + status);
1258 MediaStreamVideoBufferStatusChanged?.Invoke(this,
1259 new MediaStreamBufferStatusChangedEventArgs(status));
1262 RegisterMediaStreamBufferStatusCallback(StreamType.Audio, _mediaStreamAudioBufferStatusChangedCallback);
1263 RegisterMediaStreamBufferStatusCallback(StreamType.Video, _mediaStreamVideoBufferStatusChangedCallback);
1266 private void RegisterMediaStreamBufferStatusCallback(StreamType streamType,
1267 NativePlayer.MediaStreamBufferStatusCallback cb)
1269 NativePlayer.SetMediaStreamBufferStatusCb(Handle, streamType, cb).
1270 ThrowIfFailed("Failed to SetMediaStreamBufferStatus");
1273 private void RegisterMediaStreamSeekCallback()
1275 _mediaStreamAudioSeekCallback = (offset, _) =>
1277 Log.Debug(PlayerLog.Tag, "audio seeking offset : " + offset);
1278 MediaStreamAudioSeekingOccurred?.Invoke(this, new MediaStreamSeekingOccurredEventArgs(offset));
1280 _mediaStreamVideoSeekCallback = (offset, _) =>
1282 Log.Debug(PlayerLog.Tag, "video seeking offset : " + offset);
1283 MediaStreamVideoSeekingOccurred?.Invoke(this, new MediaStreamSeekingOccurredEventArgs(offset));
1286 RegisterMediaStreamSeekCallback(StreamType.Audio, _mediaStreamAudioSeekCallback);
1287 RegisterMediaStreamSeekCallback(StreamType.Video, _mediaStreamVideoSeekCallback);
1290 private void RegisterMediaStreamSeekCallback(StreamType streamType, NativePlayer.MediaStreamSeekCallback cb)
1292 NativePlayer.SetMediaStreamSeekCb(Handle, streamType, cb).
1293 ThrowIfFailed("Failed to SetMediaStreamSeek");
1297 #region Preparing state
1299 private int _isPreparing;
1301 private bool IsPreparing()
1303 return Interlocked.CompareExchange(ref _isPreparing, 1, 1) == 1;
1306 private void SetPreparing()
1308 Interlocked.Exchange(ref _isPreparing, 1);
1311 private void ClearPreparing()
1313 Interlocked.Exchange(ref _isPreparing, 0);