This commit is contained in:
Microsoft Open Source 2018-12-14 18:27:57 +01:00 коммит произвёл Mark Hillebrand
Родитель 6478fd8cec
Коммит 384e49ae0f
142 изменённых файлов: 28172 добавлений и 18 удалений

6
.gitattributes поставляемый
Просмотреть файл

@ -1,8 +1,14 @@
.gitattributes text
.gitignore text
.npmignore text
LICENSE text
*.cmd text
*.js text
*.json text
*.md text
*.ts text
*.txt text
*.yml text
# Bash only with Unix line endings

12
.gitignore поставляемый
Просмотреть файл

@ -0,0 +1,12 @@
# ignore the npm packages
/**/node_modules/
# ignore auto-generated (transpiled) js files
src/**/*.js
tests/**/*.js
distrib/*
secrets/*
/**/speech.key
.idea/
test-javascript-junit.xml
coverage/*
*.tgz

8
.npmignore Normal file
Просмотреть файл

@ -0,0 +1,8 @@
/.vscode/*
/node_modules/*
/.gitattributes
/.gitignore
/.npmignore
/.npmrc
/.vscode
/web.config

57
BuildTestConfig.cmd Normal file
Просмотреть файл

@ -0,0 +1,57 @@
@REM Copyright (c) Microsoft Corporation. All rights reserved.
@REM Licensed under the MIT license.
@echo off
setlocal
if "%~1" equ "/?" goto :Usage
if "%~1" equ "-?" goto :Usage
if "%~1" equ "?" goto :Usage
if "%~1" equ "/h" goto :Usage
if "%~1" equ "-h" goto :Usage
set TEST_SETTING_FILE_DIR=%~dp0\secrets
if NOT EXIST "%TEST_SETTING_FILE_DIR%" (
md "%TEST_SETTING_FILE_DIR%" || (
echo Error creating directory %TEST_SETTING_FILE_DIR%
exit /b 1
)
)
set TEST_SETTING_FILE_NAME=%TEST_SETTING_FILE_DIR%\TestConfiguration.ts
if EXIST "%TEST_SETTING_FILE_NAME%" (
echo Clearing values from settings file.
echo. > "%TEST_SETTING_FILE_NAME%" || (
echo Error creating file %TEST_SETTING_FILE_NAME%
exit /b 1
)
)
@echo import { Settings } from "../tests/Settings" > "%TEST_SETTING_FILE_NAME%"
:NextArg
if "%~1" == "" (
goto :eof
)
for /f "tokens=1,2 delims=:" %%I in ("%~1") do (
echo Setting Settings.%%I = "%%J"
echo Settings.%%I = "%%J"; >> "%TEST_SETTING_FILE_NAME%"
)
shift
goto :NextArg
exit /b 0
:Usage
@echo off
echo.
echo Usage: %~n0 ^<ParamName^>:^<Value^>
echo.
echo Writes any ^<ParamName^>:^<Value^> pair to the test settings file for JavaScript bindings tests.
echo.
echo The file will be erased before new values are added.
echo.
echo Current settings available are: [SpeechSubscriptionKey:^<key^>] [SpeechRegion:^<region^>] [LuisSubscriptionKey:^<LuisKey^>] [LuisRegion:^<region^>] [LuisAppId:^<LuisAppId^>]
echo.
exit /b 1

27
LICENSE
Просмотреть файл

@ -1,21 +1,14 @@
MIT License
------------------------------------------- START OF LICENSE -----------------------------------------
Microsoft Azure Cognitive Services Speech SDK Javascript
Copyright (c) Microsoft Corporation
All rights reserved.
Copyright (c) Microsoft Corporation. All rights reserved.
MIT License
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the Software), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE
THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
----------------------------------------------- END OF LICENSE ------------------------------------------

Просмотреть файл

@ -1,5 +1,77 @@
# Microsoft Cognitive Services Speech SDK for JavaScript
# Contributing
Visit https://aka.ms/csspeech.
## Build the project
To build the project you need to install the required packages, and then run the actual build.
### Install the required packages
Installation of the required packages is required once in your enlistment. Our automated build restores the packages with the following command:
```
npm ci
```
This will install packages exactly matching the package configuration from `package-lock.json`.
In a development environment you can also use the following command:
```
npm install
```
### Run the build
Once the dependencies are installed run the build by
```
npm run build
```
or
```
npx gulp bundle
```
or
```
npx gulp compress
```
> Note: `npx` is packaged with NPM 5.2 or above. Update NPM / Node if you
> don't have it or install it globally with `npm install -g npx` (less
> preferable).
## Data / Telemetry
This project collects data and sends it to Microsoft to help monitor our
service performance and improve our products and services. Read the [Microsoft
Privacy Statement](https://aka.ms/csspeech/privacy) to learn more.
To disable telemetry, you can call the following API:
```javascript
// disable telemetry data
sdk.Recognizer.enableTelemetry(false);
```
This is a global setting and will disable telemetry for all recognizers
(already created or new recognizers).
We strongly recommend you keep telemetry enabled. With telemetry enabled you
transmit information about your platform (operating system and possibly, Speech
Service relevant information like microphone characteristics, etc.), and
information about the performance of the Speech Service (the time when you did
send data and when you received data). It can be used to tune the service,
monitor service performance and stability, and might help us to analyze
reported problems. Without telemetry enabled, it is not possible for do any
form of detailed analysis in case of a support request.
## Contributing
This project welcomes contributions and suggestions. Most contributions require you to agree to a
Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us

7
REDIST.txt Normal file
Просмотреть файл

@ -0,0 +1,7 @@
The following libraries can be redistributed if you are using the Cognitive
Services Speech SDK for JavaScript, subject to its license:
microsoft.cognitiveservices.speech.sdk.bundle.js
microsoft-cognitiveservices-speech-sdk NPM library
Depending on your usage, you only need to redistribute a subset of the above.

53
RunTests.cmd Normal file
Просмотреть файл

@ -0,0 +1,53 @@
@REM Copyright (c) Microsoft Corporation. All rights reserved.
@REM Licensed under the MIT license.
@echo off
setlocal
if "%~1" equ "/?" goto :Usage
if "%~1" equ "-?" goto :Usage
if "%~1" equ "?" goto :Usage
if "%~1" equ "/h" goto :Usage
if "%~1" equ "-h" goto :Usage
set TEST_SETTING_FILE_NAME=%~dp0\secrets\TestConfiguration.ts
set TEST_SETTING_FILE_EXISTED=0
if EXIST "%TEST_SETTING_FILE_NAME%" set TEST_SETTING_FILE_EXISTED=1
if "%~1" NEQ "" (
call "%~dp0BuildTestConfig.cmd" %* || (
echo Error creating test config.
exit /b 1
)
) else if %TEST_SETTING_FILE_EXISTED% EQU 0 (
echo Warning: No test config and no parameters specified. This will probably fail. 1>&2
)
pushd "%~dp0"
call npm run test
set NPM_ERROR=%ERRORLEVEL%
if %TEST_SETTING_FILE_EXISTED% EQU 0 (
del "%TEST_SETTING_FILE_NAME%"
)
popd
exit /b %NPM_ERROR%
@REM TODO fix usage
:Usage
@echo off
echo.
echo Usage: %~n0 [^<ParamName^>:^<Value^>]
echo.
echo Writes any ^<ParamName^>:^<Value^> pair to the test settings file for JavaScript bindings tests.
echo.
echo The file will be erased before new values are added.
echo.
echo If no values are specified, the existing file will not be modified.
echo.
echo Current settings available are: [SpeechSubscriptionKey:^<key^>] [SpeechRegion:^<region^>] [LuisSubscriptionKey:^<LuisKey^>] [LuisRegion:^<region^>] [LuidAppId:^<LuisAppId^>]
echo.
echo Once settings are written, executes the JS unit tests and if the settings file was created, it will be deleted.
exit /b 1

1
_config.yml Normal file
Просмотреть файл

@ -0,0 +1 @@
theme: jekyll-theme-minimal

5
bundleApp.js Normal file
Просмотреть файл

@ -0,0 +1,5 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
window.SpeechSDK = require('./distrib/lib/microsoft.cognitiveservices.speech.sdk.js');

Просмотреть файл

@ -13,6 +13,9 @@ resources:
- repo: self
clean: true
variables:
SPEECHSDK_JS_ROOT: .
jobs:
- job: Pre
@ -22,3 +25,8 @@ jobs:
steps:
- bash: ./ci/check-git-head.sh
displayName: Repository checks
- template: jsbuild.yml
parameters:
dependsOn: Pre
condition: true

72
ci/jsbuild.yml Normal file
Просмотреть файл

@ -0,0 +1,72 @@
#
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT license.
#
parameters:
dependsOn: ''
condition: succeeded()
jobs:
- job: JsBuild
dependsOn: ${{ parameters.dependsOn }}
condition: ${{ parameters.condition }}
pool:
name: Hosted VS2017
demands: npm
timeoutInMinutes: 60
steps:
- bash: npm ci && npm run civersion
displayName: Set version
workingDirectory: $(SPEECHSDK_JS_ROOT)
- bash: |
F=$(SPEECHSDK_JS_ROOT)/src/common.speech/RecognizerConfig.ts
[[ -f $F ]] || exit 1
perl -i.bak -p -e 'BEGIN { $c = 0 } $c += s/(?<=const SPEECHSDK_CLIENTSDK_VERSION = ")[^"]*/$(SPEECHSDK_SEMVER2NOMETA)/g; END { die "Patched SPEECHSDK_CLIENTSDK_VERSION $c time(s), expected 1.\n" if $c != 1 }' "$F"
E=$?
rm -f "$F.bak"
git diff
exit $E
displayName: Stamp SPEECHSDK_CLIENTSDK_VERSION
- bash: npm run build && npm pack
displayName: Build and pack SDK
workingDirectory: $(SPEECHSDK_JS_ROOT)
- script: |
RunTests.cmd ^
SpeechSubscriptionKey:$(speech-ne-s0-key1) ^
SpeechRegion:northeurope ^
LuisSubscriptionKey:$(luis-westus-s0-201809-key1) ^
LuisRegion:westus ^
SpeechTestEndpointId:ec3432b2-8584-4736-865a-556213b9f6fd
displayName: Run tests
workingDirectory: $(SPEECHSDK_JS_ROOT)
- task: PublishTestResults@2
displayName: Publish test results
- bash: |
set -u -e -o pipefail -x
PACKAGE_BASE=microsoft-cognitiveservices-speech-sdk
PACKAGE_NAME=$PACKAGE_BASE-$SPEECHSDK_SEMVER2NOMETA.tgz
PACKAGE_IN=$(SPEECHSDK_JS_ROOT)/$PACKAGE_NAME
PACKAGE_OUT="$(Build.ArtifactStagingDirectory)/Out/JavaScript/npm"
ZIP_OUT="$(Build.ArtifactStagingDirectory)/Out/JavaScript/SpeechSDK-JavaScript-$SPEECHSDK_SEMVER2NOMETA"
mkdir -p "$PACKAGE_OUT" "$ZIP_OUT"
cp --preserve "$PACKAGE_IN" "$PACKAGE_OUT"
echo SRI hash for microsoft.cognitiveservices.speech.sdk.bundle.js: sha512-"$(openssl dgst -sha512 -binary $(SPEECHSDK_JS_ROOT)/distrib/browser/microsoft.cognitiveservices.speech.sdk.bundle.js | openssl base64 -A)"
cp --preserve $(SPEECHSDK_JS_ROOT)/LICENSE $(SPEECHSDK_JS_ROOT)/REDIST.txt $(SPEECHSDK_JS_ROOT)/distrib/browser/microsoft.cognitiveservices.speech.sdk.bundle.* $(SPEECHSDK_JS_ROOT)/distrib/browser/microsoft.cognitiveservices.speech.sdk.bundle-min.* "$ZIP_OUT"
displayName: Create drop
- task: ArchiveFiles@2
inputs:
rootFolderOrFile: $(Build.ArtifactStagingDirectory)/Out/JavaScript/SpeechSDK-JavaScript-$(SPEECHSDK_SEMVER2NOMETA)
includeRootFolder: true
archiveType: zip
archiveFile: $(Build.ArtifactStagingDirectory)/Out/JavaScript/SpeechSDK-JavaScript-$(SPEECHSDK_SEMVER2NOMETA).zip
displayName: Create .zip
- bash: rm -rf "$(Build.ArtifactStagingDirectory)/Out/JavaScript/SpeechSDK-JavaScript-$(SPEECHSDK_SEMVER2NOMETA)"
displayName: Remove temporary directory
- task: PublishBuildArtifacts@1
displayName: Publish drop
inputs:
PathtoPublish: '$(Build.ArtifactStagingDirectory)/Out/JavaScript'
ArtifactName: JavaScript
publishLocation: Container

83
ci/version.js Normal file
Просмотреть файл

@ -0,0 +1,83 @@
//
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
//
(function () {
'use strict';
var semver = require("semver");
var exec = require("child_process").exec;
if (!process.env.npm_package_version) {
throw "npm_package_version not set; run this via 'npm run'"
}
var givenVersion = process.env.npm_package_version;
if (!semver.valid(givenVersion)) {
throw "Invalid version " + givenVersion;
}
var baseVersion = semver.major(givenVersion) + "." + semver.minor(givenVersion) + "." + semver.patch(givenVersion);
var prerelease = semver.prerelease(givenVersion)
var buildId = process.env.BUILD_BUILDID || "1"
var buildType = "dev"
var inAzureDevOps = false
if (process.env.SYSTEM_COLLECTIONID === "19422243-19b9-4d85-9ca6-bc961861d287" &&
(process.env.SYSTEM_DEFINITIONID === "4833" || process.env.SYSTEM_DEFINITIONID === "7863")) {
inAzureDevOps = true
if (process.env.BUILD_SOURCEBRANCH.match("^refs/heads/release/")) {
buildType = "prod"
} else if (process.env.BUILD_SOURCEBRANCH === "refs/heads/master" &&
(process.env.BUILD_REASON === "Schedule" ||
process.env.BUILD_REASON === "Manual")) {
buildType = "int"
}
}
// Check our version constraints
if (buildType === "prod") {
// Full version give in package.json
if (prerelease.length != 0 &&
(prerelease.length != 2 ||
!prerelease[0].match("^(?:alpha|beta|rc)$") ||
prerelease[1] < 1)) {
throw "For prod build types, version must have no pre-release tag, or alpha / beta / rc with a positive number."
}
} else if (prerelease.length != 3 ||
(prerelease[0] !== "alpha" || prerelease[1] !== 0 || prerelease[2] !== 1)) {
throw "For non-prod build types, checked-in version must end in -alpha.0.1"
}
var versionToUse
if (buildType === "dev") {
versionToUse = baseVersion + "-alpha.0." + buildId
} else if (buildType === "int") {
versionToUse = baseVersion + "-beta.0." + buildId
} else if (buildType === "prod") {
versionToUse = givenVersion
} else {
throw "Internal error. Unexpected build type: " + buildType
}
if (inAzureDevOps) {
console.log("##vso[task.setvariable variable=SPEECHSDK_SEMVER2NOMETA]" + versionToUse);
}
if (givenVersion !== versionToUse) {
console.log("Setting version to " + versionToUse);
exec("npm version --no-git-tag-version " + versionToUse, { cwd: __dirname + "/.." },
function (error) {
if (error) {
throw error;
}
}
);
}
}());

62
gulpfile.js Normal file
Просмотреть файл

@ -0,0 +1,62 @@
(function () {
'use strict';
var gulp = require("gulp");
var ts = require('gulp-typescript');
var sourcemaps = require('gulp-sourcemaps');
var tslint = require("gulp-tslint");
var uglify = require('gulp-uglify');
var rename = require('gulp-rename');
var pump = require('pump');
var webpack = require('webpack-stream');
gulp.task("build", function build () {
return gulp.src([
"src/**/*.ts",
"microsoft.cognitiveservices.speech.sdk.ts"],
{base: '.'})
.pipe(tslint({
formatter: "prose",
configuration: "tslint.json"
}))
.pipe(tslint.report({
summarizeFailureOutput: true
}))
.pipe(sourcemaps.init())
.pipe(ts({
target: "ES5",
declaration: true,
noImplicitAny: true,
removeComments: false,
outDir: 'distrib/lib'
}))
.pipe(sourcemaps.write('.'))
.pipe(gulp.dest('distrib/lib'));
});
gulp.task("bundle", gulp.series("build", function bundle () {
return gulp.src('bundleApp.js')
.pipe(webpack({
output: { filename: 'microsoft.cognitiveservices.speech.sdk.bundle.js' },
devtool: 'source-map',
module: {
rules: [{
enforce: 'pre',
test: /\.js$/,
loader: "source-map-loader"
}]
}
}))
.pipe(gulp.dest('distrib/browser'));
}));
gulp.task('compress', gulp.series("bundle", function(cb) {
return pump([
gulp.src('distrib/browser/microsoft.cognitiveservices.speech.sdk.bundle.js'),
rename(function(path) { path.basename = "microsoft.cognitiveservices.speech.sdk.bundle-min"; }),
uglify(),
gulp.dest('distrib/browser')
],
cb
);
}));
}());

13
jest.config.js Normal file
Просмотреть файл

@ -0,0 +1,13 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
module.exports = {
transform: {
"^.+\\.ts$": "ts-jest",
},
testRegex: "tests/.*Tests\\.ts$",
testPathIgnorePatterns: ["/lib/", "/node_modules/", "/src/"],
moduleFileExtensions: ["ts", "js", "jsx", "json", "node"],
collectCoverage: true,
"reporters": [ "default", "jest-junit" ],
setupTestFrameworkScriptFile:"./secrets/TestConfiguration.ts"
};

35
jsdoc-conf.json Normal file
Просмотреть файл

@ -0,0 +1,35 @@
{
"templates": {
"applicationName": "Microsoft Cognitive Services Speech SDK",
"disqus": "",
"googleAnalytics": "",
"openGraph": {
"title": "",
"type": "website",
"image": "",
"site_name": "",
"url": ""
}
},
"meta": {
"title": "Microsoft Cognitive Services Speech SDK",
"description": "Microsoft Cognitive Services Speech SDK",
"keyword": ""
},
"linenums": true,
"source": {
"include": [
"./distrib/src/sdk"
],
"includePattern": ".+\\.js(doc)?$",
"excludePattern": "(^|\\/|\\\\)_"
},
"opts": {
"encoding": "utf8",
"recurse": true,
"private": false,
"lenient": true,
"destination": "./outjs",
"template": "templates/default"
}
}

Просмотреть файл

@ -0,0 +1,11 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { ConsoleLoggingListener, LocalStorage, SessionStorage } from "./src/common.browser/Exports";
import { Events, Storage } from "./src/common/Exports";
// Common.Storage.SetLocalStorage(new Common.Browser.LocalStorage());
// Common.Storage.SetSessionStorage(new Common.Browser.SessionStorage());
Events.instance.attachListener(new ConsoleLoggingListener());
// Speech SDK API
export * from "./src/sdk/Exports";

11871
package-lock.json сгенерированный Normal file

Разница между файлами не показана из-за своего большого размера Загрузить разницу

75
package.json Normal file
Просмотреть файл

@ -0,0 +1,75 @@
{
"name": "microsoft-cognitiveservices-speech-sdk",
"author": "Microsoft Corporation",
"homepage": "https://docs.microsoft.com/azure/cognitive-services/speech-service/",
"version": "1.2.0-alpha.0.1",
"license": "MIT",
"description": "Microsoft Cognitive Services Speech SDK for JavaScript",
"keywords": [
"microsoft",
"cognitiveservices",
"speech",
"sdk",
"javascript",
"typescript",
"ts",
"js",
"browser",
"websocket",
"speechtotext"
],
"bugs": {
"url": "https://github.com/Microsoft/cognitive-services-speech-sdk-js/issues"
},
"repository": {
"type": "git",
"url": "https://github.com/Microsoft/cognitive-services-speech-sdk-js"
},
"main": "distrib/lib/src/sdk/Exports.js",
"types": "distrib/lib/src/sdk/Exports.d.ts",
"files": [
"distrib/lib/**/*",
"LICENSE",
"REDIST.txt"
],
"devDependencies": {
"@types/jest": "^23.3.1",
"@types/node": "^10.7.0",
"gulp": "^4.0.0",
"gulp-uglify": "^3.0.1",
"gulp-rename": "^1.4.0",
"gulp-sourcemaps": "^2.6.4",
"gulp-tslint": "^8.0.0",
"gulp-typescript": "^3.2.3",
"jest": "^23.5.0",
"jest-junit": "^5.1.0",
"semver": "^5.6.0",
"source-map-loader": "^0.2.3",
"ts-jest": "^23.1.3",
"tslint": "^5.11.0",
"typescript": "^3.2.1",
"webpack-stream": "^4.0.0"
},
"scripts": {
"build": "gulp compress",
"test": "npm run lint && npm run jest",
"jest": "jest",
"lint": "tslint -p tsconfig.json",
"civersion": "node ci/version.js"
},
"jest": {
"testEnvironment": "node"
},
"jest-junit": {
"suiteName": "jest tests",
"output": "./test-javascript-junit.xml",
"classNameTemplate": "{classname}-{title}",
"titleTemplate": "{classname}-{title}",
"ancestorSeparator": " <20> ",
"usePathForSuiteName": "true"
},
"dependencies": {
"@types/ws": "^6.0.1",
"ws": "^6.1.2"
}
}

Просмотреть файл

@ -0,0 +1,70 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { EventType, IEventListener, PlatformEvent } from "../common/Exports";
export class ConsoleLoggingListener implements IEventListener<PlatformEvent> {
private privLogLevelFilter: EventType;
public constructor(logLevelFilter: EventType = EventType.Warning) {
this.privLogLevelFilter = logLevelFilter;
}
public onEvent = (event: PlatformEvent): void => {
if (event.eventType >= this.privLogLevelFilter) {
const log = this.toString(event);
switch (event.eventType) {
case EventType.Debug:
// tslint:disable-next-line:no-console
console.debug(log);
break;
case EventType.Info:
// tslint:disable-next-line:no-console
console.info(log);
break;
case EventType.Warning:
// tslint:disable-next-line:no-console
console.warn(log);
break;
case EventType.Error:
// tslint:disable-next-line:no-console
console.error(log);
break;
default:
// tslint:disable-next-line:no-console
console.log(log);
break;
}
}
}
private toString = (event: any): string => {
const logFragments = [
`${event.EventTime}`,
`${event.Name}`,
];
for (const prop in event) {
if (prop && event.hasOwnProperty(prop) &&
prop !== "eventTime" && prop !== "eventType" &&
prop !== "eventId" && prop !== "name" &&
prop !== "constructor") {
const value = event[prop];
let valueToLog = "<NULL>";
if (value !== undefined && value !== null) {
if (typeof (value) === "number" || typeof (value) === "string") {
valueToLog = value.toString();
} else {
valueToLog = JSON.stringify(value);
}
}
logFragments.push(`${prop}: ${valueToLog}`);
}
}
return logFragments.join(" | ");
}
}

Просмотреть файл

@ -0,0 +1,14 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
export * from "./ConsoleLoggingListener";
export * from "./IRecorder";
export * from "./LocalStorage";
export * from "./MicAudioSource";
export * from "./FileAudioSource";
export * from "./OpusRecorder";
export * from "./PCMRecorder";
export * from "./SessionStorage";
export * from "./WebsocketConnection";
export * from "./WebsocketMessageAdapter";
export * from "./ReplayableAudioNode";

Просмотреть файл

@ -0,0 +1,184 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { AudioStreamFormat, AudioStreamFormatImpl } from "../../src/sdk/Audio/AudioStreamFormat";
import {
AudioSourceErrorEvent,
AudioSourceEvent,
AudioSourceInitializingEvent,
AudioSourceOffEvent,
AudioSourceReadyEvent,
AudioStreamNodeAttachedEvent,
AudioStreamNodeAttachingEvent,
AudioStreamNodeDetachedEvent,
AudioStreamNodeErrorEvent,
createNoDashGuid,
Events,
EventSource,
IAudioSource,
IAudioStreamNode,
IStringDictionary,
Promise,
PromiseHelper,
Stream,
StreamReader,
} from "../common/Exports";
export class FileAudioSource implements IAudioSource {
// Recommended sample rate (bytes/second).
private static readonly SAMPLE_RATE: number = 16000 * 2; // 16 kHz * 16 bits
// We should stream audio at no faster than 2x real-time (i.e., send five chunks
// per second, with the chunk size == sample rate in bytes per second * 2 / 5).
private static readonly CHUNK_SIZE: number = FileAudioSource.SAMPLE_RATE * 2 / 5;
private static readonly UPLOAD_INTERVAL: number = 200; // milliseconds
// 10 seconds of audio in bytes =
// sample rate (bytes/second) * 600 (seconds) + 44 (size of the wave header).
private static readonly MAX_SIZE: number = FileAudioSource.SAMPLE_RATE * 600 + 44;
private static readonly FILEFORMAT: AudioStreamFormatImpl = AudioStreamFormat.getWaveFormatPCM(16000, 16, 1) as AudioStreamFormatImpl;
private privStreams: IStringDictionary<Stream<ArrayBuffer>> = {};
private privId: string;
private privEvents: EventSource<AudioSourceEvent>;
private privFile: File;
public constructor(file: File, audioSourceId?: string) {
this.privId = audioSourceId ? audioSourceId : createNoDashGuid();
this.privEvents = new EventSource<AudioSourceEvent>();
this.privFile = file;
}
public get format(): AudioStreamFormat {
return FileAudioSource.FILEFORMAT;
}
public turnOn = (): Promise<boolean> => {
if (typeof FileReader === "undefined") {
const errorMsg = "Browser does not support FileReader.";
this.onEvent(new AudioSourceErrorEvent(errorMsg, "")); // initialization error - no streamid at this point
return PromiseHelper.fromError<boolean>(errorMsg);
} else if (this.privFile.name.lastIndexOf(".wav") !== this.privFile.name.length - 4) {
const errorMsg = this.privFile.name + " is not supported. Only WAVE files are allowed at the moment.";
this.onEvent(new AudioSourceErrorEvent(errorMsg, ""));
return PromiseHelper.fromError<boolean>(errorMsg);
} else if (this.privFile.size > FileAudioSource.MAX_SIZE) {
const errorMsg = this.privFile.name + " exceeds the maximum allowed file size (" + FileAudioSource.MAX_SIZE + ").";
this.onEvent(new AudioSourceErrorEvent(errorMsg, ""));
return PromiseHelper.fromError<boolean>(errorMsg);
}
this.onEvent(new AudioSourceInitializingEvent(this.privId)); // no stream id
this.onEvent(new AudioSourceReadyEvent(this.privId));
return PromiseHelper.fromResult(true);
}
public id = (): string => {
return this.privId;
}
public attach = (audioNodeId: string): Promise<IAudioStreamNode> => {
this.onEvent(new AudioStreamNodeAttachingEvent(this.privId, audioNodeId));
return this.upload(audioNodeId).onSuccessContinueWith<IAudioStreamNode>(
(streamReader: StreamReader<ArrayBuffer>) => {
this.onEvent(new AudioStreamNodeAttachedEvent(this.privId, audioNodeId));
return {
detach: () => {
streamReader.close();
delete this.privStreams[audioNodeId];
this.onEvent(new AudioStreamNodeDetachedEvent(this.privId, audioNodeId));
this.turnOff();
},
id: () => {
return audioNodeId;
},
read: () => {
return streamReader.read();
},
};
});
}
public detach = (audioNodeId: string): void => {
if (audioNodeId && this.privStreams[audioNodeId]) {
this.privStreams[audioNodeId].close();
delete this.privStreams[audioNodeId];
this.onEvent(new AudioStreamNodeDetachedEvent(this.privId, audioNodeId));
}
}
public turnOff = (): Promise<boolean> => {
for (const streamId in this.privStreams) {
if (streamId) {
const stream = this.privStreams[streamId];
if (stream && !stream.isClosed) {
stream.close();
}
}
}
this.onEvent(new AudioSourceOffEvent(this.privId)); // no stream now
return PromiseHelper.fromResult(true);
}
public get events(): EventSource<AudioSourceEvent> {
return this.privEvents;
}
private upload = (audioNodeId: string): Promise<StreamReader<ArrayBuffer>> => {
return this.turnOn()
.onSuccessContinueWith<StreamReader<ArrayBuffer>>((_: boolean) => {
const stream = new Stream<ArrayBuffer>(audioNodeId);
this.privStreams[audioNodeId] = stream;
const reader: FileReader = new FileReader();
let startOffset = 0;
let endOffset = FileAudioSource.CHUNK_SIZE;
const processNextChunk = (event: Event): void => {
if (stream.isClosed) {
return; // output stream was closed (somebody called TurnOff). We're done here.
}
stream.write(reader.result as ArrayBuffer);
if (endOffset < this.privFile.size) {
startOffset = endOffset;
endOffset = Math.min(endOffset + FileAudioSource.CHUNK_SIZE, this.privFile.size);
const chunk = this.privFile.slice(startOffset, endOffset);
reader.readAsArrayBuffer(chunk);
} else {
// we've written the entire file to the output stream, can close it now.
stream.close();
}
};
reader.onload = processNextChunk;
reader.onerror = (event: ProgressEvent) => {
const errorMsg = `Error occurred while processing '${this.privFile.name}'. ${event}`;
this.onEvent(new AudioStreamNodeErrorEvent(this.privId, audioNodeId, errorMsg));
throw new Error(errorMsg);
};
const chunk = this.privFile.slice(startOffset, endOffset);
reader.readAsArrayBuffer(chunk);
return stream.getReader();
});
}
private onEvent = (event: AudioSourceEvent): void => {
this.privEvents.onEvent(event);
Events.instance.onEvent(event);
}
}

Просмотреть файл

