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