標籤:
如今開始做音樂播放器的模組。遇到了幾個問題
當播放音樂的過程中,去調節音量或者情景模式中的鈴聲設定,結果會有兩種聲音同一時候響起。
引起此問題的解決辦法是音樂焦點問題沒弄清
現分析一下音樂焦點的幾個屬性:原始碼在frameworks/base/media/java/andorid/media/AudioManager.java中
public static final int AUDIOFOCUS_NONE = 0;
指示申請得到的Audio Focus不知道會持續多久,通常是長期佔有。獲得了Audio Focus;
public static final int AUDIOFOCUS_GAIN = 1;
指示要申請的AudioFocus是臨時性的,會非常快用完釋放的;
public static final int AUDIOFOCUS_GAIN_TRANSIENT = 2;
不但說要申請的AudioFocus是臨時性的。還指示當前正在使用AudioFocus的能夠繼續播放,僅僅是要“duck”一下(減少音量)。
public static final int AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK = 3;
public static final int AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE = 4;
AudioManager.OnAudioFocusChangeListener是申請成功之後監聽AudioFocus使用方式的Listener,興許假設有別的程式要競爭AudioFocus,都是通過這個Listener的onAudioFocusChange()方法來通知這個Audio Focus的使用者的。
失去了Audio Focus,並將會持續非常長的時間
public static final int AUDIOFOCUS_LOSS = -1 * AUDIOFOCUS_GAIN;
臨時失去Audio Focus,並會非常快再次獲得。必須停止Audio的播放。可是由於可能會非常快再次獲得AudioFocus。這裡能夠不釋放Media資源;
public static final int AUDIOFOCUS_LOSS_TRANSIENT = -1 * AUDIOFOCUS_GAIN_TRANSIENT;
臨時失去AudioFocus,可是能夠繼續播放,只是要在減少音量。
public static final int AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK =
-1 * AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK;
看看剛才改動的一個問題:
問題描寫敘述:播放音樂時鬧鐘到來,把鬧鐘放在後台時進入檔案管理工具播放音頻。鬧鐘仍然在響應,鬧鐘和音樂同一時候響起;
問題分析:在鬧鐘鈴聲響起時,沒有去做音頻焦點的處理
解決方式:在packages/apps/deskclock/src/com/android/deskclock/alarms/AlarmKlaxon.java檔案裡加上焦點處理
改動後的原始碼:
package com.android.deskclock.alarms;import android.content.Context;import android.content.res.AssetFileDescriptor;import android.media.AudioManager;import android.media.MediaPlayer;import android.media.MediaPlayer.OnErrorListener;import android.media.RingtoneManager;import android.net.Uri;import android.os.Vibrator;import com.android.deskclock.Log;import com.android.deskclock.R;import com.android.deskclock.provider.AlarmInstance;import java.io.IOException;/*add by leo.tan 20140717 for bugzilla 20064 start */import android.os.Handler;import android.media.AudioManager.OnAudioFocusChangeListener;import android.os.Message;/*add by <span id="summary_alias_container"><span id="short_desc_nonedit_display"></span></span> leo.tan 20140717 for bugzilla 20064 end *//** * Manages playing ringtone and vibrating the device. */public class AlarmKlaxon { private static final long[] sVibratePattern = new long[] { 500, 500 }; // Volume suggested by media team for in-call alarms. private static final float IN_CALL_VOLUME = 0.125f; private static boolean sStarted = false; private static MediaPlayer sMediaPlayer = null;/*add by leo.tan 20140717 for bugzilla 20064 start */ private static final int FOCUSCHANGE = 3000; private static final int FADEDOWN = 5; private static final int FADEUP = 6; private static final int RETRY_REQUEST_FOCUS = 7; private static final int OVER_SHORT_VIBRATOR = 8; private static OnAudioFocusChangeListener mAudioFocusListener = new OnAudioFocusChangeListener() { public void onAudioFocusChange(int focusChange) { android.util.Log.v("AlarmKlaxon", "mAudioFocusListener::focusChange-->" + focusChange); mHandler.obtainMessage(FOCUSCHANGE, focusChange, 0).sendToTarget(); } };/*add by leo.tan 20140717 for bugzilla 20064 end */ public static void stop(Context context) { Log.v("AlarmKlaxon.stop()"); if (sStarted) { sStarted = false; // Stop audio playing if (sMediaPlayer != null) { sMediaPlayer.stop(); AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); audioManager.abandonAudioFocus(null); sMediaPlayer.release(); sMediaPlayer = null; }/*add by leo.tan 20140717 for bugzilla 20064 start */ final AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); audioManager.abandonAudioFocus(mAudioFocusListener);/*add by leo.tan 20140717 for bugzilla 20064 end */ ((Vibrator)context.getSystemService(Context.VIBRATOR_SERVICE)).cancel(); } } public static void start(final Context context, AlarmInstance instance, boolean inTelephoneCall) { Log.v("AlarmKlaxon.start()"); // Make sure we are stop before starting stop(context); if (!AlarmInstance.NO_RINGTONE_URI.equals(instance.mRingtone)) { Uri alarmNoise = instance.mRingtone; // Fall back on the default alarm if the database does not have an // alarm stored. if (alarmNoise == null) { alarmNoise = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM); if (Log.LOGV) { Log.v("Using default alarm: " + alarmNoise.toString()); } } TODO: Reuse mMediaPlayer instead of creating a new one and/or use RingtoneManager. sMediaPlayer = new MediaPlayer(); sMediaPlayer.setOnErrorListener(new OnErrorListener() { @Override public boolean onError(MediaPlayer mp, int what, int extra) { Log.e("Error occurred while playing audio. Stopping AlarmKlaxon."); AlarmKlaxon.stop(context); return true; } }); try { // Check if we are in a call. If we are, use the in-call alarm // resource at a low volume to not disrupt the call. if (inTelephoneCall) { Log.v("Using the in-call alarm"); sMediaPlayer.setVolume(IN_CALL_VOLUME, IN_CALL_VOLUME); setDataSourceFromResource(context, sMediaPlayer, R.raw.in_call_alarm); } else { sMediaPlayer.setDataSource(context, alarmNoise); } startAlarm(context, sMediaPlayer); }catch (Exception ex) { Log.v("Using the fallback ringtone"); // The alarmNoise may be on the sd card which could be busy right // now. Use the fallback ringtone. try { // Must reset the media player to clear the error state. sMediaPlayer.reset(); setDataSourceFromResource(context, sMediaPlayer, R.raw.fallbackring); startAlarm(context, sMediaPlayer); } catch (Exception ex2) { // At this point we just don't play anything. Log.e("Failed to play fallback ringtone", ex2); } } } if (instance.mVibrate && !inTelephoneCall) { Vibrator vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); vibrator.vibrate(sVibratePattern, 0); } sStarted = true; } // Do the common stuff when starting the alarm. private static void startAlarm(Context context, MediaPlayer player) throws IOException { AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); // do not play alarms if stream volume is 0 (typically because ringer mode is silent). if (audioManager.getStreamVolume(AudioManager.STREAM_ALARM) != 0) { player.setAudioStreamType(AudioManager.STREAM_ALARM); player.setLooping(true); player.prepare(); audioManager.requestAudioFocus(null, AudioManager.STREAM_ALARM, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT); player.start(); } /*add by leo.tan 20140717 for bugzilla 20064 start */ //在這個地方進行焦點的請求 final int requestResult = audioManager.requestAudioFocus(mAudioFocusListener, AudioManager.STREAM_ALARM, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT); /*add by leo.tan 20140717 for bugzilla 20064 end */ } private static void setDataSourceFromResource(Context context, MediaPlayer player, int res) throws IOException { AssetFileDescriptor afd = context.getResources().openRawResourceFd(res); if (afd != null) { player.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength()); afd.close(); } } /*add by leo.tan 20140717 for bugzilla 20064 start */private static void setVolume(float vol) { if(sMediaPlayer != null){ sMediaPlayer.setVolume(vol, vol); } } //用handler來對焦點進行處理 private static Handler mHandler = new Handler() { public void handleMessage(Message msg) { switch (msg.what) { case FOCUSCHANGE: { switch (msg.arg1) { case AudioManager.AUDIOFOCUS_LOSS: case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: mHandler.removeMessages(FADEUP); mHandler.sendEmptyMessage(FADEDOWN); break; case AudioManager.AUDIOFOCUS_GAIN: mHandler.removeMessages(FADEDOWN); mHandler.sendEmptyMessage(FADEUP); break; } break; } case FADEDOWN: // Turn off the sound setVolume(0.0f); break; case FADEUP: // Turn on the sound setVolume(1.0f); break; } } };/*add by leo.tan 20140717 for bugzilla 20064 end */}<span id="summary_alias_container"></span>
android AudioManager AUDIOFOCUS