1. 程式人生 > >Android學習------ExoPlayer的學習和使用(音訊)(一)

Android學習------ExoPlayer的學習和使用(音訊)(一)

ExoPlayer的學習和使用(音訊)(一)

1.前言

相關教程網站:

簡要說明:

ExoPlayer is an application level media player for Android. It provides an alternative to Android’s MediaPlayer API for playing audio and video both locally and over the Internet. ExoPlayer supports features not currently supported by Android’s MediaPlayer API, including DASH and SmoothStreaming adaptive playbacks. Unlike the MediaPlayer API, ExoPlayer is easy to customize and extend, and can be updated through Play Store application updates.

來自google翻譯:

ExoPlayer是Android的應用程式級媒體播放器。 它提供了Android的MediaPlayer API的替代品,用於在本地和網際網路上播放音訊和視訊。 ExoPlayer支援Android MediaPlayer API目前不支援的功能,包括DASH和SmoothStreaming自適應回放。 與MediaPlayer API不同,ExoPlayer易於定製和擴充套件,並可通過Play Store應用程式更新進行更新。

綜上所述,大概就是MediaPlayer不如ExoPlayer,google推薦使用ExoPlayer。

2.How To Use

如何能用才是我們關注的重點,看得到效果,才知道是不是適合我們的。

首先我們至少要能讓音訊能夠播放,我們才能做更多想做的事情,對吧,不然搗鼓半天,都不知道音訊咋播放的,那真是不高興。

接下來開始接入步驟,先讓音訊播放起來,

2.1.新增依賴

根目錄的build.gradle檔案新增

repositories {
    jcenter()
    google()
}

app或者module下的build.gradle檔案下新增

implementation 'com.google.android.exoplayer:exoplayer:2.X.X'

下面的內容按需新增,符合自己需求的

implementation 'com.google.android.exoplayer:exoplayer-core:2.X.X'
implementation 'com.google.android.exoplayer:exoplayer-dash:2.X.X' 

(這裡解釋一下DASH(Dynamic Adaptive Streaming over HTTP)即自適應流媒體傳輸,什麼意思呢,簡單概括來說,就是在伺服器端提前存好同一內容的不同位元速率、不同解析度的多個分片以及相應的描述檔案MPD,客戶端在播放時即可以根據自身效能以及網路環境選擇最適宜的版本)

implementation 'com.google.android.exoplayer:exoplayer-ui:2.X.X'

(這裡我使用的是  implementation 'com.google.android.exoplayer:exoplayer:2.8.1'
                implementation 'com.google.android.exoplayer:exoplayer-core:2.8.1'  )

2.2.程式碼編寫

1-> 

獲取player的一個例項,大多數情況可以直接使用 DefaultTrackSelector 

( DefaultTrackSelector 該類可以對當前播放的音視訊進行操作,比如設定音軌,設定約束曲目選擇,禁用渲染器)

val player = ExoPlayerFactory.newSimpleInstance(this, DefaultTrackSelector())

val defaultDataSourceFactory = DefaultDataSourceFactory(this, "audio/mpeg") //  audio/mpeg

val concatenatingMediaSource = ConcatenatingMediaSource() //建立一個媒體連線源 

2->

val mediaSource1 = ExtractorMediaSource.Factory(defaultDataSourceFactory)
.createMediaSource(Uri.parse("http://xiaxiayige.u.qiniudn.com/Big%20Big%20World.mp3")) //建立一個播放資料來源

3->

concatenatingMediaSource.addMediaSource(mediaSource1) //把資料來源新增到concatenatingMediaSource裡面,相當於新增到一個播放池

4->

player.playWhenReady = true //設定屬性,當準備好以後 自動開始播放

5->

 player.prepare(concatenatingMediaSource) //把Player和資料來源關聯起來  

6->

ok,播放的功能就這樣結束了,下面看看完整程式碼


class AudioPlayerDemo_A : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_audio_player_demo_)
    val player = ExoPlayerFactory.newSimpleInstance(this, DefaultTrackSelector())
    val defaultDataSourceFactory = DefaultDataSourceFactory(this, "audio/mpeg") //  userAgent -> audio/mpeg  不能為空
    val concatenatingMediaSource = ConcatenatingMediaSource() //建立一個媒體連線源
    val mediaSource1 = ExtractorMediaSource.Factory(defaultDataSourceFactory)
            .createMediaSource(Uri.parse("http://xiaxiayige.u.qiniudn.com/Big%20Big%20World.mp3")) //建立一個播放資料來源
    concatenatingMediaSource.addMediaSource(mediaSource1)
    player.playWhenReady = true
    player.prepare(concatenatingMediaSource)
    }
}

