Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@speechly/browser-client

Package Overview
Dependencies
Maintainers
6
Versions
79
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@speechly/browser-client - npm Package Compare versions

Comparing version 1.0.4 to 1.0.5

5

index.d.ts

@@ -86,3 +86,2 @@

private readonly isWebkit;
private readonly audioContext;
private readonly sampleRate;

@@ -98,2 +97,3 @@ private readonly nativeResamplingSupported;

private authToken?;
private audioContext?;
private state;

@@ -397,3 +397,3 @@ private stateChangeCb;

*/
initialize(isWebkit: boolean, opts: MediaStreamConstraints): Promise<void>;
initialize(audioContext: AudioContext, opts: MediaStreamConstraints): Promise<void>;
/**

@@ -616,2 +616,3 @@ * Closes the microphone, tearing down all the infrastructure.

export declare enum WebsocketResponseType {
Opened = "WEBSOCKET_OPEN",
Started = "started",

@@ -618,0 +619,0 @@ Stopped = "stopped",

9

microphone/browser_microphone.d.ts
import { Microphone } from './types';
import { APIClient } from '../websocket';
export declare class BrowserMicrophone implements Microphone {
private readonly audioContext;
private readonly isWebkit;
private readonly apiClient;
private readonly resampleRatio;
private readonly sampleRate;
private initialized;
private muted;
private audioContext?;
private resampleRatio?;
private audioTrack?;
private mediaStream?;
private audioProcessor?;
constructor(audioContext: AudioContext, sampleRate: number, apiClient: APIClient);
initialize(isWebkit: boolean, opts: MediaStreamConstraints): Promise<void>;
constructor(isWebkit: boolean, sampleRate: number, apiClient: APIClient);
initialize(audioContext: AudioContext, opts: MediaStreamConstraints): Promise<void>;
close(): Promise<void>;

@@ -16,0 +17,0 @@ mute(): void;

@@ -20,3 +20,3 @@ "use strict";

class BrowserMicrophone {
constructor(audioContext, sampleRate, apiClient) {
constructor(isWebkit, sampleRate, apiClient) {
this.initialized = false;

@@ -30,8 +30,7 @@ this.muted = false;

};
this.audioContext = audioContext;
this.isWebkit = isWebkit;
this.apiClient = apiClient;
this.sampleRate = sampleRate;
this.resampleRatio = this.audioContext.sampleRate / this.sampleRate;
}
initialize(isWebkit, opts) {
initialize(audioContext, opts) {
var _a;

@@ -42,2 +41,4 @@ return __awaiter(this, void 0, void 0, function* () {

}
this.audioContext = audioContext;
this.resampleRatio = this.audioContext.sampleRate / this.sampleRate;
// Start audio context if we are dealing with a WebKit browser.

@@ -50,3 +51,3 @@ //

// but will emit empty audio buffers.
if (isWebkit) {
if (this.isWebkit) {
yield this.audioContext.resume();

@@ -69,3 +70,3 @@ }

// `audioContext.resume()` will hang indefinitely, without being resolved or rejected.
if (!isWebkit) {
if (!this.isWebkit) {
yield this.audioContext.resume();

@@ -104,3 +105,3 @@ }

// Safari, iOS Safari and Internet Explorer
if (isWebkit) {
if (this.isWebkit) {
// Multiply base buffer size of 4 kB by the resample ratio rounded up to the next power of 2.

@@ -107,0 +108,0 @@ // i.e. for 48 kHz to 16 kHz downsampling, this will be 4096 (base) * 4 = 16384.

@@ -43,3 +43,3 @@ /**

*/
initialize(isWebkit: boolean, opts: MediaStreamConstraints): Promise<void>;
initialize(audioContext: AudioContext, opts: MediaStreamConstraints): Promise<void>;
/**

@@ -46,0 +46,0 @@ * Closes the microphone, tearing down all the infrastructure.

{
"name": "@speechly/browser-client",
"version": "1.0.4",
"version": "1.0.5",
"description": "Browser client for Speechly API",

@@ -5,0 +5,0 @@ "private": false,

@@ -16,3 +16,2 @@ import { ClientOptions, StateChangeCallback, SegmentChangeCallback, TentativeTranscriptCallback, TranscriptCallback, TentativeEntitiesCallback, EntityCallback, IntentCallback } from './types';

private readonly isWebkit;
private readonly audioContext;
private readonly sampleRate;

@@ -28,2 +27,3 @@ private readonly nativeResamplingSupported;

private authToken?;
private audioContext?;
private state;

@@ -30,0 +30,0 @@ private stateChangeCb;

@@ -143,13 +143,15 @@ "use strict";

}
const language = (_b = options.language) !== null && _b !== void 0 ? _b : defaultLanguage;
if (!locale_code_1.default.validate(language)) {
throw Error(`[SpeechlyClient] Invalid language "${language}"`);
}
this.debug = (_c = options.debug) !== null && _c !== void 0 ? _c : false;
this.loginUrl = (_d = options.loginUrl) !== null && _d !== void 0 ? _d : defaultLoginUrl;
this.appId = options.appId;
const apiUrl = generateWsUrl((_e = options.apiUrl) !== null && _e !== void 0 ? _e : defaultApiUrl, language, (_f = options.sampleRate) !== null && _f !== void 0 ? _f : microphone_1.DefaultSampleRate);
this.apiClient = (_g = options.apiClient) !== null && _g !== void 0 ? _g : new websocket_1.WebWorkerController(apiUrl);
if (window.AudioContext !== undefined) {
const opts = {};
if (this.nativeResamplingSupported) {
opts.sampleRate = this.sampleRate;
}
this.audioContext = new window.AudioContext(opts);
this.isWebkit = false;
}
else if (window.webkitAudioContext !== undefined) {
// eslint-disable-next-line new-cap
this.audioContext = new window.webkitAudioContext();
this.isWebkit = true;

@@ -160,12 +162,3 @@ }

}
const language = (_b = options.language) !== null && _b !== void 0 ? _b : defaultLanguage;
if (!locale_code_1.default.validate(language)) {
throw Error(`[SpeechlyClient] Invalid language "${language}"`);
}
this.debug = (_c = options.debug) !== null && _c !== void 0 ? _c : false;
this.loginUrl = (_d = options.loginUrl) !== null && _d !== void 0 ? _d : defaultLoginUrl;
this.appId = options.appId;
const apiUrl = generateWsUrl((_e = options.apiUrl) !== null && _e !== void 0 ? _e : defaultApiUrl, language, (_f = options.sampleRate) !== null && _f !== void 0 ? _f : microphone_1.DefaultSampleRate);
this.apiClient = (_g = options.apiClient) !== null && _g !== void 0 ? _g : new websocket_1.WebWorkerController(apiUrl);
this.microphone = (_h = options.microphone) !== null && _h !== void 0 ? _h : new microphone_1.BrowserMicrophone(this.audioContext, this.sampleRate, this.apiClient);
this.microphone = (_h = options.microphone) !== null && _h !== void 0 ? _h : new microphone_1.BrowserMicrophone(this.isWebkit, this.sampleRate, this.apiClient);
this.storage = (_j = options.storage) !== null && _j !== void 0 ? _j : new storage_1.LocalStorage();

@@ -216,2 +209,16 @@ this.apiClient.onResponse(this.handleWebsocketResponse);

}
// 2. Initialise the microphone stack.
if (this.isWebkit) {
if (window.webkitAudioContext !== undefined) {
// eslint-disable-next-line new-cap
this.audioContext = new window.webkitAudioContext();
}
}
else {
const opts = {};
if (this.nativeResamplingSupported) {
opts.sampleRate = this.sampleRate;
}
this.audioContext = new window.AudioContext(opts);
}
const opts = {

@@ -228,6 +235,10 @@ video: false,

}
// 2. Initialise websocket.
yield this.apiClient.initialize(this.authToken, this.audioContext.sampleRate, this.sampleRate);
// 3. Initialise the microphone stack.
yield this.microphone.initialize(this.isWebkit, opts);
if (this.audioContext != null) {
// 3. Initialise websocket.
yield this.apiClient.initialize(this.authToken, this.audioContext.sampleRate, this.sampleRate);
yield this.microphone.initialize(this.audioContext, opts);
}
else {
throw microphone_1.ErrDeviceNotSupported;
}
}

@@ -234,0 +245,0 @@ catch (err) {

@@ -32,2 +32,3 @@ /**

export declare enum WebsocketResponseType {
Opened = "WEBSOCKET_OPEN",
Started = "started",

@@ -34,0 +35,0 @@ Stopped = "stopped",

@@ -9,2 +9,3 @@ "use strict";

(function (WebsocketResponseType) {
WebsocketResponseType["Opened"] = "WEBSOCKET_OPEN";
WebsocketResponseType["Started"] = "started";

@@ -11,0 +12,0 @@ WebsocketResponseType["Stopped"] = "stopped";

@@ -6,2 +6,3 @@ import { APIClient, ResponseCallback, CloseCallback } from './types';

private worker?;
private resolveInitialization?;
private startCbs;

@@ -8,0 +9,0 @@ private stopCbs;

@@ -26,2 +26,7 @@ "use strict";

switch (response.type) {
case types_1.WebsocketResponseType.Opened:
if (this.resolveInitialization != null) {
this.resolveInitialization();
}
break;
case types_1.WebsocketResponseType.Started:

@@ -80,2 +85,5 @@ this.startCbs.forEach(cb => {

}
return new Promise((resolve) => {
this.resolveInitialization = resolve;
});
});

@@ -82,0 +90,0 @@ }

@@ -1,2 +0,2 @@

declare const _default: "\n// Indices for the Control SAB.\nconst CONTROL = {\n 'WRITE_INDEX': 0,\n 'FRAMES_AVAILABLE': 1,\n};\nlet ws = undefined\nlet state = {\n isContextStarted: false,\n sourceSampleRate: undefined,\n targetSampleRate: undefined,\n resampleRatio: 1,\n buffer: new Float32Array(0),\n filter: undefined,\n controlSAB: undefined,\n dataSAB: undefined\n}\n\nfunction initializeWebsocket(url, protocol) {\n ws = new WebSocket(url, protocol)\n\n return new Promise((resolve, reject) => {\n const errhandler = () => {\n ws.removeEventListener('close', errhandler)\n ws.removeEventListener('error', errhandler)\n ws.removeEventListener('open', openhandler)\n\n reject(Error('Connection failed'))\n }\n\n const openhandler = () => {\n ws.removeEventListener('close', errhandler)\n ws.removeEventListener('error', errhandler)\n ws.removeEventListener('open', openhandler)\n\n resolve(ws)\n }\n\n ws.addEventListener('close', errhandler)\n ws.addEventListener('error', errhandler)\n ws.addEventListener('open', openhandler)\n })\n}\n\nfunction closeWebsocket(code, message) {\n if (ws === undefined) {\n throw Error('Websocket is not open')\n }\n\n ws.removeEventListener('message', onWebsocketMessage)\n ws.removeEventListener('error', onWebsocketError)\n ws.removeEventListener('close', onWebsocketClose)\n\n ws.close(code, message)\n ws = undefined\n}\n\nfunction onWebsocketClose(event) {\n ws = undefined\n}\n\nfunction onWebsocketError(_event) {\n onWebsocketClose(1000, 'Client disconnecting due to an error')\n}\n\nfunction onWebsocketMessage(event) {\n let response\n try {\n response = JSON.parse(event.data)\n } catch (e) {\n console.error('[SpeechlyClient] Error parsing response from the server:', e)\n return\n }\n\n self.postMessage(response)\n}\n\nfunction float32ToInt16(buffer) {\n const buf = new Int16Array(buffer.length)\n\n for (let l = 0; l < buffer.length; l++) {\n buf[l] = buffer[l] * (buffer[l] < 0 ? 0x8000 : 0x7fff)\n }\n\n return buf\n}\n\nself.onmessage = function(e) {\n switch (e.data.type) {\n case 'INIT':\n if (ws === undefined) {\n initializeWebsocket(e.data.apiUrl, e.data.authToken).then(function() {\n ws.addEventListener('message', onWebsocketMessage)\n ws.addEventListener('error', onWebsocketError)\n ws.addEventListener('close', onWebsocketClose)\n })\n state.sourceSampleRate = e.data.sourceSampleRate\n state.targetSampleRate = e.data.targetSampleRate\n state.resampleRatio = e.data.sourceSampleRate / e.data.targetSampleRate\n if (state.resampleRatio > 1) {\n state.filter = generateFilter(e.data.sourceSampleRate, e.data.targetSampleRate, 127)\n }\n }\n break\n case 'SET_SHARED_ARRAY_BUFFERS':\n state.controlSAB = new Int32Array(e.data.controlSAB);\n state.dataSAB = new Float32Array(e.data.dataSAB);\n setInterval(sendAudioFromSAB, 4)\n break\n case 'CLOSE':\n if (ws !== undefined) {\n closeWebsocket(e.data.code, e.data.message)\n }\n break\n case 'START_CONTEXT':\n if (ws !== undefined && !state.isContextStarted) {\n state.isContextStarted = true\n const StartEventJSON = JSON.stringify({ event: 'start' })\n ws.send(StartEventJSON)\n }\n break\n case 'STOP_CONTEXT':\n if (ws !== undefined && state.isContextStarted) {\n state.isContextStarted = false\n const StopEventJSON = JSON.stringify({ event: 'stop' })\n ws.send(StopEventJSON)\n }\n break\n case 'AUDIO':\n if (ws !== undefined && state.isContextStarted) {\n if (state.resampleRatio > 1) {\n // Downsampling\n ws.send(downsample(e.data.payload))\n } else {\n ws.send(float32ToInt16(e.data.payload))\n }\n }\n break\n default:\n console.log('WORKER', e)\n }\n}\n\nfunction sendAudioFromSAB() {\n if (state.isContextStarted) {\n const data = state.dataSAB.subarray(0, state.controlSAB[CONTROL.FRAMES_AVAILABLE]);\n state.controlSAB[CONTROL.FRAMES_AVAILABLE] = 0;\n state.controlSAB[CONTROL.WRITE_INDEX] = 0;\n if (state.resampleRatio > 1) {\n ws.send(downsample(data))\n } else {\n ws.send(float32ToInt16(data))\n }\n \n }\n}\n\nfunction downsample(input) {\n const inputBuffer = new Float32Array(state.buffer.length + input.length)\n inputBuffer.set(state.buffer, 0)\n inputBuffer.set(input, state.buffer.length)\n\n const outputLength = Math.ceil((inputBuffer.length - state.filter.length) / state.resampleRatio)\n const outputBuffer = new Int16Array(outputLength)\n\n for (let i = 0; i < outputLength; i++) {\n const offset = Math.round(state.resampleRatio * i)\n let val = 0.0\n\n for (let j = 0; j < state.filter.length; j++) {\n val += inputBuffer[offset + j] * state.filter[j]\n }\n\n outputBuffer[i] = val * (val < 0 ? 0x8000 : 0x7fff)\n }\n\n const remainingOffset = Math.round(state.resampleRatio * outputLength)\n if (remainingOffset < inputBuffer.length) {\n state.buffer = inputBuffer.subarray(remainingOffset)\n } else {\n state.buffer = emptyBuffer\n }\n\n return outputBuffer\n}\n\nfunction generateFilter(sourceSampleRate, targetSampleRate, length) {\n if (length % 2 === 0) {\n throw Error('Filter length must be odd')\n }\n\n const cutoff = targetSampleRate / 2\n const filter = new Float32Array(length)\n let sum = 0\n\n for (let i = 0; i < length; i++) {\n const x = sinc(((2 * cutoff) / sourceSampleRate) * (i - (length - 1) / 2))\n\n sum += x\n filter[i] = x\n }\n\n for (let i = 0; i < length; i++) {\n filter[i] = filter[i] / sum\n }\n\n return filter\n}\n\nfunction sinc(x) {\n if (x === 0.0) {\n return 1.0\n }\n\n const piX = Math.PI * x\n return Math.sin(piX) / piX\n}\n";
declare const _default: "\n// Indices for the Control SAB.\nconst CONTROL = {\n 'WRITE_INDEX': 0,\n 'FRAMES_AVAILABLE': 1,\n};\nlet ws = undefined\nlet state = {\n isContextStarted: false,\n sourceSampleRate: undefined,\n targetSampleRate: undefined,\n resampleRatio: 1,\n buffer: new Float32Array(0),\n filter: undefined,\n controlSAB: undefined,\n dataSAB: undefined\n}\n\nfunction initializeWebsocket(url, protocol) {\n ws = new WebSocket(url, protocol)\n\n return new Promise((resolve, reject) => {\n const errhandler = () => {\n ws.removeEventListener('close', errhandler)\n ws.removeEventListener('error', errhandler)\n ws.removeEventListener('open', openhandler)\n\n reject(Error('Connection failed'))\n }\n\n const openhandler = () => {\n ws.removeEventListener('close', errhandler)\n ws.removeEventListener('error', errhandler)\n ws.removeEventListener('open', openhandler)\n\n resolve(ws)\n }\n\n ws.addEventListener('close', errhandler)\n ws.addEventListener('error', errhandler)\n ws.addEventListener('open', openhandler)\n })\n}\n\nfunction closeWebsocket(code, message) {\n if (ws === undefined) {\n throw Error('Websocket is not open')\n }\n\n ws.removeEventListener('message', onWebsocketMessage)\n ws.removeEventListener('error', onWebsocketError)\n ws.removeEventListener('close', onWebsocketClose)\n\n ws.close(code, message)\n ws = undefined\n}\n\nfunction onWebsocketClose(event) {\n ws = undefined\n}\n\nfunction onWebsocketError(_event) {\n onWebsocketClose(1000, 'Client disconnecting due to an error')\n}\n\nfunction onWebsocketMessage(event) {\n let response\n try {\n response = JSON.parse(event.data)\n } catch (e) {\n console.error('[SpeechlyClient] Error parsing response from the server:', e)\n return\n }\n\n self.postMessage(response)\n}\n\nfunction float32ToInt16(buffer) {\n const buf = new Int16Array(buffer.length)\n\n for (let l = 0; l < buffer.length; l++) {\n buf[l] = buffer[l] * (buffer[l] < 0 ? 0x8000 : 0x7fff)\n }\n\n return buf\n}\n\nself.onmessage = function(e) {\n switch (e.data.type) {\n case 'INIT':\n if (ws === undefined) {\n initializeWebsocket(e.data.apiUrl, e.data.authToken).then(function() {\n self.postMessage({\n type: 'WEBSOCKET_OPEN'\n })\n ws.addEventListener('message', onWebsocketMessage)\n ws.addEventListener('error', onWebsocketError)\n ws.addEventListener('close', onWebsocketClose)\n })\n state.sourceSampleRate = e.data.sourceSampleRate\n state.targetSampleRate = e.data.targetSampleRate\n state.resampleRatio = e.data.sourceSampleRate / e.data.targetSampleRate\n if (state.resampleRatio > 1) {\n state.filter = generateFilter(e.data.sourceSampleRate, e.data.targetSampleRate, 127)\n }\n }\n break\n case 'SET_SHARED_ARRAY_BUFFERS':\n state.controlSAB = new Int32Array(e.data.controlSAB);\n state.dataSAB = new Float32Array(e.data.dataSAB);\n setInterval(sendAudioFromSAB, 4)\n break\n case 'CLOSE':\n if (ws !== undefined) {\n closeWebsocket(e.data.code, e.data.message)\n }\n break\n case 'START_CONTEXT':\n if (ws !== undefined && !state.isContextStarted) {\n state.isContextStarted = true\n const StartEventJSON = JSON.stringify({ event: 'start' })\n ws.send(StartEventJSON)\n } else {\n console.log('can not start context')\n }\n break\n case 'STOP_CONTEXT':\n if (ws !== undefined && state.isContextStarted) {\n state.isContextStarted = false\n const StopEventJSON = JSON.stringify({ event: 'stop' })\n ws.send(StopEventJSON)\n }\n break\n case 'AUDIO':\n if (ws !== undefined && state.isContextStarted) {\n if (state.resampleRatio > 1) {\n // Downsampling\n ws.send(downsample(e.data.payload))\n } else {\n ws.send(float32ToInt16(e.data.payload))\n }\n }\n break\n default:\n console.log('WORKER', e)\n }\n}\n\nfunction sendAudioFromSAB() {\n if (state.isContextStarted) {\n const data = state.dataSAB.subarray(0, state.controlSAB[CONTROL.FRAMES_AVAILABLE]);\n state.controlSAB[CONTROL.FRAMES_AVAILABLE] = 0;\n state.controlSAB[CONTROL.WRITE_INDEX] = 0;\n if (state.resampleRatio > 1) {\n ws.send(downsample(data))\n } else {\n ws.send(float32ToInt16(data))\n }\n \n }\n}\n\nfunction downsample(input) {\n const inputBuffer = new Float32Array(state.buffer.length + input.length)\n inputBuffer.set(state.buffer, 0)\n inputBuffer.set(input, state.buffer.length)\n\n const outputLength = Math.ceil((inputBuffer.length - state.filter.length) / state.resampleRatio)\n const outputBuffer = new Int16Array(outputLength)\n\n for (let i = 0; i < outputLength; i++) {\n const offset = Math.round(state.resampleRatio * i)\n let val = 0.0\n\n for (let j = 0; j < state.filter.length; j++) {\n val += inputBuffer[offset + j] * state.filter[j]\n }\n\n outputBuffer[i] = val * (val < 0 ? 0x8000 : 0x7fff)\n }\n\n const remainingOffset = Math.round(state.resampleRatio * outputLength)\n if (remainingOffset < inputBuffer.length) {\n state.buffer = inputBuffer.subarray(remainingOffset)\n } else {\n state.buffer = emptyBuffer\n }\n\n return outputBuffer\n}\n\nfunction generateFilter(sourceSampleRate, targetSampleRate, length) {\n if (length % 2 === 0) {\n throw Error('Filter length must be odd')\n }\n\n const cutoff = targetSampleRate / 2\n const filter = new Float32Array(length)\n let sum = 0\n\n for (let i = 0; i < length; i++) {\n const x = sinc(((2 * cutoff) / sourceSampleRate) * (i - (length - 1) / 2))\n\n sum += x\n filter[i] = x\n }\n\n for (let i = 0; i < length; i++) {\n filter[i] = filter[i] / sum\n }\n\n return filter\n}\n\nfunction sinc(x) {\n if (x === 0.0) {\n return 1.0\n }\n\n const piX = Math.PI * x\n return Math.sin(piX) / piX\n}\n";
export default _default;

@@ -95,2 +95,5 @@ "use strict";

initializeWebsocket(e.data.apiUrl, e.data.authToken).then(function() {
self.postMessage({
type: 'WEBSOCKET_OPEN'
})
ws.addEventListener('message', onWebsocketMessage)

@@ -123,2 +126,4 @@ ws.addEventListener('error', onWebsocketError)

ws.send(StartEventJSON)
} else {
console.log('can not start context')
}

@@ -125,0 +130,0 @@ break

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc