‘process.env.WEB_PORT || 8080’ |
73 |
74 |
75 | ## Change recognition language
76 |
77 | To change the source recognition language, change the locale strings in `App.js` lines **32** and **66**, which sets the recognition language property on the `SpeechConfig` object.
78 |
79 | ```javascript
80 | speechConfig.speechRecognitionLanguage = 'en-US'
81 | ```
82 |
83 | For a full list of supported locales, see the [language support article](https://docs.microsoft.com/azure/cognitive-services/speech-service/language-support#speech-to-text).
84 |
85 | ## Speech-to-text from microphone
86 |
87 | To convert speech-to-text using a microphone, run the app and then click **Convert speech to text from your mic.**. This will prompt you for access to your microphone, and then listen for you to speak. The following function `sttFromMic` in `App.js` contains the implementation.
88 |
89 | ```javascript
90 | async sttFromMic() {
91 | const tokenObj = await getTokenOrRefresh();
92 | const speechConfig = speechsdk.SpeechConfig.fromAuthorizationToken(tokenObj.authToken, tokenObj.region);
93 | speechConfig.speechRecognitionLanguage = 'en-US';
94 |
95 | const audioConfig = speechsdk.AudioConfig.fromDefaultMicrophoneInput();
96 | const recognizer = new speechsdk.SpeechRecognizer(speechConfig, audioConfig);
97 |
98 | this.setState({
99 | displayText: 'speak into your microphone...'
100 | });
101 |
102 | recognizer.recognizeOnceAsync(result => {
103 | let displayText;
104 | if (result.reason === ResultReason.RecognizedSpeech) {
105 | displayText = `RECOGNIZED: Text=${result.text}`
106 | } else {
107 | displayText = 'ERROR: Speech was cancelled or could not be recognized. Ensure your microphone is working properly.';
108 | }
109 |
110 | this.setState({
111 | displayText: displayText
112 | });
113 | });
114 | }
115 | ```
116 |
117 | Running speech-to-text from a microphone is done by creating an `AudioConfig` object and using it with the recognizer.
118 |
119 | ```javascript
120 | const audioConfig = speechsdk.AudioConfig.fromDefaultMicrophoneInput();
121 | const recognizer = new speechsdk.SpeechRecognizer(speechConfig, audioConfig);
122 | ```
123 |
124 | ## Speech-to-text from file
125 |
126 | To convert speech-to-text from an audio file, run the app and then click **Convert speech to text from an audio file.**. This will open a file browser and allow you to select an audio file. The following function `fileChange` is bound to an event handler that detects the file change.
127 |
128 | ```javascript
129 | async fileChange(event) {
130 | const audioFile = event.target.files[0];
131 | console.log(audioFile);
132 | const fileInfo = audioFile.name + ` size=${audioFile.size} bytes `;
133 |
134 | this.setState({
135 | displayText: fileInfo
136 | });
137 |
138 | const tokenObj = await getTokenOrRefresh();
139 | const speechConfig = speechsdk.SpeechConfig.fromAuthorizationToken(tokenObj.authToken, tokenObj.region);
140 | speechConfig.speechRecognitionLanguage = 'en-US';
141 |
142 | const audioConfig = speechsdk.AudioConfig.fromWavFileInput(audioFile);
143 | const recognizer = new speechsdk.SpeechRecognizer(speechConfig, audioConfig);
144 |
145 | recognizer.recognizeOnceAsync(result => {
146 | let displayText;
147 | if (result.reason === ResultReason.RecognizedSpeech) {
148 | displayText = `RECOGNIZED: Text=${result.text}`
149 | } else {
150 | displayText = 'ERROR: Speech was cancelled or could not be recognized. Ensure your microphone is working properly.';
151 | }
152 |
153 | this.setState({
154 | displayText: fileInfo + displayText
155 | });
156 | });
157 | }
158 | ```
159 |
160 | You need the audio file as a JavaScript [`File`](https://developer.mozilla.org/en-US/docs/Web/API/File) object, so you can grab it directly off the event target using `const audioFile = event.target.files[0];`. Next, you use the file to create the `AudioConfig` and then pass it to the recognizer.
161 |
162 | ```javascript
163 | const audioConfig = speechsdk.AudioConfig.fromWavFileInput(audioFile);
164 | const recognizer = new speechsdk.SpeechRecognizer(speechConfig, audioConfig);
165 | ```
166 |
167 | ## Token exchange process
168 |
169 | This sample application shows an example design pattern for retrieving and managing tokens, a common task when using the Speech JavaScript SDK in a browser environment. A simple Express back-end is implemented in the same project under `server/index.js`, which abstracts the token retrieval process.
170 |
171 | The reason for this design is to prevent your speech key from being exposed on the front-end, since it can be used to make calls directly to your subscription. By using an ephemeral token, you are able to protect your speech key from being used directly. To get a token, you use the Speech REST API and make a call using your speech key and region. In the Express part of the app, this is implemented in `index.js` behind the endpoint `/api/get-speech-token`, which the front-end uses to get tokens.
172 |
173 | ```javascript
174 | app.get('/api/get-speech-token', async (req, res, next) => {
175 | res.setHeader('Content-Type', 'application/json');
176 | const speechKey = process.env.SPEECH_KEY;
177 | const speechRegion = process.env.SPEECH_REGION;
178 |
179 | if (speechKey === 'paste-your-speech-key-here' || speechRegion === 'paste-your-speech-region-here') {
180 | res.status(400).send('You forgot to add your speech key or region to the .env file.');
181 | } else {
182 | const headers = {
183 | headers: {
184 | 'Ocp-Apim-Subscription-Key': speechKey,
185 | 'Content-Type': 'application/x-www-form-urlencoded'
186 | }
187 | };
188 |
189 | try {
190 | const tokenResponse = await axios.post(`https://${speechRegion}.api.cognitive.microsoft.com/sts/v1.0/issueToken`, null, headers);
191 | res.send({ token: tokenResponse.data, region: speechRegion });
192 | } catch (err) {
193 | res.status(401).send('There was an error authorizing your speech key.');
194 | }
195 | }
196 | });
197 | ```
198 |
199 | In the request, you create a `Ocp-Apim-Subscription-Key` header, and pass your speech key as the value. Then you make a request to the **issueToken** endpoint for your region, and an authorization token is returned. In a production application, this endpoint returning the token should be *restricted by additional user authentication* whenever possible.
200 |
201 | On the front-end, `token_util.js` contains the helper function `getTokenOrRefresh` that is used to manage the refresh and retrieval process.
202 |
203 | ```javascript
204 | export async function getTokenOrRefresh() {
205 | const cookie = new Cookie();
206 | const speechToken = cookie.get('speech-token');
207 |
208 | if (speechToken === undefined) {
209 | try {
210 | const res = await axios.get('/api/get-speech-token');
211 | const token = res.data.token;
212 | const region = res.data.region;
213 | cookie.set('speech-token', region + ':' + token, {maxAge: 540, path: '/'});
214 |
215 | console.log('Token fetched from back-end: ' + token);
216 | return { authToken: token, region: region };
217 | } catch (err) {
218 | console.log(err.response.data);
219 | return { authToken: null, error: err.response.data };
220 | }
221 | } else {
222 | console.log('Token fetched from cookie: ' + speechToken);
223 | const idx = speechToken.indexOf(':');
224 | return { authToken: speechToken.slice(idx + 1), region: speechToken.slice(0, idx) };
225 | }
226 | }
227 | ```
228 |
229 | This function uses the `universal-cookie` library to store and retrieve the token from local storage. It first checks to see if there is an existing cookie, and in that case it returns the token without hitting the Express back-end. If there is no existing cookie for a token, it makes the call to `/api/get-speech-token` to fetch a new one. Since we need both the token and its corresponding region later, the cookie is stored in the format `token:region` and upon retrieval is spliced into each value.
230 |
231 | Tokens for the service expire after 10 minutes, so the sample uses the `maxAge` property of the cookie to act as a trigger for when a new token needs to be generated. It is reccommended to use 9 minutes as the expiry time to act as a buffer, so we set `maxAge` to **540 seconds**.
232 |
233 | In `App.js` you use `getTokenOrRefresh` in the functions for speech-to-text from a microphone, and from a file. Finally, use the `SpeechConfig.fromAuthorizationToken` function to create an auth context using the token.
234 |
235 | ```javascript
236 | const tokenObj = await getTokenOrRefresh();
237 | const speechConfig = speechsdk.SpeechConfig.fromAuthorizationToken(tokenObj.authToken, tokenObj.region);
238 | ```
239 |
240 | In many other Speech service samples, you will see the function `SpeechConfig.fromSubscription` used instead of `SpeechConfig.fromAuthorizationToken`, but by **avoiding the usage** of `fromSubscription` on the front-end, you prevent your speech subscription key from becoming exposed, and instead utilize the token authentication process. `fromSubscription` is safe to use in a Node.js environment, or in other Speech SDK programming languages when the call is made on a back-end, but it is best to avoid using in a browser-based JavaScript environment.
--------------------------------------------------------------------------------
/azure-speech-streaming-reactjs/common/images/sampleoutputrealtimetranscription.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amulchapla/CallCenterIntelligenceAzureAI/f5a6952db758d1f325ea1f112624d2135a7bc8f0/azure-speech-streaming-reactjs/common/images/sampleoutputrealtimetranscription.PNG
--------------------------------------------------------------------------------
/azure-speech-streaming-reactjs/common/images/speechstreamingdiagram.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amulchapla/CallCenterIntelligenceAzureAI/f5a6952db758d1f325ea1f112624d2135a7bc8f0/azure-speech-streaming-reactjs/common/images/speechstreamingdiagram.PNG
--------------------------------------------------------------------------------
/azure-speech-streaming-reactjs/front-end-ui/.env:
--------------------------------------------------------------------------------
1 | REACT_APP_PLATFORM=desktop
2 | REACT_APP_BACKEND_API="http://localhost:8080"
--------------------------------------------------------------------------------
/azure-speech-streaming-reactjs/front-end-ui/.env.desktop:
--------------------------------------------------------------------------------
1 | REACT_APP_PLATFORM=desktop
2 | REACT_APP_BACKEND_API="http://localhost:8080"
--------------------------------------------------------------------------------
/azure-speech-streaming-reactjs/front-end-ui/.env.hosted.microsoft:
--------------------------------------------------------------------------------
1 | REACT_APP_PLATFORM=hosted
2 | REACT_APP_BACKEND_API="https://speechexpressbackendamc.azurewebsites.net"
3 | REACT_APP_CLIENT_ID=6798e375-a31f-48b0-abcb-87f06d70d0b6
4 | REACT_APP_REDIRECT_URI=https://speechreactfrontendamc.azurewebsites.net/
5 | REACT_APP_POST_LOGOUT_REDIRECT_URI=http://localhost:3000/
6 | REACT_APP_TENANT_ID=72f988bf-86f1-41af-91ab-2d7cd011db47
--------------------------------------------------------------------------------
/azure-speech-streaming-reactjs/front-end-ui/.env.hosted.progger:
--------------------------------------------------------------------------------
1 | REACT_APP_PLATFORM=hosted
2 | REACT_APP_BACKEND_API="https://speechexpressbackendamc.azurewebsites.net"
3 | REACT_APP_CLIENT_ID=6798e375-a31f-48b0-abcb-87f06d70d0b6
4 | REACT_APP_REDIRECT_URI=http://localhost:3000/
5 | REACT_APP_POST_LOGOUT_REDIRECT_URI=http://localhost:3000/
6 | REACT_APP_TENANT_ID=6e8d4ac4-6168-4505-ae45-4206d841b472
--------------------------------------------------------------------------------
/azure-speech-streaming-reactjs/front-end-ui/.github/workflows/azure-static-web-apps-wonderful-wave-0f526c610.yml:
--------------------------------------------------------------------------------
1 | name: Azure Static Web Apps CI/CD
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | pull_request:
8 | types: [opened, synchronize, reopened, closed]
9 | branches:
10 | - master
11 |
12 | jobs:
13 | build_and_deploy_job:
14 | if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.action != 'closed')
15 | runs-on: ubuntu-latest
16 | name: Build and Deploy Job
17 | steps:
18 | - uses: actions/checkout@v2
19 | with:
20 | submodules: true
21 | - name: Build And Deploy
22 | id: builddeploy
23 | uses: Azure/static-web-apps-deploy@v1
24 | with:
25 | azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_WONDERFUL_WAVE_0F526C610 }}
26 | repo_token: ${{ secrets.GITHUB_TOKEN }} # Used for Github integrations (i.e. PR comments)
27 | action: "upload"
28 | ###### Repository/Build Configurations - These values can be configured to match your app requirements. ######
29 | # For more information regarding Static Web App workflow configurations, please visit: https://aka.ms/swaworkflowconfig
30 | app_location: "/" # App source code path
31 | api_location: "" # Api source code path - optional
32 | output_location: "" # Built app content directory - optional
33 | ###### End of Repository/Build Configurations ######
34 |
35 | close_pull_request_job:
36 | if: github.event_name == 'pull_request' && github.event.action == 'closed'
37 | runs-on: ubuntu-latest
38 | name: Close Pull Request Job
39 | steps:
40 | - name: Close Pull Request
41 | id: closepullrequest
42 | uses: Azure/static-web-apps-deploy@v1
43 | with:
44 | azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_WONDERFUL_WAVE_0F526C610 }}
45 | action: "close"
46 |
--------------------------------------------------------------------------------
/azure-speech-streaming-reactjs/front-end-ui/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
--------------------------------------------------------------------------------
/azure-speech-streaming-reactjs/front-end-ui/build/asset-manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "files": {
3 | "main.js": "/static/js/main.a03b50d2.chunk.js",
4 | "main.js.map": "/static/js/main.a03b50d2.chunk.js.map",
5 | "runtime-main.js": "/static/js/runtime-main.4452493b.js",
6 | "runtime-main.js.map": "/static/js/runtime-main.4452493b.js.map",
7 | "static/css/2.8c0e6b67.chunk.css": "/static/css/2.8c0e6b67.chunk.css",
8 | "static/js/2.cd9c6385.chunk.js": "/static/js/2.cd9c6385.chunk.js",
9 | "static/js/2.cd9c6385.chunk.js.map": "/static/js/2.cd9c6385.chunk.js.map",
10 | "index.html": "/index.html",
11 | "static/css/2.8c0e6b67.chunk.css.map": "/static/css/2.8c0e6b67.chunk.css.map",
12 | "static/js/2.cd9c6385.chunk.js.LICENSE.txt": "/static/js/2.cd9c6385.chunk.js.LICENSE.txt"
13 | },
14 | "entrypoints": [
15 | "static/js/runtime-main.4452493b.js",
16 | "static/css/2.8c0e6b67.chunk.css",
17 | "static/js/2.cd9c6385.chunk.js",
18 | "static/js/main.a03b50d2.chunk.js"
19 | ]
20 | }
--------------------------------------------------------------------------------
/azure-speech-streaming-reactjs/front-end-ui/build/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/amulchapla/CallCenterIntelligenceAzureAI/f5a6952db758d1f325ea1f112624d2135a7bc8f0/azure-speech-streaming-reactjs/front-end-ui/build/favicon.ico
--------------------------------------------------------------------------------
/azure-speech-streaming-reactjs/front-end-ui/build/index.html:
--------------------------------------------------------------------------------
1 | | Audio Recording is {record_status} | \r\n\r\n |
| Start Or Stop Streaming | \r\n\r\n |
| Audio Recording is {record_status} | 65 |66 | |
| Start Or Stop Streaming | 69 |70 | |
First Name: {props.graphData.givenName}
13 | {/*Last Name: {props.graphData.surname}
14 |Email: {props.graphData.userPrincipalName}
15 |Id: {props.graphData.id}
*/} 16 |Hello World
> 7 | ); 8 | }; -------------------------------------------------------------------------------- /azure-speech-streaming-reactjs/front-end-ui/src/config.json: -------------------------------------------------------------------------------- 1 | { 2 | 3 | } -------------------------------------------------------------------------------- /azure-speech-streaming-reactjs/front-end-ui/src/graph.js: -------------------------------------------------------------------------------- 1 | import { graphConfig } from "./authConfig"; 2 | 3 | /** 4 | * Attaches a given access token to a MS Graph API call. Returns information about the user 5 | * @param accessToken 6 | */ 7 | export async function callMsGraph(accessToken) { 8 | const headers = new Headers(); 9 | const bearer = `Bearer ${accessToken}`; 10 | 11 | headers.append("Authorization", bearer); 12 | 13 | const options = { 14 | method: "GET", 15 | headers: headers 16 | }; 17 | 18 | return fetch(graphConfig.graphMeEndpoint, options) 19 | .then(response => response.json()) 20 | .catch(error => console.log(error)); 21 | } 22 | -------------------------------------------------------------------------------- /azure-speech-streaming-reactjs/front-end-ui/src/index.js: -------------------------------------------------------------------------------- 1 | import 'bootstrap/dist/css/bootstrap.css'; 2 | import React from 'react'; 3 | import ReactDOM from 'react-dom'; 4 | import App from "./App.jsx"; 5 | import { BrowserRouter as Router } from "react-router-dom"; 6 | import { ThemeProvider } from '@material-ui/core/styles'; 7 | import { theme } from "./styles/theme"; 8 | import { MsalProvider } from "@azure/msal-react"; 9 | 10 | 11 | // MSAL Imports 12 | import { PublicClientApplication, EventType } from "@azure/msal-browser"; 13 | import { msalConfig } from "./authConfig"; 14 | 15 | // MSAL configuration 16 | const msalInstance = new PublicClientApplication(msalConfig); 17 | 18 | // Default to using the first account if no account is active on page load 19 | if (!msalInstance.getActiveAccount() && msalInstance.getAllAccounts().length > 0) { 20 | // Account selection logic is app dependent. Adjust as needed for different use cases. 21 | msalInstance.setActiveAccount(msalInstance.getAllAccounts()[0]); 22 | } 23 | 24 | // Optional - This will update account state if a user signs in from another tab or window 25 | msalInstance.enableAccountStorageEvents(); 26 | 27 | msalInstance.addEventCallback((event) => { 28 | if (event.eventType === EventType.LOGIN_SUCCESS && event.payload.account) { 29 | const account = event.payload.account; 30 | msalInstance.setActiveAccount(account); 31 | } 32 | }); 33 | 34 | 35 | ReactDOM.render( 36 |{this.state.displayText}
174 | {this.state.displayNLPOutput}
177 |