はじめに
こんにちは。
前回の投稿ではTextToSpeechを使い、とりあえず喋らせるところまででした。
今回はTextToSpeechをforeground service化し、バックグラウンドで再生できるようにしてみます。
(レイアウトXMLやTextToSpeechのイベントリスナーは前回と同じ内容のため、省略します)
前回の記事 : [Android] KotlinでTextToSpeech
環境
- macOS High Sierra 10.13.4
- Android Studio 3.0.1
- Nexus6 (Android8.1.0, API27 エミュレータ)
MainActivity.kt
前回はMainActivityの中でTextToSpeechを初期化・実行していましたが、今回はforeground serviceで実行しますので、MainActivityではサービスを開始するだけです。
なお、onClick内でSDKのバージョン判定を行い、OREO以降でのみ動作するコードになっています。
(もちろんNougat以下でもサービス実行できるコードを併記すべきですが、まだよく分かっておらず勉強中です…申し訳ありません)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | classMainActivity:AppCompatActivity(),View.OnClickListener{ override fun onCreate(savedInstanceState:Bundle?){ super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) navigation.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener) speech_ja.setOnClickListener(this) speech_en.setOnClickListener(this) } override fun onClick(v:View){ val input:String val langLocale:Locale when(v.id){ R.id.speech_ja->{ input=input_ja.text.toString() langLocale=Locale.JAPANESE } R.id.speech_en->{ input=input_en.text.toString() langLocale=Locale.ENGLISH } else->{ input=input_en.text.toString() langLocale=Locale.ENGLISH } } if(langLocale!=null&&input.isNotBlank()){ if(android.os.Build.VERSION.SDK_INT>=android.os.Build.VERSION_CODES.O){ // サービス内で実行されるTextToSpeechに渡す値を設定 val serviceIntent=Intent(this,ForegroundService::class.java) serviceIntent.putExtra("langLocale",langLocale) serviceIntent.putExtra("input",input) // フォアグラウンドサービス開始 startForegroundService(serviceIntent) } } } } |
ForegroundService.kt
サービス生成時に呼ばれるonCreate
の中でTextToSpeechを初期化します。
サービス開始時にはonStartCommand
が呼ばれるので、その中でTextToSpeechを実行します。
Activity側でbindService
でサービスを開始した場合には、onBind
が呼ばれます(今回は使用しません)。
また、foreground service実行に必要となるNotificationを作成します。
ここで作成されたNotificationは端末の通知欄に表示されるため、バックグラウンド処理のように実行されつつも、何の処理がじっこうされているのかがユーザーに明示されます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 | classForegroundService:Service(),TextToSpeech.OnInitListener{ privatevartts:TextToSpeech?=null override fun onBind(intent:Intent?):IBinder?{ returnnull } override fun onCreate(){ super.onCreate() tts=TextToSpeech(this,this) } override fun onStartCommand(intent:Intent?,flags:Int,startId:Int):Int{ val context=applicationContext val channelId="tts_channel" val title=context.getString(R.string.app_name) val requestCode=1 val pendingIntent=PendingIntent.getActivity(context,requestCode,intent,PendingIntent.FLAG_ONE_SHOT) val notificationManager=context.getSystemService(Context.NOTIFICATION_SERVICE)asNotificationManager val channel=NotificationChannel(channelId,title,NotificationManager.IMPORTANCE_DEFAULT) notificationManager.createNotificationChannel(channel) // 通知作成 val notificationId=1 val notification=Notification.Builder(context,channelId) .setContentTitle(title) .setSmallIcon(android.R.drawable.ic_btn_speak_now) .setContentText("テキスト読み上げ") .setAutoCancel(true) .setContentIntent(pendingIntent) .setWhen(System.currentTimeMillis()) .build() // foreground service実行 startForeground(notificationId,notification) // 読み上げ実行 val langLocale=intent!!.getSerializableExtra("langLocale")asLocale val inputText=intent.getStringExtra("input") speakText(langLocale,inputText) returnSTART_NOT_STICKY } // テキスト読み上げ privatefun speakText(langLocale:Locale,inputText:String){ // 読み上げる言語を設定する tts!!.language=langLocale // 10回読み上げる for(iin1..10){ tts!!.speak(inputText,TextToSpeech.QUEUE_ADD,null,"speech1") } } override fun onDestroy(){ super.onDestroy() stopSelf() } override fun onInit(status:Int){ if(status==TextToSpeech.SUCCESS){ Log.d("TTS","TextToSpeechが初期化されました。") // 音声再生のイベントリスナを登録 val listener:SpeechListener?=SpeechListener() tts!!.setOnUtteranceProgressListener(listener) }else{ Log.e("TTS","TextToSpeechの初期化に失敗しました。") } } } |
実行してみる
入力したテキストを10回繰り返し読み上げますので、その最中に端末をスリープ状態にして、再生が途切れなければOKです。
(エミュレータの場合は、macであれば⌘+P
でスリープできます)
さいごに
今回はTextToSpeechをサービスとして実行してみました。
ただ、OREO以上でのみ動作するコードですし、テキスト読み上げ完了のイベントを捕まえてforeground serviceを終了させるなどの処理が必要になると思います。
また、Serviceはテキスト読み上げ以外にも、音楽データの連続再生や、大きいサイズのデータのダウンロードなど、様々なケースで必要になりそうですので、引き続き調べていきます。