Glharper/speaker recognition (#192)
* 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:
Родитель
ac9eafbfb4
Коммит
db402ca8ca
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
Двоичный файл не отображается.
Двоичный файл не отображается.
Двоичный файл не отображается.
Двоичный файл не отображается.
Двоичный файл не отображается.
Загрузка…
Ссылка в новой задаче