My player is called JsPlayer, if you like, give a star ^_^Github.com/shuaijia/Js…

Here I only introduce the player packaging ideas, will post part of the code, if you want to view the complete code, you can go to Github to view, there is not clear or error or improvement, can issue me!

Written in the book before

(New bullet screen function in version 1.5)

Why SurfaceView

It inherits from the class View, so it’s essentially a View. But unlike regular View, it has its own Surface. The SurfaceView comes with a Surface, which has its own WindowState in WMS and its own Layer in SF. While it is still in the View hierachy on the App side, it is separated from the host window on the Server side (WMS and SF). The advantage of this is that rendering of the Surface can be done in a separate thread with its own GL context. This is useful for performance-related applications such as games, videos, etc., because it does not affect the main thread’s response to events.

The SurfaceView itself holds the surface, and the system handles the creation, destruction, and size changes of the surface, via the surfaceHolder callback notification. When the canvas is created, you can bind the Surface to MediaPlayer. If the SurfaceView is visible to the user, the SurfaceHolder that created the SurfaceView is used to display frames parsed by the video stream. If the SurfaceView becomes invisible to the user, the SurfaceHolder is destroyed immediately. To achieve the purpose of saving system resources.

About the introduction of more SurfaceView, refer to the other that I wrote an article: blog.csdn.net/jiashuai94/…

MediaPlayer

MediaPlayer is actually a well-packaged audio and video streaming media operation class. If you look at its source code, you will find that its internal is called native methods, so it is actually implemented in C++. Since it is a streaming media operation class, it must involve playing, pausing, stopping and so on. In fact, MediaPlayer also provides corresponding methods for us to directly operate streaming media.

  • Void statr() : starts or resumes playback.
  • Void stop() : stops the playback.
  • Void pause() : pauses playback.
  • Void setDataSource(String Path) : Specifies the MediaPlayer datasource using the address of a media resource. The path can be a local path or a network path.

Of course, there are many other methods, such as obtaining the duration of the video, obtaining the current location, locating to a location, etc., I will not list them all, read the source code of JsPlayer will understand.

Player structure

UML diagrams


SurfaceView+MediaPlayer encapsulates the video player to have the general understanding, next begins the video player encapsulates the journey!

1. Tool classes

To do a good job, you must sharpen your tools.

Want to package structure clear, easy to use video player, tool class is indispensable! JsPlayer mainly uses the following utility classes:

  • DisplayUtils
  • NetworkUtils
  • StringUtils

DisplayUtils: Responsible for interface display related tools, such as the conversion of PX, DP and SP; Get screen width height; Switch landscape, portrait, etc.;

NetworkUtils: Checks whether the mobile phone is connected to the Internet. Whether it is wifi; Whether it is traffic; Network status, etc.;

StringUtils: Mainly converts long milliseconds to time-formatted strings. I won’t paste the code, it’s very simple. If you want to find out, check it out on Github.


2. Entity classes

In order to standardize the incoming data when using the video player, as well as facilitate the user to call and encapsulate, the interface of video details is defined: it contains two abstract methods, which return the video address and video title respectively.

Public interface IVideoInfo extends Serializable {/** * video title */ String getVideoTitle(); /** * public interface IVideoInfo extends Serializable {/** * Video title */ String getVideoTitle(); /** * Video playback path (local or network) */ String getVideoPath(); }Copy the code

Users can expand it according to the actual situation of the project (need to realize this interface), such as the default map address, the number of likes, whether to buy, bullet screen information and so on. But the video title and video address must be returned!


3. Callback related

As we all know, VideoView or other video player when used, there are ready to listen, play finished listening, error listening, etc., for the developer to deal with the corresponding situation; We also sometimes need to get action callbacks when the user clicks pause, full screen, drags the progress bar, etc. Therefore, we encapsulate two callback interfaces:

  • OnVideoControlListener: Video control callback
  • OnPlayerCallback: Video status callback
