• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 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.googlecode.android_scripting.facade.bluetooth.media;
18 
19 import android.media.AudioAttributes;
20 import android.media.MediaMetadata;
21 import android.media.MediaMetadataRetriever;
22 import android.media.MediaPlayer;
23 import android.media.session.MediaSession;
24 import android.media.session.PlaybackState;
25 import android.net.Uri;
26 import android.os.Environment;
27 import android.os.SystemClock;
28 
29 //import com.googlecode.android_scripting.R;
30 import com.googlecode.android_scripting.facade.bluetooth.BluetoothMediaFacade;
31 import com.googlecode.android_scripting.Log;
32 
33 import java.io.File;
34 import java.io.IOException;
35 import java.util.ArrayList;
36 import java.util.HashMap;
37 import java.util.List;
38 
39 /**
40  * This is a UI-less MediaPlayer that is used in testing Bluetooth Media related test cases.
41  *
42  * This class handles media playback commands coming from the MediaBrowserService.
43  * This is responsible for dealing with getting the media content and creating a MediaPlayer
44  * on the MediaBrowserService's MediaSession.
45  * This codepath would be exercised an an Audio source (Phone).
46  *
47  * The nested MusicProvider utility class takes care of reading the media files and maintaining
48  * the Playing Queue.  It expects the media files to have been pushed to /sdcard/Music/test
49  */
50 
51 public class BluetoothMediaPlayback {
52     private MediaPlayer mMediaPlayer = null;
53     private MediaSession playbackSession = null;
54     private MusicProvider musicProvider = null;
55     private int queueIndex;
56     private static final String TAG = "BluetoothMediaPlayback";
57     private int mState;
58     private long mCurrentPosition = 0;
59 
60     // Passing in the Resources
BluetoothMediaPlayback()61     public BluetoothMediaPlayback() {
62         queueIndex = 0;
63         musicProvider = new MusicProvider();
64         mState = PlaybackState.STATE_NONE;
65     }
66 
67     /**
68      * MediaPlayer Callback for Completion. Used to move to the next track.
69      */
70     private MediaPlayer.OnCompletionListener mCompletionListener =
71             new MediaPlayer.OnCompletionListener() {
72                 @Override
73                 public void onCompletion(MediaPlayer player) {
74                     queueIndex++;
75                     // If we were playing the last item in the Queue, reset back to the first
76                     // item.
77                     if (queueIndex >= musicProvider.getNumberOfItemsInQueue()) {
78                         queueIndex = 0;
79                     }
80                     mCurrentPosition = 0;
81                     play();
82                 }
83             };
84 
85     /**
86      * MediaPlayer Callback for Error Handling
87      */
88     private MediaPlayer.OnErrorListener mErrorListener = new MediaPlayer.OnErrorListener() {
89         @Override
90         public boolean onError(MediaPlayer mp, int what, int extra) {
91             Log.d(TAG + " MediaPlayer Error " + what);
92             // Release the resources
93             mMediaPlayer.stop();
94             releaseMediaPlayer();
95             mMediaPlayer.release();
96             mMediaPlayer = null;
97             return false;
98         }
99     };
100 
101     /**
102      * Build & Return the AudioAtrributes for the MediaPlayer.
103      *
104      * @return {@link AudioAttributes}
105      */
createAudioAttributes(int contentType, int usage)106     private AudioAttributes createAudioAttributes(int contentType, int usage) {
107         AudioAttributes.Builder builder = new AudioAttributes.Builder();
108         return builder.setContentType(contentType).setUsage(usage).build();
109     }
110 
111     /**
112      * Update the Current Playback State on the Media Session
113      *
114      * @param state - the state to set to.
115      */
updatePlaybackState(int state)116     private void updatePlaybackState(int state) {
117         PlaybackState.Builder stateBuilder = new PlaybackState.Builder();
118         Log.d(TAG + " Update Playback Status Curr Posn: " + mCurrentPosition);
119         stateBuilder.setState(state, mCurrentPosition, 1.0f, SystemClock.elapsedRealtime());
120         stateBuilder.setActions(PlaybackState.ACTION_PLAY | PlaybackState.ACTION_PAUSE |
121                 PlaybackState.ACTION_SKIP_TO_NEXT | PlaybackState.ACTION_SKIP_TO_PREVIOUS);
122         playbackSession.setPlaybackState(stateBuilder.build());
123     }
124 
125     /**
126      * The core method that handles loading the media file from the raw resources
127      * and sets up and prepares the MediaPlayer to play the file.
128      *
129      * @param newTrack - the MediaMetadata to update the MediaSession with.
130      */
handlePlayMedia(MediaMetadata newTrack)131     private void handlePlayMedia(MediaMetadata newTrack) {
132         createMediaPlayerIfNeeded();
133         // Updates the MediaBrowserService's MediaSession's metadata
134         playbackSession.setMetadata(newTrack);
135         String url = newTrack.getString(MusicProvider.CUSTOM_URL);
136         try {
137             mMediaPlayer.setDataSource(
138                     BluetoothSL4AAudioSrcMBS.getAvrcpMediaBrowserService().getApplicationContext(),
139                     Uri.parse(url));
140             mMediaPlayer.prepare();
141         } catch (IOException e) {
142             throw new RuntimeException(e);
143         }
144         Log.d(TAG + " MediaPlayer Start");
145         mMediaPlayer.start();
146     }
147 
148     /**
149      * Sets the MediaSession to operate on
150      */
setMediaSession(MediaSession session)151     public void setMediaSession(MediaSession session) {
152         playbackSession = session;
153     }
154 
155     /**
156      * Create MediaPlayer on demand if necessary.
157      * It also sets the appropriate callbacks for Completion and Error Handling
158      */
createMediaPlayerIfNeeded()159     public void createMediaPlayerIfNeeded() {
160         if (mMediaPlayer == null) {
161             mMediaPlayer = new MediaPlayer();
162             mMediaPlayer.setOnCompletionListener(mCompletionListener);
163             mMediaPlayer.setOnErrorListener(mErrorListener);
164         } else {
165             mMediaPlayer.reset();
166         }
167     }
168 
169     /**
170      * Release the current Media Player
171      */
releaseMediaPlayer()172     public void releaseMediaPlayer() {
173         if (mMediaPlayer == null) {
174             return;
175         }
176         mMediaPlayer.reset();
177         mMediaPlayer.release();
178         mMediaPlayer = null;
179     }
180 
181     /**
182      * Sets the Volume for the MediaSession
183      */
setVolume(float leftVolume, float rightVolume)184     public void setVolume(float leftVolume, float rightVolume) {
185         if (mMediaPlayer != null) {
186             mMediaPlayer.setVolume(leftVolume, rightVolume);
187         }
188     }
189 
190     /**
191      * Gets the item to play from the MusicProvider's PlayQueue
192      * Also dispatches a "I received a Play Command" acknowledgement through the Facade.
193      */
play()194     public void play() {
195         Log.d(TAG + " play queIndex: " + queueIndex);
196         BluetoothMediaFacade.dispatchPlaybackStateChanged(PlaybackState.STATE_PLAYING);
197         MediaMetadata newMetaData = musicProvider.getItemToPlay(queueIndex);
198         if (newMetaData == null) {
199             //Error logged in getItemToPlay already.
200             return;
201         }
202         handlePlayMedia(newMetaData);
203         updatePlaybackState(PlaybackState.STATE_PLAYING);
204 
205     }
206 
207     /**
208      * Gets the currently playing MediaItem to pause
209      * Also dispatches a "I received a Pause Command" acknowledgement through the Facade.
210      */
pause()211     public void pause() {
212         BluetoothMediaFacade.dispatchPlaybackStateChanged(PlaybackState.STATE_PAUSED);
213         if (mMediaPlayer == null) {
214             Log.d(TAG + " MediaPlayer not yet created.");
215             return;
216         }
217         mMediaPlayer.pause();
218         // Cache the current position to use when play resumes
219         mCurrentPosition = mMediaPlayer.getCurrentPosition();
220         updatePlaybackState(PlaybackState.STATE_PAUSED);
221     }
222 
223     /**
224      * Skips to the next item in the MusicProvider's PlayQueue
225      * Also dispatches a "I received a SkipNext Command" acknowledgement through the Facade
226      */
skipNext()227     public void skipNext() {
228         BluetoothMediaFacade.dispatchPlaybackStateChanged(PlaybackState.STATE_SKIPPING_TO_NEXT);
229         queueIndex++;
230         if (queueIndex >= musicProvider.getNumberOfItemsInQueue()) {
231             queueIndex = 0;
232         }
233         Log.d(TAG + " skipNext queIndex: " + queueIndex);
234         MediaMetadata newMetaData = musicProvider.getItemToPlay(queueIndex);
235         if (newMetaData == null) {
236             //Error logged in getItemToPlay already.
237             return;
238         }
239         mCurrentPosition = 0;
240         handlePlayMedia(newMetaData);
241 
242     }
243 
244     /**
245      * Skips to the previous item in the MusicProvider's PlayQueue
246      * Also dispatches a "I received a SkipPrev Command" acknowledgement through the Facade.
247      */
248 
skipPrev()249     public void skipPrev() {
250         BluetoothMediaFacade.dispatchPlaybackStateChanged(PlaybackState.STATE_SKIPPING_TO_PREVIOUS);
251         queueIndex--;
252         if (queueIndex < 0) {
253             queueIndex = 0;
254         }
255         Log.d(TAG + " skipPrev queIndex: " + queueIndex);
256         MediaMetadata newMetaData = musicProvider.getItemToPlay(queueIndex);
257         if (newMetaData == null) {
258             //Error logged in getItemToPlay already.
259             return;
260         }
261         mCurrentPosition = 0;
262         handlePlayMedia(newMetaData);
263 
264     }
265 
266     /**
267      * Resets and releases the MediaPlayer
268      */
269 
stop()270     public void stop() {
271         queueIndex = 0;
272         releaseMediaPlayer();
273         updatePlaybackState(PlaybackState.STATE_STOPPED);
274     }
275 
276 
277     /**
278      * Utility Class to abstract retrieving and providing Playback with the appropriate MediaFile
279      * This looks for Media files used for the test to be present in /sdcard/Music/test directory
280      * It is the responsibility of the client side to push the media files to the above directory
281      * before or as part of the test.
282      */
283     private class MusicProvider {
284         List<String> mediaFilesPath;
285         HashMap musicResources;
286         public static final String CUSTOM_URL = "__MUSIC_URL__";
287         private static final String TAG = "BluetoothMediaMusicProvider";
288         // The test samples for the test is expected to be in the /sdcard/Music/test directory
289         private static final String MEDIA_TEST_PATH = "/Music/test";
290 
MusicProvider()291         public MusicProvider() {
292             mediaFilesPath = new ArrayList<String>();
293             // Get the Media file names from the Music directory
294             List<String> mediaFileNames = new ArrayList<String>();
295             String musicPath =
296                     Environment.getExternalStorageDirectory().toString() + MEDIA_TEST_PATH;
297             File musicDir = new File(musicPath);
298             if (musicDir != null) {
299                 if (musicDir.listFiles() != null) {
300                     for (File f : musicDir.listFiles()) {
301                         if (f.isFile()) {
302                             mediaFileNames.add(f.getName());
303                         }
304                     }
305                 }
306                 musicResources = new HashMap();
307                 // Extract the metadata from the media files and build a hashmap
308                 // of <filename, mediametadata> called musicResources.
309                 for (String song : mediaFileNames) {
310                     String songPath = musicPath + "/" + song;
311                     mediaFilesPath.add(songPath);
312                     Log.d(TAG + " Retrieving Meta Data for " + songPath);
313                     MediaMetadata track = retrieveMetaData(songPath);
314                     musicResources.put(songPath, track);
315                 }
316                 Log.d(TAG + "MusicProvider Num of Songs : " + mediaFilesPath.size());
317             } else {
318                 Log.e(TAG + " No media files found");
319             }
320         }
321 
322         /**
323          * Opens the Media File from the resources and retrieves the Metadata information
324          *
325          * @param song - the resource path of the file
326          * @return {@link MediaMetadata} corresponding to the media file loaded.
327          */
retrieveMetaData(String song)328         private MediaMetadata retrieveMetaData(String song) {
329             MediaMetadata.Builder newMetaData = new MediaMetadata.Builder();
330             MediaMetadataRetriever retriever = new MediaMetadataRetriever();
331             retriever.setDataSource(
332                     BluetoothSL4AAudioSrcMBS.getAvrcpMediaBrowserService().getApplicationContext(),
333                     Uri.parse(song));
334 
335             // Extract from the mediafile and build the MediaMetadata
336             newMetaData.putString(MediaMetadata.METADATA_KEY_TITLE,
337                     retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE));
338             Log.d(TAG + " Retriever : " + retriever.extractMetadata(
339                     MediaMetadataRetriever.METADATA_KEY_TITLE));
340             newMetaData.putString(MediaMetadata.METADATA_KEY_ALBUM,
341                     retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ALBUM));
342             newMetaData.putString(MediaMetadata.METADATA_KEY_ARTIST,
343                     retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ARTIST));
344             newMetaData.putLong(MediaMetadata.METADATA_KEY_DURATION, Long.parseLong(
345                     retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)));
346             newMetaData.putLong(MediaMetadata.METADATA_KEY_NUM_TRACKS, Long.parseLong(
347                     retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_NUM_TRACKS)));
348             //newMetaData.putLong(CUSTOM_MUSIC_PROVIDER_RESOURCE_ID, resourceId);
349             newMetaData.putString(CUSTOM_URL, song);
350             return newMetaData.build();
351 
352         }
353 
354         /**
355          * Returns the MediaMetadata of the song that corresponds to the index in the Queue.
356          *
357          * @return {@link MediaMetadata}
358          */
getItemToPlay(int queueIndex)359         public MediaMetadata getItemToPlay(int queueIndex) {
360             // We have 2 data structures in this utility class -
361             // 1. A String List called mediaFilesPath - holds the file names (incl path) of the
362             // media files
363             // 2. A hashmap called musicResources that has been built where the keys are from
364             // the List mediaFilesPath above and the values are the corresponding extracted
365             // MediaMetadata.
366             // mediaFilesPath doubles up as the Playing Queue.  The index that is passed here
367             // is used to retrieve the filename which is then keyed into the musicResources
368             // to return the MediaMetadata.
369             if (mediaFilesPath.size() == 0) {
370                 Log.e(TAG + " No Media to play");
371                 return null;
372             }
373             String song = mediaFilesPath.get(queueIndex);
374             MediaMetadata track = (MediaMetadata) musicResources.get(song);
375             return track;
376         }
377 
378         /**
379          * Number of items we have in the Play Queue
380          *
381          * @return Number of items.
382          */
getNumberOfItemsInQueue()383         public int getNumberOfItemsInQueue() {
384             return musicResources.size();
385         }
386 
387     }
388 }