どうもこんばんわ。 なんかいきなり暑くなってあつい。 夏は虫(こわい)と頭痛が痛くなる(水不足か何か)のと弱冷房車に当たる(マジでいらんだろ。スマホの発熱のほうが熱い)のがあるので嫌いです。
本題 MediaSessionってのがあるんですよ。JavaScriptの方じゃないです。(JavaScriptの方はこれ↓)
AndroidのMediaSessionもJSのMediaSessionと多分同じ感じで、
GoogleAssistantに今再生してる曲なに? って聞くと答えが帰ってきたり
Always On Display に曲名を表示したり
などなど
GoogleAssistantで操作できるのもこれ。別にBroadcastReceiverとかで受け取ってるとかではない。
ちなみに音楽プレーヤーの通知にあるあの操作パネルは別にMediaSession無しでも作れる。
ここまでExoPlayer要素なし MediaSessionってなんかネット上にも情報がなくてよくわからないんですが、 ExoPlayerとうまく連携してくれるライブラリがあるので今回はそのライブラリに頼ってMediaSessionを作っていこうと思います。
作る 環境
なまえ
あたい
Android
11 DP 4
言語
Kotlin
ExoPlayer
2.11.3
再生する曲 今回は適当にフリー音源を使います。今回は甘茶の音楽工房様のファミポップⅢを使わせてもらいます。 別にExoPlayerで再生できれば何でもいいです。
ライブラリ入れるなど 1 2 3 4 5 6 7 8 dependencies { implementation "androidx.media:media:1.1.0" implementation 'com.google.android.exoplayer:exoplayer-core:2.11.3' implementation 'com.google.android.exoplayer:extension-mediasession:2.10.4' }
音楽入れる 本当は端末内の音楽を再生するのがいいんでしょうけど、Androidのファイル読み書きがややこしいので今回はres/rawに入れて再生することにします。 ExoPlayerが対応してる再生方法なら何でもいいと思います。
仕様など めんどいのでServiceではなくActivityで作ります。(普通の音楽アプリならServiceで作る。) 音楽は前述通りres/rawから読み込む形で。
MainActivity.kt ExoPlayer再生するまで ExoPlayerってバージョン上がると一気に非推奨になったりして追いかけるの大変。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 class MainActivity : AppCompatActivity () { val FILE_NAME = "famipop3" lateinit var exoPlayer: SimpleExoPlayer override fun onCreate (savedInstanceState: Bundle ?) { super .onCreate(savedInstanceState) setContentView(R.layout.activity_main) exoPlayer = SimpleExoPlayer.Builder(this ).build() val dataSourceFactory = DefaultDataSourceFactory(this , "@takusan_23" ) val uri = RawResourceDataSource.buildRawResourceUri(R.raw.famipop3) val source = ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(uri) exoPlayer.prepare(source) play_button.setOnClickListener { exoPlayer.playWhenReady = !exoPlayer.playWhenReady } } override fun onDestroy () { super .onDestroy() exoPlayer.release() } }
ファイル名のところは各自書き換えてね。
めんどいので全部張ります
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 class MainActivity : AppCompatActivity () { lateinit var exoPlayer: SimpleExoPlayer lateinit var mediaSession: MediaSessionCompat lateinit var mediaSessionConnector: MediaSessionConnector override fun onCreate (savedInstanceState: Bundle ?) { super .onCreate(savedInstanceState) setContentView(R.layout.activity_main) exoPlayer = SimpleExoPlayer.Builder(this ).build() val dataSourceFactory = DefaultDataSourceFactory(this , "@takusan_23" ) val uri = RawResourceDataSource.buildRawResourceUri(R.raw.famipop3) val source = ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(uri) exoPlayer.prepare(source) val mediaMetadataRetriever = MediaMetadataRetriever() val afd = resources.openRawResourceFd(R.raw.famipop3) mediaMetadataRetriever.setDataSource(afd.fileDescriptor, afd.startOffset, afd.length) val duration = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION).toLong() mediaSession = MediaSessionCompat(this , "sample" ).apply { isActive = true } mediaSessionConnector = MediaSessionConnector(mediaSession) mediaSessionConnector.setPlayer(exoPlayer) mediaSessionConnector.setMediaMetadataProvider { val mediaMetadataCompat = MediaMetadataCompat.Builder().apply { putString(MediaMetadataCompat.METADATA_KEY_TITLE, "ファミポップⅢ" ) putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, "famipop" ) putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, "ファミポップⅢ" ) putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE, "甘茶の音楽工房" ) putString(MediaMetadataCompat.METADATA_KEY_ARTIST, "甘茶の音楽工房" ) putLong(MediaMetadataCompat.METADATA_KEY_DURATION, duration) }.build() mediaMetadataCompat } play_button.setOnClickListener { exoPlayer.playWhenReady = !exoPlayer.playWhenReady showNotification() } } @RequiresApi(Build.VERSION_CODES.O) private fun showNotification () { val channelId = "play_notification" val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager if (notificationManager.getNotificationChannel(channelId) == null ) { val notificationChannel = NotificationChannel(channelId, "音楽通知" , NotificationManager.IMPORTANCE_LOW) notificationManager.createNotificationChannel(notificationChannel) } val notification = NotificationCompat.Builder(this , channelId).apply { setContentTitle("ファミポップⅢ" ) setContentText("甘茶の音楽工房" ) setSmallIcon(R.drawable.ic_audiotrack_black_24dp) setStyle(androidx.media.app.NotificationCompat.MediaStyle().setMediaSession(mediaSession.sessionToken)) addAction(R.drawable.ic_audiotrack_black_24dp, "" , PendingIntent.getBroadcast(this @MainActivity , 1 , Intent(), PendingIntent.FLAG_UPDATE_CURRENT)) }.build() notificationManager.notify(1 , notification) } override fun onDestroy () { super .onDestroy() exoPlayer.release() mediaSession.release() } }
注意など
上記のコードはaddAction()
でアイコン出してるけどIntentが空なので押してもなにもなりません。
通知のsetStyle()
に入れるNotificationCompat
はandroidx.media.app.NotificationCompat
です。androidx.core.app.NotificationCompat
ではないです(名前同じなのややこC)
Android 10から通知の音楽コントローラーにシークバーが追加できるんですが(MediaMetadataCompat.METADATA_KEY_DURATION
に負の値を指定しなければいい)これ一つ以上addAction()
を追加しないとシークバーがいつまで経っても表示されません。
mediaSessionConnector.setMediaMetadataProvider{}
でメタデータを別に作成してますが、多分mp3の中にメタデータがあれば勝手に作ってくれると思います(要検証)
メタデータハードコートしてるけどMediaMetadataRetriever
からタイトルやら作者を取るほうが良いです。(かくのめんどい)
おわりに シークバー出すのにaddAction()
で一個以上アイコンを出さないとだめってことに気付かずに時間が溶けたのでもう疲れた。
写真取れなかったけどAlways On Displayでも(Android端末差はあるだろうけど)表示されてます。
参考にしました。 https://stackoverflow.com/questions/24030756/mediaextractor-mediametadataretriever-with-raw-asset-file https://stackoverflow.com/questions/30852975/exoplayer-reading-mp3-file-from-raw-folder