Android之媒體探究(一)—— 音頻

在android 中隨著手機(jī)性能的不斷完善和提高,傳統(tǒng)的圖文慢慢轉(zhuǎn)向了音視頻词裤,因?yàn)橄鄬?duì)于音視頻的信息傳遞寨昙,傳統(tǒng)的圖文就顯得有些慢了僻肖,所以現(xiàn)在很多的APP里面或多或少都會(huì)包含一些音視頻的功能在里面掠归,所以對(duì)于開(kāi)發(fā)者而言缅叠,學(xué)習(xí)和掌握音視頻技術(shù)就顯得比較重要了。廢話不多說(shuō)虏冻,直接進(jìn)入正題肤粱。

音頻部分分為兩大塊,一個(gè)是系統(tǒng)自帶的MediaPlayer厨相,一個(gè)是框架ExoPlayer领曼。

系統(tǒng)自帶的MediaPlayer

看過(guò)源碼的同學(xué)都知道,MediaPlayer在應(yīng)用層并沒(méi)有做太多的事情蛮穿,大部分的工作全部是交給Native層去實(shí)現(xiàn)的庶骄,也就是交給C/C++去實(shí)現(xiàn)的。而對(duì)于應(yīng)用層來(lái)說(shuō)践磅,使用起來(lái)非常簡(jiǎn)單单刁,直接調(diào)用系統(tǒng)的API就可以了,現(xiàn)在具體來(lái)看看府适。

第一步:創(chuàng)建MediaPlayer

創(chuàng)建MediaPlayer的方式有兩種:

通過(guò)new的方式去獲取MediaPlayer的實(shí)例
MediaPlayer mp = new MediaPlayer();

使用create的方式
MediaPlayer mp = MediaPlayer.create(this, R.raw.test);//這時(shí)就不用調(diào)用setDataSource

第二步:設(shè)置播放的音頻文件

MediaPlayer要播放的文件主要包括3個(gè)來(lái)源:

a. 用戶在應(yīng)用中事先自帶的resource資源
例如:MediaPlayer.create(this, R.raw.test);

b. 存儲(chǔ)在SD卡或其他文件路徑下的媒體文件
例如:mp.setDataSource("/sdcard/test.mp3");

c. 網(wǎng)絡(luò)上的媒體文件
例如:mp.setDataSource("http://www.citynorth.cn/music/confucius.mp3");

在這里需要強(qiáng)調(diào)的是MediaPlayer的setDataSource的重載方法有很多羔飞,下面重點(diǎn)介紹以下幾種:
(1)setDataSource (String path)
其中path可以是本地的音頻文件的絕對(duì)路徑也可以是網(wǎng)絡(luò)路徑

(2)setDataSource (Context context, Uri uri)
將本地的資源轉(zhuǎn)換為Uri

(3)setDataSource (FileDescriptor fd, long offset, long length)
使用FileDescriptor時(shí),需要將文件放到與res文件夾平級(jí)的assets文件夾里檐春。

AssetFileDescriptor fileDescriptor = getAssets().openFd("rain.mp3");
mediaPlayer.setDataSource(fileDescriptor.getFileDescriptor(),fileDescriptor.getStartOffset(), fileDescriptor.getLength());
第三步:裝載媒體資源

在設(shè)置好播放的媒體資源之后還需要做一個(gè)操作就是裝載資源逻淌,也就是將流設(shè)置到MediaPlayer當(dāng)中去,系統(tǒng)提供了兩種裝載資源的方法疟暖,即使用prepare()或prepareAsync()方法把流媒體裝載進(jìn)MediaPlayer卡儒,才可以調(diào)用start()方法播放流媒體。一般情況下我們?yōu)榱朔乐苟氯骶€程會(huì)采取異步裝載的方式俐巴,即

mediaPlayer.prepareAsync()

第四步:執(zhí)行播放操作

在裝載結(jié)束后朋贬,可以直接調(diào)用start()方法來(lái)開(kāi)始播放我們的音頻文件了,那么這里就會(huì)有一個(gè)疑問(wèn)窜骄,什么時(shí)候知道裝載結(jié)束了呢锦募?不要慌,系統(tǒng)為我們提供了監(jiān)聽(tīng)的方法setOnPreparedListener

mediaPlayer?.setOnPreparedListener {
            mediaPlayer?.start()
}
至此邻遏,一個(gè)完整的播放流程就結(jié)束了糠亩,當(dāng)然除此之外還有一些比較重要的知識(shí)點(diǎn),如下所示准验。

