Detailed explanation of Android advanced MediaPlayer and TextureView encapsulated video player (perfectly realize full screen and small window)

Detailed explanation of Android advanced MediaPlayer and TextureView encapsulated video player (perfectly realize full screen and small window)

[[419543]]

Preface

In the previous article, we introduced the basic knowledge points of SurfaceView and TextureView;

SurfaceView and TextureView are both inherited from android.view.View and are part of the control system provided by Android. Unlike ordinary Views, they are drawn and rendered in independent threads. Therefore, compared with ordinary ImageViews, they have higher performance and are often used in application scenarios with high drawing speed requirements to solve the problem of frame drops caused by ordinary Views due to drawing time delays, such as being used as a medium for camera preview and video playback;

Today we will simply use TextureView to encapsulate the video player;

1. Video player solution introduction

1. videoView+mediaPlayer

videoView inherits from SurfaceView. surfaceView creates a new Window on the existing View.

Content display and rendering are in a new Window, which allows SurfaceView to be drawn and refreshed in a separate thread.

Since the content of SurfaceView is in a newly created Window, SurfaceView cannot be placed in a RecyclerView or ScrollView, and some features in the View cannot be used.

2. textureView+mediaPlayer

textureView does not create a new window, and its usage is the same as other ordinary Views.

Considering the future scalability, this solution was finally adopted

3. Why use TextureView?

TextureView was introduced in 4.0 (API level 14). Compared with SurfaceView, it does not create a new window to display content. It directly streams content to the View and can be moved, rotated, scaled, animated, and other changes like other ordinary Views. TextureView must be used in a hardware-accelerated window.

2. Introduction to TextureView

1. TextureView cannot be used directly after it is created. It must be added to ViewGroup.

2. TextureView must wait until SurfaceTexture is ready before it can work. Here you usually need to set a listener SurfaceTextureListener for TextureView. Wait for the onSurfaceTextureAvailable callback before you can use it.

3. TextureView creation and initialization

  1. //Initialize a TextureView and add it to the ViewGroup or find your TextureView component
  2. mTextureView=new TextureView(getContext());
  3. //Set canvas monitor
  4. textureView.setSurfaceTextureListener(this);
  5. //Add to layout
  6. fragment.addView(textureView,new LayoutParams(LayoutParams.MATCH_PARENT,LayoutParams.MATCH_PARENT, Gravity.CENTER));
  7. /**
  8. * TextureView is ready for callback
  9. * @param surface internal canvas rendering surface
  10. * @param width TextureView layout width
  11. * @param height TextureView layout height
  12. */
  13. @Override
  14. public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
  15. Logger.d(TAG, "onSurfaceTextureAvailable-->width:" +width+ ",height:" +height);
  16. //Here we handle screen changes and transitions, declare an mSurfaceTexture, and update it when TextureView changes
  17. if (mSurfaceTexture == null ) {
  18. mSurfaceTexture = surface;
  19. // prepare ();
  20. } else {
  21. mTextureView.setSurfaceTexture(mSurfaceTexture);
  22. }
  23. }
  24. /**
  25. * Callback when the width and height of TextureView changes
  26. * @param surface internal surface
  27. * @param width New TextureView layout width
  28. * @param height The height of the new TextureView layout
  29. */
  30. @Override
  31. public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
  32. Logger.d(TAG, "onSurfaceTextureSizeChanged-->width:" +width+ ",height:" +height);
  33. }
  34. /**
  35. * Callback when TextureView is destroyed
  36. * @param surface internal surface
  37. * @ return Most applications should return   true .
  38. */
  39. @Override
  40. public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
  41. Logger.d(TAG, "onSurfaceTextureDestroyed" );
  42. return   null == mSurfaceTexture;
  43. }
  44. /**
  45. * TextureView refresh callback
  46. * @param surface internal surface
  47. */
  48. @Override
  49. public void onSurfaceTextureUpdated(SurfaceTexture surface) {
  50. }

3. Introduction to MediaPlayer

