convert my script for use in unity on android make it work with Unity-friendly coroutine-based networking (using UnityWebRequest),
JSON serialization with Unity's built-in JsonUtility.

keep the script name I want it the same in unity

using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;

public class TTSClient
{
    private static readonly HttpClient HttpClient = new();
    private static readonly JsonSerializerOptions JsonOpts = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase };

    private readonly string _url, _text, _apiKey, _format, _outputFilePath;
    private readonly int _chunkLength, _maxNewTokens;
    private readonly float _topP, _temperature, _repetitionPenalty;
    private readonly int? _seed;
    private readonly List<string> _referenceTexts;
    private readonly List<byte[]> _referenceAudios;

    public TTSClient(
        string url,
        string text,
        string apiKey,
        List<string> referenceTexts,
        List<byte[]> referenceAudios,
        string format = "wav",
        int chunkLength = 0,
        float topP = 0.9f,
        float temperature = 0.9f,
        int? seed = 0,
        int maxNewTokens = 0,
        float repetitionPenalty = 1.1f,
        string outputFilePath = null)
    {
        _url = url ?? throw new ArgumentNullException(nameof(url));
        _text = text ?? throw new ArgumentNullException(nameof(text));
        _apiKey = apiKey ?? throw new ArgumentNullException(nameof(apiKey));
        _referenceTexts = referenceTexts ?? throw new ArgumentNullException(nameof(referenceTexts));
        _referenceAudios = referenceAudios ?? throw new ArgumentNullException(nameof(referenceAudios));
        _format = format;
        _chunkLength = chunkLength;
        _topP = topP;
        _temperature = temperature;
        _seed = seed;
        _maxNewTokens = maxNewTokens;
        _repetitionPenalty = repetitionPenalty;

        // Set authorization once per client instance — better to set on static HttpClient logic if possible outside
        if (HttpClient.DefaultRequestHeaders.Authorization == null)
            HttpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _apiKey);

        _outputFilePath = outputFilePath ?? Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), $"output.{_format}");
    }

    public async Task<(bool success, string message)> SendRequestAndSaveAudioAsync()
    {
        var requestData = new
        {
            text = _text,
            references = CreateReferenceObjects(),
            normalize = false,
            format = _format,
            max_new_tokens = _maxNewTokens,
            chunk_length = _chunkLength,
            top_p = _topP,
            repetition_penalty = _repetitionPenalty,
            temperature = _temperature,
            streaming = false,
            use_memory_cache = "on",
            seed = _seed
        };

        // Serialize JSON directly to a UTF8 byte array to avoid extra allocation.
        byte[] jsonBytes = JsonSerializer.SerializeToUtf8Bytes(requestData, JsonOpts);
        using var content = new ByteArrayContent(jsonBytes);
        content.Headers.ContentType = new MediaTypeHeaderValue("application/json");

        HttpResponseMessage response;
        try
        {
            response = await HttpClient.PostAsync(_url, content).ConfigureAwait(false);
        }
        catch (Exception ex)
        {
            return (false, $"Request error: {ex.Message}");
        }

        if (!response.IsSuccessStatusCode)
        {
            string error = await SafeReadResponseContentAsync(response).ConfigureAwait(false);
            return (false, $"Server error {(int)response.StatusCode}: {error}");
        }

        try
        {
            // Stream directly from response to file (no in-memory buffering of large audio)
            await using var responseStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
            await using var fs = new FileStream(
                _outputFilePath, FileMode.Create, FileAccess.Write, FileShare.None,
                bufferSize: 81920, useAsync: true);

            await responseStream.CopyToAsync(fs).ConfigureAwait(false);

            return (true, _outputFilePath);
        }
        catch (Exception ex)
        {
            return (false, $"Failed to save audio: {ex.Message}");
        }
    }

    private List<object> CreateReferenceObjects()
    {
        int count = Math.Min(_referenceTexts.Count, _referenceAudios.Count);
        var refs = new List<object>(count);

        for (int i = 0; i < count; i++)
        {
            string base64Audio = _referenceAudios[i] is { Length: > 0 } data ? Convert.ToBase64String(data) : null;
            refs.Add(new { text = _referenceTexts[i], audio = base64Audio });
        }

        return refs;
    }

    private static async Task<string> SafeReadResponseContentAsync(HttpResponseMessage response)
    {
        try
        {
            return await response.Content.ReadAsStringAsync().ConfigureAwait(false);
        }
        catch
        {
            return "<Unable to read response content>";
        }
    }
}