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