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 "BaseLayerAndroid.h"
32#include "DocumentLoader.h"
33#include "Frame.h"
34#include "FrameLoader.h"
35#include "FrameView.h"
36#include "GraphicsContext.h"
37#include "SkiaUtils.h"
38#include "TilesManager.h"
39#include "VideoLayerAndroid.h"
40#include "WebCoreJni.h"
41#include "WebViewCore.h"
42#include <GraphicsJNI.h>
43#include <JNIHelp.h>
44#include <JNIUtility.h>
45#include <SkBitmap.h>
46#include <gui/SurfaceTexture.h>
47
48using namespace android;
49// Forward decl
50namespace android {
51sp<SurfaceTexture> SurfaceTexture_getSurfaceTexture(JNIEnv* env, jobject thiz);
52};
53
54namespace WebCore {
55
56static const char* g_ProxyJavaClass = "android/webkit/HTML5VideoViewProxy";
57static const char* g_ProxyJavaClassAudio = "android/webkit/HTML5Audio";
58
59struct MediaPlayerPrivate::JavaGlue {
60    jobject   m_javaProxy;
61    jmethodID m_play;
62    jmethodID m_teardown;
63    jmethodID m_seek;
64    jmethodID m_pause;
65    // Audio
66    jmethodID m_newInstance;
67    jmethodID m_setDataSource;
68    jmethodID m_getMaxTimeSeekable;
69    // Video
70    jmethodID m_getInstance;
71    jmethodID m_loadPoster;
72};
73
74MediaPlayerPrivate::~MediaPlayerPrivate()
75{
76    TilesManager::instance()->videoLayerManager()->removeLayer(m_videoLayer->uniqueId());
77    // m_videoLayer is reference counted, unref is enough here.
78    m_videoLayer->unref();
79    if (m_glue->m_javaProxy) {
80        JNIEnv* env = JSC::Bindings::getJNIEnv();
81        if (env) {
82            env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_teardown);
83            env->DeleteGlobalRef(m_glue->m_javaProxy);
84        }
85    }
86    delete m_glue;
87}
88
89void MediaPlayerPrivate::registerMediaEngine(MediaEngineRegistrar registrar)
90{
91    registrar(create, getSupportedTypes, supportsType, 0, 0, 0);
92}
93
94MediaPlayer::SupportsType MediaPlayerPrivate::supportsType(const String& type, const String& codecs)
95{
96    if (WebViewCore::isSupportedMediaMimeType(type))
97        return MediaPlayer::MayBeSupported;
98    return MediaPlayer::IsNotSupported;
99}
100
101void MediaPlayerPrivate::pause()
102{
103    JNIEnv* env = JSC::Bindings::getJNIEnv();
104    if (!env || !m_glue->m_javaProxy || !m_url.length())
105        return;
106
107    m_paused = true;
108    m_player->playbackStateChanged();
109    env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_pause);
110    checkException(env);
111}
112
113void MediaPlayerPrivate::setVisible(bool visible)
114{
115    m_isVisible = visible;
116    if (m_isVisible)
117        createJavaPlayerIfNeeded();
118}
119
120void MediaPlayerPrivate::seek(float time)
121{
122    JNIEnv* env = JSC::Bindings::getJNIEnv();
123    if (!env || !m_url.length())
124        return;
125
126    if (m_glue->m_javaProxy) {
127        env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_seek, static_cast<jint>(time * 1000.0f));
128        m_currentTime = time;
129    }
130    checkException(env);
131}
132
133void MediaPlayerPrivate::prepareToPlay()
134{
135    // We are about to start playing. Since our Java VideoView cannot
136    // buffer any data, we just simply transition to the HaveEnoughData
137    // state in here. This will allow the MediaPlayer to transition to
138    // the "play" state, at which point our VideoView will start downloading
139    // the content and start the playback.
140    m_networkState = MediaPlayer::Loaded;
141    m_player->networkStateChanged();
142    m_readyState = MediaPlayer::HaveEnoughData;
143    m_player->readyStateChanged();
144}
145
146MediaPlayerPrivate::MediaPlayerPrivate(MediaPlayer* player)
147    : m_player(player),
148    m_glue(0),
149    m_duration(1), // keep this minimal to avoid initial seek problem
150    m_currentTime(0),
151    m_paused(true),
152    m_readyState(MediaPlayer::HaveNothing),
153    m_networkState(MediaPlayer::Empty),
154    m_poster(0),
155    m_naturalSize(100, 100),
156    m_naturalSizeUnknown(true),
157    m_isVisible(false),
158    m_videoLayer(new VideoLayerAndroid())
159{
160}
161
162void MediaPlayerPrivate::onEnded()
163{
164    m_currentTime = duration();
165    m_player->timeChanged();
166    m_paused = true;
167    m_player->playbackStateChanged();
168    m_networkState = MediaPlayer::Idle;
169}
170
171void MediaPlayerPrivate::onPaused()
172{
173    m_paused = true;
174    m_player->playbackStateChanged();
175    m_networkState = MediaPlayer::Idle;
176    m_player->playbackStateChanged();
177}
178
179void MediaPlayerPrivate::onTimeupdate(int position)
180{
181    m_currentTime = position / 1000.0f;
182    m_player->timeChanged();
183}
184
185void MediaPlayerPrivate::onStopFullscreen()
186{
187    if (m_player && m_player->mediaPlayerClient()
188        && m_player->mediaPlayerClient()->mediaPlayerOwningDocument()) {
189        m_player->mediaPlayerClient()->mediaPlayerOwningDocument()->webkitCancelFullScreen();
190    }
191}
192
193class MediaPlayerVideoPrivate : public MediaPlayerPrivate {
194public:
195    void load(const String& url)
196    {
197        m_url = url;
198        // Cheat a bit here to make sure Window.onLoad event can be triggered
199        // at the right time instead of real video play time, since only full
200        // screen video play is supported in Java's VideoView.
201        // See also comments in prepareToPlay function.
202        m_networkState = MediaPlayer::Loading;
203        m_player->networkStateChanged();
204        m_readyState = MediaPlayer::HaveCurrentData;
205        m_player->readyStateChanged();
206    }
207
208    void play()
209    {
210        JNIEnv* env = JSC::Bindings::getJNIEnv();
211        if (!env || !m_url.length() || !m_glue->m_javaProxy)
212            return;
213
214        // We only play video fullscreen on Android, so stop sites playing fullscreen video in the onload handler.
215        Frame* frame = m_player->frameView()->frame();
216        if (frame && !frame->loader()->documentLoader()->wasOnloadHandled())
217            return;
218
219        m_paused = false;
220        m_player->playbackStateChanged();
221
222        if (m_currentTime == duration())
223            m_currentTime = 0;
224
225        jstring jUrl = wtfStringToJstring(env, m_url);
226        env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_play, jUrl,
227                            static_cast<jint>(m_currentTime * 1000.0f),
228                            m_videoLayer->uniqueId());
229        env->DeleteLocalRef(jUrl);
230
231        checkException(env);
232    }
233    bool canLoadPoster() const { return true; }
234    void setPoster(const String& url)
235    {
236        if (m_posterUrl == url)
237            return;
238
239        m_posterUrl = url;
240        JNIEnv* env = JSC::Bindings::getJNIEnv();
241        if (!env || !m_glue->m_javaProxy || !m_posterUrl.length())
242            return;
243        // Send the poster
244        jstring jUrl = wtfStringToJstring(env, m_posterUrl);
245        env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_loadPoster, jUrl);
246        env->DeleteLocalRef(jUrl);
247    }
248    void paint(GraphicsContext* ctxt, const IntRect& r)
249    {
250        if (ctxt->paintingDisabled())
251            return;
252
253        if (!m_isVisible)
254            return;
255
256        if (!m_poster || (!m_poster->getPixels() && !m_poster->pixelRef()))
257            return;
258
259        SkCanvas*   canvas = ctxt->platformContext()->mCanvas;
260        // We paint with the following rules in mind:
261        // - only downscale the poster, never upscale
262        // - maintain the natural aspect ratio of the poster
263        // - the poster should be centered in the target rect
264        float originalRatio = static_cast<float>(m_poster->width()) / static_cast<float>(m_poster->height());
265        int posterWidth = r.width() > m_poster->width() ? m_poster->width() : r.width();
266        int posterHeight = posterWidth / originalRatio;
267        int posterX = ((r.width() - posterWidth) / 2) + r.x();
268        int posterY = ((r.height() - posterHeight) / 2) + r.y();
269        IntRect targetRect(posterX, posterY, posterWidth, posterHeight);
270        canvas->drawBitmapRect(*m_poster, 0, targetRect, 0);
271    }
272
273    void onPosterFetched(SkBitmap* poster)
274    {
275        m_poster = poster;
276        if (m_naturalSizeUnknown) {
277            // We had to fake the size at startup, or else our paint
278            // method would not be called. If we haven't yet received
279            // the onPrepared event, update the intrinsic size to the size
280            // of the poster. That will be overriden when onPrepare comes.
281            // In case of an error, we should report the poster size, rather
282            // than our initial fake value.
283            m_naturalSize = IntSize(poster->width(), poster->height());
284            m_player->sizeChanged();
285        }
286    }
287
288    void onPrepared(int duration, int width, int height)
289    {
290        m_duration = duration / 1000.0f;
291        m_naturalSize = IntSize(width, height);
292        m_naturalSizeUnknown = false;
293        m_player->durationChanged();
294        m_player->sizeChanged();
295        TilesManager::instance()->videoLayerManager()->updateVideoLayerSize(
296            m_player->platformLayer()->uniqueId(), width*height);
297    }
298
299    virtual bool hasAudio() const { return false; } // do not display the audio UI
300    virtual bool hasVideo() const { return true; }
301    virtual bool supportsFullscreen() const { return true; }
302
303    MediaPlayerVideoPrivate(MediaPlayer* player) : MediaPlayerPrivate(player)
304    {
305        JNIEnv* env = JSC::Bindings::getJNIEnv();
306        if (!env)
307            return;
308
309        jclass clazz = env->FindClass(g_ProxyJavaClass);
310
311        if (!clazz)
312            return;
313
314        m_glue = new JavaGlue;
315        m_glue->m_getInstance = env->GetStaticMethodID(clazz, "getInstance", "(Landroid/webkit/WebViewCore;I)Landroid/webkit/HTML5VideoViewProxy;");
316        m_glue->m_loadPoster = env->GetMethodID(clazz, "loadPoster", "(Ljava/lang/String;)V");
317        m_glue->m_play = env->GetMethodID(clazz, "play", "(Ljava/lang/String;II)V");
318
319        m_glue->m_teardown = env->GetMethodID(clazz, "teardown", "()V");
320        m_glue->m_seek = env->GetMethodID(clazz, "seek", "(I)V");
321        m_glue->m_pause = env->GetMethodID(clazz, "pause", "()V");
322        m_glue->m_javaProxy = 0;
323        env->DeleteLocalRef(clazz);
324        // An exception is raised if any of the above fails.
325        checkException(env);
326    }
327
328    void createJavaPlayerIfNeeded()
329    {
330        // Check if we have been already created.
331        if (m_glue->m_javaProxy)
332            return;
333
334        JNIEnv* env = JSC::Bindings::getJNIEnv();
335        if (!env)
336            return;
337
338        jclass clazz = env->FindClass(g_ProxyJavaClass);
339
340        if (!clazz)
341            return;
342
343        jobject obj = 0;
344
345        FrameView* frameView = m_player->frameView();
346        if (!frameView)
347            return;
348        AutoJObject javaObject = WebViewCore::getWebViewCore(frameView)->getJavaObject();
349        if (!javaObject.get())
350            return;
351
352        // Get the HTML5VideoViewProxy instance
353        obj = env->CallStaticObjectMethod(clazz, m_glue->m_getInstance, javaObject.get(), this);
354        m_glue->m_javaProxy = env->NewGlobalRef(obj);
355        // Send the poster
356        jstring jUrl = 0;
357        if (m_posterUrl.length())
358            jUrl = wtfStringToJstring(env, m_posterUrl);
359        // Sending a NULL jUrl allows the Java side to try to load the default poster.
360        env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_loadPoster, jUrl);
361        if (jUrl)
362            env->DeleteLocalRef(jUrl);
363
364        // Clean up.
365        env->DeleteLocalRef(obj);
366        env->DeleteLocalRef(clazz);
367        checkException(env);
368    }
369
370    float maxTimeSeekable() const
371    {
372        return m_duration;
373    }
374};
375
376class MediaPlayerAudioPrivate : public MediaPlayerPrivate {
377public:
378    void load(const String& url)
379    {
380        m_url = url;
381        JNIEnv* env = JSC::Bindings::getJNIEnv();
382        if (!env || !m_url.length())
383            return;
384
385        createJavaPlayerIfNeeded();
386
387        if (!m_glue->m_javaProxy)
388            return;
389
390        jstring jUrl = wtfStringToJstring(env, m_url);
391        // start loading the data asynchronously
392        env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_setDataSource, jUrl);
393        env->DeleteLocalRef(jUrl);
394        checkException(env);
395    }
396
397    void play()
398    {
399        JNIEnv* env = JSC::Bindings::getJNIEnv();
400        if (!env || !m_url.length())
401            return;
402
403        createJavaPlayerIfNeeded();
404
405        if (!m_glue->m_javaProxy)
406            return;
407
408        m_paused = false;
409        m_player->playbackStateChanged();
410        env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_play);
411        checkException(env);
412    }
413
414    virtual bool hasAudio() const { return true; }
415    virtual bool hasVideo() const { return false; }
416    virtual bool supportsFullscreen() const { return false; }
417
418    float maxTimeSeekable() const
419    {
420        if (m_glue->m_javaProxy) {
421            JNIEnv* env = JSC::Bindings::getJNIEnv();
422            if (env) {
423                float maxTime = env->CallFloatMethod(m_glue->m_javaProxy,
424                                                     m_glue->m_getMaxTimeSeekable);
425                checkException(env);
426                return maxTime;
427            }
428        }
429        return 0;
430    }
431
432    MediaPlayerAudioPrivate(MediaPlayer* player) : MediaPlayerPrivate(player)
433    {
434        JNIEnv* env = JSC::Bindings::getJNIEnv();
435        if (!env)
436            return;
437
438        jclass clazz = env->FindClass(g_ProxyJavaClassAudio);
439
440        if (!clazz)
441            return;
442
443        m_glue = new JavaGlue;
444        m_glue->m_newInstance = env->GetMethodID(clazz, "<init>", "(Landroid/webkit/WebViewCore;I)V");
445        m_glue->m_setDataSource = env->GetMethodID(clazz, "setDataSource", "(Ljava/lang/String;)V");
446        m_glue->m_play = env->GetMethodID(clazz, "play", "()V");
447        m_glue->m_getMaxTimeSeekable = env->GetMethodID(clazz, "getMaxTimeSeekable", "()F");
448        m_glue->m_teardown = env->GetMethodID(clazz, "teardown", "()V");
449        m_glue->m_seek = env->GetMethodID(clazz, "seek", "(I)V");
450        m_glue->m_pause = env->GetMethodID(clazz, "pause", "()V");
451        m_glue->m_javaProxy = 0;
452        env->DeleteLocalRef(clazz);
453        // An exception is raised if any of the above fails.
454        checkException(env);
455    }
456
457    void createJavaPlayerIfNeeded()
458    {
459        // Check if we have been already created.
460        if (m_glue->m_javaProxy)
461            return;
462
463        JNIEnv* env = JSC::Bindings::getJNIEnv();
464        if (!env)
465            return;
466
467        jclass clazz = env->FindClass(g_ProxyJavaClassAudio);
468
469        if (!clazz)
470            return;
471
472        FrameView* frameView = m_player->mediaPlayerClient()->mediaPlayerOwningDocument()->view();
473        if (!frameView)
474            return;
475        AutoJObject javaObject = WebViewCore::getWebViewCore(frameView)->getJavaObject();
476        if (!javaObject.get())
477            return;
478
479        jobject obj = 0;
480
481        // Get the HTML5Audio instance
482        obj = env->NewObject(clazz, m_glue->m_newInstance, javaObject.get(), this);
483        m_glue->m_javaProxy = env->NewGlobalRef(obj);
484
485        // Clean up.
486        if (obj)
487            env->DeleteLocalRef(obj);
488        env->DeleteLocalRef(clazz);
489        checkException(env);
490    }
491
492    void onPrepared(int duration, int width, int height)
493    {
494        // Android media player gives us a duration of 0 for a live
495        // stream, so in that case set the real duration to infinity.
496        // We'll still be able to handle the case that we genuinely
497        // get an audio clip with a duration of 0s as we'll get the
498        // ended event when it stops playing.
499        if (duration > 0) {
500            m_duration = duration / 1000.0f;
501        } else {
502            m_duration = std::numeric_limits<float>::infinity();
503        }
504        m_player->durationChanged();
505        m_player->sizeChanged();
506        m_player->prepareToPlay();
507    }
508};
509
510MediaPlayerPrivateInterface* MediaPlayerPrivate::create(MediaPlayer* player)
511{
512    if (player->mediaElementType() == MediaPlayer::Video)
513       return new MediaPlayerVideoPrivate(player);
514    return new MediaPlayerAudioPrivate(player);
515}
516
517}
518
519namespace android {
520
521static void OnPrepared(JNIEnv* env, jobject obj, int duration, int width, int height, int pointer)
522{
523    if (pointer) {
524        WebCore::MediaPlayerPrivate* player = reinterpret_cast<WebCore::MediaPlayerPrivate*>(pointer);
525        player->onPrepared(duration, width, height);
526    }
527}
528
529static void OnEnded(JNIEnv* env, jobject obj, int pointer)
530{
531    if (pointer) {
532        WebCore::MediaPlayerPrivate* player = reinterpret_cast<WebCore::MediaPlayerPrivate*>(pointer);
533        player->onEnded();
534    }
535}
536
537static void OnPaused(JNIEnv* env, jobject obj, int pointer)
538{
539    if (pointer) {
540        WebCore::MediaPlayerPrivate* player = reinterpret_cast<WebCore::MediaPlayerPrivate*>(pointer);
541        player->onPaused();
542    }
543}
544
545static void OnPosterFetched(JNIEnv* env, jobject obj, jobject poster, int pointer)
546{
547    if (!pointer || !poster)
548        return;
549
550    WebCore::MediaPlayerPrivate* player = reinterpret_cast<WebCore::MediaPlayerPrivate*>(pointer);
551    SkBitmap* posterNative = GraphicsJNI::getNativeBitmap(env, poster);
552    if (!posterNative)
553        return;
554    player->onPosterFetched(posterNative);
555}
556
557static void OnBuffering(JNIEnv* env, jobject obj, int percent, int pointer)
558{
559    if (pointer) {
560        WebCore::MediaPlayerPrivate* player = reinterpret_cast<WebCore::MediaPlayerPrivate*>(pointer);
561        // TODO: player->onBuffering(percent);
562    }
563}
564
565static void OnTimeupdate(JNIEnv* env, jobject obj, int position, int pointer)
566{
567    if (pointer) {
568        WebCore::MediaPlayerPrivate* player = reinterpret_cast<WebCore::MediaPlayerPrivate*>(pointer);
569        player->onTimeupdate(position);
570    }
571}
572
573// This is called on the UI thread only.
574// The video layers are composited on the webkit thread and then copied over
575// to the UI thread with the same ID. For rendering, we are only using the
576// video layers on the UI thread. Therefore, on the UI thread, we have to use
577// the videoLayerId from Java side to find the exact video layer in the tree
578// to set the surface texture.
579// Every time a play call into Java side, the videoLayerId will be sent and
580// saved in Java side. Then every time setBaseLayer call, the saved
581// videoLayerId will be passed to this function to find the Video Layer.
582// Return value: true when the video layer is found.
583static bool SendSurfaceTexture(JNIEnv* env, jobject obj, jobject surfTex,
584                               int baseLayer, int videoLayerId,
585                               int textureName, int playerState) {
586    if (!surfTex)
587        return false;
588
589    sp<SurfaceTexture> texture = android::SurfaceTexture_getSurfaceTexture(env, surfTex);
590    if (!texture.get())
591        return false;
592
593    BaseLayerAndroid* layerImpl = reinterpret_cast<BaseLayerAndroid*>(baseLayer);
594    if (!layerImpl)
595        return false;
596    if (!layerImpl->countChildren())
597        return false;
598    LayerAndroid* compositedRoot = static_cast<LayerAndroid*>(layerImpl->getChild(0));
599    if (!compositedRoot)
600        return false;
601
602    VideoLayerAndroid* videoLayer =
603        static_cast<VideoLayerAndroid*>(compositedRoot->findById(videoLayerId));
604    if (!videoLayer)
605        return false;
606
607    // Set the SurfaceTexture to the layer we found
608    videoLayer->setSurfaceTexture(texture, textureName, static_cast<PlayerState>(playerState));
609    return true;
610}
611
612static void OnStopFullscreen(JNIEnv* env, jobject obj, int pointer)
613{
614    if (pointer) {
615        WebCore::MediaPlayerPrivate* player =
616            reinterpret_cast<WebCore::MediaPlayerPrivate*>(pointer);
617        player->onStopFullscreen();
618    }
619}
620
621/*
622 * JNI registration
623 */
624static JNINativeMethod g_MediaPlayerMethods[] = {
625    { "nativeOnPrepared", "(IIII)V",
626        (void*) OnPrepared },
627    { "nativeOnEnded", "(I)V",
628        (void*) OnEnded },
629    { "nativeOnStopFullscreen", "(I)V",
630        (void*) OnStopFullscreen },
631    { "nativeOnPaused", "(I)V",
632        (void*) OnPaused },
633    { "nativeOnPosterFetched", "(Landroid/graphics/Bitmap;I)V",
634        (void*) OnPosterFetched },
635    { "nativeSendSurfaceTexture", "(Landroid/graphics/SurfaceTexture;IIII)Z",
636        (void*) SendSurfaceTexture },
637    { "nativeOnTimeupdate", "(II)V",
638        (void*) OnTimeupdate },
639};
640
641static JNINativeMethod g_MediaAudioPlayerMethods[] = {
642    { "nativeOnBuffering", "(II)V",
643        (void*) OnBuffering },
644    { "nativeOnEnded", "(I)V",
645        (void*) OnEnded },
646    { "nativeOnPrepared", "(IIII)V",
647        (void*) OnPrepared },
648    { "nativeOnTimeupdate", "(II)V",
649        (void*) OnTimeupdate },
650};
651
652int registerMediaPlayerVideo(JNIEnv* env)
653{
654    return jniRegisterNativeMethods(env, g_ProxyJavaClass,
655            g_MediaPlayerMethods, NELEM(g_MediaPlayerMethods));
656}
657
658int registerMediaPlayerAudio(JNIEnv* env)
659{
660    return jniRegisterNativeMethods(env, g_ProxyJavaClassAudio,
661            g_MediaAudioPlayerMethods, NELEM(g_MediaAudioPlayerMethods));
662}
663
664}
665#endif // VIDEO
666