Tous les produits
Search
Centre de documentation

Alibaba Cloud Model Studio:Connexion WebRTC à Qwen-Audio-Realtime

Dernière mise à jour :Nov 06, 2026

Connexion WebRTC à Qwen-Audio-Realtime

WebRTC utilise des pistes de médias pour transmettre l’audio et DataChannel pour transmettre les événements du modèle et le texte. Le client web peut utiliser l’API native du navigateur, tandis que les autres clients peuvent utiliser des bibliothèques prenant en charge WebRTC standard. Model Studio ne fournit pas de SDK WebRTC dédié.

Ce document décrit la procédure d’intégration WebRTC de ce modèle et fournit un exemple complet côté navigateur.

Périmètre d’application

Ce document concerne Qwen-Audio-Realtime. Les instructions d’intégration existantes couvrent qwen-audio-3.1-realtime-plus, qwen-audio-3.1-realtime-flash, qwen-audio-3.0-realtime-plus et qwen-audio-3.0-realtime-flash. Le modèle de l’exemple est qwen-audio-3.1-realtime-plus.

  • Utilisez l’API Key et l’ID de l’espace de travail correspondant à la région et à l’espace de travail sélectionnés.
  • Domaine de Pékin : {WorkspaceId}.cn-beijing.maas.aliyuncs.com ; domaine de Singapour : {WorkspaceId}.ap-southeast-1.maas.aliyuncs.com.
  • Les API Keys à longue durée de validité sont stockées côté serveur et ne doivent pas être incluses dans les applications mobiles ou de navigateur. Pour leur génération et leurs autorisations, consultez Générer une API Key temporaire.

Pour capturer l’audio dans le navigateur, utilisez un contexte sécurisé ; pour utiliser le microphone, demandez l’autorisation correspondante. Lors de la réception audio, gérez les restrictions de lecture automatique du navigateur.

Authentification et échange SDP

Le navigateur envoie une Offer SDP à l’AppServer métier. AppServer transmet la requête suivante avec l’API Key et renvoie l’Answer SDP sans modification au navigateur ; les API Keys à longue durée de validité ne parviennent pas au frontend.

  • Méthode : POST
  • Adresse de Pékin : https://{WorkspaceId}.cn-beijing.maas.aliyuncs.com/api/v1/webrtc/realtime?model=qwen-audio-3.1-realtime-plus
  • Adresse de Singapour : https://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api/v1/webrtc/realtime?model=qwen-audio-3.1-realtime-plus
  • En-têtes de requête : Authorization: Bearer <API_KEY>, Content-Type: application/sdp
  • Corps de la requête : chaîne Offer SDP.
  • Réponse réussie : HTTP 200, avec l’Answer SDP dans le corps ; un échec renvoie un code 4xx.

WebRTC effectue l’authentification pendant l’échange SDP et ne nécessite pas de jeton AOQ préalable. Dans les exemples ci-dessous, /api/realtime/sdp désigne une interface personnalisée de l’AppServer métier, qui doit implémenter le transfert décrit ci-dessus ; ce n’est pas une URL fournie par Model Studio.

Déroulement

WEBRTC Diagramme de séquence de connexion
  1. Préparez la clé API et les paramètres du modèle cible sur AppServer.
  2. Créez l’objet de connexion client et enregistrez les rappels.
  3. Configurez les directions des médias et les DataChannels sans attacher les pistes média montantes.
  4. Échangez les SDP via AppServer et attendez que la connexion et le canal d’événements soient prêts.
  5. Configurez le modèle selon son protocole. Attachez les pistes média montantes nécessaires après confirmation.
  6. Arrêtez la capture, fermez la connexion et libérez les ressources à la fin de l’interaction.

Remarque

  • L’audio est transmis sur les pistes de médias WebRTC ; l’application doit obtenir la piste audio locale, associer le lecteur distant, puis envoyer et recevoir les messages DataChannel selon les définitions d’événements du modèle cible.

  • L’authentification WebRTC s’effectue lors de l’échange SDP ; aucun jeton AOQ n’est nécessaire. Relayez l’authentification et les SDP via AppServer pour ne pas exposer la clé API à long terme dans le code frontend.

  • Une connexion réussie indique que le canal de transmission est établi, mais ne signifie pas que l’initialisation du modèle est terminée. Avec qwen-audio-3.1-realtime-plus utilisé sur cette page, n’activez les médias en amont nécessaires qu’après avoir envoyé session.update et reçu session.updated.

