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