106

Well, I've been searching few days already, how to display HTML5 video in full-screen mode on android WebView.

I managed to play HTML5 videos on my webview. Problems are arising when displaying video in fullscreen mode.

As I figured out, android has two ways of handling the < video > tag:

  1. On android versions <= 2.3.3, the onShowCustomView method is fired, and I can have the VideoView instance, and set listeners when the video completes, set controllers etc. So far so good.

  2. On ICS (and probably 3.0 and above), it looks like the < video > is handled in different way. When the HTML5 video is played the onShowCustomView isn't being called on normal mode - it looks like there is an internal business inside the WebView that plays the video, and all the controls that are defined in the < video > tag are shown - I can't access it in any way. Actually, if the video is played in normal mode this is OK because the controls are there and are working.

That led me to the big problem: when displaying the video in full screen mode the onShowCustomView is being called - but on ICS the "view" parameter isn't an instance of VideoView.

I managed to find out that the instance is of VideoSurfaceView, a private inner class of HTML5VideoFullScreen class. The only way we can access this inner-class is via reflection.

After looking at GrepCode for this class, I learnt that unlike VideoView, the HTML5VideoFullScreen$VideoSurfaceView doesn't hold a MediaPlayer instance that I can listen to its events or access its controls. The only thing I can do is take this VideoSurfaceView as it is and put it inside a full-screen-layout without controlling it.

Bottom line - When displaying video in full-screen, I don't know when the video ends, its controls aren't shown - this is pretty sad. I can't get the trigger for closing the full-screen.

I tried few unsuccessful workarounds:

  1. Reflection: I tried to reach the HTML5VideoFullScreen instance, which holds a MediaPlayer member, from the inner-class VideoSurfaceView. I didn't manage to get it, I'm not sure this is possible (ViewSurfaceView doesn't hold its owner's instance).

  2. Register for the video events via Javascript (onended, for example), and handle what I need back in JAVA via JavascriptInterface: I found this solution isn't reliable because while doing this I encountered another problem: the < video > tag can be nested in an . The iframe source isn't mine and I can't get its content (getElementById or getElementsByTagName[i] are nulls) - which means, I can't reach the < video > element inside the iframe.

I'm still searching for a solution, very little is written about this issue. Did anyone manage to solve it? Help would be much appreciated!

VideoView class: Here (has MediaPlayer)

