WebView and HTML5 <video>

AndroidHtmlVideoWebviewAndroid Webview

Android Problem Overview


I'm piecing together a cheapo app that amongst other things "frames" some of our websites... Pretty simple with the WebViewClient. until I hit the video.

The video is done as HTML5

Now the rub: WebView doesn't like it. At all. I can click on the poster image, and nothing happens.

Googling, I found this which is close, but seems to be based on a 'link' (as in a href...) instead of a video element. (onDownloadListener does not appear to get invoked on video elements...)

I also see references to overriding onShowCustomView, but that seems to not get called on video elements... nor does shouldOverrideUrlLoading..

I would rather not get into "pull xml from the server, reformat it in the app".. by keeping the story layout on the server, I can control the content a bit better without forcing people to keep updating an app. So if I can convince WebView to handle

I'm clearly missing something obvious.. but I have no clue what.

Android Solutions


Solution 1 - Android

I answer this topic just in case someone read it and is interested on the result. It is possible to view a video element (video html5 tag) within a WebView, but I must say I had to deal with it for few days. These are the steps I had to follow so far:

-Find a properly encoded video

-When initializing the WebView, set the JavaScript, Plug-ins the WebViewClient and the WebChromeClient.

url = new String("http://broken-links.com/tests/video/";);
mWebView = (WebView) findViewById(R.id.webview);
mWebView.setWebChromeClient(chromeClient);
mWebView.setWebViewClient(wvClient);
mWebView.getSettings().setJavaScriptEnabled(true);
mWebView.getSettings().setPluginState(PluginState.ON);
mWebView.loadUrl(url);

-Handle the onShowCustomView in the WebChromeClient object.

@Override
public void onShowCustomView(View view, CustomViewCallback callback) {
	super.onShowCustomView(view, callback);
	if (view instanceof FrameLayout){
		FrameLayout frame = (FrameLayout) view;
		if (frame.getFocusedChild() instanceof VideoView){
			VideoView video = (VideoView) frame.getFocusedChild();
			frame.removeView(video);
			a.setContentView(video);
			video.setOnCompletionListener(this);
			video.setOnErrorListener(this);
			video.start();
		}
	}
}

-Handle the onCompletion and the onError events for the video, in order to get back to the web view.

public void onCompletion(MediaPlayer mp) {
	Log.d(TAG, "Video completo");
	a.setContentView(R.layout.main);
	WebView wb = (WebView) a.findViewById(R.id.webview);
	a.initWebView();
}

But now I should say there are still an important issue. I can play it only once. The second time I click on the video dispatcher (either the poster or some play button), it does nothing.

I would also like the video to play inside the WebView frame, instead of opening the Media Player window, but this is for me a secondary issue.

I hope it helps somebody, and I would also thank any comment or suggestion.

Saludos, terrĂ­colas.

Solution 2 - Android

After long research, I got this thing working. See the following code:

Test.java

import android.app.Activity;
import android.os.Bundle;
import android.view.KeyEvent;

public class Test extends Activity {
	
	HTML5WebView mWebView;
	
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mWebView = new HTML5WebView(this);
        
        if (savedInstanceState != null) {
        	mWebView.restoreState(savedInstanceState);
        } else {	
        	mWebView.loadUrl("http://192.168.1.18/xxxxxxxxxxxxxxxx/");
        }
        
        setContentView(mWebView.getLayout());
    }
    
    @Override
    public void onSaveInstanceState(Bundle outState) {
    	super.onSaveInstanceState(outState);
    	mWebView.saveState(outState);
    }
    
    @Override
    public void onStop() {
    	super.onStop();
    	mWebView.stopLoading();
    }
    
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
    	
    	if (keyCode == KeyEvent.KEYCODE_BACK) {
            if (mWebView.inCustomView()) {
            	mWebView.hideCustomView();
            //	mWebView.goBack();
            	//mWebView.goBack();
            	return true;
            }
            
    	}
    	return super.onKeyDown(keyCode, event);
    }
}


HTML%VIDEO.java

package com.ivz.idemandtest;

import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.webkit.GeolocationPermissions;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.FrameLayout;

public class HTML5WebView extends WebView {
	
