【问题标题】:Android MediaPlayer on ViewPager blocking UI transition and no callback for initial page loadsViewPager 上的 Android MediaPlayer 阻止 UI 转换并且初始页面加载没有回调
【发布时间】:2014-08-21 08:53:05
【问题描述】:

基本上,我们想构建一个分页的水平滚动视图,每个页面上都有一个视频播放器;当页面焦点改变时,视频应该自动播放和自动暂停/停止。所以我决定使用 ViewPager 来显示视频片段。每个片段都有自己的 SurfaceView 和 MediaPlayer 用于视频播放。目标是在初始页面加载和用户滑动选择新页面时自动播放视频。

我面临一些问题:

  1. 加载初始页面时,我无法从 ViewPager 获得回调。我尝试实现 ViewPager.OnPageChangeListener 以暂停前一页上的视频并尝试在新选择的页面上播放视频。但是,当加载索引为 0 的页面作为初始页面时,不会调用“onPageSelected”。有谁知道还有另一种方法可以回调初始页面加载?

这是我的 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 在页面转换期间会冻结几秒钟,就像您在屏幕上看到并排的两个页面一样。有时我收到应用程序无响应错误,并在 Logcat 输出中看到各种错误和警告,但并非一直发生:

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)

在 Fragment 上实现 playVideo():

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

我怀疑准备视频播放器的过程阻塞了 UI 线程,导致应用程序没有响应。虽然我真的想不出如何修复它以及为什么它偶尔会抛出 Surface being released 错误但不是每次都抛出?

【问题讨论】:

  • 你能说明你为什么使用这个 VideoFragment newPage = (VideoFragment) mVideoFragmentPagerAdapter.getFragment(i);两次忘记最后一个 getFragment
  • 您的意思是在方法 onPageSelected() 中吗?第一个 getFragment(mCurrentPagerIndex) 是获取即将离开屏幕的片段,以便我可以调用暂停视频,第二个 getFragment(i) 是调用开始播放视频。

标签: android-viewpager media-player android-mediaplayer surfaceview


【解决方案1】:

我的建议是只使用一个 MediaPlayer,而不是为每个页面创建一个。

您可以在页面滚动到中心时加载其视频,并将其 SurfaceView 设置为页面中的视图。

当视图即将移出当前页面时,您停止视频,重置 MediaPlayer 设置新的 SurfaceView 并准备下一个视频。

您还需要考虑使用计时器来延迟 onPageSelected 的调用,以避免过多的准备调用对 MediaPlayer 造成冲击。

例如:

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

【讨论】:

  • 感谢您的建议 :) 我实际上也尝试过这种方法。通过在 ViewPager 之外有一个 MediaPlayer,我可以避免第一个问题 - 在初始页面加载时没有得到回调。虽然 ViewPager 转换仍然偶尔冻结,我仍然有来自 MediaPlayer 的错误,例如 E/MediaPlayer:error (-38, 0)
  • 另一件事是尝试使用计时器延迟 onPageSelected 对 MediaPlayer 的调用,因此仅执行最后一个 onPageSelected 中的操作。因为当您在视图分页器中快速滑动时,中间的每个页面都将执行 onPageSelected,这可能会因过多的准备调用而使您的 MediaPlayer 不堪重负。
  • 是的。我用了0.5s之类的东西。公共无效initialVideoPlay(){处理程序=新处理程序(); handler.postDelayed(new Runnable() { public void run() { if (shouldAutoPlay) { playVideo(); } } }, 500); }
  • 嗨 XTAN,postDelayed 不起作用,因为延迟的目的是能够在新的呼叫进入时取消以前的待处理呼叫。使用 postDelayed 它将始终在指定时间后执行,实际上和没有延迟没什么区别。
  • 感谢您的回复。我尝试按照您的建议使用 Timer: public void cancelTimer() { //当页面不再是主要项目时调用此方法 if(timer != null){ timer.cancel(); } } 公共无效initialVideoPlay() { if(timer != null){ timer.cancel(); } 计时器 = 新计时器(); timer.schedule(new TimerTask() { @Override public void run() { playVideo(); } }, 1000); } ViewPager 仍然没有很好地响应。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-01-01
  • 1970-01-01
  • 2021-02-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多