Tous les produits
Search
Centre de documentation

Platform For AI:CosyVoice 2.0 API

Dernière mise à jour :Sep 08, 2026

CosyVoice 2.0 met à disposition des API pour la gestion des fichiers audio et la création de synthèse vocale. Cette rubrique décrit les types d'API disponibles ainsi que les modalités de leur utilisation.

Prérequis

  1. Déployez le service WebUI CosyVoice2 ou un service haute performance avec séparation du frontend et du backend, puis montez OSS ou un autre service de stockage pour héberger vos fichiers audio téléchargés. Pour obtenir des instructions détaillées, consultez Démarrage rapide : Déployer un service WebUI ou Démarrage rapide : Déployer un service haute performance avec séparation du frontend et du backend.

  2. Récupérez l'endpoint du service et le jeton d'accès (token).

    Important
    • Pour un service haute performance avec séparation du frontend et du backend, effectuez les appels d'API vers le service frontend.

    • Lors des tests de charge, privilégiez l'adresse VPC. Elle offre une vitesse de traitement nettement supérieure à celle du réseau public.

    1. Cliquez sur le nom de votre service CosyVoice2 WebUI ou de votre service frontend. Sur la page Overview, dans la section Basic Information, cliquez sur View Endpoint Information.

    2. Dans l'onglet Shared Gateway du panneau Invocation Method, récupérez l'endpoint du service (EAS_SERVICE_URL) et le jeton (EAS_TOKEN). Supprimez le caractère / final de l'endpoint du service.

      Remarque
      • Pour utiliser l'endpoint public, le client doit disposer d'un accès au réseau public.

      • Pour utiliser l'endpoint VPC, le client doit se trouver dans le même VPC que le service.

  3. Préparez le fichier audio.

    Ce guide utilise le fichier audio de référence suivant :

    • Fichier WAV de référence : zero_shot_prompt.wav

    • Transcription de référence : I hope you'll do even better than me.

Liste des API

Fichier audio de référence

  • Appel d'API

    URL de la requête

    <EAS_SERVICE_URL>/api/v1/audio/reference_audio

    Méthode de requête

    POST

    En-têtes de requête

    Authorization: Bearer <EAS_TOKEN>

    Paramètres de requête

    • file : Obligatoire. Fichier audio à télécharger. Formats pris en charge : MP3, WAV et PCM. Type : file. Par défaut : aucun.

    • text : Obligatoire. Contenu textuel associé au fichier audio. Type : string.

    Paramètres de réponse

    Renvoie un objet audio de référence. Pour plus de détails, consultez la liste des paramètres de réponse.

    Liste des paramètres de réponse

    Paramètre

    Type

    Description

    id

    string

    Identifiant du fichier audio.

    filename

    string

    Nom du fichier audio.

    bytes

    integer

    Taille du fichier en octets.

    created_at

    integer

    Horodatage de création du fichier.

    text

    string

    Contenu textuel associé au fichier.

  • Exemple de requête

    cURL

    # Replace <EAS_SERVICE_URL> with your service URL and <EAS_TOKEN> with your token. 
    curl -XPOST <EAS_SERVICE_URL>/api/v1/audio/reference_audio \
        --header 'Authorization: Bearer <EAS_TOKEN>' \
        --form 'file=@"/home/xxxx/zero_shot_prompt.wav"' \
        --form 'text="I hope you will do even better than me."'

    Python

    import requests
    response = requests.post(
        "<EAS_SERVICE_URL>/api/v1/audio/reference_audio",  # Replace <EAS_SERVICE_URL> with the service URL.
        headers={
            "Authorization": "Bearer <EAS_TOKEN>",  # Replace <EAS_TOKEN> with your token.
        },
        files={
            "file": open("./zero_shot_prompt.wav", "rb"),
        },
        data={
            "text": "I hope you will do even better than me."
        }
    )
    print(response.text)
    
  • Exemple de réponse

    {
        "id": "50a5fdb9-c3ad-445a-adbb-3be32750****",
        "filename": "zero_shot_prompt.wav",
        "bytes": 111496,
        "created_at": 1748416005,
        "text": "I hope you will do even better than me."
    }

