diff options
author | Nicolas Roard <nicolas@android.com> | 2010-02-02 13:11:32 +0000 |
---|---|---|
committer | Ben Murdoch <benm@google.com> | 2010-10-18 13:07:05 +0100 |
commit | 3d57c253bfa22c17c5f38a70e162a6115a1809b6 (patch) | |
tree | b17bd2355f8f57bd81cdac419a2c362194fd9ab1 /WebKit | |
parent | 5013a3fb99f9ca1c7be4319cdebc7cddcaa56d7c (diff) | |
download | external_webkit-3d57c253bfa22c17c5f38a70e162a6115a1809b6.zip external_webkit-3d57c253bfa22c17c5f38a70e162a6115a1809b6.tar.gz external_webkit-3d57c253bfa22c17c5f38a70e162a6115a1809b6.tar.bz2 |
Implement the audio tag in webkit -- the corresponding java CL is https://android-git.corp.google.com/g/#change,41406
Cherry pick to Gingerbread, DO NOT MERGE!
Bug:3101402
Change-Id: Idbfb0efcc777f9354fcf88df32105ca9e50a24cd
Diffstat (limited to 'WebKit')
-rw-r--r-- | WebKit/Android.mk | 1 | ||||
-rw-r--r-- | WebKit/android/RenderSkinAndroid.cpp | 4 | ||||
-rw-r--r-- | WebKit/android/RenderSkinMediaButton.cpp | 171 | ||||
-rw-r--r-- | WebKit/android/RenderSkinMediaButton.h | 61 | ||||
-rw-r--r-- | WebKit/android/WebCoreSupport/MediaPlayerPrivateAndroid.cpp | 538 | ||||
-rw-r--r-- | WebKit/android/jni/WebCoreJniOnLoad.cpp | 6 |
6 files changed, 535 insertions, 246 deletions
diff --git a/WebKit/Android.mk b/WebKit/Android.mk index 2c12ed3..47f37ca 100644 --- a/WebKit/Android.mk +++ b/WebKit/Android.mk @@ -31,6 +31,7 @@ LOCAL_SRC_FILES := \ android/RenderSkinAndroid.cpp \ android/RenderSkinButton.cpp \ android/RenderSkinCombo.cpp \ + android/RenderSkinMediaButton.cpp \ android/RenderSkinRadio.cpp \ android/TimeCounter.cpp \ \ diff --git a/WebKit/android/RenderSkinAndroid.cpp b/WebKit/android/RenderSkinAndroid.cpp index d148262..00f2b96 100644 --- a/WebKit/android/RenderSkinAndroid.cpp +++ b/WebKit/android/RenderSkinAndroid.cpp @@ -29,6 +29,7 @@ #include "RenderSkinAndroid.h" #include "RenderSkinButton.h" #include "RenderSkinCombo.h" +#include "RenderSkinMediaButton.h" #include "RenderSkinRadio.h" #include "SkImageDecoder.h" @@ -45,7 +46,8 @@ RenderSkinAndroid::RenderSkinAndroid() void RenderSkinAndroid::Init(android::AssetManager* am, String drawableDirectory) { RenderSkinButton::Init(am, drawableDirectory); - RenderSkinCombo::Init(am); + RenderSkinCombo::Init(am, drawableDirectory); + RenderSkinMediaButton::Init(am, drawableDirectory); RenderSkinRadio::Init(am, drawableDirectory); } diff --git a/WebKit/android/RenderSkinMediaButton.cpp b/WebKit/android/RenderSkinMediaButton.cpp new file mode 100644 index 0000000..149be4e --- /dev/null +++ b/WebKit/android/RenderSkinMediaButton.cpp @@ -0,0 +1,171 @@ +/* + * Copyright 2010, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#define LOG_TAG "WebCore" + +#include "config.h" +#include "CString.h" +#include "android_graphics.h" +#include "Document.h" +#include "IntRect.h" +#include "Node.h" +#include "RenderSkinMediaButton.h" +#include "SkCanvas.h" +#include "SkNinePatch.h" +#include "SkRect.h" +#include <utils/Debug.h> +#include <utils/Log.h> + +struct PatchData { + const char* name; + int8_t outset, margin; +}; + +static const PatchData gFiles[] = + { + { "btn_media_player.9.png", 0, 0 }, // DEFAULT BGD BUTTON + { "ic_media_pause.png", 0, 0}, // PAUSE + { "ic_media_play.png", 0, 0 }, // PLAY + { "ic_media_pause.png", 0, 0 }, // MUTE + { "ic_media_rew.png", 0, 0 }, // REWIND + { "ic_media_ff.png", 0, 0 }, // FORWARD + { "btn_media_player_disabled.9.png", 0, 0 }, // BACKGROUND_SLIDER + { "btn_media_player_pressed.9.png", 0, 0 }, // SLIDER_TRACK + { "btn_media_player.9.png", 0, 0 } // SLIDER_THUMB + }; + +static SkBitmap gButton[sizeof(gFiles)/sizeof(gFiles[0])]; +static bool gDecoded; +static bool gHighRes; + +namespace WebCore { + +void RenderSkinMediaButton::Init(android::AssetManager* am, String drawableDirectory) +{ + static bool gInited; + if (gInited) + return; + + gInited = true; + gDecoded = true; + gHighRes = drawableDirectory[drawableDirectory.length() - 5] == 'h'; + for (size_t i = 0; i < sizeof(gFiles)/sizeof(gFiles[0]); i++) { + String path = drawableDirectory + gFiles[i].name; + if (!RenderSkinAndroid::DecodeBitmap(am, path.utf8().data(), &gButton[i])) { + gDecoded = false; + LOGD("RenderSkinButton::Init: button assets failed to decode\n\tBrowser buttons will not draw"); + break; + } + } +} + +void RenderSkinMediaButton::Draw(SkCanvas* canvas, const IntRect& r, int buttonType) +{ + // If we failed to decode, do nothing. This way the browser still works, + // and webkit will still draw the label and layout space for us. + if (!gDecoded) { + return; + } + + bool drawsNinePatch = true; + bool drawsImage = true; + bool drawsBackgroundColor = false; + + int ninePatchIndex = 0; + int imageIndex = 0; + + SkRect bounds(r); + SkScalar imageMargin = 8; + SkPaint paint; + SkColor backgroundColor = SkColorSetARGB(255, 200, 200, 200); + SkColor trackBackgroundColor = SkColorSetARGB(255, 100, 100, 100); + + switch (buttonType) { + case PAUSE: + case PLAY: + case MUTE: + case REWIND: + case FORWARD: + { + imageIndex = buttonType + 1; + drawsBackgroundColor = true; + paint.setColor(backgroundColor); + break; + } + case BACKGROUND_SLIDER: + { + drawsImage = false; + drawsNinePatch = false; + drawsBackgroundColor = true; + paint.setColor(backgroundColor); + break; + } + case SLIDER_TRACK: + { + drawsImage = false; + drawsNinePatch = false; + drawsBackgroundColor = true; + paint.setColor(trackBackgroundColor); + bounds.fTop += 8; + bounds.fBottom -= 8; + break; + } + case SLIDER_THUMB: + { + drawsImage = false; + ninePatchIndex = buttonType + 1; + break; + } + default: + drawsImage = false; + drawsNinePatch = false; + } + + if (drawsBackgroundColor) { + canvas->drawRect(r, paint); + } + + if (drawsNinePatch) { + const PatchData& pd = gFiles[ninePatchIndex]; + int marginValue = pd.margin + pd.outset; + + SkIRect margin; + margin.set(marginValue, marginValue, marginValue, marginValue); + SkNinePatch::DrawNine(canvas, bounds, gButton[0], margin); + } + + if (drawsImage) { + SkScalar SIZE = gButton[imageIndex].width(); + SkScalar width = r.width(); + SkScalar scale = SkScalarDiv(width - 2*imageMargin, SIZE); + int saveScaleCount = canvas->save(); + canvas->translate(bounds.fLeft + imageMargin, bounds.fTop + imageMargin); + canvas->scale(scale, scale); + canvas->drawBitmap(gButton[imageIndex], 0, 0, &paint); + canvas->restoreToCount(saveScaleCount); + } +} + +} //WebCore diff --git a/WebKit/android/RenderSkinMediaButton.h b/WebKit/android/RenderSkinMediaButton.h new file mode 100644 index 0000000..b4e99f4 --- /dev/null +++ b/WebKit/android/RenderSkinMediaButton.h @@ -0,0 +1,61 @@ +/* + * Copyright 2010, The Android Open Source Project + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef RenderSkinMediaButton_h +#define RenderSkinMediaButton_h + +#include "RenderSkinAndroid.h" + +class SkCanvas; + +namespace WebCore { +class IntRect; +class RenderSkinMediaButton +{ +public: + /** + * Initialize the class before use. Uses the AssetManager to initialize any + * bitmaps the class may use. + */ + static void Init(android::AssetManager*, String drawableDirectory); + /** + * Draw the skin to the canvas, using the rectangle for its bounds and the + * State to determine which skin to use, i.e. focused or not focused. + */ + static void Draw(SkCanvas* , const IntRect& , int buttonType); + /** + * Button types + */ + enum { PAUSE, PLAY, MUTE, REWIND, FORWARD, BACKGROUND_SLIDER, SLIDER_TRACK, SLIDER_THUMB }; + /** + * Slider dimensions + */ + static int sliderThumbWidth() { return 10; } + static int sliderThumbHeight() { return 30; } + +}; + +} // WebCore +#endif // RenderSkinMediaButton_h diff --git a/WebKit/android/WebCoreSupport/MediaPlayerPrivateAndroid.cpp b/WebKit/android/WebCoreSupport/MediaPlayerPrivateAndroid.cpp index 3767516..b066511 100644 --- a/WebKit/android/WebCoreSupport/MediaPlayerPrivateAndroid.cpp +++ b/WebKit/android/WebCoreSupport/MediaPlayerPrivateAndroid.cpp @@ -30,7 +30,6 @@ #include "GraphicsContext.h" #include "SkiaUtils.h" -#include "TimeRanges.h" #include "WebCoreJni.h" #include "WebViewCore.h" @@ -44,16 +43,22 @@ using namespace android; namespace WebCore { static const char* g_ProxyJavaClass = "android/webkit/HTML5VideoViewProxy"; +static const char* g_ProxyJavaClassAudio = "android/webkit/HTML5Audio"; struct MediaPlayerPrivate::JavaGlue { jobject m_javaProxy; - jmethodID m_getInstance; jmethodID m_play; jmethodID m_teardown; - jmethodID m_loadPoster; jmethodID m_seek; jmethodID m_pause; + // Audio + jmethodID m_newInstance; + jmethodID m_setDataSource; + jmethodID m_getMaxTimeSeekable; + // Video + jmethodID m_getInstance; + jmethodID m_loadPoster; }; MediaPlayerPrivate::~MediaPlayerPrivate() @@ -65,7 +70,6 @@ MediaPlayerPrivate::~MediaPlayerPrivate() env->DeleteGlobalRef(m_glue->m_javaProxy); } } - delete m_glue; } @@ -74,29 +78,6 @@ void MediaPlayerPrivate::registerMediaEngine(MediaEngineRegistrar registrar) registrar(create, getSupportedTypes, supportsType); } -void MediaPlayerPrivate::load(const String& url) -{ - // Just save the URl. - m_url = url; -} - -void MediaPlayerPrivate::cancelLoad() -{ -} - -void MediaPlayerPrivate::play() -{ - JNIEnv* env = JSC::Bindings::getJNIEnv(); - if (!env || !m_glue->m_javaProxy || !m_url.length()) - return; - - m_paused = false; - jstring jUrl = env->NewString((unsigned short *)m_url.characters(), m_url.length()); - env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_play, jUrl); - env->DeleteLocalRef(jUrl); - checkException(env); -} - void MediaPlayerPrivate::pause() { JNIEnv* env = JSC::Bindings::getJNIEnv(); @@ -108,22 +89,6 @@ void MediaPlayerPrivate::pause() checkException(env); } -IntSize MediaPlayerPrivate::naturalSize() const -{ - return m_naturalSize; -} - -bool MediaPlayerPrivate::hasAudio() const -{ - // TODO - return false; -} - -bool MediaPlayerPrivate::hasVideo() const -{ - return m_hasVideo; -} - void MediaPlayerPrivate::setVisible(bool visible) { m_isVisible = visible; @@ -131,99 +96,19 @@ void MediaPlayerPrivate::setVisible(bool visible) createJavaPlayerIfNeeded(); } -float MediaPlayerPrivate::duration() const -{ - return m_duration; -} - -float MediaPlayerPrivate::currentTime() const -{ - return m_currentTime; -} - void MediaPlayerPrivate::seek(float time) { JNIEnv* env = JSC::Bindings::getJNIEnv(); - if (!env || !m_glue->m_javaProxy || !m_url.length()) + if (!env || !m_url.length()) return; - m_currentTime = time; - env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_seek, static_cast<jint>(time * 1000.0f)); + if (m_glue->m_javaProxy) { + env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_seek, static_cast<jint>(time * 1000.0f)); + m_currentTime = time; + } checkException(env); } -bool MediaPlayerPrivate::seeking() const -{ - return false; -} - -void MediaPlayerPrivate::setEndTime(float) -{ -} - -void MediaPlayerPrivate::setRate(float) -{ -} - -bool MediaPlayerPrivate::paused() const -{ - return m_paused; -} - -void MediaPlayerPrivate::setVolume(float) -{ -} - -MediaPlayer::NetworkState MediaPlayerPrivate::networkState() const -{ - return m_networkState; -} - -MediaPlayer::ReadyState MediaPlayerPrivate::readyState() const -{ - return m_readyState; -} - -float MediaPlayerPrivate::maxTimeSeekable() const -{ - return 0; -} - -PassRefPtr<TimeRanges> MediaPlayerPrivate::buffered() const -{ - return TimeRanges::create(); -} - -int MediaPlayerPrivate::dataRate() const -{ - return 0; -} - -unsigned MediaPlayerPrivate::totalBytes() const -{ - return 0; -} - -unsigned MediaPlayerPrivate::bytesLoaded() const -{ - return 0; -} - -void MediaPlayerPrivate::setSize(const IntSize&) -{ -} - -void MediaPlayerPrivate::setPoster(const String& url) -{ - m_posterUrl = url; - JNIEnv* env = JSC::Bindings::getJNIEnv(); - if (!env || !m_glue->m_javaProxy || !m_posterUrl.length()) - return; - // Send the poster - jstring jUrl = env->NewString((unsigned short *)m_posterUrl.characters(), m_posterUrl.length()); - env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_loadPoster, jUrl); - env->DeleteLocalRef(jUrl); -} void MediaPlayerPrivate::prepareToPlay() { // We are about to start playing. Since our Java VideoView cannot @@ -237,40 +122,6 @@ void MediaPlayerPrivate::prepareToPlay() { m_player->readyStateChanged(); } -void MediaPlayerPrivate::paint(GraphicsContext* ctxt, const IntRect& r) -{ - if (ctxt->paintingDisabled()) - return; - - if (!m_isVisible) - return; - - if (!m_poster || (!m_poster->getPixels() && !m_poster->pixelRef())) - return; - - SkCanvas* canvas = ctxt->platformContext()->mCanvas; - // We paint with the following rules in mind: - // - only downscale the poster, never upscale - // - maintain the natural aspect ratio of the poster - // - the poster should be centered in the target rect - float originalRatio = static_cast<float>(m_poster->width()) / static_cast<float>(m_poster->height()); - int posterWidth = r.width() > m_poster->width() ? m_poster->width() : r.width(); - int posterHeight = posterWidth / originalRatio; - int posterX = ((r.width() - posterWidth) / 2) + r.x(); - int posterY = ((r.height() - posterHeight) / 2) + r.y(); - IntRect targetRect(posterX, posterY, posterWidth, posterHeight); - canvas->drawBitmapRect(*m_poster, 0, targetRect, 0); -} - -MediaPlayerPrivateInterface* MediaPlayerPrivate::create(MediaPlayer* player) -{ - return new MediaPlayerPrivate(player); -} - -void MediaPlayerPrivate::getSupportedTypes(HashSet<String>&) -{ -} - MediaPlayer::SupportsType MediaPlayerPrivate::supportsType(const String& type, const String& codecs) { if (WebViewCore::supportsMimeType(type)) @@ -292,72 +143,6 @@ MediaPlayerPrivate::MediaPlayerPrivate(MediaPlayer* player) m_naturalSizeUnknown(true), m_isVisible(false) { - JNIEnv* env = JSC::Bindings::getJNIEnv(); - if (!env) - return; - - jclass clazz = env->FindClass(g_ProxyJavaClass); - if (!clazz) - return; - - m_glue = new JavaGlue; - m_glue->m_getInstance = env->GetStaticMethodID(clazz, "getInstance", "(Landroid/webkit/WebViewCore;I)Landroid/webkit/HTML5VideoViewProxy;"); - m_glue->m_play = env->GetMethodID(clazz, "play", "(Ljava/lang/String;)V"); - m_glue->m_teardown = env->GetMethodID(clazz, "teardown", "()V"); - m_glue->m_loadPoster = env->GetMethodID(clazz, "loadPoster", "(Ljava/lang/String;)V"); - m_glue->m_seek = env->GetMethodID(clazz, "seek", "(I)V"); - m_glue->m_pause = env->GetMethodID(clazz, "pause", "()V"); - m_glue->m_javaProxy = NULL; - env->DeleteLocalRef(clazz); - // An exception is raised if any of the above fails. - checkException(env); -} - -void MediaPlayerPrivate::createJavaPlayerIfNeeded() -{ - // Check if we have been already created. - if (m_glue->m_javaProxy) - return; - - FrameView* frameView = m_player->frameView(); - if (!frameView) - return; - - JNIEnv* env = JSC::Bindings::getJNIEnv(); - if (!env) - return; - - jclass clazz = env->FindClass(g_ProxyJavaClass); - if (!clazz) - return; - - WebViewCore* webViewCore = WebViewCore::getWebViewCore(frameView); - ASSERT(webViewCore); - - // Get the HTML5VideoViewProxy instance - jobject obj = env->CallStaticObjectMethod(clazz, m_glue->m_getInstance, webViewCore->getJavaObject().get(), this); - m_glue->m_javaProxy = env->NewGlobalRef(obj); - // Send the poster - jstring jUrl = 0; - if (m_posterUrl.length()) - jUrl = env->NewString((unsigned short *)m_posterUrl.characters(), m_posterUrl.length()); - // Sending a NULL jUrl allows the Java side to try to load the default poster. - env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_loadPoster, jUrl); - if (jUrl) - env->DeleteLocalRef(jUrl); - // Clean up. - env->DeleteLocalRef(obj); - env->DeleteLocalRef(clazz); - checkException(env); -} - -void MediaPlayerPrivate::onPrepared(int duration, int width, int height) { - m_duration = duration / 1000.0f; - m_naturalSize = IntSize(width, height); - m_naturalSizeUnknown = false; - m_hasVideo = true; - m_player->durationChanged(); - m_player->sizeChanged(); } void MediaPlayerPrivate::onEnded() { @@ -370,25 +155,268 @@ void MediaPlayerPrivate::onEnded() { m_readyState = MediaPlayer::HaveNothing; } -void MediaPlayerPrivate::onPosterFetched(SkBitmap* poster) { - m_poster = poster; - if (m_naturalSizeUnknown) { - // We had to fake the size at startup, or else our paint - // method would not be called. If we haven't yet received - // the onPrepared event, update the intrinsic size to the size - // of the poster. That will be overriden when onPrepare comes. - // In case of an error, we should report the poster size, rather - // than our initial fake value. - m_naturalSize = IntSize(poster->width(), poster->height()); - m_player->sizeChanged(); - } -} - void MediaPlayerPrivate::onTimeupdate(int position) { m_currentTime = position / 1000.0f; m_player->timeChanged(); } +class MediaPlayerVideoPrivate : public MediaPlayerPrivate { +public: + void load(const String& url) { m_url = url; } + void play() { + JNIEnv* env = JSC::Bindings::getJNIEnv(); + if (!env || !m_url.length() || !m_glue->m_javaProxy) + return; + + m_paused = false; + jstring jUrl = env->NewString((unsigned short *)m_url.characters(), m_url.length()); + env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_play, jUrl); + env->DeleteLocalRef(jUrl); + + checkException(env); + } + bool canLoadPoster() const { return true; } + void setPoster(const String& url) { + m_posterUrl = url; + JNIEnv* env = JSC::Bindings::getJNIEnv(); + if (!env || !m_glue->m_javaProxy || !m_posterUrl.length()) + return; + // Send the poster + jstring jUrl = env->NewString((unsigned short *)m_posterUrl.characters(), m_posterUrl.length()); + env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_loadPoster, jUrl); + env->DeleteLocalRef(jUrl); + } + void paint(GraphicsContext* ctxt, const IntRect& r) { + if (ctxt->paintingDisabled()) + return; + + if (!m_isVisible) + return; + + if (!m_poster || (!m_poster->getPixels() && !m_poster->pixelRef())) + return; + + SkCanvas* canvas = ctxt->platformContext()->mCanvas; + // We paint with the following rules in mind: + // - only downscale the poster, never upscale + // - maintain the natural aspect ratio of the poster + // - the poster should be centered in the target rect + float originalRatio = static_cast<float>(m_poster->width()) / static_cast<float>(m_poster->height()); + int posterWidth = r.width() > m_poster->width() ? m_poster->width() : r.width(); + int posterHeight = posterWidth / originalRatio; + int posterX = ((r.width() - posterWidth) / 2) + r.x(); + int posterY = ((r.height() - posterHeight) / 2) + r.y(); + IntRect targetRect(posterX, posterY, posterWidth, posterHeight); + canvas->drawBitmapRect(*m_poster, 0, targetRect, 0); + } + + void onPosterFetched(SkBitmap* poster) { + m_poster = poster; + if (m_naturalSizeUnknown) { + // We had to fake the size at startup, or else our paint + // method would not be called. If we haven't yet received + // the onPrepared event, update the intrinsic size to the size + // of the poster. That will be overriden when onPrepare comes. + // In case of an error, we should report the poster size, rather + // than our initial fake value. + m_naturalSize = IntSize(poster->width(), poster->height()); + m_player->sizeChanged(); + } + } + + void onPrepared(int duration, int width, int height) { + m_duration = duration / 1000.0f; + m_naturalSize = IntSize(width, height); + m_naturalSizeUnknown = false; + m_hasVideo = true; + m_player->durationChanged(); + m_player->sizeChanged(); + } + + bool hasAudio() { return false; } // do not display the audio UI + bool hasVideo() { return m_hasVideo; } + + MediaPlayerVideoPrivate(MediaPlayer* player) : MediaPlayerPrivate(player) { + JNIEnv* env = JSC::Bindings::getJNIEnv(); + if (!env) + return; + + jclass clazz = env->FindClass(g_ProxyJavaClass); + + if (!clazz) + return; + + m_glue = new JavaGlue; + m_glue->m_getInstance = env->GetStaticMethodID(clazz, "getInstance", "(Landroid/webkit/WebViewCore;I)Landroid/webkit/HTML5VideoViewProxy;"); + m_glue->m_loadPoster = env->GetMethodID(clazz, "loadPoster", "(Ljava/lang/String;)V"); + m_glue->m_play = env->GetMethodID(clazz, "play", "(Ljava/lang/String;)V"); + + m_glue->m_teardown = env->GetMethodID(clazz, "teardown", "()V"); + m_glue->m_seek = env->GetMethodID(clazz, "seek", "(I)V"); + m_glue->m_pause = env->GetMethodID(clazz, "pause", "()V"); + m_glue->m_javaProxy = NULL; + env->DeleteLocalRef(clazz); + // An exception is raised if any of the above fails. + checkException(env); + } + + void createJavaPlayerIfNeeded() { + // Check if we have been already created. + if (m_glue->m_javaProxy) + return; + + JNIEnv* env = JSC::Bindings::getJNIEnv(); + if (!env) + return; + + jclass clazz = env->FindClass(g_ProxyJavaClass); + + if (!clazz) + return; + + jobject obj = NULL; + + FrameView* frameView = m_player->frameView(); + if (!frameView) + return; + WebViewCore* webViewCore = WebViewCore::getWebViewCore(frameView); + ASSERT(webViewCore); + + // Get the HTML5VideoViewProxy instance + obj = env->CallStaticObjectMethod(clazz, m_glue->m_getInstance, webViewCore->getJavaObject().get(), this); + m_glue->m_javaProxy = env->NewGlobalRef(obj); + // Send the poster + jstring jUrl = 0; + if (m_posterUrl.length()) + jUrl = env->NewString((unsigned short *)m_posterUrl.characters(), m_posterUrl.length()); + // Sending a NULL jUrl allows the Java side to try to load the default poster. + env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_loadPoster, jUrl); + if (jUrl) + env->DeleteLocalRef(jUrl); + + // Clean up. + if (obj) + env->DeleteLocalRef(obj); + env->DeleteLocalRef(clazz); + checkException(env); + } +}; + +class MediaPlayerAudioPrivate : public MediaPlayerPrivate { +public: + void load(const String& url) { + m_url = url; + JNIEnv* env = JSC::Bindings::getJNIEnv(); + if (!env || !m_url.length()) + return; + + createJavaPlayerIfNeeded(); + + if (!m_glue->m_javaProxy) + return; + + jstring jUrl = env->NewString((unsigned short *)m_url.characters(), m_url.length()); + // start loading the data asynchronously + env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_setDataSource, jUrl); + env->DeleteLocalRef(jUrl); + checkException(env); + } + + void play() { + JNIEnv* env = JSC::Bindings::getJNIEnv(); + if (!env || !m_url.length()) + return; + + createJavaPlayerIfNeeded(); + + if (!m_glue->m_javaProxy) + return; + + env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_play); + checkException(env); + } + + bool hasAudio() { return true; } + + float maxTimeSeekable() const { + if (m_glue->m_javaProxy) { + JNIEnv* env = JSC::Bindings::getJNIEnv(); + if (env) { + float maxTime = env->CallFloatMethod(m_glue->m_javaProxy, + m_glue->m_getMaxTimeSeekable); + checkException(env); + return maxTime; + } + } + return 0; + } + + MediaPlayerAudioPrivate(MediaPlayer* player) : MediaPlayerPrivate(player) { + JNIEnv* env = JSC::Bindings::getJNIEnv(); + if (!env) + return; + + jclass clazz = env->FindClass(g_ProxyJavaClassAudio); + + if (!clazz) + return; + + m_glue = new JavaGlue; + m_glue->m_newInstance = env->GetMethodID(clazz, "<init>", "(I)V"); + m_glue->m_setDataSource = env->GetMethodID(clazz, "setDataSource", "(Ljava/lang/String;)V"); + m_glue->m_play = env->GetMethodID(clazz, "play", "()V"); + m_glue->m_getMaxTimeSeekable = env->GetMethodID(clazz, "getMaxTimeSeekable", "()F"); + m_glue->m_teardown = env->GetMethodID(clazz, "teardown", "()V"); + m_glue->m_seek = env->GetMethodID(clazz, "seek", "(I)V"); + m_glue->m_pause = env->GetMethodID(clazz, "pause", "()V"); + m_glue->m_javaProxy = NULL; + env->DeleteLocalRef(clazz); + // An exception is raised if any of the above fails. + checkException(env); + } + + void createJavaPlayerIfNeeded() { + // Check if we have been already created. + if (m_glue->m_javaProxy) + return; + + JNIEnv* env = JSC::Bindings::getJNIEnv(); + if (!env) + return; + + jclass clazz = env->FindClass(g_ProxyJavaClassAudio); + + if (!clazz) + return; + + jobject obj = NULL; + + // Get the HTML5Audio instance + obj = env->NewObject(clazz, m_glue->m_newInstance, this); + m_glue->m_javaProxy = env->NewGlobalRef(obj); + + // Clean up. + if (obj) + env->DeleteLocalRef(obj); + env->DeleteLocalRef(clazz); + checkException(env); + } + + void onPrepared(int duration, int width, int height) { + m_duration = duration / 1000.0f; + m_player->durationChanged(); + m_player->sizeChanged(); + m_player->prepareToPlay(); + } +}; + +MediaPlayerPrivateInterface* MediaPlayerPrivate::create(MediaPlayer* player) +{ + if (player->mediaElementType() == MediaPlayer::Video) + return new MediaPlayerVideoPrivate(player); + return new MediaPlayerAudioPrivate(player); +} + } namespace android { @@ -418,6 +446,13 @@ static void OnPosterFetched(JNIEnv* env, jobject obj, jobject poster, int pointe player->onPosterFetched(posterNative); } +static void OnBuffering(JNIEnv* env, jobject obj, int percent, int pointer) { + if (pointer) { + WebCore::MediaPlayerPrivate* player = reinterpret_cast<WebCore::MediaPlayerPrivate*>(pointer); + //TODO: player->onBuffering(percent); + } +} + static void OnTimeupdate(JNIEnv* env, jobject obj, int position, int pointer) { if (pointer) { WebCore::MediaPlayerPrivate* player = reinterpret_cast<WebCore::MediaPlayerPrivate*>(pointer); @@ -439,11 +474,28 @@ static JNINativeMethod g_MediaPlayerMethods[] = { (void*) OnTimeupdate }, }; -int register_mediaplayer(JNIEnv* env) +static JNINativeMethod g_MediaAudioPlayerMethods[] = { + { "nativeOnBuffering", "(II)V", + (void*) OnBuffering }, + { "nativeOnEnded", "(I)V", + (void*) OnEnded }, + { "nativeOnPrepared", "(IIII)V", + (void*) OnPrepared }, + { "nativeOnTimeupdate", "(II)V", + (void*) OnTimeupdate }, +}; + +int register_mediaplayer_video(JNIEnv* env) { return jniRegisterNativeMethods(env, g_ProxyJavaClass, g_MediaPlayerMethods, NELEM(g_MediaPlayerMethods)); } +int register_mediaplayer_audio(JNIEnv* env) +{ + return jniRegisterNativeMethods(env, g_ProxyJavaClassAudio, + g_MediaAudioPlayerMethods, NELEM(g_MediaAudioPlayerMethods)); +} + } #endif // VIDEO diff --git a/WebKit/android/jni/WebCoreJniOnLoad.cpp b/WebKit/android/jni/WebCoreJniOnLoad.cpp index b5bf9dd..86248db 100644 --- a/WebKit/android/jni/WebCoreJniOnLoad.cpp +++ b/WebKit/android/jni/WebCoreJniOnLoad.cpp @@ -82,7 +82,8 @@ extern int register_webstorage(JNIEnv*); extern int register_geolocation_permissions(JNIEnv*); extern int register_mock_geolocation(JNIEnv*); #if ENABLE(VIDEO) -extern int register_mediaplayer(JNIEnv*); +extern int register_mediaplayer_audio(JNIEnv*); +extern int register_mediaplayer_video(JNIEnv*); #endif } @@ -107,7 +108,8 @@ static RegistrationMethod gWebCoreRegMethods[] = { { "GeolocationPermissions", android::register_geolocation_permissions }, { "MockGeolocation", android::register_mock_geolocation }, #if ENABLE(VIDEO) - { "HTML5VideoViewProxy", android::register_mediaplayer }, + { "HTML5Audio", android::register_mediaplayer_audio }, + { "HTML5VideoViewProxy", android::register_mediaplayer_video }, #endif }; |