1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.pandora 18 19 import android.content.Intent 20 import android.graphics.Bitmap 21 import android.media.MediaPlayer 22 import android.os.Bundle 23 import android.support.v4.media.* 24 import android.support.v4.media.MediaBrowserCompat.MediaItem 25 import android.support.v4.media.MediaMetadataCompat 26 import android.support.v4.media.session.* 27 import android.support.v4.media.session.MediaSessionCompat 28 import android.support.v4.media.session.PlaybackStateCompat 29 import android.support.v4.media.session.PlaybackStateCompat.SHUFFLE_MODE_ALL 30 import android.support.v4.media.session.PlaybackStateCompat.SHUFFLE_MODE_GROUP 31 import android.support.v4.media.session.PlaybackStateCompat.SHUFFLE_MODE_NONE 32 import android.util.Log 33 import androidx.media.MediaBrowserServiceCompat 34 import androidx.media.MediaBrowserServiceCompat.BrowserRoot 35 36 /* MediaBrowserService to handle MediaButton and Browsing */ 37 class MediaPlayerBrowserService : MediaBrowserServiceCompat() { 38 private val TAG = "PandoraMediaPlayerBrowserService" 39 40 private lateinit var mediaSession: MediaSessionCompat 41 private lateinit var playbackStateBuilder: PlaybackStateCompat.Builder 42 private var mMediaPlayer: MediaPlayer? = null 43 private val mediaIdToChildren = mutableMapOf<String, MutableList<MediaItem>>() 44 private var metadataItems = mutableMapOf<String, MediaMetadataCompat>() 45 private var queue = mutableListOf<MediaSessionCompat.QueueItem>() 46 private var currentTrack = -1 47 private val testIcon = Bitmap.createBitmap(16, 16, Bitmap.Config.ARGB_8888) 48 onCreatenull49 override fun onCreate() { 50 super.onCreate() 51 initBrowseFolderList() 52 setupMediaSession() 53 instance = this 54 } 55 setupMediaSessionnull56 private fun setupMediaSession() { 57 mediaSession = MediaSessionCompat(this, "MediaSession") 58 mediaSession.setCallback(mSessionCallback) 59 initQueue() 60 mediaSession.setQueue(queue) 61 playbackStateBuilder = 62 PlaybackStateCompat.Builder() 63 .setState(PlaybackStateCompat.STATE_NONE, 0, 1.0f) 64 .setActions(getAvailableActions()) 65 .setActiveQueueItemId(QUEUE_START_INDEX.toLong()) 66 mediaSession.setPlaybackState(playbackStateBuilder.build()) 67 mediaSession.setMetadata(null) 68 mediaSession.setQueueTitle(NOW_PLAYING_PREFIX) 69 mediaSession.setActive(true) 70 setSessionToken(mediaSession.getSessionToken()) 71 } 72 getAvailableActionsnull73 private fun getAvailableActions(): Long = 74 PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS or 75 PlaybackStateCompat.ACTION_SKIP_TO_NEXT or 76 PlaybackStateCompat.ACTION_FAST_FORWARD or 77 PlaybackStateCompat.ACTION_REWIND or 78 PlaybackStateCompat.ACTION_PLAY or 79 PlaybackStateCompat.ACTION_STOP or 80 PlaybackStateCompat.ACTION_PAUSE or 81 PlaybackStateCompat.ACTION_SET_SHUFFLE_MODE 82 83 private fun setPlaybackState(state: Int) { 84 playbackStateBuilder.setState(state, 0, 1.0f).setActiveQueueItemId(currentTrack.toLong()) 85 mediaSession.setPlaybackState(playbackStateBuilder.build()) 86 } 87 startTestPlaybacknull88 fun startTestPlayback() { 89 if (mMediaPlayer == null) { 90 // File copied from: development/samples/ApiDemos/res/raw/test_cbr.mp3 91 // to: packages/modules/Bluetooth/android/pandora/server/res/raw/test_cbr.mp3 92 val resourceId: Int = getResources().getIdentifier("test_cbr", "raw", getPackageName()) 93 mMediaPlayer = MediaPlayer.create(this, resourceId) 94 if (mMediaPlayer == null) { 95 Log.e(TAG, "Failed to create MediaPlayer.") 96 return 97 } 98 } 99 100 mMediaPlayer?.setOnCompletionListener { stopTestPlayback() } 101 102 mMediaPlayer?.start() 103 } 104 stopTestPlaybacknull105 fun stopTestPlayback() { 106 mMediaPlayer?.stop() 107 mMediaPlayer?.setOnCompletionListener(null) 108 mMediaPlayer?.release() 109 mMediaPlayer = null 110 } 111 playnull112 fun play() { 113 if (currentTrack == -1 || currentTrack == QUEUE_SIZE) currentTrack = QUEUE_START_INDEX 114 else currentTrack += 1 115 setPlaybackState(PlaybackStateCompat.STATE_PLAYING) 116 mediaSession.setMetadata(metadataItems.get("" + currentTrack)) 117 } 118 playUpdatednull119 fun playUpdated() { 120 currentTrack = NEW_QUEUE_ITEM_INDEX 121 setPlaybackState(PlaybackStateCompat.STATE_PLAYING) 122 mediaSession.setMetadata(metadataItems.get("" + currentTrack)) 123 } 124 stopnull125 fun stop() { 126 setPlaybackState(PlaybackStateCompat.STATE_STOPPED) 127 mediaSession.setMetadata(null) 128 } 129 pausenull130 fun pause() { 131 setPlaybackState(PlaybackStateCompat.STATE_PAUSED) 132 } 133 rewindnull134 fun rewind() { 135 setPlaybackState(PlaybackStateCompat.STATE_REWINDING) 136 } 137 fastForwardnull138 fun fastForward() { 139 setPlaybackState(PlaybackStateCompat.STATE_FAST_FORWARDING) 140 } 141 forwardnull142 fun forward() { 143 if (currentTrack == QUEUE_SIZE || currentTrack == -1) currentTrack = QUEUE_START_INDEX 144 else currentTrack += 1 145 setPlaybackState(PlaybackStateCompat.STATE_SKIPPING_TO_NEXT) 146 mediaSession.setMetadata(metadataItems.get("" + currentTrack)) 147 setPlaybackState(PlaybackStateCompat.STATE_PLAYING) 148 } 149 backwardnull150 fun backward() { 151 if (currentTrack == QUEUE_START_INDEX || currentTrack == -1) currentTrack = QUEUE_SIZE 152 else currentTrack -= 1 153 setPlaybackState(PlaybackStateCompat.STATE_SKIPPING_TO_PREVIOUS) 154 mediaSession.setMetadata(metadataItems.get("" + currentTrack)) 155 setPlaybackState(PlaybackStateCompat.STATE_PLAYING) 156 } 157 setLargeMetadatanull158 fun setLargeMetadata() { 159 currentTrack = QUEUE_SIZE 160 mediaSession.setMetadata(metadataItems.get("" + currentTrack)) 161 setPlaybackState(PlaybackStateCompat.STATE_PLAYING) 162 } 163 updateQueuenull164 fun updateQueue() { 165 val metaData: MediaMetadataCompat = 166 MediaMetadataCompat.Builder() 167 .putString( 168 MediaMetadataCompat.METADATA_KEY_MEDIA_ID, 169 NOW_PLAYING_PREFIX + NEW_QUEUE_ITEM_INDEX 170 ) 171 .putString(MediaMetadataCompat.METADATA_KEY_TITLE, "Title" + NEW_QUEUE_ITEM_INDEX) 172 .putString(MediaMetadataCompat.METADATA_KEY_ARTIST, "Artist" + NEW_QUEUE_ITEM_INDEX) 173 .putString(MediaMetadataCompat.METADATA_KEY_ALBUM, "Album" + NEW_QUEUE_ITEM_INDEX) 174 .putLong( 175 MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER, 176 NEW_QUEUE_ITEM_INDEX.toLong() 177 ) 178 .putLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS, NEW_QUEUE_ITEM_INDEX.toLong()) 179 .build() 180 val mediaItem = MediaItem(metaData.description, MediaItem.FLAG_PLAYABLE) 181 metadataItems.put("" + NEW_QUEUE_ITEM_INDEX, metaData) 182 queue.add( 183 MediaSessionCompat.QueueItem(mediaItem.description, NEW_QUEUE_ITEM_INDEX.toLong()) 184 ) 185 mediaSession.setQueue(queue) 186 } 187 resetQueuenull188 fun resetQueue() { 189 if (metadataItems.contains("" + NEW_QUEUE_ITEM_INDEX)) { 190 metadataItems.remove("" + NEW_QUEUE_ITEM_INDEX) 191 queue.removeLast() 192 mediaSession.setQueue(queue) 193 stop() 194 currentTrack = QUEUE_START_INDEX 195 } 196 } 197 getShuffleModenull198 fun getShuffleMode(): Int { 199 val controller = mediaSession.getController() 200 return controller.getShuffleMode() 201 } 202 setShuffleModenull203 fun setShuffleMode(shuffleMode: Int) { 204 val controller = mediaSession.getController() 205 val transportControls = controller.getTransportControls() 206 when (shuffleMode) { 207 SHUFFLE_MODE_NONE, 208 SHUFFLE_MODE_ALL, 209 SHUFFLE_MODE_GROUP -> transportControls.setShuffleMode(shuffleMode) 210 else -> transportControls.setShuffleMode(SHUFFLE_MODE_NONE) 211 } 212 } 213 214 private val mSessionCallback: MediaSessionCompat.Callback = 215 object : MediaSessionCompat.Callback() { onPlaynull216 override fun onPlay() { 217 Log.i(TAG, "onPlay") 218 play() 219 } 220 onPausenull221 override fun onPause() { 222 Log.i(TAG, "onPause") 223 pause() 224 } 225 onSkipToPreviousnull226 override fun onSkipToPrevious() { 227 Log.i(TAG, "onSkipToPrevious") 228 // TODO : Need to handle to play previous audio in the list 229 } 230 onSkipToNextnull231 override fun onSkipToNext() { 232 Log.i(TAG, "onSkipToNext") 233 // TODO : Need to handle to play next audio in the list 234 } 235 onMediaButtonEventnull236 override fun onMediaButtonEvent(mediaButtonEvent: Intent): Boolean { 237 Log.i(TAG, "MediaSessionCallback——》onMediaButtonEvent $mediaButtonEvent") 238 return super.onMediaButtonEvent(mediaButtonEvent) 239 } 240 onSetShuffleModenull241 override fun onSetShuffleMode(shuffleMode: Int) { 242 Log.i(TAG, "MediaSessionCallback——》onSetShuffleMode $shuffleMode") 243 mediaSession.setShuffleMode(shuffleMode) 244 } 245 } 246 onGetRootnull247 override fun onGetRoot(p0: String, clientUid: Int, rootHints: Bundle?): BrowserRoot? { 248 Log.i(TAG, "onGetRoot") 249 return BrowserRoot(ROOT, null) 250 } 251 onLoadChildrennull252 override fun onLoadChildren(parentId: String, result: Result<MutableList<MediaItem>>) { 253 Log.i(TAG, "onLoadChildren") 254 if (parentId == ROOT) { 255 val map = mediaIdToChildren[ROOT] 256 Log.i(TAG, "onloadchildren $map") 257 result.sendResult(map) 258 } else if (parentId == NOW_PLAYING_PREFIX) { 259 result.sendResult(mediaIdToChildren[NOW_PLAYING_PREFIX]) 260 } else { 261 Log.i(TAG, "onloadchildren inside else") 262 result.sendResult(mediaIdToChildren[ROOT]) 263 } 264 } 265 initMediaItemsnull266 private fun initMediaItems() { 267 var mediaItems = mutableListOf<MediaItem>() 268 for (item in QUEUE_START_INDEX..QUEUE_SIZE) { 269 val metaData: MediaMetadataCompat = 270 MediaMetadataCompat.Builder() 271 .putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, NOW_PLAYING_PREFIX + item) 272 .putString(MediaMetadataCompat.METADATA_KEY_TITLE, "Title$item") 273 .putString( 274 MediaMetadataCompat.METADATA_KEY_ARTIST, 275 if (item != QUEUE_SIZE) "Artist$item" else generateAlphanumericString(512) 276 ) 277 .putString( 278 MediaMetadataCompat.METADATA_KEY_ALBUM, 279 if (item != QUEUE_SIZE) "Album$item" else generateAlphanumericString(512) 280 ) 281 .putLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER, item.toLong()) 282 .putLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS, QUEUE_SIZE.toLong()) 283 .putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, testIcon) 284 .build() 285 val mediaItem = MediaItem(metaData.description, MediaItem.FLAG_PLAYABLE) 286 mediaItems.add(mediaItem) 287 metadataItems.put("" + item, metaData) 288 } 289 mediaIdToChildren[NOW_PLAYING_PREFIX] = mediaItems 290 } 291 initQueuenull292 private fun initQueue() { 293 for ((key, value) in metadataItems.entries) { 294 val mediaItem = MediaItem(value.description, MediaItem.FLAG_PLAYABLE) 295 queue.add(MediaSessionCompat.QueueItem(mediaItem.description, key.toLong())) 296 } 297 } 298 initBrowseFolderListnull299 private fun initBrowseFolderList() { 300 var rootList = mediaIdToChildren[ROOT] ?: mutableListOf() 301 302 val emptyFolderMetaData = 303 MediaMetadataCompat.Builder() 304 .putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, EMPTY_FOLDER) 305 .putString(MediaMetadataCompat.METADATA_KEY_TITLE, EMPTY_FOLDER) 306 .putLong( 307 MediaMetadataCompat.METADATA_KEY_BT_FOLDER_TYPE, 308 MediaDescriptionCompat.BT_FOLDER_TYPE_PLAYLISTS 309 ) 310 .build() 311 val emptyFolderMediaItem = 312 MediaItem(emptyFolderMetaData.description, MediaItem.FLAG_BROWSABLE) 313 314 val playlistMetaData = 315 MediaMetadataCompat.Builder() 316 .apply { 317 putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, NOW_PLAYING_PREFIX) 318 putString(MediaMetadataCompat.METADATA_KEY_TITLE, NOW_PLAYING_PREFIX) 319 putLong( 320 MediaMetadataCompat.METADATA_KEY_BT_FOLDER_TYPE, 321 MediaDescriptionCompat.BT_FOLDER_TYPE_PLAYLISTS 322 ) 323 } 324 .build() 325 326 val playlistsMediaItem = MediaItem(playlistMetaData.description, MediaItem.FLAG_BROWSABLE) 327 328 rootList += emptyFolderMediaItem 329 rootList += playlistsMediaItem 330 mediaIdToChildren[ROOT] = rootList 331 initMediaItems() 332 } 333 334 companion object { 335 lateinit var instance: MediaPlayerBrowserService 336 const val ROOT = "__ROOT__" 337 const val EMPTY_FOLDER = "@empty@" 338 const val NOW_PLAYING_PREFIX = "NowPlayingId" 339 const val QUEUE_START_INDEX = 1 340 const val QUEUE_SIZE = 6 341 const val NEW_QUEUE_ITEM_INDEX = 7 342 isInitializednull343 fun isInitialized(): Boolean = this::instance.isInitialized 344 } 345 } 346