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