/** * public interface OnVideoControlListener {/** * start play button */ void onStartPlay(); /** * return */ void onBack(); /** * full screen */ void onFullScreen(); /** * retry after error */ void onRetry(int errorStatus); }Copy the code
/** * video operation callback, Public interface OnPlayerCallback {/** * ready */ void onPrepared(MediaPlayer mp); /** * video size change */ void onVideoSizeChanged(MediaPlayer MP, int width, int height); /** * @param percent */ void onBufferingUpdate(MediaPlayer mp, int percent); / / void onCompletion(MediaPlayer mp); Void onError(MediaPlayer mp, int what, int extra); void onError(MediaPlayer mp, int what, int extra); /** * @param isShow Whether loading is loaded */ void onLoadingChanged(Boolean isShow); /** * video state changes */ void onStateChanged(int curState); }Copy the code

Of course, when you use the above two callbacks, you must implement them first, then use them, and of course you can extend them!


4. Customize the view

About the view involved in the player, need to customize the main gesture to adjust the progress, volume, brightness, controller interface, error interface.

Of course, our JsPlayer video player is also a custom View, and its gesture controls also encapsulate a View, which we’ll cover in more detail later.

  • JsVideoProgressOverlay: Adjusts the progress box
  • JsVideoSystemOverlay: Adjust volume and brightness boxes
  • JsVideoErrorView: Error interface
  • JsVideoControllerView: controller

My idea is as follows: encapsulate the error interface JsVideoErrorView into the controller JsVideoControllerView, so that it is easy to deal with the error; Pop-ups, controllers and, of course, surfaceViews, which load moderately, will be packaged into the custom View of the video player JsPlayer.

JsVideoProgressOverlay

Public class JsVideoProgressOverlay extends FrameLayout {private ImageView mSeekIcon; private TextView mSeekCurProgress; private TextView mSeekDuration; private int mDuration = -1; private int mDelSeekDialogProgress = -1; private int mSeekDialogStartProgress = -1; public JsVideoProgressOverlay(Context context) { super(context); init(); } public JsVideoProgressOverlay(Context context, AttributeSet attrs) { super(context, attrs); init(); } public JsVideoProgressOverlay(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private voidinit() { LayoutInflater.from(getContext()).inflate(R.layout.video_overlay_progress, this); mSeekIcon = (ImageView) findViewById(R.id.iv_seek_direction); mSeekCurProgress = (TextView) findViewById(R.id.tv_seek_current_progress); mSeekDuration = (TextView) findViewById(R.id.tv_seek_duration); } /** * Display progress box ** @param delProgress progress value * @param curPosition Player Current progress * @param duration Player total length */ public void show(int delProgress, int curPosition, int duration) {if (duration <= 0) return; // Get the start progress from the first displayif (mSeekDialogStartProgress == -1) {
            Log.i("DDD"."show: start seek = " + mSeekDialogStartProgress);
            mSeekDialogStartProgress = curPosition;
        }

        if(getVisibility() ! = View.VISIBLE) {setVisibility(View.VISIBLE);
        }

        mDuration = duration;
        mDelSeekDialogProgress -= delProgress;
        int targetProgress = getTargetProgress();

        if(delProgress > 0) {/ / back mSeekIcon. SetImageResource (R.m ipmap. Ic_video_back); }else{/ / forward mSeekIcon. SetImageResource (R.m ipmap. Ic_video_speed); } mSeekCurProgress.setText(StringUtils.stringForTime(targetProgress)); mSeekDuration.setText(StringUtils.stringForTime(mDuration)); } /** * get target progress after slide */ public intgetTargetProgress() {
        if (mDuration == -1) {
            return- 1; } int newSeekProgress = mSeekDialogStartProgress + mDelSeekDialogProgress;if (newSeekProgress <= 0) newSeekProgress = 0;
        if (newSeekProgress >= mDuration) newSeekProgress = mDuration;
        return newSeekProgress;
    }

    public void hide() {
        mDuration = -1;
        mSeekDialogStartProgress = -1;
        mDelSeekDialogProgress = -1;
        setVisibility(GONE); }}Copy the code

Adjust system Properties popup JsVideoSystemOverlay no longer posts code, similar to above, here we just share design ideas.

Note:

  • MDelSeekDialogProgress -= delProgress, because the delProgress passed in when you swipe right is negative, and the delProgress passed in when you swipe left is positive, so the change calculated here is subtracting.
  • if (newSeekProgress <= 0) newSeekProgress = 0; if (newSeekProgress >= mDuration) newSeekProgress = mDuration; The boundary control is done to prevent the calculated data from being out of range and resulting in errors.

JsVideoErrorView

From the interface looks very simple!

Define all error code constants (instead of enumeration) :

