一聚教程网:一个值得你收藏的教程网站

最新下载

热门教程

Android自定义WebView网络视频播放控件例子

时间:2016-08-01 编辑:简简单单 来源:一聚教程网

因为业务需要,以下代码均以Youtube网站在线视频为例

实现功能:

1、初始化的时候显示标题和视频封面

2、初始化的时候显示一个play按钮

3、不需要依赖任何SDK,或者导入任何第三方库

4、播放过程中可以暂停,可以拖动进度条快进

5、可以全屏播放

6、切换页面的时候会自动暂停

7、页面退出的时候自动销毁WebView

8、不需要申请任何开发者账号或者获取授权


原理:

首先需要一个继承WebView的自定义控件,这里起名叫做YoutubePlayerView,在页面初始化的时候用这个WebView去加载一个事先写好的HTML,当然在加载之前,需要把Youtube的视频id和一些播放参数设置进去。然后一个小的播放窗口就完成了,此时已经完成用户点击play按钮就播放的功能。

但是光能播放还不行,我们还需要捕捉用户的点击事件,比如播放,暂停等等操作,而这些操作本身写在Youtube的JS代码中(Youtube已经把JS调用相关代码的位置预留好,就等着开发者来复写相关的代码了),需要在JS代码中调用java代码,这样就需要有一个JS调用java的接口,这里起名叫QualsonBridge,通过使用WebVIew的addJavascriptInterface()方法将Java代码的接口设置进去,并且需要一个接口实现类,实现的方法名称方法要和JS接口规定的方法一模一样,以便反射调用,一会会把详细的代码贴出来。

完成以上两点,就已经完成了播放,暂停等操作,但是还需要在Activity退出或者被覆盖的时候暂停WebView的播放,所以还需要给这个WebView写一个onDestroy的方法,并在fragment的onDestroy中调用,里面执行的主要就是清楚缓存的操作,还需要WebView写一个onPause的方法,在fragment的onPause中调用,里面主要执行JS代码:javascript:onVideoPause()

关于全屏播放:是通过一个自定义的WebChromeClient来实现将WebView扩大到全屏并修改旋转角度进行播放

代码实现:

首先需要让WebView去加载一块HTML,这段HTML是从Youtube的官方SDK中抽取出来的,本质上是一个HTML编写的小播放窗口,代码如下

 

 代码如下 复制代码











 




项目中把这一段代码放到了raw文件夹下,并通过下面这样一个方法去加载,并在加载的同时,把上面预留的一些参数比如视频id什么的给补上

 

 代码如下 复制代码