Exemple complet

Voici un exemple complet côté navigateur. Implémentez d’abord l’interface /api/realtime/sdp de l’AppServer métier comme décrit dans Authentification et échange SDP, puis ouvrez cette page HTML depuis la même origine que l’AppServer, avec le même protocole, hôte et port. Par exemple, la page et /api/realtime/sdp sont toutes deux servies par http://localhost:3000. Le serveur stocke l’API Key et transfère les données SDP ; n’incluez pas d’API Keys à longue durée de validité dans la page. Cliquez sur “Start” pour établir la connexion, puis sur “End” pour libérer les ressources à la fin de l’interaction. Cet exemple établit une connexion à la fois ; rechargez la page pour démarrer une autre session.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>WebRTC access example</title>
</head>
<body>
<button id="start-session" type="button">Start</button>
<button id="end-session" type="button">End</button>
<script>
const pc = new RTCPeerConnection({ iceServers: [ ] });

const channels = new Set();
const senders = new Map();
const remoteAudio = document.createElement("audio");
remoteAudio.controls = true; // Allow manual playback if autoplay is blocked.
remoteAudio.autoplay = true;
document.body.appendChild(remoteAudio);

let localStream = null;
let eventChannel = null;
let stopped = false;
let started = false;
let sessionCreated = false;
let updateSent = false;
let modelReady = false;
let mediaStarted = false;
const requestController = new AbortController();
const media = {
  sendAudio: true,
  receiveAudio: true,
};

function mediaDirection(send, receive) {
  if (send && receive) return "sendrecv";
  if (send) return "sendonly";
  if (receive) return "recvonly";
  return "inactive";
}

async function configureMedia() {
  // Keep m=audio. The target service must support the selected directions.
  const transceiver = pc.addTransceiver("audio", {
    direction: mediaDirection(media.sendAudio, media.receiveAudio),
  });
  if (media.sendAudio) senders.set("audio", transceiver.sender);
  if (!media.sendAudio) return;
  const stream = await navigator.mediaDevices.getUserMedia({
    audio: true,
    video: false,
  });
  // The user may end the call while the permission prompt is open.
  if (stopped) {
    stream.getTracks().forEach(track => track.stop());
    throw new Error("Session ended");
  }
  localStream = stream;
  // sender.track is still empty, so no captured data is sent to the model.
}
// See Send and receive events over DataChannels for bindDataChannel.
pc.ondatachannel = ({ channel }) => bindDataChannel(channel);
const clientChannel = pc.createDataChannel("oai-events");
bindDataChannel(clientChannel);
pc.onconnectionstatechange = () => {
  console.log("WebRTC state:", pc.connectionState);
  if (pc.connectionState === "connected") {
    tryInitializeModel();
  } else if (["failed", "disconnected", "closed"].includes(pc.connectionState)) {
    // This example ends the session; implement recovery as needed.
    endSession();
  }
};

function waitForIceComplete() {
  return new Promise((resolve, reject) => {
    const timer = setTimeout(() => finish(new Error("ICE gathering timed out")), 15000);
    function finish(error) {
      clearTimeout(timer);
      pc.removeEventListener("icegatheringstatechange", check);
      requestController.signal.removeEventListener("abort", cancel);
      error ? reject(error) : resolve();
    }
    function check() {
      if (pc.iceGatheringState === "complete") finish();
    }
    function cancel() { finish(new Error("Session ended")); }
    pc.addEventListener("icegatheringstatechange", check);
    requestController.signal.addEventListener("abort", cancel, { once: true });
    if (stopped) cancel();
    else check();
  });
}

function normalizeAnswerSdp(sdp) {
  return String(sdp).trim().replace(/\r?\n/g, "\r\n") + "\r\n";
}

