做安卓家用镜项目,自定义一款播放器,方便以后修改。
包括功能
0、查询本地所有音乐,筛选小于30s的音频文件;
1、播放、暂停;
2、上一曲、下一曲;
3、随机、顺序播放;
4、当前播放时间、总时间、进度条显示;
5、进度条拖动播放;
封装模型
data class MediaEntity (
var id: Int?,
var title: String?,
var displayName: String?,
var path: String?,
var duration: Int?,
var albums: String?,
var artist: String?,
var singer: String?,
var size: Long?
): Serializable
封装为anko使用
inline fun ViewManager.lfmusicPlayer() = lfmusicPlayer() {}
inline fun ViewManager.lfmusicPlayer(init: LFMusicPlayer.() -> Unit): LFMusicPlayer {
return ankoView({ LFMusicPlayer(it) },0,init)
}
image.png
播放器
class LFMusicPlayer: FrameLayout {
object ViewID {
val dt = 181251758
val name = dt + 101
val play = dt + 102
val progress = dt + 103
val currentTime = dt + 104
val totalTime = dt + 105
val random = dt + 106
}
enum class PlayControl {
PLAY, NEXT, PREVIOUS
}
private var progressTimer: Timer? = null
private var isRecycle = false
private var mediaPlayer = MediaPlayer()
private var cnt: Context
private var currentIndex = -1
private var currentPosition = 0
private var musicList: List<MediaEntity> = ArrayList<MediaEntity>()
constructor(context: Context) : this(context, null)
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int): super(context, attrs, defStyleAttr) {
this.cnt = context
//UI
verticalLayout {
linearLayout {
imageView {
imageResource = R.mipmap.ic_random
id = ViewID.random
onClick { randomOrReclycle() }
}.lparams(dip(0), matchParent) {
weight = 1f
margin = dip(5)
}
imageView {
imageResource = R.mipmap.ic_previous
onClick {
playLocalMusic(PlayControl.PREVIOUS)
}
}.lparams(dip(0), matchParent) {
weight = 1f
margin = dip(5)
}
imageView {
imageResource = R.mipmap.ic_play
id = ViewID.play
onClick {
playLocalMusic(PlayControl.PLAY)
}
}.lparams(dip(0), matchParent) {
weight = 1f
margin = dip(5)
}
imageView {
imageResource = R.mipmap.ic_next
onClick {
playLocalMusic(PlayControl.NEXT)
}
}.lparams(dip(0), matchParent) {
weight = 1f
margin = dip(5)
}
imageView {
imageResource = R.mipmap.ic_list
}.lparams(dip(0), matchParent) {
weight = 1f
margin = dip(5)
}
}.lparams(matchParent, dip(0)) {
weight = 1f
leftMargin = dip(20)
rightMargin = dip(20)
}
linearLayout {
textView {
id = ViewID.name
textSize = 17f
textColor = Color.WHITE
// text = musicList[currentIndex].displayName.toString()
}.lparams(wrapContent, wrapContent)
}.lparams(matchParent, dip(0)) {
weight = 1f
leftMargin = dip(20)
rightMargin = dip(20)
}
linearLayout {
//2020110
textView {
textSize = 12f
textColor = Color.WHITE
text = "0:0"
id = ViewID.currentTime
}.lparams(dip(0), matchParent) {
weight = 2f
}
seekBar {
// progressDrawable = context.resources.getDrawable(R.drawable.seek_bar_progress, null)
// thumb = context.resources.getDrawable(R.drawable.seek_bar_thumb, null)
id = ViewID.progress
max = 100
progress = 0
// secondaryProgress = 80
isIndeterminate = false
}.lparams(dip(0), wrapContent) {
weight = 11f
}
textView {
textSize = 12f
textColor = Color.WHITE
// text = "4:32"
id = ViewID.totalTime
}.lparams(dip(0), matchParent) {
weight = 2f
}
}.lparams(matchParent, dip(0)) {
weight = 1f
leftMargin = dip(20)
rightMargin = dip(20)
}
}
this.localMusicData()
this.seekBarChangeAction()
}
private fun randomOrReclycle() {
this.isRecycle = !this.isRecycle
val iv = find<ImageView>(ViewID.random)
doAsync { uiThread {
if (isRecycle) {
iv.imageResource = R.mipmap.ic_recycle
}else {
iv.imageResource = R.mipmap.ic_random
}
} }
}
private fun seekBarChangeAction() {
val seekBar = find<SeekBar>(ViewID.progress)
val tv = find<TextView>(ViewID.currentTime)
seekBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(p0: SeekBar?, p1: Int, p2: Boolean) {
}
override fun onStartTrackingTouch(p0: SeekBar?) {
}
override fun onStopTrackingTouch(p0: SeekBar?) {
currentPosition = ((p0?.progress!!.toFloat()/100)*mediaPlayer.duration.toFloat()).toInt()
playLocalMusic(PlayControl.PLAY, true)
}
})
}
private fun localMusicData() {
this.musicList = this.getAllMediaList()
if (this.musicList.count() < 1) {
currentIndex = -1
}else {
currentIndex = 0
}
this.displayMusicName(currentIndex)
}
//显示歌名
private fun displayMusicName(index: Int) {
val tv = find<TextView>(ViewID.name)
val totalTv = find<TextView>(ViewID.totalTime)
if (index < 0) {
tv.text = "没有本地音乐"
}else {
tv.text = this.musicList[index].displayName.toString()
val m = (this.musicList[index].duration!!/1000)/60
val s = (this.musicList[index].duration!!/1000)%60
totalTv.text = m.toString() + ":" + s.toString()
}
//一定要实现此错误处理方法,否则会很多时候比如reset()调用OnCompletion方法!!!
this.mediaPlayer.setOnErrorListener(object : MediaPlayer.OnErrorListener {
override fun onError(p0: MediaPlayer?, p1: Int, p2: Int): Boolean {
return true
}
})
this.mediaPlayer.setOnCompletionListener(object : MediaPlayer.OnCompletionListener {
override fun onCompletion(p0: MediaPlayer?) {
playLocalMusic(PlayControl.NEXT)
}
})
}
//获取本地(不包括SD卡)的音乐文件
private fun getAllMediaList(): List<MediaEntity> {
var cursor: Cursor? = null
var mediaList = ArrayList<MediaEntity>()
try {
cursor = cnt.contentResolver.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, null, null,
null, MediaStore.Audio.AudioColumns.IS_MUSIC)
if (cursor == null) {
return mediaList
}
var count = cursor.count
if (count <= 0) {
return mediaList
}
mediaList = ArrayList()
var mediaEntity: MediaEntity
while (cursor.moveToNext()) {
mediaEntity = MediaEntity(null, null, null, null, null, null, null, null, null)
mediaEntity.id = cursor.getInt(cursor.getColumnIndex(MediaStore.Audio.Media._ID))
mediaEntity.title = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.TITLE))
mediaEntity.displayName = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.DISPLAY_NAME))
mediaEntity.duration = cursor.getInt(cursor.getColumnIndex(MediaStore.Audio.Media.DURATION))
mediaEntity.size = cursor.getLong(cursor.getColumnIndex(MediaStore.Audio.Media.SIZE))
if (!checkIsMusic(cursor.getInt(cursor.getColumnIndex(MediaStore.Audio.Media.DURATION)), cursor.getLong(cursor.getColumnIndex(MediaStore.Audio.Media.SIZE)))) {
continue
}
mediaEntity.artist = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.ARTIST))
mediaEntity.path = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.DATA))
mediaList.add(mediaEntity)
}
}catch (e: Exception) {
}finally {
if (cursor != null) {
cursor.close()
}
}
return mediaList
}
fun checkIsMusic(time: Int, size: Long): Boolean {
if (time <= 0 || size <= 0) {
return false
}
var time = time/1000
var minute = time/60
var second = time%60
minute %= 60
if (minute <= 0 && second <= 30) {
return false
}
if (size <= 1024 * 1024) {
return false
}
return true
}
//判断本地是否有音乐,再进行播放选择
private fun playLocalMusic(control: PlayControl, isSeekBar: Boolean? = false) {
if (this.musicList.count() < 1) {
val builder = AlertDialog.Builder(this.cnt)
builder.setTitle("没有本地音乐").create().show()
}else {
var index: Int
when (control) {
PlayControl.PLAY -> {
//是否第一次播放
if (currentIndex < 0) currentIndex = 0
//判断是暂停还是播放操作
if (this.mediaPlayer.isPlaying && (!isSeekBar!!)) {
this.mediaPlayer.pause()
currentPosition = this.mediaPlayer.currentPosition
this.playStateChange()
}else {
this.playAssignMusic(currentIndex)
}
}
PlayControl.NEXT -> {
if (isRecycle) {
currentIndex += 1
}else {
//随机
currentIndex = (0..(this.musicList.count()-1)).shuffled().last()
}
index = currentIndex%this.musicList.count()
currentPosition = 0
this.playAssignMusic(index)
}
PlayControl.PREVIOUS -> {
currentIndex -= 1
if (currentIndex < 0) {
currentIndex += this.musicList.count()
}
index = currentIndex%this.musicList.count()
currentPosition = 0
this.playAssignMusic(index)
}
}
}
}
//播放指定音乐
private fun playAssignMusic(index: Int) {
// AlertDialog.Builder(this.cnt).setTitle("播放开始" + index.toString()).show()
try {
// 切歌之前先重置,释放掉之前的资源。注意:reset会触发OnCompletion监听方法!!!
this.mediaPlayer.reset()
this.mediaPlayer.setDataSource(this.musicList[index].path)
this.mediaPlayer.prepareAsync()
//装载完毕,开始播放
this.mediaPlayer.setOnPreparedListener(object : MediaPlayer.OnPreparedListener {
override fun onPrepared(p0: MediaPlayer?) {
//记忆播放
mediaPlayer.seekTo(currentPosition)
mediaPlayer.start()
//变更歌名
doAsync {
uiThread {
displayMusicName(index)
playStateChange()
}
}
}
})
currentIndex = index
}catch (e: Exception) {
doAsync { uiThread {
AlertDialog.Builder(cnt).setTitle("播放错误").create().show()
} }
}
}
private fun playStateChange() {
val img = find<ImageView>(ViewID.play)
if (this.mediaPlayer.isPlaying) {
img.imageResource = R.mipmap.ic_stop
//进度设置
playingProgress()
}else {
img.imageResource = R.mipmap.ic_play
//暂停显示操作
stopProgressTimer()
}
//总时长只设置一次
val tv = find<TextView>(ViewID.totalTime)
val m = (this.mediaPlayer.duration/1000)/60
val s = (this.mediaPlayer.duration/1000)%60
tv.text = m.toString() + ":" + s.toString()
}
private fun playingProgress() {
val tv = find<TextView>(ViewID.currentTime)
val seekBar = find<SeekBar>(ViewID.progress)
//定时器,1秒获取一次
if (progressTimer == null) {
progressTimer = Timer()
}
val task = object : TimerTask() {
override fun run() {
//获取当前播放位置
val m = (mediaPlayer.currentPosition/1000)/60
val s = (mediaPlayer.currentPosition/1000)%60
val progress = (mediaPlayer.currentPosition.toFloat()/mediaPlayer.duration.toFloat())
doAsync { uiThread {
tv.text = m.toString() + ":" + s.toString()
seekBar.progress = (progress*100).toInt()
} }
}
}
progressTimer?.schedule(task, 0, 1000)
}
private fun stopProgressTimer() {
progressTimer?.cancel()
progressTimer = null
}
}
网友评论