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 "WebCoreJni.h"
     34 #include "WebViewCore.h"
     35 
     36 #include <GraphicsJNI.h>
     37 #include <JNIHelp.h>
     38 #include <JNIUtility.h>
     39 #include <SkBitmap.h>
     40 
     41 using namespace android;
     42 
     43 namespace WebCore {
     44 
     45 static const char* g_ProxyJavaClass = "android/webkit/HTML5VideoViewProxy";
     46 static const char* g_ProxyJavaClassAudio = "android/webkit/HTML5Audio";
     47 
     48 struct MediaPlayerPrivate::JavaGlue
     49 {
     50     jobject   m_javaProxy;
     51     jmethodID m_play;
     52     jmethodID m_teardown;
     53     jmethodID m_seek;
     54     jmethodID m_pause;
     55     // Audio
     56     jmethodID m_newInstance;
     57     jmethodID m_setDataSource;
     58     jmethodID m_getMaxTimeSeekable;
     59     // Video
     60     jmethodID m_getInstance;
     61     jmethodID m_loadPoster;
     62 };
     63 
     64 MediaPlayerPrivate::~MediaPlayerPrivate()
     65 {
     66     if (m_glue->m_javaProxy) {
     67         JNIEnv* env = JSC::Bindings::getJNIEnv();
     68         if (env) {
     69             env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_teardown);
     70             env->DeleteGlobalRef(m_glue->m_javaProxy);
     71         }
     72     }
     73     delete m_glue;
     74 }
     75 
     76 void MediaPlayerPrivate::registerMediaEngine(MediaEngineRegistrar registrar)
     77 {
     78     registrar(create, getSupportedTypes, supportsType);
     79 }
     80 
     81 void MediaPlayerPrivate::pause()
     82 {
     83     JNIEnv* env = JSC::Bindings::getJNIEnv();
     84     if (!env || !m_glue->m_javaProxy || !m_url.length())
     85         return;
     86 
     87     m_paused = true;
     88     env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_pause);
     89     checkException(env);
     90 }
     91 
     92 void MediaPlayerPrivate::setVisible(bool visible)
     93 {
     94     m_isVisible = visible;
     95     if (m_isVisible)
     96         createJavaPlayerIfNeeded();
     97 }
     98 
     99 void MediaPlayerPrivate::seek(float time)
    100 {
    101     JNIEnv* env = JSC::Bindings::getJNIEnv();
    102     if (!env || !m_url.length())
    103         return;
    104 
    105     if (m_glue->m_javaProxy) {
    106         env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_seek, static_cast<jint>(time * 1000.0f));
    107         m_currentTime = time;
    108     }
    109     checkException(env);
    110 }
    111 
    112 
    113 void MediaPlayerPrivate::prepareToPlay() {
    114     // We are about to start playing. Since our Java VideoView cannot
    115     // buffer any data, we just simply transition to the HaveEnoughData
    116     // state in here. This will allow the MediaPlayer to transition to
    117     // the "play" state, at which point our VideoView will start downloading
    118     // the content and start the playback.
    119     m_networkState = MediaPlayer::Loaded;
    120     m_player->networkStateChanged();
    121     m_readyState = MediaPlayer::HaveEnoughData;
    122     m_player->readyStateChanged();
    123 }
    124 
    125 MediaPlayer::SupportsType MediaPlayerPrivate::supportsType(const String& type, const String& codecs)
    126 {
    127     if (WebViewCore::supportsMimeType(type))
    128         return MediaPlayer::MayBeSupported;
    129     return MediaPlayer::IsNotSupported;
    130 }
    131 
    132 MediaPlayerPrivate::MediaPlayerPrivate(MediaPlayer* player)
    133     : m_player(player),
    134     m_glue(0),
    135     m_duration(6000),
    136     m_currentTime(0),
    137     m_paused(true),
    138     m_hasVideo(false),
    139     m_readyState(MediaPlayer::HaveNothing),
    140     m_networkState(MediaPlayer::Empty),
    141     m_poster(0),
    142     m_naturalSize(100, 100),
    143     m_naturalSizeUnknown(true),
    144     m_isVisible(false)
    145 {
    146 }
    147 
    148 void MediaPlayerPrivate::onEnded() {
    149     m_currentTime = duration();
    150     m_player->timeChanged();
    151     m_paused = true;
    152     m_currentTime = 0;
    153     m_hasVideo = false;
    154     m_networkState = MediaPlayer::Idle;
    155     m_readyState = MediaPlayer::HaveNothing;
    156 }
    157 
    158 void MediaPlayerPrivate::onPaused() {
    159     m_paused = true;
    160     m_currentTime = 0;
    161     m_hasVideo = false;
    162     m_networkState = MediaPlayer::Idle;
    163     m_readyState = MediaPlayer::HaveNothing;
    164 }
    165 
    166 void MediaPlayerPrivate::onTimeupdate(int position) {
    167     m_currentTime = position / 1000.0f;
    168     m_player->timeChanged();
    169 }
    170 
    171 class MediaPlayerVideoPrivate : public MediaPlayerPrivate {
    172 public:
    173     void load(const String& url) { m_url = url; }
    174     void play() {
    175         JNIEnv* env = JSC::Bindings::getJNIEnv();
    176         if (!env || !m_url.length() || !m_glue->m_javaProxy)
    177             return;
    178 
    179         m_paused = false;
    180         jstring jUrl = env->NewString((unsigned short *)m_url.characters(), m_url.length());
    181         env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_play, jUrl);
    182         env->DeleteLocalRef(jUrl);
    183 
    184         checkException(env);
    185     }
    186     bool canLoadPoster() const { return true; }
    187     void setPoster(const String& url) {
    188         m_posterUrl = url;
    189         JNIEnv* env = JSC::Bindings::getJNIEnv();
    190         if (!env || !m_glue->m_javaProxy || !m_posterUrl.length())
    191             return;
    192         // Send the poster
    193         jstring jUrl = env->NewString((unsigned short *)m_posterUrl.characters(), m_posterUrl.length());
    194         env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_loadPoster, jUrl);
    195         env->DeleteLocalRef(jUrl);
    196     }
    197     void paint(GraphicsContext* ctxt, const IntRect& r) {
    198         if (ctxt->paintingDisabled())
    199             return;
    200 
    201         if (!m_isVisible)
    202             return;
    203 
    204         if (!m_poster || (!m_poster->getPixels() && !m_poster->pixelRef()))
    205             return;
    206 
    207         SkCanvas*   canvas = ctxt->platformContext()->mCanvas;
    208         // We paint with the following rules in mind:
    209         // - only downscale the poster, never upscale
    210         // - maintain the natural aspect ratio of the poster
    211         // - the poster should be centered in the target rect
    212         float originalRatio = static_cast<float>(m_poster->width()) / static_cast<float>(m_poster->height());
    213         int posterWidth = r.width() > m_poster->width() ? m_poster->width() : r.width();
    214         int posterHeight = posterWidth / originalRatio;
    215         int posterX = ((r.width() - posterWidth) / 2) + r.x();
    216         int posterY = ((r.height() - posterHeight) / 2) + r.y();
    217         IntRect targetRect(posterX, posterY, posterWidth, posterHeight);
    218         canvas->drawBitmapRect(*m_poster, 0, targetRect, 0);
    219     }
    220 
    221     void onPosterFetched(SkBitmap* poster) {
    222         m_poster = poster;
    223         if (m_naturalSizeUnknown) {
    224             // We had to fake the size at startup, or else our paint
    225             // method would not be called. If we haven't yet received
    226             // the onPrepared event, update the intrinsic size to the size
    227             // of the poster. That will be overriden when onPrepare comes.
    228             // In case of an error, we should report the poster size, rather
    229             // than our initial fake value.
    230             m_naturalSize = IntSize(poster->width(), poster->height());
    231             m_player->sizeChanged();
    232         }
    233     }
    234 
    235     void onPrepared(int duration, int width, int height) {
    236         m_duration = duration / 1000.0f;
    237         m_naturalSize = IntSize(width, height);
    238         m_naturalSizeUnknown = false;
    239         m_hasVideo = true;
    240         m_player->durationChanged();
    241         m_player->sizeChanged();
    242     }
    243 
    244     bool hasAudio() { return false; } // do not display the audio UI
    245     bool hasVideo() { return m_hasVideo; }
    246 
    247     MediaPlayerVideoPrivate(MediaPlayer* player) : MediaPlayerPrivate(player) {
    248         JNIEnv* env = JSC::Bindings::getJNIEnv();
    249         if (!env)
    250             return;
    251 
    252         jclass clazz = env->FindClass(g_ProxyJavaClass);
    253 
    254         if (!clazz)
    255             return;
    256 
    257         m_glue = new JavaGlue;
    258         m_glue->m_getInstance = env->GetStaticMethodID(clazz, "getInstance", "(Landroid/webkit/WebViewCore;I)Landroid/webkit/HTML5VideoViewProxy;");
    259         m_glue->m_loadPoster = env->GetMethodID(clazz, "loadPoster", "(Ljava/lang/String;)V");
    260         m_glue->m_play = env->GetMethodID(clazz, "play", "(Ljava/lang/String;)V");
    261 
    262         m_glue->m_teardown = env->GetMethodID(clazz, "teardown", "()V");
    263         m_glue->m_seek = env->GetMethodID(clazz, "seek", "(I)V");
    264         m_glue->m_pause = env->GetMethodID(clazz, "pause", "()V");
    265         m_glue->m_javaProxy = NULL;
    266         env->DeleteLocalRef(clazz);
    267         // An exception is raised if any of the above fails.
    268         checkException(env);
    269     }
    270 
    271     void createJavaPlayerIfNeeded() {
    272         // Check if we have been already created.
    273         if (m_glue->m_javaProxy)
    274             return;
    275 
    276         JNIEnv* env = JSC::Bindings::getJNIEnv();
    277         if (!env)
    278             return;
    279 
    280         jclass clazz = env->FindClass(g_ProxyJavaClass);
    281 
    282         if (!clazz)
    283             return;
    284 
    285         jobject obj = NULL;
    286 
    287         FrameView* frameView = m_player->frameView();
    288         if (!frameView)
    289             return;
    290         WebViewCore* webViewCore =  WebViewCore::getWebViewCore(frameView);
    291         ASSERT(webViewCore);
    292 
    293         // Get the HTML5VideoViewProxy instance
    294         obj = env->CallStaticObjectMethod(clazz, m_glue->m_getInstance, webViewCore->getJavaObject().get(), this);
    295         m_glue->m_javaProxy = env->NewGlobalRef(obj);
    296         // Send the poster
    297         jstring jUrl = 0;
    298         if (m_posterUrl.length())
    299             jUrl = env->NewString((unsigned short *)m_posterUrl.characters(), m_posterUrl.length());
    300         // Sending a NULL jUrl allows the Java side to try to load the default poster.
    301         env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_loadPoster, jUrl);
    302         if (jUrl)
    303             env->DeleteLocalRef(jUrl);
    304 
    305         // Clean up.
    306         if (obj)
    307             env->DeleteLocalRef(obj);
    308         env->DeleteLocalRef(clazz);
    309         checkException(env);
    310     }
    311 };
    312 
    313 class MediaPlayerAudioPrivate : public MediaPlayerPrivate {
    314 public:
    315     void load(const String& url) {
    316         m_url = url;
    317         JNIEnv* env = JSC::Bindings::getJNIEnv();
    318         if (!env || !m_url.length())
    319             return;
    320 
    321         createJavaPlayerIfNeeded();
    322 
    323         if (!m_glue->m_javaProxy)
    324             return;
    325 
    326         jstring jUrl = env->NewString((unsigned short *)m_url.characters(), m_url.length());
    327         // start loading the data asynchronously
    328         env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_setDataSource, jUrl);
    329         env->DeleteLocalRef(jUrl);
    330         checkException(env);
    331     }
    332 
    333     void play() {
    334         JNIEnv* env = JSC::Bindings::getJNIEnv();
    335         if (!env || !m_url.length())
    336             return;
    337 
    338         createJavaPlayerIfNeeded();
    339 
    340         if (!m_glue->m_javaProxy)
    341             return;
    342 
    343         m_paused = false;
    344         env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_play);
    345         checkException(env);
    346     }
    347 
    348     bool hasAudio() { return true; }
    349 
    350     float maxTimeSeekable() const {
    351         if (m_glue->m_javaProxy) {
    352             JNIEnv* env = JSC::Bindings::getJNIEnv();
    353             if (env) {
    354                 float maxTime = env->CallFloatMethod(m_glue->m_javaProxy,
    355                                                      m_glue->m_getMaxTimeSeekable);
    356                 checkException(env);
    357                 return maxTime;
    358             }
    359         }
    360         return 0;
    361     }
    362 
    363     MediaPlayerAudioPrivate(MediaPlayer* player) : MediaPlayerPrivate(player) {
    364         JNIEnv* env = JSC::Bindings::getJNIEnv();
    365         if (!env)
    366             return;
    367 
    368         jclass clazz = env->FindClass(g_ProxyJavaClassAudio);
    369 
    370         if (!clazz)
    371             return;
    372 
    373         m_glue = new JavaGlue;
    374         m_glue->m_newInstance = env->GetMethodID(clazz, "<init>", "(I)V");
    375         m_glue->m_setDataSource = env->GetMethodID(clazz, "setDataSource", "(Ljava/lang/String;)V");
    376         m_glue->m_play = env->GetMethodID(clazz, "play", "()V");
    377         m_glue->m_getMaxTimeSeekable = env->GetMethodID(clazz, "getMaxTimeSeekable", "()F");
    378         m_glue->m_teardown = env->GetMethodID(clazz, "teardown", "()V");
    379         m_glue->m_seek = env->GetMethodID(clazz, "seek", "(I)V");
    380         m_glue->m_pause = env->GetMethodID(clazz, "pause", "()V");
    381         m_glue->m_javaProxy = NULL;
    382         env->DeleteLocalRef(clazz);
    383         // An exception is raised if any of the above fails.
    384         checkException(env);
    385     }
    386 
    387     void createJavaPlayerIfNeeded() {
    388         // Check if we have been already created.
    389         if (m_glue->m_javaProxy)
    390             return;
    391 
    392         JNIEnv* env = JSC::Bindings::getJNIEnv();
    393         if (!env)
    394             return;
    395 
    396         jclass clazz = env->FindClass(g_ProxyJavaClassAudio);
    397 
    398         if (!clazz)
    399             return;
    400 
    401         jobject obj = NULL;
    402 
    403         // Get the HTML5Audio instance
    404         obj = env->NewObject(clazz, m_glue->m_newInstance, this);
    405         m_glue->m_javaProxy = env->NewGlobalRef(obj);
    406 
    407         // Clean up.
    408         if (obj)
    409             env->DeleteLocalRef(obj);
    410         env->DeleteLocalRef(clazz);
    411         checkException(env);
    412     }
    413 
    414     void onPrepared(int duration, int width, int height) {
    415         m_duration = duration / 1000.0f;
    416         m_player->durationChanged();
    417         m_player->sizeChanged();
    418         m_player->prepareToPlay();
    419     }
    420 };
    421 
    422 MediaPlayerPrivateInterface* MediaPlayerPrivate::create(MediaPlayer* player)
    423 {
    424     if (player->mediaElementType() == MediaPlayer::Video)
    425        return new MediaPlayerVideoPrivate(player);
    426     return new MediaPlayerAudioPrivate(player);
    427 }
    428 
    429 }
    430 
    431 namespace android {
    432 
    433 static void OnPrepared(JNIEnv* env, jobject obj, int duration, int width, int height, int pointer) {
    434     if (pointer) {
    435         WebCore::MediaPlayerPrivate* player = reinterpret_cast<WebCore::MediaPlayerPrivate*>(pointer);
    436         player->onPrepared(duration, width, height);
    437     }
    438 }
    439 
    440 static void OnEnded(JNIEnv* env, jobject obj, int pointer) {
    441     if (pointer) {
    442         WebCore::MediaPlayerPrivate* player = reinterpret_cast<WebCore::MediaPlayerPrivate*>(pointer);
    443         player->onEnded();
    444     }
    445 }
    446 
    447 static void OnPaused(JNIEnv* env, jobject obj, int pointer) {
    448     if (pointer) {
    449         WebCore::MediaPlayerPrivate* player = reinterpret_cast<WebCore::MediaPlayerPrivate*>(pointer);
    450         player->onPaused();
    451     }
    452 }
    453 
    454 static void OnPosterFetched(JNIEnv* env, jobject obj, jobject poster, int pointer) {
    455     if (!pointer || !poster)
    456         return;
    457 
    458     WebCore::MediaPlayerPrivate* player = reinterpret_cast<WebCore::MediaPlayerPrivate*>(pointer);
    459     SkBitmap* posterNative = GraphicsJNI::getNativeBitmap(env, poster);
    460     if (!posterNative)
    461         return;
    462     player->onPosterFetched(posterNative);
    463 }
    464 
    465 static void OnBuffering(JNIEnv* env, jobject obj, int percent, int pointer) {
    466     if (pointer) {
    467         WebCore::MediaPlayerPrivate* player = reinterpret_cast<WebCore::MediaPlayerPrivate*>(pointer);
    468         //TODO: player->onBuffering(percent);
    469     }
    470 }
    471 
    472 static void OnTimeupdate(JNIEnv* env, jobject obj, int position, int pointer) {
    473     if (pointer) {
    474         WebCore::MediaPlayerPrivate* player = reinterpret_cast<WebCore::MediaPlayerPrivate*>(pointer);
    475         player->onTimeupdate(position);
    476     }
    477 }
    478 
    479 /*
    480  * JNI registration
    481  */
    482 static JNINativeMethod g_MediaPlayerMethods[] = {
    483     { "nativeOnPrepared", "(IIII)V",
    484         (void*) OnPrepared },
    485     { "nativeOnEnded", "(I)V",
    486         (void*) OnEnded },
    487     { "nativeOnPaused", "(I)V",
    488         (void*) OnPaused },
    489     { "nativeOnPosterFetched", "(Landroid/graphics/Bitmap;I)V",
    490         (void*) OnPosterFetched },
    491     { "nativeOnTimeupdate", "(II)V",
    492         (void*) OnTimeupdate },
    493 };
    494 
    495 static JNINativeMethod g_MediaAudioPlayerMethods[] = {
    496     { "nativeOnBuffering", "(II)V",
    497         (void*) OnBuffering },
    498     { "nativeOnEnded", "(I)V",
    499         (void*) OnEnded },
    500     { "nativeOnPrepared", "(IIII)V",
    501         (void*) OnPrepared },
    502     { "nativeOnTimeupdate", "(II)V",
    503         (void*) OnTimeupdate },
    504 };
    505 
    506 int register_mediaplayer_video(JNIEnv* env)
    507 {
    508     return jniRegisterNativeMethods(env, g_ProxyJavaClass,
    509             g_MediaPlayerMethods, NELEM(g_MediaPlayerMethods));
    510 }
    511 
    512 int register_mediaplayer_audio(JNIEnv* env)
    513 {
    514     return jniRegisterNativeMethods(env, g_ProxyJavaClassAudio,
    515             g_MediaAudioPlayerMethods, NELEM(g_MediaAudioPlayerMethods));
    516 }
    517 
    518 }
    519 #endif // VIDEO
    520