HTML5VideoFullScreen$VideoSurfaceView class: Here (doesn't have MediaPlayer)

nbtk
  • 3,039
  • 4
  • 21
  • 19
  • Same problem as you, and reached to the same conclusions, also. I have worked on this for about 20 hours, but won't give up for at least 20 hours more (real work hours, I mean). Will tell you if I find the solution. Meanwhile, as this question was asked 20 days ago, are you still trying to solve it or did you apply any other workaround? – cprcrack Apr 23 '13 at 03:32
  • By the way, a quick look to HTML5VideoFullScreen's source code showed that it's parent class HTML5VideoView contains a MediaPlayer. – cprcrack Apr 23 '13 at 03:36

8 Answers8

191

Edit 2014/10: by popular demand I'm maintaining and moving this to GitHub. Please check cprcrack/VideoEnabledWebView for the last version. Will keep this answer only for reference.

Edit 2014/01: improved example usage to include the nonVideoLayout, videoLayout, and videoLoading views, for those users requesting more example code for better understading.

Edit 2013/12: some bug fixes related to Sony Xperia devices compatibility, but which in fact affected all devices.

Edit 2013/11: after the release of Android 4.4 KitKat (API level 19) with its new Chromium webview, I had to work hard again. Several improvements were made. You should update to this new version. I release this source under WTFPL.

Edit 2013/04: after 1 week of hard work, I finally have achieved everything I needed. I think this two generic classes that I have created can solve all you problems.

VideoEnabledWebChromeClient can be used alone if you do not require the functionality that VideoEnabledWebView adds. But VideoEnabledWebView must always rely on a VideoEnabledWebChromeClient. Please read all the comments of the both classes carefully.

VideoEnabledWebChromeClient class

import android.media.MediaPlayer;
import android.media.MediaPlayer.OnCompletionListener;
import android.media.MediaPlayer.OnErrorListener;
import android.media.MediaPlayer.OnPreparedListener;
import android.view.SurfaceView;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.webkit.WebChromeClient;
import android.widget.FrameLayout;

/**
 * This class serves as a WebChromeClient to be set to a WebView, allowing it to play video.
 * Video will play differently depending on target API level (in-line, fullscreen, or both).
 *
 * It has been tested with the following video classes:
 * - android.widget.VideoView (typically API level <11)
 * - android.webkit.HTML5VideoFullScreen$VideoSurfaceView/VideoTextureView (typically API level 11-18)
 * - com.android.org.chromium.content.browser.ContentVideoView$VideoSurfaceView (typically API level 19+)
 * 
 * Important notes:
 * - For API level 11+, android:hardwareAccelerated="true" must be set in the application manifest.
 * - The invoking activity must call VideoEnabledWebChromeClient's onBackPressed() inside of its own onBackPressed().
 * - Tested in Android API levels 8-19. Only tested on http://m.youtube.com.
 *
 * @author Cristian Perez (http://cpr.name)
 *
 */
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
     */
    @SuppressWarnings("unused")
    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.
     */
    @SuppressWarnings("unused")
    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.
     */
    @SuppressWarnings("unused")
    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.INVISIBLE);
            activityVideoView.addView(videoViewContainer, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
            activityVideoView.setVisibility(View.VISIBLE);

            if (focusedChild instanceof android.widget.VideoView)
            {
                // android.widget.VideoView (typically API level <11)
                android.widget.VideoView videoView = (android.widget.VideoView) focusedChild;

                // Handle all the required events
                videoView.setOnPreparedListener(this);
                videoView.setOnCompletionListener(this);
                videoView.setOnErrorListener(this);
            }
            else
            {
                // Other classes, including:
                // - android.webkit.HTML5VideoFullScreen$VideoSurfaceView, which inherits from android.view.SurfaceView (typically API level 11-18)
                // - android.webkit.HTML5VideoFullScreen$VideoTextureView, which inherits from android.view.TextureView (typically API level 11-18)
                // - com.android.org.chromium.content.browser.ContentVideoView$VideoSurfaceView, which inherits from android.view.SurfaceView (typically API level 19+)

                // Handle HTML5 video ended event only if the class is a SurfaceView
                // Test case: TextureView of Sony Xperia T API level 16 doesn't work fullscreen when loading the javascript below
                if (webView != null && webView.getSettings().getJavaScriptEnabled() && focusedChild instanceof SurfaceView)
                {
                    // Run javascript code that detects the video end and notifies the Javascript interface
                    String js = "javascript:";
                    js += "var _ytrp_html5_video_last;";
                    js += "var _ytrp_html5_video = document.getElementsByTagName('video')[0];";
                    js += "if (_ytrp_html5_video != undefined && _ytrp_html5_video != _ytrp_html5_video_last) {";
                    {
                        js += "_ytrp_html5_video_last = _ytrp_html5_video;";
                        js += "function _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 @SuppressWarnings("deprecation")
    public void onShowCustomView(View view, int requestedOrientation, CustomViewCallback callback) // Available in API level 14+, deprecated in API level 18+
    {
        onShowCustomView(view, callback);
    }

    @Override
    public void onHideCustomView()
    {
        // This method should be manually called on video end in all cases because it's not always called automatically.
        // This method must be manually 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.INVISIBLE);
            activityVideoView.removeView(videoViewContainer);
            activityNonVideoView.setVisibility(View.VISIBLE);

            // Call back (only in API level <19, because in API level 19+ with chromium webview it crashes)
            if (videoViewCallback != null && !videoViewCallback.getClass().getName().contains(".chromium."))
            {
                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
    {
        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 android.widget.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 android.widget.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 android.widget.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 was not (video view is not visible)
     */
    public boolean onBackPressed()
    {
        if (isVideoFullscreen)
        {
            onHideCustomView();
            return true;
        }
        else
        {
            return false;
        }
    }

}

VideoEnabledWebView class

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;

import java.util.Map;

/**
 * This class serves as a WebView to be used in conjunction with a VideoEnabledWebChromeClient.
 * It makes possible:
 * - To detect the HTML5 video ended event so that the VideoEnabledWebChromeClient can exit full-screen.
 * 
 * Important notes:
 * - Javascript is enabled by default and must not be disabled with getSettings().setJavaScriptEnabled(false).
 * - setWebChromeClient() must be called before any loadData(), loadDataWithBaseURL() or loadUrl() method.
 *
 * @author Cristian Perez (http://cpr.name)
 *
 */
public class VideoEnabledWebView extends WebView
{
    public class JavascriptInterface
    {
        @android.webkit.JavascriptInterface
        public void notifyVideoEnd() // Must match Javascript interface method of VideoEnabledWebChromeClient
        {
            // This code is not executed in the UI thread, so we must force that to happen
            new Handler(Looper.getMainLooper()).post(new Runnable()
            {
                @Override
                public void run()
                {
                    if (videoEnabledWebChromeClient != null)
                    {
                        videoEnabledWebChromeClient.onHideCustomView();
                    }
                }
            });
        }
    }

    private VideoEnabledWebChromeClient videoEnabledWebChromeClient;
    private boolean addedJavascriptInterface;

    public VideoEnabledWebView(Context context)
    {
        super(context);
        addedJavascriptInterface = false;
    }

    @SuppressWarnings("unused")
    public VideoEnabledWebView(Context context, AttributeSet attrs)
    {
        super(context, attrs);
        addedJavascriptInterface = false;
    }

    @SuppressWarnings("unused")
    public VideoEnabledWebView(Context context, AttributeSet attrs, int defStyle)
    {
        super(context, attrs, defStyle);
        addedJavascriptInterface = 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 videoEnabledWebChromeClient != null && videoEnabledWebChromeClient.isVideoFullscreen();
    }

    /**
     * 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()
    {
        if (!addedJavascriptInterface)
        {
            // Add javascript interface to be called when the video ends (must be done before page load)
            addJavascriptInterface(new JavascriptInterface(), "_VideoEnabledWebView"); // Must match Javascript interface name of VideoEnabledWebChromeClient

            addedJavascriptInterface = true;
        }
    }

}

Example usage:

Main layout activity_main.xml in which we put a VideoEnabledWebView and other used views:

<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" >

    <!-- View that will be hidden when video goes fullscreen -->
    <RelativeLayout
        android:id="@+id/nonVideoLayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >

        <your.package.VideoEnabledWebView
            android:id="@+id/webView"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />

    </RelativeLayout>   

    <!-- View where the video will be shown when video goes fullscreen -->
    <RelativeLayout
        android:id="@+id/videoLayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >

        <!-- View that will be shown while the fullscreen video loads (maybe include a spinner and a "Loading..." message) -->
        <View
            android:id="@+id/videoLoading"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:visibility="invisible" />

    </RelativeLayout>

</RelativeLayout>

Activity's onCreate(), in which we initialize it:

private VideoEnabledWebView webView;
private VideoEnabledWebChromeClient webChromeClient;

@Override
protected void onCreate(Bundle savedInstanceState)
{
    super.onCreate(savedInstanceState);

    // Set layout
    setContentView(R.layout.activity_main);

    // Save the web view
    webView = (VideoEnabledWebView) findViewById(R.id.webView);

    // Initialize the VideoEnabledWebChromeClient and set event handlers
    View nonVideoLayout = findViewById(R.id.nonVideoLayout); // Your own view, read class comments
    ViewGroup videoLayout = (ViewGroup) findViewById(R.id.videoLayout); // Your own view, read class comments
    View loadingView = getLayoutInflater().inflate(R.layout.view_loading_video, null); // Your own view, read class comments
    webChromeClient = new VideoEnabledWebChromeClient(nonVideoLayout, videoLayout, loadingView, webView) // See all available constructors...
    {
        // Subscribe to standard events, such as onProgressChanged()...
        @Override
        public void onProgressChanged(WebView view, int progress)
        {
            // Your code...
        }
    };
    webChromeClient.setOnToggledFullscreen(new VideoEnabledWebChromeClient.ToggledFullscreenCallback()
    {
        @Override
        public void toggledFullscreen(boolean fullscreen)
        {
            // Your code to handle the full-screen change, for example showing and hiding the title bar. Example:
            if (fullscreen)
            {
                WindowManager.LayoutParams attrs = getWindow().getAttributes();
                attrs.flags |= WindowManager.LayoutParams.FLAG_FULLSCREEN;
                attrs.flags |= WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
                getWindow().setAttributes(attrs);
                if (android.os.Build.VERSION.SDK_INT >= 14)
                {
                    getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE);
                }
            }
            else
            {
                WindowManager.LayoutParams attrs = getWindow().getAttributes();
                attrs.flags &= ~WindowManager.LayoutParams.FLAG_FULLSCREEN;
                attrs.flags &= ~WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
                getWindow().setAttributes(attrs);
                if (android.os.Build.VERSION.SDK_INT >= 14)
                {
                    getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);
                }
            }

        }
    });
    webView.setWebChromeClient(webChromeClient);

    // Navigate everywhere you want, this classes have only been tested on YouTube's mobile site
    webView.loadUrl("http://m.youtube.com");
}

And don't forget to call onBackPressed():

@Override
public void onBackPressed()
{
    // Notify the VideoEnabledWebChromeClient, and handle it ourselves if it doesn't handle it
    if (!webChromeClient.onBackPressed())
    {
        if (webView.canGoBack())
        {
            webView.goBack();
        }
        else
        {
            // Close app (presumably)
            super.onBackPressed();
        }
    }
}
cprcrack
  • 17,118
  • 7
  • 88
  • 91
  • 1
    What you suggested, will notify when the video ends only on API <= 10. On >= 11, nothing registers the onCompletionListener... – nbtk Apr 24 '13 at 07:43
  • But on >= 11, onHideCustomView() IS being called in a real Samsung Galaxy SIII. Not in the emulator. – cprcrack Apr 25 '13 at 07:37
  • I just edited my answer with major improvements. Now it should work on all devices, including the video ended event and the full-screen exit on that case. Key part has been using javascript to detect the HTML5 video tag's ended event and then notify the app with a Javascript interface. – cprcrack Apr 26 '13 at 23:21
  • 4
    Thanks for your efforts! But if the – nbtk Apr 28 '13 at 07:52
  • 1
    Thanks for noting down the problem with the iframes. I hope someone who needs that feature can try to solve it. – cprcrack Apr 29 '13 at 20:23
  • Thanks! I still have problems on some devices, though... I got this working in an emulator and some other devices, but e.g. on my Xperia S phone (which also has the HTML5VideoFullScreen$VideoTextureView instead of the SurfaceView) I don't get to see the fullscreen video; just the seemingly empty videoLayout. I'm thinking of using an Intent to launch an external player if I can't fix this... – Matthias Nov 07 '13 at 13:39
  • @Matthias yeah, that's Android in its purest form. – cprcrack Nov 07 '13 at 20:46
  • 1
    Just updated the answer with Android 4.4+ Chromium webview support. – cprcrack Nov 11 '13 at 18:59
  • My implementation is fully inspired on the code above. But I run into some strange behavior. The first activity that I open with this webview runs perfectly, playing in container view and fullscreen. But when I close the activity while playing ( I call onPause on the webview to stop the chromclient) and open a new activity with a new webview the video does not play. But when pressing fullscreen the video will play. The curious thing is that if I exit the first window when the video is paused the video plays nicely on the second window. Any ideas on this? Or enough for a separate question. – Wienke Giezeman Dec 24 '13 at 10:35
  • 3
    Incorporating the code of this answer http://stackoverflow.com/questions/20379478/android-samsung-s4-html5-video-pauses will solve the issue I posed above. – Wienke Giezeman Dec 30 '13 at 17:12
  • awesome work, Cristian. It's ridiculous how much effort one has to go through on Android - and in this case, HTML5 isn't even all that new. – Someone Somewhere Jan 16 '14 at 23:52
  • 1
    @cprcrack thanks for your efforts in editing and keeping the answer up to date. Could you share the layout for view_loading_video? – Sagar Hatekar Feb 19 '14 at 14:53
  • In your layout you have the same android:id="@+id/nonVideoLayout" set on two views. Should not be set on parent I think. – markostamcar Feb 26 '14 at 15:54
  • @cprcrack what is view_loading_video stands for? please update the code, thanks – tugce Mar 13 '14 at 08:49
  • hi,I have the following crash stackjava.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first. at android.view.ViewGroup.addViewInner(ViewGroup.java:3378) at android.view.ViewGroup.addView(ViewGroup.java:3249) at android.view.ViewGroup.addView(ViewGroup.java:3225) at android.webkit.HTML5VideoFullScreen.enterFullScreenVideoState(HTML5VideoFullScreen.java:289) – banxi1988 May 14 '14 at 08:12
  • 1
    I have one last problem with the tap highlight color. that orange ugly colour on click. – hasan Jun 03 '14 at 10:12
  • Yeah, that's horrible, let me know if you find a way to hide it. – cprcrack Jun 03 '14 at 16:01
  • The VideoEnabledWebView code does not work on API<17, because of the JavascriptInterface. I used this simpler solution for my app: http://shashikawlp.wordpress.com/2013/06/25/play-html5-video-on-android-webview/ – Boris van Schooten Jun 19 '14 at 09:53
  • is there an example on how to force the fullscreen mode to be landscape? – DennisA Jul 12 '14 at 03:58
  • Hi I am getting exception when the video goes fullscreen on some devides stacktrace is Here pastebin.com/9Gn9jmc2 If I am doing something wrong – Rajnish Mishra Aug 05 '14 at 15:26
  • Is there any reason this code is not in a repo on, say, GitHub? Would be great to see it there, especially with all those updates/fixes. I'd gladly put it up there (given the WTFPL license) unless @cprcrack wants under his account. – gregoltsov Sep 24 '14 at 11:26
  • 1
    @Goltsov I never thought this answer was going to become so popular. But yeah I should definitely put it on GitHub so that updating is easier and people can submit issues and contribute. Give me some days and I'll do it okay? – cprcrack Sep 24 '14 at 15:36
  • Sorry for the delay. It's done now: https://github.com/cprcrack/VideoEnabledWebView – cprcrack Oct 12 '14 at 02:28
  • 3
    Need upgrade, dont work on android v4.4.4+, please add option to onplay, automaticaly go to fullscreen. You can implement vimeo on this code? Thanks for sharing the code on github. @cprcrack – Florida May 13 '15 at 20:28
  • I have used these classes for show HTML5 inline video on webview. But video is not started automatically when I changed the url from "http://m.youtube.com" to "file:///android_asset/videotag.html". This is my html5 video tag in which I have a mp4 video but which is not started automatically. So, please help me as soon as possible. – Varnit Khandelwal Aug 18 '15 at 07:42
  • @cprcrack: Thanks a ton ...:) – G droid Oct 28 '15 at 12:45
  • I tried this solution but unfortunately I am getting this exception whenever there is a video on the webpage java.lang.NullPointerException: Attempt to invoke virtual method 'int android.graphics.Bitmap.getWidth()' on a null object reference at com.android.webview.chromium.WebViewContentsClientAdapter.getDefaultVideoPoster(WebViewContentsClientAdapter.java:1181) can anyone help? – Randa Omar Fahmy Oct 21 '16 at 20:10
  • On Android 5.0 (api 21), the fullscreen button doesn't appear. On Android 5.1+ (api 22+), the fullscreen button appears yet shows nothing but a blank screen in fullscreen mode, and the audio still plays. Anyone find a solution to this? Works great on apis 19 and lower. – Grux Nov 16 '16 at 00:19
  • On Android 7.1.1 focused view is null and frameLayout has only one china view of type org.chromium.android_webview.FullScreenView – Alexey Suvorov Jan 11 '17 at 01:57
  • 3
    it does not work in latest adroid versions...above 5.0, 6.0,7.0 – UMAR-MOBITSOLUTIONS Aug 02 '17 at 10:27
  • I think this is not feasible anymore as google doesn't like javascript interface injection anymore. – mabc21 Apr 03 '19 at 07:19
  • in the latest os first time video goes full screen but later it does not work. – Milon Apr 23 '19 at 04:42
61

Tested on Android 9.0 version

None of the answers worked for me . This is the final thing worked

import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.FrameLayout;
import android.widget.ProgressBar;

public class MainActivity extends AppCompatActivity {

    WebView mWebView;


    @SuppressLint("SetJavaScriptEnabled")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);


        mWebView = (WebView) findViewById(R.id.webView);


        mWebView.setWebViewClient(new WebViewClient());
        mWebView.setWebChromeClient(new MyChrome());
        WebSettings webSettings = mWebView.getSettings();
        webSettings.setJavaScriptEnabled(true);
        webSettings.setAllowFileAccess(true);
        webSettings.setAppCacheEnabled(true);

       if (savedInstanceState == null) {
          mWebView.loadUrl("https://www.youtube.com/");
       }

    }


    private class MyChrome extends WebChromeClient {

        private View mCustomView;
        private WebChromeClient.CustomViewCallback mCustomViewCallback;
        protected FrameLayout mFullscreenContainer;
        private int mOriginalOrientation;
        private int mOriginalSystemUiVisibility;

        MyChrome() {}

        public Bitmap getDefaultVideoPoster()
        {
            if (mCustomView == null) {
                return null;
            }
            return BitmapFactory.decodeResource(getApplicationContext().getResources(), 2130837573);
        }

        public void onHideCustomView()
        {
            ((FrameLayout)getWindow().getDecorView()).removeView(this.mCustomView);
            this.mCustomView = null;
            getWindow().getDecorView().setSystemUiVisibility(this.mOriginalSystemUiVisibility);
            setRequestedOrientation(this.mOriginalOrientation);
            this.mCustomViewCallback.onCustomViewHidden();
            this.mCustomViewCallback = null;
        }

        public void onShowCustomView(View paramView, WebChromeClient.CustomViewCallback paramCustomViewCallback)
        {
            if (this.mCustomView != null)
            {
                onHideCustomView();
                return;
            }
            this.mCustomView = paramView;
            this.mOriginalSystemUiVisibility = getWindow().getDecorView().getSystemUiVisibility();
            this.mOriginalOrientation = getRequestedOrientation();
            this.mCustomViewCallback = paramCustomViewCallback;
            ((FrameLayout)getWindow().getDecorView()).addView(this.mCustomView, new FrameLayout.LayoutParams(-1, -1));
            getWindow().getDecorView().setSystemUiVisibility(3846 | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
        }
    }

 @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        mWebView.saveState(outState);
    }

    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
        mWebView.restoreState(savedInstanceState);
    }
}

In AndroidManifest.xml

<activity
  android:name=".MainActivity"
  android:configChanges="orientation|screenSize" />

Source Monster Techno

Manohar
  • 22,116
  • 9
  • 108
  • 144
5

Edit: please see my other answer, as you probably don't need this now.

As you said, in API levels 11+ a HTML5VideoFullScreen$VideoSurfaceView is passed. But I don't think you are right when you say that "it doens't have a MediaPlayer".

This is the way to reach the MediaPlayer instance from the HTML5VideoFullScreen$VideoSurfaceView instance using reflection:

@SuppressWarnings("rawtypes")
Class c1 = Class.forName("android.webkit.HTML5VideoFullScreen$VideoSurfaceView");
Field f1 = c1.getDeclaredField("this$0");
f1.setAccessible(true);

@SuppressWarnings("rawtypes")
Class c2 = f1.getType().getSuperclass();
Field f2 = c2.getDeclaredField("mPlayer");
f2.setAccessible(true);

Object ___html5VideoViewInstance = f1.get(focusedChild); // Look at the code in my other answer to this same question to see whats focusedChild

Object ___mpInstance = f2.get(___html5VideoViewInstance); // This is the MediaPlayer instance.

So, now you could set the onCompletion listener of the MediaPlayer instance like this:

OnCompletionListener ocl = new OnCompletionListener()
{
    @Override
    public void onCompletion(MediaPlayer mp)
    {
        // Do stuff
    }
};

Method m1 = f2.getType().getMethod("setOnCompletionListener", new Class[] { Class.forName("android.media.MediaPlayer$OnCompletionListener") });
m1.invoke(___mpInstance, ocl);

The code doesn't fail but I'm not completely sure if that onCompletion listener will really be called or if it could be useful to your situation. But just in case someone would like to try it.

Community
  • 1
  • 1
cprcrack
  • 17,118
  • 7
  • 88
  • 91
  • 1
    Thanks for your help I tried this code - first I can see that not all devices uses VideoSurfaceView somehow - HTC One X has VideoTextureView which is probably their custom class (not in the android docs). I tried it on other devices and on non of them the completion listener was invoked. – nbtk Apr 24 '13 at 08:06
  • I get open with option, I want to play youtube video on the same activity. What do I do? – asok Buzz Jul 27 '14 at 10:44
  • Hi I am getting exception when the video goes fullscreen on some devides stacktrace is Here http://pastebin.com/9Gn9jmc2 If I am doing something wrong – Rajnish Mishra Aug 05 '14 at 15:25
  • @cprcrack is it possible to force change the activity orientation from portrait to landscape when the full screen is clicked on video? – Muhammad Dec 29 '18 at 21:16
1

Thank you so much for that class, Cristian.

I made a minor tweak to it so that the custom loading view is optional, like so:

  @Override
    public View getVideoLoadingProgressView() // Video will start loading, only called in the case of VideoView (typically API level 10-)
    {
        if (loadingView == null)
        {
            return super.getVideoLoadingProgressView();
        }
        else
        {
            loadingView.setVisibility(View.VISIBLE);
            return loadingView;
        }
    }

I also added a new constructor that just takes two parameters. Anyway, just a minor simplification if you don't need the loading view. Thanks again for providing this.

Scott Schafer
  • 487
  • 3
  • 14
1

Just set
mWebView.setWebChromeClient(new WebChromeClient());

and video plays as normally wont need any custom view.

Pranav
  • 4,172
  • 3
  • 30
  • 31
0

This is great. But if you want your website links to open in the app itself, add this code in your ExampleActivity.java:

webView.setWebViewClient(new WebViewClient() {
        @Override
        public boolean shouldOverrideUrlLoading(WebView view, String url) {
            if (Uri.parse(url).getHost().endsWith("yourwebsite.com")) {
                return false;
            }

            Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
            view.getContext().startActivity(intent);
            return true;
        }
    });
Sid
  • 1,270
  • 2
  • 15
  • 30
0

Cprcrack's answer works very well for API levels 19 and under. Just a minor addition to cprcrack's onShowCustomView will get it working on API level 21+

if (Build.VERSION.SDK_INT >= 21) {
      videoViewContainer.setBackgroundColor(Color.BLACK);
      ((ViewGroup) webView.getParent()).addView(videoViewContainer);
      webView.scrollTo(0,0);  // centers full screen view 
} else {
      activityNonVideoView.setVisibility(View.INVISIBLE);
      ViewGroup.LayoutParams vg = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                    ViewGroup.LayoutParams.MATCH_PARENT);
      activityVideoView.addView(videoViewContainer,vg);
      activityVideoView.setVisibility(View.VISIBLE);
}

You will also need to reflect the changes in onHideCustomView

Grux
  • 584
  • 1
  • 11
  • 20
0

It seems that in lollipop and up (or maybe just a different WebView Version) that calling cprcrack's onHideCustomView() method does not work. It works if it is called from the exit fullscreen button but when you specifically call the method it will only exit fullscreen but the webView stays blank. A way around it is to simply add these lines of code to onHideCustomView():

String js = "javascript:";
js += "var _ytrp_html5_video = document.getElementsByTagName('video')[0];";
js += "_ytrp_html5_video.webkitExitFullscreen();";
webView.loadUrl(js);

This will notify the webView that fullscreen has exited.

Michael Kern
  • 639
  • 8
  • 15