This article has been published exclusively by guolin_blog, an official wechat account

The previous article briefly explained the realization of Tencent News video seamless switching effect (video in the play of the page switch), if you have not read the previous article, you can first go to see the Android high imitation Tencent news video switching effect. The last article was written casually, just explaining how to switch between the two pages in the video playback (switch the container of the player) and stop the video scrolling, etc. Some effects were not realized, and some details were not well handled, so I made up a more detailed tutorial again. I won’t repeat the same thing this time. Again, the renderings first

download

  • Enter the full-screen automatic judgment of vertical and horizontal switching direction
  • After the full screen is played, the next video is automatically played and the direction is changed according to the width and height of the video
  • Add mask layer when playing, play the next text prompt
  • Add countdown and animation when playing
  • Wifi switch 4G prompt, 4G cut wifi automatically play

This time, the player is changed to JZVideoPlayer. If the player is not connected or just connected in the project, it is still recommended to change to PlayerBase, which is highly decoupled and has high scalability and provides seamless continuation assistant.

The JZVideoPlayer version is previous and has a bit of a change. Here is the main idea, with attention points, PlayerBase can also be implemented. JZVideoPlayer implements a seamless switch that essentially changes the ViewParent of the player

    public void attachToContainer(ViewGroup container) {
        detachSuperContainer();
        if(container ! = null) { container.addView(JZVideoPlayerManager.getCurrentJzvd(), new FrameLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); playerContainer = container; } } public voiddetachSuperContainer() {
        JZVideoPlayer player = JZVideoPlayerManager.getCurrentJzvd();
        ViewParent parent = player.getParent();
        if (parent != null && parent instanceof ViewGroup) {
            ((ViewGroup) parent).removeView(player);
        }
    }
Copy the code

Switching between 4G and wifi prompts: Register a broadcast to monitor

    @Override
    protected void onResume() {
        super.onResume();
        JZVideoPlayer.goOnPlayOnResume();
        IntentFilter filter = new IntentFilter();
        filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
        filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
        registerReceiver(wifiReceiver, filter);
    }

    @Override
    protected void onStop() {
        super.onStop();
        try {
            //weChat moment share will execute twice so try catch
            unregisterReceiver(wifiReceiver);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    private BroadcastReceiver wifiReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            if(intent ! = null && WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(intent.getAction())) { NetworkInfo info = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);if(info ! = null) {if (info.getState().equals(NetworkInfo.State.DISCONNECTED)) {
                        if (JZMediaManager.isWiFi) {
                            JZMediaManager.isWiFi = false;
                            JZVideoPlayer.WIFI_TIP_DIALOG_SHOWED = false;
                           if(Playing or loading){jzMediamanager.instance ().jzMediaInterface. Pause (); JZVideoPlayerManager.getCurrentJzvd().onStatePause(); }}}else if (info.getState().equals(NetworkInfo.State.CONNECTED)) {
                        if(! JZMediaManager.isWiFi) { JZMediaManager.isWiFi =true;
                            JZVideoPlayer.WIFI_TIP_DIALOG_SHOWED = true;
                            if(JZVideoPlayerManager.getCurrentJzvd() ! = null && JZVideoPlayerManager.getCurrentJzvd().currentState == JZVideoPlayer.CURRENT_STATE_PAUSE) { JZVideoPlayer.goOnPlayOnResume(); }}}}}}};Copy the code

This is registered in onResume because my project has videos on more than one page, so I need to listen here. Notice that when wechat shares, onStop is called twice, so try catch. When 4G cuts wifi, it should be noted that if the user manually pauses, it does not need to play automatically.

News page

  • Slide to stop and play the first fully visible video
    public static void onScrollPlayVideo(RecyclerView recyclerView, int firstVisiblePosition, int lastVisiblePosition) {
        if (JZMediaManager.isWiFi) {
            for (int i = 0; i <= lastVisiblePosition - firstVisiblePosition; i++) {
                View child = recyclerView.getChildAt(i);
                View view = child.findViewById(R.id.player);
                if(view ! = null && view instanceof JZVideoPlayerStandard) { JZVideoPlayerStandard player = (JZVideoPlayerStandard) view;if (getViewVisiblePercent(player) == 1f) {
                        if(JZMediaManager.instance().positionInList ! = i + firstVisiblePosition) { player.startButton.performClick(); }break;
                    }
                }
            }
        }
    }