知識(shí)點(diǎn)一:MediaPlayer的API

void start():開(kāi)始或者恢復(fù)播放

void stop():停止播放

void pause():暫停播放

這3個(gè)方法是控制播放狀態(tài)的赎线,除此之外還有一些其他的API,如下所示

int getDuration():獲取流媒體的總播放時(shí)長(zhǎng)糊饱,單位是毫秒垂寥。
int getCurrentPosition():獲取當(dāng)前流媒體的播放的位置,單位是毫秒。
void seekTo(int msec):設(shè)置當(dāng)前MediaPlayer的播放位置滞项,單位是毫秒狭归。
void setLooping(boolean looping):設(shè)置是否循環(huán)播放。
boolean isLooping():判斷是否循環(huán)播放文判。
boolean isPlaying():判斷是否正在播放过椎。
void prepare():同步的方式裝載流媒體文件。
void prepareAsync():異步的方式裝載流媒體文件戏仓。
void release ():回收流媒體資源疚宇。
void setAudioStreamType(int streamtype):設(shè)置播放流媒體類(lèi)型仆邓。
void setWakeMode(Context context, int mode):設(shè)置CPU喚醒的狀態(tài)烂瘫。

需要強(qiáng)調(diào)說(shuō)明的是:
setAudioStreamType()方法用于指定播放流媒體的類(lèi)型纳猪,它傳遞的是一個(gè)int類(lèi)型的數(shù)據(jù)厢呵,均以常量定義在AudioManager類(lèi)中桩皿, 一般我們播放音頻文件揍很,設(shè)置為AudioManager.STREAM_MUSIC即可

知識(shí)點(diǎn)二:MediaPlayer的回調(diào)
(1) setOnCompletionListener(MediaPlayer.OnCompletionListener listener):當(dāng)流媒體播放完畢的時(shí)候回調(diào)再菊。
(2) setOnErrorListener(MediaPlayer.OnErrorListener listener):當(dāng)播放中發(fā)生錯(cuò)誤的時(shí)候回調(diào)摹闽。
(3) setOnPreparedListener(MediaPlayer.OnPreparedListener listener):當(dāng)裝載流媒體完畢的時(shí)候回調(diào)股耽。
(4) setOnSeekCompleteListener(MediaPlayer.OnSeekCompleteListener listener):當(dāng)使用seekTo()設(shè)置播放位置的時(shí)候回調(diào)根盒。

知識(shí)點(diǎn)三:MediaPlayer的回收
使用完MediaPlayer需要回收資源。MediaPlayer是很消耗系統(tǒng)資源的物蝙,所以在使用完MediaPlayer炎滞,不要等待系統(tǒng)自動(dòng)回收,最好是主動(dòng)回收資源诬乞。

if (mediaPlayer != null && mediaPlayer.isPlaying()) {
           mediaPlayer.stop();
           mediaPlayer.release();
           mediaPlayer = null;
}

知識(shí)點(diǎn)四:MediaPlayer的小例子

mediaPlayer.jpg

其實(shí)在實(shí)際開(kāi)發(fā)中册赛,我們應(yīng)該將播放的邏輯與Service相結(jié)合,這里為了測(cè)試效果就沒(méi)有寫(xiě)的那么完整

class MediaPlayerActivity : AppCompatActivity() {

