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