Fichiers audio de référence

  • Référence de l'API

    Endpoint

    <EAS_SERVICE_URL>/api/v1/audio/reference_audio

    Méthode de requête

    GET

    En-têtes de requête

    Authorization: Bearer <EAS_TOKEN>

    Paramètres de requête

    • limit : Facultatif. Nombre maximal de fichiers à renvoyer. Type : integer. Par défaut : 100.

    • order : Facultatif. Type : string. Spécifie l'ordre de tri des objets selon leur horodatage created_at. Les valeurs valides sont :

      • asc : Croissant

      • desc (par défaut) : Décroissant

    Corps de la réponse

    Contient un tableau d'objets audio de référence. Pour plus de détails, consultez les paramètres de réponse pour le téléchargement d'audio.

  • Exemple de requête

    cURL

    # Replace <EAS_SERVICE_URL> with the service URL and <EAS_TOKEN> with your token.
    curl -XGET <EAS_SERVICE_URL>/api/v1/audio/reference_audio?limit=10&order=desc \
          --header 'Authorization: Bearer <EAS_TOKEN>'

    Python

    import requests
    response = requests.get(
        "<EAS_SERVICE_URL>/api/v1/audio/reference_audio",  # Replace <EAS_SERVICE_URL> with the service URL. 
        headers={
            "Authorization": "Bearer <EAS_TOKEN>",  # Replace <EAS_TOKEN> with your token.
        }
    )
    print(response.text)
  • Exemple de réponse

    [
        {
            "id": "50a5fdb9-c3ad-445a-adbb-3be32750****",
            "filename": "zero_shot_prompt.wav",
            "bytes": 111496,
            "created_at": 1748416005,
            "text": "I hope you'll do even better than me."
        }
    ]

Fichier audio de référence

  • Appel d'API

    URL de la requête

    <EAS_SERVICE_URL>/api/v1/audio/reference_audio/<reference_audio_id>

    Méthode de requête

    GET

    En-têtes de requête

    Authorization: Bearer <EAS_TOKEN>

    Paramètres de chemin

    reference_audio_id : Obligatoire. Identifiant de l'audio de référence. Consultez Lister les fichiers audio de référence. Type : String. Par défaut : aucun.

    Paramètres de réponse

    Un objet audio de référence. Pour plus de détails, consultez la liste des paramètres de réponse.

  • Exemple de requête

    cURL

    # Replace <EAS_SERVICE_URL>, <EAS_TOKEN>, and <reference_audio_id> with your service URL, token, and reference audio ID.
    curl -XGET <EAS_SERVICE_URL>/api/v1/audio/reference_audio/<reference_audio_id> \
          --header 'Authorization: Bearer <EAS_TOKEN>'

    Python

    import requests
    response = requests.get(
        "<EAS_SERVICE_URL>/api/v1/audio/reference_audio/<reference_audio_id>",  # Replace <EAS_SERVICE_URL> with your service URL and <reference_audio_id> with your reference audio ID.
        headers={
            "Authorization": "Bearer <EAS_TOKEN>",  # Replace <EAS_TOKEN> with your token.
        }
    )
    print(response.text)
    
  • Exemple de réponse

    {
        "id": "50a5fdb9-c3ad-445a-adbb-3be32750****",
        "filename": "zero_shot_prompt.wav",
        "bytes": 111496,
        "created_at": 1748416005,
        "text": "I hope you'll do even better than me."
    }

Supprimer un fichier audio de référence

  • Requête

    Endpoint

    <EAS_SERVICE_URL>/api/v1/audio/reference_audio/<reference_audio_id>

    Méthode de requête

    DELETE

    En-têtes de requête

    Authorization: Bearer <EAS_TOKEN>

    Paramètres de chemin

    reference_audio_id : Obligatoire. Spécifie l'identifiant unique de l'audio de référence. Pour retrouver cet identifiant, consultez Lister les fichiers audio de référence. Type : String. Par défaut : aucun.

    Paramètres de réponse

    Renvoie un objet audio de référence.

  • Exemple de requête

    cURL

    # Replace <EAS_SERVICE_URL> with the service endpoint, <EAS_TOKEN> with the service token, and <reference_audio_id> with the reference audio ID.
    curl -XDELETE <EAS_SERVICE_URL>/api/v1/audio/reference_audio/<reference_audio_id> \
          --header 'Authorization: Bearer <EAS_TOKEN>'

    Python

    import requests
    response = requests.delete(
        "<EAS_SERVICE_URL>/api/v1/audio/reference_audio/<reference_audio_id>",  # Replace <EAS_SERVICE_URL> with the service endpoint and <reference_audio_id> with the reference audio ID.
        headers={
            "Authorization": "Bearer <EAS_TOKEN>",  # Replace <EAS_TOKEN> with the service token.
        }
    )
    print(response.text)
    
  • Exemple de réponse

    {
        "code": "OK",
        "message": "reference audio: c0939ce0-308e-4073-918f-91ac88e3**** deleted.",
        "data": {}
    }