Public static final int STATUS_NORMAL = 0; Public static final int STATUS_VIDEO_DETAIL_ERROR = 1; Public static final int STATUS_VIDEO_SRC_ERROR = 2; Public static final int STATUS_UN_WIFI_ERROR = 3; Public static final int STATUS_NO_NETWORK_ERROR = 4;Copy the code

Another is display control:

switch (status) {
	case STATUS_VIDEO_DETAIL_ERROR:
		video_error_info.setText("Video load failed");
		video_error_retry.setText("Click here to try again");
		break;
	case STATUS_VIDEO_SRC_ERROR:
		video_error_info.setText("Video load failed");
		video_error_retry.setText("Click here to try again");
		break;
	case STATUS_NO_NETWORK_ERROR:
		video_error_info.setText("Network connection is abnormal. Please check network Settings and try again.");
		video_error_retry.setText("Try again");
		break;
	case STATUS_UN_WIFI_ERROR:
		video_error_info.setText("Warm reminder: You are using a non-wifi network, playback will incur data charges.");
		video_error_retry.setText("Keep playing.");
		break;
}

Copy the code

Note: The retry button click event: Error View has a built-in video control callback OnVideoControlListener, click the Retry button to perform the callback when retry.

JsVideoControllerView

First look at the layout:

<? xml version="1.0" encoding="utf-8"? > <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <include
        android:id="@+id/video_controller_bottom"
        layout="@layout/video_controller_bottom" />

    <ImageView
        android:id="@+id/player_lock_screen"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerVertical="true"
        android:layout_marginLeft="10dp"
        android:src="@mipmap/video_unlock"
        android:visibility="gone" />

    <com.jia.jsplayer.view.JsVideoErrorView
        android:id="@+id/video_controller_error"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <include
        android:id="@+id/video_controller_title"
        layout="@layout/video_controller_title" />

    <ImageView
        android:id="@+id/video_back"
        android:layout_width="32dp"
        android:layout_height="44dp"
        android:layout_alignTop="@id/video_controller_title"
        android:padding="12dp"
        android:scaleType="fitCenter"
        android:src="@mipmap/ic_back_white" />

    <RelativeLayout
        android:id="@+id/rl_pre"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="# 000"
        android:visibility="gone">
        <ImageView
            android:id="@+id/iv_pre_play"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@mipmap/ic_video_play"
            android:layout_centerInParent="true"/>
    </RelativeLayout>
</RelativeLayout>
Copy the code

It can be seen that: it is mainly divided into the bottom control part (play button, current position, total duration, progress bar), the head control part (return key, title), error interface, lock button and the default picture of filling the full screen.

For controllers, we should care about these:

First we must pass in the MediaPlayer object (more on its encapsulation later), because we control the MediaPlayer by clicking on the play button, controlling the play and pause of the video, controlling the location of the video when the progress bar is finished, and so on.

Note:

  • UI updates all proposed methods, easy to call elsewhere
  • Default values should be set for whether to lock the screen and display duration of the controller
  • Display controller when the video current position and update the accuracy
  • The clicking of the full-screen button and retry button is left to OnVideoControlListener

I want to focus on hiding and showing controllers:

1. As soon as the controller displays, get the current position of MediaPlayer, update UI (progress bar, current playing position), and return the current position to:

/** * sets the progress, and also returns the progress ** @return
     */
    private int setProgress() {
        if (mPlayer == null || mDragging) {
            return 0;
        }
        int position = mPlayer.getCurrentPosition();
        int duration = mPlayer.getDuration();
        if(mPlayerSeekBar ! = null) {if(duration > 0) { // use long to avoid overflow long pos = 1000L * position / duration; mPlayerSeekBar.setProgress((int) pos); } / / set the buffer progress int percent = mPlayer. GetBufferPercentage (); mPlayerSeekBar.setSecondaryProgress(percent * 10); } mVideoProgress.setText(StringUtils.stringForTime(position)); mVideoDuration.setText(StringUtils.stringForTime(duration));return position;
    }

Copy the code

2. Control the display of each UI layout and start sending messages

/** * display controller ** @param timeout Display duration */ public void show(int timeout) {setProgress();

        if(! isScreenLock) { mControllerBack.setVisibility(VISIBLE); mControllerTitle.setVisibility(VISIBLE); mControllerBottom.setVisibility(VISIBLE); }else {
            if(! DisplayUtils.isPortrait(getContext())) { mControllerBack.setVisibility(GONE); } mControllerTitle.setVisibility(GONE); mControllerBottom.setVisibility(GONE); }if(! DisplayUtils.isPortrait(getContext())) { mScreenLock.setVisibility(VISIBLE); } mShowing =true; updatePausePlay(); // Start displaying post(mShowProgress);if(timeout > 0) {// Remove the previous hidden asynchronous operation removeCallbacks(mFadeOut); // Hide postDelayed(mFadeOut, timeout); }}Copy the code
Private final Runnable mFadeOut = newRunnable() {
        @Override
        public void run() { hide(); }}; */ private final Runnable mShowProgress = newRunnable() {
        @Override
        public void run() {
            int pos = setProgress();
            if(! MDragging && mShowing && mplayer.isplaying ()) {// Resolve the error of less than 1 second so that sending messages is stuck exactly in the whole second log.e ("TAG"."run: "+ (1000 - (pos % 1000))); postDelayed(mShowProgress, 1000 - (pos % 1000)); }}};Copy the code
  • First, notice that every time you start sending a message, you should force all previous messages to be removed;
  • Two messages are sent: one is timed to get the current location and update the UI every about one second, and the other is to hide the controller after a long delay in display;
  • Why update UI every 1 second or so, postDelayed(mShowProgress, 1000 – (pos % 1000)); I made a correction operation, because each message may affect each other, and the second is that the message was not stuck in the whole second position of the video, but we did send a message in the whole second, which would cause errors!

If you want to know more about other features, you can go to Github and read my source at github.com/shuaijia/Js…


5. MediaPlayer encapsulation

Mainly encapsulated

  • OpenVideo: Plays videos and handles callbacks
  • Start: starts playing
  • Pause: Pauses the playback
  • SeekTo: Locate
  • Reset: Resets the video
  • Stop: Stops the playback
  • IsPlaying: indicates whether it isPlaying
  • GetDuration: Indicates the total duration
  • GetCurrentPosition: Gets the current progress
  • GetBufferPercentage: obtains the buffer progress

Defines the state value constants used for video playback

Public static final int STATE_ERROR = -1; Public static final int STATE_IDLE = 0; Public static final int STATE_PREPARING = 1; Public static final int STATE_PREPARED = 2; Public static final int STATE_PLAYING = 3; Public static final int STATE_PAUSED = 4; Public static final int STATE_PLAYBACK_COMPLETED = 5;Copy the code
// Use MediaPlayer private MediaPlayer player; Private int curState = STATE_IDLE; Private int currentBufferPercentage; // * Private String path; // Play listener private OnPlayerCallback onPlayerListener; // View private SurfaceHolder SurfaceHolder;Copy the code

Encapsulates the judgment of video playback status

    public boolean isInPlaybackState() {
        return(player ! = null && curState ! = STATE_ERROR && curState ! = STATE_IDLE && curState ! = STATE_PREPARING); }Copy the code

This method checks before all other methods are executed. If false is returned, no action is taken to start, replay, drag, or position.

In addition, the current playing status is updated after these operations are performed to prevent errors when videos cannot be played. Such as

/** * start playing */ public voidstart() {
        if (isInPlaybackState()) {
            player.start();
            setCurrentState(STATE_PLAYING); }}Copy the code

In openVideo:

    public void openVideo() {
        if (path == null || surfaceHolder == null) {
            return; } reset(); player = new MediaPlayer(); / / ready to monitor player. SetOnPreparedListener (new MediaPlayer.OnPreparedListener() {@override public void onPrepared(MediaPlayer mp) {// Set STATE_PREPARED to STATE_PREPARED Otherwise, the video cannot play automatically when it is opened for the first timesetCurrentState(STATE_PREPARED);
                if(onPlayerListener ! = null) { onPlayerListener.onPrepared(mp); }}}); / / buffer to monitor player. SetOnBufferingUpdateListener (new MediaPlayer.OnBufferingUpdateListener() {
            @Override
            public void onBufferingUpdate(MediaPlayer mp, int percent) {
                if(onPlayerListener ! = null) { onPlayerListener.onBufferingUpdate(mp, percent); } currentBufferPercentage = percent; }}); / / play complete listening player. SetOnCompletionListener (new MediaPlayer.OnCompletionListener() {
            @Override
            public void onCompletion(MediaPlayer mp) {
                if(onPlayerListener ! = null) { onPlayerListener.onCompletion(mp); }setCurrentState(STATE_PLAYBACK_COMPLETED); }}); SetOnInfoListener (new MediaPlayer)OnInfoListener() {
            @Override
            public boolean onInfo(MediaPlayer mp, int what, int extra) {
                if(onPlayerListener ! = null) {// 701 loadingif (what == MediaPlayer.MEDIA_INFO_BUFFERING_START) {
                        onPlayerListener.onLoadingChanged(true); // 702 loading complete}else if (what == MediaPlayer.MEDIA_INFO_BUFFERING_END) {
                        onPlayerListener.onLoadingChanged(false); }}return false; }}); / / error monitor player. SetOnErrorListener (onErrorListener); Size / / video switch to monitor player. SetOnVideoSizeChangedListener (new MediaPlayer.OnVideoSizeChangedListener() {
            @Override
            public void onVideoSizeChanged(MediaPlayer mp, int width, int height) {
                if(onPlayerListener ! = null) { onPlayerListener.onVideoSizeChanged(mp, width, height); }}}); currentBufferPercentage = 0; */ player.setdatasource (path); player.setDisplay(surfaceHolder); player.setAudioStreamType(AudioManager.STREAM_MUSIC); player.setScreenOnWhilePlaying(true);
            player.prepareAsync();
            Log.e(TAG, "openVideo: " );
            setCurrentState(STATE_PREPARING);
        } catch (Exception e) {
            Log.e(TAG, "openVideo: " + e.toString());
            setCurrentState(STATE_ERROR); onErrorListener.onError(player, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0); }}Copy the code

OpenVideo is the core method for playing video: create a MediaPlayer object; Assign each video playback callback to OnPlayerCallback; Set the SurfaceHolder to MediaPlayer and prepareAsync to play. Don’t forget to update the state!

The SurfaceHolder is an abstract interface to the Surface that allows you to control the size and format of the surface, edit pixels on the surface, and monitor surace changes. The SurfaceHolder is used to display the frame picture parsed by the video stream. If the SurfaceView becomes invisible to the user, the SurfaceHolder of the SurfaceView is destroyed immediately to save system resources.


6. Gesture control

When it comes to gestures, it’s mostly gestures that control video progress, gestures that control volume and screen brightness.

For the gesture control, I customized a BehaviorView that implemented the GestureDetector’s OnGestureListener

public class VideoBehaviorView extends FrameLayout implements GestureDetector.OnGestureListener{
Copy the code

Define the following methods in this view to implement the updated UI and have subclasses override it:

Protected void updateSeekUI(int delProgress) {// sub} Protected void updateVolumeUI(int Max, int progress) {// sub} Protected void updateLightUI(int Max, int progress) {// subCopy the code

My idea is to hand the View’s touch events over to the GestureDetector:

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        mGestureDetector.onTouchEvent(event);
        switch (event.getAction() & MotionEvent.ACTION_MASK) {
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_OUTSIDE:
            case MotionEvent.ACTION_CANCEL:
                endGesture(mFingerBehavior);
                break;
        }
        return true;
    }
Copy the code

Reset finger behavior when finger is pressed to get current volume and brightness

@override public Boolean onDown(MotionEvent e) {mFingerBehavior = -1; mCurrentVolume = am.getStreamVolume(AudioManager.STREAM_MUSIC); try { mCurrentBrightness = (int) (activity.getWindow().getAttributes().screenBrightness * mMaxBrightness); } catch (Exception exception) { exception.printStackTrace(); }return false;
    }
Copy the code

In the onScroll method:

Determine the current gesture type: swiping left and right to adjust progress, swiping up and down on the left half screen to adjust brightness, swiping up and down on the right half screen to adjust volume

/** * asserts subsequent actions based on the initial 2 points of the gesture. The rules are as follows: * Screen segmentation is: * 1. The left and right sector is video progress adjustment * 2. Fan area up and down the left half screen brightness adjustment rear half screen volume adjustment. */if (mFingerBehavior < 0) {
            float moveX = e2.getX() - e1.getX();
            floatmoveY = e2.getY() - e1.getY(); // If the horizontal sliding distance is greater than the vertical sliding distance, it is considered to be adjusting the progressif(Math.abs(moveX) >= Math.abs(moveY)) mFingerBehavior = FINGER_BEHAVIOR_PROGRESS; // Otherwise, to adjust volume or brightness // Press the position on the left half of the screen, to adjust brightnesselse if(e1.getX() <= width / 2) mFingerBehavior = FINGER_BEHAVIOR_BRIGHTNESS; // Press on the right half of the screen to adjust the volumeelse mFingerBehavior = FINGER_BEHAVIOR_VOLUME;
        }
Copy the code

Signal processing

        switch (mFingerBehavior) {
            caseFINGER_BEHAVIOR_PROGRESS: Int delProgress = (int) (1.0f * distanceX/width * 480 * 1000); int delProgress = (int) (1.0f * distanceX/width * 480 * 1000); // Update quick forward popbox updateSeekUI(delProgress);break;
            }
            caseFINGER_BEHAVIOR_VOLUME: {// The volume changesfloatprogress = mMaxVolume * (distanceY / height) + mCurrentVolume; // Control the critical rangeif (progress <= 0) progress = 0;
                if(progress >= mMaxVolume) progress = mMaxVolume; am.setStreamVolume(AudioManager.STREAM_MUSIC, Math.round(progress), 0); updateVolumeUI(mMaxVolume, Math.round(progress)); // Update current value mCurrentVolume = progress;break;
            }
            caseFINGER_BEHAVIOR_BRIGHTNESS: {// Brightness change try {// If the brightness is automatically adjusted, change it to manualif(Settings.System.getInt(getContext().getContentResolver(), Settings.System.SCREEN_BRIGHTNESS_MODE) == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC) { Settings.System.putInt(getContext().getContentResolver(), Settings.System.SCREEN_BRIGHTNESS_MODE, Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL); } int progress = (int) (mMaxBrightness * (distanceY / height) + mCurrentBrightness); // Control the critical rangeif (progress <= 0) progress = 0;
                    if (progress >= mMaxBrightness) progress = mMaxBrightness;

                    Window window = activity.getWindow();
                    WindowManager.LayoutParams params = window.getAttributes();
                    params.screenBrightness = progress / (float) mMaxBrightness; window.setAttributes(params); updateLightUI(mMaxBrightness, progress); // Update the current value mCurrentBrightness = progress; } catch (Exception e) { e.printStackTrace(); }break; }}Copy the code

Note:

  • All update UI operations are handed over to subclasses
  • Note the control of the critical range
  • When controlling the progress, the percentage is finally multiplied by 8 minutes to achieve a more moderate user experience, so as to prevent the change of the progress from being too obvious or the effect is not obvious when the video length is too long or too small.

7, player JsPlayer package

Let’s look at the layout first

<? xml version="1.0" encoding="utf-8"? > <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <SurfaceView
        android:id="@+id/video_surface"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <com.jia.jsplayer.view.JsVideoControllerView
        android:id="@+id/video_controller"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

    <include
        android:id="@+id/video_loading"
        layout="@layout/video_controller_loading" />

    <com.jia.jsplayer.view.JsVideoSystemOverlay
        android:id="@+id/video_system_overlay"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:visibility="gone"/>

    <com.jia.jsplayer.view.JsVideoProgressOverlay
        android:id="@+id/video_progress_overlay"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:visibility="gone"/>

</RelativeLayout>
Copy the code

JsPlayer Integrates the VideoBehaviorView from the previous step. Note that the UI update method of VideoBehaviorView is copied.

    private SurfaceView surfaceView;
    private View loadingView;
    private JsVideoProgressOverlay progressView;
    private JsVideoSystemOverlay systemView;
    private JsVideoControllerView mediaController;

    private JsMediaPlayer mMediaPlayer;
Copy the code

Built-in encapsulation of JsMediaPlayer objects, controllers, and SurfaceView, as well as the network state broadcast receiver.

Initialize the Player, create the JsMediaPlayer object, set the video playback callback handling, and then set it to ControllerView.

Note:

  • In the ready listener, mediaPlayer starts playing, the controller is displayed, and the error screen is hidden.
  • The controller checks the error type and displays it when the playback error occurs
  • Hide and show loading when loading status changes
    private void initPlayer() { mMediaPlayer = new JsMediaPlayer(); / / todo here can be optimized, the callback all exposed out mMediaPlayer setOnPlayerListener (newOnPlayerCallback() {
            @Override
            public void onPrepared(MediaPlayer mp) {
                Log.e(TAG, "onPrepared: " );
                mMediaPlayer.start();
                mediaController.show();
                mediaController.hideErrorView();
            }

            @Override
            public void onVideoSizeChanged(MediaPlayer mp, int width, int height) {

            }

            @Override
            public void onBufferingUpdate(MediaPlayer mp, int percent) {

            }

            @Override
            public void onCompletion(MediaPlayer mp) {
                mediaController.updatePausePlay();
            }

            @Override
            public void onError(MediaPlayer mp, int what, int extra) {
                mediaController.checkShowError(false);
            }

            @Override
            public void onLoadingChanged(boolean isShow) {
                if (isShow) showLoading();
                else hideLoading();
            }

            @Override
            public void onStateChanged(int curState) {
                switch (curState) {
                    case JsMediaPlayer.STATE_IDLE:
                        am.abandonAudioFocus(null);
                        break;
                    case JsMediaPlayer.STATE_PREPARING:
                        am.requestAudioFocus(null, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
                        break; }}}); mediaController.setMediaPlayer(mMediaPlayer); }Copy the code

Callback to SurfaceView, return SurfaceHolder to JsMediaPlayer

        surfaceView.getHolder().addCallback(new SurfaceHolder.Callback() {
            @Override
            public void surfaceCreated(SurfaceHolder holder) {
                Log.e(TAG, "surfaceCreated: " );
                initWidth = getWidth();
                initHeight = getHeight();

                if(mMediaPlayer ! = null) { mMediaPlayer.setSurfaceHolder(holder); } } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } @Override public void surfaceDestroyed(SurfaceHolder holder) { } });Copy the code

Set the path and start playing

    public void setPath(final IVideoInfo video) {
        if (video == null) {
            return;
        }

        mMediaPlayer.reset();

        String videoPath = video.getVideoPath();
        mediaController.setVideoInfo(video);
        mMediaPlayer.setPath(videoPath);

    }

    public void startPlay(){
        mMediaPlayer.openVideo();
    }
Copy the code

Update the UI

    @Override
    protected void updateSeekUI(int delProgress) {
        progressView.show(delProgress, mMediaPlayer.getCurrentPosition(), mMediaPlayer.getDuration());
    }

    @Override
    protected void updateVolumeUI(int max, int progress) {
        systemView.show(JsVideoSystemOverlay.SystemType.VOLUME, max, progress);
    }

    @Override
    protected void updateLightUI(int max, int progress) {
        systemView.show(JsVideoSystemOverlay.SystemType.BRIGHTNESS, max, progress);
    }
Copy the code

Of course, the basic methods of encapsulating play, pausing, stopping, positioning, getting total duration, and so on are not forgotten.


Eight, the use of

When it comes to playing network video, permissions are indispensable

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
Copy the code

Don’t forget 6.0 permission adaptation when playing local videos

Add to the layout

In the code

        player = (JsPlayer) findViewById(R.id.player);

        player.setOnVideoControlListener(new OnVideoControlListener() {
            @Override
            public void onStartPlay() {
                player.startPlay();
            }

            @Override
            public void onBack() {

            }

            @Override
            public void onFullScreen() {
                DisplayUtils.toggleScreenOrientation(MainActivity.this);
            }

            @Override
            public void onRetry(int errorStatus) {

            }
        });

        player.setPath(new VideoInfo("Life in art", path));
Copy the code

Life cycle binding

    @Override
    protected void onStop() {
        super.onStop();
        player.onStop();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        player.onDestroy();
    }
Copy the code

Full screen operation


    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
            getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
        } else if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
            getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
        }
    }

    @Override
    public void onBackPressed() {
        if(! DisplayUtils.isPortrait(this)) {if (!player.isLock()) {
                DisplayUtils.toggleScreenOrientation(this);
            }
        } else{ super.onBackPressed(); }}Copy the code

Pay attention to in the Activity in the manifest file should set the android: configChanges = “orientation” | keyboardHidden | screenSize”

This is ok, the player packaging is perfect!

Hope to help you!

For more exciting content, please follow my wechat public account ————Android Motor vehicle