Search code examples
javaandroidkotlinmemory-leaksandroid-mediaplayer

How to memory management in Android MediaPlayer?


I used Android 4.4.4 version. And My application used MediaPlayer.

I have get media list in device and each media put in mutableListOf. Next In the for loop that medias played.

But, It was dead after some time.

10-18 12:58:29.599 1466-1466/? E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.hanmedia.android, PID: 1466
    java.lang.OutOfMemoryError
        at android.graphics.BitmapFactory.nativeDecodeAsset(Native Method)
        at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:594)
        at android.graphics.BitmapFactory.decodeResourceStream(BitmapFactory.java:429)
        at android.graphics.drawable.Drawable.createFromResourceStream(Drawable.java:840)
        at android.content.res.Resources.loadDrawable(Resources.java:2110)
        at android.content.res.Resources.getDrawable(Resources.java:700)
        at android.graphics.drawable.AnimationDrawable.inflate(AnimationDrawable.java:282)
        at android.graphics.drawable.Drawable.createFromXmlInner(Drawable.java:937)
        at android.graphics.drawable.Drawable.createFromXml(Drawable.java:877)
        at android.content.res.Resources.loadDrawable(Resources.java:2092)
        at android.content.res.TypedArray.getDrawable(TypedArray.java:602)
        at android.widget.ProgressBar.<init>(ProgressBar.java:294)
        at android.widget.ProgressBar.<init>(ProgressBar.java:246)
        at android.widget.AbsSeekBar.<init>(AbsSeekBar.java:69)
        at android.widget.SeekBar.<init>(SeekBar.java:83)
        at androidx.appcompat.widget.AppCompatSeekBar.<init>(AppCompatSeekBar.java:50)
        at androidx.appcompat.widget.AppCompatSeekBar.<init>(AppCompatSeekBar.java:45)
        at androidx.appcompat.app.AppCompatViewInflater.createSeekBar(AppCompatViewInflater.java:256)
        at androidx.appcompat.app.AppCompatViewInflater.createView(AppCompatViewInflater.java:163)
        at androidx.appcompat.app.AppCompatDelegateImpl.createView(AppCompatDelegateImpl.java:1551)
        at androidx.appcompat.app.AppCompatDelegateImpl.onCreateView(AppCompatDelegateImpl.java:1602)
        at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:684)
        at android.view.LayoutInflater.rInflate(LayoutInflater.java:755)
        at android.view.LayoutInflater.rInflate(LayoutInflater.java:758)
        at android.view.LayoutInflater.inflate(LayoutInflater.java:492)
        at android.view.LayoutInflater.inflate(LayoutInflater.java:397)
        at android.view.LayoutInflater.inflate(LayoutInflater.java:353)
        at android.widget.MediaController.makeControllerView(MediaController.java:244)
        at android.widget.MediaController.setAnchorView(MediaController.java:232)
        at android.widget.VideoView.attachMediaController(VideoView.java:380)
        at android.widget.VideoView.openVideo(VideoView.java:349)
        at android.widget.VideoView.access$2100(VideoView.java:71)
        at android.widget.VideoView$7.surfaceCreated(VideoView.java:607)
        at android.view.SurfaceView.updateWindow(SurfaceView.java:572)
        at android.view.SurfaceView.access$000(SurfaceView.java:86)
        at android.view.SurfaceView$3.onPreDraw(SurfaceView.java:175)
        at android.view.ViewTreeObserver.dispatchOnPreDraw(ViewTreeObserver.java:847)
        at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1876)
        at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1001)
        at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:5680)
        at android.view.Choreographer$CallbackRecord.run(Choreographer.java:761)
        at android.view.Choreographer.doCallbacks(Choreographer.java:574)
        at android.view.Choreographer.doFrame(Choreographer.java:544)
        at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:747)
        at android.os.Handler.handleCallback(Handler.java:733)
        at android.os.Handler.dispatchMessage(Handler.java:95)
        at android.os.Looper.loop(Looper.java:136)
        at android.app.ActivityThread.main(ActivityThread.java:5001)
        at java.lang.reflect.Method.invokeNative(Native Method)
        at java.lang.reflect.Method.invoke(Method.java:515)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:815)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:631)
        at dalvik.system.NativeStart.main(Native Method)

