MediaPlayer的跳向前旋转约6秒钟 [英] MediaPlayer skips forward about 6 seconds on rotation

查看:232
本文介绍了MediaPlayer的跳向前旋转约6秒钟的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个片段,它保留对更改配置的实例的MediaPlayer的。该播放器正在播放从我的资产目录中加载视频。我的情况下设置与再现YouTube应用程序播放其中的音频保存在配置更改播放和显示器分离,并重新连接到媒体播放器的目的。

I have a MediaPlayer in a Fragment which retains its instance on configuration changes. The player is playing a video loaded from my assets directory. I have the scenario set up with the goal of reproducing the YouTube app playback where the audio keeps playing during the configuration changes and the display is detached and reattached to the media player.

当我开始播放,旋转装置,位置向前跳约6秒(一定)的声音中断时发生这种情况。之后,播放将继续正常进行。我不知道这可能是导致这种情况发生。

When I start the playback and rotate the device, the position jumps forward about 6 seconds and (necessarily) the audio cuts out when this happens. Afterwards, the playback continues normally. I have no idea what could be causing this to happen.

根据要求,这里是code:

As requested, here is the code:

public class MainFragment extends Fragment implements SurfaceHolder.Callback, MediaController.MediaPlayerControl {

    private static final String TAG = MainFragment.class.getSimpleName();

    AssetFileDescriptor mVideoFd;

    SurfaceView mSurfaceView;
    MediaPlayer mMediaPlayer;
    MediaController mMediaController;
    boolean mPrepared;
    boolean mShouldResumePlayback;
    int mBufferingPercent;
    SurfaceHolder mSurfaceHolder;

    @Override
    public void onInflate(Activity activity, AttributeSet attrs, Bundle savedInstanceState) {
        super.onInflate(activity, attrs, savedInstanceState);
        final String assetFileName = "test-video.mp4";
        try {
            mVideoFd = activity.getAssets().openFd(assetFileName);
        } catch (IOException ioe) {
            Log.e(TAG, "Can't open file " + assetFileName + "!");
        }
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setRetainInstance(true);

        // initialize the media player
        mMediaPlayer = new MediaPlayer();
        try {
            mMediaPlayer.setDataSource(mVideoFd.getFileDescriptor(), mVideoFd.getStartOffset(), mVideoFd.getLength());
        } catch (IOException ioe) {
            Log.e(TAG, "Unable to read video file when setting data source.");
            throw new RuntimeException("Can't read assets file!");
        }

        mMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
            @Override
            public void onPrepared(MediaPlayer mp) {
                mPrepared = true;
            }
        });

        mMediaPlayer.setOnBufferingUpdateListener(new MediaPlayer.OnBufferingUpdateListener() {
            @Override
            public void onBufferingUpdate(MediaPlayer mp, int percent) {
                mBufferingPercent = percent;
            }
        });

        mMediaPlayer.prepareAsync();
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        super.onCreateView(inflater, container, savedInstanceState);
        View view = inflater.inflate(R.layout.fragment_main, container, false);
        mSurfaceView = (SurfaceView) view.findViewById(R.id.surface);
        mSurfaceView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mMediaController.show();
            }
        });

        mSurfaceHolder = mSurfaceView.getHolder();
        if (mSurfaceHolder == null) {
            throw new RuntimeException("SufraceView's holder is null");
        }
        mSurfaceHolder.addCallback(this);
        return view;
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        mMediaController = new MediaController(getActivity());
        mMediaController.setEnabled(false);
        mMediaController.setMediaPlayer(this);
        mMediaController.setAnchorView(view);
    }

    @Override
    public void onResume() {
        super.onResume();
        if (mShouldResumePlayback) {
            start();
        } else {
            mSurfaceView.post(new Runnable() {
                @Override
                public void run() {
                    mMediaController.show();
                }
            });
        }
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        mMediaPlayer.setDisplay(mSurfaceHolder);
        mMediaController.setEnabled(true);
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        // nothing
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        mMediaPlayer.setDisplay(null);
    }

    @Override
    public void onPause() {
        if (mMediaPlayer.isPlaying() && !getActivity().isChangingConfigurations()) {
            pause();
            mShouldResumePlayback = true;
        }
        super.onPause();
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);

    }

    @Override
    public void onDestroyView() {
        mMediaController.setAnchorView(null);
        mMediaController = null;
        mMediaPlayer.setDisplay(null);
        mSurfaceHolder.removeCallback(this);
        mSurfaceHolder = null;
        mSurfaceView = null;
        super.onDestroyView();
    }

    @Override
    public void onDestroy() {
        mMediaPlayer.release();
        mMediaPlayer = null;
        try {
            mVideoFd.close();
        } catch (IOException ioe) {
            Log.e(TAG, "Can't close asset file..", ioe);
        }
        mVideoFd = null;
        super.onDestroy();
    }

    // MediaControler methods:
    @Override
    public void start() {
        mMediaPlayer.start();
    }

    @Override
    public void pause() {
        mMediaPlayer.pause();
    }

    @Override
    public int getDuration() {
        return mMediaPlayer.getDuration();
    }

    @Override
    public int getCurrentPosition() {
        return mMediaPlayer.getCurrentPosition();
    }

    @Override
    public void seekTo(int pos) {
        mMediaPlayer.seekTo(pos);
    }

    @Override
    public boolean isPlaying() {
        return mMediaPlayer.isPlaying();
    }

    @Override
    public int getBufferPercentage() {
        return mBufferingPercent;
    }

    @Override
    public boolean canPause() {
        return true;
    }

    @Override
    public boolean canSeekBackward() {
        return true;
    }

    @Override
    public boolean canSeekForward() {
        return true;
    }

    @Override
    public int getAudioSessionId() {
        return mMediaPlayer.getAudioSessionId();
    }
}

