1*90c8c64dSAndroid Build Coastguard Worker /* 2*90c8c64dSAndroid Build Coastguard Worker * Copyright (C) 2014 The Android Open Source Project 3*90c8c64dSAndroid Build Coastguard Worker * 4*90c8c64dSAndroid Build Coastguard Worker * Licensed under the Apache License, Version 2.0 (the "License"); 5*90c8c64dSAndroid Build Coastguard Worker * you may not use this file except in compliance with the License. 6*90c8c64dSAndroid Build Coastguard Worker * You may obtain a copy of the License at 7*90c8c64dSAndroid Build Coastguard Worker * 8*90c8c64dSAndroid Build Coastguard Worker * http://www.apache.org/licenses/LICENSE-2.0 9*90c8c64dSAndroid Build Coastguard Worker * 10*90c8c64dSAndroid Build Coastguard Worker * Unless required by applicable law or agreed to in writing, software 11*90c8c64dSAndroid Build Coastguard Worker * distributed under the License is distributed on an "AS IS" BASIS, 12*90c8c64dSAndroid Build Coastguard Worker * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13*90c8c64dSAndroid Build Coastguard Worker * See the License for the specific language governing permissions and 14*90c8c64dSAndroid Build Coastguard Worker * limitations under the License. 15*90c8c64dSAndroid Build Coastguard Worker */ 16*90c8c64dSAndroid Build Coastguard Worker package com.example.android.mediabrowserservice; 17*90c8c64dSAndroid Build Coastguard Worker 18*90c8c64dSAndroid Build Coastguard Worker import android.content.BroadcastReceiver; 19*90c8c64dSAndroid Build Coastguard Worker import android.content.Context; 20*90c8c64dSAndroid Build Coastguard Worker import android.content.Intent; 21*90c8c64dSAndroid Build Coastguard Worker import android.content.IntentFilter; 22*90c8c64dSAndroid Build Coastguard Worker import android.media.AudioManager; 23*90c8c64dSAndroid Build Coastguard Worker import android.media.MediaMetadata; 24*90c8c64dSAndroid Build Coastguard Worker import android.media.MediaPlayer; 25*90c8c64dSAndroid Build Coastguard Worker import android.media.session.PlaybackState; 26*90c8c64dSAndroid Build Coastguard Worker import android.net.wifi.WifiManager; 27*90c8c64dSAndroid Build Coastguard Worker import android.os.PowerManager; 28*90c8c64dSAndroid Build Coastguard Worker import android.text.TextUtils; 29*90c8c64dSAndroid Build Coastguard Worker 30*90c8c64dSAndroid Build Coastguard Worker import com.example.android.mediabrowserservice.model.MusicProvider; 31*90c8c64dSAndroid Build Coastguard Worker import com.example.android.mediabrowserservice.utils.LogHelper; 32*90c8c64dSAndroid Build Coastguard Worker import com.example.android.mediabrowserservice.utils.MediaIDHelper; 33*90c8c64dSAndroid Build Coastguard Worker 34*90c8c64dSAndroid Build Coastguard Worker import java.io.IOException; 35*90c8c64dSAndroid Build Coastguard Worker 36*90c8c64dSAndroid Build Coastguard Worker import static android.media.MediaPlayer.OnCompletionListener; 37*90c8c64dSAndroid Build Coastguard Worker import static android.media.MediaPlayer.OnErrorListener; 38*90c8c64dSAndroid Build Coastguard Worker import static android.media.MediaPlayer.OnPreparedListener; 39*90c8c64dSAndroid Build Coastguard Worker import static android.media.MediaPlayer.OnSeekCompleteListener; 40*90c8c64dSAndroid Build Coastguard Worker import static android.media.session.MediaSession.QueueItem; 41*90c8c64dSAndroid Build Coastguard Worker 42*90c8c64dSAndroid Build Coastguard Worker /** 43*90c8c64dSAndroid Build Coastguard Worker * A class that implements local media playback using {@link android.media.MediaPlayer} 44*90c8c64dSAndroid Build Coastguard Worker */ 45*90c8c64dSAndroid Build Coastguard Worker public class Playback implements AudioManager.OnAudioFocusChangeListener, 46*90c8c64dSAndroid Build Coastguard Worker OnCompletionListener, OnErrorListener, OnPreparedListener, OnSeekCompleteListener { 47*90c8c64dSAndroid Build Coastguard Worker 48*90c8c64dSAndroid Build Coastguard Worker private static final String TAG = LogHelper.makeLogTag(Playback.class); 49*90c8c64dSAndroid Build Coastguard Worker 50*90c8c64dSAndroid Build Coastguard Worker // The volume we set the media player to when we lose audio focus, but are 51*90c8c64dSAndroid Build Coastguard Worker // allowed to reduce the volume instead of stopping playback. 52*90c8c64dSAndroid Build Coastguard Worker public static final float VOLUME_DUCK = 0.2f; 53*90c8c64dSAndroid Build Coastguard Worker // The volume we set the media player when we have audio focus. 54*90c8c64dSAndroid Build Coastguard Worker public static final float VOLUME_NORMAL = 1.0f; 55*90c8c64dSAndroid Build Coastguard Worker 56*90c8c64dSAndroid Build Coastguard Worker // we don't have audio focus, and can't duck (play at a low volume) 57*90c8c64dSAndroid Build Coastguard Worker private static final int AUDIO_NO_FOCUS_NO_DUCK = 0; 58*90c8c64dSAndroid Build Coastguard Worker // we don't have focus, but can duck (play at a low volume) 59*90c8c64dSAndroid Build Coastguard Worker private static final int AUDIO_NO_FOCUS_CAN_DUCK = 1; 60*90c8c64dSAndroid Build Coastguard Worker // we have full audio focus 61*90c8c64dSAndroid Build Coastguard Worker private static final int AUDIO_FOCUSED = 2; 62*90c8c64dSAndroid Build Coastguard Worker 63*90c8c64dSAndroid Build Coastguard Worker private final MusicService mService; 64*90c8c64dSAndroid Build Coastguard Worker private final WifiManager.WifiLock mWifiLock; 65*90c8c64dSAndroid Build Coastguard Worker private int mState; 66*90c8c64dSAndroid Build Coastguard Worker private boolean mPlayOnFocusGain; 67*90c8c64dSAndroid Build Coastguard Worker private Callback mCallback; 68*90c8c64dSAndroid Build Coastguard Worker private MusicProvider mMusicProvider; 69*90c8c64dSAndroid Build Coastguard Worker private volatile boolean mAudioNoisyReceiverRegistered; 70*90c8c64dSAndroid Build Coastguard Worker private volatile int mCurrentPosition; 71*90c8c64dSAndroid Build Coastguard Worker private volatile String mCurrentMediaId; 72*90c8c64dSAndroid Build Coastguard Worker 73*90c8c64dSAndroid Build Coastguard Worker // Type of audio focus we have: 74*90c8c64dSAndroid Build Coastguard Worker private int mAudioFocus = AUDIO_NO_FOCUS_NO_DUCK; 75*90c8c64dSAndroid Build Coastguard Worker private AudioManager mAudioManager; 76*90c8c64dSAndroid Build Coastguard Worker private MediaPlayer mMediaPlayer; 77*90c8c64dSAndroid Build Coastguard Worker 78*90c8c64dSAndroid Build Coastguard Worker private IntentFilter mAudioNoisyIntentFilter = 79*90c8c64dSAndroid Build Coastguard Worker new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY); 80*90c8c64dSAndroid Build Coastguard Worker 81*90c8c64dSAndroid Build Coastguard Worker private BroadcastReceiver mAudioNoisyReceiver = new BroadcastReceiver() { 82*90c8c64dSAndroid Build Coastguard Worker @Override 83*90c8c64dSAndroid Build Coastguard Worker public void onReceive(Context context, Intent intent) { 84*90c8c64dSAndroid Build Coastguard Worker if (AudioManager.ACTION_AUDIO_BECOMING_NOISY.equals(intent.getAction())) { 85*90c8c64dSAndroid Build Coastguard Worker LogHelper.d(TAG, "Headphones disconnected."); 86*90c8c64dSAndroid Build Coastguard Worker if (isPlaying()) { 87*90c8c64dSAndroid Build Coastguard Worker Intent i = new Intent(context, MusicService.class); 88*90c8c64dSAndroid Build Coastguard Worker i.setAction(MusicService.ACTION_CMD); 89*90c8c64dSAndroid Build Coastguard Worker i.putExtra(MusicService.CMD_NAME, MusicService.CMD_PAUSE); 90*90c8c64dSAndroid Build Coastguard Worker mService.startService(i); 91*90c8c64dSAndroid Build Coastguard Worker } 92*90c8c64dSAndroid Build Coastguard Worker } 93*90c8c64dSAndroid Build Coastguard Worker } 94*90c8c64dSAndroid Build Coastguard Worker }; 95*90c8c64dSAndroid Build Coastguard Worker Playback(MusicService service, MusicProvider musicProvider)96*90c8c64dSAndroid Build Coastguard Worker public Playback(MusicService service, MusicProvider musicProvider) { 97*90c8c64dSAndroid Build Coastguard Worker this.mService = service; 98*90c8c64dSAndroid Build Coastguard Worker this.mMusicProvider = musicProvider; 99*90c8c64dSAndroid Build Coastguard Worker this.mAudioManager = (AudioManager) service.getSystemService(Context.AUDIO_SERVICE); 100*90c8c64dSAndroid Build Coastguard Worker // Create the Wifi lock (this does not acquire the lock, this just creates it) 101*90c8c64dSAndroid Build Coastguard Worker this.mWifiLock = ((WifiManager) service.getSystemService(Context.WIFI_SERVICE)) 102*90c8c64dSAndroid Build Coastguard Worker .createWifiLock(WifiManager.WIFI_MODE_FULL, "sample_lock"); 103*90c8c64dSAndroid Build Coastguard Worker } 104*90c8c64dSAndroid Build Coastguard Worker start()105*90c8c64dSAndroid Build Coastguard Worker public void start() { 106*90c8c64dSAndroid Build Coastguard Worker } 107*90c8c64dSAndroid Build Coastguard Worker stop(boolean notifyListeners)108*90c8c64dSAndroid Build Coastguard Worker public void stop(boolean notifyListeners) { 109*90c8c64dSAndroid Build Coastguard Worker mState = PlaybackState.STATE_STOPPED; 110*90c8c64dSAndroid Build Coastguard Worker if (notifyListeners && mCallback != null) { 111*90c8c64dSAndroid Build Coastguard Worker mCallback.onPlaybackStatusChanged(mState); 112*90c8c64dSAndroid Build Coastguard Worker } 113*90c8c64dSAndroid Build Coastguard Worker mCurrentPosition = getCurrentStreamPosition(); 114*90c8c64dSAndroid Build Coastguard Worker // Give up Audio focus 115*90c8c64dSAndroid Build Coastguard Worker giveUpAudioFocus(); 116*90c8c64dSAndroid Build Coastguard Worker unregisterAudioNoisyReceiver(); 117*90c8c64dSAndroid Build Coastguard Worker // Relax all resources 118*90c8c64dSAndroid Build Coastguard Worker relaxResources(true); 119*90c8c64dSAndroid Build Coastguard Worker if (mWifiLock.isHeld()) { 120*90c8c64dSAndroid Build Coastguard Worker mWifiLock.release(); 121*90c8c64dSAndroid Build Coastguard Worker } 122*90c8c64dSAndroid Build Coastguard Worker } 123*90c8c64dSAndroid Build Coastguard Worker setState(int state)124*90c8c64dSAndroid Build Coastguard Worker public void setState(int state) { 125*90c8c64dSAndroid Build Coastguard Worker this.mState = state; 126*90c8c64dSAndroid Build Coastguard Worker } 127*90c8c64dSAndroid Build Coastguard Worker getState()128*90c8c64dSAndroid Build Coastguard Worker public int getState() { 129*90c8c64dSAndroid Build Coastguard Worker return mState; 130*90c8c64dSAndroid Build Coastguard Worker } 131*90c8c64dSAndroid Build Coastguard Worker isConnected()132*90c8c64dSAndroid Build Coastguard Worker public boolean isConnected() { 133*90c8c64dSAndroid Build Coastguard Worker return true; 134*90c8c64dSAndroid Build Coastguard Worker } 135*90c8c64dSAndroid Build Coastguard Worker isPlaying()136*90c8c64dSAndroid Build Coastguard Worker public boolean isPlaying() { 137*90c8c64dSAndroid Build Coastguard Worker return mPlayOnFocusGain || (mMediaPlayer != null && mMediaPlayer.isPlaying()); 138*90c8c64dSAndroid Build Coastguard Worker } 139*90c8c64dSAndroid Build Coastguard Worker getCurrentStreamPosition()140*90c8c64dSAndroid Build Coastguard Worker public int getCurrentStreamPosition() { 141*90c8c64dSAndroid Build Coastguard Worker return mMediaPlayer != null ? 142*90c8c64dSAndroid Build Coastguard Worker mMediaPlayer.getCurrentPosition() : mCurrentPosition; 143*90c8c64dSAndroid Build Coastguard Worker } 144*90c8c64dSAndroid Build Coastguard Worker play(QueueItem item)145*90c8c64dSAndroid Build Coastguard Worker public void play(QueueItem item) { 146*90c8c64dSAndroid Build Coastguard Worker mPlayOnFocusGain = true; 147*90c8c64dSAndroid Build Coastguard Worker tryToGetAudioFocus(); 148*90c8c64dSAndroid Build Coastguard Worker registerAudioNoisyReceiver(); 149*90c8c64dSAndroid Build Coastguard Worker String mediaId = item.getDescription().getMediaId(); 150*90c8c64dSAndroid Build Coastguard Worker boolean mediaHasChanged = !TextUtils.equals(mediaId, mCurrentMediaId); 151*90c8c64dSAndroid Build Coastguard Worker if (mediaHasChanged) { 152*90c8c64dSAndroid Build Coastguard Worker mCurrentPosition = 0; 153*90c8c64dSAndroid Build Coastguard Worker mCurrentMediaId = mediaId; 154*90c8c64dSAndroid Build Coastguard Worker } 155*90c8c64dSAndroid Build Coastguard Worker 156*90c8c64dSAndroid Build Coastguard Worker if (mState == PlaybackState.STATE_PAUSED && !mediaHasChanged && mMediaPlayer != null) { 157*90c8c64dSAndroid Build Coastguard Worker configMediaPlayerState(); 158*90c8c64dSAndroid Build Coastguard Worker } else { 159*90c8c64dSAndroid Build Coastguard Worker mState = PlaybackState.STATE_STOPPED; 160*90c8c64dSAndroid Build Coastguard Worker relaxResources(false); // release everything except MediaPlayer 161*90c8c64dSAndroid Build Coastguard Worker MediaMetadata track = mMusicProvider.getMusic( 162*90c8c64dSAndroid Build Coastguard Worker MediaIDHelper.extractMusicIDFromMediaID(item.getDescription().getMediaId())); 163*90c8c64dSAndroid Build Coastguard Worker 164*90c8c64dSAndroid Build Coastguard Worker String source = track.getString(MusicProvider.CUSTOM_METADATA_TRACK_SOURCE); 165*90c8c64dSAndroid Build Coastguard Worker 166*90c8c64dSAndroid Build Coastguard Worker try { 167*90c8c64dSAndroid Build Coastguard Worker createMediaPlayerIfNeeded(); 168*90c8c64dSAndroid Build Coastguard Worker 169*90c8c64dSAndroid Build Coastguard Worker mState = PlaybackState.STATE_BUFFERING; 170*90c8c64dSAndroid Build Coastguard Worker 171*90c8c64dSAndroid Build Coastguard Worker mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); 172*90c8c64dSAndroid Build Coastguard Worker mMediaPlayer.setDataSource(source); 173*90c8c64dSAndroid Build Coastguard Worker 174*90c8c64dSAndroid Build Coastguard Worker // Starts preparing the media player in the background. When 175*90c8c64dSAndroid Build Coastguard Worker // it's done, it will call our OnPreparedListener (that is, 176*90c8c64dSAndroid Build Coastguard Worker // the onPrepared() method on this class, since we set the 177*90c8c64dSAndroid Build Coastguard Worker // listener to 'this'). Until the media player is prepared, 178*90c8c64dSAndroid Build Coastguard Worker // we *cannot* call start() on it! 179*90c8c64dSAndroid Build Coastguard Worker mMediaPlayer.prepareAsync(); 180*90c8c64dSAndroid Build Coastguard Worker 181*90c8c64dSAndroid Build Coastguard Worker // If we are streaming from the internet, we want to hold a 182*90c8c64dSAndroid Build Coastguard Worker // Wifi lock, which prevents the Wifi radio from going to 183*90c8c64dSAndroid Build Coastguard Worker // sleep while the song is playing. 184*90c8c64dSAndroid Build Coastguard Worker mWifiLock.acquire(); 185*90c8c64dSAndroid Build Coastguard Worker 186*90c8c64dSAndroid Build Coastguard Worker if (mCallback != null) { 187*90c8c64dSAndroid Build Coastguard Worker mCallback.onPlaybackStatusChanged(mState); 188*90c8c64dSAndroid Build Coastguard Worker } 189*90c8c64dSAndroid Build Coastguard Worker 190*90c8c64dSAndroid Build Coastguard Worker } catch (IOException ex) { 191*90c8c64dSAndroid Build Coastguard Worker LogHelper.e(TAG, ex, "Exception playing song"); 192*90c8c64dSAndroid Build Coastguard Worker if (mCallback != null) { 193*90c8c64dSAndroid Build Coastguard Worker mCallback.onError(ex.getMessage()); 194*90c8c64dSAndroid Build Coastguard Worker } 195*90c8c64dSAndroid Build Coastguard Worker } 196*90c8c64dSAndroid Build Coastguard Worker } 197*90c8c64dSAndroid Build Coastguard Worker } 198*90c8c64dSAndroid Build Coastguard Worker pause()199*90c8c64dSAndroid Build Coastguard Worker public void pause() { 200*90c8c64dSAndroid Build Coastguard Worker if (mState == PlaybackState.STATE_PLAYING) { 201*90c8c64dSAndroid Build Coastguard Worker // Pause media player and cancel the 'foreground service' state. 202*90c8c64dSAndroid Build Coastguard Worker if (mMediaPlayer != null && mMediaPlayer.isPlaying()) { 203*90c8c64dSAndroid Build Coastguard Worker mMediaPlayer.pause(); 204*90c8c64dSAndroid Build Coastguard Worker mCurrentPosition = mMediaPlayer.getCurrentPosition(); 205*90c8c64dSAndroid Build Coastguard Worker } 206*90c8c64dSAndroid Build Coastguard Worker // while paused, retain the MediaPlayer but give up audio focus 207*90c8c64dSAndroid Build Coastguard Worker relaxResources(false); 208*90c8c64dSAndroid Build Coastguard Worker giveUpAudioFocus(); 209*90c8c64dSAndroid Build Coastguard Worker } 210*90c8c64dSAndroid Build Coastguard Worker mState = PlaybackState.STATE_PAUSED; 211*90c8c64dSAndroid Build Coastguard Worker if (mCallback != null) { 212*90c8c64dSAndroid Build Coastguard Worker mCallback.onPlaybackStatusChanged(mState); 213*90c8c64dSAndroid Build Coastguard Worker } 214*90c8c64dSAndroid Build Coastguard Worker unregisterAudioNoisyReceiver(); 215*90c8c64dSAndroid Build Coastguard Worker } 216*90c8c64dSAndroid Build Coastguard Worker seekTo(int position)217*90c8c64dSAndroid Build Coastguard Worker public void seekTo(int position) { 218*90c8c64dSAndroid Build Coastguard Worker LogHelper.d(TAG, "seekTo called with ", position); 219*90c8c64dSAndroid Build Coastguard Worker 220*90c8c64dSAndroid Build Coastguard Worker if (mMediaPlayer == null) { 221*90c8c64dSAndroid Build Coastguard Worker // If we do not have a current media player, simply update the current position 222*90c8c64dSAndroid Build Coastguard Worker mCurrentPosition = position; 223*90c8c64dSAndroid Build Coastguard Worker } else { 224*90c8c64dSAndroid Build Coastguard Worker if (mMediaPlayer.isPlaying()) { 225*90c8c64dSAndroid Build Coastguard Worker mState = PlaybackState.STATE_BUFFERING; 226*90c8c64dSAndroid Build Coastguard Worker } 227*90c8c64dSAndroid Build Coastguard Worker mMediaPlayer.seekTo(position); 228*90c8c64dSAndroid Build Coastguard Worker if (mCallback != null) { 229*90c8c64dSAndroid Build Coastguard Worker mCallback.onPlaybackStatusChanged(mState); 230*90c8c64dSAndroid Build Coastguard Worker } 231*90c8c64dSAndroid Build Coastguard Worker } 232*90c8c64dSAndroid Build Coastguard Worker } 233*90c8c64dSAndroid Build Coastguard Worker setCallback(Callback callback)234*90c8c64dSAndroid Build Coastguard Worker public void setCallback(Callback callback) { 235*90c8c64dSAndroid Build Coastguard Worker this.mCallback = callback; 236*90c8c64dSAndroid Build Coastguard Worker } 237*90c8c64dSAndroid Build Coastguard Worker 238*90c8c64dSAndroid Build Coastguard Worker /** 239*90c8c64dSAndroid Build Coastguard Worker * Try to get the system audio focus. 240*90c8c64dSAndroid Build Coastguard Worker */ tryToGetAudioFocus()241*90c8c64dSAndroid Build Coastguard Worker private void tryToGetAudioFocus() { 242*90c8c64dSAndroid Build Coastguard Worker LogHelper.d(TAG, "tryToGetAudioFocus"); 243*90c8c64dSAndroid Build Coastguard Worker if (mAudioFocus != AUDIO_FOCUSED) { 244*90c8c64dSAndroid Build Coastguard Worker int result = mAudioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC, 245*90c8c64dSAndroid Build Coastguard Worker AudioManager.AUDIOFOCUS_GAIN); 246*90c8c64dSAndroid Build Coastguard Worker if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { 247*90c8c64dSAndroid Build Coastguard Worker mAudioFocus = AUDIO_FOCUSED; 248*90c8c64dSAndroid Build Coastguard Worker } 249*90c8c64dSAndroid Build Coastguard Worker } 250*90c8c64dSAndroid Build Coastguard Worker } 251*90c8c64dSAndroid Build Coastguard Worker 252*90c8c64dSAndroid Build Coastguard Worker /** 253*90c8c64dSAndroid Build Coastguard Worker * Give up the audio focus. 254*90c8c64dSAndroid Build Coastguard Worker */ giveUpAudioFocus()255*90c8c64dSAndroid Build Coastguard Worker private void giveUpAudioFocus() { 256*90c8c64dSAndroid Build Coastguard Worker LogHelper.d(TAG, "giveUpAudioFocus"); 257*90c8c64dSAndroid Build Coastguard Worker if (mAudioFocus == AUDIO_FOCUSED) { 258*90c8c64dSAndroid Build Coastguard Worker if (mAudioManager.abandonAudioFocus(this) == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { 259*90c8c64dSAndroid Build Coastguard Worker mAudioFocus = AUDIO_NO_FOCUS_NO_DUCK; 260*90c8c64dSAndroid Build Coastguard Worker } 261*90c8c64dSAndroid Build Coastguard Worker } 262*90c8c64dSAndroid Build Coastguard Worker } 263*90c8c64dSAndroid Build Coastguard Worker 264*90c8c64dSAndroid Build Coastguard Worker /** 265*90c8c64dSAndroid Build Coastguard Worker * Reconfigures MediaPlayer according to audio focus settings and 266*90c8c64dSAndroid Build Coastguard Worker * starts/restarts it. This method starts/restarts the MediaPlayer 267*90c8c64dSAndroid Build Coastguard Worker * respecting the current audio focus state. So if we have focus, it will 268*90c8c64dSAndroid Build Coastguard Worker * play normally; if we don't have focus, it will either leave the 269*90c8c64dSAndroid Build Coastguard Worker * MediaPlayer paused or set it to a low volume, depending on what is 270*90c8c64dSAndroid Build Coastguard Worker * allowed by the current focus settings. This method assumes mPlayer != 271*90c8c64dSAndroid Build Coastguard Worker * null, so if you are calling it, you have to do so from a context where 272*90c8c64dSAndroid Build Coastguard Worker * you are sure this is the case. 273*90c8c64dSAndroid Build Coastguard Worker */ configMediaPlayerState()274*90c8c64dSAndroid Build Coastguard Worker private void configMediaPlayerState() { 275*90c8c64dSAndroid Build Coastguard Worker LogHelper.d(TAG, "configMediaPlayerState. mAudioFocus=", mAudioFocus); 276*90c8c64dSAndroid Build Coastguard Worker if (mAudioFocus == AUDIO_NO_FOCUS_NO_DUCK) { 277*90c8c64dSAndroid Build Coastguard Worker // If we don't have audio focus and can't duck, we have to pause, 278*90c8c64dSAndroid Build Coastguard Worker if (mState == PlaybackState.STATE_PLAYING) { 279*90c8c64dSAndroid Build Coastguard Worker pause(); 280*90c8c64dSAndroid Build Coastguard Worker } 281*90c8c64dSAndroid Build Coastguard Worker } else { // we have audio focus: 282*90c8c64dSAndroid Build Coastguard Worker if (mAudioFocus == AUDIO_NO_FOCUS_CAN_DUCK) { 283*90c8c64dSAndroid Build Coastguard Worker mMediaPlayer.setVolume(VOLUME_DUCK, VOLUME_DUCK); // we'll be relatively quiet 284*90c8c64dSAndroid Build Coastguard Worker } else { 285*90c8c64dSAndroid Build Coastguard Worker if (mMediaPlayer != null) { 286*90c8c64dSAndroid Build Coastguard Worker mMediaPlayer.setVolume(VOLUME_NORMAL, VOLUME_NORMAL); // we can be loud again 287*90c8c64dSAndroid Build Coastguard Worker } // else do something for remote client. 288*90c8c64dSAndroid Build Coastguard Worker } 289*90c8c64dSAndroid Build Coastguard Worker // If we were playing when we lost focus, we need to resume playing. 290*90c8c64dSAndroid Build Coastguard Worker if (mPlayOnFocusGain) { 291*90c8c64dSAndroid Build Coastguard Worker if (mMediaPlayer != null && !mMediaPlayer.isPlaying()) { 292*90c8c64dSAndroid Build Coastguard Worker LogHelper.d(TAG,"configMediaPlayerState startMediaPlayer. seeking to ", 293*90c8c64dSAndroid Build Coastguard Worker mCurrentPosition); 294*90c8c64dSAndroid Build Coastguard Worker if (mCurrentPosition == mMediaPlayer.getCurrentPosition()) { 295*90c8c64dSAndroid Build Coastguard Worker mMediaPlayer.start(); 296*90c8c64dSAndroid Build Coastguard Worker mState = PlaybackState.STATE_PLAYING; 297*90c8c64dSAndroid Build Coastguard Worker } else { 298*90c8c64dSAndroid Build Coastguard Worker mMediaPlayer.seekTo(mCurrentPosition); 299*90c8c64dSAndroid Build Coastguard Worker mState = PlaybackState.STATE_BUFFERING; 300*90c8c64dSAndroid Build Coastguard Worker } 301*90c8c64dSAndroid Build Coastguard Worker } 302*90c8c64dSAndroid Build Coastguard Worker mPlayOnFocusGain = false; 303*90c8c64dSAndroid Build Coastguard Worker } 304*90c8c64dSAndroid Build Coastguard Worker } 305*90c8c64dSAndroid Build Coastguard Worker if (mCallback != null) { 306*90c8c64dSAndroid Build Coastguard Worker mCallback.onPlaybackStatusChanged(mState); 307*90c8c64dSAndroid Build Coastguard Worker } 308*90c8c64dSAndroid Build Coastguard Worker } 309*90c8c64dSAndroid Build Coastguard Worker 310*90c8c64dSAndroid Build Coastguard Worker /** 311*90c8c64dSAndroid Build Coastguard Worker * Called by AudioManager on audio focus changes. 312*90c8c64dSAndroid Build Coastguard Worker * Implementation of {@link android.media.AudioManager.OnAudioFocusChangeListener} 313*90c8c64dSAndroid Build Coastguard Worker */ 314*90c8c64dSAndroid Build Coastguard Worker @Override onAudioFocusChange(int focusChange)315*90c8c64dSAndroid Build Coastguard Worker public void onAudioFocusChange(int focusChange) { 316*90c8c64dSAndroid Build Coastguard Worker LogHelper.d(TAG, "onAudioFocusChange. focusChange=", focusChange); 317*90c8c64dSAndroid Build Coastguard Worker if (focusChange == AudioManager.AUDIOFOCUS_GAIN) { 318*90c8c64dSAndroid Build Coastguard Worker // We have gained focus: 319*90c8c64dSAndroid Build Coastguard Worker mAudioFocus = AUDIO_FOCUSED; 320*90c8c64dSAndroid Build Coastguard Worker 321*90c8c64dSAndroid Build Coastguard Worker } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS || 322*90c8c64dSAndroid Build Coastguard Worker focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT || 323*90c8c64dSAndroid Build Coastguard Worker focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) { 324*90c8c64dSAndroid Build Coastguard Worker // We have lost focus. If we can duck (low playback volume), we can keep playing. 325*90c8c64dSAndroid Build Coastguard Worker // Otherwise, we need to pause the playback. 326*90c8c64dSAndroid Build Coastguard Worker boolean canDuck = focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK; 327*90c8c64dSAndroid Build Coastguard Worker mAudioFocus = canDuck ? AUDIO_NO_FOCUS_CAN_DUCK : AUDIO_NO_FOCUS_NO_DUCK; 328*90c8c64dSAndroid Build Coastguard Worker 329*90c8c64dSAndroid Build Coastguard Worker // If we are playing, we need to reset media player by calling configMediaPlayerState 330*90c8c64dSAndroid Build Coastguard Worker // with mAudioFocus properly set. 331*90c8c64dSAndroid Build Coastguard Worker if (mState == PlaybackState.STATE_PLAYING && !canDuck) { 332*90c8c64dSAndroid Build Coastguard Worker // If we don't have audio focus and can't duck, we save the information that 333*90c8c64dSAndroid Build Coastguard Worker // we were playing, so that we can resume playback once we get the focus back. 334*90c8c64dSAndroid Build Coastguard Worker mPlayOnFocusGain = true; 335*90c8c64dSAndroid Build Coastguard Worker } 336*90c8c64dSAndroid Build Coastguard Worker } else { 337*90c8c64dSAndroid Build Coastguard Worker LogHelper.e(TAG, "onAudioFocusChange: Ignoring unsupported focusChange: ", focusChange); 338*90c8c64dSAndroid Build Coastguard Worker } 339*90c8c64dSAndroid Build Coastguard Worker configMediaPlayerState(); 340*90c8c64dSAndroid Build Coastguard Worker } 341*90c8c64dSAndroid Build Coastguard Worker 342*90c8c64dSAndroid Build Coastguard Worker /** 343*90c8c64dSAndroid Build Coastguard Worker * Called when MediaPlayer has completed a seek 344*90c8c64dSAndroid Build Coastguard Worker * 345*90c8c64dSAndroid Build Coastguard Worker * @see android.media.MediaPlayer.OnSeekCompleteListener 346*90c8c64dSAndroid Build Coastguard Worker */ 347*90c8c64dSAndroid Build Coastguard Worker @Override onSeekComplete(MediaPlayer mp)348*90c8c64dSAndroid Build Coastguard Worker public void onSeekComplete(MediaPlayer mp) { 349*90c8c64dSAndroid Build Coastguard Worker LogHelper.d(TAG, "onSeekComplete from MediaPlayer:", mp.getCurrentPosition()); 350*90c8c64dSAndroid Build Coastguard Worker mCurrentPosition = mp.getCurrentPosition(); 351*90c8c64dSAndroid Build Coastguard Worker if (mState == PlaybackState.STATE_BUFFERING) { 352*90c8c64dSAndroid Build Coastguard Worker mMediaPlayer.start(); 353*90c8c64dSAndroid Build Coastguard Worker mState = PlaybackState.STATE_PLAYING; 354*90c8c64dSAndroid Build Coastguard Worker } 355*90c8c64dSAndroid Build Coastguard Worker if (mCallback != null) { 356*90c8c64dSAndroid Build Coastguard Worker mCallback.onPlaybackStatusChanged(mState); 357*90c8c64dSAndroid Build Coastguard Worker } 358*90c8c64dSAndroid Build Coastguard Worker } 359*90c8c64dSAndroid Build Coastguard Worker 360*90c8c64dSAndroid Build Coastguard Worker /** 361*90c8c64dSAndroid Build Coastguard Worker * Called when media player is done playing current song. 362*90c8c64dSAndroid Build Coastguard Worker * 363*90c8c64dSAndroid Build Coastguard Worker * @see android.media.MediaPlayer.OnCompletionListener 364*90c8c64dSAndroid Build Coastguard Worker */ 365*90c8c64dSAndroid Build Coastguard Worker @Override onCompletion(MediaPlayer player)366*90c8c64dSAndroid Build Coastguard Worker public void onCompletion(MediaPlayer player) { 367*90c8c64dSAndroid Build Coastguard Worker LogHelper.d(TAG, "onCompletion from MediaPlayer"); 368*90c8c64dSAndroid Build Coastguard Worker // The media player finished playing the current song, so we go ahead 369*90c8c64dSAndroid Build Coastguard Worker // and start the next. 370*90c8c64dSAndroid Build Coastguard Worker if (mCallback != null) { 371*90c8c64dSAndroid Build Coastguard Worker mCallback.onCompletion(); 372*90c8c64dSAndroid Build Coastguard Worker } 373*90c8c64dSAndroid Build Coastguard Worker } 374*90c8c64dSAndroid Build Coastguard Worker 375*90c8c64dSAndroid Build Coastguard Worker /** 376*90c8c64dSAndroid Build Coastguard Worker * Called when media player is done preparing. 377*90c8c64dSAndroid Build Coastguard Worker * 378*90c8c64dSAndroid Build Coastguard Worker * @see android.media.MediaPlayer.OnPreparedListener 379*90c8c64dSAndroid Build Coastguard Worker */ 380*90c8c64dSAndroid Build Coastguard Worker @Override onPrepared(MediaPlayer player)381*90c8c64dSAndroid Build Coastguard Worker public void onPrepared(MediaPlayer player) { 382*90c8c64dSAndroid Build Coastguard Worker LogHelper.d(TAG, "onPrepared from MediaPlayer"); 383*90c8c64dSAndroid Build Coastguard Worker // The media player is done preparing. That means we can start playing if we 384*90c8c64dSAndroid Build Coastguard Worker // have audio focus. 385*90c8c64dSAndroid Build Coastguard Worker configMediaPlayerState(); 386*90c8c64dSAndroid Build Coastguard Worker } 387*90c8c64dSAndroid Build Coastguard Worker 388*90c8c64dSAndroid Build Coastguard Worker /** 389*90c8c64dSAndroid Build Coastguard Worker * Called when there's an error playing media. When this happens, the media 390*90c8c64dSAndroid Build Coastguard Worker * player goes to the Error state. We warn the user about the error and 391*90c8c64dSAndroid Build Coastguard Worker * reset the media player. 392*90c8c64dSAndroid Build Coastguard Worker * 393*90c8c64dSAndroid Build Coastguard Worker * @see android.media.MediaPlayer.OnErrorListener 394*90c8c64dSAndroid Build Coastguard Worker */ 395*90c8c64dSAndroid Build Coastguard Worker @Override onError(MediaPlayer mp, int what, int extra)396*90c8c64dSAndroid Build Coastguard Worker public boolean onError(MediaPlayer mp, int what, int extra) { 397*90c8c64dSAndroid Build Coastguard Worker LogHelper.e(TAG, "Media player error: what=" + what + ", extra=" + extra); 398*90c8c64dSAndroid Build Coastguard Worker if (mCallback != null) { 399*90c8c64dSAndroid Build Coastguard Worker mCallback.onError("MediaPlayer error " + what + " (" + extra + ")"); 400*90c8c64dSAndroid Build Coastguard Worker } 401*90c8c64dSAndroid Build Coastguard Worker return true; // true indicates we handled the error 402*90c8c64dSAndroid Build Coastguard Worker } 403*90c8c64dSAndroid Build Coastguard Worker 404*90c8c64dSAndroid Build Coastguard Worker /** 405*90c8c64dSAndroid Build Coastguard Worker * Makes sure the media player exists and has been reset. This will create 406*90c8c64dSAndroid Build Coastguard Worker * the media player if needed, or reset the existing media player if one 407*90c8c64dSAndroid Build Coastguard Worker * already exists. 408*90c8c64dSAndroid Build Coastguard Worker */ createMediaPlayerIfNeeded()409*90c8c64dSAndroid Build Coastguard Worker private void createMediaPlayerIfNeeded() { 410*90c8c64dSAndroid Build Coastguard Worker LogHelper.d(TAG, "createMediaPlayerIfNeeded. needed? ", (mMediaPlayer==null)); 411*90c8c64dSAndroid Build Coastguard Worker if (mMediaPlayer == null) { 412*90c8c64dSAndroid Build Coastguard Worker mMediaPlayer = new MediaPlayer(); 413*90c8c64dSAndroid Build Coastguard Worker 414*90c8c64dSAndroid Build Coastguard Worker // Make sure the media player will acquire a wake-lock while 415*90c8c64dSAndroid Build Coastguard Worker // playing. If we don't do that, the CPU might go to sleep while the 416*90c8c64dSAndroid Build Coastguard Worker // song is playing, causing playback to stop. 417*90c8c64dSAndroid Build Coastguard Worker mMediaPlayer.setWakeMode(mService.getApplicationContext(), 418*90c8c64dSAndroid Build Coastguard Worker PowerManager.PARTIAL_WAKE_LOCK); 419*90c8c64dSAndroid Build Coastguard Worker 420*90c8c64dSAndroid Build Coastguard Worker // we want the media player to notify us when it's ready preparing, 421*90c8c64dSAndroid Build Coastguard Worker // and when it's done playing: 422*90c8c64dSAndroid Build Coastguard Worker mMediaPlayer.setOnPreparedListener(this); 423*90c8c64dSAndroid Build Coastguard Worker mMediaPlayer.setOnCompletionListener(this); 424*90c8c64dSAndroid Build Coastguard Worker mMediaPlayer.setOnErrorListener(this); 425*90c8c64dSAndroid Build Coastguard Worker mMediaPlayer.setOnSeekCompleteListener(this); 426*90c8c64dSAndroid Build Coastguard Worker } else { 427*90c8c64dSAndroid Build Coastguard Worker mMediaPlayer.reset(); 428*90c8c64dSAndroid Build Coastguard Worker } 429*90c8c64dSAndroid Build Coastguard Worker } 430*90c8c64dSAndroid Build Coastguard Worker 431*90c8c64dSAndroid Build Coastguard Worker /** 432*90c8c64dSAndroid Build Coastguard Worker * Releases resources used by the service for playback. This includes the 433*90c8c64dSAndroid Build Coastguard Worker * "foreground service" status, the wake locks and possibly the MediaPlayer. 434*90c8c64dSAndroid Build Coastguard Worker * 435*90c8c64dSAndroid Build Coastguard Worker * @param releaseMediaPlayer Indicates whether the Media Player should also 436*90c8c64dSAndroid Build Coastguard Worker * be released or not 437*90c8c64dSAndroid Build Coastguard Worker */ relaxResources(boolean releaseMediaPlayer)438*90c8c64dSAndroid Build Coastguard Worker private void relaxResources(boolean releaseMediaPlayer) { 439*90c8c64dSAndroid Build Coastguard Worker LogHelper.d(TAG, "relaxResources. releaseMediaPlayer=", releaseMediaPlayer); 440*90c8c64dSAndroid Build Coastguard Worker 441*90c8c64dSAndroid Build Coastguard Worker mService.stopForeground(true); 442*90c8c64dSAndroid Build Coastguard Worker 443*90c8c64dSAndroid Build Coastguard Worker // stop and release the Media Player, if it's available 444*90c8c64dSAndroid Build Coastguard Worker if (releaseMediaPlayer && mMediaPlayer != null) { 445*90c8c64dSAndroid Build Coastguard Worker mMediaPlayer.reset(); 446*90c8c64dSAndroid Build Coastguard Worker mMediaPlayer.release(); 447*90c8c64dSAndroid Build Coastguard Worker mMediaPlayer = null; 448*90c8c64dSAndroid Build Coastguard Worker } 449*90c8c64dSAndroid Build Coastguard Worker 450*90c8c64dSAndroid Build Coastguard Worker // we can also release the Wifi lock, if we're holding it 451*90c8c64dSAndroid Build Coastguard Worker if (mWifiLock.isHeld()) { 452*90c8c64dSAndroid Build Coastguard Worker mWifiLock.release(); 453*90c8c64dSAndroid Build Coastguard Worker } 454*90c8c64dSAndroid Build Coastguard Worker } 455*90c8c64dSAndroid Build Coastguard Worker registerAudioNoisyReceiver()456*90c8c64dSAndroid Build Coastguard Worker private void registerAudioNoisyReceiver() { 457*90c8c64dSAndroid Build Coastguard Worker if (!mAudioNoisyReceiverRegistered) { 458*90c8c64dSAndroid Build Coastguard Worker mService.registerReceiver(mAudioNoisyReceiver, mAudioNoisyIntentFilter); 459*90c8c64dSAndroid Build Coastguard Worker mAudioNoisyReceiverRegistered = true; 460*90c8c64dSAndroid Build Coastguard Worker } 461*90c8c64dSAndroid Build Coastguard Worker } 462*90c8c64dSAndroid Build Coastguard Worker unregisterAudioNoisyReceiver()463*90c8c64dSAndroid Build Coastguard Worker private void unregisterAudioNoisyReceiver() { 464*90c8c64dSAndroid Build Coastguard Worker if (mAudioNoisyReceiverRegistered) { 465*90c8c64dSAndroid Build Coastguard Worker mService.unregisterReceiver(mAudioNoisyReceiver); 466*90c8c64dSAndroid Build Coastguard Worker mAudioNoisyReceiverRegistered = false; 467*90c8c64dSAndroid Build Coastguard Worker } 468*90c8c64dSAndroid Build Coastguard Worker } 469*90c8c64dSAndroid Build Coastguard Worker 470*90c8c64dSAndroid Build Coastguard Worker interface Callback { 471*90c8c64dSAndroid Build Coastguard Worker /** 472*90c8c64dSAndroid Build Coastguard Worker * On current music completed. 473*90c8c64dSAndroid Build Coastguard Worker */ onCompletion()474*90c8c64dSAndroid Build Coastguard Worker void onCompletion(); 475*90c8c64dSAndroid Build Coastguard Worker /** 476*90c8c64dSAndroid Build Coastguard Worker * on Playback status changed 477*90c8c64dSAndroid Build Coastguard Worker * Implementations can use this callback to update 478*90c8c64dSAndroid Build Coastguard Worker * playback state on the media sessions. 479*90c8c64dSAndroid Build Coastguard Worker */ onPlaybackStatusChanged(int state)480*90c8c64dSAndroid Build Coastguard Worker void onPlaybackStatusChanged(int state); 481*90c8c64dSAndroid Build Coastguard Worker 482*90c8c64dSAndroid Build Coastguard Worker /** 483*90c8c64dSAndroid Build Coastguard Worker * @param error to be added to the PlaybackState 484*90c8c64dSAndroid Build Coastguard Worker */ onError(String error)485*90c8c64dSAndroid Build Coastguard Worker void onError(String error); 486*90c8c64dSAndroid Build Coastguard Worker 487*90c8c64dSAndroid Build Coastguard Worker } 488*90c8c64dSAndroid Build Coastguard Worker 489*90c8c64dSAndroid Build Coastguard Worker } 490