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.google.android.car.kitchensink.audio; 18 19 import static android.media.AudioManager.AUDIOFOCUS_GAIN; 20 import static android.media.AudioManager.AUDIOFOCUS_GAIN_TRANSIENT; 21 import static android.media.AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE; 22 import static android.media.AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK; 23 import static android.media.AudioManager.AUDIOFOCUS_LOSS; 24 import static android.media.AudioManager.AUDIOFOCUS_LOSS_TRANSIENT; 25 import static android.media.AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK; 26 import static android.media.AudioManager.AUDIOFOCUS_REQUEST_DELAYED; 27 import static android.media.AudioManager.AUDIOFOCUS_REQUEST_FAILED; 28 import static android.media.AudioManager.AUDIOFOCUS_REQUEST_GRANTED; 29 30 import static com.google.android.car.kitchensink.audio.AudioTestFragment.getAudioLogTag; 31 32 import android.annotation.IntDef; 33 import android.content.Context; 34 import android.content.res.AssetFileDescriptor; 35 import android.media.AudioAttributes; 36 import android.media.AudioDeviceInfo; 37 import android.media.AudioFocusRequest; 38 import android.media.AudioManager; 39 import android.media.AudioRouting.OnRoutingChangedListener; 40 import android.media.MediaPlayer; 41 import android.os.Handler; 42 import android.util.Log; 43 44 import androidx.annotation.Nullable; 45 46 import java.io.IOException; 47 import java.lang.annotation.Retention; 48 import java.lang.annotation.RetentionPolicy; 49 import java.util.concurrent.atomic.AtomicBoolean; 50 51 /** 52 * Class for playing music. 53 * 54 * This is not thread safe and all public calls should be made from main thread. 55 * 56 * MP3 used is from http://freemusicarchive.org/music/John_Harrison_with_the_Wichita_State_University_Chamber_Players/The_Four_Seasons_Vivaldi/05_-_Vivaldi_Summer_mvt_2_Adagio_-_John_Harrison_violin 57 * from John Harrison with the Wichita State University Chamber Players 58 * Copyright under Create Commons license. 59 */ 60 public class AudioPlayer { 61 62 private static final String TAG = getAudioLogTag(AudioPlayer.class); 63 64 @IntDef(flag = false, prefix = "PLAYER_STATE", value = { 65 PLAYER_STATE_COMPLETED, 66 PLAYER_STATE_STARTED, 67 PLAYER_STATE_STOPPED, 68 PLAYER_STATE_DELAYED, 69 PLAYER_STATE_PAUSED, 70 }) 71 @Retention(RetentionPolicy.SOURCE) 72 public @interface AudioPlayerState {} 73 public static final int PLAYER_STATE_COMPLETED = 0; 74 public static final int PLAYER_STATE_STARTED = 1; 75 public static final int PLAYER_STATE_STOPPED = 2; 76 public static final int PLAYER_STATE_DELAYED = 3; 77 public static final int PLAYER_STATE_PAUSED = 4; 78 79 public interface PlayStateListener { onStateChange(@udioPlayerState int state)80 void onStateChange(@AudioPlayerState int state); 81 } 82 83 private final AudioManager.OnAudioFocusChangeListener mFocusListener = 84 new AudioManager.OnAudioFocusChangeListener() { 85 86 @Override 87 public void onAudioFocusChange(int focusChange) { 88 if (mPlayer == null) { 89 Log.e(TAG, "mPlayer is null"); 90 return; 91 } 92 switch (focusChange) { 93 case AUDIOFOCUS_GAIN: 94 case AUDIOFOCUS_GAIN_TRANSIENT: 95 case AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE: 96 case AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK: 97 Log.i(TAG, "Audio focus change AUDIOFOCUS_GAIN for usage " 98 + mAttrib.getUsage()); 99 mPlayer.setVolume(1.0f, 1.0f); 100 if (mRepeat && isPlaying()) { 101 // Resume 102 Log.i(TAG, "resuming player"); 103 mPlayer.start(); 104 sendPlayerStateChanged(PLAYER_STATE_STARTED); 105 } 106 return; 107 case AUDIOFOCUS_LOSS_TRANSIENT: 108 Log.i(TAG, "Audio focus change AUDIOFOCUS_LOSS_TRANSIENT for usage " 109 + mAttrib.getUsage()); 110 if (mRepeat && isPlaying()) { 111 Log.i(TAG, "pausing repeating player"); 112 mPlayer.pause(); 113 sendPlayerStateChanged(PLAYER_STATE_PAUSED); 114 } else { 115 Log.i(TAG, "stopping one shot player"); 116 stop(); 117 } 118 return; 119 case AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: 120 // While we used to setVolume on the player to 20%, we don't do this anymore 121 // because we expect the car's audio hal do handle ducking as it sees fit. 122 Log.i(TAG, "Audio focus change AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK " 123 + "-> do nothing"); 124 return; 125 case AUDIOFOCUS_LOSS: 126 default: 127 if (isPlaying()) { 128 Log.i(TAG, "stopping player"); 129 stop(); 130 } 131 } 132 } 133 }; 134 135 private AudioManager mAudioManager; 136 private MediaPlayer mPlayer; 137 138 private final Context mContext; 139 private final int mResourceId; 140 private final AudioAttributes mAttrib; 141 private final AudioDeviceInfo mPreferredDeviceInfo; 142 143 private final AtomicBoolean mPlaying = new AtomicBoolean(false); 144 @Nullable 145 private final OnRoutingChangedListener mAudioRoutingListener; 146 147 private volatile boolean mHandleFocus; 148 private volatile boolean mRepeat; 149 private PlayStateListener mListener; 150 private AudioFocusRequest mFocusRequest; 151 152 AudioPlayer(Context context, int resourceId, AudioAttributes attrib)153 public AudioPlayer(Context context, int resourceId, AudioAttributes attrib) { 154 this(context, resourceId, attrib, /* preferredDeviceInfo= */ null, 155 /* routingListener= */ null); 156 } 157 AudioPlayer(Context context, int resourceId, AudioAttributes attrib, @Nullable AudioDeviceInfo preferredDeviceInfo, @Nullable OnRoutingChangedListener routingListener)158 public AudioPlayer(Context context, int resourceId, AudioAttributes attrib, 159 @Nullable AudioDeviceInfo preferredDeviceInfo, 160 @Nullable OnRoutingChangedListener routingListener) { 161 mContext = context; 162 mResourceId = resourceId; 163 mAttrib = attrib; 164 mPreferredDeviceInfo = preferredDeviceInfo; 165 mAudioRoutingListener = routingListener; 166 } 167 getUsage()168 public int getUsage() { 169 return mAttrib.getSystemUsage(); 170 } 171 172 /** 173 * Starts player 174 * @param handleFocus true to handle focus 175 * @param repeat true to repeat track 176 * @param focusRequest type of focus to request 177 */ start(boolean handleFocus, boolean repeat, int focusRequest)178 public void start(boolean handleFocus, boolean repeat, int focusRequest) { 179 mHandleFocus = handleFocus; 180 mRepeat = repeat; 181 mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); 182 int ret = AUDIOFOCUS_REQUEST_GRANTED; 183 if (mHandleFocus) { 184 // NOTE: We are CONSCIOUSLY asking for focus again even if already playing in order 185 // exercise the framework's focus logic when faced with a (sloppy) application which 186 // might do this. 187 Log.i(TAG, "Asking for focus for usage " + mAttrib.getUsage()); 188 mFocusRequest = new AudioFocusRequest 189 .Builder(focusRequest) 190 .setAudioAttributes(mAttrib) 191 .setOnAudioFocusChangeListener(mFocusListener) 192 .setForceDucking(false) 193 .setWillPauseWhenDucked(false) 194 .setAcceptsDelayedFocusGain(false) 195 .build(); 196 ret = mAudioManager.requestAudioFocus(mFocusRequest); 197 } 198 switch (ret) { 199 case AUDIOFOCUS_REQUEST_GRANTED: 200 Log.i(TAG, "MediaPlayer got focus for usage " + mAttrib.getUsage()); 201 doStart(); 202 break; 203 case AUDIOFOCUS_REQUEST_DELAYED: 204 Log.i(TAG, "MediaPlayer delayed focus for usage " + mAttrib.getUsage()); 205 sendPlayerStateChanged(PLAYER_STATE_DELAYED); 206 break; 207 case AUDIOFOCUS_REQUEST_FAILED: 208 default: 209 Log.i(TAG, "MediaPlayer denied focus for usage " + mAttrib.getUsage()); 210 } 211 } 212 start(boolean handleFocus, boolean repeat, int focusRequest, PlayStateListener listener)213 public void start(boolean handleFocus, boolean repeat, int focusRequest, 214 PlayStateListener listener) { 215 mListener = listener; 216 start(handleFocus, repeat, focusRequest); 217 } 218 doStart()219 private void doStart() { 220 if (mPlaying.getAndSet(true)) { 221 Log.i(TAG, "already playing"); 222 return; 223 } 224 Log.i(TAG, "doStart audio"); 225 mPlayer = new MediaPlayer(); 226 mPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() { 227 228 @Override 229 public boolean onError(MediaPlayer mp, int what, int extra) { 230 Log.e(TAG, "audio error what " + what + " extra " + extra); 231 mPlaying.set(false); 232 if (!mRepeat && mHandleFocus) { 233 mPlayer.stop(); 234 mPlayer.release(); 235 mPlayer = null; 236 mAudioManager.abandonAudioFocus(mFocusListener); 237 sendPlayerStateChanged(PLAYER_STATE_STOPPED); 238 } 239 return false; 240 } 241 242 }); 243 mPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { 244 @Override 245 public void onCompletion(MediaPlayer mp) { 246 Log.i(TAG, "AudioPlayer onCompletion"); 247 mPlaying.set(false); 248 if (!mRepeat && mHandleFocus) { 249 mPlayer.stop(); 250 mPlayer = null; 251 mAudioManager.abandonAudioFocus(mFocusListener); 252 sendPlayerStateChanged(PLAYER_STATE_COMPLETED); 253 } 254 } 255 }); 256 mPlayer.setAudioAttributes(mAttrib); 257 mPlayer.setLooping(mRepeat); 258 mPlayer.setVolume(1.0f, 1.0f); 259 try { 260 AssetFileDescriptor afd = 261 mContext.getResources().openRawResourceFd(mResourceId); 262 if (afd == null) { 263 throw new RuntimeException("resource not found"); 264 } 265 mPlayer.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), 266 afd.getLength()); 267 afd.close(); 268 mPlayer.prepare(); 269 } catch (IOException e) { 270 throw new RuntimeException(e); 271 } 272 273 // Search for preferred device 274 if (mPreferredDeviceInfo != null) { 275 mPlayer.setPreferredDevice(mPreferredDeviceInfo); 276 Log.d(TAG, "doStart preferred device address: " + mPreferredDeviceInfo.getAddress()); 277 } 278 279 mPlayer.start(); 280 if (mAudioRoutingListener != null) { 281 Log.i(TAG, "doStart addOnRoutingChangedListener " + mAudioRoutingListener); 282 mPlayer.addOnRoutingChangedListener(mAudioRoutingListener, Handler.getMain()); 283 } 284 sendPlayerStateChanged(PLAYER_STATE_STARTED); 285 } 286 stop()287 public void stop() { 288 if (!mPlaying.getAndSet(false)) { 289 Log.i(TAG, "already stopped"); 290 if (mPlayer == null) { 291 return; 292 } 293 mPlayer.release(); 294 mPlayer = null; 295 return; 296 } 297 Log.i(TAG, "stop"); 298 299 mPlayer.stop(); 300 mPlayer.release(); 301 mPlayer = null; 302 sendPlayerStateChanged(PLAYER_STATE_STOPPED); 303 304 if (mHandleFocus) { 305 mAudioManager.abandonAudioFocusRequest(mFocusRequest); 306 } 307 } 308 isPlaying()309 public boolean isPlaying() { 310 return mPlaying.get(); 311 } 312 sendPlayerStateChanged(@udioPlayerState int state)313 private void sendPlayerStateChanged(@AudioPlayerState int state) { 314 if (mListener != null) { 315 mListener.onStateChange(state); 316 } 317 } 318 } 319