/* * 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.Collections.Generic; using System.Runtime.InteropServices; using static Interop.Tts; namespace Tizen.Uix.Tts { /// /// Enumeration for the states. /// /// 3 public enum State { /// /// Created state. /// /// 3 Created = 0, /// /// Ready state. /// /// 3 Ready = 1, /// /// Playing state. /// /// 3 Playing = 2, /// /// Paused state. /// /// 3 Paused = 3, /// /// Unavailable state. /// /// 3 Unavailable }; /// /// Enumeration for TTS mode. /// /// 3 public enum Mode { /// /// Default mode for normal application. /// /// 3 Default = 0, /// /// Notification mode. /// /// 3 Notification = 1, /// /// Accessibility mode. /// /// 3 ScreenReader = 2 }; /// /// Enumeration for error values that can occur. /// /// 3 public enum Error { /// /// Successful, no error. /// /// 3 None, /// /// Out of memory. /// /// 3 OutOfMemory, /// /// I/O error. /// /// 3 IoError, /// /// Invalid parameter. /// /// 3 InvalidParameter, /// /// No answer from the TTS service. /// /// 3 TimedOut, /// /// Network is down. /// /// 3 OutOfNetwork, /// /// Permission denied. /// /// 3 PermissionDenied, /// /// TTS not supported. /// /// 3 NotSupported, /// /// Invalid state. /// /// 3 InvalidState, /// /// Invalid voice. /// /// 3 InvalidVoice, /// /// No available engine. /// /// 3 EngineNotFound, /// /// Operation failed. /// /// 3 OperationFailed, /// /// Audio policy blocked. /// /// 3 AudioPolicyBlocked, /// /// Not supported feature of current engine. /// /// 10 NotSupportedFeature, /// /// Service reset. /// /// 10 ServiceReset, /// /// Screen reader off. /// /// 10 ScreenReaderOff }; /// /// Enumeration for the voice types. /// /// 3 public enum Voice { /// /// The automatic voice type. /// /// 3 Auto, /// /// The male voice type. /// /// 3 Male, /// /// The female voice type. /// /// 3 Female, /// /// The child voice type. /// /// 3 Child }; /// /// Enumeration for the states of TTS service. /// /// 10 public enum ServiceState { /// /// Ready state. /// Ready = 0, /// /// Synthesizing state. /// Synthesizing = 1, /// /// Playing state. /// Playing = 2, /// /// Unavailable state. /// Unavailable }; /// /// You can use Text-To-Speech (TTS) API's to read sound data transformed by the engine from input texts. /// Applications can add input-text to queue for reading continuously and control the player that can play, pause, and stop sound data synthesized from text. /// /// 3 public class TtsClient : IDisposable { private IntPtr _handle; private event EventHandler _stateChanged; private event EventHandler _utteranceStarted; private event EventHandler _utteranceCompleted; private event EventHandler _errorOccurred; private event EventHandler _defaultVoiceChanged; private event EventHandler _engineChanged; private event EventHandler _screenReaderChanged; private event EventHandler _serviceStateChanged; private bool disposedValue = false; private readonly Object _stateChangedLock = new Object(); private readonly Object _utteranceStartedLock = new Object(); private readonly Object _utteranceCompletedLock = new Object(); private readonly Object _errorOccurredLock = new Object(); private readonly Object _defaultVoiceChangedLock = new Object(); private readonly Object _engineChangedLock = new Object(); private readonly Object _screenReaderChangedLock = new Object(); private readonly Object _serviceStateChangedLock = new Object(); private TtsStateChangedCB _stateDelegate; private TtsUtteranceStartedCB _utteranceStartedResultDelegate; private TtsUtteranceCompletedCB _utteranceCompletedResultDelegate; private TtsErrorCB _errorDelegate; private TtsDefaultVoiceChangedCB _voiceChangedDelegate; private TtsEngineChangedCB _engineDelegate; private TtsScreenReaderChangedCB _screenReaderDelegate; private TtsServiceStateChangedCB _serviceStateDelegate; private TtsSupportedVoiceCB _supportedvoiceDelegate; /// /// Constructor to create a TTS instance. /// /// 3 /// /// http://tizen.org/feature/speech.synthesis /// /// /// This exception can be due to the following reasons: /// 1. Operation Failed /// 2. Engine Not Found /// /// This exception can be due to out Of memory. /// This exception can be due to TTS not supported. public TtsClient() { IntPtr handle; TtsError error = TtsCreate(out handle); if (error != TtsError.None) { Log.Error(LogTag, "Create Failed with error " + error); throw ExceptionFactory.CreateException(error); } _handle = handle; } /// /// Destructor to destroy TtsClient handle. /// ~TtsClient() { Dispose(false); } /// /// Event to be invoked when TTS state changes. /// /// 3 public event EventHandler StateChanged { add { lock (_stateChangedLock) { if (_stateChanged == null) { _stateDelegate = (IntPtr handle, State previous, State current, IntPtr userData) => { StateChangedEventArgs args = new StateChangedEventArgs(previous, current); _stateChanged?.Invoke(this, args); }; TtsError error = TtsSetStateChangedCB(_handle, _stateDelegate, IntPtr.Zero); if (error != TtsError.None) { Log.Error(LogTag, "Add StateChanged Failed with error " + error); } } _stateChanged += value; } } remove { lock (_stateChangedLock) { _stateChanged -= value; if (_stateChanged == null) { TtsError error = TtsUnsetStateChangedCB(_handle); if (error != TtsError.None) { Log.Error(LogTag, "Remove StateChanged Failed with error " + error); } } } } } /// /// Event to be invoked when the utterance starts. /// /// 3 public event EventHandler UtteranceStarted { add { lock (_utteranceStartedLock) { if (_utteranceStarted == null) { _utteranceStartedResultDelegate = (IntPtr handle, int uttId, IntPtr userData) => { UtteranceEventArgs args = new UtteranceEventArgs(uttId); _utteranceStarted?.Invoke(this, args); }; TtsError error = TtsSetUtteranceStartedCB(_handle, _utteranceStartedResultDelegate, IntPtr.Zero); if (error != TtsError.None) { Log.Error(LogTag, "Add UtteranceStarted Failed with error " + error); } } _utteranceStarted += value; } } remove { lock (_utteranceStartedLock) { _utteranceStarted -= value; if (_utteranceStarted == null) { TtsError error = TtsUnsetUtteranceStartedCB(_handle); if (error != TtsError.None) { Log.Error(LogTag, "Remove UtteranceStarted Failed with error " + error); } } } } } /// /// Event to be invoked when the utterance completes. /// /// 3 public event EventHandler UtteranceCompleted { add { lock (_utteranceCompletedLock) { if (_utteranceCompleted == null) { _utteranceCompletedResultDelegate = (IntPtr handle, int uttId, IntPtr userData) => { UtteranceEventArgs args = new UtteranceEventArgs(uttId); _utteranceCompleted?.Invoke(this, args); }; TtsError error = TtsSetUtteranceCompletedCB(_handle, _utteranceCompletedResultDelegate, IntPtr.Zero); if (error != TtsError.None) { Log.Error(LogTag, "Add UtteranceCompleted Failed with error " + error); } } _utteranceCompleted += value; } } remove { lock (_utteranceCompletedLock) { _utteranceCompleted -= value; if (_utteranceCompleted == null) { TtsError error = TtsUnsetUtteranceCompletedCB(_handle); if (error != TtsError.None) { Log.Error(LogTag, "Remove UtteranceCompleted Failed with error " + error); } } } } } /// /// Event to be invoked when an error occurs. /// /// 4 public event EventHandler ErrorOccurred { add { lock (_errorOccurredLock) { if (_errorOccurred == null) { _errorDelegate = (IntPtr handle, int uttId, TtsError reason, IntPtr userData) => { ErrorOccurredEventArgs args = new ErrorOccurredEventArgs(handle, uttId, reason); _errorOccurred?.Invoke(this, args); }; TtsError error = TtsSetErrorCB(_handle, _errorDelegate, IntPtr.Zero); if (error != TtsError.None) { Log.Error(LogTag, "Add ErrorOccurred Failed with error " + error); } } _errorOccurred += value; } } remove { lock (_errorOccurredLock) { _errorOccurred -= value; if (_errorOccurred == null) { TtsError error = TtsUnsetErrorCB(_handle); if (error != TtsError.None) { Log.Error(LogTag, "Remove ErrorOccurred Failed with error " + error); } } } } } /// /// Event to be invoked when an error occurs. /// /// 3 public event EventHandler DefaultVoiceChanged { add { lock (_defaultVoiceChangedLock) { if (_defaultVoiceChanged == null) { _voiceChangedDelegate = (IntPtr handle, IntPtr previousLanguage, int previousVoiceType, IntPtr currentLanguage, int currentVoiceType, IntPtr userData) => { string previousLanguageString = Marshal.PtrToStringAnsi(previousLanguage); string currentLanguageString = Marshal.PtrToStringAnsi(currentLanguage); DefaultVoiceChangedEventArgs args = new DefaultVoiceChangedEventArgs(previousLanguageString, previousVoiceType, currentLanguageString, currentVoiceType); _defaultVoiceChanged?.Invoke(this, args); }; TtsError error = TtsSetDefaultVoiceChangedCB(_handle, _voiceChangedDelegate, IntPtr.Zero); if (error != TtsError.None) { Log.Error(LogTag, "Add DefaultVoiceChanged Failed with error " + error); } } _defaultVoiceChanged += value; } } remove { lock (_defaultVoiceChangedLock) { _defaultVoiceChanged -= value; if (_defaultVoiceChanged == null) { TtsError error = TtsUnsetDefaultVoiceChangedCB(_handle); if (error != TtsError.None) { Log.Error(LogTag, "Remove DefaultVoiceChanged Failed with error " + error); } } } } } /// /// Event to be invoked to detect engine change. /// /// 3 public event EventHandler EngineChanged { add { lock (_engineChangedLock) { if (_engineChanged == null) { _engineDelegate = (IntPtr handle, IntPtr engineId, IntPtr language, int voiceType, bool needCredential, IntPtr userData) => { string engineIdString = Marshal.PtrToStringAnsi(engineId); string languageString = Marshal.PtrToStringAnsi(language); EngineChangedEventArgs args = new EngineChangedEventArgs(engineIdString, languageString, voiceType, needCredential); _engineChanged?.Invoke(this, args); }; TtsError error = TtsSetEngineChangedCB(_handle, _engineDelegate, IntPtr.Zero); if (error != TtsError.None) { Log.Error(LogTag, "Add EngineChanged Failed with error " + error); } } _engineChanged += value; } } remove { lock (_engineChangedLock) { _engineChanged -= value; if (_engineChanged == null) { TtsError error = TtsUnsetEngineChangedCB(_handle); if (error != TtsError.None) { Log.Error(LogTag, "Remove EngineChanged Failed with error " + error); } } } } } /// /// Event to be invoked to detect screen reader status change. /// /// 9 public event EventHandler ScreenReaderChanged { add { lock (_screenReaderChangedLock) { if (_screenReaderChanged == null) { _screenReaderDelegate = (IntPtr handle, bool isOn, IntPtr userData) => { ScreenReaderChangedEventArgs args = new ScreenReaderChangedEventArgs(isOn); _screenReaderChanged?.Invoke(this, args); }; TtsError error = TtsSetScreenReaderChangedCB(_handle, _screenReaderDelegate, IntPtr.Zero); if (error != TtsError.None) { Log.Error(LogTag, "Add ScreenReaderChanged Failed with error " + error); } } _screenReaderChanged += value; } } remove { lock (_screenReaderChangedLock) { _screenReaderChanged -= value; if (_screenReaderChanged == null) { TtsError error = TtsUnsetScreenReaderChangedCB(_handle); if (error != TtsError.None) { Log.Error(LogTag, "Remove ScreenReaderChanged Failed with error " + error); } } } } } /// /// Event to be invoked when the state of TTS service changes. /// /// 10 public event EventHandler ServiceStateChanged { add { lock (_serviceStateChangedLock) { if (_serviceStateChanged == null) { _serviceStateDelegate = (IntPtr handle, ServiceState previous, ServiceState current, IntPtr userData) => { ServiceStateChangedEventArgs args = new ServiceStateChangedEventArgs(previous, current); _serviceStateChanged?.Invoke(this, args); }; TtsError error = TtsSetServiceStateChangedCB(_handle, _serviceStateDelegate, IntPtr.Zero); if (error != TtsError.None) { Log.Error(LogTag, "Add ServiceStateChanged Failed with error " + error); } } _serviceStateChanged += value; } } remove { lock (_serviceStateChangedLock) { _serviceStateChanged -= value; if (_serviceStateChanged == null) { TtsError error = TtsUnsetStateChangedCB(_handle); if (error != TtsError.None) { Log.Error(LogTag, "Remove ServiceStateChanged Failed with error " + error); } } } } } /// /// Gets the default voice set by the user. /// /// 3 /// /// The default voice in TTS. /// /// /// The default voice SupportedVoice value. /// public SupportedVoice DefaultVoice { get { string language; int voiceType; TtsError error = TtsGetDefaultVoice(_handle, out language, out voiceType); if (error != TtsError.None) { Log.Error(LogTag, "DefaultVoice Failed with error " + error); return new SupportedVoice(); } return new SupportedVoice(language, voiceType); } } /// /// Gets the maximum byte size for text. /// /// 3 /// /// The Maximum byte size for text. /// /// /// The Default Voice SupportedVoice value, 0 if unable to get the value. /// ///
        /// The Client must be in the  state.
        /// 
public uint MaxTextSize { get { uint maxTextSize; TtsError error = TtsGetMaxTextSize(_handle, out maxTextSize); if (error != TtsError.None) { Log.Error(LogTag, "MaxTextSize Failed with error " + error); return 0; } return maxTextSize; } } /// /// Gets the current TTS state. /// /// 3 /// /// The current state of TTS. /// /// /// Current TTS State value. /// public State CurrentState { get { State state; TtsError error = TtsGetState(_handle, out state); if (error != TtsError.None) { Log.Error(LogTag, "CurrentState Failed with error " + error); return State.Unavailable; } return state; } } /// /// Gets the current state of TTS service. /// /// /// The current state of TTS service. /// /// 10 public ServiceState CurrentServiceState { get { ServiceState state; TtsError error = TtsGetServiceState(_handle, out state); if (error != TtsError.None) { Log.Error(LogTag, "CurrentServiceState Failed with error " + error); return ServiceState.Unavailable; } return state; } } /// /// The TTS Mode can be set using this property. /// /// 3 /// /// The current TTS mode (default, screen-reader, notification). /// /// /// The Mode value. /// /// /// This exception can be due to the following reasons while setting the value: /// 1. Operation Failed /// 2. Engine Not Found /// /// This exception can be due to out Of memory. /// This exception can be due to TTS not supported. ///
        /// The Client must be in the  state.
        /// 
public Mode CurrentMode { get { Mode mode = Mode.Default; TtsError error = TtsGetMode(_handle, out mode); if (error != TtsError.None) { Log.Error(LogTag, "Get Mode Failed with error " + error); return Mode.Default; } return mode; } set { TtsError error; error = TtsSetMode(_handle, value); if (error != TtsError.None) { Log.Error(LogTag, "Set Mode Failed with error " + error); throw ExceptionFactory.CreateException(error); } } } /// /// Gets the current status of screen reader. /// /// 9 /// /// The current status of screen reader. /// /// /// Boolean value whether screen reader is on or off. /// /// /// http://tizen.org/feature/speech.synthesis /// /// This exception can be due to TTS not supported. public bool IsScreenReaderOn { get { bool isOn = true; TtsError error = TtsCheckScreenReaderOn(_handle, out isOn); if (error != TtsError.None) { Log.Error(LogTag, "Fail to check screen reader on with error " + error); return false; } return isOn; } } /// /// Sets the application credential. /// /// 3 /// . /// The credential string. /// /// /// http://tizen.org/feature/speech.synthesis /// /// This exception can be due to an invalid state. /// This exception can be due to TTS not supported. /// This exception can be due to improper value provided while setting the value. ///
        /// The Client must be in the  or  state.
        /// 
public void SetCredential(string credential) { TtsError error = TtsSetCredential(_handle, credential); if (error != TtsError.None) { Tizen.Log.Error(LogTag, "SetCredential Failed with error " + error); throw ExceptionFactory.CreateException(error); } } /// /// Connects to the TTS service asynchronously. /// /// 3 /// /// http://tizen.org/feature/speech.synthesis /// /// /// This exception can be due to the following reasons while setting the value: /// 1. Invalid state /// 2. Screen reader off /// /// This exception can be due to TTS not supported. ///
        /// The Client must be in the  state.
        /// 
/// /// If this function is successful, the Client will be in the state. /// If this function is unsuccessful, ErrorOccurred event will be invoked. /// public void Prepare() { TtsError error = TtsPrepare(_handle); if (error != TtsError.None) { Log.Error(LogTag, "Prepare Failed with error " + error); throw ExceptionFactory.CreateException(error); } } /// /// Disconnects from the TTS service. /// /// 3 /// /// http://tizen.org/feature/speech.synthesis /// /// This exception can be due to an invalid state. /// This exception can be due to TTS not supported. ///
        /// The Client must be in the  state.
        /// 
/// /// If this function is successful, the Client will be in the state. /// public void Unprepare() { TtsError error = TtsUnprepare(_handle); if (error != TtsError.None) { Log.Error(LogTag, "Unprepare Failed with error " + error); throw ExceptionFactory.CreateException(error); } } /// /// Retrieves all supported voices of the current engine. /// /// 3 /// /// The list of SupportedVoice. /// /// /// http://tizen.org/feature/speech.synthesis /// /// /// This exception can be due to the following reasons: /// 1. Engine Not Found /// 2. Operation Failed /// /// This exception can be due to TTS not supported. public IEnumerable GetSupportedVoices() { List voicesList = new List(); _supportedvoiceDelegate = (IntPtr handle, IntPtr language, int voiceType, IntPtr userData) => { string lang = Marshal.PtrToStringAnsi(language); SupportedVoice voice = new SupportedVoice(lang, voiceType); voicesList.Add(voice); return true; }; TtsError error = TtsForeachSupportedVoices(_handle, _supportedvoiceDelegate, IntPtr.Zero); if (error != TtsError.None) { Log.Error(LogTag, "GetSupportedVoices Failed with error " + error); throw ExceptionFactory.CreateException(error); } return voicesList; } /// /// Gets the private data from TTS engine. /// /// 3 /// /// The key string. /// /// /// The data corresponding to the provided key. /// /// /// http://tizen.org/feature/speech.synthesis /// /// /// This exception can be due to the following reasons: /// 1. Invalid State /// 2. Engine Not found /// 3. Operation Failure /// /// This exception can be due to TTS not supported. ///
        /// The Client must be in the  state.
        /// 
public string GetPrivateData(string key) { string data; TtsError error = TtsGetPrivateData(_handle, key, out data); if (error != TtsError.None) { Log.Error(LogTag, "GetPrivateData Failed with error " + error); throw ExceptionFactory.CreateException(error); } return data; } /// /// Sets the private data to tts engine. /// /// 3 /// /// The key string. /// /// /// The data string. /// /// /// http://tizen.org/feature/speech.synthesis /// /// /// This exception can be due to the following reasons: /// 1. Invalid State /// 2. Engine Not found /// 3. Operation Failure /// /// This exception can be due to TTS not supported. /// This exception can be due to improper value provided while setting the value. ///
        /// The Client must be in the  state.
        /// 
public void SetPrivateData(string key, string data) { TtsError error = TtsSetPrivateData(_handle, key, data); if (error != TtsError.None) { Log.Error(LogTag, "SetPrivateData Failed with error " + error); throw ExceptionFactory.CreateException(error); } } /// /// Gets the speed range. /// /// 3 /// /// The SpeedRange value. /// /// /// http://tizen.org/feature/speech.synthesis /// /// /// This exception can be due to the following reasons: /// 1. Invalid State /// 2. Operation Failure /// /// This exception can be due to TTS not supported. ///
        /// The Client must be in the  state.
        /// 
public SpeedRange GetSpeedRange() { int min = 0, max = 0, normal = 0; TtsError error = TtsGetSpeedRange(_handle, out min, out normal, out max); if (error != TtsError.None) { Log.Error(LogTag, "GetSpeedRange Failed with error " + error); throw ExceptionFactory.CreateException(error); } return new SpeedRange(min, normal, max); } /// /// Adds a text to the queue. /// /// 3 /// /// Locale MUST be set for text validation check. /// /// /// An input text. /// /// /// The language selected from the SupportedVoice.Language Property obtained from GetSupportedVoices()(e.g. 'NULL'(Automatic),'en_US'). /// /// /// The voice type selected from the SupportedVoice.VoiceType Property obtained from GetSupportedVoices(). /// /// /// A speaking speed (e.g.0 for Auto or the value from SpeedRange Property). /// /// /// The utterance ID. /// /// /// http://tizen.org/feature/speech.synthesis /// /// /// This exception can be due to the following reasons: /// 1. Invalid State /// 2. Operation Failure /// 3. Invalid Voice /// 4. Screen reader off /// /// This exception can be due to TTS not supported. /// This exception can be due to permission denied. /// This exception can be due to improper value provided while setting the value. ///
        /// The Client must be in the , , or  state.
        /// 
public int AddText(string text, string language, int voiceType, int speed) { int id; TtsError error = TtsAddText(_handle, text, language, voiceType, speed, out id); if (error != TtsError.None) { Log.Error(LogTag, "AddText Failed with error " + error); throw ExceptionFactory.CreateException(error); } return id; } /// /// Starts synthesizing voice from the text and plays the synthesized audio data. /// /// 3 /// /// http://tizen.org/feature/speech.synthesis /// /// /// This exception can be due to the following reasons: /// 1. Invalid State /// 2. Operation Failure /// 3. Out of Network /// 4. Screen reader off /// /// This exception can be due to TTS not supported. /// This exception can be due to permission denied. ///
        /// The Client must be in the  or  state.
        /// 
/// /// If this function succeeds, the Client will be in the state. /// public void Play() { TtsError error = TtsPlay(_handle); if (error != TtsError.None) { Log.Error(LogTag, "Play Failed with error " + error); throw ExceptionFactory.CreateException(error); } } /// /// Stops playing the utterance and clears the queue. /// /// 3 /// /// http://tizen.org/feature/speech.synthesis /// /// /// This exception can be due to the following reasons: /// 1. Invalid Stat /// 2. Operation Failure /// 3. Screen reader off /// /// This exception can be due to TTS not supported. ///
        /// The Client must be in the , , or  state.
        /// 
/// /// If this function succeeds, the Client will be in the state. /// This function will remove all text added via AddText() and synthesized sound data. /// public void Stop() { TtsError error = TtsStop(_handle); if (error != TtsError.None) { Log.Error(LogTag, "Stop Failed with error " + error); throw ExceptionFactory.CreateException(error); } } /// /// Pauses the currently playing utterance. /// /// 3 /// /// http://tizen.org/feature/speech.synthesis /// /// /// This exception can be due to the following reasons: /// 1. Invalid State /// 2. Operation Failure /// 3. Screen reader off /// /// This exception can be due to TTS not supported. ///
        /// The Client must be in the  state.
        /// 
/// /// If this function succeeds, the Client will be in the state. /// public void Pause() { TtsError error = TtsPause(_handle); if (error != TtsError.None) { Log.Error(LogTag, "Pause Failed with error " + error); throw ExceptionFactory.CreateException(error); } } /// /// Repeats the last added text. /// /// 10 /// /// The RepeatedText instance which stores the text to repeat and its utterance ID. /// /// /// http://tizen.org/feature/speech.synthesis /// /// /// This exception can be due to the following reasons: /// 1. Invalid State /// 2. Operation Failure /// 3. Screen reader off /// /// This exception can be due to TTS not supported. ///
        /// The Client must be in the  state.
        /// 
/// /// If this function succeeds, the Client will be in the state. /// public RepeatedText Repeat() { TtsError error = TtsRepeat(_handle, out string text, out int uttId); if (error != TtsError.None) { Log.Error(LogTag, "Repeat Failed with error " + error); throw ExceptionFactory.CreateException(error); } return new RepeatedText(text, uttId); } /// /// Method to release resources. /// /// 3 public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// /// Method to release resources. /// /// 3 /// /// The boolean value for destoying tts handle. /// protected virtual void Dispose(bool disposing) { if (!disposedValue) { if (_handle != IntPtr.Zero) { TtsError error = TtsDestroy(_handle); if (error != TtsError.None) { Log.Error(LogTag, "Destroy Failed with error " + error); } _handle = IntPtr.Zero; } disposedValue = true; } } } }