1. Important status

  • idle: idle state. Before prepareAsync is called, mediaPlayer is in idle state.
  • prepared: prepared state. If you want the mediaPlayer to start playing, you cannot start it directly, you must prepareSync it first. During this period, the mediaPlayer will keep preparing until it enters the prepared state.
  • started: When the mediaPlayer is ready, you can call the start method of the mediaPlayer to enter the started state.
  • paused: When the pause method is called, it enters the paused state.
  • completed: Playback is complete and enters the completed state.
  • error: playback error.

2. Important methods

  • prepareAsync: To use mediaPlayer, you must first call prepareAsync. This is the first step.
  • start: start
  • pause: pause
  • reset: After playback is complete, if you want to restart, call this method.

3. Important callbacks

  • onSurfaceTextureAvailable: Start associating mediaPlayer
  • onPrepared: Start calling mediaPlayer.start() here
  • onInfo: After playback starts, the video status is handled in onInfo
  1. @Override
  2. public boolean onInfo(MediaPlayer mp, int what, int extra) {
  3. if (what == MediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START) {
  4. // The player renders the first frame
  5. mCurrentState = STATE_PLAYING;
  6. mController.onPlayStateChanged(mCurrentState);
  7. } else if (what == MediaPlayer.MEDIA_INFO_BUFFERING_START) {
  8. // MediaPlayer does not play temporarily to buffer more data
  9. if (mCurrentState == STATE_PAUSED || mCurrentState == STATE_BUFFERING_PAUSED) {
  10. mCurrentState = STATE_BUFFERING_PAUSED;
  11. } else {
  12. mCurrentState = STATE_BUFFERING_PLAYING;
  13. }
  14. mController.onPlayStateChanged(mCurrentState);
  15. } else if (what == MediaPlayer.MEDIA_INFO_BUFFERING_END) {
  16. // After filling the buffer, MediaPlayer resumes playing/pausing
  17. if (mCurrentState == STATE_BUFFERING_PLAYING) {
  18. mCurrentState = STATE_PLAYING;
  19. mController.onPlayStateChanged(mCurrentState);
  20. }
  21. if (mCurrentState == STATE_BUFFERING_PAUSED) {
  22. mCurrentState = STATE_PAUSED;
  23. mController.onPlayStateChanged(mCurrentState);
  24. }
  25. } else {
  26. LogUtil.d( "onInfo --> what: " + what);
  27. }
  28. return   true ;
  29. }

4. MediaPlayer initialization and preparation for playback

  1. mMediaPlayer = new MediaPlayer();
  2. mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
  3. //Set the prepare to play listener and start playing in the onPrepared callback
  4. mMediaPlayer.setOnPreparedListener(this);
  5. //...A series of monitoring settings are omitted here
  6. //Asynchronous preparation
  7. mMediaPlayer.prepareAsync();
  8. /**
  9. * The player is ready
  10. * @param mp decoder
  11. */
  12. @Override
  13. public void onPrepared(MediaPlayer mp) {
  14. Logger.d(TAG, "onPrepared" );
  15. if( null !=mSurfaceTexture){
  16. if( null !=mSurface){
  17. mSurface.release();
  18. mSurface= null ;
  19. }
  20. mSurface =new Surface(mSurfaceTexture);
  21. mp.setSurface(mSurface);
  22. }
  23. //Start playing
  24. mp.start();
  25. }

4. Encapsulate the video player

1. Package the player

The video playback control should contain two layers: the top layer is the player controller mController, and the bottom layer is the TextureView that plays the video content. Here, these two layers are encapsulated in a container FrameLayout;

  1. public VideoPlayer(@NonNull Context context, @Nullable AttributeSet attrs) {
  2. super(context, attrs);
  3. mContext = context;
  4. if (mNetworkChangeReceiver == null ) {
  5. mNetworkChangeReceiver = new NetworkChangeReceiver(this);
  6. }
  7. allow4GFlag = false ;
  8. init();
  9. }
  10. private void init() {
  11. mContainer = new FrameLayout(mContext);
  12. mContainer.setBackgroundColor(Color.BLACK);
  13. LayoutParams params = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
  14. this.addView(mContainer, params);
  15. }

