構(gòu)建一個(gè)Android視頻播放器應(yīng)用(1/5)

原文地址:https://medium.com/google-developers/building-a-video-player-app-in-android-part-1-5-d95770ef762d

Android的多媒體APIs可以讓你創(chuàng)建具有豐富的媒體體驗(yàn)的應(yīng)用,它們還提供藍(lán)牙耳機(jī),汽車音響系統(tǒng)痴柔,有線耳機(jī),甚至Google智能助理和Android Auto外部控制的控件木张。

本系列文章的目標(biāo)是讓您開(kāi)始使用ExoPlayer和MediaSession構(gòu)建簡(jiǎn)單但功能豐富的視頻播放器應(yīng)用程序。

這個(gè)app將會(huì)支持如下功能:

  1. 播放本地和遠(yuǎn)程的視頻資源
  2. 支持播放列表端三,以便于你可以將視頻列表串聯(lián)在一起
  3. 支持MediaSession舷礼,以便于藍(lán)牙耳機(jī)可以控制你的視頻,并且可以查看當(dāng)前播放的視頻郊闯。
  4. 支持音頻對(duì)焦妻献,以便您遵守Android的音頻對(duì)焦系統(tǒng),并在播放其他內(nèi)容或用戶收到電話時(shí)暫停播放虚婿。
  5. 在Android Oreo上支持畫中畫模式旋奢,以便在使用其他應(yīng)用程序時(shí)泳挥,您的應(yīng)用程序的視頻播放可以在最小化的窗口中繼續(xù)播放然痊。

我們將使用kotlin來(lái)開(kāi)發(fā)這個(gè)app。

ExoPlayer概述

為了使用ExoPlayer來(lái)播放網(wǎng)絡(luò)中或者是Apk中的視頻文件屉符,首先需要?jiǎng)?chuàng)建SimpleExoPlayerMediaSource實(shí)例剧浸,你還需要一個(gè)包含PlayerView的Activity將用來(lái)實(shí)際呈現(xiàn)播放器加載的視頻內(nèi)容。

播放器

播放什么
你需要告訴播放器要加載什么媒體文件矗钟,并且從哪里加載唆香,MediaSource可以讓你做到這一點(diǎn),媒體源表示為Uris吨艇,它可以指向Apk中的assets文件夾或者是Http躬它。你提供的Uris用于加載準(zhǔn)備播放的內(nèi)容,ExtractorMediaSource允許您處理這些源东涡。

注意:為了適配格式冯吓,你可以使用DashMediaSource(DASH 資源),SsMediaSource(SmoothStreaming資源)疮跑,和HlsMediaSource(HLS資源)组贺。

如果你通過(guò)HTTP加載媒體文件需要在AndroidManifest.xml文件中添加網(wǎng)絡(luò)權(quán)限,如果是從資源文件中加載媒體文件祖娘,那么就不需要添加任何權(quán)限失尖。

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

在哪里渲染播放

然后,你需要將播放器附加到PlayerView中,他將視頻呈現(xiàn)給用戶掀潮,并且還提供用于音視頻播放的UI控件菇夸。

你還必須準(zhǔn)備播放器,該播放器會(huì)通知開(kāi)始加載數(shù)據(jù)胧辽,因?yàn)閺木W(wǎng)絡(luò)上緩存數(shù)據(jù)到播放之前會(huì)有一段時(shí)間峻仇,您還必須將playWhenReady設(shè)置為true,這會(huì)告知ExoPlayer播放邑商。

playWhenReady = true -> PLAY(在足夠的數(shù)據(jù)被緩沖之后開(kāi)始)
playWhenReady = false -> PAUSE

為了支持ExoPlayer摄咆,需要在build.gradle中添加如下配置:

ext { ver = "2.7.0" }
dependencies {
  implementation "com.google.android.exoplayer:exoplayer-core:$ver"
  implementation “com.google.android.exoplayer:exoplayer-ui:$ver”
}

