MediaSessionConnectorを使った再生/停止制御でハマった話

# はじめに
音楽や動画を扱うアプリではExoPlayerとMedia Sessionを組み合わせることが多いと思います。
そのため、ExoPlayerにはMedia SessionのCallbackと同期してExoPlayerの再生/停止などの制御を行う**ExoPlayer MediaSession extension**という拡張機能が用意されています。

[ExoPlayer MediaSession extension](https://github.com/google/ExoPlayer/tree/release-v2/extensions/mediasession)

このコアとなっているのは`MediaSessionConnector`というクラスで、このクラスがExoPlayerとMedia Sessionを接続する役割を担っています。
このクラスを使って再生/停止制御をしようと思ったときにハマった部分があったので、その内容を以下に記載します。

# r2.9.6までonPlay()/onPause()はExoPlayerの制御を行ってくれなかった
冒頭にも記載した通り、`MediaSessionConnector`はMedia SessionのCallbackと同期してExoPlayerの再生/停止などの制御を行うときのコアとなるクラスです。
利用するときの最低限の実装は以下のような感じになります(`onGetRoot()`と`onLoadChildren()`は省略)。

```kotlin
class SampleMediaBrowserService : MediaBrowserServiceCompat() {

  private lateinit var exoPlayer: SimpleExoPlayer
  private lateinit var mediaSession: MediaSessionCompat

  override onCreate() {

    // ExoPlayerの初期化
    exoPlayer = SimpleExoPlayer.newSimpleInstance(this)

    // MediaSessionの初期化
    mediaSession = MediaSessionCompat(this, "media_session").apply {
      setFlags(
        MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS
        or MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS
      )
      setSessionToken(sessionToken)
      isActive = true
      ...
    }

    // MediaSessionConnectorの初期化
    // 必要に応じて各種Callbackをセットする
    // 例：PlaybackPreparer、PlaybackControllerなど
    MediaSessionConnector(mediaSession, /* playbackController= */ null).apply {
      setPlayer(exoPlayer, /* playbackPreparer= */ null)
      ...
    }
  }

  ...

}
```
<br>
このように設定することでExoPlayerの再生/停止制御を行ってくれるとてっきり思っていたのですが、どうやらそうではなく、`MediaSessionConnector`の処理としてはMedia Session側に処理を委譲しているだけでした。
ソースコード全体は[こちら](https://github.com/google/ExoPlayer/blob/r2.9.6/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.java)から確認できます。

```java:MediaSessionConnector(r2.9.6)#LL850-862
@Override
public void onPlay() {
  if (canDispatchToPlaybackController(PlaybackStateCompat.ACTION_PLAY)) {
    playbackController.onPlay(player);
  }
}

@Override
public void onPause() {
  if (canDispatchToPlaybackController(PlaybackStateCompat.ACTION_PAUSE)) {
    playbackController.onPause(player);
  }
}
```
<br>
そのため、**Media SessionにCallback(PlaybackController)をセットし、その実装の中でExoPlayerに対して自前で再生/停止制御を行ってあげなければなりません**。
(正直「マジかよ…overrideしないといけないメソッド増えてむしろ分かりづらくなるやん…」と思い、一旦は使うのをやめてしまいました。)

# r2.10.0からはonPlay()/onPause()はExoPlayerの制御を行ってくれるようになった
ところが、r2.10.0からは`MediaSessionConnector`内で再生/停止制御を行ってくれる実装になっていました。ソースコード全体は[こちら](https://github.com/google/ExoPlayer/blob/r2.10.0/extensions/mediasession/src/main/java/com/google/android/exoplayer2/ext/mediasession/MediaSessionConnector.java#L1045-L1064)から確認できます。

```java:MediaSessionConnector(r2.10.0)#LL1045-1064
@Override
public void onPlay() {
  if (canDispatchPlaybackAction(PlaybackStateCompat.ACTION_PLAY)) {
    if (player.getPlaybackState() == Player.STATE_IDLE) {
      if (playbackPreparer != null) {
        playbackPreparer.onPrepare();
      }
    } else if (player.getPlaybackState() == Player.STATE_ENDED) {
      controlDispatcher.dispatchSeekTo(player, player.getCurrentWindowIndex(), C.TIME_UNSET);
    }
    controlDispatcher.dispatchSetPlayWhenReady(player, /* playWhenReady= */ true);
  }
}

@Override
public void onPause() {
  if (canDispatchPlaybackAction(PlaybackStateCompat.ACTION_PAUSE)) {
    controlDispatcher.dispatchSetPlayWhenReady(player, /* playWhenReady= */ false);
  }
}
```

# おわりに
ExoPlayerはバージョンアップの度に結構な破壊的変更が入るので、現在使っているバージョン次第ではさくっとアップデートできる場合とそうでない場合があるとは思いますが、これでやっとExoPlayerに対して自前で再生/停止制御を行わずに済みそうです。