addTextureView

  1. private void addTextureView() {
  2. mContainer.removeView(mTextureView);
  3. LayoutParams params = new LayoutParams(
  4. ViewGroup.LayoutParams.MATCH_PARENT,
  5. ViewGroup.LayoutParams.MATCH_PARENT,
  6. Gravity.CENTER);
  7. mContainer.addView(mTextureView, 0, params);
  8. }

setController

  1. public void setController(IVideoController controller) {
  2. mContainer.removeView(mController);
  3. mController = controller;
  4. mController.reset();
  5. mController.setVideoPlayer(this);
  6. LayoutParams params = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
  7. mContainer.addView(mController, params);
  8. }

Play, initialize TextureView, MediaPlayer, and Controller. After the TextureView data channel SurfaceTexture is ready, open the player

  1. private void openMediaPlayer() {
  2. //Screen always on
  3. mContainer.setKeepScreenOn( true );
  4. //Set up monitoring
  5. mMediaPlayer.setOnPreparedListener(this);
  6. mMediaPlayer.setOnVideoSizeChangedListener(this);
  7. mMediaPlayer.setOnCompletionListener(this);
  8. mMediaPlayer.setOnErrorListener(this);
  9. mMediaPlayer.setOnInfoListener(this);
  10. mMediaPlayer.setOnBufferingUpdateListener(this);
  11. mCurrentNetworkState = NetworkChangeReceiver.getNetworkStatus(CtripBaseApplication.getInstance());
  12. mNetworkChangeReceiver.registerNetworkChangeBroadcast();
  13. // Set dataSource
  14. try {
  15. mMediaPlayer.setDataSource(mUrl);
  16. if (mSurface == null ) {
  17. mSurface = new Surface(mSurfaceTexture);
  18. }
  19. mMediaPlayer.setSurface(mSurface);
  20. mMediaPlayer.prepareAsync();
  21. mCurrentState = STATE_PREPARING;
  22. mController.onPlayStateChanged(mCurrentState);
  23. } catch (IOException e) {
  24. e.printStackTrace();
  25. LogUtil.e( "Error opening the player" , e);
  26. }
  27. }
  28. private void initMediaPlayer() {
  29. if (mMediaPlayer == null ) {
  30. mMediaPlayer = new MediaPlayer();
  31. mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
  32. }
  33. }
  34. private void initTextureView() {
  35. if (mTextureView == null ) {
  36. mTextureView = new TourTextureView(mContext);
  37. mTextureView.setSurfaceTextureListener(this); //Call back onSurfaceTextureAvailable at this time
  38. }
  39. }
  40. @Override
  41. public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int i, int i1) {
  42. if (mSurfaceTexture == null ) {
  43. mSurfaceTexture = surfaceTexture;
  44. openMediaPlayer();
  45. } else {
  46. mTextureView.setSurfaceTexture(mSurfaceTexture);
  47. }
  48. }

After the playback logic is written, the specific UI display logic is in VideoPlayerController. VideoPlayerController displays different UIs according to different states.

  1. public   static final int STATE_ERROR = -1; //Play error
  2. public   static final int STATE_IDLE = 0; //Playback has not started
  3. public   static final int STATE_PREPARING = 1; //Preparing for playback
  4. public   static final int STATE_PREPARED = 2; //Ready to play
  5. public   static final int STATE_PLAYING = 3; //Playing
  6. public   static final int STATE_PAUSED = 4; //Pause playback
  7. public   static final int STATE_BUFFERING_PLAYING = 5; // Buffering
  8. public   static final int STATE_BUFFERING_PAUSED = 6; // Buffering player
  9. public   static final int STATE_COMPLETED = 7; //Playback completed
  10. public   static final int STATE_NOTE_4G = 8; //Tip 4G
  11. public   static final int STATE_NOTE_DISCONNECT = 9; //Prompt that the network is disconnected
  12. public   static final int MODE_NORMAL = 10; //Normal mode
  13. public   static final int MODE_FULL_SCREEN = 11; //Full screen mode
  14. public   static final int MODE_TINY_WINDOW = 13; //Small window mode

