├── .gitignore
├── 04-speech
├── src
│ └── main
│ │ ├── resources
│ │ └── jetty.keystore
│ │ ├── java
│ │ └── com
│ │ │ └── example
│ │ │ └── flexible
│ │ │ └── speak
│ │ │ ├── Constraints.java
│ │ │ ├── TranscribeServlet.java
│ │ │ └── TranscribeSocket.java
│ │ └── webapp
│ │ ├── index.html
│ │ ├── css
│ │ └── main.css
│ │ └── javascript
│ │ └── main.js
├── jetty.xml
├── webdefault.xml
├── jetty-ssl.xml
├── jetty-https.xml
└── pom.xml
├── 02-webaudio
├── src
│ └── main
│ │ ├── resources
│ │ └── jetty.keystore
│ │ ├── webapp
│ │ ├── index.html
│ │ ├── css
│ │ │ └── main.css
│ │ └── javascript
│ │ │ └── main.js
│ │ └── java
│ │ └── com
│ │ └── example
│ │ └── flexible
│ │ └── speak
│ │ └── TranscribeServlet.java
├── jetty.xml
├── webdefault.xml
├── jetty-ssl.xml
├── pom.xml
└── jetty-https.xml
├── 03-websockets
├── src
│ └── main
│ │ ├── resources
│ │ └── jetty.keystore
│ │ ├── webapp
│ │ ├── index.html
│ │ ├── css
│ │ │ └── main.css
│ │ └── javascript
│ │ │ └── main.js
│ │ └── java
│ │ └── com
│ │ └── example
│ │ └── flexible
│ │ └── speak
│ │ ├── TranscribeServlet.java
│ │ └── TranscribeSocket.java
├── jetty.xml
├── webdefault.xml
├── jetty-ssl.xml
├── jetty-https.xml
└── pom.xml
├── 01-hello-https
├── src
│ └── main
│ │ ├── resources
│ │ └── jetty.keystore
│ │ ├── webapp
│ │ ├── index.html
│ │ ├── css
│ │ │ └── main.css
│ │ └── javascript
│ │ │ └── main.js
│ │ └── java
│ │ └── com
│ │ └── example
│ │ └── flexible
│ │ └── speak
│ │ └── TranscribeServlet.java
├── jetty.xml
├── webdefault.xml
├── jetty-ssl.xml
├── pom.xml
└── jetty-https.xml
├── README.md
├── CONTRIBUTING.md
└── LICENSE
/.gitignore:
--------------------------------------------------------------------------------
1 | target/
2 | .*
3 |
--------------------------------------------------------------------------------
/04-speech/src/main/resources/jetty.keystore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecodelabs/speaking-with-a-webpage/HEAD/04-speech/src/main/resources/jetty.keystore
--------------------------------------------------------------------------------
/02-webaudio/src/main/resources/jetty.keystore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecodelabs/speaking-with-a-webpage/HEAD/02-webaudio/src/main/resources/jetty.keystore
--------------------------------------------------------------------------------
/03-websockets/src/main/resources/jetty.keystore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecodelabs/speaking-with-a-webpage/HEAD/03-websockets/src/main/resources/jetty.keystore
--------------------------------------------------------------------------------
/01-hello-https/src/main/resources/jetty.keystore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/googlecodelabs/speaking-with-a-webpage/HEAD/01-hello-https/src/main/resources/jetty.keystore
--------------------------------------------------------------------------------
/01-hello-https/src/main/webapp/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Speaking with a webpage
4 |
5 |
6 |
7 | Hello world, from html.
8 |
--------------------------------------------------------------------------------
/01-hello-https/src/main/webapp/css/main.css:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2017, Google, Inc.
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software
10 | * distributed under the License is distributed on an "AS IS" BASIS,
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | * See the License for the specific language governing permissions and
13 | * limitations under the License.
14 | */
15 | body {}
16 |
--------------------------------------------------------------------------------
/01-hello-https/src/main/webapp/javascript/main.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2017, Google, Inc.
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software
10 | * distributed under the License is distributed on an "AS IS" BASIS,
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | * See the License for the specific language governing permissions and
13 | * limitations under the License.
14 | */
15 | (function() {
16 | alert('Hello world, from JS');
17 | })();
18 |
--------------------------------------------------------------------------------
/04-speech/jetty.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | https
8 |
9 | 32768
10 | 8192
11 | 8192
12 | true
13 | false
14 | 512
15 |
16 |
--------------------------------------------------------------------------------
/01-hello-https/jetty.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | https
8 |
9 | 32768
10 | 8192
11 | 8192
12 | true
13 | false
14 | 512
15 |
16 |
--------------------------------------------------------------------------------
/02-webaudio/jetty.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | https
8 |
9 | 32768
10 | 8192
11 | 8192
12 | true
13 | false
14 | 512
15 |
16 |
--------------------------------------------------------------------------------
/03-websockets/jetty.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | https
8 |
9 | 32768
10 | 8192
11 | 8192
12 | true
13 | false
14 | 512
15 |
16 |
--------------------------------------------------------------------------------
/04-speech/src/main/java/com/example/flexible/speak/Constraints.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2017 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.example.flexible.speak;
17 |
18 | /**
19 | * POJO to hold JSON configuration from the client.
20 | */
21 | class Constraints {
22 | public int sampleRate;
23 | }
24 |
--------------------------------------------------------------------------------
/02-webaudio/webdefault.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 | default
10 | org.eclipse.jetty.servlet.DefaultServlet
11 |
12 |
13 | cacheControl
14 | no-cache, no-store, must-revalidate
15 |
16 |
17 |
18 |
19 | default
20 | /
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/04-speech/webdefault.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 | default
10 | org.eclipse.jetty.servlet.DefaultServlet
11 |
12 |
13 | cacheControl
14 | no-cache, no-store, must-revalidate
15 |
16 |
17 |
18 |
19 | default
20 | /
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/01-hello-https/webdefault.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 | default
10 | org.eclipse.jetty.servlet.DefaultServlet
11 |
12 |
13 | cacheControl
14 | no-cache, no-store, must-revalidate
15 |
16 |
17 |
18 |
19 | default
20 | /
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/03-websockets/webdefault.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 | default
10 | org.eclipse.jetty.servlet.DefaultServlet
11 |
12 |
13 | cacheControl
14 | no-cache, no-store, must-revalidate
15 |
16 |
17 |
18 |
19 | default
20 | /
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/02-webaudio/src/main/webapp/index.html:
--------------------------------------------------------------------------------
1 |
2 |
21 |
22 | Speaking with a webpage
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/04-speech/src/main/webapp/index.html:
--------------------------------------------------------------------------------
1 |
2 |
21 |
22 | Speaking with a webpage
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/03-websockets/src/main/webapp/index.html:
--------------------------------------------------------------------------------
1 |
2 |
21 |
22 | Speaking with a webpage
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/02-webaudio/src/main/java/com/example/flexible/speak/TranscribeServlet.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2015 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.example.flexible.speak;
18 |
19 | import java.io.IOException;
20 | import java.io.PrintWriter;
21 |
22 | import javax.servlet.annotation.WebServlet;
23 | import javax.servlet.http.HttpServlet;
24 | import javax.servlet.http.HttpServletRequest;
25 | import javax.servlet.http.HttpServletResponse;
26 |
27 | // [START example]
28 | @WebServlet("/transcribe")
29 | @SuppressWarnings("serial")
30 | public class TranscribeServlet extends HttpServlet {
31 | @Override
32 | public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
33 | PrintWriter out = resp.getWriter();
34 | out.println("Hello, world - App Engine Flexible");
35 | }
36 | }
37 | // [END example]
38 |
--------------------------------------------------------------------------------
/01-hello-https/src/main/java/com/example/flexible/speak/TranscribeServlet.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2015 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.example.flexible.speak;
18 |
19 | import java.io.IOException;
20 | import java.io.PrintWriter;
21 |
22 | import javax.servlet.annotation.WebServlet;
23 | import javax.servlet.http.HttpServlet;
24 | import javax.servlet.http.HttpServletRequest;
25 | import javax.servlet.http.HttpServletResponse;
26 |
27 | // [START example]
28 | @WebServlet("/transcribe")
29 | @SuppressWarnings("serial")
30 | public class TranscribeServlet extends HttpServlet {
31 | @Override
32 | public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
33 | PrintWriter out = resp.getWriter();
34 | out.println("Hello, world - App Engine Flexible");
35 | }
36 | }
37 | // [END example]
38 |
--------------------------------------------------------------------------------
/04-speech/src/main/java/com/example/flexible/speak/TranscribeServlet.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2017 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.example.flexible.speak;
18 |
19 | import org.eclipse.jetty.websocket.servlet.WebSocketServlet;
20 | import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
21 |
22 | import javax.servlet.annotation.WebServlet;
23 |
24 | // [START example]
25 | @WebServlet("/transcribe")
26 | @SuppressWarnings("serial")
27 | public class TranscribeServlet extends WebSocketServlet {
28 |
29 | // Timeout in milliseconds
30 | private static final int TIMEOUT = 10000;
31 |
32 | @Override
33 | public void configure(WebSocketServletFactory factory) {
34 | factory.getPolicy().setIdleTimeout(TIMEOUT);
35 |
36 | // The WebSocket to create on upgrade
37 | factory.register(TranscribeSocket.class);
38 | }
39 | }
40 | // [END example]
41 |
--------------------------------------------------------------------------------
/03-websockets/src/main/java/com/example/flexible/speak/TranscribeServlet.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2017 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.example.flexible.speak;
18 |
19 | import org.eclipse.jetty.websocket.servlet.WebSocketServlet;
20 | import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
21 |
22 | import javax.servlet.annotation.WebServlet;
23 |
24 | // [START example]
25 | @WebServlet("/transcribe")
26 | @SuppressWarnings("serial")
27 | public class TranscribeServlet extends WebSocketServlet {
28 |
29 | // Timeout in milliseconds
30 | private static final int TIMEOUT = 10000;
31 |
32 | @Override
33 | public void configure(WebSocketServletFactory factory) {
34 | factory.getPolicy().setIdleTimeout(TIMEOUT);
35 |
36 | // The WebSocket to create on upgrade
37 | factory.register(TranscribeSocket.class);
38 | }
39 | }
40 | // [END example]
41 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Codelab: Speaking with a Webpage
2 |
3 | This is a sample solution for the codelab "Speaking with a Webpage".
4 |
5 | ## Step 0 - Configure Compute Engine
6 |
7 | Install Java and Maven:
8 |
9 | sudo apt-get update
10 | sudo apt-get install -y maven openjdk-8-jdk
11 |
12 | The maven jetty plugin listens for http and https connections on ports `8080`
13 | and `8443` by default. Open them up on the Compute Engine firewall:
14 |
15 | gcloud compute firewall-rules create dev-ports \
16 | --allow=tcp:8080,tcp:8443 \
17 | --source-ranges=0.0.0.0/0
18 |
19 | ## Step 1
20 |
21 | Create a webapp on Google Compute Engine that can serve static javascript, a
22 | static `index.html`, and a dynamic controller.
23 |
24 | When using `getUserInput` to access microphone input, browsers require the
25 | connection to be `https`. Configure the app for https - for development
26 | purposes, a self-signed certificate suffices.
27 |
28 | Generate a self-signed SSL cert for now:
29 |
30 | keytool -genkey -alias jetty -keyalg RSA \
31 | -keystore src/main/resources/jetty.keystore \
32 | -storepass secret -keypass secret -dname "CN=localhost"
33 |
34 | Then run:
35 |
36 | cd 01-hello-https/
37 | mvn clean jetty:run
38 |
39 | ## Step 2
40 |
41 | Use the WebAudio API on the frontend to get microphone data.
42 |
43 | ## Step 3
44 |
45 | Stream the raw audio data from the client to the server using WebSockets.
46 |
47 | When on an `https` webpage, websocket connections are required to be secure
48 | `wss`.
49 |
50 | ## Step 4
51 |
52 | Use the Speech API to stream transcriptions of speech to the client.
53 |
54 | ## Disclaimer
55 |
56 | This is not an official Google product
57 |
--------------------------------------------------------------------------------
/03-websockets/src/main/java/com/example/flexible/speak/TranscribeSocket.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2017 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.example.flexible.speak;
18 |
19 | import org.eclipse.jetty.websocket.api.WebSocketAdapter;
20 |
21 | import java.io.IOException;
22 |
23 | public class TranscribeSocket extends WebSocketAdapter {
24 | @Override
25 | public void onWebSocketBinary(byte[] payload, int offset, int len) {
26 | if (isConnected()) {
27 | try {
28 | System.out.println("Got binary message");
29 | // echo the message back
30 | getRemote().sendString(String.format("Got binary %s:%s", offset, len));
31 | } catch (IOException e) {
32 | e.printStackTrace(System.err);
33 | }
34 | }
35 | }
36 |
37 | @Override
38 | public void onWebSocketText(String message) {
39 | if (isConnected()) {
40 | try {
41 | System.out.printf("Echoing back message [%s]%n", message);
42 | // echo the message back
43 | getRemote().sendString(message);
44 | } catch (IOException e) {
45 | e.printStackTrace(System.err);
46 | }
47 | }
48 | }
49 |
50 | @Override
51 | public void onWebSocketClose(int statusCode, String reason) {
52 | }
53 |
54 | @Override
55 | public void onWebSocketError(Throwable cause) {
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/04-speech/jetty-ssl.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | /
10 |
11 |
12 | /
13 |
14 |
15 |
16 |
17 | - SSL_RSA_WITH_DES_CBC_SHA
18 | - SSL_DHE_RSA_WITH_DES_CBC_SHA
19 | - SSL_DHE_DSS_WITH_DES_CBC_SHA
20 | - SSL_RSA_EXPORT_WITH_RC4_40_MD5
21 | - SSL_RSA_EXPORT_WITH_DES40_CBC_SHA
22 | - SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA
23 | - SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/01-hello-https/jetty-ssl.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | /
10 |
11 |
12 | /
13 |
14 |
15 |
16 |
17 | - SSL_RSA_WITH_DES_CBC_SHA
18 | - SSL_DHE_RSA_WITH_DES_CBC_SHA
19 | - SSL_DHE_DSS_WITH_DES_CBC_SHA
20 | - SSL_RSA_EXPORT_WITH_RC4_40_MD5
21 | - SSL_RSA_EXPORT_WITH_DES40_CBC_SHA
22 | - SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA
23 | - SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/02-webaudio/jetty-ssl.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | /
10 |
11 |
12 | /
13 |
14 |
15 |
16 |
17 | - SSL_RSA_WITH_DES_CBC_SHA
18 | - SSL_DHE_RSA_WITH_DES_CBC_SHA
19 | - SSL_DHE_DSS_WITH_DES_CBC_SHA
20 | - SSL_RSA_EXPORT_WITH_RC4_40_MD5
21 | - SSL_RSA_EXPORT_WITH_DES40_CBC_SHA
22 | - SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA
23 | - SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/03-websockets/jetty-ssl.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | /
10 |
11 |
12 | /
13 |
14 |
15 |
16 |
17 | - SSL_RSA_WITH_DES_CBC_SHA
18 | - SSL_DHE_RSA_WITH_DES_CBC_SHA
19 | - SSL_DHE_DSS_WITH_DES_CBC_SHA
20 | - SSL_RSA_EXPORT_WITH_RC4_40_MD5
21 | - SSL_RSA_EXPORT_WITH_DES40_CBC_SHA
22 | - SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA
23 | - SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/02-webaudio/pom.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 | 4.0.0
18 | war
19 | 1.0-SNAPSHOT
20 | com.example.codelab
21 | speaking-with-a-webpage
22 |
23 |
24 | 1.8
25 | 1.8
26 |
27 | 1.0.0
28 | 9.3.8.v20160314
29 |
30 | false
31 |
32 |
33 |
34 |
35 | javax.servlet
36 | javax.servlet-api
37 | 3.1.0
38 | jar
39 | provided
40 |
41 |
42 |
43 |
44 |
45 | ${project.build.directory}/${project.build.finalName}/WEB-INF/classes
46 |
47 |
48 |
49 | com.google.cloud.tools
50 | appengine-maven-plugin
51 | ${appengine.maven.plugin}
52 |
53 |
54 |
55 |
56 |
57 |
58 | org.eclipse.jetty
59 | jetty-maven-plugin
60 | ${jetty.maven.plugin}
61 |
62 | jetty.xml,jetty-ssl.xml,jetty-https.xml
63 |
64 | webdefault.xml
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/01-hello-https/pom.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 | 4.0.0
18 | war
19 | 1.0-SNAPSHOT
20 | com.example.codelab
21 | speaking-with-a-webpage
22 |
23 |
24 | 1.8
25 | 1.8
26 |
27 | 1.0.0
28 | 9.3.8.v20160314
29 |
30 | false
31 |
32 |
33 |
34 |
35 | javax.servlet
36 | javax.servlet-api
37 | 3.1.0
38 | jar
39 | provided
40 |
41 |
42 |
43 |
44 |
45 | ${project.build.directory}/${project.build.finalName}/WEB-INF/classes
46 |
47 |
48 |
49 | com.google.cloud.tools
50 | appengine-maven-plugin
51 | ${appengine.maven.plugin}
52 |
53 |
54 |
55 |
56 |
57 |
58 | org.eclipse.jetty
59 | jetty-maven-plugin
60 | ${jetty.maven.plugin}
61 |
62 | jetty.xml,jetty-ssl.xml,jetty-https.xml
63 |
64 | webdefault.xml
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/04-speech/jetty-https.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | -
27 |
28 | http/1.1
29 |
30 |
31 |
32 | -
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/01-hello-https/jetty-https.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | -
27 |
28 | http/1.1
29 |
30 |
31 |
32 | -
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/02-webaudio/jetty-https.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | -
27 |
28 | http/1.1
29 |
30 |
31 |
32 | -
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/03-websockets/jetty-https.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | -
27 |
28 | http/1.1
29 |
30 |
31 |
32 | -
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/03-websockets/pom.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 | 4.0.0
18 | war
19 | 1.0-SNAPSHOT
20 | com.example.codelab
21 | speaking-with-a-webpage
22 |
23 |
24 | 1.8
25 | 1.8
26 |
27 | 1.0.0
28 | 9.3.8.v20160314
29 |
30 | false
31 |
32 |
33 |
34 |
35 | javax.servlet
36 | javax.servlet-api
37 | 3.1.0
38 | jar
39 | provided
40 |
41 |
42 | org.eclipse.jetty.websocket
43 | websocket-servlet
44 | 9.4.1.v20170120
45 |
46 |
47 |
48 |
49 |
50 | ${project.build.directory}/${project.build.finalName}/WEB-INF/classes
51 |
52 |
53 |
54 | com.google.cloud.tools
55 | appengine-maven-plugin
56 | ${appengine.maven.plugin}
57 |
58 |
59 |
60 |
61 |
62 |
63 | org.eclipse.jetty
64 | jetty-maven-plugin
65 | ${jetty.maven.plugin}
66 |
67 | jetty.xml,jetty-ssl.xml,jetty-https.xml
68 |
69 | webdefault.xml
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
--------------------------------------------------------------------------------
/04-speech/pom.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 | 4.0.0
18 | war
19 | 1.0-SNAPSHOT
20 | com.example.codelab
21 | speaking-with-a-webpage
22 |
23 |
24 | 1.8
25 | 1.8
26 |
27 | 1.0.0
28 | 9.3.8.v20160314
29 |
30 | false
31 |
32 |
33 |
34 |
35 | javax.servlet
36 | javax.servlet-api
37 | 3.1.0
38 | jar
39 | provided
40 |
41 |
42 | org.eclipse.jetty.websocket
43 | websocket-servlet
44 | 9.4.1.v20170120
45 |
46 |
47 | com.google.cloud
48 | google-cloud-speech
49 | 0.49.0-alpha
50 |
51 |
52 | com.google.code.gson
53 | gson
54 | 2.8.0
55 |
56 |
57 |
58 |
59 |
60 | ${project.build.directory}/${project.build.finalName}/WEB-INF/classes
61 |
62 |
63 |
64 | com.google.cloud.tools
65 | appengine-maven-plugin
66 | ${appengine.maven.plugin}
67 |
68 |
69 |
70 |
71 |
72 |
73 | org.eclipse.jetty
74 | jetty-maven-plugin
75 | ${jetty.maven.plugin}
76 |
77 | jetty.xml,jetty-ssl.xml,jetty-https.xml
78 |
79 | webdefault.xml
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # How to become a contributor and submit your own code
2 |
3 | ## Contributor License Agreements
4 |
5 | We'd love to accept your sample apps and patches! Before we can take them, we
6 | have to jump a couple of legal hurdles.
7 |
8 | Please fill out either the individual or corporate Contributor License Agreement
9 | (CLA).
10 |
11 | * If you are an individual writing original source code and you're sure you
12 | own the intellectual property, then you'll need to sign an [individual
13 | CLA](https://developers.google.com/open-source/cla/individual).
14 | * If you work for a company that wants to allow you to contribute your work,
15 | then you'll need to sign a [corporate
16 | CLA](https://developers.google.com/open-source/cla/corporate).
17 |
18 | Follow either of the two links above to access the appropriate CLA and
19 | instructions for how to sign and return it. Once we receive it, we'll be able to
20 | accept your pull requests.
21 |
22 | ## Contributing a Patch
23 |
24 | 1. Submit an issue describing your proposed change to the repo in question.
25 | 1. The repo owner will respond to your issue promptly.
26 | 1. If your proposed change is accepted, and you haven't already done so, sign a
27 | Contributor License Agreement (see details above).
28 | 1. Fork the desired repo, develop and test your code changes.
29 | 1. Ensure that your code adheres to the existing style in the sample to which
30 | you are contributing.
31 | 1. Ensure that your code has an appropriate set of unit tests which all pass.
32 | 1. Submit a pull request.
33 |
34 | ## Style
35 |
36 | Samples in this repository follow the [Google Java Style Guide][java-style].
37 | This is enforced using the [Maven Checkstyle Plugin][checkstyle-plugin].
38 |
39 | [java-style]: https://google.github.io/styleguide/javaguide.html
40 | [checkstyle-plugin]: https://maven.apache.org/plugins/maven-checkstyle-plugin/
41 |
42 | Use the [google-java-format][google-java-format] tool to automatically reformat
43 | your source code to adhere to the style guide. It is available as a command-line
44 | tool or IntelliJ plugin.
45 |
46 | [google-java-format]: https://github.com/google/google-java-format
47 |
48 | ### Running the Linter
49 |
50 | To run the checkstyle plugin on an existing sample, run
51 |
52 | ```shell
53 | mvn clean verify -DskipTests
54 | ```
55 |
56 | The `-DskipTests` is optional. It is useful if you want to verify that your code
57 | builds and adheres to the style guide without waiting for tests to complete.
58 |
59 | ### Adding the Checkstyle Plugin to New Samples
60 |
61 | The samples in this repository use a common parent POM to define plugins used
62 | for linting and testing. Add the following to your sample POM to ensure that it
63 | uses the common Checkstyle configuration.
64 |
65 | ```xml
66 |
67 | com.google.cloud
68 | doc-samples
69 | 1.0.0
70 |
71 | ../..
72 |
73 | ```
74 |
75 | This is just used for testing. The sample should build without a parent defined.
76 |
--------------------------------------------------------------------------------
/02-webaudio/src/main/webapp/css/main.css:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2017, Google, Inc.
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software
10 | * distributed under the License is distributed on an "AS IS" BASIS,
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | * See the License for the specific language governing permissions and
13 | * limitations under the License.
14 | */
15 | html, body {
16 | height: 100%;
17 | overflow: hidden;
18 | margin: 0;
19 | padding: 0;
20 | }
21 | body {
22 | color: #222;
23 | font-family: 'Open Sans', arial, sans-serif;
24 | font-weight: 300;
25 | -webkit-font-smoothing: antialiased;
26 | padding: 2em;
27 | background: -webkit-gradient(radial, center center, 500, center center, 1400, from(rgba(0,0,0,0)), to(rgba(0,0,0,0.6))) #fff;
28 | background: -moz-radial-gradient(farthest-side, rgba(0,0,0,0) 90%, rgba(0,0,0,0.2) 150%) #fff;
29 | background: -webkit-radial-gradient(farthest-side, rgba(0,0,0,0) 90%, rgba(0,0,0,0.2) 150%) #fff;
30 | background: -ms-radial-gradient(farthest-side, rgba(0,0,0,0) 90%, rgba(0,0,0,0.2) 150%) #fff;
31 | background: -o-radial-gradient(farthest-side, rgba(0,0,0,0) 90%, rgba(0,0,0,0.2) 150%) #fff;
32 | background: radial-gradient(farthest-side, rgba(0,0,0,0) 90%, rgba(0,0,0,0.2) 150%) #fff;
33 | box-sizing: border-box;
34 | }
35 | a {
36 | color: navy;
37 | }
38 |
39 | body > section {
40 | display: flex;
41 | flex-direction: column;
42 | justify-content: center;
43 | align-content: center;
44 | align-items: center;
45 | box-sizing: border-box;
46 | height: 100%;
47 | -webkit-perspective: 800;
48 | -webkit-transform-style: preserve-3d;
49 | }
50 | section > * {
51 | display: flex;
52 | align-items: center;
53 | position: relative;
54 | z-index: 1;
55 | }
56 |
57 | .fft {
58 | position: absolute;
59 | z-index: 1;
60 | -webkit-box-reflect: below 5px -webkit-linear-gradient(top, transparent, transparent 50%, rgba(255,255,255,0.2));
61 | }
62 | #fft {
63 | transform: translateX(-50%) rotateX(-30deg) rotateY(30deg);
64 | }
65 | #fft2 {
66 | transform: translateX(-50%) rotateX(-30deg) rotateY(-30deg);
67 | }
68 |
69 | #playbutton {
70 | cursor: pointer;
71 | position: relative;
72 | z-index: 10;
73 | box-sizing: border-box;
74 | width: 125px;
75 | height: 125px;
76 | border-radius: 100%;
77 | border: 4px solid rgba(0,0,0,.9);
78 | }
79 |
80 | #playbutton.playing {
81 | background: rgba(0,0,0,.25);
82 | opacity: 0.1;
83 | }
84 | #playbutton::before {
85 | content: '\feff';
86 | display: inline-block;
87 | border-style: solid;
88 | border-color: transparent transparent transparent rgba(0,0,0,.9);
89 | border-width: 30px 0 30px 40px;
90 | box-sizing: border-box;
91 | height:0;
92 | width: 50%;
93 | margin-left: 45px;
94 | }
95 |
96 | #playbutton.playing::before, #playbutton.playing:hover::before {
97 | content: '\feff';
98 | display: inline-block;
99 | border: 4px solid rgba(0,0,0,.9);
100 | border-width: 0 20px;
101 | box-sizing: border-box;
102 | height: 60%;
103 | width: 50%;
104 | margin: 0 auto;
105 | }
106 |
107 | #transcript {
108 | font-size: 1.2em;
109 | line-height: 1.3em;
110 | max-width: 50em;
111 | height: calc(1.3em * 5);
112 | position: relative;
113 | min-width: 40em;
114 | overflow: hidden;
115 | box-sizing: border-box;
116 | transform: translateY(-1em);
117 | }
118 | #transcript>div {
119 | position: absolute;
120 | bottom: 0;
121 | padding: .7em;
122 | margin: 0 auto;
123 | left: 50%;
124 | transform: translate(-50%, -1rem);
125 | width: 100%;
126 | text-align: center;
127 | box-sizing: border-box;
128 | background: rgba(195,215,264, .5);
129 | border: 1px solid rgba(0,0,0,.2);
130 | border-radius: .5em;
131 | }
132 | #transcript>div:empty {
133 | visibility: hidden;
134 | }
135 |
--------------------------------------------------------------------------------
/03-websockets/src/main/webapp/css/main.css:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2017, Google, Inc.
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software
10 | * distributed under the License is distributed on an "AS IS" BASIS,
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | * See the License for the specific language governing permissions and
13 | * limitations under the License.
14 | */
15 | html, body {
16 | height: 100%;
17 | overflow: hidden;
18 | margin: 0;
19 | padding: 0;
20 | }
21 | body {
22 | color: #222;
23 | font-family: 'Open Sans', arial, sans-serif;
24 | font-weight: 300;
25 | -webkit-font-smoothing: antialiased;
26 | padding: 2em;
27 | background: -webkit-gradient(radial, center center, 500, center center, 1400, from(rgba(0,0,0,0)), to(rgba(0,0,0,0.6))) #fff;
28 | background: -moz-radial-gradient(farthest-side, rgba(0,0,0,0) 90%, rgba(0,0,0,0.2) 150%) #fff;
29 | background: -webkit-radial-gradient(farthest-side, rgba(0,0,0,0) 90%, rgba(0,0,0,0.2) 150%) #fff;
30 | background: -ms-radial-gradient(farthest-side, rgba(0,0,0,0) 90%, rgba(0,0,0,0.2) 150%) #fff;
31 | background: -o-radial-gradient(farthest-side, rgba(0,0,0,0) 90%, rgba(0,0,0,0.2) 150%) #fff;
32 | background: radial-gradient(farthest-side, rgba(0,0,0,0) 90%, rgba(0,0,0,0.2) 150%) #fff;
33 | box-sizing: border-box;
34 | }
35 | a {
36 | color: navy;
37 | }
38 |
39 | body > section {
40 | display: flex;
41 | flex-direction: column;
42 | justify-content: center;
43 | align-content: center;
44 | align-items: center;
45 | box-sizing: border-box;
46 | height: 100%;
47 | -webkit-perspective: 800;
48 | -webkit-transform-style: preserve-3d;
49 | }
50 | section > * {
51 | display: flex;
52 | align-items: center;
53 | position: relative;
54 | z-index: 1;
55 | }
56 |
57 | .fft {
58 | position: absolute;
59 | z-index: 1;
60 | -webkit-box-reflect: below 5px -webkit-linear-gradient(top, transparent, transparent 50%, rgba(255,255,255,0.2));
61 | }
62 | #fft {
63 | transform: translateX(-50%) rotateX(-30deg) rotateY(30deg);
64 | }
65 | #fft2 {
66 | transform: translateX(-50%) rotateX(-30deg) rotateY(-30deg);
67 | }
68 |
69 | #playbutton {
70 | cursor: pointer;
71 | position: relative;
72 | z-index: 10;
73 | box-sizing: border-box;
74 | width: 125px;
75 | height: 125px;
76 | border-radius: 100%;
77 | border: 4px solid rgba(0,0,0,.9);
78 | }
79 |
80 | #playbutton.playing {
81 | background: rgba(0,0,0,.25);
82 | opacity: 0.1;
83 | }
84 | #playbutton::before {
85 | content: '\feff';
86 | display: inline-block;
87 | border-style: solid;
88 | border-color: transparent transparent transparent rgba(0,0,0,.9);
89 | border-width: 30px 0 30px 40px;
90 | box-sizing: border-box;
91 | height:0;
92 | width: 50%;
93 | margin-left: 45px;
94 | }
95 |
96 | #playbutton.playing::before, #playbutton.playing:hover::before {
97 | content: '\feff';
98 | display: inline-block;
99 | border: 4px solid rgba(0,0,0,.9);
100 | border-width: 0 20px;
101 | box-sizing: border-box;
102 | height: 60%;
103 | width: 50%;
104 | margin: 0 auto;
105 | }
106 |
107 | #transcript {
108 | font-size: 1.2em;
109 | line-height: 1.3em;
110 | max-width: 50em;
111 | height: calc(1.3em * 5);
112 | position: relative;
113 | min-width: 40em;
114 | overflow: hidden;
115 | box-sizing: border-box;
116 | transform: translateY(-1em);
117 | }
118 | #transcript>div {
119 | position: absolute;
120 | bottom: 0;
121 | padding: .7em;
122 | margin: 0 auto;
123 | left: 50%;
124 | transform: translate(-50%, -1rem);
125 | width: 100%;
126 | text-align: center;
127 | box-sizing: border-box;
128 | background: rgba(195,215,264, .5);
129 | border: 1px solid rgba(0,0,0,.2);
130 | border-radius: .5em;
131 | }
132 | #transcript>div:empty {
133 | visibility: hidden;
134 | }
135 |
--------------------------------------------------------------------------------
/04-speech/src/main/webapp/css/main.css:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2017, Google, Inc.
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software
10 | * distributed under the License is distributed on an "AS IS" BASIS,
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | * See the License for the specific language governing permissions and
13 | * limitations under the License.
14 | */
15 | html, body {
16 | height: 100%;
17 | overflow: hidden;
18 | margin: 0;
19 | padding: 0;
20 | }
21 | body {
22 | color: #222;
23 | font-family: 'Open Sans', arial, sans-serif;
24 | font-weight: 300;
25 | -webkit-font-smoothing: antialiased;
26 | padding: 2em;
27 | background: -webkit-gradient(radial, center center, 500, center center, 1400, from(rgba(0,0,0,0)), to(rgba(0,0,0,0.6))) #fff;
28 | background: -moz-radial-gradient(farthest-side, rgba(0,0,0,0) 90%, rgba(0,0,0,0.2) 150%) #fff;
29 | background: -webkit-radial-gradient(farthest-side, rgba(0,0,0,0) 90%, rgba(0,0,0,0.2) 150%) #fff;
30 | background: -ms-radial-gradient(farthest-side, rgba(0,0,0,0) 90%, rgba(0,0,0,0.2) 150%) #fff;
31 | background: -o-radial-gradient(farthest-side, rgba(0,0,0,0) 90%, rgba(0,0,0,0.2) 150%) #fff;
32 | background: radial-gradient(farthest-side, rgba(0,0,0,0) 90%, rgba(0,0,0,0.2) 150%) #fff;
33 | box-sizing: border-box;
34 | }
35 | a {
36 | color: navy;
37 | }
38 |
39 | body > section {
40 | display: flex;
41 | flex-direction: column;
42 | justify-content: center;
43 | align-content: center;
44 | align-items: center;
45 | box-sizing: border-box;
46 | height: 100%;
47 | -webkit-perspective: 800;
48 | -webkit-transform-style: preserve-3d;
49 | }
50 | section > * {
51 | display: flex;
52 | align-items: center;
53 | position: relative;
54 | z-index: 1;
55 | }
56 |
57 | .fft {
58 | position: absolute;
59 | z-index: 1;
60 | -webkit-box-reflect: below 5px -webkit-linear-gradient(top, transparent, transparent 50%, rgba(255,255,255,0.2));
61 | }
62 | #fft {
63 | transform: translateX(-50%) rotateX(-30deg) rotateY(30deg);
64 | }
65 | #fft2 {
66 | transform: translateX(-50%) rotateX(-30deg) rotateY(-30deg);
67 | }
68 |
69 | #playbutton {
70 | cursor: pointer;
71 | position: relative;
72 | z-index: 10;
73 | box-sizing: border-box;
74 | width: 125px;
75 | height: 125px;
76 | border-radius: 100%;
77 | border: 4px solid rgba(0,0,0,.9);
78 | }
79 |
80 | #playbutton.playing {
81 | background: rgba(0,0,0,.25);
82 | opacity: 0.1;
83 | }
84 | #playbutton::before {
85 | content: '\feff';
86 | display: inline-block;
87 | border-style: solid;
88 | border-color: transparent transparent transparent rgba(0,0,0,.9);
89 | border-width: 30px 0 30px 40px;
90 | box-sizing: border-box;
91 | height:0;
92 | width: 50%;
93 | margin-left: 45px;
94 | }
95 |
96 | #playbutton.playing::before, #playbutton.playing:hover::before {
97 | content: '\feff';
98 | display: inline-block;
99 | border: 4px solid rgba(0,0,0,.9);
100 | border-width: 0 20px;
101 | box-sizing: border-box;
102 | height: 60%;
103 | width: 50%;
104 | margin: 0 auto;
105 | }
106 |
107 | #transcript {
108 | font-size: 1.2em;
109 | line-height: 1.3em;
110 | max-width: 50em;
111 | height: calc(1.3em * 5);
112 | position: relative;
113 | min-width: 40em;
114 | overflow: hidden;
115 | box-sizing: border-box;
116 | transform: translateY(-1em);
117 | }
118 | #transcript>div {
119 | position: absolute;
120 | bottom: 0;
121 | padding: .7em;
122 | margin: 0 auto;
123 | left: 50%;
124 | transform: translate(-50%, -1rem);
125 | width: 100%;
126 | text-align: center;
127 | box-sizing: border-box;
128 | background: rgba(195,215,264, .5);
129 | border: 1px solid rgba(0,0,0,.2);
130 | border-radius: .5em;
131 | }
132 | #transcript>div:empty {
133 | visibility: hidden;
134 | }
135 |
--------------------------------------------------------------------------------
/04-speech/src/main/java/com/example/flexible/speak/TranscribeSocket.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2017 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.example.flexible.speak;
18 |
19 | import com.google.api.gax.rpc.ApiStreamObserver;
20 | import com.google.api.gax.rpc.BidiStreamingCallable;
21 | import com.google.cloud.speech.v1.RecognitionConfig;
22 | import com.google.cloud.speech.v1.RecognitionConfig.AudioEncoding;
23 | import com.google.cloud.speech.v1.SpeechClient;
24 | import com.google.cloud.speech.v1.StreamingRecognitionConfig;
25 | import com.google.cloud.speech.v1.StreamingRecognitionResult;
26 | import com.google.cloud.speech.v1.StreamingRecognizeRequest;
27 | import com.google.cloud.speech.v1.StreamingRecognizeResponse;
28 | import com.google.gson.Gson;
29 | import com.google.protobuf.ByteString;
30 | import io.grpc.auth.ClientAuthInterceptor;
31 | import org.eclipse.jetty.websocket.api.WebSocketAdapter;
32 |
33 | import java.io.IOException;
34 | import java.util.Arrays;
35 | import java.util.List;
36 | import java.util.concurrent.Executors;
37 | import java.util.concurrent.TimeUnit;
38 | import java.util.logging.Level;
39 | import java.util.logging.Logger;
40 |
41 | public class TranscribeSocket extends WebSocketAdapter
42 | implements ApiStreamObserver {
43 |
44 | private static final Logger logger = Logger.getLogger(TranscribeSocket.class.getName());
45 | ApiStreamObserver requestObserver;
46 | private Gson gson;
47 | SpeechClient speech;
48 |
49 | public TranscribeSocket() {
50 | gson = new Gson();
51 | }
52 |
53 | /**
54 | * Called when the client sends this server some raw bytes (ie audio data).
55 | */
56 | @Override
57 | public void onWebSocketBinary(byte[] payload, int offset, int len) {
58 | if (isConnected()) {
59 | StreamingRecognizeRequest request =
60 | StreamingRecognizeRequest.newBuilder()
61 | .setAudioContent(ByteString.copyFrom(payload, offset, len))
62 | .build();
63 | requestObserver.onNext(request);
64 | }
65 | }
66 |
67 | /**
68 | * Called when the client sends this server some text.
69 | */
70 | @Override
71 | public void onWebSocketText(String message) {
72 | if (isConnected()) {
73 | Constraints constraints = gson.fromJson(message, Constraints.class);
74 | logger.info(String.format("Got sampleRate: %s", constraints.sampleRate));
75 |
76 | try {
77 | speech = SpeechClient.create();
78 | BidiStreamingCallable callable =
79 | speech.streamingRecognizeCallable();
80 |
81 | requestObserver = callable.bidiStreamingCall(this);
82 | // Build and send a StreamingRecognizeRequest containing the parameters for
83 | // processing the audio.
84 | RecognitionConfig config =
85 | RecognitionConfig.newBuilder()
86 | .setEncoding(AudioEncoding.LINEAR16)
87 | .setSampleRateHertz(constraints.sampleRate)
88 | .setLanguageCode("en-US")
89 | .build();
90 | StreamingRecognitionConfig streamingConfig =
91 | StreamingRecognitionConfig.newBuilder()
92 | .setConfig(config)
93 | .setInterimResults(true)
94 | .setSingleUtterance(false)
95 | .build();
96 |
97 | StreamingRecognizeRequest initial =
98 | StreamingRecognizeRequest.newBuilder().setStreamingConfig(streamingConfig).build();
99 | requestObserver.onNext(initial);
100 |
101 | getRemote().sendString(message);
102 | } catch (IOException e) {
103 | logger.log(Level.WARNING, "Error onWebSocketText", e);
104 | }
105 | }
106 | }
107 |
108 | public void closeApiChannel() {
109 | speech.close();
110 | }
111 |
112 | /**
113 | * Called when the connection to the client is closed.
114 | */
115 | @Override
116 | public void onWebSocketClose(int statusCode, String reason) {
117 | logger.info("Websocket close.");
118 | requestObserver.onCompleted();
119 | closeApiChannel();
120 | }
121 |
122 | /**
123 | * Called if there's an error connecting with the client.
124 | */
125 | @Override
126 | public void onWebSocketError(Throwable cause) {
127 | logger.log(Level.WARNING, "Websocket error", cause);
128 | requestObserver.onError(cause);
129 | closeApiChannel();
130 | }
131 |
132 |
133 | /**
134 | * Called when the Speech API has a transcription result for us.
135 | */
136 | @Override
137 | public void onNext(StreamingRecognizeResponse response) {
138 | List results = response.getResultsList();
139 | if (results.size() < 1) {
140 | return;
141 | }
142 |
143 | try {
144 | StreamingRecognitionResult result = results.get(0);
145 | logger.info("Got result " + result);
146 | //String transcript = result.getAlternatives(0).getTranscript();
147 | getRemote().sendString(gson.toJson(result));
148 | } catch (IOException e) {
149 | logger.log(Level.WARNING, "Error sending to websocket", e);
150 | }
151 | }
152 |
153 | /**
154 | * Called if the API call throws an error.
155 | */
156 | @Override
157 | public void onError(Throwable error) {
158 | logger.log(Level.WARNING, "recognize failed", error);
159 | // Close the websocket
160 | getSession().close(500, error.toString());
161 | closeApiChannel();
162 | }
163 |
164 | /**
165 | * Called when the API call is complete.
166 | */
167 | @Override
168 | public void onCompleted() {
169 | logger.info("recognize completed.");
170 | // Close the websocket
171 | getSession().close();
172 | closeApiChannel();
173 | }
174 |
175 | // Taken wholesale from StreamingRecognizeClient.java
176 | private static final List OAUTH2_SCOPES =
177 | Arrays.asList("https://www.googleapis.com/auth/cloud-platform");
178 | }
179 |
--------------------------------------------------------------------------------
/02-webaudio/src/main/webapp/javascript/main.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2017, Google, Inc.
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software
10 | * distributed under the License is distributed on an "AS IS" BASIS,
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | * See the License for the specific language governing permissions and
13 | * limitations under the License.
14 | */
15 | (function helperDrawingFunctions() {
16 | CanvasRenderingContext2D.prototype.line = function(x1, y1, x2, y2) {
17 | this.lineCap = 'round';
18 | this.beginPath();
19 | this.moveTo(x1, y1);
20 | this.lineTo(x2, y2);
21 | this.closePath();
22 | this.stroke();
23 | }
24 | CanvasRenderingContext2D.prototype.circle = function(x, y, r, fill_opt) {
25 | this.beginPath();
26 | this.arc(x, y, r, 0, Math.PI * 2, true);
27 | this.closePath();
28 | if (fill_opt) {
29 | this.fillStyle = 'rgba(0,0,0,1)';
30 | this.fill();
31 | this.stroke();
32 | } else {
33 | this.stroke();
34 | }
35 | }
36 | CanvasRenderingContext2D.prototype.rectangle = function(x, y, w, h, fill_opt) {
37 | this.beginPath();
38 | this.rect(x, y, w, h);
39 | this.closePath();
40 | if (fill_opt) {
41 | this.fillStyle = 'rgba(0,0,0,1)';
42 | this.fill();
43 | } else {
44 | this.stroke();
45 | }
46 | }
47 | CanvasRenderingContext2D.prototype.triangle = function(p1, p2, p3, fill_opt) {
48 | // Stroked triangle.
49 | this.beginPath();
50 | this.moveTo(p1.x, p1.y);
51 | this.lineTo(p2.x, p2.y);
52 | this.lineTo(p3.x, p3.y);
53 | this.closePath();
54 | if (fill_opt) {
55 | this.fillStyle = 'rgba(0,0,0,1)';
56 | this.fill();
57 | } else {
58 | this.stroke();
59 | }
60 | }
61 | CanvasRenderingContext2D.prototype.clear = function() {
62 | this.clearRect(0, 0, this.canvas.clientWidth, this.canvas.clientHeight);
63 | }
64 | })();
65 |
66 |
67 | (function playButtonHandler() {
68 | // The play button is the canonical state, which changes via events.
69 | var playButton = document.getElementById('playbutton');
70 |
71 | playButton.addEventListener('click', function(e) {
72 | if (this.classList.contains('playing')) {
73 | playButton.dispatchEvent(new Event('pause'));
74 | } else {
75 | playButton.dispatchEvent(new Event('play'));
76 | }
77 | }, true);
78 |
79 | // Update the appearance when the state changes
80 | playButton.addEventListener('play', function(e) {
81 | this.classList.add('playing');
82 | });
83 | playButton.addEventListener('pause', function(e) {
84 | this.classList.remove('playing');
85 | });
86 | })();
87 |
88 |
89 | (function audioInit() {
90 | // Check for non Web Audio API browsers.
91 | if (!window.AudioContext) {
92 | alert("Web Audio isn't available in your browser.");
93 | return;
94 | }
95 |
96 | var canvas = document.getElementById('fft');
97 | var ctx = canvas.getContext('2d');
98 |
99 | var canvas2 = document.getElementById('fft2');
100 | var ctx2 = canvas2.getContext('2d');
101 |
102 | const CANVAS_HEIGHT = canvas.height;
103 | const CANVAS_WIDTH = canvas.width;
104 |
105 | var analyser;
106 |
107 | function rafCallback(time) {
108 | window.requestAnimationFrame(rafCallback, canvas);
109 |
110 | if (!analyser) return;
111 | var freqByteData = new Uint8Array(analyser.frequencyBinCount);
112 | analyser.getByteFrequencyData(freqByteData); //analyser.getByteTimeDomainData(freqByteData);
113 |
114 | var SPACER_WIDTH = 10;
115 | var BAR_WIDTH = 5;
116 | var OFFSET = 100;
117 | var CUTOFF = 23;
118 | var numBars = Math.round(CANVAS_WIDTH / SPACER_WIDTH);
119 |
120 | ctx.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
121 | ctx.fillStyle = '#F6D565';
122 | ctx.lineCap = 'round';
123 |
124 | ctx2.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
125 | ctx2.fillStyle = '#3A5E8C';
126 | ctx2.lineCap = 'round';
127 |
128 | // Draw rectangle for each frequency bin.
129 | for (var i = 0; i < numBars; ++i) {
130 | var magnitude = freqByteData[i + OFFSET];
131 | ctx.fillRect(i * SPACER_WIDTH, CANVAS_HEIGHT, BAR_WIDTH, -magnitude);
132 | ctx2.fillRect(i * SPACER_WIDTH, CANVAS_HEIGHT, BAR_WIDTH, -magnitude);
133 | }
134 | }
135 | rafCallback();
136 |
137 | // per https://g.co/cloud/speech/reference/rest/v1beta1/RecognitionConfig
138 | const SAMPLE_RATE = 16000;
139 | const SAMPLE_SIZE = 16;
140 |
141 | var playButton = document.getElementById('playbutton');
142 |
143 | // Hook up the play/pause state to the microphone context
144 | var context = new AudioContext();
145 | playButton.addEventListener('pause', context.suspend.bind(context));
146 | playButton.addEventListener('play', context.resume.bind(context));
147 |
148 | // The first time you hit play, connect to the microphone
149 | playButton.addEventListener('play', function startRecording() {
150 | var audioPromise = navigator.mediaDevices.getUserMedia({
151 | audio: {
152 | echoCancellation: true,
153 | channelCount: 1,
154 | sampleRate: {
155 | ideal: SAMPLE_RATE
156 | },
157 | sampleSize: SAMPLE_SIZE
158 | }
159 | });
160 |
161 | audioPromise.then(function(micStream) {
162 | var microphone = context.createMediaStreamSource(micStream);
163 | analyser = context.createAnalyser();
164 | microphone.connect(analyser);
165 | }).catch(console.log.bind(console));
166 |
167 | (function testGetRawAudioBytes() {
168 | var scriptNode, sourceNode;
169 | audioPromise.then(function(micStream) {
170 | // Create a node to access raw audio bytes
171 | var scriptNode = context.createScriptProcessor(4096, 1, 1);
172 | scriptNode.addEventListener('audioprocess', function(e) {
173 | var floatSamples = e.inputBuffer.getChannelData(0);
174 |
175 | // Display the first couple samples to make sure this is working
176 | document.getElementById('transcript').childNodes[0].innerText =
177 | 'sampleRate: ' + context.sampleRate +
178 | 'Hz\nSample: ' + floatSamples;
179 | });
180 |
181 | var sourceNode = context.createMediaStreamSource(micStream);
182 | sourceNode.connect(scriptNode);
183 | scriptNode.connect(context.destination);
184 |
185 | // Disconnect the display after a bit, since it's fairly intensive
186 | setTimeout(function() {
187 | scriptNode.disconnect();
188 | sourceNode.disconnect();
189 | }, 1000);
190 | });
191 | })();
192 |
193 | }, {once: true});
194 | })();
195 |
196 |
--------------------------------------------------------------------------------
/03-websockets/src/main/webapp/javascript/main.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2017, Google, Inc.
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software
10 | * distributed under the License is distributed on an "AS IS" BASIS,
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | * See the License for the specific language governing permissions and
13 | * limitations under the License.
14 | */
15 | (function helperDrawingFunctions() {
16 | CanvasRenderingContext2D.prototype.line = function(x1, y1, x2, y2) {
17 | this.lineCap = 'round';
18 | this.beginPath();
19 | this.moveTo(x1, y1);
20 | this.lineTo(x2, y2);
21 | this.closePath();
22 | this.stroke();
23 | }
24 | CanvasRenderingContext2D.prototype.circle = function(x, y, r, fill_opt) {
25 | this.beginPath();
26 | this.arc(x, y, r, 0, Math.PI * 2, true);
27 | this.closePath();
28 | if (fill_opt) {
29 | this.fillStyle = 'rgba(0,0,0,1)';
30 | this.fill();
31 | this.stroke();
32 | } else {
33 | this.stroke();
34 | }
35 | }
36 | CanvasRenderingContext2D.prototype.rectangle = function(x, y, w, h, fill_opt) {
37 | this.beginPath();
38 | this.rect(x, y, w, h);
39 | this.closePath();
40 | if (fill_opt) {
41 | this.fillStyle = 'rgba(0,0,0,1)';
42 | this.fill();
43 | } else {
44 | this.stroke();
45 | }
46 | }
47 | CanvasRenderingContext2D.prototype.triangle = function(p1, p2, p3, fill_opt) {
48 | // Stroked triangle.
49 | this.beginPath();
50 | this.moveTo(p1.x, p1.y);
51 | this.lineTo(p2.x, p2.y);
52 | this.lineTo(p3.x, p3.y);
53 | this.closePath();
54 | if (fill_opt) {
55 | this.fillStyle = 'rgba(0,0,0,1)';
56 | this.fill();
57 | } else {
58 | this.stroke();
59 | }
60 | }
61 | CanvasRenderingContext2D.prototype.clear = function() {
62 | this.clearRect(0, 0, this.canvas.clientWidth, this.canvas.clientHeight);
63 | }
64 | })();
65 |
66 |
67 | (function playButtonHandler() {
68 | // The play button is the canonical state, which changes via events.
69 | var playButton = document.getElementById('playbutton');
70 |
71 | playButton.addEventListener('click', function(e) {
72 | if (this.classList.contains('playing')) {
73 | playButton.dispatchEvent(new Event('pause'));
74 | } else {
75 | playButton.dispatchEvent(new Event('play'));
76 | }
77 | }, true);
78 |
79 | // Update the appearance when the state changes
80 | playButton.addEventListener('play', function(e) {
81 | this.classList.add('playing');
82 | });
83 | playButton.addEventListener('pause', function(e) {
84 | this.classList.remove('playing');
85 | });
86 | })();
87 |
88 |
89 | (function audioInit() {
90 | // Check for non Web Audio API browsers.
91 | if (!window.AudioContext) {
92 | alert("Web Audio isn't available in your browser.");
93 | return;
94 | }
95 |
96 | var canvas = document.getElementById('fft');
97 | var ctx = canvas.getContext('2d');
98 |
99 | var canvas2 = document.getElementById('fft2');
100 | var ctx2 = canvas2.getContext('2d');
101 |
102 | const CANVAS_HEIGHT = canvas.height;
103 | const CANVAS_WIDTH = canvas.width;
104 |
105 | var analyser;
106 |
107 | function rafCallback(time) {
108 | window.requestAnimationFrame(rafCallback, canvas);
109 |
110 | if (!analyser) return;
111 | var freqByteData = new Uint8Array(analyser.frequencyBinCount);
112 | analyser.getByteFrequencyData(freqByteData); //analyser.getByteTimeDomainData(freqByteData);
113 |
114 | var SPACER_WIDTH = 10;
115 | var BAR_WIDTH = 5;
116 | var OFFSET = 100;
117 | var CUTOFF = 23;
118 | var numBars = Math.round(CANVAS_WIDTH / SPACER_WIDTH);
119 |
120 | ctx.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
121 | ctx.fillStyle = '#F6D565';
122 | ctx.lineCap = 'round';
123 |
124 | ctx2.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
125 | ctx2.fillStyle = '#3A5E8C';
126 | ctx2.lineCap = 'round';
127 |
128 | // Draw rectangle for each frequency bin.
129 | for (var i = 0; i < numBars; ++i) {
130 | var magnitude = freqByteData[i + OFFSET];
131 | ctx.fillRect(i * SPACER_WIDTH, CANVAS_HEIGHT, BAR_WIDTH, -magnitude);
132 | ctx2.fillRect(i * SPACER_WIDTH, CANVAS_HEIGHT, BAR_WIDTH, -magnitude);
133 | }
134 | }
135 | rafCallback();
136 |
137 | // per https://g.co/cloud/speech/reference/rest/v1beta1/RecognitionConfig
138 | const SAMPLE_RATE = 16000;
139 | const SAMPLE_SIZE = 16;
140 |
141 | var playButton = document.getElementById('playbutton');
142 |
143 | // Hook up the play/pause state to the microphone context
144 | var context = new AudioContext();
145 | playButton.addEventListener('pause', context.suspend.bind(context));
146 | playButton.addEventListener('play', context.resume.bind(context));
147 |
148 | // The first time you hit play, connect to the microphone
149 | playButton.addEventListener('play', function startRecording() {
150 | var audioPromise = navigator.mediaDevices.getUserMedia({
151 | audio: {
152 | echoCancellation: true,
153 | channelCount: 1,
154 | sampleRate: {
155 | ideal: SAMPLE_RATE
156 | },
157 | sampleSize: SAMPLE_SIZE
158 | }
159 | });
160 |
161 | audioPromise.then(function(micStream) {
162 | var microphone = context.createMediaStreamSource(micStream);
163 | analyser = context.createAnalyser();
164 | microphone.connect(analyser);
165 | }).catch(console.log.bind(console));
166 |
167 | initWebsocket(audioPromise);
168 | }, {once: true});
169 |
170 |
171 | /**
172 | * Hook up event handlers to create / destroy websockets, and audio nodes to
173 | * transmit audio bytes through it.
174 | */
175 | function initWebsocket(audioPromise) {
176 | var socket;
177 | var sourceNode;
178 |
179 | // Create a node that sends raw bytes across the websocket
180 | var scriptNode = context.createScriptProcessor(4096, 1, 1);
181 | scriptNode.addEventListener('audioprocess', function(e) {
182 | var floatSamples = e.inputBuffer.getChannelData(0);
183 | socket.send(floatSamples);
184 | });
185 |
186 | function newWebsocket() {
187 | var websocketPromise = new Promise(function(resolve, reject) {
188 | var socket = new WebSocket('wss://' + location.host + '/transcribe');
189 | socket.addEventListener('open', resolve);
190 | socket.addEventListener('error', reject);
191 | });
192 |
193 | Promise.all([audioPromise, websocketPromise]).then(function(values) {
194 | var micStream = values[0];
195 | socket = values[1].target;
196 |
197 | // If the socket is closed for whatever reason, pause the mic
198 | socket.addEventListener('close', function(e) {
199 | console.log('Websocket closing..');
200 | playButton.dispatchEvent(new Event('pause'));
201 | });
202 | socket.addEventListener('error', function(e) {
203 | console.log('Error from websocket', e);
204 | playButton.dispatchEvent(new Event('pause'));
205 | });
206 |
207 | function startByteStream(e) {
208 | // Hook up the scriptNode to the mic
209 | sourceNode = context.createMediaStreamSource(micStream);
210 | sourceNode.connect(scriptNode);
211 | scriptNode.connect(context.destination);
212 | }
213 |
214 | // Send the initial configuration message. When the server acknowledges
215 | // it, start streaming the audio bytes to the server and listening for
216 | // transcriptions.
217 | socket.addEventListener('message', function(e) {
218 | socket.addEventListener('message', onTranscription);
219 | startByteStream(e);
220 | }, {once: true});
221 |
222 | socket.send(JSON.stringify({sampleRate: context.sampleRate}));
223 |
224 | }).catch(console.log.bind(console));
225 | }
226 |
227 | function closeWebsocket() {
228 | scriptNode.disconnect();
229 | if (sourceNode) sourceNode.disconnect();
230 | if (socket && socket.readyState === socket.OPEN) socket.close();
231 | }
232 |
233 | function toggleWebsocket(e) {
234 | var context = e.target;
235 | if (context.state === 'running') {
236 | newWebsocket();
237 | } else if (context.state === 'suspended') {
238 | closeWebsocket();
239 | }
240 | }
241 |
242 | /**
243 | * This function is called with the transcription result from the server.
244 | */
245 | function onTranscription(e) {
246 | console.log(e.data);
247 | }
248 |
249 | // When the mic is resumed or paused, change the state of the websocket too
250 | context.addEventListener('statechange', toggleWebsocket);
251 | // initialize for the current state
252 | toggleWebsocket({target: context});
253 | }
254 | })();
255 |
256 |
--------------------------------------------------------------------------------
/04-speech/src/main/webapp/javascript/main.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2017, Google, Inc.
3 | * Licensed under the Apache License, Version 2.0 (the "License");
4 | * you may not use this file except in compliance with the License.
5 | * You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software
10 | * distributed under the License is distributed on an "AS IS" BASIS,
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | * See the License for the specific language governing permissions and
13 | * limitations under the License.
14 | */
15 | (function helperDrawingFunctions() {
16 | CanvasRenderingContext2D.prototype.line = function(x1, y1, x2, y2) {
17 | this.lineCap = 'round';
18 | this.beginPath();
19 | this.moveTo(x1, y1);
20 | this.lineTo(x2, y2);
21 | this.closePath();
22 | this.stroke();
23 | }
24 | CanvasRenderingContext2D.prototype.circle = function(x, y, r, fill_opt) {
25 | this.beginPath();
26 | this.arc(x, y, r, 0, Math.PI * 2, true);
27 | this.closePath();
28 | if (fill_opt) {
29 | this.fillStyle = 'rgba(0,0,0,1)';
30 | this.fill();
31 | this.stroke();
32 | } else {
33 | this.stroke();
34 | }
35 | }
36 | CanvasRenderingContext2D.prototype.rectangle = function(x, y, w, h, fill_opt) {
37 | this.beginPath();
38 | this.rect(x, y, w, h);
39 | this.closePath();
40 | if (fill_opt) {
41 | this.fillStyle = 'rgba(0,0,0,1)';
42 | this.fill();
43 | } else {
44 | this.stroke();
45 | }
46 | }
47 | CanvasRenderingContext2D.prototype.triangle = function(p1, p2, p3, fill_opt) {
48 | // Stroked triangle.
49 | this.beginPath();
50 | this.moveTo(p1.x, p1.y);
51 | this.lineTo(p2.x, p2.y);
52 | this.lineTo(p3.x, p3.y);
53 | this.closePath();
54 | if (fill_opt) {
55 | this.fillStyle = 'rgba(0,0,0,1)';
56 | this.fill();
57 | } else {
58 | this.stroke();
59 | }
60 | }
61 | CanvasRenderingContext2D.prototype.clear = function() {
62 | this.clearRect(0, 0, this.canvas.clientWidth, this.canvas.clientHeight);
63 | }
64 | })();
65 |
66 |
67 | (function playButtonHandler() {
68 | // The play button is the canonical state, which changes via events.
69 | var playButton = document.getElementById('playbutton');
70 |
71 | playButton.addEventListener('click', function(e) {
72 | if (this.classList.contains('playing')) {
73 | playButton.dispatchEvent(new Event('pause'));
74 | } else {
75 | playButton.dispatchEvent(new Event('play'));
76 | }
77 | }, true);
78 |
79 | // Update the appearance when the state changes
80 | playButton.addEventListener('play', function(e) {
81 | this.classList.add('playing');
82 | });
83 | playButton.addEventListener('pause', function(e) {
84 | this.classList.remove('playing');
85 | });
86 | })();
87 |
88 |
89 | (function audioInit() {
90 | // Check for non Web Audio API browsers.
91 | if (!window.AudioContext) {
92 | alert("Web Audio isn't available in your browser.");
93 | return;
94 | }
95 |
96 | var canvas = document.getElementById('fft');
97 | var ctx = canvas.getContext('2d');
98 |
99 | var canvas2 = document.getElementById('fft2');
100 | var ctx2 = canvas2.getContext('2d');
101 |
102 | const CANVAS_HEIGHT = canvas.height;
103 | const CANVAS_WIDTH = canvas.width;
104 |
105 | var analyser;
106 |
107 | function rafCallback(time) {
108 | window.requestAnimationFrame(rafCallback, canvas);
109 |
110 | if (!analyser) return;
111 | var freqByteData = new Uint8Array(analyser.frequencyBinCount);
112 | analyser.getByteFrequencyData(freqByteData); //analyser.getByteTimeDomainData(freqByteData);
113 |
114 | var SPACER_WIDTH = 10;
115 | var BAR_WIDTH = 5;
116 | var OFFSET = 100;
117 | var CUTOFF = 23;
118 | var numBars = Math.round(CANVAS_WIDTH / SPACER_WIDTH);
119 |
120 | ctx.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
121 | ctx.fillStyle = '#F6D565';
122 | ctx.lineCap = 'round';
123 |
124 | ctx2.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
125 | ctx2.fillStyle = '#3A5E8C';
126 | ctx2.lineCap = 'round';
127 |
128 | // Draw rectangle for each frequency bin.
129 | for (var i = 0; i < numBars; ++i) {
130 | var magnitude = freqByteData[i + OFFSET];
131 | ctx.fillRect(i * SPACER_WIDTH, CANVAS_HEIGHT, BAR_WIDTH, -magnitude);
132 | ctx2.fillRect(i * SPACER_WIDTH, CANVAS_HEIGHT, BAR_WIDTH, -magnitude);
133 | }
134 | }
135 | rafCallback();
136 |
137 | // per https://g.co/cloud/speech/reference/rest/v1beta1/RecognitionConfig
138 | const SAMPLE_RATE = 16000;
139 | const SAMPLE_SIZE = 16;
140 |
141 | var playButton = document.getElementById('playbutton');
142 |
143 | // Hook up the play/pause state to the microphone context
144 | var context = new AudioContext();
145 | playButton.addEventListener('pause', context.suspend.bind(context));
146 | playButton.addEventListener('play', context.resume.bind(context));
147 |
148 | // The first time you hit play, connect to the microphone
149 | playButton.addEventListener('play', function startRecording() {
150 | var audioPromise = navigator.mediaDevices.getUserMedia({
151 | audio: {
152 | echoCancellation: true,
153 | channelCount: 1,
154 | sampleRate: {
155 | ideal: SAMPLE_RATE
156 | },
157 | sampleSize: SAMPLE_SIZE
158 | }
159 | });
160 |
161 | audioPromise.then(function(micStream) {
162 | var microphone = context.createMediaStreamSource(micStream);
163 | analyser = context.createAnalyser();
164 | microphone.connect(analyser);
165 | }).catch(console.log.bind(console));
166 |
167 | initWebsocket(audioPromise);
168 | }, {once: true});
169 |
170 |
171 | /**
172 | * Hook up event handlers to create / destroy websockets, and audio nodes to
173 | * transmit audio bytes through it.
174 | */
175 | function initWebsocket(audioPromise) {
176 | var socket;
177 | var sourceNode;
178 |
179 | // Create a node that sends raw bytes across the websocket
180 | var scriptNode = context.createScriptProcessor(4096, 1, 1);
181 | // Need the maximum value for 16-bit signed samples, to convert from float.
182 | const MAX_INT = Math.pow(2, 16 - 1) - 1;
183 | scriptNode.addEventListener('audioprocess', function(e) {
184 | var floatSamples = e.inputBuffer.getChannelData(0);
185 | // The samples are floats in range [-1, 1]. Convert to 16-bit signed
186 | // integer.
187 | socket.send(Int16Array.from(floatSamples.map(function(n) {
188 | return n * MAX_INT;
189 | })));
190 | });
191 |
192 | function newWebsocket() {
193 | var websocketPromise = new Promise(function(resolve, reject) {
194 | var socket = new WebSocket('wss://' + location.host + '/transcribe');
195 | socket.addEventListener('open', resolve);
196 | socket.addEventListener('error', reject);
197 | });
198 |
199 | Promise.all([audioPromise, websocketPromise]).then(function(values) {
200 | var micStream = values[0];
201 | socket = values[1].target;
202 |
203 | // If the socket is closed for whatever reason, pause the mic
204 | socket.addEventListener('close', function(e) {
205 | console.log('Websocket closing..');
206 | playButton.dispatchEvent(new Event('pause'));
207 | });
208 | socket.addEventListener('error', function(e) {
209 | console.log('Error from websocket', e);
210 | playButton.dispatchEvent(new Event('pause'));
211 | });
212 |
213 | function startByteStream(e) {
214 | // Hook up the scriptNode to the mic
215 | sourceNode = context.createMediaStreamSource(micStream);
216 | sourceNode.connect(scriptNode);
217 | scriptNode.connect(context.destination);
218 | }
219 |
220 | // Send the initial configuration message. When the server acknowledges
221 | // it, start streaming the audio bytes to the server and listening for
222 | // transcriptions.
223 | socket.addEventListener('message', function(e) {
224 | socket.addEventListener('message', onTranscription);
225 | startByteStream(e);
226 | }, {once: true});
227 |
228 | socket.send(JSON.stringify({sampleRate: context.sampleRate}));
229 |
230 | }).catch(console.log.bind(console));
231 | }
232 |
233 | function closeWebsocket() {
234 | scriptNode.disconnect();
235 | if (sourceNode) sourceNode.disconnect();
236 | if (socket && socket.readyState === socket.OPEN) socket.close();
237 | }
238 |
239 | function toggleWebsocket(e) {
240 | var context = e.target;
241 | if (context.state === 'running') {
242 | newWebsocket();
243 | } else if (context.state === 'suspended') {
244 | closeWebsocket();
245 | }
246 | }
247 |
248 | var transcript = {
249 | el: document.getElementById('transcript').childNodes[0],
250 | current: document.createElement('div')
251 | };
252 | transcript.el.appendChild(transcript.current);
253 | /**
254 | * This function is called with the transcription result from the server.
255 | */
256 | function onTranscription(e) {
257 | var result = JSON.parse(e.data);
258 | if (result.alternatives_) {
259 | transcript.current.innerHTML = result.alternatives_[0].transcript_;
260 | }
261 | if (result.isFinal_) {
262 | transcript.current = document.createElement('div');
263 | transcript.el.appendChild(transcript.current);
264 | }
265 | }
266 |
267 | // When the mic is resumed or paused, change the state of the websocket too
268 | context.addEventListener('statechange', toggleWebsocket);
269 | // initialize for the current state
270 | toggleWebsocket({target: context});
271 | }
272 | })();
273 |
274 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [yyyy] [name of copyright owner]
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------