在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的小例子
其實(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)阿宅,如下所示。
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í)在實(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")
}
}