音視頻開發(fā)之旅(44)-ExoPlayer介紹及簡單使用

目錄

  1. ExoPlayer基本介紹
  2. ExoPlayer的基本使用
  3. 遇到的問題
  4. 資料
  5. 收獲

從這篇開始我們進(jìn)入階段五 —— 一些音視頻開源項目的學(xué)習(xí)使用分析栗恩,今天我們進(jìn)入ExoPlayer部分的學(xué)習(xí)實踐

一、ExoPlayer基本介紹

1.1 ExoPlayer優(yōu)缺點
ExoPlayer是谷歌開源的一個應(yīng)用級的音視頻播放器洪燥。ExoPlayer 支持基于 HTTP 的動態(tài)自適應(yīng)流 (DASH)磕秤、SmoothStreaming 和通用加密乳乌、以及可以很好的支持播放隊列、播放源的無縫切換等功能市咆。它采用易于自定義和擴展的設(shè)計汉操。
內(nèi)部的實現(xiàn)也是調(diào)用了低層API,比如:MediaCodec蒙兰、AudioTrack等

畫張表格來對比下ExoPlayer和MediaPlayer磷瘤,更直觀的了解

ExoPlayer的代碼倉庫地址是* https://github.com/google/ExoPlayer*

紅色框框起來的,核心部分加ui的library也是我們這個系列學(xué)習(xí)使用重點癞己。

1.2 ExoPlayer架構(gòu)設(shè)計
ExoPlayer的核心是ExoPlayer的接口,其中定義了包涵傳統(tǒng)播放器的功能(緩沖音視頻梭伐、播放痹雅、暫停、seek等)糊识。ExoPlayer沒有設(shè)定可以播放的媒體類型绩社、存儲方式以及渲染方式,也沒有直接實現(xiàn)加載和播放赂苗。而是在播放器被創(chuàng)建或者準(zhǔn)備播放時將這些工作代理給注冊的組件來實現(xiàn)愉耙。下面是一些常見ExoPlayer的組件實現(xiàn):

  1. MediaSource 加載媒體,通過ExoPlayer.prepare注冊
  2. TrackSelector:音/視軌提取器拌滋,從MediaSource中提取出軌道的數(shù)據(jù)
  3. Render:對TrackSelector提取出來的數(shù)據(jù)進(jìn)行渲染朴沿,AudioTrack播放音頻、Surface渲染視頻
  4. LoadControl:對MediaSource進(jìn)行控制(什么時候開始緩沖败砂、緩沖多少等)
    ExoPlayer為這些組件提供了默認(rèn)的實現(xiàn)赌渣,如果需要定制可以自定義組件來擴展實現(xiàn)。

通過ExoPlayer的架構(gòu)圖昌犹,我們也可以看到其組件模塊化的設(shè)計坚芜,這個架構(gòu)設(shè)計值得學(xué)習(xí),也是好的組件/SDK的一個重要要求斜姥。在我們的日常項目開發(fā)中鸿竖,開發(fā)一個組件 從易用性和以擴展性方面考慮,既要保證使用者很容易上手使用(提供一套默認(rèn)實現(xiàn))铸敏,又要有方便使用者根據(jù)自己的場景進(jìn)行方便的擴展的能力缚忧。

1.3 狀態(tài)機
在看ExoPlayer的狀態(tài)機之前,我們先看下MeidaPlayer的狀態(tài)機

可以看到MediaPlayer的狀態(tài)比較多杈笔,使用時如果在不當(dāng)?shù)奈恢糜|發(fā)了不匹配的操作搔谴,直接回崩潰。
相比MediaPlayer桩撮,ExoPlayer的狀態(tài)少了些敦第,也更容易使用區(qū)分峰弹,不像MediaPlayer在沒有prepared之前都不可以進(jìn)行播放相關(guān)操作,ExoPlayer很多l(xiāng)istener以及isplaying的API監(jiān)聽狀態(tài)的變化芜果。ExoPlayer的四種狀態(tài)如下

 /**
   * Playback state. One of {@link #STATE_IDLE}, {@link #STATE_BUFFERING}, {@link #STATE_READY} or
   * {@link #STATE_ENDED}.
   */
  @Retention(RetentionPolicy.SOURCE)
  @IntDef({STATE_IDLE, STATE_BUFFERING, STATE_READY, STATE_ENDED})
  @interface State {}
  /** The player does not have any media to play. */
  int STATE_IDLE = 1;
  /**
   * The player is not able to immediately play from its current position. This state typically
   * occurs when more data needs to be loaded.
   */
  int STATE_BUFFERING = 2;
  /**
   * The player is able to immediately play from its current position. The player will be playing if
   * {@link #getPlayWhenReady()} is true, and paused otherwise.
   */
  int STATE_READY = 3;
  /** The player has finished playing the media. */
  int STATE_ENDED = 4;

