diff options
Diffstat (limited to 'MusicDemo/src/main/java/com/example/android/musicservicedemo/MusicService.java')
-rw-r--r-- | MusicDemo/src/main/java/com/example/android/musicservicedemo/MusicService.java | 934 |
1 files changed, 0 insertions, 934 deletions
diff --git a/MusicDemo/src/main/java/com/example/android/musicservicedemo/MusicService.java b/MusicDemo/src/main/java/com/example/android/musicservicedemo/MusicService.java deleted file mode 100644 index c737ba4..0000000 --- a/MusicDemo/src/main/java/com/example/android/musicservicedemo/MusicService.java +++ /dev/null @@ -1,934 +0,0 @@ -/* - * Copyright (C) 2014 Google Inc. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.example.android.musicservicedemo; - -import android.content.Context; -import android.content.Intent; -import android.media.AudioManager; -import android.media.MediaDescription; -import android.media.MediaMetadata; -import android.media.MediaPlayer; -import android.media.MediaPlayer.OnCompletionListener; -import android.media.MediaPlayer.OnErrorListener; -import android.media.MediaPlayer.OnPreparedListener; -import android.media.browse.MediaBrowser; -import android.media.browse.MediaBrowser.MediaItem; -import android.media.session.MediaSession; -import android.media.session.PlaybackState; -import android.net.Uri; -import android.net.wifi.WifiManager; -import android.net.wifi.WifiManager.WifiLock; -import android.os.Bundle; -import android.os.Handler; -import android.os.Message; -import android.os.PowerManager; -import android.os.SystemClock; -import android.service.media.MediaBrowserService; - -import com.example.android.musicservicedemo.model.MusicProvider; -import com.example.android.musicservicedemo.utils.LogHelper; -import com.example.android.musicservicedemo.utils.MediaIDHelper; -import com.example.android.musicservicedemo.utils.QueueHelper; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -import static com.example.android.musicservicedemo.utils.MediaIDHelper.MEDIA_ID_MUSICS_BY_GENRE; -import static com.example.android.musicservicedemo.utils.MediaIDHelper.MEDIA_ID_ROOT; -import static com.example.android.musicservicedemo.utils.MediaIDHelper.createBrowseCategoryMediaID; -import static com.example.android.musicservicedemo.utils.MediaIDHelper.extractBrowseCategoryFromMediaID; - -/** - * Main entry point for the Android Automobile integration. This class needs to: - * - * <ul> - * - * <li> Extend {@link android.service.media.MediaBrowserService}, implementing the media browsing - * related methods {@link android.service.media.MediaBrowserService#onGetRoot} and - * {@link android.service.media.MediaBrowserService#onLoadChildren}; - * <li> Start a new {@link android.media.session.MediaSession} and notify its parent with the - * session's token {@link android.service.media.MediaBrowserService#setSessionToken}; - * - * <li> Set a callback on the - * {@link android.media.session.MediaSession#setCallback(android.media.session.MediaSession.Callback)}. - * The callback will receive all the user's actions, like play, pause, etc; - * - * <li> Handle all the actual music playing using any method your app prefers (for example, - * {@link android.media.MediaPlayer}) - * - * <li> Update playbackState, "now playing" metadata and queue, using MediaSession proper methods - * {@link android.media.session.MediaSession#setPlaybackState(android.media.session.PlaybackState)} - * {@link android.media.session.MediaSession#setMetadata(android.media.MediaMetadata)} and - * {@link android.media.session.MediaSession#setQueue(java.util.List)}) - * - * <li> Be declared in AndroidManifest as an intent receiver for the action - * android.media.browse.MediaBrowserService - * - * <li> Declare a meta-data tag in AndroidManifest.xml linking to a xml resource - * with a <automotiveApp> root element. For a media app, this must include - * an <uses name="media"/> element as a child. - * For example, in AndroidManifest.xml: - * <meta-data android:name="com.google.android.gms.car.application" - * android:resource="@xml/automotive_app_desc"/> - * And in res/values/automotive_app_desc.xml: - * <automotiveApp> - * <uses name="media"/> - * </automotiveApp> - * - * </ul> - - * <p> - * Customization: - * - * <li> Add custom actions in the state passed to setPlaybackState(state) - * <li> Handle custom actions in the MediaSession.Callback.onCustomAction - * <li> Use UI theme primaryColor to set the player color - * - * @see <a href="README.txt">README.txt</a> for more details. - * - */ - -public class MusicService extends MediaBrowserService implements OnPreparedListener, - OnCompletionListener, OnErrorListener, AudioManager.OnAudioFocusChangeListener { - - private static final String TAG = "MusicService"; - - // Action to thumbs up a media item - private static final String CUSTOM_ACTION_THUMBS_UP = "thumbs_up"; - // Delay stopSelf by using a handler. - private static final int STOP_DELAY = 30000; - - // The volume we set the media player to when we lose audio focus, but are - // allowed to reduce the volume instead of stopping playback. - public static final float VOLUME_DUCK = 0.2f; - - // The volume we set the media player when we have audio focus. - public static final float VOLUME_NORMAL = 1.0f; - public static final String ANDROID_AUTO_PACKAGE_NAME = "com.google.android.projection.gearhead"; - public static final String ANDROID_AUTO_EMULATOR_PACKAGE_NAME = "com.example.android.media"; - - // Music catalog manager - private MusicProvider mMusicProvider; - - private MediaSession mSession; - private MediaPlayer mMediaPlayer; - - // "Now playing" queue: - private List<MediaSession.QueueItem> mPlayingQueue; - private int mCurrentIndexOnQueue; - - // Current local media player state - private int mState = PlaybackState.STATE_NONE; - - // Wifi lock that we hold when streaming files from the internet, in order - // to prevent the device from shutting off the Wifi radio - private WifiLock mWifiLock; - - private MediaNotification mMediaNotification; - - // Indicates whether the service was started. - private boolean mServiceStarted; - - enum AudioFocus { - NoFocusNoDuck, // we don't have audio focus, and can't duck - NoFocusCanDuck, // we don't have focus, but can play at a low volume - // ("ducking") - Focused // we have full audio focus - } - - // Type of audio focus we have: - private AudioFocus mAudioFocus = AudioFocus.NoFocusNoDuck; - private AudioManager mAudioManager; - - // Indicates if we should start playing immediately after we gain focus. - private boolean mPlayOnFocusGain; - - private Handler mDelayedStopHandler = new Handler() { - @Override - public void handleMessage(Message msg) { - if ((mMediaPlayer != null && mMediaPlayer.isPlaying()) || - mPlayOnFocusGain) { - LogHelper.d(TAG, "Ignoring delayed stop since the media player is in use."); - return; - } - LogHelper.d(TAG, "Stopping service with delay handler."); - stopSelf(); - mServiceStarted = false; - } - }; - - /* - * (non-Javadoc) - * @see android.app.Service#onCreate() - */ - @Override - public void onCreate() { - super.onCreate(); - LogHelper.d(TAG, "onCreate"); - - mPlayingQueue = new ArrayList<>(); - - // Create the Wifi lock (this does not acquire the lock, this just creates it) - mWifiLock = ((WifiManager) getSystemService(Context.WIFI_SERVICE)) - .createWifiLock(WifiManager.WIFI_MODE_FULL, "MusicDemo_lock"); - - - // Create the music catalog metadata provider - mMusicProvider = new MusicProvider(); - mMusicProvider.retrieveMedia(new MusicProvider.Callback() { - @Override - public void onMusicCatalogReady(boolean success) { - mState = success ? PlaybackState.STATE_STOPPED : PlaybackState.STATE_ERROR; - } - }); - - mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); - - // Start a new MediaSession - mSession = new MediaSession(this, "MusicService"); - setSessionToken(mSession.getSessionToken()); - mSession.setCallback(new MediaSessionCallback()); - mSession.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS | - MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS); - - // Use these extras to reserve space for the corresponding actions, even when they are disabled - // in the playbackstate, so the custom actions don't reflow. - Bundle extras = new Bundle(); - extras.putBoolean( - "com.google.android.gms.car.media.ALWAYS_RESERVE_SPACE_FOR.ACTION_SKIP_TO_NEXT", - true); - extras.putBoolean( - "com.google.android.gms.car.media.ALWAYS_RESERVE_SPACE_FOR.ACTION_SKIP_TO_PREVIOUS", - true); - // If you want to reserve the Queue slot when there is no queue - // (mSession.setQueue(emptylist)), uncomment the lines below: - // extras.putBoolean( - // "com.google.android.gms.car.media.ALWAYS_RESERVE_SPACE_FOR.ACTION_QUEUE", - // true); - mSession.setExtras(extras); - - updatePlaybackState(null); - - mMediaNotification = new MediaNotification(this); - } - - /* - * (non-Javadoc) - * @see android.app.Service#onDestroy() - */ - @Override - public void onDestroy() { - LogHelper.d(TAG, "onDestroy"); - - // Service is being killed, so make sure we release our resources - handleStopRequest(null); - - mDelayedStopHandler.removeCallbacksAndMessages(null); - // In particular, always release the MediaSession to clean up resources - // and notify associated MediaController(s). - mSession.release(); - } - - - // ********* MediaBrowserService methods: - - @Override - public BrowserRoot onGetRoot(String clientPackageName, int clientUid, Bundle rootHints) { - LogHelper.d(TAG, "OnGetRoot: clientPackageName=" + clientPackageName, - "; clientUid=" + clientUid + " ; rootHints=", rootHints); - // To ensure you are not allowing any arbitrary app to browse your app's contents, you - // need to check the origin: - if (!ANDROID_AUTO_PACKAGE_NAME.equals(clientPackageName) && - !ANDROID_AUTO_EMULATOR_PACKAGE_NAME.equals(clientPackageName) && - !getApplication().getPackageName().equals(clientPackageName)) { - // If the request comes from an untrusted package, return null. No further calls will - // be made to other media browsing methods. - LogHelper.w(TAG, "OnGetRoot: IGNORING request from untrusted package " + clientPackageName); - return null; - } - return new BrowserRoot(MEDIA_ID_ROOT, null); - } - - @Override - public void onLoadChildren(final String parentMediaId, final Result<List<MediaItem>> result) { - if (!mMusicProvider.isInitialized()) { - // Use result.detach to allow calling result.sendResult from another thread: - result.detach(); - - mMusicProvider.retrieveMedia(new MusicProvider.Callback() { - @Override - public void onMusicCatalogReady(boolean success) { - if (success) { - loadChildrenImpl(parentMediaId, result); - } else { - updatePlaybackState(getString(R.string.error_no_metadata)); - result.sendResult(new ArrayList<MediaItem>()); - } - } - }); - - } else { - // If our music catalog is already loaded/cached, load them into result immediately - loadChildrenImpl(parentMediaId, result); - } - } - - /** - * Actual implementation of onLoadChildren that assumes that MusicProvider is already - * initialized. - */ - private void loadChildrenImpl(final String parentMediaId, - final Result<List<MediaBrowser.MediaItem>> result) { - LogHelper.d(TAG, "OnLoadChildren: parentMediaId=", parentMediaId); - - List<MediaBrowser.MediaItem> mediaItems = new ArrayList<>(); - - if (MEDIA_ID_ROOT.equals(parentMediaId)) { - LogHelper.d(TAG, "OnLoadChildren.ROOT"); - mediaItems.add(new MediaBrowser.MediaItem( - new MediaDescription.Builder() - .setMediaId(MEDIA_ID_MUSICS_BY_GENRE) - .setTitle(getString(R.string.browse_genres)) - .setIconUri(Uri.parse("android.resource://com.example.android.musicservicedemo/drawable/ic_by_genre")) - .setSubtitle(getString(R.string.browse_genre_subtitle)) - .build(), MediaBrowser.MediaItem.FLAG_BROWSABLE - )); - - } else if (MEDIA_ID_MUSICS_BY_GENRE.equals(parentMediaId)) { - LogHelper.d(TAG, "OnLoadChildren.GENRES"); - for (String genre: mMusicProvider.getGenres()) { - MediaBrowser.MediaItem item = new MediaBrowser.MediaItem( - new MediaDescription.Builder() - .setMediaId(createBrowseCategoryMediaID(MEDIA_ID_MUSICS_BY_GENRE, genre)) - .setTitle(genre) - .setSubtitle(getString(R.string.browse_musics_by_genre_subtitle, genre)) - .build(), MediaBrowser.MediaItem.FLAG_BROWSABLE - ); - mediaItems.add(item); - } - - } else if (parentMediaId.startsWith(MEDIA_ID_MUSICS_BY_GENRE)) { - String genre = extractBrowseCategoryFromMediaID(parentMediaId)[1]; - LogHelper.d(TAG, "OnLoadChildren.SONGS_BY_GENRE genre=", genre); - for (MediaMetadata track: mMusicProvider.getMusicsByGenre(genre)) { - // Since mediaMetadata fields are immutable, we need to create a copy, so we - // can set a hierarchy-aware mediaID. We will need to know the media hierarchy - // when we get a onPlayFromMusicID call, so we can create the proper queue based - // on where the music was selected from (by artist, by genre, random, etc) - String hierarchyAwareMediaID = MediaIDHelper.createTrackMediaID( - MEDIA_ID_MUSICS_BY_GENRE, genre, track); - MediaMetadata trackCopy = new MediaMetadata.Builder(track) - .putString(MediaMetadata.METADATA_KEY_MEDIA_ID, hierarchyAwareMediaID) - .build(); - MediaBrowser.MediaItem bItem = new MediaBrowser.MediaItem( - trackCopy.getDescription(), MediaItem.FLAG_PLAYABLE); - mediaItems.add(bItem); - } - } else { - LogHelper.w(TAG, "Skipping unmatched parentMediaId: ", parentMediaId); - } - result.sendResult(mediaItems); - } - - - - // ********* MediaSession.Callback implementation: - - private final class MediaSessionCallback extends MediaSession.Callback { - @Override - public void onPlay() { - LogHelper.d(TAG, "play"); - - if (mPlayingQueue == null || mPlayingQueue.isEmpty()) { - mPlayingQueue = QueueHelper.getRandomQueue(mMusicProvider); - mSession.setQueue(mPlayingQueue); - mSession.setQueueTitle(getString(R.string.random_queue_title)); - // start playing from the beginning of the queue - mCurrentIndexOnQueue = 0; - } - - if (mPlayingQueue != null && !mPlayingQueue.isEmpty()) { - handlePlayRequest(); - } - } - - @Override - public void onSkipToQueueItem(long queueId) { - LogHelper.d(TAG, "OnSkipToQueueItem:" + queueId); - if (mPlayingQueue != null && !mPlayingQueue.isEmpty()) { - - // set the current index on queue from the music Id: - mCurrentIndexOnQueue = QueueHelper.getMusicIndexOnQueue(mPlayingQueue, queueId); - - // play the music - handlePlayRequest(); - } - } - - @Override - public void onPlayFromMediaId(String mediaId, Bundle extras) { - LogHelper.d(TAG, "playFromMediaId mediaId:", mediaId, " extras=", extras); - - // The mediaId used here is not the unique musicId. This one comes from the - // MediaBrowser, and is actually a "hierarchy-aware mediaID": a concatenation of - // the hierarchy in MediaBrowser and the actual unique musicID. This is necessary - // so we can build the correct playing queue, based on where the track was - // selected from. - mPlayingQueue = QueueHelper.getPlayingQueue(mediaId, mMusicProvider); - mSession.setQueue(mPlayingQueue); - String queueTitle = getString(R.string.browse_musics_by_genre_subtitle, - MediaIDHelper.extractBrowseCategoryValueFromMediaID(mediaId)); - mSession.setQueueTitle(queueTitle); - - if (mPlayingQueue != null && !mPlayingQueue.isEmpty()) { - String uniqueMusicID = MediaIDHelper.extractMusicIDFromMediaID(mediaId); - // set the current index on queue from the music Id: - mCurrentIndexOnQueue = QueueHelper.getMusicIndexOnQueue( - mPlayingQueue, uniqueMusicID); - - // play the music - handlePlayRequest(); - } - } - - @Override - public void onPause() { - LogHelper.d(TAG, "pause. current state=" + mState); - handlePauseRequest(); - } - - @Override - public void onStop() { - LogHelper.d(TAG, "stop. current state=" + mState); - handleStopRequest(null); - } - - @Override - public void onSkipToNext() { - LogHelper.d(TAG, "skipToNext"); - mCurrentIndexOnQueue++; - if (mPlayingQueue != null && mCurrentIndexOnQueue >= mPlayingQueue.size()) { - mCurrentIndexOnQueue = 0; - } - if (QueueHelper.isIndexPlayable(mCurrentIndexOnQueue, mPlayingQueue)) { - mState = PlaybackState.STATE_PLAYING; - handlePlayRequest(); - } else { - LogHelper.e(TAG, "skipToNext: cannot skip to next. next Index=" + - mCurrentIndexOnQueue + " queue length=" + - (mPlayingQueue == null ? "null" : mPlayingQueue.size())); - handleStopRequest("Cannot skip"); - } - } - - @Override - public void onSkipToPrevious() { - LogHelper.d(TAG, "skipToPrevious"); - - mCurrentIndexOnQueue--; - if (mPlayingQueue != null && mCurrentIndexOnQueue < 0) { - // This sample's behavior: skipping to previous when in first song restarts the - // first song. - mCurrentIndexOnQueue = 0; - } - if (QueueHelper.isIndexPlayable(mCurrentIndexOnQueue, mPlayingQueue)) { - mState = PlaybackState.STATE_PLAYING; - handlePlayRequest(); - } else { - LogHelper.e(TAG, "skipToPrevious: cannot skip to previous. previous Index=" + - mCurrentIndexOnQueue + " queue length=" + - (mPlayingQueue == null ? "null" : mPlayingQueue.size())); - handleStopRequest("Cannot skip"); - } - } - - @Override - public void onCustomAction(String action, Bundle extras) { - if (CUSTOM_ACTION_THUMBS_UP.equals(action)) { - LogHelper.i(TAG, "onCustomAction: favorite for current track"); - MediaMetadata track = getCurrentPlayingMusic(); - if (track != null) { - String mediaId = track.getString(MediaMetadata.METADATA_KEY_MEDIA_ID); - mMusicProvider.setFavorite(mediaId, !mMusicProvider.isFavorite(mediaId)); - } - updatePlaybackState(null); - } else { - LogHelper.e(TAG, "Unsupported action: ", action); - } - - } - - @Override - public void onPlayFromSearch(String query, Bundle extras) { - LogHelper.d(TAG, "playFromSearch query=", query); - - mPlayingQueue = QueueHelper.getPlayingQueueFromSearch(query, mMusicProvider); - LogHelper.d(TAG, "playFromSearch playqueue.length=" + mPlayingQueue.size()); - mSession.setQueue(mPlayingQueue); - - if (mPlayingQueue != null && !mPlayingQueue.isEmpty()) { - - // start playing from the beginning of the queue - mCurrentIndexOnQueue = 0; - - handlePlayRequest(); - } - } - } - - - - // ********* MediaPlayer listeners: - - /* - * Called when media player is done playing current song. - * @see android.media.MediaPlayer.OnCompletionListener - */ - @Override - public void onCompletion(MediaPlayer player) { - LogHelper.d(TAG, "onCompletion from MediaPlayer"); - // The media player finished playing the current song, so we go ahead - // and start the next. - if (mPlayingQueue != null && !mPlayingQueue.isEmpty()) { - // In this sample, we restart the playing queue when it gets to the end: - mCurrentIndexOnQueue++; - if (mCurrentIndexOnQueue >= mPlayingQueue.size()) { - mCurrentIndexOnQueue = 0; - } - handlePlayRequest(); - } else { - // If there is nothing to play, we stop and release the resources: - handleStopRequest(null); - } - } - - /* - * Called when media player is done preparing. - * @see android.media.MediaPlayer.OnPreparedListener - */ - @Override - public void onPrepared(MediaPlayer player) { - LogHelper.d(TAG, "onPrepared from MediaPlayer"); - // The media player is done preparing. That means we can start playing if we - // have audio focus. - configMediaPlayerState(); - } - - /** - * Called when there's an error playing media. When this happens, the media - * player goes to the Error state. We warn the user about the error and - * reset the media player. - * - * @see android.media.MediaPlayer.OnErrorListener - */ - @Override - public boolean onError(MediaPlayer mp, int what, int extra) { - LogHelper.e(TAG, "Media player error: what=" + what + ", extra=" + extra); - handleStopRequest("MediaPlayer error " + what + " (" + extra + ")"); - return true; // true indicates we handled the error - } - - - - - // ********* OnAudioFocusChangeListener listener: - - - /** - * Called by AudioManager on audio focus changes. - */ - @Override - public void onAudioFocusChange(int focusChange) { - LogHelper.d(TAG, "onAudioFocusChange. focusChange=" + focusChange); - if (focusChange == AudioManager.AUDIOFOCUS_GAIN) { - // We have gained focus: - mAudioFocus = AudioFocus.Focused; - - } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS || - focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT || - focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) { - // We have lost focus. If we can duck (low playback volume), we can keep playing. - // Otherwise, we need to pause the playback. - boolean canDuck = focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK; - mAudioFocus = canDuck ? AudioFocus.NoFocusCanDuck : AudioFocus.NoFocusNoDuck; - - // If we are playing, we need to reset media player by calling configMediaPlayerState - // with mAudioFocus properly set. - if (mState == PlaybackState.STATE_PLAYING && !canDuck) { - // If we don't have audio focus and can't duck, we save the information that - // we were playing, so that we can resume playback once we get the focus back. - mPlayOnFocusGain = true; - } - } else { - LogHelper.e(TAG, "onAudioFocusChange: Ignoring unsupported focusChange: " + focusChange); - } - - configMediaPlayerState(); - } - - - - // ********* private methods: - - /** - * Handle a request to play music - */ - private void handlePlayRequest() { - LogHelper.d(TAG, "handlePlayRequest: mState=" + mState); - - mDelayedStopHandler.removeCallbacksAndMessages(null); - if (!mServiceStarted) { - LogHelper.v(TAG, "Starting service"); - // The MusicService needs to keep running even after the calling MediaBrowser - // is disconnected. Call startService(Intent) and then stopSelf(..) when we no longer - // need to play media. - startService(new Intent(getApplicationContext(), MusicService.class)); - mServiceStarted = true; - } - - mPlayOnFocusGain = true; - tryToGetAudioFocus(); - - if (!mSession.isActive()) { - mSession.setActive(true); - } - - // actually play the song - if (mState == PlaybackState.STATE_PAUSED) { - // If we're paused, just continue playback and restore the - // 'foreground service' state. - configMediaPlayerState(); - } else { - // If we're stopped or playing a song, - // just go ahead to the new song and (re)start playing - playCurrentSong(); - } - } - - - /** - * Handle a request to pause music - */ - private void handlePauseRequest() { - LogHelper.d(TAG, "handlePauseRequest: mState=" + mState); - - if (mState == PlaybackState.STATE_PLAYING) { - // Pause media player and cancel the 'foreground service' state. - mState = PlaybackState.STATE_PAUSED; - if (mMediaPlayer.isPlaying()) { - mMediaPlayer.pause(); - } - // while paused, retain the MediaPlayer but give up audio focus - relaxResources(false); - giveUpAudioFocus(); - } - updatePlaybackState(null); - } - - /** - * Handle a request to stop music - */ - private void handleStopRequest(String withError) { - LogHelper.d(TAG, "handleStopRequest: mState=" + mState + " error=", withError); - mState = PlaybackState.STATE_STOPPED; - - // let go of all resources... - relaxResources(true); - giveUpAudioFocus(); - updatePlaybackState(withError); - - mMediaNotification.stopNotification(); - - // service is no longer necessary. Will be started again if needed. - stopSelf(); - mServiceStarted = false; - } - - /** - * Releases resources used by the service for playback. This includes the - * "foreground service" status, the wake locks and possibly the MediaPlayer. - * - * @param releaseMediaPlayer Indicates whether the Media Player should also - * be released or not - */ - private void relaxResources(boolean releaseMediaPlayer) { - LogHelper.d(TAG, "relaxResources. releaseMediaPlayer=" + releaseMediaPlayer); - // stop being a foreground service - stopForeground(true); - - // reset the delayed stop handler. - mDelayedStopHandler.removeCallbacksAndMessages(null); - mDelayedStopHandler.sendEmptyMessageDelayed(0, STOP_DELAY); - - // stop and release the Media Player, if it's available - if (releaseMediaPlayer && mMediaPlayer != null) { - mMediaPlayer.reset(); - mMediaPlayer.release(); - mMediaPlayer = null; - } - - // we can also release the Wifi lock, if we're holding it - if (mWifiLock.isHeld()) { - mWifiLock.release(); - } - } - - /** - * Reconfigures MediaPlayer according to audio focus settings and - * starts/restarts it. This method starts/restarts the MediaPlayer - * respecting the current audio focus state. So if we have focus, it will - * play normally; if we don't have focus, it will either leave the - * MediaPlayer paused or set it to a low volume, depending on what is - * allowed by the current focus settings. This method assumes mPlayer != - * null, so if you are calling it, you have to do so from a context where - * you are sure this is the case. - */ - private void configMediaPlayerState() { - LogHelper.d(TAG, "configAndStartMediaPlayer. mAudioFocus=" + mAudioFocus); - if (mAudioFocus == AudioFocus.NoFocusNoDuck) { - // If we don't have audio focus and can't duck, we have to pause, - if (mState == PlaybackState.STATE_PLAYING) { - handlePauseRequest(); - } - } else { // we have audio focus: - if (mAudioFocus == AudioFocus.NoFocusCanDuck) { - mMediaPlayer.setVolume(VOLUME_DUCK, VOLUME_DUCK); // we'll be relatively quiet - } else { - mMediaPlayer.setVolume(VOLUME_NORMAL, VOLUME_NORMAL); // we can be loud again - } - // If we were playing when we lost focus, we need to resume playing. - if (mPlayOnFocusGain) { - if (!mMediaPlayer.isPlaying()) { - LogHelper.d(TAG, "configAndStartMediaPlayer startMediaPlayer."); - mMediaPlayer.start(); - } - mPlayOnFocusGain = false; - mState = PlaybackState.STATE_PLAYING; - } - } - updatePlaybackState(null); - } - - /** - * Makes sure the media player exists and has been reset. This will create - * the media player if needed, or reset the existing media player if one - * already exists. - */ - private void createMediaPlayerIfNeeded() { - LogHelper.d(TAG, "createMediaPlayerIfNeeded. needed? " + (mMediaPlayer==null)); - if (mMediaPlayer == null) { - mMediaPlayer = new MediaPlayer(); - - // Make sure the media player will acquire a wake-lock while - // playing. If we don't do that, the CPU might go to sleep while the - // song is playing, causing playback to stop. - mMediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK); - - // we want the media player to notify us when it's ready preparing, - // and when it's done playing: - mMediaPlayer.setOnPreparedListener(this); - mMediaPlayer.setOnCompletionListener(this); - mMediaPlayer.setOnErrorListener(this); - } else { - mMediaPlayer.reset(); - } - } - - /** - * Starts playing the current song in the playing queue. - */ - void playCurrentSong() { - MediaMetadata track = getCurrentPlayingMusic(); - if (track == null) { - LogHelper.e(TAG, "playSong: ignoring request to play next song, because cannot" + - " find it." + - " currentIndex=" + mCurrentIndexOnQueue + - " playQueue.size=" + (mPlayingQueue==null?"null": mPlayingQueue.size())); - return; - } - String source = track.getString(MusicProvider.CUSTOM_METADATA_TRACK_SOURCE); - LogHelper.d(TAG, "playSong: current (" + mCurrentIndexOnQueue + ") in playingQueue. " + - " musicId=" + track.getString(MediaMetadata.METADATA_KEY_MEDIA_ID) + - " source=" + source); - - mState = PlaybackState.STATE_STOPPED; - relaxResources(false); // release everything except MediaPlayer - - try { - createMediaPlayerIfNeeded(); - - mState = PlaybackState.STATE_BUFFERING; - - mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); - mMediaPlayer.setDataSource(source); - - // Starts preparing the media player in the background. When - // it's done, it will call our OnPreparedListener (that is, - // the onPrepared() method on this class, since we set the - // listener to 'this'). Until the media player is prepared, - // we *cannot* call start() on it! - mMediaPlayer.prepareAsync(); - - // If we are streaming from the internet, we want to hold a - // Wifi lock, which prevents the Wifi radio from going to - // sleep while the song is playing. - mWifiLock.acquire(); - - updatePlaybackState(null); - updateMetadata(); - - } catch (IOException ex) { - LogHelper.e(TAG, ex, "IOException playing song"); - updatePlaybackState(ex.getMessage()); - } - } - - - - private void updateMetadata() { - if (!QueueHelper.isIndexPlayable(mCurrentIndexOnQueue, mPlayingQueue)) { - LogHelper.e(TAG, "Can't retrieve current metadata."); - mState = PlaybackState.STATE_ERROR; - updatePlaybackState(getResources().getString(R.string.error_no_metadata)); - return; - } - MediaSession.QueueItem queueItem = mPlayingQueue.get(mCurrentIndexOnQueue); - String mediaId = queueItem.getDescription().getMediaId(); - MediaMetadata track = mMusicProvider.getMusic(mediaId); - String trackId = track.getString(MediaMetadata.METADATA_KEY_MEDIA_ID); - if (!mediaId.equals(trackId)) { - throw new IllegalStateException("track ID (" + trackId + ") " + - "should match mediaId (" + mediaId + ")"); - } - LogHelper.d(TAG, "Updating metadata for MusicID= " + mediaId); - mSession.setMetadata(track); - } - - - /** - * Update the current media player state, optionally showing an error message. - * - * @param error if not null, error message to present to the user. - * - */ - private void updatePlaybackState(String error) { - - LogHelper.d(TAG, "updatePlaybackState, setting session playback state to " + mState); - long position = PlaybackState.PLAYBACK_POSITION_UNKNOWN; - if (mMediaPlayer != null && mMediaPlayer.isPlaying()) { - position = mMediaPlayer.getCurrentPosition(); - } - PlaybackState.Builder stateBuilder = new PlaybackState.Builder() - .setActions(getAvailableActions()); - - setCustomAction(stateBuilder); - - // If there is an error message, send it to the playback state: - if (error != null) { - // Error states are really only supposed to be used for errors that cause playback to - // stop unexpectedly and persist until the user takes action to fix it. - stateBuilder.setErrorMessage(error); - mState = PlaybackState.STATE_ERROR; - } - stateBuilder.setState(mState, position, 1.0f, SystemClock.elapsedRealtime()); - - // Set the activeQueueItemId if the current index is valid. - if (QueueHelper.isIndexPlayable(mCurrentIndexOnQueue, mPlayingQueue)) { - MediaSession.QueueItem item = mPlayingQueue.get(mCurrentIndexOnQueue); - stateBuilder.setActiveQueueItemId(item.getQueueId()); - } - - mSession.setPlaybackState(stateBuilder.build()); - - if (mState == PlaybackState.STATE_PLAYING || mState == PlaybackState.STATE_PAUSED) { - mMediaNotification.startNotification(); - } - } - - private void setCustomAction(PlaybackState.Builder stateBuilder) { - MediaMetadata currentMusic = getCurrentPlayingMusic(); - if (currentMusic != null) { - // Set appropriate "Favorite" icon on Custom action: - String mediaId = currentMusic.getString(MediaMetadata.METADATA_KEY_MEDIA_ID); - int favoriteIcon = R.drawable.ic_star_off; - if (mMusicProvider.isFavorite(mediaId)) { - favoriteIcon = R.drawable.ic_star_on; - } - LogHelper.d(TAG, "updatePlaybackState, setting Favorite custom action of music ", - mediaId, " current favorite=", mMusicProvider.isFavorite(mediaId)); - stateBuilder.addCustomAction(CUSTOM_ACTION_THUMBS_UP, getString(R.string.favorite), - favoriteIcon); - } - } - - private long getAvailableActions() { - long actions = PlaybackState.ACTION_PLAY | PlaybackState.ACTION_PLAY_FROM_MEDIA_ID | - PlaybackState.ACTION_PLAY_FROM_SEARCH; - if (mPlayingQueue == null || mPlayingQueue.isEmpty()) { - return actions; - } - if (mState == PlaybackState.STATE_PLAYING) { - actions |= PlaybackState.ACTION_PAUSE; - } - if (mCurrentIndexOnQueue > 0) { - actions |= PlaybackState.ACTION_SKIP_TO_PREVIOUS; - } - if (mCurrentIndexOnQueue < mPlayingQueue.size() - 1) { - actions |= PlaybackState.ACTION_SKIP_TO_NEXT; - } - return actions; - } - - private MediaMetadata getCurrentPlayingMusic() { - if (QueueHelper.isIndexPlayable(mCurrentIndexOnQueue, mPlayingQueue)) { - MediaSession.QueueItem item = mPlayingQueue.get(mCurrentIndexOnQueue); - if (item != null) { - LogHelper.d(TAG, "getCurrentPlayingMusic for musicId=", - item.getDescription().getMediaId()); - return mMusicProvider.getMusic(item.getDescription().getMediaId()); - } - } - return null; - } - - /** - * Try to get the system audio focus. - */ - void tryToGetAudioFocus() { - LogHelper.d(TAG, "tryToGetAudioFocus"); - if (mAudioFocus != AudioFocus.Focused) { - int result = mAudioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC, - AudioManager.AUDIOFOCUS_GAIN); - if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { - mAudioFocus = AudioFocus.Focused; - } - } - } - - /** - * Give up the audio focus. - */ - void giveUpAudioFocus() { - LogHelper.d(TAG, "giveUpAudioFocus"); - if (mAudioFocus == AudioFocus.Focused) { - if (mAudioManager.abandonAudioFocus(this) == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { - mAudioFocus = AudioFocus.NoFocusNoDuck; - } - } - } -} |