添加ExoPlayer依賴并不會(huì)明顯增加apk的大小。

創(chuàng)建ExoPlayer實(shí)例

此代碼演示了如何使用默認(rèn)的選項(xiàng)創(chuàng)建一個(gè)播放器人断,并且把它附加到PlayerView(必須要在顯示視頻的Activity的布局文件中聲明)吭从。

enum class SourceType{
  local_audio, local_video, http_audio, http_video, playlist
}

data class PlayerState(var window: Int = 0,
                                      var position: Long = 0,
                                      var whenReady: Boolean = true,
                                      var source: SourceType = local_audio)

class PlayerHolder(val context: Context,
                                val playerView: PlayerView,
                                val PlayerState: PlayerState) : AnkoLogger {
    val player: ExoPlayer
    init{
      player = ExoPlayerFactory.newSimpleInstance(ctx, DefaultTrackSelector())
                    .also{
                      playerView.player = it
                      info { "SimpleExoPlayer created " }
      }
    }
    fun start() {
            // Load media.
            prepare(buildMediaSource(selectMediaToPlay(state.source)))
            // Start playback when media has buffered enough.
            playWhenReady = true
    }

    fun selectMediaToPlay(source: Source): Uri {
        return when (source) {
            Source.local_audio -> Uri.parse("asset:///audio/file.mp3")
            Source.local_video -> Uri.parse("asset:///video/file.mp4")
            Source.http_audio -> Uri.parse("http://site.../file.mp3")
            Source.http_video -> Uri.parse("http://site.../file.mp4")
        }
    }

    private fun buildMediaSource(uri: Uri): ExtractorMediaSource {
        return ExtractorMediaSource.Factory(
                DefaultDataSourceFactory(ctx, "videoapp")).createMediaSource(uri)
    }

    fun stop() { ... }
    
    fun release() { ... }
}
}

PlayerView需要在Activity的布局文件中進(jìn)行聲明,例如:

<com.google.android.exoplayer2.ui.PlayerView 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/exoplayerview_activity_video"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
/>

使用ExoPlayer加載APK中的貝蒂文件

DefaultDataSource允許通過(guò)Uris加載本地文件:

  • file:///
  • asset:///
  • content:///
  • rtmp
  • data
  • http(s)

你可以用如下方式加載assets中的文件(你也可以在assets文件夾下面創(chuàng)建嵌套的文件夾):

  • Uri.parse("file:///android_asset/video/video.mp4")
  • Uri.parse("asset:///video/video.mp4")

請(qǐng)注意:

  • ExoPlayer不允許使用Uri.parse("android.resource://${packageName}/${R.raw.id}")加載res文件夾中的文件,并且恶迈,Android不允許在res文件夾中添加文件夾涩金。
  • 你可以使用RawResourceDataSource去構(gòu)建一個(gè)Uri指向你的res文件夾中的資源id,例如:RawResourceDataSource.buildRawResourceUri(R.raw.my_media_file)

釋放ExoPlayer資源

當(dāng)播放完畢時(shí)暇仲,要停止播放器步做,因?yàn)樗麜?huì)消耗網(wǎng)絡(luò),內(nèi)存和系統(tǒng)編解碼器等資源奈附。編解碼器是手機(jī)上共享的資源全度,根據(jù)手機(jī)和操作系統(tǒng)的版本斥滤,可能數(shù)量有限将鸵,所以在不使用的時(shí)候及時(shí)釋放很重要,如果需要的話佑颇,下面的代碼允許你再次重用相同的ExoPlayer實(shí)例

fun stop(){
  player.stop(true)
  info { "SimpleExoPlayer is stopped" }
}

fun release() {
  player.release()
  info{ "SimpleExoPlayer is released" }
}