STATE_IDLE:初始狀態(tài)鞠呈,此時播放器沒有可以播放的資源,播放器停止播放或者播放失敗后也會處于該狀態(tài)
STATE_BUFFERING: 沒有足夠的數(shù)據(jù)可以加載播放右钾,此時無法立即播放
STATE_READY : 播放器可以立即播放蚁吝,是否播放取決于playWhenReady的值,該值表達(dá)了使用者的意愿舀射,為true窘茁,將會開始播放,否則不播脆烟。
STATE_ENDED: 播放完了所有的資源后處于改狀態(tài)

二山林、ExoPlayer的簡單使用

這一小節(jié)我們學(xué)習(xí)實踐ExoPlayer的使用

2.1 AS中引入library
ExoPlayer有很好的擴展性和可定制性,可以根據(jù)項目需要進(jìn)行選擇對應(yīng)的模塊邢羔,也可以全部包含驼抹。

exoplayer-core: Core functionality (required).
exoplayer-dash: Support for DASH content.
exoplayer-hls: Support for HLS content.
exoplayer-smoothstreaming: Support for SmoothStreaming content.
exoplayer-ui: UI components and resources for use with ExoPlayer.

我們根據(jù)需要來添加library

    implementation 'com.google.android.exoplayer:exoplayer-core:2.13.3'
    implementation 'com.google.android.exoplayer:exoplayer-ui: 2.13.3'

接下來出創(chuàng)建一個容器PlayerView以及ExoPlayerView進(jìn)行播放

2.2 創(chuàng)建播放器、綁定播放器容器拜鹤、設(shè)置數(shù)據(jù)源框冀、prepare

 //1. 創(chuàng)建播放器
        player = SimpleExoPlayer.Builder(this).build()
        printCurPlaybackState("init")  //  此時處于STATE_IDLE = 1;

        //2. 播放器和播放器容器綁定
        playerView.player = player

        //3. 設(shè)置數(shù)據(jù)源
        //音頻
        val mediaItem = MediaItem.fromUri(" https://storage.googleapis.com/exoplayer-test-media-0/play.mp3")
        player.setMediaItem(mediaItem)
    
    //4.當(dāng)Player處于STATE_READY狀態(tài)時,進(jìn)行播放
        player.playWhenReady = true

    //5. 調(diào)用prepare開始加載準(zhǔn)備數(shù)據(jù)敏簿,該方法時異步方法明也,不會阻塞ui線程
        player.prepare()
        printCurPlaybackState("prepare") //  此時處于 STATE_BUFFERING = 2;

2.3 播放監(jiān)聽
當(dāng)前是否在播放中

public final boolean isPlaying() {
    return getPlaybackState() == Player.STATE_READY
        && getPlayWhenReady()
        && getPlaybackSuppressionReason() == PLAYBACK_SUPPRESSION_REASON_NONE;
  }

播放狀態(tài)改變的listener、音頻相關(guān)的listener惯裕、視頻相關(guān)的listener

        playbackListener = PlaybackListener()
        player.addListener(playbackListener)
        player.addAudioListener(playbackListener)
        player.addVideoListener(playbackListener)


class PlaybackListener : Player.EventListener, AudioListener, VideoListener {
        override fun onPlaybackStateChanged(playbackState: Int) {
            val stateString: String
            stateString = when (playbackState) {
                ExoPlayer.STATE_IDLE -> "ExoPlayer.STATE_IDLE      -"
                ExoPlayer.STATE_BUFFERING -> "ExoPlayer.STATE_BUFFERING -"
                ExoPlayer.STATE_READY -> "ExoPlayer.STATE_READY     -"
                ExoPlayer.STATE_ENDED -> "ExoPlayer.STATE_ENDED     -" //播放列表存在時播放最后一個播放完成才會回掉該方法
                else -> "UNKNOWN_STATE             -"
            }
            Log.d("ExoBaseUserActivity", "changed state to $stateString")
        }

