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)
358 Log.Info(PlayerLog.Tag, "set display to none");
359 return NativePlayer.SetDisplay(Handle, DisplayType.None, IntPtr.Zero);
362 return display.ApplyTo(this);
365 private void ReplaceDisplay(Display newDisplay)
367 _display?.SetOwner(null);
368 _display = newDisplay;
369 _display?.SetOwner(this);
373 /// Gets or sets the display.
375 /// <value>A <see cref="Multimedia.Display"/> that specifies the display.</value>
376 /// <remarks>The player must be in the <see cref="PlayerState.Idle"/> state.</remarks>
377 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
378 /// <exception cref="ArgumentException">The value has already been assigned to another player.</exception>
379 /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
380 public Display Display
388 ValidatePlayerState(PlayerState.Idle);
390 if (value?.Owner != null)
392 if (ReferenceEquals(this, value.Owner))
397 throw new ArgumentException("The display has already been assigned to another.");
399 SetDisplay(value).ThrowIfFailed("Failed to set the display to the player");
401 ReplaceDisplay(value);
405 PlayerErrorCode IDisplayable<PlayerErrorCode>.ApplyEvasDisplay(DisplayType type, ElmSharp.EvasObject evasObject)
407 Debug.Assert(IsDisposed == false);
409 Debug.Assert(Enum.IsDefined(typeof(DisplayType), type));
411 return NativePlayer.SetDisplay(Handle, type, evasObject);
415 private PlayerTrackInfo _audioTrack;
418 /// Gets the track info for audio.
420 /// <value>A <see cref="PlayerTrackInfo"/> for audio.</value>
421 public PlayerTrackInfo AudioTrackInfo
425 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
426 if (_audioTrack == null)
428 _audioTrack = new PlayerTrackInfo(this, StreamType.Audio);
434 private PlayerTrackInfo _subtitleTrackInfo;
437 /// Gets the track info for subtitle.
439 /// <value>A <see cref="PlayerTrackInfo"/> for subtitle.</value>
440 public PlayerTrackInfo SubtitleTrackInfo
444 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
445 if (_subtitleTrackInfo == null)
447 _subtitleTrackInfo = new PlayerTrackInfo(this, StreamType.Text);
449 return _subtitleTrackInfo;
453 private StreamInfo _streamInfo;
456 /// Gets the stream information.
458 /// <value>A <see cref="StreamInfo"/> for this player.</value>
459 public StreamInfo StreamInfo
463 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
464 if (_streamInfo == null)
466 _streamInfo = new StreamInfo(this);
472 private readonly AudioEffect _audioEffect;
475 /// Gets the audio effect.
477 /// <feature>http://tizen.org/feature/multimedia.custom_audio_effect</feature>
478 /// <exception cref="NotSupportedException">The required feature is not supported.</exception>
479 public AudioEffect AudioEffect
483 if (_audioEffect == null)
485 throw new NotSupportedException($"The feature({Features.AudioEffect}) is not supported.");
494 #region Dispose support
495 private bool _disposed;
497 public void Dispose()
499 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
503 private void Dispose(bool disposing)
507 ReplaceDisplay(null);
513 _source.DetachFrom(this);
517 Log.Error(PlayerLog.Tag, e.ToString());
530 internal void ValidateNotDisposed()
534 Log.Warn(PlayerLog.Tag, "player was disposed");
535 throw new ObjectDisposedException(nameof(Player));
539 internal bool IsDisposed => _disposed;
545 /// Gets or sets the mute state.
547 /// <value>true if the player is muted; otherwise, false.</value>
548 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
553 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
556 NativePlayer.IsMuted(Handle, out value).ThrowIfFailed("Failed to get the mute state of the player");
558 Log.Info(PlayerLog.Tag, "get mute : " + value);
564 NativePlayer.SetMute(Handle, value).ThrowIfFailed("Failed to set the mute state of the player");
569 /// Get Streaming download Progress.
571 /// <remarks>The player must be in the <see cref="PlayerState.Playing"/> or <see cref="PlayerState.Paused"/> state.</remarks>
572 /// <exception cref="InvalidOperationException">
573 /// The player is not streaming.\n
575 /// The player is not in the valid state.
577 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
578 public DownloadProgress GetDownloadProgress()
580 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
581 ValidatePlayerState(PlayerState.Playing, PlayerState.Paused);
585 NativePlayer.GetStreamingDownloadProgress(Handle, out start, out current).
586 ThrowIfFailed("Failed to get download progress");
588 Log.Info(PlayerLog.Tag, "get download progress : " + start + ", " + current);
590 return new DownloadProgress(start, current);
595 /// Gets or sets the current volume.
597 /// <remarks>Valid volume range is from 0 to 1.0, inclusive.</remarks>
598 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
599 /// <exception cref="ArgumentOutOfRangeException">
600 /// <paramref name="value"/> is less than zero.\n
602 /// <paramref name="value"/> is greater than 1.0.
609 NativePlayer.GetVolume(Handle, out value, out value).
610 ThrowIfFailed("Failed to get the volume of the player");
615 if (value < 0F || 1.0F < value)
617 throw new ArgumentOutOfRangeException(nameof(value), value,
618 $"Valid volume range is 0 <= value <= 1.0, but got { value }.");
621 NativePlayer.SetVolume(Handle, value, value).
622 ThrowIfFailed("Failed to set the volume of the player");
629 /// Sets the subtitle path for playback.
631 /// <remarks>Only MicroDVD/SubViewer(*.sub), SAMI(*.smi), and SubRip(*.srt) subtitle formats are supported.
632 /// <para>The mediastorage privilege(http://tizen.org/privilege/mediastorage) must be added if any files are used to play located in the internal storage.
633 /// The externalstorage privilege(http://tizen.org/privilege/externalstorage) must be added if any files are used to play located in the external storage.</para>
635 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
636 /// <exception cref="ArgumentException"><paramref name="path"/> is an empty string.</exception>
637 /// <exception cref="FileNotFoundException">The specified path does not exist.</exception>
638 /// <exception cref="ArgumentNullException">The path is null.</exception>
639 public void SetSubtitle(string path)
641 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
642 ValidateNotDisposed();
646 throw new ArgumentNullException(nameof(path));
649 if (path.Length == 0)
651 throw new ArgumentException("The path is empty.", nameof(path));
654 if (!File.Exists(path))
656 throw new FileNotFoundException($"The specified file does not exist.", path);
659 NativePlayer.SetSubtitlePath(Handle, path).
660 ThrowIfFailed("Failed to set the subtitle path to the player");
662 Log.Debug(PlayerLog.Tag, PlayerLog.Leave);
666 /// Removes the subtitle path.
668 /// <remarks>The player must be in the <see cref="PlayerState.Idle"/> state.</remarks>
669 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
670 /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
671 public void ClearSubtitle()
673 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
674 ValidatePlayerState(PlayerState.Idle);
676 NativePlayer.SetSubtitlePath(Handle, null).
677 ThrowIfFailed("Failed to clear the subtitle of the player");
678 Log.Debug(PlayerLog.Tag, PlayerLog.Leave);
682 /// Sets the offset for the subtitle.
684 /// <remarks>The player must be in the <see cref="PlayerState.Playing"/> or <see cref="PlayerState.Paused"/> state.</remarks>
685 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
686 /// <exception cref="InvalidOperationException">
687 /// The player is not in the valid state.\n
689 /// No subtitle is set.
691 /// <seealso cref="SetSubtitle(string)"/>
692 public void SetSubtitleOffset(int offset)
694 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
695 ValidatePlayerState(PlayerState.Playing, PlayerState.Paused);
697 var err = NativePlayer.SetSubtitlePositionOffset(Handle, offset);
699 if (err == PlayerErrorCode.FeatureNotSupported)
701 throw new InvalidOperationException("No subtitle set");
704 err.ThrowIfFailed("Failed to the subtitle offset of the player");
705 Log.Debug(PlayerLog.Tag, PlayerLog.Leave);
708 private void Prepare()
710 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
711 NativePlayer.Prepare(Handle).ThrowIfFailed("Failed to prepare the player");
714 protected virtual void OnPreparing()
720 /// Prepares the media player for playback, asynchronously.
722 /// <remarks>To prepare the player, the player must be in the <see cref="PlayerState.Idle"/> state,
723 /// and a source must be set.</remarks>
724 /// <exception cref="InvalidOperationException">No source is set.</exception>
725 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
726 /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
727 public virtual Task PrepareAsync()
729 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
732 throw new InvalidOperationException("No source is set.");
735 ValidatePlayerState(PlayerState.Idle);
739 var completionSource = new TaskCompletionSource<bool>();
749 completionSource.SetResult(true);
754 completionSource.TrySetException(e);
757 Log.Debug(PlayerLog.Tag, PlayerLog.Leave);
759 return completionSource.Task;
763 /// Unprepares the player.
766 /// The most recently used source is reset and no longer associated with the player. Playback is no longer possible.
767 /// If you want to use the player again, you have to set a source and call <see cref="PrepareAsync"/> again.
769 /// The player must be in the <see cref="PlayerState.Ready"/>, <see cref="PlayerState.Playing"/> or <see cref="PlayerState.Paused"/> state.
770 /// It has no effect if the player is already in the <see cref="PlayerState.Idle"/> state.
773 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
774 /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
775 public virtual void Unprepare()
777 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
778 if (State == PlayerState.Idle)
780 Log.Warn(PlayerLog.Tag, "idle state already");
783 ValidatePlayerState(PlayerState.Ready, PlayerState.Paused, PlayerState.Playing);
785 NativePlayer.Unprepare(Handle).ThrowIfFailed("Failed to unprepare the player");
790 protected virtual void OnUnprepared()
792 _source?.DetachFrom(this);
796 //TODO remarks needs to be updated. see the native reference.
798 /// Starts or resumes playback.
801 /// The player must be in the <see cref="PlayerState.Ready"/> or <see cref="PlayerState.Paused"/> state.
802 /// It has no effect if the player is already in the <see cref="PlayerState.Playing"/> state.
804 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
805 /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
806 /// <seealso cref="PrepareAsync"/>
807 /// <seealso cref="Stop"/>
808 /// <seealso cref="Pause"/>
809 /// <seealso cref="PlaybackCompleted"/>
810 public virtual void Start()
812 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
813 if (State == PlayerState.Playing)
815 Log.Warn(PlayerLog.Tag, "playing state already");
818 ValidatePlayerState(PlayerState.Ready, PlayerState.Paused);
820 NativePlayer.Start(Handle).ThrowIfFailed("Failed to start the player");
821 Log.Debug(PlayerLog.Tag, PlayerLog.Leave);
825 /// Stops playing media content.
828 /// The player must be in the <see cref="PlayerState.Playing"/> or <see cref="PlayerState.Paused"/> state.
829 /// It has no effect if the player is already in the <see cref="PlayerState.Ready"/> state.
831 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
832 /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
833 /// <seealso cref="Start"/>
834 /// <seealso cref="Pause"/>
835 public virtual void Stop()
837 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
838 if (State == PlayerState.Ready)
840 Log.Warn(PlayerLog.Tag, "ready state already");
843 ValidatePlayerState(PlayerState.Paused, PlayerState.Playing);
845 NativePlayer.Stop(Handle).ThrowIfFailed("Failed to stop the player");
846 Log.Debug(PlayerLog.Tag, PlayerLog.Leave);
850 /// Pauses the player.
853 /// The player must be in the <see cref="PlayerState.Playing"/> state.
854 /// It has no effect if the player is already in the <see cref="PlayerState.Paused"/> state.
856 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
857 /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
858 /// <seealso cref="Start"/>
859 public virtual void Pause()
861 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
862 if (State == PlayerState.Paused)
864 Log.Warn(PlayerLog.Tag, "pause state already");
868 ValidatePlayerState(PlayerState.Playing);
870 NativePlayer.Pause(Handle).ThrowIfFailed("Failed to pause the player");
871 Log.Debug(PlayerLog.Tag, PlayerLog.Leave);
874 private MediaSource _source;
877 /// Sets a media source for the player.
879 /// <param name="source">A <see cref="MediaSource"/> that specifies the source for playback.</param>
880 /// <remarks>The player must be in the <see cref="PlayerState.Idle"/> state.</remarks>
881 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
882 /// <exception cref="InvalidOperationException">
883 /// The player is not in the valid state.\n
885 /// It is not able to assign the source to the player.
887 /// <seealso cref="PrepareAsync"/>
888 public void SetSource(MediaSource source)
890 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
891 ValidatePlayerState(PlayerState.Idle);
895 source.AttachTo(this);
900 _source.DetachFrom(this);
904 Log.Debug(PlayerLog.Tag, PlayerLog.Leave);
908 /// Captures a video frame asynchronously.
910 /// <feature>http://tizen.org/feature/multimedia.raw_video</feature>
911 /// <remarks>The player must be in the <see cref="PlayerState.Playing"/> or <see cref="PlayerState.Paused"/> state.</remarks>
912 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
913 /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
914 /// <exception cref="NotSupportedException">The required feature is not supported.</exception>
915 public async Task<CapturedFrame> CaptureVideoAsync()
917 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
919 ValidationUtil.ValidateFeatureSupported(Features.RawVideo);
921 ValidatePlayerState(PlayerState.Playing, PlayerState.Paused);
923 TaskCompletionSource<CapturedFrame> t = new TaskCompletionSource<CapturedFrame>();
925 NativePlayer.VideoCaptureCallback cb = (data, width, height, size, _) =>
927 Debug.Assert(size <= int.MaxValue);
929 byte[] buf = new byte[size];
930 Marshal.Copy(data, buf, 0, (int)size);
932 t.TrySetResult(new CapturedFrame(buf, width, height));
935 using (var cbKeeper = ObjectKeeper.Get(cb))
937 NativePlayer.CaptureVideo(Handle, cb)
938 .ThrowIfFailed("Failed to capture the video");
945 /// Gets the play position in milliseconds.
947 /// <remarks>The player must be in the <see cref="PlayerState.Ready"/>, <see cref="PlayerState.Playing"/> or <see cref="PlayerState.Paused"/> state.</remarks>
948 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
949 /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
950 /// <seealso cref="SetPlayPositionAsync(int, bool)"/>
951 public int GetPlayPosition()
953 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
954 ValidatePlayerState(PlayerState.Ready, PlayerState.Paused, PlayerState.Playing);
956 int playPosition = 0;
958 NativePlayer.GetPlayPosition(Handle, out playPosition).
959 ThrowIfFailed("Failed to get the play position of the player");
961 Log.Info(PlayerLog.Tag, "get play position : " + playPosition);
966 private void SetPlayPosition(int milliseconds, bool accurate,
967 NativePlayer.SeekCompletedCallback cb)
969 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
970 var ret = NativePlayer.SetPlayPosition(Handle, milliseconds, accurate, cb, IntPtr.Zero);
972 //Note that we assume invalid param error is returned only when the position value is invalid.
973 if (ret == PlayerErrorCode.InvalidArgument)
975 throw new ArgumentOutOfRangeException(nameof(milliseconds), milliseconds,
976 "The position is not valid.");
978 if (ret != PlayerErrorCode.None)
980 Log.Error(PlayerLog.Tag, "Failed to set play position, " + (PlayerError)ret);
982 ret.ThrowIfFailed("Failed to set play position");
986 /// Sets the seek position for playback, asynchronously.
988 /// <param name="position">The value indicating a desired position in milliseconds.</param>
989 /// <param name="accurate">The value indicating whether the operation performs with accuracy.</param>
991 /// <para>The player must be in the <see cref="PlayerState.Ready"/>, <see cref="PlayerState.Playing"/> or <see cref="PlayerState.Paused"/> state.</para>
992 /// <para>If the <paramref name="accurate"/> is true, the play position will be adjusted as the specified <paramref name="position"/> value,
993 /// but this might be considerably slow. If false, the play position will be a nearest keyframe position.</para>
995 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
996 /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
997 /// <exception cref="ArgumentOutOfRangeException">The specified position is not valid.</exception>
998 /// <seealso cref="GetPlayPosition"/>
999 public async Task SetPlayPositionAsync(int position, bool accurate)
1001 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
1002 ValidatePlayerState(PlayerState.Ready, PlayerState.Playing, PlayerState.Paused);
1004 var taskCompletionSource = new TaskCompletionSource<bool>();
1006 bool immediateResult = _source is MediaStreamSource;
1008 NativePlayer.SeekCompletedCallback cb = _ => taskCompletionSource.TrySetResult(true);
1010 using (var cbKeeper = ObjectKeeper.Get(cb))
1012 SetPlayPosition(position, accurate, cb);
1013 if (immediateResult)
1015 taskCompletionSource.TrySetResult(true);
1018 await taskCompletionSource.Task;
1021 Log.Debug(PlayerLog.Tag, PlayerLog.Leave);
1025 /// Sets playback rate.
1027 /// <param name="rate">The value for the playback rate. Valid range is -5.0 to 5.0, inclusive.</param>
1029 /// <para>The player must be in the <see cref="PlayerState.Ready"/>, <see cref="PlayerState.Playing"/> or <see cref="PlayerState.Paused"/> state.</para>
1030 /// <para>The sound will be muted, when the playback rate is under 0.0 or over 2.0.</para>
1032 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
1033 /// <exception cref="InvalidOperationException">
1034 /// The player is not in the valid state.\n
1036 /// Streaming playback.
1038 /// <exception cref="ArgumentOutOfRangeException">
1039 /// <paramref name="rate"/> is less than 5.0.\n
1041 /// <paramref name="rate"/> is greater than 5.0.\n
1043 /// <paramref name="rate"/> is zero.
1045 public void SetPlaybackRate(float rate)
1047 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
1048 if (rate < -5.0F || 5.0F < rate || rate == 0.0F)
1050 throw new ArgumentOutOfRangeException(nameof(rate), rate, "Valid range is -5.0 to 5.0 (except 0.0)");
1053 ValidatePlayerState(PlayerState.Ready, PlayerState.Playing, PlayerState.Paused);
1055 NativePlayer.SetPlaybackRate(Handle, rate).ThrowIfFailed("Failed to set the playback rate.");
1056 Log.Debug(PlayerLog.Tag, PlayerLog.Leave);
1060 /// Applies the audio stream policy.
1062 /// <param name="policy">The <see cref="AudioStreamPolicy"/> to apply.</param>
1063 /// <remarks>The player must be in the <see cref="PlayerState.Idle"/> state.</remarks>
1064 /// <exception cref="ObjectDisposedException">
1065 /// The player has already been disposed of.\n
1067 /// <paramref name="policy"/> has already been disposed of.
1069 /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
1070 /// <exception cref="ArgumentNullException"><paramref name="policy"/> is null.</exception>
1071 public void ApplyAudioStreamPolicy(AudioStreamPolicy policy)
1073 Log.Debug(PlayerLog.Tag, PlayerLog.Enter);
1076 throw new ArgumentNullException(nameof(policy));
1079 if (policy.Handle == IntPtr.Zero)
1081 throw new ObjectDisposedException(nameof(policy));
1084 ValidatePlayerState(PlayerState.Idle);
1086 NativePlayer.SetAudioPolicyInfo(Handle, policy.Handle).
1087 ThrowIfFailed("Failed to set the audio stream policy to the player");
1091 #region Callback registrations
1092 private void RegisterSubtitleUpdatedCallback()
1094 _subtitleUpdatedCallback = (duration, text, _) =>
1096 Log.Debug(PlayerLog.Tag, "duration : " + duration + ", text : " + text);
1097 SubtitleUpdated?.Invoke(this, new SubtitleUpdatedEventArgs(duration, text));
1100 NativePlayer.SetSubtitleUpdatedCb(Handle, _subtitleUpdatedCallback).
1101 ThrowIfFailed("Failed to initialize the player");
1104 private void RegisterPlaybackCompletedCallback()
1106 _playbackCompletedCallback = _ =>
1108 Log.Debug(PlayerLog.Tag, "completed callback");
1109 PlaybackCompleted?.Invoke(this, EventArgs.Empty);
1111 NativePlayer.SetCompletedCb(Handle, _playbackCompletedCallback).
1112 ThrowIfFailed("Failed to set PlaybackCompleted");
1115 private void RegisterPlaybackInterruptedCallback()
1117 _playbackInterruptedCallback = (code, _) =>
1119 if (!Enum.IsDefined(typeof(PlaybackInterruptionReason), code))
1124 if (code == PlaybackInterruptionReason.ResourceConflict)
1129 Log.Warn(PlayerLog.Tag, "interrupted reason : " + code);
1130 PlaybackInterrupted?.Invoke(this, new PlaybackInterruptedEventArgs(code));
1133 NativePlayer.SetInterruptedCb(Handle, _playbackInterruptedCallback).
1134 ThrowIfFailed("Failed to set PlaybackInterrupted");
1137 private void RegisterErrorOccurredCallback()
1139 _playbackErrorCallback = (code, _) =>
1141 //TODO handle service disconnected error.
1142 Log.Warn(PlayerLog.Tag, "error code : " + code);
1143 ErrorOccurred?.Invoke(this, new PlayerErrorOccurredEventArgs((PlayerError)code));
1146 NativePlayer.SetErrorCb(Handle, _playbackErrorCallback).
1147 ThrowIfFailed("Failed to set PlaybackError");
1150 #region VideoFrameDecoded event
1152 private EventHandler<VideoFrameDecodedEventArgs> _videoFrameDecoded;
1154 private NativePlayer.VideoFrameDecodedCallback _videoFrameDecodedCallback;
1157 /// Occurs when a video frame is decoded.
1160 /// <para>The event handler will be executed on an internal thread.</para>
1161 /// <para>The <see cref="VideoFrameDecodedEventArgs.Packet"/> in event args should be disposed after use.</para>
1163 /// <feature>http://tizen.org/feature/multimedia.raw_video</feature>
1164 /// <exception cref="NotSupportedException">The required feature is not supported.</exception>
1165 /// <seealso cref="VideoFrameDecodedEventArgs.Packet"/>
1166 public event EventHandler<VideoFrameDecodedEventArgs> VideoFrameDecoded
1170 ValidationUtil.ValidateFeatureSupported(Features.RawVideo);
1172 _videoFrameDecoded += value;
1176 ValidationUtil.ValidateFeatureSupported(Features.RawVideo);
1178 _videoFrameDecoded -= value;
1182 private void RegisterVideoFrameDecodedCallback()
1184 _videoFrameDecodedCallback = (packetHandle, _) =>
1186 var handler = _videoFrameDecoded;
1187 if (handler != null)
1189 Log.Debug(PlayerLog.Tag, "packet : " + packetHandle);
1190 handler.Invoke(this,
1191 new VideoFrameDecodedEventArgs(MediaPacket.From(packetHandle)));
1195 MediaPacket.From(packetHandle).Dispose();
1199 NativePlayer.SetVideoFrameDecodedCb(Handle, _videoFrameDecodedCallback).
1200 ThrowIfFailed("Failed to register the VideoFrameDecoded");
1204 private void RegisterVideoStreamChangedCallback()
1206 ValidatePlayerState(PlayerState.Idle);
1208 _videoStreamChangedCallback = (width, height, fps, bitrate, _) =>
1210 Log.Debug(PlayerLog.Tag, "height : " + height + ", width : " + width
1211 + ", fps : " + fps + ", bitrate : " + bitrate);
1213 VideoStreamChanged?.Invoke(this, new VideoStreamChangedEventArgs(height, width, fps, bitrate));
1216 NativePlayer.SetVideoStreamChangedCb(Handle, _videoStreamChangedCallback).
1217 ThrowIfFailed("Failed to set the video stream changed callback");
1220 private void RegisterBufferingCallback()
1222 _bufferingProgressCallback = (percent, _) =>
1224 Log.Debug(PlayerLog.Tag, $"Buffering callback with percent { percent }");
1225 BufferingProgressChanged?.Invoke(this, new BufferingProgressChangedEventArgs(percent));
1228 NativePlayer.SetBufferingCb(Handle, _bufferingProgressCallback).
1229 ThrowIfFailed("Failed to set BufferingProgress");
1232 private void RegisterMediaStreamBufferStatusCallback()
1234 _mediaStreamAudioBufferStatusChangedCallback = (status, _) =>
1236 Debug.Assert(Enum.IsDefined(typeof(MediaStreamBufferStatus), status));
1237 Log.Debug(PlayerLog.Tag, "audio buffer status : " + status);
1238 MediaStreamAudioBufferStatusChanged?.Invoke(this,
1239 new MediaStreamBufferStatusChangedEventArgs(status));
1241 _mediaStreamVideoBufferStatusChangedCallback = (status, _) =>
1243 Debug.Assert(Enum.IsDefined(typeof(MediaStreamBufferStatus), status));
1244 Log.Debug(PlayerLog.Tag, "video buffer status : " + status);
1245 MediaStreamVideoBufferStatusChanged?.Invoke(this,
1246 new MediaStreamBufferStatusChangedEventArgs(status));
1249 RegisterMediaStreamBufferStatusCallback(StreamType.Audio, _mediaStreamAudioBufferStatusChangedCallback);
1250 RegisterMediaStreamBufferStatusCallback(StreamType.Video, _mediaStreamVideoBufferStatusChangedCallback);
1253 private void RegisterMediaStreamBufferStatusCallback(StreamType streamType,
1254 NativePlayer.MediaStreamBufferStatusCallback cb)
1256 NativePlayer.SetMediaStreamBufferStatusCb(Handle, streamType, cb).
1257 ThrowIfFailed("Failed to SetMediaStreamBufferStatus");
1260 private void RegisterMediaStreamSeekCallback()
1262 _mediaStreamAudioSeekCallback = (offset, _) =>
1264 Log.Debug(PlayerLog.Tag, "audio seeking offset : " + offset);
1265 MediaStreamAudioSeekingOccurred?.Invoke(this, new MediaStreamSeekingOccurredEventArgs(offset));
1267 _mediaStreamVideoSeekCallback = (offset, _) =>
1269 Log.Debug(PlayerLog.Tag, "video seeking offset : " + offset);
1270 MediaStreamVideoSeekingOccurred?.Invoke(this, new MediaStreamSeekingOccurredEventArgs(offset));
1273 RegisterMediaStreamSeekCallback(StreamType.Audio, _mediaStreamAudioSeekCallback);
1274 RegisterMediaStreamSeekCallback(StreamType.Video, _mediaStreamVideoSeekCallback);
1277 private void RegisterMediaStreamSeekCallback(StreamType streamType, NativePlayer.MediaStreamSeekCallback cb)
1279 NativePlayer.SetMediaStreamSeekCb(Handle, streamType, cb).
1280 ThrowIfFailed("Failed to SetMediaStreamSeek");
1284 #region Preparing state
1286 private int _isPreparing;
1288 private bool IsPreparing()
1290 return Interlocked.CompareExchange(ref _isPreparing, 1, 1) == 1;
1293 private void SetPreparing()
1295 Interlocked.Exchange(ref _isPreparing, 1);
1298 private void ClearPreparing()
1300 Interlocked.Exchange(ref _isPreparing, 0);