Alain Vanderbroeck Alain Vanderbroeck - 1 month ago 16
Android Question

Android webview video does not reach readyState=4

I am auto-playing a video in an Android Webview.
This gives problem with slow internet connections. I get the ANR popup (Application not responding)

I think it happens because the video receives a video.play() before the data is loaded. After 5 seconds the ANR popup appears.

What i want to do to solve this, is to wait for the video to be fully loaded, before calling video.play()

The problem is that the video in the Webview doesn't receive video.readystate == 4. It does never reach further as readystate 2. Even at normal internet connections.
In my Chrome browser it does receive readystate 4. So why is this? Why does the video in the Webview not receive readystate 4?

Below my code:

<!DOCTYPE html>
<html>
<head>
<title>Video</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script>
</head>
<body>
<div id="item" class="item">
<video preload="auto" autobuffer width="1024" height="576" id="video">
<source id="videomp4" src="http://www.quirksmode.org/html5/videos/big_buck_bunny.mp4" type='video/mp4' />
</video>
<script>
var interval;
$(document).ready(function () {
interval = setInterval('playVideoWhenReady()', 1000);
/*
// this doesn't work either
video.addEventListener('loadeddata', function() {
// Video is loaded and can be played
console.log("video play is triggered");
video.play();
}, false);
*/
});

function playVideoWhenReady() {
var video = document.getElementById('video');
console.log("!!!!! curstate: " + video.readyState);
if ( video.readyState == 4 ) { // this is never true in Android Webview
video.play();
clearInterval(interval);
}
}
</script>
</div>
</body>
</html>


MainActivity:

package net.eyefinder.www.testvideo;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebView;

public class MainActivity extends AppCompatActivity {

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

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

final WebSettings settings = webView.getSettings();
settings.setJavaScriptEnabled(true);
settings.setMediaPlaybackRequiresUserGesture(false);
settings.setUseWideViewPort(true);

webView.setWebChromeClient(new WebChromeClient());

webView.loadUrl("file:///android_asset/video.html");
}
}


and this it the manifest file:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="net.eyefinder.www.testvideo">

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:hardwareAccelerated="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<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" />

</manifest>


Then the log output is:

10-31 10:55:37.369 2181-2195/net.myapp.www.testvideo D/MediaPlayerPrivateAndroid: load url=http://www.quirksmode.org/html5/videos/big_buck_bunny.mp4
10-31 10:55:37.389 2181-2181/net.myapp.www.testvideo D/TilesManager: Starting TG #0, 0x67360d10
10-31 10:55:37.399 2181-2181/net.myapp.www.testvideo D/TilesManager: new EGLContext from framework: 63c6cd40
10-31 10:55:37.399 2181-2181/net.myapp.www.testvideo D/GLWebViewState: Reinit shader
10-31 10:55:37.429 2181-2181/net.myapp.www.testvideo D/GLWebViewState: Reinit transferQueue
10-31 10:55:37.449 2181-2181/net.myapp.www.testvideo D/VideoLayerManager: Reinit GLResource for VideoLayer
10-31 10:55:38.439 2181-2181/net.myapp.www.testvideo I/Web Console: !!!!! curstate: 2 at ../video.html:23
10-31 10:55:39.379 2181-2181/net.myapp.www.testvideo I/Web Console: !!!!! curstate: 2 at ../video.html:23
10-31 10:55:40.379 2181-2181/net.myapp.www.testvideo I/Web Console: !!!!! curstate: 2 at ../video.html:23


And it keeps hanging in curstate 2

Answer

After some research i came to the conclusion that there is no proper way to wait for a video before it is loaded fully in the Android Webview.

Of course, there are some solutions like:

<video preload="auto" autobuffer width="1024" height="576" id="video"></video>
<script>
    var interval;
    $(function () {
        var video = document.getElementById('video');

        var req = new XMLHttpRequest();
        req.open('GET', 'http://your-video-url.mp4', true);
        req.responseType = 'blob';

        req.onload = function() {
            // Onload is triggered even on 404
            // so we need to check the status code
            if (this.status === 200) {
                var videoBlob = req.response;
                var vid = (window.URL || window.webkitURL || window || {}).createObjectURL(videoBlob); // IE10+
                // Video is now downloaded
                // and we can set it as source on the video element
                video.src = vid;
                video.load();
            }
        }
        req.onerror = function() {
            // Error
        }

        req.send();

        video.addEventListener('progress', function() {
            var range = 0;
            var bf = this.buffered;
            var time = this.currentTime;

            while(!(bf.start(range) <= time && time <= bf.end(range))) {
                range += 1;
            }
            var loadStartPercentage = bf.start(range) / this.duration;
            var loadEndPercentage = bf.end(range) / this.duration;
            var loadPercentage = loadEndPercentage - loadStartPercentage;

            console.log("time: " + time);
            console.log("loadStartPercentage: " + loadStartPercentage);
            console.log("loadEndPercentage: " + loadEndPercentage);
            console.log("loadPercentage: " + loadPercentage);

            if ( loadPercentage == 1 ) {
                 video.play();
            }
        });
    });

BUT this solution does NOT work on Android 4.2 (regarding to Why can't my Android device play HTML5 video loaded as Blob via XHR?).

So i implemented a video downloader in Android and parse the local file URL to JavaScript. Now there is no need to wait for a video to be loaded.

My problem with slow connections is now solved!