    private var mediaPlayer: MediaPlayer? = null
    private var mList: MutableList<String> = mutableListOf()
    private var mCurrentPlayPosition = 0

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        initMusic()
        initMediaPlayer()
    }

    /**
     * 加載音頻文件
     */
    private fun initMusic() {
        mList.add("https://xysx-voice.oss-cn-shanghai.aliyuncs.com/audio/2e76a133b898a2f6c8fb62e963b87ce4_tongkuercanlandeyisheng.mp3")
        mList.add("https://xysx-voice.oss-cn-shanghai.aliyuncs.com/audio/e845a564e795c628c6bdd652ddbc24e3_fulidadeqingshaonianshidai.mp3")
        mList.add("https://xysx-voice.oss-cn-shanghai.aliyuncs.com/audio/e9b9abdcc3855586ec2c4651293c11e9_abuyuwulei.mp3")
        mList.add("https://xysx-voice.oss-cn-shanghai.aliyuncs.com/audio/ba2eaf007614fdf81c8d9e895ba88ff2_abuchuangzuodekaishi.mp3")
    }

    /**
     * 初始化MediaPlayer
     */
    private fun initMediaPlayer() {
        // 1.實(shí)例化MediaPlayer
        // 也可以使用create的方式震嫉,如:
        // MediaPlayer mp = MediaPlayer.create(this, R.raw.test);//這時(shí)就不用調(diào)用setDataSource了
        mediaPlayer = MediaPlayer()
        // 2.設(shè)置播放的音頻文件
        // 設(shè)置的音頻文件的來(lái)源有3種
        // a. 用戶在應(yīng)用中事先自帶的resource資源
        // 例如:MediaPlayer.create(this, R.raw.test);
        // b. 存儲(chǔ)在SD卡或其他文件路徑下的媒體文件,傳入的是本地音頻文件的絕對(duì)路徑
        // 例如:mp.setDataSource("/sdcard/test.mp3");
        // c. 網(wǎng)絡(luò)上的媒體文件森瘪,傳入的是網(wǎng)絡(luò)地址
        // 例如:mp.setDataSource("http://www.citynorth.cn/music/confucius.mp3");
        mediaPlayer?.setDataSource(mList[mCurrentPlayPosition])
        // MediaPlayer的setDataSource一共四個(gè)方法:
        // setDataSource (String path)
        // setDataSource (FileDescriptor fd)
        // setDataSource (Context context, Uri uri)
        // setDataSource (FileDescriptor fd, long offset, long length)
        // 其中使用FileDescriptor時(shí),需要將文件放到與res文件夾平級(jí)的assets文件夾里票堵,然后使用:
        // AssetFileDescriptor fileDescriptor = getAssets().openFd("rain.mp3");
        // mediaPlayer.setDataSource(fileDescriptor.getFileDescriptor(),fileDescriptor.getStartOffset(), fileDescriptor.getLength());來(lái)設(shè)置datasource
        // 3.裝載我們的音頻文件
        mediaPlayer?.prepareAsync()
        // 4.播放我們的音頻文件
        mediaPlayer?.setOnPreparedListener {
            mediaPlayer?.start()
        }
    }

    /**
     * 上一首
     */
    fun lastMusic(view: View) {
        mediaPlayer?.reset()
        if (mCurrentPlayPosition == 0) {
            mCurrentPlayPosition = mList.size - 1
        } else {
            mCurrentPlayPosition--
        }
        mediaPlayer?.setDataSource(mList[mCurrentPlayPosition])
        mediaPlayer?.prepareAsync()
        mediaPlayer?.setOnPreparedListener {
            mediaPlayer?.start()
        }
    }

    /**
     * 播放
     */
    fun startMusic(view: View) {
        mediaPlayer?.start()
    }

    /**
     * 暫停
     */
    fun pauseMusic(view: View) {
        mediaPlayer?.pause()
    }

    /**
     * 結(jié)束
     */
    fun stopMusic(view: View) {
        mediaPlayer?.stop()
    }

    /**
     * 下一首
     */
    fun nextMusic(view: View) {
        try {
            mediaPlayer?.reset()
            if (mCurrentPlayPosition == mList.size - 1) {
                mCurrentPlayPosition = 0
            } else {
                mCurrentPlayPosition++
            }
            mediaPlayer?.setDataSource(mList[mCurrentPlayPosition])
            mediaPlayer?.prepareAsync()
            mediaPlayer?.setOnPreparedListener {
                mediaPlayer?.start()
            }
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }
}

框架ExoPlayer

ExoPlayer是運(yùn)行在YouTube app Android版本上的視頻播放器而并非系統(tǒng)內(nèi)置的API扼睬,所以相對(duì)于MediaPlayer來(lái)說(shuō),其功能更加強(qiáng)大悴势,除了MediaPlayer本身所支持的格式外窗宇,ExoPlayer還可以支持DASH和SmoothStreaming格式。接下來(lái)我們就一起來(lái)看看吧特纤。

