• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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