• 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.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