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