Code/TikTokSpeech.cs
using System;
using Sandbox;
namespace TikTokTTS;
public class TikTokSpeech : Component
{
/// <summary>
/// The text that will be spoken by the TTS.
/// </summary>
[Property] public string Text { get; set; } = "Hello World!";
/// <summary>
/// The voice that will be used to speak the text.
/// </summary>
[Property] public TikTokVoice Voice { get; set; } = TikTokVoice.en_us_002;
/// <summary>
/// Whether or not the audio should be positional (based on the position of the GameObject that this component is attached to)
/// </summary>
[Property] public bool PositionalAudio { get; set; } = false;
/// <summary>
/// Whether or not to cache the files that are generated by the TTS. When enabled, the files will be stored in the data directory and referenced if the same call is made again.
/// </summary>
[Property] public bool CacheFiles { get; set; } = true;
/// <summary>
/// An event that is fired when the TTS has started speaking.
/// </summary>
[Property] public Action<string, TikTokVoice> OnSpeak { get; set; }
private string VoiceName => Enum.GetName( typeof( TikTokVoice ), Voice );
private string FileName => $"tts/tts-{Text.ToLowerInvariant().UrlEncode()}-{VoiceName}.mp3";
private MusicPlayer MusicPlayer;
/// <summary>
/// Speak the current text using the TTS.
/// </summary>
public async void Speak()
{
MusicPlayer?.Dispose();
if ( CacheFiles )
{
if ( !FileSystem.Data.FileExists( FileName ) )
{
await TikTokTTS.Download( Text, VoiceName, FileName, FileSystem.Data );
}
MusicPlayer = MusicPlayer.Play( FileSystem.Data, FileName );
}
else
{
MusicPlayer = await TikTokTTS.Say( Text, VoiceName );
if ( MusicPlayer is null )
{
Log.Error( "Failed to speak" );
return;
}
}
OnSpeak?.Invoke( Text, Voice );
MusicPlayer.ListenLocal = !PositionalAudio;
}
protected override void OnUpdate()
{
if ( MusicPlayer is not null )
{
MusicPlayer.Position = Transform.Position;
}
}
}