	private Context 							mContext;
	private MyWebChromeClient					mWebChromeClient;
	private View								mCustomView;
	private FrameLayout							mCustomViewContainer;
	private WebChromeClient.CustomViewCallback 	mCustomViewCallback;
	
	private FrameLayout							mContentView;
	private FrameLayout							mBrowserFrameLayout;
	private FrameLayout							mLayout;
	
    static final String LOGTAG = "HTML5WebView";
	    
	private void init(Context context) {
		mContext = context;		
		Activity a = (Activity) mContext;
		
		mLayout = new FrameLayout(context);
		
		mBrowserFrameLayout = (FrameLayout) LayoutInflater.from(a).inflate(R.layout.custom_screen, null);
		mContentView = (FrameLayout) mBrowserFrameLayout.findViewById(R.id.main_content);
		mCustomViewContainer = (FrameLayout) mBrowserFrameLayout.findViewById(R.id.fullscreen_custom_content);
		
		mLayout.addView(mBrowserFrameLayout, COVER_SCREEN_PARAMS);

	    // Configure the webview
	    WebSettings s = getSettings();
	    s.setBuiltInZoomControls(true);
	    s.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NARROW_COLUMNS);
	    s.setUseWideViewPort(true);
	    s.setLoadWithOverviewMode(true);
	  //  s.setSavePassword(true);
	    s.setSaveFormData(true);
	    s.setJavaScriptEnabled(true);
		mWebChromeClient = new MyWebChromeClient();
	    setWebChromeClient(mWebChromeClient);
	    
	    setWebViewClient(new WebViewClient());
	       
setScrollBarStyle(View.SCROLLBARS_INSIDE_OVERLAY);
	    
	    // enable navigator.geolocation 
	   // s.setGeolocationEnabled(true);
	   // s.setGeolocationDatabasePath("/data/data/org.itri.html5webview/databases/");
	    
	    // enable Web Storage: localStorage, sessionStorage
	    s.setDomStorageEnabled(true);
	    
	    mContentView.addView(this);
	}

	public HTML5WebView(Context context) {
		super(context);
		init(context);
	}

	public HTML5WebView(Context context, AttributeSet attrs) {
		super(context, attrs);
		init(context);
	}

	public HTML5WebView(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		init(context);
	}
	
	public FrameLayout getLayout() {
		return mLayout;
	}
	
    public boolean inCustomView() {
		return (mCustomView != null);
	}
    
    public void hideCustomView() {
		mWebChromeClient.onHideCustomView();
	}
    
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
    	if (keyCode == KeyEvent.KEYCODE_BACK) {
    		if ((mCustomView == null) && canGoBack()){
    			goBack();
    			return true;
    		}
    	}
    	return super.onKeyDown(keyCode, event);
    }

    private class MyWebChromeClient extends WebChromeClient {
		private Bitmap 		mDefaultVideoPoster;
		private View 		mVideoProgressView;
    	
    	@Override
		public void onShowCustomView(View view, WebChromeClient.CustomViewCallback callback)
		{
			//Log.i(LOGTAG, "here in on ShowCustomView");
	        HTML5WebView.this.setVisibility(View.GONE);
	        
	        // if a view already exists then immediately terminate the new one
	        if (mCustomView != null) {
	            callback.onCustomViewHidden();
	            return;
	        }
	        
	        mCustomViewContainer.addView(view);
	        mCustomView = view;
	        mCustomViewCallback = callback;
	        mCustomViewContainer.setVisibility(View.VISIBLE);
		}
		
		@Override
		public void onHideCustomView() {
			System.out.println("customview hideeeeeeeeeeeeeeeeeeeeeeeeeee");
			if (mCustomView == null)
				return;	       
			
			// Hide the custom view.
			mCustomView.setVisibility(View.GONE);
			
			// Remove the custom view from its container.
			mCustomViewContainer.removeView(mCustomView);
			mCustomView = null;
			mCustomViewContainer.setVisibility(View.GONE);
			mCustomViewCallback.onCustomViewHidden();
			
			HTML5WebView.this.setVisibility(View.VISIBLE);
			HTML5WebView.this.goBack();
	        //Log.i(LOGTAG, "set it to webVew");
		}
		
		
		@Override
		public View getVideoLoadingProgressView() {
			//Log.i(LOGTAG, "here in on getVideoLoadingPregressView");
			
	        if (mVideoProgressView == null) {
	            LayoutInflater inflater = LayoutInflater.from(mContext);
	            mVideoProgressView = inflater.inflate(R.layout.video_loading_progress, null);
	        }
	        return mVideoProgressView; 
		}
    	
    	 @Override
         public void onReceivedTitle(WebView view, String title) {
            ((Activity) mContext).setTitle(title);
         }

         @Override
         public void onProgressChanged(WebView view, int newProgress) {
        	 ((Activity) mContext).getWindow().setFeatureInt(Window.FEATURE_PROGRESS, newProgress*100);
         }
         
         @Override
         public void onGeolocationPermissionsShowPrompt(String origin, GeolocationPermissions.Callback callback) {
             callback.invoke(origin, true, false);
         }
    }

	
	static final FrameLayout.LayoutParams COVER_SCREEN_PARAMS =
        new FrameLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
}


