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.media.MediaPlayer 21 import android.os.Bundle 22 import android.support.v4.media.* 23 import android.support.v4.media.MediaBrowserCompat.MediaItem 24 import android.support.v4.media.MediaMetadataCompat 25 import android.support.v4.media.session.* 26 import android.support.v4.media.session.MediaSessionCompat 27 import android.support.v4.media.session.PlaybackStateCompat 28 import android.support.v4.media.session.PlaybackStateCompat.SHUFFLE_MODE_ALL 29 import android.support.v4.media.session.PlaybackStateCompat.SHUFFLE_MODE_GROUP 30 import android.support.v4.media.session.PlaybackStateCompat.SHUFFLE_MODE_NONE 31 import android.util.Log 32 import androidx.media.MediaBrowserServiceCompat 33 import androidx.media.MediaBrowserServiceCompat.BrowserRoot 34 35 /* MediaBrowserService to handle MediaButton and Browsing */ 36 class MediaPlayerBrowserService : MediaBrowserServiceCompat() { 37 private val TAG = "PandoraMediaPlayerBrowserService" 38 39 private lateinit var mediaSession: MediaSessionCompat 40 private lateinit var playbackStateBuilder: PlaybackStateCompat.Builder 41 private var mMediaPlayer: MediaPlayer? = null 42 private val mediaIdToChildren = mutableMapOf<String, MutableList<MediaItem>>() 43 private var metadataItems = mutableMapOf<String, MediaMetadataCompat>() 44 private var queue = mutableListOf<MediaSessionCompat.QueueItem>() 45 private var currentTrack = -1 46 onCreatenull47 override fun onCreate() { 48 super.onCreate() 49 initBrowseFolderList() 50 setupMediaSession() 51 instance = this 52 } 53 setupMediaSessionnull54 private fun setupMediaSession() { 55 mediaSession = MediaSessionCompat(this, "MediaSession") 56 mediaSession.setCallback(mSessionCallback) 57 initQueue() 58 mediaSession.setQueue(queue) 59 playbackStateBuilder = 60 PlaybackStateCompat.Builder() 61 .setState(PlaybackStateCompat.STATE_NONE, 0, 1.0f) 62 .setActions(getAvailableActions()) 63 .setActiveQueueItemId(QUEUE_START_INDEX.toLong()) 64 mediaSession.setPlaybackState(playbackStateBuilder.build()) 65 mediaSession.setMetadata(null) 66 mediaSession.setQueueTitle(NOW_PLAYING_PREFIX) 67 mediaSession.setActive(true) 68 setSessionToken(mediaSession.getSessionToken()) 69 } 70 getAvailableActionsnull71 private fun getAvailableActions(): Long = 72 PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS or 73 PlaybackStateCompat.ACTION_SKIP_TO_NEXT or 74 PlaybackStateCompat.ACTION_FAST_FORWARD or 75 PlaybackStateCompat.ACTION_REWIND or 76 PlaybackStateCompat.ACTION_PLAY or 77 PlaybackStateCompat.ACTION_STOP or 78 PlaybackStateCompat.ACTION_PAUSE or 79 PlaybackStateCompat.ACTION_SET_SHUFFLE_MODE 80 81 private fun setPlaybackState(state: Int) { 82 playbackStateBuilder.setState(state, 0, 1.0f).setActiveQueueItemId(currentTrack.toLong()) 83 mediaSession.setPlaybackState(playbackStateBuilder.build()) 84 } 85 startTestPlaybacknull86 fun startTestPlayback() { 87 if (mMediaPlayer == null) { 88 // File copied from: development/samples/ApiDemos/res/raw/test_cbr.mp3 89 // to: packages/modules/Bluetooth/android/pandora/server/res/raw/test_cbr.mp3 90 val resourceId: Int = getResources().getIdentifier("test_cbr", "raw", getPackageName()) 91 mMediaPlayer = MediaPlayer.create(this, resourceId) 92 if (mMediaPlayer == null) { 93 Log.e(TAG, "Failed to create MediaPlayer.") 94 return 95 } 96 } 97 98 mMediaPlayer?.setOnCompletionListener { stopTestPlayback() } 99 100 mMediaPlayer?.start() 101 } 102 stopTestPlaybacknull103 fun stopTestPlayback() { 104 mMediaPlayer?.stop() 105 mMediaPlayer?.setOnCompletionListener(null) 106 mMediaPlayer?.release() 107 mMediaPlayer = null 108 } 109 playnull110 fun play() { 111 if (currentTrack == -1 || currentTrack == QUEUE_SIZE) currentTrack = QUEUE_START_INDEX 112 else currentTrack += 1 113 setPlaybackState(PlaybackStateCompat.STATE_PLAYING) 114 mediaSession.setMetadata(metadataItems.get("" + currentTrack)) 115 } 116 stopnull117 fun stop() { 118 setPlaybackState(PlaybackStateCompat.STATE_STOPPED) 119 mediaSession.setMetadata(null) 120 } 121 pausenull122 fun pause() { 123 setPlaybackState(PlaybackStateCompat.STATE_PAUSED) 124 } 125 rewindnull126 fun rewind() { 127 setPlaybackState(PlaybackStateCompat.STATE_REWINDING) 128 } 129 fastForwardnull130 fun fastForward() { 131 setPlaybackState(PlaybackStateCompat.STATE_FAST_FORWARDING) 132 } 133 forwardnull134 fun forward() { 135 if (currentTrack == QUEUE_SIZE || currentTrack == -1) currentTrack = QUEUE_START_INDEX 136 else currentTrack += 1 137 setPlaybackState(PlaybackStateCompat.STATE_SKIPPING_TO_NEXT) 138 mediaSession.setMetadata(metadataItems.get("" + currentTrack)) 139 setPlaybackState(PlaybackStateCompat.STATE_PLAYING) 140 } 141 backwardnull142 fun backward() { 143 if (currentTrack == QUEUE_START_INDEX || currentTrack == -1) currentTrack = QUEUE_SIZE 144 else currentTrack -= 1 145 setPlaybackState(PlaybackStateCompat.STATE_SKIPPING_TO_PREVIOUS) 146 mediaSession.setMetadata(metadataItems.get("" + currentTrack)) 147 setPlaybackState(PlaybackStateCompat.STATE_PLAYING) 148 } 149 setLargeMetadatanull150 fun setLargeMetadata() { 151 currentTrack = QUEUE_SIZE 152 mediaSession.setMetadata(metadataItems.get("" + currentTrack)) 153 setPlaybackState(PlaybackStateCompat.STATE_PLAYING) 154 } 155 updateQueuenull156 fun updateQueue() { 157 val metaData: MediaMetadataCompat = 158 MediaMetadataCompat.Builder() 159 .putString( 160 MediaMetadataCompat.METADATA_KEY_MEDIA_ID, 161 NOW_PLAYING_PREFIX + NEW_QUEUE_ITEM_INDEX 162 ) 163 .putString(MediaMetadataCompat.METADATA_KEY_TITLE, "Title" + NEW_QUEUE_ITEM_INDEX) 164 .putString(MediaMetadataCompat.METADATA_KEY_ARTIST, "Artist" + NEW_QUEUE_ITEM_INDEX) 165 .putString(MediaMetadataCompat.METADATA_KEY_ALBUM, "Album" + NEW_QUEUE_ITEM_INDEX) 166 .putLong( 167 MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER, 168 NEW_QUEUE_ITEM_INDEX.toLong() 169 ) 170 .putLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS, NEW_QUEUE_ITEM_INDEX.toLong()) 171 .build() 172 val mediaItem = MediaItem(metaData.description, MediaItem.FLAG_PLAYABLE) 173 queue.add( 174 MediaSessionCompat.QueueItem(mediaItem.description, NEW_QUEUE_ITEM_INDEX.toLong()) 175 ) 176 mediaSession.setQueue(queue) 177 } 178 getShuffleModenull179 fun getShuffleMode(): Int { 180 val controller = mediaSession.getController() 181 return controller.getShuffleMode() 182 } 183 setShuffleModenull184 fun setShuffleMode(shuffleMode: Int) { 185 val controller = mediaSession.getController() 186 val transportControls = controller.getTransportControls() 187 when (shuffleMode) { 188 SHUFFLE_MODE_NONE, 189 SHUFFLE_MODE_ALL, 190 SHUFFLE_MODE_GROUP -> transportControls.setShuffleMode(shuffleMode) 191 else -> transportControls.setShuffleMode(SHUFFLE_MODE_NONE) 192 } 193 } 194 195 private val mSessionCallback: MediaSessionCompat.Callback = 196 object : MediaSessionCompat.Callback() { onPlaynull197 override fun onPlay() { 198 Log.i(TAG, "onPlay") 199 play() 200 } 201 onPausenull202 override fun onPause() { 203 Log.i(TAG, "onPause") 204 pause() 205 } 206 onSkipToPreviousnull207 override fun onSkipToPrevious() { 208 Log.i(TAG, "onSkipToPrevious") 209 // TODO : Need to handle to play previous audio in the list 210 } 211 onSkipToNextnull212 override fun onSkipToNext() { 213 Log.i(TAG, "onSkipToNext") 214 // TODO : Need to handle to play next audio in the list 215 } 216 onMediaButtonEventnull217 override fun onMediaButtonEvent(mediaButtonEvent: Intent): Boolean { 218 Log.i(TAG, "MediaSessionCallback——》onMediaButtonEvent $mediaButtonEvent") 219 return super.onMediaButtonEvent(mediaButtonEvent) 220 } 221 onSetShuffleModenull222 override fun onSetShuffleMode(shuffleMode: Int) { 223 Log.i(TAG, "MediaSessionCallback——》onSetShuffleMode $shuffleMode") 224 mediaSession.setShuffleMode(shuffleMode) 225 } 226 } 227 onGetRootnull228 override fun onGetRoot(p0: String, clientUid: Int, rootHints: Bundle?): BrowserRoot? { 229 Log.i(TAG, "onGetRoot") 230 return BrowserRoot(ROOT, null) 231 } 232 onLoadChildrennull233 override fun onLoadChildren(parentId: String, result: Result<MutableList<MediaItem>>) { 234 Log.i(TAG, "onLoadChildren") 235 if (parentId == ROOT) { 236 val map = mediaIdToChildren[ROOT] 237 Log.i(TAG, "onloadchildren $map") 238 result.sendResult(map) 239 } else if (parentId == NOW_PLAYING_PREFIX) { 240 result.sendResult(mediaIdToChildren[NOW_PLAYING_PREFIX]) 241 } else { 242 Log.i(TAG, "onloadchildren inside else") 243 result.sendResult(null) 244 } 245 } 246 initMediaItemsnull247 private fun initMediaItems() { 248 var mediaItems = mutableListOf<MediaItem>() 249 for (item in QUEUE_START_INDEX..QUEUE_SIZE) { 250 val metaData: MediaMetadataCompat = 251 MediaMetadataCompat.Builder() 252 .putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, NOW_PLAYING_PREFIX + item) 253 .putString(MediaMetadataCompat.METADATA_KEY_TITLE, "Title$item") 254 .putString( 255 MediaMetadataCompat.METADATA_KEY_ARTIST, 256 if (item != QUEUE_SIZE) "Artist$item" else generateAlphanumericString(512) 257 ) 258 .putString( 259 MediaMetadataCompat.METADATA_KEY_ALBUM, 260 if (item != QUEUE_SIZE) "Album$item" else generateAlphanumericString(512) 261 ) 262 .putLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER, item.toLong()) 263 .putLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS, QUEUE_SIZE.toLong()) 264 .build() 265 val mediaItem = MediaItem(metaData.description, MediaItem.FLAG_PLAYABLE) 266 mediaItems.add(mediaItem) 267 metadataItems.put("" + item, metaData) 268 } 269 mediaIdToChildren[NOW_PLAYING_PREFIX] = mediaItems 270 } 271 initQueuenull272 private fun initQueue() { 273 for ((key, value) in metadataItems.entries) { 274 val mediaItem = MediaItem(value.description, MediaItem.FLAG_PLAYABLE) 275 queue.add(MediaSessionCompat.QueueItem(mediaItem.description, key.toLong())) 276 } 277 } 278 initBrowseFolderListnull279 private fun initBrowseFolderList() { 280 var rootList = mediaIdToChildren[ROOT] ?: mutableListOf() 281 282 val emptyFolderMetaData = 283 MediaMetadataCompat.Builder() 284 .putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, EMPTY_FOLDER) 285 .putString(MediaMetadataCompat.METADATA_KEY_TITLE, EMPTY_FOLDER) 286 .putLong( 287 MediaMetadataCompat.METADATA_KEY_BT_FOLDER_TYPE, 288 MediaDescriptionCompat.BT_FOLDER_TYPE_PLAYLISTS 289 ) 290 .build() 291 val emptyFolderMediaItem = 292 MediaItem(emptyFolderMetaData.description, MediaItem.FLAG_BROWSABLE) 293 294 val playlistMetaData = 295 MediaMetadataCompat.Builder() 296 .apply { 297 putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, NOW_PLAYING_PREFIX) 298 putString(MediaMetadataCompat.METADATA_KEY_TITLE, NOW_PLAYING_PREFIX) 299 putLong( 300 MediaMetadataCompat.METADATA_KEY_BT_FOLDER_TYPE, 301 MediaDescriptionCompat.BT_FOLDER_TYPE_PLAYLISTS 302 ) 303 } 304 .build() 305 306 val playlistsMediaItem = MediaItem(playlistMetaData.description, MediaItem.FLAG_BROWSABLE) 307 308 rootList += emptyFolderMediaItem 309 rootList += playlistsMediaItem 310 mediaIdToChildren[ROOT] = rootList 311 initMediaItems() 312 } 313 314 companion object { 315 lateinit var instance: MediaPlayerBrowserService 316 const val ROOT = "__ROOT__" 317 const val EMPTY_FOLDER = "@empty@" 318 const val NOW_PLAYING_PREFIX = "NowPlayingId" 319 const val QUEUE_START_INDEX = 1 320 const val QUEUE_SIZE = 6 321 const val NEW_QUEUE_ITEM_INDEX = 7 322 isInitializednull323 fun isInitialized(): Boolean = this::instance.isInitialized 324 } 325 } 326