当流式传输rtsp视频时,Android随机调用mediaPlayer onCompletion进行方向更改

时间:2014-09-02 16:48:11

标签: android android-fragments android-mediaplayer rtsp android-orientation

现在搜索天数并且无法弄清楚为什么当设备的方向发生变化时,mediaPlayer会随机触发onCompletion。看起来那些具有更多内存的设备(如更强大的平板电脑)不会经常崩溃,但有时它们也会崩溃。

我的MediaPlayer正在Fragment中运行,我尝试使用持久的MediaPlayer技术调用setRetainInstance(true)。

作为提示,有些设备有时会在LogCat中说当主线程中发生了太多工作。

这是我的片段代码:

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();
    }
}

任何人都可以看到我如何处理MediaPlayer的SurfaceView或其他东西的错误,以免应用程序崩溃。

以下是LogCat旋转两次后的日志,并在第二个方向更改后崩溃:

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

1 个答案:

答案 0 :(得分:0)

关于OnCompletionListener 这是某些三星设备中发生的典型错误。克服它的唯一方法是,保持标准播放器是检查视频资产的长度,如果当前位置不接近结束,则应忽略OnCompletion事件。

关于setRetainInstance(true): 调用此方法时,片段生命周期将为: onCreate(只需一次,无论旋转多少次) onCreateView(每次旋转设备时) - 此处重新创建所有视图,因此此时您的表面,持有者无效。 等...

看看我创建的允许轮换的POC。 How to play audio continuously while orientation changes in Android?

祝你好运