@ -0,0 +1,9 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { Stream } from "../common/Exports";
export interface IRecorder {
record(context: AudioContext, mediaStream: MediaStream, outputStream: Stream<ArrayBuffer>): void;
releaseMediaResources(context: AudioContext): void;
}

Просмотреть файл

@ -0,0 +1,44 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { ArgumentNullError, IKeyValueStorage } from "../common/Exports";
export class LocalStorage implements IKeyValueStorage {
public get = (key: string): string => {
if (!key) {
throw new ArgumentNullError("key");
}
return localStorage.getItem(key);
}
public getOrAdd = (key: string, valueToAdd: string): string => {
if (!key) {
throw new ArgumentNullError("key");
}
const value = localStorage.getItem(key);
if (value === null || value === undefined) {
localStorage.setItem(key, valueToAdd);
}
return localStorage.getItem(key);
}
public set = (key: string, value: string): void => {
if (!key) {
throw new ArgumentNullError("key");
}
localStorage.setItem(key, value);
}
public remove = (key: string): void => {
if (!key) {
throw new ArgumentNullError("key");
}
localStorage.removeItem(key);
}
}

Просмотреть файл

@ -0,0 +1,257 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { AudioStreamFormat, AudioStreamFormatImpl } from "../../src/sdk/Audio/AudioStreamFormat";
import {
AudioSourceErrorEvent,
AudioSourceEvent,
AudioSourceInitializingEvent,
AudioSourceOffEvent,
AudioSourceReadyEvent,
AudioStreamNodeAttachedEvent,
AudioStreamNodeAttachingEvent,
AudioStreamNodeDetachedEvent,
AudioStreamNodeErrorEvent,
createNoDashGuid,
Deferred,
Events,
EventSource,
IAudioSource,
IAudioStreamNode,
IStringDictionary,
Promise,
PromiseHelper,
Stream,
StreamReader,
} from "../common/Exports";
import { IRecorder } from "./IRecorder";
// Extending the default definition with browser specific definitions for backward compatibility
interface INavigatorUserMedia extends NavigatorUserMedia {
webkitGetUserMedia?: (constraints: MediaStreamConstraints, successCallback: NavigatorUserMediaSuccessCallback, errorCallback: NavigatorUserMediaErrorCallback) => void;
mozGetUserMedia?: (constraints: MediaStreamConstraints, successCallback: NavigatorUserMediaSuccessCallback, errorCallback: NavigatorUserMediaErrorCallback) => void;
msGetUserMedia?: (constraints: MediaStreamConstraints, successCallback: NavigatorUserMediaSuccessCallback, errorCallback: NavigatorUserMediaErrorCallback) => void;
}
export class MicAudioSource implements IAudioSource {
private static readonly AUDIOFORMAT: AudioStreamFormatImpl = AudioStreamFormat.getDefaultInputFormat() as AudioStreamFormatImpl;
private privStreams: IStringDictionary<Stream<ArrayBuffer>> = {};
private privId: string;
private privEvents: EventSource<AudioSourceEvent>;
private privInitializeDeferral: Deferred<boolean>;
private privRecorder: IRecorder;
private privMediaStream: MediaStream;
private privContext: AudioContext;
public constructor(recorder: IRecorder, audioSourceId?: string) {
this.privId = audioSourceId ? audioSourceId : createNoDashGuid();
this.privEvents = new EventSource<AudioSourceEvent>();
this.privRecorder = recorder;
}
public get format(): AudioStreamFormat {
return MicAudioSource.AUDIOFORMAT;
}
public turnOn = (): Promise<boolean> => {
if (this.privInitializeDeferral) {
return this.privInitializeDeferral.promise();
}
this.privInitializeDeferral = new Deferred<boolean>();
this.createAudioContext();
const nav = window.navigator as INavigatorUserMedia;
let getUserMedia = (
nav.getUserMedia ||
nav.webkitGetUserMedia ||
nav.mozGetUserMedia ||
nav.msGetUserMedia
);
if (!!nav.mediaDevices) {
getUserMedia = (constraints: MediaStreamConstraints, successCallback: NavigatorUserMediaSuccessCallback, errorCallback: NavigatorUserMediaErrorCallback): void => {
nav.mediaDevices
.getUserMedia(constraints)
.then(successCallback)
.catch(errorCallback);
};
}
if (!getUserMedia) {
const errorMsg = "Browser does not support getUserMedia.";
this.privInitializeDeferral.reject(errorMsg);
this.onEvent(new AudioSourceErrorEvent(errorMsg, "")); // mic initialized error - no streamid at this point
} else {
const next = () => {
this.onEvent(new AudioSourceInitializingEvent(this.privId)); // no stream id
getUserMedia(
{ audio: true, video: false },
(mediaStream: MediaStream) => {
this.privMediaStream = mediaStream;
this.onEvent(new AudioSourceReadyEvent(this.privId));
this.privInitializeDeferral.resolve(true);
}, (error: MediaStreamError) => {
const errorMsg = `Error occurred during microphone initialization: ${error}`;
const tmp = this.privInitializeDeferral;
// HACK: this should be handled through onError callbacks of all promises up the stack.
// Unfortunately, the current implementation does not provide an easy way to reject promises
// without a lot of code replication.
// TODO: fix promise implementation, allow for a graceful reject chaining.
this.privInitializeDeferral = null;
tmp.reject(errorMsg); // this will bubble up through the whole chain of promises,
// with each new level adding extra "Unhandled callback error" prefix to the error message.
// The following line is not guaranteed to be executed.
this.onEvent(new AudioSourceErrorEvent(this.privId, errorMsg));
});
};
if (this.privContext.state === "suspended") {
// NOTE: On iOS, the Web Audio API requires sounds to be triggered from an explicit user action.
// https://github.com/WebAudio/web-audio-api/issues/790
this.privContext.resume().then(next, (reason: any) => {
this.privInitializeDeferral.reject(`Failed to initialize audio context: ${reason}`);
});
} else {
next();
}
}
return this.privInitializeDeferral.promise();
}
public id = (): string => {
return this.privId;
}
public attach = (audioNodeId: string): Promise<IAudioStreamNode> => {
this.onEvent(new AudioStreamNodeAttachingEvent(this.privId, audioNodeId));
return this.listen(audioNodeId).onSuccessContinueWith<IAudioStreamNode>(
(streamReader: StreamReader<ArrayBuffer>) => {
this.onEvent(new AudioStreamNodeAttachedEvent(this.privId, audioNodeId));
return {
detach: () => {
streamReader.close();
delete this.privStreams[audioNodeId];
this.onEvent(new AudioStreamNodeDetachedEvent(this.privId, audioNodeId));
this.turnOff();
},
id: () => {
return audioNodeId;
},
read: () => {
return streamReader.read();
},
};
});
}
public detach = (audioNodeId: string): void => {
if (audioNodeId && this.privStreams[audioNodeId]) {
this.privStreams[audioNodeId].close();
delete this.privStreams[audioNodeId];
this.onEvent(new AudioStreamNodeDetachedEvent(this.privId, audioNodeId));
}
}
public turnOff = (): Promise<boolean> => {
for (const streamId in this.privStreams) {
if (streamId) {
const stream = this.privStreams[streamId];
if (stream) {
stream.close();
}
}
}
this.onEvent(new AudioSourceOffEvent(this.privId)); // no stream now
this.privInitializeDeferral = null;
this.destroyAudioContext();
return PromiseHelper.fromResult(true);
}
public get events(): EventSource<AudioSourceEvent> {
return this.privEvents;
}
private listen = (audioNodeId: string): Promise<StreamReader<ArrayBuffer>> => {
return this.turnOn()
.onSuccessContinueWith<StreamReader<ArrayBuffer>>((_: boolean) => {
const stream = new Stream<ArrayBuffer>(audioNodeId);
this.privStreams[audioNodeId] = stream;
try {
this.privRecorder.record(this.privContext, this.privMediaStream, stream);
} catch (error) {
this.onEvent(new AudioStreamNodeErrorEvent(this.privId, audioNodeId, error));
throw error;
}
return stream.getReader();
});
}
private onEvent = (event: AudioSourceEvent): void => {
this.privEvents.onEvent(event);
Events.instance.onEvent(event);
}
private createAudioContext = (): void => {
if (!!this.privContext) {
return;
}
// https://developer.mozilla.org/en-US/docs/Web/API/AudioContext
const AudioContext = ((window as any).AudioContext)
|| ((window as any).webkitAudioContext)
|| false;
if (!AudioContext) {
throw new Error("Browser does not support Web Audio API (AudioContext is not available).");
}
this.privContext = new AudioContext();
}
private destroyAudioContext = (): void => {
if (!this.privContext) {
return;
}
this.privRecorder.releaseMediaResources(this.privContext);
// This pattern brought to you by a bug in the TypeScript compiler where it
// confuses the ("close" in this.privContext) with this.privContext always being null as the alternate.
// https://github.com/Microsoft/TypeScript/issues/11498
let hasClose: boolean = false;
if ("close" in this.privContext) {
hasClose = true;
}
if (hasClose) {
this.privContext.close();
this.privContext = null;
} else if (null !== this.privContext && this.privContext.state === "running") {
// Suspend actually takes a callback, but analogous to the
// resume method, it'll be only fired if suspend is called
// in a direct response to a user action. The later is not always
// the case, as TurnOff is also called, when we receive an
// end-of-speech message from the service. So, doing a best effort
// fire-and-forget here.
this.privContext.suspend();
}
}
}

Просмотреть файл

@ -0,0 +1,59 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { Stream } from "../common/Exports";
import { IRecorder } from "./IRecorder";
// getting around the build error for MediaRecorder as Typescript does not have a definition for this one.
declare var MediaRecorder: any;
export class OpusRecorder implements IRecorder {
private privMediaResources: IMediaResources;
private privMediaRecorderOptions: any;
constructor(options?: { mimeType: string, bitsPerSecond: number }) {
this.privMediaRecorderOptions = options;
}
public record = (context: AudioContext, mediaStream: MediaStream, outputStream: Stream<ArrayBuffer>): void => {
const mediaRecorder: any = new MediaRecorder(mediaStream, this.privMediaRecorderOptions);
const timeslice = 100; // this is in ms - 100 ensures that the chunk doesn't exceed the max size of chunk allowed in WS connection
mediaRecorder.ondataavailable = (dataAvailableEvent: any) => {
if (outputStream) {
const reader = new FileReader();
reader.readAsArrayBuffer(dataAvailableEvent.data);
reader.onloadend = (event: ProgressEvent) => {
outputStream.write(reader.result as ArrayBuffer);
};
}
};
this.privMediaResources = {
recorder: mediaRecorder,
stream: mediaStream,
};
mediaRecorder.start(timeslice);
}
public releaseMediaResources = (context: AudioContext): void => {
if (this.privMediaResources.recorder.state !== "inactive") {
this.privMediaResources.recorder.stop();
}
this.privMediaResources.stream.getTracks().forEach((track: any) => track.stop());
}
}
interface IMediaResources {
stream: MediaStream;
recorder: any;
}
/* Declaring this inline to avoid compiler warnings
declare class MediaRecorder {
constructor(mediaStream: MediaStream, options: any);
public state: string;
public ondataavailable(dataAvailableEvent: any): void;
public stop(): void;
}*/

Просмотреть файл

@ -0,0 +1,75 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { RiffPcmEncoder, Stream } from "../common/Exports";
import { IRecorder } from "./IRecorder";
export class PcmRecorder implements IRecorder {
private privMediaResources: IMediaResources;
public record = (context: AudioContext, mediaStream: MediaStream, outputStream: Stream<ArrayBuffer>): void => {
const desiredSampleRate = 16000;
const scriptNode = (() => {
let bufferSize = 0;
try {
return context.createScriptProcessor(bufferSize, 1, 1);
} catch (error) {
// Webkit (<= version 31) requires a valid bufferSize.
bufferSize = 2048;
let audioSampleRate = context.sampleRate;
while (bufferSize < 16384 && audioSampleRate >= (2 * desiredSampleRate)) {
bufferSize <<= 1 ;
audioSampleRate >>= 1;
}
return context.createScriptProcessor(bufferSize, 1, 1);
}
})();
const waveStreamEncoder = new RiffPcmEncoder(context.sampleRate, desiredSampleRate);
let needHeader: boolean = true;
const that = this;
scriptNode.onaudioprocess = (event: AudioProcessingEvent) => {
const inputFrame = event.inputBuffer.getChannelData(0);
if (outputStream && !outputStream.isClosed) {
const waveFrame = waveStreamEncoder.encode(needHeader, inputFrame);
if (!!waveFrame) {
outputStream.write(waveFrame);
needHeader = false;
}
}
};
const micInput = context.createMediaStreamSource(mediaStream);
this.privMediaResources = {
scriptProcessorNode: scriptNode,
source: micInput,
stream: mediaStream,
};
micInput.connect(scriptNode);
scriptNode.connect(context.destination);
}
public releaseMediaResources = (context: AudioContext): void => {
if (this.privMediaResources) {
if (this.privMediaResources.scriptProcessorNode) {
this.privMediaResources.scriptProcessorNode.disconnect(context.destination);
this.privMediaResources.scriptProcessorNode = null;
}
if (this.privMediaResources.source) {
this.privMediaResources.source.disconnect();
this.privMediaResources.stream.getTracks().forEach((track: any) => track.stop());
this.privMediaResources.source = null;
}
}
}
}
interface IMediaResources {
source: MediaStreamAudioSourceNode;
scriptProcessorNode: ScriptProcessorNode;
stream: MediaStream;
}

Просмотреть файл

@ -0,0 +1,131 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { AudioStreamFormatImpl } from "../../src/sdk/Audio/AudioStreamFormat";
import {
IAudioStreamNode,
IStreamChunk,
Promise,
PromiseHelper,
} from "../common/Exports";
export class ReplayableAudioNode implements IAudioStreamNode {
private privAudioNode: IAudioStreamNode;
private privFormat: AudioStreamFormatImpl;
private privBuffers: BufferEntry[] = [];
private privReplayOffset: number = 0;
private privLastShrinkOffset: number = 0;
private privBufferStartOffset: number = 0;
private privBufferSerial: number = 0;
private privBufferedBytes: number = 0;
private privReplay: boolean = false;
public constructor(audioSource: IAudioStreamNode, format: AudioStreamFormatImpl) {
this.privAudioNode = audioSource;
this.privFormat = format;
}
public id = (): string => {
return this.privAudioNode.id();
}
// Reads and returns the next chunk of audio buffer.
// If replay of existing buffers are needed, read() will first seek and replay
// existing content, and upoin completion it will read new content from the underlying
// audio node, saving that content into the replayable buffers.
public read(): Promise<IStreamChunk<ArrayBuffer>> {
// if there is a replay request to honor.
if (!!this.privReplay && this.privBuffers.length !== 0) {
// Find the start point in the buffers.
// Offsets are in 100ns increments.
// So how many bytes do we need to seek to get the right offset?
const offsetToSeek: number = this.privReplayOffset - this.privBufferStartOffset;
let bytesToSeek: number = Math.round(offsetToSeek * this.privFormat.avgBytesPerSec * 1e-7);
if (0 !== (bytesToSeek % 2)) {
bytesToSeek++;
}
let i: number = 0;
while (i < this.privBuffers.length && bytesToSeek >= this.privBuffers[i].buffer.byteLength) {
bytesToSeek -= this.privBuffers[i++].buffer.byteLength;
}
const retVal: ArrayBuffer = this.privBuffers[i].buffer.slice(bytesToSeek);
this.privReplayOffset += (retVal.byteLength / this.privFormat.avgBytesPerSec) * 1e+7;
// If we've reached the end of the buffers, stop replaying.
if (i === this.privBuffers.length - 1) {
this.privReplay = false;
}
return PromiseHelper.fromResult<IStreamChunk<ArrayBuffer>>({
buffer: retVal,
isEnd: false,
});
}
return this.privAudioNode.read()
.onSuccessContinueWith((result: IStreamChunk<ArrayBuffer>) => {
if (result.buffer) {
this.privBuffers.push(new BufferEntry(result.buffer, this.privBufferSerial++, this.privBufferedBytes));
this.privBufferedBytes += result.buffer.byteLength;
}
return result;
});
}
public detach(): void {
this.privAudioNode.detach();
this.privBuffers = undefined;
}
public replay(): void {
if (0 !== this.privBuffers.length) {
this.privReplay = true;
this.privReplayOffset = this.privLastShrinkOffset;
}
}
// Shrinks the existing audio buffers to start at the new offset, or at the
// beginnign of the buffer closest to the requested offset.
// A replay request will start from the last shrink point.
public shrinkBuffers(offset: number): void {
this.privLastShrinkOffset = offset;
// Find the start point in the buffers.
// Offsets are in 100ns increments.
// So how many bytes do we need to seek to get the right offset?
const offsetToSeek: number = offset - this.privBufferStartOffset;
let bytesToSeek: number = Math.round(offsetToSeek * this.privFormat.avgBytesPerSec * 1e-7);
let i: number = 0;
while (i < this.privBuffers.length && bytesToSeek >= this.privBuffers[i].buffer.byteLength) {
bytesToSeek -= this.privBuffers[i++].buffer.byteLength;
}
this.privBufferStartOffset = Math.round(offset - ((bytesToSeek / this.privFormat.avgBytesPerSec) * 1e+7));
this.privBuffers = this.privBuffers.slice(i);
}
}
// Primary use of this class is to help debugging problems with the replay
// code. If the memory cost of alloc / dealloc gets too much, drop it and just use
// the ArrayBuffer directly.
// tslint:disable-next-line:max-classes-per-file
class BufferEntry {
public buffer: ArrayBuffer;
public serial: number;
public byteOffset: number;
public constructor(buffer: ArrayBuffer, serial: number, byteOffset: number) {
this.buffer = buffer;
this.serial = serial;
this.byteOffset = byteOffset;
}
}

Просмотреть файл

@ -0,0 +1,44 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { ArgumentNullError, IKeyValueStorage } from "../common/Exports";
export class SessionStorage implements IKeyValueStorage {
public get = (key: string): string => {
if (!key) {
throw new ArgumentNullError("key");
}
return sessionStorage.getItem(key);
}
public getOrAdd = (key: string, valueToAdd: string): string => {
if (!key) {
throw new ArgumentNullError("key");
}
const value = sessionStorage.getItem(key);
if (value === null || value === undefined) {
sessionStorage.setItem(key, valueToAdd);
}
return sessionStorage.getItem(key);
}
public set = (key: string, value: string): void => {
if (!key) {
throw new ArgumentNullError("key");
}
sessionStorage.setItem(key, value);
}
public remove = (key: string): void => {
if (!key) {
throw new ArgumentNullError("key");
}
sessionStorage.removeItem(key);
}
}

Просмотреть файл

@ -0,0 +1,113 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import {
ArgumentNullError,
ConnectionEvent,
ConnectionMessage,
ConnectionOpenResponse,
ConnectionState,
createNoDashGuid,
EventSource,
IConnection,
IStringDictionary,
IWebsocketMessageFormatter,
Promise,
} from "../common/Exports";
import { WebsocketMessageAdapter } from "./WebsocketMessageAdapter";
export class WebsocketConnection implements IConnection {
private privUri: string;
private privMessageFormatter: IWebsocketMessageFormatter;
private privConnectionMessageAdapter: WebsocketMessageAdapter;
private privId: string;
private privIsDisposed: boolean = false;
public constructor(
uri: string,
queryParameters: IStringDictionary<string>,
headers: IStringDictionary<string>,
messageFormatter: IWebsocketMessageFormatter,
connectionId?: string) {
if (!uri) {
throw new ArgumentNullError("uri");
}
if (!messageFormatter) {
throw new ArgumentNullError("messageFormatter");
}
this.privMessageFormatter = messageFormatter;
let queryParams = "";
let i = 0;
if (queryParameters) {
for (const paramName in queryParameters) {
if (paramName) {
queryParams += ((i === 0) && (uri.indexOf("?") === -1)) ? "?" : "&";
const val = encodeURIComponent(queryParameters[paramName]);
queryParams += `${paramName}=${val}`;
i++;
}
}
}
if (headers) {
for (const headerName in headers) {
if (headerName) {
queryParams += i === 0 ? "?" : "&";
const val = encodeURIComponent(headers[headerName]);
queryParams += `${headerName}=${val}`;
i++;
}
}
}
this.privUri = uri + queryParams;
this.privId = connectionId ? connectionId : createNoDashGuid();
this.privConnectionMessageAdapter = new WebsocketMessageAdapter(
this.privUri,
this.id,
this.privMessageFormatter);
}
public dispose = (): void => {
this.privIsDisposed = true;
if (this.privConnectionMessageAdapter) {
this.privConnectionMessageAdapter.close();
}
}
public isDisposed = (): boolean => {
return this.privIsDisposed;
}
public get id(): string {
return this.privId;
}
public state = (): ConnectionState => {
return this.privConnectionMessageAdapter.state;
}
public open = (): Promise<ConnectionOpenResponse> => {
return this.privConnectionMessageAdapter.open();
}
public send = (message: ConnectionMessage): Promise<boolean> => {
return this.privConnectionMessageAdapter.send(message);
}
public read = (): Promise<ConnectionMessage> => {
return this.privConnectionMessageAdapter.read();
}
public get events(): EventSource<ConnectionEvent> {
return this.privConnectionMessageAdapter.events;
}
}

Просмотреть файл

@ -0,0 +1,271 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import {
ArgumentNullError,
ConnectionClosedEvent,
ConnectionEstablishedEvent,
ConnectionEvent,
ConnectionMessage,
ConnectionMessageReceivedEvent,
ConnectionMessageSentEvent,
ConnectionOpenResponse,
ConnectionStartEvent,
ConnectionState,
Deferred,
Events,
EventSource,
IWebsocketMessageFormatter,
MessageType,
Promise,
PromiseHelper,
Queue,
RawWebsocketMessage,
} from "../common/Exports";
import ws = require("ws");
interface ISendItem {
Message: ConnectionMessage;
RawWebsocketMessage: RawWebsocketMessage;
sendStatusDeferral: Deferred<boolean>;
}
export class WebsocketMessageAdapter {
private privConnectionState: ConnectionState;
private privMessageFormatter: IWebsocketMessageFormatter;
private privWebsocketClient: WebSocket | ws;
private privSendMessageQueue: Queue<ISendItem>;
private privReceivingMessageQueue: Queue<ConnectionMessage>;
private privConnectionEstablishDeferral: Deferred<ConnectionOpenResponse>;
private privDisconnectDeferral: Deferred<boolean>;
private privConnectionEvents: EventSource<ConnectionEvent>;
private privConnectionId: string;
private privUri: string;
public static forceNpmWebSocket: boolean = false;
public constructor(
uri: string,
connectionId: string,
messageFormatter: IWebsocketMessageFormatter) {
if (!uri) {
throw new ArgumentNullError("uri");
}
if (!messageFormatter) {
throw new ArgumentNullError("messageFormatter");
}
this.privConnectionEvents = new EventSource<ConnectionEvent>();
this.privConnectionId = connectionId;
this.privMessageFormatter = messageFormatter;
this.privConnectionState = ConnectionState.None;
this.privUri = uri;
}
public get state(): ConnectionState {
return this.privConnectionState;
}
public open = (): Promise<ConnectionOpenResponse> => {
if (this.privConnectionState === ConnectionState.Disconnected) {
return PromiseHelper.fromError<ConnectionOpenResponse>(`Cannot open a connection that is in ${this.privConnectionState} state`);
}
if (this.privConnectionEstablishDeferral) {
return this.privConnectionEstablishDeferral.promise();
}
this.privConnectionEstablishDeferral = new Deferred<ConnectionOpenResponse>();
this.privConnectionState = ConnectionState.Connecting;
try {
if (typeof WebSocket !== "undefined" && !WebsocketMessageAdapter.forceNpmWebSocket) {
this.privWebsocketClient = new WebSocket(this.privUri);
} else {
this.privWebsocketClient = new ws(this.privUri);
}
this.privWebsocketClient.binaryType = "arraybuffer";
this.privReceivingMessageQueue = new Queue<ConnectionMessage>();
this.privDisconnectDeferral = new Deferred<boolean>();
this.privSendMessageQueue = new Queue<ISendItem>();
this.processSendQueue();
} catch (error) {
this.privConnectionEstablishDeferral.resolve(new ConnectionOpenResponse(500, error));
return this.privConnectionEstablishDeferral.promise();
}
this.onEvent(new ConnectionStartEvent(this.privConnectionId, this.privUri));
this.privWebsocketClient.onopen = (e: { target: WebSocket | ws }) => {
this.privConnectionState = ConnectionState.Connected;
this.onEvent(new ConnectionEstablishedEvent(this.privConnectionId));
this.privConnectionEstablishDeferral.resolve(new ConnectionOpenResponse(200, ""));
};
this.privWebsocketClient.onerror = (e: { error: any; message: string; type: string; target: WebSocket | ws }) => {
// TODO: Understand what this is error is. Will we still get onClose ?
if (this.privConnectionState !== ConnectionState.Connecting) {
// TODO: Is this required ?
// this.onEvent(new ConnectionErrorEvent(errorMsg, connectionId));
}
};
this.privWebsocketClient.onclose = (e: { wasClean: boolean; code: number; reason: string; target: WebSocket | ws }) => {
if (this.privConnectionState === ConnectionState.Connecting) {
this.privConnectionState = ConnectionState.Disconnected;
// this.onEvent(new ConnectionEstablishErrorEvent(this.connectionId, e.code, e.reason));
this.privConnectionEstablishDeferral.resolve(new ConnectionOpenResponse(e.code, e.reason));
} else {
this.onEvent(new ConnectionClosedEvent(this.privConnectionId, e.code, e.reason));
}
this.onClose(e.code, e.reason);
};
this.privWebsocketClient.onmessage = (e: { data: ws.Data; type: string; target: WebSocket | ws }) => {
const networkReceivedTime = new Date().toISOString();
if (this.privConnectionState === ConnectionState.Connected) {
const deferred = new Deferred<ConnectionMessage>();
// let id = ++this.idCounter;
this.privReceivingMessageQueue.enqueueFromPromise(deferred.promise());
if (e.data instanceof ArrayBuffer) {
const rawMessage = new RawWebsocketMessage(MessageType.Binary, e.data);
this.privMessageFormatter
.toConnectionMessage(rawMessage)
.on((connectionMessage: ConnectionMessage) => {
this.onEvent(new ConnectionMessageReceivedEvent(this.privConnectionId, networkReceivedTime, connectionMessage));
deferred.resolve(connectionMessage);
}, (error: string) => {
// TODO: Events for these ?
deferred.reject(`Invalid binary message format. Error: ${error}`);
});
} else {
const rawMessage = new RawWebsocketMessage(MessageType.Text, e.data);
this.privMessageFormatter
.toConnectionMessage(rawMessage)
.on((connectionMessage: ConnectionMessage) => {
this.onEvent(new ConnectionMessageReceivedEvent(this.privConnectionId, networkReceivedTime, connectionMessage));
deferred.resolve(connectionMessage);
}, (error: string) => {
// TODO: Events for these ?
deferred.reject(`Invalid text message format. Error: ${error}`);
});
}
}
};
return this.privConnectionEstablishDeferral.promise();
}
public send = (message: ConnectionMessage): Promise<boolean> => {
if (this.privConnectionState !== ConnectionState.Connected) {
return PromiseHelper.fromError<boolean>(`Cannot send on connection that is in ${this.privConnectionState} state`);
}
const messageSendStatusDeferral = new Deferred<boolean>();
const messageSendDeferral = new Deferred<ISendItem>();
this.privSendMessageQueue.enqueueFromPromise(messageSendDeferral.promise());
this.privMessageFormatter
.fromConnectionMessage(message)
.on((rawMessage: RawWebsocketMessage) => {
messageSendDeferral.resolve({
Message: message,
RawWebsocketMessage: rawMessage,
sendStatusDeferral: messageSendStatusDeferral,
});
}, (error: string) => {
messageSendDeferral.reject(`Error formatting the message. ${error}`);
});
return messageSendStatusDeferral.promise();
}
public read = (): Promise<ConnectionMessage> => {
if (this.privConnectionState !== ConnectionState.Connected) {
return PromiseHelper.fromError<ConnectionMessage>(`Cannot read on connection that is in ${this.privConnectionState} state`);
}
return this.privReceivingMessageQueue.dequeue();
}
public close = (reason?: string): Promise<boolean> => {
if (this.privWebsocketClient) {
if (this.privConnectionState !== ConnectionState.Disconnected) {
this.privWebsocketClient.close(1000, reason ? reason : "Normal closure by client");
}
} else {
const deferral = new Deferred<boolean>();
deferral.resolve(true);
return deferral.promise();
}
return this.privDisconnectDeferral.promise();
}
public get events(): EventSource<ConnectionEvent> {
return this.privConnectionEvents;
}
private sendRawMessage = (sendItem: ISendItem): Promise<boolean> => {
try {
// indicates we are draining the queue and it came with no message;
if (!sendItem) {
return PromiseHelper.fromResult(true);
}
this.onEvent(new ConnectionMessageSentEvent(this.privConnectionId, new Date().toISOString(), sendItem.Message));
this.privWebsocketClient.send(sendItem.RawWebsocketMessage.payload);
return PromiseHelper.fromResult(true);
} catch (e) {
return PromiseHelper.fromError<boolean>(`websocket send error: ${e}`);
}
}
private onClose = (code: number, reason: string): void => {
const closeReason = `Connection closed. ${code}: ${reason}`;
this.privConnectionState = ConnectionState.Disconnected;
this.privDisconnectDeferral.resolve(true);
this.privReceivingMessageQueue.dispose(reason);
this.privReceivingMessageQueue.drainAndDispose((pendingReceiveItem: ConnectionMessage) => {
// TODO: Events for these ?
// Logger.instance.onEvent(new LoggingEvent(LogType.Warning, null, `Failed to process received message. Reason: ${closeReason}, Message: ${JSON.stringify(pendingReceiveItem)}`));
}, closeReason);
this.privSendMessageQueue.drainAndDispose((pendingSendItem: ISendItem) => {
pendingSendItem.sendStatusDeferral.reject(closeReason);
}, closeReason);
}
private processSendQueue = (): void => {
this.privSendMessageQueue
.dequeue()
.on((sendItem: ISendItem) => {
// indicates we are draining the queue and it came with no message;
if (!sendItem) {
return;
}
this.sendRawMessage(sendItem)
.on((result: boolean) => {
sendItem.sendStatusDeferral.resolve(result);
this.processSendQueue();
}, (sendError: string) => {
sendItem.sendStatusDeferral.reject(sendError);
this.processSendQueue();
});
}, (error: string) => {
// do nothing
});
}
private onEvent = (event: ConnectionEvent): void => {
this.privConnectionEvents.onEvent(event);
Events.instance.onEvent(event);
}
}