async function startSession() {
  if (started || stopped) return;
  started = true;
  try {
    await configureMedia();
    if (stopped) return;
    const offer = await pc.createOffer();
    await pc.setLocalDescription(offer);
    await waitForIceComplete();

    // Implement this same-origin application endpoint; it is not a Model Studio API.
    // AppServer forwards SDP with the API key and returns the raw Answer SDP.
    const response = await fetch("/api/realtime/sdp", {
      method: "POST",
      headers: { "Content-Type": "application/sdp" },
      body: pc.localDescription.sdp,
      signal: requestController.signal,
    });
    if (!response.ok) {
      throw new Error(`SDP exchange failed:${response.status} ${await response.text()}`);
    }
    const answerSdp = normalizeAnswerSdp(await response.text());
    if (stopped) return;
    await pc.setRemoteDescription({ type: "answer", sdp: answerSdp });
    // Continue through connectionstatechange, DataChannel, and model events.
  } catch (error) {
    if (!stopped) console.error("Connection failed:", error);
    endSession();
  }
}
function tryInitializeModel() {
  if (stopped || updateSent || !sessionCreated ||
      pc.connectionState !== "connected" ||
      eventChannel?.readyState !== "open") return;
  sendModelEvent({
    event_id: `event_${crypto.randomUUID()}`,
    type: "session.update",
    session: {
      "modalities": media.receiveAudio ? ["audio", "text"] : ["text"],
      "voice": "longanqian_v3.1",
    },
  });
  updateSent = true;
}

async function handleModelEvent(event, channel) {
  if (stopped) return;
  if (event.type === "session.created") {
    sessionCreated = true;
    eventChannel = channel; // Send configuration through the channel that receives session events.
    tryInitializeModel();
  } else if (event.type === "session.updated" && updateSent) {
    modelReady = true;
    await startSendingMedia();
  } else if (event.type === "error") {
    console.error("Model error:", event.error);
    endSession();
  } else {
    // Display text, transcripts, or response status as defined by the selected model. Receive audio over media tracks.
    console.log("Model event:", event);
  }
}
async function startSendingMedia() {
  if (stopped || !modelReady || mediaStarted) return;
  mediaStarted = true;

  for (const track of localStream?.getTracks() ?? []) {

    if (stopped) return;
    const sender = senders.get(track.kind);
    if (sender) await sender.replaceTrack(track);
  }
}

// Register before startSession(). Tracks without streams are also received correctly.
const remoteAudioStream = new MediaStream();
pc.ontrack = ({ track }) => {
  if (stopped) return;
  if (track.kind === "audio" && media.receiveAudio) {
    remoteAudioStream.addTrack(track);
    remoteAudio.srcObject = remoteAudioStream;
    remoteAudio.play().catch(() => {
      console.info("Autoplay is blocked. Use the audio controls to play.");
    });
  }
};
function endSession() {
  if (stopped) return;
  stopped = true;
  modelReady = false;
  sessionCreated = false;
  updateSent = false;
  mediaStarted = false;
  requestController.abort(); // Cancel pending SDP requests and ICE gathering.
  localStream?.getTracks().forEach(track => track.stop());
  localStream = null;
  channels.forEach(channel => channel.close());
  channels.clear();
  eventChannel = null;
  pc.close();
  senders.clear();
  remoteAudioStream.getTracks().forEach(track => track.stop());
  remoteAudio.pause();
  remoteAudio.srcObject = null;
  remoteAudio.remove();
}
function sendModelEvent(event, channel = eventChannel) {
  if (stopped || pc.connectionState !== "connected" ||
      channel?.readyState !== "open") {
    throw new Error("Data channel is not ready");
  }
  channel.send(JSON.stringify(event));
}

