/* * Copyright (c) 2016 Samsung Electronics Co., Ltd All Rights Reserved * * Licensed under the Apache License, Version 2.0 (the License); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an AS IS BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ using System; using System.Threading.Tasks; using System.Runtime.InteropServices; using System.Diagnostics; using System.IO; using System.Threading; using static Interop; namespace Tizen.Multimedia { static internal class PlayerLog { internal const string Tag = "Tizen.Multimedia.Player"; internal const string Enter = "[ENTER]"; internal const string Leave = "[LEAVE]"; } /// /// Provides the ability to control media playback. /// /// /// The Player provides functions to play a media content. /// It also provides functions to adjust the configurations of the player such as playback rate, volume, looping etc. /// Note that only one video player can be played at one time. /// public class Player : IDisposable, IDisplayable { private PlayerHandle _handle; /// /// Occurs when playback of a media is finished. /// public event EventHandler PlaybackCompleted; private NativePlayer.PlaybackCompletedCallback _playbackCompletedCallback; /// /// Occurs when playback of a media is interrupted. /// public event EventHandler PlaybackInterrupted; private NativePlayer.PlaybackInterruptedCallback _playbackInterruptedCallback; /// /// Occurs when any error occurs. /// /// The event handler will be executed on an internal thread. public event EventHandler ErrorOccurred; private NativePlayer.PlaybackErrorCallback _playbackErrorCallback; /// /// Occurs when the video stream changed. /// /// The event handler will be executed on an internal thread. public event EventHandler VideoStreamChanged; private NativePlayer.VideoStreamChangedCallback _videoStreamChangedCallback; /// /// Occurs when the subtitle is updated. /// /// The event handler will be executed on an internal thread. public event EventHandler SubtitleUpdated; private NativePlayer.SubtitleUpdatedCallback _subtitleUpdatedCallback; /// /// Occurs when there is a change in the buffering status of streaming. /// public event EventHandler BufferingProgressChanged; private NativePlayer.BufferingProgressCallback _bufferingProgressCallback; internal event EventHandler MediaStreamAudioBufferStatusChanged; private NativePlayer.MediaStreamBufferStatusCallback _mediaStreamAudioBufferStatusChangedCallback; internal event EventHandler MediaStreamVideoBufferStatusChanged; private NativePlayer.MediaStreamBufferStatusCallback _mediaStreamVideoBufferStatusChangedCallback; internal event EventHandler MediaStreamAudioSeekingOccurred; private NativePlayer.MediaStreamSeekCallback _mediaStreamAudioSeekCallback; internal event EventHandler MediaStreamVideoSeekingOccurred; private NativePlayer.MediaStreamSeekCallback _mediaStreamVideoSeekCallback; /// /// Initialize a new instance of the Player class. /// public Player() { Log.Debug(PlayerLog.Tag, PlayerLog.Enter); NativePlayer.Create(out _handle).ThrowIfFailed("Failed to create player"); Debug.Assert(_handle != null); RetrieveProperties(); if (Features.IsSupported(Features.AudioEffect)) { _audioEffect = new AudioEffect(this); } if (Features.IsSupported(Features.RawVideo)) { RegisterVideoFrameDecodedCallback(); } DisplaySettings = PlayerDisplaySettings.Create(this); } private void RetrieveProperties() { Log.Debug(PlayerLog.Tag, PlayerLog.Enter); NativePlayer.GetAudioLatencyMode(Handle, out _audioLatencyMode). ThrowIfFailed("Failed to initialize the player"); NativePlayer.IsLooping(Handle, out _isLooping).ThrowIfFailed("Failed to initialize the player"); Log.Debug(PlayerLog.Tag, PlayerLog.Leave); } private bool _callbackRegistered; private void RegisterCallbacks() { Log.Debug(PlayerLog.Tag, PlayerLog.Enter); if (_callbackRegistered) { return; } RegisterSubtitleUpdatedCallback(); RegisterErrorOccurredCallback(); RegisterPlaybackInterruptedCallback(); RegisterVideoStreamChangedCallback(); RegisterBufferingCallback(); RegisterMediaStreamBufferStatusCallback(); RegisterMediaStreamSeekCallback(); RegisterPlaybackCompletedCallback(); _callbackRegistered = true; } /// /// Gets the native handle of the player. /// /// An IntPtr that contains the native handle of the player. /// The player has already been disposed of. public IntPtr Handle { get { ValidateNotDisposed(); return _handle.DangerousGetHandle(); } } internal void ValidatePlayerState(params PlayerState[] desiredStates) { Debug.Assert(desiredStates.Length > 0); ValidateNotDisposed(); var curState = State; if (curState.IsAnyOf(desiredStates)) { return; } throw new InvalidOperationException($"The player is not in a valid state. " + $"Current State : { curState }, Valid State : { string.Join(", ", desiredStates) }."); } #region Properties #region Network configuration private string _cookie = ""; private string _userAgent = ""; /// /// Gets or Sets the cookie for streaming playback. /// /// To set, the player must be in the state. /// The player is not in the valid state. /// The player has already been disposed of. /// The value to set is null. public string Cookie { get { Log.Info(PlayerLog.Tag, "get cookie : " + _cookie); return _cookie; } set { Log.Debug(PlayerLog.Tag, PlayerLog.Enter); ValidatePlayerState(PlayerState.Idle); if (value == null) { Log.Error(PlayerLog.Tag, "cookie can't be null"); throw new ArgumentNullException(nameof(value), "Cookie can't be null."); } NativePlayer.SetStreamingCookie(Handle, value, value.Length). ThrowIfFailed("Failed to set the cookie to the player"); _cookie = value; Log.Debug(PlayerLog.Tag, PlayerLog.Leave); } } /// /// Gets or Sets the user agent for streaming playback. /// /// To set, the player must be in the state. /// The player is not in the valid state. /// The player has already been disposed of. /// The value to set is null. public string UserAgent { get { Log.Info(PlayerLog.Tag, "get useragent : " + _userAgent); return _userAgent; } set { Log.Debug(PlayerLog.Tag, PlayerLog.Enter); ValidatePlayerState(PlayerState.Idle); if (value == null) { Log.Error(PlayerLog.Tag, "UserAgent can't be null"); throw new ArgumentNullException(nameof(value), "UserAgent can't be null."); } NativePlayer.SetStreamingUserAgent(Handle, value, value.Length). ThrowIfFailed("Failed to set the user agent to the player"); _userAgent = value; Log.Debug(PlayerLog.Tag, PlayerLog.Leave); } } #endregion /// /// Gets the state of the player. /// /// The current state of the player. /// The player has already been disposed of. public PlayerState State { get { ValidateNotDisposed(); if (IsPreparing()) { return PlayerState.Preparing; } int state = 0; NativePlayer.GetState(Handle, out state).ThrowIfFailed("Failed to retrieve the state of the player"); Debug.Assert(Enum.IsDefined(typeof(PlayerState), state)); return (PlayerState)state; } } private AudioLatencyMode _audioLatencyMode; /// /// Gets or sets the audio latency mode. /// /// A that specifies the mode. The default is . /// /// If the mode is , /// audio output interval can be increased so, it can keep more audio data to play. /// But, state transition like pause or resume can be more slower than default(). /// /// The player has already been disposed of. /// The value is not valid. public AudioLatencyMode AudioLatencyMode { get { Log.Info(PlayerLog.Tag, "get audio latency mode : " + _audioLatencyMode); return _audioLatencyMode; } set { Log.Debug(PlayerLog.Tag, PlayerLog.Enter); ValidateNotDisposed(); if (_audioLatencyMode == value) { return; } ValidationUtil.ValidateEnum(typeof(AudioLatencyMode), value); NativePlayer.SetAudioLatencyMode(Handle, value). ThrowIfFailed("Failed to set the audio latency mode of the player"); _audioLatencyMode = value; } } private bool _isLooping; /// /// Gets or sets the looping state. /// /// true if the playback is looping; otherwise, false. The default value is false. /// The player has already been disposed of. public bool IsLooping { get { Log.Info(PlayerLog.Tag, "get looping : " + _isLooping); return _isLooping; } set { Log.Debug(PlayerLog.Tag, PlayerLog.Enter); ValidateNotDisposed(); if (_isLooping == value) { return; } NativePlayer.SetLooping(Handle, value).ThrowIfFailed("Failed to set the looping state of the player"); _isLooping = value; } } #region Display methods /// /// Gets the display settings. /// /// A that specifies the display settings. public PlayerDisplaySettings DisplaySettings { get; } private Display _display; private PlayerErrorCode SetDisplay(Display display) { if (display == null) { Log.Info(PlayerLog.Tag, "set display to none"); return NativePlayer.SetDisplay(Handle, DisplayType.None, IntPtr.Zero); } return display.ApplyTo(this); } private void ReplaceDisplay(Display newDisplay) { _display?.SetOwner(null); _display = newDisplay; _display?.SetOwner(this); } /// /// Gets or sets the display. /// /// A that specifies the display. /// The player must be in the state. /// The player has already been disposed of. /// The value has already been assigned to another player. /// The player is not in the valid state. public Display Display { get { return _display; } set { ValidatePlayerState(PlayerState.Idle); if (value?.Owner != null) { if (ReferenceEquals(this, value.Owner)) { return; } throw new ArgumentException("The display has already been assigned to another."); } SetDisplay(value).ThrowIfFailed("Failed to set the display to the player"); ReplaceDisplay(value); } } PlayerErrorCode IDisplayable.ApplyEvasDisplay(DisplayType type, ElmSharp.EvasObject evasObject) { Debug.Assert(IsDisposed == false); Debug.Assert(Enum.IsDefined(typeof(DisplayType), type)); return NativePlayer.SetDisplay(Handle, type, evasObject); } #endregion private PlayerTrackInfo _audioTrack; /// /// Gets the track info for audio. /// /// A for audio. public PlayerTrackInfo AudioTrackInfo { get { Log.Debug(PlayerLog.Tag, PlayerLog.Enter); if (_audioTrack == null) { _audioTrack = new PlayerTrackInfo(this, StreamType.Audio); } return _audioTrack; } } private PlayerTrackInfo _subtitleTrackInfo; /// /// Gets the track info for subtitle. /// /// A for subtitle. public PlayerTrackInfo SubtitleTrackInfo { get { Log.Debug(PlayerLog.Tag, PlayerLog.Enter); if (_subtitleTrackInfo == null) { _subtitleTrackInfo = new PlayerTrackInfo(this, StreamType.Text); } return _subtitleTrackInfo; } } private StreamInfo _streamInfo; /// /// Gets the stream information. /// /// A for this player. public StreamInfo StreamInfo { get { Log.Debug(PlayerLog.Tag, PlayerLog.Enter); if (_streamInfo == null) { _streamInfo = new StreamInfo(this); } return _streamInfo; } } private readonly AudioEffect _audioEffect; /// /// Gets the audio effect. /// /// http://tizen.org/feature/multimedia.custom_audio_effect /// The required feature is not supported. public AudioEffect AudioEffect { get { if (_audioEffect == null) { throw new NotSupportedException($"The feature({Features.AudioEffect}) is not supported."); } return _audioEffect; } } #endregion #region Dispose support private bool _disposed; /// /// Releases all resources used by the current instance. /// public void Dispose() { Log.Debug(PlayerLog.Tag, PlayerLog.Enter); Dispose(true); } private void Dispose(bool disposing) { if (!_disposed) { ReplaceDisplay(null); if (_source != null) { try { _source.DetachFrom(this); } catch (Exception e) { Log.Error(PlayerLog.Tag, e.ToString()); } } _source = null; if (_handle != null) { _handle.Dispose(); } _disposed = true; } } internal void ValidateNotDisposed() { if (_disposed) { Log.Warn(PlayerLog.Tag, "player was disposed"); throw new ObjectDisposedException(nameof(Player)); } } internal bool IsDisposed => _disposed; #endregion #region Methods /// /// Gets or sets the mute state. /// /// true if the player is muted; otherwise, false. /// The player has already been disposed of. public bool Muted { get { Log.Debug(PlayerLog.Tag, PlayerLog.Enter); bool value = false; NativePlayer.IsMuted(Handle, out value).ThrowIfFailed("Failed to get the mute state of the player"); Log.Info(PlayerLog.Tag, "get mute : " + value); return value; } set { NativePlayer.SetMute(Handle, value).ThrowIfFailed("Failed to set the mute state of the player"); } } /// /// Gets the streaming download Progress. /// /// The containing current download progress. /// The player must be in the or state. /// /// The player is not streaming.\n /// -or-\n /// The player is not in the valid state. /// /// The player has already been disposed of. public DownloadProgress GetDownloadProgress() { Log.Debug(PlayerLog.Tag, PlayerLog.Enter); ValidatePlayerState(PlayerState.Playing, PlayerState.Paused); int start = 0; int current = 0; NativePlayer.GetStreamingDownloadProgress(Handle, out start, out current). ThrowIfFailed("Failed to get download progress"); Log.Info(PlayerLog.Tag, "get download progress : " + start + ", " + current); return new DownloadProgress(start, current); } #region Volume /// /// Gets or sets the current volume. /// /// Valid volume range is from 0 to 1.0, inclusive. /// The player has already been disposed of. /// /// is less than zero.\n /// -or-\n /// is greater than 1.0. /// public float Volume { get { float value = 0.0F; NativePlayer.GetVolume(Handle, out value, out value). ThrowIfFailed("Failed to get the volume of the player"); return value; } set { if (value < 0F || 1.0F < value) { throw new ArgumentOutOfRangeException(nameof(value), value, $"Valid volume range is 0 <= value <= 1.0, but got { value }."); } NativePlayer.SetVolume(Handle, value, value). ThrowIfFailed("Failed to set the volume of the player"); } } #endregion /// /// Sets the subtitle path for playback. /// /// Only MicroDVD/SubViewer(*.sub), SAMI(*.smi), and SubRip(*.srt) subtitle formats are supported. /// The mediastorage privilege(http://tizen.org/privilege/mediastorage) must be added if any files are used to play located in the internal storage. /// The externalstorage privilege(http://tizen.org/privilege/externalstorage) must be added if any files are used to play located in the external storage. /// /// The player has already been disposed of. /// is an empty string. /// The specified path does not exist. /// The path is null. public void SetSubtitle(string path) { Log.Debug(PlayerLog.Tag, PlayerLog.Enter); ValidateNotDisposed(); if (path == null) { throw new ArgumentNullException(nameof(path)); } if (path.Length == 0) { throw new ArgumentException("The path is empty.", nameof(path)); } if (!File.Exists(path)) { throw new FileNotFoundException($"The specified file does not exist.", path); } NativePlayer.SetSubtitlePath(Handle, path). ThrowIfFailed("Failed to set the subtitle path to the player"); Log.Debug(PlayerLog.Tag, PlayerLog.Leave); } /// /// Removes the subtitle path. /// /// The player must be in the state. /// The player has already been disposed of. /// The player is not in the valid state. public void ClearSubtitle() { Log.Debug(PlayerLog.Tag, PlayerLog.Enter); ValidatePlayerState(PlayerState.Idle); NativePlayer.SetSubtitlePath(Handle, null). ThrowIfFailed("Failed to clear the subtitle of the player"); Log.Debug(PlayerLog.Tag, PlayerLog.Leave); } /// /// Sets the offset for the subtitle. /// /// The value indicating a desired offset in milliseconds. /// The player must be in the or state. /// The player has already been disposed of. /// /// The player is not in the valid state.\n /// -or-\n /// No subtitle is set. /// /// public void SetSubtitleOffset(int offset) { Log.Debug(PlayerLog.Tag, PlayerLog.Enter); ValidatePlayerState(PlayerState.Playing, PlayerState.Paused); var err = NativePlayer.SetSubtitlePositionOffset(Handle, offset); if (err == PlayerErrorCode.FeatureNotSupported) { throw new InvalidOperationException("No subtitle set"); } err.ThrowIfFailed("Failed to the subtitle offset of the player"); Log.Debug(PlayerLog.Tag, PlayerLog.Leave); } private void Prepare() { Log.Debug(PlayerLog.Tag, PlayerLog.Enter); NativePlayer.Prepare(Handle).ThrowIfFailed("Failed to prepare the player"); } protected virtual void OnPreparing() { RegisterCallbacks(); } /// /// Prepares the media player for playback, asynchronously. /// /// A task that represents the asynchronous prepare operation. /// To prepare the player, the player must be in the state, /// and a source must be set. /// No source is set. /// The player has already been disposed of. /// The player is not in the valid state. public virtual Task PrepareAsync() { Log.Debug(PlayerLog.Tag, PlayerLog.Enter); if (_source == null) { throw new InvalidOperationException("No source is set."); } ValidatePlayerState(PlayerState.Idle); OnPreparing(); var completionSource = new TaskCompletionSource(); SetPreparing(); Task.Run(() => { try { Prepare(); ClearPreparing(); completionSource.SetResult(true); } catch (Exception e) { ClearPreparing(); completionSource.TrySetException(e); } }); Log.Debug(PlayerLog.Tag, PlayerLog.Leave); return completionSource.Task; } /// /// Unprepares the player. /// /// /// The most recently used source is reset and no longer associated with the player. Playback is no longer possible. /// If you want to use the player again, you have to set a source and call again. /// /// The player must be in the , or state. /// It has no effect if the player is already in the state. /// /// /// The player has already been disposed of. /// The player is not in the valid state. public virtual void Unprepare() { Log.Debug(PlayerLog.Tag, PlayerLog.Enter); if (State == PlayerState.Idle) { Log.Warn(PlayerLog.Tag, "idle state already"); return; } ValidatePlayerState(PlayerState.Ready, PlayerState.Paused, PlayerState.Playing); NativePlayer.Unprepare(Handle).ThrowIfFailed("Failed to unprepare the player"); OnUnprepared(); } protected virtual void OnUnprepared() { _source?.DetachFrom(this); _source = null; } /// /// Starts or resumes playback. /// /// /// The player must be in the or state. /// It has no effect if the player is already in the state.\n /// \n /// Sound can be mixed with other sounds if you don't control the stream focus using . /// /// The player has already been disposed of. /// The player is not in the valid state. /// /// /// /// /// public virtual void Start() { Log.Debug(PlayerLog.Tag, PlayerLog.Enter); if (State == PlayerState.Playing) { Log.Warn(PlayerLog.Tag, "playing state already"); return; } ValidatePlayerState(PlayerState.Ready, PlayerState.Paused); NativePlayer.Start(Handle).ThrowIfFailed("Failed to start the player"); Log.Debug(PlayerLog.Tag, PlayerLog.Leave); } /// /// Stops playing media content. /// /// /// The player must be in the or state. /// It has no effect if the player is already in the state. /// /// The player has already been disposed of. /// The player is not in the valid state. /// /// public virtual void Stop() { Log.Debug(PlayerLog.Tag, PlayerLog.Enter); if (State == PlayerState.Ready) { Log.Warn(PlayerLog.Tag, "ready state already"); return; } ValidatePlayerState(PlayerState.Paused, PlayerState.Playing); NativePlayer.Stop(Handle).ThrowIfFailed("Failed to stop the player"); Log.Debug(PlayerLog.Tag, PlayerLog.Leave); } /// /// Pauses the player. /// /// /// The player must be in the state. /// It has no effect if the player is already in the state. /// /// The player has already been disposed of. /// The player is not in the valid state. /// public virtual void Pause() { Log.Debug(PlayerLog.Tag, PlayerLog.Enter); if (State == PlayerState.Paused) { Log.Warn(PlayerLog.Tag, "pause state already"); return; } ValidatePlayerState(PlayerState.Playing); NativePlayer.Pause(Handle).ThrowIfFailed("Failed to pause the player"); Log.Debug(PlayerLog.Tag, PlayerLog.Leave); } private MediaSource _source; /// /// Sets a media source for the player. /// /// A that specifies the source for playback. /// The player must be in the state. /// The player has already been disposed of. /// /// The player is not in the valid state.\n /// -or-\n /// It is not able to assign the source to the player. /// /// public void SetSource(MediaSource source) { Log.Debug(PlayerLog.Tag, PlayerLog.Enter); ValidatePlayerState(PlayerState.Idle); if (source != null) { source.AttachTo(this); } if (_source != null) { _source.DetachFrom(this); } _source = source; Log.Debug(PlayerLog.Tag, PlayerLog.Leave); } /// /// Captures a video frame asynchronously. /// /// A task that represents the asynchronous capture operation. /// http://tizen.org/feature/multimedia.raw_video /// The player must be in the or state. /// The player has already been disposed of. /// The player is not in the valid state. /// The required feature is not supported. public async Task CaptureVideoAsync() { Log.Debug(PlayerLog.Tag, PlayerLog.Enter); ValidationUtil.ValidateFeatureSupported(Features.RawVideo); ValidatePlayerState(PlayerState.Playing, PlayerState.Paused); TaskCompletionSource t = new TaskCompletionSource(); NativePlayer.VideoCaptureCallback cb = (data, width, height, size, _) => { Debug.Assert(size <= int.MaxValue); byte[] buf = new byte[size]; Marshal.Copy(data, buf, 0, (int)size); t.TrySetResult(new CapturedFrame(buf, width, height)); }; using (var cbKeeper = ObjectKeeper.Get(cb)) { NativePlayer.CaptureVideo(Handle, cb) .ThrowIfFailed("Failed to capture the video"); return await t.Task; } } /// /// Gets the play position in milliseconds. /// /// The player must be in the , or state. /// The player has already been disposed of. /// The player is not in the valid state. /// public int GetPlayPosition() { Log.Debug(PlayerLog.Tag, PlayerLog.Enter); ValidatePlayerState(PlayerState.Ready, PlayerState.Paused, PlayerState.Playing); int playPosition = 0; NativePlayer.GetPlayPosition(Handle, out playPosition). ThrowIfFailed("Failed to get the play position of the player"); Log.Info(PlayerLog.Tag, "get play position : " + playPosition); return playPosition; } private void SetPlayPosition(int milliseconds, bool accurate, NativePlayer.SeekCompletedCallback cb) { Log.Debug(PlayerLog.Tag, PlayerLog.Enter); var ret = NativePlayer.SetPlayPosition(Handle, milliseconds, accurate, cb, IntPtr.Zero); //Note that we assume invalid param error is returned only when the position value is invalid. if (ret == PlayerErrorCode.InvalidArgument) { throw new ArgumentOutOfRangeException(nameof(milliseconds), milliseconds, "The position is not valid."); } if (ret != PlayerErrorCode.None) { Log.Error(PlayerLog.Tag, "Failed to set play position, " + (PlayerError)ret); } ret.ThrowIfFailed("Failed to set play position"); } /// /// Sets the seek position for playback, asynchronously. /// /// The value indicating a desired position in milliseconds. /// The value indicating whether the operation performs with accuracy. /// /// The player must be in the , or state. /// If the is true, the play position will be adjusted as the specified value, /// but this might be considerably slow. If false, the play position will be a nearest keyframe position. /// /// The player has already been disposed of. /// The player is not in the valid state. /// The specified position is not valid. /// public async Task SetPlayPositionAsync(int position, bool accurate) { Log.Debug(PlayerLog.Tag, PlayerLog.Enter); ValidatePlayerState(PlayerState.Ready, PlayerState.Playing, PlayerState.Paused); var taskCompletionSource = new TaskCompletionSource(); bool immediateResult = _source is MediaStreamSource; NativePlayer.SeekCompletedCallback cb = _ => taskCompletionSource.TrySetResult(true); using (var cbKeeper = ObjectKeeper.Get(cb)) { SetPlayPosition(position, accurate, cb); if (immediateResult) { taskCompletionSource.TrySetResult(true); } await taskCompletionSource.Task; } Log.Debug(PlayerLog.Tag, PlayerLog.Leave); } /// /// Sets playback rate. /// /// The value for the playback rate. Valid range is -5.0 to 5.0, inclusive. /// /// The player must be in the , or state. /// The sound will be muted, when the playback rate is under 0.0 or over 2.0. /// /// The player has already been disposed of. /// /// The player is not in the valid state.\n /// -or-\n /// Streaming playback. /// /// /// is less than 5.0.\n /// -or-\n /// is greater than 5.0.\n /// -or-\n /// is zero. /// public void SetPlaybackRate(float rate) { Log.Debug(PlayerLog.Tag, PlayerLog.Enter); if (rate < -5.0F || 5.0F < rate || rate == 0.0F) { throw new ArgumentOutOfRangeException(nameof(rate), rate, "Valid range is -5.0 to 5.0 (except 0.0)"); } ValidatePlayerState(PlayerState.Ready, PlayerState.Playing, PlayerState.Paused); NativePlayer.SetPlaybackRate(Handle, rate).ThrowIfFailed("Failed to set the playback rate."); Log.Debug(PlayerLog.Tag, PlayerLog.Leave); } /// /// Applies the audio stream policy. /// /// The to apply. /// The player must be in the state. /// /// The player has already been disposed of.\n /// -or-\n /// has already been disposed of. /// /// The player is not in the valid state. /// is null. public void ApplyAudioStreamPolicy(AudioStreamPolicy policy) { Log.Debug(PlayerLog.Tag, PlayerLog.Enter); if (policy == null) { throw new ArgumentNullException(nameof(policy)); } if (policy.Handle == IntPtr.Zero) { throw new ObjectDisposedException(nameof(policy)); } ValidatePlayerState(PlayerState.Idle); NativePlayer.SetAudioPolicyInfo(Handle, policy.Handle). ThrowIfFailed("Failed to set the audio stream policy to the player"); } #endregion #region Callback registrations private void RegisterSubtitleUpdatedCallback() { _subtitleUpdatedCallback = (duration, text, _) => { Log.Debug(PlayerLog.Tag, "duration : " + duration + ", text : " + text); SubtitleUpdated?.Invoke(this, new SubtitleUpdatedEventArgs(duration, text)); }; NativePlayer.SetSubtitleUpdatedCb(Handle, _subtitleUpdatedCallback). ThrowIfFailed("Failed to initialize the player"); } private void RegisterPlaybackCompletedCallback() { _playbackCompletedCallback = _ => { Log.Debug(PlayerLog.Tag, "completed callback"); PlaybackCompleted?.Invoke(this, EventArgs.Empty); }; NativePlayer.SetCompletedCb(Handle, _playbackCompletedCallback). ThrowIfFailed("Failed to set PlaybackCompleted"); } private void RegisterPlaybackInterruptedCallback() { _playbackInterruptedCallback = (code, _) => { if (!Enum.IsDefined(typeof(PlaybackInterruptionReason), code)) { return; } if (code == PlaybackInterruptionReason.ResourceConflict) { OnUnprepared(); } Log.Warn(PlayerLog.Tag, "interrupted reason : " + code); PlaybackInterrupted?.Invoke(this, new PlaybackInterruptedEventArgs(code)); }; NativePlayer.SetInterruptedCb(Handle, _playbackInterruptedCallback). ThrowIfFailed("Failed to set PlaybackInterrupted"); } private void RegisterErrorOccurredCallback() { _playbackErrorCallback = (code, _) => { //TODO handle service disconnected error. Log.Warn(PlayerLog.Tag, "error code : " + code); ErrorOccurred?.Invoke(this, new PlayerErrorOccurredEventArgs((PlayerError)code)); }; NativePlayer.SetErrorCb(Handle, _playbackErrorCallback). ThrowIfFailed("Failed to set PlaybackError"); } #region VideoFrameDecoded event private EventHandler _videoFrameDecoded; private NativePlayer.VideoFrameDecodedCallback _videoFrameDecodedCallback; /// /// Occurs when a video frame is decoded. /// /// /// The event handler will be executed on an internal thread. /// The in event args should be disposed after use. /// /// http://tizen.org/feature/multimedia.raw_video /// The required feature is not supported. /// public event EventHandler VideoFrameDecoded { add { ValidationUtil.ValidateFeatureSupported(Features.RawVideo); _videoFrameDecoded += value; } remove { ValidationUtil.ValidateFeatureSupported(Features.RawVideo); _videoFrameDecoded -= value; } } private void RegisterVideoFrameDecodedCallback() { _videoFrameDecodedCallback = (packetHandle, _) => { var handler = _videoFrameDecoded; if (handler != null) { Log.Debug(PlayerLog.Tag, "packet : " + packetHandle); handler.Invoke(this, new VideoFrameDecodedEventArgs(MediaPacket.From(packetHandle))); } else { MediaPacket.From(packetHandle).Dispose(); } }; NativePlayer.SetVideoFrameDecodedCb(Handle, _videoFrameDecodedCallback). ThrowIfFailed("Failed to register the VideoFrameDecoded"); } #endregion private void RegisterVideoStreamChangedCallback() { ValidatePlayerState(PlayerState.Idle); _videoStreamChangedCallback = (width, height, fps, bitrate, _) => { Log.Debug(PlayerLog.Tag, "height : " + height + ", width : " + width + ", fps : " + fps + ", bitrate : " + bitrate); VideoStreamChanged?.Invoke(this, new VideoStreamChangedEventArgs(height, width, fps, bitrate)); }; NativePlayer.SetVideoStreamChangedCb(Handle, _videoStreamChangedCallback). ThrowIfFailed("Failed to set the video stream changed callback"); } private void RegisterBufferingCallback() { _bufferingProgressCallback = (percent, _) => { Log.Debug(PlayerLog.Tag, $"Buffering callback with percent { percent }"); BufferingProgressChanged?.Invoke(this, new BufferingProgressChangedEventArgs(percent)); }; NativePlayer.SetBufferingCb(Handle, _bufferingProgressCallback). ThrowIfFailed("Failed to set BufferingProgress"); } private void RegisterMediaStreamBufferStatusCallback() { _mediaStreamAudioBufferStatusChangedCallback = (status, _) => { Debug.Assert(Enum.IsDefined(typeof(MediaStreamBufferStatus), status)); Log.Debug(PlayerLog.Tag, "audio buffer status : " + status); MediaStreamAudioBufferStatusChanged?.Invoke(this, new MediaStreamBufferStatusChangedEventArgs(status)); }; _mediaStreamVideoBufferStatusChangedCallback = (status, _) => { Debug.Assert(Enum.IsDefined(typeof(MediaStreamBufferStatus), status)); Log.Debug(PlayerLog.Tag, "video buffer status : " + status); MediaStreamVideoBufferStatusChanged?.Invoke(this, new MediaStreamBufferStatusChangedEventArgs(status)); }; RegisterMediaStreamBufferStatusCallback(StreamType.Audio, _mediaStreamAudioBufferStatusChangedCallback); RegisterMediaStreamBufferStatusCallback(StreamType.Video, _mediaStreamVideoBufferStatusChangedCallback); } private void RegisterMediaStreamBufferStatusCallback(StreamType streamType, NativePlayer.MediaStreamBufferStatusCallback cb) { NativePlayer.SetMediaStreamBufferStatusCb(Handle, streamType, cb). ThrowIfFailed("Failed to SetMediaStreamBufferStatus"); } private void RegisterMediaStreamSeekCallback() { _mediaStreamAudioSeekCallback = (offset, _) => { Log.Debug(PlayerLog.Tag, "audio seeking offset : " + offset); MediaStreamAudioSeekingOccurred?.Invoke(this, new MediaStreamSeekingOccurredEventArgs(offset)); }; _mediaStreamVideoSeekCallback = (offset, _) => { Log.Debug(PlayerLog.Tag, "video seeking offset : " + offset); MediaStreamVideoSeekingOccurred?.Invoke(this, new MediaStreamSeekingOccurredEventArgs(offset)); }; RegisterMediaStreamSeekCallback(StreamType.Audio, _mediaStreamAudioSeekCallback); RegisterMediaStreamSeekCallback(StreamType.Video, _mediaStreamVideoSeekCallback); } private void RegisterMediaStreamSeekCallback(StreamType streamType, NativePlayer.MediaStreamSeekCallback cb) { NativePlayer.SetMediaStreamSeekCb(Handle, streamType, cb). ThrowIfFailed("Failed to SetMediaStreamSeek"); } #endregion #region Preparing state private int _isPreparing; private bool IsPreparing() { return Interlocked.CompareExchange(ref _isPreparing, 1, 1) == 1; } private void SetPreparing() { Interlocked.Exchange(ref _isPreparing, 1); } private void ClearPreparing() { Interlocked.Exchange(ref _isPreparing, 0); } #endregion /// /// This method supports the product infrastructure and is not intended to be used directly from application code. /// protected static Exception GetException(int errorCode, string message) => ((PlayerErrorCode) errorCode).GetException(message); } }