WeChat mini program

Updated at:

The Intelligent Speech Interaction SDK for WeChat mini programs converts short audio recordings into text and delivers intermediate and final results through callbacks.

Prerequisites

  • Activate Intelligent Speech Interaction, create a project in the Intelligent Speech Interaction console, and obtain its Appkey.

  • Install WeChat DevTools and configure a mini program project. WeChat Base Library 2.4.4 or later is required.

  • Obtain permission to record audio in the mini program. In its server domain configuration, add the HTTPS domain of the application server that provides tokens to the allowed request domains, and add wss://nls-gateway-cn-shanghai.aliyuncs.com to the allowed socket domains.

Limitations

Each short sentence recognition request supports up to 60 seconds of audio. The audio format and sample rate must match the recognition parameters. The example uses mono PCM audio at 16 kHz and stops recording after 55 seconds.

Important

The SDK in the downloadable attachment does not invoke the application's completed callback when VAD automatically ends recognition. To receive final results through this callback, set enable_voice_detection to false and call close() after sending all audio. shutdown() only disconnects the connection and does not replace normal recognition completion.

Download and install

Download the SDK from the GitHub repository or download alibabacloud-nls-wx-sdk-master.zip directly. Copy the SDK's utils directory into the mini program project, then use require with the appropriate relative path to import a module.

Obtain a token

Obtain an NLS token on the application server and provide it to the mini program through an authenticated HTTPS endpoint. For the token acquisition procedure, see Obtain a token.

Keep the AccessKey ID and AccessKey secret on the server. Do not include them in mini program code or local storage. The server can cache a token based on its expiration time and obtain a new token before it expires to avoid frequent token requests.

Code example

The example records and streams audio after recognition starts. When recording stops, it waits for both the last audio frame and the recording stop event before requesting the final result. Application-side timeouts cover starting recognition, finishing recognition, and waiting for recording to stop. Adjust these timeouts to suit the application; they are not service limits.

Replace APPKEY and TOKEN_URL, and add the authentication required by the application server to the request in getToken(). TOKEN_URL is an application-defined endpoint, not an NLS OpenAPI endpoint. The example expects it to return token (a string) and expireTime (a Unix timestamp in seconds).

Register the pages/sr/sr page in the project's app.json and configure the recording purpose and privacy disclosures required by WeChat. The following JavaScript and WXML together implement the page.

Page logic: pages/sr/sr.js

const SpeechRecognition = require("../../utils/sr");
const APPKEY = "YOUR_APPKEY";
const TOKEN_URL = "https://your-server.example.com/nls/token";
const GATEWAY = "wss://nls-gateway-cn-shanghai.aliyuncs.com/ws/v1";

function withTimeout(promise, milliseconds) {
  let timer;
  return Promise.race([
    promise,
    new Promise((_, reject) => {
      timer = setTimeout(() => reject(new Error("Operation timed out")), milliseconds);
    })
  ]).finally(() => clearTimeout(timer));
}

function getToken() {
  return new Promise((resolve, reject) => {
    wx.request({
      url: TOKEN_URL,
      method: "GET",
      timeout: 10000,
      // Add the authentication required by your application server.
      success(res) {
        const value = res.data;
        if (res.statusCode === 200 && value && typeof value.token === "string"
            && value.token && Number(value.expireTime) > Date.now() / 1000 + 60) {
          resolve(value.token);
        } else {
          reject(new Error("No valid token returned"));
        }
      },
      fail: reject
    });
  });
}

Page({
  data: { busy: false, recording: false, result: "", error: "" },

  onLoad() {
    this.recorder = wx.getRecorderManager();
    const listen = (name, handler) => {
      this[name] = (...args) => {
        if (!this.unloaded) handler(...args);
      };
      this.recorder[name](this[name]);
    };
    listen("onStart", () => {
      if (!this.session) {
        this.recorder.stop();
        return;
      }
      this.setData({ recording: true });
    });
    listen("onFrameRecorded", (frame) => {
      const session = this.session;
      if (!session || !session.sr || session.closing) return;
      try {
        if (frame.frameBuffer.byteLength && !session.sr.sendAudio(frame.frameBuffer)) {
          throw new Error("Audio connection unavailable");
        }
        if (frame.isLastFrame) session.lastFrame = true;
        this.finishRecording(session);
      } catch (error) {
        this.abort(error.message);
      }
    });
    listen("onStop", () => {
      this.recorderBusy = false;
      const session = this.session;
      if (!session) return;
      session.stopped = true;
      this.setData({ recording: false });
      this.waitForLastFrame(session);
      this.finishRecording(session);
    });
    listen("onError", () => {
      this.recorderBusy = false;
      this.abort("Recording failed. Check microphone permission.");
    });
  },

  async onSrStart() {
    if (this.session || this.recorderBusy) return;
    const session = {};
    this.session = session;
    this.setData({ busy: true, recording: false, result: "", error: "" });
    try {
      const token = await getToken();
      if (this.session !== session) return;
      const sr = new SpeechRecognition({ url: GATEWAY, appkey: APPKEY, token });
      session.sr = sr;
      sr.on("changed", (message) => {
        if (this.session === session) {
          this.setData({ result: JSON.parse(message).payload.result || "" });
        }
      });
      sr.on("completed", (message) => {
        if (this.session !== session) return;
        session.completed = true;
        const response = JSON.parse(message);
        console.log("task_id:", response.header.task_id);
        this.setData({ result: response.payload.result || "" });
      });
      sr.on("failed", (message) => {
        if (this.session !== session) return;
        const header = JSON.parse(message).header;
        console.error("status:", header.status, "task_id:", header.task_id);
        this.abort("Recognition failed. Check the status code.");
      });
      sr.on("closed", () => {
        if (this.session === session && !session.completed) {
          this.abort("Connection closed before recognition completed.");
        }
      });

      const params = sr.defaultStartParams();
      params.enable_voice_detection = false;
      await withTimeout(sr.start(params), 10000);
      if (this.session !== session) return;
      this.recorderBusy = true;
      try {
        this.recorder.start({
        duration: 55000,
        numberOfChannels: 1,
        sampleRate: 16000,
        format: "PCM",
        frameSize: 4
        });
      } catch (error) {
        this.recorderBusy = false;
        throw error;
      }
    } catch (error) {
      if (this.session === session) this.abort(error.message || "Unable to start recognition.");
    }
  },

  onSrStop() {
    const session = this.session;
    if (!session || !this.data.recording || session.stopRequested) return;
    session.stopRequested = true;
    this.setData({ recording: false });
    this.waitForLastFrame(session);
    this.recorder.stop();
  },

  waitForLastFrame(session) {
    if (session.timer) return;
    session.timer = setTimeout(() => {
      if (this.session === session) this.abort("Timed out while stopping recording.");
    }, 5000);
  },

  async finishRecording(session) {
    if (!session.stopped || !session.lastFrame || session.closing) return;
    session.closing = true;
    clearTimeout(session.timer);
    try {
      await withTimeout(session.sr.close(), 10000);
      if (this.session === session) {
        this.session = null;
        this.setData({ busy: false, recording: false });
      }
    } catch (error) {
      if (this.session === session) this.abort(error.message || "Unable to finish recognition.");
    }
  },

  abort(message) {
    const session = this.session;
    this.session = null;
    if (session) {
      clearTimeout(session.timer);
      if (session.sr) session.sr.shutdown();
    }
    if (this.recorderBusy) this.recorder.stop();
    if (!this.unloaded) {
      this.setData({ busy: false, recording: false, error: message || "" });
    }
  },

  onHide() { this.abort(); },
  onUnload() {
    this.unloaded = true;
    this.abort();
    for (const name of ["onStart", "onFrameRecorded", "onStop", "onError"]) {
      const off = this.recorder["off" + name.slice(2)];
      if (typeof off === "function") off.call(this.recorder, this[name]);
    }
  }
});

