# Zhen's Voice App - Readme
###### tags: `Zhen` `Android` `Android Studio`
這裡是Voice App的內容說明文件,一些參考資料和每日更新如下
https://hackmd.io/qC6DvXmUSFabkgIgLlV_7g?both=#
## 語音的部分
這邊統一使用RecognizerIntent作為應用延伸,範例影片如下:
{%youtube wcrRjqCgnl8 %}
### Google語音套件 - RecognizerIntent
[RecognizerIntent for Java in Android](https://developer.android.com/reference/android/speech/RecognizerIntent)
* 所屬在*java.lang.Object*下的*android.speech.RecognizerIntent*
* 需要透過Intent來啟動語音輸入
### RecognizerIntent實作
#### 1. 匯入相關套件
因為Alt+Enter其實就好了,這步驟可省略
``` java Java=
import android.content.Intent;
import java.util.ArrayList; //用來儲存多個辨識結果
import android.speech.RecognizerIntent; //語音辨識套件
```
#### 2. 點擊圖示來啟動RecognizerIntent介面
先在onCreat()取得圖示speak並建立其監聽事件,RecognizerIntent應該要在每一次圖示被點擊時啟動,所以我們把它寫進監聽事件裡
``` java Java=
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//取得ImageView which id==speak
textView = (TextView) this.findViewById(R.id.text);
ImageView speak = (ImageView) findViewById(R.id.speak);
//如果麥克風圖示(speak)被點擊
speak.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//放RecongnizerIntent相關設定
}
});
}
```
#### 3. 開啟語音辨識功能並送出辨識請求給Google
*RecognizerIntent.ACTION_RECOGNIZE_SPEECH* 是指啟動一activity,他會提示user開始說話並通過語音識別器發送語音,結果也會透過這個activity返回,返回結果會存放進intent變成array文字檔
```java Java=
//創建一個intent存放語音識別器回傳的文字檔
Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
```
#### 4. 設定語音辨識模型 RecognizerIntent.EXTRA_LANGUAGE_MODEL
* EXTRA_LANGUAGE_MODEL
RecognizerIntent.LANGUAGE_MODEL_FREE_FORM - Use a language model based on free-form speech recognition.
LANGUAGE_MODEL_WEB_SEARCH - Use a language model based on web search terms.
```java Java=
intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
intent.putExtra(RecognizerIntent.EXTRA_PROMPT, "請說~");
try{
startActivityForResult(intent,200);
}catch (ActivityNotFoundException a){
//網路連接失敗或沒讀取到則顯示錯誤訊息
Toast.makeText(getApplicationContext(),"Intent problem", Toast.LENGTH_SHORT).show();
}
```
#### 啟用使用INTERNET權限(實機測試可不加)
因為必須將錄製的語音送到Google的伺服器進行辨識,所以APP需要有網路的使用權限
###### 在AndroidManifast.xml檔案中加入uses-permission
```xml=
<manifest>
<application>
...
</application>
<!--給予internet權限-->
<uses-permission android:name="android.permission.INTERNET" />
</manifest>
```
那為什麼實機測試不用呢?因為一般我們的輸入法已經幫我們下載好離線翻譯,因此不會真正的傳到google伺服器唷
最終語音流程和simple code如下


``` java Java=
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//取得ImageView which id==speak
textView = (TextView) this.findViewById(R.id.text);
ImageView speak = (ImageView) findViewById(R.id.speak);
//如果麥克風圖示(speak)被點擊
speak.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH); //創建一個intent存放"錄到的音訊"的文字檔
intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
intent.putExtra(RecognizerIntent.EXTRA_PROMPT, "請說~");
try{
startActivityForResult(intent,200);
}catch (ActivityNotFoundException a){
//網路連接失敗或沒讀取到則顯示錯誤訊息
Toast.makeText(getApplicationContext(),"Intent problem", Toast.LENGTH_SHORT).show();
}
}
});
}
```
官方文件可能有點啊砸,中文精簡版可以看[這篇](https://ithelp.ithome.com.tw/m/articles/10194014)
## 畫面的部分
### 標題列和狀態列的顯示或隱藏
APP預設都是顯示的,但一撥放影片若不是全螢幕時,標題列和狀態列就會壓縮畫面空間,所以我們在onCreate()加入兩行程式將它們隱藏
```java Java=
//設定隱藏標題
getSupportActionBar().hide();
//設定隱藏狀態
getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_FULLSCREEN);
```
### 畫面顯示方向
Andriod 預設的方向為直式,但常因為手機偵測到轉向,View就會跟著畫面做比例變動位置~~強迫症的心態炸裂~~,這時工程師就要想辦法讓畫面維持水水的樣子。~~以下是會讓人爆炸的app~~
{%youtube LNsfVZ62NkA %}
**一般會做的兩種方式**
* **關掉手機的自動轉向**
* **強制手機的畫面轉向**
前者是最簡單快速的做法,但你怎麼能保證使用者不手動~~手濺~~切回來呢?再來第二個問題就是雖然關掉了自動轉向,但手機還是會偵測到轉向,此時沒有用後者強制設定的話,影片可能會自動暫停哦~
下面會說明**強制應用方向的3大種類**,因為想要從語音機器人那邊一切換(啟動)到此就是"橫屏",所以強制轉向的方法設置在onCreate()
#### 1. 強制豎屏 SCREEN_ORIENTATION_PORTRAIT
讓app介面維持在直向顯示,一般購物app會設定 e.g.蝦皮
```java Java=
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);//強制豎屏
}
```

#### 2. 強制橫屏 SCREEN_ORIENTATION_LANDSCAPE
讓app介面維持在橫向顯示,**此為範例所用**
發現她會給你warring
```java Java=
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);//強制橫屏
}
```

#### 3. 螢幕不隨手機旋轉 SCREEN_ORIENTATION_NOSENSOR
讓app畫面忽略物理感應器的方向,好處是可以從code內用條件設定e.g.縱向顯示A、橫向顯示B,但仔細想想你看影片也只會長時間直的或橫的,有點多此一舉XD
```java Java=
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR);//螢幕不隨手機旋轉
}
```
其實總共有10來種更多的螢幕畫面的感測方式,就不贅述了~~懶癌末期~~
詳細可以看[Android手動切換屏幕方向](https://www.jianshu.com/p/1903511d677f),有更多的說明與應用!
#### 強制轉向的warning message
後來發現Andriod Studio會偵測這是不良寫法,原因是為了讓所有體驗者有更好的視覺享受,但我們這邊先忽略XD ~~反正他一樣能跑~~
另外系統建議的SCREEN_ORIENTATION_UNSPECIFIED,意思是未指定,也是Android本身的默認值

## 影片的部分
嵌入影片的方式有三種,以下說明
### VideoView嵌入
* 優點:只要知道影片的實際位置就可使用(雲端也可以)、內建功能完善(暫停切換至某時間點等功能應有盡有)
* 缺點:YOUTUBE影片不會讓你知道他的實體位置~~放棄吧騷年~~
### YoutubeAPI串接
* 優點:因為是官方提供的api,不太會有改版即失效的問題
* 缺點:使用或超過流量需要付費
### WebView嵌入
* 優點:像是寫html一樣,照著[iframe標籤](https://www.w3schools.com/html/html_youtube.asp)的寫法走就好
* 缺點:使用嵌入的方式會讓原本影片的標籤無法使用
**11/01** app加上[**網路權限**](######在AndroidManifast.xml檔案中加入uses-permission)後,WebView可以正常撥放影片,示意如下
* **虛擬機**

* **實機**

沒有什麼比較要注意的地方,就是要記得[Java的特殊字元](https://www.itread01.com/p/552090.html)要記得轉成下面的寫法

將以下code加入onCreate即可顯示
```java Java=
//你的iframe格式放入網址和設定影片大小
String html="<iframe width=\"420\" height=\"315\" src=\"https://www.youtube.com/embed/zkpvaIRwiBI\"></iframe>";
WebView webView = (WebView) findViewById(R.id.webview);
WebSettings webSettings = webView.getSettings();
webSettings.setJavaScriptEnabled(true);
webSettings.setSupportZoom(true);
webSettings.setBuiltInZoomControls(true);
webSettings.setJavaScriptCanOpenWindowsAutomatically(true);
webSettings.setAppCacheEnabled(true);
webView.setWebViewClient(new WebViewClient());
webView.loadData(html, "text/html", "utf-8");
```