直接拷貝上面程式碼 ,是可以直接打卡播放音訊的哦,當前Activity沒有做其他任何操作,開啟以後自動播放。

注意:如果需要自己控制播放或者暫停可以呼叫 player.playWhenReady = true 或者 player.playWhenReady = false 可以控制播放的暫停和繼續播放

3.完成需求

上面已經講了如何播放一個音訊,已經差不多完成了我們一個需求,畢竟至少是要能夠播放。

接下來我們完成更多的需求。

1.多個音訊連續播放
2.實現倍速播放-慢速或快速
3.more and more (後臺播放,鎖屏播放,通知欄ui顯示播放情況)

3.1.多個音訊連續播放

首先完成來完成第一個需求,第一個需求比較簡單,上面已經涉及到了。

val mediaSource1 = ExtractorMediaSource.Factory(defaultDataSourceFactory)
            .createMediaSource(Uri.parse("http://xiaxiayige.u.qiniudn.com/Big%20Big%20World.mp3")) //建立一個播放資料來源
   concatenatingMediaSource.addMediaSource(mediaSource1)

上面這串程式碼是把mediaSource1 新增到一個列表中,當然既然提供了Add方法,肯定還能繼續Add,內部也是通過一個list來儲存新增的資料。

這裡需要了解一下 LoopingMediaSourceMergingMediaSourceConcatenatingMediaSourceClippingMediaSource 這4個MediaSource都是繼承了CompositeMediaSource這個抽象類,所以我們一個一個來看看都是什麼作用

3.1.1.LoopingMediaSource:

可以將ConcatenatingMediaSource新增到LoopingMediaSource中 

 val mediaSource1 = ExtractorMediaSource.Factory(defaultDataSourceFactory)
            .createMediaSource(Uri.parse("http://xiaxiayige.u.qiniudn.com/Big%20Big%20World.mp3")) //建立一個播放資料來源

 val mediaSource2 = ExtractorMediaSource.Factory(defaultDataSourceFactory)
            .createMediaSource(Uri.parse("http://xiaxiayige.u.qiniudn.com/Let%20It%20Go%20%281%29.mp3")) //建立一個播放資料來源

concatenatingMediaSource.addMediaSource(mediaSource1)
concatenatingMediaSource.addMediaSource(mediaSource2) //新增多個MediaSorce

val loopMediaSouce=LoopingMediaSource(concatenatingMediaSource)// 實現多個音訊迴圈播放
player.playWhenReady = true
player.prepare(loopMediaSouce) //呼叫次方法播放完成以後將不再繼續播放

player.prepare(concatenatingMediaSource)//呼叫次方法播放完成以後將不再繼續播放

3.1.2.ConcatenatingMediaSource: (執行緒安全,播放期間可以修改播放列表)

可以呼叫add或remove修改播放列表

3.1.3.MergingMediaSource:

類似ConcatenatingMediaSource,合併2個音訊,該類應該多用於視訊,可以合併視訊+字幕等資訊 需要在同一個timeline上,如果音訊
播放使用該類,也相當於新增到一個list中,使用player.prepare(MergingMediaSource) 也將迴圈播放,不能動態修改播放列表

以上三個型別的程式碼如下:

/***
* 音訊的連續播放
*/
 class AudioPlayerDemo_B : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_audio_player_demo_)
    val player = ExoPlayerFactory.newSimpleInstance(this, DefaultTrackSelector())
    val defaultDataSourceFactory = DefaultDataSourceFactory(this, "audio/mpeg") //  userAgent -> audio/mpeg  不能為空
    val concatenatingMediaSource = ConcatenatingMediaSource() //建立一個媒體連線源
    val mediaSource1 = ExtractorMediaSource.Factory(defaultDataSourceFactory)
            .createMediaSource(Uri.parse("http://xiaxiayige.u.qiniudn.com/Big%20Big%20World.mp3")) //建立一個播放資料來源

    val mediaSource2 = ExtractorMediaSource.Factory(defaultDataSourceFactory)
            .createMediaSource(Uri.parse("http://xiaxiayige.u.qiniudn.com/Let%20It%20Go%20%281%29.mp3")) //建立一個播放資料來源

    concatenatingMediaSource.addMediaSource(mediaSource1)
    concatenatingMediaSource.addMediaSource(mediaSource2)

    val loopMediaSouce = LoopingMediaSource(concatenatingMediaSource)// 實現多個音訊迴圈播放
    val mergingMediaSource = MergingMediaSource(mediaSource1, mediaSource2) //音訊合併
    player.playWhenReady = true


//        player.prepare(loopMediaSouce) 講loop關聯player
//        player.prepare(concatenatingMediaSource) //concatenatingMediaSource 關聯串聯的MediSource
    player.prepare(mergingMediaSource)
}
}

3.1.4.ClippingMediaSource

提供剪下功能,能夠裁剪一個一段音訊的區間,試一試,程式碼如下:


class AudioPlayerDemo_C : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_audio_player_demo_)
    val player = ExoPlayerFactory.newSimpleInstance(this, DefaultTrackSelector())
    val defaultDataSourceFactory = DefaultDataSourceFactory(this, "audio/mpeg") //  userAgent -> audio/mpeg  不能為空
    val concatenatingMediaSource = ConcatenatingMediaSource() //建立一個媒體連線源
    val mediaSource1 = ExtractorMediaSource.Factory(defaultDataSourceFactory)
            .createMediaSource(Uri.parse("http://xiaxiayige.u.qiniudn.com/Big%20Big%20World.mp3")) //建立一個播放資料來源
    concatenatingMediaSource.addMediaSource(mediaSource1)

    val clippingMediaSource=ClippingMediaSource(mediaSource1,10*1000*1000,20*1000*1000)  // 這裡需要注意的是後面的開始時間和結束時間是微秒的單位,這裡需要注意   ,並且結束時間不能小於開始時間。

    player.playWhenReady = true
    player.prepare(clippingMediaSource)
 }
}

現在到這裡我們差不多已經知道了,上面的第一需求,怎麼讓音訊連續播放,其中還擴充套件了其他幾個功能 裁剪,合併,迴圈播放的功能。

3.2 音訊倍速播放

實現倍速播放通過ExoPlayer的playbackParameters 屬性設定

playbackParameters =   PlaybackParameters(speed,pitch,skipSilence)

speed :播放速率
pitch:聲調的變化
skipSilence:是否跳過音訊流中的靜音

3.2.1 首先新增2個按鈕控制播放速度的加減

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">

<SeekBar
    android:id="@+id/seek"
    android:layout_width="match_parent"
    android:layout_height="20dp" />


<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal">

    <Button
        android:id="@+id/b1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:text="-0.1倍速"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/b2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:text="+0.1倍速"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</LinearLayout>

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal">

    <Button
        android:id="@+id/b4"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:text="減音調"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/b5"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:text="加音調"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</LinearLayout>

</LinearLayout>

如圖:

前面已經說了通過PlaybackParameters來控制倍速和音調的加減 所以直接上程式碼了。

    /***
    * 倍速播放
    */
class AudioPlayerDemo_D : AppCompatActivity() {
var speed = 1.0f
var pitch = 1.0f
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_audio_player_demo_d)

    val player = ExoPlayerFactory.newSimpleInstance(this, DefaultTrackSelector())
    val defaultDataSourceFactory = DefaultDataSourceFactory(this, "audio/mpeg") //  userAgent -> audio/mpeg  不能為空
    val concatenatingMediaSource = ConcatenatingMediaSource() //建立一個媒體連線源
    val mediaSource1 = ExtractorMediaSource.Factory(defaultDataSourceFactory)
            .createMediaSource(Uri.parse("http://xiaxiayige.u.qiniudn.com/Big%20Big%20World.mp3")) //建立一個播放資料來源
    concatenatingMediaSource.addMediaSource(mediaSource1)
    player.playWhenReady = true
    player.prepare(concatenatingMediaSource)


    b1.setOnClickListener {
        speed -= 0.1f
        if (speed <= 0) speed = 0.1f
        player.playbackParameters = PlaybackParameters(speed, pitch)
    }

    b2.setOnClickListener {
        speed += 0.1f
        player.playbackParameters = PlaybackParameters(speed, pitch)
    }


    b4.setOnClickListener {
        pitch -= 0.1f
        player.playbackParameters = PlaybackParameters(speed, pitch)
    }

    b5.setOnClickListener {
        pitch += 0.1f
        player.playbackParameters = PlaybackParameters(speed, pitch)
    }


}
}

前面的程式碼基本一致,沒有做什麼變動

上圖可以看到,頂部有一個進度條,那麼,我們也順便來做下如何獲取播放進度吧,並且調整進度。

步驟:

 1.需要通過監聽音訊開始播放的時候
 2.需要不斷的去獲取當前播放進度,也就是需要一個定時器去獲取當前播放時長
 3.通過SeekBar調整播放進度

/***
* 倍速播放
 */
class AudioPlayerDemo_D : AppCompatActivity() {
var speed = 1.0f
var pitch = 1.0f
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_audio_player_demo_d)

    val player = ExoPlayerFactory.newSimpleInstance(this, DefaultTrackSelector())
    val defaultDataSourceFactory = DefaultDataSourceFactory(this, "audio/mpeg") //  userAgent -> audio/mpeg  不能為空
    val concatenatingMediaSource = ConcatenatingMediaSource() //建立一個媒體連線源
    val mediaSource1 = ExtractorMediaSource.Factory(defaultDataSourceFactory)
            .createMediaSource(Uri.parse("http://xiaxiayige.u.qiniudn.com/Big%20Big%20World.mp3")) //建立一個播放資料來源
    concatenatingMediaSource.addMediaSource(mediaSource1)
    player.playWhenReady = true
    player.prepare(concatenatingMediaSource)


    b1.setOnClickListener {
        speed -= 0.1f
        if (speed <= 0) speed = 0.1f
        player.playbackParameters = PlaybackParameters(speed, pitch)
    }

    b2.setOnClickListener {
        speed += 0.1f
        player.playbackParameters = PlaybackParameters(speed, pitch)
    }


    b4.setOnClickListener {
        pitch -= 0.1f
        player.playbackParameters = PlaybackParameters(speed, pitch)
    }

    b5.setOnClickListener {
        pitch += 0.1f
        player.playbackParameters = PlaybackParameters(speed, pitch)
    }

    val playHandler = PlayHandler(seek, player)
    concatenatingMediaSource.addEventListener(playHandler, object : DefaultMediaSourceEventListener() {
        override fun onLoadStarted(windowIndex: Int, mediaPeriodId: MediaSource.MediaPeriodId?, loadEventInfo: MediaSourceEventListener.LoadEventInfo?, mediaLoadData: MediaSourceEventListener.MediaLoadData?) {
            super.onLoadStarted(windowIndex, mediaPeriodId, loadEventInfo, mediaLoadData)
            seek.max = player.duration.toInt()
            playHandler.sendEmptyMessage(0)
        }
    })

    seek.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
        override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
        }

        override fun onStartTrackingTouch(seekBar: SeekBar?) {
        }

        override fun onStopTrackingTouch(seekBar: SeekBar?) {
            player.seekTo(seekBar?.progress!!.toLong())
        }

    })


}

//更新進度條
class PlayHandler(seekBar: SeekBar, player: ExoPlayer) : Handler() {
    val seekBars = WeakReference<SeekBar>(seekBar)
    val players = WeakReference<ExoPlayer>(player)

    override fun handleMessage(msg: Message?) {
        super.handleMessage(msg)
        when (msg?.what) {
            0 -> {
                seekBars.get()?.max = players.get()?.duration!!.toInt()
                seekBars.get()?.progress = players.get()?.currentPosition!!.toInt()
                sendEmptyMessageDelayed(0, 300)
            }
        }
    }
}

}

結尾

好的,到這裡基本上暫時告一段落,從上面,學習了

1.如何使用ExoPlayer進行簡單的音訊的播放
2.使用ExoPlayer對多個音訊順序播放或者迴圈播放,並且使用concatenatingMediaSource可以在過程中操作更新MediaSource-新增或移除,MergingMediaSource不能實現更新操作,
3.學習了音訊的裁剪,可以指定播放某一段音訊資料
4.學習了音訊的倍速播放,已經音訊的聲調修改
5.使用SeekBar實現對ExoPlayer指定播放,並且獲取播放當前位置

最後的最後在Activity銷燬的時候別忘記釋放掉player例項

 override fun onDestroy() {
    super.onDestroy()
    player?.release()
}