Просмотреть файл

@ -0,0 +1,24 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { LanguageUnderstandingModelImpl } from "../sdk/LanguageUnderstandingModel";
/**
* @class AddedLmIntent
*/
// tslint:disable-next-line:max-classes-per-file
export class AddedLmIntent {
public modelImpl: LanguageUnderstandingModelImpl;
public intentName: string;
/**
* Creates and initializes an instance of this class.
* @constructor
* @param modelImpl - The model.
* @param intentName - The intent name.
*/
public constructor(modelImpl: LanguageUnderstandingModelImpl, intentName: string) {
this.modelImpl = modelImpl;
this.intentName = intentName;
}
}

Просмотреть файл

@ -0,0 +1,49 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { ArgumentNullError, Promise, PromiseHelper } from "../common/Exports";
import { AuthInfo, IAuthentication } from "./IAuthentication";
const AuthHeader: string = "Ocp-Apim-Subscription-Key";
/**
* @class
*/
export class CognitiveSubscriptionKeyAuthentication implements IAuthentication {
private privAuthInfo: AuthInfo;
/**
* Creates and initializes an instance of the CognitiveSubscriptionKeyAuthentication class.
* @constructor
* @param {string} subscriptionKey - The subscription key
*/
constructor(subscriptionKey: string) {
if (!subscriptionKey) {
throw new ArgumentNullError("subscriptionKey");
}
this.privAuthInfo = new AuthInfo(AuthHeader, subscriptionKey);
}
/**
* Fetches the subscription key.
* @member
* @function
* @public
* @param {string} authFetchEventId - The id to fetch.
*/
public fetch = (authFetchEventId: string): Promise<AuthInfo> => {
return PromiseHelper.fromResult(this.privAuthInfo);
}
/**
* Fetches the subscription key.
* @member
* @function
* @public
* @param {string} authFetchEventId - The id to fetch.
*/
public fetchOnExpiry = (authFetchEventId: string): Promise<AuthInfo> => {
return PromiseHelper.fromResult(this.privAuthInfo);
}
}

Просмотреть файл

@ -0,0 +1,33 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { ArgumentNullError, Promise } from "../common/Exports";
import { AuthInfo, IAuthentication } from "./IAuthentication";
const AuthHeader: string = "Authorization";
export class CognitiveTokenAuthentication implements IAuthentication {
private privFetchCallback: (authFetchEventId: string) => Promise<string>;
private privFetchOnExpiryCallback: (authFetchEventId: string) => Promise<string>;
constructor(fetchCallback: (authFetchEventId: string) => Promise<string>, fetchOnExpiryCallback: (authFetchEventId: string) => Promise<string>) {
if (!fetchCallback) {
throw new ArgumentNullError("fetchCallback");
}
if (!fetchOnExpiryCallback) {
throw new ArgumentNullError("fetchOnExpiryCallback");
}
this.privFetchCallback = fetchCallback;
this.privFetchOnExpiryCallback = fetchOnExpiryCallback;
}
public fetch = (authFetchEventId: string): Promise<AuthInfo> => {
return this.privFetchCallback(authFetchEventId).onSuccessContinueWith((token: string) => new AuthInfo(AuthHeader, token));
}
public fetchOnExpiry = (authFetchEventId: string): Promise<AuthInfo> => {
return this.privFetchOnExpiryCallback(authFetchEventId).onSuccessContinueWith((token: string) => new AuthInfo(AuthHeader, token));
}
}

Просмотреть файл

@ -0,0 +1,46 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { CancellationReason, ResultReason } from "../sdk/Exports";
import { RecognitionStatus } from "./Exports";
export class EnumTranslation {
public static implTranslateRecognitionResult(recognitionStatus: RecognitionStatus): ResultReason {
let reason = ResultReason.Canceled;
switch (recognitionStatus) {
case RecognitionStatus.Success:
reason = ResultReason.RecognizedSpeech;
break;
case RecognitionStatus.NoMatch:
case RecognitionStatus.InitialSilenceTimeout:
case RecognitionStatus.BabbleTimeout:
case RecognitionStatus.EndOfDictation:
reason = ResultReason.NoMatch;
break;
case RecognitionStatus.Error:
default:
reason = ResultReason.Canceled;
break;
}
return reason;
}
public static implTranslateCancelResult(recognitionStatus: RecognitionStatus): CancellationReason {
let reason = CancellationReason.EndOfStream;
switch (recognitionStatus) {
case RecognitionStatus.Success:
case RecognitionStatus.EndOfDictation:
case RecognitionStatus.NoMatch:
reason = CancellationReason.EndOfStream;
break;
case RecognitionStatus.InitialSilenceTimeout:
case RecognitionStatus.BabbleTimeout:
case RecognitionStatus.Error:
default:
reason = CancellationReason.Error;
break;
}
return reason;
}
}

Просмотреть файл

@ -0,0 +1,35 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
// Make sure not to export internal modules.
//
export * from "./CognitiveSubscriptionKeyAuthentication";
export * from "./CognitiveTokenAuthentication";
export * from "./IAuthentication";
export * from "./IConnectionFactory";
export * from "./IntentConnectionFactory";
export * from "./RecognitionEvents";
export * from "./ServiceRecognizerBase";
export * from "./RecognizerConfig";
export * from "./SpeechServiceInterfaces";
export * from "./WebsocketMessageFormatter";
export * from "./SpeechConnectionFactory";
export * from "./TranslationConnectionFactory";
export * from "./EnumTranslation";
export * from "./ServiceMessages/Enums";
export * from "./ServiceMessages/TranslationSynthesisEnd";
export * from "./ServiceMessages/TranslationHypothesis";
export * from "./ServiceMessages/TranslationPhrase";
export * from "./TranslationServiceRecognizer";
export * from "./ServiceMessages/SpeechDetected";
export * from "./ServiceMessages/SpeechHypothesis";
export * from "./SpeechServiceRecognizer";
export * from "./ServiceMessages/DetailedSpeechPhrase";
export * from "./ServiceMessages/SimpleSpeechPhrase";
export * from "./AddedLmIntent";
export * from "./IntentServiceRecognizer";
export * from "./ServiceMessages/IntentResponse";
export * from "./RequestSession";
export const OutputFormatPropertyName: string = "OutputFormat";
export const CancellationErrorCodePropertyName: string = "CancellationErrorCode";

Просмотреть файл

@ -0,0 +1,27 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { Promise } from "../common/Exports";
export interface IAuthentication {
fetch(authFetchEventId: string): Promise<AuthInfo>;
fetchOnExpiry(authFetchEventId: string): Promise<AuthInfo>;
}
export class AuthInfo {
private privHeaderName: string;
private privToken: string;
public constructor(headerName: string, token: string) {
this.privHeaderName = headerName;
this.privToken = token;
}
public get headerName(): string {
return this.privHeaderName;
}
public get token(): string {
return this.privToken;
}
}

Просмотреть файл

@ -0,0 +1,13 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { IConnection } from "../common/Exports";
import { AuthInfo } from "./IAuthentication";
import { RecognizerConfig } from "./RecognizerConfig";
export interface IConnectionFactory {
create(
config: RecognizerConfig,
authInfo: AuthInfo,
connectionId?: string): IConnection;
}

Просмотреть файл

@ -0,0 +1,105 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { WebsocketConnection } from "../common.browser/Exports";
import { IConnection, IStringDictionary, Storage } from "../common/Exports";
import { PropertyId } from "../sdk/Exports";
import { AuthInfo, IConnectionFactory, RecognizerConfig, WebsocketMessageFormatter } from "./Exports";
const TestHooksParamName: string = "testhooks";
const ConnectionIdHeader: string = "X-ConnectionId";
export class IntentConnectionFactory implements IConnectionFactory {
public create = (
config: RecognizerConfig,
authInfo: AuthInfo,
connectionId?: string): IConnection => {
let endpoint: string = config.parameters.getProperty(PropertyId.SpeechServiceConnection_Endpoint);
if (!endpoint) {
const region: string = config.parameters.getProperty(PropertyId.SpeechServiceConnection_IntentRegion);
endpoint = this.host() + Storage.local.getOrAdd("TranslationRelativeUri", "/speech/" + this.getSpeechRegionFromIntentRegion(region) + "/recognition/interactive/cognitiveservices/v1");
}
const queryParams: IStringDictionary<string> = {
format: "simple",
language: config.parameters.getProperty(PropertyId.SpeechServiceConnection_RecoLanguage),
};
if (this.isDebugModeEnabled) {
queryParams[TestHooksParamName] = "1";
}
const headers: IStringDictionary<string> = {};
headers[authInfo.headerName] = authInfo.token;
headers[ConnectionIdHeader] = connectionId;
return new WebsocketConnection(endpoint, queryParams, headers, new WebsocketMessageFormatter(), connectionId);
}
private host(): string {
return Storage.local.getOrAdd("Host", "wss://speech.platform.bing.com");
}
private get isDebugModeEnabled(): boolean {
const value = Storage.local.getOrAdd("IsDebugModeEnabled", "false");
return value.toLowerCase() === "true";
}
private getSpeechRegionFromIntentRegion(intentRegion: string): string {
switch (intentRegion) {
case "West US":
case "US West":
case "westus":
return "uswest";
case "West US 2":
case "US West 2":
case "westus2":
return "uswest2";
case "South Central US":
case "US South Central":
case "southcentralus":
return "ussouthcentral";
case "West Central US":
case "US West Central":
case "westcentralus":
return "uswestcentral";
case "East US":
case "US East":
case "eastus":
return "useast";
case "East US 2":
case "US East 2":
case "eastus2":
return "useast2";
case "West Europe":
case "Europe West":
case "westeurope":
return "europewest";
case "North Europe":
case "Europe North":
case "northeurope":
return "europenorth";
case "Brazil South":
case "South Brazil":
case "southbrazil":
return "brazilsouth";
case "Australia East":
case "East Australia":
case "eastaustralia":
return "australiaeast";
case "Southeast Asia":
case "Asia Southeast":
case "southeastasia":
return "asiasoutheast";
case "East Asia":
case "Asia East":
case "eastasia":
return "asiaeast";
default:
return intentRegion;
}
}
}

Просмотреть файл

@ -0,0 +1,290 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { IAudioSource, IConnection } from "../common/Exports";
import {
CancellationErrorCode,
CancellationReason,
IntentRecognitionCanceledEventArgs,
IntentRecognitionEventArgs,
IntentRecognitionResult,
IntentRecognizer,
PropertyCollection,
PropertyId,
ResultReason,
SpeechRecognitionResult,
} from "../sdk/Exports";
import {
AddedLmIntent,
CancellationErrorCodePropertyName,
EnumTranslation,
IntentResponse,
RequestSession,
ServiceRecognizerBase,
SimpleSpeechPhrase,
SpeechHypothesis,
} from "./Exports";
import { IAuthentication } from "./IAuthentication";
import { IConnectionFactory } from "./IConnectionFactory";
import { RecognizerConfig } from "./RecognizerConfig";
import { SpeechConnectionMessage } from "./SpeechConnectionMessage.Internal";
// tslint:disable-next-line:max-classes-per-file
export class IntentServiceRecognizer extends ServiceRecognizerBase {
private privIntentRecognizer: IntentRecognizer;
private privAddedLmIntents: { [id: string]: AddedLmIntent; };
private privIntentDataSent: boolean;
private privUmbrellaIntent: AddedLmIntent;
private privPendingIntentArgs: IntentRecognitionEventArgs;
public constructor(
authentication: IAuthentication,
connectionFactory: IConnectionFactory,
audioSource: IAudioSource,
recognizerConfig: RecognizerConfig,
recognizer: IntentRecognizer,
intentDataSent: boolean) {
super(authentication, connectionFactory, audioSource, recognizerConfig, recognizer);
this.privIntentRecognizer = recognizer;
this.privIntentDataSent = intentDataSent;
}
public setIntents(addedIntents: { [id: string]: AddedLmIntent; }, umbrellaIntent: AddedLmIntent): void {
this.privAddedLmIntents = addedIntents;
this.privUmbrellaIntent = umbrellaIntent;
}
protected processTypeSpecificMessages(
connectionMessage: SpeechConnectionMessage,
requestSession: RequestSession,
connection: IConnection,
successCallback?: (e: IntentRecognitionResult) => void,
errorCallBack?: (e: string) => void): void {
let result: IntentRecognitionResult;
let ev: IntentRecognitionEventArgs;
switch (connectionMessage.path.toLowerCase()) {
case "speech.hypothesis":
const speechHypothesis: SpeechHypothesis = SpeechHypothesis.fromJSON(connectionMessage.textBody);
result = new IntentRecognitionResult(
undefined,
requestSession.requestId,
ResultReason.RecognizingIntent,
speechHypothesis.Text,
speechHypothesis.Duration,
speechHypothesis.Offset + requestSession.currentTurnAudioOffset,
undefined,
connectionMessage.textBody,
undefined);
ev = new IntentRecognitionEventArgs(result, speechHypothesis.Offset + requestSession.currentTurnAudioOffset, requestSession.sessionId);
if (!!this.privIntentRecognizer.recognizing) {
try {
this.privIntentRecognizer.recognizing(this.privIntentRecognizer, ev);
/* tslint:disable:no-empty */
} catch (error) {
// Not going to let errors in the event handler
// trip things up.
}
}
break;
case "speech.phrase":
const simple: SimpleSpeechPhrase = SimpleSpeechPhrase.fromJSON(connectionMessage.textBody);
result = new IntentRecognitionResult(
undefined,
requestSession.requestId,
EnumTranslation.implTranslateRecognitionResult(simple.RecognitionStatus),
simple.DisplayText,
simple.Duration,
simple.Offset + requestSession.currentTurnAudioOffset,
undefined,
connectionMessage.textBody,
undefined);
ev = new IntentRecognitionEventArgs(result, result.offset + requestSession.currentTurnAudioOffset, requestSession.sessionId);
const sendEvent: () => void = () => {
if (this.privRecognizerConfig.isContinuousRecognition) {
// For continuous recognition telemetry has to be sent for every phrase as per spec.
this.sendTelemetryData(requestSession, requestSession.getTelemetry());
}
if (!!this.privIntentRecognizer.recognized) {
try {
this.privIntentRecognizer.recognized(this.privIntentRecognizer, ev);
/* tslint:disable:no-empty */
} catch (error) {
// Not going to let errors in the event handler
// trip things up.
}
}
// report result to promise.
if (!!successCallback) {
try {
successCallback(result);
} catch (e) {
if (!!errorCallBack) {
errorCallBack(e);
}
}
// Only invoke the call back once.
// and if it's successful don't invoke the
// error after that.
successCallback = undefined;
errorCallBack = undefined;
}
};
// If intent data was sent, the terminal result for this recognizer is an intent being found.
// If no intent data was sent, the terminal event is speech recognition being successful.
if (false === this.privIntentDataSent || ResultReason.NoMatch === ev.result.reason) {
sendEvent();
} else {
// Squirrel away the args, when the response event arrives it will build upon them
// and then return
this.privPendingIntentArgs = ev;
}
break;
case "response":
// Response from LUIS
if (this.privRecognizerConfig.isContinuousRecognition) {
// For continuous recognition telemetry has to be sent for every phrase as per spec.
this.sendTelemetryData(requestSession, requestSession.getTelemetry());
}
ev = this.privPendingIntentArgs;
this.privPendingIntentArgs = undefined;
if (undefined === ev) {
if ("" === connectionMessage.textBody) {
// This condition happens if there is nothing but silence in the
// audio sent to the service.
return;
}
// Odd... Not sure this can happen
ev = new IntentRecognitionEventArgs(new IntentRecognitionResult(), 0 /*TODO*/, requestSession.sessionId);
}
const intentResponse: IntentResponse = IntentResponse.fromJSON(connectionMessage.textBody);
// If LUIS didn't return anything, send the existing event, else
// modify it to show the match.
// See if the intent found is in the list of intents asked for.
let addedIntent: AddedLmIntent = this.privAddedLmIntents[intentResponse.topScoringIntent.intent];
if (this.privUmbrellaIntent !== undefined) {
addedIntent = this.privUmbrellaIntent;
}
if (null !== intentResponse && addedIntent !== undefined) {
const intentId = addedIntent.intentName === undefined ? intentResponse.topScoringIntent.intent : addedIntent.intentName;
let reason = ev.result.reason;
if (undefined !== intentId) {
reason = ResultReason.RecognizedIntent;
}
// make sure, properties is set.
const properties = (undefined !== ev.result.properties) ?
ev.result.properties : new PropertyCollection();
properties.setProperty(PropertyId.LanguageUnderstandingServiceResponse_JsonResult, connectionMessage.textBody);
ev = new IntentRecognitionEventArgs(
new IntentRecognitionResult(
intentId,
ev.result.resultId,
reason,
ev.result.text,
ev.result.duration,
ev.result.offset + requestSession.currentTurnAudioOffset,
ev.result.errorDetails,
ev.result.json,
properties),
ev.offset + requestSession.currentTurnAudioOffset,
ev.sessionId);
}
if (!!this.privIntentRecognizer.recognized) {
try {
this.privIntentRecognizer.recognized(this.privIntentRecognizer, ev);
/* tslint:disable:no-empty */
} catch (error) {
// Not going to let errors in the event handler
// trip things up.
}
}
// report result to promise.
if (!!successCallback) {
try {
successCallback(ev.result);
} catch (e) {
if (!!errorCallBack) {
errorCallBack(e);
}
}
// Only invoke the call back once.
// and if it's successful don't invoke the
// error after that.
successCallback = undefined;
errorCallBack = undefined;
}
break;
default:
break;
}
}
// Cancels recognition.
protected cancelRecognition(
sessionId: string,
requestId: string,
cancellationReason: CancellationReason,
errorCode: CancellationErrorCode,
error: string,
cancelRecoCallback: (e: SpeechRecognitionResult) => void): void {
if (!!this.privIntentRecognizer.canceled) {
const properties: PropertyCollection = new PropertyCollection();
properties.setProperty(CancellationErrorCodePropertyName, CancellationErrorCode[errorCode]);
const cancelEvent: IntentRecognitionCanceledEventArgs = new IntentRecognitionCanceledEventArgs(
cancellationReason,
error,
errorCode,
undefined,
undefined,
sessionId);
try {
this.privIntentRecognizer.canceled(this.privIntentRecognizer, cancelEvent);
/* tslint:disable:no-empty */
} catch { }
if (!!cancelRecoCallback) {
const result: IntentRecognitionResult = new IntentRecognitionResult(
undefined, // Intent Id
requestId,
ResultReason.Canceled,
undefined, // Text
undefined, // Druation
undefined, // Offset
error,
undefined, // Json
properties);
try {
cancelRecoCallback(result);
/* tslint:disable:no-empty */
} catch { }
}
}
}
}

Просмотреть файл

@ -0,0 +1,26 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
export class QueryParameterNames {
public static get TestHooksParamName(): string {
return "testhooks";
}
public static get ConnectionIdHeader(): string {
return "X-ConnectionId";
}
public static get DeploymentIdParamName(): string {
return "cid";
}
public static get FormatParamName(): string {
return "format";
}
public static get LanguageParamName(): string {
return "language";
}
public static get TranslationFromParamName(): string {
return "from";
}
public static get TranslationToParamName(): string {
return "to";
}
}

Просмотреть файл

@ -0,0 +1,173 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { EventType, PlatformEvent } from "../common/Exports";
export class SpeechRecognitionEvent extends PlatformEvent {
private privRequestId: string;
private privSessionId: string;
constructor(eventName: string, requestId: string, sessionId: string, eventType: EventType = EventType.Info) {
super(eventName, eventType);
this.privRequestId = requestId;
this.privSessionId = sessionId;
}
public get requestId(): string {
return this.privRequestId;
}
public get sessionId(): string {
return this.privSessionId;
}
}
// tslint:disable-next-line:max-classes-per-file
export class RecognitionTriggeredEvent extends SpeechRecognitionEvent {
private privAudioSourceId: string;
private privAudioNodeId: string;
constructor(requestId: string, sessionId: string, audioSourceId: string, audioNodeId: string) {
super("RecognitionTriggeredEvent", requestId, sessionId);
this.privAudioSourceId = audioSourceId;
this.privAudioNodeId = audioNodeId;
}
public get audioSourceId(): string {
return this.privAudioSourceId;
}
public get audioNodeId(): string {
return this.privAudioNodeId;
}
}
// tslint:disable-next-line:max-classes-per-file
export class ListeningStartedEvent extends SpeechRecognitionEvent {
private privAudioSourceId: string;
private privAudioNodeId: string;
constructor(requestId: string, sessionId: string, audioSourceId: string, audioNodeId: string) {
super("ListeningStartedEvent", requestId, sessionId);
this.privAudioSourceId = audioSourceId;
this.privAudioNodeId = audioNodeId;
}
public get audioSourceId(): string {
return this.privAudioSourceId;
}
public get audioNodeId(): string {
return this.privAudioNodeId;
}
}
// tslint:disable-next-line:max-classes-per-file
export class ConnectingToServiceEvent extends SpeechRecognitionEvent {
private privAuthFetchEventid: string;
constructor(requestId: string, authFetchEventid: string, sessionId: string) {
super("ConnectingToServiceEvent", requestId, sessionId);
this.privAuthFetchEventid = authFetchEventid;
}
public get authFetchEventid(): string {
return this.privAuthFetchEventid;
}
}
// tslint:disable-next-line:max-classes-per-file
export class RecognitionStartedEvent extends SpeechRecognitionEvent {
private privAudioSourceId: string;
private privAudioNodeId: string;
private privAuthFetchEventId: string;
constructor(requestId: string, audioSourceId: string, audioNodeId: string, authFetchEventId: string, sessionId: string) {
super("RecognitionStartedEvent", requestId, sessionId);
this.privAudioSourceId = audioSourceId;
this.privAudioNodeId = audioNodeId;
this.privAuthFetchEventId = authFetchEventId;
}
public get audioSourceId(): string {
return this.privAudioSourceId;
}
public get audioNodeId(): string {
return this.privAudioNodeId;
}
public get authFetchEventId(): string {
return this.privAuthFetchEventId;
}
}
export enum RecognitionCompletionStatus {
Success,
AudioSourceError,
AudioSourceTimeout,
AuthTokenFetchError,
AuthTokenFetchTimeout,
UnAuthorized,
ConnectTimeout,
ConnectError,
ClientRecognitionActivityTimeout,
UnknownError,
}
// tslint:disable-next-line:max-classes-per-file
export class RecognitionEndedEvent extends SpeechRecognitionEvent {
private privAudioSourceId: string;
private privAudioNodeId: string;
private privAuthFetchEventId: string;
private privServiceTag: string;
private privStatus: RecognitionCompletionStatus;
private privError: string;
constructor(
requestId: string,
audioSourceId: string,
audioNodeId: string,
authFetchEventId: string,
sessionId: string,
serviceTag: string,
status: RecognitionCompletionStatus,
error: string) {
super("RecognitionEndedEvent", requestId, sessionId, status === RecognitionCompletionStatus.Success ? EventType.Info : EventType.Error);
this.privAudioSourceId = audioSourceId;
this.privAudioNodeId = audioNodeId;
this.privAuthFetchEventId = authFetchEventId;
this.privStatus = status;
this.privError = error;
this.privServiceTag = serviceTag;
}
public get audioSourceId(): string {
return this.privAudioSourceId;
}
public get audioNodeId(): string {
return this.privAudioNodeId;
}
public get authFetchEventId(): string {
return this.privAuthFetchEventId;
}
public get serviceTag(): string {
return this.privServiceTag;
}
public get status(): RecognitionCompletionStatus {
return this.privStatus;
}
public get error(): string {
return this.privError;
}
}

Просмотреть файл

@ -0,0 +1,136 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { PropertyCollection } from "../sdk/Exports";
export enum RecognitionMode {
Interactive,
Conversation,
Dictation,
}
export enum SpeechResultFormat {
Simple,
Detailed,
}
export class RecognizerConfig {
private privRecognitionMode: RecognitionMode = RecognitionMode.Interactive;
private privPlatformConfig: PlatformConfig;
private privRecognitionActivityTimeout: number;
private privSpeechConfig: PropertyCollection;
constructor(
platformConfig: PlatformConfig,
recognitionMode: RecognitionMode = RecognitionMode.Interactive,
speechConfig: PropertyCollection) {
this.privPlatformConfig = platformConfig ? platformConfig : new PlatformConfig(new Context(null));
this.privRecognitionMode = recognitionMode;
this.privRecognitionActivityTimeout = recognitionMode === RecognitionMode.Interactive ? 8000 : 25000;
this.privSpeechConfig = speechConfig;
}
public get parameters(): PropertyCollection {
return this.privSpeechConfig;
}
public get recognitionMode(): RecognitionMode {
return this.privRecognitionMode;
}
public get platformConfig(): PlatformConfig {
return this.privPlatformConfig;
}
public get recognitionActivityTimeout(): number {
return this.privRecognitionActivityTimeout;
}
public get isContinuousRecognition(): boolean {
return this.privRecognitionMode !== RecognitionMode.Interactive;
}
}
// tslint:disable-next-line:max-classes-per-file
export class PlatformConfig {
private context: Context;
constructor(context: Context) {
this.context = context;
}
public serialize = (): string => {
return JSON.stringify(this, (key: any, value: any): any => {
if (value && typeof value === "object") {
const replacement: any = {};
for (const k in value) {
if (Object.hasOwnProperty.call(value, k)) {
replacement[k && k.charAt(0).toLowerCase() + k.substring(1)] = value[k];
}
}
return replacement;
}
return value;
});
}
public get Context(): Context {
return this.context;
}
}
// tslint:disable-next-line:max-classes-per-file
export class Context {
public system: System;
public os: OS;
constructor(os: OS) {
this.system = new System();
this.os = os;
}
}
// tslint:disable-next-line:max-classes-per-file
export class System {
public name: string;
public version: string;
public build: string;
public lang: string;
constructor() {
// Note: below will be patched for official builds.
const SPEECHSDK_CLIENTSDK_VERSION = "1.1.0-alpha.0.1";
this.name = "SpeechSDK";
this.version = SPEECHSDK_CLIENTSDK_VERSION;
this.build = "JavaScript";
this.lang = "JavaScript";
}
}
// tslint:disable-next-line:max-classes-per-file
export class OS {
public platform: string;
public name: string;
public version: string;
constructor(platform: string, name: string, version: string) {
this.platform = platform;
this.name = name;
this.version = version;
}
}
// tslint:disable-next-line:max-classes-per-file
export class Device {
public manufacturer: string;
public model: string;
public version: string;
constructor(manufacturer: string, model: string, version: string) {
this.manufacturer = manufacturer;
this.model = model;
this.version = version;
}
}

Просмотреть файл

@ -0,0 +1,191 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { ReplayableAudioNode } from "../common.browser/Exports";
import {
createNoDashGuid,
Deferred,
Events,
IAudioStreamNode,
IDetachable,
IEventSource,
PlatformEvent,
Promise,
} from "../common/Exports";
import {
ConnectingToServiceEvent,
ListeningStartedEvent,
RecognitionStartedEvent,
RecognitionTriggeredEvent,
SpeechRecognitionEvent,
} from "./RecognitionEvents";
import { ServiceTelemetryListener } from "./ServiceTelemetryListener.Internal";
export class RequestSession {
private privIsDisposed: boolean = false;
private privServiceTelemetryListener: ServiceTelemetryListener;
private privDetachables: IDetachable[] = new Array<IDetachable>();
private privRequestId: string;
private privAudioSourceId: string;
private privAudioNodeId: string;
private privAudioNode: ReplayableAudioNode;
private privAuthFetchEventId: string;
private privIsAudioNodeDetached: boolean = false;
private privIsCompleted: boolean = false;
private privRequestCompletionDeferral: Deferred<boolean>;
private privIsSpeechEnded: boolean = false;
private privIsCanceled: boolean = false;
private privContextJson: string;
private privTurnStartAudioOffset: number = 0;
private privLastRecoOffset: number = 0;
protected privSessionId: string;
constructor(audioSourceId: string, contextJson: string) {
this.privAudioSourceId = audioSourceId;
this.privRequestId = createNoDashGuid();
this.privAudioNodeId = createNoDashGuid();
this.privRequestCompletionDeferral = new Deferred<boolean>();
this.privContextJson = contextJson;
this.privServiceTelemetryListener = new ServiceTelemetryListener(this.privRequestId, this.privAudioSourceId, this.privAudioNodeId);
this.onEvent(new RecognitionTriggeredEvent(this.requestId, this.privSessionId, this.privAudioSourceId, this.privAudioNodeId));
}
public get contextJson(): string {
return this.privContextJson;
}
public get sessionId(): string {
return this.privSessionId;
}
public get requestId(): string {
return this.privRequestId;
}
public get audioNodeId(): string {
return this.privAudioNodeId;
}
public get completionPromise(): Promise<boolean> {
return this.privRequestCompletionDeferral.promise();
}
public get isSpeechEnded(): boolean {
return this.privIsSpeechEnded;
}
public get isCompleted(): boolean {
return this.privIsCompleted;
}
public get isCanceled(): boolean {
return this.privIsCanceled;
}
public get currentTurnAudioOffset(): number {
return this.privTurnStartAudioOffset;
}
public listenForServiceTelemetry(eventSource: IEventSource<PlatformEvent>): void {
this.privDetachables.push(eventSource.attachListener(this.privServiceTelemetryListener));
}
public onAudioSourceAttachCompleted = (audioNode: ReplayableAudioNode, isError: boolean, error?: string): void => {
this.privAudioNode = audioNode;
if (isError) {
this.onComplete();
} else {
this.onEvent(new ListeningStartedEvent(this.privRequestId, this.privSessionId, this.privAudioSourceId, this.privAudioNodeId));
}
}
public onPreConnectionStart = (authFetchEventId: string, connectionId: string): void => {
this.privAuthFetchEventId = authFetchEventId;
this.privSessionId = connectionId;
this.onEvent(new ConnectingToServiceEvent(this.privRequestId, this.privAuthFetchEventId, this.privSessionId));
}
public onAuthCompleted = (isError: boolean, error?: string): void => {
if (isError) {
this.onComplete();
}
}
public onConnectionEstablishCompleted = (statusCode: number, reason?: string): void => {
if (statusCode === 200) {
this.onEvent(new RecognitionStartedEvent(this.requestId, this.privAudioSourceId, this.privAudioNodeId, this.privAuthFetchEventId, this.privSessionId));
this.privAudioNode.replay();
this.privTurnStartAudioOffset = this.privLastRecoOffset;
return;
} else if (statusCode === 403) {
this.onComplete();
} else {
this.onComplete();
}
}
public onServiceTurnEndResponse = (continuousRecognition: boolean): void => {
if (!continuousRecognition || this.isSpeechEnded) {
this.onComplete();
} else {
// Start a new request set.
this.privTurnStartAudioOffset = this.privLastRecoOffset;
this.privRequestId = createNoDashGuid();
this.privAudioNode.replay();
}
}
public onServiceRecognized(offset: number): void {
this.privLastRecoOffset = offset;
this.privAudioNode.shrinkBuffers(offset);
}
public dispose = (error?: string): void => {
if (!this.privIsDisposed) {
// we should have completed by now. If we did not its an unknown error.
this.privIsDisposed = true;
for (const detachable of this.privDetachables) {
detachable.detach();
}
this.privServiceTelemetryListener.dispose();
}
}
public getTelemetry = (): string => {
return this.privServiceTelemetryListener.getTelemetry();
}
public onCancelled(): void {
this.privIsCanceled = true;
}
// Should be called with the audioNode for this session has indicated that it is out of speech.
public onSpeechEnded(): void {
this.privIsSpeechEnded = true;
}
protected onEvent = (event: SpeechRecognitionEvent): void => {
this.privServiceTelemetryListener.onEvent(event);
Events.instance.onEvent(event);
}
private onComplete = (): void => {
if (!this.privIsCompleted) {
this.privIsCompleted = true;
this.detachAudioNode();
}
}
private detachAudioNode = (): void => {
if (!this.privIsAudioNodeDetached) {
this.privIsAudioNodeDetached = true;
if (this.privAudioNode) {
this.privAudioNode.detach();
}
}
}
}

Просмотреть файл

@ -0,0 +1,46 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { RecognitionStatus } from "../Exports";
// speech.phrase for detailed
export interface IDetailedSpeechPhrase {
RecognitionStatus: RecognitionStatus;
NBest: IPhrase[];
Duration?: number;
Offset?: number;
}
export interface IPhrase {
Confidence?: number;
Lexical: string;
ITN: string;
MaskedITN: string;
Display: string;
}
export class DetailedSpeechPhrase implements IDetailedSpeechPhrase {
private privDetailedSpeechPhrase: IDetailedSpeechPhrase;
private constructor(json: string) {
this.privDetailedSpeechPhrase = JSON.parse(json);
this.privDetailedSpeechPhrase.RecognitionStatus = (RecognitionStatus as any)[this.privDetailedSpeechPhrase.RecognitionStatus];
}
public static fromJSON(json: string): DetailedSpeechPhrase {
return new DetailedSpeechPhrase(json);
}
public get RecognitionStatus(): RecognitionStatus {
return this.privDetailedSpeechPhrase.RecognitionStatus;
}
public get NBest(): IPhrase[] {
return this.privDetailedSpeechPhrase.NBest;
}
public get Duration(): number {
return this.privDetailedSpeechPhrase.Duration;
}
public get Offset(): number {
return this.privDetailedSpeechPhrase.Offset;
}
}

Просмотреть файл

@ -0,0 +1,35 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
/**
* @class SynthesisStatus
* @private
*/
export enum SynthesisStatus {
/**
* The response contains valid audio data.
* @member SynthesisStatus.Success
*/
Success,
/**
* Indicates the end of audio data. No valid audio data is included in the message.
* @member SynthesisStatus.SynthesisEnd
*/
SynthesisEnd,
/**
* Indicates an error occurred during synthesis data processing.
* @member SynthesisStatus.Error
*/
Error,
}
export enum RecognitionStatus {
Success,
NoMatch,
InitialSilenceTimeout,
BabbleTimeout,
Error,
EndOfDictation,
}

Просмотреть файл

@ -0,0 +1,46 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
// response
export interface IIntentResponse {
query: string;
topScoringIntent: ISingleIntent;
entities: IIntentEntity[];
}
export interface IIntentEntity {
entity: string;
type: string;
startIndex: number;
endIndex: number;
score: number;
}
export interface ISingleIntent {
intent: string;
score: number;
}
export class IntentResponse implements IIntentResponse {
private privIntentResponse: IIntentResponse;
private constructor(json: string) {
this.privIntentResponse = JSON.parse(json);
}
public static fromJSON(json: string): IntentResponse {
return new IntentResponse(json);
}
public get query(): string {
return this.privIntentResponse.query;
}
public get topScoringIntent(): ISingleIntent {
return this.privIntentResponse.topScoringIntent;
}
public get entities(): IIntentEntity[] {
return this.privIntentResponse.entities;
}
}

Просмотреть файл

@ -0,0 +1,41 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { RecognitionStatus } from "../Exports";
// speech.phrase
export interface ISimpleSpeechPhrase {
RecognitionStatus: RecognitionStatus;
DisplayText: string;
Offset?: number;
Duration?: number;
}
export class SimpleSpeechPhrase implements ISimpleSpeechPhrase {
private privSimpleSpeechPhrase: ISimpleSpeechPhrase;
private constructor(json: string) {
this.privSimpleSpeechPhrase = JSON.parse(json);
this.privSimpleSpeechPhrase.RecognitionStatus = (RecognitionStatus as any)[this.privSimpleSpeechPhrase.RecognitionStatus];
}
public static fromJSON(json: string): SimpleSpeechPhrase {
return new SimpleSpeechPhrase(json);
}
public get RecognitionStatus(): RecognitionStatus {
return this.privSimpleSpeechPhrase.RecognitionStatus;
}
public get DisplayText(): string {
return this.privSimpleSpeechPhrase.DisplayText;
}
public get Offset(): number {
return this.privSimpleSpeechPhrase.Offset;
}
public get Duration(): number {
return this.privSimpleSpeechPhrase.Duration;
}
}

Просмотреть файл

@ -0,0 +1,23 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
// speech.endDetected
export interface ISpeechDetected {
Offset: number;
}
export class SpeechDetected implements ISpeechDetected {
private privSpeechStartDetected: ISpeechDetected;
private constructor(json: string) {
this.privSpeechStartDetected = JSON.parse(json);
}
public static fromJSON(json: string): SpeechDetected {
return new SpeechDetected(json);
}
public get Offset(): number {
return this.privSpeechStartDetected.Offset;
}
}

Просмотреть файл

@ -0,0 +1,33 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
// speech.hypothesis
export interface ISpeechHypothesis {
Text: string;
Offset: number;
Duration: number;
}
export class SpeechHypothesis implements ISpeechHypothesis {
private privSpeechHypothesis: ISpeechHypothesis;
private constructor(json: string) {
this.privSpeechHypothesis = JSON.parse(json);
}
public static fromJSON(json: string): SpeechHypothesis {
return new SpeechHypothesis(json);
}
public get Text(): string {
return this.privSpeechHypothesis.Text;
}
public get Offset(): number {
return this.privSpeechHypothesis.Offset;
}
public get Duration(): number {
return this.privSpeechHypothesis.Duration;
}
}

Просмотреть файл

@ -0,0 +1,42 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { ITranslations } from "../Exports";
import { TranslationStatus } from "../TranslationStatus";
// translation.hypothesis
export interface ITranslationHypothesis {
Duration: number;
Offset: number;
Text: string;
Translation: ITranslations;
}
export class TranslationHypothesis implements ITranslationHypothesis {
private privTranslationHypothesis: ITranslationHypothesis;
private constructor(json: string) {
this.privTranslationHypothesis = JSON.parse(json);
this.privTranslationHypothesis.Translation.TranslationStatus = (TranslationStatus as any)[this.privTranslationHypothesis.Translation.TranslationStatus];
}
public static fromJSON(json: string): TranslationHypothesis {
return new TranslationHypothesis(json);
}
public get Duration(): number {
return this.privTranslationHypothesis.Duration;
}
public get Offset(): number {
return this.privTranslationHypothesis.Offset;
}
public get Text(): string {
return this.privTranslationHypothesis.Text;
}
public get Translation(): ITranslations {
return this.privTranslationHypothesis.Translation;
}
}

Просмотреть файл

@ -0,0 +1,50 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { ITranslations, RecognitionStatus } from "../Exports";
import { TranslationStatus } from "../TranslationStatus";
// translation.phrase
export interface ITranslationPhrase {
RecognitionStatus: RecognitionStatus;
Offset: number;
Duration: number;
Text: string;
Translation: ITranslations;
}
export class TranslationPhrase implements ITranslationPhrase {
private privTranslationPhrase: ITranslationPhrase;
private constructor(json: string) {
this.privTranslationPhrase = JSON.parse(json);
this.privTranslationPhrase.RecognitionStatus = (RecognitionStatus as any)[this.privTranslationPhrase.RecognitionStatus];
if (this.privTranslationPhrase.Translation !== undefined) {
this.privTranslationPhrase.Translation.TranslationStatus = (TranslationStatus as any)[this.privTranslationPhrase.Translation.TranslationStatus];
}
}
public static fromJSON(json: string): TranslationPhrase {
return new TranslationPhrase(json);
}
public get RecognitionStatus(): RecognitionStatus {
return this.privTranslationPhrase.RecognitionStatus;
}
public get Offset(): number {
return this.privTranslationPhrase.Offset;
}
public get Duration(): number {
return this.privTranslationPhrase.Duration;
}
public get Text(): string {
return this.privTranslationPhrase.Text;
}
public get Translation(): ITranslations {
return this.privTranslationPhrase.Translation;
}
}

Просмотреть файл

@ -0,0 +1,31 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { SynthesisStatus } from "../Exports";
// translation.synthesis.end
export interface ITranslationSynthesisEnd {
SynthesisStatus: SynthesisStatus;
FailureReason: string;
}
export class TranslationSynthesisEnd implements ITranslationSynthesisEnd {
private privSynthesisEnd: ITranslationSynthesisEnd;
private constructor(json: string) {
this.privSynthesisEnd = JSON.parse(json);
this.privSynthesisEnd.SynthesisStatus = (SynthesisStatus as any)[this.privSynthesisEnd.SynthesisStatus];
}
public static fromJSON(json: string): TranslationSynthesisEnd {
return new TranslationSynthesisEnd(json);
}
public get SynthesisStatus(): SynthesisStatus {
return this.privSynthesisEnd.SynthesisStatus;
}
public get FailureReason(): string {
return this.privSynthesisEnd.FailureReason;
}
}

Просмотреть файл

@ -0,0 +1,500 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { ReplayableAudioNode } from "../common.browser/Exports";
import {
ArgumentNullError,
ConnectionClosedEvent,
ConnectionEvent,
ConnectionMessage,
ConnectionOpenResponse,
ConnectionState,
createNoDashGuid,
Deferred,
IAudioSource,
IAudioStreamNode,
IConnection,
IDisposable,
IStreamChunk,
MessageType,
Promise,
PromiseHelper,
PromiseResult,
} from "../common/Exports";
import { AudioStreamFormatImpl } from "../sdk/Audio/AudioStreamFormat";
import {
CancellationErrorCode,
CancellationReason,
PropertyId,
RecognitionEventArgs,
Recognizer,
SessionEventArgs,
SpeechRecognitionResult,
} from "../sdk/Exports";
import {
RequestSession,
SpeechDetected,
} from "./Exports";
import {
AuthInfo,
IAuthentication,
} from "./IAuthentication";
import { IConnectionFactory } from "./IConnectionFactory";
import { RecognizerConfig } from "./RecognizerConfig";
import { SpeechConnectionMessage } from "./SpeechConnectionMessage.Internal";
export abstract class ServiceRecognizerBase implements IDisposable {
private privAuthentication: IAuthentication;
private privConnectionFactory: IConnectionFactory;
private privAudioSource: IAudioSource;
private privSpeechConfigConnectionId: string;
private privConnectionConfigurationPromise: Promise<IConnection>;
private privConnectionId: string;
private privAuthFetchEventId: string;
private privIsDisposed: boolean;
private privRecognizer: Recognizer;
protected privRecognizerConfig: RecognizerConfig;
public constructor(
authentication: IAuthentication,
connectionFactory: IConnectionFactory,
audioSource: IAudioSource,
recognizerConfig: RecognizerConfig,
recognizer: Recognizer) {
if (!authentication) {
throw new ArgumentNullError("authentication");
}
if (!connectionFactory) {
throw new ArgumentNullError("connectionFactory");
}
if (!audioSource) {
throw new ArgumentNullError("audioSource");
}
if (!recognizerConfig) {
throw new ArgumentNullError("recognizerConfig");
}
this.privAuthentication = authentication;
this.privConnectionFactory = connectionFactory;
this.privAudioSource = audioSource;
this.privRecognizerConfig = recognizerConfig;
this.privIsDisposed = false;
this.privRecognizer = recognizer;
}
public get audioSource(): IAudioSource {
return this.privAudioSource;
}
public isDisposed(): boolean {
return this.privIsDisposed;
}
public dispose(reason?: string): void {
this.privIsDisposed = true;
if (this.privConnectionConfigurationPromise) {
this.privConnectionConfigurationPromise.onSuccessContinueWith((connection: IConnection) => {
connection.dispose(reason);
});
}
}
public recognize(
speechContextJson: string,
successCallback: (e: SpeechRecognitionResult) => void,
errorCallBack: (e: string) => void,
): Promise<boolean> {
const requestSession = new RequestSession(this.privAudioSource.id(), speechContextJson);
requestSession.listenForServiceTelemetry(this.privAudioSource.events);
return this.audioSource
.attach(requestSession.audioNodeId)
.continueWithPromise<boolean>((result: PromiseResult<IAudioStreamNode>) => {
let audioNode: ReplayableAudioNode;
if (result.isError) {
this.cancelRecognitionLocal(requestSession, CancellationReason.Error, CancellationErrorCode.ConnectionFailure, result.error, successCallback);
return PromiseHelper.fromError<boolean>(result.error);
} else {
audioNode = new ReplayableAudioNode(result.result, this.audioSource.format as AudioStreamFormatImpl);
requestSession.onAudioSourceAttachCompleted(audioNode, false);
}
return this.configureConnection(requestSession)
.on((_: IConnection) => {
const sessionStartEventArgs: SessionEventArgs = new SessionEventArgs(requestSession.sessionId);
if (!!this.privRecognizer.sessionStarted) {
this.privRecognizer.sessionStarted(this.privRecognizer, sessionStartEventArgs);
}
const messageRetrievalPromise = this.receiveMessage(requestSession, successCallback, errorCallBack);
const audioSendPromise = this.sendAudio(audioNode, requestSession);
/* tslint:disable:no-empty */
audioSendPromise.on((_: boolean) => { }, (error: string) => {
this.cancelRecognitionLocal(requestSession, CancellationReason.Error, CancellationErrorCode.RuntimeError, error, successCallback);
});
const completionPromise = PromiseHelper.whenAll([messageRetrievalPromise, audioSendPromise]);
return completionPromise.on((r: boolean) => {
requestSession.dispose();
this.sendTelemetryData(requestSession, requestSession.getTelemetry());
}, (error: string) => {
requestSession.dispose(error);
this.sendTelemetryData(requestSession, requestSession.getTelemetry());
this.cancelRecognitionLocal(requestSession, CancellationReason.Error, CancellationErrorCode.RuntimeError, error, successCallback);
});
}, (error: string) => {
this.cancelRecognitionLocal(requestSession, CancellationReason.Error, CancellationErrorCode.ConnectionFailure, error, successCallback);
}).on(() => {
return requestSession.completionPromise;
}, (error: string) => {
this.cancelRecognitionLocal(requestSession, CancellationReason.Error, CancellationErrorCode.RuntimeError, error, successCallback);
}).onSuccessContinueWithPromise((_: IConnection): Promise<boolean> => {
return PromiseHelper.fromResult(true);
});
});
}
// Called when telemetry data is sent to the service.
// Used for testing Telemetry capture.
public static telemetryData: (json: string) => void;
public static telemetryDataEnabled: boolean = true;
protected abstract processTypeSpecificMessages(
connectionMessage: SpeechConnectionMessage,
requestSession: RequestSession,
connection: IConnection,
successCallback?: (e: SpeechRecognitionResult) => void,
errorCallBack?: (e: string) => void): void;
protected sendTelemetryData = (requestSession: RequestSession, telemetryData: string) => {
if (ServiceRecognizerBase.telemetryDataEnabled !== true ||
this.privIsDisposed) {
return PromiseHelper.fromResult(true);
}
if (!!ServiceRecognizerBase.telemetryData) {
try {
ServiceRecognizerBase.telemetryData(telemetryData);
/* tslint:disable:no-empty */
} catch { }
}
return this.fetchConnection(requestSession).onSuccessContinueWithPromise((connection: IConnection): Promise<boolean> => {
return connection.send(new SpeechConnectionMessage(
MessageType.Text,
"telemetry",
requestSession.requestId,
"application/json",
telemetryData));
});
}
// Cancels recognition.
protected abstract cancelRecognition(
sessionId: string,
requestId: string,
cancellationReason: CancellationReason,
errorCode: CancellationErrorCode,
error: string,
cancelRecoCallback: (r: SpeechRecognitionResult) => void): void;
// Cancels recognition.
protected cancelRecognitionLocal(
requestSession: RequestSession,
cancellationReason: CancellationReason,
errorCode: CancellationErrorCode,
error: string,
cancelRecoCallback: (r: SpeechRecognitionResult) => void): void {
if (!requestSession.isCanceled) {
requestSession.onCancelled();
this.cancelRecognition(
requestSession.sessionId,
requestSession.requestId,
cancellationReason,
errorCode,
error,
cancelRecoCallback);
}
}
private fetchConnection = (requestSession: RequestSession): Promise<IConnection> => {
return this.configureConnection(requestSession);
}
private configureConnection = (requestSession: RequestSession, isUnAuthorized: boolean = false): Promise<IConnection> => {
if (this.privConnectionConfigurationPromise) {
if (this.privConnectionConfigurationPromise.result().isCompleted &&
(this.privConnectionConfigurationPromise.result().isError
|| this.privConnectionConfigurationPromise.result().result.state() === ConnectionState.Disconnected)) {
this.privConnectionId = null;
this.privConnectionConfigurationPromise = null;
return this.configureConnection(requestSession);
} else {
// requestSession.onConnectionEstablishCompleted(200);
return this.privConnectionConfigurationPromise;
}
}
this.privAuthFetchEventId = createNoDashGuid();
this.privConnectionId = createNoDashGuid();
requestSession.onPreConnectionStart(this.privAuthFetchEventId, this.privConnectionId);
const authPromise = isUnAuthorized ? this.privAuthentication.fetchOnExpiry(this.privAuthFetchEventId) : this.privAuthentication.fetch(this.privAuthFetchEventId);
this.privConnectionConfigurationPromise = authPromise
.continueWithPromise((result: PromiseResult<AuthInfo>) => {
if (result.isError) {
requestSession.onAuthCompleted(true, result.error);
throw new Error(result.error);
} else {
requestSession.onAuthCompleted(false);
}
const connection: IConnection = this.privConnectionFactory.create(this.privRecognizerConfig, result.result, this.privConnectionId);
requestSession.listenForServiceTelemetry(connection.events);
return connection.open().onSuccessContinueWithPromise((response: ConnectionOpenResponse): Promise<IConnection> => {
if (response.statusCode === 200) {
requestSession.onPreConnectionStart(this.privAuthFetchEventId, this.privConnectionId);
requestSession.onConnectionEstablishCompleted(response.statusCode);
// requestSession.listenForServiceTelemetry(this.privConnectionFetchPromise.result().result.events);
return this.sendSpeechConfig(connection, requestSession, this.privRecognizerConfig.platformConfig.serialize())
.onSuccessContinueWithPromise((_: boolean) => {
return this.sendSpeechContext(connection, requestSession, requestSession.contextJson).onSuccessContinueWith((_: boolean) => {
return connection;
});
});
} else if (response.statusCode === 403 && !isUnAuthorized) {
return this.configureConnection(requestSession, true);
} else {
requestSession.onConnectionEstablishCompleted(response.statusCode, response.reason);
return PromiseHelper.fromError<IConnection>(`Unable to contact server. StatusCode: ${response.statusCode}, ${this.privRecognizerConfig.parameters.getProperty(PropertyId.SpeechServiceConnection_Endpoint)} Reason: ${response.reason}`);
}
});
});
return this.privConnectionConfigurationPromise;
}
private receiveMessage = (
requestSession: RequestSession,
successCallback: (e: SpeechRecognitionResult) => void,
errorCallBack: (e: string) => void,
): Promise<boolean> => {
return this.fetchConnection(requestSession).onSuccessContinueWithPromise((connection: IConnection): Promise<boolean> => {
return connection.read()
.onSuccessContinueWithPromise((message: ConnectionMessage) => {
if (this.privIsDisposed) {
// We're done.
return PromiseHelper.fromResult(true);
}
// indicates we are draining the queue and it came with no message;
if (!message) {
if (requestSession.isCompleted) {
return PromiseHelper.fromResult(true);
} else {
return this.receiveMessage(requestSession, successCallback, errorCallBack);
}
}
const connectionMessage = SpeechConnectionMessage.fromConnectionMessage(message);
if (connectionMessage.requestId.toLowerCase() === requestSession.requestId.toLowerCase()) {
switch (connectionMessage.path.toLowerCase()) {
case "turn.start":
break;
case "speech.startdetected":
const speechStartDetected: SpeechDetected = SpeechDetected.fromJSON(connectionMessage.textBody);
const speechStartEventArgs = new RecognitionEventArgs(speechStartDetected.Offset, requestSession.sessionId);
if (!!this.privRecognizer.speechStartDetected) {
this.privRecognizer.speechStartDetected(this.privRecognizer, speechStartEventArgs);
}
break;
case "speech.enddetected":
let json: string;
if (connectionMessage.textBody.length > 0) {
json = connectionMessage.textBody;
} else {
// If the request was empty, the JSON returned is empty.
json = "{ Offset: 0 }";
}
const speechStopDetected: SpeechDetected = SpeechDetected.fromJSON(json);
requestSession.onServiceRecognized(speechStopDetected.Offset + requestSession.currentTurnAudioOffset);
const speechStopEventArgs = new RecognitionEventArgs(speechStopDetected.Offset + requestSession.currentTurnAudioOffset, requestSession.sessionId);
if (!!this.privRecognizer.speechEndDetected) {
this.privRecognizer.speechEndDetected(this.privRecognizer, speechStopEventArgs);
}
if (requestSession.isSpeechEnded && this.privRecognizerConfig.isContinuousRecognition) {
this.cancelRecognitionLocal(requestSession, CancellationReason.EndOfStream, CancellationErrorCode.NoError, undefined, successCallback);
}
break;
case "turn.end":
const sessionStopEventArgs: SessionEventArgs = new SessionEventArgs(requestSession.sessionId);
requestSession.onServiceTurnEndResponse(this.privRecognizerConfig.isContinuousRecognition);
if (!this.privRecognizerConfig.isContinuousRecognition || requestSession.isSpeechEnded) {
if (!!this.privRecognizer.sessionStopped) {
this.privRecognizer.sessionStopped(this.privRecognizer, sessionStopEventArgs);
}
return PromiseHelper.fromResult(true);
} else {
this.fetchConnection(requestSession).onSuccessContinueWith((connection: IConnection) => {
this.sendSpeechContext(connection, requestSession, requestSession.contextJson);
});
}
default:
this.processTypeSpecificMessages(
connectionMessage,
requestSession,
connection,
successCallback,
errorCallBack);
}
}
return this.receiveMessage(requestSession, successCallback, errorCallBack);
});
});
}
private sendSpeechConfig = (connection: IConnection, requestSession: RequestSession, speechConfigJson: string): Promise<boolean> => {
// filter out anything that is not required for the service to work.
if (ServiceRecognizerBase.telemetryDataEnabled !== true) {
const withTelemetry = JSON.parse(speechConfigJson);
const replacement: any = {
context: {
system: withTelemetry.context.system,
},
};
speechConfigJson = JSON.stringify(replacement);
}
if (speechConfigJson && this.privConnectionId !== this.privSpeechConfigConnectionId) {
this.privSpeechConfigConnectionId = this.privConnectionId;
return connection.send(new SpeechConnectionMessage(
MessageType.Text,
"speech.config",
requestSession.requestId,
"application/json",
speechConfigJson));
}
return PromiseHelper.fromResult(true);
}
private sendSpeechContext = (connection: IConnection, requestSession: RequestSession, speechContextJson: string): Promise<boolean> => {
if (speechContextJson) {
return connection.send(new SpeechConnectionMessage(
MessageType.Text,
"speech.context",
requestSession.requestId,
"application/json",
speechContextJson));
}
return PromiseHelper.fromResult(true);
}
private sendAudio = (
audioStreamNode: IAudioStreamNode,
requestSession: RequestSession): Promise<boolean> => {
// NOTE: Home-baked promises crash ios safari during the invocation
// of the error callback chain (looks like the recursion is way too deep, and
// it blows up the stack). The following construct is a stop-gap that does not
// bubble the error up the callback chain and hence circumvents this problem.
// TODO: rewrite with ES6 promises.
const deferred = new Deferred<boolean>();
// The time we last sent data to the service.
let lastSendTime: number = Date.now();
const audioFormat: AudioStreamFormatImpl = this.privAudioSource.format as AudioStreamFormatImpl;
const readAndUploadCycle = (_: boolean) => {
// If speech is done, stop sending audio.
if (!this.privIsDisposed && !requestSession.isSpeechEnded && !requestSession.isCompleted) {
this.fetchConnection(requestSession).onSuccessContinueWith((connection: IConnection) => {
audioStreamNode.read().on(
(audioStreamChunk: IStreamChunk<ArrayBuffer>) => {
// we have a new audio chunk to upload.
if (requestSession.isSpeechEnded) {
// If service already recognized audio end then dont send any more audio
deferred.resolve(true);
return;
}
const payload = (audioStreamChunk.isEnd) ? null : audioStreamChunk.buffer;
const uploaded: Promise<boolean> = connection.send(
new SpeechConnectionMessage(
MessageType.Binary, "audio", requestSession.requestId, null, payload));
if (!audioStreamChunk.isEnd) {
// Caculate any delay to the audio stream needed. /2 to allow 2x real time transmit rate max.
const minSendTime = ((payload.byteLength / audioFormat.avgBytesPerSec) / 2) * 1000;
const delay: number = Math.max(0, (lastSendTime - Date.now() + minSendTime));
uploaded.onSuccessContinueWith((result: boolean) => {
setTimeout(() => {
lastSendTime = Date.now();
readAndUploadCycle(result);
}, delay);
});
} else {
// the audio stream has been closed, no need to schedule next
// read-upload cycle.
requestSession.onSpeechEnded();
deferred.resolve(true);
}
},
(error: string) => {
if (requestSession.isSpeechEnded) {
// For whatever reason, Reject is used to remove queue subscribers inside
// the Queue.DrainAndDispose invoked from DetachAudioNode down below, which
// means that sometimes things can be rejected in normal circumstances, without
// any errors.
deferred.resolve(true); // TODO: remove the argument, it's is completely meaningless.
} else {
// Only reject, if there was a proper error.
deferred.reject(error);
}
});
});
}
};
readAndUploadCycle(true);
return deferred.promise();
}
}

Просмотреть файл

