I have an Activity
which is a WebView
. I have a WebChromeClient
inside it. Inside that, there are several callbacks which are meant to return the MediaPlayer
handling the video bits. For example:
@Override
public void onPrepared(MediaPlayer mp) {
Log.i(TAG, " -------------> onPrepared");
}
These fail to fire when I load an MP4 stream into the WebView
using HTML <video>
tags (via injection).
When I finish()
the activity, the logcat reports this:
09-13 23:55:24.590: E/MediaPlayer(7949): mOnBufferingUpdateListener is null. Failed to send MEDIA_BUFFERING_UPDATE message.
09-13 23:55:24.680: E/MediaPlayer(7949): mOnBufferingUpdateListener is null. Failed to send MEDIA_BUFFERING_UPDATE message.
09-13 23:55:24.680: E/MediaPlayer(7949): mOnVideoSizeChangedListener is null. Failed to send MEDIA_SET_VIDEO_SIZE message.
09-13 23:55:25.675: E/MediaPlayer(7949): mOnBufferingUpdateListener is null. Failed to send MEDIA_BUFFERING_UPDATE message.
09-13 23:55:26.735: E/MediaPlayer(7949): mOnBufferingUpdateListener is null. Failed to send MEDIA_BUFFERING_UPDATE message.
09-13 23:55:27.755: E/MediaPlayer(7949): mOnBufferingUpdateListener is null. Failed to send MEDIA_BUFFERING_UPDATE message.
09-13 23:55:28.705: E/MediaPlayer(7949): mOnBufferingUpdateListener is null. Failed to send MEDIA_BUFFERING_UPDATE message.
Try as I may, I can't get it to stop, even though the WebView
is cleared, and then destroyed. When loading video using <video>
tags, I don't know of any way to force the WebChromeClient
to use a particular MediaPlayer
that I create for it. It seems determined to use some hidden one, which reports the above. Is there a way to locate the MediaPlayer
created from via a <video>
tag on a WebView
?
--Update
Here is the code for initializing the WebView:
mWebView = new WebView(mContext);
mWebView.getSettings().setJavaScriptEnabled(true);
mWebView.getSettings().setPluginState(PluginState.OFF);
mWebView.setVisibility(View.INVISIBLE);
mWebView.getSettings().setDefaultZoom(WebSettings.ZoomDensity.FAR);
mWebView.getSettings().setBuiltInZoomControls(false);
mWebView.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE);
mWebView.getSettings().setLoadWithOverviewMode(true);
mWebView.getSettings().setUseWideViewPort(true);
mWebView.clearHistory();
mWebView.clearFormData();
mWebView.clearCache(true);
mWebView.getSettings().setAllowFileAccess(true);
mWebView.getSettings().setUserAgentString("Android Mozilla/5.0 AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30");
chrome = new MyWebChromeClient();
mWebView.setWebChromeClient(chrome);
wvc = new MyWebViewClient();
mWebView.setWebViewClient(wvc);
mDomain = "http://foo.bar.com";
mWebView.requestFocus(View.FOCUS_DOWN);
String meat = genMainHTML(R.raw.frame);
mWebView.loadDataWithBaseURL(mDomain, meat, "text/html", "utf-8", null);
The "frame" code is an iframe to launch some video (both Vimeo and YouTube seem to adopt this approach, at least). I have pruned this a bit to avoid cruft:
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<div>
<iframe src="-target.url.with.params-" frameborder="0" webkitAllowFullScreen mozallowfullscreen allowFullScreen>
</iframe>
</div>
</body>
</html>
Within that WebView class, there exists this class:
private class MyWebViewClient extends WebViewClient {
@Override
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
String injection = injectPageMonitor();
if(injection != null && !injectionComplete) {
// Log.i(TAG, " ---------------> Page Loaded . . .");
view.loadUrl(injection);
injectionComplete = true;
}
}
@Override
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
Log.d(TAG, "*** Error ["+description+"] ["+failingUrl+"]");
Toast.makeText(mContext, description, Toast.LENGTH_SHORT).show();
}
}
And also this class:
private class MyWebChromeClient extends WebChromeClient implements MediaPlayer.OnInfoListener, MediaPlayer.OnSeekCompleteListener, MediaPlayer.OnErrorListener, MediaPlayer.OnVideoSizeChangedListener, MediaPlayer.OnCompletionListener, MediaPlayer.OnPreparedListener, MediaPlayer.OnBufferingUpdateListener {
@Override
public void onProgressChanged(WebView view, int progress) {
//Log.e(TAG, " -------------> Progress Changed . . . . ["+progress+"] mWebView ["+mWebView+"] ["+view+"]");
if(progress == 100) {
// Do something really interesting
} else {
updateBuffering(UPDATE_PERCENT_LAUNCH_EXTRACTOR + (progress / 2));
}
}
@Override
public boolean onConsoleMessage(ConsoleMessage cm) {
// Spit out lots of console messages here
return(true);
}
}
And I have all the usual suspects overridden, just in case I get lucky and get some response out of the MediaPlayer:
@Override
public void onBufferingUpdate(MediaPlayer mp, int percent) {
// Jump up and down because we got a mp!
// never happens, so no jumping
}
As a side-note, I did dig around using reflection and got ahold of the MediaPlayer that way, but I always feel a bit ookey after doing that kind of thing. And even though I have the MediaPlayer object, I'm reluctant to just release it. A little studying of the source code for MP suggests that nothing bad would happen, but . . .
This does not happen on every device, I have noticed. HTC seems to behave better than Samsung, for example ("same" OS/API level -- I'm not really ambitious enough to compare the two source trees).
The behavior noted below is annoying, but only for me (who is watching the logcat output. For the user or application it seems to have no effect. It's just that I really dislike the notion that I'm kicking off a MediaPlayer instance, setting it busily caching away, and then (if/when the user terminates my app) leaving that dingleberry hanging out there. I have a lot of money tied up in therapy at this point, and it doesn't seem to be helping.
Thanks in advance.
There is no way to access the MediaPlayer without reflection - the WebViewClient and WebChromeClient do not intentionally expose it. And you are correct in saying that using reflection could cause you to break your app or the WebView as the code can vary across Android versions, and even across OEMs.
If you would like to do this, you will have to locate the instance of HTML5VideoView that corresponds to your video element. This object creates and maintains an instance to the MediaPlayer as "mPlayer". Please see https://github.com/android/platform_frameworks_base/blob/jb-mr2-release/core/java/android/webkit/HTML5VideoView.java.