自定义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

    }

}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335