MediaSessionの話

IMANAKA, Kouta (@pside)

potatotips #38

発表のねらい

  • メディア再生アプリ開発において
    `MediaSession`を知ってるかどうかで、かなり差が出てくる
    • 独自定義のIntent ActionをServiceに放つ、、、
    • RemoteViewsでめっちゃ頑張った通知実装、、、
    • Android Wearにコントローラー出ないぞどうすればいいんだ\(^o^)/
  • 一方で日本語の情報が少なく今ひとつパッとしない
  • 知名度向上していくぞ :muscle: :muscle: :muscle:

Ian Lakeの「I/O 2016: Best Practices in Media Playback on Android」が良い資料なので、英語分かる人はそっちを見た方が良いかも

MediaSessionなにそれ

余談: ChromeがMediaSession APIを持ってるらしくて、ぶったまげた

MediaSessionCompat …?

  • MediaController、音量ボタン、メディアボタン、(transport control)の各操作との連携を可能にする
    • NotificationのMediaStyleのボタン操作をMediaSessionCompat.Callbackで受ける
    • Android WearのメディアコントローラーのボタンイベントをCallbackで受ける
    • MediaControllerクラスからの操作をCallbackで受ける
      • Service等でINTENT受ける〜みたいな実装を書かなくてよくなる
      • メディアライブラリ(e.g. ExoPlayer)に依存しない
    • PlaybackStateの内容に応じてボタンの活性化状態を変える
  • 全ての再生系アプリがこれを使っていれば、
    どのアプリがいまプライマリでイベントを取るか等やってくれる

com.android.support:support-media-compat:25.3.0

PlaybackStateCompat … ?

  • 今再生しているもの、或いは何も再生されていないということ
    (=セッション)を
    MediaSessionを通じて各々コンポーネントに知らせる為のデータ
    • 「一時停止中」「再生中」のような、
      PlaybackStateCompat.STATE_* で定義された `State`
    • このセッションで受け入れ可能な制御イベントを定義する `Actions`
    • 等(MediaSessionCompat.Builderから生えているメソッド見てね)
    • Googleのサンプルコードなどを見ると完全な実装が分かる

特にActionsの指定をしない、足りない、ミスると
呼ばれて欲しいCallbackが呼ばれなくなるのでめっちゃハマる、ハマった

MediaSessionCompatの実装例 (1/2)

// ServiceのonCreate等でAudioManagerの初期化などと並んで実行する

mediaSession = new MediaSessionCompat(this, TAG);

// MediaBrowserServiceCompatを使う場合

setSessionToken(mediaSession.getSessionToken());

mediaSession.setCallback(mediaSessionCallbacks);

mediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS | MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);

// このあとすぐ説明します

updatePlaybackState(PlaybackStateCompat.STATE_NONE, 0);

MediaSessionCompatの実装例 (2/2)

// MediaSessionのStateを設定する

private void updatePlaybackState(@PlaybackStateCompat.State int state, long position) {

mediaSession.setPlaybackState(

new PlaybackStateCompat.Builder()

.setState(state, position, 1.0f)

.setActions(PlaybackStateCompat.ACTION_PLAY |

PlaybackStateCompat.ACTION_PAUSE |

PlaybackStateCompat.ACTION_PLAY_PAUSE |

PlaybackStateCompat.ACTION_SKIP_TO_NEXT |

PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS)

.build()

);

}

MediaSessionCompatのルール

  • 再生しているときだけ MediaPlayerCompat#setActive(boolean) で
    setActive(true)しましょう
  • 再生が完全に終わったときは setActive(false) にしましょう
  • メディアライブラリ(e.g. ExoPlayer)の再生ステータスが変わったら
    忘れずにPlaybackStateも適切値で更新してあげましょう
    • さっきの `updatePlaybackState()`
  • 完全に使い終わったら MediaSessionCompat#release() しましょう
  • メディアボタン関係はIntentで飛んでくるので、
    AndroidManifest側対応+onStartCommand対応が必要

メディアボタンのキーイベントを受け取る (1/2)

// AndroidManifest

<service

android:name="com.example.android.YourService"

android:exported="true">

<intent-filter>

<action android:name="android.media.browse.MediaBrowserService" />

</intent-filter>

</service>

...

<receiver android:name="android.support.v4.media.session.MediaButtonReceiver">

<intent-filter>

<action android:name="android.intent.action.MEDIA_BUTTON" />

</intent-filter>

</receiver>

メディアボタンのキーイベントを受け取る (2/2)

// YourService.java

@Override

public int onStartCommand(Intent intent, int flags, int startId) {

KeyEvent keyEvent = MediaButtonReceiver.handleIntent(mediaSession, intent);

if (keyEvent != null) {

Timber.d("onStartCommand: keyEvent: %s", keyEvent.toString());

}

return START_NOT_STICKY;

}

おわりに雑感

  • 分かってしまえばどうってことはないが、ここまで辿り着くのに相当かかった
  • Ian Lakeのスライドは有益な資料ですが、それを見ただけだと気づかないハマりどころもあった
  • ぶっちゃけこのクラスを知っているかどうかで、
    実装方針がだいぶ変わるので、みんなは俺の屍を越えてゆけ