ExoPlayer庫(kù)的核心是ExoPlayer接口军俊。ExoPlayer公開(kāi)了傳統(tǒng)的高水平媒體播放器的功能,例如媒體緩沖捧存,播放粪躬,暫停和快進(jìn)功能担败。ExoPlayer實(shí)現(xiàn)旨在對(duì)正在播放的媒體類(lèi)型,存儲(chǔ)方式和位置以及渲染方式做出一些假設(shè)(因此幾乎沒(méi)有限制)镰官。ExoPlayer沒(méi)有直接實(shí)現(xiàn)媒體文件的加載和渲染提前,而是把這些工作委托給了在創(chuàng)建播放器或者播放器準(zhǔn)備好播放的時(shí)候注入的組件。所有ExoPlayer實(shí)現(xiàn)的通用組件有:

(1) ExoPlayer:創(chuàng)建ExoPlayer的實(shí)現(xiàn)實(shí)例

 if (mExoPlayer == null) {
     // 創(chuàng)建ExoPlayer的實(shí)例
     mExoPlayer = ExoPlayerFactory.newSimpleInstance(this, DefaultRenderersFactory(this), DefaultTrackSelector(), DefaultLoadControl())
     // 添加ExoPlayer的監(jiān)聽(tīng)
     mExoPlayer!!.addListener(this)
}

對(duì)于ExoPlayer來(lái)說(shuō)朋魔,其創(chuàng)建實(shí)例的時(shí)候后面的幾個(gè)參數(shù)是比較重要的岖研,我們點(diǎn)擊進(jìn)去看下源碼里面

  public static SimpleExoPlayer newSimpleInstance(
      Context context,
      RenderersFactory renderersFactory,
      TrackSelector trackSelector,
      LoadControl loadControl) {
    return newSimpleInstance(
        context,
        renderersFactory,
        trackSelector,
        loadControl,
        /* drmSessionManager= */ null,
        Util.getLooper());
  }

參數(shù)1:Context卿操;對(duì)應(yīng)的是上下文實(shí)例警检,這個(gè)沒(méi)什么好說(shuō)的。
參數(shù)2:RenderersFactory害淤;渲染器扇雕,用于渲染媒體文件。當(dāng)創(chuàng)建播放器的時(shí)候窥摄,Renderers被注入镶奉。
參數(shù)3:TrackSelector ;軌道選擇器崭放,用于選擇MediaSource提供的軌道(tracks)哨苛,供每個(gè)可用的渲染器使用。
參數(shù)4:LoadControl 币砂;用于控制MediaSource何時(shí)緩沖更多的媒體資源以及緩沖多少媒體資源建峭。

ExoPlayer庫(kù)提供了在普通使用場(chǎng)景下上述組件的默認(rèn)實(shí)現(xiàn)。ExoPlayer可以使用這些默認(rèn)的組件决摧,也可以使用自定義組件亿蒸。例如可以注入一個(gè)自定義的LoadControl用來(lái)改變播放器的緩存策略,或者可以注入一個(gè)自定義渲染器以使用Android本身不支持的視頻解碼器掌桩。

(2) MediaSource:媒體資源
用于定義要播放的媒體边锁,加載媒體,以及加載媒體的路徑波岛,具體的寫(xiě)法為:

  val uri = Uri.parse(mList!![mPlayBackPosition])
  val mediaSource = ProgressiveMediaSource.Factory(DefaultHttpDataSourceFactory("exoplayer-codelab")).createMediaSource(uri)
  // 設(shè)置好MediaSource之后通過(guò)prepare進(jìn)行注入
  mExoPlayer!!.prepare(mediaSource)

注意Uri.parse()里面設(shè)置的值可以是本地音頻的路徑也可以是網(wǎng)絡(luò)音頻的路徑茅坛。

了解了ExoPlayer的基本信息后,接下來(lái)我們看看到底應(yīng)該怎么去使用则拷。

ExoPlayer的使用
第一步:添加依賴
 implementation 'com.google.android.exoplayer:exoplayer:2.10.5'

為了省事灰蛙,我們依賴了整個(gè)ExoPlayer庫(kù)。你也可以只依賴你真正需要的庫(kù)隔躲。例如果你要播放DASH類(lèi)型的媒體資源摩梧,你可以只依賴Core,DASH,UI這三個(gè)庫(kù)。即

implementation 'com.google.android.exoplayer:exoplayer-core:2.10.5'
implementation 'com.google.android.exoplayer:exoplayer-dash:2.10.5'
implementation 'com.google.android.exoplayer:exoplayer-ui:2.10.5'

整個(gè)ExoPlayer庫(kù)包括5個(gè)子庫(kù)宣旱,依賴了整個(gè)ExoPlayer庫(kù)和依賴5個(gè)子庫(kù)是等效的仅父。

