カテゴリー: Android

[Android] TextToSpeechをforeground serviceで実行する

はじめに

こんにちは。
前回の投稿では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以下でもサービス実行できるコードを併記すべきですが、まだよく分かっておらず勉強中です…申し訳ありません)

class MainActivity : 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は端末の通知欄に表示されるため、バックグラウンド処理のように実行されつつも、何の処理がじっこうされているのかがユーザーに明示されます。

class ForegroundService : Service(), TextToSpeech.OnInitListener {
    private var tts : TextToSpeech? = null

    override fun onBind(intent: Intent?): IBinder? {
        return null
    }

    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) as NotificationManager
        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") as Locale
        val inputText = intent.getStringExtra("input")
        speakText(langLocale, inputText)

        return START_NOT_STICKY
    }

    // テキスト読み上げ
    private fun speakText(langLocale:Locale, inputText:String) {
        // 読み上げる言語を設定する
        tts!!.language = langLocale
        // 10回読み上げる
        for (i in 1..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はテキスト読み上げ以外にも、音楽データの連続再生や、大きいサイズのデータのダウンロードなど、様々なケースで必要になりそうですので、引き続き調べていきます。

nomura

シェア
執筆者:
nomura

最近の投稿

フロントエンドで動画デコレーション&レンダリング

はじめに 今回は、以下のように…

3週間 前

Goのクエリビルダー goqu を使ってみる

はじめに 最近携わっているとあ…

1か月 前

【Xcode15】プライバシーマニフェスト対応に備えて

はじめに こんにちは、suzu…

2か月 前

FSMを使った状態管理をGoで実装する

はじめに 一般的なアプリケーシ…

3か月 前