WebView и HTML5 <video> - PullRequest
       51

WebView и HTML5 <video>

122 голосов
/ 28 сентября 2010

Я собираю дешевое приложение, которое, помимо прочего, «обрамляет» некоторые из наших веб-сайтов ... Довольно просто с WebViewClient. пока я не нажму на видео.

Видео выполнено в виде HTML5 элементов, и они прекрасно работают на Chrome, iPhone, и теперь, когда мы исправили проблемы с кодированием, оно прекрасно работает на Android в собственном браузере.

Теперь руб: WebView это не нравится. Совсем. Я могу нажать на изображение плаката, и ничего не происходит.

Гугл, я нашел этот , который близок, но, кажется, основан на «ссылке» (как в href ...) вместо элемента видео. (onDownloadListener не вызывается для элементов видео ...)

Я также вижу ссылки на переопределение onShowCustomView, но это, похоже, не вызывается для элементов видео ... и не должен OverrideUrlLoading ..

Я бы не стал "извлекать xml с сервера, переформатировать его в приложении" ... сохраняя макет истории на сервере, я могу немного лучше контролировать контент, не заставляя людей продолжать обновлять приложение. Так что, если я смогу убедить WebView работать с тегами, такими как собственный браузер, это было бы лучше.

Я явно упускаю что-то очевидное ... но я понятия не имею, что.

Ответы [ 13 ]

92 голосов
/ 07 октября 2010

Я отвечаю на эту тему на тот случай, если кто-то прочитает ее и заинтересован в результате. Можно просмотреть элемент видео (тег HTML html5) в WebView, но я должен сказать, что мне пришлось иметь дело с ним в течение нескольких дней. Вот шаги, которые я должен был выполнить до сих пор:

-Найдите правильно закодированное видео

-При инициализации 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);

-Отключите onShowCustomView в объекте WebChromeClient.

@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 для видео, чтобы вернуться к веб-представлению.

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 вместо открытия окна медиаплеера, но для меня это второстепенная проблема.

Я надеюсь, что это кому-нибудь поможет, и я также хотел бы поблагодарить за любой комментарий или предложение.

Saludos, terrícolas.

60 голосов
/ 06 июня 2012

После долгих исследований у меня все заработало.См. Следующий код:

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>

Ожидая остальных вещей, которые вы можете понять.

33 голосов
/ 09 января 2011

Ответ mdelolmo был невероятно полезным, но, как он сказал, видео воспроизводится только один раз, и вы не можете открыть его снова.

Я немного разбирался в этом, и вот что нашел, на случай, если какие-нибудь уставшие путешественники WebView, такие как я, натолкнутся на этот пост в будущем.

Прежде всего, я посмотрел документацию VideoView и MediaPlayer и понял, как они работают. Я настоятельно рекомендую их.

Затем я посмотрел на исходный код , чтобы увидеть, как это делает браузер Android . Найдите страницу и посмотрите, как они справляются onShowCustomView(). Они сохраняют ссылку на CustomViewCallback и на пользовательский вид.

Учитывая все это и имея в виду ответ mdelolmo, когда вы закончите с видео, все, что вам нужно сделать, это две вещи. Во-первых, на VideoView, на который вы сохранили ссылку, вызовите stopPlayback(), который высвободит MediaPlayer для последующего использования в другом месте. Вы можете увидеть это в исходном коде VideoView . Во-вторых, на CustomViewCallback вы сохранили ссылку на звонок CustomViewCallback.onCustomViewHidden().

После выполнения этих двух действий вы можете нажать на то же видео или любое другое видео, и оно откроется, как и раньше. Нет необходимости перезагружать весь WebView.

Надеюсь, это поможет.

8 голосов
/ 09 марта 2012

На самом деле, кажется достаточно просто подключить стандартный WebChromeClient к клиентскому представлению, аля

mWebView.setWebChromeClient(new WebChromeClient());

и вам нужно включить аппаратное ускорение!

По крайней мере, если вам не нужно воспроизводить полноэкранное видео, нет необходимости извлекать VideoView из WebView и вставлять его в представление Activity. Он будет воспроизводиться в выделенном прямоугольнике видеоэлемента.

Есть идеи, как перехватить кнопку расширения видео?

7 голосов
/ 09 августа 2011

Я знаю, что этой теме уже несколько месяцев, но я нашел решение для воспроизведения видео внутри 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();
         }
     }
 }

Итак, я надеюсь, что смогу помочь ... Мне тоже пришлось поиграться с кодированием видео и увидеть различные виды использования WebView с видео html5 - в конце мой рабочий код представлял собой дикое сочетание различных частей кода Я нашел в Интернете и некоторые вещи, которые я должен был выяснить сам. Это действительно была боль в а *.

4 голосов
/ 11 марта 2012

Этот подход хорошо работает до 2.3 И добавив аппаратное ускорение = true, он работает даже с 3.0 до ICS Одна проблема, с которой я сталкиваюсь в настоящее время, заключается в том, что при втором запуске приложения медиаплеера происходит сбой, поскольку я не остановил воспроизведение и не выпустил медиаплеер. Поскольку объект VideoSurfaceView, который мы получаем в функции onShowCustomView из ОС 3.0, специфичен для браузера, а не для объекта VideoView, как в ОС до версии 2.3 Как я могу получить к нему доступ, остановить воспроизведение и освободить ресурсы?

3 голосов
/ 16 декабря 2011

A-M аналогичен тому, что делает BrowerActivity . за FrameLayout.LayoutParams LayoutParameters = new FrameLayout.LayoutParams (768, 512);

Я думаю, что мы можем использовать

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

вместо.

Еще одна проблема, с которой я столкнулся, - это то, что видео воспроизводится, и пользователь нажимает кнопку «Назад». В следующий раз вы перейдете к этому действию (одиночное) и не сможете воспроизвести видео. чтобы это исправить, я позвонил

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

в методе действия onBackPressed.

2 голосов
/ 05 октября 2016

Этому вопросу уже много лет, но, возможно, мой ответ поможет таким людям, как я, которые должны поддерживать старую версию Android.Я пробовал много разных подходов, которые работали на некоторых версиях Android, но не на всех.Лучшее решение, которое я нашел, это использовать Crosswalk Webview , который оптимизирован для поддержки функций HTML5 и работает на Android 4.1 и выше.Он так же прост в использовании, как Android WebView по умолчанию.Вы просто должны включить библиотеку.Здесь вы можете найти простое руководство по его использованию: https://diego.org/2015/01/07/embedding-crosswalk-in-android-studio/

2 голосов
/ 18 апреля 2012

Я использовал 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 = "direction" в свою активность например (Androidmanifest.xml)

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

и переопределить метод onConfigurationChanged.

@Override
public void onConfigurationChanged(Configuration newConfig) {
     super.onConfigurationChanged(newConfig);
}
2 голосов
/ 03 ноября 2011

Я знаю, что это очень старый вопрос, но вы пробовали флаг манифеста hardwareAccelerated="true" для своего приложения или действия?

С этим набором, похоже, он работает без какой-либо модификации WebChromeClient (что я хотел быожидать от DOM-элемента.)

...