#if UNITY_IOS || UNITY_TVOS || UNITY_EDITOR
using UnityEngine;
using System.Collections;
using System.Linq;
namespace Crosstales.RTVoice.Provider
{
/// iOS voice provider.
public class VoiceProviderIOS : BaseVoiceProvider
{
#region Variables
//private static VoiceProviderIOS instance;
private static System.Collections.Generic.List cachediOSVoices = new System.Collections.Generic.List();
#if !UNITY_EDITOR || CT_DEVELOP
private static string[] speechTextArray;
private static int wordIndex;
private static bool isWorking;
private static Model.Wrapper wrapperNative;
private static bool isPaused;
#endif
#endregion
#region Properties
/*
/// Returns the singleton instance of this class.
/// Singleton instance of this class.
public static VoiceProviderIOS Instance => instance ?? (instance = new VoiceProviderIOS());
*/
public override string AudioFileExtension => "none";
public override AudioType AudioFileType => AudioType.UNKNOWN;
public override string DefaultVoiceName => "Daniel";
public override System.Collections.Generic.List Voices => cachediOSVoices;
public override bool isWorkingInEditor => false;
public override bool isWorkingInPlaymode => false;
public override int MaxTextLength => 256000;
public override bool isSpeakNativeSupported => true;
public override bool isSpeakSupported => false;
public override bool isPlatformSupported => Util.Helper.isIOSBasedPlatform;
public override bool isSSMLSupported => false;
public override bool isOnlineService => false;
public override bool hasCoRoutines => true;
public override bool isIL2CPPSupported => true;
public override bool hasVoicesInEditor => false;
#endregion
#region Wrapper callbacks
#if !UNITY_EDITOR || CT_DEVELOP
/// Receives all voices
/// All voices as text string.
public static void SetVoices(string voicesText)
{
string[] voices = voicesText.Split(new[] {','}, System.StringSplitOptions.RemoveEmptyEntries);
if (voices.Length % 3 == 0)
{
System.Collections.Generic.List voicesList = new System.Collections.Generic.List(60);
//for (int ii = 0; ii < voices.Length; ii += 2)
for (int ii = 0; ii < voices.Length; ii += 3)
{
string name = voices[ii + 1];
string culture = voices[ii + 2];
Model.Voice newVoice = new Model.Voice(name, "iOS voice: " + name + " " + culture,
Util.Helper.AppleVoiceNameToGender(name), "unknown", culture, voices[ii], "Apple");
voicesList.Add(newVoice);
}
cachediOSVoices = voicesList.OrderBy(s => s.Name).ToList();
if (Util.Constants.DEV_DEBUG)
Debug.Log("Voices read: " + cachediOSVoices.CTDump());
}
else
{
Debug.LogWarning($"Voice-string contains wrong number of elements: {voices.Length}");
}
Instance.onVoicesReady();
//NativeMethods.FreeMemory();
}
/// Receives the state of the speaker.
/// The state of the speaker.
public static void SetState(string state)
{
if (state.Equals("Start"))
{
// do nothing
}
else if (state.Equals("Finsish"))
{
isWorking = false;
}
else
{
//cancel
isWorking = false;
}
}
/// Called every time a new word is spoken.
public static void WordSpoken()
{
if (wrapperNative != null)
{
Instance.onSpeakCurrentWord(wrapperNative, speechTextArray, wordIndex);
wordIndex++;
}
}
#endif
#endregion
#region Implemented methods
public override void Load(bool forceReload = false)
{
#if !UNITY_EDITOR || CT_DEVELOP
if (cachediOSVoices?.Count == 0 || forceReload)
{
NativeMethods.RTVGetVoices();
}
else
{
onVoicesReady();
}
#endif
}
public override IEnumerator SpeakNative(Model.Wrapper wrapper)
{
yield return speak(wrapper, true);
}
public override IEnumerator Speak(Model.Wrapper wrapper)
{
yield return speak(wrapper, false);
}
public override IEnumerator Generate(Model.Wrapper wrapper)
{
Debug.LogError("'Generate' is not supported for iOS!");
yield return null;
}
public override void Silence()
{
#if !UNITY_EDITOR || CT_DEVELOP
NativeMethods.RTVStop();
//NativeMethods.FreeMemory();
#endif
base.Silence();
}
public override void Silence(string uid)
{
Silence();
base.Silence(uid);
}
public void Pause()
{
#if !UNITY_EDITOR || CT_DEVELOP
isPaused = true;
#endif
Silence();
}
#endregion
#region Private methods
private IEnumerator speak(Model.Wrapper wrapper, bool isNative)
{
#if !UNITY_EDITOR || CT_DEVELOP
if (wrapper == null)
{
Debug.LogWarning("'wrapper' is null!");
}
else
{
if (string.IsNullOrEmpty(wrapper.Text))
{
Debug.LogWarning("'wrapper.Text' is null or empty!");
}
else
{
yield return null; //return to the main process (uid)
string voiceId = getVoiceId(wrapper);
isPaused = false;
silence = false;
if (!isNative && !wrapper.isPartial)
{
onSpeakAudioGenerationStart(wrapper); //just a fake event if some code needs the feedback...
yield return null;
onSpeakAudioGenerationComplete(wrapper); //just a fake event if some code needs the feedback...
}
if (!wrapper.isPartial)
onSpeakStart(wrapper);
isWorking = true;
speechTextArray = Util.Helper.CleanText(wrapper.Text, false)
.Split(splitCharWords, System.StringSplitOptions.RemoveEmptyEntries);
wordIndex = 0;
wrapperNative = wrapper;
NativeMethods.RTVSpeak(voiceId, wrapper.Text, calculateRate(wrapper.Rate), wrapper.Pitch,
wrapper.Volume);
do
{
yield return null;
} while (isWorking && !silence);
if (Util.Config.DEBUG)
Debug.Log("Text spoken: " + wrapper.Text);
wrapperNative = null;
if (!isPaused)
onSpeakComplete(wrapper);
//NativeMethods.FreeMemory();
}
}
#else
yield return null;
#endif
}
private static float calculateRate(float rate)
{
float result = rate;
if (rate > 1f)
{
//result = (rate + 1f) * 0.5f;
result = 1f + (rate - 1f) * 0.25f;
}
if (Util.Constants.DEV_DEBUG)
Debug.Log("calculateRate: " + result + " - " + rate);
return result;
}
private string getVoiceId(Model.Wrapper wrapper)
{
if (wrapper != null && string.IsNullOrEmpty(wrapper.Voice?.Identifier))
{
if (Util.Config.DEBUG)
Debug.LogWarning(
"'wrapper.Voice' or 'wrapper.Voice.Identifier' is null! Using the OS 'default' voice.");
return Speaker.Instance.VoiceForName(DefaultVoiceName).Identifier;
}
return wrapper != null ? wrapper.Voice?.Identifier : Speaker.Instance.VoiceForName(DefaultVoiceName).Identifier;
}
#endregion
#region Editor-only methods
#if UNITY_EDITOR
public override void GenerateInEditor(Model.Wrapper wrapper)
{
Debug.LogError("'GenerateInEditor' is not supported for iOS!");
}
public override void SpeakNativeInEditor(Model.Wrapper wrapper)
{
Debug.LogError("'SpeakNativeInEditor' is not supported for iOS!");
}
#endif
#endregion
}
/// Native methods (bridge to iOS).
internal static class NativeMethods
{
/*
/// Bridge to the native tts system
/// Name of the voice to speak.
/// Text to speak.
/// Speech rate of the speaker in percent (default: 1, optional).
/// Pitch of the speech in percent (default: 1, optional).
/// Volume of the speaker in percent (default: 1, optional).
[System.Runtime.InteropServices.DllImport("__Internal")]
internal static extern Speak(string name, string text, float rate = 1f, float pitch = 1f, float volume = 1f);
*/
/// Bridge to the native tts system
/// Identifier of the voice to speak.
/// Text to speak.
/// Speech rate of the speaker in percent (default: 1, optional).
/// Pitch of the speech in percent (default: 1, optional).
/// Volume of the speaker in percent (default: 1, optional).
[System.Runtime.InteropServices.DllImport("__Internal")]
internal static extern void RTVSpeak(string id, string text, float rate = 1f, float pitch = 1f, float volume = 1f);
/// Silence the current TTS-provider.
[System.Runtime.InteropServices.DllImport("__Internal")]
internal static extern void RTVGetVoices();
/// Silence the current TTS-provider.
[System.Runtime.InteropServices.DllImport("__Internal")]
internal static extern void RTVStop();
}
}
#endif
// © 2016-2020 crosstales LLC (https://www.crosstales.com)