Android 音頻焦點(Audio Focus)

CONTENTS

  1. 引子
  2. 音頻焦點
  3. 一個簡單的示例
  4. 注意:

引子

說 Audio Focus 前先說個很簡單需求:來電暫停正在播放的音樂,電話結束時恢復播放。

看到這個需求,第一反應肯定是:監聽用戶來電狀態,作相應操作。這裏不多做介紹,這樣做有個不好的地方就是需要隱私權限!這樣做一點也不優雅

後來搜索時看到一篇分析文章:Android來電時停止音樂播放的流程(順便說一嘴,這篇轉載居然不註明出處!!)。文章裏的分析很明確的指出,系統在框架層就很好的幫我們處理了這個需求,問題是如何將音樂交給系統框架來處理呢?

音頻焦點

問題的解決方法就是:請求系統的音頻焦點Request the Audio Focus)。

如果英文還行,強烈建議請看官方的原文:Managing Audio Playback,裏面介紹的很清楚。以下爲簡單概述。

官方文檔指出Android 在處理音頻播放是分了多個“音頻流”的,如音樂流、音效流、電話聲音流等,使控制音量時可以互不干涉。多數情況下我們播放音樂都是使用 STREAM_MUSIC 音頻流。

另外,系統中可能會有多個應用程序會播放音頻,所以需要考慮他們之間該如何協調,爲了避免同時播放音樂,Android 系統使用音頻焦點來進行統一管理,即只有獲得了音頻焦點的應用程序纔可以播放音樂。

那麼,播放音頻應該這樣來做:

  1. 獲取音頻焦點 requestAudioFocus
  2. 獲取成功後,開始播放音頻
  3. 處理音頻焦點的丟失和“DUCK”
  4. 播放完畢後取消焦點

如此便可以完美的解決引子裏的需求。

一個簡單的示例

MusicService.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
public class MusicService extends Service {
private AudioManager mAm;
private boolean isPlaymusic;
private String url;
private MediaPlayer mediaPlayer;
@Override
public void onCreate() {
super.onCreate();
mAm = (AudioManager) getSystemService(AUDIO_SERVICE);
}
@Override
public void onStart(Intent intent, int startId) {
if (intent != null) {
Bundle bundle = intent.getExtras();
if (bundle != null) {
isPlaymusic = bundle.getBoolean("isPlay", true);
url = bundle.getString("url");
if (isPlaymusic)
play();
else
stop();
}
}
}
OnAudioFocusChangeListener afChangeListener = new OnAudioFocusChangeListener() {
public void onAudioFocusChange(int focusChange) {
if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT) {
// Pause playback
pause();
} else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {
// Resume playback
resume();
} else if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {
// mAm.unregisterMediaButtonEventReceiver(RemoteControlReceiver);
mAm.abandonAudioFocus(afChangeListener);
// Stop playback
stop();
}
}
};
private boolean requestFocus() {
// Request audio focus for playback
int result = mAm.requestAudioFocus(afChangeListener,
// Use the music stream.
AudioManager.STREAM_MUSIC,
// Request permanent focus.
AudioManager.AUDIOFOCUS_GAIN);
return result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
}
private void resume() {
if (mediaPlayer != null) {
mediaPlayer.start();
}
}
private void pause() {
if (mediaPlayer != null && mediaPlayer.isPlaying()) {
mediaPlayer.pause();
}
}
OnCompletionListener completionListener = new OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer player) {
if(!player.isLooping()){
mAm.abandonAudioFocus(afChangeListener);
}
}
};
private void play() {
if (requestFocus()) {
if (mediaPlayer == null) {
try {
mediaPlayer = new MediaPlayer();
mediaPlayer.setDataSource(url);
mediaPlayer.prepare();
mediaPlayer.setOnCompletionListener(completionListener);
} catch (IOException e) {
e.printStackTrace();
}
}
if (!mediaPlayer.isPlaying()) {
mediaPlayer.start();
}
}
}
@Override
public void onDestroy() {
super.onDestroy();
if (mediaPlayer != null)
mediaPlayer.release();
}
private void stop() {
if (mediaPlayer != null) {
mediaPlayer.stop();
}
}
@Override
public IBinder onBind(Intent arg0) {
// TODO Auto-generated method stub
return null;
}
}

經模擬器測試,當來電時音頻焦點會給到鈴聲流,並打出日誌:

I/AudioService(1235):  AudioFocus  requestAudioFocus() from AudioFocus_For_Phone_Ring_And_Calls

此時MusicService中的afChangeListener會得到AUDIOFOCUS_LOSS_TRANSIENT,於是會暫停播放音頻。
當通話結束或者掛掉電話,afChangeListener會得到AUDIOFOCUS_GAIN,於是恢復播放音頻。

注意:

  1. 播放完畢一定要禁止掉請求的音頻焦點abandonAudioFocus(afChangeListener),否則,如果播放完畢後的某個時段剛好有個通話結束,並且此時沒有其他的應用佔用了焦點,系統會重新通知服務裏的afChangeListener,導致音頻再次的播放。
  2. 如果丟失的短暫音頻焦點允許DUCK狀態AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK,在這種情況下,應用程序降低音量繼續播放,不需要暫停。再次獲取後,恢復原來的音量。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章