Android MediaPlayer on ViewPager blocking UI trans

2019-08-04 19:04发布

问题:

Basically, we want to build a paged horizontal scroll view with one video player on each page; Video should auto-play and auto-pause/stop when page focus is changed. So I decided to use a ViewPager to show video Fragments. Each fragment has its own SurfaceView and MediaPlayer for video playing. The goal is to auto-play video when initial page loads and when user swipes to select a new page.

I am facing a few issues:

  1. I couldn't get callback from ViewPager when initial page is loaded. I’ve try implementing ViewPager.OnPageChangeListener in order to pause video on previous page and attempt to play video on the newly selected page. However, when page with Index 0 as the initial page is loaded, “onPageSelected“ is not called. Does anyone know there is another way to get call back of the initial page loads?

Here's my implementation of ViewPager.OnPageChangeListener

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        …
        mViewPager.setAdapter(mVideoFragmentPagerAdapter);
        mViewPager.setOnPageChangeListener(this);
        mViewPager.setCurrentItem(mSelectedClipIndex);
        …
     }

    ....

    /* ViewPager.OnPageChangeListener  */
    private int mCurrentPagerIndex = 0;

    @Override
    public void onPageScrolled(int i, float v, int i2) {
    }

    @Override
    public void onPageSelected(int i) {
        if (mCurrentPagerIndex != i) {
            VideoFragment currentPage = (VideoFragment)mVideoFragmentPagerAdapter.getFragment(mCurrentPagerIndex);
            if (currentPage != null) {
                currentPage.pause();
            }
        }
        VideoFragment newPage = (VideoFragment) mVideoFragmentPagerAdapter.getFragment(i);
        if (newPage != null) {
            newPage.playVideo();
        }
        mCurrentPagerIndex = i;
    }

    @Override
    public void onPageScrollStateChanged(int state) {
    }

    ...

    /* part of the adapter implementation */
    ....
    private Map<Integer, Fragment> mPageReferenceMap = new HashMap<Integer, Fragment>();
    public Fragment getItem(int i) {
        VideoFragment fragment = VideoFragment.newInstance(i, video);
        mPageReferenceMap.put(i, fragment);
        return fragment;
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        mPageReferenceMap.remove(position);
        super.destroyItem(container, position, object);
    }

    public Fragment getFragment(int key) {
        return mPageReferenceMap.get(key);
    }

2. UI freezes for seconds during the page transition if I attempt to swipe through quickly, as in you literally see two pages on the screen side by side. Sometimes I get application not responding error and see various errors and warnings in Logcat output but it doesn’t happen all the time:

I/Choreographer﹕ Skipped 169 frames!  The application may be doing too much work on its main thread.

…

W/System.err﹕ java.lang.IllegalArgumentException: The surface has been released

W/System.err﹕ at android.media.MediaPlayer._setVideoSurface(Native Method)

W/System.err﹕ at android.media.MediaPlayer.setDisplay(MediaPlayer.java:688)

W/System.err﹕ at com.fbwmedia.AFV.fragments.VideoFragment.playVideo(VideoFragment.java:232)

W/System.err﹕ at com.fbwmedia.AFV.activities.VideosActivity.onPageSelected(VideosActivity.java:145)

W/System.err﹕ at android.support.v4.view.ViewPager.scrollToItem(ViewPager.java:572)

….

E/MediaPlayer﹕ Attempt to call getDuration without a valid mediaplayer

E/MediaPlayer﹕ error (-38, 0)

implementation of playVideo() on Fragment:

public void playVideo() {

    mMediaController.setEnabled(true);
    mMediaController.setMediaPlayer(this);
    mMediaController.setAnchorView(this.getView().findViewById(R.id.layout_video_player));

    AudioManager am = (AudioManager) this.getActivity().getSystemService(Context.AUDIO_SERVICE);
    am.requestAudioFocus(this, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);

    try {
        if (mMediaPlayer == null) {
            mMediaPlayer = new MediaPlayer();
        } else {
            if (mMediaPlayer.isPlaying()) {
                mMediaPlayer.stop();
            }
            mMediaPlayer.reset();
        }
        mMediaPlayer.setDataSource(mPath);
        mMediaPlayer.setDisplay(mHolder);
        mMediaPlayer.setScreenOnWhilePlaying(true);
        mMediaPlayer.setOnErrorListener(this);
        mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
        mMediaPlayer.setOnPreparedListener(this);
        mMediaPlayer.prepareAsync();
        mMediaPlayer.setOnCompletionListener(this);
        wasPlayStarted = true;
        isPrepared = false;
    } catch (Exception e) {
        e.printStackTrace();
    }
}


public void onPrepared(MediaPlayer mediaplayer) {
    mWidth = mediaplayer.getVideoWidth();
    mHeight = mediaplayer.getVideoHeight();

    if (mWidth != 0 && mHeight != 0 && this.getView() != null) {
        setVideoProgressContentVisibility(View.GONE);
        mHolder.setFixedSize(mWidth, mHeight);
        mMediaPlayer.start();
        mMediaController.show();
    }
}

public void pause() {
    if (mMediaPlayer != null && isPlaying()) {
        mMediaPlayer.pause();
    }
}

I'm suspecting that the process of preparing video player is blocking the UI thread causing the application not responding. Though I really ran out of ideas how to fix it and why it occasionally throwing the Surface being released error but not every time?

回答1:

My suggestion is to use only one MediaPlayer, instead of creating one for each page.

You can load its video when a page is scrolled to center, and set its SurfaceView to the view in your page.

When a view is about to move out of current page, you stop the video, reset the MediaPlayer set new SurfaceView and prepare the next video.

You also need to consider using a timer to delay the call from onPageSelected in order to avoid hammering MediaPlayer with too many prepare calls.

for example:

onPageSelected() {
    if (timer != null) {
        timer.cancel();
    }
    timer = new Timer(new TimerRunnable() {
        mediaPlayer.prepare(....
    }......
}