exoplayer-core:核心功能 (必要)
exoplayer-dash:支持DASH內(nèi)容
exoplayer-hls:支持HLS內(nèi)容
exoplayer-smoothstreaming:支持SmoothStreaming內(nèi)容
exoplayer-ui:用于ExoPlayer的UI組件和相關(guān)的資源。
第二步:創(chuàng)建ExoPlayer的實(shí)例
    private var mExoPlayer: ExoPlayer? = null
    if (mExoPlayer == null) {
            mExoPlayer = ExoPlayerFactory.newSimpleInstance(this, DefaultRenderersFactory(this), DefaultTrackSelector(), DefaultLoadControl())
            mExoPlayer!!.addListener(this)
    }
第三步:創(chuàng)建媒體資源類(lèi)MediaSource并裝載資源文件
   val uri = Uri.parse(mList!![mPlayBackPosition])
   val mediaSource = ProgressiveMediaSource.Factory(DefaultHttpDataSourceFactory("exoplayer-codelab")).createMediaSource(uri)
   mExoPlayer!!.prepare(mediaSource)
第四步:開(kāi)始播放
mExoPlayer!!.playWhenReady = true

需要強(qiáng)調(diào)的是ExoPlayer的播放并沒(méi)有像MediaPlayer一樣通過(guò)很明顯的start()、pause()笙纤、stop()方法去實(shí)現(xiàn)耗溜,而是統(tǒng)一使用void setPlayWhenReady(boolean playWhenReady)方法來(lái)實(shí)現(xiàn),true代表的就是播放/繼續(xù)播放省容,false代表的就是暫停/停止抖拴。

至此,一個(gè)完整的播放流程就結(jié)束了腥椒,當(dāng)然除此之外還有一些比較重要的知識(shí)點(diǎn)阿宅,如下所示。

知識(shí)點(diǎn)一:ExoPlayer的API

prepare(MediaSource mediaSource):裝載資源文件
getDuration():獲取流媒體的總播放時(shí)長(zhǎng)笼蛛,單位是毫秒洒放。
setPlayWhenReady(boolean playWhenReady):播放/暫停/繼續(xù)播放
seekTo(long positionMs):設(shè)置當(dāng)前ExoPlayer的播放位置,單位是毫秒滨砍。
isPlaying():當(dāng)前是否在播放
stop():停止播放
getCurrentPosition():獲取當(dāng)前ExoPlayer的播放位置往湿,單位是毫秒。
getRepeatMode():獲取當(dāng)前是否循環(huán)播放
release():釋放播放資源

setRepeatMode(int value):設(shè)置是否循環(huán)播放惋戏,可供取值為(1)Player.REPEAT_MODE_ONE(不允許循環(huán)播放领追,只會(huì)播放一次)
(2)Player.REPEAT_MODE_ALL(循環(huán)播放)

setPlaybackParameters:設(shè)置播放倍速
PlaybackParameters playbackParameters = new PlaybackParameters(speed, 1.0F);
mExoPlayer.setPlaybackParameters(playbackParameters);

當(dāng)然,除了列舉的這些API响逢,還有其他的很多的API绒窑,這就需要我們平時(shí)在使用的過(guò)程中去把握了。

知識(shí)點(diǎn)二:ExoPlayer的回調(diào)

interface EventListener {

    //播放總時(shí)間線改變龄句,這里可用于設(shè)置播放總時(shí)長(zhǎng)
    default void onTimelineChanged(Timeline timeline, @TimelineChangeReason int reason) { 
    }

    //播放資源有改變
    default void onStaticMetadataChanged(List<Metadata> metadataList) {}

    
    //是否在加載
    default void onIsLoadingChanged(boolean isLoading) {
      onLoadingChanged(isLoading);
    }
 
    //播放器播放狀態(tài)改變回论,查看 State 有IDLE,BUFFERING加載中分歇, READY 資源準(zhǔn)備好傀蓉, ENDED 已結(jié)束 
    default void onPlaybackStateChanged(@State int state) {}

   //視頻資源準(zhǔn)備好就播放的設(shè)置改變    
    default void onPlayWhenReadyChanged(
        boolean playWhenReady, @PlayWhenReadyChangeReason int reason) {}
 
    //播放狀態(tài)改變,開(kāi)始播放或暫停
    default void onIsPlayingChanged(boolean isPlaying) {}

