1. 程式人生 > >解決WebView視屏播放問題記錄

解決WebView視屏播放問題記錄

情景

專案中有一大板塊是載入web頁面,開始集成了騰訊的X5核心WebView(因為本身集成了視訊播放功能,使用起來比較方便)。但是後來前端大神寫了一個web頁面用到了<canvas>標籤,使用X5載入不出來該標籤的內容,因為我們設定了webView關閉了硬體加速( webview.setLayerType(View.LAYER_TYPE_SOFTWARE,null)),今日仔細查看了X5的官方文件才知道:X5是不建議使用以下兩個api的:


經過考慮,我們決定使用Android原生的WebView,然後自己處理播放網路視訊時的問題,簡單記錄過程中遇到的問題。

問題一:視訊黑屏

我遇到的視訊黑屏是:視訊播放時只有聲音無畫面的情況,原因就是前面我說的我在初始化的時候講webview的硬體加速關閉了,後來查資料說webView播放視訊必須開啟硬體加速:

在AndroidManifest.xml檔案中的application或者webView所在的activity標籤中開啟硬體加速:


然後在程式碼中:

getWindow().setFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, WindowManager

                .LayoutParams.FLAG_HARDWARE_ACCELERATED);

即可解決黑屏問題。

問題二:全屏問題

一般前端頁面視訊標籤使用的是video標籤,點選全屏按鈕時,會回撥webView的WebChromClient的onShowCustomView方法,點選退出全屏時會回撥webView的WebChromClient的onHideCustomView方法。所以實現全屏的邏輯我們就重寫這兩個方法即可:

為了在其它地方使用webView方便,我就把WebView封裝了一下,首先看CustomWebView的佈局檔案:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <WebView
        android:id="@+id/webview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scrollbars="none"/>

    <!--作為視訊全屏播放時的容器-->
    <FrameLayout
        android:id="@+id/frame_full_screen"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:visibility="gone"
        android:background="#000000"/>


</FrameLayout>

自定義CustomWebView的邏輯程式碼:

public class CustomWebView extends FrameLayout {
    private Context context;
    private WebView  webView;
    //全屏播放容器
    private FrameLayout fullScreenLayout;  

    public CustomWebView(Context context, AttributeSet arg1) {
        super(context, arg1);
        this.context = context;
        init(context);
    }

    private void init(Context context){
        LayoutInflater layoutInflater = LayoutInflater.from(context);
        View rootView = layoutInflater.inflate(R.layout.custom_web_layout,this,true);
        webView = (WebView) rootView.findViewById(R.id.webview);
        fullScreenLayout = (FrameLayout) rootView.findViewById(R.id.frame_full_screen);
        initWebViewSettings();
    }


    private void initWebViewSettings() {
        WebSettings s = webView.getSettings();
        s.setBuiltInZoomControls(true);
        s.setPluginState(WebSettings.PluginState.ON);
        s.setCacheMode(WebSettings.LOAD_DEFAULT);
        s.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NARROW_COLUMNS);
        s.setRenderPriority(WebSettings.RenderPriority.HIGH);
        s.setAppCacheEnabled(false);
        s.setJavaScriptCanOpenWindowsAutomatically(true);
        s.setUseWideViewPort(true);
        s.setLoadWithOverviewMode(true);
        s.setSavePassword(false);
        s.setSaveFormData(true);
        s.setJavaScriptEnabled(true);
        s.setLoadsImagesAutomatically(true);
        s.setSupportZoom(false);// ql
        s.setBuiltInZoomControls(false);
        s.setGeolocationEnabled(true);
        s.setGeolocationDatabasePath("http://www.cvbaoli.com/webak/public/showAgreement");
        s.setDomStorageEnabled(true);
        //如果大於5.0設定混合模式
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            s.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
        }
    }
	
    public WebView getWebView(){
        return webView;
    }

    public FrameLayout getFullScreenLayout(){
        return fullScreenLayout;
    }


}

CustomWebChromeClient繼承WebChromeClient

public abstract class CustomWebChromClient extends WebChromeClient {

    private FrameLayout fullScreenLayout;
    private View customView;
    private CustomViewCallback customCallback;
    private Context context;
    private WebView webView;

