* Create more idiomatic RestConnection class and methods

* Fix lint for new classes

* Initial implementation of create profile

* Fix broken conversation translator test

* Fixes to return actual VoiceProfile object

* Build fixes

* Clean up callback code

* Initial version of voice enrollment

* Add delete profile to client, add rest connection class in case needed for speaker recognizer.

* Add blob promise to IAudioSource interface in place of file

* Add reset profile method

* Initial implementation of verifySpeaker and identifySpeaker

* Fix create model bug, rename public methods to align with SDK conventions

* SpeakerRecognition recognizeOnceAsync() works now for Identification models/profiles

* speaker verify now working

* Add CancellationDetail classes for SR results

* Handle service errors and result property setting

* Better error handling for VoiceProfile, VoiceProfileResult callbacks

* Bug fixes, misspelled detail fields

* Fix error creation for Enrollments

* Add tests for speaker recognition

* Generate VoiceProfileResult errors using response statusText

* Remove currently unused RestConnection class

* Use "reason" getter for result consistency

* Use PromiseHelper for instant promise fulfillment

* add xmlhttprequest dependency for node

* Node support for XMLHttpRequest primitive

* Allow CancellationDetails to be seen externally

* Allow PushAudioInputStream to be read as Blob for REST connections

* Address code review issues, simplify callback handling in VoiceProfileClient, lengthen rest timeout

* More code review corrections

* Fix weird node promise issue, clean up promise code

* Another fix from input stream

* Use Buffer when sending AudioPushStream with node.js

* Add china host detection, use Buffer.from instead of deprecated Buffer constructor

* Use PromiseHelper to send error promise for unimplemented blob getter

* Add new static test variables for Speaker ID tests
This commit is contained in:
glharper 2020-06-17 08:32:38 -07:00 коммит произвёл GitHub
Родитель ac9eafbfb4
Коммит db402ca8ca
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
46 изменённых файлов: 2023 добавлений и 775 удалений

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

@ -55,7 +55,9 @@ jobs:
LuisRegion:westus ^
SpeechTestEndpointId:ec3432b2-8584-4736-865a-556213b9f6fd ^
BotSubscription:$(BotSubscriptionKeyJS) ^
BotRegion:$(BotRegionJS)
BotRegion:$(BotRegionJS) ^
SpeakerIDSubscriptionKey:$(SpeakerRecognition-WestUS-Key) ^
SpeakerIDRegion:westus
displayName: Run tests

599
package-lock.json сгенерированный
Просмотреть файл

@ -1657,9 +1657,9 @@
}
},
"@types/jest": {
"version": "25.2.2",
"resolved": "https://registry.npmjs.org/@types/jest/-/jest-25.2.2.tgz",
"integrity": "sha512-aRctFbG8Pb7DSLzUt/fEtL3q/GKb9mretFuYhRub2J0q6NhzBYbx9HTQzHrWgBNIxYOlxGNVe6Z54cpbUt+Few==",
"version": "25.2.3",
"resolved": "https://registry.npmjs.org/@types/jest/-/jest-25.2.3.tgz",
"integrity": "sha512-JXc1nK/tXHiDhV55dvfzqtmP4S3sy3T3ouV2tkViZgxY/zeUkcpQcQPGRlgF4KmWzWW5oiWYSZwtCB+2RsE4Fw==",
"dev": true,
"requires": {
"jest-diff": "^25.2.1",
@ -1679,9 +1679,9 @@
}
},
"@types/yargs": {
"version": "15.0.4",
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.4.tgz",
"integrity": "sha512-9T1auFmbPZoxHz0enUFlUuKRy3it01R+hlggyVUMtnCTQRunsQYifnSGb8hET4Xo8yiC0o0r1paW3ud5+rbURg==",
"version": "15.0.5",
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.5.tgz",
"integrity": "sha512-Dk/IDOPtOgubt/IaevIUbTgV7doaKkoorvOyYM2CMwuDyP89bekI7H4xLIwunNYiK9jhCkmc6pUrJk3cj2AB9w==",
"dev": true,
"requires": {
"@types/yargs-parser": "*"
@ -2685,6 +2685,16 @@
"integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==",
"dev": true
},
"bindings": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
"integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
"dev": true,
"optional": true,
"requires": {
"file-uri-to-path": "1.0.0"
}
},
"bluebird": {
"version": "3.7.2",
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
@ -4278,6 +4288,13 @@
"integrity": "sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw==",
"dev": true
},
"file-uri-to-path": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
"integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
"dev": true,
"optional": true
},
"fill-range": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz",
@ -4443,551 +4460,14 @@
"dev": true
},
"fsevents": {
"version": "1.2.9",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.9.tgz",
"integrity": "sha512-oeyj2H3EjjonWcFjD5NvZNE9Rqe4UW+nQBU2HNeKw0koVLEFIhtyETyAakeAM3de7Z/SW5kcA+fZUait9EApnw==",
"version": "1.2.13",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz",
"integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==",
"dev": true,
"optional": true,
"requires": {
"nan": "^2.12.1",
"node-pre-gyp": "^0.12.0"
},
"dependencies": {
"abbrev": {
"version": "1.1.1",
"bundled": true,
"dev": true,
"optional": true
},
"ansi-regex": {
"version": "2.1.1",
"bundled": true,
"dev": true,
"optional": true
},
"aproba": {
"version": "1.2.0",
"bundled": true,
"dev": true,
"optional": true
},
"are-we-there-yet": {
"version": "1.1.5",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"delegates": "^1.0.0",
"readable-stream": "^2.0.6"
}
},
"balanced-match": {
"version": "1.0.0",
"bundled": true,
"dev": true,
"optional": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"chownr": {
"version": "1.1.1",
"bundled": true,
"dev": true,
"optional": true
},
"code-point-at": {
"version": "1.1.0",
"bundled": true,
"dev": true,
"optional": true
},
"concat-map": {
"version": "0.0.1",
"bundled": true,
"dev": true,
"optional": true
},
"console-control-strings": {
"version": "1.1.0",
"bundled": true,
"dev": true,
"optional": true
},
"core-util-is": {
"version": "1.0.2",
"bundled": true,
"dev": true,
"optional": true
},
"debug": {
"version": "4.1.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"ms": "^2.1.1"
}
},
"deep-extend": {
"version": "0.6.0",
"bundled": true,
"dev": true,
"optional": true
},
"delegates": {
"version": "1.0.0",
"bundled": true,
"dev": true,
"optional": true
},
"detect-libc": {
"version": "1.0.3",
"bundled": true,
"dev": true,
"optional": true
},
"fs-minipass": {
"version": "1.2.5",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"minipass": "^2.2.1"
}
},
"fs.realpath": {
"version": "1.0.0",
"bundled": true,
"dev": true,
"optional": true
},
"gauge": {
"version": "2.7.4",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"aproba": "^1.0.3",
"console-control-strings": "^1.0.0",
"has-unicode": "^2.0.0",
"object-assign": "^4.1.0",
"signal-exit": "^3.0.0",
"string-width": "^1.0.1",
"strip-ansi": "^3.0.1",
"wide-align": "^1.1.0"
}
},
"glob": {
"version": "7.1.3",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.0.4",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
}
},
"has-unicode": {
"version": "2.0.1",
"bundled": true,
"dev": true,
"optional": true
},
"iconv-lite": {
"version": "0.4.24",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"safer-buffer": ">= 2.1.2 < 3"
}
},
"ignore-walk": {
"version": "3.0.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"minimatch": "^3.0.4"
}
},
"inflight": {
"version": "1.0.6",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"once": "^1.3.0",
"wrappy": "1"
}
},
"inherits": {
"version": "2.0.3",
"bundled": true,
"dev": true,
"optional": true
},
"ini": {
"version": "1.3.5",
"bundled": true,
"dev": true,
"optional": true
},
"is-fullwidth-code-point": {
"version": "1.0.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
},
"isarray": {
"version": "1.0.0",
"bundled": true,
"dev": true,
"optional": true
},
"minimatch": {
"version": "3.0.4",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
},
"minimist": {
"version": "0.0.8",
"bundled": true,
"dev": true,
"optional": true
},
"minipass": {
"version": "2.3.5",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"safe-buffer": "^5.1.2",
"yallist": "^3.0.0"
}
},
"minizlib": {
"version": "1.2.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"minipass": "^2.2.1"
}
},
"mkdirp": {
"version": "0.5.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"minimist": "0.0.8"
}
},
"ms": {
"version": "2.1.1",
"bundled": true,
"dev": true,
"optional": true
},
"needle": {
"version": "2.3.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"debug": "^4.1.0",
"iconv-lite": "^0.4.4",
"sax": "^1.2.4"
}
},
"node-pre-gyp": {
"version": "0.12.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"detect-libc": "^1.0.2",
"mkdirp": "^0.5.1",
"needle": "^2.2.1",
"nopt": "^4.0.1",
"npm-packlist": "^1.1.6",
"npmlog": "^4.0.2",
"rc": "^1.2.7",
"rimraf": "^2.6.1",
"semver": "^5.3.0",
"tar": "^4"
}
},
"nopt": {
"version": "4.0.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"abbrev": "1",
"osenv": "^0.1.4"
}
},
"npm-bundled": {
"version": "1.0.6",
"bundled": true,
"dev": true,
"optional": true
},
"npm-packlist": {
"version": "1.4.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"ignore-walk": "^3.0.1",
"npm-bundled": "^1.0.1"
}
},
"npmlog": {
"version": "4.1.2",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"are-we-there-yet": "~1.1.2",
"console-control-strings": "~1.1.0",
"gauge": "~2.7.3",
"set-blocking": "~2.0.0"
}
},
"number-is-nan": {
"version": "1.0.1",
"bundled": true,
"dev": true,
"optional": true
},
"object-assign": {
"version": "4.1.1",
"bundled": true,
"dev": true,
"optional": true
},
"once": {
"version": "1.4.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"wrappy": "1"
}
},
"os-homedir": {
"version": "1.0.2",
"bundled": true,
"dev": true,
"optional": true
},
"os-tmpdir": {
"version": "1.0.2",
"bundled": true,
"dev": true,
"optional": true
},
"osenv": {
"version": "0.1.5",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"os-homedir": "^1.0.0",
"os-tmpdir": "^1.0.0"
}
},
"path-is-absolute": {
"version": "1.0.1",
"bundled": true,
"dev": true,
"optional": true
},
"process-nextick-args": {
"version": "2.0.0",
"bundled": true,
"dev": true,
"optional": true
},
"rc": {
"version": "1.2.8",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"deep-extend": "^0.6.0",
"ini": "~1.3.0",
"minimist": "^1.2.0",
"strip-json-comments": "~2.0.1"
},
"dependencies": {
"minimist": {
"version": "1.2.0",
"bundled": true,
"dev": true,
"optional": true
}
}
},
"readable-stream": {
"version": "2.3.6",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
"isarray": "~1.0.0",
"process-nextick-args": "~2.0.0",
"safe-buffer": "~5.1.1",
"string_decoder": "~1.1.1",
"util-deprecate": "~1.0.1"
}
},
"rimraf": {
"version": "2.6.3",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"glob": "^7.1.3"
}
},
"safe-buffer": {
"version": "5.1.2",
"bundled": true,
"dev": true,
"optional": true
},
"safer-buffer": {
"version": "2.1.2",
"bundled": true,
"dev": true,
"optional": true
},
"sax": {
"version": "1.2.4",
"bundled": true,
"dev": true,
"optional": true
},
"semver": {
"version": "5.7.0",
"bundled": true,
"dev": true,
"optional": true
},
"set-blocking": {
"version": "2.0.0",
"bundled": true,
"dev": true,
"optional": true
},
"signal-exit": {
"version": "3.0.2",
"bundled": true,
"dev": true,
"optional": true
},
"string-width": {
"version": "1.0.2",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
"strip-ansi": "^3.0.0"
}
},
"string_decoder": {
"version": "1.1.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"safe-buffer": "~5.1.0"
}
},
"strip-ansi": {
"version": "3.0.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
},
"strip-json-comments": {
"version": "2.0.1",
"bundled": true,
"dev": true,
"optional": true
},
"tar": {
"version": "4.4.8",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"chownr": "^1.1.1",
"fs-minipass": "^1.2.5",
"minipass": "^2.3.4",
"minizlib": "^1.1.1",
"mkdirp": "^0.5.0",
"safe-buffer": "^5.1.2",
"yallist": "^3.0.2"
}
},
"util-deprecate": {
"version": "1.0.2",
"bundled": true,
"dev": true,
"optional": true
},
"wide-align": {
"version": "1.1.3",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"string-width": "^1.0.2 || 2"
}
},
"wrappy": {
"version": "1.0.2",
"bundled": true,
"dev": true,
"optional": true
},
"yallist": {
"version": "3.0.3",
"bundled": true,
"dev": true,
"optional": true
}
"bindings": "^1.5.0",
"nan": "^2.12.1"
}
},
"function-bind": {
@ -6705,9 +6185,9 @@
}
},
"@types/yargs": {
"version": "15.0.4",
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.4.tgz",
"integrity": "sha512-9T1auFmbPZoxHz0enUFlUuKRy3it01R+hlggyVUMtnCTQRunsQYifnSGb8hET4Xo8yiC0o0r1paW3ud5+rbURg==",
"version": "15.0.5",
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.5.tgz",
"integrity": "sha512-Dk/IDOPtOgubt/IaevIUbTgV7doaKkoorvOyYM2CMwuDyP89bekI7H4xLIwunNYiK9jhCkmc6pUrJk3cj2AB9w==",
"dev": true,
"requires": {
"@types/yargs-parser": "*"
@ -9707,9 +9187,9 @@
"dev": true
},
"nan": {
"version": "2.14.0",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz",
"integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==",
"version": "2.14.1",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.14.1.tgz",
"integrity": "sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==",
"dev": true,
"optional": true
},
@ -11842,8 +11322,7 @@
"tslib": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz",
"integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==",
"dev": true
"integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ=="
},
"tslint": {
"version": "5.20.1",
@ -12509,6 +11988,14 @@
"integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
"dev": true
},
"xmlhttprequest-ts": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/xmlhttprequest-ts/-/xmlhttprequest-ts-1.0.1.tgz",
"integrity": "sha1-ezy0oZeu44zy1PndYYnR0h8INbI=",
"requires": {
"tslib": "^1.9.2"
}
},
"xtend": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",

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