2. Implementation of full screen and small window playback

To achieve full screen: remove mContainer, add it to android.R.content, and set it to landscape mode

  1. @Override
  2. public void enterFullScreen() {
  3. if (mCurrentMode == MODE_FULL_SCREEN) return ;
  4. // Hide the ActionBar, status bar, and turn the screen horizontally
  5. TourVideoUtil.hideActionBar(mContext);
  6. TourVideoUtil.scanForActivity(mContext)
  7. .setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
  8. new Handler().post(new Runnable() {
  9. @Override
  10. public void run() {
  11. ViewGroup contentView = (ViewGroup) TourVideoUtil.scanForActivity(mContext)
  12. .findViewById(android.R.id.content);
  13. if (mCurrentMode == MODE_TINY_WINDOW) {
  14. contentView.removeView(mContainer);
  15. } else {
  16. TourVideoPlayer.this.removeView(mContainer);
  17. }
  18. LayoutParams params = new LayoutParams(
  19. ViewGroup.LayoutParams.MATCH_PARENT,
  20. ViewGroup.LayoutParams.MATCH_PARENT);
  21. contentView.addView(mContainer, params);
  22. }
  23. });
  24. mCurrentMode = MODE_FULL_SCREEN;
  25. mController.onPlayModeChanged(mCurrentMode);
  26. }

Implement a small window: remove mContainer, add it to android.R.content, and set the width and height

  1. @Override
  2. public void enterTinyWindow() {
  3. if (mCurrentMode == MODE_TINY_WINDOW) return ;
  4. this.removeView(mContainer);
  5. new Handler().post(new Runnable() {
  6. @Override
  7. public void run() {
  8. ViewGroup contentView = (ViewGroup) TourVideoUtil.scanForActivity(mContext)
  9. .findViewById(android.R.id.content);
  10. // The width of the small window is 60% of the screen width, the aspect ratio is 16:9 by default, and the right and bottom margins are 8dp.
  11. FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
  12. ( int ) (CommonUtil.getScreenWidth(mContext) * 0.6f),
  13. ( int ) (CommonUtil.getScreenWidth(mContext) * 0.6f * 9f / 16f));
  14. params.gravity = Gravity.TOP | Gravity.START;
  15. params.topMargin = CommonUtil.dp2px(mContext, 48f);
  16. contentView.addView(mContainer, params);
  17. }
  18. });
  19. mCurrentMode = MODE_TINY_WINDOW;
  20. mController.onPlayModeChanged(mCurrentMode);
  21. }

Summarize

There are many knowledge points about video player packaging. Today, I will briefly introduce the steps and ideas of packaging.

If you want to package it yourself, you can refer to NiceVieoPlayer on the Internet;

I will continue to explain the knowledge points about video players in the future;

This article is reproduced from the WeChat public account "Android Development Programming"

<<:  How to write an interactive description document that everyone agrees on?

>>:  How detailed are the design details of big companies? Let’s take a look at the actual combat case of QQ Browser!

Recommend

FunBox Gaming TV Box Review: A Powerful "Lunch Box" with Tegra 4 Core

Speaking of games, I believe that domestic players...

A brief analysis of the private domain traffic growth model!

Today, the reason why fission is so popular is th...

Live streaming and street stall economy

Operator thinking is a complete thinking framewor...

How much does it cost to develop a wine utensils mini app in Xi'an?

How much does it cost to develop a Xi’an wineware...

Some common problems about website filing in Yinchuan

Those of us who build websites in Yinchuan all kn...

Teach you to write a super simple swoole chat room in five minutes

I was originally planning to continue writing my ...

WeChat users have reached 1.26 billion! QQ continues to fall sharply

Tencent released its third-quarter 2021 financial...

2019 Kuaishou and Douyin User Research Report!

The following is the full report: (Reply "DK...

2019 Complete Plan for Online Event Promotion!

How to plan an online event? Even those who are j...

10 Questions About Lemonade Answered

Whenever I talk about lemonade, I get a ton of qu...