Skip to content

Commit

Permalink
Merge pull request #848 from m-ripper/bugfix/web-client-SpeechRecognizer
Browse files Browse the repository at this point in the history
✨ Improve Web-Client browser-detection
  • Loading branch information
aswetlow authored Nov 9, 2020
2 parents ec59421 + 85f4c8e commit 6e00dc4
Show file tree
Hide file tree
Showing 12 changed files with 160 additions and 138 deletions.
1 change: 1 addition & 0 deletions jovo-clients/jovo-client-web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"author": "jovotech",
"license": "Apache-2.0",
"dependencies": {
"detect-browser": "^5.2.0",
"jovo-platform-web": "^3.2.2",
"lodash.defaultsdeep": "^4.6.1",
"lodash.get": "^4.4.2",
Expand Down
35 changes: 16 additions & 19 deletions jovo-clients/jovo-client-web/src/Client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,22 @@ export interface Config {
}

export class Client extends EventEmitter {
get isInitialized(): boolean {
return this.initialized;
}

get isPlayingAudio(): boolean {
return this.$audioPlayer.isPlaying || this.$speechSynthesizer.isSpeaking;
}

get isRecordingInput(): boolean {
return this.$audioRecorder.isRecording || this.$speechRecognizer.isRecording;
}

get isUsingSpeechRecognition(): boolean {
return this.useSpeechRecognition;
}

static getDefaultConfig(): Config {
return {
version: '3.2.1',
Expand Down Expand Up @@ -96,13 +112,10 @@ export class Client extends EventEmitter {
readonly $speechRecognizer: SpeechRecognizer;
readonly $speechSynthesizer: SpeechSynthesizer;
readonly $store: Store;

readonly config: Config;

readonly $actionHandler: ActionHandler;
readonly $repromptHandler: RepromptHandler;
readonly $ssmlHandler: SSMLHandler;

private useSpeechRecognition = true;
private isInputProcessOngoing = false;
private initialized = false;
Expand Down Expand Up @@ -173,22 +186,6 @@ export class Client extends EventEmitter {
});
}

get isInitialized(): boolean {
return this.initialized;
}

get isPlayingAudio(): boolean {
return this.$audioPlayer.isPlaying || this.$speechSynthesizer.isSpeaking;
}

get isRecordingInput(): boolean {
return this.$audioRecorder.isRecording || this.$speechRecognizer.isRecording;
}

get isUsingSpeechRecognition(): boolean {
return this.useSpeechRecognition;
}

addListener(event: ClientEvent.Request, listener: ClientRequestListener): this;
addListener(event: ClientEvent.Response, listener: ClientResponseListener): this;
addListener(event: ClientEvent.Action, listener: ClientActionListener): this;
Expand Down
3 changes: 0 additions & 3 deletions jovo-clients/jovo-client-web/src/core/RepromptHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,10 @@ export class RepromptHandler {
maxAttempts: 1,
};
}

private actions: Action[] = [];
private attempts = 0;
private hasAddedEvents = false;

private useSpeechRecognition = true;

private timeoutFn = this.onInputTimeout.bind(this);
private endFn = this.onInputEnd.bind(this);

Expand Down
38 changes: 18 additions & 20 deletions jovo-clients/jovo-client-web/src/standalone/AudioPlayer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,26 +23,6 @@ export interface AudioPlayerConfig {
}

export class AudioPlayer extends EventEmitter {
static getDefaultConfig(): AudioPlayerConfig {
return {
enabled: true,
};
}

readonly config: AudioPlayerConfig;

private $volume = 1.0;
private audio: HTMLAudioElement | null = null;
private isAudioPlaying: boolean = false;
private initialized = false;

constructor(config?: DeepPartial<AudioPlayerConfig>) {
super();

const defaultConfig = AudioPlayer.getDefaultConfig();
this.config = config ? _defaultsDeep(config, defaultConfig) : defaultConfig;
}

get isInitialized(): boolean {
return this.initialized;
}
Expand Down Expand Up @@ -74,6 +54,24 @@ export class AudioPlayer extends EventEmitter {
return !!this.audio && !this.audio.ended;
}

static getDefaultConfig(): AudioPlayerConfig {
return {
enabled: true,
};
}
readonly config: AudioPlayerConfig;
private $volume = 1.0;
private audio: HTMLAudioElement | null = null;
private isAudioPlaying: boolean = false;
private initialized = false;

constructor(config?: DeepPartial<AudioPlayerConfig>) {
super();

const defaultConfig = AudioPlayer.getDefaultConfig();
this.config = config ? _defaultsDeep(config, defaultConfig) : defaultConfig;
}

addListener(event: AudioPlayerVoidEvents, listener: VoidListener): this;
addListener(event: AudioPlayerEvent.Play, listener: AudioPlayerPlayListener): this;
addListener(event: AudioPlayerEvent.Error, listener: ErrorListener): this;
Expand Down
59 changes: 27 additions & 32 deletions jovo-clients/jovo-client-web/src/standalone/AudioRecorder.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { EventEmitter } from 'events';
import _defaultsDeep from 'lodash.defaultsdeep';
import { DeepPartial, OSHelper, VoidListener } from '..';
import { DeepPartial, OSDetector, VoidListener } from '..';

interface AudioRecorderNodes {
inputStream?: MediaStreamAudioSourceNode;
Expand Down Expand Up @@ -62,6 +62,30 @@ export interface AudioRecorderConfig {
}

export class AudioRecorder extends EventEmitter {
get isInitialized(): boolean {
return this.initialized;
}

get isRecording(): boolean {
return this.recording;
}

get startDetectionEnabled(): boolean {
return !!(
this.config.startDetection.enabled &&
this.config.startDetection.threshold &&
this.config.startDetection.timeoutInMs
);
}

get silenceDetectionEnabled(): boolean {
return !!(
this.config.silenceDetection.enabled &&
this.config.silenceDetection.threshold &&
this.config.silenceDetection.timeoutInMs
);
}

static getDefaultConfig(): AudioRecorderConfig {
return {
sampleRate: 16000,
Expand Down Expand Up @@ -89,19 +113,14 @@ export class AudioRecorder extends EventEmitter {
},
};
}

readonly config: AudioRecorderConfig;

private readonly audioNodes: AudioRecorderNodes;
private audioCtx: AudioContext | null;
private audioStream: MediaStream | null;

private initialized = false;

private recording = false;
private recordingStartedAt?: Date;
private startThresholdPassed = false;

private chunks: Float32Array[] = [];
private chunkLength = 0;

Expand All @@ -117,30 +136,6 @@ export class AudioRecorder extends EventEmitter {
this.audioStream = null;
}

get isInitialized(): boolean {
return this.initialized;
}

get isRecording(): boolean {
return this.recording;
}

get startDetectionEnabled(): boolean {
return !!(
this.config.startDetection.enabled &&
this.config.startDetection.threshold &&
this.config.startDetection.timeoutInMs
);
}

get silenceDetectionEnabled(): boolean {
return !!(
this.config.silenceDetection.enabled &&
this.config.silenceDetection.threshold &&
this.config.silenceDetection.timeoutInMs
);
}

addListener(event: AudioRecorderVoidEvents, listener: VoidListener): this;
addListener(event: AudioRecorderEvent.Stop, listener: AudioRecorderStopListener): this;
addListener(
Expand Down Expand Up @@ -219,7 +214,7 @@ export class AudioRecorder extends EventEmitter {
}
this.checkForBrowserCompatibility();

if (OSHelper.isWindows) {
if (OSDetector.isWindows()) {
if (!this.audioStream) {
this.audioStream = await navigator.mediaDevices.getUserMedia({
audio: this.config.audioConstraints,
Expand Down Expand Up @@ -307,7 +302,7 @@ export class AudioRecorder extends EventEmitter {
this.audioNodes.inputGain?.disconnect();
this.audioNodes.inputStream?.disconnect();

if (this.audioStream && !OSHelper.isWindows) {
if (this.audioStream && !OSDetector.isWindows) {
this.audioStream.getTracks().forEach((track) => {
track.stop();
});
Expand Down
63 changes: 31 additions & 32 deletions jovo-clients/jovo-client-web/src/standalone/SpeechRecognizer.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { EventEmitter } from 'events';
import _defaultsDeep from 'lodash.defaultsdeep';
import { DeepPartial, ErrorListener, VoidListener } from '..';
import { BrowserDetector, DeepPartial, ErrorListener, VoidListener } from '..';

// TODO maybe rename SpeechRecognized to Processing to have almost identical events as the AudioRecorder
export enum SpeechRecognizerEvent {
Expand Down Expand Up @@ -42,8 +42,36 @@ export interface SpeechRecognizerConfig extends SpeechRecognitionConfig {
}

export class SpeechRecognizer extends EventEmitter {
get isRecording(): boolean {
return this.recording;
}

get isAvailable(): boolean {
return !!this.recognition;
}

get startDetectionEnabled(): boolean {
return !!(
this.config.continuous &&
this.config.interimResults &&
this.config.startDetection.enabled &&
this.config.startDetection.timeoutInMs
);
}

get silenceDetectionEnabled(): boolean {
return !!(
this.config.continuous &&
this.config.interimResults &&
this.config.silenceDetection.enabled &&
this.config.silenceDetection.timeoutInMs
);
}

static isSupported(): boolean {
return !!(window.SpeechRecognition || window.webkitSpeechRecognition);
return (
!!(window.SpeechRecognition || window.webkitSpeechRecognition) && BrowserDetector.isChrome()
);
}

static getDefaultConfig(): SpeechRecognizerConfig {
Expand All @@ -66,12 +94,9 @@ export class SpeechRecognizer extends EventEmitter {
}

readonly config: SpeechRecognizerConfig;

private readonly recognition: SpeechRecognition | null = null;

private recording = false;
private lastRecognitionEvent: SpeechRecognitionEvent | null = null;

private timeoutId?: number;
private ignoreNextEnd = false;

Expand All @@ -82,38 +107,12 @@ export class SpeechRecognizer extends EventEmitter {
const defaultConfig = SpeechRecognizer.getDefaultConfig();
this.config = config ? _defaultsDeep(config, defaultConfig) : defaultConfig;

if (window.SpeechRecognition) {
if (SpeechRecognizer.isSupported()) {
this.recognition = new window.SpeechRecognition();
this.setupSpeechRecognition(this.recognition);
}
}

get isRecording(): boolean {
return this.recording;
}

get isAvailable(): boolean {
return !!this.recognition;
}

get startDetectionEnabled(): boolean {
return !!(
this.config.continuous &&
this.config.interimResults &&
this.config.startDetection.enabled &&
this.config.startDetection.timeoutInMs
);
}

get silenceDetectionEnabled(): boolean {
return !!(
this.config.continuous &&
this.config.interimResults &&
this.config.silenceDetection.enabled &&
this.config.silenceDetection.timeoutInMs
);
}

addListener(
event: SpeechRecognizerEvent.SpeechRecognized,
listener: SpeechRecognizerSpeechRecognizedListener,
Expand Down
43 changes: 20 additions & 23 deletions jovo-clients/jovo-client-web/src/standalone/SpeechSynthesizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,29 +25,6 @@ export interface SpeechSynthesizerConfig {
}

export class SpeechSynthesizer extends EventEmitter {
static getDefaultConfig(): SpeechSynthesizerConfig {
return {
enabled: true,
language: 'en',
};
}

readonly config: SpeechSynthesizerConfig;

private $volume = 1.0;
private readonly synthesis: SpeechSynthesis | null;

private isSpeakingUtterance = false;

constructor(config?: DeepPartial<SpeechSynthesizerConfig>) {
super();

const defaultConfig = SpeechSynthesizer.getDefaultConfig();
this.config = config ? _defaultsDeep(config, defaultConfig) : defaultConfig;

this.synthesis = window.speechSynthesis || null;
}

get volume(): number {
return this.$volume;
}
Expand Down Expand Up @@ -76,6 +53,26 @@ export class SpeechSynthesizer extends EventEmitter {
return !!this.synthesis && this.synthesis.speaking;
}

static getDefaultConfig(): SpeechSynthesizerConfig {
return {
enabled: true,
language: 'en',
};
}
readonly config: SpeechSynthesizerConfig;
private $volume = 1.0;
private readonly synthesis: SpeechSynthesis | null;
private isSpeakingUtterance = false;

constructor(config?: DeepPartial<SpeechSynthesizerConfig>) {
super();

const defaultConfig = SpeechSynthesizer.getDefaultConfig();
this.config = config ? _defaultsDeep(config, defaultConfig) : defaultConfig;

this.synthesis = window.speechSynthesis || null;
}

addListener(event: SpeechSynthesizerVoidEvents, listener: VoidListener): this;
addListener(event: SpeechSynthesizerEvent.Speak, listener: SpeechSynthesizerSpeakListener): this;
addListener(event: SpeechSynthesizerEvent.Error, listener: ErrorListener): this;
Expand Down
Loading

0 comments on commit 6e00dc4

Please sign in to comment.