Azure のText to Speechをブラウザ上でSDKではなくダウンロードして再生させる

Text to SpeechはSDKを使えば容易に再生が可能になります。
そういう方法ではなくSDKは使わずmp3ファイルを再生させるという方法なため、ブラウザからダウンロードしてローカルに保存もできるという方法です。
うん。需要が見えないですね。

# 全体のフロー

フローとしては以下のような形です。途中から簡単な雰囲気じゃなく変態感あふれてわくわくしますね。なぜならバッチ用で複数音声ファイルを生成できるRESTのAPIを利用してるため、複数ファイルをダウンロードさせる前提なのでzipファイルになるのです。そのためそのzipファイルをどうするか？が見所（）です。

1. Fetch APIでファイルを生成する
1. Fetch APIでファイルをダウンロードする
1. zipファイルをダウンロードする
1. zipファイルをブラウザ上で解凍してmp3ファイルを取り出す
1. Web Audio APIでmp3を再生させる

## Fetch APIでファイルを生成する
Azureにファイルの生成を指示します。ここでRESTで戻ってきたIDを元にファイルの生成状態などを確認することになります。

```javascript
function startAzureTextToAudioFile() {
    let url = "https://japaneast.customvoice.api.speech.microsoft.com/api/texttospeech/3.1-preview1/batchsynthesis";
    let body = {
        "displayName": "voicemp3 hoge",
        "description": "hogehoge",
        "textType": "PlainText",
        "synthesisConfig": {
            "voice": "ja-JP-DaichiNeural" // 声を設定
        },
        "inputs": [{ "text": "こんにちは。こんばんわ。" }],
        "properties": {
            "outputFormat": "audio-16khz-32kbitrate-mono-mp3"
        },
    };

    fetch(url,
        {
            headers: {
                'Ocp-Apim-Subscription-Key': 'XXX-key-XXXXXXXXXXXXXX',
                'Content-Type': 'application/json'
            },
            method: "POST",
            body: JSON.stringify(body)
        })
        .then(function (res) {
            return res.json();
        })
        .then((res) => {
            // res.idに状態確認などで使うidが入っている
            setTimeout(() => {
                checkAzureStatus(res.id); // 定期的に状態をチェックするための関数にわたす
            }, 500);
        })
        .catch(function (res) {
            // 取得失敗
        })
}
```

## Fetch APIでファイルをダウンロードする
生成状況を確認します。statusがSucceededになるまでループして待機します。生成失敗などのステータスの処理がないのでそのあたりはちゃんと追加実装しましょう（汗）
```javascript
function checkAzureStatus(id) {
    let url = "https://japaneast.customvoice.api.speech.microsoft.com/api/texttospeech/3.1-preview1/batchsynthesis/" + id;
    fetch(url,
        {
            headers: {
                'Ocp-Apim-Subscription-Key': 'XXX-key-XXXXXXXXXXXXXX',
                'Content-Type': 'application/json'
            },
            method: "GET"
        })
        .then(function (res) {
            return res.json();
        })
        .then((res) => {
            if (res.status == "Succeeded") {
                getAzureAudioFile(res.outputs.result);
            } else {
                setTimeout(() => {
                    checkAzureStatus(id);
                }, 500);
            }
        })
        .catch(function (res) {
            // 取得失敗
        })
}
```
## zipファイルをダウンロードする

```javascript
function getAzureAudioFile(url) {
	fetch(url)
        .then(function (res) {
	      return res.arrayBuffer();
        })
        .then((res) => {
	      decompressionAzureAudioFile(new Blob(res));
        })
        .catch(function (res) {
            // 取得失敗
        })

   /* 
    // 確認できてないが上記でなぜかうまくいブラウザもあるようなのでこちらも参考に
   let xhr = new XMLHttpRequest
    xhr.onload = function () {
        decompressionAzureAudioFile(new Blob([xhr.response]));
    }
    xhr.open('GET', url, true)
    xhr.responseType = 'arraybuffer'
    xhr.send()
    */
}
```


## zipファイルをブラウザ上で解凍してmp3ファイルを取り出す
ブラウザ上でzipを解凍して処理をするので以下のライブラリを利用するのでダウンロードして読み込んでください。
https://github.com/imaya/zlib.js

```javascript
// オーディオファイルを格納するため場所
let aBuffer;
function decompressionAzureAudioFile(file) {
    const zipReader = new FileReader();
    zipReader.onload = function () {
        try {
            const zipArr = new Uint8Array(zipReader.result);
            const unzip = new Zlib.Unzip(zipArr);
            const importFileList = unzip.getFilenames();

            let filePath;
            // 今回は1つしか音声ファイル作ってないので見つかったものだけ
            for (let i in importFileList) {
                if (importFileList[i].indexOf(".mp3") > 0) {
                    filePath = importFileList[i];
                }
            }
            if (!filePath) throw new Error("mp3 not include")

            const AudioContext = window.AudioContext || window.webkitAudioContext;
            const audioCtx = new AudioContext();
            audioCtx.decodeAudioData(unzip.decompress(filePath).buffer, function (audioBuffer) {
                aBuffer = audioBuffer;
                startAzureBlobStartBtn();
            });
        } catch (e) {
            console.log(e.message);
        }
    }
    zipReader.readAsArrayBuffer(file);
}
```

## Web Audio APIでmp3を再生させる
mp3ファイルを取得したとしてもAudioオブジェクトでとはいかないので、Web Audio APIで再生させます。ここで取得したファイルを再生と同時にダウンロードさせてもOKです。
iOS+safariなどで自動再生が難しいためaBufferという変数に一旦保存してから再生処理を別タイミングでしています。

```javascript
function startAzureBlobStartBtn() {
    var aCtx = new AudioContext();
    var audioSource = aCtx.createBufferSource();
    audioSource.buffer = aBuffer;
    audioSource.connect(aCtx.destination);
    audioSource.start();
}
```