Manage Audio

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);
        }
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容