@ -0,0 +1,225 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import {
AudioSourceErrorEvent,
AudioStreamNodeAttachedEvent,
AudioStreamNodeAttachingEvent,
AudioStreamNodeDetachedEvent,
AudioStreamNodeErrorEvent,
ConnectionEstablishedEvent,
ConnectionEstablishErrorEvent,
ConnectionMessageReceivedEvent,
ConnectionStartEvent,
IEventListener,
IStringDictionary,
PlatformEvent,
} from "../common/Exports";
import { ConnectingToServiceEvent, RecognitionTriggeredEvent } from "./RecognitionEvents";
interface ITelemetry {
Metrics: IMetric[];
ReceivedMessages: IStringDictionary<string[]>;
}
// tslint:disable-next-line:max-classes-per-file
interface IMetric {
End: string;
Error?: string;
Id?: string;
Name: string;
Start: string;
}
// tslint:disable-next-line:max-classes-per-file
export class ServiceTelemetryListener implements IEventListener<PlatformEvent> {
private privIsDisposed: boolean = false;
private privRequestId: string;
private privAudioSourceId: string;
private privAudioNodeId: string;
private privListeningTriggerMetric: IMetric = null;
private privMicMetric: IMetric = null;
private privConnectionEstablishMetric: IMetric = null;
private privMicStartTime: string;
private privConnectionId: string;
private privConnectionStartTime: string;
private privReceivedMessages: IStringDictionary<string[]>;
constructor(requestId: string, audioSourceId: string, audioNodeId: string) {
this.privRequestId = requestId;
this.privAudioSourceId = audioSourceId;
this.privAudioNodeId = audioNodeId;
this.privReceivedMessages = {};
}
public onEvent = (e: PlatformEvent): void => {
if (this.privIsDisposed) {
return;
}
if (e instanceof RecognitionTriggeredEvent && e.requestId === this.privRequestId) {
this.privListeningTriggerMetric = {
End: e.eventTime,
Name: "ListeningTrigger",
Start: e.eventTime,
};
}
if (e instanceof AudioStreamNodeAttachingEvent && e.audioSourceId === this.privAudioSourceId && e.audioNodeId === this.privAudioNodeId) {
this.privMicStartTime = e.eventTime;
}
if (e instanceof AudioStreamNodeAttachedEvent && e.audioSourceId === this.privAudioSourceId && e.audioNodeId === this.privAudioNodeId) {
this.privMicStartTime = e.eventTime;
}
if (e instanceof AudioSourceErrorEvent && e.audioSourceId === this.privAudioSourceId) {
if (!this.privMicMetric) {
this.privMicMetric = {
End: e.eventTime,
Error: e.error,
Name: "Microphone",
Start: this.privMicStartTime,
};
}
}
if (e instanceof AudioStreamNodeErrorEvent && e.audioSourceId === this.privAudioSourceId && e.audioNodeId === this.privAudioNodeId) {
if (!this.privMicMetric) {
this.privMicMetric = {
End: e.eventTime,
Error: e.error,
Name: "Microphone",
Start: this.privMicStartTime,
};
}
}
if (e instanceof AudioStreamNodeDetachedEvent && e.audioSourceId === this.privAudioSourceId && e.audioNodeId === this.privAudioNodeId) {
if (!this.privMicMetric) {
this.privMicMetric = {
End: e.eventTime,
Name: "Microphone",
Start: this.privMicStartTime,
};
}
}
if (e instanceof ConnectingToServiceEvent && e.requestId === this.privRequestId) {
this.privConnectionId = e.sessionId;
}
if (e instanceof ConnectionStartEvent && e.connectionId === this.privConnectionId) {
this.privConnectionStartTime = e.eventTime;
}
if (e instanceof ConnectionEstablishedEvent && e.connectionId === this.privConnectionId) {
if (!this.privConnectionEstablishMetric) {
this.privConnectionEstablishMetric = {
End: e.eventTime,
Id: this.privConnectionId,
Name: "Connection",
Start: this.privConnectionStartTime,
};
}
}
if (e instanceof ConnectionEstablishErrorEvent && e.connectionId === this.privConnectionId) {
if (!this.privConnectionEstablishMetric) {
this.privConnectionEstablishMetric = {
End: e.eventTime,
Error: this.getConnectionError(e.statusCode),
Id: this.privConnectionId,
Name: "Connection",
Start: this.privConnectionStartTime,
};
}
}
if (e instanceof ConnectionMessageReceivedEvent && e.connectionId === this.privConnectionId) {
if (e.message && e.message.headers && e.message.headers.path) {
if (!this.privReceivedMessages[e.message.headers.path]) {
this.privReceivedMessages[e.message.headers.path] = new Array<string>();
}
this.privReceivedMessages[e.message.headers.path].push(e.networkReceivedTime);
}
}
}
public getTelemetry = (): string => {
const metrics = new Array<IMetric>();
if (this.privListeningTriggerMetric) {
metrics.push(this.privListeningTriggerMetric);
}
if (this.privMicMetric) {
metrics.push(this.privMicMetric);
}
if (this.privConnectionEstablishMetric) {
metrics.push(this.privConnectionEstablishMetric);
}
const telemetry: ITelemetry = {
Metrics: metrics,
ReceivedMessages: this.privReceivedMessages,
};
const json = JSON.stringify(telemetry);
// We dont want to send the same telemetry again. So clean those out.
this.privReceivedMessages = {};
this.privListeningTriggerMetric = null;
this.privMicMetric = null;
this.privConnectionEstablishMetric = null;
return json;
}
public dispose = (): void => {
this.privIsDisposed = true;
}
private getConnectionError = (statusCode: number): string => {
/*
-- Websocket status codes --
NormalClosure = 1000,
EndpointUnavailable = 1001,
ProtocolError = 1002,
InvalidMessageType = 1003,
Empty = 1005,
InvalidPayloadData = 1007,
PolicyViolation = 1008,
MessageTooBig = 1009,
MandatoryExtension = 1010,
InternalServerError = 1011
*/
switch (statusCode) {
case 400:
case 1002:
case 1003:
case 1005:
case 1007:
case 1008:
case 1009: return "BadRequest";
case 401: return "Unauthorized";
case 403: return "Forbidden";
case 503:
case 1001: return "ServerUnavailable";
case 500:
case 1011: return "ServerError";
case 408:
case 504: return "Timeout";
default: return "statuscode:" + statusCode.toString();
}
}
}

Просмотреть файл

@ -0,0 +1,86 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { WebsocketConnection } from "../common.browser/Exports";
import { OutputFormatPropertyName } from "../common.speech/Exports";
import { IConnection, IStringDictionary, Storage } from "../common/Exports";
import { OutputFormat, PropertyId } from "../sdk/Exports";
import { AuthInfo, IConnectionFactory, RecognitionMode, RecognizerConfig, WebsocketMessageFormatter } from "./Exports";
import { QueryParameterNames } from "./QueryParameterNames";
export class SpeechConnectionFactory implements IConnectionFactory {
public create = (
config: RecognizerConfig,
authInfo: AuthInfo,
connectionId?: string): IConnection => {
let endpoint: string = config.parameters.getProperty(PropertyId.SpeechServiceConnection_Endpoint, undefined);
const queryParams: IStringDictionary<string> = {};
const endpointId: string = config.parameters.getProperty(PropertyId.SpeechServiceConnection_EndpointId, undefined);
const language: string = config.parameters.getProperty(PropertyId.SpeechServiceConnection_RecoLanguage, undefined);
if (endpointId) {
if (!endpoint || endpoint.search(QueryParameterNames.DeploymentIdParamName) === -1) {
queryParams[QueryParameterNames.DeploymentIdParamName] = endpointId;
}
} else if (language) {
if (!endpoint || endpoint.search(QueryParameterNames.LanguageParamName) === -1) {
queryParams[QueryParameterNames.LanguageParamName] = language;
}
}
if (!endpoint || endpoint.search(QueryParameterNames.FormatParamName) === -1) {
queryParams[QueryParameterNames.FormatParamName] = config.parameters.getProperty(OutputFormatPropertyName, OutputFormat[OutputFormat.Simple]).toLowerCase();
}
if (this.isDebugModeEnabled) {
queryParams[QueryParameterNames.TestHooksParamName] = "1";
}
if (!endpoint) {
const region: string = config.parameters.getProperty(PropertyId.SpeechServiceConnection_Region, undefined);
switch (config.recognitionMode) {
case RecognitionMode.Conversation:
endpoint = this.host(region) + this.conversationRelativeUri;
break;
case RecognitionMode.Dictation:
endpoint = this.host(region) + this.dictationRelativeUri;
break;
default:
endpoint = this.host(region) + this.interactiveRelativeUri; // default is interactive
break;
}
}
const headers: IStringDictionary<string> = {};
headers[authInfo.headerName] = authInfo.token;
headers[QueryParameterNames.ConnectionIdHeader] = connectionId;
return new WebsocketConnection(endpoint, queryParams, headers, new WebsocketMessageFormatter(), connectionId);
}
private host(region: string): string {
return Storage.local.getOrAdd("Host", "wss://" + region + ".stt.speech.microsoft.com");
}
private get interactiveRelativeUri(): string {
return Storage.local.getOrAdd("InteractiveRelativeUri", "/speech/recognition/interactive/cognitiveservices/v1");
}
private get conversationRelativeUri(): string {
return Storage.local.getOrAdd("ConversationRelativeUri", "/speech/recognition/conversation/cognitiveservices/v1");
}
private get dictationRelativeUri(): string {
return Storage.local.getOrAdd("DictationRelativeUri", "/speech/recognition/dictation/cognitiveservices/v1");
}
private get isDebugModeEnabled(): boolean {
const value = Storage.local.getOrAdd("IsDebugModeEnabled", "false");
return value.toLowerCase() === "true";
}
}

Просмотреть файл

@ -0,0 +1,114 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { ArgumentNullError, ConnectionMessage, IStringDictionary, MessageType } from "../common/Exports";
const PathHeaderName: string = "path";
const ContentTypeHeaderName: string = "content-type";
const RequestIdHeaderName: string = "x-requestid";
const RequestTimestampHeaderName: string = "x-timestamp";
export class SpeechConnectionMessage extends ConnectionMessage {
private privPath: string;
private privRequestId: string;
private privContentType: string;
private privAdditionalHeaders: IStringDictionary<string>;
public constructor(
messageType: MessageType,
path: string,
requestId: string,
contentType: string,
body: any,
additionalHeaders?: IStringDictionary<string>,
id?: string) {
if (!path) {
throw new ArgumentNullError("path");
}
if (!requestId) {
throw new ArgumentNullError("requestId");
}
const headers: IStringDictionary<string> = {};
headers[PathHeaderName] = path;
headers[RequestIdHeaderName] = requestId;
headers[RequestTimestampHeaderName] = new Date().toISOString();
if (contentType) {
headers[ContentTypeHeaderName] = contentType;
}
if (additionalHeaders) {
for (const headerName in additionalHeaders) {
if (headerName) {
headers[headerName] = additionalHeaders[headerName];
}
}
}
if (id) {
super(messageType, body, headers, id);
} else {
super(messageType, body, headers);
}
this.privPath = path;
this.privRequestId = requestId;
this.privContentType = contentType;
this.privAdditionalHeaders = additionalHeaders;
}
public get path(): string {
return this.privPath;
}
public get requestId(): string {
return this.privRequestId;
}
public get contentType(): string {
return this.privContentType;
}
public get additionalHeaders(): IStringDictionary<string> {
return this.privAdditionalHeaders;
}
public static fromConnectionMessage = (message: ConnectionMessage): SpeechConnectionMessage => {
let path = null;
let requestId = null;
let contentType = null;
let requestTimestamp = null;
const additionalHeaders: IStringDictionary<string> = {};
if (message.headers) {
for (const headerName in message.headers) {
if (headerName) {
if (headerName.toLowerCase() === PathHeaderName.toLowerCase()) {
path = message.headers[headerName];
} else if (headerName.toLowerCase() === RequestIdHeaderName.toLowerCase()) {
requestId = message.headers[headerName];
} else if (headerName.toLowerCase() === RequestTimestampHeaderName.toLowerCase()) {
requestTimestamp = message.headers[headerName];
} else if (headerName.toLowerCase() === ContentTypeHeaderName.toLowerCase()) {
contentType = message.headers[headerName];
} else {
additionalHeaders[headerName] = message.headers[headerName];
}
}
}
}
return new SpeechConnectionMessage(
message.messageType,
path,
requestId,
contentType,
message.body,
additionalHeaders,
message.id);
}
}

Просмотреть файл

@ -0,0 +1,34 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { RecognitionCompletionStatus } from "../../src/common.speech/Exports";
import { TranslationStatus } from "./TranslationStatus";
export interface ITranslations {
TranslationStatus: TranslationStatus;
Translations: ITranslation[];
FailureReason: string;
}
export interface ITranslation {
Language: string;
Text: string;
}
export interface ISpeechEndDetectedResult {
Offset?: number;
}
// turn.start
export interface ITurnStart {
context: ITurnStartContext;
}
export interface ITurnStartContext {
serviceTag: string;
}
export interface IResultErrorDetails {
errorText: string;
recogSate: RecognitionCompletionStatus;
}

Просмотреть файл

@ -0,0 +1,219 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { IAudioSource, IConnection } from "../common/Exports";
import {
CancellationErrorCode,
CancellationReason,
OutputFormat,
PropertyCollection,
ResultReason,
SpeechRecognitionCanceledEventArgs,
SpeechRecognitionEventArgs,
SpeechRecognitionResult,
SpeechRecognizer,
} from "../sdk/Exports";
import {
CancellationErrorCodePropertyName,
DetailedSpeechPhrase,
EnumTranslation,
OutputFormatPropertyName,
RecognitionStatus,
RequestSession,
ServiceRecognizerBase,
SimpleSpeechPhrase,
SpeechHypothesis,
} from "./Exports";
import { IAuthentication } from "./IAuthentication";
import { IConnectionFactory } from "./IConnectionFactory";
import { RecognizerConfig } from "./RecognizerConfig";
import { SpeechConnectionMessage } from "./SpeechConnectionMessage.Internal";
// tslint:disable-next-line:max-classes-per-file
export class SpeechServiceRecognizer extends ServiceRecognizerBase {
private privSpeechRecognizer: SpeechRecognizer;
public constructor(
authentication: IAuthentication,
connectionFactory: IConnectionFactory,
audioSource: IAudioSource,
recognizerConfig: RecognizerConfig,
speechRecognizer: SpeechRecognizer) {
super(authentication, connectionFactory, audioSource, recognizerConfig, speechRecognizer);
this.privSpeechRecognizer = speechRecognizer;
}
protected processTypeSpecificMessages(
connectionMessage: SpeechConnectionMessage,
requestSession: RequestSession,
connection: IConnection,
successCallback?: (e: SpeechRecognitionResult) => void,
errorCallBack?: (e: string) => void): void {
let result: SpeechRecognitionResult;
switch (connectionMessage.path.toLowerCase()) {
case "speech.hypothesis":
const hypothesis: SpeechHypothesis = SpeechHypothesis.fromJSON(connectionMessage.textBody);
result = new SpeechRecognitionResult(
requestSession.requestId,
ResultReason.RecognizingSpeech,
hypothesis.Text,
hypothesis.Duration,
hypothesis.Offset + requestSession.currentTurnAudioOffset,
undefined,
connectionMessage.textBody,
undefined);
const ev = new SpeechRecognitionEventArgs(result, hypothesis.Duration, requestSession.sessionId);
if (!!this.privSpeechRecognizer.recognizing) {
try {
this.privSpeechRecognizer.recognizing(this.privSpeechRecognizer, ev);
/* tslint:disable:no-empty */
} catch (error) {
// Not going to let errors in the event handler
// trip things up.
}
}
break;
case "speech.phrase":
// Always send telemetry because we want it to to up for recognize once which will listening to the service
// after recognition happens.
this.sendTelemetryData(requestSession, requestSession.getTelemetry());
const simple: SimpleSpeechPhrase = SimpleSpeechPhrase.fromJSON(connectionMessage.textBody);
const resultReason: ResultReason = EnumTranslation.implTranslateRecognitionResult(simple.RecognitionStatus);
requestSession.onServiceRecognized(requestSession.currentTurnAudioOffset + simple.Offset);
if (ResultReason.Canceled === resultReason) {
const cancelReason: CancellationReason = EnumTranslation.implTranslateCancelResult(simple.RecognitionStatus);
result = new SpeechRecognitionResult(
requestSession.requestId,
resultReason,
undefined,
undefined,
undefined,
undefined,
connectionMessage.textBody,
undefined);
if (!!this.privSpeechRecognizer.canceled) {
const cancelEvent: SpeechRecognitionCanceledEventArgs = new SpeechRecognitionCanceledEventArgs(
cancelReason,
undefined,
cancelReason === CancellationReason.Error ? CancellationErrorCode.ServiceError : CancellationErrorCode.NoError,
undefined,
requestSession.sessionId);
try {
this.privSpeechRecognizer.canceled(this.privSpeechRecognizer, cancelEvent);
/* tslint:disable:no-empty */
} catch { }
}
} else {
if (!(requestSession.isSpeechEnded && resultReason === ResultReason.NoMatch && simple.RecognitionStatus !== RecognitionStatus.InitialSilenceTimeout)) {
if (this.privRecognizerConfig.parameters.getProperty(OutputFormatPropertyName) === OutputFormat[OutputFormat.Simple]) {
result = new SpeechRecognitionResult(
requestSession.requestId,
resultReason,
simple.DisplayText,
simple.Duration,
simple.Offset + requestSession.currentTurnAudioOffset,
undefined,
connectionMessage.textBody,
undefined);
} else {
const detailed: DetailedSpeechPhrase = DetailedSpeechPhrase.fromJSON(connectionMessage.textBody);
result = new SpeechRecognitionResult(
requestSession.requestId,
resultReason,
detailed.RecognitionStatus === RecognitionStatus.Success ? detailed.NBest[0].Display : undefined,
detailed.Duration,
detailed.Offset + requestSession.currentTurnAudioOffset,
undefined,
connectionMessage.textBody,
undefined);
}
const event: SpeechRecognitionEventArgs = new SpeechRecognitionEventArgs(result, result.offset, requestSession.sessionId);
if (!!this.privSpeechRecognizer.recognized) {
try {
this.privSpeechRecognizer.recognized(this.privSpeechRecognizer, event);
/* tslint:disable:no-empty */
} catch (error) {
// Not going to let errors in the event handler
// trip things up.
}
}
}
}
// report result to promise.
if (!!successCallback) {
try {
successCallback(result);
} catch (e) {
if (!!errorCallBack) {
errorCallBack(e);
}
}
// Only invoke the call back once.
// and if it's successful don't invoke the
// error after that.
successCallback = undefined;
errorCallBack = undefined;
}
break;
default:
break;
}
}
// Cancels recognition.
protected cancelRecognition(
sessionId: string,
requestId: string,
cancellationReason: CancellationReason,
errorCode: CancellationErrorCode,
error: string,
cancelRecoCallback: (e: SpeechRecognitionResult) => void): void {
const properties: PropertyCollection = new PropertyCollection();
properties.setProperty(CancellationErrorCodePropertyName, CancellationErrorCode[errorCode]);
if (!!this.privSpeechRecognizer.canceled) {
const cancelEvent: SpeechRecognitionCanceledEventArgs = new SpeechRecognitionCanceledEventArgs(
cancellationReason,
error,
errorCode,
undefined,
sessionId);
try {
this.privSpeechRecognizer.canceled(this.privSpeechRecognizer, cancelEvent);
/* tslint:disable:no-empty */
} catch { }
}
if (!!cancelRecoCallback) {
const result: SpeechRecognitionResult = new SpeechRecognitionResult(
requestId,
ResultReason.Canceled,
undefined, // Text
undefined, // Druation
undefined, // Offset
error,
undefined, // Json
properties);
try {
cancelRecoCallback(result);
/* tslint:disable:no-empty */
} catch { }
}
}
}

Просмотреть файл

@ -0,0 +1,58 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { WebsocketConnection } from "../common.browser/Exports";
import { IConnection, IStringDictionary, Storage } from "../common/Exports";
import { PropertyId } from "../sdk/Exports";
import { AuthInfo, IConnectionFactory, RecognizerConfig, WebsocketMessageFormatter } from "./Exports";
const TestHooksParamName: string = "testhooks";
const ConnectionIdHeader: string = "X-ConnectionId";
export class TranslationConnectionFactory implements IConnectionFactory {
public create = (
config: RecognizerConfig,
authInfo: AuthInfo,
connectionId?: string): IConnection => {
let endpoint: string = config.parameters.getProperty(PropertyId.SpeechServiceConnection_Endpoint, undefined);
if (!endpoint) {
const region: string = config.parameters.getProperty(PropertyId.SpeechServiceConnection_Region, undefined);
endpoint = this.host(region) + Storage.local.getOrAdd("TranslationRelativeUri", "/speech/translation/cognitiveservices/v1");
}
const queryParams: IStringDictionary<string> = {
from: config.parameters.getProperty(PropertyId.SpeechServiceConnection_RecoLanguage),
to: config.parameters.getProperty(PropertyId.SpeechServiceConnection_TranslationToLanguages),
};
if (this.isDebugModeEnabled) {
queryParams[TestHooksParamName] = "1";
}
const voiceName: string = "voice";
const featureName: string = "features";
if (config.parameters.getProperty(PropertyId.SpeechServiceConnection_TranslationVoice, undefined) !== undefined) {
queryParams[voiceName] = config.parameters.getProperty(PropertyId.SpeechServiceConnection_TranslationVoice);
queryParams[featureName] = "texttospeech";
}
const headers: IStringDictionary<string> = {};
headers[authInfo.headerName] = authInfo.token;
headers[ConnectionIdHeader] = connectionId;
return new WebsocketConnection(endpoint, queryParams, headers, new WebsocketMessageFormatter(), connectionId);
}
private host(region: string): string {
return Storage.local.getOrAdd("Host", "wss://" + region + ".s2s.speech.microsoft.com");
}
private get isDebugModeEnabled(): boolean {
const value = Storage.local.getOrAdd("IsDebugModeEnabled", "false");
return value.toLowerCase() === "true";
}
}

Просмотреть файл

@ -0,0 +1,326 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { IAudioSource, IConnection, TranslationStatus } from "../common/Exports";
import {
CancellationErrorCode,
CancellationReason,
PropertyCollection,
ResultReason,
SpeechRecognitionResult,
TranslationRecognitionCanceledEventArgs,
TranslationRecognitionEventArgs,
TranslationRecognitionResult,
TranslationRecognizer,
Translations,
TranslationSynthesisEventArgs,
TranslationSynthesisResult,
} from "../sdk/Exports";
import {
CancellationErrorCodePropertyName,
EnumTranslation,
RecognitionStatus,
RequestSession,
ServiceRecognizerBase,
SynthesisStatus,
TranslationHypothesis,
TranslationPhrase,
TranslationSynthesisEnd,
} from "./Exports";
import { IAuthentication } from "./IAuthentication";
import { IConnectionFactory } from "./IConnectionFactory";
import { RecognizerConfig } from "./RecognizerConfig";
import { SpeechConnectionMessage } from "./SpeechConnectionMessage.Internal";
// tslint:disable-next-line:max-classes-per-file
export class TranslationServiceRecognizer extends ServiceRecognizerBase {
private privTranslationRecognizer: TranslationRecognizer;
public constructor(
authentication: IAuthentication,
connectionFactory: IConnectionFactory,
audioSource: IAudioSource,
recognizerConfig: RecognizerConfig,
translationRecognizer: TranslationRecognizer) {
super(authentication, connectionFactory, audioSource, recognizerConfig, translationRecognizer);
this.privTranslationRecognizer = translationRecognizer;
}
protected processTypeSpecificMessages(
connectionMessage: SpeechConnectionMessage,
requestSession: RequestSession,
connection: IConnection,
successCallback?: (e: TranslationRecognitionResult) => void,
errorCallBack?: (e: string) => void): void {
switch (connectionMessage.path.toLowerCase()) {
case "translation.hypothesis":
const result: TranslationRecognitionEventArgs = this.fireEventForResult(TranslationHypothesis.fromJSON(connectionMessage.textBody), requestSession);
if (!!this.privTranslationRecognizer.recognizing) {
try {
this.privTranslationRecognizer.recognizing(this.privTranslationRecognizer, result);
/* tslint:disable:no-empty */
} catch (error) {
// Not going to let errors in the event handler
// trip things up.
}
}
break;
case "translation.phrase":
if (this.privRecognizerConfig.isContinuousRecognition) {
// For continuous recognition telemetry has to be sent for every phrase as per spec.
this.sendTelemetryData(requestSession, requestSession.getTelemetry());
}
const translatedPhrase: TranslationPhrase = TranslationPhrase.fromJSON(connectionMessage.textBody);
if (translatedPhrase.RecognitionStatus === RecognitionStatus.Success) {
// OK, the recognition was successful. How'd the translation do?
const result: TranslationRecognitionEventArgs = this.fireEventForResult(translatedPhrase, requestSession);
if (!!this.privTranslationRecognizer.recognized) {
try {
this.privTranslationRecognizer.recognized(this.privTranslationRecognizer, result);
/* tslint:disable:no-empty */
} catch (error) {
// Not going to let errors in the event handler
// trip things up.
}
}
// report result to promise.
if (!!successCallback) {
try {
successCallback(result.result);
} catch (e) {
if (!!errorCallBack) {
errorCallBack(e);
}
}
// Only invoke the call back once.
// and if it's successful don't invoke the
// error after that.
successCallback = undefined;
errorCallBack = undefined;
}
break;
} else {
const reason: ResultReason = EnumTranslation.implTranslateRecognitionResult(translatedPhrase.RecognitionStatus);
const result = new TranslationRecognitionResult(
undefined,
requestSession.requestId,
reason,
translatedPhrase.Text,
translatedPhrase.Duration,
translatedPhrase.Offset,
undefined,
connectionMessage.textBody,
undefined);
if (reason === ResultReason.Canceled) {
const cancelReason: CancellationReason = EnumTranslation.implTranslateCancelResult(translatedPhrase.RecognitionStatus);
const ev = new TranslationRecognitionCanceledEventArgs(
requestSession.sessionId,
cancelReason,
null,
cancelReason === CancellationReason.Error ? CancellationErrorCode.ServiceError : CancellationErrorCode.NoError,
result);
if (!!this.privTranslationRecognizer.canceled) {
try {
this.privTranslationRecognizer.canceled(this.privTranslationRecognizer, ev);
/* tslint:disable:no-empty */
} catch (error) {
// Not going to let errors in the event handler
// trip things up.
}
}
} else {
if (!(requestSession.isSpeechEnded && reason === ResultReason.NoMatch && translatedPhrase.RecognitionStatus !== RecognitionStatus.InitialSilenceTimeout)) {
const ev = new TranslationRecognitionEventArgs(result, 0/*offset*/, requestSession.sessionId);
if (!!this.privTranslationRecognizer.recognized) {
try {
this.privTranslationRecognizer.recognized(this.privTranslationRecognizer, ev);
/* tslint:disable:no-empty */
} catch (error) {
// Not going to let errors in the event handler
// trip things up.
}
}
}
}
// report result to promise.
if (!!successCallback) {
try {
successCallback(result);
} catch (e) {
if (!!errorCallBack) {
errorCallBack(e);
}
}
// Only invoke the call back once.
// and if it's successful don't invoke the
// error after that.
successCallback = undefined;
errorCallBack = undefined;
}
}
break;
case "translation.synthesis":
this.sendSynthesisAudio(connectionMessage.binaryBody, requestSession.sessionId);
break;
case "translation.synthesis.end":
const synthEnd: TranslationSynthesisEnd = TranslationSynthesisEnd.fromJSON(connectionMessage.textBody);
switch (synthEnd.SynthesisStatus) {
case SynthesisStatus.Error:
if (!!this.privTranslationRecognizer.synthesizing) {
const result = new TranslationSynthesisResult(ResultReason.Canceled, undefined);
const retEvent: TranslationSynthesisEventArgs = new TranslationSynthesisEventArgs(result, requestSession.sessionId);
try {
this.privTranslationRecognizer.synthesizing(this.privTranslationRecognizer, retEvent);
/* tslint:disable:no-empty */
} catch (error) {
// Not going to let errors in the event handler
// trip things up.
}
}
if (!!this.privTranslationRecognizer.canceled) {
// And raise a canceled event to send the rich(er) error message back.
const canceledResult: TranslationRecognitionCanceledEventArgs = new TranslationRecognitionCanceledEventArgs(
requestSession.sessionId,
CancellationReason.Error,
synthEnd.FailureReason,
CancellationErrorCode.ServiceError,
null);
try {
this.privTranslationRecognizer.canceled(this.privTranslationRecognizer, canceledResult);
/* tslint:disable:no-empty */
} catch (error) {
// Not going to let errors in the event handler
// trip things up.
}
}
break;
case SynthesisStatus.Success:
this.sendSynthesisAudio(undefined, requestSession.sessionId);
break;
default:
break;
}
break;
default:
break;
}
}
// Cancels recognition.
protected cancelRecognition(
sessionId: string,
requestId: string,
cancellationReason: CancellationReason,
errorCode: CancellationErrorCode,
error: string,
cancelRecoCallback: (e: SpeechRecognitionResult) => void): void {
if (!!this.privTranslationRecognizer.canceled) {
const properties: PropertyCollection = new PropertyCollection();
properties.setProperty(CancellationErrorCodePropertyName, CancellationErrorCode[errorCode]);
const cancelEvent: TranslationRecognitionCanceledEventArgs = new TranslationRecognitionCanceledEventArgs(
sessionId,
cancellationReason,
error,
errorCode,
undefined);
try {
this.privTranslationRecognizer.canceled(this.privTranslationRecognizer, cancelEvent);
/* tslint:disable:no-empty */
} catch { }
if (!!cancelRecoCallback) {
const result: TranslationRecognitionResult = new TranslationRecognitionResult(
undefined, // Translations
requestId,
ResultReason.Canceled,
undefined, // Text
undefined, // Druation
undefined, // Offset
error,
undefined, // Json
properties);
try {
cancelRecoCallback(result);
/* tslint:disable:no-empty */
} catch { }
}
}
}
private fireEventForResult(serviceResult: TranslationHypothesis | TranslationPhrase, requestSession: RequestSession): TranslationRecognitionEventArgs {
let translations: Translations;
if (undefined !== serviceResult.Translation.Translations) {
translations = new Translations();
for (const translation of serviceResult.Translation.Translations) {
translations.set(translation.Language, translation.Text);
}
}
let resultReason: ResultReason;
if (serviceResult instanceof TranslationPhrase) {
if (serviceResult.Translation.TranslationStatus === TranslationStatus.Success) {
resultReason = ResultReason.TranslatedSpeech;
} else {
resultReason = ResultReason.RecognizedSpeech;
}
} else {
resultReason = ResultReason.TranslatingSpeech;
}
const result = new TranslationRecognitionResult(
translations,
requestSession.requestId,
resultReason,
serviceResult.Text,
serviceResult.Duration,
serviceResult.Offset,
serviceResult.Translation.FailureReason,
JSON.stringify(serviceResult),
undefined);
const ev = new TranslationRecognitionEventArgs(result, serviceResult.Offset, requestSession.sessionId);
return ev;
}
private sendSynthesisAudio(audio: ArrayBuffer, sessionId: string): void {
const reason = (undefined === audio) ? ResultReason.SynthesizingAudioCompleted : ResultReason.SynthesizingAudio;
const result = new TranslationSynthesisResult(reason, audio);
const retEvent: TranslationSynthesisEventArgs = new TranslationSynthesisEventArgs(result, sessionId);
if (!!this.privTranslationRecognizer.synthesizing) {
try {
this.privTranslationRecognizer.synthesizing(this.privTranslationRecognizer, retEvent);
/* tslint:disable:no-empty */
} catch (error) {
// Not going to let errors in the event handler
// trip things up.
}
}
}
}