這將釋放播放器持有的所有資源顶掉,為了再次使用播放器,請(qǐng)調(diào)用prepare并設(shè)置playWhenReady挑胸,如上面要點(diǎn)所示痒筒。
ExoPlayer.release()是一個(gè)與stop完全相同的方法,只有一個(gè)例外:他也停止播放線程茬贵,放置ExoPlayer實(shí)例被重用簿透,當(dāng)播放器不再使用時(shí),例如在Service的onDestory或者Activity的onDestroy方法中闷沥,調(diào)用該方法釋放資源萎战。

Activity的生命周期集成

您必須與Android的Activity生命周期集成才能創(chuàng)建,使用和釋放播放器舆逃,下面是一個(gè)簡(jiǎn)單的例子蚂维。

class VideoActivity : AppCompatActivity(){
    lateinit var playerHolder: PlayerHolder
    var state = PlayerState()

    override fun onCreate(savedInstanceState: Bundle?){
      ...
      playerHolder = PlayerHolder(this, exoplayerview_activity_video, state)
    }

    override fun onStart(){
      super.onStart()
      playerHolder.start()
    }

    override fun onStop(){
      super.onStop()
      playerHolder.stop()
    }

    override fun onDestroy(){
      super.onDestroy()
      playerHolder.release()
    }
}

在onStart()和onStop()之間保持播放器狀態(tài)

PlayerState數(shù)據(jù)類用于在播放器第一次運(yùn)行之前加載播放器的狀態(tài)信息戳粒。當(dāng)播放器釋放了,一些播放器的狀態(tài)將會(huì)保存在對(duì)象中虫啥,當(dāng)一個(gè)新的播放器被創(chuàng)建蔚约,這個(gè)簡(jiǎn)單的狀態(tài)對(duì)象用于配置播放器以恢復(fù)實(shí)例以前停止的位置。

data class PlayerState(var window: Int = 0,
                                      var position: Long = 0,
                                      var whenReady: Boolean = true,
                                      var source: SourceType = local_audio)

從用戶體驗(yàn)角度來(lái)說(shuō)涂籽,這意味著當(dāng)你運(yùn)行應(yīng)用程序苹祟,播放一些媒體文件,并點(diǎn)擊主頁(yè)按鈕评雌,播放器資源就會(huì)被釋放树枫,當(dāng)你切回該應(yīng)用程序時(shí),播放器會(huì)再次初始化景东,并且應(yīng)該恢復(fù)先前的狀態(tài)信息砂轻,以便用戶可以從停止播放的位置繼續(xù)播放。
在播放資源沒(méi)有被釋放以前斤吐,播放器的currentWindowIndex搔涝,currentPositionplayWhenReady和播放列表或媒體項(xiàng)目信息被保存到PlayerState對(duì)象和措。一旦播放器重新初始化庄呈,狀態(tài)就會(huì)恢復(fù),以下是來(lái)自PlayerHolder類的方法派阱,演示了如何完成此操作诬留。

class PlayerHolder(...) : AnkoPlayer {
  ...
  fun start() {
    player.prepare(...)
    with(playerState) {
      player.playWhenReady = whenReady
      player.seekTo(window, position)
    }
    info { "SimpleExoPlayer is started" }
  }

  fun stop(){
    with(player) {
      with(state) {
        position = currentPosition
        window = currentWindowIndex
        whenReady = playWhenReady
      }
      stop()
    }
    info {"SimpleExoPlayer is released"}
  }
}

多一點(diǎn)控制播放器的功能

你也可以使用ExoPlayerFactor.newSimpleInstance(...)工廠方法的不同簽名來(lái)自定義您的播放器,例如:即使使用DefaultLoadControl類你也可以更改ExoPlayer的緩沖策略颁褂,更好的滿足您的需求故响。

player = ExoPlayerFactory.newSimpleInstance(
  DefaultRenderersFactory(ctx),
  DefaultTrackSelector(),
  DefaultLoadControl()
).apply {...}

對(duì)于更復(fù)雜的用例傀广,您可以為傳遞給ExoPlayerFactory.newSimpleInstance(...)工廠方法的所有參數(shù)提供自己的實(shí)現(xiàn)颁独,這使您可以對(duì)ExoPlayer執(zhí)行的操作有很大的靈活性。

