Android媒體播放的基礎(chǔ)知識

1.前言


媒體播放是Android開發(fā)中常見的功能符喝,就拿視頻來說招狸,通常都是基于封裝好的框架做二次開發(fā)兄世,主要任務(wù)是界面的展示和用戶交互啼辣,無需考慮視頻是否支持,資源如何讀取御滩,保持屏幕常亮和防止設(shè)備休眠等鸥拧。這樣適合快速開發(fā),但那些不以視頻為主要業(yè)務(wù)的怎么辦艾恼?要知道成熟的視頻框架體積都不小住涉,殺雞焉用宰牛刀。
  其實Android系統(tǒng)已經(jīng)支持大部分的媒體資源钠绍,對于播放要求不高的舆声,可以直接使用自帶的API開發(fā),還避免設(shè)備不兼容問題。

2.媒體概念


媒體是指傳播信息的媒介媳握,在Android設(shè)備上包括圖片碱屁、音頻和視頻。拆文解字一下蛾找,有三個關(guān)鍵點:信息娩脾、媒介和傳播,對設(shè)備而言就是內(nèi)容編解碼打毛、容器格式和網(wǎng)絡(luò)協(xié)議柿赊。為什么要說這些孩革,因為平時大家所接觸到的資源文件就是為了能廣泛地被使用而基于這三點設(shè)計出來的廉油,是能否在設(shè)備上播放和如何播放的決定因素,像PNG烫饼、MP3熬甫、MP4都是文件類型胰挑,可以認(rèn)為是方便大家去稱呼的。
  為了容易理解椿肩,舉個例子吧瞻颂。南方潮濕、北方干燥郑象,想把空氣中的水汽由南運(yùn)到北贡这,并沿途分發(fā)水資源,怎么辦:

辦法 水汽 媒體
減少體積厂榛,方便運(yùn)輸 對水汽壓縮形成水 使用算法將信息編碼成更少的字節(jié)
使用容器裝載 不同材質(zhì)藕坯、不同形狀的杯子 不同文件類型所包含的不同后綴名的文件
動態(tài)調(diào)整使用的資源 根據(jù)當(dāng)?shù)厝彼闆r分發(fā) 根據(jù)網(wǎng)絡(luò)情況切換碼率和緩沖大小

知道了媒體的組成,也就能理解官網(wǎng)指南中給出的支持媒體格式表中數(shù)據(jù)的含義了噪沙。大家可以看看,知道什么情況下不用引入框架吐根。

3.API及權(quán)限


在Android系統(tǒng)中播放音頻和視頻主要是以下兩個類:

  • MediaPlayer正歼,播放音頻和視頻的邏輯由它實現(xiàn)。
  • AudioManager拷橘,管理音頻資源和在設(shè)備上的輸出局义。

當(dāng)播放的資源來自網(wǎng)絡(luò)中的數(shù)據(jù)流時,添加網(wǎng)絡(luò)訪問權(quán)限:

<uses-permission android:name="android.permission.INTERNET" />

當(dāng)需要播放時屏幕常亮或阻止設(shè)備休眠時冗疮,添加喚醒鎖權(quán)限:

<uses-permission android:name="android.permission.WAKE_LOCK" />

MediaPlayer.setScreenOnWhilePlaying(); // 若是視頻萄唇,保持屏幕常量

4.播放流程


4.1.播放不同的來源

使用MediaPlayer可以簡單完成獲取、解碼和播放操作术幔。不過根據(jù)資源來源不同另萤,寫法略有不同。

  • 本地資源(Raw資源)
MediaPlayer mediaPlayer = MediaPlayer.create(context, R.raw.sound_file_1);
mediaPlayer.start(); // no need to call prepare(); create() does that for you
  • 內(nèi)部URIs(設(shè)備文件系統(tǒng),可通過Content Resolver獲得)
Uri myUri = ....; // initialize Uri here
MediaPlayer mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.setDataSource(getApplicationContext(), myUri);
mediaPlayer.prepare();
mediaPlayer.start();
  • 網(wǎng)絡(luò)URLs(數(shù)據(jù)流)
// 由于緩存需要四敞,數(shù)據(jù)源必須支持邊下邊播
String url = "http://........"; // your URL here
MediaPlayer mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.setDataSource(url);
mediaPlayer.prepare(); // might take long! (for buffering, etc)
mediaPlayer.start();
4.2.異步準(zhǔn)備資源

