自动暂停和恢复网页中的视频播放

序言

在日常开发中,有时候一级栏目可能是个H5页面,当切换到其他栏目的时候需要自动暂停H5中的视频,切换回来以后需要自动恢复播放。实现的思路是通过JS来操作,难点是感知fragment的可见状态。还有js代码的编写。

比如下面这个页面就是H5页面。切换到首页的时候需要暂停视频,切换回来需要自动播放。

在这里插入图片描述

解决方案

对fragment可见状态的监视

通过一下工具类实现。相关的使用方式在注释中,只需要在fragment中调用相关方法就行了。该类采用了观察者模式,如果后面还有其他基于可见性的业务代码,通过添加观察者就可以扩展。

package com.trs.library.fragment;


import java.util.Observable;

/**
 * <pre>
 * Created by zhuguohui
 * Date: 2024/2/6
 * Time: 9:46
 * Desc:该类是用于监视fragment的可见状态的工具类
 * 采用观察者模式,注册观察者以后,可见状态会以参数的形式传递。
 * 这里的可见的定义是对用户可见。屏蔽了viewpager中的预加载不算
 * 或者通过fragmentManger的hide方法隐藏了fragment。也不算可见。
 *
 * 使用方法。需要在fragment的生命周期方法中调用同名的以下方法。
 * {@link #setUserVisibleHint(boolean)}
 * {@link #onResume()}
 * {@link #onPause()}
 * {@link #onHiddenChanged(boolean)}
 *
 * 提示:
 * 由于setUserVisibleHint 比fragment的onCreate方法还要提前。所以这个工具的初始化不能放在生命周期方法中。
 * 最好以类的成员变量的形式初始化。
 * </pre>
 */
public  class FragmentShowStateMonitor extends Observable {

    boolean isVisibleToUser = true;
    boolean isHide = false;

    /**
     * 这个方法由ViewPager调用。
     *
     * @param isVisibleToUser
     */
    public final void setUserVisibleHint(boolean isVisibleToUser) {
        this.isVisibleToUser = isVisibleToUser;
        onFragmentShowStateChange(isVisibleToUser);

    }


    public final void onPause() {
        if (isShow()) {
            onFragmentShowStateChange(false);
        }
    }

    private boolean isShow() {
        if (isHide||!isVisibleToUser) {
            return false;//viewPager中也可能hide
        }
        return true;
    }


    /**
     * 使用FragmentTransaction的show或者hide方法的时候。会回调改方法。
     * hide的本质就是将fragment中的view设置为GONE。
     * 注意:如果fragment嵌套了子Fragment。当外层的fragment的onHiddenChanged被调用以后
     * 子Fragment默认是不会被调用了。如果要记录子Fragment的显示时长。需要自己去重新父Fragment的
     * onHiddenChanged方法。
     *
     * @param hidden
     */
    public final void onHiddenChanged(boolean hidden) {
        isHide = hidden;
        onFragmentShowStateChange(!hidden);
    }


    /**
     * 在viewPager中由于有预加载功能,当调用onResume的时候,其实并没有显示给用户
     * 需要通过isVisibleToUser 来判断是否显示
     *
     */
    public final void onResume() {

        if (isShow()) {
            onFragmentShowStateChange(true);
        }
    }



    /**
     * 子类重写该方法用于记录fragment给用户展示的时间
     *
     * @param showToUser
     */
    protected  void onFragmentShowStateChange(boolean showToUser){
        setChanged();
        notifyObservers(showToUser);
    }

}

使用参考

package com.trs.library.fragment;

import android.os.Bundle;
import android.util.Log;
import android.view.View;

import com.gyf.immersionbar.components.ImmersionFragment;
import com.trs.library.rx.bus.RxBus;

import java.util.LinkedList;
import java.util.Queue;

import io.reactivex.disposables.CompositeDisposable;
import rx.subscriptions.CompositeSubscription;

/**
 * Created by Vincent Woo
 * Date: 2016/6/8
 * Time: 10:41
 */
public class TRSFragment extends Fragment{

    protected FragmentShowStateMonitor showStateMonitor=new FragmentShowStateMonitor();
    

    @Override
    public void onResume() {
        super.onResume();
        showStateMonitor.onResume();
    }


    @Override
    public void onPause() {
        super.onPause();
        showStateMonitor.onPause();
    }

    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        showStateMonitor.setUserVisibleHint(isVisibleToUser);
    }

    @Override
    public void onHiddenChanged(boolean hidden) {
        super.onHiddenChanged(hidden);
        showStateMonitor.onHiddenChanged(hidden);
    }
}

由于目前项目中H5的页面没有出现在ViewPager中。所以监视代码比较简单。

如果出现在ViewPager中。当一个fragment A 包含ViewPager,viewPager又包含很多子fragment。当A通过fragmentManger的hide方法隐藏的时候,只有A的onHiddenChanged方法会被调用,子Fragment的相关方法不会被调用。

如果我们要观察的fragment属于ViewPager中那么我们需要手动的找出正在显示的fragment,手动调用它的onHiddenChanged。因为默认的情况下,onHiddenChanged方法没有传递性。具体的解决方案参考我的另外一篇博客。

记录Fragment的显示时长

视频的暂停