的onPause 如果块没有被击中。​​

The if block in the onPause method is not being hit.

更新:

做了一些更多的调试,拆卸与SurfaceHolder相互作用后引起该问题消失。没有停顿,没有跳转:换句话说,如果我不上的MediaPlayer setDisplay音频将配置更改过程中正常工作。这似乎也与设置上是混淆玩家的MediaPlayer显示一些时间问题。

After doing a bit more debugging, removing the interaction with the SurfaceHolder causes the problem to go away. In other words, if I don't setDisplay on the MediaPlayer the audio will work fine during the configuration change: no pause, no skip. It would seem there is some timing issue with setting the display on the MediaPlayer that is confusing the player.

此外,我发现,你必须隐藏()中的MediaController你的配置更改时将其取出。这提高了稳定性,但不能解决逃课问题。

Additionally, I have found that you must hide() the MediaController before you remove it during the configuration change. This improves stability but does not fix the skipping issue.

另一个更新:

如果你在乎,Android的媒体堆栈看起来是这样的:

If you care, the Android media stack looks like this:


MediaPlayer.java 
 -> android_media_MediaPlayer.cpp 
 -> MediaPlayer.cpp
 -> IMediaPlayer.cpp 
 -> MediaPlayerService.cpp 
 -> BnMediaPlayerService.cpp 
 -> IMediaPlayerService.cpp 
 -> *ConcreteMediaPlayer*
 -> *BaseMediaPlayer* (Stagefright, NuPlayerDriver, Midi, etc) 
 -> *real MediaPlayerProxy* (AwesomePlayer, NuPlayer, etc) 
 -> *RealMediaPlayer* (AwesomePlayerSource, NuPlayerDecoder, etc) 
  -> Codec
  -> HW/SW decoder

经审查AwesomePlayer,看来这真棒玩家需要暂停本身对你的自由,当你 setSurface()

Upon examining AwesomePlayer, it appears this awesome player takes the liberty of pausing itself for you when you setSurface():

status_t AwesomePlayer::setNativeWindow_l(const sp<ANativeWindow> &native) {
    mNativeWindow = native;

    if (mVideoSource == NULL) {
        return OK;
    }

    ALOGV("attempting to reconfigure to use new surface");

    bool wasPlaying = (mFlags & PLAYING) != 0;

    pause_l();
    mVideoRenderer.clear();

    shutdownVideoDecoder_l();

    status_t err = initVideoDecoder();

    if (err != OK) {
        ALOGE("failed to reinstantiate video decoder after surface change.");
        return err;
    }

    if (mLastVideoTimeUs >= 0) {
        mSeeking = SEEK;
        mSeekTimeUs = mLastVideoTimeUs;
        modifyFlags((AT_EOS | AUDIO_AT_EOS | VIDEO_AT_EOS), CLEAR);
    }

    if (wasPlaying) {
        play_l();
    }

    return OK;
}