@ -34,7 +34,8 @@
"https-proxy-agent": false,
"simple-lru-cache": false,
"ws": false,
"fs": false
"fs": false,
"xmlhttprequest-ts": false
},
"main": "distrib/lib/microsoft.cognitiveservices.speech.sdk.js",
"module": "distrib/es2015/microsoft.cognitiveservices.speech.sdk.js",
@ -93,7 +94,8 @@
"asn1.js-rfc5280": "^3.0.0",
"https-proxy-agent": "^3.0.1",
"simple-lru-cache": "0.0.2",
"ws": "^7.2.0"
"ws": "^7.2.0",
"xmlhttprequest-ts": "^1.0.1"
},
"resolutions": {
"extend": "3.0.2"

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

@ -10,3 +10,5 @@ export * from "./WebsocketConnection";
export * from "./WebsocketMessageAdapter";
export * from "./ReplayableAudioNode";
export * from "./ProxyInfo";
export * from "./RestMessageAdapter";
export * from "./RestConfigBase";

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

@ -56,6 +56,10 @@ export class FileAudioSource implements IAudioSource {
return this.privAudioFormatPromise;
}
public get blob(): Promise<Blob | Buffer> {
return PromiseHelper.fromResult(this.privFile);
}
public turnOn = (): Promise<boolean> => {
if (typeof FileReader === "undefined") {
const errorMsg = "Browser does not support FileReader.";

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

@ -78,6 +78,10 @@ export class MicAudioSource implements IAudioSource {
return PromiseHelper.fromResult(MicAudioSource.AUDIOFORMAT);
}
public get blob(): Promise<Blob> {
return PromiseHelper.fromError("Not implemented for Mic input");
}
public turnOn = (): Promise<boolean> => {
if (this.privInitializeDeferral) {
return this.privInitializeDeferral.promise();

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

@ -0,0 +1,83 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { IErrorMessages } from "../common/Exports";
/**
* HTTP request helper
*/
export interface IRequestOptions {
headers?: {[key: string]: string};
ignoreCache?: boolean;
timeout?: number;
}
export interface IRestParams {
apiVersion: string;
authorization: string;
clientAppId: string;
contentTypeKey: string;
correlationId: string;
languageCode: string;
nickname: string;
profanity: string;
requestId: string;
roomId: string;
sessionToken: string;
subscriptionKey: string;
subscriptionRegion: string;
token: string;
}
export class RestConfigBase {
public static get requestOptions(): IRequestOptions {
return RestConfigBase.privDefaultRequestOptions;
}
public static get configParams(): IRestParams {
return RestConfigBase.privDefaultParams;
}
public static get restErrors(): IErrorMessages {
return RestConfigBase.privRestErrors;
}
private static readonly privDefaultRequestOptions: IRequestOptions = {
headers: {
Accept: "application/json",
},
ignoreCache: false,
timeout: 10000,
};
private static readonly privRestErrors: IErrorMessages = {
authInvalidSubscriptionKey: "You must specify either an authentication token to use, or a Cognitive Speech subscription key.",
authInvalidSubscriptionRegion: "You must specify the Cognitive Speech region to use.",
invalidArgs: "Required input not found: {arg}.",
invalidCreateJoinConversationResponse: "Creating/Joining conversation failed with HTTP {status}.",
invalidParticipantRequest: "The requested participant was not found.",
permissionDeniedConnect: "Required credentials not found.",
permissionDeniedConversation: "Invalid operation: only the host can {command} the conversation.",
permissionDeniedParticipant: "Invalid operation: only the host can {command} a participant.",
permissionDeniedSend: "Invalid operation: the conversation is not in a connected state.",
permissionDeniedStart: "Invalid operation: there is already an active conversation.",
};
private static readonly privDefaultParams: IRestParams = {
apiVersion: "api-version",
authorization: "Authorization",
clientAppId: "X-ClientAppId",
contentTypeKey: "Content-Type",
correlationId: "X-CorrelationId",
languageCode: "language",
nickname: "nickname",
profanity: "profanity",
requestId: "X-RequestId",
roomId: "roomid",
sessionToken: "token",
subscriptionKey: "Ocp-Apim-Subscription-Key",
subscriptionRegion: "Ocp-Apim-Subscription-Region",
token: "X-CapitoToken",
};
}

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

@ -0,0 +1,141 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import {
ArgumentNullError,
Deferred,
Promise,
} from "../common/Exports";
import { IRequestOptions } from "./Exports";
// Node.JS specific xmlhttprequest / browser support.
import * as XHR from "xmlhttprequest-ts";
export enum RestRequestType {
Get = "get",
Post = "post",
Delete = "delete",
File = "file",
}
export interface IRestResponse {
ok: boolean;
status: number;
statusText: string;
data: string;
json: <T>() => T;
headers: string;
}
// accept rest operations via request method and return abstracted objects from server response
export class RestMessageAdapter {
private privTimeout: number;
private privIgnoreCache: boolean;
private privHeaders: { [key: string]: string; };
public constructor(
configParams: IRequestOptions,
connectionId?: string
) {
if (!configParams) {
throw new ArgumentNullError("configParams");
}
this.privHeaders = configParams.headers;
this.privTimeout = configParams.timeout;
this.privIgnoreCache = configParams.ignoreCache;
}
public setHeaders(key: string, value: string ): void {
this.privHeaders[key] = value;
}
public request(
method: RestRequestType,
uri: string,
queryParams: any = {},
body: any = null,
binaryBody: Blob | Buffer = null,
): Promise<IRestResponse> {
const responseReceivedDeferral = new Deferred<IRestResponse>();
let xhr: XMLHttpRequest | XHR.XMLHttpRequest;
if (typeof (XMLHttpRequest) === "undefined") {
xhr = new XHR.XMLHttpRequest();
} else {
xhr = new XMLHttpRequest();
}
const requestCommand = method === RestRequestType.File ? "post" : method;
xhr.open(requestCommand, this.withQuery(uri, queryParams), true);
if (this.privHeaders) {
Object.keys(this.privHeaders).forEach((key: any) => xhr.setRequestHeader(key, this.privHeaders[key]));
}
if (this.privIgnoreCache) {
xhr.setRequestHeader("Cache-Control", "no-cache");
}
xhr.timeout = this.privTimeout;
xhr.onload = () => {
responseReceivedDeferral.resolve(this.parseXHRResult(xhr));
};
xhr.onerror = () => {
responseReceivedDeferral.resolve(this.errorResponse(xhr, "Failed to make request."));
};
xhr.ontimeout = () => {
responseReceivedDeferral.resolve(this.errorResponse(xhr, "Request took longer than expected."));
};
if (method === RestRequestType.File && binaryBody) {
xhr.setRequestHeader("Content-Type", "multipart/form-data");
xhr.send(binaryBody);
} else if (method === RestRequestType.Post && body) {
xhr.setRequestHeader("Content-Type", "application/json");
xhr.send(JSON.stringify(body));
} else {
xhr.send();
}
return responseReceivedDeferral.promise();
}
private parseXHRResult(xhr: XMLHttpRequest | XHR.XMLHttpRequest): IRestResponse {
return {
data: xhr.responseText,
headers: xhr.getAllResponseHeaders(),
json: <T>() => JSON.parse(xhr.responseText) as T,
ok: xhr.status >= 200 && xhr.status < 300,
status: xhr.status,
statusText: xhr.statusText,
};
}
private errorResponse(xhr: XMLHttpRequest | XHR.XMLHttpRequest, message: string | null = null): IRestResponse {
return {
data: message || xhr.statusText,
headers: xhr.getAllResponseHeaders(),
json: <T>() => JSON.parse(message || ("\"" + xhr.statusText + "\"")) as T,
ok: false,
status: xhr.status,
statusText: xhr.statusText,
};
}
private withQuery(url: string, params: any = {}): any {
const queryString = this.queryParams(params);
return queryString ? url + (url.indexOf("?") === -1 ? "?" : "&") + queryString : url;
}
private queryParams(params: any = {}): any {
return Object.keys(params)
.map((k: any) => encodeURIComponent(k) + "=" + encodeURIComponent(params[k]))
.join("&");
}
}

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

@ -43,6 +43,8 @@ export * from "./SynthesisTurn";
export * from "./SynthesisAdapterBase";
export * from "./SynthesizerConfig";
export * from "./SynthesisContext";
export * from "./SpeakerRecognitionConfig";
export * from "./SpeakerIdMessageAdapter";
export const OutputFormatPropertyName: string = "OutputFormat";
export const CancellationErrorCodePropertyName: string = "CancellationErrorCode";

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

@ -0,0 +1,162 @@
import {
IRequestOptions,
IRestResponse,
RestConfigBase,
RestMessageAdapter,
RestRequestType,
} from "../common.browser/Exports";
import {
createNoDashGuid,
Deferred,
IAudioSource,
Promise,
PromiseResult,
} from "../common/Exports";
import {
PropertyId,
SpeakerIdentificationModel,
SpeakerVerificationModel,
VoiceProfile,
VoiceProfileType,
} from "../sdk/Exports";
import { SpeakerRecognitionConfig } from "./Exports";
/**
* Implements methods for speaker recognition classes, sending requests to endpoint
* and parsing response into expected format
* @class SpeakerIdMessageAdapter
*/
export class SpeakerIdMessageAdapter {
private privRestAdapter: RestMessageAdapter;
private privUri: string;
public constructor(config: SpeakerRecognitionConfig) {
let endpoint = config.parameters.getProperty(PropertyId.SpeechServiceConnection_Endpoint, undefined);
if (!endpoint) {
const region: string = config.parameters.getProperty(PropertyId.SpeechServiceConnection_Region, "westus");
const hostSuffix: string = (region && region.toLowerCase().startsWith("china")) ? ".azure.cn" : ".microsoft.com";
const host: string = config.parameters.getProperty(PropertyId.SpeechServiceConnection_Host, "https://" + region + ".api.cognitive" + hostSuffix + "/speaker/{mode}/v2.0/{dependency}");
endpoint = host + "/profiles";
}
this.privUri = endpoint;
const options: IRequestOptions = RestConfigBase.requestOptions;
options.headers[RestConfigBase.configParams.subscriptionKey] = config.parameters.getProperty(PropertyId.SpeechServiceConnection_Key, undefined);
this.privRestAdapter = new RestMessageAdapter(options);
}
/**
* Sends create profile request to endpoint.
* @function
* @param {VoiceProfileType} profileType - type of voice profile to create.
* @param {string} lang - language/locale of voice profile
* @public
* @returns {Promise<IRestResponse>} promised rest response containing id of created profile.
*/
public createProfile(profileType: VoiceProfileType, lang: string):
Promise<IRestResponse> {
const uri = this.getOperationUri(profileType);
this.privRestAdapter.setHeaders(RestConfigBase.configParams.contentTypeKey, "application/json");
return this.privRestAdapter.request(RestRequestType.Post, uri, {}, { locale: lang });
}
/**
* Sends create enrollment request to endpoint.
* @function
* @param {VoiceProfile} profileType - voice profile for which to create new enrollment.
* @param {IAudioSource} audioSource - audioSource from which to pull data to send
* @public
* @returns {Promise<IRestResponse>} rest response to enrollment request.
*/
public createEnrollment(profile: VoiceProfile, audioSource: IAudioSource):
Promise<IRestResponse> {
this.privRestAdapter.setHeaders(RestConfigBase.configParams.contentTypeKey, "multipart/form-data");
const uri = this.getOperationUri(profile.profileType) + "/" + profile.profileId + "/enrollments";
return audioSource.blob.onSuccessContinueWithPromise<IRestResponse>((result: Blob | Buffer): Promise<IRestResponse> => {
return this.privRestAdapter.request(RestRequestType.File, uri, { ignoreMinLength: "true" }, null, result);
});
}
/**
* Sends verification request to endpoint.
* @function
* @param {SpeakerVerificationModel} model - voice model to verify against.
* @param {IAudioSource} audioSource - audioSource from which to pull data to send
* @public
* @returns {Promise<IRestResponse>} rest response to enrollment request.
*/
public verifySpeaker(model: SpeakerVerificationModel, audioSource: IAudioSource):
Promise<IRestResponse> {
this.privRestAdapter.setHeaders(RestConfigBase.configParams.contentTypeKey, "multipart/form-data");
const uri = this.getOperationUri(model.voiceProfile.profileType) + "/" + model.voiceProfile.profileId + "/verify";
return audioSource.blob.continueWithPromise<IRestResponse>((result: PromiseResult<Blob | Buffer>): Promise<IRestResponse> => {
if (result.isError) {
const response: Deferred<IRestResponse> = new Deferred<IRestResponse>();
response.resolve({ data: result.error } as IRestResponse);
return response.promise();
}
return this.privRestAdapter.request(RestRequestType.File, uri, { ignoreMinLength: "true" }, null, result.result);
});
}
/**
* Sends identification request to endpoint.
* @function
* @param {SpeakerIdentificationModel} model - voice profiles against which to identify.
* @param {IAudioSource} audioSource - audioSource from which to pull data to send
* @public
* @returns {Promise<IRestResponse>} rest response to enrollment request.
*/
public identifySpeaker(model: SpeakerIdentificationModel, audioSource: IAudioSource):
Promise<IRestResponse> {
this.privRestAdapter.setHeaders(RestConfigBase.configParams.contentTypeKey, "multipart/form-data");
const uri = this.getOperationUri(VoiceProfileType.TextIndependentIdentification) + "/identifySingleSpeaker";
return audioSource.blob.continueWithPromise<IRestResponse>((result: PromiseResult<Blob | Buffer>): Promise<IRestResponse> => {
if (result.isError) {
const response: Deferred<IRestResponse> = new Deferred<IRestResponse>();
response.resolve({ data: result.error } as IRestResponse);
return response.promise();
}
return this.privRestAdapter.request(RestRequestType.File, uri, { profileIds: model.voiceProfileIds, ignoreMinLength: "true" }, null, result.result);
});
}
/**
* Sends delete profile request to endpoint.
* @function
* @param {VoiceProfile} profile - voice profile to delete.
* @public
* @returns {Promise<IRestResponse>} rest response to deletion request
*/
public deleteProfile(profile: VoiceProfile): Promise<IRestResponse> {
const uri = this.getOperationUri(profile.profileType) + "/" + profile.profileId;
return this.privRestAdapter.request(RestRequestType.Delete, uri, {});
}
/**
* Sends reset profile request to endpoint.
* @function
* @param {VoiceProfile} profile - voice profile to reset enrollments for.
* @public
* @returns {Promise<IRestResponse>} rest response to reset request
*/
public resetProfile(profile: VoiceProfile): Promise<IRestResponse> {
const uri = this.getOperationUri(profile.profileType) + "/" + profile.profileId + "/reset";
return this.privRestAdapter.request(RestRequestType.Post, uri, {});
}
private getOperationUri(profileType: VoiceProfileType): string {
const mode = profileType === VoiceProfileType.TextIndependentIdentification ? "identification" : "verification";
const dependency = profileType === VoiceProfileType.TextDependentVerification ? "text-dependent" : "text-independent";
return this.privUri.replace("{mode}", mode).replace("{dependency}", dependency);
}
}

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

@ -0,0 +1,25 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { PropertyCollection } from "../sdk/Exports";
import { Context } from "./Exports";
export class SpeakerRecognitionConfig {
private privParameters: PropertyCollection;
private privContext: Context;
constructor(
context: Context,
parameters: PropertyCollection) {
this.privContext = context ? context : new Context(null);
this.privParameters = parameters;
}
public get parameters(): PropertyCollection {
return this.privParameters;
}
public get Context(): Context {
return this.privContext;
}
}

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

@ -0,0 +1,51 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import {
RestConfigBase
} from "../../common.browser/RestConfigBase";
export class ConversationConnectionConfig extends RestConfigBase {
private static readonly privHost: string = "dev.microsofttranslator.com";
private static readonly privRestPath: string = "/capito/room";
private static readonly privApiVersion: string = "2.0";
private static readonly privDefaultLanguageCode: string = "en-US";
private static readonly privClientAppId: string = "FC539C22-1767-4F1F-84BC-B4D811114F15";
private static readonly privWebSocketPath: string = "/capito/translate";
private static readonly privSpeechHost: string = "{region}.s2s.speech.microsoft.com";
private static readonly privSpeechPath: string = "/speech/translation/cognitiveservices/v1";
public static get host(): string {
return ConversationConnectionConfig.privHost;
}
public static get apiVersion(): string {
return ConversationConnectionConfig.privApiVersion;
}
public static get clientAppId(): string {
return ConversationConnectionConfig.privClientAppId;
}
public static get defaultLanguageCode(): string {
return ConversationConnectionConfig.privDefaultLanguageCode;
}
public static get restPath(): string {
return ConversationConnectionConfig.privRestPath;
}
public static get webSocketPath(): string {
return ConversationConnectionConfig.privWebSocketPath;
}
public static get speechHost(): string {
return ConversationConnectionConfig.privSpeechHost;
}
public static get speechPath(): string {
return ConversationConnectionConfig.privSpeechPath;
}
}

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

@ -7,7 +7,7 @@ import { Contracts } from "../../sdk/Contracts";
import { PropertyId } from "../../sdk/Exports";
import { ConnectionFactoryBase } from "../ConnectionFactoryBase";
import { AuthInfo, RecognizerConfig } from "../Exports";
import { ConversationTranslatorConfig } from "./ConversationUtils";
import { ConversationConnectionConfig } from "./ConversationConnectionConfig";
import { ConversationWebsocketMessageFormatter } from "./ConversationWebsocketMessageFormatter";
/**
@ -18,19 +18,18 @@ export class ConversationConnectionFactory extends ConnectionFactoryBase {
public create(config: RecognizerConfig, authInfo: AuthInfo, connectionId?: string): IConnection {
const endpointHost: string = config.parameters.getProperty(PropertyId.ConversationTranslator_Host, ConversationTranslatorConfig.host);
const endpointHost: string = config.parameters.getProperty(PropertyId.ConversationTranslator_Host, ConversationConnectionConfig.host);
const correlationId: string = config.parameters.getProperty(PropertyId.ConversationTranslator_CorrelationId, createGuid());
const endpoint: string = `wss://${endpointHost}${ConversationTranslatorConfig.webSocketPath}`;
const endpoint: string = `wss://${endpointHost}${ConversationConnectionConfig.webSocketPath}`;
const token: string = config.parameters.getProperty(PropertyId.ConversationTranslator_Token, undefined);
Contracts.throwIfNullOrUndefined(token, "token");
const queryParams: IStringDictionary<string> = {};
queryParams[ConversationTranslatorConfig.params.apiVersion] = ConversationTranslatorConfig.apiVersion;
queryParams[ConversationTranslatorConfig.params.token] = token;
queryParams[ConversationTranslatorConfig.params.correlationId] = correlationId;
queryParams[ConversationConnectionConfig.configParams.apiVersion] = ConversationConnectionConfig.apiVersion;
queryParams[ConversationConnectionConfig.configParams.token] = token;
queryParams[ConversationConnectionConfig.configParams.correlationId] = correlationId;
return new WebsocketConnection(endpoint, queryParams, {}, new ConversationWebsocketMessageFormatter(), ProxyInfo.fromRecognizerConfig(config), connectionId);
}
}

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

@ -1,16 +1,32 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { IStringDictionary } from "../../common/Exports";
import {
IRequestOptions,
IRestParams,
} from "../../common.browser/RestConfigBase";
import { IErrorMessages, IStringDictionary } from "../../common/Exports";
import { Contracts } from "../../sdk/Contracts";
import { PropertyCollection, PropertyId } from "../../sdk/Exports";
import { IConversationResponseError, IInternalConversation, IRequestOptions, IResponse } from "./ConversationTranslatorInterfaces";
import { ConversationTranslatorConfig, extractHeaderValue, request } from "./ConversationUtils";
import { ConversationConnectionConfig } from "./ConversationConnectionConfig";
import { IConversationResponseError, IInternalConversation, IResponse } from "./ConversationTranslatorInterfaces";
import { extractHeaderValue, request } from "./ConversationUtils";
export class ConversationManager {
constructor() {
private privRequestParams: IRestParams;
private privErrors: IErrorMessages;
private privHost: string;
private privApiVersion: string;
private privRestPath: string;
public constructor() {
//
this.privRequestParams = ConversationConnectionConfig.configParams;
this.privErrors = ConversationConnectionConfig.restErrors;
this.privHost = ConversationConnectionConfig.host;
this.privApiVersion = ConversationConnectionConfig.apiVersion;
this.privRestPath = ConversationConnectionConfig.restPath;
}
/**
@ -26,9 +42,9 @@ export class ConversationManager {
Contracts.throwIfNullOrUndefined(args, "args");
const languageCode: string = args.getProperty(PropertyId.SpeechServiceConnection_RecoLanguage, ConversationTranslatorConfig.defaultLanguageCode);
const languageCode: string = args.getProperty(PropertyId.SpeechServiceConnection_RecoLanguage, ConversationConnectionConfig.defaultLanguageCode);
const nickname: string = args.getProperty(PropertyId.ConversationTranslator_Name);
const endpointHost: string = args.getProperty(PropertyId.ConversationTranslator_Host, ConversationTranslatorConfig.host);
const endpointHost: string = args.getProperty(PropertyId.ConversationTranslator_Host, this.privHost);
const correlationId: string = args.getProperty(PropertyId.ConversationTranslator_CorrelationId);
const subscriptionKey: string = args.getProperty(PropertyId.SpeechServiceConnection_Key);
const subscriptionRegion: string = args.getProperty(PropertyId.SpeechServiceConnection_Region);
@ -39,44 +55,44 @@ export class ConversationManager {
Contracts.throwIfNullOrWhitespace(endpointHost, "endpointHost");
const queryParams: IStringDictionary<string> = {};
queryParams[ConversationTranslatorConfig.params.apiVersion] = ConversationTranslatorConfig.apiVersion;
queryParams[ConversationTranslatorConfig.params.languageCode] = languageCode;
queryParams[ConversationTranslatorConfig.params.nickname] = nickname;
queryParams[this.privRequestParams.apiVersion] = this.privApiVersion;
queryParams[this.privRequestParams.languageCode] = languageCode;
queryParams[this.privRequestParams.nickname] = nickname;
const headers: IStringDictionary<string> = {};
if (correlationId) {
headers[ConversationTranslatorConfig.params.correlationId] = correlationId;
headers[this.privRequestParams.correlationId] = correlationId;
}
headers[ConversationTranslatorConfig.params.clientAppId] = ConversationTranslatorConfig.clientAppId;
headers[this.privRequestParams.clientAppId] = ConversationConnectionConfig.clientAppId;
if (conversationCode !== undefined) {
queryParams[ConversationTranslatorConfig.params.roomId] = conversationCode;
queryParams[this.privRequestParams.roomId] = conversationCode;
} else {
Contracts.throwIfNullOrUndefined(subscriptionRegion, ConversationTranslatorConfig.strings.authInvalidSubscriptionRegion);
headers[ConversationTranslatorConfig.params.subscriptionRegion] = subscriptionRegion;
Contracts.throwIfNullOrUndefined(subscriptionRegion, this.privErrors.authInvalidSubscriptionRegion);
headers[this.privRequestParams.subscriptionRegion] = subscriptionRegion;
if (subscriptionKey) {
headers[ConversationTranslatorConfig.params.subscriptionKey] = subscriptionKey;
headers[this.privRequestParams.subscriptionKey] = subscriptionKey;
} else if (authToken) {
headers[ConversationTranslatorConfig.params.authorization] = `Bearer ${authToken}`;
headers[this.privRequestParams.authorization] = `Bearer ${authToken}`;
} else {
Contracts.throwIfNullOrUndefined(subscriptionKey, ConversationTranslatorConfig.strings.authInvalidSubscriptionKey);
Contracts.throwIfNullOrUndefined(subscriptionKey, this.privErrors.authInvalidSubscriptionKey);
}
}
const config: IRequestOptions = {};
config.headers = headers;
const endpoint: string = `https://${endpointHost}${ConversationTranslatorConfig.restPath}`;
const endpoint: string = `https://${endpointHost}${this.privRestPath}`;
// TODO: support a proxy and certificate validation
request("post", endpoint, queryParams, null, config, (response: IResponse) => {
const requestId: string = extractHeaderValue(ConversationTranslatorConfig.params.requestId, response.headers);
const requestId: string = extractHeaderValue(this.privRequestParams.requestId, response.headers);
if (!response.ok) {
if (!!err) {
// get the error
let errorMessage: string = ConversationTranslatorConfig.strings.invalidCreateJoinConversationResponse.replace("{status}", response.status.toString());
let errorMessage: string = this.privErrors.invalidCreateJoinConversationResponse.replace("{status}", response.status.toString());
let errMessageRaw: IConversationResponseError;
try {
errMessageRaw = JSON.parse(response.data) as IConversationResponseError;
@ -132,25 +148,25 @@ export class ConversationManager {
try {
Contracts.throwIfNullOrUndefined(args, ConversationTranslatorConfig.strings.invalidArgs.replace("{arg}", "config"));
Contracts.throwIfNullOrWhitespace(sessionToken, ConversationTranslatorConfig.strings.invalidArgs.replace("{arg}", "token"));
Contracts.throwIfNullOrUndefined(args, this.privErrors.invalidArgs.replace("{arg}", "config"));
Contracts.throwIfNullOrWhitespace(sessionToken, this.privErrors.invalidArgs.replace("{arg}", "token"));
const endpointHost: string = args.getProperty(PropertyId.ConversationTranslator_Host, ConversationTranslatorConfig.host);
const endpointHost: string = args.getProperty(PropertyId.ConversationTranslator_Host, this.privHost);
const correlationId: string = args.getProperty(PropertyId.ConversationTranslator_CorrelationId);
const queryParams: IStringDictionary<string> = {};
queryParams[ConversationTranslatorConfig.params.apiVersion] = ConversationTranslatorConfig.apiVersion;
queryParams[ConversationTranslatorConfig.params.sessionToken] = sessionToken;
queryParams[this.privRequestParams.apiVersion] = this.privApiVersion;
queryParams[this.privRequestParams.sessionToken] = sessionToken;
const headers: IStringDictionary<string> = {};
if (correlationId) {
headers[ConversationTranslatorConfig.params.correlationId] = correlationId;
headers[this.privRequestParams.correlationId] = correlationId;
}
const config: IRequestOptions = {};
config.headers = headers;
const endpoint: string = `https://${endpointHost}${ConversationTranslatorConfig.restPath}`;
const endpoint: string = `https://${endpointHost}${this.privRestPath}`;
// TODO: support a proxy and certificate validation
request("delete", endpoint, queryParams, null, config, (response: IResponse) => {

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

@ -267,15 +267,6 @@ export const ConversationTranslatorCommandTypes = {
setUseTTS: "SetUseTTS"
};
/**
* HTTP request helper
*/
export interface IRequestOptions {
headers?: {[key: string]: string};
ignoreCache?: boolean;
timeout?: number;
}
/**
* HTTP response helper
*/

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

@ -1,63 +1,14 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { IRequestOptions, RestConfigBase } from "../../common.browser/RestConfigBase";
import { Promise, PromiseResult } from "../../common/Promise";
import { Callback } from "../../sdk/Transcription/IConversation";
import { IRequestOptions, IResponse } from "./ConversationTranslatorInterfaces";
import { IResponse } from "./ConversationTranslatorInterfaces";
/**
* Config settings for Conversation Translator
*/
export const ConversationTranslatorConfig = {
apiVersion: "2.0",
auth: {
placeholderRegion: "westus",
placeholderSubscriptionKey: "abcdefghijklmnopqrstuvwxyz012345",
},
clientAppId: "FC539C22-1767-4F1F-84BC-B4D811114F15",
defaultLanguageCode: "en-US",
defaultRequestOptions: {
headers: {
Accept: "application/json",
},
ignoreCache: false,
timeout: 5000,
},
host: "dev.microsofttranslator.com",
params: {
apiVersion: "api-version",
authorization: "Authorization",
clientAppId: "X-ClientAppId",
correlationId: "X-CorrelationId",
languageCode: "language",
nickname: "nickname",
profanity: "profanity",
requestId: "X-RequestId",
roomId: "roomid",
sessionToken: "token",
subscriptionKey: "Ocp-Apim-Subscription-Key",
subscriptionRegion: "Ocp-Apim-Subscription-Region",
token: "X-CapitoToken",
},
restPath: "/capito/room",
speechHost: "{region}.s2s.speech.microsoft.com",
speechPath: "/speech/translation/cognitiveservices/v1",
strings: {
authInvalidSubscriptionKey: "You must specify either an authentication token to use, or a Cognitive Speech subscription key.",
authInvalidSubscriptionRegion: "You must specify the Cognitive Speech region to use.",
invalidArgs: "Required input not found: {arg}.",
invalidCreateJoinConversationResponse: "Creating/Joining conversation failed with HTTP {status}.",
invalidParticipantRequest: "The requested participant was not found.",
permissionDeniedConnect: "Required credentials not found.",
permissionDeniedConversation: "Invalid operation: only the host can {command} the conversation.",
permissionDeniedParticipant: "Invalid operation: only the host can {command} a participant.",
permissionDeniedSend: "Invalid operation: the conversation is not in a connected state.",
permissionDeniedStart: "Invalid operation: there is already an active conversation.",
},
textMessageMaxLength: 1000,
webSocketPath: "/capito/translate"
};
/**
* Helpers for sending / receiving HTTPS requests / responses.
* @param params
@ -121,12 +72,14 @@ export function request(method: "get" | "post" | "delete",
url: string,
queryParams: any = {},
body: any = null,
options: IRequestOptions = ConversationTranslatorConfig.defaultRequestOptions,
options: IRequestOptions = {},
callback: any): any {
const ignoreCache = options.ignoreCache || ConversationTranslatorConfig.defaultRequestOptions.ignoreCache;
const headers = options.headers || ConversationTranslatorConfig.defaultRequestOptions.headers;
const timeout = options.timeout || ConversationTranslatorConfig.defaultRequestOptions.timeout;
const defaultRequestOptions = RestConfigBase.requestOptions;
const ignoreCache = options.ignoreCache || defaultRequestOptions.ignoreCache;
const headers = options.headers || defaultRequestOptions.headers;
const timeout = options.timeout || defaultRequestOptions.timeout;
const xhr = new XMLHttpRequest();
xhr.open(method, withQuery(url, queryParams), true);

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

@ -2,8 +2,8 @@
// Licensed under the MIT license.
export { ConversationManager } from "./ConversationManager";
export { ConversationTranslatorConfig } from "./ConversationUtils";
export { ConversationTranslatorRecognizer } from "./ConversationTranslatorRecognizer";
export { ConversationConnectionConfig } from "./ConversationConnectionConfig";
export {
ConversationReceivedTranslationEventArgs,
LockRoomEventArgs,

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

@ -15,6 +15,7 @@ export * from "./IDetachable";
export * from "./IDictionary";
export * from "./IDisposable";
export * from "./IEventSource";
export * from "./IErrorMessages";
export * from "./ITimer";
export * from "./IWebsocketMessageFormatter";
export * from "./List";

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

@ -18,6 +18,7 @@ export interface IAudioSource {
events: EventSource<AudioSourceEvent>;
format: Promise<AudioStreamFormatImpl>;
deviceInfo: Promise<ISpeechConfigAudioDevice>;
blob: Promise<Blob | Buffer>;
setProperty?(name: string, value: string): void;
getProperty?(name: string, def?: string): string;
}

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

@ -0,0 +1,12 @@
export interface IErrorMessages {
authInvalidSubscriptionKey: string;
authInvalidSubscriptionRegion: string;
invalidArgs: string;
invalidCreateJoinConversationResponse: string;
invalidParticipantRequest: string;
permissionDeniedConnect: string;
permissionDeniedConversation: string;
permissionDeniedParticipant: string;
permissionDeniedSend: string;
permissionDeniedStart: string;
}

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

@ -10,6 +10,7 @@ import {
import { ISpeechConfigAudioDevice } from "../../common.speech/Exports";
import {
AudioSourceEvent,
Deferred,
EventSource,
IAudioDestination,
IAudioSource,
@ -247,6 +248,15 @@ export class AudioConfigImpl extends AudioConfig implements IAudioSource {
return this.privSource.id();
}
/**
* @member AudioConfigImpl.prototype.blob
* @function
* @public
*/
public get blob(): Promise<Blob | Buffer> {
return this.privSource.blob;
}
/**
* @member AudioConfigImpl.prototype.turnOn
* @function

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

@ -14,6 +14,7 @@ import {
AudioStreamNodeAttachingEvent,
AudioStreamNodeDetachedEvent,
ChunkedArrayBufferStream,
Deferred,
Events,
EventSource,
IAudioSource,
@ -21,6 +22,7 @@ import {
IStreamChunk,
Promise,
PromiseHelper,
PromiseResult,
Stream,
StreamReader,
} from "../../common/Exports";
@ -183,6 +185,32 @@ export class PushAudioInputStreamImpl extends PushAudioInputStream implements IA
return this.privId;
}
public get blob(): Promise<Blob | Buffer> {
return this.attach("id").onSuccessContinueWithPromise<Blob | Buffer>((audioNode: IAudioStreamNode) => {
const data: ArrayBuffer[] = [];
let bufferData = Buffer.from("");
const readCycle = (): Promise<Blob | Buffer> => {
return audioNode.read().onSuccessContinueWithPromise<Blob | Buffer>((audioStreamChunk: IStreamChunk<ArrayBuffer>) => {
if (!audioStreamChunk || audioStreamChunk.isEnd) {
if (typeof (XMLHttpRequest) !== "undefined") {
return PromiseHelper.fromResult(new Blob(data));
} else {
return PromiseHelper.fromResult(Buffer.from(bufferData));
}
} else {
if (typeof (Blob) !== "undefined") {
data.push(audioStreamChunk.buffer);
} else {
bufferData = Buffer.concat([bufferData, this.toBuffer(audioStreamChunk.buffer)]);
}
return readCycle();
}
});
};
return readCycle();
});
}
public turnOn(): Promise<boolean> {
this.onEvent(new AudioSourceInitializingEvent(this.privId)); // no stream id
this.onEvent(new AudioSourceReadyEvent(this.privId));
@ -246,6 +274,15 @@ export class PushAudioInputStreamImpl extends PushAudioInputStream implements IA
this.privEvents.onEvent(event);
Events.instance.onEvent(event);
}
private toBuffer(arrayBuffer: ArrayBuffer): Buffer {
const buf: Buffer = Buffer.alloc(arrayBuffer.byteLength);
const view: Uint8Array = new Uint8Array(arrayBuffer);
for (let i = 0; i < buf.length; ++i) {
buf[i] = view[i];
}
return buf;
}
}
/*
@ -346,6 +383,10 @@ export class PullAudioInputStreamImpl extends PullAudioInputStream implements IA
return this.privId;
}
public get blob(): Promise<Blob | Buffer> {
return PromiseHelper.fromError("Not implemented");
}
public turnOn(): Promise<boolean> {
this.onEvent(new AudioSourceInitializingEvent(this.privId)); // no stream id
this.onEvent(new AudioSourceReadyEvent(this.privId));

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

@ -2,6 +2,7 @@
// Licensed under the MIT license.
import { CancellationErrorCodePropertyName, EnumTranslation, SimpleSpeechPhrase } from "../common.speech/Exports";
import { CancellationDetailsBase } from "./CancellationDetailsBase";
import {
CancellationErrorCode,
CancellationReason,
@ -13,21 +14,10 @@ import {
* Contains detailed information about why a result was canceled.
* @class CancellationDetails
*/
export class CancellationDetails {
private privReason: CancellationReason;
private privErrorDetails: string;
private privErrorCode: CancellationErrorCode;
export class CancellationDetails extends CancellationDetailsBase {
/**
* 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;
super(reason, errorDetails, errorCode);
}
/**
@ -52,38 +42,6 @@ export class CancellationDetails {
}
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,61 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import {
CancellationErrorCode,
CancellationReason,
} from "./Exports";
/**
* Contains detailed information about why a result was canceled.
* @class CancellationDetailsBase
*/
export class CancellationDetailsBase {
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.
*/
protected constructor(reason: CancellationReason, errorDetails: string, errorCode: CancellationErrorCode) {
this.privReason = reason;
this.privErrorDetails = errorDetails;
this.privErrorCode = errorCode;
}
/**
* The reason the recognition was canceled.
* @member CancellationDetailsBase.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 CancellationDetailsBase.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;
}
}

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

@ -31,12 +31,14 @@ export { PropertyId } from "./PropertyId";
export { Recognizer } from "./Recognizer";
export { SpeechRecognizer } from "./SpeechRecognizer";
export { IntentRecognizer } from "./IntentRecognizer";
export { VoiceProfileType } from "./VoiceProfileType";
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 { CancellationDetailsBase } from "./CancellationDetailsBase";
export { CancellationDetails } from "./CancellationDetails";
export { CancellationErrorCode } from "./CancellationErrorCodes";
export { ConnectionEventArgs } from "./ConnectionEventArgs";
@ -53,6 +55,14 @@ export { ProfanityOption } from "./ProfanityOption";
export { BaseAudioPlayer } from "./Audio/BaseAudioPlayer";
export { ConnectionMessageEventArgs } from "./ConnectionMessageEventArgs";
export { ConnectionMessage } from "./ConnectionMessage";
export { VoiceProfile } from "./VoiceProfile";
export { VoiceProfileEnrollmentResult, VoiceProfileEnrollmentCancellationDetails } from "./VoiceProfileEnrollmentResult";
export { VoiceProfileResult, VoiceProfileCancellationDetails } from "./VoiceProfileResult";
export { VoiceProfileClient } from "./VoiceProfileClient";
export { SpeakerRecognizer } from "./SpeakerRecognizer";
export { SpeakerIdentificationModel } from "./SpeakerIdentificationModel";
export { SpeakerVerificationModel } from "./SpeakerVerificationModel";
export { SpeakerRecognitionResult, SpeakerRecognitionResultType, SpeakerRecognitionCancellationDetails } from "./SpeakerRecognitionResult";
export { Conversation,
ConversationExpirationEventArgs,
ConversationParticipantsChangedEventArgs,

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

@ -36,8 +36,8 @@ export enum PropertyId {
/**
* The Cognitive Services Speech Service authorization token (aka access token). Under normal circumstances,
* you shouldn't have to use this property directly.
* Instead, use [[SpeechConfig.fromAuthorizationToken]],
* [[SpeechRecognizer.authorizationToken]], [[IntentRecognizer.authorizationToken]], [[TranslationRecognizer.authorizationToken]].
* Instead, use [[SpeechConfig.fromAuthorizationToken]], [[SpeechRecognizer.authorizationToken]],
* [[IntentRecognizer.authorizationToken]], [[TranslationRecognizer.authorizationToken]], [[SpeakerRecognizer.authorizationToken]].
* @member PropertyId.SpeechServiceAuthorization_Token
*/
SpeechServiceAuthorization_Token,

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

@ -76,4 +76,40 @@ export enum ResultReason {
* @member ResultReason.SynthesizingAudioStarted
*/
SynthesizingAudioStarted,
/**
* Indicates the voice profile is being enrolled and customers need to send more audio to create a voice profile.
* @member ResultReason.EnrollingVoiceProfile
*/
EnrollingVoiceProfile,
/**
* Indicates the voice profile has been enrolled.
* @member ResultReason.EnrolledVoiceProfile
*/
EnrolledVoiceProfile,
/**
* Indicates successful identification of some speakers.
* @member ResultReason.RecognizedSpeakers
*/
RecognizedSpeakers,
/**
* Indicates successfully verified one speaker.
* @member ResultReason.RecognizedSpeaker
*/
RecognizedSpeaker,
/**
* Indicates a voice profile has been reset successfully.
* @member ResultReason.ResetVoiceProfile
*/
ResetVoiceProfile,
/**
* Indicates a voice profile has been deleted successfully.
* @member ResultReason.DeletedVoiceProfile
*/
DeletedVoiceProfile,
}

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

@ -0,0 +1,38 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { Contracts } from "./Contracts";
import {
VoiceProfile,
VoiceProfileType,
} from "./Exports";
/**
* Defines SpeakerIdentificationModel class for Speaker Recognition
* Model contains a set of profiles against which to identify speaker(s)
* @class SpeakerIdentificationModel
*/
export class SpeakerIdentificationModel {
private privVoiceProfiles: VoiceProfile[] = [];
private constructor(profiles: VoiceProfile[]) {
Contracts.throwIfNullOrUndefined(profiles, "VoiceProfiles");
if (profiles.length === 0) {
throw new Error("Empty Voice Profiles array");
}
profiles.forEach((profile: VoiceProfile) => {
if (profile.profileType !== VoiceProfileType.TextIndependentIdentification) {
throw new Error("Identification model can only be created from Identification profile: " + profile.profileId);
}
this.privVoiceProfiles.push(profile);
});
}
public static fromProfiles(profiles: VoiceProfile[]): SpeakerIdentificationModel {
return new SpeakerIdentificationModel(profiles);
}
public get voiceProfileIds(): string {
return this.privVoiceProfiles.map((profile: VoiceProfile) => profile.profileId).join(",");
}
}

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

@ -0,0 +1,108 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { CancellationErrorCodePropertyName } from "../common.speech/Exports";
import { Contracts } from "./Contracts";
import {
CancellationDetailsBase,
CancellationErrorCode,
CancellationReason,
PropertyCollection,
PropertyId,
ResultReason,
} from "./Exports";
export enum SpeakerRecognitionResultType {
Verify,
Identify
}
/**
* Output format
* @class SpeakerRecognitionResult
*/
export class SpeakerRecognitionResult {
private privReason: ResultReason;
private privProperties: PropertyCollection;
private privProfileId: string;
private privScore: number;
private privErrorDetails: string;
public constructor(resultType: SpeakerRecognitionResultType, data: string, profileId: string, resultReason: ResultReason = ResultReason.RecognizedSpeaker) {
this.privProperties = new PropertyCollection();
this.privReason = resultReason;
if (this.privReason !== ResultReason.Canceled) {
if (resultType === SpeakerRecognitionResultType.Identify) {
const json: { identifiedProfile: { profileId: string, score: number } } = JSON.parse(data);
Contracts.throwIfNullOrUndefined(json, "JSON");
this.privProfileId = json.identifiedProfile.profileId;
this.privScore = json.identifiedProfile.score;
} else {
const json: { recognitionResult: string, score: number } = JSON.parse(data);
Contracts.throwIfNullOrUndefined(json, "JSON");
this.privScore = json.score;
if (json.recognitionResult.toLowerCase() !== "accept") {
this.privReason = ResultReason.NoMatch;
}
if (profileId !== undefined && profileId !== "") {
this.privProfileId = profileId;
}
}
} else {
const json: { statusText: string } = JSON.parse(data);
Contracts.throwIfNullOrUndefined(json, "JSON");
this.privErrorDetails = json.statusText;
this.privProperties.setProperty(CancellationErrorCodePropertyName, CancellationErrorCode[CancellationErrorCode.ServiceError]);
}
this.privProperties.setProperty(PropertyId.SpeechServiceResponse_JsonResult, data);
}
public get properties(): PropertyCollection {
return this.privProperties;
}
public get reason(): ResultReason {
return this.privReason;
}
public get profileId(): string {
return this.privProfileId;
}
public get errorDetails(): string {
return this.privErrorDetails;
}
public get score(): number {
return this.privScore;
}
}
/**
* @class SpeakerRecognitionCancellationDetails
*/
// tslint:disable-next-line:max-classes-per-file
export class SpeakerRecognitionCancellationDetails extends CancellationDetailsBase {
private constructor(reason: CancellationReason, errorDetails: string, errorCode: CancellationErrorCode) {
super(reason, errorDetails, errorCode);
}
/**
* Creates an instance of SpeakerRecognitionCancellationDetails object for the canceled SpeakerRecognitionResult
* @member SpeakerRecognitionCancellationDetails.fromResult
* @function
* @public
* @param {SpeakerRecognitionResult} result - The result that was canceled.
* @returns {SpeakerRecognitionCancellationDetails} The cancellation details object being created.
*/
public static fromResult(result: SpeakerRecognitionResult): SpeakerRecognitionCancellationDetails {
const reason = CancellationReason.Error;
let errorCode: CancellationErrorCode = CancellationErrorCode.NoError;
if (!!result.properties) {
errorCode = (CancellationErrorCode as any)[result.properties.getProperty(CancellationErrorCodePropertyName, CancellationErrorCode[CancellationErrorCode.NoError])];
}
return new SpeakerRecognitionCancellationDetails(reason, result.errorDetails, errorCode);
}
}

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

@ -0,0 +1,165 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import {
IRestResponse,
} from "../common.browser/Exports";
import {
Context,
OS,
SpeakerIdMessageAdapter,
SpeakerRecognitionConfig,
} from "../common.speech/Exports";
import { IAudioSource, PromiseResult } from "../common/Exports";
import { AudioConfig, AudioConfigImpl } from "./audio/AudioConfig";
import { Contracts } from "./Contracts";
import {
PropertyCollection,
PropertyId,
ResultReason,
SpeakerIdentificationModel,
SpeakerRecognitionResult,
SpeakerRecognitionResultType,
SpeakerVerificationModel,
VoiceProfile,
} from "./Exports";
import { SpeechConfig, SpeechConfigImpl } from "./SpeechConfig";
/**
* Defines SpeakerRecognizer class for Speaker Recognition
* Handles operations from user for Voice Profile operations (e.g. createProfile, deleteProfile)
* @class SpeakerRecognizer
*/
export class SpeakerRecognizer {
protected privProperties: PropertyCollection;
private privAdapter: SpeakerIdMessageAdapter;
private privAudioConfigImpl: AudioConfigImpl;
/**
* Gets the authorization token used to communicate with the service.
* @member SpeakerRecognizer.prototype.authorizationToken
* @function
* @public
* @returns {string} Authorization token.
*/
public get authorizationToken(): string {
return this.properties.getProperty(PropertyId.SpeechServiceAuthorization_Token);
}
/**
* Gets/Sets the authorization token used to communicate with the service.
* @member SpeakerRecognizer.prototype.authorizationToken
* @function
* @public
* @param {string} token - Authorization token.
*/
public set authorizationToken(token: string) {
Contracts.throwIfNullOrWhitespace(token, "token");
this.properties.setProperty(PropertyId.SpeechServiceAuthorization_Token, token);
}
/**
* The collection of properties and their values defined for this SpeakerRecognizer.
* @member SpeakerRecognizer.prototype.properties
* @function
* @public
* @returns {PropertyCollection} The collection of properties and their values defined for this SpeakerRecognizer.
*/
public get properties(): PropertyCollection {
return this.privProperties;
}
/**
* SpeakerRecognizer constructor.
* @constructor
* @param {SpeechConfig} speechConfig - An set of initial properties for this recognizer (authentication key, region, &c)
*/
public constructor(speechConfig: SpeechConfig, audioConfig: AudioConfig) {
const speechConfigImpl: SpeechConfigImpl = speechConfig as SpeechConfigImpl;
Contracts.throwIfNull(speechConfigImpl, "speechConfig");
this.privAudioConfigImpl = audioConfig as AudioConfigImpl;
Contracts.throwIfNull(this.privAudioConfigImpl, "audioConfig");
this.privProperties = speechConfigImpl.properties.clone();
this.implSRSetup();
}
/**
* Get recognition result for model using given audio
* @member SpeakerRecognizer.prototype.recognizeOnceAsync
* @function
* @public
* @param {SpeakerIdentificationModel} model Model containing Voice Profiles to be identified
* @param cb - Callback invoked once result is returned.
* @param err - Callback invoked in case of an error.
*/
public recognizeOnceAsync(model: SpeakerIdentificationModel | SpeakerVerificationModel, cb?: (e: SpeakerRecognitionResult) => void, err?: (e: string) => void): void {
if (model instanceof SpeakerIdentificationModel) {
this.privAdapter.identifySpeaker(model, this.privAudioConfigImpl).continueWith((promiseResult: PromiseResult<IRestResponse>) => {
this.handleResultCallbacks(promiseResult, SpeakerRecognitionResultType.Identify, undefined, cb, err);
});
} else if (model instanceof SpeakerVerificationModel) {
this.privAdapter.verifySpeaker(model, this.privAudioConfigImpl).continueWith((promiseResult: PromiseResult<IRestResponse>) => {
this.handleResultCallbacks(promiseResult, SpeakerRecognitionResultType.Verify, model.voiceProfile.profileId, cb, err);
});
} else {
throw new Error("SpeakerRecognizer.recognizeOnce: Unexpected model type");
}
}
/**
* Included for compatibility
* @member SpeakerRecognizer.prototype.close
* @function
* @public
*/
public close(): void {
return;
}
// Does class setup, swiped from Recognizer.
private implSRSetup(): void {
let osPlatform = (typeof window !== "undefined") ? "Browser" : "Node";
let osName = "unknown";
let osVersion = "unknown";
if (typeof navigator !== "undefined") {
osPlatform = osPlatform + "/" + navigator.platform;
osName = navigator.userAgent;
osVersion = navigator.appVersion;
}
const recognizerConfig =
new SpeakerRecognitionConfig(
new Context(new OS(osPlatform, osName, osVersion)),
this.privProperties);
this.privAdapter = new SpeakerIdMessageAdapter(recognizerConfig);
}
private handleResultCallbacks(promiseResult: PromiseResult<IRestResponse>, resultType: SpeakerRecognitionResultType, profileId?: string, cb?: (response: SpeakerRecognitionResult) => void, err?: (e: string) => void): void {
try {
if (promiseResult.isError) {
if (!!err) {
err(promiseResult.error);
}
} else if (promiseResult.isCompleted && !!cb) {
cb(
new SpeakerRecognitionResult(
resultType,
promiseResult.result.data,
profileId,
promiseResult.result.ok ? ResultReason.RecognizedSpeaker : ResultReason.Canceled,
)
);
}
} catch (e) {
if (!!err) {
err(e);
}
}
}
}

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

@ -0,0 +1,33 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { Contracts } from "./Contracts";
import {
VoiceProfile,
VoiceProfileType,
} from "./Exports";
/**
* Defines SpeakerVerificationModel class for Speaker Recognition
* Model contains a profile against which to verify a speaker
* @class SpeakerVerificationModel
*/
export class SpeakerVerificationModel {
private privVoiceProfile: VoiceProfile;
private constructor(profile: VoiceProfile) {
Contracts.throwIfNullOrUndefined(profile, "VoiceProfile");
if (profile.profileType === VoiceProfileType.TextIndependentIdentification) {
throw new Error("Verification model cannot be created from Identification profile");
}
this.privVoiceProfile = profile;
}
public static fromProfile(profile: VoiceProfile): SpeakerVerificationModel {
return new SpeakerVerificationModel(profile);
}
public get voiceProfile(): VoiceProfile {
return this.privVoiceProfile;
}
}

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

@ -3,10 +3,10 @@
// Multi-device Conversation is a Preview feature.
import {
ConversationConnectionConfig,
ConversationManager,
ConversationReceivedTranslationEventArgs,
ConversationTranslatorCommandTypes,
ConversationTranslatorConfig,
ConversationTranslatorMessageTypes,
ConversationTranslatorRecognizer,
IInternalConversation,
@ -18,7 +18,7 @@ import {
ParticipantEventArgs,
ParticipantsListEventArgs
} from "../../common.speech/Exports";
import { IDisposable } from "../../common/Exports";
import { IDisposable, IErrorMessages } from "../../common/Exports";
import { Contracts } from "../Contracts";
import {
Connection,
@ -59,10 +59,10 @@ export abstract class Conversation implements IConversation {
* @param err
*/
public static createConversationAsync(speechConfig: SpeechTranslationConfig, cb?: Callback, err?: Callback): Conversation {
Contracts.throwIfNullOrUndefined(speechConfig, ConversationTranslatorConfig.strings.invalidArgs.replace("{arg}", "config"));
Contracts.throwIfNullOrUndefined(speechConfig.region, ConversationTranslatorConfig.strings.invalidArgs.replace("{arg}", "SpeechServiceConnection_Region"));
Contracts.throwIfNullOrUndefined(speechConfig, ConversationConnectionConfig.restErrors.invalidArgs.replace("{arg}", "config"));
Contracts.throwIfNullOrUndefined(speechConfig.region, ConversationConnectionConfig.restErrors.invalidArgs.replace("{arg}", "SpeechServiceConnection_Region"));
if (!speechConfig.subscriptionKey && !speechConfig.getProperty(PropertyId[PropertyId.SpeechServiceAuthorization_Token])) {
Contracts.throwIfNullOrUndefined(speechConfig.subscriptionKey, ConversationTranslatorConfig.strings.invalidArgs.replace("{arg}", "SpeechServiceConnection_Key"));
Contracts.throwIfNullOrUndefined(speechConfig.subscriptionKey, ConversationConnectionConfig.restErrors.invalidArgs.replace("{arg}", "SpeechServiceConnection_Key"));
}
const conversationImpl: ConversationImpl = new ConversationImpl(speechConfig);
@ -140,6 +140,8 @@ export class ConversationImpl extends Conversation implements IDisposable {
private privParticipants: InternalParticipants;
private privIsReady: boolean;
private privConversationTranslator: ConversationTranslator;
private privErrors: IErrorMessages = ConversationConnectionConfig.restErrors;
private readonly privTextMessageMaxLength: number;
public set conversationTranslator(value: ConversationTranslator) {
this.privConversationTranslator = value;
@ -215,7 +217,7 @@ export class ConversationImpl extends Conversation implements IDisposable {
// check the speech language
const language: string = speechConfig.getProperty(PropertyId[PropertyId.SpeechServiceConnection_RecoLanguage]);
if (!language) {
speechConfig.setProperty(PropertyId[PropertyId.SpeechServiceConnection_RecoLanguage], ConversationTranslatorConfig.defaultLanguageCode);
speechConfig.setProperty(PropertyId[PropertyId.SpeechServiceConnection_RecoLanguage], ConversationConnectionConfig.defaultLanguageCode);
}
this.privLanguage = speechConfig.getProperty(PropertyId[PropertyId.SpeechServiceConnection_RecoLanguage]);
@ -249,6 +251,7 @@ export class ConversationImpl extends Conversation implements IDisposable {
this.privIsConnected = false;
this.privParticipants = new InternalParticipants();
this.privIsReady = false;
this.privTextMessageMaxLength = 1000;
}
/**
@ -259,12 +262,12 @@ export class ConversationImpl extends Conversation implements IDisposable {
public createConversationAsync(cb?: Callback, err?: Callback): void {
try {
if (!!this.privConversationRecognizer) {
this.handleError(new Error(ConversationTranslatorConfig.strings.permissionDeniedStart), err);
this.handleError(new Error(this.privErrors.permissionDeniedStart), err);
}
this.privManager.createOrJoin(this.privProperties, undefined,
((room: IInternalConversation) => {
if (!room) {
this.handleError(new Error(ConversationTranslatorConfig.strings.permissionDeniedConnect), err);
this.handleError(new Error(this.privErrors.permissionDeniedConnect), err);
}
this.privRoom = room;
this.handleCallback(cb, err);
@ -286,10 +289,10 @@ export class ConversationImpl extends Conversation implements IDisposable {
try {
// check if there is already a recognizer
if (!!this.privConversationRecognizer) {
this.handleError(new Error(ConversationTranslatorConfig.strings.permissionDeniedStart), err);
this.handleError(new Error(this.privErrors.permissionDeniedStart), err);
}
// check if there is conversation data available
Contracts.throwIfNullOrUndefined(this.privRoom, ConversationTranslatorConfig.strings.permissionDeniedConnect);
Contracts.throwIfNullOrUndefined(this.privRoom, this.privErrors.permissionDeniedConnect);
// connect to the conversation websocket
this.privParticipants.meId = this.privRoom.participantId;
this.privConversationRecognizer = new ConversationTranslatorRecognizer(this.privConfig);
@ -330,15 +333,15 @@ export class ConversationImpl extends Conversation implements IDisposable {
try {
// TODO
// if (!!this.privConversationRecognizer) {
// throw new Error(ConversationTranslatorConfig.strings.permissionDeniedStart);
// throw new Error(this.privErrors.permissionDeniedStart);
// }
Contracts.throwIfNullOrWhitespace(conversationId, ConversationTranslatorConfig.strings.invalidArgs.replace("{arg}", "conversationId"));
Contracts.throwIfNullOrWhitespace(nickname, ConversationTranslatorConfig.strings.invalidArgs.replace("{arg}", "nickname"));
Contracts.throwIfNullOrWhitespace(lang, ConversationTranslatorConfig.strings.invalidArgs.replace("{arg}", "language"));
Contracts.throwIfNullOrWhitespace(conversationId, this.privErrors.invalidArgs.replace("{arg}", "conversationId"));
Contracts.throwIfNullOrWhitespace(nickname, this.privErrors.invalidArgs.replace("{arg}", "nickname"));
Contracts.throwIfNullOrWhitespace(lang, this.privErrors.invalidArgs.replace("{arg}", "language"));
// join the conversation
this.privManager.createOrJoin(this.privProperties, conversationId,
((room: IInternalConversation) => {
Contracts.throwIfNullOrUndefined(room, ConversationTranslatorConfig.strings.permissionDeniedConnect);
Contracts.throwIfNullOrUndefined(room, this.privErrors.permissionDeniedConnect);
this.privRoom = room;
this.privConfig.authorizationToken = room.cognitiveSpeechAuthToken;
// join callback
@ -361,8 +364,8 @@ export class ConversationImpl extends Conversation implements IDisposable {
*/
public deleteConversationAsync(cb?: Callback, err?: Callback): void {
try {
Contracts.throwIfNullOrUndefined(this.privProperties, ConversationTranslatorConfig.strings.permissionDeniedConnect);
Contracts.throwIfNullOrWhitespace(this.privRoom.token, ConversationTranslatorConfig.strings.permissionDeniedConnect);
Contracts.throwIfNullOrUndefined(this.privProperties, this.privErrors.permissionDeniedConnect);
Contracts.throwIfNullOrWhitespace(this.privRoom.token, this.privErrors.permissionDeniedConnect);
this.privManager.leave(this.privProperties, this.privRoom.token,
(() => {
this.handleCallback(cb, err);
@ -399,9 +402,9 @@ export class ConversationImpl extends Conversation implements IDisposable {
try {
Contracts.throwIfDisposed(this.privIsDisposed);
Contracts.throwIfDisposed(this.privConversationRecognizer.isDisposed());
Contracts.throwIfNullOrUndefined(this.privRoom, ConversationTranslatorConfig.strings.permissionDeniedSend);
Contracts.throwIfNullOrUndefined(this.privRoom, this.privErrors.permissionDeniedSend);
if (!this.canSendAsHost) {
this.handleError(new Error(ConversationTranslatorConfig.strings.permissionDeniedConversation.replace("{command}", "lock")), err);
this.handleError(new Error(this.privErrors.permissionDeniedConversation.replace("{command}", "lock")), err);
}
this.privConversationRecognizer?.sendLockRequest(true,
(() => {
@ -425,11 +428,11 @@ export class ConversationImpl extends Conversation implements IDisposable {
try {
Contracts.throwIfDisposed(this.privIsDisposed);
Contracts.throwIfDisposed(this.privConversationRecognizer.isDisposed());
Contracts.throwIfNullOrUndefined(this.privConversationRecognizer, ConversationTranslatorConfig.strings.permissionDeniedSend);
Contracts.throwIfNullOrUndefined(this.privRoom, ConversationTranslatorConfig.strings.permissionDeniedSend);
Contracts.throwIfNullOrUndefined(this.privConversationRecognizer, this.privErrors.permissionDeniedSend);
Contracts.throwIfNullOrUndefined(this.privRoom, this.privErrors.permissionDeniedSend);
// check the user's permissions
if (!this.canSendAsHost) {
this.handleError(new Error(ConversationTranslatorConfig.strings.permissionDeniedConversation.replace("{command}", "mute")), err);
this.handleError(new Error(this.privErrors.permissionDeniedConversation.replace("{command}", "mute")), err);
}
this.privConversationRecognizer?.sendMuteAllRequest(true,
(() => {
@ -453,20 +456,20 @@ export class ConversationImpl extends Conversation implements IDisposable {
try {
Contracts.throwIfDisposed(this.privIsDisposed);
Contracts.throwIfDisposed(this.privConversationRecognizer.isDisposed());
Contracts.throwIfNullOrWhitespace(userId, ConversationTranslatorConfig.strings.invalidArgs.replace("{arg}", "userId"));
Contracts.throwIfNullOrUndefined(this.privRoom, ConversationTranslatorConfig.strings.permissionDeniedSend);
Contracts.throwIfNullOrWhitespace(userId, this.privErrors.invalidArgs.replace("{arg}", "userId"));
Contracts.throwIfNullOrUndefined(this.privRoom, this.privErrors.permissionDeniedSend);
// check the connection is open (host + participant can perform the mute command)
if (!this.canSend) {
this.handleError(new Error(ConversationTranslatorConfig.strings.permissionDeniedSend), err);
this.handleError(new Error(this.privErrors.permissionDeniedSend), err);
}
// if not host, check the participant is not muting another participant
if (!this.me.isHost && this.me.id !== userId) {
this.handleError(new Error(ConversationTranslatorConfig.strings.permissionDeniedParticipant.replace("{command}", "mute")), err);
this.handleError(new Error(this.privErrors.permissionDeniedParticipant.replace("{command}", "mute")), err);
}
// check the user exists
const exists: number = this.privParticipants.getParticipantIndex(userId);
if (exists === -1) {
this.handleError(new Error(ConversationTranslatorConfig.strings.invalidParticipantRequest), err);
this.handleError(new Error(this.privErrors.invalidParticipantRequest), err);
}
this.privConversationRecognizer?.sendMuteRequest(userId, true, (() => {
this.handleCallback(cb, err);
@ -489,9 +492,9 @@ export class ConversationImpl extends Conversation implements IDisposable {
try {
Contracts.throwIfDisposed(this.privIsDisposed);
Contracts.throwIfDisposed(this.privConversationRecognizer.isDisposed());
Contracts.throwIfNullOrUndefined(this.privRoom, ConversationTranslatorConfig.strings.permissionDeniedSend);
Contracts.throwIfNullOrUndefined(this.privRoom, this.privErrors.permissionDeniedSend);
if (!this.canSendAsHost) {
this.handleError(new Error(ConversationTranslatorConfig.strings.permissionDeniedParticipant.replace("{command}", "remove")), err);
this.handleError(new Error(this.privErrors.permissionDeniedParticipant.replace("{command}", "remove")), err);
}
let participantId: string = "";
if (typeof userId === "string") {
@ -503,11 +506,11 @@ export class ConversationImpl extends Conversation implements IDisposable {
const user: IUser = userId as IUser;
participantId = user.userId;
}
Contracts.throwIfNullOrWhitespace(participantId, ConversationTranslatorConfig.strings.invalidArgs.replace("{arg}", "userId"));
Contracts.throwIfNullOrWhitespace(participantId, this.privErrors.invalidArgs.replace("{arg}", "userId"));
// check the participant exists
const index: number = this.participants.findIndex((p: Participant) => p.id === participantId);
if (index === -1) {
this.handleError(new Error(ConversationTranslatorConfig.strings.invalidParticipantRequest), err);
this.handleError(new Error(this.privErrors.invalidParticipantRequest), err);
}
this.privConversationRecognizer?.sendEjectRequest(participantId, (() => {
this.handleCallback(cb, err);
@ -529,9 +532,9 @@ export class ConversationImpl extends Conversation implements IDisposable {
try {
Contracts.throwIfDisposed(this.privIsDisposed);
Contracts.throwIfDisposed(this.privConversationRecognizer.isDisposed());
Contracts.throwIfNullOrUndefined(this.privRoom, ConversationTranslatorConfig.strings.permissionDeniedSend);
Contracts.throwIfNullOrUndefined(this.privRoom, this.privErrors.permissionDeniedSend);
if (!this.canSendAsHost) {
this.handleError(new Error(ConversationTranslatorConfig.strings.permissionDeniedConversation.replace("{command}", "unlock")), err);
this.handleError(new Error(this.privErrors.permissionDeniedConversation.replace("{command}", "unlock")), err);
}
this.privConversationRecognizer?.sendLockRequest(false, (() => {
this.handleCallback(cb, err);
@ -553,9 +556,9 @@ export class ConversationImpl extends Conversation implements IDisposable {
try {
Contracts.throwIfDisposed(this.privIsDisposed);
Contracts.throwIfDisposed(this.privConversationRecognizer.isDisposed());
Contracts.throwIfNullOrUndefined(this.privRoom, ConversationTranslatorConfig.strings.permissionDeniedSend);
Contracts.throwIfNullOrUndefined(this.privRoom, this.privErrors.permissionDeniedSend);
if (!this.canSendAsHost) {
this.handleError(new Error(ConversationTranslatorConfig.strings.permissionDeniedConversation.replace("{command}", "unmute all")), err);
this.handleError(new Error(this.privErrors.permissionDeniedConversation.replace("{command}", "unmute all")), err);
}
this.privConversationRecognizer?.sendMuteAllRequest(false, (() => {
this.handleCallback(cb, err);
@ -578,20 +581,20 @@ export class ConversationImpl extends Conversation implements IDisposable {
try {
Contracts.throwIfDisposed(this.privIsDisposed);
Contracts.throwIfDisposed(this.privConversationRecognizer.isDisposed());
Contracts.throwIfNullOrWhitespace(userId, ConversationTranslatorConfig.strings.invalidArgs.replace("{arg}", "userId"));
Contracts.throwIfNullOrUndefined(this.privRoom, ConversationTranslatorConfig.strings.permissionDeniedSend);
Contracts.throwIfNullOrWhitespace(userId, this.privErrors.invalidArgs.replace("{arg}", "userId"));
Contracts.throwIfNullOrUndefined(this.privRoom, this.privErrors.permissionDeniedSend);
// check the connection is open (host + participant can perform the mute command)
if (!this.canSend) {
this.handleError(new Error(ConversationTranslatorConfig.strings.permissionDeniedSend), err);
this.handleError(new Error(this.privErrors.permissionDeniedSend), err);
}
// if not host, check the participant is not muting another participant
if (!this.me.isHost && this.me.id !== userId) {
this.handleError(new Error(ConversationTranslatorConfig.strings.permissionDeniedParticipant.replace("{command}", "mute")), err);
this.handleError(new Error(this.privErrors.permissionDeniedParticipant.replace("{command}", "mute")), err);
}
// check the user exists
const exists: number = this.privParticipants.getParticipantIndex(userId);
if (exists === -1) {
this.handleError(new Error(ConversationTranslatorConfig.strings.invalidParticipantRequest), err);
this.handleError(new Error(this.privErrors.invalidParticipantRequest), err);
}
this.privConversationRecognizer?.sendMuteRequest(userId, false, (() => {
this.handleCallback(cb, err);
@ -614,14 +617,14 @@ export class ConversationImpl extends Conversation implements IDisposable {
try {
Contracts.throwIfDisposed(this.privIsDisposed);
Contracts.throwIfDisposed(this.privConversationRecognizer.isDisposed());
Contracts.throwIfNullOrWhitespace(message, ConversationTranslatorConfig.strings.invalidArgs.replace("{arg}", "message"));
Contracts.throwIfNullOrUndefined(this.privRoom, ConversationTranslatorConfig.strings.permissionDeniedSend);
Contracts.throwIfNullOrWhitespace(message, this.privErrors.invalidArgs.replace("{arg}", "message"));
Contracts.throwIfNullOrUndefined(this.privRoom, this.privErrors.permissionDeniedSend);
if (!this.canSend) {
this.handleError(new Error(ConversationTranslatorConfig.strings.permissionDeniedSend), err);
this.handleError(new Error(this.privErrors.permissionDeniedSend), err);
}
// TODO: is a max length check required?
if (message.length > ConversationTranslatorConfig.textMessageMaxLength) {
this.handleError(new Error(ConversationTranslatorConfig.strings.invalidArgs.replace("{arg}", "message length")), err);
if (message.length > this.privTextMessageMaxLength) {
this.handleError(new Error(this.privErrors.invalidArgs.replace("{arg}", "message length")), err);
}
this.privConversationRecognizer?.sendMessageRequest(message, (() => {
this.handleCallback(cb, err);
@ -644,10 +647,10 @@ export class ConversationImpl extends Conversation implements IDisposable {
try {
Contracts.throwIfDisposed(this.privIsDisposed);
Contracts.throwIfDisposed(this.privConversationRecognizer.isDisposed());
Contracts.throwIfNullOrWhitespace(nickname, ConversationTranslatorConfig.strings.invalidArgs.replace("{arg}", "nickname"));
Contracts.throwIfNullOrUndefined(this.privRoom, ConversationTranslatorConfig.strings.permissionDeniedSend);
Contracts.throwIfNullOrWhitespace(nickname, this.privErrors.invalidArgs.replace("{arg}", "nickname"));
Contracts.throwIfNullOrUndefined(this.privRoom, this.privErrors.permissionDeniedSend);
if (!this.canSend) {
this.handleError(new Error(ConversationTranslatorConfig.strings.permissionDeniedSend), err);
this.handleError(new Error(this.privErrors.permissionDeniedSend), err);
}
this.privConversationRecognizer?.sendChangeNicknameRequest(nickname, (() => {
this.handleCallback(cb, err);

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

@ -2,8 +2,8 @@
// Licensed under the MIT license.
// Multi-device Conversation is a Preview feature.
import { ConversationTranslatorConfig } from "../../common.speech/Exports";
import { IDisposable } from "../../common/Exports";
import { ConversationConnectionConfig } from "../../common.speech/Exports";
import { IDisposable, IErrorMessages } from "../../common/Exports";
import { Contracts } from "../Contracts";
import {
AudioConfig,
@ -51,6 +51,9 @@ export class ConversationTranslator implements IConversationTranslator, IDisposa
private privIsSpeaking: boolean = false;
private privConversation: ConversationImpl;
private privSpeechState: SpeechState = SpeechState.Inactive;
private privErrors: IErrorMessages = ConversationConnectionConfig.restErrors;
private privPlaceholderKey: string = "abcdefghijklmnopqrstuvwxyz012345";
private privPlaceholderRegion: string = "westus";
public constructor(audioConfig?: AudioConfig) {
this.privProperties = new PropertyCollection();
@ -94,20 +97,20 @@ export class ConversationTranslator implements IConversationTranslator, IDisposa
if (typeof conversation === "string") {
Contracts.throwIfNullOrUndefined(conversation, ConversationTranslatorConfig.strings.invalidArgs.replace("{arg}", "conversation id"));
Contracts.throwIfNullOrWhitespace(nickname, ConversationTranslatorConfig.strings.invalidArgs.replace("{arg}", "nickname"));
Contracts.throwIfNullOrUndefined(conversation, this.privErrors.invalidArgs.replace("{arg}", "conversation id"));
Contracts.throwIfNullOrWhitespace(nickname, this.privErrors.invalidArgs.replace("{arg}", "nickname"));
if (!!this.privConversation) {
this.handleError(new Error(ConversationTranslatorConfig.strings.permissionDeniedStart), param3);
this.handleError(new Error(this.privErrors.permissionDeniedStart), param3);
}
let lang: string = param1 as string;
if (lang === undefined || lang === null || lang === "") { lang = ConversationTranslatorConfig.defaultLanguageCode; }
if (lang === undefined || lang === null || lang === "") { lang = ConversationConnectionConfig.defaultLanguageCode; }
// create a placecholder config
this.privSpeechTranslationConfig = SpeechTranslationConfig.fromSubscription(
ConversationTranslatorConfig.auth.placeholderSubscriptionKey,
ConversationTranslatorConfig.auth.placeholderRegion);
this.privPlaceholderKey,
this.privPlaceholderRegion);
this.privSpeechTranslationConfig.setProfanity(ProfanityOption.Masked);
this.privSpeechTranslationConfig.addTargetLanguage(lang);
this.privSpeechTranslationConfig.setProperty(PropertyId[PropertyId.SpeechServiceConnection_RecoLanguage], lang);
@ -133,7 +136,7 @@ export class ConversationTranslator implements IConversationTranslator, IDisposa
((result: string) => {
if (!result) {
this.handleError(new Error(ConversationTranslatorConfig.strings.permissionDeniedConnect), param3);
this.handleError(new Error(this.privErrors.permissionDeniedConnect), param3);
}
this.privSpeechTranslationConfig.authorizationToken = result;
@ -154,8 +157,8 @@ export class ConversationTranslator implements IConversationTranslator, IDisposa
} else if (typeof conversation === "object") {
Contracts.throwIfNullOrUndefined(conversation, ConversationTranslatorConfig.strings.invalidArgs.replace("{arg}", "conversation id"));
Contracts.throwIfNullOrWhitespace(nickname, ConversationTranslatorConfig.strings.invalidArgs.replace("{arg}", "nickname"));
Contracts.throwIfNullOrUndefined(conversation, this.privErrors.invalidArgs.replace("{arg}", "conversation id"));
Contracts.throwIfNullOrWhitespace(nickname, this.privErrors.invalidArgs.replace("{arg}", "nickname"));
// save the nickname
this.privProperties.setProperty(PropertyId.ConversationTranslator_Name, nickname);
@ -164,15 +167,15 @@ export class ConversationTranslator implements IConversationTranslator, IDisposa
// ref the conversation translator object
this.privConversation.conversationTranslator = this;
Contracts.throwIfNullOrUndefined(this.privConversation, ConversationTranslatorConfig.strings.permissionDeniedConnect);
Contracts.throwIfNullOrUndefined(this.privConversation.room.token, ConversationTranslatorConfig.strings.permissionDeniedConnect);
Contracts.throwIfNullOrUndefined(this.privConversation, this.privErrors.permissionDeniedConnect);
Contracts.throwIfNullOrUndefined(this.privConversation.room.token, this.privErrors.permissionDeniedConnect);
this.privSpeechTranslationConfig = conversation.config;
this.handleCallback(param1 as Callback, param2);
} else {
this.handleError(
new Error(ConversationTranslatorConfig.strings.invalidArgs.replace("{arg}", "invalid conversation type")),
new Error(this.privErrors.invalidArgs.replace("{arg}", "invalid conversation type")),
param2);
}
@ -224,8 +227,8 @@ export class ConversationTranslator implements IConversationTranslator, IDisposa
public sendTextMessageAsync(message: string, cb?: Callback, err?: Callback): void {
try {
Contracts.throwIfNullOrUndefined(this.privConversation, ConversationTranslatorConfig.strings.permissionDeniedSend);
Contracts.throwIfNullOrWhitespace(message, ConversationTranslatorConfig.strings.invalidArgs.replace("{arg}", message));
Contracts.throwIfNullOrUndefined(this.privConversation, this.privErrors.permissionDeniedSend);
Contracts.throwIfNullOrWhitespace(message, this.privErrors.invalidArgs.replace("{arg}", message));
this.privConversation?.sendTextMessageAsync(message, cb, err);
} catch (error) {
@ -242,11 +245,11 @@ export class ConversationTranslator implements IConversationTranslator, IDisposa
public startTranscribingAsync(cb?: Callback, err?: Callback): void {
try {
Contracts.throwIfNullOrUndefined(this.privConversation, ConversationTranslatorConfig.strings.permissionDeniedSend);
Contracts.throwIfNullOrUndefined(this.privConversation.room.token, ConversationTranslatorConfig.strings.permissionDeniedConnect);
Contracts.throwIfNullOrUndefined(this.privConversation, this.privErrors.permissionDeniedSend);
Contracts.throwIfNullOrUndefined(this.privConversation.room.token, this.privErrors.permissionDeniedConnect);
if (!this.canSpeak) {
this.handleError(new Error(ConversationTranslatorConfig.strings.permissionDeniedSend), err);
this.handleError(new Error(this.privErrors.permissionDeniedSend), err);
}
if (this.privTranslationRecognizer === undefined) {
@ -359,7 +362,7 @@ export class ConversationTranslator implements IConversationTranslator, IDisposa
// clear the temp subscription key if it's a participant joining
if (this.privSpeechTranslationConfig.getProperty(PropertyId[PropertyId.SpeechServiceConnection_Key])
=== ConversationTranslatorConfig.auth.placeholderSubscriptionKey) {
=== this.privPlaceholderKey) {
this.privSpeechTranslationConfig.setProperty(PropertyId[PropertyId.SpeechServiceConnection_Key], "");
}
@ -367,10 +370,10 @@ export class ConversationTranslator implements IConversationTranslator, IDisposa
const token: string = encodeURIComponent(this.privConversation.room.token);
let endpointHost: string = this.privSpeechTranslationConfig.getProperty(
PropertyId[PropertyId.SpeechServiceConnection_Host], ConversationTranslatorConfig.speechHost);
PropertyId[PropertyId.SpeechServiceConnection_Host], ConversationConnectionConfig.speechHost);
endpointHost = endpointHost.replace("{region}", this.privConversation.room.cognitiveSpeechRegion);
const url: string = `wss://${endpointHost}${ConversationTranslatorConfig.speechPath}?${ConversationTranslatorConfig.params.token}=${token}`;
const url: string = `wss://${endpointHost}${ConversationConnectionConfig.speechPath}?${ConversationConnectionConfig.configParams.token}=${token}`;
this.privSpeechTranslationConfig.setProperty(PropertyId[PropertyId.SpeechServiceConnection_Endpoint], url);

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

@ -0,0 +1,47 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { VoiceProfileType } from "./Exports";
/**
* Defines Voice Profile class for Speaker Recognition
* @class VoiceProfile
*/
export class VoiceProfile {
private privId: string;
private privProfileType: VoiceProfileType;
/**
* Creates and initializes an instance of this class.
* @constructor
* @param {string} profileId - profileId of this Voice Profile.
* @param {VoiceProfileType} profileType - profileType of this Voice Profile.
*/
constructor(profileId: string, profileType: VoiceProfileType) {
this.privId = profileId;
this.privProfileType = profileType;
}
/**
* profileId of this Voice Profile instance
* @member VoiceProfile.prototype.profileId
* @function
* @public
* @returns {string} profileId of this Voice Profile instance.
*/
public get profileId(): string {
return this.privId;
}
/**
* profileType of this Voice Profile instance
* @member VoiceProfile.prototype.profileType
* @function
* @public
* @returns {VoiceProfileType} profile type of this Voice Profile instance.
*/
public get profileType(): VoiceProfileType {
return this.privProfileType;
}
}

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

@ -0,0 +1,222 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import {
FileAudioSource,
IRestResponse,
} from "../common.browser/Exports";
import {
Context,
OS,
SpeakerIdMessageAdapter,
SpeakerRecognitionConfig,
} from "../common.speech/Exports";
import { IAudioSource, PromiseResult } from "../common/Exports";
import { AudioConfig, AudioConfigImpl } from "./audio/AudioConfig";
import { Contracts } from "./Contracts";
import {
PropertyCollection,
PropertyId,
ResultReason,
VoiceProfile,
VoiceProfileEnrollmentResult,
VoiceProfileResult,
VoiceProfileType,
} from "./Exports";
import { SpeechConfig, SpeechConfigImpl } from "./SpeechConfig";
/**
* Defines VoiceProfileClient class for Speaker Recognition
* Handles operations from user for Voice Profile operations (e.g. createProfile, deleteProfile)
* @class VoiceProfileClient
*/
export class VoiceProfileClient {
protected privProperties: PropertyCollection;
private privAdapter: SpeakerIdMessageAdapter;
/**
* Gets the authorization token used to communicate with the service.
* @member VoiceProfileClient.prototype.authorizationToken
* @function
* @public
* @returns {string} Authorization token.
*/
public get authorizationToken(): string {
return this.properties.getProperty(PropertyId.SpeechServiceAuthorization_Token);
}
/**
* Gets/Sets the authorization token used to communicate with the service.
* @member VoiceProfileClient.prototype.authorizationToken
* @function
* @public
* @param {string} token - Authorization token.
*/
public set authorizationToken(token: string) {
Contracts.throwIfNullOrWhitespace(token, "token");
this.properties.setProperty(PropertyId.SpeechServiceAuthorization_Token, token);
}
/**
* The collection of properties and their values defined for this VoiceProfileClient.
* @member VoiceProfileClient.prototype.properties
* @function
* @public
* @returns {PropertyCollection} The collection of properties and their values defined for this VoiceProfileClient.
*/
public get properties(): PropertyCollection {
return this.privProperties;
}
/**
* VoiceProfileClient constructor.
* @constructor
* @param {SpeechConfig} speechConfig - An set of initial properties for this synthesizer (authentication key, region, &c)
*/
public constructor(speechConfig: SpeechConfig) {
const speechConfigImpl: SpeechConfigImpl = speechConfig as SpeechConfigImpl;
Contracts.throwIfNull(speechConfigImpl, "speechConfig");
this.privProperties = speechConfigImpl.properties.clone();
this.implClientSetup();
}
/**
* Create a speaker recognition voice profile
* @member VoiceProfileClient.prototype.createProfileAsync
* @function
* @public
* @param {VoiceProfileType} profileType Type of Voice Profile to be created
* specifies the keyword to be recognized.
* @param {string} lang Language string (locale) for Voice Profile
* @param cb - Callback invoked once Voice Profile has been created.
* @param err - Callback invoked in case of an error.
*/
public createProfileAsync(profileType: VoiceProfileType, lang: string, cb?: (e: VoiceProfile) => void, err?: (e: string) => void): void {
this.privAdapter.createProfile(profileType, lang).on((result: IRestResponse) => {
if (!!cb) {
const response: { profileId: string } = result.json();
const profile = new VoiceProfile(response.profileId, profileType);
cb(profile);
}
},
(error: string) => {
if (!!err) {
err(error);
}
});
}
/**
* Create a speaker recognition voice profile
* @member VoiceProfileClient.prototype.enrollProfileAsync
* @function
* @public
* @param {VoiceProfile} profile Voice Profile to create enrollment for
* @param {AudioConfig} audioConfig source info from which to create enrollment
* @param cb - Callback invoked once Enrollment request has been submitted.
* @param err - Callback invoked in case of an error.
*/
public enrollProfileAsync(profile: VoiceProfile, audioConfig: AudioConfig, cb?: (e: VoiceProfileEnrollmentResult) => void, err?: (e: string) => void): void {
const configImpl: AudioConfigImpl = audioConfig as AudioConfigImpl;
Contracts.throwIfNullOrUndefined(configImpl, "audioConfig");
this.privAdapter.createEnrollment(profile, configImpl).on((result: IRestResponse) => {
if (!!cb) {
cb(
new VoiceProfileEnrollmentResult(
result.ok ? ResultReason.EnrolledVoiceProfile : ResultReason.Canceled,
result.data,
result.statusText,
)
);
}
},
(error: string) => {
if (!!err) {
err(error);
}
});
}
/**
* Delete a speaker recognition voice profile
* @member VoiceProfileClient.prototype.deleteProfileAsync
* @function
* @public
* @param {VoiceProfile} profile Voice Profile to be deleted
* @param cb - Callback invoked once Voice Profile has been deleted.
* @param err - Callback invoked in case of an error.
*/
public deleteProfileAsync(profile: VoiceProfile, cb?: (response: VoiceProfileResult) => void, err?: (e: string) => void): void {
this.privAdapter.deleteProfile(profile).on((result: IRestResponse) => {
this.handleResultCallbacks(result, ResultReason.DeletedVoiceProfile, cb);
},
(error: string) => {
if (!!err) {
err(error);
}
});
}
/**
* Remove all enrollments for a speaker recognition voice profile
* @member VoiceProfileClient.prototype.resetProfileAsync
* @function
* @public
* @param {VoiceProfile} profile Voice Profile to be reset
* @param cb - Callback invoked once Voice Profile has been reset.
* @param err - Callback invoked in case of an error.
*/
public resetProfileAsync(profile: VoiceProfile, cb?: (response: VoiceProfileResult) => void, err?: (e: string) => void): void {
this.privAdapter.resetProfile(profile).on((result: IRestResponse) => {
this.handleResultCallbacks(result, ResultReason.ResetVoiceProfile, cb);
},
(error: string) => {
if (!!err) {
err(error);
}
});
}
/**
* Included for compatibility
* @member VoiceProfileClient.prototype.close
* @function
* @public
*/
public close(): void {
return;
}
// Does class setup, swiped from Recognizer.
protected implClientSetup(): void {
let osPlatform = (typeof window !== "undefined") ? "Browser" : "Node";
let osName = "unknown";
let osVersion = "unknown";
if (typeof navigator !== "undefined") {
osPlatform = osPlatform + "/" + navigator.platform;
osName = navigator.userAgent;
osVersion = navigator.appVersion;
}
const recognizerConfig =
new SpeakerRecognitionConfig(
new Context(new OS(osPlatform, osName, osVersion)),
this.privProperties);
this.privAdapter = new SpeakerIdMessageAdapter(recognizerConfig);
}
private handleResultCallbacks(result: IRestResponse, successReason: ResultReason, cb?: (response: VoiceProfileResult) => void): void {
if (!!cb) {
const response: VoiceProfileResult =
new VoiceProfileResult(
result.ok ? successReason : ResultReason.Canceled,
result.statusText
);
cb(response);
}
}
}

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

@ -0,0 +1,100 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { CancellationErrorCodePropertyName } from "../common.speech/Exports";
import { Contracts } from "./Contracts";
import {
CancellationDetailsBase,
CancellationErrorCode,
CancellationReason,
PropertyCollection,
PropertyId,
ResultReason,
} from "./Exports";
export interface IEnrollmentResultDetails {
enrollmentsCount: number;
enrollmentsLength: number;
enrollmentsSpeechLength: number;
remainingEnrollmentsCount: number;
remainingEnrollmentsSpeechLength: number;
audioLength: number;
audioSpeechLength: number;
enrollmentStatus: string;
}
/**
* Output format
* @class VoiceProfileEnrollmentResult
*/
export class VoiceProfileEnrollmentResult {
private privReason: ResultReason;
private privDetails: IEnrollmentResultDetails;
private privProperties: PropertyCollection;
private privErrorDetails: string;
public constructor(reason: ResultReason, json: string, statusText: string) {
this.privReason = reason;
this.privProperties = new PropertyCollection();
if (this.privReason !== ResultReason.Canceled) {
this.privDetails = JSON.parse(json);
Contracts.throwIfNullOrUndefined(json, "JSON");
if (this.privDetails.enrollmentStatus.toLowerCase() === "enrolling") {
this.privReason = ResultReason.EnrollingVoiceProfile;
}
} else {
this.privErrorDetails = statusText;
this.privProperties.setProperty(CancellationErrorCodePropertyName, CancellationErrorCode[CancellationErrorCode.ServiceError]);
}
}
public get reason(): ResultReason {
return this.privReason;
}
public get enrollmentsCount(): number {
return this.privDetails.enrollmentsCount;
}
public get enrollmentsLength(): number {
return this.privDetails.enrollmentsLength;
}
public get properties(): PropertyCollection {
return this.privProperties;
}
public get errorDetails(): string {
return this.privErrorDetails;
}
}
/**
* @class VoiceProfileEnrollmentCancellationDetails
*/
// tslint:disable-next-line:max-classes-per-file
export class VoiceProfileEnrollmentCancellationDetails extends CancellationDetailsBase {
private constructor(reason: CancellationReason, errorDetails: string, errorCode: CancellationErrorCode) {
super(reason, errorDetails, errorCode);
}
/**
* Creates an instance of VoiceProfileEnrollmentCancellationDetails object for the canceled VoiceProfileEnrollmentResult.
* @member VoiceProfileEnrollmentCancellationDetails.fromResult
* @function
* @public
* @param {VoiceProfileEnrollmentResult} result - The result that was canceled.
* @returns {VoiceProfileEnrollmentCancellationDetails} The cancellation details object being created.
*/
public static fromResult(result: VoiceProfileEnrollmentResult): VoiceProfileEnrollmentCancellationDetails {
const reason = CancellationReason.Error;
let errorCode: CancellationErrorCode = CancellationErrorCode.NoError;
if (!!result.properties) {
errorCode = (CancellationErrorCode as any)[result.properties.getProperty(CancellationErrorCodePropertyName, CancellationErrorCode[CancellationErrorCode.NoError])];
}
return new VoiceProfileEnrollmentCancellationDetails(reason, result.errorDetails, errorCode);
}
}

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

@ -0,0 +1,74 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { CancellationErrorCodePropertyName } from "../common.speech/Exports";
import { Contracts } from "./Contracts";
import {
CancellationDetailsBase,
CancellationErrorCode,
CancellationReason,
PropertyCollection,
ResultReason,
} from "./Exports";
/**
* Output format
* @class VoiceProfileResult
*/
export class VoiceProfileResult {
private privReason: ResultReason;
private privProperties: PropertyCollection;
private privErrorDetails: string;
public constructor(reason: ResultReason, statusText: string) {
this.privReason = reason;
this.privProperties = new PropertyCollection();
if (reason === ResultReason.Canceled) {
Contracts.throwIfNullOrUndefined(statusText, "statusText");
this.privErrorDetails = statusText;
this.privProperties.setProperty(CancellationErrorCodePropertyName, CancellationErrorCode[CancellationErrorCode.ServiceError]);
}
}
public get reason(): ResultReason {
return this.privReason;
}
public get properties(): PropertyCollection {
return this.privProperties;
}
public get errorDetails(): string {
return this.privErrorDetails;
}
}
/**
* @class VoiceProfileCancellationDetails
*/
// tslint:disable-next-line:max-classes-per-file
export class VoiceProfileCancellationDetails extends CancellationDetailsBase {
private constructor(reason: CancellationReason, errorDetails: string, errorCode: CancellationErrorCode) {
super(reason, errorDetails, errorCode);
}
/**
* Creates an instance of VoiceProfileCancellationDetails object for the canceled VoiceProfileResult.
* @member VoiceProfileCancellationDetails.fromResult
* @function
* @public
* @param {VoiceProfileResult} result - The result that was canceled.
* @returns {VoiceProfileCancellationDetails} The cancellation details object being created.
*/
public static fromResult(result: VoiceProfileResult): VoiceProfileCancellationDetails {
const reason = CancellationReason.Error;
let errorCode: CancellationErrorCode = CancellationErrorCode.NoError;
if (!!result.properties) {
errorCode = (CancellationErrorCode as any)[result.properties.getProperty(CancellationErrorCodePropertyName, CancellationErrorCode[CancellationErrorCode.NoError])];
}
return new VoiceProfileCancellationDetails(reason, result.errorDetails, errorCode);
}
}

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

@ -0,0 +1,27 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
/**
* Output format
* @class VoiceProfileType
*/
export enum VoiceProfileType {
/**
* Text independent speaker identification
* @member VoiceProfileType.TextIndependentIdentification
*/
TextIndependentIdentification,
/**
* Text dependent speaker verification
* @member VoiceProfileType.TextDependentVerification
*/
TextDependentVerification,
/**
* Text independent speaker verification
* @member VoiceProfileType.TextIndependentVerification
*/
TextIndependentVerification,
}

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

@ -39,6 +39,21 @@ test("testRecognizer2", () => {
r.close();
s.close();
});
test("testRecognizer3", () => {
const s = sdk.SpeechConfig.fromSubscription(Settings.SpeechSubscriptionKey, Settings.SpeechRegion);
expect(s).not.toBeUndefined();
const f: File = WaveFileAudioInput.LoadFile(Settings.WaveFile);
const config: sdk.AudioConfig = sdk.AudioConfig.fromWavFileInput(f);
const r = new sdk.SpeakerRecognizer(s, config);
expect(r).not.toBeUndefined();
expect(r instanceof sdk.SpeakerRecognizer);
r.close();
s.close();
});
/*
// TODO does not work with microphone
test.skip("testRecognizer3", () => {

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

@ -29,6 +29,9 @@ export class Settings {
public static BotSubscription: string = "<<YOUR_BOT_SUBSCRIPTION>>";
public static BotRegion: string = "<<YOUR_BOT_REGION>>";
public static SpeakerIDSubscriptionKey: string = "<<YOUR_SPEAKER_ID_SUBSCRIPTION_KEY>>";
public static SpeakerIDRegion: string = "<<YOUR_SPEAKER_ID_REGION>>";
public static InputDir: string = "tests/input/audio/";
public static ExecuteLongRunningTests: string = "false";
@ -66,6 +69,14 @@ export class Settings {
public static AmbiguousWaveFile: string = Settings.InputDir + "wreck-a-nice-beach.wav";
public static IndependentIdentificationWaveFile: string = Settings.InputDir + "aboutSpeechSdk.wav";
public static DependentVerificationWaveFile: string = Settings.InputDir + "myVoiceIsMyPassportVerifyMe04.wav";
public static VerificationWaveFiles: string[] = [
Settings.InputDir + "myVoiceIsMyPassportVerifyMe01.wav",
Settings.InputDir + "myVoiceIsMyPassportVerifyMe02.wav",
Settings.InputDir + "myVoiceIsMyPassportVerifyMe03.wav",
];
public static ConversationTranslatorHost: string = "";
public static ConversationTranslatorSpeechHost: string = "";

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

@ -0,0 +1,348 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import * as fs from "fs";
import * as sdk from "../microsoft.cognitiveservices.speech.sdk";
import { ConsoleLoggingListener } from "../src/common.browser/Exports";
import {
Events,
EventType
} from "../src/common/Exports";
import { Settings } from "./Settings";
import { WaveFileAudioInput } from "./WaveFileAudioInputStream";
let objsToClose: any[];
beforeAll(() => {
// Override inputs, if necessary
Settings.LoadSettings();
Events.instance.attachListener(new ConsoleLoggingListener(EventType.Debug));
});
// Test cases are run linerally, still looking for a way to get the test name to print that doesn't mean changing each test.
beforeEach(() => {
objsToClose = [];
// tslint:disable-next-line:no-console
console.info("---------------------------------------Starting test case-----------------------------------");
// tslint:disable-next-line:no-console
console.info("Start Time: " + new Date(Date.now()).toLocaleString());
});
afterEach(() => {
// tslint:disable-next-line:no-console
console.info("End Time: " + new Date(Date.now()).toLocaleString());
objsToClose.forEach((value: any, index: number, array: any[]) => {
if (typeof value.close === "function") {
value.close();
}
});
});
const BuildClient: (speechConfig?: sdk.SpeechConfig) => sdk.VoiceProfileClient = (speechConfig?: sdk.SpeechConfig): sdk.VoiceProfileClient => {
let s: sdk.SpeechConfig = speechConfig;
if (s === undefined) {
s = BuildSpeechConfig();
// Since we're not going to return it, mark it for closure.
objsToClose.push(s);
}
const r: sdk.VoiceProfileClient = new sdk.VoiceProfileClient(s);
expect(r).not.toBeUndefined();
return r;
};
const BuildSpeechConfig: () => sdk.SpeechConfig = (): sdk.SpeechConfig => {
const s: sdk.SpeechConfig = sdk.SpeechConfig.fromSubscription(Settings.SpeakerIDSubscriptionKey, Settings.SpeakerIDRegion);
expect(s).not.toBeUndefined();
return s;
};
const BuildRecognizer: (speechConfig?: sdk.SpeechConfig) => sdk.SpeakerRecognizer = (speechConfig?: sdk.SpeechConfig): sdk.SpeakerRecognizer => {
let s: sdk.SpeechConfig = speechConfig;
if (s === undefined) {
s = BuildSpeechConfig();
// Since we're not going to return it, mark it for closure.
objsToClose.push(s);
}
const f: File = WaveFileAudioInput.LoadFile(Settings.DependentVerificationWaveFile);
const config: sdk.AudioConfig = sdk.AudioConfig.fromWavFileInput(f);
const r: sdk.SpeakerRecognizer = new sdk.SpeakerRecognizer(s, config);
expect(r).not.toBeUndefined();
return r;
};
test("VoiceProfileClient", () => {
// tslint:disable-next-line:no-console
console.info("Name: VoiceProfileClient");
const r: sdk.VoiceProfileClient = BuildClient();
objsToClose.push(r);
});
test("GetParameters", () => {
// tslint:disable-next-line:no-console
console.info("Name: GetParameters");
const r: sdk.VoiceProfileClient = BuildClient();
objsToClose.push(r);
expect(r.properties).not.toBeUndefined();
});
describe.each([true, false])("Service based tests", () => {
test("Create and Delete Voice Profile using push stream - Independent Identification", (done: jest.DoneCallback) => {
// tslint:disable-next-line:no-console
console.info("Name: Create and Delete Voice Profile using push stream - Independent Identification");
const s: sdk.SpeechConfig = BuildSpeechConfig();
objsToClose.push(s);
const r: sdk.VoiceProfileClient = BuildClient(s);
objsToClose.push(r);
const type: sdk.VoiceProfileType = sdk.VoiceProfileType.TextIndependentIdentification;
r.createProfileAsync(
type,
"en-us",
(res: sdk.VoiceProfile) => {
expect(res).not.toBeUndefined();
expect(res.profileId).not.toBeUndefined();
expect(res.profileType).not.toBeUndefined();
expect(res.profileType).toEqual(type);
// Create the push stream we need for the speech sdk.
const pushStream: sdk.PushAudioInputStream = sdk.AudioInputStream.createPushStream();
// Open the file and push it to the push stream.
fs.createReadStream(Settings.IndependentIdentificationWaveFile).on("data", (arrayBuffer: { buffer: ArrayBuffer }) => {
pushStream.write(arrayBuffer.buffer);
}).on("end", () => {
pushStream.close();
});
const config: sdk.AudioConfig = sdk.AudioConfig.fromStreamInput(pushStream);
r.enrollProfileAsync(
res,
config,
(enrollResult: sdk.VoiceProfileEnrollmentResult) => {
expect(enrollResult).not.toBeUndefined();
expect(enrollResult.reason).not.toBeUndefined();
expect(enrollResult.reason).toEqual(sdk.ResultReason.EnrolledVoiceProfile);
expect(enrollResult.enrollmentsCount).toEqual(1);
expect(() => sdk.SpeakerVerificationModel.fromProfile(res)).toThrow();
r.resetProfileAsync(
res,
(resetResult: sdk.VoiceProfileResult) => {
expect(resetResult).not.toBeUndefined();
expect(resetResult.reason).not.toBeUndefined();
expect(resetResult.reason).toEqual(sdk.ResultReason.ResetVoiceProfile);
r.deleteProfileAsync(
res,
(result: sdk.VoiceProfileResult) => {
expect(result).not.toBeUndefined();
expect(result.reason).toEqual(sdk.ResultReason.DeletedVoiceProfile);
done();
},
(error: string) => {
done.fail(error);
});
},
(error: string) => {
done.fail(error);
});
},
(error: string) => {
done.fail(error);
});
},
(error: string) => {
done.fail(error);
});
});
test("Create and Delete Voice Profile - Independent Identification", (done: jest.DoneCallback) => {
// tslint:disable-next-line:no-console
console.info("Name: Create and Delete Voice Profile - Independent Identification");
const s: sdk.SpeechConfig = BuildSpeechConfig();
objsToClose.push(s);
const r: sdk.VoiceProfileClient = BuildClient(s);
objsToClose.push(r);
const type: sdk.VoiceProfileType = sdk.VoiceProfileType.TextIndependentIdentification;
r.createProfileAsync(
type,
"en-us",
(res: sdk.VoiceProfile) => {
expect(res).not.toBeUndefined();
expect(res.profileId).not.toBeUndefined();
expect(res.profileType).not.toBeUndefined();
expect(res.profileType).toEqual(type);
const f: File = WaveFileAudioInput.LoadFile(Settings.IndependentIdentificationWaveFile);
const config: sdk.AudioConfig = sdk.AudioConfig.fromWavFileInput(f);
r.enrollProfileAsync(
res,
config,
(enrollResult: sdk.VoiceProfileEnrollmentResult) => {
expect(enrollResult).not.toBeUndefined();
expect(enrollResult.reason).not.toBeUndefined();
expect(enrollResult.reason).toEqual(sdk.ResultReason.EnrolledVoiceProfile);
expect(enrollResult.enrollmentsCount).toEqual(1);
expect(() => sdk.SpeakerVerificationModel.fromProfile(res)).toThrow();
r.resetProfileAsync(
res,
(resetResult: sdk.VoiceProfileResult) => {
expect(resetResult).not.toBeUndefined();
expect(resetResult.reason).not.toBeUndefined();
expect(resetResult.reason).toEqual(sdk.ResultReason.ResetVoiceProfile);
r.deleteProfileAsync(
res,
(result: sdk.VoiceProfileResult) => {
expect(result).not.toBeUndefined();
expect(result.reason).toEqual(sdk.ResultReason.DeletedVoiceProfile);
done();
},
(error: string) => {
done.fail(error);
});
},
(error: string) => {
done.fail(error);
});
},
(error: string) => {
done.fail(error);
});
},
(error: string) => {
done.fail(error);
});
});
test("Create and Delete Voice Profile - Independent Verification", (done: jest.DoneCallback) => {
// tslint:disable-next-line:no-console
console.info("Name: Create and Delete Voice Profile - Independent Verification");
const s: sdk.SpeechConfig = BuildSpeechConfig();
objsToClose.push(s);
const r: sdk.VoiceProfileClient = BuildClient(s);
objsToClose.push(r);
const type: sdk.VoiceProfileType = sdk.VoiceProfileType.TextIndependentVerification;
r.createProfileAsync(
type,
"en-us",
(res: sdk.VoiceProfile) => {
expect(res).not.toBeUndefined();
expect(res.profileId).not.toBeUndefined();
expect(res.profileType).not.toBeUndefined();
expect(res.profileType).toEqual(type);
expect(() => sdk.SpeakerIdentificationModel.fromProfiles([res])).toThrow();
r.deleteProfileAsync(
res,
(result: sdk.VoiceProfileResult) => {
expect(result).not.toBeUndefined();
expect(result.reason).toEqual(sdk.ResultReason.DeletedVoiceProfile);
done();
},
(error: string) => {
done.fail(error);
});
},
(error: string) => {
done.fail(error);
});
});
test("Create and Delete Voice Profile - Dependent Verification", (done: jest.DoneCallback) => {
// tslint:disable-next-line:no-console
console.info("Name: Create and Delete Voice Profile - Dependent Verification");
const s: sdk.SpeechConfig = BuildSpeechConfig();
objsToClose.push(s);
const r: sdk.VoiceProfileClient = BuildClient(s);
objsToClose.push(r);
const type: sdk.VoiceProfileType = sdk.VoiceProfileType.TextDependentVerification;
r.createProfileAsync(
type,
"en-us",
(res: sdk.VoiceProfile) => {
expect(res).not.toBeUndefined();
expect(res.profileId).not.toBeUndefined();
expect(res.profileType).not.toBeUndefined();
expect(res.profileType).toEqual(type);
const configs: sdk.AudioConfig[] = [];
Settings.VerificationWaveFiles.forEach((file: string) => {
configs.push(sdk.AudioConfig.fromWavFileInput(WaveFileAudioInput.LoadFile(file)));
});
r.enrollProfileAsync(
res,
configs[0],
(enrollResult1: sdk.VoiceProfileEnrollmentResult) => {
expect(enrollResult1).not.toBeUndefined();
expect(enrollResult1.reason).not.toBeUndefined();
expect(enrollResult1.reason).toEqual(sdk.ResultReason.EnrollingVoiceProfile);
expect(enrollResult1.enrollmentsCount).toEqual(1);
r.enrollProfileAsync(
res,
configs[1],
(enrollResult2: sdk.VoiceProfileEnrollmentResult) => {
expect(enrollResult2).not.toBeUndefined();
expect(enrollResult2.reason).not.toBeUndefined();
expect(enrollResult2.reason).toEqual(sdk.ResultReason.EnrollingVoiceProfile);
expect(enrollResult2.enrollmentsCount).toEqual(2);
r.enrollProfileAsync(
res,
configs[2],
(enrollResult3: sdk.VoiceProfileEnrollmentResult) => {
expect(enrollResult3).not.toBeUndefined();
expect(enrollResult3.reason).not.toBeUndefined();
expect(enrollResult3.reason).toEqual(sdk.ResultReason.EnrolledVoiceProfile);
expect(enrollResult3.enrollmentsCount).toEqual(3);
const reco: sdk.SpeakerRecognizer = BuildRecognizer();
const m: sdk.SpeakerVerificationModel = sdk.SpeakerVerificationModel.fromProfile(res);
reco.recognizeOnceAsync(
m,
(recognizeResult: sdk.SpeakerRecognitionResult) => {
expect(recognizeResult).not.toBeUndefined();
expect(recognizeResult.reason).not.toBeUndefined();
expect(recognizeResult.reason).toEqual(sdk.ResultReason.RecognizedSpeaker);
expect(recognizeResult.profileId).toEqual(res.profileId);
r.deleteProfileAsync(
res,
(result: sdk.VoiceProfileResult) => {
expect(result).not.toBeUndefined();
expect(result.reason).toEqual(sdk.ResultReason.DeletedVoiceProfile);
done();
},
(error: string) => {
done.fail(error);
});
},
(error: string) => {
done.fail(error);
});
},
(error: string) => {
done.fail(error);
});
},
(error: string) => {
done.fail(error);
});
},
(error: string) => {
done.fail(error);
});
},
(error: string) => {
done.fail(error);
});
});
});

Двоичные данные
tests/input/audio/aboutSpeechSdk.wav Normal file

Двоичный файл не отображается.

Двоичные данные
tests/input/audio/myVoiceIsMyPassportVerifyMe01.wav Normal file

Двоичный файл не отображается.

Двоичные данные
tests/input/audio/myVoiceIsMyPassportVerifyMe02.wav Normal file

Двоичный файл не отображается.

Двоичные данные
tests/input/audio/myVoiceIsMyPassportVerifyMe03.wav Normal file

Двоичный файл не отображается.

Двоичные данные
tests/input/audio/myVoiceIsMyPassportVerifyMe04.wav Normal file

Двоичный файл не отображается.