主要通过js实现,相关的情况说明都写在注释了。

package com.trs.myrb.fragment.common;

import com.tencent.smtt.sdk.WebView;

/**
 * <pre>
 * Created by zhuguohui
 * Date: 2023/12/27
 * Time: 15:48
 * Desc:该工具类的作用是通过js来暂停或者恢复网页中的视频或音频播放。
 * </pre>
 */
public class PauseWebVideoUtil {

    public static void pauseAudio(WebView webView){

        //以下两行代码的目的是清除网页中的定时器
        //在业务中发现,不清除定时器的话,在其他页面,用原生播放器播放视频的话可能会自动暂停
        //可能是网页中有定时器在播放视频,抢走了音频焦点。需要暂停视频
        //为什么不用WebView的pauseTimers()方法。因为这个方法它会暂停所有webView的渲染和js的执行。
        //结果就是其他的网页都加载不出来。使用js来清楚定时器,它的影响范围在单个WebView中

        // 清除所有 setTimeout
        webView.evaluateJavascript("for (var i = 1; i < 10000; i++) { clearTimeout(i); }", null);
        // 清除所有 setInterval


        webView.evaluateJavascript("for (var i = 1; i < 10000; i++) { clearInterval(i); }", null);
        //为什么要把暂停的视频保存在window.LastPauseVideoFromApp变量中。
        //主要是为了避免一个页面中有多个可以播放的模块。如果不记录之前暂停的控件的话
        //到了恢复播放的时候,可能就会错误的播放多个控件。造成一片混乱
        webView.loadUrl(
                        "javascript:( function (){\n" +
                                "  var videos = document.getElementsByTagName('video');\n" +
                                "  var audios = document.getElementsByTagName('audio');\n" +
                                "\tvar len = videos.length;\n" +
                                " window.LastPauseVideoFromApp;\n" +
                                "\tfor (var i = 0; i < len; i++) {\n" +
                                "      \tif(!videos[i].paused){\n" +
                                "        \twindow.LastPauseVideoFromApp=videos[i];\n" +
                                "        }\n" +
                                "\t}\n" +
                                "  \tvar len = audios.length;\n" +
                                "\tfor (var i = 0; i < len; i++) {\n" +
                                "\t \tif(!audios[i].paused){\n" +
                                "        \twindow.LastPauseVideoFromApp=videos[i];\n" +
                                "        }\n" +
                                "\t}\n" +
                                "  if(window.LastPauseVideoFromApp!=null){\n" +
                                "    window.LastPauseVideoFromApp.pause();\n" +
                                "  }\n" +
                                "})();\n"
        );
    }

    public static void resumeAudio (WebView webView){
        webView.loadUrl(
                "javascript:(function(){\n" +
                        " if(window.LastPauseVideoFromApp!=null){\n" +
                        " \twindow.LastPauseVideoFromApp.play();\n" +
                        "   window.LastPauseVideo=null;\n" +
                        " }\n" +
                        "})();"
        );
    }
}

观察者对接


package com.trs.myrb.fragment.common;

import com.tencent.smtt.sdk.WebView;
import com.trs.myrb.douyin.action.TRSFunction;

import java.util.Observable;
import java.util.Observer;

/**
 * <pre>
 * Created by zhuguohui
 * Date: 2024/2/6
 * Time: 9:57
 * Desc: 这类的作用是在fragment用户不可见的情况下自动暂停网页中的视频,在可见的状态下自动播放
 * </pre>
 */
public class AutoPauseVideoInWebObserver implements Observer {

    TRSFunction<Void,WebView> webViewGetter;

    public AutoPauseVideoInWebObserver(TRSFunction<Void, WebView> webViewGetter) {
        //使用回调来获取webView是为了防止webView没有创建,比如在fragment的onCrate的情况下
        this.webViewGetter = webViewGetter;
    }

    @Override
    public void update(Observable o, Object arg) {
        boolean showToUser= (boolean) arg;
        pauseOrResumeWebView(!showToUser);

    }

    boolean webViewIsPause = false;

    private void pauseOrResumeWebView(boolean pause) {
        WebView x5WebView = webViewGetter.call(null);
        //如果网页中有视频,可以暂停视频,或者恢复视频
        //比如从当前fragment切换到其他fragment避免再出现声音
        if (webViewGetter.call(null) == null) {
            return;
        }

        if (pause == webViewIsPause) {
            //状态相同
            return;
        }

        if (pause) {

            //通过js 暂停音频
            PauseWebVideoUtil.pauseAudio(x5WebView);
            x5WebView.onPause();
        } else {
            PauseWebVideoUtil.resumeAudio(x5WebView);
            x5WebView.onResume();
        }
        webViewIsPause = pause;
    }
}

使用

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //以下代码的作用是在fragment用户不可见的情况下自动暂停网页中的视频,在可见的状态下自动播放
        showStateMonitor.addObserver(new AutoPauseVideoInWebObserver(unused -> x5WebView));
    }

总结

实现起来不是很难,主要是把各个功能疏解到单独的类中。保证每个类的职责单一,提高逻辑的清晰性。增加维护的便利性。通过观察者模式还可以为后面的业务拓展提供支撑,增加了系统的弹性。这也是提高自己代码的设计性的尝试。

  • 11
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值