Copy the code

If the position of your item changes (don’t ask me why, it does, manual dog head), use

JZVideoPlayerManager.getCurrentJzvd() ! = playerCopy the code

To judge. Calculate the percentage of views visible, ranging from 0 to 1

    public static float getViewVisiblePercent(View view) {
        if (view == null) {
            return 0f;
        }
        float height = view.getHeight();
        Rect rect = new Rect();
        if(! view.getLocalVisibleRect(rect)) {return 0f;
        }
        float visibleHeight = rect.bottom - rect.top;
        Log.d(TAG, "getViewVisiblePercent: emm " + visibleHeight);
        return visibleHeight / height;
    }
Copy the code
  • Part rolls off the screen and stops playing
    public static void onScrollReleaseAllVideos(int firstVisiblePosition, int lastVisiblePosition,float percent) {
        int currentPlayPosition = JZMediaManager.instance().positionInList;
        if (currentPlayPosition >= 0) {
            if ((currentPlayPosition <= firstVisiblePosition || currentPlayPosition >= lastVisiblePosition - 1)) {
                if(getViewVisiblePercent(JZVideoPlayerManager.getCurrentJzvd()) < percent) { JZVideoPlayer.releaseAllVideos(); }}}}Copy the code
  • There was a slight flaw in the last version of the seamless transition code (the Y coordinate was not calculated correctly), so here is the translation code, notice the value of translationY here
/ / the first holder. ItemView. SetTranslationY (attr. GetY () - l [1]). holder.container.setScaleX(attr.getWidth() / (float) holder.container.getMeasuredWidth());
                    holder.container.setScaleY(attr.getHeight() / (float) holder.container.getMeasuredHeight()); / / modified version holder. ItemView. SetTranslationY (attr. GetY () - l [1] - [holder. Container. GetMeasuredHeight () - attr. GetHeight () 2); holder.container.setScaleX(attr.getWidth() / (float) holder.container.getMeasuredWidth());
                    holder.container.setScaleY(attr.getHeight() / (float) holder.container.getMeasuredHeight());
Copy the code

If the containers are the same size (the video list page goes to the comment page), you can just subtract the coordinates, but here you’re changing the size of the player, so you have to subtract half the height difference, and divide by 2 because the center of the zoom is the midpoint of the view.

Because JZVideoPlayer is used here, the width and height of the playback container need to be fixed, otherwise the onMeasure of the view will be triggered and flicker

Video List page

When entering this page, it is necessary to enter directly and play video into two situations. Enter directly, that is, directly add fragment, then play the first video, video play into the seamless switching effect. Same with exit page

PS: Note that when switching seamlessly, in the news page, clicking takes you directly to the video list, while in the video list, clicking brings up the controller. There is a countdown animation on the news page, but not on the video list page. When these pages are switched, the corresponding Settings of show and hide and click events are needed.

  • The code for the first fully visible video that plays after the slide stops is pretty much the same as the code above, but I won’t go into it again, see this in the demo here and pay attention to the listener that is recovered after the video slides out of the area.
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);
                if(dy ! = 0) { JZUtils.onScrollReleaseAllVideos(mLayoutManager.findFirstVisibleItemPosition(), MLayoutManager. FindLastVisibleItemPosition (), 0.2 f); }}Copy the code

The onScrolled() method here is a little bit flawed (personally).

         * Callback method to be invoked when the RecyclerView has been scrolled. This will be
         * called after the scroll has completed.
         * <p>
         * This callback will also be called if visible item range changes after a layout
         * calculation. In that case, dx and dy will be 0.
         *
         * @param recyclerView The RecyclerView which scrolled.
         * @param dx The amount of horizontal scroll.
         * @param dy The amount of vertical scroll.
         */
        public void onScrolled(RecyclerView recyclerView, int dx, int dy)
Copy the code

