【Android】~ちょこっとメモ~「MediaPlayer.create()」の謎

調べたきっかけ

本でMediaPlayerについて学習中、

サンプルコードではMediaPlayerのインスタンス生成をコンストラクタを使って生成していた。

class MainActivity : AppCompatActivity() {

    private var _player:MediaPlayer? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        //~省略~

        _player = MediaPlayer()

        val mediaFileUriStr = "android.resource://${packageName}/${R.raw.samplemusic}"
        val mediaFileUri = Uri.parse(mediaFileUriStr)

        try {

            _player?.setDataSource(applicationContext,mediaFileUri)
            _player?.prepareAsync()

        }catch (e: IllegalArgumentException){
            Log.e("MediaSample","IllegalState : メディアプレーヤー準備時の例外発生",e)
        }catch (e: IOException){
            Log.e("MediaSample","IO : メディアプレーヤー準備時の例外発生",e)
        }
    }
    //~省略~
}

今まで「MediaPlayer.create()」を使う方法しか知らなかったので、

試しにデータソースを設定しているところを変えて

(create()内でソース指定してるし代えるならここだろ、って理屈で)

override fun onCreate(savedInstanceState: Bundle?) {
    try {

        _player = MediaPlayer.create(this, R.raw.samplemusic)
        _player?.prepareAsync()

    }catch (e: IllegalArgumentException){
        Log.e("MediaSample","メディアプレーヤー準備時の例外発生",e)
    }catch (e: IOException){
        Log.e("MediaSample","メディアプレーヤー準備時の例外発生",e)
}

と、してみたら

IllegalStateExceptionが発生した。

どちらもMediaPlayerのインスタンスを生成しているはず。

この二つの違いは何だろう?ということで調べてみた

MediaPlayerの状態には種類がある

両者の違いを理解するためには、

まずMediaPlayerの状態について知らないといけない。

いままでMadiaPlayerは「作ってリソース指定したら使える」と簡単に思っていけど

そんな単純ではなかったらしい(当然といえば当然)

以下はMediaPlayerの状態遷移図(引用:AndroidDeveloper)

f:id:ukiuki0518:20191124210606j:plain

こんなにたくさんの状態を遷移しながら実行されているらしいΣ(・ω・ノ)ノ!

なんかごちゃごちゃしててポンコツの私にはわからないので

今回肝になっている「Idle状態」「Initialized状態」「Prepared状態」だけを見ていこうと思う

Idle状態

コンストラクタで生成された時の状態。

まだリソースの指定もされていないし、start()での再生も不可

Initialized状態

setDataSource()でリソースをセットし、初期化された状態

まだ再生はできない

Prepared状態

prepare()、またはprepareAsync()を呼び出して、準備された状態。

これらのメソッドはは同期実行か、非同期実行かの違い。

ここでやっと再生できる

※prepareAsync()を呼び出した場合、非同期で読み込んでいる状態(Preparing状態)に移行してから、読込完了後にPrepared状態になる。(という解釈をしている)

で、注意しないといけないのはこれらの状態に遷移するためのメソッド(setDataSource()やprepare()など)は

連続して複数回呼び出だせないこと。

たとえば、

setDataSource() Initialized状態に遷移後

もう一度setDataSource()リソースの再設定はできないし、

prepare()Prepared状態に遷移後

setDataSource()prepareAsync()などは呼べない、ということ。

これをやるためには「reset()」を呼び出してIdle状態に戻す必要があるようですな

MediaPlayer.create()の謎

MediaPlayerの状態遷移についてはわかったけど、

じゃあ結局MediaPlayer.create()ってなにしてるの?

ってことなので、調べてみた。

簡単に言うと、

Idle状態からPrepared状態までの設定を一括でやってくれる」メソッド

らしい。

パッと見、create()の引数にはリソースデータのURIもしくはリソースIDを指定しているので、

Inisialized状態まで遷移するのはなんとなく予想がつく。

ただ、しっかりと「prepare()」まで呼ばれているそう。

なので先ほどのサンプルコードでは、

create()でPrepared状態まで遷移しているにも関わらず、

もう一度prapareAsync()を呼び出しているため例外が発生した

ということになる。

早速

「_player?.prepareAsync()」

の行をコメントアウトしたら、動いた。なるほどねぇ~

余談

AndroidDeveloperでは、Prepared状態への遷移メソッドを、

UIスレッドで実行することは推奨していない。(下記リンク参照)

MediaPlayer overview  |  Android Developers

これは「リソースの読み込みが長いとユーザーを待たせるから」だそう。

場合によっては「アプリが応答しない」となることもあるらしい。

自分で別スレッド作成して実行してもいいし、

prepareAsync()使えば楽に非同期実行できるよ~とのこと。

ただ、リソースの読み込みが長い場合、ユーザーの連続操作によって

Preparing状態でpreparedAsync()を再度呼び出してしまったりという問題もあるので、

ボタンの制御などは必要。

ただ一つ疑問なのが

create()では「preapare()」の方が呼ばれているらしいので、

非同期実行したい場合はIdle状態から生成するしかないのかな?

実際にそういったパターンになったことがないので今のところ不明。

またわかったら追記しますね~

ってことで「どこがちょこっとやねん!」って感じですがちょこっとメモとします(笑)