• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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