Просмотреть файл

@ -0,0 +1,18 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
/**
* Defines translation status.
* @class TranslationStatus
*/
export enum TranslationStatus {
/**
* @member TranslationStatus.Success
*/
Success = 0,
/**
* @member TranslationStatus.Error
*/
Error,
}

Просмотреть файл

@ -0,0 +1,160 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import {
ConnectionMessage,
Deferred,
IStringDictionary,
IWebsocketMessageFormatter,
MessageType,
Promise,
RawWebsocketMessage,
} from "../common/Exports";
const CRLF: string = "\r\n";
export class WebsocketMessageFormatter implements IWebsocketMessageFormatter {
public toConnectionMessage = (message: RawWebsocketMessage): Promise<ConnectionMessage> => {
const deferral = new Deferred<ConnectionMessage>();
try {
if (message.messageType === MessageType.Text) {
const textMessage: string = message.textContent;
let headers: IStringDictionary<string> = {};
let body: string = null;
if (textMessage) {
const headerBodySplit = textMessage.split("\r\n\r\n");
if (headerBodySplit && headerBodySplit.length > 0) {
headers = this.parseHeaders(headerBodySplit[0]);
if (headerBodySplit.length > 1) {
body = headerBodySplit[1];
}
}
}
deferral.resolve(new ConnectionMessage(message.messageType, body, headers, message.id));
} else if (message.messageType === MessageType.Binary) {
const binaryMessage: ArrayBuffer = message.binaryContent;
let headers: IStringDictionary<string> = {};
let body: ArrayBuffer = null;
if (!binaryMessage || binaryMessage.byteLength < 2) {
throw new Error("Invalid binary message format. Header length missing.");
}
const dataView = new DataView(binaryMessage);
const headerLength = dataView.getInt16(0);
if (binaryMessage.byteLength < headerLength + 2) {
throw new Error("Invalid binary message format. Header content missing.");
}
let headersString = "";
for (let i = 0; i < headerLength; i++) {
headersString += String.fromCharCode((dataView).getInt8(i + 2));
}
headers = this.parseHeaders(headersString);
if (binaryMessage.byteLength > headerLength + 2) {
body = binaryMessage.slice(2 + headerLength);
}
deferral.resolve(new ConnectionMessage(message.messageType, body, headers, message.id));
}
} catch (e) {
deferral.reject(`Error formatting the message. Error: ${e}`);
}
return deferral.promise();
}
public fromConnectionMessage = (message: ConnectionMessage): Promise<RawWebsocketMessage> => {
const deferral = new Deferred<RawWebsocketMessage>();
try {
if (message.messageType === MessageType.Text) {
const payload = `${this.makeHeaders(message)}${CRLF}${message.textBody ? message.textBody : ""}`;
deferral.resolve(new RawWebsocketMessage(MessageType.Text, payload, message.id));
} else if (message.messageType === MessageType.Binary) {
const headersString = this.makeHeaders(message);
const content = message.binaryBody;
const headerInt8Array = new Int8Array(this.stringToArrayBuffer(headersString));
const payload = new ArrayBuffer(2 + headerInt8Array.byteLength + (content ? content.byteLength : 0));
const dataView = new DataView(payload);
dataView.setInt16(0, headerInt8Array.length);
for (let i = 0; i < headerInt8Array.byteLength; i++) {
dataView.setInt8(2 + i, headerInt8Array[i]);
}
if (content) {
const bodyInt8Array = new Int8Array(content);
for (let i = 0; i < bodyInt8Array.byteLength; i++) {
dataView.setInt8(2 + headerInt8Array.byteLength + i, bodyInt8Array[i]);
}
}
deferral.resolve(new RawWebsocketMessage(MessageType.Binary, payload, message.id));
}
} catch (e) {
deferral.reject(`Error formatting the message. ${e}`);
}
return deferral.promise();
}
private makeHeaders = (message: ConnectionMessage): string => {
let headersString: string = "";
if (message.headers) {
for (const header in message.headers) {
if (header) {
headersString += `${header}: ${message.headers[header]}${CRLF}`;
}
}
}
return headersString;
}
private parseHeaders = (headersString: string): IStringDictionary<string> => {
const headers: IStringDictionary<string> = {};
if (headersString) {
const headerMatches = headersString.match(/[^\r\n]+/g);
if (headers) {
for (const header of headerMatches) {
if (header) {
const separatorIndex = header.indexOf(":");
const headerName = separatorIndex > 0 ? header.substr(0, separatorIndex).trim().toLowerCase() : header;
const headerValue =
separatorIndex > 0 && header.length > (separatorIndex + 1) ?
header.substr(separatorIndex + 1).trim() :
"";
headers[headerName] = headerValue;
}
}
}
}
return headers;
}
private stringToArrayBuffer = (str: string): ArrayBuffer => {
const buffer = new ArrayBuffer(str.length);
const view = new DataView(buffer);
for (let i = 0; i < str.length; i++) {
view.setUint8(i, str.charCodeAt(i));
}
return buffer;
}
}

Просмотреть файл

@ -0,0 +1,101 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { EventType, PlatformEvent } from "./PlatformEvent";
export class AudioSourceEvent extends PlatformEvent {
private privAudioSourceId: string;
constructor(eventName: string, audioSourceId: string, eventType: EventType = EventType.Info) {
super(eventName, eventType);
this.privAudioSourceId = audioSourceId;
}
public get audioSourceId(): string {
return this.privAudioSourceId;
}
}
// tslint:disable-next-line:max-classes-per-file
export class AudioSourceInitializingEvent extends AudioSourceEvent {
constructor(audioSourceId: string) {
super("AudioSourceInitializingEvent", audioSourceId);
}
}
// tslint:disable-next-line:max-classes-per-file
export class AudioSourceReadyEvent extends AudioSourceEvent {
constructor(audioSourceId: string) {
super("AudioSourceReadyEvent", audioSourceId);
}
}
// tslint:disable-next-line:max-classes-per-file
export class AudioSourceOffEvent extends AudioSourceEvent {
constructor(audioSourceId: string) {
super("AudioSourceOffEvent", audioSourceId);
}
}
// tslint:disable-next-line:max-classes-per-file
export class AudioSourceErrorEvent extends AudioSourceEvent {
private privError: string;
constructor(audioSourceId: string, error: string) {
super("AudioSourceErrorEvent", audioSourceId, EventType.Error);
this.privError = error;
}
public get error(): string {
return this.privError;
}
}
// tslint:disable-next-line:max-classes-per-file
export class AudioStreamNodeEvent extends AudioSourceEvent {
private privAudioNodeId: string;
constructor(eventName: string, audioSourceId: string, audioNodeId: string) {
super(eventName, audioSourceId);
this.privAudioNodeId = audioNodeId;
}
public get audioNodeId(): string {
return this.privAudioNodeId;
}
}
// tslint:disable-next-line:max-classes-per-file
export class AudioStreamNodeAttachingEvent extends AudioStreamNodeEvent {
constructor(audioSourceId: string, audioNodeId: string) {
super("AudioStreamNodeAttachingEvent", audioSourceId, audioNodeId);
}
}
// tslint:disable-next-line:max-classes-per-file
export class AudioStreamNodeAttachedEvent extends AudioStreamNodeEvent {
constructor(audioSourceId: string, audioNodeId: string) {
super("AudioStreamNodeAttachedEvent", audioSourceId, audioNodeId);
}
}
// tslint:disable-next-line:max-classes-per-file
export class AudioStreamNodeDetachedEvent extends AudioStreamNodeEvent {
constructor(audioSourceId: string, audioNodeId: string) {
super("AudioStreamNodeDetachedEvent", audioSourceId, audioNodeId);
}
}
// tslint:disable-next-line:max-classes-per-file
export class AudioStreamNodeErrorEvent extends AudioStreamNodeEvent {
private privError: string;
constructor(audioSourceId: string, audioNodeId: string, error: string) {
super("AudioStreamNodeErrorEvent", audioSourceId, audioNodeId);
this.privError = error;
}
public get error(): string {
return this.privError;
}
}

Просмотреть файл

@ -0,0 +1,126 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { ConnectionMessage } from "./ConnectionMessage";
import { IStringDictionary } from "./IDictionary";
import { EventType, PlatformEvent } from "./PlatformEvent";
export class ConnectionEvent extends PlatformEvent {
private privConnectionId: string;
constructor(eventName: string, connectionId: string, eventType: EventType = EventType.Info) {
super(eventName, eventType);
this.privConnectionId = connectionId;
}
public get connectionId(): string {
return this.privConnectionId;
}
}
// tslint:disable-next-line:max-classes-per-file
export class ConnectionStartEvent extends ConnectionEvent {
private privUri: string;
private privHeaders: IStringDictionary<string>;
constructor(connectionId: string, uri: string, headers?: IStringDictionary<string>) {
super("ConnectionStartEvent", connectionId);
this.privUri = uri;
this.privHeaders = headers;
}
public get uri(): string {
return this.privUri;
}
public get headers(): IStringDictionary<string> {
return this.privHeaders;
}
}
// tslint:disable-next-line:max-classes-per-file
export class ConnectionEstablishedEvent extends ConnectionEvent {
constructor(connectionId: string, metadata?: IStringDictionary<string>) {
super("ConnectionEstablishedEvent", connectionId);
}
}
// tslint:disable-next-line:max-classes-per-file
export class ConnectionClosedEvent extends ConnectionEvent {
private privRreason: string;
private privStatusCode: number;
constructor(connectionId: string, statusCode: number, reason: string) {
super("ConnectionClosedEvent", connectionId, EventType.Debug);
this.privRreason = reason;
this.privStatusCode = statusCode;
}
public get reason(): string {
return this.privRreason;
}
public get statusCode(): number {
return this.privStatusCode;
}
}
// tslint:disable-next-line:max-classes-per-file
export class ConnectionEstablishErrorEvent extends ConnectionEvent {
private privStatusCode: number;
private privReason: string;
constructor(connectionId: string, statuscode: number, reason: string) {
super("ConnectionEstablishErrorEvent", connectionId, EventType.Error);
this.privStatusCode = statuscode;
this.privReason = reason;
}
public get reason(): string {
return this.privReason;
}
public get statusCode(): number {
return this.privStatusCode;
}
}
// tslint:disable-next-line:max-classes-per-file
export class ConnectionMessageReceivedEvent extends ConnectionEvent {
private privNetworkReceivedTime: string;
private privMessage: ConnectionMessage;
constructor(connectionId: string, networkReceivedTimeISO: string, message: ConnectionMessage) {
super("ConnectionMessageReceivedEvent", connectionId);
this.privNetworkReceivedTime = networkReceivedTimeISO;
this.privMessage = message;
}
public get networkReceivedTime(): string {
return this.privNetworkReceivedTime;
}
public get message(): ConnectionMessage {
return this.privMessage;
}
}
// tslint:disable-next-line:max-classes-per-file
export class ConnectionMessageSentEvent extends ConnectionEvent {
private privNetworkSentTime: string;
private privMessage: ConnectionMessage;
constructor(connectionId: string, networkSentTimeISO: string, message: ConnectionMessage) {
super("ConnectionMessageSentEvent", connectionId);
this.privNetworkSentTime = networkSentTimeISO;
this.privMessage = message;
}
public get networkSentTime(): string {
return this.privNetworkSentTime;
}
public get message(): ConnectionMessage {
return this.privMessage;
}
}

Просмотреть файл

@ -0,0 +1,72 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { InvalidOperationError } from "./Error";
import { createNoDashGuid } from "./Guid";
import { IStringDictionary } from "./IDictionary";
export enum MessageType {
Text,
Binary,
}
export class ConnectionMessage {
private privMessageType: MessageType;
private privHeaders: IStringDictionary<string>;
private privBody: any = null;
private privId: string;
public constructor(
messageType: MessageType,
body: any,
headers?: IStringDictionary<string>,
id?: string) {
if (messageType === MessageType.Text && body && !(typeof (body) === "string")) {
throw new InvalidOperationError("Payload must be a string");
}
if (messageType === MessageType.Binary && body && !(body instanceof ArrayBuffer)) {
throw new InvalidOperationError("Payload must be ArrayBuffer");
}
this.privMessageType = messageType;
this.privBody = body;
this.privHeaders = headers ? headers : {};
this.privId = id ? id : createNoDashGuid();
}
public get messageType(): MessageType {
return this.privMessageType;
}
public get headers(): any {
return this.privHeaders;
}
public get body(): any {
return this.privBody;
}
public get textBody(): string {
if (this.privMessageType === MessageType.Binary) {
throw new InvalidOperationError("Not supported for binary message");
}
return this.privBody as string;
}
public get binaryBody(): ArrayBuffer {
if (this.privMessageType === MessageType.Text) {
throw new InvalidOperationError("Not supported for text message");
}
return this.privBody;
}
public get id(): string {
return this.privId;
}
}

Просмотреть файл

@ -0,0 +1,20 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
export class ConnectionOpenResponse {
private privStatusCode: number;
private privReason: string;
constructor(statusCode: number, reason: string) {
this.privStatusCode = statusCode;
this.privReason = reason;
}
public get statusCode(): number {
return this.privStatusCode;
}
public get reason(): string {
return this.privReason;
}
}

74
src/common/Error.ts Normal file
Просмотреть файл

@ -0,0 +1,74 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
/**
* The error that is thrown when an argument passed in is null.
*
* @export
* @class ArgumentNullError
* @extends {Error}
*/
export class ArgumentNullError extends Error {
/**
* Creates an instance of ArgumentNullError.
*
* @param {string} argumentName - Name of the argument that is null
*
* @memberOf ArgumentNullError
*/
public constructor(argumentName: string) {
super(argumentName);
this.name = "ArgumentNull";
this.message = argumentName;
}
}
/**
* The error that is thrown when an invalid operation is performed in the code.
*
* @export
* @class InvalidOperationError
* @extends {Error}
*/
// tslint:disable-next-line:max-classes-per-file
export class InvalidOperationError extends Error {
/**
* Creates an instance of InvalidOperationError.
*
* @param {string} error - The error
*
* @memberOf InvalidOperationError
*/
public constructor(error: string) {
super(error);
this.name = "InvalidOperation";
this.message = error;
}
}
/**
* The error that is thrown when an object is disposed.
*
* @export
* @class ObjectDisposedError
* @extends {Error}
*/
// tslint:disable-next-line:max-classes-per-file
export class ObjectDisposedError extends Error {
/**
* Creates an instance of ObjectDisposedError.
*
* @param {string} objectName - The object that is disposed
* @param {string} error - The error
*
* @memberOf ObjectDisposedError
*/
public constructor(objectName: string, error?: string) {
super(error);
this.name = objectName + "ObjectDisposed";
this.message = error;
}
}

70
src/common/EventSource.ts Normal file
Просмотреть файл

@ -0,0 +1,70 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { ObjectDisposedError } from "./Error";
import { createNoDashGuid } from "./Guid";
import { IDetachable } from "./IDetachable";
import { IStringDictionary } from "./IDictionary";
import { IEventListener, IEventSource } from "./IEventSource";
import { PlatformEvent } from "./PlatformEvent";
export class EventSource<TEvent extends PlatformEvent> implements IEventSource<TEvent> {
private privEventListeners: IStringDictionary<(event: TEvent) => void> = {};
private privMetadata: IStringDictionary<string>;
private privIsDisposed: boolean = false;
constructor(metadata?: IStringDictionary<string>) {
this.privMetadata = metadata;
}
public onEvent = (event: TEvent): void => {
if (this.isDisposed()) {
throw (new ObjectDisposedError("EventSource"));
}
if (this.metadata) {
for (const paramName in this.metadata) {
if (paramName) {
if (event.metadata) {
if (!event.metadata[paramName]) {
event.metadata[paramName] = this.metadata[paramName];
}
}
}
}
}
for (const eventId in this.privEventListeners) {
if (eventId && this.privEventListeners[eventId]) {
this.privEventListeners[eventId](event);
}
}
}
public attach = (onEventCallback: (event: TEvent) => void): IDetachable => {
const id = createNoDashGuid();
this.privEventListeners[id] = onEventCallback;
return {
detach: () => {
delete this.privEventListeners[id];
},
};
}
public attachListener = (listener: IEventListener<TEvent>): IDetachable => {
return this.attach(listener.onEvent);
}
public isDisposed = (): boolean => {
return this.privIsDisposed;
}
public dispose = (): void => {
this.privEventListeners = null;
this.privIsDisposed = true;
}
public get metadata(): IStringDictionary<string> {
return this.privMetadata;
}
}

23
src/common/Events.ts Normal file
Просмотреть файл

@ -0,0 +1,23 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { ArgumentNullError } from "./Error";
import { EventSource } from "./EventSource";
import { IEventSource } from "./IEventSource";
import { PlatformEvent } from "./PlatformEvent";
export class Events {
private static privInstance: IEventSource<PlatformEvent> = new EventSource<PlatformEvent>();
public static setEventSource = (eventSource: IEventSource<PlatformEvent>): void => {
if (!eventSource) {
throw new ArgumentNullError("eventSource");
}
Events.privInstance = eventSource;
}
public static get instance(): IEventSource<PlatformEvent> {
return Events.privInstance;
}
}

30
src/common/Exports.ts Normal file
Просмотреть файл

@ -0,0 +1,30 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
export * from "./AudioSourceEvents";
export * from "./ConnectionEvents";
export * from "./ConnectionMessage";
export * from "./ConnectionOpenResponse";
export * from "./Error";
export * from "./Events";
export * from "./EventSource";
export * from "./Guid";
export * from "./IAudioSource";
export * from "./IConnection";
export * from "./IDetachable";
export * from "./IDictionary";
export * from "./IDisposable";
export * from "./IEventSource";
export * from "./IKeyValueStorage";
export * from "./InMemoryStorage";
export * from "./ITimer";
export * from "./IWebsocketMessageFormatter";
export * from "./List";
export * from "./PlatformEvent";
export * from "./Promise";
export * from "./Queue";
export * from "./RawWebsocketMessage";
export * from "./RiffPcmEncoder";
export * from "./Storage";
export * from "./Stream";
export { TranslationStatus } from "../common.speech/TranslationStatus";

19
src/common/Guid.ts Normal file
Просмотреть файл

@ -0,0 +1,19 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
const createGuid: () => string = (): string => {
let d = new Date().getTime();
const guid = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c: string) => {
const r = (d + Math.random() * 16) % 16 | 0;
d = Math.floor(d / 16);
return (c === "x" ? r : (r & 0x3 | 0x8)).toString(16);
});
return guid;
};
const createNoDashGuid: () => string = (): string => {
return createGuid().replace(new RegExp("-", "g"), "").toUpperCase();
};
export { createGuid, createNoDashGuid };

Просмотреть файл

@ -0,0 +1,24 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { AudioStreamFormat } from "../../src/sdk/Exports";
import { AudioSourceEvent } from "./AudioSourceEvents";
import { EventSource } from "./EventSource";
import { IDetachable } from "./IDetachable";
import { Promise } from "./Promise";
import { IStreamChunk } from "./Stream";
export interface IAudioSource {
id(): string;
turnOn(): Promise<boolean>;
attach(audioNodeId: string): Promise<IAudioStreamNode>;
detach(audioNodeId: string): void;
turnOff(): Promise<boolean>;
events: EventSource<AudioSourceEvent>;
format: AudioStreamFormat;
}
export interface IAudioStreamNode extends IDetachable {
id(): string;
read(): Promise<IStreamChunk<ArrayBuffer>>;
}

25
src/common/IConnection.ts Normal file
Просмотреть файл

@ -0,0 +1,25 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { ConnectionEvent } from "./ConnectionEvents";
import { ConnectionMessage } from "./ConnectionMessage";
import { ConnectionOpenResponse } from "./ConnectionOpenResponse";
import { EventSource } from "./EventSource";
import { IDisposable } from "./IDisposable";
import { Promise } from "./Promise";
export enum ConnectionState {
None,
Connected,
Connecting,
Disconnected,
}
export interface IConnection extends IDisposable {
id: string;
state(): ConnectionState;
open(): Promise<ConnectionOpenResponse>;
send(message: ConnectionMessage): Promise<boolean>;
read(): Promise<ConnectionMessage>;
events: EventSource<ConnectionEvent>;
}

Просмотреть файл

@ -0,0 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
export interface IDetachable {
detach(): void;
}

10
src/common/IDictionary.ts Normal file
Просмотреть файл

@ -0,0 +1,10 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
export interface IStringDictionary<TValue> {
[propName: string]: TValue;
}
export interface INumberDictionary<TValue> extends Object {
[propName: number]: TValue;
}

26
src/common/IDisposable.ts Normal file
Просмотреть файл

@ -0,0 +1,26 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
/**
* @export
* @interface IDisposable
*/
export interface IDisposable {
/**
* @returns {boolean}
*
* @memberOf IDisposable
*/
isDisposed(): boolean;
/**
* Performs cleanup operations on this instance
*
* @param {string} [reason] - optional reason for disposing the instance.
* This will be used to throw errors when a operations are performed on the disposed object.
*
* @memberOf IDisposable
*/
dispose(reason?: string): void;
}

Просмотреть файл

@ -0,0 +1,21 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { IDetachable } from "./IDetachable";
import { IStringDictionary } from "./IDictionary";
import { IDisposable } from "./IDisposable";
import { PlatformEvent } from "./PlatformEvent";
export interface IEventListener<TEvent extends PlatformEvent> {
onEvent(e: TEvent): void;
}
export interface IEventSource<TEvent extends PlatformEvent> extends IDisposable {
metadata: IStringDictionary<string>;
onEvent(e: TEvent): void;
attach(onEventCallback: (event: TEvent) => void): IDetachable;
attachListener(listener: IEventListener<TEvent>): IDetachable;
}

Просмотреть файл

@ -0,0 +1,9 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
export interface IKeyValueStorage {
get(key: string): string;
getOrAdd(key: string, valueToAdd: string): string;
set(key: string, value: string): void;
remove(key: string): void;
}

24
src/common/ITimer.ts Normal file
Просмотреть файл

@ -0,0 +1,24 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
export interface ITimer {
/**
* start timer
*
* @param {number} delay
* @param {(...args: any[]) => any} successCallback
* @returns {*}
*
* @memberOf ITimer
*/
start(): void;
/**
* stops timer
*
* @param {*} timerId
*
* @memberOf ITimer
*/
stop(): void;
}

Просмотреть файл

@ -0,0 +1,11 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { ConnectionMessage } from "./ConnectionMessage";
import { Promise } from "./Promise";
import { RawWebsocketMessage } from "./RawWebsocketMessage";
export interface IWebsocketMessageFormatter {
toConnectionMessage(message: RawWebsocketMessage): Promise<ConnectionMessage>;
fromConnectionMessage(message: ConnectionMessage): Promise<RawWebsocketMessage>;
}

Просмотреть файл

@ -0,0 +1,49 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { ArgumentNullError } from "./Error";
import { IStringDictionary } from "./IDictionary";
import { IKeyValueStorage } from "./IKeyValueStorage";
export class InMemoryStorage implements IKeyValueStorage {
private privStore: IStringDictionary<string> = {};
public get = (key: string): string => {
if (!key) {
throw new ArgumentNullError("key");
}
return this.privStore[key];
}
public getOrAdd = (key: string, valueToAdd: string): string => {
if (!key) {
throw new ArgumentNullError("key");
}
if (this.privStore[key] === undefined) {
this.privStore[key] = valueToAdd;
}
return this.privStore[key];
}
public set = (key: string, value: string): void => {
if (!key) {
throw new ArgumentNullError("key");
}
this.privStore[key] = value;
}
public remove = (key: string): void => {
if (!key) {
throw new ArgumentNullError("key");
}
if (this.privStore[key] !== undefined) {
delete this.privStore[key];
}
}
}

275
src/common/List.ts Normal file
Просмотреть файл

@ -0,0 +1,275 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { ObjectDisposedError } from "./Error";
import { IDetachable } from "./IDetachable";
import { IStringDictionary } from "./IDictionary";
import { IDisposable } from "./IDisposable";
export interface IList<TItem> extends IDisposable {
get(itemIndex: number): TItem;
first(): TItem;
last(): TItem;
add(item: TItem): void;
insertAt(index: number, item: TItem): void;
removeFirst(): TItem;
removeLast(): TItem;
removeAt(index: number): TItem;
remove(index: number, count: number): TItem[];
clear(): void;
length(): number;
onAdded(addedCallback: () => void): IDetachable;
onRemoved(removedCallback: () => void): IDetachable;
onDisposed(disposedCallback: () => void): IDetachable;
join(seperator?: string): string;
toArray(): TItem[];
any(callback?: (item: TItem, index: number) => boolean): boolean;
all(callback: (item: TItem) => boolean): boolean;
forEach(callback: (item: TItem, index: number) => void): void;
select<T2>(callback: (item: TItem, index: number) => T2): List<T2>;
where(callback: (item: TItem, index: number) => boolean): List<TItem>;
orderBy(compareFn: (a: TItem, b: TItem) => number): List<TItem>;
orderByDesc(compareFn: (a: TItem, b: TItem) => number): List<TItem>;
clone(): List<TItem>;
concat(list: List<TItem>): List<TItem>;
concatArray(array: TItem[]): List<TItem>;
}
export class List<TItem> implements IList<TItem> {
private privList: TItem[];
private privSubscriptionIdCounter: number = 0;
private privAddSubscriptions: IStringDictionary<() => void> = {};
private privRemoveSubscriptions: IStringDictionary<() => void> = {};
private privDisposedSubscriptions: IStringDictionary<() => void> = {};
private privDisposeReason: string = null;
public constructor(list?: TItem[]) {
this.privList = [];
// copy the list rather than taking as is.
if (list) {
for (const item of list) {
this.privList.push(item);
}
}
}
public get = (itemIndex: number): TItem => {
this.throwIfDisposed();
return this.privList[itemIndex];
}
public first = (): TItem => {
return this.get(0);
}
public last = (): TItem => {
return this.get(this.length() - 1);
}
public add = (item: TItem): void => {
this.throwIfDisposed();
this.insertAt(this.privList.length, item);
}
public insertAt = (index: number, item: TItem): void => {
this.throwIfDisposed();
if (index === 0) {
this.privList.unshift(item);
} else if (index === this.privList.length) {
this.privList.push(item);
} else {
this.privList.splice(index, 0, item);
}
this.triggerSubscriptions(this.privAddSubscriptions);
}
public removeFirst = (): TItem => {
this.throwIfDisposed();
return this.removeAt(0);
}
public removeLast = (): TItem => {
this.throwIfDisposed();
return this.removeAt(this.length() - 1);
}
public removeAt = (index: number): TItem => {
this.throwIfDisposed();
return this.remove(index, 1)[0];
}
public remove = (index: number, count: number): TItem[] => {
this.throwIfDisposed();
const removedElements = this.privList.splice(index, count);
this.triggerSubscriptions(this.privRemoveSubscriptions);
return removedElements;
}
public clear = (): void => {
this.throwIfDisposed();
this.remove(0, this.length());
}
public length = (): number => {
this.throwIfDisposed();
return this.privList.length;
}
public onAdded = (addedCallback: () => void): IDetachable => {
this.throwIfDisposed();
const subscriptionId = this.privSubscriptionIdCounter++;
this.privAddSubscriptions[subscriptionId] = addedCallback;
return {
detach: () => {
delete this.privAddSubscriptions[subscriptionId];
},
};
}
public onRemoved = (removedCallback: () => void): IDetachable => {
this.throwIfDisposed();
const subscriptionId = this.privSubscriptionIdCounter++;
this.privRemoveSubscriptions[subscriptionId] = removedCallback;
return {
detach: () => {
delete this.privRemoveSubscriptions[subscriptionId];
},
};
}
public onDisposed = (disposedCallback: () => void): IDetachable => {
this.throwIfDisposed();
const subscriptionId = this.privSubscriptionIdCounter++;
this.privDisposedSubscriptions[subscriptionId] = disposedCallback;
return {
detach: () => {
delete this.privDisposedSubscriptions[subscriptionId];
},
};
}
public join = (seperator?: string): string => {
this.throwIfDisposed();
return this.privList.join(seperator);
}
public toArray = (): TItem[] => {
const cloneCopy = Array<TItem>();
this.privList.forEach((val: TItem) => {
cloneCopy.push(val);
});
return cloneCopy;
}
public any = (callback?: (item: TItem, index: number) => boolean): boolean => {
this.throwIfDisposed();
if (callback) {
return this.where(callback).length() > 0;
} else {
return this.length() > 0;
}
}
public all = (callback: (item: TItem) => boolean): boolean => {
this.throwIfDisposed();
return this.where(callback).length() === this.length();
}
public forEach = (callback: (item: TItem, index: number) => void): void => {
this.throwIfDisposed();
for (let i = 0; i < this.length(); i++) {
callback(this.privList[i], i);
}
}
public select = <T2>(callback: (item: TItem, index: number) => T2): List<T2> => {
this.throwIfDisposed();
const selectList: T2[] = [];
for (let i = 0; i < this.privList.length; i++) {
selectList.push(callback(this.privList[i], i));
}
return new List<T2>(selectList);
}
public where = (callback: (item: TItem, index: number) => boolean): List<TItem> => {
this.throwIfDisposed();
const filteredList = new List<TItem>();
for (let i = 0; i < this.privList.length; i++) {
if (callback(this.privList[i], i)) {
filteredList.add(this.privList[i]);
}
}
return filteredList;
}
public orderBy = (compareFn: (a: TItem, b: TItem) => number): List<TItem> => {
this.throwIfDisposed();
const clonedArray = this.toArray();
const orderedArray = clonedArray.sort(compareFn);
return new List(orderedArray);
}
public orderByDesc = (compareFn: (a: TItem, b: TItem) => number): List<TItem> => {
this.throwIfDisposed();
return this.orderBy((a: TItem, b: TItem) => compareFn(b, a));
}
public clone = (): List<TItem> => {
this.throwIfDisposed();
return new List<TItem>(this.toArray());
}
public concat = (list: List<TItem>): List<TItem> => {
this.throwIfDisposed();
return new List<TItem>(this.privList.concat(list.toArray()));
}
public concatArray = (array: TItem[]): List<TItem> => {
this.throwIfDisposed();
return new List<TItem>(this.privList.concat(array));
}
public isDisposed = (): boolean => {
return this.privList == null;
}
public dispose = (reason?: string): void => {
if (!this.isDisposed()) {
this.privDisposeReason = reason;
this.privList = null;
this.privAddSubscriptions = null;
this.privRemoveSubscriptions = null;
this.triggerSubscriptions(this.privDisposedSubscriptions);
}
}
private throwIfDisposed = (): void => {
if (this.isDisposed()) {
throw new ObjectDisposedError("List", this.privDisposeReason);
}
}
private triggerSubscriptions = (subscriptions: IStringDictionary<() => void>): void => {
if (subscriptions) {
for (const subscriptionId in subscriptions) {
if (subscriptionId) {
subscriptions[subscriptionId]();
}
}
}
}
}