custom_screen.xml

<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2009 The Android Open Source Project

     Licensed under the Apache License, Version 2.0 (the "License");
     you may not use this file except in compliance with the License.
     You may obtain a copy of the License at

          http://www.apache.org/licenses/LICENSE-2.0

     Unless required by applicable law or agreed to in writing, software
     distributed under the License is distributed on an "AS IS" BASIS,
     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     See the License for the specific language governing permissions and
     limitations under the License.
-->

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android">
    <FrameLayout android:id="@+id/fullscreen_custom_content"
        android:visibility="gone"
        android:background="@color/black"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
    />
    <LinearLayout android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <LinearLayout android:id="@+id/error_console"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
        />

        <FrameLayout android:id="@+id/main_content"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
        />
    </LinearLayout>
</FrameLayout>


video_loading_progress.xml

<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2009 The Android Open Source Project

     Licensed under the Apache License, Version 2.0 (the "License");
     you may not use this file except in compliance with the License.
     You may obtain a copy of the License at

          http://www.apache.org/licenses/LICENSE-2.0

     Unless required by applicable law or agreed to in writing, software
     distributed under the License is distributed on an "AS IS" BASIS,
     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     See the License for the specific language governing permissions and
     limitations under the License.
-->

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
         android:id="@+id/progress_indicator"
         android:orientation="vertical"
         android:layout_centerInParent="true"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content">

       <ProgressBar android:id="@android:id/progress"
           style="?android:attr/progressBarStyleLarge"
           android:layout_gravity="center"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content" />

       <TextView android:paddingTop="5dip"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:layout_gravity="center"
           android:text="@string/loading_video" android:textSize="14sp"
           android:textColor="?android:attr/textColorPrimary" />
 </LinearLayout>


colors.xml

<?xml version="1.0" encoding="utf-8"?>
<!--
/* //device/apps/common/assets/res/any/http_authentication_colors.xml
**
** Copyright 2006, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License"); 
** you may not use this file except in compliance with the License. 
** You may obtain a copy of the License at 
**
**     http://www.apache.org/licenses/LICENSE-2.0 
**
** Unless required by applicable law or agreed to in writing, software 
** distributed under the License is distributed on an "AS IS" BASIS, 
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
** See the License for the specific language governing permissions and 
** limitations under the License.
*/
-->
<!-- FIXME: Change the name of this file!  It is now being used generically
    for the browser -->
<resources>
    <color name="username_text">#ffffffff</color>
    <color name="username_edit">#ff000000</color>

    <color name="password_text">#ffffffff</color>
    <color name="password_edit">#ff000000</color>

    <color name="ssl_text_label">#ffffffff</color>
    <color name="ssl_text_value">#ffffffff</color>
    
    <color name="white">#ffffffff</color>
    <color name="black">#ff000000</color>
    
    

    <color name="geolocation_permissions_prompt_background">#ffdddddd</color>
</resources>


Manifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="com.test"
      android:versionCode="1"
      android:versionName="1.0">
    <uses-sdk android:minSdkVersion="7" />

    <application android:icon="@drawable/icon" android:label="@string/app_name">
        <activity android:name=".Test"
                  android:label="@string/app_name" android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
            android:configChanges="orientation|keyboardHidden|keyboard">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

    </application>  