function bindDataChannel(channel) {
  channels.add(channel);
  channel.onopen = () => {
    if (stopped) return;
    console.log("DataChannel opened:", channel.label);
    tryInitializeModel();
  };
  channel.onmessage = ({ data }) => {
    if (stopped) return;
    let event;
    try { event = JSON.parse(data); }
    catch (error) {
      console.warn("Cannot parse model event:", error);
      return;
    }
    if (!event || typeof event !== "object") return;
    handleModelEvent(event, channel).catch(error => {
      if (!stopped) console.error("Model event handling failed:", error);
      endSession();
    });
  };
  channel.onerror = error => console.error("DataChannel error:", error);
  channel.onclose = () => {
    channels.delete(channel);
    if (channel === eventChannel) endSession();
  };
  if (channel.readyState === "open") channel.onopen();
}

document.getElementById("start-session").addEventListener("click", startSession);
document.getElementById("end-session").addEventListener("click", endSession);
</script>
</body>
</html>

1. Préparer l’authentification et les paramètres de connexion

AppServer configure l’échange SDP selon le modèle cible, la région et l’espace de travail. Les paramètres suivants concernent les connexions Realtime standard. Pour les paramètres propres à une application, suivez la bonne pratique correspondante.

SettingValue
MéthodePOST
URLhttps://{endpoint}/api/v1/webrtc/realtime?model={model_name}
Content-Typeapplication/sdp
AuthorizationBearer <API_KEY>
Corps de la requêteOffer SDP
Réponse réussieAnswer SDP

Pour les régions, les adresses et l’authentification, consultez Authentification et échange SDP. L’interface de l’AppServer métier est implémentée par l’entreprise et ne fait pas partie de l’API SDK de Model Studio.

2. Initialiser l’objet de connexion et enregistrer les callbacks

Côté web, créez une RTCPeerConnection ; sur les autres plateformes, initialisez la bibliothèque correspondante. Avant d’établir la connexion, enregistrez les gestionnaires suivants :

  • Changements d’état de la connexion : déterminez si la connexion a réussi, a échoué ou a été interrompue.

  • Création, ouverture, messages et fermeture de DataChannel : transporte les événements du modèle.

  • Piste de médias distante : reçoit et lit l’audio, ou traite les autres médias pris en charge par l’application cible.

L’exemple complet crée l’objet de connexion, les variables d’état et les callbacks dans le même script. Chaque appel crée une seule connexion ; une fois l’appel terminé, réinitialisez ces objets pour effectuer un autre appel.

L’objet de connexion et les variables d’état sont détaillés dans Exemple complet.

Enregistrez les écouteurs de messages avant l’initialisation du modèle pour éviter de manquer les événements de session envoyés spontanément par le serveur. N’écoutez pas uniquement les canaux créés par le client : le serveur transmet les événements sur un canal nommé txt ; vous devez donc traiter le callback datachannel.

3. Configurer les directions des médias et les DataChannels

Sélectionner la direction audio

Les directions sont relatives au client. Les émetteurs-récepteurs WebRTC négocient l’envoi et la réception : sendrecv est bidirectionnel, sendonly envoie uniquement et recvonly reçoit uniquement. Les capacités disponibles dépendent aussi de la réponse SDP du serveur et de la prise en charge du modèle.

DirectionConfiguration
Envoyer l’audioNégociez l’envoi audio et préparez le microphone ou une piste audio externe. Ajoutez la piste une fois le modèle prêt.
Recevoir l’audioNégociez la réception audio et configurez la lecture dans le callback de réception d’une piste distante.

Par exemple, une conversation vocale utilise généralement un flux audio bidirectionnel. L’Offer SDP doit inclure la section de média audio m=audio ; ne la supprimez pas simplement parce que l’application n’a pas besoin de lire l’audio.

Directions des médias pour ce modèle

Modèle ou applicationEnvoyer de l’audioRecevoir de l’audioEnvoyer de la vidéoRecevoir de la vidéo
Qwen-Audio-RealtimeÀ activer pour les conversations vocalesÀ activer lorsqu’une réponse vocale est nécessaireNe peut pas être activéNe peut pas être activé

Ce tableau décrit les directions des médias nécessaires à l’application ; il ne garantit pas que toutes les combinaisons puissent être négociées indépendamment. La configuration dépend du modèle cible et des capacités de négociation du serveur.

Préparer les médias sans les envoyer

Négociez les directions requises avant de créer l’Offer. Si le modèle nécessite une confirmation d’initialisation, réservez les capacités d’envoi sans attacher les pistes capturées. Lorsque le modèle est prêt, utilisez replaceTrack sur l’émetteur correspondant pour démarrer la transmission. Enregistrez à l’avance les gestionnaires de réception.

Configurez séparément l’envoi et la réception audio selon les besoins ; ne regroupez pas l’activation du microphone, la lecture audio et l’initialisation du modèle dans une seule opération.

Exemple de configuration des médias

L’exemple suivant présente une conversation vocale bidirectionnelle. Deux options dans media contrôlent respectivement l’envoi et la réception audio ; addTransceiver négocie la direction à l’avance, et la piste capturée reste d’abord stockée localement jusqu’à ce que le modèle soit prêt, puis elle est associée à l’émetteur.

Consultez le code dans l’exemple complet.

Créer les canaux d’événements

Créez un DataChannel avant de créer une Offer pour que la SDP inclue la négociation du canal de données. Consultez Envoi et réception d’événements sur le canal Data pour savoir comment le client et le serveur créent les canaux.

DataChannel envoie les événements d’initialisation du modèle, les événements textuels et de contrôle, et reçoit les résultats, les états et les erreurs du modèle. La piste de médias transmet l’audio.

L’exemple complet crée un canal d’événements client et enregistre le callback du canal serveur avant de créer une Offer.

4. Établir la connexion

Effectuez la connexion dans l’ordre suivant :

  1. Appelez createOffer et setLocalDescription.
  2. Collectez les informations sur les candidats selon les méthodes ICE prises en charge par le serveur ; dans cet exemple, la SDP locale est envoyée une fois la collecte ICE terminée.
  3. Envoyez la SDP locale à l’AppServer métier, qui utilise l’API Key pour appeler l’interface d’échange SDP de Model Studio.
  4. Vérifiez l’état HTTP et, en cas de succès, appelez setRemoteDescription avec l’Answer SDP renvoyée.
  5. Attendez que l’état de la connexion indique un succès et confirmez que le DataChannel utilisé pour envoyer les événements du modèle est ouvert.

La réussite de l’échange SDP ou la fin de setRemoteDescription indique uniquement que l’étape de négociation correspondante est terminée ; elle ne prouve pas que la connexion des médias a réussi. Les événements du modèle ne peuvent pas non plus être envoyés si DataChannel n’est pas à l’état open.

Code de connexion et callbacks d’état

Écoutez connectionstatechange pour déterminer l’état de la connexion et icegatheringstatechange pour attendre la SDP locale complète. L’initialisation du modèle nécessite également l’état open de DataChannel et l’événement de disponibilité du modèle ; elle ne doit pas dépendre uniquement de la fin de l’échange SDP.

L’exemple complet effectue l’échange SDP avec startSession() et traite le résultat de la connexion dans le callback d’état de la connexion.

L’AppServer métier doit transférer l’Offer selon l’adresse et les en-têtes de requête de la section 1 ; l’API d’exemple doit renvoyer du texte SDP brut. Ne transmettez pas directement à setRemoteDescription du contenu incluant des en-têtes de réponse HTTP, des journaux ou des enveloppes JSON.

5. Échanger les événements du modèle et les médias

Initialiser le modèle

Lorsque la connexion et le canal d’événements sont disponibles, initialisez le modèle cible selon son protocole. Conservez les événements d’initialisation serveur et combinez-les avec l’état du canal pour avancer ; ne supposez pas un ordre fixe d’arrivée des rappels.

Par exemple, lorsque vous configurez un modèle avec session.update, construisez l’événement selon son protocole et attendez la confirmation de configuration avant d’envoyer les entrées. Les paramètres de voix, de modalités de sortie, de VAD et de fréquence d’échantillonnage doivent tous provenir de la documentation du modèle actuel.

session.created indique la création de la session ; il ne signifie pas systématiquement que la configuration est appliquée. Pour les modèles exigeant une confirmation de la configuration de session, attendez session.updated.

Les définitions des événements du modèle figurent dans Événements client et Événements serveur. Gérez la transmission des médias et les modes disponibles comme décrit sur cette page.

Exemples d’événements du modèle

RemarqueL’audio est transmis sur la piste de médias et n’utilise pas l’événement WebSocket d’envoi des médias.

L’exemple suivant présente le déroulement session.created → session.update → session.updated pour qwen-audio-3.1-realtime-plus. Configurez les champs selon la référence des événements du modèle. N’associez la piste audio en amont qu’après avoir reçu session.updated, qui confirme la fin de la configuration.

Consultez le code dans l’exemple complet.

Pour le modèle d’exemple qwen-audio-3.1-realtime-plus, l’activation de la sortie audio permet de suivre l’état de génération audio avec response.audio.done ; cet événement ne signifie pas que la lecture audio du client est terminée.

Démarrer la transmission et la réception des médias

Une fois le modèle prêt, associez la piste audio en amont nécessaire. Traitez la sortie du modèle dans le callback de piste distante et analysez en continu les événements renvoyés par DataChannel.

  • L’audio transite par les pistes média RTP ; aucun événement input_audio_buffer.append n’est nécessaire.

  • Les DataChannels transportent le texte et les événements d’état, de contrôle et d’erreur.

Dans l’exemple complet, startSendingMedia() associe la piste en amont et ontrack traite les médias reçus.

6. Terminer l’interaction et libérer les ressources

Lorsque la connexion n’est plus nécessaire, arrêtez les pistes de capture locales de votre application, fermez les DataChannels et le RTCPeerConnection, libérez les lecteurs et effacez l’état de l’application.

Effacez l’état de disponibilité du modèle après un échec ou une déconnexion inattendue. Initialisez à nouveau le modèle sur une nouvelle connexion au lieu de réutiliser l’état de session précédent.

Dans l’exemple complet, endSession() libère la connexion et les ressources de médias.

Appelez endSession() lorsque l’utilisateur clique sur le bouton de fin ou ferme la page.

Envoyer et recevoir des événements sur le canal Data

Vérifiez que le canal est open avant d’appeler send avec un événement sérialisé. Analysez les messages entrants dans son rappel message, y compris sur les canaux créés par le serveur.

Les noms, champs, paramètres et moments d’envoi des événements suivent les définitions des événements client du modèle cible ; l’analyse, les changements d’état, les résultats et la gestion des erreurs après réception suivent les définitions des événements serveur du modèle. Les événements d’initialisation sont envoyés selon les exigences du modèle, puis les événements textuels et de contrôle selon les besoins métier.

Exemple DataChannel

Utilisez la même fonction de liaison pour les canaux créés par le client et le serveur. Elle gère le transport et l’analyse JSON ; handleModelEvent de la section 5 traite la sémantique du modèle. Utilisez sendModelEvent pour l’initialisation, le texte ou le contrôle uniquement lorsque le protocole du modèle le permet.

Dans l’exemple complet, sendModelEvent() et bindDataChannel() envoient et reçoivent les événements.

Dans l’exemple complet, le bouton de démarrage appelle startSession() et le bouton de fin appelle endSession(). Ce sont des fonctions définies dans cet exemple, et non des API WebRTC intégrées.

Définitions des événements Qwen-Audio-Realtime

Modèle ou applicationÉvénements client (envoi)Événements serveur (réception)
Qwen-Audio-RealtimeÉvénements clientÉvénements serveur

Respectez également les règles suivantes :

  • Utilisez le champ type des événements pour identifier les événements du modèle et traitez les identifiants associés selon la référence des événements de ce modèle.

  • Les opérations requises par le modèle, telles que la confirmation de l’initialisation, la soumission des entrées, l’annulation des réponses et la fin des tâches, sont toutes traitées sur le canal Data selon le protocole correspondant ; elles définissent les interactions du modèle et sont distinctes de la déconnexion du transport.

  • Les données audio sont transmises sur les pistes de médias. Lorsque vous utilisez la référence des événements du modèle, suivez cette page pour l’encapsulation des médias et les modes d’interaction disponibles. N’appliquez pas directement la méthode WebSocket d’envoi des médias.

Références des événements