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