<uses-permission android:name="android.permission.INTERNET"></uses-permission>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_GPS" />
<uses-permission android:name="android.permission.ACCESS_ASSISTED_GPS" />
<uses-permission android:name="android.permission.ACCESS_LOCATION" />
</manifest>

Expecting rest of things you can understand.

Solution 3 - Android

mdelolmo's answer was incredibly helpful, but like he said, the video only plays once and then you can't open it again.

I looked into this a bit and here is what I found, in case any weary WebView travelers like myself stumble on this post in the future.

First off, I looked at the VideoView and MediaPlayer's documentation and got a better sense of how those work. I strongly recommend those.

Then, I looked at the source code to see how the Android Browser does it. Do a page find and go look at how they handle onShowCustomView(). They keep a reference to the CustomViewCallbackand to the custom view.

With all of that, and with mdelolmo's answer in mind, when you are done with the video, all you need to do is two things. First, on the VideoView that you saved a reference to, call stopPlayback() that will release the MediaPlayer to be used later elsewhere. You can see it in the VideoView source code. Second, on the CustomViewCallback you saved a reference to call CustomViewCallback.onCustomViewHidden().

After doing those two things, you can click on the same video or any other video and it will open like before. No need to restart the entire WebView.

Hope that helps.

Solution 4 - Android

I know this thread is several months old, but I found a solution for playing the video inside the WebView without doing it fullscreen (but still in the media player...). So far, I didn't find any hint on this in the internet so maybe this is also interesting for others. I'm still struggling on some issues (i.e. placing the media player in the right section of the screen, don't know why I'm doing it wrong but it's a relatively small issue I think...).

In the Custom ChromeClient specify LayoutParams:

// 768x512 is the size of my video
FrameLayout.LayoutParams LayoutParameters = 
                                     new FrameLayout.LayoutParams (768, 512); 

My onShowCustomView method looks like this:

public void onShowCustomView(final View view, final CustomViewCallback callback) {
     // super.onShowCustomView(view, callback);
     if (view instanceof FrameLayout) {
         this.mCustomViewContainer = (FrameLayout) view;
         this.mCustomViewCallback = callback;
         this.mContentView = (WebView) this.kameha.findViewById(R.id.webview);
         if (this.mCustomViewContainer.getFocusedChild() instanceof VideoView) {
             this.mCustomVideoView = (VideoView) 
                                     this.mCustomViewContainer.getFocusedChild();
             this.mCustomViewContainer.setVisibility(View.VISIBLE);
             final int viewWidth = this.mContentView.getWidth();
             final int viewLeft = (viewWidth - 1024) / 2;
             // get the x-position for the video (I'm porting an iPad-Webapp to Xoom, 
             // so I can use those numbers... you have to find your own of course...
             this.LayoutParameters.leftMargin = viewLeft + 256; 
             this.LayoutParameters.topMargin = 128;
             // just add this view so the webview underneath will still be visible, 
             // but apply the LayoutParameters specified above
             this.kameha.addContentView(this.mCustomViewContainer, 
                                             this.LayoutParameters); 
             this.mCustomVideoView.setOnCompletionListener(this);
             this.mCustomVideoView.setOnErrorListener(this);
             // handle clicks on the screen (turning off the video) so you can still
             // navigate in your WebView without having the video lying over it
             this.mCustomVideoView.setOnFocusChangeListener(this); 
             this.mCustomVideoView.start();
         }
     }
 }

So, I hope I could help... I too had to play around with video-Encoding and saw different kinds of using the WebView with html5 video - in the end my working code was a wild mix of different code-parts I found in the internet and some things I had to figure out by myself. It really was a pain in the a*.

Solution 5 - Android

Actually, it seems sufficient to merely attach a stock WebChromeClient to the client view, ala

mWebView.setWebChromeClient(new WebChromeClient());

and you need to have hardware acceleration turned on!

At least, if you don't need to play a full-screen video, there's no need to pull the VideoView out of the WebView and push it into the Activity's view. It will play in the video element's allotted rect.

Any ideas how to intercept the expand video button?

Solution 6 - Android

This approach works well very till 2.3 And by adding hardwareaccelerated=true it even works from 3.0 to ICS One problem i am facing currently is upon second launch of media player application is getting crashed because i have not stopped playback and released Media player. As VideoSurfaceView object, which we get in onShowCustomView function from 3.0 OS, are specific to browser and not a VideoView object as in, till 2.3 OS How can i access it and stopPlayback and release resources?

Solution 7 - Android

A-M's is similar to what the BrowerActivity does. for FrameLayout.LayoutParams LayoutParameters = new FrameLayout.LayoutParams (768, 512);

I think we can use

FrameLayout.LayoutParams LayoutParameters = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.FILL_PARENT,
            FrameLayout.LayoutParams.FILL_PARENT) 