I guess MediaPlayer use many memory. And Android can't clear buffer when play each media. How to clear buffer in this code?

// class NormalFragment : Fragment() Code

// Type is mutableListOf<MediaItemPlayer>
val playerList = device.mediaLinks
                .filter { link ->
                    link.mediaLinkType == type &&
                            nowKr.time < link.expire
                }
                .sortedBy {
                    it.position
                }
                .let { links ->
                    val playerList = mutableListOf<MediaItemPlayer>()
                    links.forEachIndexed { index, _ ->
                        links[index].waitTime = device.pictureSlideTime.toLong()
                        links[index].sound = sound
                        it.runOnUiThread {
                            val player = MediaItemPlayer(it, links[index])
                            view.addView(player.view)
                            playerList.add(player)
                        }
                    }
                    playerList
                }.toList()

// And then loop each video
playerList.forEachIndexed { index, mediaItemPlayer ->

                    val listener = if (index != (playerList.count() - 1)) {
                        object : PlayerEventListener {
                            override fun next() {
                                it.runOnUiThread {
                                    logger.i("[${type}] $index")
                                    val playIndex = (index + 1) % playerList.size
                                    val prepareIndex = (index + 2) % playerList.size

                                    playerList[playIndex].run {
                                        setVolume(getSound(type))
                                        start()
                                    }

                                    playerList[prepareIndex].run{
                                        prepare()
                                    }

                                }
                            }
// MediaItemPlayer class
// class MediaItemPlayer(private val context : Context, private val mediaLink : DeviceWithMediaLink)

private var repeatCount = 0
    private var ready : Boolean = false
    private var startFlag = false
    private var startIgnoreFlag = false
    private var prepareFlag = false

    private lateinit var player : MediaPlayer
    private lateinit var nextListener: PlayerEventListener

fun start(){
        if(ready) {
            logger.i("ready [${mediaLink.mediaLinkType}]${mediaLink.media.originalName}")
            when (mediaLink.media.mediaType) {
                MediaType.TEXT, MediaType.PICTURE -> {
                    val delayTime = mediaLink.waitTime * (mediaLink.repeatCount + 1)
                    logger.i("[${mediaLink.mediaLinkType}] delay $delayTime")
                    view.postDelayed({
                        goNext()
                    }, delayTime)
                }
                MediaType.VIDEO -> {
                    logger.i("[${mediaLink.mediaLinkType}] Video start")
                    if(prepareFlag){
                        player.start()
                    }else{
                        view.postDelayed({
                            if(!prepareFlag) {
                                logger.e("[${mediaLink.mediaLinkType}] not prepare")
                                startIgnoreFlag = true
                                logger.e("[${mediaLink.mediaLinkType}] start ignore and play next video")
                                nextListener.next()
                            }
                        }, 2000)
                        startFlag = true
                    }
                }
            }
            context.runOnUiThread {
                view.visibility = View.VISIBLE
            }
        }else{
            logger.i("not ready [${mediaLink.mediaLinkType}]${mediaLink.media.originalName}")
            goNext()
        }
    }

    private fun goNext(){
        nextListener.next()
        context.runOnUiThread {
            view.visibility = View.GONE
        }
    }

Solution

  • I think the problem is that you are creating mutiple instances of MediaPlayer which is causing OutOfMemoryError exception. Also, do not forget to release MediaPlayer instance when you are done with playback. According to MediaPlayer Documentation:

    It is also recommended that once a MediaPlayer object is no longer being used, call release() immediately so that resources used by the internal player engine associated with the MediaPlayer object can be released immediately. Resource may include singleton resources such as hardware acceleration components and failure to call release() may cause subsequent instances of MediaPlayer objects to fallback to software implementations or fail altogether. Once the MediaPlayer object is in the End state, it can no longer be used and there is no way to bring it back to any other state.

    Add a method in MediaItemPlayer to release MediaPlayer after use:

        private fun releaseMediaPlayer(){
           if (player.isPlaying()) {
               player.stop();
           }
           player.reset();
           player.release();
           player = null;
    }
    

    Then call releaseMediaPlayer from NormalFragment after you are done playing a media file.