Promise で Web Speech API をラップして使っていましたが、キャンセルできるように実装するのに試行錯誤しました。想定していたような動きが実現できたので、メモを残しておきます。
See the Pen Web Speech API with Promise by 七誌 (@7shi) on CodePen.
↑ エラーになる場合は一度 CodePen を開いてから、この記事をリロードしてください。
概要
前回の記事では正常終了 onend
を resolve
、異常終了 onerror
を reject
として扱いました。
function speak(lang, text) {
return new Promise((resolve, reject) => {
let u = new SpeechSynthesisUtterance(text);
u.lang = lang;
u.onend = resolve;
u.onerror = reject;
speechSynthesis.speak(u);
});
}
読み上げ中に speechSynthesis.cancel()
を呼ぶことでキャンセルできます。通常終了と同じ onend
イベントが発生するため、イベントではキャンセルされたことが検知できません。
何らかの手段でキャンセルされたことを通知する必要があります。
戻り値
resolve
への引数は await
を通して戻り値になります。
> p = new Promise((resolve, reject) => resolve(123))
> await p
123
これを利用して正常終了かキャンセルかを戻り値で区別するように speak
を実装します。例外を無視するため終了と同じ扱いとします。
let stop = () => false;
function speak(lang, text) {
return new Promise((resolve, reject) => {
let speakend = cancel => {
speakend = () => false;
if (cancel) speechSynthesis.cancel();
resolve(cancel);
return cancel;
};
stop = () => speakend(true);
let u = new SpeechSynthesisUtterance(text);
u.lang = lang;
u.onend = u.onerror = () => speakend(false);
speechSynthesis.speak(u);
});
}
キャンセルするには外部から stop()
を呼びます。Promise のコンストラクタで stop
を書き換えて speakend
経由で resolve(true)
を呼べるようにしておくことで、終了イベント onend
よりも先に終了させます。
利用方法
複雑さは Promise の中に閉じ込めたため、利用側のコードは簡単になります。
button.onclick = async function() {
if (stop()) return;
button.textContent = "Stop";
for (let [element, lang, text] of texts) {
element.classList.add("speaking");
let cancel = await speak(lang, text);
element.classList.remove("speaking");
if (cancel) break;
}
button.textContent = "Start";
};
ループによっていくつかのテキストを読み上げます。speaking
をマークすることで読み上げ個所を示します。await speak()
から戻ってマークを解除して、キャンセルされていればループから抜けます。
reject
で例外によってキャンセルを通知することも可能ですが、今回は resolve
によって戻り値で通知した方が利用側のコードが簡単になると判断しました。
戻り値は正常終了のときに true
にした方が自然かもしれませんが、今回はキャンセルに注目して値を設定しました。
参考
Web Speech API の使い方は以下の記事を参照してください。