| // Copyright 2023 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| // Provides a concrete implementation of SpeechRecognitionEventObserver. |
| class AtpSpeechRecognitionEventObserver { |
| /** |
| * @param {!ax.mojom.SpeechRecognitionEventObserverPendingReceiver} |
| * pendingReceiver |
| * @param {!function(): void} onStopCallback |
| * @param {!function(!ax.mojom.SpeechRecognitionResultEvent): void} |
| * onResultCallback |
| * @param {!function(): void} onErrorCallback |
| */ |
| constructor(pendingReceiver, onStopCallback, onResultCallback, |
| onErrorCallback) { |
| this.receiver_ = new ax.mojom.SpeechRecognitionEventObserverReceiver(this); |
| this.receiver_.$.bindHandle(pendingReceiver.handle); |
| /** @private {!function(): void} */ |
| this.onStopCallback_ = onStopCallback; |
| /** @private {!function(!ax.mojom.SpeechRecognitionResultEvent): void} */ |
| this.onResultCallback_ = onResultCallback; |
| /** @private {!function(!ax.mojom.SpeechRecognitionErrorEvent): void} */ |
| this.onErrorCallback_ = onErrorCallback; |
| } |
| |
| onStop() { |
| this.onStopCallback_(); |
| } |
| |
| /** @param {!ax.mojom.SpeechRecognitionResultEvent} event */ |
| onResult(event) { |
| this.onResultCallback_(event); |
| } |
| |
| /** @param {ax.mojom.SpeechRecognitionErrorEvent} event */ |
| onError(event) { |
| this.onErrorCallback_(event); |
| } |
| } |
| |
| // The ATP shim of the speech recognition private API. |
| class AtpSpeechRecognition { |
| constructor() { |
| const SpeechRecognitionApi = ax.mojom.SpeechRecognition; |
| this.remote_ = SpeechRecognitionApi.getRemote(); |
| /** |
| * @private {!Map< |
| * !ax.mojom.AssistiveTechnologyType, !AtpSpeechRecognitionEventObserver |
| * >} |
| */ |
| this.observers_ = new Map(); |
| /** @type {!ChromeEvent} */ |
| this.onStop = new ChromeEvent(); |
| /** @type {!ChromeEvent} */ |
| this.onResult = new ChromeEvent(); |
| /** @type {!ChromeEvent} */ |
| this.onError = new ChromeEvent(); |
| } |
| |
| /** |
| * @param {!chrome.speechRecognitionPrivate.StartOptions} options |
| * @param {function(!chrome.speechRecognitionPrivate.SpeechRecognitionType): |
| * void} callback |
| * Called when speech recognition has begun listening to the user's |
| * audio. The callback's parameter specifies which type of speech |
| * recognition is being used. |
| */ |
| start(options, callback) { |
| const mojoOptions = AtpSpeechRecognition.convertStartOptions_(options); |
| this.remote_.start(mojoOptions).then(result => { |
| const type = AtpSpeechRecognition.convertRecognitionType_( |
| result.info.type); |
| const observerOrError = result.info.observerOrError; |
| const error = observerOrError.error; |
| if (error) { |
| this.runCallbackWithError_(error, callback, type); |
| return; |
| } |
| |
| const observer = new AtpSpeechRecognitionEventObserver( |
| /*pendingReceiver=*/observerOrError.observer, |
| /*onStopCallback=*/() => { |
| this.handleOnStop_(); |
| }, |
| /*onResultCallback=*/(event) => { |
| this.handleOnResult_(event); |
| }, |
| /*onErrorCallback=*/(event) => { |
| this.handleOnError_(event); |
| }); |
| this.observers_.set(mojoOptions.type, observer); |
| callback(type); |
| }); |
| } |
| |
| /** |
| * @param {!chrome.speechRecognitionPrivate.StopOptions} options |
| * @param {function(): void} callback |
| * Called when speech recognition has stopped listening to the user's audio. |
| */ |
| stop(options, callback) { |
| const mojoOptions = AtpSpeechRecognition.convertStopOptions_(options); |
| this.remote_.stop(mojoOptions).then((result) => { |
| const error = result.error; |
| if (error) { |
| this.runCallbackWithError_(error, callback); |
| return; |
| } |
| |
| this.observers_.delete(mojoOptions.type); |
| callback(); |
| }); |
| } |
| |
| /** @private */ |
| handleOnStop_() { |
| // TODO(b/304305202): Ensure we remove the relevant event observer. |
| this.onStop.callListeners(); |
| } |
| |
| /** |
| * @param {!ax.mojom.SpeechRecognitionResultEvent} event |
| * @private |
| */ |
| handleOnResult_(event) { |
| this.onResult.callListeners( |
| /** |
| * @type {!chrome.speechRecognitionPrivate.SpeechRecognitionResultEvent} |
| */ (event)); |
| } |
| |
| /** |
| * @param {!ax.mojom.SpeechRecognitionErrorEvent} event |
| * @private |
| */ |
| handleOnError_(event) { |
| this.onError.callListeners( |
| /** |
| * @type {!chrome.speechRecognitionPrivate.SpeechRecognitionErrorEvent} |
| */ (event)); |
| } |
| |
| /** |
| * TODO(b/304305202): Move this function to a separate file (runtime.js). |
| * @param {string} error |
| * @param {!Function} callback |
| * @private |
| */ |
| runCallbackWithError_(error, callback, ...args) { |
| // To mirror the behavior of extension APIs, we set |
| // chrome.runtime.lastError for the duration of the callback and reset |
| // it after it finishes execution. |
| chrome.runtime.lastError = {message: error}; |
| callback(...args); |
| chrome.runtime.lastError = undefined; |
| } |
| |
| /** |
| * @param {!chrome.speechRecognitionPrivate.StartOptions} source |
| * @return {!ax.mojom.StartOptions} |
| * @private |
| */ |
| static convertStartOptions_(source) { |
| const options = new ax.mojom.StartOptions(); |
| options.type = AtpSpeechRecognition.clientIdToAssistiveTechnologyType_( |
| source.clientId); |
| if (source.locale !== undefined) { |
| options.locale = source.locale; |
| } |
| if (source.interimResults !== undefined) { |
| options.interimResults = source.interimResults; |
| } |
| |
| return options; |
| } |
| |
| /** |
| * @param {!chrome.speechRecognitionPrivate.StopOptions} source |
| * @return {!ax.mojom.StopOptions} |
| * @private |
| */ |
| static convertStopOptions_(source) { |
| const options = new ax.mojom.StopOptions(); |
| options.type = AtpSpeechRecognition.clientIdToAssistiveTechnologyType_( |
| source.clientId); |
| return options; |
| } |
| |
| /** |
| * @param {!ax.mojom.SpeechRecognitionType} type |
| * @return {string} |
| * @private |
| */ |
| static convertRecognitionType_(type) { |
| if (type == ax.mojom.SpeechRecognitionType.kOnDevice) { |
| return 'onDevice'; |
| } |
| |
| return 'network'; |
| } |
| |
| /** |
| * @param {number|undefined} clientId |
| * @return {!ax.mojom.AssistiveTechnologyType} |
| * @private |
| */ |
| static clientIdToAssistiveTechnologyType_(clientId) { |
| // Use Dictation as the type since it's the only accessibility feature that |
| // uses speech recognition. |
| return ax.mojom.AssistiveTechnologyType.kDictation; |
| } |
| } |
| |
| chrome.speechRecognitionPrivate = new AtpSpeechRecognition(); |