4
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

【Android TV / Fire TV 📺】テレビの電源ON/OFF、入力切替を検知する🔌

Posted at

はじめに

この記事では、Nexus PlayerやFire TVなどのAndroid OS(またはFire OS)を搭載したAndroid TV対応端末使用時における、TV端末の電源ON/OFF、入力切替を検知する方法を紹介します。

用語定義

似たような用語が多く出てくるので、わかりやすいよう、本記事では以下のように定義して使用させていただきます🙇‍♂️

用語 概要
外部端末 Nexus Player/Fire TVなどの付属のHDMIをテレビ端末に接続する端末
内蔵端末 BRAVIAなどのAndroid OSを内蔵するテレビ端末
TV端末 Android OSの内蔵有無に関わらない全てのテレビ端末やディスプレイ端末
オーディオ端末 外付けスピーカーなどの付属のHDMIをテレビ端末に接続する端末
アプリ 開発対象のAndroid TVアプリケーション

背景

Nexus PlayerやFire TVなどを使って、TV端末で映像コンテンツを楽しんでいる方も多いと思います。
みなさん、これらの端末ってどうやって使ってますか?
見終わったら、TV端末のリモコンぽちーってやってませんか?

そうなんです!実は、TV端末の電源を消しただけでは外部端末の電源が供給されている限り、外部端末は起動しっぱなしなんですよ!😨
これじゃあ、実際にユーザがいつアプリの使用をやめたかっていうのが分からないですよね...

ということで、何かいい方法はないかなーと調べた結果を紹介します🙋‍♀️
実は、AmazonのDeveloperサイトにFire TV上のHDMIイベントの処理という項目で、説明がされています。
しかし、このドキュメントだけだと分からない様々なユースケースがあるので、今回はその検証結果についてもまとめておきます。

使用するAPIについて

BroadcastReceiverを使用して、取得できるイベントの一つに ACTION_HDMI_AUDIO_PLUG というものが存在します。
このイベントは AudioManager クラスに定義されており、映像や音声を出力するHDMIの接続状態を通知するイベントです。
ここで、出力を強調している訳は、本記事を読み進めていくと理解できると思います。
この ACTION_HDMI_AUDIO_PLUG というイベントは、物理的にHDMIコードを接続/切断する場合だけでなく、TV端末の電源をON/OFFにした場合などにも通知されます。
これを利用して、TV端末の電源ON/OFF、入力切替を検知します。

出力HDMIの接続/切断状態について

ACTION_HDMI_AUDIO_PLUG を利用して、出力HDMIの接続状態変化を通知することは分かったと思います。
では、出力HDMIの接続状態変化が起こった時の状態を取得する方法を説明します。
AudioManager クラスの中には EXTRA_AUDIO_PLUG_STATEというものが存在します。ACTION_HDMI_AUDIO_PLUG イベントが飛んできた時に、この EXTRA_AUDIO_PLUG_STATE の値を Intent から取得することができます。
EXTRA_AUDIO_PLUG_STATE の値は、出力HDMI接続時に 1 となり、出力HDMI切断時に 0 となります。
つまり、TV端末の電源がONになった時入力切替によって、別の入力からアプリを起動している入力に切り替えられた時には 1 になります。
逆に、TV端末の電源がOFFになった時入力切替によって、アプリを起動している入力から別の入力に切り替えられた時には 0になります。

実装

説明はこのくらいにして、実装を見ていきましょう!

https://github.com/ronnnnn/HDMIPlugStateSample
こちらにサンプルアプリを作成したので、ぜひ参考にしてください🙏

BroadcastReceiver について

ACTION_HDMI_AUDIO_PLUG イベントを処理するには、BroadcastReceiver を継承したクラスを作成します。
そして、BroadcastReceiver#onReceiver をOverrideして、その中にイベントを受け取った時にやりたいことを実装していきます。
この辺は基本的な BroadcastReceiver による Intent アクションのハンドリング方法と同じですね🙆‍♀️

HDMIPlugStateBroadcastReceiver.kt
class HDMIPlugStateBroadcastReceiver : BroadcastReceiver() {

    companion object {
        private const val PLUGGED_IN: Int = 1
        private const val UNPLUGGED: Int = 0
    }

    override fun onReceive(context: Context?, intent: Intent?) {

        // ここで ACTION_HDMI_AUDIO_PLUG イベントを処理する

        intent ?: return
        if (intent.action == AudioManager.ACTION_HDMI_AUDIO_PLUG) {
            val plugState = intent.getIntExtra(AudioManager.EXTRA_AUDIO_PLUG_STATE, -1) // こいつが現在の接続状態
            val address = intent.getStringExtra("address")
            val state = intent.getIntExtra("state", -1)
            val portName = intent.getStringExtra("portName")

            when (plugState) {
                UNPLUGGED -> {
                    Timber.d(StringBuilder(" \n")
                            .append("---------------  HDMI is unplugged  ---------------\n")
                            .append("address is ${address}\n")
                            .append("state is ${state}\n")
                            .append("port name is ${portName}\n")
                            .append("---------------------------------------------------")
                            .toString()
                    )
                }

                PLUGGED_IN -> {
                    Timber.d(StringBuilder(" \n")
                            .append("---------------  HDMI is plugged in ---------------\n")
                            .append("address is ${address}\n")
                            .append("state is ${state}\n")
                            .append("port name is ${portName}\n")
                            .append("---------------------------------------------------")
                            .toString()
                    )
                }

                else -> Timber.e("invalid ACTION_HDMI_AUDIO_PLUG value")
            }
        }
    }
}