Page layout: pages/sr/sr.wxml

<view>
  <button bindtap="onSrStart" disabled="{{busy}}">Start recognition</button>
  <button bindtap="onSrStop" disabled="{{!recording}}">Stop recognition</button>
  <text>{{result}}</text>
  <text>{{error}}</text>
</view>

result displays recognized text, and error displays error messages. For service errors, the example logs the status code and task ID to the console without logging the token.

API reference

SpeechRecognition

Call new SpeechRecognition(config) to create a short sentence recognition object. The config connection object contains the following fields.

Parameter

Type

Description

url

String

The WebSocket service URL. The example uses wss://nls-gateway-cn-shanghai.aliyuncs.com/ws/v1.

token

String

A valid NLS token provided by the application server.

appkey

String

The Appkey of the Intelligent Speech Interaction project.

defaultStartParams()

This method takes no parameters and returns an object containing the default recognition parameters. The SDK in the downloadable attachment returns the following fields. The example explicitly disables VAD.

{
  "format": "pcm",
  "sample_rate": 16000,
  "enable_intermediate_result": true,
  "enable_punctuation_prediction": true,
  "enable_inverse_text_normalization": true,
  "enable_voice_detection": true,
  "max_end_silence": 2000
}

You can modify the returned object before passing it to start(). Intermediate results, punctuation prediction, and inverse text normalization (ITN) are enabled by default. When VAD is enabled, max_start_silence and max_end_silence control silence detection. Valid values for max_end_silence range from 200 to 6,000 milliseconds. For the complete parameter descriptions, see the short sentence recognition API reference.

on(which, handler)

Registers an event callback and returns no value. Calling on() again for the same event replaces the previous callback.

Parameter

Type

Description

which

String

The event name.

handler

Function

The callback function.

Event

Callback argument

Description

started

String

The recognition start message as a JSON string.

changed

String

An intermediate result as a JSON string. The text is in payload.result.

completed

String

The recognition completion message received after calling close(), as a JSON string. The final text is in payload.result.

closed

None

The WebSocket connection is closed. This does not indicate successful recognition.

failed

String

A service error message as a JSON string. Use header.status and header.task_id for troubleshooting.

header.task_id identifies the recognition task, and header.message_id identifies the message. No further audio is processed after a task ends. Call start() again to begin another recognition task.

start(param)

Starts a recognition task. param is an Object containing recognition parameters. Returns a Promise that resolves with the recognition start message as a JSON string. Wait for recognition to start before sending audio.

Errors such as a connection establishment failure reject the Promise. However, when the service returns TaskFailed, the SDK may invoke only the failed callback and leave the Promise pending. Handle failed and closed and set an application-side timeout instead of relying only on try/catch.

close(param)

Sends a stop recognition request. param is an optional Object containing stop parameters and can be omitted for a normal call. Send all audio frames before calling this method.

Returns a Promise that resolves with the recognition completion message as a JSON string and invokes completed. The Promise rejects if no client exists or if TaskFailed is received while waiting. A connection closure does not guarantee that the Promise settles. Set an application-side timeout and handle the connection close event.

shutdown()

Forcibly disconnects the connection. This method takes no parameters and returns no value. Use it to cancel an operation or clean up when leaving the page or handling an error. It does not wait for a final recognition result.

sendAudio(data)

Sends binary audio data as an ArrayBuffer. The audio format and sample rate must match the parameters passed to start().

Returns false if no client exists, or true after calling the client's send method. This boolean value is not an acknowledgement from the service. Recognition results and errors are delivered through event callbacks.

Related information