When recyclerView slides, this method is called back. It’s normal. But there are still two lines left. This method is also triggered when the visible item is remeasured and rearranged, and dx and dy are both 0. The important thing to note here is that we have full screen, and we’re going to switch between vertical and horizontal, which will trigger this method. It’s not functioning properly. So I put a non-zero judgment up there.

  • When it’s done it’s going to play the next video and notice that I’m going to play the next video by sliding the next video to the top. However, there is also the case that you have very few videos (our app is one of them), and the last video cannot be played. Tencent’s data volume is large enough to not have this problem generally. So when there is no more, need to insert a data at the bottom of recyclerView, show no more data, you can play the video, Tencent is so processing, I can see that the design is very comprehensive, a page can only fully display a video, consider very comprehensive ah.

  • mask

The mask here is a custom view, drawing a semi-clear background.

When the list is played, the mask is displayed and a transition effect is required (transparency animation).

Hide the mask when sliding the screen to display the comment page, toggle full screen, and exit the video list without transition effects.

  • Play the next prompt. Listen to the remaining time of the video through the player. When the remaining time is less than 5s, the prompt will be displayed. Play the next link or slide the page, the prompt disappears. Click the prompt, then play the next video (the prompt is not displayed in full screen state).
  • Enter the full-screen automatic judgment horizontal and vertical screen switching direction mainly through the playeronVideoSizeChanged()Method to listen.
        if (JZMediaManager.instance().currentVideoWidth > JZMediaManager.instance().currentVideoHeight) {
            JZVideoPlayer.FULLSCREEN_ORIENTATION = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
            if(JZVideoPlayerManager.getCurrentJzvd().currentScreen == SCREEN_WINDOW_FULLSCREEN){ JZUtils.setRequestedOrientation(JZVideoPlayerManager.getCurrentJzvd().getContext(), ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); }}else {
            JZVideoPlayer.FULLSCREEN_ORIENTATION = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
            if(JZVideoPlayerManager.getCurrentJzvd().currentScreen == SCREEN_WINDOW_FULLSCREEN){ JZUtils.setRequestedOrientation(JZVideoPlayerManager.getCurrentJzvd().getContext(), ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); }}Copy the code

According to the width and height Settings into the full screen is portrait or landscape (if your company is not mainstream, can not be judged according to the width and height of the video, then add a field setting background).

There is a bug in Android P, the screen will be black when switching the direction of the screen, we have not found the solution, if you know how to solve it, please leave a message below!! In addition, some Chinese mobile phone seekbar click not directly to the corresponding progress, but a little forward, is also surprised…

  • After the full-screen video is played, switch the URL to play the next video, determine whether to switch the screen direction according to the width, height and current direction of the video, and slide the list to switch the URL
    public void changeUrl(String url, Object... objects) {
        this.currentUrlMapIndex = 0;
        this.seekToInAdvance = 0;
        LinkedHashMap map = new LinkedHashMap();
        map.put(URL_KEY_DEFAULT, url);
        Object[] dataSourceObjects = new Object[1];
        dataSourceObjects[0] = map;
        this.dataSourceObjects = dataSourceObjects;
        this.objects = objects;
        setState(CURRENT_STATE_PREPARING_CHANGING_URL);
        resetProgressAndTime();
    }
Copy the code

Basically resetting some state, changing the value of a variable. The direction check will also trigger onVideoSizeChanged(). Inside the check, this is the code above.

PS: it is best to hide the rendering layer when switching URLS and display it when playing. Otherwise, some machines may stretch the last frame.

The list of sliding

It’s important to note that we listened for the slide above and cannot call the smoothScrollTo() or smoothScrollBy() methods. Here can be directly call scrollToPositionWithOffset (), sliding directly to the corresponding position (if you are not a LinearLayoutManager, then think of some way to yourself.)

If, like me, you use JZVideoPlayer, here’s something to watch out for

if (JZVideoPlayerManager.getCurrentJzvd().currentScreen == SCREEN_WINDOW_FULLSCREEN) {
                JZMediaManager.instance().positionInList++;
                JZVideoPlayerManager.getCurrentJzvd().changeUrl(mList.get(JZMediaManager.instance().positionInList).getVideoUrl());
                mLayoutManager.scrollToPositionWithOffset(JZMediaManager.instance().positionInList, 0);
                mRecycler.postDelayed(new Runnable() {
                    @Override
                    public void run() { JZVideoPlayerManager.setFirstFloor((JZVideoPlayer) mRecycler.getChildAt(0).findViewById(R.id.player)); }}, 500); }Copy the code

