天天看點

Android開發指南(34) —— Multimedia and Camera - Media Playback

前言

聲明

  歡迎轉載,但請保留文章原始出處:) 

媒體播放

譯者署名: 呆呆大蝦

版本:android 4.0 r1

原文

<a href="http://developer.android.com/guide/topics/media/mediaplayer.html">http://developer.android.com/guide/topics/media/mediaplayer.html</a>

在本文中

<a href="http://www.cnblogs.com/over140/archive/2011/11/17/2252210.html#basic">簡介</a>

<a href="http://www.cnblogs.com/over140/archive/2011/11/17/2252210.html#manifest">manifest聲明</a>

<a href="http://www.cnblogs.com/over140/archive/2011/11/17/2252210.html#mediaplayer">mediaplayer的使用</a>

<a href="http://www.cnblogs.com/over140/archive/2011/11/17/2252210.html#async_prep">異步準備</a>

<a href="http://www.cnblogs.com/over140/archive/2011/11/17/2252210.html#state">狀态管理</a>

<a href="http://www.cnblogs.com/over140/archive/2011/11/17/2252210.html#release">釋放mediaplayer</a>

<a href="http://www.cnblogs.com/over140/archive/2011/11/17/2252210.html#service">使用帶mediaplayer的服務</a>

<a href="http://www.cnblogs.com/over140/archive/2011/11/17/2252210.html#async_run">異步運作</a>

<a href="http://www.cnblogs.com/over140/archive/2011/11/17/2252210.html#async_error">異步錯誤的處理</a>

<a href="http://www.cnblogs.com/over140/archive/2011/11/17/2252210.html#wakelock">wake lock的使用</a>

<a href="http://www.cnblogs.com/over140/archive/2011/11/17/2252210.html#run_as_service">作為背景服務運作</a>

<a href="http://www.cnblogs.com/over140/archive/2011/11/17/2252210.html#cleanup">進行清理</a>

<a href="http://www.cnblogs.com/over140/archive/2011/11/17/2252210.html#contentresolver">從content resolver中讀取媒體</a>

關鍵類

<a href="http://developer.android.com/reference/android/media/mediaplayer.html">mediaplayer</a>

<a href="http://developer.android.com/reference/android/media/audiomanager.html">audiomanager</a>

<a href="http://developer.android.com/reference/android/media/soundpool.html">soundpool</a>

參閱

<a href="http://developer.android.com/guide/topics/media/jetplayer.html">jetplayer</a>

<a href="http://developer.android.com/guide/topics/media/audio-capture.html">音頻捕獲</a>

<a href="http://developer.android.com/guide/appendix/media-formats.html">android支援的媒體格式</a>

<a href="http://developer.android.com/guide/topics/data/data-storage.html">資料存儲</a>

本文示範了如何編寫一個媒體播放程式。為了兼顧良好的性能和舒适的使用者體驗,它還實作了播放期間使用者和系統之間的互動。

注意: 隻能在标準的輸出裝置上播放音頻資料,目前即為移動裝置的揚聲器或藍牙耳機。并且不能在通話期間同時播放音頻檔案。

簡介

下列類用于在android架構中播放音視訊:

本類是播放音視訊的主要api。

本類管理音頻源和裝置的音頻輸出。

manifest聲明

在開始開發mediaplayer的應用程式之前,請確定manifest已經正确地聲明了以下相關feature:

·       internet permission —— 如果正在用mediaplayer來播放基于網絡的流媒體,應用程式必須請求網絡通路權限。

&lt;uses-permission android:name="android.permission.internet" /&gt;

&lt;uses-permission android:name="android.permission.wake_lock" /&gt;

媒體播放器的使用

·       本地資源

·       内部uri,比如可能來自content resolver

·       外部url(流)

下面的例子展示了如何播放本地以裸資源方式提供的音頻(儲存于程式的res/raw/目錄下):

mediaplayer mediaplayer = mediaplayer.create(context, r.raw.sound_file_1); 

mediaplayer.start(); // 不必調用prepare(); create()會自動調用

這裡的“裸”資源是指系統不會以任何特定方式進行解析的檔案。當然,這個資源的内容不應該是原始音頻資料,而應是用所支援的格式正确編碼并格式化過的媒體檔案。

下面是如何播放來自系統本地提供的uri資源(比如通過content resolver擷取的):

uri myuri = ....; // 在此初始化uri

mediaplayer mediaplayer = new mediaplayer(); 

mediaplayer.setaudiostreamtype(audiomanager.stream_music); 

mediaplayer.setdatasource(getapplicationcontext(), myuri); 

mediaplayer.prepare(); 

mediaplayer.start();

下例是播放來自遠端url的http流:

string url = "http://........"; // 在此指定url

mediaplayer.setdatasource(url); 

mediaplayer.prepare(); // 可能會耗時很長! (需建立緩存等)mediaplayer.start();