Créer une synthèse vocale (Text-to-Speech)

  • Appel d'API

    URL de la requête

    <EAS_SERVICE_URL>/api/v1/audio/speech

    Méthode de requête

    POST

    En-têtes de requête

    • Authorization: Bearer <EAS_TOKEN>

    • Content-Type: application/json

    Paramètres de requête

    • model : Obligatoire. Nom du modèle. Actuellement, seul CosyVoice2-0,5B est pris en charge. Type : string. Par défaut : aucun.

    • input : Obligatoire. Objet contenant les paramètres d'entrée. Type : object. Par défaut : aucun. Il inclut les paramètres suivants :

      • mode : Obligatoire. Mode de synthèse vocale. Type : string. Les valeurs valides incluent :

        • fast_replication : réplication rapide

        • cross_lingual_replication : réplication interlangues

        • natural_language_replication : réplication en langage naturel

      • text : Obligatoire. Texte à synthétiser. Type : string. Par défaut : aucun.

      • reference_audio_id : Obligatoire. Identifiant de l'audio de référence. Pour obtenir cet identifiant, consultez Lister les fichiers audio de référence. Type : string. Par défaut : aucun.

      • instruct : Facultatif. Instructions permettant d'ajuster dynamiquement le style vocal (ton, émotion ou débit). Ce paramètre s'applique uniquement lorsque mode est défini sur natural_language_replication. Type : string. Par défaut : aucun.

      • sample_rate : Facultatif. Fréquence d'échantillonnage. Par défaut : 24000.

      • bit_rate : Facultatif. Débit binaire. Type : string. Par défaut : 192k. Valeurs prises en charge : 16k, 32k, 48k, 64k, 128k, 192k, 256k, 320k, 384k.

      • volume : Facultatif. Volume. Type : float. Par défaut : 1,0. Par exemple, 3,0 signifie un volume triplé, et 0,8 signifie 80 % du volume initial.

      • speed : Facultatif. Débit de parole. La valeur doit se situer dans l'intervalle [0,5 ; 2,0]. Type : float. Par défaut : 1.0.

      • output_format : Facultatif. Format du fichier audio de sortie. Formats pris en charge : wav, mp3, pcm. Par défaut : wav.

    • stream : Facultatif. Indique si la sortie doit être diffusée en continu (streaming). Type : boolean. Par défaut : true.

    Paramètres de réponse

    Réponse de l'API. Pour plus de détails sur les paramètres de réponse, consultez la liste des paramètres de réponse.

    Liste des paramètres de réponse

    Paramètre

    Type

    Description

    request_id

    string

    Identifiant de la requête.

    output

    string

    Contenu de la sortie.

    audio

    object

    Contenu audio.

    audio.id

    string

    Identifiant de l'audio.

    audio.data

    string

    Flux d'octets WAV encodé sous forme de chaîne Base64.

    finish_reason

    string

    • Sortie non continue (non-streaming) : Renvoie null en cas de succès. En cas d'échec, renvoie la raison.

    • Sortie en continu (streaming) : Renvoie null pendant la génération de l'audio. Renvoie "stop" lorsque le modèle termine naturellement ou atteint une condition stop issue des paramètres d'entrée.

    usage

    integer

    Taille du fichier en octets.

  • Exemples de requête

    Appel non continu (Non-streaming)

    cURL

    • Remplacez par l'endpoint du service et par votre jeton.

    • Remplacez par l'identifiant de l'audio de référence.

    # Replace <EAS_SERVICE_URL> with the service endpoint and <EAS_TOKEN> with your token.
    # Replace <reference_audio_id> with the reference audio ID. 
    curl -XPOST <EAS_SERVICE_URL>/api/v1/audio/speech \
    --header 'Authorization: Bearer <EAS_TOKEN>' \
    --header 'Content-Type: application/json' \
    --data '{
        "model": "CosyVoice2-0.5B",
        "input": {
            "mode": "natural_language_replication",
            "reference_audio_id": "<reference_audio_id>",
            "text": "Receiving a birthday gift from a friend far away filled my heart with joy. The surprise and warm wishes made me smile.",
            "speed": 1.0,
            "output_format": "mp3",
            "sample_rate": 32000,
            "bit_rate": "48k",
            "volume": 2.0,
            "instruct": "Speak in Sichuan dialect."
        },
        "stream": false
    }'

    L'API renvoie un objet JSON contenant les données audio encodées en Base64 :

    {"output":{"finish_reason":null,"audio":{"data":"DNgB9djax9su3Ba...."}},"request_id": "f90a65be-f47b-46b5-9ddc-70bae550****"}

    Python

    Installez les dépendances suivantes :

    pip install requests==2.32.3 packaging==24.2
    import json
    import base64
    import requests
    from packaging import version
    required_version = "2.32.3"
    if version.parse(requests.__version__) < version.parse(required_version):
        raise RuntimeError(f"requests version must >= {required_version}")
    with requests.post(
        "<EAS_SERVICE_URL>/api/v1/audio/speech",    # Replace <EAS_SERVICE_URL> with the service endpoint.
                                                    # Example: "http://cosyvoice-frontend-test.1534081855183999.cn-hangzhou.pai-eas.aliyuncs.com/api/v1/audio/speech"
        headers={
            "Authorization": "Bearer <EAS_TOKEN>",  # Replace <EAS_TOKEN> with the service token.
            "Content-Type": "application/json",
        },
        json={
            "model": "CosyVoice2-0.5B",
            "input": {
                "mode": "natural_language_replication",
                "reference_audio_id": "<reference_audio_id>",  # Replace <reference_audio_id> with the reference audio ID.
                "text": "Receiving a birthday gift from a friend far away filled my heart with joy. The surprise and warm wishes made me smile.",
                "output_format": "mp3",
                "sample_rate": 24000,
                "speed": 1.0,
                "bit_rate": "48k",
                "volume": 2.0,
                "instruct": "Speak in Sichuan dialect."
            },
            "stream": False
        },
        timeout=10
    ) as response:
        if response.status_code != 200:
            print(response.text)
            exit()
        data = json.loads(response.content)
        encode_buffer = data['output']['audio']['data']
        decode_buffer = base64.b64decode(encode_buffer)
        with open('./http_non_stream.mp3', 'wb') as f:
            f.write(decode_buffer)
    

    Appel en continu (Streaming)

    cURL

    • Remplacez par l'endpoint du service et par votre jeton.

    • Remplacez par l'identifiant de l'audio de référence.

    # Replace <EAS_SERVICE_URL> with the service endpoint and <EAS_TOKEN> with your token.
    # Replace <reference_audio_id> with the reference audio ID. 
    curl -XPOST <EAS_SERVICE_URL>/api/v1/audio/speech \
    --header 'Authorization: Bearer <EAS_TOKEN>' \
    --header 'Content-Type: application/json' \
    --data '{
        "model": "CosyVoice2-0.5B",
        "input": {
            "mode": "natural_language_replication",
            "reference_audio_id": "<reference_audio_id>",
            "text": "Receiving a birthday gift from a friend far away filled my heart with joy. The surprise and warm wishes made me smile.",
            "speed": 1.0,
            "output_format": "mp3",
            "sample_rate": 32000,
            "bit_rate": "48k",
            "volume": 2.0,
            "instruct": "Speak in Sichuan dialect."
        },
        "stream": true
    }'

    La réponse est un flux d'événements envoyés par le serveur (SSE) contenant des fragments audio encodés en Base64 :

    data: {"output":{"finish_reason":null,"audio":{"data":"DNgB9djax9su3Ba...."}},"request_id": "f90a65be-f47b-46b5-9ddc-70bae550****"}
    data: {"output":{"finish_reason":null,"audio":{"data":"DNgB9djax9su3Ba...."}},"request_id": "f90a65be-f47b-46b5-9ddc-70bae550****"}
    data: {"output":{"finish_reason":null,"audio":{"data":"DNgB9djax9su3Ba...."}},"request_id": "f90a65be-f47b-46b5-9ddc-70bae550****"}
    data: {"output":{"finish_reason":null,"audio":{"data":"DNgB9djax9su3Ba...."}},"request_id": "f90a65be-f47b-46b5-9ddc-70bae550****"}

    Python

    Installez le client SSE pour Python :

    pip install requests==2.32.3 packaging==24.2 sseclient-py==1.8.0 -i http://mirrors.cloud.aliyuncs.com/pypi/simple --trusted-host mirrors.cloud.aliyuncs.com
    import io
    import json
    import base64
    import wave
    import requests
    from sseclient import SSEClient			# pip install sseclient-py
    from packaging import version
    required_version = "2.32.3"
    if version.parse(requests.__version__) < version.parse(required_version):
        raise RuntimeError(f"requests version must >= {required_version}")
    with requests.post(
        "<EAS_SERVICE_URL>/api/v1/audio/speech",    # Replace <EAS_SERVICE_URL> with the service endpoint.
                                                    # Example: "http://cosyvoice-frontend-test.1534081855183999.cn-hangzhou.pai-eas.aliyuncs.com/api/v1/audio/speech"
        headers={
            "Authorization": "Bearer <EAS_TOKEN>",  # Replace <EAS_TOKEN> with the service token.
            "Content-Type": "application/json",
        },
        json={
            "model": "CosyVoice2-0.5B",
            "input": {
                "mode": "natural_language_replication",
                "reference_audio_id": "<reference_audio_id>",  # Replace <reference_audio_id> with the reference audio ID. 
                "text": "Receiving a birthday gift from a friend far away filled my heart with joy. The surprise and warm wishes made me smile.",
                "output_format": "mp3",
                "sample_rate": 24000,
                "speed": 1.0,
                "bit_rate": "48k",
                "volume": 2.0,
                "instruct": "Speak in Sichuan dialect.",
                "debug": True
            },
            "stream": True
        },
        stream=True,
        timeout=10
    ) as response:
        if response.status_code != 200:
            print(response.text)
            exit()
        messages = SSEClient(response)
        with open('./http_stream.mp3', 'wb') as f:
            for i, msg in enumerate(messages.events()):
                print(f"Event: {msg.event}, Data: {msg.data}")
                data = json.loads(msg.data)
                if data['error'] is not None:
                    print(data['error'])
                    break
                metrics = data['metrics']
                print(f"{metrics=}")
                encode_buffer = data['output']['audio']['data']
                decode_buffer = base64.b64decode(encode_buffer)
                f.write(decode_buffer)
    

    API WebSocket

    Installez la dépendance suivante :

    pip install websocket-client==1.8.0 -i http://mirrors.cloud.aliyuncs.com/pypi/simple --trusted-host mirrors.cloud.aliyuncs.com
    #!/usr/bin/python
    # -*- coding: utf-8 -*-
    import base64
    import json
    import logging
    import sys
    import time
    import uuid
    import traceback
    import websocket
    class TTSClient:
        def __init__(self, api_key, uri, params, log_level='INFO'):
            """
        Initialize a TTSClient instance.
        Parameters:
            api_key (str): The API key used for authentication.
            uri (str): The WebSocket service endpoint.
        """
            self._api_key = api_key
            self._uri = uri  # Replace with your WebSocket endpoint.
            self._task_id = str(uuid.uuid4())  # Generate a unique task ID.
            self._ws = None  # WebSocketApp instance.
            self._task_started = False  # Whether task-started is received.
            self._task_finished = False  # Whether task-finished / task-failed is received.
            self._check_params(params)
            self._params = params
            self._chunk_metrics = []
            self._metrics = {}
            self._first_package_time = None
            self._last_time = None
            self._init_log(log_level)
            self.audio_data = b''
        def _init_log(self, log_level):
            self._log = logging.getLogger("ws_client")
            log_formatter = logging.Formatter('%(asctime)s - Process(%(process)s) - %(levelname)s - %(message)s')
            stream_handler = logging.StreamHandler(stream=sys.stdout)
            stream_handler.setFormatter(log_formatter)
            self._log.addHandler(stream_handler)
            self._log.setLevel(log_level)
        def get_metrics(self):
            """Gets the performance metrics of the synthesis task."""
            return self._metrics
        def _check_params(self, params):
            assert 'mode' in params and params['mode'] in ['fast_replication', 'cross_lingual_replication', 'natural_language_replication']
            assert 'reference_audio_id' in params
            assert 'output_format' in params and params['output_format'] in ['wav', 'mp3', 'pcm']
            if params['mode'] == 'natural_language_replication':
                assert 'instruct' in params and params['instruct']
            else:
                if 'instruct' in params:
                    del params['instruct']
        def on_open(self, ws):
            """
        Callback function for when the WebSocket connection is established.
        Sends a 'run-task' command to start the speech synthesis task.
        """
            self._log.debug("WebSocket connection established.")
            # Build the run-task command.
            run_task_cmd = {
                "header": {
                    "action": "run-task",
                    "task_id": self._task_id,
                    "streaming": "duplex"
                },
                "payload": {
                    "task_group": "audio",
                    "task": "tts",
                    "function": "SpeechSynthesizer",
                    "model": "cosyvoice-v2",
                    "parameters": {
                        "mode": self._params['mode'],
                        "reference_audio_id": self._params['reference_audio_id'],
                        "output_format": self._params.get('output_format', 'wav'),
                        "sample_rate": self._params.get('sample_rate', 24000),
                        "bit_rate": self._params.get('bit_rate', '192k'),
                        "volume": self._params.get('volume', 1.0),
                        "instruct": self._params.get('instruct', ''),
                        "speed": self._params.get('speed', 1.0),
                        "debug": True,
                    },
                    "input": {}
                }
            }
            # Send the run-task command.
            ws.send(json.dumps(run_task_cmd))
            self._log.debug("Sent the run-task command.")
        def on_message(self, ws, message):
            """
        Callback function for when a message is received.
        Handles text and binary messages.
        """
            try:
                msg_json = json.loads(message)
                # self._log.debug(f"Received JSON message: {msg_json}")
                self._log.debug(f"Received JSON message: {msg_json['header']['event']}")
                if "header" in msg_json:
                    header = msg_json["header"]
                    if "event" in header:
                        event = header["event"]
                        if event == "task-started":
                            self._log.debug("Task started.")
                            self._task_started = True
                            # Send continue-task commands.
                            for text in self._params['texts']:
                                self.send_continue_task(text)
                            # Send finish-task after all continue-task commands are sent.
                            self.send_finish_task()
                            self._last_time = time.time()
                        elif event == "result-generated":
                            metrics = msg_json['payload']['metrics']
                            cur_time = time.time()
                            metrics['client_cost_time'] = cur_time - self._last_time
                            self._last_time = cur_time
                            encode_data = msg_json["payload"]["output"]["audio"]["data"]
                            decode_data = base64.b64decode(encode_data)
                            self._log.debug(f"Received audio data. Size: {len(decode_data)} bytes.")
                            self.audio_data += decode_data
                            metrics['client_rtf'] = metrics['client_cost_time'] / metrics['speech_len']
                            self._chunk_metrics.append(metrics)
                        elif event == "task-finished":
                            self._metrics = {
                                'client_first_package_time': self._chunk_metrics[0]['client_cost_time'],
                                "client_rtf": sum([m["client_cost_time"] for m in self._chunk_metrics]) / sum([m["speech_len"] for m in self._chunk_metrics]),
                                'client_cost_time': sum([m["client_cost_time"] for m in self._chunk_metrics]),
                                'speech_len': sum([m["speech_len"] for m in self._chunk_metrics]),
                                'server_first_package_time': self._chunk_metrics[0]['server_cost_time'],
                                'server_rtf': sum([m["server_cost_time"] for m in self._chunk_metrics]) / sum([m["speech_len"] for m in self._chunk_metrics]),
                                'server_cost_time': sum([m["server_cost_time"] for m in self._chunk_metrics]),
                                "generate_time": sum([m["generate_time"] for m in self._chunk_metrics])
                            }
                            self._log.debug(f"Task finished. Request performance metrics: client_first_package_time: {self._metrics['client_first_package_time']:.3f}, client_rtf: {self._metrics['client_rtf']:.3f}, client_cost_time: {self._metrics['client_cost_time']:.3f}, speech_len: {self._metrics['speech_len']:.3f}, server_cost_time: {self._metrics['server_cost_time']:.3f}, generate_time: {self._metrics['generate_time']:.3f}")
                            self._task_finished = True
                            self.close(ws)
                        elif event == "task-failed":
                            self._log.error(f"Task failed: {msg_json}")
                            self._task_finished = True
                            self.close(ws)
            except json.JSONDecodeError as e:
                self._log.error(f"JSON parsing failed: {str(e)}\t{traceback.format_exc()}")
        def on_error(self, ws, error):
            """Callback for when an error occurs."""
            self._log.error(f"WebSocket error: {error}\t{traceback.format_exc()}")
            self._metrics = {'error': error}
        def on_close(self, ws, close_status_code, close_msg):
            """Callback for when the connection is closed."""
            self._log.debug(f"WebSocket closed: {close_msg} ({close_status_code})")
        def send_continue_task(self, text):
            """Sends a 'continue-task' command with the text to be synthesized."""
            cmd = {
                "header": {
                    "action": "continue-task",
                    "task_id": self._task_id,
                    "streaming": "duplex"
                },
                "payload": {
                    "input": {
                        "text": text
                    }
                }
            }
            self._ws.send(json.dumps(cmd))
            self._log.debug(f"Sent 'continue-task' command with text: {text}")
        def send_finish_task(self):
            """Sends a 'finish-task' command to end the speech synthesis task."""
            cmd = {
                "header": {
                    "action": "finish-task",
                    "task_id": self._task_id,
                    "streaming": "duplex"
                },
                "payload": {
                    "input": {}
                }
            }
            self._ws.send(json.dumps(cmd))
            self._log.debug("Sent 'finish-task' command.")
        def close(self, ws):
            """Actively closes the connection."""
            if ws and ws.sock and ws.sock.connected:
                ws.close()
                self._log.debug("Connection actively closed.")
        def run(self):
            """Starts the WebSocket client."""
            # Set request headers for authentication.
            header = {
                "Authorization": f"Bearer {self._api_key}",
            }
            # Create a WebSocketApp instance.
            self._ws = websocket.WebSocketApp(
                self._uri,
                header=header,
                on_open=self.on_open,
                on_message=self.on_message,
                on_error=self.on_error,
                on_close=self.on_close
            )
            self._log.debug("Listening for WebSocket messages...")
            self._ws.run_forever()  # Start the persistent connection.
    # Example usage
    if __name__ == "__main__":
        API_KEY = "<EAS_TOKEN>"                                      # Replace <EAS_TOKEN> with the service token.
        SERVER_URI = "ws://<EAS_SERVICE_URL>/api-ws/v1/audio/speech" # Replace <EAS_SERVICE_URL> with the service endpoint.
                                                                     # Example: "ws://cosyvoice-frontend-test.1534081855183999.cn-hangzhou.pai-eas.aliyuncs.com/api-ws/v1/audio/speech"
        texts = [
            "Receiving a birthday gift from a friend far away filled my heart with joy. The surprise and warm wishes made me smile."
        ]
        params = {
            "mode": "natural_language_replication",
            "texts": texts,
            "reference_audio_id": "<reference_audio_id>",
            "speed": 1.0,
            "output_format": "mp3",
            "sample_rate": 24000,
            "bit_rate": "48k",
            "volume": 2.0,
            "instruct": "Speak in a calm tone."
        }
        client = TTSClient(API_KEY, SERVER_URI, params, log_level='DEBUG')
        client.run()
        with open('./websocket_stream.mp3', 'wb') as wfile:
            wfile.write(client.audio_data)
        metrics = client.get_metrics()
        print(f"{metrics=}")