android 4.0以上WebView不能全屏播放視頻的解決辦法
上次鄙人做了一個(gè)簡(jiǎn)單的利用webView實(shí)現(xiàn)的一個(gè)瀏覽器!其中遇到了兩個(gè)問(wèn)題,一個(gè)是將瀏覽器中需要下載的內(nèi)容托管到系統(tǒng)默認(rèn)的下載程序進(jìn)行下載,這個(gè)比較簡(jiǎn)單就不在這里討論了;另一個(gè)問(wèn)題就是我們的Android設(shè)備版本是4.0.3,不能像Android2.3那樣支持全屏播放視頻,這個(gè)問(wèn)題比較糾結(jié),但是經(jīng)過(guò)不斷的摸索,終于解決了這個(gè)問(wèn)題。在這里和大家分享一下解決方法:
1、首先定義一個(gè)VideoEnabledWebView繼承自WebView,復(fù)寫(xiě)其中的loadData,loadDataWithBaseURL,loadUrl方法,道理很簡(jiǎn)單就是在加載url或者js的時(shí)候初始化一些內(nèi)容。見(jiàn)代碼:
- package com.danielme.android.webviewdemo;
- import java.util.Map;
- import android.annotation.SuppressLint;
- import android.content.Context;
- import android.os.Handler;
- import android.os.Looper;
- import android.util.AttributeSet;
- import android.webkit.WebChromeClient;
- import android.webkit.WebView;
- public class VideoEnabledWebView extends WebView
- {
- public interface ToggledFullscreenCallback
- {
- public void toggledFullscreen(boolean fullscreen);
- }
- private VideoEnabledWebChromeClient videoEnabledWebChromeClient;
- private boolean addedJavascriptInterface;
- public VideoEnabledWebView(Context context)
- {
- super(context);
- addedJavascriptInterface = false;
- }
- public VideoEnabledWebView(Context context, AttributeSet attrs)
- {
- super(context, attrs);
- addedJavascriptInterface = false;
- }
- public VideoEnabledWebView(Context context, AttributeSet attrs, int defStyle)
- {
- super(context, attrs, defStyle);
- addedJavascriptInterface = false;
- }
- /**
- * Pass only a VideoEnabledWebChromeClient instance.
- */
- @Override
- @SuppressLint ("SetJavaScriptEnabled")
- public void setWebChromeClient(WebChromeClient client)
- {
- getSettings().setJavaScriptEnabled(true);
- if (client instanceof VideoEnabledWebChromeClient)
- {
- this.videoEnabledWebChromeClient = (VideoEnabledWebChromeClient) client;
- }
- super.setWebChromeClient(client);
- }
- @Override
- public void loadData(String data, String mimeType, String encoding)
- {
- addJavascriptInterface();
- super.loadData(data, mimeType, encoding);
- }
- @Override
- public void loadDataWithBaseURL(String baseUrl, String data,
- String mimeType, String encoding,
- String historyUrl)
- {
- addJavascriptInterface();
- super.loadDataWithBaseURL(baseUrl, data, mimeType, encoding, historyUrl);
- }
- @Override
- public void loadUrl(String url)
- {
- addJavascriptInterface();
- super.loadUrl(url);
- }
- @Override
- public void loadUrl(String url, Map<String, String> additionalHttpHeaders)
- {
- addJavascriptInterface();
- super.loadUrl(url, additionalHttpHeaders);
- }
- private void addJavascriptInterface()
- {
- System.out.println(addedJavascriptInterface);
- if (!addedJavascriptInterface)
- {
- // Add javascript interface to be called when the video ends (must be done before page load)
- addJavascriptInterface(new Object()
- {
- }, "_VideoEnabledWebView"); // Must match Javascript interface name of VideoEnabledWebChromeClient
- addedJavascriptInterface = true;
- }
- }
- }
其中addJavascriptInterface方法是將一個(gè)當(dāng)前的java對(duì)象綁定到一個(gè)javascript上面,使用如下方法
webv.addJavascriptInterface(this, "_VideoEnabledWebView");//this為當(dāng)前對(duì)象,綁定到j(luò)s的_VideoEnabledWebView上面,主要_VideoEnabledWebView的作用域是全局的。這個(gè)部分的內(nèi)容我不是很懂,提供鏈接給大家學(xué)習(xí)下,希望看懂的朋友能教教這個(gè)步驟是干嘛的?。?a rel="nofollow" target="_blank">http://www.oschina.net/code/snippet_232612_8531)
2、定義一個(gè)類VideoEnabledWebChromeClient繼承自WebChromeClient,這個(gè)WebChromeClient中的onShowCustomView方法就是播放網(wǎng)絡(luò)視頻時(shí)會(huì)被調(diào)用的方法,onHideCustomView方法就是視頻播放完成會(huì)被調(diào)用的。其中有個(gè)構(gòu)造函數(shù)需要提出來(lái):
- public VideoEnabledWebChromeClient(View activityNonVideoView, ViewGroup activityVideoView, View loadingView, VideoEnabledWebView webView)
- {
- this.activityNonVideoView = activityNonVideoView;
- this.activityVideoView = activityVideoView;
- this.loadingView = loadingView;
- this.webView = webView;
- this.isVideoFullscreen = false;
- }
這個(gè)構(gòu)造函數(shù)中的參數(shù),***個(gè)是webView的父布局,activityVideoView是另外的一個(gè)占滿整個(gè)屏幕的布局,loadingView是播放器的那個(gè)顯示緩沖狀態(tài)的view,webView就是webView啦!
見(jiàn)activity_main.xml
- <?xml version="1.0" encoding="utf-8"?>
- <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- tools:context=".MainActivity" >
- <RelativeLayout
- android:id="@+id/nonVideoLayout"
- android:layout_width="match_parent"
- android:layout_height="match_parent" >
- <com.danielme.android.webviewdemo.VideoEnabledWebView
- android:id="@+id/webView"
- android:layout_width="match_parent"
- android:layout_height="match_parent" />
- </RelativeLayout>
- <FrameLayout
- android:id="@+id/videoLayout"
- android:layout_width="match_parent"
- android:layout_height="match_parent" >
- </FrameLayout>
- </RelativeLayout>
不多說(shuō)了,直接貼代碼VideoEnabledWebChromeClient.java代碼。
- package com.danielme.android.webviewdemo;
- import android.app.ActionBar.LayoutParams;
- import android.media.MediaPlayer;
- import android.media.MediaPlayer.OnCompletionListener;
- import android.media.MediaPlayer.OnErrorListener;
- import android.media.MediaPlayer.OnPreparedListener;
- import android.view.View;
- import android.view.ViewGroup;
- import android.webkit.WebChromeClient;
- import android.widget.FrameLayout;
- import android.widget.VideoView;
- public class VideoEnabledWebChromeClient extends WebChromeClient implements OnPreparedListener, OnCompletionListener, OnErrorListener
- {
- public interface ToggledFullscreenCallback
- {
- public void toggledFullscreen(boolean fullscreen);
- }
- private View activityNonVideoView;
- private ViewGroup activityVideoView;
- private View loadingView;
- private VideoEnabledWebView webView;
- private boolean isVideoFullscreen; // Indicates if the video is being displayed using a custom view (typically full-screen)
- private FrameLayout videoViewContainer;
- private CustomViewCallback videoViewCallback;
- private ToggledFullscreenCallback toggledFullscreenCallback;
- /**
- * Never use this constructor alone.
- * This constructor allows this class to be defined as an inline inner class in which the user can override methods
- */
- public VideoEnabledWebChromeClient()
- {
- }
- /**
- * Builds a video enabled WebChromeClient.
- * @param activityNonVideoView A View in the activity's layout that contains every other view that should be hidden when the video goes full-screen.
- * @param activityVideoView A ViewGroup in the activity's layout that will display the video. Typically you would like this to fill the whole layout.
- */
- public VideoEnabledWebChromeClient(View activityNonVideoView, ViewGroup activityVideoView)
- {
- this.activityNonVideoView = activityNonVideoView;
- this.activityVideoView = activityVideoView;
- this.loadingView = null;
- this.webView = null;
- this.isVideoFullscreen = false;
- }
- /**
- * Builds a video enabled WebChromeClient.
- * @param activityNonVideoView A View in the activity's layout that contains every other view that should be hidden when the video goes full-screen.
- * @param activityVideoView A ViewGroup in the activity's layout that will display the video. Typically you would like this to fill the whole layout.
- * @param loadingView A View to be shown while the video is loading (typically only used in API level <11). Must be already inflated and without a parent view.
- */
- public VideoEnabledWebChromeClient(View activityNonVideoView, ViewGroup activityVideoView, View loadingView)
- {
- this.activityNonVideoView = activityNonVideoView;
- this.activityVideoView = activityVideoView;
- this.loadingView = loadingView;
- this.webView = null;
- this.isVideoFullscreen = false;
- }
- /**
- * Builds a video enabled WebChromeClient.
- * @param activityNonVideoView A View in the activity's layout that contains every other view that should be hidden when the video goes full-screen.
- * @param activityVideoView A ViewGroup in the activity's layout that will display the video. Typically you would like this to fill the whole layout.
- * @param loadingView A View to be shown while the video is loading (typically only used in API level <11). Must be already inflated and without a parent view.
- * @param webView The owner VideoEnabledWebView. Passing it will enable the VideoEnabledWebChromeClient to detect the HTML5 video ended event and exit full-screen.
- * Note: The web page must only contain one video tag in order for the HTML5 video ended event to work. This could be improved if needed (see Javascript code).
- */
- public VideoEnabledWebChromeClient(View activityNonVideoView, ViewGroup activityVideoView, View loadingView, VideoEnabledWebView webView)
- {
- this.activityNonVideoView = activityNonVideoView;
- this.activityVideoView = activityVideoView;
- this.loadingView = loadingView;
- this.webView = webView;
- this.isVideoFullscreen = false;
- }
- /**
- * Indicates if the video is being displayed using a custom view (typically full-screen)
- * @return true it the video is being displayed using a custom view (typically full-screen)
- */
- public boolean isVideoFullscreen()
- {
- return isVideoFullscreen;
- }
- /**
- * Set a callback that will be fired when the video starts or finishes displaying using a custom view (typically full-screen)
- * @param callback A VideoEnabledWebChromeClient.ToggledFullscreenCallback callback
- */
- public void setOnToggledFullscreen(ToggledFullscreenCallback callback)
- {
- this.toggledFullscreenCallback = callback;
- }
- @Override
- public void onShowCustomView(View view, CustomViewCallback callback)
- {
- if (view instanceof FrameLayout)
- {
- // A video wants to be shown
- FrameLayout frameLayout = (FrameLayout) view;
- View focusedChild = frameLayout.getFocusedChild();
- // Save video related variables
- this.isVideoFullscreen = true;
- this.videoViewContainer = frameLayout;
- this.videoViewCallback = callback;
- // Hide the non-video view, add the video view, and show it
- activityNonVideoView.setVisibility(View.GONE);
- activityVideoView.addView(videoViewContainer, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
- activityVideoView.setVisibility(View.VISIBLE);
- if (focusedChild instanceof VideoView)
- {
- // VideoView (typically API level <11)
- VideoView videoView = (VideoView) focusedChild;
- // Handle all the required events
- videoView.setOnPreparedListener(this);
- videoView.setOnCompletionListener(this);
- videoView.setOnErrorListener(this);
- }
- else // Usually android.webkit.HTML5VideoFullScreen$VideoSurfaceView, sometimes android.webkit.HTML5VideoFullScreen$VideoTextureView
- {
- // HTML5VideoFullScreen (typically API level 11+)
- // Handle HTML5 video ended event
- if (webView != null && webView.getSettings().getJavaScriptEnabled())
- {
- // Run javascript code that detects the video end and notifies the interface
- String js = "javascript:";
- js += "_ytrp_html5_video = document.getElementsByTagName('video')[0];";
- js += "if (_ytrp_html5_video !== undefined) {";
- {
- js += "function _ytrp_html5_video_ended() {";
- {
- js += "_ytrp_html5_video.removeEventListener('ended', _ytrp_html5_video_ended);";
- js += "_VideoEnabledWebView.notifyVideoEnd();"; // Must match Javascript interface name and method of VideoEnableWebView
- }
- js += "}";
- js += "_ytrp_html5_video.addEventListener('ended', _ytrp_html5_video_ended);";
- }
- js += "}";
- webView.loadUrl(js);
- }
- }
- // Notify full-screen change
- if (toggledFullscreenCallback != null)
- {
- toggledFullscreenCallback.toggledFullscreen(true);
- }
- }
- }
- @Override
- public void onShowCustomView(View view, int requestedOrientation, CustomViewCallback callback) // Only available in API level 14+
- {
- onShowCustomView(view, callback);
- }
- @Override
- public void onHideCustomView()
- {
- // This method must be manually (internally) called on video end in the case of VideoView (typically API level <11)
- // This method must be manually (internally) called on video end in the case of HTML5VideoFullScreen (typically API level 11+) because it's not always called automatically
- // This method must be manually (internally) called on back key press (from this class' onBackPressed() method)
- if (isVideoFullscreen)
- {
- // Hide the video view, remove it, and show the non-video view
- activityVideoView.setVisibility(View.GONE);//播放視頻的
- activityVideoView.removeView(videoViewContainer);
- activityNonVideoView.setVisibility(View.VISIBLE);
- // Call back
- if (videoViewCallback != null) videoViewCallback.onCustomViewHidden();
- // Reset video related variables
- isVideoFullscreen = false;
- videoViewContainer = null;
- videoViewCallback = null;
- // Notify full-screen change
- if (toggledFullscreenCallback != null)
- {
- toggledFullscreenCallback.toggledFullscreen(false);
- }
- }
- }
- @Override
- public View getVideoLoadingProgressView() // Video will start loading, only called in the case of VideoView (typically API level <11)
- {
- if (loadingView != null)
- {
- loadingView.setVisibility(View.VISIBLE);
- return loadingView;
- }
- else
- {
- return super.getVideoLoadingProgressView();
- }
- }
- @Override
- public void onPrepared(MediaPlayer mp) // Video will start playing, only called in the case of VideoView (typically API level <11)
- {
- if (loadingView != null)
- {
- loadingView.setVisibility(View.GONE);
- }
- }
- @Override
- public void onCompletion(MediaPlayer mp) // Video finished playing, only called in the case of VideoView (typically API level <11)
- {
- onHideCustomView();
- }
- @Override
- public boolean onError(MediaPlayer mp, int what, int extra) // Error while playing video, only called in the case of VideoView (typically API level <11)
- {
- return false; // By returning false, onCompletion() will be called
- }
- /**
- * Notifies the class that the back key has been pressed by the user.
- * This must be called from the Activity's onBackPressed(), and if it returns false, the activity itself should handle it. Otherwise don't do anything.
- * @return Returns true if the event was handled, and false if it is not (video view is not visible)
- */
- public boolean onBackPressed()
- {
- if (isVideoFullscreen)
- {
- onHideCustomView();
- return true;
- }
- else
- {
- return false;
- }
- }
- }
主要是onShowCustomView方法中,當(dāng)這個(gè)方法被調(diào)用,將含有webView的那個(gè)父布局隱藏掉(GONE),然后將***個(gè)參數(shù)view加到布局中。獲取***個(gè)參數(shù)view的子控件childView,進(jìn)行判斷childView是否屬于VideoView(Android 4.0之前是VideoView),如果是Android 4.0之后,則會(huì)執(zhí)行else中的代碼,新建String類型js代碼,然后調(diào)用loadUrl(js)就可以進(jìn)行視頻播放了。其中我個(gè)人不知道它是如何通過(guò)js來(lái)播放視頻的,我覺(jué)得和之前的addJavascriptInterface這個(gè)方法有一定關(guān)系,希望知道如何實(shí)現(xiàn)的能夠指導(dǎo)一下本人。其它的函數(shù)就很好理解了。
其中多說(shuō)一句,Android 4.0之前的那個(gè)***個(gè)參數(shù)view是videoView,Android 4.0之后是那個(gè)HTML5VideoFullScreen$VideoSurfaceView