ここで注目したいのが、 Intent を経由して取得できる値についてです。
EXTRA_AUDIO_PLUG_STATE の値を取得できるのは先で説明しましたが、このイベントの Intent には、これ以外にも "address""state""portName" というkeyの値が付随してくるようです。
私の環境では "state" の値は EXTRA_AUDIO_PLUG_STATE の値と同じで、"state""portName" の値は空でした。ドキュメントにもこれらのkeyについての説明を見つけられなかったので、何かわかる方がいましたら教えてください🙋‍♀️

BroadcastReceiver の登録について

ドキュメントにも記載されていますが、今回説明している ACTION_HDMI_AUDIO_PLUG のイベントは、Context#registerReceiver を使用して BroadcastReceiver を継承したクラスを登録する必要があります。
AndroidManifest.xml にこのクラスを登録してもイベントを受け取ることができませんので、注意してください。

今回は、Application クラスを継承したクラスで登録、解除を行っています。

App.kt
class App : Application() {

    private val hdmiPlugStateBroadcastReceiver: HDMIPlugStateBroadcastReceiver =
            HDMIPlugStateBroadcastReceiver()

    override fun onCreate() {
        super.onCreate()

        initializeReceiver()
    }

    override fun onTerminate() {
        terminateReceiver()

        super.onTerminate()
    }

    private fun initializeReceiver() {
        registerReceiver(
                hdmiPlugStateBroadcastReceiver,
                IntentFilter(AudioManager.ACTION_HDMI_AUDIO_PLUG)
        )
    }

    private fun terminateReceiver() {
        unregisterReceiver(hdmiPlugStateBroadcastReceiver)
    }
}

Context#registerReceiver の第二引数に ACTION_HDMI_AUDIO_PLUGIntentFilter を渡すことをお忘れなく!

EXTRA_AUDIO_PLUG_STATE の値について

これまで説明してきた、EXTRA_AUDIO_PLUG_STATE の値が、どういった場合に変化するかを検証したので、下にまとめておきます。
他にもこのパターンの時はどうなんだ🤔などありましたら、教えてください!

↓の値が取得できるのは基本的に、外部端末の電源が入っており(コンセント電源供給)、アプリが起動していることが前提です。

パターン EXTRA_AUDIO_PLUG_STATE の値 備考
外部端末付属のHDMIをTV端末から切断 0 テレビ端末でないディスプレイ端末での検証では 0 のみが取れたが、BRAVIAでの検証では 0 -> 1 -> 0 を確認
外部端末付属のHDMIをTV端末に接続 1
TV端末の入力切替で別の入力へ遷移 0
TV端末の入力切替で別の入力から移動 1
TV端末の電源off (TV端末の主電源on) 0
TV端末の電源off (TV端末の主電源も同時にoff) - アプリが強制終了されるため何も取れない
TV端末の電源off (外部端末の電源も同時にoff) - TV端末のUSBポートを電源供給源としている場合には、TV端末の電源offと同時にアプリも強制終了されるため何も取れない
TV端末の電源on 1
アプリ起動中に外部端末の電源off->電源on 1 外部端末の再起動時にアプリが裏で起動され、 1 のイベントが発火する (FireTVのみでしか検証してない)
ターゲットとする外部端末が刺さっているHDMIポート以外のHDMIポートへのHDMI接続/切断 - 別の外部端末がアプリを所有し、起動していてもイベントは発火されない
外部端末でアプリの起動 1 ただし、アプリが起動中の場合はイベントは発火されない
オーディオ端末の接続・切断 -

おまけ

今回はNexus PlayerやFire TVなどのTV端末に接続して使用するAndroid TV端末の場合を説明してきました。
ですが、BRAVIAなどのAndroid OS内蔵端末はどうなんだ🤔と思った方もいるかと思います。
私が行った検証の限りだと、内蔵端末の場合は、基本的に外部端末のように出力HDMIを持たないため、TV端末のHDMIポートにHDMIが接続/切断されても ACTION_HDMI_AUDIO_PLUG イベントは発火されません。

ただし、オーディオ端末を使用して、内蔵端末の音声をHDMI経由で出力している場合には、オーディオ端末のHDMI接続/切断により ACTION_HDMI_AUDIO_PLUG イベントが発火されてしまいます😭

そのため、内蔵端末において、TV端末の電源ON/OFF、入力切替を検知するには、ApplicationActivity などのライフサイクルを利用するのがいいかと考えています。

ちょっとニッチな内容だったかもしれませんが、最後までお読みいただきありがとうございます!
何かありましたら、ぜひコメントください🙏

参考

4
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?