instead.

Another issue I met is if the video is playing, and user clicks the back button, next time, you go to this activity(singleTop one) and can not play the video. to fix this, I called the

try { 
    mCustomVideoView.stopPlayback();  
    mCustomViewCallback.onCustomViewHidden();
} catch(Throwable e) { //ignore }

in the activity's onBackPressed method.

Solution 8 - Android

I know this is an very old question, but have you tried the hardwareAccelerated="true" manifest flag for your application or activity?

With this set, it seems to work without any WebChromeClient modification (which I would expect from an DOM-Element.)

Solution 9 - Android

I used html5webview to solve this problem.Download and put it into your project then you can code just like this.

private HTML5WebView mWebView;
String url = "SOMEURL";
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    mWebView = new HTML5WebView(this);
    if (savedInstanceState != null) {
            mWebView.restoreState(savedInstanceState);
    } else {
            mWebView.loadUrl(url);
    }
    setContentView(mWebView.getLayout());
}
@Override
public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    mWebView.saveState(outState);
}

To make the video rotatable, put android:configChanges="orientation" code into your Activity for example (Androidmanifest.xml)

<activity android:name=".ui.HTML5Activity" android:configChanges="orientation"/>

and override the onConfigurationChanged method.

@Override
public void onConfigurationChanged(Configuration newConfig) {
     super.onConfigurationChanged(newConfig);
}

Solution 10 - Android

This question is years old, but maybe my answer will help people like me who have to support old Android version. I tried a lot of different approaches which worked on some Android versions, however not on all. The best solution I found is to use the Crosswalk Webview which is optimized for HTML5 feature support and works on Android 4.1 and higher. It is as simple to use as the default Android WebView. You just have to include the library. Here you can find a simple tutorial on how to use it: https://diego.org/2015/01/07/embedding-crosswalk-in-android-studio/

Solution 11 - Android

Well, apparently this is just not possible without using a JNI to register a plugin to get the video event. (Personally, I am avoiding JNI's since I really don't want to deal with a mess when Atom-based android tablets come out in the next few months, losing the portability of Java.)

The only real alternative seems to be to create a new web page just for WebView and do video the old-school way with an A HREF link as cited in the Codelark url above.

Icky.

Solution 12 - Android

On honeycomb use hardwareaccelerated=true and pluginstate.on_demand seems to work

Solution 13 - Android

I had a similar problem. I had HTML files and videos in the assets-folder of my app.

Therefore the videos were located inside of the APK. Because the APK is really a ZIP-file, the WebView was not able to read the video-files.

Copying all HTML- and video-files onto the SD-Card worked for me.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionbrian mooreView Question on Stackoverflow
Solution 1 - AndroidmdelolmoView Answer on Stackoverflow
Solution 2 - AndroidsurendraView Answer on Stackoverflow
Solution 3 - AndroidcottonBallPawsView Answer on Stackoverflow
Solution 4 - AndroidA-MView Answer on Stackoverflow
Solution 5 - AndroidRuss SchnappView Answer on Stackoverflow
Solution 6 - AndroidNovaView Answer on Stackoverflow
Solution 7 - AndroidyincanView Answer on Stackoverflow
Solution 8 - AndroidNappyView Answer on Stackoverflow
Solution 9 - AndroidMethuz Kaewsai-kaoView Answer on Stackoverflow
Solution 10 - AndroidApfelsaftView Answer on Stackoverflow
Solution 11 - Androidbrian mooreView Answer on Stackoverflow
Solution 12 - Androiduser953501View Answer on Stackoverflow
Solution 13 - AndroidRhodanV5500View Answer on Stackoverflow