WebView 和 HTML5 < video >

我正在拼凑一个便宜的应用程序,除了其他事情“框架”我们的一些网站... 非常简单的 WebViewClient。直到我看到视频。

这个视频是以 HTML5元素的形式完成的,这些元素在 Chrome 和 iPhone 上运行良好,现在我们修复了编码问题,它在本地浏览器的 Android上运行良好。

现在的问题是: WebView不喜欢它。完全没有。我可以点击海报图片,什么都没有发生。

在谷歌上,我发现 this很接近,但似乎是基于一个“链接”(比如 href...)而不是一个视频元素。(onDownloadListener 似乎没有在视频元素上被调用...)

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

我宁愿不进入“从服务器拉 xml,在应用程序中重新格式化它”。.通过在服务器上保持故事布局,我可以更好地控制内容,而不必强迫人们不断更新应用程序。因此,如果我能说服 WebView 处理标签,比如本地浏览器,那将是最好的。

很明显我漏掉了什么,但我不知道是什么。

149999 次浏览

显然,如果不使用 JNI 注册一个插件来获取视频事件,这是不可能的。(就个人而言,我正在避免使用 JNI,因为我真的不想在未来几个月基于 Atom 的安卓平板电脑上市时处理一团糟的事情,因为它将失去 Java 的可移植性。)

唯一真正的替代方案似乎是为 WebView 创建一个新的网页,并以老式的方式使用上述 Codelark 网址中引用的 A HREF 链接进行视频。

在 WebView 中查看 video 元素(video html5标签)是可能的,但是我必须说我必须处理它几天。以下是我到目前为止必须遵循的步骤:

  • 找到一个正确编码的视频

  • 初始化 WebView 时,设置 JavaScript,插件 WebViewClient 和 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);
    
  • 处理 WebChromeClient 对象中的 onShowCustomView。

    @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();
    }
    }
    }
    
  • 处理视频的 onCompletion 和 onError 事件,以返回到 Web 视图。

    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();
    }
    

但是现在我应该说还有一个重要的问题。我只能弹一次。第二次我点击视频调度器(海报或播放按钮) ,它什么也没做。

我也希望视频播放在 WebView 框架内,而不是打开媒体播放器窗口,但这是我的次要问题。

Mdelolmo 的回答非常有帮助,但就像他说的,视频只播放一次,然后你就不能再打开它了。

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.

首先,我查看了 视像MediaPlayer的文档,对它们是如何工作的有了更好的了解。我强烈推荐。

然后,我看着 源代码,看看如何 Android 浏览器做到了这一点。做一个页面查找,去看看他们如何处理 onShowCustomView()。它们保留对 CustomViewCallback和自定义视图的引用。

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().

做完这两件事之后,你可以点击同一个视频或者其他任何视频,它就会像之前一样打开。不需要重新启动整个 WebView。

我知道这个帖子已经有好几个月了,但是我找到了一个解决方案,可以在 WebView 中播放视频,而不需要全屏(但是仍然在媒体播放器中...)。到目前为止,我没有在互联网上找到任何关于这方面的提示,所以也许这也是其他人感兴趣的。 我仍然在一些问题上挣扎(例如,把媒体播放器放在屏幕的正确部分,不知道为什么我做错了,但我认为这是一个相对较小的问题...)。

在自定义 ChromeClient 中指定 LayoutParams:

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

我的 onShowCustomView 方法如下:

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*.

我知道这是一个非常古老的问题,但是您是否尝试过为您的应用程序或活动使用 hardwareAccelerated="true"清单标志?

使用这个集合,它似乎不需要任何 WebChromeClient 修改(我期望从 DOM-Element 中得到修改)就可以工作

A-M 类似于 浏览活动。 for 布局参数 = 新的框架布局。布局参数(768,512) ;

我想我们可以利用

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

instead.

我遇到的另一个问题是,如果视频正在播放,用户点击后退按钮,下一次,你去这个活动(singleTop 之一) ,不能播放视频。为了解决这个问题,我打电话给

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

在活动的 onBackPress 方法中。

在蜂窝使用 hardwareaccelerated=truepluginstate.on_demand似乎工作

实际上,仅仅在客户端视图中附加一个股票 WebChromeClient 就足够了,ala

mWebView.setWebChromeClient(new WebChromeClient());

and you need to have hardware acceleration turned on!

至少,如果你不需要播放全屏视频,就不需要把 VideoView 从 WebView 中拉出来,然后把它放到活动视图中。它将在视频元素的分配正确播放。

有办法拦截扩展视频按钮吗?

这种方法在2.3版本之前运行良好 并且通过添加硬件加速 = true,它甚至可以在3.0到 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. 作为 VideoSurfaceView 对象,我们从3.0操作系统的 onShowCustomView 函数中获得,直到2.3操作系统,都是特定于浏览器而不是 VideoView 对象 我如何访问它,停止播放和释放资源?

我用 Html5webview解决了这个问题。下载并把它放到你的项目中,然后你就可以像这样编写代码了。

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);
}

要使视频可旋转,将 android: configChanges = “方向”代码放到 Activity 中 例如(AndroidManif.xml)

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

并重写 onConfigurationChanged 方法。

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

经过长时间的研究,我让这个东西工作起来了:

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);
}

自定义 _ 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 _ load _ 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>

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>

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>

期待其他你能理解的事情。

我也有过类似的问题,我的应用程序的资产文件夹里有 HTML 文件和视频。

因此,视频位于内部的 APK。因为 APK 实际上是一个 ZIP 文件,WebView 无法读取视频文件。

把所有的 HTML 和视频文件拷贝到 SD 卡上对我来说很有用。

这个问题已经有好几年了,但是也许我的回答能够帮助像我这样需要支持旧的 Android 版本的人。我尝试了很多不同的方法,这些方法在一些 Android 版本上有效,但不是所有的版本。我发现最好的解决方案是使用 人行横道网景,它针对 HTML5特性支持进行了优化,可以在 Android 4.1或更高版本上运行。它与默认的 Android WebView 一样易于使用。你只需要包括图书馆。在这里你可以找到一个关于如何使用它的简单教程: https://diego.org/2015/01/07/embedding-crosswalk-in-android-studio/