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 "GraphicsContext.h" 32 #include "SkiaUtils.h" 33 #include "WebCoreJni.h" 34 #include "WebViewCore.h" 35 36 #include <GraphicsJNI.h> 37 #include <JNIHelp.h> 38 #include <JNIUtility.h> 39 #include <SkBitmap.h> 40 41 using namespace android; 42 43 namespace WebCore { 44 45 static const char* g_ProxyJavaClass = "android/webkit/HTML5VideoViewProxy"; 46 static const char* g_ProxyJavaClassAudio = "android/webkit/HTML5Audio"; 47 48 struct MediaPlayerPrivate::JavaGlue 49 { 50 jobject m_javaProxy; 51 jmethodID m_play; 52 jmethodID m_teardown; 53 jmethodID m_seek; 54 jmethodID m_pause; 55 // Audio 56 jmethodID m_newInstance; 57 jmethodID m_setDataSource; 58 jmethodID m_getMaxTimeSeekable; 59 // Video 60 jmethodID m_getInstance; 61 jmethodID m_loadPoster; 62 }; 63 64 MediaPlayerPrivate::~MediaPlayerPrivate() 65 { 66 if (m_glue->m_javaProxy) { 67 JNIEnv* env = JSC::Bindings::getJNIEnv(); 68 if (env) { 69 env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_teardown); 70 env->DeleteGlobalRef(m_glue->m_javaProxy); 71 } 72 } 73 delete m_glue; 74 } 75 76 void MediaPlayerPrivate::registerMediaEngine(MediaEngineRegistrar registrar) 77 { 78 registrar(create, getSupportedTypes, supportsType); 79 } 80 81 void MediaPlayerPrivate::pause() 82 { 83 JNIEnv* env = JSC::Bindings::getJNIEnv(); 84 if (!env || !m_glue->m_javaProxy || !m_url.length()) 85 return; 86 87 m_paused = true; 88 env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_pause); 89 checkException(env); 90 } 91 92 void MediaPlayerPrivate::setVisible(bool visible) 93 { 94 m_isVisible = visible; 95 if (m_isVisible) 96 createJavaPlayerIfNeeded(); 97 } 98 99 void MediaPlayerPrivate::seek(float time) 100 { 101 JNIEnv* env = JSC::Bindings::getJNIEnv(); 102 if (!env || !m_url.length()) 103 return; 104 105 if (m_glue->m_javaProxy) { 106 env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_seek, static_cast<jint>(time * 1000.0f)); 107 m_currentTime = time; 108 } 109 checkException(env); 110 } 111 112 113 void MediaPlayerPrivate::prepareToPlay() { 114 // We are about to start playing. Since our Java VideoView cannot 115 // buffer any data, we just simply transition to the HaveEnoughData 116 // state in here. This will allow the MediaPlayer to transition to 117 // the "play" state, at which point our VideoView will start downloading 118 // the content and start the playback. 119 m_networkState = MediaPlayer::Loaded; 120 m_player->networkStateChanged(); 121 m_readyState = MediaPlayer::HaveEnoughData; 122 m_player->readyStateChanged(); 123 } 124 125 MediaPlayer::SupportsType MediaPlayerPrivate::supportsType(const String& type, const String& codecs) 126 { 127 if (WebViewCore::supportsMimeType(type)) 128 return MediaPlayer::MayBeSupported; 129 return MediaPlayer::IsNotSupported; 130 } 131 132 MediaPlayerPrivate::MediaPlayerPrivate(MediaPlayer* player) 133 : m_player(player), 134 m_glue(0), 135 m_duration(6000), 136 m_currentTime(0), 137 m_paused(true), 138 m_hasVideo(false), 139 m_readyState(MediaPlayer::HaveNothing), 140 m_networkState(MediaPlayer::Empty), 141 m_poster(0), 142 m_naturalSize(100, 100), 143 m_naturalSizeUnknown(true), 144 m_isVisible(false) 145 { 146 } 147 148 void MediaPlayerPrivate::onEnded() { 149 m_currentTime = duration(); 150 m_player->timeChanged(); 151 m_paused = true; 152 m_currentTime = 0; 153 m_hasVideo = false; 154 m_networkState = MediaPlayer::Idle; 155 m_readyState = MediaPlayer::HaveNothing; 156 } 157 158 void MediaPlayerPrivate::onPaused() { 159 m_paused = true; 160 m_currentTime = 0; 161 m_hasVideo = false; 162 m_networkState = MediaPlayer::Idle; 163 m_readyState = MediaPlayer::HaveNothing; 164 } 165 166 void MediaPlayerPrivate::onTimeupdate(int position) { 167 m_currentTime = position / 1000.0f; 168 m_player->timeChanged(); 169 } 170 171 class MediaPlayerVideoPrivate : public MediaPlayerPrivate { 172 public: 173 void load(const String& url) { m_url = url; } 174 void play() { 175 JNIEnv* env = JSC::Bindings::getJNIEnv(); 176 if (!env || !m_url.length() || !m_glue->m_javaProxy) 177 return; 178 179 m_paused = false; 180 jstring jUrl = env->NewString((unsigned short *)m_url.characters(), m_url.length()); 181 env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_play, jUrl); 182 env->DeleteLocalRef(jUrl); 183 184 checkException(env); 185 } 186 bool canLoadPoster() const { return true; } 187 void setPoster(const String& url) { 188 m_posterUrl = url; 189 JNIEnv* env = JSC::Bindings::getJNIEnv(); 190 if (!env || !m_glue->m_javaProxy || !m_posterUrl.length()) 191 return; 192 // Send the poster 193 jstring jUrl = env->NewString((unsigned short *)m_posterUrl.characters(), m_posterUrl.length()); 194 env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_loadPoster, jUrl); 195 env->DeleteLocalRef(jUrl); 196 } 197 void paint(GraphicsContext* ctxt, const IntRect& r) { 198 if (ctxt->paintingDisabled()) 199 return; 200 201 if (!m_isVisible) 202 return; 203 204 if (!m_poster || (!m_poster->getPixels() && !m_poster->pixelRef())) 205 return; 206 207 SkCanvas* canvas = ctxt->platformContext()->mCanvas; 208 // We paint with the following rules in mind: 209 // - only downscale the poster, never upscale 210 // - maintain the natural aspect ratio of the poster 211 // - the poster should be centered in the target rect 212 float originalRatio = static_cast<float>(m_poster->width()) / static_cast<float>(m_poster->height()); 213 int posterWidth = r.width() > m_poster->width() ? m_poster->width() : r.width(); 214 int posterHeight = posterWidth / originalRatio; 215 int posterX = ((r.width() - posterWidth) / 2) + r.x(); 216 int posterY = ((r.height() - posterHeight) / 2) + r.y(); 217 IntRect targetRect(posterX, posterY, posterWidth, posterHeight); 218 canvas->drawBitmapRect(*m_poster, 0, targetRect, 0); 219 } 220 221 void onPosterFetched(SkBitmap* poster) { 222 m_poster = poster; 223 if (m_naturalSizeUnknown) { 224 // We had to fake the size at startup, or else our paint 225 // method would not be called. If we haven't yet received 226 // the onPrepared event, update the intrinsic size to the size 227 // of the poster. That will be overriden when onPrepare comes. 228 // In case of an error, we should report the poster size, rather 229 // than our initial fake value. 230 m_naturalSize = IntSize(poster->width(), poster->height()); 231 m_player->sizeChanged(); 232 } 233 } 234 235 void onPrepared(int duration, int width, int height) { 236 m_duration = duration / 1000.0f; 237 m_naturalSize = IntSize(width, height); 238 m_naturalSizeUnknown = false; 239 m_hasVideo = true; 240 m_player->durationChanged(); 241 m_player->sizeChanged(); 242 } 243 244 bool hasAudio() { return false; } // do not display the audio UI 245 bool hasVideo() { return m_hasVideo; } 246 247 MediaPlayerVideoPrivate(MediaPlayer* player) : MediaPlayerPrivate(player) { 248 JNIEnv* env = JSC::Bindings::getJNIEnv(); 249 if (!env) 250 return; 251 252 jclass clazz = env->FindClass(g_ProxyJavaClass); 253 254 if (!clazz) 255 return; 256 257 m_glue = new JavaGlue; 258 m_glue->m_getInstance = env->GetStaticMethodID(clazz, "getInstance", "(Landroid/webkit/WebViewCore;I)Landroid/webkit/HTML5VideoViewProxy;"); 259 m_glue->m_loadPoster = env->GetMethodID(clazz, "loadPoster", "(Ljava/lang/String;)V"); 260 m_glue->m_play = env->GetMethodID(clazz, "play", "(Ljava/lang/String;)V"); 261 262 m_glue->m_teardown = env->GetMethodID(clazz, "teardown", "()V"); 263 m_glue->m_seek = env->GetMethodID(clazz, "seek", "(I)V"); 264 m_glue->m_pause = env->GetMethodID(clazz, "pause", "()V"); 265 m_glue->m_javaProxy = NULL; 266 env->DeleteLocalRef(clazz); 267 // An exception is raised if any of the above fails. 268 checkException(env); 269 } 270 271 void createJavaPlayerIfNeeded() { 272 // Check if we have been already created. 273 if (m_glue->m_javaProxy) 274 return; 275 276 JNIEnv* env = JSC::Bindings::getJNIEnv(); 277 if (!env) 278 return; 279 280 jclass clazz = env->FindClass(g_ProxyJavaClass); 281 282 if (!clazz) 283 return; 284 285 jobject obj = NULL; 286 287 FrameView* frameView = m_player->frameView(); 288 if (!frameView) 289 return; 290 WebViewCore* webViewCore = WebViewCore::getWebViewCore(frameView); 291 ASSERT(webViewCore); 292 293 // Get the HTML5VideoViewProxy instance 294 obj = env->CallStaticObjectMethod(clazz, m_glue->m_getInstance, webViewCore->getJavaObject().get(), this); 295 m_glue->m_javaProxy = env->NewGlobalRef(obj); 296 // Send the poster 297 jstring jUrl = 0; 298 if (m_posterUrl.length()) 299 jUrl = env->NewString((unsigned short *)m_posterUrl.characters(), m_posterUrl.length()); 300 // Sending a NULL jUrl allows the Java side to try to load the default poster. 301 env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_loadPoster, jUrl); 302 if (jUrl) 303 env->DeleteLocalRef(jUrl); 304 305 // Clean up. 306 if (obj) 307 env->DeleteLocalRef(obj); 308 env->DeleteLocalRef(clazz); 309 checkException(env); 310 } 311 }; 312 313 class MediaPlayerAudioPrivate : public MediaPlayerPrivate { 314 public: 315 void load(const String& url) { 316 m_url = url; 317 JNIEnv* env = JSC::Bindings::getJNIEnv(); 318 if (!env || !m_url.length()) 319 return; 320 321 createJavaPlayerIfNeeded(); 322 323 if (!m_glue->m_javaProxy) 324 return; 325 326 jstring jUrl = env->NewString((unsigned short *)m_url.characters(), m_url.length()); 327 // start loading the data asynchronously 328 env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_setDataSource, jUrl); 329 env->DeleteLocalRef(jUrl); 330 checkException(env); 331 } 332 333 void play() { 334 JNIEnv* env = JSC::Bindings::getJNIEnv(); 335 if (!env || !m_url.length()) 336 return; 337 338 createJavaPlayerIfNeeded(); 339 340 if (!m_glue->m_javaProxy) 341 return; 342 343 m_paused = false; 344 env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_play); 345 checkException(env); 346 } 347 348 bool hasAudio() { return true; } 349 350 float maxTimeSeekable() const { 351 if (m_glue->m_javaProxy) { 352 JNIEnv* env = JSC::Bindings::getJNIEnv(); 353 if (env) { 354 float maxTime = env->CallFloatMethod(m_glue->m_javaProxy, 355 m_glue->m_getMaxTimeSeekable); 356 checkException(env); 357 return maxTime; 358 } 359 } 360 return 0; 361 } 362 363 MediaPlayerAudioPrivate(MediaPlayer* player) : MediaPlayerPrivate(player) { 364 JNIEnv* env = JSC::Bindings::getJNIEnv(); 365 if (!env) 366 return; 367 368 jclass clazz = env->FindClass(g_ProxyJavaClassAudio); 369 370 if (!clazz) 371 return; 372 373 m_glue = new JavaGlue; 374 m_glue->m_newInstance = env->GetMethodID(clazz, "<init>", "(I)V"); 375 m_glue->m_setDataSource = env->GetMethodID(clazz, "setDataSource", "(Ljava/lang/String;)V"); 376 m_glue->m_play = env->GetMethodID(clazz, "play", "()V"); 377 m_glue->m_getMaxTimeSeekable = env->GetMethodID(clazz, "getMaxTimeSeekable", "()F"); 378 m_glue->m_teardown = env->GetMethodID(clazz, "teardown", "()V"); 379 m_glue->m_seek = env->GetMethodID(clazz, "seek", "(I)V"); 380 m_glue->m_pause = env->GetMethodID(clazz, "pause", "()V"); 381 m_glue->m_javaProxy = NULL; 382 env->DeleteLocalRef(clazz); 383 // An exception is raised if any of the above fails. 384 checkException(env); 385 } 386 387 void createJavaPlayerIfNeeded() { 388 // Check if we have been already created. 389 if (m_glue->m_javaProxy) 390 return; 391 392 JNIEnv* env = JSC::Bindings::getJNIEnv(); 393 if (!env) 394 return; 395 396 jclass clazz = env->FindClass(g_ProxyJavaClassAudio); 397 398 if (!clazz) 399 return; 400 401 jobject obj = NULL; 402 403 // Get the HTML5Audio instance 404 obj = env->NewObject(clazz, m_glue->m_newInstance, this); 405 m_glue->m_javaProxy = env->NewGlobalRef(obj); 406 407 // Clean up. 408 if (obj) 409 env->DeleteLocalRef(obj); 410 env->DeleteLocalRef(clazz); 411 checkException(env); 412 } 413 414 void onPrepared(int duration, int width, int height) { 415 m_duration = duration / 1000.0f; 416 m_player->durationChanged(); 417 m_player->sizeChanged(); 418 m_player->prepareToPlay(); 419 } 420 }; 421 422 MediaPlayerPrivateInterface* MediaPlayerPrivate::create(MediaPlayer* player) 423 { 424 if (player->mediaElementType() == MediaPlayer::Video) 425 return new MediaPlayerVideoPrivate(player); 426 return new MediaPlayerAudioPrivate(player); 427 } 428 429 } 430 431 namespace android { 432 433 static void OnPrepared(JNIEnv* env, jobject obj, int duration, int width, int height, int pointer) { 434 if (pointer) { 435 WebCore::MediaPlayerPrivate* player = reinterpret_cast<WebCore::MediaPlayerPrivate*>(pointer); 436 player->onPrepared(duration, width, height); 437 } 438 } 439 440 static void OnEnded(JNIEnv* env, jobject obj, int pointer) { 441 if (pointer) { 442 WebCore::MediaPlayerPrivate* player = reinterpret_cast<WebCore::MediaPlayerPrivate*>(pointer); 443 player->onEnded(); 444 } 445 } 446 447 static void OnPaused(JNIEnv* env, jobject obj, int pointer) { 448 if (pointer) { 449 WebCore::MediaPlayerPrivate* player = reinterpret_cast<WebCore::MediaPlayerPrivate*>(pointer); 450 player->onPaused(); 451 } 452 } 453 454 static void OnPosterFetched(JNIEnv* env, jobject obj, jobject poster, int pointer) { 455 if (!pointer || !poster) 456 return; 457 458 WebCore::MediaPlayerPrivate* player = reinterpret_cast<WebCore::MediaPlayerPrivate*>(pointer); 459 SkBitmap* posterNative = GraphicsJNI::getNativeBitmap(env, poster); 460 if (!posterNative) 461 return; 462 player->onPosterFetched(posterNative); 463 } 464 465 static void OnBuffering(JNIEnv* env, jobject obj, int percent, int pointer) { 466 if (pointer) { 467 WebCore::MediaPlayerPrivate* player = reinterpret_cast<WebCore::MediaPlayerPrivate*>(pointer); 468 //TODO: player->onBuffering(percent); 469 } 470 } 471 472 static void OnTimeupdate(JNIEnv* env, jobject obj, int position, int pointer) { 473 if (pointer) { 474 WebCore::MediaPlayerPrivate* player = reinterpret_cast<WebCore::MediaPlayerPrivate*>(pointer); 475 player->onTimeupdate(position); 476 } 477 } 478 479 /* 480 * JNI registration 481 */ 482 static JNINativeMethod g_MediaPlayerMethods[] = { 483 { "nativeOnPrepared", "(IIII)V", 484 (void*) OnPrepared }, 485 { "nativeOnEnded", "(I)V", 486 (void*) OnEnded }, 487 { "nativeOnPaused", "(I)V", 488 (void*) OnPaused }, 489 { "nativeOnPosterFetched", "(Landroid/graphics/Bitmap;I)V", 490 (void*) OnPosterFetched }, 491 { "nativeOnTimeupdate", "(II)V", 492 (void*) OnTimeupdate }, 493 }; 494 495 static JNINativeMethod g_MediaAudioPlayerMethods[] = { 496 { "nativeOnBuffering", "(II)V", 497 (void*) OnBuffering }, 498 { "nativeOnEnded", "(I)V", 499 (void*) OnEnded }, 500 { "nativeOnPrepared", "(IIII)V", 501 (void*) OnPrepared }, 502 { "nativeOnTimeupdate", "(II)V", 503 (void*) OnTimeupdate }, 504 }; 505 506 int register_mediaplayer_video(JNIEnv* env) 507 { 508 return jniRegisterNativeMethods(env, g_ProxyJavaClass, 509 g_MediaPlayerMethods, NELEM(g_MediaPlayerMethods)); 510 } 511 512 int register_mediaplayer_audio(JNIEnv* env) 513 { 514 return jniRegisterNativeMethods(env, g_ProxyJavaClassAudio, 515 g_MediaAudioPlayerMethods, NELEM(g_MediaAudioPlayerMethods)); 516 } 517 518 } 519 #endif // VIDEO 520