Просмотреть файл

@ -0,0 +1,48 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { createNoDashGuid } from "./Guid";
import { IStringDictionary } from "./IDictionary";
export enum EventType {
Debug,
Info,
Warning,
Error,
}
export class PlatformEvent {
private privName: string;
private privEventId: string;
private privEventTime: string;
private privEventType: EventType;
private privMetadata: IStringDictionary<string>;
constructor(eventName: string, eventType: EventType) {
this.privName = eventName;
this.privEventId = createNoDashGuid();
this.privEventTime = new Date().toISOString();
this.privEventType = eventType;
this.privMetadata = { };
}
public get name(): string {
return this.privName;
}
public get eventId(): string {
return this.privEventId;
}
public get eventTime(): string {
return this.privEventTime;
}
public get eventType(): EventType {
return this.privEventType;
}
public get metadata(): IStringDictionary<string> {
return this.privMetadata;
}
}

457
src/common/Promise.ts Normal file
Просмотреть файл

@ -0,0 +1,457 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { ArgumentNullError } from "./Error";
export enum PromiseState {
None,
Resolved,
Rejected,
}
export interface IPromise<T> {
result(): PromiseResult<T>;
continueWith<TContinuationResult>(
continuationCallback: (promiseResult: PromiseResult<T>) => TContinuationResult): IPromise<TContinuationResult>;
continueWithPromise<TContinuationResult>(
continuationCallback: (promiseResult: PromiseResult<T>) => IPromise<TContinuationResult>): IPromise<TContinuationResult>;
onSuccessContinueWith<TContinuationResult>(
continuationCallback: (result: T) => TContinuationResult): IPromise<TContinuationResult>;
onSuccessContinueWithPromise<TContinuationResult>(
continuationCallback: (result: T) => IPromise<TContinuationResult>): IPromise<TContinuationResult>;
on(successCallback: (result: T) => void, errorCallback: (error: string) => void): IPromise<T>;
finally(callback: () => void): IPromise<T>;
}
export interface IDeferred<T> {
state(): PromiseState;
promise(): IPromise<T>;
resolve(result: T): IDeferred<T>;
reject(error: string): IDeferred<T>;
}
export class PromiseResult<T> {
protected privIsCompleted: boolean;
protected privIsError: boolean;
protected privError: string;
protected privResult: T;
public constructor(promiseResultEventSource: PromiseResultEventSource<T>) {
promiseResultEventSource.on((result: T) => {
if (!this.privIsCompleted) {
this.privIsCompleted = true;
this.privIsError = false;
this.privResult = result;
}
}, (error: string) => {
if (!this.privIsCompleted) {
this.privIsCompleted = true;
this.privIsError = true;
this.privError = error;
}
});
}
public get isCompleted(): boolean {
return this.privIsCompleted;
}
public get isError(): boolean {
return this.privIsError;
}
public get error(): string {
return this.privError;
}
public get result(): T {
return this.privResult;
}
public throwIfError = (): void => {
if (this.isError) {
throw this.error;
}
}
}
// tslint:disable-next-line:max-classes-per-file
export class PromiseResultEventSource<T> {
private privOnSetResult: (result: T) => void;
private privOnSetError: (error: string) => void;
public setResult = (result: T): void => {
this.privOnSetResult(result);
}
public setError = (error: string): void => {
this.privOnSetError(error);
}
public on = (onSetResult: (result: T) => void, onSetError: (error: string) => void): void => {
this.privOnSetResult = onSetResult;
this.privOnSetError = onSetError;
}
}
// tslint:disable-next-line:max-classes-per-file
export class PromiseHelper {
public static whenAll = (promises: Array<Promise<any>>): Promise<boolean> => {
if (!promises || promises.length === 0) {
throw new ArgumentNullError("promises");
}
const deferred = new Deferred<boolean>();
const errors: string[] = [];
let completedPromises: number = 0;
const checkForCompletion = () => {
completedPromises++;
if (completedPromises === promises.length) {
if (errors.length === 0) {
deferred.resolve(true);
} else {
deferred.reject(errors.join(", "));
}
}
};
for (const promise of promises) {
promise.on((r: any) => {
checkForCompletion();
}, (e: string) => {
errors.push(e);
checkForCompletion();
});
}
return deferred.promise();
}
public static fromResult = <TResult>(result: TResult): Promise<TResult> => {
const deferred = new Deferred<TResult>();
deferred.resolve(result);
return deferred.promise();
}
public static fromError = <TResult>(error: string): Promise<TResult> => {
const deferred = new Deferred<TResult>();
deferred.reject(error);
return deferred.promise();
}
}
// TODO: replace with ES6 promises
// tslint:disable-next-line:max-classes-per-file
export class Promise<T> implements IPromise<T> {
private privSink: Sink<T>;
public constructor(sink: Sink<T>) {
this.privSink = sink;
}
public result = (): PromiseResult<T> => {
return this.privSink.result;
}
public continueWith = <TContinuationResult>(
continuationCallback: (promiseResult: PromiseResult<T>) => TContinuationResult): Promise<TContinuationResult> => {
if (!continuationCallback) {
throw new ArgumentNullError("continuationCallback");
}
const continuationDeferral = new Deferred<TContinuationResult>();
this.privSink.on(
(r: T) => {
try {
const continuationResult: TContinuationResult = continuationCallback(this.privSink.result);
continuationDeferral.resolve(continuationResult);
} catch (e) {
continuationDeferral.reject(e);
}
},
(error: string) => {
try {
const continuationResult: TContinuationResult = continuationCallback(this.privSink.result);
continuationDeferral.resolve(continuationResult);
} catch (e) {
continuationDeferral.reject(`'Error handler for error ${error} threw error ${e}'`);
}
},
);
return continuationDeferral.promise();
}
public onSuccessContinueWith = <TContinuationResult>(
continuationCallback: (result: T) => TContinuationResult): Promise<TContinuationResult> => {
if (!continuationCallback) {
throw new ArgumentNullError("continuationCallback");
}
const continuationDeferral = new Deferred<TContinuationResult>();
this.privSink.on(
(r: T) => {
try {
const continuationResult: TContinuationResult = continuationCallback(r);
continuationDeferral.resolve(continuationResult);
} catch (e) {
continuationDeferral.reject(e);
}
},
(error: string) => {
continuationDeferral.reject(error);
},
);
return continuationDeferral.promise();
}
public continueWithPromise = <TContinuationResult>(
continuationCallback: (promiseResult: PromiseResult<T>) => Promise<TContinuationResult>): Promise<TContinuationResult> => {
if (!continuationCallback) {
throw new ArgumentNullError("continuationCallback");
}
const continuationDeferral = new Deferred<TContinuationResult>();
this.privSink.on(
(r: T) => {
try {
const continuationPromise: Promise<TContinuationResult> = continuationCallback(this.privSink.result);
if (!continuationPromise) {
throw new Error("'Continuation callback did not return promise'");
}
continuationPromise.on((continuationResult: TContinuationResult) => {
continuationDeferral.resolve(continuationResult);
}, (e: string) => {
continuationDeferral.reject(e);
});
} catch (e) {
continuationDeferral.reject(e);
}
},
(error: string) => {
try {
const continuationPromise: Promise<TContinuationResult> = continuationCallback(this.privSink.result);
if (!continuationPromise) {
throw new Error("Continuation callback did not return promise");
}
continuationPromise.on((continuationResult: TContinuationResult) => {
continuationDeferral.resolve(continuationResult);
}, (e: string) => {
continuationDeferral.reject(e);
});
} catch (e) {
continuationDeferral.reject(`'Error handler for error ${error} threw error ${e}'`);
}
},
);
return continuationDeferral.promise();
}
public onSuccessContinueWithPromise = <TContinuationResult>(
continuationCallback: (result: T) => Promise<TContinuationResult>): Promise<TContinuationResult> => {
if (!continuationCallback) {
throw new ArgumentNullError("continuationCallback");
}
const continuationDeferral = new Deferred<TContinuationResult>();
this.privSink.on(
(r: T) => {
try {
const continuationPromise: Promise<TContinuationResult> = continuationCallback(r);
if (!continuationPromise) {
throw new Error("Continuation callback did not return promise");
}
continuationPromise.on((continuationResult: TContinuationResult) => {
continuationDeferral.resolve(continuationResult);
}, (e: string) => {
continuationDeferral.reject(e);
});
} catch (e) {
continuationDeferral.reject(e);
}
},
(error: string) => {
continuationDeferral.reject(error);
},
);
return continuationDeferral.promise();
}
public on = (
successCallback: (result: T) => void,
errorCallback: (error: string) => void): Promise<T> => {
if (!successCallback) {
throw new ArgumentNullError("successCallback");
}
if (!errorCallback) {
throw new ArgumentNullError("errorCallback");
}
this.privSink.on(successCallback, errorCallback);
return this;
}
public finally = (callback: () => void): Promise<T> => {
if (!callback) {
throw new ArgumentNullError("callback");
}
const callbackWrapper = (_: any) => {
callback();
};
return this.on(callbackWrapper, callbackWrapper);
}
}
// tslint:disable-next-line:max-classes-per-file
export class Deferred<T> implements IDeferred<T> {
private privPromise: Promise<T>;
private privSink: Sink<T>;
public constructor() {
this.privSink = new Sink<T>();
this.privPromise = new Promise<T>(this.privSink);
}
public state = (): PromiseState => {
return this.privSink.state;
}
public promise = (): Promise<T> => {
return this.privPromise;
}
public resolve = (result: T): Deferred<T> => {
this.privSink.resolve(result);
return this;
}
public reject = (error: string): Deferred<T> => {
this.privSink.reject(error);
return this;
}
}
// tslint:disable-next-line:max-classes-per-file
export class Sink<T> {
private privState: PromiseState = PromiseState.None;
private privPromiseResult: PromiseResult<T> = null;
private privPromiseResultEvents: PromiseResultEventSource<T> = null;
private privSuccessHandlers: Array<((result: T) => void)> = [];
private privErrorHandlers: Array<(e: string) => void> = [];
public constructor() {
this.privPromiseResultEvents = new PromiseResultEventSource();
this.privPromiseResult = new PromiseResult(this.privPromiseResultEvents);
}
public get state(): PromiseState {
return this.privState;
}
public get result(): PromiseResult<T> {
return this.privPromiseResult;
}
public resolve = (result: T): void => {
if (this.privState !== PromiseState.None) {
throw new Error("'Cannot resolve a completed promise'");
}
this.privState = PromiseState.Resolved;
this.privPromiseResultEvents.setResult(result);
for (let i = 0; i < this.privSuccessHandlers.length; i++) {
this.executeSuccessCallback(result, this.privSuccessHandlers[i], this.privErrorHandlers[i]);
}
this.detachHandlers();
}
public reject = (error: string): void => {
if (this.privState !== PromiseState.None) {
throw new Error("'Cannot reject a completed promise'");
}
this.privState = PromiseState.Rejected;
this.privPromiseResultEvents.setError(error);
for (const errorHandler of this.privErrorHandlers) {
this.executeErrorCallback(error, errorHandler);
}
this.detachHandlers();
}
public on = (
successCallback: (result: T) => void,
errorCallback: (error: string) => void): void => {
if (successCallback == null) {
successCallback = (r: T) => { return; };
}
if (this.privState === PromiseState.None) {
this.privSuccessHandlers.push(successCallback);
this.privErrorHandlers.push(errorCallback);
} else {
if (this.privState === PromiseState.Resolved) {
this.executeSuccessCallback(this.privPromiseResult.result, successCallback, errorCallback);
} else if (this.privState === PromiseState.Rejected) {
this.executeErrorCallback(this.privPromiseResult.error, errorCallback);
}
this.detachHandlers();
}
}
private executeSuccessCallback = (result: T, successCallback: (result: T) => void, errorCallback: (error: string) => void): void => {
try {
successCallback(result);
} catch (e) {
this.executeErrorCallback(`'Unhandled callback error: ${e}'`, errorCallback);
}
}
private executeErrorCallback = (error: string, errorCallback: (error: string) => void): void => {
if (errorCallback) {
try {
errorCallback(error);
} catch (e) {
throw new Error(`'Unhandled callback error: ${e}. InnerError: ${error}'`);
}
} else {
throw new Error(`'Unhandled error: ${error}'`);
}
}
private detachHandlers = (): void => {
this.privErrorHandlers = [];
this.privSuccessHandlers = [];
}
}

212
src/common/Queue.ts Normal file
Просмотреть файл

@ -0,0 +1,212 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { InvalidOperationError, ObjectDisposedError } from "./Error";
import { IDetachable } from "./IDetachable";
import { IDisposable } from "./IDisposable";
import { List } from "./List";
import { Deferred, Promise, PromiseHelper } from "./Promise";
export interface IQueue<TItem> extends IDisposable {
enqueue(item: TItem): void;
enqueueFromPromise(promise: Promise<TItem>): void;
dequeue(): Promise<TItem>;
peek(): Promise<TItem>;
length(): number;
}
enum SubscriberType {
Dequeue,
Peek,
}
export class Queue<TItem> implements IQueue<TItem> {
private privPromiseStore: List<Promise<TItem>> = new List<Promise<TItem>>();
private privList: List<TItem>;
private privDetachables: IDetachable[];
private privSubscribers: List<{ type: SubscriberType, deferral: Deferred<TItem> }>;
private privIsDrainInProgress: boolean = false;
private privIsDisposing: boolean = false;
private privDisposeReason: string = null;
public constructor(list?: List<TItem>) {
this.privList = list ? list : new List<TItem>();
this.privDetachables = [];
this.privSubscribers = new List<{ type: SubscriberType, deferral: Deferred<TItem> }>();
this.privDetachables.push(this.privList.onAdded(this.drain));
}
public enqueue = (item: TItem): void => {
this.throwIfDispose();
this.enqueueFromPromise(PromiseHelper.fromResult(item));
}
public enqueueFromPromise = (promise: Promise<TItem>): void => {
this.throwIfDispose();
this.privPromiseStore.add(promise);
promise.finally(() => {
while (this.privPromiseStore.length() > 0) {
if (!this.privPromiseStore.first().result().isCompleted) {
break;
} else {
const p = this.privPromiseStore.removeFirst();
if (!p.result().isError) {
this.privList.add(p.result().result);
} else {
// TODO: Log as warning.
}
}
}
});
}
public dequeue = (): Promise<TItem> => {
this.throwIfDispose();
const deferredSubscriber = new Deferred<TItem>();
if (this.privSubscribers) {
this.privSubscribers.add({ deferral: deferredSubscriber, type: SubscriberType.Dequeue });
this.drain();
}
return deferredSubscriber.promise();
}
public peek = (): Promise<TItem> => {
this.throwIfDispose();
const deferredSubscriber = new Deferred<TItem>();
const subs = this.privSubscribers;
if (subs) {
this.privSubscribers.add({ deferral: deferredSubscriber, type: SubscriberType.Peek });
this.drain();
}
return deferredSubscriber.promise();
}
public length = (): number => {
this.throwIfDispose();
return this.privList.length();
}
public isDisposed = (): boolean => {
return this.privSubscribers == null;
}
public drainAndDispose = (pendingItemProcessor: (pendingItemInQueue: TItem) => void, reason?: string): Promise<boolean> => {
if (!this.isDisposed() && !this.privIsDisposing) {
this.privDisposeReason = reason;
this.privIsDisposing = true;
const subs = this.privSubscribers;
if (subs) {
while (subs.length() > 0) {
const subscriber = subs.removeFirst();
// TODO: this needs work (Resolve(null) instead?).
subscriber.deferral.resolve(undefined);
// subscriber.deferral.reject("Disposed");
}
// note: this block assumes cooperative multitasking, i.e.,
// between the if-statement and the assignment there are no
// thread switches.
// Reason is that between the initial const = this.; and this
// point there is the derral.resolve() operation that might have
// caused recursive calls to the Queue, especially, calling
// Dispose() on the queue alredy (which would reset the var
// here to null!).
// That should generally hold true for javascript...
if (this.privSubscribers === subs) {
this.privSubscribers = subs;
}
}
for (const detachable of this.privDetachables) {
detachable.detach();
}
if (this.privPromiseStore.length() > 0 && pendingItemProcessor) {
return PromiseHelper
.whenAll(this.privPromiseStore.toArray())
.continueWith(() => {
this.privSubscribers = null;
this.privList.forEach((item: TItem, index: number): void => {
pendingItemProcessor(item);
});
this.privList = null;
return true;
});
} else {
this.privSubscribers = null;
this.privList = null;
}
}
return PromiseHelper.fromResult(true);
}
public dispose = (reason?: string): void => {
this.drainAndDispose(null, reason);
}
private drain = (): void => {
if (!this.privIsDrainInProgress && !this.privIsDisposing) {
this.privIsDrainInProgress = true;
const subs = this.privSubscribers;
const lists = this.privList;
if (subs && lists) {
while (lists.length() > 0 && subs.length() > 0 && !this.privIsDisposing) {
const subscriber = subs.removeFirst();
if (subscriber.type === SubscriberType.Peek) {
subscriber.deferral.resolve(lists.first());
} else {
const dequeuedItem = lists.removeFirst();
subscriber.deferral.resolve(dequeuedItem);
}
}
// note: this block assumes cooperative multitasking, i.e.,
// between the if-statement and the assignment there are no
// thread switches.
// Reason is that between the initial const = this.; and this
// point there is the derral.resolve() operation that might have
// caused recursive calls to the Queue, especially, calling
// Dispose() on the queue alredy (which would reset the var
// here to null!).
// That should generally hold true for javascript...
if (this.privSubscribers === subs) {
this.privSubscribers = subs;
}
// note: this block assumes cooperative multitasking, i.e.,
// between the if-statement and the assignment there are no
// thread switches.
// Reason is that between the initial const = this.; and this
// point there is the derral.resolve() operation that might have
// caused recursive calls to the Queue, especially, calling
// Dispose() on the queue alredy (which would reset the var
// here to null!).
// That should generally hold true for javascript...
if (this.privList === lists) {
this.privList = lists;
}
}
this.privIsDrainInProgress = false;
}
}
private throwIfDispose = (): void => {
if (this.isDisposed()) {
if (this.privDisposeReason) {
throw new InvalidOperationError(this.privDisposeReason);
}
throw new ObjectDisposedError("Queue");
} else if (this.privIsDisposing) {
throw new InvalidOperationError("Queue disposing");
}
}
}

Просмотреть файл

@ -0,0 +1,58 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { MessageType } from "./ConnectionMessage";
import { ArgumentNullError, InvalidOperationError } from "./Error";
import { createNoDashGuid } from "./Guid";
export class RawWebsocketMessage {
private privMessageType: MessageType;
private privPayload: any = null;
private privId: string;
public constructor(messageType: MessageType, payload: any, id?: string) {
if (!payload) {
throw new ArgumentNullError("payload");
}
if (messageType === MessageType.Binary && !(payload instanceof ArrayBuffer)) {
throw new InvalidOperationError("Payload must be ArrayBuffer");
}
if (messageType === MessageType.Text && !(typeof (payload) === "string")) {
throw new InvalidOperationError("Payload must be a string");
}
this.privMessageType = messageType;
this.privPayload = payload;
this.privId = id ? id : createNoDashGuid();
}
public get messageType(): MessageType {
return this.privMessageType;
}
public get payload(): any {
return this.privPayload;
}
public get textContent(): string {
if (this.privMessageType === MessageType.Binary) {
throw new InvalidOperationError("Not supported for binary message");
}
return this.privPayload as string;
}
public get binaryContent(): ArrayBuffer {
if (this.privMessageType === MessageType.Text) {
throw new InvalidOperationError("Not supported for text message");
}
return this.privPayload;
}
public get id(): string {
return this.privId;
}
}

Просмотреть файл

@ -0,0 +1,115 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
export class RiffPcmEncoder {
private privActualSampleRate: number;
private privDesiredSampleRate: number;
private privChannelCount: number = 1;
public constructor(actualSampleRate: number, desiredSampleRate: number) {
this.privActualSampleRate = actualSampleRate;
this.privDesiredSampleRate = desiredSampleRate;
}
public encode = (
needHeader: boolean,
actualAudioFrame: Float32Array): ArrayBuffer => {
const audioFrame = this.downSampleAudioFrame(actualAudioFrame, this.privActualSampleRate, this.privDesiredSampleRate);
if (!audioFrame) {
return null;
}
const audioLength = audioFrame.length * 2;
if (!needHeader) {
const buffer = new ArrayBuffer(audioLength);
const view = new DataView(buffer);
this.floatTo16BitPCM(view, 0, audioFrame);
return buffer;
}
const buffer = new ArrayBuffer(44 + audioLength);
const bitsPerSample = 16;
const bytesPerSample = bitsPerSample / 8;
// We dont know ahead of time about the length of audio to stream. So set to 0.
const fileLength = 0;
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView
const view = new DataView(buffer);
/* RIFF identifier */
this.setString(view, 0, "RIFF");
/* file length */
view.setUint32(4, fileLength, true);
/* RIFF type & Format */
this.setString(view, 8, "WAVEfmt ");
/* format chunk length */
view.setUint32(16, 16, true);
/* sample format (raw) */
view.setUint16(20, 1, true);
/* channel count */
view.setUint16(22, this.privChannelCount, true);
/* sample rate */
view.setUint32(24, this.privDesiredSampleRate, true);
/* byte rate (sample rate * block align) */
view.setUint32(28, this.privDesiredSampleRate * this.privChannelCount * bytesPerSample, true);
/* block align (channel count * bytes per sample) */
view.setUint16(32, this.privChannelCount * bytesPerSample, true);
/* bits per sample */
view.setUint16(34, bitsPerSample, true);
/* data chunk identifier */
this.setString(view, 36, "data");
/* data chunk length */
view.setUint32(40, fileLength, true);
this.floatTo16BitPCM(view, 44, audioFrame);
return buffer;
}
private setString = (view: DataView, offset: number, str: string): void => {
for (let i = 0; i < str.length; i++) {
view.setUint8(offset + i, str.charCodeAt(i));
}
}
private floatTo16BitPCM = (view: DataView, offset: number, input: Float32Array): void => {
for (let i = 0; i < input.length; i++ , offset += 2) {
const s = Math.max(-1, Math.min(1, input[i]));
view.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7FFF, true);
}
}
private downSampleAudioFrame = (
srcFrame: Float32Array,
srcRate: number,
dstRate: number): Float32Array => {
if (dstRate === srcRate || dstRate > srcRate) {
return srcFrame;
}
const ratio = srcRate / dstRate;
const dstLength = Math.round(srcFrame.length / ratio);
const dstFrame = new Float32Array(dstLength);
let srcOffset = 0;
let dstOffset = 0;
while (dstOffset < dstLength) {
const nextSrcOffset = Math.round((dstOffset + 1) * ratio);
let accum = 0;
let count = 0;
while (srcOffset < nextSrcOffset && srcOffset < srcFrame.length) {
accum += srcFrame[srcOffset++];
count++;
}
dstFrame[dstOffset++] = accum / count;
}
return dstFrame;
}
}

35
src/common/Storage.ts Normal file
Просмотреть файл

@ -0,0 +1,35 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { ArgumentNullError } from "./Error";
import { IKeyValueStorage } from "./IKeyValueStorage";
import { InMemoryStorage } from "./InMemoryStorage";
export class Storage {
private static privSessionStorage: IKeyValueStorage = new InMemoryStorage();
private static privLocalStorage: IKeyValueStorage = new InMemoryStorage();
public static setSessionStorage = (sessionStorage: IKeyValueStorage): void => {
if (!sessionStorage) {
throw new ArgumentNullError("sessionStorage");
}
Storage.privSessionStorage = sessionStorage;
}
public static setLocalStorage = (localStorage: IKeyValueStorage): void => {
if (!localStorage) {
throw new ArgumentNullError("localStorage");
}
Storage.privLocalStorage = localStorage;
}
public static get session(): IKeyValueStorage {
return Storage.privSessionStorage;
}
public static get local(): IKeyValueStorage {
return Storage.privLocalStorage;
}
}

137
src/common/Stream.ts Normal file
Просмотреть файл

@ -0,0 +1,137 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { InvalidOperationError } from "./Error";
import { createNoDashGuid } from "./Guid";
import { IStringDictionary } from "./IDictionary";
import { Promise } from "./Promise";
import { Queue } from "./Queue";
import { IStreamChunk } from "./Stream";
export interface IStreamChunk<TBuffer> {
isEnd: boolean;
buffer: TBuffer;
}
export class Stream<TBuffer> {
private privId: string;
private privReaderIdCounter: number = 1;
private privStreambuffer: Array<IStreamChunk<TBuffer>>;
private privIsEnded: boolean = false;
private privReaderQueues: IStringDictionary<Queue<IStreamChunk<TBuffer>>>;
public constructor(streamId?: string) {
this.privId = streamId ? streamId : createNoDashGuid();
this.privStreambuffer = [];
this.privReaderQueues = {};
}
public get isClosed(): boolean {
return this.privIsEnded;
}
public get id(): string {
return this.privId;
}
public write = (buffer2: TBuffer): void => {
this.throwIfClosed();
this.writeStreamChunk({
buffer: buffer2,
isEnd: false,
});
}
public getReader = (): StreamReader<TBuffer> => {
const readerId = this.privReaderIdCounter;
this.privReaderIdCounter++;
const readerQueue = new Queue<IStreamChunk<TBuffer>>();
const currentLength = this.privStreambuffer.length;
this.privReaderQueues[readerId] = readerQueue;
for (let i = 0; i < currentLength; i++) {
readerQueue.enqueue(this.privStreambuffer[i]);
}
return new StreamReader(
this.privId,
readerQueue,
() => {
delete this.privReaderQueues[readerId];
});
}
public close = (): void => {
if (!this.privIsEnded) {
this.writeStreamChunk({
buffer: null,
isEnd: true,
});
this.privIsEnded = true;
}
}
private writeStreamChunk = (streamChunk: IStreamChunk<TBuffer>): void => {
this.throwIfClosed();
this.privStreambuffer.push(streamChunk);
for (const readerId in this.privReaderQueues) {
if (!this.privReaderQueues[readerId].isDisposed()) {
try {
this.privReaderQueues[readerId].enqueue(streamChunk);
} catch (e) {
// Do nothing
}
}
}
}
private throwIfClosed = (): void => {
if (this.privIsEnded) {
throw new InvalidOperationError("Stream closed");
}
}
}
// tslint:disable-next-line:max-classes-per-file
export class StreamReader<TBuffer> {
private privReaderQueue: Queue<IStreamChunk<TBuffer>>;
private privOnClose: () => void;
private privIsClosed: boolean = false;
private privStreamId: string;
public constructor(streamId: string, readerQueue: Queue<IStreamChunk<TBuffer>>, onClose: () => void) {
this.privReaderQueue = readerQueue;
this.privOnClose = onClose;
this.privStreamId = streamId;
}
public get isClosed(): boolean {
return this.privIsClosed;
}
public get streamId(): string {
return this.privStreamId;
}
public read = (): Promise<IStreamChunk<TBuffer>> => {
if (this.isClosed) {
throw new InvalidOperationError("StreamReader closed");
}
return this.privReaderQueue
.dequeue()
.onSuccessContinueWith((streamChunk: IStreamChunk<TBuffer>) => {
if (streamChunk.isEnd) {
this.privReaderQueue.dispose("End of stream reached");
}
return streamChunk;
});
}
public close = (): void => {
if (!this.privIsClosed) {
this.privIsClosed = true;
this.privReaderQueue.dispose("StreamReader closed");
this.privOnClose();
}
}
}

