0

Searching for days now and cannot figure out why the mediaPlayer fires onCompletion randomly when the orientation of the device changes. It looks like that devices with more memory like the more powerful tablet crashes not so often but sometimes they do also.

My MediaPlayer is running in a Fragment and I tried the persistent MediaPlayer technique with calling the setRetainInstance(true).

As a hint, some devices sometimes says in the LogCat that too much work is being done in the Main Thread when this happened.

Here is my fragment code:

final public class MediaPlayerFragment extends Fragment implements OnCompletionListener, OnPreparedListener, OnBufferingUpdateListener, OnInfoListener, OnErrorListener, SurfaceHolder.Callback {

    /**
     * 
     */
    public final static String  TAG                 = "MediaPlayerFragment";

    private MediaPlayer         mp                  = null;
    private SurfaceView         surfaceView         = null;
    private String              path;

    // create an handler
    private final Handler       myHandler           = new Handler();

    private static final double ASPECT_RATIO        = 4.0 / 3.0;

    /**
     * Update the ui, so that background tasks can call this update on ui thread
     */
    final Runnable              updateUIRunnable    = new Runnable() {
                                                        @Override
                                                        public void run() {
                                                            MediaPlayerFragment.this.updateSurfaceSizeToLayout();
                                                        }
                                                    };

    @Override
    public void onCreate(final Bundle savedInstanceState) {
        Log.v(MediaPlayerFragment.TAG, "INFO: onCreate");
        super.onCreate(savedInstanceState);

        // Control whether a fragment instance is retained across Activity
        // re-creation
        this.setRetainInstance(true);

        // get the extra data from the Intent (Container)
        this.path = this.getArguments().getString("path");
        if (this.path == null)
            throw new RuntimeException("No path where passed to the mediaplayerFragment");
    }

    /**
     * onCreateView
     * 
     * Setup the view of the media player with a surface to show the mediaPlayer
     */
    @Override
    public View onCreateView(final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) {
        Log.v(MediaPlayerFragment.TAG, "INFO: onCreateView");

        this.surfaceView = new SurfaceView(this.getActivity());

        // Push surfaceView to maximum available space
        final android.widget.FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
        this.surfaceView.setLayoutParams(layoutParams);

        // Placeholder for the area of the video, region to display
        final SurfaceHolder surfaceHolder = this.surfaceView.getHolder();
        surfaceHolder.addCallback(this);
        surfaceHolder.setSizeFromLayout();

        return this.surfaceView;
    }

    @Override
    public void onPause() {
        Log.v(MediaPlayerFragment.TAG, "onPause()");

        if (this.mp != null) {
            this.mp.pause();
            this.mp.setDisplay(null);
        }

        super.onPause();
    }

    /**
     * Prepare the media player for playback. This sets the filepath and start
     * the buffering for the player
     */
    private void preparePlayer() {

        Log.v(MediaPlayerFragment.TAG, "preparePlayer");

        try {
            this.mp.setLooping(false);
            this.mp.setDataSource(this.path);
            this.mp.prepareAsync();

        }
        catch (final IOException e) {
            Log.e(MediaPlayerFragment.TAG, "ERROR: Caught ioExeption" + e.getMessage(), e);
        }
        catch (final Exception e) {
            // IllegalStateException if it is called in an invalid state
            Log.e(MediaPlayerFragment.TAG, "ERROR: " + e.getMessage(), e);
        }
    }

    @Override
    public void onCompletion(final MediaPlayer arg0) {
        Log.d(MediaPlayerFragment.TAG, "onCompletion called");

        this.releaseMediaPlayer();
    }

    @Override
    public void onPrepared(final MediaPlayer mediaplayer) {
        Log.d(MediaPlayerFragment.TAG, "onPrepared called");

        if (!this.mp.isPlaying()) {
            this.mp.start();
        }
    }

    @Override
    public boolean onError(final MediaPlayer arg0, final int arg1, final int arg2) {
        Log.v(MediaPlayerFragment.TAG, "onError MediaPlayer");

        return false;
    }

    /**
     * Release the media player object and set its variable to null.
     */
    private void releaseMediaPlayer() {
        if (this.mp != null) {
            this.mp.reset();
            this.mp.release();
            this.mp = null;
        }
    }