    //重復(fù)播放的模式改變 
    default void onRepeatModeChanged(@RepeatMode int repeatMode) {}
 
    default void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) {}

    //播放器報(bào)錯(cuò)
    default void onPlayerError(ExoPlaybackException error) {}
 

    //參數(shù)改變    
    default void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {}
 
     
    default void onEvents(Player player, Events events) {}
  }

知識(shí)點(diǎn)三:ExoPlayer的回收
使用完ExoPlayer需要回收資源职抡。ExoPlayer是很消耗系統(tǒng)資源的葬燎,所以在使用完ExoPlayer,不要等待系統(tǒng)自動(dòng)回收缚甩,最好是主動(dòng)回收資源谱净。

if (mExoPlayer!= null) {
           mExoPlayer.stop();
           mExoPlayer.release();
           mExoPlayer= null;
}

知識(shí)點(diǎn)四:ExoPlayer的小例子

mediaPlayer.jpg

其實(shí)在實(shí)際開(kāi)發(fā)中,我們應(yīng)該將播放的邏輯與Service相結(jié)合擅威,這里為了測(cè)試效果就沒(méi)有寫(xiě)的那么完整

/**
 * 框架ExoPlayer制作簡(jiǎn)單的媒體播放器
 */
class MainActivity : AppCompatActivity(), Player.EventListener {