        override fun onAudioSessionIdChanged(audioSessionId: Int) {
            Log.d("ExoBaseUserActivity", "onAudioSessionIdChanged--sessionId=" + audioSessionId)
        }

        override fun onAudioAttributesChanged(audioAttributes: AudioAttributes) {
            Log.d("ExoBaseUserActivity", "onAudioAttributesChanged--audioAttributes=" + audioAttributes.toString())
        }

        override fun onVolumeChanged(volume: Float) {
            Log.d("ExoBaseUserActivity", "onVolumeChanged--volume=" + volume)
        }

        override fun onSkipSilenceEnabledChanged(skipSilenceEnabled: Boolean) {
            Log.d("ExoBaseUserActivity", "onSkipSilenceEnabledChanged--skipSilenceEnabled=" + skipSilenceEnabled)
        }

        override fun onVideoSizeChanged(width: Int, height: Int, unappliedRotationDegrees: Int, pixelWidthHeightRatio: Float) {
            Log.d("ExoBaseUserActivity", "onVideoSizeChanged--width=" + width + " height=" + height + " unappliedRotationDegrees=" + unappliedRotationDegrees + " pixelWidthHeightRatio=" + pixelWidthHeightRatio)
        }

        override fun onSurfaceSizeChanged(width: Int, height: Int) {
            Log.d("ExoBaseUserActivity", "onSurfaceSizeChanged--width=" + width + " height=" + height)
        }

        override fun onRenderedFirstFrame() {
            Log.d("ExoBaseUserActivity", "onRenderedFirstFrame")
        }
    }

用于分析用的listener(會輸出更詳細(xì)的信息)

   //通過AnalyticsListener可以輸出更多信息
        analyticsListener = EventLogger(DefaultTrackSelector())
        player.addAnalyticsListener(analyticsListener)

2.4 釋放資源
在頁面不可見/銷毀(看是否需要后臺播放)時要釋放資源

    override fun onDestroy() {
        super.onDestroy()
        player.removeAnalyticsListener(analyticsListener)
        player.removeListener(playbackListener)
        player.removeAudioListener(playbackListener)
        player.removeVideoListener(playbackListener)

        player.release()
    }

完整代碼已上傳至 github https://github.com/ayyb1988/mediajourney

三诡右、遇到的問題

問題1

Failed to resolve: com.google.android.exoplayer:exoplayer: 2.13.3

2.13.3前多了一個空格,這個太….轻猖,細(xì)節(jié)有時候不注意就好浪費不少時間帆吻。

問題2

      java.lang.SecurityException: Permission denied (missing INTERNET permission?)
        at java.net.Inet6AddressImpl.lookupHostByName(Inet6AddressImpl.java:150)
        at java.net.Inet6AddressImpl.lookupAllHostAddr(Inet6AddressImpl.java:103)
        at java.net.InetAddress.getAllByName(InetAddress.java:1152)
        at com.android.okhttp.Dns$1.lookup(Dns.java:41)
        at com.android.okhttp.internal.http.RouteSelector.resetNextInetSocketAddress(RouteSelector.java:178)
        at com.android.okhttp.internal.http.RouteSelector.nextProxy(RouteSelector.java:144)
        at com.android.okhttp.internal.http.RouteSelector.next(RouteSelector.java:86)
        at com.android.okhttp.internal.http.StreamAllocation.findConnection(StreamAllocation.java:176)
        at com.android.okhttp.internal.http.StreamAllocation.findHealthyConnection(StreamAllocation.java:128)
        at com.android.okhttp.internal.http.StreamAllocation.newStream(StreamAllocation.java:97)
        at com.android.okhttp.internal.http.HttpEngine.connect(HttpEngine.java:289)
        at com.android.okhttp.internal.http.HttpEngine.sendRequest(HttpEngine.java:232)
        at com.android.okhttp.internal.huc.HttpURLConnectionImpl.execute(HttpURLConnectionImpl.java:465)
        at com.android.okhttp.internal.huc.HttpURLConnectionImpl.connect(HttpURLConnectionImpl.java:131)
        at com.android.okhttp.internal.huc.DelegatingHttpsURLConnection.connect(DelegatingHttpsURLConnection.java:90)
        at com.android.okhttp.internal.huc.HttpsURLConnectionImpl.connect(HttpsURLConnectionImpl.java:30)
        at com.google.android.exoplayer2.upstream.DefaultHttpDataSource.makeConnection(DefaultHttpDataSource.java:641)
        at com.google.android.exoplayer2.upstream.DefaultHttpDataSource.makeConnection(DefaultHttpDataSource.java:528)
        at com.google.android.exoplayer2.upstream.DefaultHttpDataSource.open(DefaultHttpDataSource.java:349)
        at com.google.android.exoplayer2.upstream.DefaultDataSource.open(DefaultDataSource.java:201)
        at com.google.android.exoplayer2.upstream.StatsDataSource.open(StatsDataSource.java:84)
        at com.google.android.exoplayer2.source.ProgressiveMediaPeriod$ExtractingLoadable.load(ProgressiveMediaPeriod.java:1015)
        at com.google.android.exoplayer2.upstream.Loader$LoadTask.run(Loader.java:415)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
        at java.lang.Thread.run(Thread.java:919)

