自定义MediaPlayer音频播放器

做安卓家用镜项目,自定义一款播放器,方便以后修改。
包括功能

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

    }

}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。