Просмотреть файл

@ -0,0 +1,165 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { AudioStreamFormat } from "../../../src/sdk/Exports";
import { FileAudioSource, MicAudioSource, PcmRecorder } from "../../common.browser/Exports";
import { AudioSourceEvent, EventSource, IAudioSource, IAudioStreamNode, Promise } from "../../common/Exports";
import { AudioInputStream, PullAudioInputStreamCallback } from "../Exports";
import { PullAudioInputStreamImpl, PushAudioInputStreamImpl } from "./AudioInputStream";
/**
* Represents audio input configuration used for specifying what type of input to use (microphone, file, stream).
* @class AudioConfig
*/
export abstract class AudioConfig {
/**
* Creates an AudioConfig object representing the default microphone on the system.
* @member AudioConfig.fromDefaultMicrophoneInput
* @function
* @public
* @returns {AudioConfig} The audio input configuration being created.
*/
public static fromDefaultMicrophoneInput(): AudioConfig {
const pcmRecorder = new PcmRecorder();
return new AudioConfigImpl(new MicAudioSource(pcmRecorder));
}
/**
* Creates an AudioConfig object representing the specified file.
* @member AudioConfig.fromWavFileInput
* @function
* @public
* @param {File} fileName - Specifies the audio input file. Currently, only WAV / PCM with 16-bit
* samples, 16 kHz sample rate, and a single channel (Mono) is supported.
* @returns {AudioConfig} The audio input configuration being created.
*/
public static fromWavFileInput(file: File): AudioConfig {
return new AudioConfigImpl(new FileAudioSource(file));
}
/**
* Creates an AudioConfig object representing the specified stream.
* @member AudioConfig.fromStreamInput
* @function
* @public
* @param {AudioInputStream | PullAudioInputStreamCallback} audioStream - Specifies the custom audio input
* stream. Currently, only WAV / PCM with 16-bit samples, 16 kHz sample rate, and a single channel
* (Mono) is supported.
* @returns {AudioConfig} The audio input configuration being created.
*/
public static fromStreamInput(audioStream: AudioInputStream | PullAudioInputStreamCallback): AudioConfig {
if (audioStream instanceof PullAudioInputStreamCallback) {
return new AudioConfigImpl(new PullAudioInputStreamImpl(audioStream as PullAudioInputStreamCallback));
}
if (audioStream instanceof AudioInputStream) {
return new AudioConfigImpl(audioStream as PushAudioInputStreamImpl);
}
throw new Error("Not Supported Type");
}
/**
* Explicitly frees any external resource attached to the object
* @member AudioConfig.prototype.close
* @function
* @public
*/
public abstract close(): void;
}
/**
* Represents audio input stream used for custom audio input configurations.
* @private
* @class AudioConfigImpl
*/
// tslint:disable-next-line:max-classes-per-file
export class AudioConfigImpl extends AudioConfig implements IAudioSource {
private privSource: IAudioSource;
/**
* Creates and initializes an instance of this class.
* @constructor
* @param {IAudioSource} source - An audio source.
*/
public constructor(source: IAudioSource) {
super();
this.privSource = source;
}
/**
* Format information for the audio
*/
public get format(): AudioStreamFormat {
return this.privSource.format;
}
/**
* @member AudioConfigImpl.prototype.close
* @function
* @public
*/
public close(): void {
this.privSource.turnOff();
}
/**
* @member AudioConfigImpl.prototype.id
* @function
* @public
*/
public id(): string {
return this.privSource.id();
}
/**
* @member AudioConfigImpl.prototype.turnOn
* @function
* @public
* @returns {Promise<boolean>} A promise.
*/
public turnOn(): Promise<boolean> {
return this.privSource.turnOn();
}
/**
* @member AudioConfigImpl.prototype.attach
* @function
* @public
* @param {string} audioNodeId - The audio node id.
* @returns {Promise<IAudioStreamNode>} A promise.
*/
public attach(audioNodeId: string): Promise<IAudioStreamNode> {
return this.privSource.attach(audioNodeId);
}
/**
* @member AudioConfigImpl.prototype.detach
* @function
* @public
* @param {string} audioNodeId - The audio node id.
*/
public detach(audioNodeId: string): void {
return this.detach(audioNodeId);
}
/**
* @member AudioConfigImpl.prototype.turnOff
* @function
* @public
* @returns {Promise<boolean>} A promise.
*/
public turnOff(): Promise<boolean> {
return this.privSource.turnOff();
}
/**
* @member AudioConfigImpl.prototype.events
* @function
* @public
* @returns {EventSource<AudioSourceEvent>} An event source for audio events.
*/
public get events(): EventSource<AudioSourceEvent> {
return this.privSource.events;
}
}

Просмотреть файл

@ -0,0 +1,386 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { createNoDashGuid } from "../../../src/common/Guid";
import {
AudioSourceEvent,
AudioSourceInitializingEvent,
AudioSourceReadyEvent,
AudioStreamNodeAttachedEvent,
AudioStreamNodeAttachingEvent,
AudioStreamNodeDetachedEvent,
Events,
EventSource,
IAudioSource,
IAudioStreamNode,
IStreamChunk,
Promise,
PromiseHelper,
Stream,
StreamReader,
} from "../../common/Exports";
import { AudioStreamFormat, PullAudioInputStreamCallback } from "../Exports";
import { AudioStreamFormatImpl } from "./AudioStreamFormat";
const bufferSize: number = 4096;
/**
* Represents audio input stream used for custom audio input configurations.
* @class AudioInputStream
*/
export abstract class AudioInputStream {
/**
* Creates and initializes an instance.
* @constructor
*/
protected constructor() { }
/**
* Creates a memory backed PushAudioInputStream with the specified audio format.
* @member AudioInputStream.createPushStream
* @function
* @public
* @param {AudioStreamFormat} format - The audio data format in which audio will be
* written to the push audio stream's write() method (currently only support 16 kHz 16bit mono PCM).
* @returns {PushAudioInputStream} The audio input stream being created.
*/
public static createPushStream(format?: AudioStreamFormat): PushAudioInputStream {
return PushAudioInputStream.create(format);
}
/**
* Creates a PullAudioInputStream that delegates to the specified callback interface for read()
* and close() methods.
* @member AudioInputStream.createPullStream
* @function
* @public
* @param {PullAudioInputStreamCallback} callback - The custom audio input object, derived from
* PullAudioInputStreamCallback
* @param {AudioStreamFormat} format - The audio data format in which audio will be returned from
* the callback's read() method (currently only support 16 kHz 16bit mono PCM).
* @returns {PullAudioInputStream} The audio input stream being created.
*/
public static createPullStream(callback: PullAudioInputStreamCallback, format?: AudioStreamFormat): PullAudioInputStream {
return PullAudioInputStream.create(callback, format);
// throw new Error("Oops");
}
/**
* Explicitly frees any external resource attached to the object
* @member AudioInputStream.prototype.close
* @function
* @public
*/
public abstract close(): void;
}
/**
* Represents memory backed push audio input stream used for custom audio input configurations.
* @class PushAudioInputStream
*/
// tslint:disable-next-line:max-classes-per-file
export abstract class PushAudioInputStream extends AudioInputStream {
/**
* Creates a memory backed PushAudioInputStream with the specified audio format.
* @member PushAudioInputStream.create
* @function
* @public
* @param {AudioStreamFormat} format - The audio data format in which audio will be written to the
* push audio stream's write() method (currently only support 16 kHz 16bit mono PCM).
* @returns {PushAudioInputStream} The push audio input stream being created.
*/
public static create(format?: AudioStreamFormat): PushAudioInputStream {
return new PushAudioInputStreamImpl(format);
}
/**
* Writes the audio data specified by making an internal copy of the data.
* @member PushAudioInputStream.prototype.write
* @function
* @public
* @param {ArrayBuffer} dataBuffer - The audio buffer of which this function will make a copy.
*/
public abstract write(dataBuffer: ArrayBuffer): void;
/**
* Closes the stream.
* @member PushAudioInputStream.prototype.close
* @function
* @public
*/
public abstract close(): void;
}
/**
* Represents memory backed push audio input stream used for custom audio input configurations.
* @private
* @class PushAudioInputStreamImpl
*/
// tslint:disable-next-line:max-classes-per-file
export class PushAudioInputStreamImpl extends PushAudioInputStream implements IAudioSource {
private privFormat: AudioStreamFormat;
private privId: string;
private privEvents: EventSource<AudioSourceEvent>;
private privStream: Stream<ArrayBuffer> = new Stream<ArrayBuffer>();
/**
* Creates and initalizes an instance with the given values.
* @constructor
* @param {AudioStreamFormat} format - The audio stream format.
*/
public constructor(format?: AudioStreamFormat) {
super();
if (format === undefined) {
this.privFormat = AudioStreamFormatImpl.getDefaultInputFormat();
} else {
this.privFormat = format;
}
this.privEvents = new EventSource<AudioSourceEvent>();
this.privId = createNoDashGuid();
}
/**
* Format information for the audio
*/
public get format(): AudioStreamFormat {
return this.privFormat;
}
/**
* Writes the audio data specified by making an internal copy of the data.
* @member PushAudioInputStreamImpl.prototype.write
* @function
* @public
* @param {ArrayBuffer} dataBuffer - The audio buffer of which this function will make a copy.
*/
public write(dataBuffer: ArrayBuffer): void {
// Break the data up into smaller chunks if needed.
let i: number;
for (i = bufferSize - 1; i < dataBuffer.byteLength; i += bufferSize) {
this.privStream.write(dataBuffer.slice(i - (bufferSize - 1), i + 1));
}
if ((i - (bufferSize - 1)) !== dataBuffer.byteLength) {
this.privStream.write(dataBuffer.slice(i - (bufferSize - 1), dataBuffer.byteLength));
}
}
/**
* Closes the stream.
* @member PushAudioInputStreamImpl.prototype.close
* @function
* @public
*/
public close(): void {
this.privStream.close();
}
public id(): string {
return this.privId;
}
public turnOn(): Promise<boolean> {
this.onEvent(new AudioSourceInitializingEvent(this.privId)); // no stream id
this.onEvent(new AudioSourceReadyEvent(this.privId));
return PromiseHelper.fromResult(true);
}
public attach(audioNodeId: string): Promise<IAudioStreamNode> {
this.onEvent(new AudioStreamNodeAttachingEvent(this.privId, audioNodeId));
return this.turnOn()
.onSuccessContinueWith<StreamReader<ArrayBuffer>>((_: boolean) => {
// For now we support a single parallel reader of the pushed stream.
// So we can simiply hand the stream to the recognizer and let it recognize.
return this.privStream.getReader();
})
.onSuccessContinueWith((streamReader: StreamReader<ArrayBuffer>) => {
this.onEvent(new AudioStreamNodeAttachedEvent(this.privId, audioNodeId));
return {
detach: () => {
streamReader.close();
this.onEvent(new AudioStreamNodeDetachedEvent(this.privId, audioNodeId));
this.turnOff();
},
id: () => {
return audioNodeId;
},
read: () => {
return streamReader.read();
},
};
});
}
public detach(audioNodeId: string): void {
this.onEvent(new AudioStreamNodeDetachedEvent(this.privId, audioNodeId));
}
public turnOff(): Promise<boolean> {
return PromiseHelper.fromResult(false);
}
public get events(): EventSource<AudioSourceEvent> {
return this.privEvents;
}
private onEvent = (event: AudioSourceEvent): void => {
this.privEvents.onEvent(event);
Events.instance.onEvent(event);
}
}
/*
* Represents audio input stream used for custom audio input configurations.
* @class PullAudioInputStream
*/
// tslint:disable-next-line:max-classes-per-file
export abstract class PullAudioInputStream extends AudioInputStream {
/**
* Creates and initializes and instance.
* @constructor
*/
protected constructor() { super(); }
/**
* Creates a PullAudioInputStream that delegates to the specified callback interface for
* read() and close() methods, using the default format (16 kHz 16bit mono PCM).
* @member PullAudioInputStream.create
* @function
* @public
* @param {PullAudioInputStreamCallback} callback - The custom audio input object,
* derived from PullAudioInputStreamCustomCallback
* @param {AudioStreamFormat} format - The audio data format in which audio will be
* returned from the callback's read() method (currently only support 16 kHz 16bit mono PCM).
* @returns {PullAudioInputStream} The push audio input stream being created.
*/
public static create(callback: PullAudioInputStreamCallback, format?: AudioStreamFormat): PullAudioInputStream {
return new PullAudioInputStreamImpl(callback, format);
}
/**
* Explicitly frees any external resource attached to the object
* @member PullAudioInputStream.prototype.close
* @function
* @public
*/
public abstract close(): void;
}
/**
* Represents audio input stream used for custom audio input configurations.
* @private
* @class PullAudioInputStreamImpl
*/
// tslint:disable-next-line:max-classes-per-file
export class PullAudioInputStreamImpl extends PullAudioInputStream implements IAudioSource {
private privCallback: PullAudioInputStreamCallback;
private privFormat: AudioStreamFormat;
private privId: string;
private privEvents: EventSource<AudioSourceEvent>;
private privIsClosed: boolean;
/**
* Creates a PullAudioInputStream that delegates to the specified callback interface for
* read() and close() methods, using the default format (16 kHz 16bit mono PCM).
* @constructor
* @param {PullAudioInputStreamCallback} callback - The custom audio input object,
* derived from PullAudioInputStreamCustomCallback
* @param {AudioStreamFormat} format - The audio data format in which audio will be
* returned from the callback's read() method (currently only support 16 kHz 16bit mono PCM).
*/
public constructor(callback: PullAudioInputStreamCallback, format?: AudioStreamFormat) {
super();
if (undefined === format) {
this.privFormat = AudioStreamFormat.getDefaultInputFormat();
} else {
this.privFormat = format;
}
this.privEvents = new EventSource<AudioSourceEvent>();
this.privId = createNoDashGuid();
this.privCallback = callback;
this.privIsClosed = false;
}
/**
* Format information for the audio
*/
public get format(): AudioStreamFormat {
return this.privFormat;
}
/**
* Closes the stream.
* @member PullAudioInputStreamImpl.prototype.close
* @function
* @public
*/
public close(): void {
this.privIsClosed = true;
this.privCallback.close();
}
public id(): string {
return this.privId;
}
public turnOn(): Promise<boolean> {
this.onEvent(new AudioSourceInitializingEvent(this.privId)); // no stream id
this.onEvent(new AudioSourceReadyEvent(this.privId));
return PromiseHelper.fromResult(true);
}
public attach(audioNodeId: string): Promise<IAudioStreamNode> {
this.onEvent(new AudioStreamNodeAttachingEvent(this.privId, audioNodeId));
return this.turnOn()
.onSuccessContinueWith((result: boolean) => {
this.onEvent(new AudioStreamNodeAttachedEvent(this.privId, audioNodeId));
return {
detach: () => {
this.privCallback.close();
this.onEvent(new AudioStreamNodeDetachedEvent(this.privId, audioNodeId));
this.turnOff();
},
id: () => {
return audioNodeId;
},
read: (): Promise<IStreamChunk<ArrayBuffer>> => {
const readBuff: ArrayBuffer = new ArrayBuffer(bufferSize);
const pulledBytes: number = this.privCallback.read(readBuff);
return PromiseHelper.fromResult<IStreamChunk<ArrayBuffer>>({
buffer: readBuff.slice(0, pulledBytes),
isEnd: this.privIsClosed,
});
},
};
});
}
public detach(audioNodeId: string): void {
this.onEvent(new AudioStreamNodeDetachedEvent(this.privId, audioNodeId));
}
public turnOff(): Promise<boolean> {
return PromiseHelper.fromResult(false);
}
public get events(): EventSource<AudioSourceEvent> {
return this.privEvents;
}
private onEvent = (event: AudioSourceEvent): void => {
this.privEvents.onEvent(event);
Events.instance.onEvent(event);
}
}

Просмотреть файл

@ -0,0 +1,134 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
/**
* Represents audio stream format used for custom audio input configurations.
* @class AudioStreamFormat
*/
export abstract class AudioStreamFormat {
/**
* Creates an audio stream format object representing the default audio stream
* format (16KHz 16bit mono PCM).
* @member AudioStreamFormat.getDefaultInputFormat
* @function
* @public
* @returns {AudioStreamFormat} The audio stream format being created.
*/
public static getDefaultInputFormat(): AudioStreamFormat {
return AudioStreamFormatImpl.getDefaultInputFormat();
}
/**
* Creates an audio stream format object with the specified pcm waveformat characteristics.
* @member AudioStreamFormat.getWaveFormatPCM
* @function
* @public
* @param {number} samplesPerSecond - Sample rate, in samples per second (Hertz).
* @param {number} bitsPerSample - Bits per sample, typically 16.
* @param {number} channels - Number of channels in the waveform-audio data. Monaural data
* uses one channel and stereo data uses two channels.
* @returns {AudioStreamFormat} The audio stream format being created.
*/
public static getWaveFormatPCM(samplesPerSecond: number, bitsPerSample: number, channels: number): AudioStreamFormat {
return new AudioStreamFormatImpl(samplesPerSecond, bitsPerSample, channels);
}
/**
* Explicitly frees any external resource attached to the object
* @member AudioStreamFormat.prototype.close
* @function
* @public
*/
public abstract close(): void;
}
/**
* @private
* @class AudioStreamFormatImpl
*/
// tslint:disable-next-line:max-classes-per-file
export class AudioStreamFormatImpl extends AudioStreamFormat {
/**
* Creates an instance with the given values.
* @constructor
* @param {number} samplesPerSec - Samples per second.
* @param {number} bitsPerSample - Bits per sample.
* @param {number} channels - Number of channels.
*/
public constructor(samplesPerSec: number = 16000, bitsPerSample: number = 16, channels: number = 1) {
super();
this.formatTag = 1;
this.bitsPerSample = bitsPerSample;
this.samplesPerSec = samplesPerSec;
this.channels = channels;
this.avgBytesPerSec = this.samplesPerSec * this.channels * (this.bitsPerSample / 8);
this.blockAlign = this.channels * Math.max(this.bitsPerSample, 8);
}
/**
* Retrieves the default input format.
* @member AudioStreamFormatImpl.getDefaultInputFormat
* @function
* @public
* @returns {AudioStreamFormatImpl} The default input format.
*/
public static getDefaultInputFormat(): AudioStreamFormatImpl {
return new AudioStreamFormatImpl();
}
/**
* Closes the configuration object.
* @member AudioStreamFormatImpl.prototype.close
* @function
* @public
*/
public close(): void { return; }
/**
* The format of the audio, valid values: 1 (PCM)
* @member AudioStreamFormatImpl.prototype.formatTag
* @function
* @public
*/
public formatTag: number;
/**
* The number of channels, valid values: 1 (Mono).
* @member AudioStreamFormatImpl.prototype.channels
* @function
* @public
*/
public channels: number;
/**
* The sample rate, valid values: 16000.
* @member AudioStreamFormatImpl.prototype.samplesPerSec
* @function
* @public
*/
public samplesPerSec: number;
/**
* The bits per sample, valid values: 16
* @member AudioStreamFormatImpl.prototype.b
* @function
* @public
*/
public bitsPerSample: number;
/**
* Average bytes per second, usually calculated as nSamplesPerSec * nChannels * ceil(wBitsPerSample, 8).
* @member AudioStreamFormatImpl.prototype.avgBytesPerSec
* @function
* @public
*/
public avgBytesPerSec: number;
/**
* The size of a single frame, valid values: nChannels * ceil(wBitsPerSample, 8).
* @member AudioStreamFormatImpl.prototype.blockAlign
* @function
* @public
*/
public blockAlign: number;
}

Просмотреть файл

@ -0,0 +1,28 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
/**
* An abstract base class that defines callback methods (read() and close()) for
* custom audio input streams).
* @class PullAudioInputStreamCallback
*/
export abstract class PullAudioInputStreamCallback {
/**
* Reads data from audio input stream into the data buffer. The maximal number of bytes
* to be read is determined by the size of dataBuffer.
* @member PullAudioInputStreamCallback.prototype.read
* @function
* @public
* @param {ArrayBuffer} dataBuffer - The byte array to store the read data.
* @returns {number} the number of bytes have been read.
*/
public abstract read(dataBuffer: ArrayBuffer): number;
/**
* Closes the audio input stream.
* @member PullAudioInputStreamCallback.prototype.close
* @function
* @public
*/
public abstract close(): void;
}

Просмотреть файл

@ -0,0 +1,84 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { CancellationErrorCodePropertyName, EnumTranslation, SimpleSpeechPhrase } from "../common.speech/Exports";
import { CancellationErrorCode, CancellationReason, RecognitionResult } from "./Exports";
/**
* Contains detailed information about why a result was canceled.
* @class CancellationDetails
*/
export class CancellationDetails {
private privReason: CancellationReason;
private privErrorDetails: string;
private privErrorCode: CancellationErrorCode;
/**
* Creates and initializes an instance of this class.
* @constructor
* @param {CancellationReason} reason - The cancellation reason.
* @param {string} errorDetails - The error details, if provided.
*/
private constructor(reason: CancellationReason, errorDetails: string, errorCode: CancellationErrorCode) {
this.privReason = reason;
this.privErrorDetails = errorDetails;
this.privErrorCode = errorCode;
}
/**
* Creates an instance of CancellationDetails object for the canceled RecognitionResult.
* @member CancellationDetails.fromResult
* @function
* @public
* @param {RecognitionResult} result - The result that was canceled.
* @returns {CancellationDetails} The cancellation details object being created.
*/
public static fromResult(result: RecognitionResult): CancellationDetails {
let reason = CancellationReason.Error;
let errorCode: CancellationErrorCode = CancellationErrorCode.NoError;
if (!!result.json) {
const simpleSpeech: SimpleSpeechPhrase = SimpleSpeechPhrase.fromJSON(result.json);
reason = EnumTranslation.implTranslateCancelResult(simpleSpeech.RecognitionStatus);
}
if (!!result.properties) {
errorCode = (CancellationErrorCode as any)[result.properties.getProperty(CancellationErrorCodePropertyName, CancellationErrorCode[CancellationErrorCode.NoError])];
}
return new CancellationDetails(reason, result.errorDetails, errorCode);
}
/**
* The reason the recognition was canceled.
* @member CancellationDetails.prototype.reason
* @function
* @public
* @returns {CancellationReason} Specifies the reason canceled.
*/
public get reason(): CancellationReason {
return this.privReason;
}
/**
* In case of an unsuccessful recognition, provides details of the occurred error.
* @member CancellationDetails.prototype.errorDetails
* @function
* @public
* @returns {string} A String that represents the error details.
*/
public get errorDetails(): string {
return this.privErrorDetails;
}
/**
* The error code in case of an unsuccessful recognition.
* Added in version 1.1.0.
* @return An error code that represents the error reason.
*/
public get ErrorCode(): CancellationErrorCode {
return this.privErrorCode;
}
}

Просмотреть файл

@ -0,0 +1,49 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
/**
* Defines error code in case that CancellationReason is Error.
* Added in version 1.1.0.
*/
export enum CancellationErrorCode {
/**
* Indicates that no error occurred during speech recognition.
*/
NoError,
/**
* Indicates an authentication error.
*/
AuthenticationFailure,
/**
* Indicates that one or more recognition parameters are invalid.
*/
BadRequestParameters,
/**
* Indicates that the number of parallel requests exceeded the number of allowed
* concurrent transcriptions for the subscription.
*/
TooManyRequests,
/**
* Indicates a connection error.
*/
ConnectionFailure,
/**
* Indicates a time-out error when waiting for response from service.
*/
ServiceTimeout,
/**
* Indicates that an error is returned by the service.
*/
ServiceError,
/**
* Indicates an unexpected runtime error.
*/
RuntimeError,
}

Просмотреть файл

@ -0,0 +1,20 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
/**
* Defines the possible reasons a recognition result might be canceled.
* @class CancellationReason
*/
export enum CancellationReason {
/**
* Indicates that an error occurred during speech recognition.
* @member CancellationReason.Error
*/
Error,
/**
* Indicates that the end of the audio stream was reached.
* @member CancellationReason.EndOfStream
*/
EndOfStream,
}

52
src/sdk/Contracts.ts Normal file
Просмотреть файл

@ -0,0 +1,52 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
/**
* @class Contracts
* @private
*/
export class Contracts {
public static throwIfNullOrUndefined(param: any, name: string): void {
if (param === undefined || param === null) {
throw new Error("throwIfNullOrUndefined:" + name);
}
}
public static throwIfNull(param: any, name: string): void {
if (param === null) {
throw new Error("throwIfNull:" + name);
}
}
public static throwIfNullOrWhitespace(param: string, name: string): void {
Contracts.throwIfNullOrUndefined(param, name);
if (("" + param).trim().length < 1) {
throw new Error("throwIfNullOrWhitespace:" + name);
}
}
public static throwIfDisposed(isDisposed: boolean): void {
if (isDisposed) {
throw new Error("the object is already disposed");
}
}
public static throwIfArrayEmptyOrWhitespace(array: string[], name: string): void {
Contracts.throwIfNullOrUndefined(array, name);
if (array.length === 0) {
throw new Error("throwIfArrayEmptyOrWhitespace:" + name);
}
for (const item of array) {
Contracts.throwIfNullOrWhitespace(item, name);
}
}
public static throwIfFileDoesNotExist(param: any, name: string): void {
Contracts.throwIfNullOrWhitespace(param, name);
// TODO check for file existence.
}
}

39
src/sdk/Exports.ts Normal file
Просмотреть файл

@ -0,0 +1,39 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
export { AudioConfig } from "./Audio/AudioConfig";
export { AudioStreamFormat } from "./Audio/AudioStreamFormat";
export { AudioInputStream, PullAudioInputStream, PushAudioInputStream } from "./Audio/AudioInputStream";
export { CancellationReason } from "./CancellationReason";
export { PullAudioInputStreamCallback } from "./Audio/PullAudioInputStreamCallback";
export { KeywordRecognitionModel } from "./KeywordRecognitionModel";
export { SessionEventArgs } from "./SessionEventArgs";
export { RecognitionEventArgs } from "./RecognitionEventArgs";
export { OutputFormat } from "./OutputFormat";
export { IntentRecognitionEventArgs } from "./IntentRecognitionEventArgs";
export { RecognitionResult } from "./RecognitionResult";
export { SpeechRecognitionResult } from "./SpeechRecognitionResult";
export { IntentRecognitionResult } from "./IntentRecognitionResult";
export { LanguageUnderstandingModel } from "./LanguageUnderstandingModel";
export { SpeechRecognitionEventArgs } from "./SpeechRecognitionEventArgs";
export { SpeechRecognitionCanceledEventArgs } from "./SpeechRecognitionCanceledEventArgs";
export { TranslationRecognitionEventArgs } from "./TranslationRecognitionEventArgs";
export { TranslationSynthesisEventArgs } from "./TranslationSynthesisEventArgs";
export { TranslationRecognitionResult } from "./TranslationRecognitionResult";
export { TranslationSynthesisResult } from "./TranslationSynthesisResult";
export { ResultReason } from "./ResultReason";
export { SpeechConfig } from "./SpeechConfig";
export { SpeechTranslationConfig } from "./SpeechTranslationConfig";
export { PropertyCollection } from "./PropertyCollection";
export { PropertyId } from "./PropertyId";
export { Recognizer } from "./Recognizer";
export { SpeechRecognizer } from "./SpeechRecognizer";
export { IntentRecognizer } from "./IntentRecognizer";
export { TranslationRecognizer } from "./TranslationRecognizer";
export { Translations } from "./Translations";
export { NoMatchReason } from "./NoMatchReason";
export { NoMatchDetails } from "./NoMatchDetails";
export { TranslationRecognitionCanceledEventArgs } from "./TranslationRecognitionCanceledEventArgs";
export { IntentRecognitionCanceledEventArgs } from "./IntentRecognitionCanceledEventArgs";
export { CancellationDetails } from "./CancellationDetails";
export { CancellationErrorCode } from "./CancellationErrorCodes";

Просмотреть файл

@ -0,0 +1,66 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { CancellationErrorCode, CancellationReason, IntentRecognitionEventArgs, IntentRecognitionResult } from "./Exports";
/**
* Define payload of intent recognition canceled result events.
* @class IntentRecognitionCanceledEventArgs
*/
export class IntentRecognitionCanceledEventArgs extends IntentRecognitionEventArgs {
private privReason: CancellationReason;
private privErrorDetails: string;
private privErrorCode: CancellationErrorCode;
/**
* Creates and initializes an instance of this class.
* @constructor
* @param {CancellationReason} result - The result of the intent recognition.
* @param {string} offset - The offset.
* @param {IntentRecognitionResult} sessionId - The session id.
*/
public constructor(
reason: CancellationReason,
errorDetails: string,
errorCode: CancellationErrorCode,
result?: IntentRecognitionResult,
offset?: number,
sessionId?: string) {
super(result, offset, sessionId);
this.privReason = reason;
this.privErrorDetails = errorDetails;
this.privErrorCode = errorCode;
}
/**
* The reason the recognition was canceled.
* @member IntentRecognitionCanceledEventArgs.prototype.reason
* @function
* @public
* @returns {CancellationReason} Specifies the reason canceled.
*/
public get reason(): CancellationReason {
return this.privReason;
}
/**
* The error code in case of an unsuccessful recognition.
* Added in version 1.1.0.
* @return An error code that represents the error reason.
*/
public get errorCode(): CancellationErrorCode {
return this.privErrorCode;
}
/**
* In case of an unsuccessful recognition, provides details of the occurred error.
* @member IntentRecognitionCanceledEventArgs.prototype.errorDetails
* @function
* @public
* @returns {string} A String that represents the error details.
*/
public get errorDetails(): string {
return this.privErrorDetails;
}
}

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше