天天看點

Android學習------ExoPlayer的學習和使用(音頻)(一)ExoPlayer的學習和使用(音頻)(一)結尾

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

1.前言

Google GitHub ExoPlayer位址:https://github.com/google/ExoPlayer

相關教程網站:

https://google.github.io/ExoPlayer/guide.html

簡要說明:

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來存儲添加的資料。

這裡需要了解一下 LoopingMediaSource ,MergingMediaSource,ConcatenatingMediaSource,ClippingMediaSource 這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>
           

如圖:

Android學習------ExoPlayer的學習和使用(音頻)(一)ExoPlayer的學習和使用(音頻)(一)結尾

前面已經說了通過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()
}
           

Github:

https://github.com/zhangiqlin/ExoPlayerDemo