    private var mList: MutableList<String>? = null
    private var mExoPlayer: ExoPlayer? = null
    private var mPlayWhenReady: Boolean = false
    private var mPlayBackPosition = 0

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        initMusic()
        initializePlayer()
    }

    /**
     * 加載音頻文件
     */
    private fun initMusic() {
        mList = ArrayList()
        mList!!.add("https://xysx-voice.oss-cn-shanghai.aliyuncs.com/audio/2e76a133b898a2f6c8fb62e963b87ce4_tongkuercanlandeyisheng.mp3")
        mList!!.add("https://xysx-voice.oss-cn-shanghai.aliyuncs.com/audio/e845a564e795c628c6bdd652ddbc24e3_fulidadeqingshaonianshidai.mp3")
        mList!!.add("https://xysx-voice.oss-cn-shanghai.aliyuncs.com/audio/e9b9abdcc3855586ec2c4651293c11e9_abuyuwulei.mp3")
        mList!!.add("https://xysx-voice.oss-cn-shanghai.aliyuncs.com/audio/ba2eaf007614fdf81c8d9e895ba88ff2_abuchuangzuodekaishi.mp3")
    }

    /**
     * 初始化媒體播放器
     */
    private fun initializePlayer() {
        if (mExoPlayer == null) {
            mExoPlayer = ExoPlayerFactory.newSimpleInstance(this, DefaultRenderersFactory(this), DefaultTrackSelector(), DefaultLoadControl())
            mExoPlayer!!.addListener(this)
        }
        // 創(chuàng)建一個(gè)音頻文件
        val uri = Uri.parse(mList!![mPlayBackPosition])
        val mediaSource = ProgressiveMediaSource.Factory(DefaultHttpDataSourceFactory("exoplayer-codelab")).createMediaSource(uri)
        mExoPlayer!!.prepare(mediaSource)
        mExoPlayer!!.playWhenReady = mPlayWhenReady
    }

    /**
     * 釋放媒體資源
     */
    private fun releasePlayer() {
        if (mExoPlayer != null) {
            mExoPlayer!!.stop()
            mExoPlayer!!.release()
            mExoPlayer = null
        }
    }

    /**
     * 上一首
     */
    fun lastMusic(view: View) {
        releasePlayer()
        mPlayWhenReady = true
        if (mPlayBackPosition == 0) {
            mPlayBackPosition = mList!!.size - 1
        } else {
            mPlayBackPosition--
        }
        initializePlayer()
    }


    /**
     * 播放
     */
    fun startMusic(view: View) {
        mPlayWhenReady = true
        mExoPlayer!!.playWhenReady = mPlayWhenReady
    }

    /**
     * 暫停
     */
    fun pauseMusic(view: View) {
        mPlayWhenReady = false
        mExoPlayer!!.playWhenReady = mPlayWhenReady
    }

    /**
     * 結(jié)束
     */
    fun stopMusic(view: View) {
        mPlayWhenReady = false
        mExoPlayer!!.stop()
        mExoPlayer!!.release()
    }


    /**
     * 下一首
     */
    fun nextMusic(view: View) {
        releasePlayer()
        mPlayWhenReady = true
        if (mPlayBackPosition == mList!!.size - 1) {
            mPlayBackPosition = 0
        } else {
            mPlayBackPosition++
        }
        initializePlayer()
    }

     /**
     * 播放狀態(tài)監(jiān)聽(tīng)
     */
    override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) {
        var stateString: String? = null
        if (playWhenReady && playbackState === Player.STATE_READY) {
            Log.d("zhoufan", "onPlayerStateChanged: actually playing media")
        }
        stateString = when (playbackState) {
            Player.STATE_IDLE -> "ExoPlayer.STATE_IDLE      -"
            Player.STATE_BUFFERING -> "ExoPlayer.STATE_BUFFERING -"
            Player.STATE_READY -> {
                "ExoPlayer.STATE_READY     -"
            }
            Player.STATE_ENDED -> "ExoPlayer.STATE_ENDED     -"
            else -> "UNKNOWN_STATE             -"
        }
        Log.d("zhoufan", "changed state to $stateString playWhenReady: $playWhenReady")
    }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末壕探,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子郊丛,更是在濱河造成了極大的恐慌李请,老刑警劉巖瞧筛,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異导盅,居然都是意外死亡较幌,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)白翻,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)乍炉,“玉大人,你說(shuō)我怎么就攤上這事滤馍〉呵恚” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵纪蜒,是天一觀的道長(zhǎng)衷恭。 經(jīng)常有香客問(wèn)我此叠,道長(zhǎng)纯续,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任灭袁,我火速辦了婚禮猬错,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘茸歧。我一直安慰自己倦炒,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布软瞎。 她就那樣靜靜地躺著逢唤,像睡著了一般。 火紅的嫁衣襯著肌膚如雪涤浇。 梳的紋絲不亂的頭發(fā)上鳖藕,一...
    開(kāi)封第一講書(shū)人閱讀 51,631評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音只锭,去河邊找鬼著恩。 笑死,一個(gè)胖子當(dāng)著我的面吹牛蜻展,可吹牛的內(nèi)容都是我干的喉誊。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼纵顾,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼伍茄!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起施逾,我...
    開(kāi)封第一講書(shū)人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤敷矫,失蹤者是張志新(化名)和其女友劉穎贞盯,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體沪饺,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡躏敢,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了整葡。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片件余。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖遭居,靈堂內(nèi)的尸體忽然破棺而出啼器,到底是詐尸還是另有隱情,我是刑警寧澤俱萍,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布端壳,位于F島的核電站,受9級(jí)特大地震影響枪蘑,放射性物質(zhì)發(fā)生泄漏损谦。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一岳颇、第九天 我趴在偏房一處隱蔽的房頂上張望照捡。 院中可真熱鬧,春花似錦话侧、人聲如沸栗精。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)悲立。三九已至,卻和暖如春新博,著一層夾襖步出監(jiān)牢的瞬間薪夕,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工叭披, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留寥殖,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓涩蜘,卻偏偏與公主長(zhǎng)得像嚼贡,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子同诫,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

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

  • 一粤策、流媒體 什么是流媒體技術(shù)?簡(jiǎn)單的說(shuō),就是邊下載误窖,邊播放叮盘。也就是說(shuō)秩贰,客戶端在播放前,無(wú)需下載整個(gè)媒體文件柔吼,而是在...
    szy啊閱讀 16,077評(píng)論 2 23
  • Android的多媒體框架支持各種常見(jiàn)的多媒體類(lèi)型毒费,這樣在程序中可以很容易地集成音頻、視頻或者圖片愈魏。Android...
    _Ryan閱讀 61,401評(píng)論 13 54
  • Android三種播放視頻的方式(以下內(nèi)容大多使用真機(jī)測(cè)試觅玻,所以沒(méi)有運(yùn)行圖片,大家可以自己實(shí)戰(zhàn)看看) 1培漏、使用其自...
    酷酷的Demo閱讀 297評(píng)論 0 1
  • MediaPlayer是Android中播放視頻最簡(jiǎn)單的控件溪厘,這篇文章將介紹MediaPlaer播放視頻的基本方法...
    iso8859_1閱讀 2,292評(píng)論 0 0
  • 淺談Android MediaPlayer 前言 MediaPlayer是Android中多媒體框架中一個(gè)重要的組...
    sunnyslxie閱讀 219評(píng)論 0 1