#freeze
[[.NET]]
今回のポイントは二点あるの。
- そもそもどうして非同期再生するの?
- 再生終了をどこで検出するの?
* そもそもどうして非同期再生するの? [#x0e11a4b]
音声再生をはじめてやろうとした時、再生中に困った事のある人がいるんじゃないかしら。特にFormやWPFのGUI画面から再生かけた人。
そう、 ''再生中はウインドウがフリーズ'' しちゃうのよね。『[[時報をしゃべる時計]]』でわたしが Task.Run を使った理由もソレなの。あそこで直接 speak させると、再生中は時計が固まっちゃうのよ。
時報程度だとテキストは短いから、あんなものでいいの。
でもたとえば、青空文庫の小説を朗読するとか言い出したらそうはいかないでしょう?
こういう時には、非同期再生をさせつつ「終わったら次を再生する」ようにすればいいってわけ。
* 再生終了をどこで検出するの? [#b09ac96f]
やり方は二つあるの。
- Completed イベントを捕まえて次の再生を指示する。
- 定期的に再生中かどうか確認し、終わっていたら次にとりかかる。
もちろん、効率からいうとCompleted捕まえるのがいいわね。
だけど実は ''「 Completed イコール 読み上げおわり 」ではない'' の。
それに実際のアプリに再生を組み込んでみると、パソコンが重い時にイベントをとりこぼしてしまって、以降、何も再生されない沈黙しちゃうなんて事もあったわね。
だから、確実性を持たせるなら Speech Synthesizer の自己申告である「Completed」を信用しないで定期的に再生状態を確認するか、両方のハイブリッドでいく事をおススメするわ。
たとえば、基本的に Completedを見るけど、再生リストが掃けてないのに再生が止まってるっぽいのを検出したら強制的に再生再開を試みるとかね。そこのあたりはセンスでやればいいと思う。
* 再生コード [#uc34743a]
以下をこのままコピペしても再生されないわ。 List<string> talks に何かAddするコードを追加してあげてね。
簡単に解説すると、再生したいテキストを talks.Add("テキスト"); してどんどん追加すれば、100ミリ秒のタイマーが talks を監視していてSpeech Synthesizer の状態を確認、再生可能なら一番古い一行を流し込んで再生させるって仕組みなの。
欠点として、再生の間に 100ミリ秒そこそこの間隔が空いてしまう可能性がある。このへんが問題になるなら、 Completed を捕まえる方法と組み合わせるなり別の道を考えて。
ところで、こんな仕組みを作るって事は、大量の再生要求がある時よね。 だから tasks にはたくさんのテキストがたまると思う。メモリの残量が気になるほど食わせるのなら、その時は要注意かもね。
using System.Speech.Synthesis;
using System.Windows.Threading;
namespace talk2
{
/// <summary>
/// MainWindow.xaml の相互作用ロジック
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
initTimer();
}
SpeechSynthesizer syn = initSound();
List<string> talks = new List<string>();
DispatcherTimer timer1 = new DispatcherTimer();
private void initTimer()
{
timer1.Interval = new TimeSpan(0, 0, 0, 0, 100);
timer1.Tick += timer1_Tick;
timer1.Start();
}
/// <summary>
/// 音声再生を取り仕切る。
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void timer1_Tick(object sender, EventArgs e)
{
if (talks.Count > 0) // 再生して欲しい音声がある
{
if (syn.State == SynthesizerState.Ready) // 利用可能
{
getTalks();
}
}
}
/// <summary>
/// 最も古い文字列をひとつ非同期再生する。
/// </summary>
private void getTalks()
{
try
{
string x = talks[0];
talks.RemoveAt(0);
to1.Text = x;
syn.SpeakAsync(x);
}
catch (Exception ex)
{
syn.SpeakAsync(ex.Message);
}
}
private static SpeechSynthesizer initSound()
{
SpeechSynthesizer r = new SpeechSynthesizer();
try
{
r.SelectVoice("Microsoft Server Speech Text to Speech Voice (ja-JP, Haruka)");
}
catch (Exception ex)
{
}
return (r);
}
}
}