嵌入 ViewPager 的視訊播放器
這次的目標是實現一個能夠播放視訊的輪播器,即每個頁面都能放置視訊且能隨頁面的切換實現視訊的切換。這個需求類似於在ListView中實現小視訊的播放和切換,核心內容是怎麼將視訊播放器與控制元件的狀態變化連線在一起。

視訊播放器
實現思路
- 使用 ViewPager 進行輪播
- 使用 MediaPlayer 對視訊進行播放
- 使用 TextureView 展示視訊介面
具體實現
為了提高效能,當然不能為每一頁配置一個 MediaPlayer 用來播放視訊,那麼最好的解決方法是當頁面切換時切換 MediaPlayer 的播放源。利用 ViewPager.OnPageChangeListener
監聽輪播器的頁面切換。
@Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { if (first && positionOffset == 0 && positionOffsetPixels == 0) { onPageSelected(position); first = false; } } @Override public void onPageSelected(int position) { //從快取的檢視中找到當前檢視並傳遞給擴充套件的MediaPlayer進行控制 View view = cacheView.get(position); mMediaPlayer.playWithVideoLayout(view); } @Override public void onPageScrollStateChanged(int state) { }
在每一頁的 view 中需要有 TextureView 用來顯示視訊介面,同時利用 TextureView.SurfaceTextureListener
監聽當前TextureView的可用性。SurfaceTextureListener介面用於讓TextureView的使用者知道SurfaceTexture已準備好,這樣就可以把SurfaceTexture交給相應的內容源。
TextureView可用於承載顯示『資料流』的場合,之前看到『流』不太明確其意義,這裡給兩個具體的場景大家體會一下:camera模組從sensor採集了1080p@30fps的預覽資料『流』,視訊通話模組從網路包裡解出實時視訊資料『流』。
public class VpVideoView extends TextureView implements TextureView.SurfaceTextureListener, MediaPlayer.OnVideoSizeChangedListener { //some params private Uri mSource;//視訊源 private Surface surface;//用於承接播放器 private VPMediaPlayer mediaPlayer;//視訊播放器 // some methods public void setMediaPlayer(VPMediaPlayer mediaPlayer) { this.mediaPlayer = mediaPlayer; } public void initSource(String videoPath) { mSource = Uri.parse(videoPath); } public void setSeekProgress(int seekProgress) { this.seekProgress = seekProgress; } @Override protected void onDetachedFromWindow() { // release resources on detach super.onDetachedFromWindow(); } /* * TextureView.SurfaceTextureListener */ @Override public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) { if (mSource == null) return; surface = new Surface(surfaceTexture); if (mediaPlayer != null) { mediaPlayer.replayWithCurrentLayout(); } } @Override public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { } @Override public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { if (mediaPlayer != null) { mediaPlayer.pause(); } return true; } @Override public void onSurfaceTextureUpdated(SurfaceTexture surface) { } @Override public void onVideoSizeChanged(MediaPlayer mp, int width, int height) { mVideoHeight = mp.getVideoHeight(); mVideoWidth = mp.getVideoWidth(); updateViewViewSize(); updateTextureViewSizeCenter(); } /** * 讓控制元件大小適配視訊大小 */ private void updateViewViewSize() { float sx = (float) getMeasuredWidth() / (float) mVideoWidth; int height = (int) (sx * mVideoHeight); setMeasuredDimension(MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)); postInvalidate(); } //重新計算video的顯示位置,讓其全部顯示並據中 private void updateTextureViewSizeCenter() { float sx = (float) getMeasuredWidth() / (float) mVideoWidth; float sy = (float) getMeasuredHeight() / (float) mVideoHeight; Matrix matrix = new Matrix(); //第1步:把視訊區移動到View區,使兩者中心點重合. matrix.preTranslate((getMeasuredWidth() - mVideoWidth) / 2, (getMeasuredHeight() - mVideoHeight) / 2); //第2步:因為預設視訊是fitXY的形式顯示的,所以首先要縮放還原回來. matrix.preScale(mVideoWidth / (float) getMeasuredWidth(), mVideoHeight / (float) getMeasuredHeight()); //第3步,等比例放大或縮小,直到視訊區的一邊和View一邊相等.如果另一邊和view的一邊不相等,則留下空隙 if (sx >= sy) { matrix.postScale(sy, sy, getMeasuredWidth() / 2, getMeasuredHeight() / 2); } else { matrix.postScale(sx, sx, getMeasuredWidth() / 2, getMeasuredHeight() / 2); } setTransform(matrix); postInvalidate(); } }
這個類主要有以下幾個功能:(1)使用Texture來展示視訊流;(2)記錄視訊源和播放進度(注意當view被銷燬時進度也會重置,所有如果需要進度不隨ViewPager的快取機制影響要另做儲存);(3)利用 MediaPlayer.OnVideoSizeChangedListener
調整視訊長與寬;(4)利用 SurfaceTextureListener
監聽TextureView是否可用。當判斷可用時開始啟動MediaPlayer播放視訊。
在另一方面,需要對MediaPlayer做一些處理,以便做視訊的切換播放。
public class VPMediaPlayer extends MediaPlayer implements MediaPlayer.OnCompletionListener, MediaPlayer.OnErrorListener, MediaPlayer.OnPreparedListener { //some params public VPMediaPlayer(SimpleViewPager simpleViewPager) { this.simpleViewPager = simpleViewPager; setLooping(false); setOnPreparedListener(this); setOnCompletionListener(this); setOnErrorListener(this); } //some methods @Override public void start() throws IllegalStateException { super.start(); seekTo(videoView.getSeekProgress()); } public void playWithVideoLayout() { isScrolling = false; try { reset(); setOnVideoSizeChangedListener(videoView); setDataSource(videoView.getContext(), videoView.getmSource()); setSurface(videoView.getSurface()); prepareAsync(); } catch (NullPointerException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } public void replayWithCurrentLayout() { simpleViewPager.stopAutoScroll(); isScrolling = false; try { reset(); setOnVideoSizeChangedListener(videoView); setDataSource(videoView.getContext(), videoView.getmSource()); setSurface(videoView.getSurface()); prepareAsync(); } catch (NullPointerException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } public void pauseCurrentVideoLayout() { simpleViewPager.startAutoScroll(true); isScrolling = true; } @Override public void pause() throws IllegalStateException { super.pause(); if (videoView != null) { videoView.setSeekProgress(getCurrentPosition()); } } @Override public void onCompletion(MediaPlayer mp) { simpleViewPager.startAutoScroll(true); isScrolling = true; } @Override public boolean onError(MediaPlayer mp, int what, int extra) { simpleViewPager.startAutoScroll(true); isScrolling = true; reset(); return false; } @Override public void onPrepared(MediaPlayer mp) { start(); } public boolean isScrolling() { return isScrolling; } }
VPMediaPlayer 主要處理一些播放器的邏輯,主要功能包括:(1)提供視訊的播放暫停功能;(2)能夠在視訊播放結束後自動切換到下一頁。
結束語
整個部分暫時沒有新增視訊控制的功能和UI,但有了主要的核心程式碼其他的功能新增起來應該都是水到渠成的事。