When entering and exiting the video list page for seamless playback, changes are made to the parent view of the player, which requires addView or removeView and modification of related interfaces.

 if(in the first video) {videoListFragment. RemoveVideoList (); recycler.postDelayed(newRunnable() {
                @Override
                public void run() {
                    JZMediaManager.instance().positionInList = clickPosition;
                    int first = mLayoutManager.findFirstVisibleItemPosition();
                    View v = recycler.getChildAt(clickPosition - first);
                    if(v ! = null) { final PlayerContainer container = v.findViewById(R.id.adapter_video_container);ifContainer. RemoveAllViews (); } / / player interface, the state set up} FragmentTransaction transaction = getSupportFragmentManager (). The beginTransaction (); transaction.remove(videoListFragment); transaction.commitAllowingStateLoss(); }}, 800); }else {
            JZVideoPlayer.releaseAllVideos();
            FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
            transaction.remove(videoListFragment);
            transaction.commitAllowingStateLoss();
            if(is gapless playback to video page list) {int first = mLayoutManager. FindFirstVisibleItemPosition (); View v = recycler.getChildAt(clickPosition - first);if(v ! = null) { final PlayerContainer container = v.findViewById(R.id.adapter_video_container); container.removeAllViews(); // Add player}}}Copy the code

To explain, there are four cases:

  • Entry and exit play seamlessly, just modify the viewParent of the player
  • Neither entry nor exit is seamless and requires no processing
  • Entry is seamless, exit is not. That is, you move the player when you enter the page, but you don’t move it back when you exit. You need to add the same player in its place
  • Entering is not seamless play, exiting is. So when you exit, you move the player to the news page, but the news page originally had a player. Then you need to remove the original player and interface the moving player to change the state.

The logic is really complicated and needs to be read several times

If you don’t have a player, use PlayerBase instead.

Comments on the page

The comments page is not much different from the last one, with a small change: after the video is played, it will reset to normal state, exit the comment page and return to the video list page, and the next video will automatically play. Code will not paste, see demo. We’re done. Let’s celebrate with a Sprite ’82.


Dynamic loading of ijkPlayer so file: ijkplayer so file:

  • Modify libPath with reflection to add a load path
  • Load (String filename) instead of system. loadLibrary(String libname) For convenience, only one of the SOS is dynamically loaded in the demo. IntentService is used to check whether the so file exists and is complete (MD5). If so, switch iJK. Otherwise, request the interface (need to transfer the supported AIBS) to download the file (AIBS, breakpoint continuation, MD5 check please handle by yourself,demo has not implemented), switch after downloading. Notice the download path
 File dir = getDir("libs", Context.MODE_PRIVATE);
 File soFile = new File(dir, "ijkffmpeg.so");
Copy the code

Is the path to soFile. Then load is so library, at first I thought the IjkMediaPlayer. Directly to the Java kao congrats modify the loading paths, but is still an error, can’t find the way. The method name of JNI needs package name + class name + method name, but I copy it directly here. The package name is changed, so I can’t find the method. Therefore, it is necessary to copy the whole iJK library and introduce it into the project for modification (you can also modify the package name in the SO library). PS: If you are worried that you still can’t find so, you can do this

try {
        jzMediaInterface.prepare();
 } catch (Throwable e) {
      e.printStackTrace();
      Object dataSource = JZMediaManager.getCurrentDataSource();
      Log.e(TAG, "handleMessage: " + e.getMessage());
      Toast.makeText(MyApplication.getInstance(), "so error", Toast.LENGTH_SHORT).show();
      JZVideoPlayer.setMediaInterface(new JZExoPlayer());
       jzMediaInterface.currentDataSource = dataSource;
       jzMediaInterface.prepare();
    }
Copy the code

Catch the initialization error and switch back to the standby kernel.

Dragged for a long time finally finished writing this thing, the high difficulty of the thing is not much, all is the processing of details. Although the effect is ok, but still can not escape the last said the problem, can not switch between activities, logic complex, coupling degree is too high.

Finally, attached source code, have a question or a better way to achieve, welcome to leave a message below, free to see will reply.