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.
19 using System.ComponentModel;
20 using System.Diagnostics;
22 using System.Runtime.InteropServices;
23 using System.Threading;
24 using System.Threading.Tasks;
26 namespace Tizen.Multimedia
28 internal static class PlayerLog
30 internal const string Tag = "Tizen.Multimedia.Player";
34 /// Provides the ability to control media playback.
37 /// The player provides functions to play a media content.
38 /// It also provides functions to adjust the configurations of the player such as playback rate, volume, looping etc.
39 /// Note that only one video player can be played at one time.
41 public partial class Player : IDisposable, IDisplayable<PlayerErrorCode>
43 private readonly PlayerHandle _handle;
46 /// Initializes a new instance of the <see cref="Player"/> class.
48 /// <since_tizen> 3 </since_tizen>
51 NativePlayer.Create(out _handle).ThrowIfFailed(null, "Failed to create player");
53 Debug.Assert(_handle != null);
59 /// Initializes a new instance of the <see cref="Player"/> class with a native handle.
60 /// The class takes care of the life cycle of the handle.
61 /// Thus, it should not be closed/destroyed in another location.
64 /// This supports the product infrastructure and is not intended to be used directly from application code.
66 [EditorBrowsable(EditorBrowsableState.Never)]
67 protected Player(IntPtr handle, Action<int, string> errorHandler)
69 // This constructor is to support TV product player.
70 // Be careful with 'handle'. It must be wrapped in safe handle, first.
71 _handle = handle != IntPtr.Zero ? new PlayerHandle(handle) :
72 throw new ArgumentException("Handle is invalid.", nameof(handle));
74 _errorHandler = errorHandler;
77 private bool _initialized;
80 /// This supports the product infrastructure and is not intended to be used directly from application code.
82 [EditorBrowsable(EditorBrowsableState.Never)]
83 protected void Initialize()
87 throw new InvalidOperationException("It has already been initialized.");
90 if (Features.IsSupported(PlayerFeatures.AudioEffect))
92 _audioEffect = new AudioEffect(this);
95 if (Features.IsSupported(PlayerFeatures.RawVideo))
97 RegisterVideoFrameDecodedCallback();
102 _displaySettings = PlayerDisplaySettings.Create(this);
107 internal void ValidatePlayerState(params PlayerState[] desiredStates)
109 Debug.Assert(desiredStates.Length > 0);
111 ValidateNotDisposed();
113 var curState = State;
114 if (curState.IsAnyOf(desiredStates))
119 throw new InvalidOperationException($"The player is not in a valid state. " +
120 $"Current State : { curState }, Valid State : { string.Join(", ", desiredStates) }.");
123 #region Dispose support
124 private bool _disposed;
127 /// Releases all resources used by the current instance.
129 /// <since_tizen> 3 </since_tizen>
130 public void Dispose()
136 /// Releases the unmanaged resources used by the <see cref="Player"/>.
138 /// <param name="disposing">
139 /// true to release both managed and unmanaged resources; false to release only unmanaged resources.
141 [EditorBrowsable(EditorBrowsableState.Never)]
142 protected virtual void Dispose(bool disposing)
146 ReplaceDisplay(null);
152 _source.DetachFrom(this);
156 Log.Error(PlayerLog.Tag, e.ToString());
169 internal void ValidateNotDisposed()
173 Log.Warn(PlayerLog.Tag, "player was disposed");
174 throw new ObjectDisposedException(nameof(Player));
178 internal bool IsDisposed => _disposed;
184 /// Gets the streaming download progress.
186 /// <returns>The <see cref="DownloadProgress"/> containing current download progress.</returns>
187 /// <remarks>The player must be in the <see cref="PlayerState.Playing"/> or <see cref="PlayerState.Paused"/> state.</remarks>
188 /// <exception cref="InvalidOperationException">
189 /// The player is not streaming.<br/>
191 /// The player is not in the valid state.
193 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
194 /// <since_tizen> 3 </since_tizen>
195 public DownloadProgress GetDownloadProgress()
197 ValidatePlayerState(PlayerState.Playing, PlayerState.Paused);
201 NativePlayer.GetStreamingDownloadProgress(Handle, out start, out current).
202 ThrowIfFailed(this, "Failed to get download progress");
204 Log.Info(PlayerLog.Tag, "get download progress : " + start + ", " + current);
206 return new DownloadProgress(start, current);
210 /// Sets the subtitle path for playback.
212 /// <remarks>Only MicroDVD/SubViewer(*.sub), SAMI(*.smi), and SubRip(*.srt) subtitle formats are supported.
213 /// <para>The mediastorage privilege(http://tizen.org/privilege/mediastorage) must be added if any files are used to play located in the internal storage.
214 /// The externalstorage privilege(http://tizen.org/privilege/externalstorage) must be added if any files are used to play located in the external storage.</para>
216 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
217 /// <exception cref="ArgumentException"><paramref name="path"/> is an empty string.</exception>
218 /// <exception cref="FileNotFoundException">The specified path does not exist.</exception>
219 /// <exception cref="ArgumentNullException"><paramref name="path"/> is null.</exception>
220 /// <since_tizen> 3 </since_tizen>
221 public void SetSubtitle(string path)
223 ValidateNotDisposed();
227 throw new ArgumentNullException(nameof(path));
230 if (path.Length == 0)
232 throw new ArgumentException("The path is empty.", nameof(path));
235 if (!File.Exists(path))
237 throw new FileNotFoundException($"The specified file does not exist.", path);
240 NativePlayer.SetSubtitlePath(Handle, path).
241 ThrowIfFailed(this, "Failed to set the subtitle path to the player");
245 /// Removes the subtitle path.
247 /// <remarks>The player must be in the <see cref="PlayerState.Idle"/> state.</remarks>
248 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
249 /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
250 /// <since_tizen> 3 </since_tizen>
251 public void ClearSubtitle()
253 ValidatePlayerState(PlayerState.Idle);
255 NativePlayer.SetSubtitlePath(Handle, null).
256 ThrowIfFailed(this, "Failed to clear the subtitle of the player");
260 /// Sets the offset for the subtitle.
262 /// <param name="offset">The value indicating a desired offset in milliseconds.</param>
263 /// <remarks>The player must be in the <see cref="PlayerState.Playing"/> or <see cref="PlayerState.Paused"/> state.</remarks>
264 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
265 /// <exception cref="InvalidOperationException">
266 /// The player is not in the valid state.<br/>
268 /// No subtitle is set.
270 /// <seealso cref="SetSubtitle(string)"/>
271 /// <since_tizen> 3 </since_tizen>
272 public void SetSubtitleOffset(int offset)
274 ValidatePlayerState(PlayerState.Playing, PlayerState.Paused);
276 var err = NativePlayer.SetSubtitlePositionOffset(Handle, offset);
278 if (err == PlayerErrorCode.FeatureNotSupported)
280 throw new InvalidOperationException("No subtitle set");
283 err.ThrowIfFailed(this, "Failed to the subtitle offset of the player");
286 private void Prepare()
288 NativePlayer.Prepare(Handle).ThrowIfFailed(this, "Failed to prepare the player");
292 /// Called when the <see cref="Prepare"/> is invoked.
294 /// <since_tizen> 3 </since_tizen>
295 protected virtual void OnPreparing()
300 /// Prepares the media player for playback, asynchronously.
302 /// <returns>A task that represents the asynchronous prepare operation.</returns>
303 /// <remarks>To prepare the player, the player must be in the <see cref="PlayerState.Idle"/> state,
304 /// and a source must be set.</remarks>
305 /// <exception cref="InvalidOperationException">No source is set.</exception>
306 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
307 /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
308 /// <since_tizen> 3 </since_tizen>
309 public virtual Task PrepareAsync()
313 throw new InvalidOperationException("No source is set.");
316 ValidatePlayerState(PlayerState.Idle);
322 return Task.Factory.StartNew(() =>
332 }, CancellationToken.None,
333 TaskCreationOptions.DenyChildAttach | TaskCreationOptions.LongRunning,
334 TaskScheduler.Default);
338 /// Unprepares the player.
341 /// The most recently used source is reset and is no longer associated with the player. Playback is no longer possible.
342 /// If you want to use the player again, you have to set a source and call <see cref="PrepareAsync"/> again.
344 /// The player must be in the <see cref="PlayerState.Ready"/>, <see cref="PlayerState.Playing"/>, or <see cref="PlayerState.Paused"/> state.
345 /// It has no effect if the player is already in the <see cref="PlayerState.Idle"/> state.
348 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
349 /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
350 /// <since_tizen> 3 </since_tizen>
351 public virtual void Unprepare()
353 if (State == PlayerState.Idle)
355 Log.Warn(PlayerLog.Tag, "idle state already");
358 ValidatePlayerState(PlayerState.Ready, PlayerState.Paused, PlayerState.Playing);
360 NativePlayer.Unprepare(Handle).ThrowIfFailed(this, "Failed to unprepare the player");
366 /// Called after the <see cref="Player"/> is unprepared.
368 /// <seealso cref="Unprepare"/>
369 /// <since_tizen> 3 </since_tizen>
370 protected virtual void OnUnprepared()
372 _source?.DetachFrom(this);
377 /// Starts or resumes playback.
380 /// The player must be in the <see cref="PlayerState.Ready"/> or <see cref="PlayerState.Paused"/> state.
381 /// It has no effect if the player is already in the <see cref="PlayerState.Playing"/> state.<br/>
383 /// Sound can be mixed with other sounds if you don't control the stream focus using <see cref="ApplyAudioStreamPolicy"/>.
385 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
386 /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
387 /// <seealso cref="PrepareAsync"/>
388 /// <seealso cref="Stop"/>
389 /// <seealso cref="Pause"/>
390 /// <seealso cref="PlaybackCompleted"/>
391 /// <seealso cref="ApplyAudioStreamPolicy"/>
392 /// <since_tizen> 3 </since_tizen>
393 public virtual void Start()
395 if (State == PlayerState.Playing)
397 Log.Warn(PlayerLog.Tag, "playing state already");
400 ValidatePlayerState(PlayerState.Ready, PlayerState.Paused);
402 NativePlayer.Start(Handle).ThrowIfFailed(this, "Failed to start the player");
406 /// Stops playing the media content.
409 /// The player must be in the <see cref="PlayerState.Playing"/> or <see cref="PlayerState.Paused"/> state.
410 /// It has no effect if the player is already in the <see cref="PlayerState.Ready"/> state.
412 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
413 /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
414 /// <seealso cref="Start"/>
415 /// <seealso cref="Pause"/>
416 /// <since_tizen> 3 </since_tizen>
417 public virtual void Stop()
419 if (State == PlayerState.Ready)
421 Log.Warn(PlayerLog.Tag, "ready state already");
424 ValidatePlayerState(PlayerState.Paused, PlayerState.Playing);
426 NativePlayer.Stop(Handle).ThrowIfFailed(this, "Failed to stop the player");
430 /// Pauses the player.
433 /// The player must be in the <see cref="PlayerState.Playing"/> state.
434 /// It has no effect if the player is already in the <see cref="PlayerState.Paused"/> state.
436 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
437 /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
438 /// <seealso cref="Start"/>
439 /// <since_tizen> 3 </since_tizen>
440 public virtual void Pause()
442 if (State == PlayerState.Paused)
444 Log.Warn(PlayerLog.Tag, "pause state already");
448 ValidatePlayerState(PlayerState.Playing);
450 NativePlayer.Pause(Handle).ThrowIfFailed(this, "Failed to pause the player");
453 private MediaSource _source;
456 /// Determines whether MediaSource has set.
457 /// This supports the product infrastructure and is not intended to be used directly from application code.
459 [EditorBrowsable(EditorBrowsableState.Never)]
460 protected bool HasSource => _source != null;
463 /// Sets a media source for the player.
465 /// <param name="source">A <see cref="MediaSource"/> that specifies the source for playback.</param>
466 /// <remarks>The player must be in the <see cref="PlayerState.Idle"/> state.</remarks>
467 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
468 /// <exception cref="InvalidOperationException">
469 /// The player is not in the valid state.<br/>
471 /// It is not able to assign the source to the player.
473 /// <seealso cref="PrepareAsync"/>
474 /// <since_tizen> 3 </since_tizen>
475 public void SetSource(MediaSource source)
477 ValidatePlayerState(PlayerState.Idle);
481 source.AttachTo(this);
486 _source.DetachFrom(this);
493 /// Captures a video frame, asynchronously.
495 /// <returns>A task that represents the asynchronous capture operation.</returns>
496 /// <feature>http://tizen.org/feature/multimedia.raw_video</feature>
497 /// <remarks>The player must be in the <see cref="PlayerState.Playing"/> or <see cref="PlayerState.Paused"/> state.</remarks>
498 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
499 /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
500 /// <exception cref="NotSupportedException">The required feature is not supported.</exception>
501 /// <since_tizen> 3 </since_tizen>
502 public async Task<CapturedFrame> CaptureVideoAsync()
504 ValidationUtil.ValidateFeatureSupported(PlayerFeatures.RawVideo);
506 ValidatePlayerState(PlayerState.Playing, PlayerState.Paused);
508 TaskCompletionSource<CapturedFrame> t = new TaskCompletionSource<CapturedFrame>();
510 NativePlayer.VideoCaptureCallback cb = (data, width, height, size, _) =>
512 Debug.Assert(size <= int.MaxValue);
514 byte[] buf = new byte[size];
515 Marshal.Copy(data, buf, 0, (int)size);
517 t.TrySetResult(new CapturedFrame(buf, width, height));
520 using (var cbKeeper = ObjectKeeper.Get(cb))
522 NativePlayer.CaptureVideo(Handle, cb)
523 .ThrowIfFailed(this, "Failed to capture the video");
530 /// Gets the play position in milliseconds.
532 /// <remarks>The player must be in the <see cref="PlayerState.Ready"/>, <see cref="PlayerState.Playing"/>,
533 /// or <see cref="PlayerState.Paused"/> state.</remarks>
534 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
535 /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
536 /// <seealso cref="SetPlayPositionAsync(int, bool)"/>
537 /// <since_tizen> 3 </since_tizen>
538 public int GetPlayPosition()
540 ValidatePlayerState(PlayerState.Ready, PlayerState.Paused, PlayerState.Playing);
542 int playPosition = 0;
544 NativePlayer.GetPlayPosition(Handle, out playPosition).
545 ThrowIfFailed(this, "Failed to get the play position of the player");
547 Log.Info(PlayerLog.Tag, "get play position : " + playPosition);
552 private void SetPlayPosition(int milliseconds, bool accurate,
553 NativePlayer.SeekCompletedCallback cb)
555 var ret = NativePlayer.SetPlayPosition(Handle, milliseconds, accurate, cb, IntPtr.Zero);
557 //Note that we assume invalid param error is returned only when the position value is invalid.
558 if (ret == PlayerErrorCode.InvalidArgument)
560 throw new ArgumentOutOfRangeException(nameof(milliseconds), milliseconds,
561 "The position is not valid.");
563 if (ret != PlayerErrorCode.None)
565 Log.Error(PlayerLog.Tag, "Failed to set play position, " + (PlayerError)ret);
567 ret.ThrowIfFailed(this, "Failed to set play position");
571 /// Sets the seek position for playback, asynchronously.
573 /// <param name="position">The value indicating a desired position in milliseconds.</param>
574 /// <param name="accurate">The value indicating whether the operation performs with accuracy.</param>
576 /// <para>The player must be in the <see cref="PlayerState.Ready"/>, <see cref="PlayerState.Playing"/>,
577 /// or <see cref="PlayerState.Paused"/> state.</para>
578 /// <para>If the <paramref name="accurate"/> is true, the play position will be adjusted as the specified <paramref name="position"/> value,
579 /// but this might be considerably slow. If false, the play position will be a nearest keyframe position.</para>
581 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
582 /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
583 /// <exception cref="ArgumentOutOfRangeException">The specified position is not valid.</exception>
584 /// <seealso cref="GetPlayPosition"/>
585 /// <since_tizen> 3 </since_tizen>
586 public async Task SetPlayPositionAsync(int position, bool accurate)
588 ValidatePlayerState(PlayerState.Ready, PlayerState.Playing, PlayerState.Paused);
590 var taskCompletionSource = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
592 bool immediateResult = _source is MediaStreamSource;
594 NativePlayer.SeekCompletedCallback cb = _ => taskCompletionSource.TrySetResult(true);
596 using (var cbKeeper = ObjectKeeper.Get(cb))
598 SetPlayPosition(position, accurate, cb);
601 taskCompletionSource.TrySetResult(true);
604 await taskCompletionSource.Task;
609 /// Sets the playback rate.
611 /// <param name="rate">The value for the playback rate. Valid range is -5.0 to 5.0, inclusive.</param>
613 /// <para>The player must be in the <see cref="PlayerState.Ready"/>, <see cref="PlayerState.Playing"/>,
614 /// or <see cref="PlayerState.Paused"/> state.</para>
615 /// <para>The sound will be muted, when the playback rate is under 0.0 or over 2.0.</para>
617 /// <exception cref="ObjectDisposedException">The player has already been disposed of.</exception>
618 /// <exception cref="InvalidOperationException">
619 /// The player is not in the valid state.<br/>
621 /// Streaming playback.
623 /// <exception cref="ArgumentOutOfRangeException">
624 /// <paramref name="rate"/> is less than -5.0.<br/>
626 /// <paramref name="rate"/> is greater than 5.0.<br/>
628 /// <paramref name="rate"/> is zero.
630 /// <since_tizen> 3 </since_tizen>
631 public void SetPlaybackRate(float rate)
633 if (rate < -5.0F || 5.0F < rate || rate == 0.0F)
635 throw new ArgumentOutOfRangeException(nameof(rate), rate, "Valid range is -5.0 to 5.0 (except 0.0)");
638 ValidatePlayerState(PlayerState.Ready, PlayerState.Playing, PlayerState.Paused);
640 NativePlayer.SetPlaybackRate(Handle, rate).ThrowIfFailed(this, "Failed to set the playback rate.");
644 /// Applies the audio stream policy.
646 /// <param name="policy">The <see cref="AudioStreamPolicy"/> to apply.</param>
648 /// The player must be in the <see cref="PlayerState.Idle"/> state.<br/>
650 /// <see cref="Player"/> does not support all <see cref="AudioStreamType"/>.<br/>
651 /// Supported types are <see cref="AudioStreamType.Media"/>, <see cref="AudioStreamType.System"/>,
652 /// <see cref="AudioStreamType.Alarm"/>, <see cref="AudioStreamType.Notification"/>,
653 /// <see cref="AudioStreamType.Emergency"/>, <see cref="AudioStreamType.VoiceInformation"/>,
654 /// <see cref="AudioStreamType.RingtoneVoip"/> and <see cref="AudioStreamType.MediaExternalOnly"/>.
656 /// <exception cref="ObjectDisposedException">
657 /// The player has already been disposed of.<br/>
659 /// <paramref name="policy"/> has already been disposed of.
661 /// <exception cref="InvalidOperationException">The player is not in the valid state.</exception>
662 /// <exception cref="ArgumentNullException"><paramref name="policy"/> is null.</exception>
663 /// <exception cref="NotSupportedException">
664 /// The required feature is not supported.<br/>
666 /// <see cref="AudioStreamType"/> of <paramref name="policy"/> is not supported on the current platform.
668 /// <seealso cref="AudioStreamPolicy"/>
669 /// <feature>http://tizen.org/feature/multimedia.player.stream_info</feature>
670 /// <since_tizen> 3 </since_tizen>
671 public void ApplyAudioStreamPolicy(AudioStreamPolicy policy)
673 ValidationUtil.ValidateFeatureSupported("http://tizen.org/feature/multimedia.player.stream_info");
677 throw new ArgumentNullException(nameof(policy));
680 ValidatePlayerState(PlayerState.Idle);
682 var ret = NativePlayer.SetAudioPolicyInfo(Handle, policy.Handle);
684 if (ret == PlayerErrorCode.InvalidArgument)
686 throw new NotSupportedException("The specified policy is not supported on the current system.");
689 ret.ThrowIfFailed(this, "Failed to set the audio stream policy to the player");
693 #region Preparing state
695 private int _isPreparing;
697 private bool IsPreparing()
699 return Interlocked.CompareExchange(ref _isPreparing, 1, 1) == 1;
703 /// This supports the product infrastructure and is not intended to be used directly from application code.
705 [EditorBrowsable(EditorBrowsableState.Never)]
706 protected void SetPreparing()
708 Interlocked.Exchange(ref _isPreparing, 1);
712 /// This supports the product infrastructure and is not intended to be used directly from application code.
714 [EditorBrowsable(EditorBrowsableState.Never)]
715 protected void ClearPreparing()
717 Interlocked.Exchange(ref _isPreparing, 0);