    public void setCustomWebView(@NotNull CustomWebView customWebView) {
        this.webView = customWebView.getWebView();
        this.fullScreenLayout = customWebView.getFullScreenLayout();
    }

    public void setContext(Context context) {
        this.context = context;
    }

    public CustomWebChromClient() {
    }

    @Override
    public void onShowCustomView(View view, CustomViewCallback callback) {
        super.onShowCustomView(view, callback);
        if (webView == null || fullScreenLayout == null) {
            init();
        }
        showCustomView(view, callback);
    }

    @Override
    public void onHideCustomView() {
        super.onHideCustomView();
        hideCustomView();
    }

    /**
     * 視訊播放全屏
     **/
    private void showCustomView(View view, CustomViewCallback callback) {
        // if a view already exists then immediately terminate the new one
        if (customView != null) {
            callback.onCustomViewHidden();
            return;
        }
        setStatusBarVisibility(false);
        fullScreenLayout.addView(view);
        customView = view;
        customCallback = callback;
        webView.setVisibility(View.GONE);
        fullScreenLayout.setVisibility(VISIBLE);
        fullScreenLayout.bringToFront();
        // 橫屏顯示
        ((Activity) context).setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
//        //設定全屏
//        ((Activity)context).getParent()
//                .getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
//               WindowManager.LayoutParams.FLAG_FULLSCREEN);
    }

    /**
     * 隱藏視訊全屏
     */
    public void hideCustomView() {
        if (customView == null) {
            return;
        }
        setStatusBarVisibility(true);
        customView.setVisibility(GONE);
        fullScreenLayout.removeAllViews();
        customView = null;
        fullScreenLayout.setVisibility(GONE);
        try {
            customCallback.onCustomViewHidden();
        } catch (Exception e) {
            e.printStackTrace();
        }
        webView.setVisibility(VISIBLE);
        //換成豎屏
        ((Activity) context).setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
    }

    private void setStatusBarVisibility(boolean visible) {
        int flag = visible ? 0 : WindowManager.LayoutParams.FLAG_FULLSCREEN;
        ((Activity) context).getWindow().setFlags(flag, WindowManager.LayoutParams.FLAG_FULLSCREEN);
        setBarVisible(visible);
    }


    protected abstract void setBarVisible(boolean visible);

    protected abstract void init();

    public boolean webIsFullScreen() {
        return customView != null;
    }

在Activity中使用

 customWebView = (CustomWebView) findViewById(R.id.customWebView);
 webview = customWebView.getWebView();
webview.setWebChromeClient(mWebChromeClient);
private CustomWebChromClient mWebChromeClient = new CustomWebChromClient() {

        @Override
        protected void setBarVisible(boolean visible) {//此方法設定全屏切換時標題欄等view的可見性,這個根據自己的需求自己實現即可
            if (titleBar==null){
                return;
            }
            titleBar.setVisibility(visible?View.VISIBLE:View.GONE);
        }

        @Override
        protected void init() {
            this.setCustomWebView(customWebView);
            this.setContext(WebviewActivity.this);
        }

    };

以上就是實現視訊播放全屏的邏輯。實測可用。

問題三:在某些機型上退至後臺時聲音不停止

經測試,在大部分機型上是ok的,但是在華為P系列手機上會有問題,當webView的宿主Activity或者Fragment不可見時,視訊聲音不能自動停止。重寫宿主的生命週期,加入以下程式碼解決此Bug:

 @Override
    public void onPause() {
        super.onPause();
        if (webview != null) {
            String videoJs = "javascript: var v = document.getElementsByTagName('video'); for(var i=0;i<v.length;i++){v[i].pause();} ";
            webview.loadUrl(videoJs);//遍歷所有的Vedio標籤,主動呼叫暫停方法
            webview.onPause();
            webview.pauseTimers();
        }
    }

    @Override
    public void onResume() {
        super.onResume();
        if (webview != null) {
            webview.resumeTimers();
            webview.onResume();
        }
    }
WebView在19即4.4之前使用的是WebKit核心,4.4及以後採用了Chrome核心,又由於Androidd的碎片化問題,導致國內Android手機使用WebView會遇到各種各樣的問題。以上便是我踩過的坑,希望能夠幫到遇到同樣問題的童鞋。