Media Playback
Android多媒體框架包涵了對(duì)播放多種通用媒體的類(lèi)型的支持,所以你可以很容易的集成音頻,視頻和圖像到你的應(yīng)用中。你可以使用MediaPlayer APIs播放保存在應(yīng)用raw資源中單獨(dú)的媒體文件,或者通過(guò)網(wǎng)絡(luò)連接下載的數(shù)據(jù)流
The Basics
以下的類(lèi)在Android框架中被用于播放聲音和視頻
MediaPlayer 這個(gè)類(lèi)主要用于播放聲音和視頻
AudioManager 這個(gè)類(lèi)管理設(shè)備上的音頻源和音頻輸出
Manifest Declarations
在使用MediaPlayer開(kāi)發(fā)你的應(yīng)用前,確定你的應(yīng)用聲明了允許使用相關(guān)的特性
Internet Permission
如果你使用MediaPlayer去播放網(wǎng)絡(luò)的內(nèi)容,你的應(yīng)用必須請(qǐng)求網(wǎng)絡(luò)權(quán)限
<uses-permission android:name="android.permission.INTERNET" />
Wake Lock Permission
如果你的播放器應(yīng)用程序需要保持屏幕變暗或處理器睡眠,要么使用MediaPlayer.setScreenOnWhilePlaying()方法,要么使用MediaPlayer.setWakeMode()方法,但是你必須請(qǐng)求這個(gè)權(quán)限
<uses-permission android:name="android.permission.WAKE_LOCK" />
Using MediaPlayer
media框架最重要的類(lèi)之一就是MediaPlayer類(lèi)。這個(gè)類(lèi)的對(duì)象可以使用最少步驟獲取,解析,播放音頻和視頻。它支持幾個(gè)不同的媒體源。
比如:
本地資源
網(wǎng)絡(luò)URIs,比如你從Content Resolver獲取到的
外部URLs(流)
具體android支持的媒體格式,請(qǐng)查看文檔
以下例子演示了怎么播放一個(gè)本地raw資源中可用的音頻
MediaPlayer mediaPlayer = MediaPlayer.create(context, R.raw.sound_file_1);
mediaPlayer.start(); // no need to call prepare(); create() does that for you
以下是從本地播放可用URI(從Content Resolver獲取到的)
Uri myUri = ....; // initialize Uri here
MediaPlayer mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.setDataSource(getApplicationContext(), myUri);
mediaPlayer.prepare();
mediaPlayer.start();
以下是通過(guò)HTTP遠(yuǎn)程播放遠(yuǎn)程的流
String url = "http://........";
// your URL hereMediaPlayer mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.setDataSource(url);
mediaPlayer.prepare(); // might take long! (for buffering, etc)
mediaPlayer.start();
注意:當(dāng)你使用setDataSource時(shí),你必須處理 IllegalArgumentException或IOException,因?yàn)槟闼饕奈募赡懿淮嬖?/p>
Asynchronous Preparation
MediaPlayer原則上是可以直接使用的,但是有一些要點(diǎn)是需要記住的,比如,prepare()的調(diào)用可能會(huì)執(zhí)行很長(zhǎng)時(shí)間,因?yàn)樗婕暗教崛?,解析媒體數(shù)據(jù),所以,像其他需要長(zhǎng)時(shí)間運(yùn)行的方法,你應(yīng)該避免在UI線程中調(diào)用。因?yàn)檫@樣做會(huì)使UI線程掛起直到方法返回,這是非常不好的用戶體驗(yàn)并且可能導(dǎo)致ANR錯(cuò)誤。即使你預(yù)計(jì)你的資源會(huì)加載的很快,超多1/10秒才回應(yīng)就會(huì)導(dǎo)致卡頓,這會(huì)給用戶留下你的app很慢的映像
為了避免UI線程掛起,創(chuàng)建一個(gè)新的線程去prepare MediaPlayer并在完成時(shí)通知主線程,框架提供了一個(gè)便捷的方法prepareAsync()去完成這個(gè)任務(wù),這個(gè)方法在后臺(tái)prepare音頻并在完成時(shí)立馬返回,當(dāng)音頻完成preparing時(shí),將調(diào)用通過(guò)setOnPreparedListener()配置的MediaPlayer.OnPreparedListener的onPrepared()方法。
Managing State
從MediaPlayer的另一個(gè)方面來(lái)說(shuō)它是基于狀態(tài)的。也就是說(shuō),MediaPlayer有一個(gè)內(nèi)部狀態(tài),你在編寫(xiě)代碼時(shí)必須始終注意,因?yàn)槟承┎僮髦挥性诓シ牌魈幱谔囟顟B(tài)時(shí)才有效。如果在錯(cuò)誤狀態(tài)下執(zhí)行操作,系統(tǒng)可能會(huì)拋出異?;?qū)е缕渌恍枰男袨椤?/p>
MediaPlayer類(lèi)中的文檔顯示了一個(gè)完整的狀態(tài)圖,說(shuō)明了哪些方法將MediaPlayer從一個(gè)狀態(tài)移動(dòng)到另一個(gè)狀態(tài)。例如,當(dāng)您創(chuàng)建一個(gè)新的MediaPlayer時(shí),它處于空閑狀態(tài)。此時(shí),您應(yīng)該通過(guò)調(diào)用setDataSource()初始化它,使其處于Initialized狀態(tài)。之后,您必須使用prepare()或prepareAsync()方法來(lái)準(zhǔn)備它。當(dāng)MediaPlayer完成準(zhǔn)備時(shí),它將進(jìn)入準(zhǔn)備狀態(tài),這意味著您可以調(diào)用start()使其播放媒體。此時(shí),如圖所示,您可以通過(guò)調(diào)用start(),pause()和seekTo()等方法在Started,Paused和PlaybackCompleted狀態(tài)之間切換。但是,當(dāng)調(diào)用stop()時(shí),請(qǐng)注意,在再次準(zhǔn)備MediaPlayer之前,不能再次調(diào)用start()(從狀態(tài)圖中看出需要調(diào)用prepareAsync)。
當(dāng)編寫(xiě)與MediaPlayer對(duì)象交互的代碼時(shí),始終記得狀態(tài)圖,因?yàn)閺腻e(cuò)誤狀態(tài)調(diào)用其方法是錯(cuò)誤的常見(jiàn)原因。
Releasing the MediaPlayer
MediaPlayer會(huì)消耗有限的系統(tǒng)資源,所以,你應(yīng)該預(yù)防在不需要的時(shí)候還持有Mediaplayer的實(shí)例,當(dāng)你完成你的任務(wù)時(shí),你應(yīng)該調(diào)用release()去釋放任何分配給你的資源,例如,如果你正在使用MediaPlayer但是你的Activity的onStop方法被回調(diào)了,那么你應(yīng)該立即釋放MediaPlayer,因?yàn)楫?dāng)你的活動(dòng)沒(méi)有與用戶交互時(shí)(除非你在后臺(tái)播放媒體,這將在下一節(jié)討論),保持它是沒(méi)有意義的。當(dāng)你的Activity恢復(fù)或者重新啟動(dòng)了,你需要?jiǎng)?chuàng)建一個(gè)新的MediaPlayer并重新prapare
mediaPlayer.release();
mediaPlayer = null;
例如,如果你在活動(dòng)停止時(shí)忘記釋放MediaPlayer,但在活動(dòng)再次啟動(dòng)時(shí)創(chuàng)建一個(gè)新的MediaPlayer,可能會(huì)發(fā)生的問(wèn)題。我們都知道,當(dāng)用戶更改屏幕方向(或以其他方式更改設(shè)備配置)時(shí),系統(tǒng)會(huì)通過(guò)重新啟動(dòng)活動(dòng)(默認(rèn)情況下)來(lái)處理,因此當(dāng)設(shè)備在縱向和橫向之間來(lái)回切換時(shí)會(huì)快速消耗所有系統(tǒng)資源,因?yàn)樵诿總€(gè)方向更改時(shí),你將創(chuàng)建一個(gè)新的MediaPlayer,但你從不釋放。 (有關(guān)運(yùn)行時(shí)重新啟動(dòng)的詳細(xì)信息,請(qǐng)參閱處理運(yùn)行時(shí)更改。)
你可能想知道,如果你想繼續(xù)播放“后臺(tái)媒體”,即使用戶離開(kāi)你的活動(dòng),很大程度上與內(nèi)置音樂(lè)應(yīng)用程序的行為相同的方式。 在這種情況下,你需要的是由服務(wù)控制的MediaPlayer,如在使用MediaPlayer中的服務(wù)中所述。
Using a Service with MediaPlayer
如果你希望你的媒體在后臺(tái)播放,即使你的應(yīng)用程序沒(méi)有運(yùn)行在當(dāng)前屏幕上,也就是說(shuō),你希望你的媒體在用戶與其他應(yīng)用程序交互時(shí)繼續(xù)播放 - 那么你必須啟動(dòng)一個(gè)服務(wù)并在服務(wù)中控制MediaPlayer實(shí)例。 你應(yīng)該小心這個(gè)設(shè)置,因?yàn)橛脩艉拖到y(tǒng)都期望運(yùn)行后臺(tái)服務(wù)的應(yīng)用程序如何與系統(tǒng)的其余部分交互。 如果你的應(yīng)用程序不滿足這些期望,用戶可能會(huì)有不好的體驗(yàn)。 本節(jié)介紹了你應(yīng)該注意的主要問(wèn)題,并提供有關(guān)如何處理這些問(wèn)題的建議。
Running asynchronously
首先,像Activity一樣,默認(rèn)情況下服務(wù)中的所有工作都是在單個(gè)線程中完成的 - 事實(shí)上,如果你在同一個(gè)應(yīng)用程序運(yùn)行中一個(gè)活動(dòng)和一個(gè)服務(wù),默認(rèn)他們?cè)谕粋€(gè)線程(“主線程 “)中。 因此,服務(wù)需要快速處理傳入意圖,并且在響應(yīng)它們時(shí)不執(zhí)行冗長(zhǎng)的計(jì)算。 如果有任何繁重的工作或阻塞調(diào)用,你必須異步地執(zhí)行這些任務(wù):從你自己實(shí)現(xiàn)的另一個(gè)線程,或使用框架的許多設(shè)施進(jìn)行異步處理。
例如,當(dāng)你在主線程中使用MediaPlayer,你應(yīng)該調(diào)用prepareAsync()方法而不是prepare()方法,實(shí)現(xiàn)MediaPlayer.OnPreparedListener 是為了在preparation完成時(shí)可以被通知并且你可以開(kāi)始播放。例如:
public class MyService extends Service implements MediaPlayer.OnPreparedListener {
private static final String ACTION_PLAY = "com.example.action.PLAY";
MediaPlayer mMediaPlayer = null;
public int onStartCommand(Intent intent, int flags, int startId) {
...
if (intent.getAction().equals(ACTION_PLAY)) {
mMediaPlayer = ... // initialize it here
mMediaPlayer.setOnPreparedListener(this);
mMediaPlayer.prepareAsync(); // prepare async to not block main thread
}
}
/** Called when MediaPlayer is ready */
public void onPrepared(MediaPlayer player) {
player.start();
}
}
Handling asynchronous errors
在同步操作中,通常會(huì)使用異?;蝈e(cuò)誤代碼來(lái)報(bào)告錯(cuò)誤,但是當(dāng)使用異步資源時(shí),你應(yīng)該確保你的應(yīng)用正確地收到錯(cuò)誤通知。 在MediaPlayer中,你可以通過(guò)實(shí)現(xiàn)MediaPlayer.OnErrorListener并將你的MediaPlayer實(shí)例作為參數(shù)設(shè)置:
public class MyService extends Service implements MediaPlayer.OnErrorListener {
MediaPlayer mMediaPlayer;
public void initMediaPlayer() {
// ...initialize the MediaPlayer here...
mMediaPlayer.setOnErrorListener(this);
}
@Override
public boolean onError(MediaPlayer mp, int what, int extra) {
// ... react appropriately ...
// The MediaPlayer has moved to the Error state, must be reset!
}
}
重要的是記住當(dāng)一個(gè)錯(cuò)誤發(fā)生時(shí),MediaPlayer會(huì)切換到錯(cuò)誤狀態(tài)(完整狀態(tài)圖的MediaPlayer類(lèi)的文檔),你必須重置它,然后才能再次使用它。
Using wake locks
當(dāng)設(shè)計(jì)在后臺(tái)播放媒體的應(yīng)用程序時(shí),設(shè)備可能會(huì)在服務(wù)運(yùn)行時(shí)進(jìn)入睡眠狀態(tài)。 由于Android系統(tǒng)嘗試在設(shè)備休眠時(shí)節(jié)省電池,因此系統(tǒng)會(huì)嘗試關(guān)閉手機(jī)的任何不需要的功能,包括CPU和WiFi硬件。 但是,如果你的服務(wù)正在播放或流式傳輸音樂(lè),則希望系統(tǒng)不要干擾你的播放。
為了確保你的服務(wù)在這些條件下繼續(xù)運(yùn)行,你必須使用“喚醒鎖”。 喚醒鎖是通知系統(tǒng)你的應(yīng)用程序正在使用一些功能,系統(tǒng)應(yīng)該保持可用,即使設(shè)備處于空閑狀態(tài)。
注意:你應(yīng)始終謹(jǐn)慎使用喚醒鎖,并且只在真正必要的時(shí)間內(nèi)保持它們,因?yàn)樗鼈儠?huì)顯著縮短設(shè)備的電池壽命。
要確保CPU在播放MediaPlayer時(shí)繼續(xù)運(yùn)行,請(qǐng)?jiān)诔跏蓟疢ediaPlayer時(shí)調(diào)用setWakeMode()方法。 一旦完成,MediaPlayer在播放時(shí)保持指定的鎖定,并在暫?;蛲V箷r(shí)釋放鎖定:
mMediaPlayer = new MediaPlayer();
// ... other initialization here ...
mMediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK);
但是,在此示例中獲取的喚醒鎖僅保證CPU保持喚醒。 如果你通過(guò)網(wǎng)絡(luò)流傳輸媒體,并且使用的是Wi-Fi,則你可能還需要持有一個(gè)WifiLock,你必須手動(dòng)獲取和釋放。 因此,當(dāng)你開(kāi)始使用遠(yuǎn)程URL準(zhǔn)備MediaPlayer時(shí),應(yīng)創(chuàng)建并獲取Wi-Fi鎖。 例如:
WifiLock wifiLock = ((WifiManager) getSystemService(Context.WIFI_SERVICE)) .createWifiLock(WifiManager.WIFI_MODE_FULL, "mylock");
wifiLock.acquire();
當(dāng)你暫?;蛲V姑襟w,或不再需要網(wǎng)絡(luò)時(shí),應(yīng)釋放鎖定:
wifiLock.release();
Running as a foreground service
服務(wù)通常用于執(zhí)行后臺(tái)任務(wù),諸如獲取電子郵件,同步數(shù)據(jù),下載內(nèi)容以及其它可能性。在這些情況下,用戶沒(méi)有主動(dòng)地了解服務(wù)的執(zhí)行,并且可能不會(huì)注意到這些服務(wù)是否被中斷并且稍后重新啟動(dòng)。
考慮正在播放音樂(lè)的服務(wù)。這種服務(wù)用戶可以清晰的意識(shí)到它的活動(dòng),任何中斷的都會(huì)嚴(yán)重影響體驗(yàn)。此外,在服務(wù)執(zhí)行期間用戶可能希望與之交互。在這種情況下,服務(wù)應(yīng)作為“前臺(tái)服務(wù)”運(yùn)行。前臺(tái)服務(wù)在系統(tǒng)中重要性等級(jí)更高 - 系統(tǒng)幾乎不會(huì)殺死服務(wù),因?yàn)樗鼘?duì)用戶是直接重要的。當(dāng)在前臺(tái)運(yùn)行時(shí),服務(wù)還必須提供狀態(tài)欄通知,以確保用戶知道正在運(yùn)行的服務(wù)并允許他們打開(kāi)可與服務(wù)交互的Activity。
為了將你的服務(wù)切換到前臺(tái),你必須為狀態(tài)欄創(chuàng)建通知,并且為服務(wù)調(diào)用startForeground。示例:
String songName;
// assign the song name to songName
PendingIntent pi = PendingIntent.getActivity(getApplicationContext(), 0,new Intent(getApplicationContext(), MainActivity.class), PendingIntent.FLAG_UPDATE_CURRENT);
Notification notification = new Notification();
notification.tickerText = text;notification.icon = R.drawable.play0;
notification.flags |= Notification.FLAG_ONGOING_EVENT;
notification.setLatestEventInfo(getApplicationContext(), "MusicPlayerSample","Playing: " + songName, pi);
startForeground(NOTIFICATION_ID, notification);
當(dāng)你的服務(wù)在前臺(tái)運(yùn)行時(shí),你配置的通知在設(shè)備的通知區(qū)域中可見(jiàn)。 如果用戶選擇通知,系統(tǒng)會(huì)調(diào)用你提供的PendingIntent。 在上面的示例中,它打開(kāi)一個(gè)Activity(MainActivity)。
你應(yīng)該只保持“前臺(tái)服務(wù)”狀態(tài),而你的服務(wù)實(shí)際執(zhí)行用戶主動(dòng)感知的東西。 一旦這不再是true,你應(yīng)該通過(guò)調(diào)用stopForeground()釋放它:
stopForeground(true);
Handling audio focus
Android是一個(gè)多任務(wù)環(huán)境,但在同一時(shí)間中只有一個(gè)活動(dòng)可以在當(dāng)前屏幕運(yùn)行。這對(duì)使用音頻的應(yīng)用提出了特別的挑戰(zhàn),因?yàn)橹挥幸粋€(gè)音頻輸出,并且可能存在若干媒體服務(wù)競(jìng)爭(zhēng)其使用。在Android 2.2之前,沒(méi)有內(nèi)置的機(jī)制來(lái)解決這個(gè)問(wèn)題,這在某些情況下可能會(huì)導(dǎo)致糟糕的用戶體驗(yàn)。例如,當(dāng)用戶正在聽(tīng)音樂(lè)而此時(shí)另一應(yīng)用需要向用戶通知非常重要的事情時(shí),由于大聲的音樂(lè),用戶可能聽(tīng)不到通知鈴聲。從Android 2.2開(kāi)始,平臺(tái)為應(yīng)用程序提供了一種方式,協(xié)商其使用設(shè)備的音頻輸出。這種機(jī)制稱(chēng)為音頻焦點(diǎn)。
當(dāng)你的應(yīng)用程序需要輸出音頻(如音樂(lè)或通知)時(shí),應(yīng)始終請(qǐng)求音頻焦點(diǎn)。一旦它有焦點(diǎn),它可以自由地使用聲音輸出,但它應(yīng)該總是監(jiān)聽(tīng)焦點(diǎn)的更改。如果它被通知已經(jīng)失去了音頻焦點(diǎn),它應(yīng)該立即殺死音頻或?qū)⒁袅拷档偷桨察o的級(jí)別(稱(chēng)為“ducking” - 有一個(gè)標(biāo)志,指示哪一個(gè)是適當(dāng)?shù)模⑶抑挥性俅谓邮战裹c(diǎn)才能恢復(fù)高聲播放。
音頻焦點(diǎn)應(yīng)該是合作的。也就是說(shuō),應(yīng)用程序需要(并高度鼓勵(lì))遵守音頻焦點(diǎn)指南,但規(guī)則不是由系統(tǒng)強(qiáng)制執(zhí)行。如果應(yīng)用程序想要播放大聲的音樂(lè),即使失去音頻焦點(diǎn),系統(tǒng)中的任何東西都不會(huì)阻止。然而,用戶更可能具有不良體驗(yàn),并且將更可能卸載行為不當(dāng)?shù)膽?yīng)用程序
要請(qǐng)求音頻焦點(diǎn),您必須從AudioManager調(diào)用requestAudioFocus(),如下面的示例所示:
AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
int result = audioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
if (result != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
// could not get audio focus.
}
requestAudioFocus()的第一個(gè)參數(shù)是AudioManager.OnAudioFocusChangeListener,只要音頻焦點(diǎn)發(fā)生變化,就會(huì)調(diào)用onAudioFocusChange()方法。 因此,你還應(yīng)該為你的服務(wù)和Activity實(shí)現(xiàn)此接口。 例如:
class MyService extends Service implements AudioManager.OnAudioFocusChangeListener {
// ....
public void onAudioFocusChange(int focusChange) {
// Do something based on focus change...
}
}
focusChange參數(shù)告訴你音頻焦點(diǎn)是如何改變的,并且可以是以下值之一(它們都是在AudioManager中定義的常量):
AUDIOFOCUS_GAIN:你已經(jīng)獲得了音頻焦點(diǎn)。
AUDIOFOCUS_LOSS:你大概已經(jīng)失去了音頻焦點(diǎn)很長(zhǎng)時(shí)間。 你必須停止所有音頻播放。 因?yàn)槟悴荒荛L(zhǎng)時(shí)間期待焦點(diǎn)回來(lái),對(duì)于盡可能多地清理你的資源,這將是一個(gè)很好的地方。 例如,您應(yīng)該釋放MediaPlayer。
AUDIOFOCUS_LOSS_TRANSIENT:你暫時(shí)失去了音頻焦點(diǎn),但很快就會(huì)收到。 你必須停止所有音頻播放,但你可以保留你的資源,因?yàn)槟愫芸赡軙?huì)很快就得到焦點(diǎn)。
AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:你暫時(shí)失去了音頻焦點(diǎn),但你可以繼續(xù)靜靜地播放音頻(音量低),而不是完全停止音頻。
以下是一個(gè)??
public void onAudioFocusChange(int focusChange) {
switch (focusChange) {
case AudioManager.AUDIOFOCUS_GAIN:
// resume playback
if (mMediaPlayer == null) initMediaPlayer();
else if (!mMediaPlayer.isPlaying()){
mMediaPlayer.start();
mMediaPlayer.setVolume(1.0f, 1.0f);
}
break;
case AudioManager.AUDIOFOCUS_LOSS:
// Lost focus for an unbounded amount of time: stop playback and release media player
if (mMediaPlayer.isPlaying()) mMediaPlayer.stop();
mMediaPlayer.release();
mMediaPlayer = null;
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
// Lost focus for a short time, but we have to stop
// playback. We don't release the media player because playback
// is likely to resume
if (mMediaPlayer.isPlaying()) mMediaPlayer.pause();
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
// Lost focus for a short time, but it's ok to keep playing
// at an attenuated level
if (mMediaPlayer.isPlaying()) mMediaPlayer.setVolume(0.1f, 0.1f);
break;
}
}
請(qǐng)注意,音頻焦點(diǎn)API僅適用于API級(jí)別8(Android 2.2)及更高版本,因此如果你要支持以前的Android版本,則應(yīng)采用向后兼容性策略,以便可以使用此功能(如果有)
你可以通過(guò)反射調(diào)用音頻焦點(diǎn)方法或通過(guò)在單獨(dú)的類(lèi)(例如AudioFocusHelper)中實(shí)現(xiàn)所有音頻焦點(diǎn)功能來(lái)實(shí)現(xiàn)向后兼容。 這里有一個(gè)這樣的類(lèi)的例子:
public class AudioFocusHelper implements AudioManager.OnAudioFocusChangeListener {
AudioManager mAudioManager;
// other fields here, you'll probably hold a reference to an interface
// that you can use to communicate the focus changes to your Service
public AudioFocusHelper(Context ctx, /* other arguments here */) {
mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
// ...
}
public boolean requestFocus() {
return AudioManager.AUDIOFOCUS_REQUEST_GRANTED == mAudioManager.requestAudioFocus(mContext, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
}
public boolean abandonFocus() {
return AudioManager.AUDIOFOCUS_REQUEST_GRANTED == mAudioManager.abandonAudioFocus(this);
}
@Override
public void onAudioFocusChange(int focusChange) {
// let your service know about the focus change
}
}
僅當(dāng)檢測(cè)到系統(tǒng)運(yùn)行的API級(jí)別為8或更高時(shí),才能創(chuàng)建AudioFocusHelper類(lèi)的實(shí)例
if (android.os.Build.VERSION.SDK_INT >= 8) {
mAudioFocusHelper = new AudioFocusHelper(getApplicationContext(), this);
} else {
mAudioFocusHelper = null;
}
Performing cleanup
如前所述,MediaPlayer對(duì)象會(huì)消耗大量的系統(tǒng)資源,因此你應(yīng)該在你需要時(shí)保留它,并在完成后調(diào)用release()。 顯示的調(diào)用這個(gè)清除方法,而不是依賴(lài)系統(tǒng)垃圾收集器時(shí)很重要的,因?yàn)樵诶占骰厥誐ediaPlayer前它可能需要花一些時(shí)間,因?yàn)樗粚?duì)內(nèi)存需求敏感,而不是缺乏其他媒體相關(guān)資源。 因此,在使用服務(wù)的情況下,您應(yīng)該總是覆蓋onDestroy()方法,以確保你釋放MediaPlayer:
public class MyService extends Service {
MediaPlayer mMediaPlayer;
// ...
@Override
public void onDestroy() {
if (mMediaPlayer != null) mMediaPlayer.release();
}
}
除了在關(guān)閉時(shí)釋放MediaPlayer以外,你也應(yīng)該總是尋找其他機(jī)會(huì)釋放你的MediaPlayer。 例如,如果你預(yù)計(jì)不能長(zhǎng)時(shí)間播放媒體(例如,失去音頻焦點(diǎn)后),你應(yīng)該確保會(huì)釋放您現(xiàn)有的MediaPlayer并稍后重新創(chuàng)建。 另一方面,如果你只希望短時(shí)間停止播放,那么你應(yīng)該保存MediaPlayer,以避免創(chuàng)建和準(zhǔn)備再次的開(kāi)銷(xiāo)。
Handling the AUDIO_BECOMING_NOISY Intent
許多精心編寫(xiě)的音頻播放程序會(huì)在發(fā)生導(dǎo)致音頻外放(通過(guò)外部揚(yáng)聲器輸出)的事件時(shí)自動(dòng)停止播放。 例如,可能發(fā)生當(dāng)用戶通過(guò)耳機(jī)收聽(tīng)音樂(lè)并意外地將耳機(jī)從設(shè)備拔掉這種情況。 但是,這種行為不會(huì)自動(dòng)發(fā)生。 如果你不實(shí)現(xiàn)此功能,音頻會(huì)從設(shè)備的外部揚(yáng)聲器播放,這可能不是用戶想要的。
您可以通過(guò)處理ACTION_AUDIO_BECOMING_NOISY意圖,確保你的應(yīng)用在這些情況下停止播放音樂(lè),您可以通過(guò)向清單添加以下內(nèi)容來(lái)注冊(cè)接收者:
<receiver android:name=".MusicIntentReceiver">
<intent-filter>
<action android:name="android.media.AUDIO_BECOMING_NOISY" />
</intent-filter>
</receiver>
為ACTION_AUDIO_BECOMING_NOISY的intent注冊(cè)MusicIntentReceiver類(lèi)為廣播接收器
public class MusicIntentReceiver extends android.content.BroadcastReceiver {
@Override
public void onReceive(Context ctx, Intent intent) {
if (intent.getAction().equals(android.media.AudioManager.ACTION_AUDIO_BECOMING_NOISY)) {
// signal your service to stop playback
// (via an Intent, for instance)
}
}
}
Retrieving Media from a Content Resolver
對(duì)一個(gè)音頻播放器app有用的另一個(gè)特性是可以在用戶的設(shè)備上通過(guò)ContentResolver檢索外部的音樂(lè)
ContentResolver contentResolver = getContentResolver();
Uri uri = android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
Cursor cursor = contentResolver.query(uri, null, null, null, null);
if (cursor == null) {
// query failed, handle error.
} else if (!cursor.moveToFirst()) {
// no media on the device
} else { int titleColumn = cursor.getColumnIndex(android.provider.MediaStore.Audio.Media.TITLE);
int idColumn = cursor.getColumnIndex(android.provider.MediaStore.Audio.Media._ID);
do {
long thisId = cursor.getLong(idColumn);
String thisTitle = cursor.getString(titleColumn);
// ...process entry...
} while (cursor.moveToNext());
}
long id = /* retrieve it from somewhere */;
Uri contentUri = ContentUris.withAppendedId( android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, id);
mMediaPlayer = new MediaPlayer();
mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mMediaPlayer.setDataSource(getApplicationContext(), contentUri);
// ...prepare and start...
Manage Audio Playback
1.Controlling Your App’s Volume and Playback
良好的用戶體驗(yàn)是可以預(yù)測(cè)的。如果你的app可以播放media,那么你的用戶可以使用他們?cè)O(shè)備,藍(lán)牙耳機(jī),頭戴耳機(jī)的硬件或者軟件去控制你的app的音量是很重要的。
同樣,通過(guò)你的app他們可以適當(dāng)使用play,stop,pause,skip,和previous媒體播放鍵在音頻流上做出各自的行為
Identify Which Audio Stream to Use
創(chuàng)建可預(yù)測(cè)音頻體驗(yàn)的第一步是知道你的app將要使用的音頻流,Android支持單獨(dú)播放音樂(lè),鬧鐘,通知,來(lái)電鈴聲,系統(tǒng)聲音,呼叫中的聲音,DTMF音。這主要是為了允許用戶獨(dú)立地控制每個(gè)流的音量。大部分音頻流僅限于系統(tǒng)事件,所以除非你app替代鬧鐘,不然,你基本上都是使用STREAM_MUSIC播放你的音頻
默認(rèn)情況下,使用物理音量鍵區(qū)控制你app的音量,按下音量控制鍵調(diào)整正在播放的音頻流的音量。如果你的app當(dāng)前沒(méi)有播放任何音頻流,那么按下音量鍵調(diào)整的將是鈴聲的音量
如果你開(kāi)發(fā)的是游戲或者音樂(lè)app,那么即使他們處于在歌曲切換時(shí)或游戲的當(dāng)前時(shí)間點(diǎn)沒(méi)有音樂(lè),當(dāng)用戶按下音量鍵也表示他們想要控制游戲或者音樂(lè)的音量。
當(dāng)你的音頻流調(diào)整聲音的時(shí)候你可能想要監(jiān)聽(tīng)音量鍵的按下事件。Android提供了便利的方法setVolumeControlStream(int)去直接監(jiān)聽(tīng)你指定的音頻流的音量鍵的按下事件
確定你的應(yīng)用將使用音頻流,你因該設(shè)置方法到目標(biāo)音頻流上。該方法你應(yīng)該在應(yīng)用生命周期的早期調(diào)用,因?yàn)樵贏ctivity的生命周期你只需要調(diào)用一次,典型的,你應(yīng)該在控制你音頻流的Activity或者Fragment的onCreate方法中調(diào)用,這確保了一旦你的app可見(jiàn),音量控制功能也如用戶期望的可用
setVolumeControlStream(AudioManager.STREAM_MUSIC);
從這時(shí)起,當(dāng)目標(biāo)Activity或者Fragment可見(jiàn)的時(shí)候,在設(shè)備上按下音量鍵會(huì)影響你指定的音頻流。使用物理播放鍵去控制你的App音頻的播放
Use Hardware Playback Control Keys to Control Your App’s Audio Playback
當(dāng)用戶按下這些硬件按鈕(比如:耳機(jī),許多已連接或者無(wú)線連接的頭戴耳機(jī)的播放,暫停,停止,下一首,上一首),系統(tǒng)會(huì)廣播一個(gè)帶ACTION_MEDIA_BUTTON行為的Intent,為了回應(yīng)音頻設(shè)備按鈕的點(diǎn)擊,你需要在你的manifest中注冊(cè)一個(gè)BroadcastReceiver去監(jiān)聽(tīng)這個(gè)行為的廣播,如以下示例代碼
<receiver android:name=".RemoteControlReceiver">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</receiver>
接收器實(shí)現(xiàn)本身需要提取哪個(gè)鍵被按下來(lái)引起的廣播,Intent中的EXTRA_KEY_EVENT鍵下包括此鍵,而KeyEvent類(lèi)中包括一系列表示媒體按鈕的靜態(tài)常量KEYCODE_MEDIA_ *列表,例如KEYCODE_MEDIA_PLAY_PAUSE和KEYCODE_MEDIA_NEXT。以下片段顯示如何提取按下的媒體按鈕,并相應(yīng)地影響媒體播放。
public class RemoteControlReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (Intent.ACTION_MEDIA_BUTTON.equals(intent.getAction())) {
KeyEvent event = (KeyEvent)intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
if (KeyEvent.KEYCODE_MEDIA_PLAY == event.getKeyCode()) {
// Handle key press.
}
}
}
}
因?yàn)榭赡芏鄠€(gè)應(yīng)用想要監(jiān)聽(tīng)media按鈕的按下,所以你還必須通過(guò)程序控制應(yīng)用程序何時(shí)應(yīng)該接收媒體按鈕按下事件。
以下代碼可在你的應(yīng)用程序中使用AudioManager注冊(cè)和注銷(xiāo)媒體按鈕事件接收器。 注冊(cè)后,你的廣播接收器是所有media按鈕廣播的專(zhuān)屬接收器。
AudioManager am = mContext.getSystemService(Context.AUDIO_SERVICE);
...// Start listening for button
pressesam.registerMediaButtonEventReceiver(RemoteControlReceiver);
...// Stop listening for button
pressesam.unregisterMediaButtonEventReceiver(RemoteControlReceiver);
通常,apps應(yīng)當(dāng)在它變得非活動(dòng)或者被可見(jiàn)的時(shí)候注銷(xiāo)它們的接收器(比如onStop回調(diào)中),然而,對(duì)于媒體播放應(yīng)用程序來(lái)說(shuō)并不簡(jiǎn)單,事實(shí)上,當(dāng)應(yīng)用程序不可見(jiàn)且無(wú)法通過(guò)屏幕上的UI控制時(shí),響應(yīng)媒體播放按鈕是最重要的。更好的方法是在應(yīng)用程序獲取音頻焦點(diǎn)時(shí)注冊(cè)media按鈕事件接收器,丟失音頻焦點(diǎn)時(shí)注銷(xiāo)media按鈕事件接收器。
2.Managing Audio Focus
有多個(gè)應(yīng)用程序可能播放音頻,重要的是要考慮他們應(yīng)該如何交互。 為了避免每個(gè)音樂(lè)應(yīng)用程序同時(shí)播放,Android使用音頻焦點(diǎn)來(lái)控制音頻播放 - 只有擁有音頻焦點(diǎn)的應(yīng)用程序才能播放音頻。
在您的應(yīng)用程序開(kāi)始播放音頻之前,應(yīng)該請(qǐng)求并接收音頻焦點(diǎn)。 同樣,它應(yīng)該知道如何監(jiān)聽(tīng)音頻焦點(diǎn)的丟失,并在發(fā)生這種情況時(shí)適當(dāng)?shù)刈龀龇磻?yīng)。
Request the Audio Focus
在你的app開(kāi)始播放任何音頻前,app應(yīng)該為將要播放的流獲取到音頻焦點(diǎn)。這是通過(guò)調(diào)用requestAudioFocus()完成的,如果您的請(qǐng)求成功,它返回AUDIOFOCUS_REQUEST_GRANTED。
你必須指定你要使用的流,以及是需要暫時(shí)還是永久的音頻焦點(diǎn)。 當(dāng)你希望只在短時(shí)間內(nèi)播放音頻時(shí)則請(qǐng)求暫時(shí)聚焦(例如在播放導(dǎo)航指示時(shí))。 當(dāng)您計(jì)劃在可預(yù)見(jiàn)的未來(lái)播放音頻時(shí)則請(qǐng)求永久音頻聚焦(例如,在播放音樂(lè)時(shí))。
以下片段會(huì)為音樂(lè)音頻流請(qǐng)求的永久音頻焦點(diǎn)。 你應(yīng)該在開(kāi)始播放之前立即請(qǐng)求音頻焦點(diǎn),例如當(dāng)用戶按下播放或下一個(gè)游戲關(guān)卡的背景音樂(lè)開(kāi)始前。
AudioManager am = mContext.getSystemService(Context.AUDIO_SERVICE);
...// Request audio focus for playback
int result = am.requestAudioFocus(afChangeListener,
// Use the music stream.
AudioManager.STREAM_MUSIC,
// Request permanent focus.
AudioManager.AUDIOFOCUS_GAIN);
if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
am.registerMediaButtonEventReceiver(RemoteControlReceiver);
// Start playback.
}
一旦你完成播放,請(qǐng)確定調(diào)用了abandonAudioFocus()方法,這將通知系統(tǒng)你不在需要焦點(diǎn)并注銷(xiāo)相關(guān)聯(lián)的AudioManager.OnAudioFocusChangeListener。在放棄暫時(shí)焦點(diǎn)的情況下,這允許任何被中斷的app繼續(xù)播放
// Abandon audio focus when playback
completeam.abandonAudioFocus(afChangeListener);
當(dāng)請(qǐng)求暫時(shí)音頻焦點(diǎn)時(shí),您有一個(gè)額外選項(xiàng):是否要啟用“低音”(“Ducking”)。 通常,當(dāng)優(yōu)秀的音頻應(yīng)用程序失去音頻焦點(diǎn)時(shí),它會(huì)立即停止它的播放。 通過(guò)請(qǐng)求一個(gè)暫時(shí)音頻焦點(diǎn),你告訴其他音頻應(yīng)用程序你可以接受他們繼續(xù)播放,只要他們降低音量,直到焦點(diǎn)回到他們。
// Request audio focus for playback
int result = am.requestAudioFocus(afChangeListener,
// Use the music stream.
AudioManager.STREAM_MUSIC,
// Request permanent focus.
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK);
if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
// Start playback.
}
低音尤其適用于間歇性使用音頻流的應(yīng)用程序,例如用于可聽(tīng)見(jiàn)的駕駛方向。
每當(dāng)另一個(gè)應(yīng)用程序像之前描述的那樣請(qǐng)求音頻焦點(diǎn),你在請(qǐng)求焦點(diǎn)時(shí)注冊(cè)的偵聽(tīng)器就會(huì)接收另一個(gè)應(yīng)用選擇的永久或暫時(shí)的音頻焦點(diǎn)。
Handle the Loss of Audio Focus
如果你的應(yīng)用程序可以請(qǐng)求音頻焦點(diǎn),那么當(dāng)其他應(yīng)用程序請(qǐng)求焦點(diǎn)時(shí),它會(huì)轉(zhuǎn)而失去焦點(diǎn)。 你的應(yīng)用程序如何響應(yīng)音頻焦點(diǎn)的丟失取決于丟失的方式。
音頻焦點(diǎn)改變監(jiān)聽(tīng)器的回調(diào)方法onAudioFocusChange()有一個(gè)參數(shù)它描述了焦點(diǎn)改變事件。具體來(lái)說(shuō),可能的焦點(diǎn)丟失事件來(lái)自前焦點(diǎn)請(qǐng)求類(lèi)型一部分 - 永久丟失,暫時(shí)丟失和暫時(shí)被允許的ducking。
如果音頻焦點(diǎn)永久的丟失,假如另一個(gè)應(yīng)用正在使用音頻,那么你的app應(yīng)該快速的結(jié)束音頻的使用,實(shí)際上,這意味著停止播放,移除音頻按鈕監(jiān)聽(tīng)器-允許新的音頻播放器去單獨(dú)處理這些事件-并丟棄你的音頻焦點(diǎn)。這時(shí),你將期待一個(gè)用戶行為(在你的app中按下播放)去請(qǐng)求音頻焦點(diǎn)在你恢復(fù)音頻播放前
在以下的代碼片段中,我們暫停播放或者我們的音頻播放器暫時(shí)丟失了焦點(diǎn),那么當(dāng)我們獲取焦點(diǎn)時(shí)恢復(fù)它。如果是永久丟失,那么注銷(xiāo)我們的音頻按鈕事件接收器并停止監(jiān)聽(tīng)音頻焦點(diǎn)改變
AudioManager.OnAudioFocusChangeListener afChangeListener = new AudioManager.OnAudioFocusChangeListener() {
public void onAudioFocusChange(int focusChange) {
if (focusChange == AUDIOFOCUS_LOSS_TRANSIENT) {
// Pause playback
} else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {
// Resume playback
} else if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {
am.unregisterMediaButtonEventReceiver(RemoteControlReceiver);
am.abandonAudioFocus(afChangeListener);
// Stop playback
}
}
};
在ducking被允許的情況下暫時(shí)丟失音頻焦點(diǎn),相對(duì)于暫停播放,你可以使用duck代替
Duck!
低音是降低音頻流輸出音量的過(guò)程,使來(lái)自另一個(gè)應(yīng)用程序暫時(shí)的音頻更容易聽(tīng)到,而不會(huì)完全中斷你自己的應(yīng)用程序的音頻。
在以下代碼段中,當(dāng)我們暫時(shí)失去音頻焦點(diǎn)時(shí),降低媒體播放器對(duì)象上的音量,然后在我們重新獲得焦點(diǎn)時(shí)將其返回到原來(lái)的音量。
OnAudioFocusChangeListener afChangeListener = new OnAudioFocusChangeListener() {
public void onAudioFocusChange(int focusChange) {
if (focusChange == AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) {
// Lower the volume
} else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {
// Raise it back to normal
}
}
};
3.Dealing With Audio Output Hardware
當(dāng)用戶從他們的Android設(shè)備享受音頻時(shí),用戶有很多選擇。 大多數(shù)設(shè)備都有內(nèi)置揚(yáng)聲器,有線耳機(jī)的耳機(jī)插孔,許多設(shè)備還具有藍(lán)牙連接和支持A2DP音頻。
Check What Hardware is Being Used
你的應(yīng)用程序的行為可能會(huì)受到路由到的硬件輸出的影響。
您可以查詢AudioManager以確定音頻當(dāng)前是否路由到設(shè)備揚(yáng)聲器,有線耳機(jī)或連接的藍(lán)牙設(shè)備,如以下代碼段所示:
if (isBluetoothA2dpOn()) {
// Adjust output for Bluetooth.
} else if (isSpeakerphoneOn()) {
// Adjust output for Speakerphone.
} else if (isWiredHeadsetOn()) {
// Adjust output for headsets
} else {
// If audio plays and noone can hear it, is it still playing?
}
Handle Changes in the Audio Output Hardware
當(dāng)耳機(jī)拔掉或藍(lán)牙設(shè)備斷開(kāi)連接時(shí),音頻流自動(dòng)重新路由到內(nèi)置揚(yáng)聲器。 如果你使用最大音量聽(tīng)你的音樂(lè),這可能是一個(gè)噪音。
幸運(yùn)的是,當(dāng)發(fā)生這種情況時(shí),系統(tǒng)廣播ACTION_AUDIO_BECOMING_NOISY的Intent。 當(dāng)你在播放音頻時(shí)注冊(cè)一個(gè)BroadcastReceiver監(jiān)聽(tīng)這個(gè)意圖是一個(gè)好習(xí)慣。 在音樂(lè)播放器的情況下,用戶通常期望暫停播放,而對(duì)于游戲,可以選擇顯著降低音量。
private class NoisyAudioStreamReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (AudioManager.ACTION_AUDIO_BECOMING_NOISY.equals(intent.getAction())) {
// Pause the playback
}
}
}
private IntentFilter intentFilter = new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
private void startPlayback() {
registerReceiver(myNoisyAudioStreamReceiver(), intentFilter);
}
private void stopPlayback() {
unregisterReceiver(myNoisyAudioStreamReceiver);
}
小實(shí)現(xiàn)
public class UseAudioActivity extends AppCompatActivity implements AudioManager.OnAudioFocusChangeListener {
private Button playAudio,pauseAudio,stopAudio;
private MediaPlayer mp;
private AudioManager audioManager;
private NoisyBroadCastReceiver receiver;
private boolean isPrepared;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_use_audio);
playAudio = (Button) findViewById(R.id.play_audio);
pauseAudio = (Button) findViewById(R.id.pause_audio);
stopAudio = (Button) findViewById(R.id.stop_audio);
playAudio.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mp != null && isPrepared){
mp.start();
}else{
requestAudioFocus();
}
}
});
pauseAudio.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mp != null){
mp.pause();
}
}
});
stopAudio.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mp != null){
mp.stop();
//if you do this,please prepare again
}
}
});
requestAudioFocus();
IntentFilter filter = new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
receiver = new NoisyBroadCastReceiver();
this.registerReceiver(receiver,filter);
}
public void requestAudioFocus(){
audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
int result = audioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
if (result != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
// could not get audio focus.
Toast.makeText(this,"can't play audio now,try later",Toast.LENGTH_SHORT).show();
}else {
initMediaPlayer();
}
}
@Override
public void onAudioFocusChange(int focusChange) {
Log.e("Audio","is change focus " + focusChange);
switch (focusChange) {
case AudioManager.AUDIOFOCUS_GAIN:
// resume playback
if (mp == null){
initMediaPlayer();
} else if (!mp.isPlaying()) {
mp.start();
mp.setVolume(1.0f, 1.0f);
}
break;
case AudioManager.AUDIOFOCUS_LOSS:
// Lost focus for an unbounded amount of time: stop playback and release media player
if (mp != null) {
if (mp.isPlaying()) mp.stop();
mp.release();
mp = null;
}
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
// Lost focus for a short time, but we have to stop
// playback. We don't release the media player because playback
// is likely to resume
if (mp != null) {
if (mp.isPlaying()) mp.pause();
}
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
// Lost focus for a short time, but it's ok to keep playing
// at an attenuated level
if (mp != null) {
if (mp.isPlaying()) mp.setVolume(0.1f, 0.1f);
}
break;
}
}
public void initMediaPlayer(){
try {
mp = new MediaPlayer();
mp.setAudioStreamType(AudioManager.STREAM_MUSIC);
AssetFileDescriptor afd = this.getResources().openRawResourceFd(R.raw.neighborhood);
if (afd == null){
throw new Exception("res is not found");
}
mp.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
afd.close();
setPrepareListener();
mp.prepareAsync();
} catch (Exception e) {
e.printStackTrace();
}
}
public void setPrepareListener(){
if (mp != null){
mp.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
isPrepared = true;
}
});
}
}
public class NoisyBroadCastReceiver extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(AudioManager.ACTION_AUDIO_BECOMING_NOISY)){
Log.e("Audio","ouch,the headset is out,did you do it?");
if (mp != null && mp.isPlaying()){
mp.pause();
}
}
}
}
@Override
protected void onStop() {
super.onStop();
if (mp != null){
if(mp.isPlaying()) mp.stop();
mp.release();
mp = null;
}
if(audioManager != null){
audioManager.abandonAudioFocus(this);
}
if(receiver != null) {
this.unregisterReceiver(receiver);
}
}
}