學(xué)習(xí)資源

ExoPlayer

MediaSession, Audio Focus

DASH, HLS

Picture in Picture

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末伪冰,一起剝皮案震驚了整個(gè)濱河市誓酒,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌贮聂,老刑警劉巖靠柑,帶你破解...
    沈念sama閱讀 218,204評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異吓懈,居然都是意外死亡歼冰,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門耻警,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)隔嫡,“玉大人甸怕,你說(shuō)我怎么就攤上這事∪鳎” “怎么了梢杭?”我有些...
    開(kāi)封第一講書人閱讀 164,548評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)秸滴。 經(jīng)常有香客問(wèn)我武契,道長(zhǎng),這世上最難降的妖魔是什么荡含? 我笑而不...
    開(kāi)封第一講書人閱讀 58,657評(píng)論 1 293
  • 正文 為了忘掉前任咒唆,我火速辦了婚禮,結(jié)果婚禮上释液,老公的妹妹穿的比我還像新娘钧排。我一直安慰自己,他們只是感情好均澳,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,689評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布恨溜。 她就那樣靜靜地躺著,像睡著了一般找前。 火紅的嫁衣襯著肌膚如雪糟袁。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 51,554評(píng)論 1 305
  • 那天躺盛,我揣著相機(jī)與錄音项戴,去河邊找鬼。 笑死槽惫,一個(gè)胖子當(dāng)著我的面吹牛周叮,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播界斜,決...
    沈念sama閱讀 40,302評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼仿耽,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了各薇?” 一聲冷哼從身側(cè)響起项贺,我...
    開(kāi)封第一講書人閱讀 39,216評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎峭判,沒(méi)想到半個(gè)月后开缎,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,661評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡林螃,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,851評(píng)論 3 336
  • 正文 我和宋清朗相戀三年奕删,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片疗认。...
    茶點(diǎn)故事閱讀 39,977評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡完残,死狀恐怖砌滞,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情坏怪,我是刑警寧澤贝润,帶...
    沈念sama閱讀 35,697評(píng)論 5 347
  • 正文 年R本政府宣布,位于F島的核電站铝宵,受9級(jí)特大地震影響打掘,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜鹏秋,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,306評(píng)論 3 330
  • 文/蒙蒙 一尊蚁、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧侣夷,春花似錦横朋、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,898評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至衙传,卻和暖如春决帖,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蓖捶。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,019評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工地回, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人俊鱼。 一個(gè)月前我還...
    沈念sama閱讀 48,138評(píng)論 3 370
  • 正文 我出身青樓刻像,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親并闲。 傳聞我的和親對(duì)象是個(gè)殘疾皇子细睡,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,927評(píng)論 2 355

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

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,135評(píng)論 25 707
  • 最近開(kāi)發(fā)的項(xiàng)目涉及音頻纹冤、視頻播放洒宝,搜索了解到 ExoPlayer 2.x可以很好的滿足需求购公,簡(jiǎn)單翻譯了一下幫助文檔...
    SavySoda閱讀 41,239評(píng)論 13 63
  • ¥開(kāi)啟¥ 【iAPP實(shí)現(xiàn)進(jìn)入界面執(zhí)行逐一顯】 〖2017-08-25 15:22:14〗 《//首先開(kāi)一個(gè)線程,因...
    小菜c閱讀 6,419評(píng)論 0 17
  • 要堅(jiān)持做一件事挺難 心里有話想了千遍也不愿拿起筆就寫下來(lái) 一旦松懈很難再提起 每天我都在忙忙碌碌中看群里你們寫的字...
    雀西i閱讀 155評(píng)論 0 0
  • 版權(quán)歸作者所有雁歌,任何形式轉(zhuǎn)載請(qǐng)聯(lián)系作者宏浩。 作者:天蝎心塵之路(來(lái)自豆瓣) 來(lái)源:https://www.douba...
    天蝎心塵之路閱讀 627評(píng)論 1 2