注意:如果通過url來傳送一個來自線上媒體檔案的資料流,則該檔案必須支援漸進下載下傳(progressive download)。

<a>異步準備</a>

<a>狀态管理</a>

mediaplayer.release(); 

mediaplayer = null;

使用帶mediaplayer的服務

<a>異步運作</a>

public class myservice extends service implements mediaplayer.onpreparedlistener { 

    private static final 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 = ... // 在此初始化

            mmediaplayer.setonpreparedlistener(this); 

            mmediaplayer.prepareasync();

            // 為了不阻塞主線程而異步準備

        } 

    } 

    /** 由mediaplayer準備完畢後調用 */ 

    public void onprepared(mediaplayer player) { 

        player.start(); 

}

<a>異步錯誤的處理</a>

public class myservice extends service implements mediaplayer.onerrorlistener { 

    mediaplayer mmediaplayer; 

    public void initmediaplayer() { 

        // ...在此初始化mediaplayer... 

        mmediaplayer.setonerrorlistener(this); 

    @override 

    public boolean onerror(mediaplayer mp, int what, int extra) { 

        // ... 合适地處理 ... 

        // mediaplayer已經切換到error狀态,必須重新開機! 

如果應用程式是為背景播放媒體而設計的,那麼即使服務仍在運作,但裝置可能會進入休眠狀态。因為裝置休眠時android系統會嘗試節省電力,任何不必要的手機功能将會關閉,包括cpu和wifi部件。但是,如果服務正在播放音樂或讀取音樂資料流,就需要防止系統對播放進行幹擾。

為了確定服務在上述情況下能維持正常運作,必須使用“喚醒鎖”(wake lock)。wake lock是一種通知系統的途徑:表示應用程式需要用到一些手機空閑時也保持可用的功能。

注意: 應該盡量少用wake lock,并且僅當确實需要時才保持鎖定狀态,因為它會顯著減少裝置的電池壽命。

mmediaplayer = new mediaplayer(); 

// ... 在此執行其它初始化工作 ... 

mmediaplayer.setwakemode(getapplicationcontext(), powermanager.partial_wake_lock);

wifilock wifilock = ((wifimanager) getsystemservice(context.wifi_service)) 

    .createwifilock(wifimanager.wifi_mode_full, "mylock"); 

wifilock.acquire();

如果暫停或停止播放媒體,或者不再需要使用網絡,就應該及時釋放該鎖:

wifilock.release();

<a>作為背景服務運作</a>

服務經常用于執行一些背景任務,比如讀取郵件、同步資料、下載下傳資料等。這些情況下,使用者不會明顯察覺服務正在運作,甚至可能都不會注意到某些服務曾被中止而過段時間又重新開始運作。

但是就播放音樂的服務而言,顯然這是使用者能明顯察覺的服務,任何中斷都會顯著影響到使用者的體驗。此外,該服務還是使用者可能期望與其互動的服務。在這種情況下,此服務應該作為“背景服務”來運作。背景服務保持較高的系統重要性級别——系統幾乎永遠都不會關閉服務,因為服務對于使用者而言至關重要。即使是運作在背景,服務仍必須提供狀态欄通知,以保證使用者知曉服務正在運作,并允許使用者打開與服務互動的activity。

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);

圖1展示了通知如何顯示給使用者:

Android開發指南(34) —— Multimedia and Camera - Media Playback
Android開發指南(34) —— Multimedia and Camera - Media Playback

圖1.背景服務通知的螢幕截圖,左圖是狀态欄的通知圖示,右圖是展開的view。

stopforeground(true);

雖然在任一給定時刻隻能運作一個activity,android仍是一個多任務環境。這向使用音頻的應用程式提出了一個特殊的挑戰,因為系統隻有一路音頻輸出,但可能會存在多個媒體服務,它們會互相争奪這個音頻輸出的使用權。android 2.2之前,沒有什麼内部機制來解決這個問題,某些情況下這可能會導緻使用者體驗很糟糕。比如在使用者聽音樂時,其它應用程式需要通知使用者一個非常重要的事件,使用者可能會由于音樂聲音較大而聽不到通知提示音。自android 2.2開始,系統為應用程式提供了一種使用裝置音頻輸出的協調機制。這種機制叫做audio focus。

當應用程式需要輸出諸如音樂或通知音之類的音頻時,應該總是提出audio focus請求。一但獲得了focus,就可以自由使用音頻輸出,但應該時刻注意focus的變化情況。一旦接到放棄audio focus的通知,就應該立即關閉音頻或者把音量調低至靜音狀态(正如“ducking”——此标志表明哪一個更合适),并且在再次獲得focus之後才能恢複正常音量播放。

audio focus事實上具有良好的協作性。也就是說,雖然期望(強烈建議)應用程式能遵守audio focus規則,但此規則并不是系統強制要求的。如果應用程式需要在失去audio focus後也能以正常音量播放音樂,系統也不會阻止。但是使用者體驗很可能會很糟糕,并且很可能會删除這種不夠禮貌的應用程式。

audiomanager audiomanager = (audiomanager) getsystemservice(context.audio_service); 

int result = audiomanager.requestaudiofocus(this, audiomanager.stream_music, 

    audiomanager.audiofocus_gain); 

if (result != audiomanager.audiofocus_request_granted) { 

    // 無法擷取audio focus. 

class myservice extends service 

                           implements audiomanager.onaudiofocuschangelistener { 

    // .... 

    public void onaudiofocuschange(int focuschange) { 

        // 根據focus的改變進行處理... 

以下是實作的例子:

public void onaudiofocuschange(int focuschange) { 

    switch (focuschange) { 

        case audiomanager.audiofocus_gain: 

            // 恢複播放

            if (mmediaplayer == null) initmediaplayer(); 

            else if (!mmediaplayer.isplaying()) mmediaplayer.start(); 

            mmediaplayer.setvolume(1.0f, 1.0f); 

            break; 

        case audiomanager.audiofocus_loss: 

            // 長時間失去focus:停止播放并釋放media player 

            if (mmediaplayer.isplaying()) mmediaplayer.stop(); 

            mmediaplayer.release(); 

            mmediaplayer = null; 

        case audiomanager.audiofocus_loss_transient: 

            // 暫時失去focus,但必須停止播放

            // 可能會很快恢複播放,是以不釋放media player

            if (mmediaplayer.isplaying()) mmediaplayer.pause(); 

        case audiomanager.audiofocus_loss_transient_can_duck: 

            // 暫時失去focus,但可以保持較低級别的播放

            if (mmediaplayer.isplaying()) mmediaplayer.setvolume(0.1f, 0.1f); 

請記住audio focus api在api level 8 (android 2.2)以上版本才可用。假如需要支援較低版本的android,應該采取向後相容的方案,使得程式能在獲得支援時使用此功能、未獲支援時則向下平滑過渡。

通過反射機制調用audio focus方法,或者在單獨的類中(叫做audiofocushelper)實作全部audio focus功能,就可以獲得良好的向後相容性。以下是這種類的一個示例:

public class audiofocushelper implements audiomanager.onaudiofocuschangelistener { 

    audiomanager maudiomanager; 

// 在此寫入其它内容,

// 将儲存一個接口的引用,用于通知服務focus 已發生改變。

    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() { 

            maudiomanager.abandonaudiofocus(this); 

        // 服務獲知focus的變動

僅當檢測到系統運作于api level 8以上版本時,才可以建立audiofocushelper類的執行個體。比如:

if (android.os.build.version.sdk_int &gt;= 8) { 

    maudiofocushelper = new audiofocushelper(getapplicationcontext(), this); 

} else { 

    maudiofocushelper = null; 

<a>進行清理</a>

public class myservice extends service { 

   mediaplayer mmediaplayer; 

   // ... 

   @override 

   public void ondestroy() { 

       if (mmediaplayer != null) mmediaplayer.release(); 

   } 

對意圖audio_becoming_noisy的處理

在事件到來時,很多編碼優秀的音頻播放程式都會自動停止播放,因為事件會讓音頻輸出産生噪音(通過外部揚聲器播放出來)。比如,使用者用耳機聽音樂時突然把耳機從裝置上拔出來,就可能會産生噪音。不過好在這種現象不會自動發生。如果不實作暫停播放,聲音就會從外部揚聲器中傳出,這可能是使用者不願意聽到的。

&lt;receiver android:name=".musicintentreceiver"&gt; 

   &lt;intent-filter&gt; 

      &lt;action android:name="android.media.audio_becoming_noisy" /&gt; 

   &lt;/intent-filter&gt; 

&lt;/receiver&gt;

以下代碼注冊了musicintentreceiver類,用作該意圖的廣播接收器:

public class musicintentreceiver implements android.content.broadcastreceiver { 

   public void onreceive(context ctx, intent intent) { 

      if (intent.getaction().equals( 

                    android.media.audiomanager.action_audio_becoming_noisy)) { 

          // 通知服務來停止播放

          // (比如通過一個意圖)

      } 

從content resolver中讀取媒體

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) { 

    // 查詢失敗,處理錯誤。

} else if (!cursor.movetofirst()) { 

    //裝置上不存在媒體檔案

    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); 

       // ...開始處理... 

    } while (cursor.movetonext()); 

long id = /* 從某處讀取 */; 

uri contenturi = contenturis.withappendedid( 

        android.provider.mediastore.audio.media.external_content_uri, id); 

mmediaplayer.setaudiostreamtype(audiomanager.stream_music); 

mmediaplayer.setdatasource(getapplicationcontext(), contenturi); 

// ...準備并開始播放...

轉載:http://www.cnblogs.com/over140/archive/2011/11/17/2252210.html

繼續閱讀