資源的解碼是比較耗時的泛源,尤其是來自網(wǎng)絡(luò)的視頻,緩沖不穩(wěn)定且解碼量大忿危。不建議將 prepare() 方法放在UI線程中處理达箍,因為阻塞超過十分之一秒就會引起用戶反感。系統(tǒng)提供另一個方法 prepareAsync() 來替代它铺厨, 先通過 setOnPreparedListener() 方法給MediaPlayer設(shè)置監(jiān)聽缎玫,
當(dāng)資源準(zhǔn)備完成后將回調(diào) MediaPlayer.OnPreparedListener 接口的 onPrepared() 方法,執(zhí)行你寫的播放邏輯解滓。

4.3.管理播放狀態(tài)

MediaPlayer的播放流程嚴(yán)格按照當(dāng)前的狀態(tài)一步一步執(zhí)行赃磨,只要不對就會報錯,請記住下面的圖伐蒂。


State.png
4.4.釋放資源

媒體播放是很耗資源的煞躬,當(dāng)不用時,記得通過以下代碼釋放它:

mediaPlayer.release();
mediaPlayer = null;

根據(jù)情況在 onStart()逸邦、onResume()恩沛、onRestart() 方法中創(chuàng)建,在 onStop()缕减、onDestory() 方法中釋放雷客。有種情況得注意,當(dāng)改變手機(jī)方向或者配置時會重啟界面桥狡,若不及時釋放搅裙,幾次就會占滿資源,詳見 Handling Runtime Changes裹芝。

5.后臺播放


關(guān)于這塊其實邏輯比較固定部逮,系統(tǒng)提供了MediaXXXCompat系列API幫助快速實現(xiàn),詳見huyongl1989的文章嫂易。那自己如何實現(xiàn)呢兄朋?

5.1.消息監(jiān)聽

直接將代碼移到Service中,不過需要注意的是Service默認(rèn)和Activity一樣運(yùn)行在主線程怜械,對于耗時操作也一樣得異步處理颅和。

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

和異步準(zhǔn)備資源一樣,對于錯誤也要異步通知缕允,不過監(jiān)聽器改為 MediaPlayer.OnErrorListener峡扩。

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!
    }
}

由于播放控制涉及Activity和Service的交互,不熟的看看圣騎士Wind的博客障本。

5.2.喚醒鎖

當(dāng)應(yīng)用程序在后臺播放時教届,設(shè)備仍可能進(jìn)入休眠,而系統(tǒng)此狀態(tài)下為了省電,嘗試關(guān)閉任何非必需功能巍佑,包括CPU和WiFi茴迁。所以得通過喚醒鎖告訴系統(tǒng)別休眠,但是會明顯減少電池使用時間萤衰,建議只在必需情況下使用堕义。相關(guān)代碼如下:

mMediaPlayer = new MediaPlayer();
// ... other initialization here ...
mMediaPlayer.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK); // CPU不休眠

WifiLock wifiLock = ((WifiManager) getSystemService(Context.WIFI_SERVICE))
    .createWifiLock(WifiManager.WIFI_MODE_FULL, "mylock");

wifiLock.acquire(); // WiFi不休眠

在MediaPlayer暫停或停止播放時脆栋,會自動釋放CPU鎖倦卖,不再需要WiFi功能時,得主動釋放鎖椿争,wifiLock.release()怕膛。

5.3.執(zhí)行清理

垃圾回收機(jī)制只對內(nèi)存敏感,可能會花不短時間才能清理MediaPlayer秦踪,所以 onDestory() 方法中記得釋放褐捻。

public class MyService extends Service {
   MediaPlayer mMediaPlayer;
   // ...

   @Override
   public void onDestroy() {
       if (mMediaPlayer != null) mMediaPlayer.release();
   }
}

對于短時間的停止播放就別清理了,重建也是挺耗資源的椅邓。

6.通過ContentResolver檢索資源


設(shè)備的媒體庫中有許多資源柠逞,可以通過以下代碼檢索:

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

檢索到后,拿ID拼接URI景馁,進(jìn)行播放:

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...

7.總結(jié)


