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.os.Bundle 21 import android.media.MediaPlayer; 22 import android.support.v4.media.* 23 import android.support.v4.media.MediaBrowserCompat.MediaItem 24 import android.support.v4.media.session.* 25 import android.support.v4.media.MediaMetadataCompat 26 import android.support.v4.media.session.MediaControllerCompat 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 34 import androidx.media.MediaBrowserServiceCompat 35 import androidx.media.MediaBrowserServiceCompat.BrowserRoot 36 37 /* MediaBrowserService to handle MediaButton and Browsing */ 38 class MediaPlayerBrowserService : MediaBrowserServiceCompat() { 39 private val TAG = "PandoraMediaPlayerBrowserService" 40 41 private lateinit var mediaSession: MediaSessionCompat 42 private lateinit var playbackStateBuilder: PlaybackStateCompat.Builder 43 private var mMediaPlayer: MediaPlayer? = null 44 private val mediaIdToChildren = mutableMapOf<String, MutableList<MediaItem>>() 45 private var metadataItems = mutableMapOf<String, MediaMetadataCompat>() 46 private var queue = mutableListOf<MediaSessionCompat.QueueItem>() 47 private var currentTrack = -1 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 { 101 stopTestPlayback() 102 } 103 104 mMediaPlayer?.start() 105 } 106 stopTestPlaybacknull107 fun stopTestPlayback() { 108 mMediaPlayer?.stop() 109 mMediaPlayer?.setOnCompletionListener(null) 110 mMediaPlayer?.release() 111 mMediaPlayer = null 112 } 113 playnull114 fun play() { 115 if (currentTrack == -1 || currentTrack == QUEUE_SIZE) currentTrack = QUEUE_START_INDEX 116 else currentTrack += 1 117 setPlaybackState(PlaybackStateCompat.STATE_PLAYING) 118 mediaSession.setMetadata(metadataItems.get("" + currentTrack)) 119 } 120 stopnull121 fun stop() { 122 setPlaybackState(PlaybackStateCompat.STATE_STOPPED) 123 mediaSession.setMetadata(null) 124 } 125 pausenull126 fun pause() { 127 setPlaybackState(PlaybackStateCompat.STATE_PAUSED) 128 } 129 rewindnull130 fun rewind() { 131 setPlaybackState(PlaybackStateCompat.STATE_REWINDING) 132 } 133 fastForwardnull134 fun fastForward() { 135 setPlaybackState(PlaybackStateCompat.STATE_FAST_FORWARDING) 136 } 137 forwardnull138 fun forward() { 139 if (currentTrack == QUEUE_SIZE || currentTrack == -1) currentTrack = QUEUE_START_INDEX 140 else currentTrack += 1 141 setPlaybackState(PlaybackStateCompat.STATE_SKIPPING_TO_NEXT) 142 mediaSession.setMetadata(metadataItems.get("" + currentTrack)) 143 setPlaybackState(PlaybackStateCompat.STATE_PLAYING) 144 } 145 backwardnull146 fun backward() { 147 if (currentTrack == QUEUE_START_INDEX || currentTrack == -1) currentTrack = QUEUE_SIZE 148 else currentTrack -= 1 149 setPlaybackState(PlaybackStateCompat.STATE_SKIPPING_TO_PREVIOUS) 150 mediaSession.setMetadata(metadataItems.get("" + currentTrack)) 151 setPlaybackState(PlaybackStateCompat.STATE_PLAYING) 152 } 153 setLargeMetadatanull154 fun setLargeMetadata() { 155 currentTrack = QUEUE_SIZE 156 mediaSession.setMetadata(metadataItems.get("" + currentTrack)) 157 setPlaybackState(PlaybackStateCompat.STATE_PLAYING) 158 } 159 updateQueuenull160 fun updateQueue() { 161 val metaData: MediaMetadataCompat = 162 MediaMetadataCompat.Builder() 163 .putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, NOW_PLAYING_PREFIX + NEW_QUEUE_ITEM_INDEX) 164 .putString(MediaMetadataCompat.METADATA_KEY_TITLE, "Title" + NEW_QUEUE_ITEM_INDEX) 165 .putString(MediaMetadataCompat.METADATA_KEY_ARTIST, "Artist" + NEW_QUEUE_ITEM_INDEX) 166 .putString(MediaMetadataCompat.METADATA_KEY_ALBUM, "Album" + NEW_QUEUE_ITEM_INDEX) 167 .putLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER, NEW_QUEUE_ITEM_INDEX.toLong()) 168 .putLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS, NEW_QUEUE_ITEM_INDEX.toLong()) 169 .build() 170 val mediaItem = MediaItem(metaData.description, MediaItem.FLAG_PLAYABLE) 171 queue.add(MediaSessionCompat.QueueItem(mediaItem.description, NEW_QUEUE_ITEM_INDEX.toLong())) 172 mediaSession.setQueue(queue) 173 } 174 getShuffleModenull175 fun getShuffleMode() : Int { 176 val controller = mediaSession.getController() 177 return controller.getShuffleMode() 178 } 179 setShuffleModenull180 fun setShuffleMode(shuffleMode: Int) { 181 val controller = mediaSession.getController() 182 val transportControls = controller.getTransportControls() 183 when (shuffleMode) { 184 SHUFFLE_MODE_NONE, 185 SHUFFLE_MODE_ALL, 186 SHUFFLE_MODE_GROUP -> transportControls.setShuffleMode(shuffleMode) 187 else -> transportControls.setShuffleMode(SHUFFLE_MODE_NONE) 188 } 189 } 190 191 private val mSessionCallback: MediaSessionCompat.Callback = 192 object : MediaSessionCompat.Callback() { onPlaynull193 override fun onPlay() { 194 Log.i(TAG, "onPlay") 195 play() 196 } 197 onPausenull198 override fun onPause() { 199 Log.i(TAG, "onPause") 200 pause() 201 } 202 onSkipToPreviousnull203 override fun onSkipToPrevious() { 204 Log.i(TAG, "onSkipToPrevious") 205 // TODO : Need to handle to play previous audio in the list 206 } 207 onSkipToNextnull208 override fun onSkipToNext() { 209 Log.i(TAG, "onSkipToNext") 210 // TODO : Need to handle to play next audio in the list 211 } 212 onMediaButtonEventnull213 override fun onMediaButtonEvent(mediaButtonEvent: Intent): Boolean { 214 Log.i(TAG, "MediaSessionCallback——》onMediaButtonEvent $mediaButtonEvent") 215 return super.onMediaButtonEvent(mediaButtonEvent) 216 } 217 onSetShuffleModenull218 override fun onSetShuffleMode(shuffleMode: Int) { 219 Log.i(TAG, "MediaSessionCallback——》onSetShuffleMode $shuffleMode") 220 mediaSession.setShuffleMode(shuffleMode) 221 } 222 } 223 onGetRootnull224 override fun onGetRoot(p0: String, clientUid: Int, rootHints: Bundle?): BrowserRoot? { 225 Log.i(TAG, "onGetRoot") 226 return BrowserRoot(ROOT, null) 227 } 228 onLoadChildrennull229 override fun onLoadChildren(parentId: String, result: Result<MutableList<MediaItem>>) { 230 Log.i(TAG, "onLoadChildren") 231 if (parentId == ROOT) { 232 val map = mediaIdToChildren[ROOT] 233 Log.i(TAG, "onloadchildren $map") 234 result.sendResult(map) 235 } else if (parentId == NOW_PLAYING_PREFIX) { 236 result.sendResult(mediaIdToChildren[NOW_PLAYING_PREFIX]) 237 } else { 238 Log.i(TAG, "onloadchildren inside else") 239 result.sendResult(null) 240 } 241 } 242 initMediaItemsnull243 private fun initMediaItems() { 244 var mediaItems = mutableListOf<MediaItem>() 245 for (item in QUEUE_START_INDEX..QUEUE_SIZE) { 246 val metaData: MediaMetadataCompat = 247 MediaMetadataCompat.Builder() 248 .putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, NOW_PLAYING_PREFIX + item) 249 .putString(MediaMetadataCompat.METADATA_KEY_TITLE, "Title$item") 250 .putString( 251 MediaMetadataCompat.METADATA_KEY_ARTIST, 252 if (item != QUEUE_SIZE) "Artist$item" else generateAlphanumericString(512) 253 ) 254 .putString( 255 MediaMetadataCompat.METADATA_KEY_ALBUM, 256 if (item != QUEUE_SIZE) "Album$item" else generateAlphanumericString(512) 257 ) 258 .putLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER, item.toLong()) 259 .putLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS, QUEUE_SIZE.toLong()) 260 .build() 261 val mediaItem = MediaItem(metaData.description, MediaItem.FLAG_PLAYABLE) 262 mediaItems.add(mediaItem) 263 metadataItems.put("" + item, metaData) 264 } 265 mediaIdToChildren[NOW_PLAYING_PREFIX] = mediaItems 266 } 267 initQueuenull268 private fun initQueue() { 269 for ((key, value) in metadataItems.entries) { 270 val mediaItem = MediaItem(value.description, MediaItem.FLAG_PLAYABLE) 271 queue.add(MediaSessionCompat.QueueItem(mediaItem.description, key.toLong())) 272 } 273 } 274 initBrowseFolderListnull275 private fun initBrowseFolderList() { 276 var rootList = mediaIdToChildren[ROOT] ?: mutableListOf() 277 278 val emptyFolderMetaData = 279 MediaMetadataCompat.Builder() 280 .putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, EMPTY_FOLDER) 281 .putString(MediaMetadataCompat.METADATA_KEY_TITLE, EMPTY_FOLDER) 282 .putLong( 283 MediaMetadataCompat.METADATA_KEY_BT_FOLDER_TYPE, 284 MediaDescriptionCompat.BT_FOLDER_TYPE_PLAYLISTS 285 ) 286 .build() 287 val emptyFolderMediaItem = MediaItem(emptyFolderMetaData.description, MediaItem.FLAG_BROWSABLE) 288 289 val playlistMetaData = 290 MediaMetadataCompat.Builder() 291 .apply { 292 putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, NOW_PLAYING_PREFIX) 293 putString(MediaMetadataCompat.METADATA_KEY_TITLE, NOW_PLAYING_PREFIX) 294 putLong( 295 MediaMetadataCompat.METADATA_KEY_BT_FOLDER_TYPE, 296 MediaDescriptionCompat.BT_FOLDER_TYPE_PLAYLISTS 297 ) 298 } 299 .build() 300 301 val playlistsMediaItem = MediaItem(playlistMetaData.description, MediaItem.FLAG_BROWSABLE) 302 303 rootList += emptyFolderMediaItem 304 rootList += playlistsMediaItem 305 mediaIdToChildren[ROOT] = rootList 306 initMediaItems() 307 } 308 309 companion object { 310 lateinit var instance: MediaPlayerBrowserService 311 const val ROOT = "__ROOT__" 312 const val EMPTY_FOLDER = "@empty@" 313 const val NOW_PLAYING_PREFIX = "NowPlayingId" 314 const val QUEUE_START_INDEX = 1 315 const val QUEUE_SIZE = 6 316 const val NEW_QUEUE_ITEM_INDEX = 7 317 isInitializednull318 fun isInitialized(): Boolean = this::instance.isInitialized 319 } 320 } 321