4️⃣
JavaScript 20 Projects - 4 Joke Teller
前言
這系列是課程 JavaScript Web Projects: 20 Projects to Build Your Portfolio 的筆記,學習利用 Javascript 做出各種互動網站。
以下是這系列的文章:
Project 1 | Quote Generator |
---|---|
Project 2 | Infinity Scroll |
Project 3 | Picture In Picture |
Project 4 | Joke Teller |
目標
這次會利用語音 API (Text-to-speech API) 跟 JokeAPI ,來實作讓電腦隨機說笑話。
下面是這次要實作的畫面。範例的連結。
建立 HTML
主要就是 <button>
以及 <audio>
兩個部分。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Joke Teller</title>
<link rel="icon" type="image/png" href="favicon.png">
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="container">
<button id="button">Tell Me A Joke</button>
<audio id="audio" controls hidden></audio>
</div>
<!-- Script -->
<script src="voiceRSS.js"></script>
<script src="script.js"></script>
</body>
</html>
若 <audio>
沒有隱藏的話會顯示出播放的部分,如下圖。
思考 javascript 所需要的動作
- 如何使用 Text-to-speech API ?
- 如何使用 Joker API ?
- 整合在一起
動作解讀
如何使用 Text-to-speech API ?
- 這次 Text-to-speech API 使用 Voice RSS。註冊後就可以取得一個 API Key。
- 取得 Text-to-Speech JavaScript SDK 所提供下載的 JavaScript 文件,以及 reference 。
- 修改下載的 JavaScript 文件。
- 原本的檔案內容沒有辦法傳到
<audio>
element,使得無法對它多做控制。 - 刪除原本的
new Audio(t.responseText).play();
- 增加
audioElement.src = t.responseText; audioElement.play();
- 原本的檔案內容沒有辦法傳到
const button = document.getElementById("button");
const audioElement = document.getElementById("audio");
// VoiceRSS Javascript SDK
const VoiceRSS={speech(e){this._validate(e),this._request(e)},_validate(e){if(!e)throw"The settings are undefined";if(!e.key)throw"The API key is undefined";if(!e.src)throw"The text is undefined";if(!e.hl)throw"The language is undefined";if(e.c&&"auto"!=e.c.toLowerCase()){let a=!1;switch(e.c.toLowerCase()){case"mp3":a=(new Audio).canPlayType("audio/mpeg").replace("no","");break;case"wav":a=(new Audio).canPlayType("audio/wav").replace("no","");break;case"aac":a=(new Audio).canPlayType("audio/aac").replace("no","");break;case"ogg":a=(new Audio).canPlayType("audio/ogg").replace("no","");break;case"caf":a=(new Audio).canPlayType("audio/x-caf").replace("no","")}if(!a)throw`The browser does not support the audio codec ${e.c}`}},_request(e){const a=this._buildRequest(e),t=this._getXHR();t.onreadystatechange=function(){if(4==t.readyState&&200==t.status){if(0==t.responseText.indexOf("ERROR"))throw t.responseText;let e=t.responseText;audioElement.src=e,audioElement.onloadedmetadata=(()=>{audioElement.play()})}},t.open("POST","https://api.voicerss.org/",!0),t.setRequestHeader("Content-Type","application/x-www-form-urlencoded; charset=UTF-8"),t.send(a)},_buildRequest(e){const a=e.c&&"auto"!=e.c.toLowerCase()?e.c:this._detectCodec();return`key=${e.key||""}&src=${e.src||""}&hl=${e.hl||""}&r=${e.r||""}&c=${a||""}&f=${e.f||""}&ssml=${e.ssml||""}&b64=true`},_detectCodec(){const e=new Audio;return e.canPlayType("audio/mpeg").replace("no","")?"mp3":e.canPlayType("audio/wav").replace("no","")?"wav":e.canPlayType("audio/aac").replace("no","")?"aac":e.canPlayType("audio/ogg").replace("no","")?"ogg":e.canPlayType("audio/x-caf").replace("no","")?"caf":""},_getXHR(){try{return new XMLHttpRequest}catch(e){}try{return new ActiveXObject("Msxml3.XMLHTTP")}catch(e){}try{return new ActiveXObject("Msxml2.XMLHTTP.6.0")}catch(e){}try{return new ActiveXObject("Msxml2.XMLHTTP.3.0")}catch(e){}try{return new ActiveXObject("Msxml2.XMLHTTP")}catch(e){}try{return new ActiveXObject("Microsoft.XMLHTTP")}catch(e){}throw"The browser does not support HTTP request"}};
// VoiceRSS Speech Function
function test() {
VoiceRSS.speech({
key: '<API key>',
src: 'Hello, world!',
hl: 'en-us',
v: 'Linda',
r: 0,
c: 'mp3',
f: '44khz_16bit_stereo',
ssml: false
});
}
如何使用 Joker API ?
- 利用連結可以取得 Joker API 的 Url。
- 利用 Url 取得資料,並把 joke 設為 empty string。
- 因為 joke 有分 single 以及 twopart,所以要先確認為哪一種形式,再設定給 joke。
// Get jokes from Joke API
async function getJokes() {
let joke = "";
const apiUrl =
"https://sv443.net/jokeapi/v2/joke/Programming?blacklistFlags=nsfw,racist,sexist";
try {
const response = await fetch(apiUrl);
const data = await response.json();
// Assign One or Two Part Joke
if (data.setup) {
joke = `${data.setup} ... ${data.delivery}`;
} else {
joke = data.joke;
}
} catch (error) {
// Catch Error Here
}
}
整合在一起
- 把原本的
test()
function 改成tellMe()
,並在getJokes()
中設定 joke 完後來呼叫tellMe()
。 - 在
tellMe()
裡多加const jokeString = joke.trim().replace(/ /g, "%20”);
,來去除字串前後的空格,以及把字串中的所有空格替換成 "%20”,確保URL 中編碼空格時使用的方式。-
joke.trim()
:trim()
方法用於去除字串的前後空白(包括空格、換行符等)。 -
replace(/ /g, "%20")
:replace()
方法用於將字串中選定的字串替換成另一個字串。在這個程式碼中,使用了正則表達式/ /g
來表示所有的空格(" "),並把他替換成 "%20"。在 URL 編碼中,"%20" 代表空格。
-
- 設置
toggleButton()
來防止 button 在載入資料時有其他動作。 - 設定 button 以及 audioElement 的 Event Listeners。
const button = document.getElementById("button");
const audioElement = document.getElementById("audio");
// Disable/Enable Button
function toggleButton() {
button.disabled = !button.disabled;
}
// VoiceRSS Speech Function
function tellMe(joke) {
const jokeString = joke.trim().replace(/ /g, "%20");
// VoiceRSS Speech Parameters
VoiceRSS.speech({
key: "API Key",
src: jokeString,
hl: "en-us",
r: 0,
c: "mp3",
f: "44khz_16bit_stereo",
ssml: false,
});
}
// Get jokes from Joke API
async function getJokes() {
let joke = "";
const apiUrl =
"https://sv443.net/jokeapi/v2/joke/Programming?blacklistFlags=nsfw,racist,sexist";
try {
const response = await fetch(apiUrl);
const data = await response.json();
// Assign One or Two Part Joke
if (data.setup) {
joke = `${data.setup} ... ${data.delivery}`;
} else {
joke = data.joke;
}
// Passing Joke to VoiceRSS API
tellMe(joke);
// Disable Button
toggleButton();
} catch (error) {
// Catch Error Here
}
}
// Event Listeners
button.addEventListener("click", getJokes);
audioElement.addEventListener("ended", toggleButton);
Discussion