Home | History | Annotate | Download | only in WebCoreSupport
      1 /*
      2  * Copyright 2009, The Android Open Source Project
      3  *
      4  * Redistribution and use in source and binary forms, with or without
      5  * modification, are permitted provided that the following conditions
      6  * are met:
      7  *  * Redistributions of source code must retain the above copyright
      8  *    notice, this list of conditions and the following disclaimer.
      9  *  * Redistributions in binary form must reproduce the above copyright
     10  *    notice, this list of conditions and the following disclaimer in the
     11  *    documentation and/or other materials provided with the distribution.
     12  *
     13  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
     14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
     17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
     18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
     20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
     21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     24  */
     25 
     26 #include "config.h"
     27 #include "MediaPlayerPrivateAndroid.h"
     28 
     29 #if ENABLE(VIDEO)
     30 
     31 #include "GraphicsContext.h"
     32 #include "SkiaUtils.h"
     33 #include "TimeRanges.h"
     34 #include "WebCoreJni.h"
     35 #include "WebViewCore.h"
     36 
     37 #include <GraphicsJNI.h>
     38 #include <JNIHelp.h>
     39 #include <JNIUtility.h>
     40 #include <SkBitmap.h>
     41 
     42 using namespace android;
     43 
     44 namespace WebCore {
     45 
     46 static const char* g_ProxyJavaClass = "android/webkit/HTML5VideoViewProxy";
     47 
     48 struct MediaPlayerPrivate::JavaGlue
     49 {
     50     jobject   m_javaProxy;
     51     jmethodID m_getInstance;
     52     jmethodID m_play;
     53     jmethodID m_teardown;
     54     jmethodID m_loadPoster;
     55     jmethodID m_seek;
     56     jmethodID m_pause;
     57 };
     58 
     59 MediaPlayerPrivate::~MediaPlayerPrivate()
     60 {
     61     if (m_glue->m_javaProxy) {
     62         JNIEnv* env = JSC::Bindings::getJNIEnv();
     63         if (env) {
     64             env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_teardown);
     65             env->DeleteGlobalRef(m_glue->m_javaProxy);
     66         }
     67     }
     68 
     69     delete m_glue;
     70 }
     71 
     72 void MediaPlayerPrivate::registerMediaEngine(MediaEngineRegistrar registrar)
     73 {
     74     registrar(create, getSupportedTypes, supportsType);
     75 }
     76 
     77 void MediaPlayerPrivate::load(const String& url)
     78 {
     79     // Just save the URl.
     80     m_url = url;
     81 }
     82 
     83 void MediaPlayerPrivate::cancelLoad()
     84 {
     85 }
     86 
     87 void MediaPlayerPrivate::play()
     88 {
     89     JNIEnv* env = JSC::Bindings::getJNIEnv();
     90     if (!env || !m_glue->m_javaProxy || !m_url.length())
     91         return;
     92 
     93     m_paused = false;
     94     jstring jUrl = env->NewString((unsigned short *)m_url.characters(), m_url.length());
     95     env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_play, jUrl);
     96     env->DeleteLocalRef(jUrl);
     97     checkException(env);
     98 }
     99 
    100 void MediaPlayerPrivate::pause()
    101 {
    102     JNIEnv* env = JSC::Bindings::getJNIEnv();
    103     if (!env || !m_glue->m_javaProxy || !m_url.length())
    104         return;
    105 
    106     m_paused = true;
    107     env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_pause);
    108     checkException(env);
    109 }
    110 
    111 IntSize MediaPlayerPrivate::naturalSize() const
    112 {
    113     return m_naturalSize;
    114 }
    115 
    116 bool MediaPlayerPrivate::hasAudio() const
    117 {
    118     // TODO
    119     return false;
    120 }
    121 
    122 bool MediaPlayerPrivate::hasVideo() const
    123 {
    124     return m_hasVideo;
    125 }
    126 
    127 void MediaPlayerPrivate::setVisible(bool visible)
    128 {
    129     m_isVisible = visible;
    130     if (m_isVisible)
    131         createJavaPlayerIfNeeded();
    132 }
    133 
    134 float MediaPlayerPrivate::duration() const
    135 {
    136     return m_duration;
    137 }
    138 
    139 float MediaPlayerPrivate::currentTime() const
    140 {
    141     return m_currentTime;
    142 }
    143 
    144 void MediaPlayerPrivate::seek(float time)
    145 {
    146     JNIEnv* env = JSC::Bindings::getJNIEnv();
    147     if (!env || !m_glue->m_javaProxy || !m_url.length())
    148         return;
    149 
    150     m_currentTime = time;
    151     env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_seek, static_cast<jint>(time * 1000.0f));
    152     checkException(env);
    153 }
    154 
    155 bool MediaPlayerPrivate::seeking() const
    156 {
    157     return false;
    158 }
    159 
    160 void MediaPlayerPrivate::setEndTime(float)
    161 {
    162 }
    163 
    164 void MediaPlayerPrivate::setRate(float)
    165 {
    166 }
    167 
    168 bool MediaPlayerPrivate::paused() const
    169 {
    170     return m_paused;
    171 }
    172 
    173 void MediaPlayerPrivate::setVolume(float)
    174 {
    175 }
    176 
    177 MediaPlayer::NetworkState MediaPlayerPrivate::networkState() const
    178 {
    179     return m_networkState;
    180 }
    181 
    182 MediaPlayer::ReadyState MediaPlayerPrivate::readyState() const
    183 {
    184     return m_readyState;
    185 }
    186 
    187 float MediaPlayerPrivate::maxTimeSeekable() const
    188 {
    189     return 0;
    190 }
    191 
    192 PassRefPtr<TimeRanges> MediaPlayerPrivate::buffered() const
    193 {
    194     return TimeRanges::create();
    195 }
    196 
    197 int MediaPlayerPrivate::dataRate() const
    198 {
    199     return 0;
    200 }
    201 
    202 unsigned MediaPlayerPrivate::totalBytes() const
    203 {
    204     return 0;
    205 }
    206 
    207 unsigned MediaPlayerPrivate::bytesLoaded() const
    208 {
    209     return 0;
    210 }
    211 
    212 void MediaPlayerPrivate::setSize(const IntSize&)
    213 {
    214 }
    215 
    216 void MediaPlayerPrivate::setPoster(const String& url)
    217 {
    218     m_posterUrl = url;
    219     JNIEnv* env = JSC::Bindings::getJNIEnv();
    220     if (!env || !m_glue->m_javaProxy || !m_posterUrl.length())
    221         return;
    222     // Send the poster
    223     jstring jUrl = env->NewString((unsigned short *)m_posterUrl.characters(), m_posterUrl.length());
    224     env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_loadPoster, jUrl);
    225     env->DeleteLocalRef(jUrl);
    226 }
    227 
    228 void MediaPlayerPrivate::prepareToPlay() {
    229     // We are about to start playing. Since our Java VideoView cannot
    230     // buffer any data, we just simply transition to the HaveEnoughData
    231     // state in here. This will allow the MediaPlayer to transition to
    232     // the "play" state, at which point our VideoView will start downloading
    233     // the content and start the playback.
    234     m_networkState = MediaPlayer::Loaded;
    235     m_player->networkStateChanged();
    236     m_readyState = MediaPlayer::HaveEnoughData;
    237     m_player->readyStateChanged();
    238 }
    239 
    240 void MediaPlayerPrivate::paint(GraphicsContext* ctxt, const IntRect& r)
    241 {
    242     if (ctxt->paintingDisabled())
    243         return;
    244 
    245     if (!m_isVisible)
    246         return;
    247 
    248     if (!m_poster || (!m_poster->getPixels() && !m_poster->pixelRef()))
    249         return;
    250 
    251     SkCanvas*   canvas = ctxt->platformContext()->mCanvas;
    252     // We paint with the following rules in mind:
    253     // - only downscale the poster, never upscale
    254     // - maintain the natural aspect ratio of the poster
    255     // - the poster should be centered in the target rect
    256     float originalRatio = static_cast<float>(m_poster->width()) / static_cast<float>(m_poster->height());
    257     int posterWidth = r.width() > m_poster->width() ? m_poster->width() : r.width();
    258     int posterHeight = posterWidth / originalRatio;
    259     int posterX = ((r.width() - posterWidth) / 2) + r.x();
    260     int posterY = ((r.height() - posterHeight) / 2) + r.y();
    261     IntRect targetRect(posterX, posterY, posterWidth, posterHeight);
    262     canvas->drawBitmapRect(*m_poster, 0, targetRect, 0);
    263 }
    264 
    265 MediaPlayerPrivateInterface* MediaPlayerPrivate::create(MediaPlayer* player)
    266 {
    267     return new MediaPlayerPrivate(player);
    268 }
    269 
    270 void MediaPlayerPrivate::getSupportedTypes(HashSet<String>&)
    271 {
    272 }
    273 
    274 MediaPlayer::SupportsType MediaPlayerPrivate::supportsType(const String& type, const String& codecs)
    275 {
    276     return MediaPlayer::IsNotSupported;
    277 }
    278 
    279 MediaPlayerPrivate::MediaPlayerPrivate(MediaPlayer* player)
    280     : m_player(player),
    281     m_glue(0),
    282     m_duration(6000),
    283     m_currentTime(0),
    284     m_paused(true),
    285     m_hasVideo(false),
    286     m_readyState(MediaPlayer::HaveNothing),
    287     m_networkState(MediaPlayer::Empty),
    288     m_poster(0),
    289     m_naturalSize(100, 100),
    290     m_naturalSizeUnknown(true),
    291     m_isVisible(false)
    292 {
    293     JNIEnv* env = JSC::Bindings::getJNIEnv();
    294     if (!env)
    295         return;
    296 
    297     jclass clazz = env->FindClass(g_ProxyJavaClass);
    298     if (!clazz)
    299         return;
    300 
    301     m_glue = new JavaGlue;
    302     m_glue->m_getInstance = env->GetStaticMethodID(clazz, "getInstance", "(Landroid/webkit/WebViewCore;I)Landroid/webkit/HTML5VideoViewProxy;");
    303     m_glue->m_play = env->GetMethodID(clazz, "play", "(Ljava/lang/String;)V");
    304     m_glue->m_teardown = env->GetMethodID(clazz, "teardown", "()V");
    305     m_glue->m_loadPoster = env->GetMethodID(clazz, "loadPoster", "(Ljava/lang/String;)V");
    306     m_glue->m_seek = env->GetMethodID(clazz, "seek", "(I)V");
    307     m_glue->m_pause = env->GetMethodID(clazz, "pause", "()V");
    308     m_glue->m_javaProxy = NULL;
    309     env->DeleteLocalRef(clazz);
    310     // An exception is raised if any of the above fails.
    311     checkException(env);
    312 }
    313 
    314 void MediaPlayerPrivate::createJavaPlayerIfNeeded()
    315 {
    316     // Check if we have been already created.
    317     if (m_glue->m_javaProxy)
    318         return;
    319 
    320     FrameView* frameView = m_player->frameView();
    321     if (!frameView)
    322         return;
    323 
    324     JNIEnv* env = JSC::Bindings::getJNIEnv();
    325     if (!env)
    326         return;
    327 
    328     jclass clazz = env->FindClass(g_ProxyJavaClass);
    329     if (!clazz)
    330         return;
    331 
    332     WebViewCore* webViewCore =  WebViewCore::getWebViewCore(frameView);
    333     ASSERT(webViewCore);
    334 
    335     // Get the HTML5VideoViewProxy instance
    336     jobject obj = env->CallStaticObjectMethod(clazz, m_glue->m_getInstance, webViewCore->getJavaObject().get(), this);
    337     m_glue->m_javaProxy = env->NewGlobalRef(obj);
    338     // Send the poster
    339     jstring jUrl = 0;
    340     if (m_posterUrl.length())
    341         jUrl = env->NewString((unsigned short *)m_posterUrl.characters(), m_posterUrl.length());
    342     // Sending a NULL jUrl allows the Java side to try to load the default poster.
    343     env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_loadPoster, jUrl);
    344     if (jUrl)
    345         env->DeleteLocalRef(jUrl);
    346     // Clean up.
    347     env->DeleteLocalRef(obj);
    348     env->DeleteLocalRef(clazz);
    349     checkException(env);
    350 }
    351 
    352 void MediaPlayerPrivate::onPrepared(int duration, int width, int height) {
    353     m_duration = duration / 1000.0f;
    354     m_naturalSize = IntSize(width, height);
    355     m_naturalSizeUnknown = false;
    356     m_hasVideo = true;
    357     m_player->durationChanged();
    358     m_player->sizeChanged();
    359 }
    360 
    361 void MediaPlayerPrivate::onEnded() {
    362     m_currentTime = duration();
    363     m_player->timeChanged();
    364     m_paused = true;
    365     m_currentTime = 0;
    366     m_hasVideo = false;
    367     m_networkState = MediaPlayer::Idle;
    368     m_readyState = MediaPlayer::HaveNothing;
    369 }
    370 
    371 void MediaPlayerPrivate::onPosterFetched(SkBitmap* poster) {
    372     m_poster = poster;
    373     if (m_naturalSizeUnknown) {
    374         // We had to fake the size at startup, or else our paint
    375         // method would not be called. If we haven't yet received
    376         // the onPrepared event, update the intrinsic size to the size
    377         // of the poster. That will be overriden when onPrepare comes.
    378         // In case of an error, we should report the poster size, rather
    379         // than our initial fake value.
    380         m_naturalSize = IntSize(poster->width(), poster->height());
    381         m_player->sizeChanged();
    382     }
    383 }
    384 
    385 void MediaPlayerPrivate::onTimeupdate(int position) {
    386     m_currentTime = position / 1000.0f;
    387     m_player->timeChanged();
    388 }
    389 
    390 }
    391 
    392 namespace android {
    393 
    394 static void OnPrepared(JNIEnv* env, jobject obj, int duration, int width, int height, int pointer) {
    395     if (pointer) {
    396         WebCore::MediaPlayerPrivate* player = reinterpret_cast<WebCore::MediaPlayerPrivate*>(pointer);
    397         player->onPrepared(duration, width, height);
    398     }
    399 }
    400 
    401 static void OnEnded(JNIEnv* env, jobject obj, int pointer) {
    402     if (pointer) {
    403         WebCore::MediaPlayerPrivate* player = reinterpret_cast<WebCore::MediaPlayerPrivate*>(pointer);
    404         player->onEnded();
    405     }
    406 }
    407 
    408 static void OnPosterFetched(JNIEnv* env, jobject obj, jobject poster, int pointer) {
    409     if (!pointer || !poster)
    410         return;
    411 
    412     WebCore::MediaPlayerPrivate* player = reinterpret_cast<WebCore::MediaPlayerPrivate*>(pointer);
    413     SkBitmap* posterNative = GraphicsJNI::getNativeBitmap(env, poster);
    414     if (!posterNative)
    415         return;
    416     player->onPosterFetched(posterNative);
    417 }
    418 
    419 static void OnTimeupdate(JNIEnv* env, jobject obj, int position, int pointer) {
    420     if (pointer) {
    421         WebCore::MediaPlayerPrivate* player = reinterpret_cast<WebCore::MediaPlayerPrivate*>(pointer);
    422         player->onTimeupdate(position);
    423     }
    424 }
    425 
    426 /*
    427  * JNI registration
    428  */
    429 static JNINativeMethod g_MediaPlayerMethods[] = {
    430     { "nativeOnPrepared", "(IIII)V",
    431         (void*) OnPrepared },
    432     { "nativeOnEnded", "(I)V",
    433         (void*) OnEnded },
    434     { "nativeOnPosterFetched", "(Landroid/graphics/Bitmap;I)V",
    435         (void*) OnPosterFetched },
    436     { "nativeOnTimeupdate", "(II)V",
    437         (void*) OnTimeupdate },
    438 };
    439 
    440 int register_mediaplayer(JNIEnv* env)
    441 {
    442     return jniRegisterNativeMethods(env, g_ProxyJavaClass,
    443             g_MediaPlayerMethods, NELEM(g_MediaPlayerMethods));
    444 }
    445 
    446 }
    447 #endif // VIDEO
    448