// Performs download to disk cache protected virtual void DownloadToDiskCache(TTSClipData clipData, Action <TTSClipData, string, string> onDownloadComplete) { // Add delegates if needed AddDelegates(); // Check if cached to disk & log string downloadPath = DiskCacheHandler.GetDiskCachePath(clipData); bool found = DiskCacheHandler.IsCachedToDisk(clipData); LogClip($"Disk Cache {(found ? "Found" : "Missing")}\nPath: {downloadPath}", clipData); // Found if (found) { onDownloadComplete?.Invoke(clipData, downloadPath, string.Empty); return; } // Fail if not preloaded if (Application.isPlaying && clipData.diskCacheSettings.DiskCacheLocation == TTSDiskCacheLocation.Preload) { onDownloadComplete?.Invoke(clipData, downloadPath, "File is not Preloaded"); return; } // Return error clipData.onDownloadComplete += (error) => onDownloadComplete(clipData, downloadPath, error); // Download to cache & then stream WebHandler.RequestDownloadFromWeb(clipData, downloadPath); }
// Play begin protected virtual void OnPlaybackBegin(TTSClipData clipData) { // If already speaking, stop doing so OnPlaybackCancel(); // Remove previous clip if (clipData != _lastClip && _lastClip != null) { _lastClip = null; } // Apply clip _lastClip = clipData; // If clip missing if (_lastClip == null || _lastClip.clip == null) { Debug.LogError("TTS Speaker - Clip destroyed prior to playback"); return; } // Started speaking _speaking = true; Events?.OnStartSpeaking?.Invoke(this, _lastClip.textToSpeak); // Play clip & wait _source.PlayOneShot(_lastClip.clip); _player = StartCoroutine(OnPlaybackWait()); }
/// <summary> /// Perform clip unload /// </summary> /// <param name="clipID"></param> private void OnUnloadBegin(TTSClipData clipData) { // Abort if currently preparing if (clipData.loadState == TTSClipLoadState.Preparing) { // Cancel web stream WebHandler?.CancelWebStream(clipData); // Cancel web download to cache WebHandler?.CancelWebDownload(clipData, GetDiskCachePath(clipData.textToSpeak, clipData.clipID, clipData.voiceSettings, clipData.diskCacheSettings)); // Cancel disk cache stream DiskCacheHandler?.CancelDiskCacheStream(clipData); } // Destroy clip else if (clipData.clip != null) { MonoBehaviour.DestroyImmediate(clipData.clip); } // Clip is now unloaded SetClipLoadState(clipData, TTSClipLoadState.Unloaded); // Unload LogClip($"Unload Clip", clipData); Events?.OnClipUnloaded?.Invoke(clipData); }
// Load complete protected virtual void OnLoadComplete(TTSClipData clipData, string error) { // Incorrect clip, ignore if (clipData != _loadingClip) { return; } // Loading complete _loadingClip = null; // Load failed if (clipData.clip == null) { Debug.LogError($"TTS Speaker - Load Clip - Failed\n{error}"); Events?.OnClipLoadFailed?.Invoke(this, clipData.textToSpeak); return; } // Load success Events?.OnClipLoadSuccess?.Invoke(this, clipData.textToSpeak); // Play clip OnPlaybackBegin(clipData); }
/// <summary> /// Logs for TTSService /// </summary> protected virtual void LogClip(string logMessage, TTSClipData clipData, TTSLogType logType = TTSLogType.Info) { StringBuilder builder = new StringBuilder(); builder.AppendLine(logMessage); if (clipData != null) { builder.AppendLine($"Voice: {(clipData.voiceSettings == null ? "Default" : clipData.voiceSettings.settingsID)}"); builder.AppendLine($"Text: {clipData.textToSpeak}"); builder.AppendLine($"ID: {clipData.clipID}"); TTSDiskCacheLocation cacheLocation = TTSDiskCacheLocation.Stream; if (DiskCacheHandler != null) { TTSDiskCacheSettings settings = clipData.diskCacheSettings; if (settings == null) { settings = DiskCacheHandler.DiskCacheDefaultSettings; } if (settings != null) { cacheLocation = settings.DiskCacheLocation; } } builder.AppendLine($"Cache: {cacheLocation}"); } Log(builder.ToString(), logType); }
/// <summary> /// Add clip to cache and ensure it is most recently referenced /// </summary> /// <param name="clipData"></param> public void AddClip(TTSClipData clipData) { // Do not add null if (clipData == null) { return; } // Remove from order bool wasAdded = true; int clipIndex = _clipOrder.IndexOf(clipData.clipID); if (clipIndex != -1) { wasAdded = false; _clipOrder.RemoveAt(clipIndex); } // Add clip _clips[clipData.clipID] = clipData; // Add to end of order _clipOrder.Add(clipData.clipID); // Evict least recently used clips while (IsCacheFull() && _clipOrder.Count > 0) { RemoveClip(_clipOrder[0]); } // Call add delegate if (wasAdded && _clips.Keys.Count > 0) { OnClipAdded?.Invoke(clipData); } }
/// <summary> /// Perform a download for a TTS audio clip /// </summary> /// <param name="textToSpeak">Text to be spoken in clip</param> /// <param name="clipID">Unique clip id</param> /// <param name="voiceSettings">Custom voice settings</param> /// <param name="diskCacheSettings">Custom disk cache settings</param> /// <param name="onDownloadComplete">Callback when file has finished downloading</param> /// <returns>Generated TTS clip data</returns> public TTSClipData DownloadToDiskCache(string textToSpeak, string clipID, TTSVoiceSettings voiceSettings, TTSDiskCacheSettings diskCacheSettings, Action <TTSClipData, string, string> onDownloadComplete = null) { TTSClipData clipData = CreateClipData(textToSpeak, clipID, voiceSettings, diskCacheSettings); DownloadToDiskCache(clipData, onDownloadComplete); return(clipData); }
// Begin a load protected virtual void OnLoadBegin(string textToSpeak, string clipID, TTSVoiceSettings voiceSettings, TTSDiskCacheSettings diskCacheSettings) { // Load begin Events?.OnClipLoadBegin?.Invoke(this, textToSpeak); // Perform load request _loadingClip = TTSService.Instance.Load(textToSpeak, clipID, voiceSettings, diskCacheSettings, OnLoadComplete); }
// Cancel load protected virtual void OnLoadAbort() { // Abort any loading if (_loadingClip != null) { Events?.OnClipLoadAbort?.Invoke(this, _loadingClip.textToSpeak); _loadingClip = null; } }
// Load begin private void OnLoadBegin(TTSClipData clipData) { // Now preparing SetClipLoadState(clipData, TTSClipLoadState.Preparing); // Begin load LogClip($"Load Clip", clipData); Events?.OnClipCreated?.Invoke(clipData); }
// On web download complete private void OnWebDownloadCancel(TTSClipData clipData, string downloadPath) { // Invoke clip callback & clear clipData.onDownloadComplete?.Invoke(CANCEL_WARNING); clipData.onDownloadComplete = null; // Log LogClip($"Download Clip - Canceled\nPath: {downloadPath}", clipData); Events?.Download?.OnDownloadCancel?.Invoke(clipData, downloadPath); }
// On web download complete private void OnWebDownloadError(TTSClipData clipData, string downloadPath, string error) { // Invoke clip callback & clear clipData.onDownloadComplete?.Invoke(error); clipData.onDownloadComplete = null; // Log LogClip($"Download Clip - Failed\nPath: {downloadPath}", clipData, TTSLogType.Error); Events?.Download?.OnDownloadError?.Invoke(clipData, downloadPath, error); }
// On web download complete private void OnWebDownloadSuccess(TTSClipData clipData, string downloadPath) { // Invoke clip callback & clear clipData.onDownloadComplete?.Invoke(string.Empty); clipData.onDownloadComplete = null; // Log LogClip($"Download Clip - Success\nPath: {downloadPath}", clipData); Events?.Download?.OnDownloadSuccess?.Invoke(clipData, downloadPath); }
/// <summary> /// Force a runtime cache unload /// </summary> public void Unload(TTSClipData clipData) { if (RuntimeCacheHandler != null) { RuntimeCacheHandler.RemoveClip(clipData.clipID); } else { OnUnloadBegin(clipData); } }
// Clip data private void OnClipGUI(TTSClipData clip) { // Generation Settings WitEditorUI.LayoutKeyLabel("Text", clip.textToSpeak); WitEditorUI.LayoutKeyObjectLabels("Voice Settings", clip.voiceSettings); WitEditorUI.LayoutKeyObjectLabels("Cache Settings", clip.diskCacheSettings); // Clip Settings EditorGUILayout.TextField("Clip ID", clip.clipID); EditorGUILayout.ObjectField("Clip", clip.clip, typeof(AudioClip), true); // Load Settings WitEditorUI.LayoutKeyLabel("Load State", clip.loadState.ToString()); WitEditorUI.LayoutKeyLabel("Load Progress", (clip.loadProgress * 100f).ToString() + "%"); }
// Compare clip with new text protected virtual bool IsClipSame(string clipID, TTSClipData clipData) { // Not same if (clipData == null) { return(false); } // Not same text if (!string.Equals(clipID, clipData.clipID, StringComparison.CurrentCultureIgnoreCase)) { return(false); } // Success return(true); }
private void OnStreamCancel(TTSClipData clipData, bool fromDisk) { // Handled as an error SetClipLoadState(clipData, TTSClipLoadState.Error); // Invoke clipData.onPlaybackReady?.Invoke(CANCEL_WARNING); clipData.onPlaybackReady = null; // Callback delegate LogClip($"{(fromDisk ? "Disk" : "Web")} Stream Canceled", clipData); Events?.Stream?.OnStreamCancel?.Invoke(clipData); // Unload clip Unload(clipData); }
// Clip unloaded externally protected virtual void OnClipUnload(TTSClipData clipData) { // Handle abort for loading if (clipData == _loadingClip) { OnLoadAbort(); } // Cancel playback & remove clip else if (clipData == _lastClip) { // Cancel playback OnPlaybackCancel(); // Remove reference _lastClip = null; } }
private void OnStreamReady(TTSClipData clipData, bool fromDisk) { // Refresh cache for file size RuntimeCacheHandler?.AddClip(clipData); // Now loaded SetClipLoadState(clipData, TTSClipLoadState.Loaded); // Invoke playback is ready clipData.onPlaybackReady?.Invoke(string.Empty); clipData.onPlaybackReady = null; // Callback delegate LogClip($"{(fromDisk ? "Disk" : "Web")} Stream Ready", clipData); Events?.Stream?.OnStreamReady?.Invoke(clipData); }
private void OnStreamError(TTSClipData clipData, string error, bool fromDisk) { // Error SetClipLoadState(clipData, TTSClipLoadState.Error); // Invoke playback is ready clipData.onPlaybackReady?.Invoke(error); clipData.onPlaybackReady = null; // Stream error LogClip($"{(fromDisk ? "Disk" : "Web")} Stream Error\nError: {error}", clipData, TTSLogType.Error); Events?.Stream?.OnStreamError?.Invoke(clipData, error); // Unload clip Unload(clipData); }
// Returns an error if request is not valid private string IsRequestValid(TTSClipData clipData, WitConfiguration configuration) { // Invalid tts string valid = IsValid(); if (!string.IsNullOrEmpty(valid)) { return(valid); } // Invalid clip if (clipData == null) { return("No clip data provided"); } // Success return(string.Empty); }
/// <summary> /// Unload all audio clips from the runtime cache /// </summary> public void UnloadAll() { // Failed TTSClipData[] clips = RuntimeCacheHandler?.GetClips(); if (clips == null) { return; } // Copy array TTSClipData[] copy = new TTSClipData[clips.Length]; clips.CopyTo(copy, 0); // Unload all clips foreach (var clip in copy) { Unload(clip); } }
/// <summary> /// Creates new clip data or returns existing cached clip /// </summary> /// <param name="textToSpeak">Text to speak</param> /// <param name="clipID">Unique clip id</param> /// <param name="voiceSettings">Voice settings</param> /// <param name="diskCacheSettings">Disk Cache settings</param> /// <returns>Clip data structure</returns> protected virtual TTSClipData CreateClipData(string textToSpeak, string clipID, TTSVoiceSettings voiceSettings, TTSDiskCacheSettings diskCacheSettings) { // Use default voice settings if none are set if (voiceSettings == null && VoiceProvider != null) { voiceSettings = VoiceProvider.VoiceDefaultSettings; } // Use default disk cache settings if none are set if (diskCacheSettings == null && DiskCacheHandler != null) { diskCacheSettings = DiskCacheHandler.DiskCacheDefaultSettings; } // Determine clip id if empty if (string.IsNullOrEmpty(clipID)) { clipID = GetClipID(textToSpeak, voiceSettings); } // Get clip from runtime cache if applicable TTSClipData clipData = GetRuntimeCachedClip(clipID); if (clipData != null) { return(clipData); } // Generate new clip data clipData = new TTSClipData() { clipID = clipID, textToSpeak = textToSpeak, voiceSettings = voiceSettings, diskCacheSettings = diskCacheSettings, loadState = TTSClipLoadState.Unloaded, loadProgress = 0f, queryParameters = VoiceProvider?.EncodeVoiceSettings(voiceSettings) }; // Return generated clip return(clipData); }
/// <summary> /// Remove clip from cache immediately /// </summary> /// <param name="clipID"></param> public void RemoveClip(string clipID) { // Id not found if (!_clips.ContainsKey(clipID)) { return; } // Remove from dictionary TTSClipData clipData = _clips[clipID]; _clips.Remove(clipID); // Remove from order list int clipIndex = _clipOrder.IndexOf(clipID); _clipOrder.RemoveAt(clipIndex); // Call remove delegate OnClipRemoved?.Invoke(clipData); }
/// <summary> /// Method for cancelling a running load request /// </summary> /// <param name="clipData">Clip request data</param> public bool CancelWebDownload(TTSClipData clipData, string downloadPath) { // Ignore if not performing if (!_webDownloads.ContainsKey(clipData.clipID)) { return(false); } // Get request WitUnityRequest request = _webDownloads[clipData.clipID]; _webDownloads.Remove(clipData.clipID); // Destroy immediately request?.Unload(); // Download cancelled WebDownloadEvents?.OnDownloadCancel?.Invoke(clipData, downloadPath); // Success return(true); }
/// <summary> /// Cancel web stream /// </summary> /// <param name="clipID">Unique clip id</param> public bool CancelWebStream(TTSClipData clipData) { // Ignore without if (!_webStreams.ContainsKey(clipData.clipID)) { return(false); } // Get request WitUnityRequest request = _webStreams[clipData.clipID]; _webStreams.Remove(clipData.clipID); // Destroy immediately request?.Unload(); // Call delegate WebStreamEvents?.OnStreamCancel?.Invoke(clipData); // Success return(true); }
/// <summary> /// Method for performing a web load request /// </summary> /// <param name="clipData">Clip request data</param> /// <param name="onStreamSetupComplete">Stream setup complete: returns clip and error if applicable</param> public void RequestStreamFromWeb(TTSClipData clipData) { // Stream begin WebStreamEvents?.OnStreamBegin?.Invoke(clipData); // Check if valid string validError = IsRequestValid(clipData, RequestSettings.configuration); if (!string.IsNullOrEmpty(validError)) { WebStreamEvents?.OnStreamError?.Invoke(clipData, validError); return; } // Ignore if already performing if (_webStreams.ContainsKey(clipData.clipID)) { CancelWebStream(clipData); } // Request tts _webStreams[clipData.clipID] = WitUnityRequest.RequestTTSStream(RequestSettings.configuration, clipData.textToSpeak, clipData.queryParameters, (progress) => clipData.loadProgress = progress, (clip, error) => { _webStreams.Remove(clipData.clipID); clipData.clip = clip; if (string.IsNullOrEmpty(error)) { WebStreamEvents?.OnStreamReady?.Invoke(clipData); } else { WebStreamEvents?.OnStreamError?.Invoke(clipData, error); } }); }
/// <summary> /// Method for performing a web load request /// </summary> /// <param name="clipData">Clip request data</param> /// <param name="downloadPath">Path to save clip</param> public void RequestDownloadFromWeb(TTSClipData clipData, string downloadPath) { // Begin WebDownloadEvents?.OnDownloadBegin?.Invoke(clipData, downloadPath); // Ensure valid string validError = IsRequestValid(clipData, RequestSettings.configuration); if (!string.IsNullOrEmpty(validError)) { WebDownloadEvents?.OnDownloadError?.Invoke(clipData, downloadPath, validError); return; } // Abort if already performing if (_webDownloads.ContainsKey(clipData.clipID)) { CancelWebDownload(clipData, downloadPath); } // Request tts _webDownloads[clipData.clipID] = WitUnityRequest.RequestTTSDownload(downloadPath, RequestSettings.configuration, clipData.textToSpeak, clipData.queryParameters, (progress) => clipData.loadProgress = progress, (error) => { _webDownloads.Remove(clipData.clipID); if (string.IsNullOrEmpty(error)) { WebDownloadEvents?.OnDownloadSuccess?.Invoke(clipData, downloadPath); } else { WebDownloadEvents?.OnDownloadError?.Invoke(clipData, downloadPath, error); } }); }
/// <summary> /// Whether a specific clip should be cached /// </summary> /// <param name="clipData">Clip data</param> /// <returns>True if should be cached</returns> public bool ShouldCacheToDisk(TTSClipData clipData) => DiskCacheHandler != null && DiskCacheHandler.ShouldCacheToDisk(clipData);
// On web download begin private void OnWebDownloadBegin(TTSClipData clipData, string downloadPath) { LogClip($"Download Clip - Begin\nPath: {downloadPath}", clipData); Events?.Download?.OnDownloadBegin?.Invoke(clipData, downloadPath); }