這里僅僅講了媒體的播放板壮,那如何展示呢?系統(tǒng)提供了VideoView來播放視頻合住,它繼承SurfaceView來保證高效地渲染影片绰精,內(nèi)部封裝了MediaPlayer來獲取資源并監(jiān)聽控制。大家有興趣可以看看透葛,便于理解視頻播放笨使。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市僚害,隨后出現(xiàn)的幾起案子阱表,更是在濱河造成了極大的恐慌,老刑警劉巖贡珊,帶你破解...
    沈念sama閱讀 217,542評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異涉馁,居然都是意外死亡门岔,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評論 3 394
  • 文/潘曉璐 我一進(jìn)店門烤送,熙熙樓的掌柜王于貴愁眉苦臉地迎上來寒随,“玉大人,你說我怎么就攤上這事∑尥” “怎么了互艾?”我有些...
    開封第一講書人閱讀 163,912評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長讯泣。 經(jīng)常有香客問我纫普,道長,這世上最難降的妖魔是什么好渠? 我笑而不...
    開封第一講書人閱讀 58,449評論 1 293
  • 正文 為了忘掉前任昨稼,我火速辦了婚禮,結(jié)果婚禮上拳锚,老公的妹妹穿的比我還像新娘假栓。我一直安慰自己,他們只是感情好霍掺,可當(dāng)我...
    茶點故事閱讀 67,500評論 6 392
  • 文/花漫 我一把揭開白布匾荆。 她就那樣靜靜地躺著,像睡著了一般杆烁。 火紅的嫁衣襯著肌膚如雪牙丽。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,370評論 1 302
  • 那天连躏,我揣著相機(jī)與錄音剩岳,去河邊找鬼。 笑死入热,一個胖子當(dāng)著我的面吹牛拍棕,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播勺良,決...
    沈念sama閱讀 40,193評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼绰播,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了尚困?” 一聲冷哼從身側(cè)響起蠢箩,我...
    開封第一講書人閱讀 39,074評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎事甜,沒想到半個月后谬泌,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,505評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡逻谦,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,722評論 3 335
  • 正文 我和宋清朗相戀三年掌实,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片邦马。...
    茶點故事閱讀 39,841評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡贱鼻,死狀恐怖宴卖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情邻悬,我是刑警寧澤症昏,帶...
    沈念sama閱讀 35,569評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站父丰,受9級特大地震影響肝谭,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜础米,卻給世界環(huán)境...
    茶點故事閱讀 41,168評論 3 328
  • 文/蒙蒙 一分苇、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧屁桑,春花似錦医寿、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至竖瘾,卻和暖如春沟突,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背捕传。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評論 1 269
  • 我被黑心中介騙來泰國打工惠拭, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人庸论。 一個月前我還...
    沈念sama閱讀 47,962評論 2 370
  • 正文 我出身青樓职辅,卻偏偏與公主長得像,于是被迫代替她去往敵國和親聂示。 傳聞我的和親對象是個殘疾皇子域携,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,781評論 2 354

推薦閱讀更多精彩內(nèi)容

  • Media Playback Android多媒體框架包涵了對播放多種通用媒體的類型的支持,所以你可以很容易的集成...
    VegetableAD閱讀 883評論 0 0
  • Android 多媒體框架包含了支持播放的一系列常見多媒體類型鱼喉,以此可以很容易地整合諸如音頻秀鞭、視頻、圖片到你的應(yīng)用...
    LeaYw閱讀 3,208評論 1 22
  • Android基礎(chǔ)入門教程——9.2 MediaPlayer播放音頻與視頻 標(biāo)簽(空格分隔): Android基礎(chǔ)...
    Jannonx閱讀 8,022評論 4 20
  • 近來宠默,解雇的事頻繁發(fā)生,我在想自己什么時候也會變成這樣灵巧。公司里做HR的好友說搀矫,不會的,你工作這么認(rèn)真刻肄。 我說瓤球,那難...
    本姑涼馥羽閱讀 3,370評論 50 87
  • 又看到他的動態(tài)了,神態(tài)依舊那么迷人敏弃,習(xí)慣性地想從犄角咯拉里找出一絲蛛絲馬跡卦羡,然后又是一陣暗自欣喜,之后便是失落了麦到,...
    小黃人58閱讀 257評論 0 0