├── .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 | --------------------------------------------------------------------------------