iOS Second Stageの21日目を担当させていただく@ushisantoasobuと申します。よろしくお願いします。
トレンディなネタでもなければ、ほとんどの人が興味ないであろうAVSpeechSynthsizer
についての記事です。タイトルは冗談です。
AVSpeechSynthsizer
はiOS7
から利用できるようになったテキストを音声で読み上げるAPIです。
AVSpeechSynthsizer
を利用したアプリを当時個人でリリースしたのですが、iOS9
で少し変更点があったので(iOS8
のときは変更点なし)、今年そのアプリをフルスクラッチで書き直して別アプリとしてリリースしました。その備忘録としてこの記事では、
-
AVSpeechSynthsizer
の基本的な話 -
AVSpeechSynthsizer
のiOS9
での変更点について -
AVSpeechSynthsizer
を利用したサービスをつくってみて
といった内容で書いていきたいと思います
リリースしたアプリ
Twitter
を耳で楽しむ、音声読み上げアプリです。
- ホーム
- トレンド
- リスト
のツイートのリストを取得して、それらをAVSpeechSynthsizer
が順次読み上げていき、耳で聞いて楽しむというアプリになっています。
こちらに実際に動いているところの動画もあげています(Nexus 9
で撮ったのですが、画質悪いです)。普段自分は非公開のiOSデベロッパのリスト
から情報を追っているのですが、この動画ではそれを実際に読み上げたものになります。
もし気になった方は良ければ使ってみてください。
AVSpeechSynthsizer
の基本的な使い方
以下がテキストを音声で読み上げるまでの基本的なコードです
import AVFoundation
class SomeViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let synthesizer = AVSpeechSynthesizer()
// 読み上げる文字列を指定する
let utterance = AVSpeechUtterance(string: "ushisantoasobu")
// 読み上げの速度を指定する
utterance.rate = AVSpeechUtteranceMinimumSpeechRate
// 声の高さを指定する
utterance.pitchMultiplier = 1
// 声のボリュームを指定する
utterance.volume = 1.0
let voice = AVSpeechSynthesisVoice()
utterance.voice = voice
// 読み上げる
synthesizer.speakUtterance(utterance)
}
}
もちろん、読み上げを一時停止したりすることもできます
// 読み上げを途中で終了する(終了したところからまた再生したい場合は下のpauseを使う)
synthesizer.stopSpeakingAtBoundary(AVSpeechBoundary.Immediate)
// 読み上げを一時停止する
synthesizer.pauseSpeakingAtBoundary(AVSpeechBoundary.Immediate)
// 一時停止していた読み上げを再生する
synthesizer.continueSpeaking()
デリゲート(AVSpeechSynthesizerDelegate
)メソッドには以下のようなものがあります
// 読み上げが開始したとき
func speechSynthesizer(synthesizer: AVSpeechSynthesizer, didStartSpeechUtterance utterance: AVSpeechUtterance)
// 読み上げが終了したとき
func speechSynthesizer(synthesizer: AVSpeechSynthesizer, didFinishSpeechUtterance utterance: AVSpeechUtterance)
// 読み上げが一時停止したとき
func speechSynthesizer(synthesizer: AVSpeechSynthesizer, didPauseSpeechUtterance utterance: AVSpeechUtterance)
// 読み上げが一時停止から再生されたとき
func speechSynthesizer(synthesizer: AVSpeechSynthesizer, didContinueSpeechUtterance utterance: AVSpeechUtterance)
// 読み上げが途中で終了したとき
func speechSynthesizer(synthesizer: AVSpeechSynthesizer, didCancelSpeechUtterance utterance: AVSpeechUtterance)
// 読み上げ中で字句ごとに呼ばれる
func speechSynthesizer(synthesizer: AVSpeechSynthesizer, willSpeakRangeOfSpeechString characterRange: NSRange, utterance: AVSpeechUtterance)
iOS9
で新しくなったポイント
残念なことにワクワクするような変更点はありません。以下が新たに追加された部分になります
@available(iOS 9.0, *)
public enum AVSpeechSynthesisVoiceQuality : Int {
case Default
case Enhanced
}
// Use the Alex identifier with voiceWithIdentifier:. If the voice is present on the system,
// an AVSpeechSynthesisVoice will be returned. Alex is en-US only.
@available(iOS 9.0, *)
public let AVSpeechSynthesisVoiceIdentifierAlex: String
/*!
@method voiceWithIdentifier:
@abstract Retrieve a voice by its identifier.
@param identifier
A unique identifier for a voice.
@discussion
Passing in an invalid identifier will return nil.
Returns nil if the identifier is valid, but the voice is not available on device (i.e. not yet downloaded by the user).
*/
@available(iOS 9.0, *)
public /*not inherited*/ init?(identifier: String)
@available(iOS 9.0, *)
public var identifier: String { get }
@available(iOS 9.0, *)
public var name: String { get }
@available(iOS 9.0, *)
public var quality: AVSpeechSynthesisVoiceQuality { get }
下の3つのプロパティはget-only
です。なので注目すべきはinit?(identifier: String)
になると思います。identifier
をなにやら指定できるとのこと。ふむふむ。ただしここのidentifier
に指定できるのが、現状だとAVSpeechSynthesisVoiceIdentifierAlex
のみのようです。コメントに、
Alex is en-US only
とありますが、読み上げの言語設定がen-US
の状態で日本語を読み上げようとしても、正しく読み上げられません(無音)。
ちなみにAlex
の声を利用する場合はダウンロードする必要があるらしく、その方法がこちらに書かれていて、
「設定」>「一般」>「アクセシビリティ」>「スピーチ」>「声」
を言語設定が日本語の状態でみてみると、以下のような感じになっています。
ここらへんのSiri
の声だったり、Mac OS X
にあるOtoya
がAVSpeechSynthsizer
でいずれダウンロードすることで利用できるようになるのかなと思っています。
気をつけるべきこと
こちらはAVSpeechSynthsizer
に限った話ではないのですが、音を再生するようなアプリケーションをつくるときにはいくつか考慮しておくべき項目がありますので、以下に挙げていこうと思います
バックグラウンド再生
バックグラウンド再生に対応するかどうかはもちろん考慮する必要があります。これについては他の記事で多く見つかると思うのでここでは割愛させていただきます
イヤホンジャックを抜いたとき
音を再生するアプリにおいて、イヤホンジャックを抜いたときに再生されていた音が停止する、というのは大抵の場合対応マストの項目だと思います。電車でイヤホンジャックが抜けて、聞いていたものが車内に響き渡って大恥をかかせる、なんてことはあってはいけないですからね。。。「ミュージック」や「Podcast」ももちろんそうなっています。
実装方法は以下のようになります
NSNotificationCenter.defaultCenter().addObserver(self, selector: "onAudioSessionRouteChange:", name: AVAudioSessionRouteChangeNotification, object: nil)
@objc func onAudioSessionRouteChange(notification :NSNotification) {
// ここで再生をとめる処理を書く
}
リモコン等からの操作に対応する
Apple純正のイヤホンやロックスクリーンからの操作に対応しておくとベターかと思います。
実装方法は以下のようになります
UIApplication.sharedApplication().beginReceivingRemoteControlEvents()
override func remoteControlReceivedWithEvent(event: UIEvent?) {
// 以下に各イベントに対する処理を書く
switch event!.subtype {
case UIEventSubtype.RemoteControlPlay:
//
case UIEventSubtype.RemoteControlPause:
//
case UIEventSubtype.RemoteControlTogglePlayPause:
//
case UIEventSubtype.RemoteControlPreviousTrack:
//
case UIEventSubtype.RemoteControlNextTrack:
//
default:
//
}
}
実際にサービスでつくってみて思ったこと
AVSpeechSynthsizer
を使ったiOSアプリを実際につくってみて思ったことを以下に書いていきたいと思います
読みの精度はまだまだ(日本語)
少なくとも日本語の読み上げの精度はまだまだで、ここらへんは難しいとは思うのですが重要なところです。以下自分のタイムラインでよく出てくるテキストで読み上げに不満があるものです
-
(日)
=> 「ひ」(イベント情報なんかでよく出てくる) -
Github
=> 「ギサブ」
ちなみにTwitilized
では、'Twitter'クライアントアプリということで一部独自に変換しているものがあります(工夫しているつもり)。
- 各種リンクは「リンク」という文字列に変換
- 各種ハッシュタグは「#」を「ハッシュタグ」という文字列に変換
- リツイートをあらわす「RT」は「リツイート」という文字列に変換
rate
は実質Default
のみ
utterance.rate = AVSpeechUtteranceDefaultSpeechRate
で指定できる読み上げの速度のパラメータであるrate
については以下の3つがあります。
@available(iOS 7.0, *)
public let AVSpeechUtteranceMinimumSpeechRate: Float
@available(iOS 7.0, *)
public let AVSpeechUtteranceMaximumSpeechRate: Float
@available(iOS 7.0, *)
public let AVSpeechUtteranceDefaultSpeechRate: Float
が、Minimum
は遅すぎるし、Maximum
は速すぎて・・・個人的にはDegault
以外は使い物にならないように感じていますが・・・
参考記事