[[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 - //Initialize a TextureView and add it to the ViewGroup or find your TextureView component
- mTextureView=new TextureView(getContext());
- //Set canvas monitor
- textureView.setSurfaceTextureListener(this);
- //Add to layout
- fragment.addView(textureView,new LayoutParams(LayoutParams.MATCH_PARENT,LayoutParams.MATCH_PARENT, Gravity.CENTER));
- /**
- * TextureView is ready for callback
- * @param surface internal canvas rendering surface
- * @param width TextureView layout width
- * @param height TextureView layout height
- */
- @Override
- public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
- Logger.d(TAG, "onSurfaceTextureAvailable-->width:" +width+ ",height:" +height);
- //Here we handle screen changes and transitions, declare an mSurfaceTexture, and update it when TextureView changes
- if (mSurfaceTexture == null ) {
- mSurfaceTexture = surface;
- // prepare ();
- } else {
- mTextureView.setSurfaceTexture(mSurfaceTexture);
- }
- }
- /**
- * Callback when the width and height of TextureView changes
- * @param surface internal surface
- * @param width New TextureView layout width
- * @param height The height of the new TextureView layout
- */
- @Override
- public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
- Logger.d(TAG, "onSurfaceTextureSizeChanged-->width:" +width+ ",height:" +height);
- }
- /**
- * Callback when TextureView is destroyed
- * @param surface internal surface
- * @ return Most applications should return true .
- */
- @Override
- public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
- Logger.d(TAG, "onSurfaceTextureDestroyed" );
- return null == mSurfaceTexture;
- }
- /**
- * TextureView refresh callback
- * @param surface internal surface
- */
- @Override
- public void onSurfaceTextureUpdated(SurfaceTexture surface) {
- }
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
- @Override
- public boolean onInfo(MediaPlayer mp, int what, int extra) {
- if (what == MediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START) {
- // The player renders the first frame
- mCurrentState = STATE_PLAYING;
- mController.onPlayStateChanged(mCurrentState);
- } else if (what == MediaPlayer.MEDIA_INFO_BUFFERING_START) {
- // MediaPlayer does not play temporarily to buffer more data
- if (mCurrentState == STATE_PAUSED || mCurrentState == STATE_BUFFERING_PAUSED) {
- mCurrentState = STATE_BUFFERING_PAUSED;
- } else {
- mCurrentState = STATE_BUFFERING_PLAYING;
- }
- mController.onPlayStateChanged(mCurrentState);
- } else if (what == MediaPlayer.MEDIA_INFO_BUFFERING_END) {
- // After filling the buffer, MediaPlayer resumes playing/pausing
- if (mCurrentState == STATE_BUFFERING_PLAYING) {
- mCurrentState = STATE_PLAYING;
- mController.onPlayStateChanged(mCurrentState);
- }
- if (mCurrentState == STATE_BUFFERING_PAUSED) {
- mCurrentState = STATE_PAUSED;
- mController.onPlayStateChanged(mCurrentState);
- }
- } else {
- LogUtil.d( "onInfo --> what: " + what);
- }
- return true ;
- }
4. MediaPlayer initialization and preparation for playback- mMediaPlayer = new MediaPlayer();
- mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
- //Set the prepare to play listener and start playing in the onPrepared callback
- mMediaPlayer.setOnPreparedListener(this);
- //...A series of monitoring settings are omitted here
- //Asynchronous preparation
- mMediaPlayer.prepareAsync();
- /**
- * The player is ready
- * @param mp decoder
- */
- @Override
- public void onPrepared(MediaPlayer mp) {
- Logger.d(TAG, "onPrepared" );
- if( null !=mSurfaceTexture){
- if( null !=mSurface){
- mSurface.release();
- mSurface= null ;
- }
- mSurface =new Surface(mSurfaceTexture);
- mp.setSurface(mSurface);
- }
- //Start playing
- mp.start();
- }
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; - public VideoPlayer(@NonNull Context context, @Nullable AttributeSet attrs) {
- super(context, attrs);
- mContext = context;
- if (mNetworkChangeReceiver == null ) {
- mNetworkChangeReceiver = new NetworkChangeReceiver(this);
- }
- allow4GFlag = false ;
- init();
- }
- private void init() {
- mContainer = new FrameLayout(mContext);
- mContainer.setBackgroundColor(Color.BLACK);
- LayoutParams params = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
- this.addView(mContainer, params);
- }
addTextureView- private void addTextureView() {
- mContainer.removeView(mTextureView);
- LayoutParams params = new LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.MATCH_PARENT,
- Gravity.CENTER);
- mContainer.addView(mTextureView, 0, params);
- }
setController- public void setController(IVideoController controller) {
- mContainer.removeView(mController);
- mController = controller;
- mController.reset();
- mController.setVideoPlayer(this);
- LayoutParams params = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
- mContainer.addView(mController, params);
- }
Play, initialize TextureView, MediaPlayer, and Controller. After the TextureView data channel SurfaceTexture is ready, open the player- private void openMediaPlayer() {
- //Screen always on
- mContainer.setKeepScreenOn( true );
- //Set up monitoring
- mMediaPlayer.setOnPreparedListener(this);
- mMediaPlayer.setOnVideoSizeChangedListener(this);
- mMediaPlayer.setOnCompletionListener(this);
- mMediaPlayer.setOnErrorListener(this);
- mMediaPlayer.setOnInfoListener(this);
- mMediaPlayer.setOnBufferingUpdateListener(this);
- mCurrentNetworkState = NetworkChangeReceiver.getNetworkStatus(CtripBaseApplication.getInstance());
- mNetworkChangeReceiver.registerNetworkChangeBroadcast();
- // Set dataSource
- try {
- mMediaPlayer.setDataSource(mUrl);
- if (mSurface == null ) {
- mSurface = new Surface(mSurfaceTexture);
- }
- mMediaPlayer.setSurface(mSurface);
- mMediaPlayer.prepareAsync();
- mCurrentState = STATE_PREPARING;
- mController.onPlayStateChanged(mCurrentState);
- } catch (IOException e) {
- e.printStackTrace();
- LogUtil.e( "Error opening the player" , e);
- }
- }
- private void initMediaPlayer() {
- if (mMediaPlayer == null ) {
- mMediaPlayer = new MediaPlayer();
- mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
- }
- }
- private void initTextureView() {
- if (mTextureView == null ) {
- mTextureView = new TourTextureView(mContext);
- mTextureView.setSurfaceTextureListener(this); //Call back onSurfaceTextureAvailable at this time
- }
- }
- @Override
- public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int i, int i1) {
- if (mSurfaceTexture == null ) {
- mSurfaceTexture = surfaceTexture;
- openMediaPlayer();
- } else {
- mTextureView.setSurfaceTexture(mSurfaceTexture);
- }
- }
After the playback logic is written, the specific UI display logic is in VideoPlayerController. VideoPlayerController displays different UIs according to different states.- public static final int STATE_ERROR = -1; //Play error
- public static final int STATE_IDLE = 0; //Playback has not started
- public static final int STATE_PREPARING = 1; //Preparing for playback
- public static final int STATE_PREPARED = 2; //Ready to play
- public static final int STATE_PLAYING = 3; //Playing
- public static final int STATE_PAUSED = 4; //Pause playback
- public static final int STATE_BUFFERING_PLAYING = 5; // Buffering
- public static final int STATE_BUFFERING_PAUSED = 6; // Buffering player
- public static final int STATE_COMPLETED = 7; //Playback completed
- public static final int STATE_NOTE_4G = 8; //Tip 4G
- public static final int STATE_NOTE_DISCONNECT = 9; //Prompt that the network is disconnected
- public static final int MODE_NORMAL = 10; //Normal mode
- public static final int MODE_FULL_SCREEN = 11; //Full screen mode
- 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- @Override
- public void enterFullScreen() {
- if (mCurrentMode == MODE_FULL_SCREEN) return ;
- // Hide the ActionBar, status bar, and turn the screen horizontally
- TourVideoUtil.hideActionBar(mContext);
- TourVideoUtil.scanForActivity(mContext)
- .setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
- new Handler().post(new Runnable() {
- @Override
- public void run() {
- ViewGroup contentView = (ViewGroup) TourVideoUtil.scanForActivity(mContext)
- .findViewById(android.R.id.content);
- if (mCurrentMode == MODE_TINY_WINDOW) {
- contentView.removeView(mContainer);
- } else {
- TourVideoPlayer.this.removeView(mContainer);
- }
- LayoutParams params = new LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.MATCH_PARENT);
- contentView.addView(mContainer, params);
- }
- });
- mCurrentMode = MODE_FULL_SCREEN;
- mController.onPlayModeChanged(mCurrentMode);
- }
Implement a small window: remove mContainer, add it to android.R.content, and set the width and height - @Override
- public void enterTinyWindow() {
- if (mCurrentMode == MODE_TINY_WINDOW) return ;
- this.removeView(mContainer);
- new Handler().post(new Runnable() {
- @Override
- public void run() {
- ViewGroup contentView = (ViewGroup) TourVideoUtil.scanForActivity(mContext)
- .findViewById(android.R.id.content);
- // 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.
- FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
- ( int ) (CommonUtil.getScreenWidth(mContext) * 0.6f),
- ( int ) (CommonUtil.getScreenWidth(mContext) * 0.6f * 9f / 16f));
- params.gravity = Gravity.TOP | Gravity.START;
- params.topMargin = CommonUtil.dp2px(mContext, 48f);
- contentView.addView(mContainer, params);
- }
- });
- mCurrentMode = MODE_TINY_WINDOW;
- mController.onPlayModeChanged(mCurrentMode);
- }
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" |