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