WebSocket problems with IBM Watson

499 views Asked by At

It is so frustrating. I want to create simple speech to text app, but I have problems with the connection. I am able to get the token, but then something is wrong, here are the screens:handshake error, http error. First I tried to follow this youtube guy, but his code doesn't work. Then I tried to follow the docs, watson-speech npm and ibm-watson npm, but now I'm stuck.

I'm getting the token, but then I can't go further, I'm using the server.js and App.js to achieve this goal and here is the code:

server.js

'use strict';

const express = require('express');
const app = express();
const vcapServices = require('vcap_services');
const cors = require('cors');

const { IamTokenManager } = require('ibm-watson/auth');

if (process.env.VCAP_SERVICES) {

  // enable rate-limiting
  const RateLimit = require('express-rate-limit');
  app.enable('trust proxy'); // required to work properly behind Bluemix's reverse proxy

  const limiter = new RateLimit({
    windowMs: 30000, // 0.5 minute
    max: 100 // limit each IP to 100 requests per windowMs
  });

  //  apply to /api/*
  app.use('/api/', limiter);
  const secure = require('express-secure-only');
  app.use(secure());
}

// app.use(express.static(__dirname + '/static'));
app.use(cors())

const sttAuthenticator = new IamTokenManager(
  {
     apikey: 'knkk_???_XjW7C9p4DUxlk6p1B9-rez8gJykyh-rYm'
  }
);

// speech to text token endpoint
app.use('/api/speech-to-text/token', function(req, res) {
  return sttAuthenticator
    .requestToken()
    .then(({ result }) => {
      res.json({ accessToken: result.access_token, url: 'https://stream.watsonplatform.net/speech-to-text/knkk_???_XjW7C9p4DUxlk6p1B9-rez8gJykyh-rYm' });
    })
    .catch(console.error);
});

const port = process.env.PORT || process.env.VCAP_APP_PORT || 3002;
app.listen(port, function() {
  console.log('Example IBM Watson Speech JS SDK client app & token server live at http://localhost:%s/', port);
});

// Chrome requires https to access the user's microphone unless it's a localhost url so
// this sets up a basic server on port 3001 using an included self-signed certificate
// note: this is not suitable for production use
// however bluemix automatically adds https support at https://<myapp>.mybluemix.net

if (!process.env.VCAP_SERVICES) {
  const fs = require('fs');
  const https = require('https');
  const HTTPS_PORT = 3001;

  const options = {
    key: fs.readFileSync(__dirname + '/keys/localhost.pem'),
    cert: fs.readFileSync(__dirname + '/keys/localhost.cert')
  };
  https.createServer(options, app).listen(HTTPS_PORT, function() {
    console.log('Secure server live at https://localhost:%s/', HTTPS_PORT);
  });
}

App.js

import React, { Component } from 'react';
import './App.css';

import recognizeMic from 'watson-speech/speech-to-text/recognize-microphone';
import WatsonSpeech from 'watson-speech/dist/watson-speech';

class App extends Component {

  constructor() {
    super()
    this.state = {

    }
  }

  onListenClick() {
    fetch('http://localhost:3002/api/speech-to-text/token')
      .then(function(response) {
          return response.text();
      }).then((token) => {
        console.log('token is', token)
        var stream = recognizeMic({
            token: token,
            objectMode: true, // send objects instead of text
            extractResults: true, // convert {results: [{alternatives:[...]}], result_index: 0} to {alternatives: [...], index: 0}
            format: false // optional - performs basic formatting on the results such as capitals an periods
        });
        stream.on('data', (data) => {
          this.setState({
            text: data.alternatives[0].transcript
          })
        });
        stream.on('error', function(err) {
            console.log(err);
        });
        document.querySelector('#stop').onclick = stream.stop.bind(stream);
      }).catch(function(error) {
          console.log(error);
      });
  }
  render() {
    return (
      <div className="App">
        <button onClick={this.onListenClick.bind(this)}>Start</button>
        <div style={{fontSize: '40px'}}>{this.state.text}</div>

      </div>
    );
  }
}

export default App;

and I'm getting the handshake error when I change the stream variable to this:

var stream = WatsonSpeech.SpeechToText.recognizeMicrophone(Object.assign(token, {
          objectMode: true, // send objects instead of text
          format: false // optional - performs basic formatting on the results such as capitals an periods
      })

After changing to the IamAuthenticator and using the API reference I still can't use it in the terminal. In meanwhile I have tried the Google speech to text and it works:on the left google, on the right ibm.

Can you see any errors in my code?

'use strict';

// Node-Record-lpcm16
const recorder = require('node-record-lpcm16');

const SpeechToTextV1 = require('ibm-watson/speech-to-text/v1');
const { IamAuthenticator } = require('ibm-watson/auth');

const speechToText = new SpeechToTextV1({
  authenticator: new IamAuthenticator({
    apikey: 'knkk_O4nfg_XjW7C9p4DUxlk6p1B9-rez8gJykyh-rYm',
  }),
  url: 'https://api.eu-gb.speech-to-text.watson.cloud.ibm.com/instances/c7572958-6b9c-4215-943f-7ff935bb0037',
});

const params = {
  objectMode: true,
  contentType: 'audio/flac',
  model: 'en-US_BroadbandModel',
  keywords: ['turn on', 'turn off', 'on', 'off'],
  keywordsThreshold: 0.5,
  maxAlternatives: 3,
  headers: 'chunked'
};

// Create the stream.
const recognizeStream = speechToText.recognizeUsingWebSocket(params);

// Listen for events.
recognizeStream.on('data', function(event) { onEvent('Data:', event); });
recognizeStream.on('error', function(event) { onEvent('Error:', event); });
recognizeStream.on('close', function(event) { onEvent('Close:', event); });

// Display events on the console.
function onEvent(name, event) {
    console.log(name, JSON.stringify(event, null, 2));
};

// Start recording and send the microphone input to the Speech API
recorder
.record({
    sampleRateHertz: 16000,
    threshold: 0, //silence threshold
    recordProgram: 'rec', // Try also "arecord" or "sox"
    silence: '5.0', //seconds of silence before ending
})
.stream()
.on('error', console.error)
.pipe(recognizeStream);
2

There are 2 answers

2
Allen Dean On

Support for the token parameter has been removed in version 5. Use IamAuthenticator.

Take a look at the MIGRATION doc and the API reference example

0
dpopp07 On

Your code looks fine for the most part so you should be very close to getting this to work.

Your first example posted looks like it should work with one exception - you need to use the accessToken parameter rather than the token parameter:

var stream = recognizeMic({
        accessToken: token,
        objectMode: true, // send objects instead of text
        extractResults: true, // convert {results: [{alternatives:[...]}], result_index: 0} to {alternatives: [...], index: 0}
        format: false // optional - performs basic formatting on the results such as capitals an periods
    });

You're fine to use the Token Manager as you have (although eventually, it will be deprecated in favor of the Authenticators).

As for your last example, the server-side code, everything looks good. Where you might be running into problems is that a stop code is never sent to the server to trigger it sending a response. Try passing it the parameter interimResults: true. If you get data as expected, then that is your problem. The way to fix this is to add a finish-handler to your code, as demonstrated in this example.