ExoPlayerで音声のみ簡単に再生させる方法



## 概要
ExoPlayerは動画・音声再生を便利にするライブラリです
動画プレイヤーを使って再生というサンプルやコード例は多いですが、ボタンを押して音声だけ再生させたいというシンプルな構造をExoPlayerで行うにはどうするかを載せます　よりよい方法や間違い・指摘があれば是非ご教授ください
(MediaPlayerでも十分ですが良いライブラリはどんどん使っていきたい)

## 導入

```build.gradle
// android 部分
compileOptions {
    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
}

kotlinOptions {
    jvmTarget = "1.8"
}
```

```build.gradle
// dependencies 部分
def exoplayer_version = "2.11.6"
implementation "com.google.android.exoplayer:exoplayer:$exoplayer_version"
```

## 実装

### XML

ただボタンを配置するだけです
ExoPlayerのサンプルのように[PlayerView](https://exoplayer.dev/doc/reference/com/google/android/exoplayer2/ui/PlayerView.html)などは使いません

```activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <ImageView
        android:id="@+id/audio"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:contentDescription="null"
        app:srcCompat="@drawable/ic_sound_24"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>
```

### View

```MainActivity.kt
package com.example.exoplayersoundonly

import android.net.Uri
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import com.google.android.exoplayer2.*
import com.google.android.exoplayer2.source.MediaSource
import com.google.android.exoplayer2.source.ProgressiveMediaSource
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory
import com.google.android.exoplayer2.util.Util
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

    private var audioPlayer: SimpleExoPlayer? = null

    private val playerEventListener = object : Player.EventListener {
        override fun onIsPlayingChanged(isPlaying: Boolean) {
        }

        override fun onPlayerError(error: ExoPlaybackException) {
        }

        override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) {
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        initPlayer(buildMediaSource())

        audio.setOnClickListener {
            audioPlayer?.playWhenReady = true
        }
    }

    private fun initPlayer(mediaSource: MediaSource?) {
        mediaSource ?: return
        val trackSelector = DefaultTrackSelector(this)
        val loadControl = DefaultLoadControl()
        audioPlayer = SimpleExoPlayer.Builder(this).setTrackSelector(trackSelector).setLoadControl(loadControl).build()
        audioPlayer?.prepare(mediaSource)
        audioPlayer?.addListener(playerEventListener)
        audioPlayer?.playWhenReady = false
    }

    private fun buildMediaSource(): MediaSource? {
        return try {
            val uri = Uri.parse(SAMPLE_MP3)
            val factory = DefaultDataSourceFactory(this, Util.getUserAgent(this, getString(R.string.app_name)))
            ProgressiveMediaSource.Factory(factory).createMediaSource(uri)
        } catch (e: Exception) {
            null
        }
    }

    private fun releasePlayer() {
        audioPlayer?.let {
            it.stop()
            it.release()
            audioPlayer = null
        }
    }

    override fun onPause() {
        releasePlayer()
        super.onPause()
    }

    companion object {
        private const val SAMPLE_MP3 = "http://www.ne.jp/asahi/music/myuu/wave/menuettm.mp3"
    }
}
```


やっていることはとてもシンプルです

1. 音声のもとになる`MediaSource`を作る
2. その`MediaSource`を使って`ExoPlayer`インスタンスを生成する
3. ボタンを押した時に音声が再生されるようにする
4. 使わないときは`release`する

だけです

### MediaSource作成
ここではMP3のURLから`MediaSource`を生成しています ただ`Uri.parse`したものを渡すだけなのでとても簡単です
対応する形式に伴い`MediaSource`を指定します　今回はmp3のため[ProgressiveMediaSource](https://exoplayer.dev/doc/reference/com/google/android/exoplayer2/source/ProgressiveMediaSource.html)になります

```snippet.kt
private fun buildMediaSource(): MediaSource? {
    return try {
        val uri = Uri.parse(SAMPLE_MP3)
        val factory = DefaultDataSourceFactory(this, Util.getUserAgent(this, getString(R.string.app_name)))
        ProgressiveMediaSource.Factory(factory).createMediaSource(uri)
    } catch (e: Exception) {
        null
    }
}
```

### ExoPlayerインスタンスの生成と初期化

```snippet.kt
private fun initPlayer(mediaSource: MediaSource?) {
    mediaSource ?: return
    val trackSelector = DefaultTrackSelector(this)
    val loadControl = DefaultLoadControl()
    audioPlayer = SimpleExoPlayer.Builder(this).setTrackSelector(trackSelector).setLoadControl(loadControl).build()
    audioPlayer?.prepare(mediaSource)
    audioPlayer?.addListener(playerEventListener)
    audioPlayer?.playWhenReady = false
}
```

Builderで生成します
その後、

* `#prepare`で対応する`MediaSource`をセット
* `#addListener`で状態を検知するListenerをセット(任意)
* `playWhenReady`で再生・停止の状態を設定 (ここでは初期化のみのため`false`を指定しています)

とします

### 再生

onClickされた際に`playWhenReady`を`true`にします

```snippet.kt
audio.setOnClickListener {
    audioPlayer?.playWhenReady = true
}
```

### リリース

```snippet.kt
private fun releasePlayer() {
    audioPlayer?.let {
        it.stop()
        it.release()
        audioPlayer = null
    }
}
```

使わなくなったら停止・リリースしましょう
`onPause`や`onDestroy`でよく使われます

## おわりに

これで音声のみ簡単に再生させることができます

参考になれば幸いです

[こちらのレポジトリ](https://github.com/sudo5in5k/ExoPlayerSoundOnly)から簡単にお試しできます

## 他初心者向けの参考

* [ExoPlayer公式デモアプリ](https://exoplayer.dev/demo-application.html)
* [Media streaming with ExoPlayer (codelab)](https://codelabs.developers.google.com/codelabs/exoplayer-intro/#1)