/**
     * 自己写一段HTML,并设置好Youtube的视频id,放到WebView中进行显示
     * @param videoId
     * @return
     */
    private String getVideoHTML(String videoId) {
        try {
            InputStream in = getResources().openRawResource(R.raw.players);
            if (in != null) {
                InputStreamReader stream = new InputStreamReader(in, "utf-8");
                BufferedReader buffer = new BufferedReader(stream);
                String read;
                StringBuilder sb = new StringBuilder("");

                while ((read = buffer.readLine()) != null) {
                    sb.append(read + "\n");
                }

                in.close();

                String html = sb.toString().replace("[VIDEO_ID]", videoId).replace("[BG_COLOR]", backgroundColor);
                html = html.replace("[AUTO_PLAY]", String.valueOf(params.getAutoplay())).replace("[AUTO_HIDE]", String.valueOf(params.getAutohide())).replace("[REL]", String.valueOf(params.getRel())).replace("[SHOW_INFO]", String.valueOf(params.getShowinfo())).replace("[ENABLE_JS_API]", String.valueOf(params.getEnablejsapi())).replace("[DISABLE_KB]", String.valueOf(params.getDisablekb())).replace("[CC_LANG_PREF]", String.valueOf(params.getCc_lang_pref())).replace("[CONTROLS]", String.valueOf(params.getControls())).replace("[FS]", String.valueOf(params.getFs()));
                return html;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "";
    }这里面传来的videoId一般是从Youtube视频分享url中用正则解析出来的,比如:https://youtu.be/DdRwiH4mR0Q

DdRwiH4mR0Q就是videoId


还需要一个给JS代码调用java代码的接口,要复写上面html中events中和function中的所有方法,如下


/**
     * WEB TO APP Javascript的安卓接口,用于在安卓上部署JS代码,这里是将JS回调到Android中,让JS触发Java代码
     * 需要在JS代码合适地方调用这里面的方法,在js中有一个函数如下:
     * function onPlayerStateChange(event)
     * 和这样一个函数
     * function onStateChange(e){
            window.QualsonInterface.onStateChange(e);//用于回调java代码
        }
     并且这个需要在java代码中使用 this.addJavascriptInterface(bridge, "QualsonInterface");来注册
     */
    private class QualsonBridge {

        @JavascriptInterface
        public void onReady(String arg) {
            JLogUtils.d(TAG, "onReady(" + arg + ")");
            if (youTubeListener != null) {
                youTubeListener.onReady();
            }
        }

        @JavascriptInterface
        public void onStateChange(String arg) {
            JLogUtils.d(TAG, "onStateChange(" + arg + ")");
            if ("UNSTARTED".equalsIgnoreCase(arg)) {
                notifyStateChange(STATE.UNSTARTED);
            } else if ("ENDED".equalsIgnoreCase(arg)) {
                notifyStateChange(STATE.ENDED);
            } else if ("PLAYING".equalsIgnoreCase(arg)) {
                notifyStateChange(STATE.PLAYING);
            } else if ("PAUSED".equalsIgnoreCase(arg)) {
                notifyStateChange(STATE.PAUSED);
            } else if ("BUFFERING".equalsIgnoreCase(arg)) {
                notifyStateChange(STATE.BUFFERING);
            } else if ("CUED".equalsIgnoreCase(arg)) {
                notifyStateChange(STATE.CUED);
            }
        }

        @JavascriptInterface
        public void onPlaybackQualityChange(String arg) {
            JLogUtils.d(TAG, "onPlaybackQualityChange(" + arg + ")");
            if (youTubeListener != null) {
                youTubeListener.onPlaybackQualityChange(arg);
            }
        }

        @JavascriptInterface
        public void onPlaybackRateChange(String arg) {
            JLogUtils.d(TAG, "onPlaybackRateChange(" + arg + ")");
            if (youTubeListener != null) {
                youTubeListener.onPlaybackRateChange(arg);
            }
        }

        @JavascriptInterface
        public void onError(String arg) {
            JLogUtils.e(TAG, "onError(" + arg + ")");
            if (youTubeListener != null) {
                youTubeListener.onError(arg);
            }
        }

        @JavascriptInterface
        public void onApiChange(String arg) {
            JLogUtils.d(TAG, "onApiChange(" + arg + ")");
            if (youTubeListener != null) {
                youTubeListener.onApiChange(arg);
            }
        }

        @JavascriptInterface
        public void currentSeconds(String seconds) {
            if (youTubeListener != null) {
                youTubeListener.onCurrentSecond(Double.parseDouble(seconds));
            }
        }

        @JavascriptInterface
        public void duration(String seconds) {
            if (youTubeListener != null) {
                youTubeListener.onDuration(Double.parseDouble(seconds));
            }
        }

        @JavascriptInterface
        public void logs(String arg) {
            JLogUtils.d(TAG, "logs(" + arg + ")");
            if (youTubeListener != null) {
                youTubeListener.logs(arg);
            }
        }
    }


向js注册这个JAVA接口使用WebView的addJavascriptInterface(bridge, "QualsonInterface");方法

这里面的youTubeListener是自己写的一个接口类,用于方便回调UI方法的


下面贴出这个自定义WebView的完整代码:

 代码如下 复制代码

package com.imaginato.qravedconsumer.widget.player;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.View;
import android.webkit.JavascriptInterface;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;

import com.qraved.app.R;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.ref.WeakReference;
import java.lang.reflect.Field;

public class YoutubePlayerView extends WebView {

    private static final String TAG = YoutubePlayerView.class.getSimpleName();

    private QualsonBridge bridge = new QualsonBridge();

    private YTParams params = new YTParams();

    private YouTubeListener youTubeListener;
    private String backgroundColor = "#000000";
    private STATE mPlayState = STATE.UNSTARTED;

    public YoutubePlayerView(Context context) {
        super(context);
        setWebViewClient(new MyWebViewClient((Activity) context));
    }

    public YoutubePlayerView(Context context, AttributeSet attrs) {
        super(context, attrs);
        setWebViewClient(new MyWebViewClient((Activity) context));
    }

    @SuppressLint("JavascriptInterface")
    public void initialize(String videoId, YouTubeListener youTubeListener, WebChromeClient webChromeClient) {
        WebSettings set = this.getSettings();
        set.setJavaScriptEnabled(true);
        set.setUseWideViewPort(true);
        set.setLoadWithOverviewMode(true);
        set.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NORMAL);
        set.setCacheMode(WebSettings.LOAD_NO_CACHE);
        set.setPluginState(WebSettings.PluginState.ON);
        set.setPluginState(WebSettings.PluginState.ON_DEMAND);
        set.setAllowContentAccess(true);
        set.setAllowFileAccess(true);

        if (webChromeClient != null) {
            this.setWebChromeClient(webChromeClient);
        }

        this.mPlayState = STATE.UNSTARTED;
        this.youTubeListener = youTubeListener;
        this.setLayerType(View.LAYER_TYPE_NONE, null);
        this.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
        this.addJavascriptInterface(bridge, "QualsonInterface");//注册js代码调用java代码的接口
        this.loadDataWithBaseURL("https://www.youtube.com", getVideoHTML(videoId), "text/html", "utf-8", null);
        this.setLongClickable(true);
        this.setOnLongClickListener(new OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                return true;
            }
        });

    }

    public void initialize(String videoId, YTParams params, YouTubeListener youTubeListener, WebChromeClient webChromeClient) {
        if (params != null) {
            this.params = params;
        }
        initialize(videoId, youTubeListener, webChromeClient);
    }

    public void setWhiteBackgroundColor() {
        backgroundColor = "#ffffff";
    }

    public void setAutoPlayerHeight(Context context) {
        DisplayMetrics displayMetrics = new DisplayMetrics();
        ((Activity) context).getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
        this.getLayoutParams().height = (int) (displayMetrics.widthPixels * 0.5625);
    }

    /**
     * 让WebView去执行JS代码javascript:onVideoPause(),来暂停视频
     */
    public void pause() {
        Log.d(TAG, "pause");
        this.loadUrl("javascript:onVideoPause()");
    }
    /**
     * 让WebView去执行JS代码,来停止视频
     */
    public void stop(){
        Log.d(TAG,"stop");
        this.loadUrl("javascript:onVideoStop()");
    }

    public STATE getPlayerState(){
        Log.d(TAG,"getPlayerState");
        return mPlayState;
    }

    public void play() {
        Log.d(TAG, "play");
        this.loadUrl("javascript:onVideoPlay()");
    }

    private void notifyStateChange(STATE state){
        if(youTubeListener!=null){
            youTubeListener.onStateChange(state);
        }
        this.mPlayState = state;
    }

    /**
     * WEB TO APP Javascript的安卓接口,用于在安卓上部署JS代码,这里是将JS回调到Android中,让JS触发Java代码
     * 需要在JS代码合适地方调用这里面的方法,在js中有一个函数如下:
     * function onPlayerStateChange(event)
     * 和这样一个函数
     * function onStateChange(e){
            window.QualsonInterface.onStateChange(e);//用于回调java代码
        }
     并且这个需要在java代码中使用 this.addJavascriptInterface(bridge, "QualsonInterface");来注册
     */
    private class QualsonBridge {

        @JavascriptInterface
        public void onReady(String arg) {
            Log.d(TAG, "onReady(" + arg + ")");
            if (youTubeListener != null) {
                youTubeListener.onReady();
            }
        }

        @JavascriptInterface
        public void onStateChange(String arg) {
            Log.d(TAG, "onStateChange(" + arg + ")");
            if ("UNSTARTED".equalsIgnoreCase(arg)) {
                notifyStateChange(STATE.UNSTARTED);
            } else if ("ENDED".equalsIgnoreCase(arg)) {
                notifyStateChange(STATE.ENDED);
            } else if ("PLAYING".equalsIgnoreCase(arg)) {
                notifyStateChange(STATE.PLAYING);
            } else if ("PAUSED".equalsIgnoreCase(arg)) {
                notifyStateChange(STATE.PAUSED);
            } else if ("BUFFERING".equalsIgnoreCase(arg)) {
                notifyStateChange(STATE.BUFFERING);
            } else if ("CUED".equalsIgnoreCase(arg)) {
                notifyStateChange(STATE.CUED);
            }
        }

        @JavascriptInterface
        public void onPlaybackQualityChange(String arg) {
            Log.d(TAG, "onPlaybackQualityChange(" + arg + ")");
            if (youTubeListener != null) {
                youTubeListener.onPlaybackQualityChange(arg);
            }
        }

        @JavascriptInterface
        public void onPlaybackRateChange(String arg) {
            Log.d(TAG, "onPlaybackRateChange(" + arg + ")");
            if (youTubeListener != null) {
                youTubeListener.onPlaybackRateChange(arg);
            }
        }

        @JavascriptInterface
        public void onError(String arg) {
            Log.e(TAG, "onError(" + arg + ")");
            if (youTubeListener != null) {
                youTubeListener.onError(arg);
            }
        }

        @JavascriptInterface
        public void onApiChange(String arg) {
            Log.d(TAG, "onApiChange(" + arg + ")");
            if (youTubeListener != null) {
                youTubeListener.onApiChange(arg);
            }
        }

        @JavascriptInterface
        public void currentSeconds(String seconds) {
            if (youTubeListener != null) {
                youTubeListener.onCurrentSecond(Double.parseDouble(seconds));
            }
        }

        @JavascriptInterface
        public void duration(String seconds) {
            if (youTubeListener != null) {
                youTubeListener.onDuration(Double.parseDouble(seconds));
            }
        }

        @JavascriptInterface
        public void logs(String arg) {
            Log.d(TAG, "logs(" + arg + ")");
            if (youTubeListener != null) {
                youTubeListener.logs(arg);
            }
        }
    }


    /**
     * NonLeakingWebView
     */
    private static Field sConfigCallback;

    static {
        try {
            sConfigCallback = Class.forName("android.webkit.BrowserFrame").getDeclaredField("sConfigCallback");
            sConfigCallback.setAccessible(true);
        } catch (Exception e) {
            // ignored
        }
    }

    public void onDestroy() {
        super.onDetachedFromWindow();
        // View is now detached, and about to be destroyed
        youTubeListener = null;
        this.clearCache(true);
        this.clearHistory();
        try {
            if (sConfigCallback != null)
                sConfigCallback.set(null, null);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private class MyWebViewClient extends WebViewClient {
        protected WeakReference activityRef;

        public MyWebViewClient(Activity activity) {
            this.activityRef = new WeakReference(activity);
        }

        @Override
        public boolean shouldOverrideUrlLoading(WebView view, String url) {
            try {
                final Activity activity = activityRef.get();
                if (activity != null)
                    activity.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
            } catch (RuntimeException ignored) {
                // ignore any url parsing exceptions
            }
            return true;
        }

        @Override
        public void onPageFinished(WebView view, String url) {
            super.onPageFinished(view, url);
            Log.d(TAG, "onPageFinished()");
        }
    }

    public interface YouTubeListener {
        void onReady();//可以显示播放按钮进行播放

        void onStateChange(STATE state);//暂停等等状态

        void onPlaybackQualityChange(String arg);//清晰度改变

        void onPlaybackRateChange(String arg);

        void onError(String arg);

        void onApiChange(String arg);

        void onCurrentSecond(double second);

        void onDuration(double duration);

        void logs(String log);
    }

    public enum STATE {
        UNSTARTED,
        ENDED,
        PLAYING,
        PAUSED,
        BUFFERING,
        CUED,
        NONE
    }

    /**
     * 自己写一段HTML,并设置好Youtube的视频id,放到WebView中进行显示
     * @param videoId
     * @return
     */
    private String getVideoHTML(String videoId) {
        try {
            InputStream in = getResources().openRawResource(R.raw.players);
            if (in != null) {
                InputStreamReader stream = new InputStreamReader(in, "utf-8");
                BufferedReader buffer = new BufferedReader(stream);
                String read;
                StringBuilder sb = new StringBuilder("");

                while ((read = buffer.readLine()) != null) {
                    sb.append(read + "\n");
                }

                in.close();

                String html = sb.toString().replace("[VIDEO_ID]", videoId).replace("[BG_COLOR]", backgroundColor);
                html = html.replace("[AUTO_PLAY]", String.valueOf(params.getAutoplay())).replace("[AUTO_HIDE]", String.valueOf(params.getAutohide())).replace("[REL]", String.valueOf(params.getRel())).replace("[SHOW_INFO]", String.valueOf(params.getShowinfo())).replace("[ENABLE_JS_API]", String.valueOf(params.getEnablejsapi())).replace("[DISABLE_KB]", String.valueOf(params.getDisablekb())).replace("[CC_LANG_PREF]", String.valueOf(params.getCc_lang_pref())).replace("[CONTROLS]", String.valueOf(params.getControls())).replace("[FS]", String.valueOf(params.getFs()));
                return html;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "";
    }
}

在Fragment中初始化的代码

 

View youtubeView = LayoutInflater.from(journalActivity).inflate(R.layout.layout_youtube_player, null);
        YoutubePlayerView youtubePlayerView = (YoutubePlayerView) youtubeView.findViewById(R.id.youtubePlayerView);
        youtubePlayerView.setAutoPlayerHeight(journalActivity);
        youtubePlayerView.initialize(videoID, new YoutubePlayerCallBack(youtubePlayerView), mWebChromeClient);
        ll_journal.addView(youtubeView,ll_journal.getChildCount()-1);
上面提到的布局文件R.layout.layout_youtube_player如下



             android:layout_
             android:layout_
             android:orientation="vertical">

            android:id="@+id/youtubePlayerView"
        android:layout_
        android:layout_
        android:layout_marginLeft="15dp"
        android:layout_marginRight="15dp"
        android:layout_marginTop="10dp"/>

            android:layout_
        android:layout_
        android:layout_gravity="top"
        android:clickable="true"
        android:layout_marginLeft="15dp"
        android:layout_marginRight="15dp"
        android:layout_marginTop="10dp">
   


上面提到的WebChromeClient定义如下,用于控制全屏播放的


private WebChromeClient mWebChromeClient = new WebChromeClient(){

        @Override
        public View getVideoLoadingProgressView() {
                LayoutInflater inflater = LayoutInflater.from(activity);
                mVideoProgressView = inflater.inflate(R.layout.video_layout_loading, null);
           
            return mVideoProgressView;
        }

        @Override
        public void onShowCustomView(View view,
                                     WebChromeClient.CustomViewCallback callback) {
            // if a view already exists then immediately terminate the new one
            if(journalActivity==null){
                return;
            }
            if (mCustomView != null) {
                onHideCustomView();
                return;
            }

            // 1. Stash the current state
            mCustomView = view;
            mOriginalSystemUiVisibility = journalActivity.getWindow().getDecorView().getSystemUiVisibility();
            mOriginalOrientation = journalActivity.getRequestedOrientation();

            // 2. Stash the custom view callback
            mCustomViewCallback = callback;

            // 3. Add the custom view to the view hierarchy
            FrameLayout decor = (FrameLayout) journalActivity.getWindow().getDecorView();
            decor.addView(mCustomView, new FrameLayout.LayoutParams(
                    ViewGroup.LayoutParams.MATCH_PARENT,
                    ViewGroup.LayoutParams.MATCH_PARENT));
            if(mVideoFullScreenBack!=null){
                mVideoFullScreenBack.setVisibility(View.VISIBLE);
            }

            // 4. Change the state of the window
            activity.getWindow().getDecorView().setSystemUiVisibility(
                    View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
                            View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
                            View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
                            View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
                            View.SYSTEM_UI_FLAG_FULLSCREEN |
                            View.SYSTEM_UI_FLAG_IMMERSIVE);
            activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
        }

        @Override
        public void onHideCustomView() {
            if (journalActivity == null) {
                return;
            }
            // 1. Remove the custom view
            FrameLayout decor = (FrameLayout) journalActivity.getWindow().getDecorView();
            decor.removeView(mCustomView);
            mCustomView = null;
            if(mVideoFullScreenBack!=null){
                mVideoFullScreenBack.setVisibility(View.GONE);
            }

            // 2. Restore the state to it's original form
            journalActivity.getWindow().getDecorView()
                    .setSystemUiVisibility(mOriginalSystemUiVisibility);
            journalActivity.setRequestedOrientation(mOriginalOrientation);

            // 3. Call the custom view callback
            if(mCustomViewCallback!=null){
                mCustomViewCallback.onCustomViewHidden();
                mCustomViewCallback = null;
            }

        }
    };

上面提到的R.layout.view_layout_loading布局文件如下,仅仅是一个progressBar当占位符用的



             android:layout_
             android:layout_
             android:gravity="center"
             android:orientation="vertical">

            android:id="@android:id/progress"
        style="?android:attr/progressBarStyleLarge"
        android:layout_
        android:layout_
        android:layout_gravity="center"/>


从url中抽取VideoId的方法如下


 private String parseIDfromVideoUrl(String videoUrl){
       
        int startIndex = videoUrl.indexOf(VIDEO_ID_START);
        if(startIndex != -1){
            startIndex = startIndex + VIDEO_ID_START.length();
            int endIndex = videoUrl.indexOf("?");
            if(endIndex == -1){
                endIndex = videoUrl.length();
            }
            if(startIndex < endIndex){
                return videoUrl.substring(startIndex,endIndex);
            }
        }
        return "";
    }
因为本项目在同一个fragment中放了好多的这样的视频播放控件,所以为了统一他们暂停,销毁操作,这里使用了一个ArrayList进行维护

当切换到其他fragment或者有新的Activity压到上面的时候暂停WebView的播放,fragment总的onPause方法这么写:


@Override
    public void onPause() {
        if(playerViewList!=null){
            for(YoutubePlayerView v : playerViewList){
                if(v.getPlayerState() == YoutubePlayerView.STATE.PLAYING ){
                    v.pause();
                }else if(v.getPlayerState() == YoutubePlayerView.STATE.BUFFERING){
                    v.stop();
                }
            }
        }
        super.onPause();
    }还需要让fragment在销毁的时候释放WebView的资源如下:


@Override
    public void onDestroy() {
        super.onDestroy();

        if(playerViewList!=null){
            for(YoutubePlayerView v : playerViewList){
                if(v!=null){
                    v.onDestroy();
                }
            }
        }
}
在按下返回按钮时关闭全屏显示的代码


 @Override
    public void onBackPressed() {
      
                        boolean isClose = currentJournalFragment.closeFullScreen();
                        if(isClose){
                  &nbs