当我开始播放并旋转设备时,位置会向前跳跃约6秒,并且(必要时)音频会在发生这种情况时切断.然后,播放继续正常进行.我不知道是什么导致这种情况发生.
根据要求,这是代码:
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,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方法中的if块未被命中.
更新:
在进行了一些调试之后,删除与SurfaceHolder的交互会导致问题消失.换句话说,如果我没有在MediaPlayer上设置显示,则在配置更改期间音频将正常工作:无暂停,无跳过.看起来设置MediaPlayer上的显示有一些时间问题会让玩家感到困惑.
另外,我发现在配置更改期间删除它之前必须隐藏()MediaController.这样可以提高稳定性,但不能解决跳过问题.
另一个更新:
如果您在意,Android媒体堆栈如下所示:
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()时可以自由地暂停自己:
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; }
这表明设置表面将导致玩家破坏以前使用的任何表面以及视频解码器.将曲面设置为null不应导致音频停止,将其设置为新曲面需要重新初始化视频解码器并且播放器要搜索视频中的当前位置.按照惯例,寻求永远不会超过你的要求,也就是说,如果你在寻求时超过一个关键帧,你应该落在你超越的框架上(而不是下一个).
因此,我的假设是,Android MediaPlayer不遵守此约定,并在搜索时跳转到下一个关键帧.这与具有稀疏关键帧的视频源相结合,可以解释我正在经历的跳跃.不过,我没有看过AwesomePlayer的搜索实现.有人提到,跳过下一个关键帧是需要发生的事情,如果您的MediaPlayer开发时考虑了流式传输,因为流可以在消费后立即丢弃.重点是,可能不会认为MediaPlayer会选择向前跳而不是向后跳.
最后更新:
虽然我仍然不知道为什么在将新Surface作为MediaPlayer的显示器附加时播放会跳过,但由于接受了答案,我在播放期间让播放无缝.
解决方法
我有一个片段,我标记在配置更改中保留.此片段处理媒体播放(MediaPlayer)和标准TextureView(提供视频缓冲区转储的SurfaceTexture).只有在我的Activity完成onResume()并且SurfaceTexture可用后,我才会初始化媒体播放.一旦我收到对SurfaceTexture的引用,我只是在我的片段中调用setSurfaceTexture(因为它是公共的)而不是子类化TextureView.配置更改发生时保留的唯一两件事是MediaPlayer引用和SurfaceTexture引用.
我已经将source of my sample project上传到了Github.随意看看!