这表明,设置表面会造成玩家摧毁任何表面是previously正在使用的以及视频去codeR随之的。而一个表面设置为空不应致使音频停止,其设置为一个新的表面需要视频去codeR要重新初始化和玩家寻求在视频中的当前位置。按照惯例,追求永远不会把你比你要求的更远,那就是,如果你过冲的时候寻求一个关键帧,你应该降落在你下颚(而不是下一个)。

This reveals that setting the surface will cause the player to destroy whatever surface was previously being used as well as the video decoder along with it. While setting a surface to null should not cause the audio to stop, setting it to a new surface requires the video decoder to be reinitialized and the player to seek to the current location in the video. By convention, seeking will never take you farther than you request, that is, if you overshoot a keyframe when seeking, you should land on the frame you overshot (as opposed to the next one).

我的假设,那么,就是在Android的MediaPlayer不遵守此约定,并寻求在跳跃前进到下一个关键帧。这一点,再加上有稀疏的关键帧视频源,可以解释跳跃我遇到。我没有看过AwesomePlayer的实现求,虽然。有人向我提到,跳转到下一个关键帧是一些需要,如果你的MediaPlayer正在精心制定的流,因为数据流可以尽快,因为它已经被消耗丢弃的情况发生。点是,它可能不那么远取认为在MediaPlayer会选择跳跃式前进,而不是倒退。

My hypothesis, then, is that the Android MediaPlayer does not honor this convention and jumps forward to the next keyframe when seeking. This, coupled with a video source that has sparse keyframes, could explain the jumping I am experiencing. I have not looked at AwesomePlayer's implementation of seek, though. It was mentioned to me that jumping to the next keyframe is something that needs to happen if your MediaPlayer is developed with streaming in mind since the stream can be discarded as soon as it has been consumed. Point being, it might not be that far fetch to think the MediaPlayer would choose to jump forward as opposed to backwards.

最后更新:

虽然我还是不知道为什么播放跳过安装一个新的表面时作为显示器的的MediaPlayer ,感谢接受的答案,我已经得到了回放是在旋转过程中无缝连接。

While I still don't know why the playback skips when attaching a new Surface as the display for a MediaPlayer, thanks to the accepted answer, I have gotten the playback to be seamless during rotation.

推荐答案

由于natez0r的回答,我已经设法设置说明工作。但是,我用一个稍微不同的方法。我将它的细节在这里,以供参考。

Thanks to natez0r's answer, I have managed to get the setup described working. However, I use a slightly different method. I'll detail it here for reference.

我有一个片段这是我标志被保留在配置更改。这个片段同时处理媒体播放(的MediaPlayer ),以及标准 TextureView (提供了表面纹理,其中视频缓冲器被抛弃)。我只初始化一次我的活动结束onResume(媒体播放),一旦表面纹理可用。而不是继承TextureView,我只需调用setSurfaceTexture(因为它是公共的)在我的片段,有一次我收到一个参考表面纹理。只有两样东西保留下来,当配置变化情况是MediaPlayer的基准,并参考表面纹理。

I have one Fragment which I flag to be retained on configuration changes. This fragment handles both the media playback (MediaPlayer), and the standard TextureView (which provides the SurfaceTexture where the video buffer gets dumped). I initialize the media playback only once my Activity has finished onResume() and once the SurfaceTexture is available. Instead of subclassing TextureView, I simply call setSurfaceTexture (since it's public) in my fragment once I receive a reference to the SurfaceTexture. The only two things retained when a configuration change happens are the MediaPlayer reference, and the SurfaceTexture reference.

我已经上传了我的样本项目到Github上的源。随意看看!

I've uploaded the source of my sample project to Github. Feel free to take a look!

这篇关于MediaPlayer的跳向前旋转约6秒钟的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