/*
* Copyright (c) 2018 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 static Interop;
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
namespace Tizen.Multimedia
{
internal static class PlayerLog
{
internal const string Tag = "Tizen.Multimedia.Player";
}
///
/// 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 partial class Player : IDisposable, IDisplayable
{
private readonly PlayerHandle _handle;
///
/// Initializes a new instance of the class.
///
/// 3
public Player()
{
NativePlayer.Create(out _handle).ThrowIfFailed(null, "Failed to create player");
Debug.Assert(_handle != null);
Initialize();
}
///
/// Initializes a new instance of the class with a native handle.
/// The class takes care of the life cycle of the handle.
/// Thus, it should not be closed/destroyed in another location.
///
/// The handle for the media player.
/// The handle for occuring error.
///
/// This supports the product infrastructure and is not intended to be used directly from application code.
///
[EditorBrowsable(EditorBrowsableState.Never)]
protected Player(IntPtr handle, Action errorHandler)
{
// This constructor is to support TV product player.
// Be careful with 'handle'. It must be wrapped in safe handle, first.
_handle = handle != IntPtr.Zero ? new PlayerHandle(handle) :
throw new ArgumentException("Handle is invalid.", nameof(handle));
_errorHandler = errorHandler;
}
private bool _initialized;
///
/// This supports the product infrastructure and is not intended to be used directly from application code.
///
[EditorBrowsable(EditorBrowsableState.Never)]
protected void Initialize()
{
if (_initialized)
{
throw new InvalidOperationException("It has already been initialized.");
}
if (Features.IsSupported(PlayerFeatures.AudioEffect))
{
_audioEffect = new AudioEffect(this);
}
if (Features.IsSupported(PlayerFeatures.RawVideo))
{
RegisterVideoFrameDecodedCallback();
}
RegisterEvents();
_displaySettings = PlayerDisplaySettings.Create(this);
_initialized = true;
}
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 Dispose support
private bool _disposed;
///
/// Releases all resources used by the current instance.
///
/// 3
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
///
/// Releases the unmanaged resources used by the .
///
///
/// true to release both managed and unmanaged resources; false to release only unmanaged resources.
///
[EditorBrowsable(EditorBrowsableState.Never)]
protected virtual void Dispose(bool disposing)
{
if (_disposed)
{
return;
}
if (disposing)
{
ReplaceDisplay(null);
if (_source != null)
{
try
{
_source.DetachFrom(this);
_source = null;
}
catch (Exception e)
{
Log.Error(PlayerLog.Tag, e.ToString());
}
}
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 the streaming download progress.
///
/// The containing current download progress.
/// The player must be in the , ,
/// or state.
///
/// The player is not streaming.
/// -or-
/// The player is not in the valid state.
///
/// The player has already been disposed of.
/// 3
public DownloadProgress GetDownloadProgress()
{
ValidatePlayerState(PlayerState.Ready, PlayerState.Playing, PlayerState.Paused);
int start = 0;
int current = 0;
NativePlayer.GetStreamingDownloadProgress(Handle, out start, out current).
ThrowIfFailed(this, "Failed to get download progress");
Log.Info(PlayerLog.Tag, $"get download progress : {start}, {current}");
return new DownloadProgress(start, current);
}
///
/// Sets the subtitle path for playback.
///
/// The absolute path of the subtitle file, it can be NULL in the state.
/// 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.
/// is null.
/// 3
public void SetSubtitle(string path)
{
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(this, "Failed to set the subtitle path to the player");
}
///
/// 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.
/// 3
public void ClearSubtitle()
{
ValidatePlayerState(PlayerState.Idle);
NativePlayer.SetSubtitlePath(Handle, null).
ThrowIfFailed(this, "Failed to clear the subtitle of the player");
}
///
/// 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.
/// -or-
/// No subtitle is set.
///
///
/// 3
public void SetSubtitleOffset(int offset)
{
ValidatePlayerState(PlayerState.Playing, PlayerState.Paused);
var err = NativePlayer.SetSubtitlePositionOffset(Handle, offset);
if (err == PlayerErrorCode.FeatureNotSupported)
{
throw new InvalidOperationException("No subtitle set");
}
err.ThrowIfFailed(this, "Failed to the subtitle offset of the player");
}
private void Prepare()
{
NativePlayer.Prepare(Handle).ThrowIfFailed(this, "Failed to prepare the player");
}
///
/// Called when the is invoked.
///
/// 3
protected virtual void OnPreparing()
{
}
///
/// 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.
///
/// 3
public virtual Task PrepareAsync()
{
if (_source == null)
{
throw new InvalidOperationException("No source is set.");
}
ValidatePlayerState(PlayerState.Idle);
OnPreparing();
SetPreparing();
return Task.Factory.StartNew(() =>
{
try
{
Prepare();
}
finally
{
ClearPreparing();
}
}, CancellationToken.None,
TaskCreationOptions.DenyChildAttach | TaskCreationOptions.LongRunning,
TaskScheduler.Default);
}
///
/// Prepares the cancellable media player for playback, asynchronously.
///
/// The cancellation token to cancel preparing.
///
/// 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.
/// The state must be to cancel preparing.
/// When preparing is cancelled, a state will be changed to from .
/// The player has already been disposed of.
///
/// Operation failed; internal error.
/// -or-
/// The player is not in the valid state.
///
///
///
/// 6
public virtual async Task PrepareAsync(CancellationToken cancellationToken)
{
ValidateNotDisposed();
var taskCompletionSource = new TaskCompletionSource();
if (_source == null)
{
throw new InvalidOperationException("No source is set.");
}
ValidatePlayerState(PlayerState.Idle);
OnPreparing();
SetPreparing();
// register a callback to handle cancellation token anytime it occurs
cancellationToken.Register(() =>
{
ValidatePlayerState(PlayerState.Preparing);
// a user can get the state before finally block is called.
ClearPreparing();
Log.Warn(PlayerLog.Tag, $"preparing will be cancelled.");
NativePlayer.Unprepare(Handle).ThrowIfFailed(this, "Failed to unprepare the player");
taskCompletionSource.TrySetCanceled();
});
_prepareCallback = _ =>
{
Log.Warn(PlayerLog.Tag, $"prepared callback is called.");
taskCompletionSource.TrySetResult(true);
};
try
{
NativePlayer.PrepareAsync(Handle, _prepareCallback, IntPtr.Zero).
ThrowIfFailed(this, "Failed to prepare the player");
await taskCompletionSource.Task.ConfigureAwait(false);
}
finally
{
ClearPreparing();
}
}
///
/// Unprepares the player.
///
///
/// The most recently used source is reset and is 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.
/// 3
public virtual void Unprepare()
{
if (State == PlayerState.Idle)
{
Log.Warn(PlayerLog.Tag, "idle state already");
return;
}
ValidatePlayerState(PlayerState.Ready, PlayerState.Paused, PlayerState.Playing);
NativePlayer.Unprepare(Handle).ThrowIfFailed(this, "Failed to unprepare the player");
OnUnprepared();
}
///
/// Called after the is unprepared.
///
///
/// 3
protected virtual void OnUnprepared()
{
_source?.DetachFrom(this);
_source = null;
}
///
/// Starts or resumes playback.
///
///
/// Sound can be mixed with other sounds if you don't control the stream focus using .
/// Before Tizen 5.0, The player must be in the or state.
/// It has no effect if the player is already in the state.
/// Since Tizen 5.0, The player must be in the , ,
/// or state.
/// In case of HTTP streaming playback, the player could be internally paused for buffering.
/// If the application calls this function during the buffering, the playback will be resumed by force
/// and the buffering message posting by will be stopped.
/// In other cases, the player will keep playing without returning error.
///
/// The player has already been disposed of.
/// The player is not in the valid state.
///
///
///
///
///
///
/// 3
public virtual void Start()
{
ValidatePlayerState(PlayerState.Ready, PlayerState.Paused, PlayerState.Playing);
NativePlayer.Start(Handle).ThrowIfFailed(this, "Failed to start the player");
}
///
/// Stops playing the 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.
///
///
/// 3
public virtual void Stop()
{
if (State == PlayerState.Ready)
{
Log.Warn(PlayerLog.Tag, "ready state already");
return;
}
ValidatePlayerState(PlayerState.Paused, PlayerState.Playing);
NativePlayer.Stop(Handle).ThrowIfFailed(this, "Failed to stop the player");
}
///
/// 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.
///
/// 3
public virtual void Pause()
{
if (State == PlayerState.Paused)
{
Log.Warn(PlayerLog.Tag, "pause state already");
return;
}
ValidatePlayerState(PlayerState.Playing);
NativePlayer.Pause(Handle).ThrowIfFailed(this, "Failed to pause the player");
}
private MediaSource _source;
///
/// Determines whether MediaSource has set.
/// This supports the product infrastructure and is not intended to be used directly from application code.
///
[EditorBrowsable(EditorBrowsableState.Never)]
protected bool HasSource => _source != null;
///
/// 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.
/// -or-
/// It is not able to assign the source to the player.
///
///
/// 3
public void SetSource(MediaSource source)
{
ValidatePlayerState(PlayerState.Idle);
if (source != null)
{
source.AttachTo(this);
}
if (_source != null)
{
_source.DetachFrom(this);
}
_source = source;
}
///
/// 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.
/// 3
public async Task CaptureVideoAsync()
{
ValidationUtil.ValidateFeatureSupported(PlayerFeatures.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(this, "Failed to capture the video");
return await t.Task;
}
}
///
/// Gets the play position in milliseconds.
///
/// The current 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.
///
///
///
/// 3
public int GetPlayPosition()
{
ValidatePlayerState(PlayerState.Ready, PlayerState.Paused, PlayerState.Playing);
int playPosition = 0;
NativePlayer.GetPlayPosition(Handle, out playPosition).
ThrowIfFailed(this, "Failed to get the play position of the player");
Log.Info(PlayerLog.Tag, $"get play position : {playPosition}");
return playPosition;
}
private void NativeSetPlayPosition(long position, bool accurate, bool nanoseconds,
NativePlayer.SeekCompletedCallback cb)
{
//Check if it is nanoseconds or milliseconds.
var ret = !nanoseconds ? NativePlayer.SetPlayPosition(Handle, (int)position, accurate, cb, IntPtr.Zero) :
NativePlayer.SetPlayPositionNanoseconds(Handle, position, 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(position), position,
"The position is not valid.");
}
if (ret != PlayerErrorCode.None)
{
Log.Error(PlayerLog.Tag, "Failed to set play position, " + (PlayerError)ret);
}
ret.ThrowIfFailed(this, "Failed to set play position");
}
private async Task SetPlayPosition(long position, bool accurate, bool nanoseconds)
{
var taskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
bool immediateResult = _source is MediaStreamSource;
NativePlayer.SeekCompletedCallback cb = _ => taskCompletionSource.TrySetResult(true);
using (var cbKeeper = ObjectKeeper.Get(cb))
{
NativeSetPlayPosition(position, accurate, nanoseconds, immediateResult ? null : cb);
if (immediateResult)
{
taskCompletionSource.TrySetResult(true);
}
await taskCompletionSource.Task;
}
}
///
/// Sets the seek position for playback, asynchronously.
///
/// The value indicating a desired position in milliseconds.
/// The value indicating whether the operation performs with accuracy.
/// A task that represents the asynchronous operation.
///
/// 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.
/// -or-
/// In case of non-seekable content, the player will return error and keep playing without changing the play position.
/// The specified position is not valid.
///
///
///
/// 3
public async Task SetPlayPositionAsync(int position, bool accurate)
{
ValidatePlayerState(PlayerState.Ready, PlayerState.Playing, PlayerState.Paused);
await SetPlayPosition(position, accurate, false);
}
///
/// Gets the play position in nanoseconds.
///
/// The current position in nanoseconds.
/// The player must be in the , ,
/// or state.
/// The player has already been disposed of.
/// The player is not in the valid state.
///
///
///
/// 5
public long GetPlayPositionNanoseconds()
{
ValidatePlayerState(PlayerState.Ready, PlayerState.Paused, PlayerState.Playing);
NativePlayer.GetPlayPositionNanoseconds(Handle, out long playPosition).
ThrowIfFailed(this, "Failed to get the play position(nsec) of the player");
Log.Info(PlayerLog.Tag, $"get play position(nsec) : {playPosition}");
return playPosition;
}
///
/// Sets the seek position in nanoseconds for playback, asynchronously.
///
/// The value indicating a desired position in nanoseconds.
/// The value indicating whether the operation performs with accuracy.
/// A task that represents the asynchronous operation.
///
/// 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.
/// -or-
/// In case of non-seekable content, the player will return error and keep playing without changing the play position.
/// The specified position is not valid.
///
///
///
/// 5
public async Task SetPlayPositionNanosecondsAsync(long position, bool accurate)
{
ValidatePlayerState(PlayerState.Ready, PlayerState.Playing, PlayerState.Paused);
await SetPlayPosition(position, accurate, true);
}
///
/// Sets the 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.
/// -or-
/// Streaming playback.
///
///
/// is less than -5.0.
/// -or-
/// is greater than 5.0.
/// -or-
/// is zero.
///
/// 3
public void SetPlaybackRate(float rate)
{
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(this, "Failed to set the playback rate.");
}
///
/// Applies the audio stream policy.
///
/// The to apply.
///
/// The player must be in the state.
///
/// does not support all .
/// Supported types are , ,
/// , ,
/// , ,
/// and .
///
///
/// The player has already been disposed of.
/// -or-
/// has already been disposed of.
///
/// The player is not in the valid state.
/// is null.
///
/// The required feature is not supported.
/// -or-
/// of is not supported on the current platform.
///
///
/// http://tizen.org/feature/multimedia.player.stream_info
/// 3
public void ApplyAudioStreamPolicy(AudioStreamPolicy policy)
{
ValidationUtil.ValidateFeatureSupported("http://tizen.org/feature/multimedia.player.stream_info");
if (policy == null)
{
throw new ArgumentNullException(nameof(policy));
}
ValidatePlayerState(PlayerState.Idle);
var ret = NativePlayer.SetAudioPolicyInfo(Handle, policy.Handle);
if (ret == PlayerErrorCode.InvalidArgument)
{
throw new NotSupportedException("The specified policy is not supported on the current system.");
}
ret.ThrowIfFailed(this, "Failed to set the audio stream policy to the player");
}
///
/// Set the relative ROI (Region Of Interest) area as a decimal fraction based on the video source.
/// It can be regarded as zooming operation because the specified video area will be rendered to fit to the display.
///
/// The containing the ROI area information.
///
/// This function requires the ratio of the each coordinate and size to the video resolution size
/// to guarantee of showing the same area for the dynamic resolution video content.
/// This function have to be called after setting
///
/// The player has already been disposed of.
///
/// Operation failed; internal error.
/// -or-
/// The is not set to .
///
///
/// is less than 0.0 or greater than 1.0.
/// -or-
/// is less than 0.0 or greater than 1.0.
/// -or-
/// is less than or equal to 0.0 or greater than 1.0.
/// -or-
/// is less than or equal to 0.0 or greater than 1.0.
///
///
///
///
///
/// 5
public void SetVideoRoi(ScaleRectangle scaleRectangle)
{
ValidateNotDisposed();
if (scaleRectangle.ScaleX < 0 || scaleRectangle.ScaleX > 1)
{
throw new ArgumentOutOfRangeException(nameof(scaleRectangle.ScaleX), scaleRectangle.ScaleX, "Valid range is 0 to 1.0");
}
if (scaleRectangle.ScaleY < 0 || scaleRectangle.ScaleY > 1)
{
throw new ArgumentOutOfRangeException(nameof(scaleRectangle.ScaleY), scaleRectangle.ScaleY, "Valid range is 0 to 1.0");
}
if (scaleRectangle.ScaleWidth <= 0 || scaleRectangle.ScaleWidth > 1)
{
throw new ArgumentOutOfRangeException(nameof(scaleRectangle.ScaleWidth), scaleRectangle.ScaleWidth, "Valid range is 0 to 1.0 (except 0.0)");
}
if (scaleRectangle.ScaleHeight <= 0 || scaleRectangle.ScaleHeight > 1)
{
throw new ArgumentOutOfRangeException(nameof(scaleRectangle.ScaleHeight), scaleRectangle.ScaleHeight, "Valid range is 0 to 1.0 (except 0.0)");
}
NativePlayer.SetVideoRoi(Handle, scaleRectangle.ScaleX, scaleRectangle.ScaleY, scaleRectangle.ScaleWidth, scaleRectangle.ScaleHeight).ThrowIfFailed(this, "Failed to set the video roi area.");
}
///
/// Get the relative ROI (Region Of Interest) area as a decimal fraction based on the video source.
///
/// The containing the ROI area information.
/// The specified ROI area is valid only in .
/// The player has already been disposed of.
///
/// Operation failed; internal error.
///
///
///
///
/// 5
public ScaleRectangle GetVideoRoi()
{
ValidateNotDisposed();
NativePlayer.GetVideoRoi(Handle, out var scaleX, out var scaleY,
out var scaleWidth, out var scaleHeight).ThrowIfFailed(this, "Failed to get the video roi area");
return new ScaleRectangle(scaleX, scaleY, scaleWidth, scaleHeight);
}
///
/// This supports the product infrastructure and is not intended to be used directly from application code.
///
[EditorBrowsable(EditorBrowsableState.Never)]
protected MediaPacket GetMediaPacket(IntPtr handle)
{
MediaPacket mediaPacket = handle != IntPtr.Zero ? MediaPacket.From(handle) :
throw new ArgumentException("MediaPacket handle is invalid.", nameof(handle));
return mediaPacket;
}
#endregion
#region Preparing state
private int _isPreparing;
private bool IsPreparing()
{
return Interlocked.CompareExchange(ref _isPreparing, 1, 1) == 1;
}
///
/// This supports the product infrastructure and is not intended to be used directly from application code.
///
[EditorBrowsable(EditorBrowsableState.Never)]
protected void SetPreparing()
{
Interlocked.Exchange(ref _isPreparing, 1);
}
///
/// This supports the product infrastructure and is not intended to be used directly from application code.
///
[EditorBrowsable(EditorBrowsableState.Never)]
protected void ClearPreparing()
{
Interlocked.Exchange(ref _isPreparing, 0);
}
#endregion
///
/// Enable to decode an audio data for exporting PCM from a data.
///
/// The media format handle required to audio PCM specification.
/// The format has to include ,
/// and .
/// If the format is NULL, the original PCM format or platform default PCM format will be applied.
/// The audio extract option.
/// The player must be in the state.
/// A event is called in a separate thread(not in the main loop).
/// The audio PCM data can be retrieved using a event as a media packet
/// and it is available until it's destroyed by .
/// The packet has to be destroyed as quickly as possible after rendering the data
/// and all the packets have to be destroyed before is called.
/// The player has already been disposed of.
/// The value is not valid.
///
/// Operation failed; internal error.
/// -or-
/// The player is not in the valid state.
///
///
///
/// 6
public void EnableExportingAudioData(AudioMediaFormat format, PlayerAudioExtractOption option)
{
ValidatePlayerState(PlayerState.Idle);
ValidationUtil.ValidateEnum(typeof(PlayerAudioExtractOption), option, nameof(option));
_audioFrameDecodedCallback = (IntPtr packetHandle, IntPtr userData) =>
{
var handler = AudioDataDecoded;
if (handler != null)
{
Log.Debug(PlayerLog.Tag, "packet : " + packetHandle.ToString());
handler.Invoke(this,
new AudioDataDecodedEventArgs(MediaPacket.From(packetHandle)));
}
else
{
MediaPacket.From(packetHandle).Dispose();
}
};
NativePlayer.SetAudioFrameDecodedCb(Handle, format == null ? IntPtr.Zero : format.AsNativeHandle(), option,
_audioFrameDecodedCallback, IntPtr.Zero).ThrowIfFailed(this, "Failed to register the _audioFrameDecoded");
}
///
/// Disable to decode an audio data.
///
/// The player must be in the or
/// state.
/// The player has already been disposed of.
/// The player is not in the valid state.
///
/// 6
public void DisableExportingAudioData()
{
ValidatePlayerState(PlayerState.Idle, PlayerState.Ready);
NativePlayer.UnsetAudioFrameDecodedCb(Handle).
ThrowIfFailed(this, "Failed to unset the AudioFrameDecoded");
_audioFrameDecodedCallback = null;
}
}
}