    /**
     * display is turned
     */
    @Override
    public void surfaceCreated(final SurfaceHolder holder) {
        if (this.mp == null) {
            // get a MediaPlayer to display the video
            // store the MediaPlayer in the Application, so it does not get
            this.mp = new MediaPlayer();
            this.mp.setOnCompletionListener(this);
            this.mp.setOnPreparedListener(this); 
            this.mp.setOnBufferingUpdateListener(this);
            this.mp.setOnInfoListener(this);
            this.mp.setOnErrorListener(this); 
            this.mp.setScreenOnWhilePlaying(true);

            this.mp.setDisplay(holder);

            this.preparePlayer();
        } else {
            // re-establish connection to given surfaceHolder
            this.mp.setDisplay(holder);
        }
    }

    @Override
    public void surfaceDestroyed(final SurfaceHolder surfaceHolder1) {
        Log.v(MediaPlayerFragment.TAG, "surfaceDestroyed called");

        // disengage old surface holder
        if (this.mp != null) {
            this.mp.setDisplay(null);
        }
    }

    /**
     * This is called immediately after any structural changes (format or size)
     * have been made to the surface. You should at this point update the
     * imagery in the surface. This method is always called at least once, after
     * surfaceCreated(SurfaceHolder).
     * 
     * @param holder
     *            The SurfaceHolder whose surface has changed.
     * @param format
     *            The new PixelFormat of the surface.
     * @param width
     *            The new width of the surface.
     * @param height
     *            The new height of the surface.
     */
    @Override
    public void surfaceChanged(final SurfaceHolder holder, final int format, final int width, final int height) {
        Log.v(MediaPlayerFragment.TAG, "surfaceChanged called");

        // disengage old surface holder
        if (this.mp != null) {
            this.myHandler.post(this.updateUIRunnable);
            this.mp.setDisplay(holder);
        }
    }

    /**
     * The looks at the dimensions of the surfaceView that holds the MediaPlayer
     * (Video) and update the size of it to fit that dimension. This is
     * necessary because of orientation changes that alters the possible height
     * and width of the surface on that the video is drawn.
     */
    @SuppressWarnings("boxing")
    public void updateSurfaceSizeToLayout() {
        // Surface.ROTATION_0 (no rotation), Surface.ROTATION_90,
        // Surface.ROTATION_180, or Surface.ROTATION_270
        final int orientation = this.getResources().getConfiguration().orientation;

        Log.d(MediaPlayerFragment.TAG, "Orientation :" + orientation);

        // Get the SurfaceView layout parameters
        final android.view.ViewGroup.LayoutParams lp = this.surfaceView.getLayoutParams();

        final FrameLayout frameLayout = (FrameLayout) this.surfaceView.getParent();
        final int width = frameLayout.getWidth();
        final int height = frameLayout.getHeight();

        if (orientation == android.content.res.Configuration.ORIENTATION_PORTRAIT) {
            // Display in portrait
            lp.width = LayoutParams.MATCH_PARENT;
            lp.height = (int) (width / MediaPlayerFragment.ASPECT_RATIO);
            Log.d(MediaPlayerFragment.TAG, String.format("New surfaceView size: width:max ,height:%d", lp.height));
        } else {
            lp.width = (int) (height * MediaPlayerFragment.ASPECT_RATIO);
            lp.height = LayoutParams.MATCH_PARENT;
            Log.d(MediaPlayerFragment.TAG, String.format("New surfaceView size: width:%d , height: max", lp.width));
        }

        // Commit the layout parameters
        this.surfaceView.setLayoutParams(lp);
        this.surfaceView.getHolder().setSizeFromLayout();
    }
}

Can anybody see a great mistake in how I handle the surfaceView of the MediaPlayer or something else so that the app crashes.

Here is the log from LogCat after rotate it twice and on the second orientation change it crashed:

09-02 18:59:05.796: W/MediaPlayer(19521): info/warning (702, 0)
09-02 18:59:05.806: D/MediaPlayerFragment(19521): MediaPlayer.OnInfoListener: what:702, extra:0
09-02 18:59:05.836: W/MediaPlayer(19521): info/warning (701, 0)
09-02 18:59:05.836: D/MediaPlayerFragment(19521): MediaPlayer.OnInfoListener: what:701, extra:0
09-02 18:59:06.356: V/MediaPlayerFragment(19521): DEBUG: onPause
09-02 18:59:06.436: V/MediaPlayerFragment(19521): surfaceDestroyed called
09-02 18:59:06.466: V/MediaPlayerFragment(19521): onDetach
09-02 18:59:06.546: D/MPActivity(19521): onCreate
09-02 18:59:06.546: V/MediaPlayerFragment(19521): DEBUG: onAttach
09-02 18:59:06.596: D/MPActivity(19521): addVideoFragment
09-02 18:59:06.596: V/MediaPlayerFragment(19521): INFO: onCreateView
09-02 18:59:06.606: V/MediaPlayerFragment(19521): DEBUG: onResume
09-02 18:59:06.696: D/MediaPlayerFragment(19521): surfaceCreated called
09-02 18:59:06.726: V/MediaPlayerFragment(19521): surfaceChanged called
09-02 18:59:06.796: D/MediaPlayerFragment(19521): Orientation :2
09-02 18:59:06.796: D/MediaPlayerFragment(19521): width:810 ,height:-1
09-02 18:59:06.826: V/MediaPlayerFragment(19521): surfaceChanged called
09-02 18:59:06.906: D/MediaPlayerFragment(19521): Orientation :2
09-02 18:59:06.916: D/MediaPlayerFragment(19521): width:810 ,height:-1
09-02 18:59:07.886: W/MediaPlayer(19521): info/warning (702, 0)
09-02 18:59:07.886: D/MediaPlayerFragment(19521): MediaPlayer.OnInfoListener: what:702, extra:0
09-02 18:59:08.186: W/MediaPlayer(19521): info/warning (701, 0)
09-02 18:59:08.186: D/MediaPlayerFragment(19521): MediaPlayer.OnInfoListener: what:701, extra:0
09-02 18:59:09.036: V/MediaPlayerFragment(19521): DEBUG: onPause
09-02 18:59:09.056: V/MediaPlayerFragment(19521): surfaceDestroyed called
09-02 18:59:09.066: V/MediaPlayerFragment(19521): onDetach
09-02 18:59:09.086: E/MediaPlayer(19521): error (1, -2147483648)
09-02 18:59:09.086: D/MPActivity(19521): onCreate
09-02 18:59:09.086: V/MediaPlayerFragment(19521): DEBUG: onAttach
09-02 18:59:09.116: D/MPActivity(19521): addVideoFragment
09-02 18:59:09.116: V/MediaPlayerFragment(19521): INFO: onCreateView
09-02 18:59:09.126: V/MediaPlayerFragment(19521): DEBUG: onResume
09-02 18:59:09.176: D/MediaPlayerFragment(19521): surfaceCreated called
09-02 18:59:09.196: V/MediaPlayerFragment(19521): surfaceChanged called
09-02 18:59:09.196: E/MediaPlayer(19521): Error (1,-2147483648)
09-02 18:59:09.196: V/MediaPlayerFragment(19521): onError MediaPlayer
09-02 18:59:09.196: D/MediaPlayerFragment(19521): onCompletion called
09-02 18:59:09.366: V/MediaPlayerFragment(19521): surfaceDestroyed called
09-02 18:59:09.376: D/MediaPlayerFragment(19521): Orientation :1
09-02 18:59:09.376: D/MediaPlayerFragment(19521): width:-1 ,height:600
09-02 18:59:09.376: V/MediaPlayerFragment(19521): DEBUG: onPause
09-02 18:59:09.396: D/VideoListActivity(19521): onResume()
09-02 18:59:09.496: V/MediaPlayerFragment(19521): INFO: onDestroy
09-02 18:59:09.496: V/MediaPlayerFragment(19521): onDetach
Dr Evil
  • 1
  • 1

1 Answers1

0

About the OnCompletionListener This is a typical error happening in some Samsung devices. The only way to overcome it, keeping the standard player is checking the length of the video asset and if the current position is not close to the end, you should ignore the OnCompletion events.

About setRetainInstance(true): When this method is called, the fragment lifecycle will be: onCreate (just once, no matter how many times is rotated) onCreateView (every time the device is rotated) - Here all views are recreated, so your surface, your holder are invalid at this point. etc...

Have a look at this POC I've created that allows rotation. How to play audio continuously while orientation changes in Android?

Good luck

Community
  • 1
  • 1
Gaspar de Elias
  • 244
  • 3
  • 12