沒有加網(wǎng)絡(luò)權(quán)限的原因,Mainfest中靜態(tài)注冊后咙边,在requesetPermission中動態(tài)的請求下猜煮。通過這個崩潰堆棧,我們可以看到ExoPlayer加載網(wǎng)絡(luò)視頻使用的是Okhttp

問題3

2021-05-15 18:41:17.414 11144-11144/? I/av.mediajourne: Not late-enabling -Xcheck:jni (already on)
2021-05-15 18:41:17.487 11144-11144/? E/av.mediajourne: Unknown bits set in runtime_flags: 0x8000
2021-05-15 18:41:17.489 11144-11144/? W/av.mediajourne: Unexpected CPU variant for X86 using defaults: x86

X86模擬器播放時偶爾會閃退败许,真機正常王带。機型設(shè)備的適配問題始終是一個大問題

四、資料

  1. Media streaming with ExoPlayer
  2. ExoPlayer blog
  3. ExoPlayer developer guide
  4. ExoPlayer播放音視頻的使用介紹

五市殷、 收獲

通過本次學(xué)習(xí)實踐收獲如下:

  1. 了解ExoPlayer的背景以及相比MediaPlayer的優(yōu)缺點
  2. 了解ExoPlayer的基本功能
  3. 簡單實踐

感謝你的閱讀

下一篇我們繼續(xù)學(xué)習(xí)實踐ExoPlayer愕撰,實現(xiàn)一個簡單的音頻播放器,歡迎關(guān)注公眾號“音視頻開發(fā)之旅”,一起學(xué)習(xí)成長搞挣。

歡迎交流

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末带迟,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子囱桨,更是在濱河造成了極大的恐慌仓犬,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件舍肠,死亡現(xiàn)場離奇詭異搀继,居然都是意外死亡,警方通過查閱死者的電腦和手機翠语,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進(jìn)店門叽躯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人肌括,你說我怎么就攤上這事点骑。” “怎么了们童?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵畔况,是天一觀的道長鲸鹦。 經(jīng)常有香客問我慧库,道長,這世上最難降的妖魔是什么馋嗜? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任齐板,我火速辦了婚禮,結(jié)果婚禮上葛菇,老公的妹妹穿的比我還像新娘甘磨。我一直安慰自己,他們只是感情好眯停,可當(dāng)我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布济舆。 她就那樣靜靜地躺著,像睡著了一般莺债。 火紅的嫁衣襯著肌膚如雪滋觉。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天齐邦,我揣著相機與錄音椎侠,去河邊找鬼。 笑死措拇,一個胖子當(dāng)著我的面吹牛我纪,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼浅悉,長吁一口氣:“原來是場噩夢啊……” “哼趟据!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起仇冯,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤之宿,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后苛坚,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體比被,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年泼舱,在試婚紗的時候發(fā)現(xiàn)自己被綠了等缀。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡娇昙,死狀恐怖尺迂,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情冒掌,我是刑警寧澤噪裕,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站股毫,受9級特大地震影響膳音,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜铃诬,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一祭陷、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧趣席,春花似錦兵志、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至霉涨,卻和暖如春按价,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背嵌纲。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工俘枫, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人逮走。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓鸠蚪,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子茅信,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,577評論 2 353

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