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