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 48 using namespace android; 49 // Forward decl 50 namespace android { 51 sp<SurfaceTexture> SurfaceTexture_getSurfaceTexture(JNIEnv* env, jobject thiz); 52 }; 53 54 namespace WebCore { 55 56 static const char* g_ProxyJavaClass = "android/webkit/HTML5VideoViewProxy"; 57 static const char* g_ProxyJavaClassAudio = "android/webkit/HTML5Audio"; 58 59 struct 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 74 MediaPlayerPrivate::~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 89 void MediaPlayerPrivate::registerMediaEngine(MediaEngineRegistrar registrar) 90 { 91 registrar(create, getSupportedTypes, supportsType, 0, 0, 0); 92 } 93 94 MediaPlayer::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 101 void 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 113 void MediaPlayerPrivate::setVisible(bool visible) 114 { 115 m_isVisible = visible; 116 if (m_isVisible) 117 createJavaPlayerIfNeeded(); 118 } 119 120 void 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 133 void 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 146 MediaPlayerPrivate::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 162 void 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 171 void MediaPlayerPrivate::onPaused() 172 { 173 m_paused = true; 174 m_player->playbackStateChanged(); 175 m_networkState = MediaPlayer::Idle; 176 m_player->playbackStateChanged(); 177 } 178 179 void MediaPlayerPrivate::onTimeupdate(int position) 180 { 181 m_currentTime = position / 1000.0f; 182 m_player->timeChanged(); 183 } 184 185 void MediaPlayerPrivate::onStopFullscreen() 186 { 187 if (m_player && m_player->mediaPlayerClient() 188 && m_player->mediaPlayerClient()->mediaPlayerOwningDocument()) { 189 m_player->mediaPlayerClient()->mediaPlayerOwningDocument()->webkitCancelFullScreen(); 190 } 191 } 192 193 class MediaPlayerVideoPrivate : public MediaPlayerPrivate { 194 public: 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 376 class MediaPlayerAudioPrivate : public MediaPlayerPrivate { 377 public: 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 510 MediaPlayerPrivateInterface* 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 519 namespace android { 520 521 static 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 529 static 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 537 static 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 545 static 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 557 static 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 565 static 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. 583 static 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 612 static 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 */ 624 static 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 641 static 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 652 int registerMediaPlayerVideo(JNIEnv* env) 653 { 654 return jniRegisterNativeMethods(env, g_ProxyJavaClass, 655 g_MediaPlayerMethods, NELEM(g_MediaPlayerMethods)); 656 } 657 658 int registerMediaPlayerAudio(JNIEnv* env) 659 { 660 return jniRegisterNativeMethods(env, g_ProxyJavaClassAudio, 661 g_MediaAudioPlayerMethods, NELEM(g_MediaAudioPlayerMethods)); 662 } 663 664 } 665 #endif // VIDEO 666