2 * Copyright 2009, The Android Open Source Project
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
27 #include "MediaPlayerPrivateAndroid.h"
31 #include "BaseLayerAndroid.h"
32 #include "DocumentLoader.h"
34 #include "FrameLoader.h"
35 #include "FrameView.h"
36 #include "GraphicsContext.h"
37 #include "SkiaUtils.h"
38 #include "VideoLayerAndroid.h"
39 #include "WebCoreJni.h"
40 #include "WebViewCore.h"
41 #include <GraphicsJNI.h>
43 #include <JNIUtility.h>
45 #include <gui/SurfaceTexture.h>
47 using namespace android;
50 sp<SurfaceTexture> SurfaceTexture_getSurfaceTexture(JNIEnv* env, jobject thiz);
55 static const char* g_ProxyJavaClass = "android/webkit/HTML5VideoViewProxy";
56 static const char* g_ProxyJavaClassAudio = "android/webkit/HTML5Audio";
58 struct MediaPlayerPrivate::JavaGlue {
65 jmethodID m_newInstance;
66 jmethodID m_setDataSource;
67 jmethodID m_getMaxTimeSeekable;
69 jmethodID m_getInstance;
70 jmethodID m_loadPoster;
73 MediaPlayerPrivate::~MediaPlayerPrivate()
75 // m_videoLayer is reference counted, unref is enough here.
76 m_videoLayer->unref();
77 if (m_glue->m_javaProxy) {
78 JNIEnv* env = JSC::Bindings::getJNIEnv();
80 env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_teardown);
81 env->DeleteGlobalRef(m_glue->m_javaProxy);
87 void MediaPlayerPrivate::registerMediaEngine(MediaEngineRegistrar registrar)
89 registrar(create, getSupportedTypes, supportsType);
92 MediaPlayer::SupportsType MediaPlayerPrivate::supportsType(const String& type, const String& codecs)
94 if (WebViewCore::isSupportedMediaMimeType(type))
95 return MediaPlayer::MayBeSupported;
96 return MediaPlayer::IsNotSupported;
99 void MediaPlayerPrivate::pause()
101 JNIEnv* env = JSC::Bindings::getJNIEnv();
102 if (!env || !m_glue->m_javaProxy || !m_url.length())
106 m_player->playbackStateChanged();
107 env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_pause);
111 void MediaPlayerPrivate::setVisible(bool visible)
113 m_isVisible = visible;
115 createJavaPlayerIfNeeded();
118 void MediaPlayerPrivate::seek(float time)
120 JNIEnv* env = JSC::Bindings::getJNIEnv();
121 if (!env || !m_url.length())
124 if (m_glue->m_javaProxy) {
125 env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_seek, static_cast<jint>(time * 1000.0f));
126 m_currentTime = time;
131 void MediaPlayerPrivate::prepareToPlay()
133 // We are about to start playing. Since our Java VideoView cannot
134 // buffer any data, we just simply transition to the HaveEnoughData
135 // state in here. This will allow the MediaPlayer to transition to
136 // the "play" state, at which point our VideoView will start downloading
137 // the content and start the playback.
138 m_networkState = MediaPlayer::Loaded;
139 m_player->networkStateChanged();
140 m_readyState = MediaPlayer::HaveEnoughData;
141 m_player->readyStateChanged();
144 MediaPlayerPrivate::MediaPlayerPrivate(MediaPlayer* player)
147 m_duration(1), // keep this minimal to avoid initial seek problem
151 m_readyState(MediaPlayer::HaveNothing),
152 m_networkState(MediaPlayer::Empty),
154 m_naturalSize(100, 100),
155 m_naturalSizeUnknown(true),
157 m_videoLayer(new VideoLayerAndroid())
161 void MediaPlayerPrivate::onEnded()
163 m_currentTime = duration();
164 m_player->timeChanged();
166 m_player->playbackStateChanged();
168 m_networkState = MediaPlayer::Idle;
171 void MediaPlayerPrivate::onPaused()
174 m_player->playbackStateChanged();
176 m_networkState = MediaPlayer::Idle;
177 m_player->playbackStateChanged();
180 void MediaPlayerPrivate::onTimeupdate(int position)
182 m_currentTime = position / 1000.0f;
183 m_player->timeChanged();
186 class MediaPlayerVideoPrivate : public MediaPlayerPrivate {
188 void load(const String& url)
191 // Cheat a bit here to make sure Window.onLoad event can be triggered
192 // at the right time instead of real video play time, since only full
193 // screen video play is supported in Java's VideoView.
194 // See also comments in prepareToPlay function.
195 m_networkState = MediaPlayer::Loading;
196 m_player->networkStateChanged();
197 m_readyState = MediaPlayer::HaveCurrentData;
198 m_player->readyStateChanged();
203 JNIEnv* env = JSC::Bindings::getJNIEnv();
204 if (!env || !m_url.length() || !m_glue->m_javaProxy)
207 // We only play video fullscreen on Android, so stop sites playing fullscreen video in the onload handler.
208 Frame* frame = m_player->frameView()->frame();
209 if (frame && !frame->loader()->documentLoader()->wasOnloadHandled())
213 m_player->playbackStateChanged();
215 if (m_currentTime == duration())
218 jstring jUrl = wtfStringToJstring(env, m_url);
219 env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_play, jUrl,
220 static_cast<jint>(m_currentTime * 1000.0f),
221 m_videoLayer->uniqueId());
222 env->DeleteLocalRef(jUrl);
226 bool canLoadPoster() const { return true; }
227 void setPoster(const String& url)
229 if (m_posterUrl == url)
233 JNIEnv* env = JSC::Bindings::getJNIEnv();
234 if (!env || !m_glue->m_javaProxy || !m_posterUrl.length())
237 jstring jUrl = wtfStringToJstring(env, m_posterUrl);
238 env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_loadPoster, jUrl);
239 env->DeleteLocalRef(jUrl);
241 void paint(GraphicsContext* ctxt, const IntRect& r)
243 if (ctxt->paintingDisabled())
249 if (!m_poster || (!m_poster->getPixels() && !m_poster->pixelRef()))
252 SkCanvas* canvas = ctxt->platformContext()->mCanvas;
253 // We paint with the following rules in mind:
254 // - only downscale the poster, never upscale
255 // - maintain the natural aspect ratio of the poster
256 // - the poster should be centered in the target rect
257 float originalRatio = static_cast<float>(m_poster->width()) / static_cast<float>(m_poster->height());
258 int posterWidth = r.width() > m_poster->width() ? m_poster->width() : r.width();
259 int posterHeight = posterWidth / originalRatio;
260 int posterX = ((r.width() - posterWidth) / 2) + r.x();
261 int posterY = ((r.height() - posterHeight) / 2) + r.y();
262 IntRect targetRect(posterX, posterY, posterWidth, posterHeight);
263 canvas->drawBitmapRect(*m_poster, 0, targetRect, 0);
266 void onPosterFetched(SkBitmap* poster)
269 if (m_naturalSizeUnknown) {
270 // We had to fake the size at startup, or else our paint
271 // method would not be called. If we haven't yet received
272 // the onPrepared event, update the intrinsic size to the size
273 // of the poster. That will be overriden when onPrepare comes.
274 // In case of an error, we should report the poster size, rather
275 // than our initial fake value.
276 m_naturalSize = IntSize(poster->width(), poster->height());
277 m_player->sizeChanged();
281 void onPrepared(int duration, int width, int height)
283 m_duration = duration / 1000.0f;
284 m_naturalSize = IntSize(width, height);
285 m_naturalSizeUnknown = false;
287 m_player->durationChanged();
288 m_player->sizeChanged();
291 virtual bool hasAudio() const { return false; } // do not display the audio UI
292 virtual bool hasVideo() const { return m_hasVideo; }
293 virtual bool supportsFullscreen() const { return true; }
295 MediaPlayerVideoPrivate(MediaPlayer* player) : MediaPlayerPrivate(player)
297 JNIEnv* env = JSC::Bindings::getJNIEnv();
301 jclass clazz = env->FindClass(g_ProxyJavaClass);
306 m_glue = new JavaGlue;
307 m_glue->m_getInstance = env->GetStaticMethodID(clazz, "getInstance", "(Landroid/webkit/WebViewCore;I)Landroid/webkit/HTML5VideoViewProxy;");
308 m_glue->m_loadPoster = env->GetMethodID(clazz, "loadPoster", "(Ljava/lang/String;)V");
309 m_glue->m_play = env->GetMethodID(clazz, "play", "(Ljava/lang/String;II)V");
311 m_glue->m_teardown = env->GetMethodID(clazz, "teardown", "()V");
312 m_glue->m_seek = env->GetMethodID(clazz, "seek", "(I)V");
313 m_glue->m_pause = env->GetMethodID(clazz, "pause", "()V");
314 m_glue->m_javaProxy = 0;
315 env->DeleteLocalRef(clazz);
316 // An exception is raised if any of the above fails.
320 void createJavaPlayerIfNeeded()
322 // Check if we have been already created.
323 if (m_glue->m_javaProxy)
326 JNIEnv* env = JSC::Bindings::getJNIEnv();
330 jclass clazz = env->FindClass(g_ProxyJavaClass);
337 FrameView* frameView = m_player->frameView();
340 WebViewCore* webViewCore = WebViewCore::getWebViewCore(frameView);
343 // Get the HTML5VideoViewProxy instance
344 obj = env->CallStaticObjectMethod(clazz, m_glue->m_getInstance, webViewCore->getJavaObject().get(), this);
345 m_glue->m_javaProxy = env->NewGlobalRef(obj);
348 if (m_posterUrl.length())
349 jUrl = wtfStringToJstring(env, m_posterUrl);
350 // Sending a NULL jUrl allows the Java side to try to load the default poster.
351 env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_loadPoster, jUrl);
353 env->DeleteLocalRef(jUrl);
357 env->DeleteLocalRef(obj);
358 env->DeleteLocalRef(clazz);
362 float maxTimeSeekable() const
368 class MediaPlayerAudioPrivate : public MediaPlayerPrivate {
370 void load(const String& url)
373 JNIEnv* env = JSC::Bindings::getJNIEnv();
374 if (!env || !m_url.length())
377 createJavaPlayerIfNeeded();
379 if (!m_glue->m_javaProxy)
382 jstring jUrl = wtfStringToJstring(env, m_url);
383 // start loading the data asynchronously
384 env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_setDataSource, jUrl);
385 env->DeleteLocalRef(jUrl);
391 JNIEnv* env = JSC::Bindings::getJNIEnv();
392 if (!env || !m_url.length())
395 createJavaPlayerIfNeeded();
397 if (!m_glue->m_javaProxy)
401 m_player->playbackStateChanged();
402 env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_play);
406 virtual bool hasAudio() const { return true; }
407 virtual bool hasVideo() const { return false; }
408 virtual bool supportsFullscreen() const { return false; }
410 float maxTimeSeekable() const
412 if (m_glue->m_javaProxy) {
413 JNIEnv* env = JSC::Bindings::getJNIEnv();
415 float maxTime = env->CallFloatMethod(m_glue->m_javaProxy,
416 m_glue->m_getMaxTimeSeekable);
424 MediaPlayerAudioPrivate(MediaPlayer* player) : MediaPlayerPrivate(player)
426 JNIEnv* env = JSC::Bindings::getJNIEnv();
430 jclass clazz = env->FindClass(g_ProxyJavaClassAudio);
435 m_glue = new JavaGlue;
436 m_glue->m_newInstance = env->GetMethodID(clazz, "<init>", "(I)V");
437 m_glue->m_setDataSource = env->GetMethodID(clazz, "setDataSource", "(Ljava/lang/String;)V");
438 m_glue->m_play = env->GetMethodID(clazz, "play", "()V");
439 m_glue->m_getMaxTimeSeekable = env->GetMethodID(clazz, "getMaxTimeSeekable", "()F");
440 m_glue->m_teardown = env->GetMethodID(clazz, "teardown", "()V");
441 m_glue->m_seek = env->GetMethodID(clazz, "seek", "(I)V");
442 m_glue->m_pause = env->GetMethodID(clazz, "pause", "()V");
443 m_glue->m_javaProxy = 0;
444 env->DeleteLocalRef(clazz);
445 // An exception is raised if any of the above fails.
449 void createJavaPlayerIfNeeded()
451 // Check if we have been already created.
452 if (m_glue->m_javaProxy)
455 JNIEnv* env = JSC::Bindings::getJNIEnv();
459 jclass clazz = env->FindClass(g_ProxyJavaClassAudio);
466 // Get the HTML5Audio instance
467 obj = env->NewObject(clazz, m_glue->m_newInstance, this);
468 m_glue->m_javaProxy = env->NewGlobalRef(obj);
472 env->DeleteLocalRef(obj);
473 env->DeleteLocalRef(clazz);
477 void onPrepared(int duration, int width, int height)
479 // Android media player gives us a duration of 0 for a live
480 // stream, so in that case set the real duration to infinity.
481 // We'll still be able to handle the case that we genuinely
482 // get an audio clip with a duration of 0s as we'll get the
483 // ended event when it stops playing.
485 m_duration = duration / 1000.0f;
487 m_duration = std::numeric_limits<float>::infinity();
489 m_player->durationChanged();
490 m_player->sizeChanged();
491 m_player->prepareToPlay();
495 MediaPlayerPrivateInterface* MediaPlayerPrivate::create(MediaPlayer* player)
497 if (player->mediaElementType() == MediaPlayer::Video)
498 return new MediaPlayerVideoPrivate(player);
499 return new MediaPlayerAudioPrivate(player);
506 static void OnPrepared(JNIEnv* env, jobject obj, int duration, int width, int height, int pointer)
509 WebCore::MediaPlayerPrivate* player = reinterpret_cast<WebCore::MediaPlayerPrivate*>(pointer);
510 player->onPrepared(duration, width, height);
514 static void OnEnded(JNIEnv* env, jobject obj, int pointer)
517 WebCore::MediaPlayerPrivate* player = reinterpret_cast<WebCore::MediaPlayerPrivate*>(pointer);
522 static void OnPaused(JNIEnv* env, jobject obj, int pointer)
525 WebCore::MediaPlayerPrivate* player = reinterpret_cast<WebCore::MediaPlayerPrivate*>(pointer);
530 static void OnPosterFetched(JNIEnv* env, jobject obj, jobject poster, int pointer)
532 if (!pointer || !poster)
535 WebCore::MediaPlayerPrivate* player = reinterpret_cast<WebCore::MediaPlayerPrivate*>(pointer);
536 SkBitmap* posterNative = GraphicsJNI::getNativeBitmap(env, poster);
539 player->onPosterFetched(posterNative);
542 static void OnBuffering(JNIEnv* env, jobject obj, int percent, int pointer)
545 WebCore::MediaPlayerPrivate* player = reinterpret_cast<WebCore::MediaPlayerPrivate*>(pointer);
546 // TODO: player->onBuffering(percent);
550 static void OnTimeupdate(JNIEnv* env, jobject obj, int position, int pointer)
553 WebCore::MediaPlayerPrivate* player = reinterpret_cast<WebCore::MediaPlayerPrivate*>(pointer);
554 player->onTimeupdate(position);
558 // This is called on the UI thread only.
559 // The video layers are composited on the webkit thread and then copied over
560 // to the UI thread with the same ID. For rendering, we are only using the
561 // video layers on the UI thread. Therefore, on the UI thread, we have to use
562 // the videoLayerId from Java side to find the exact video layer in the tree
563 // to set the surface texture.
564 // Every time a play call into Java side, the videoLayerId will be sent and
565 // saved in Java side. Then every time setBaseLayer call, the saved
566 // videoLayerId will be passed to this function to find the Video Layer.
567 // Return value: true when the video layer is found.
568 static bool SendSurfaceTexture(JNIEnv* env, jobject obj, jobject surfTex,
569 int baseLayer, int videoLayerId,
570 int textureName, bool updateTexture) {
574 sp<SurfaceTexture> texture = android::SurfaceTexture_getSurfaceTexture(env, surfTex);
578 BaseLayerAndroid* layerImpl = reinterpret_cast<BaseLayerAndroid*>(baseLayer);
581 if (!layerImpl->countChildren())
583 LayerAndroid* compositedRoot = static_cast<LayerAndroid*>(layerImpl->getChild(0));
587 VideoLayerAndroid* videoLayer =
588 static_cast<VideoLayerAndroid*>(compositedRoot->findById(videoLayerId));
592 // Set the SurfaceTexture to the layer we found
593 videoLayer->setSurfaceTexture(texture, textureName, updateTexture);
601 static JNINativeMethod g_MediaPlayerMethods[] = {
602 { "nativeOnPrepared", "(IIII)V",
603 (void*) OnPrepared },
604 { "nativeOnEnded", "(I)V",
606 { "nativeOnPaused", "(I)V",
608 { "nativeOnPosterFetched", "(Landroid/graphics/Bitmap;I)V",
609 (void*) OnPosterFetched },
610 { "nativeSendSurfaceTexture", "(Landroid/graphics/SurfaceTexture;IIIZ)Z",
611 (void*) SendSurfaceTexture },
612 { "nativeOnTimeupdate", "(II)V",
613 (void*) OnTimeupdate },
616 static JNINativeMethod g_MediaAudioPlayerMethods[] = {
617 { "nativeOnBuffering", "(II)V",
618 (void*) OnBuffering },
619 { "nativeOnEnded", "(I)V",
621 { "nativeOnPrepared", "(IIII)V",
622 (void*) OnPrepared },
623 { "nativeOnTimeupdate", "(II)V",
624 (void*) OnTimeupdate },
627 int registerMediaPlayerVideo(JNIEnv* env)
629 return jniRegisterNativeMethods(env, g_ProxyJavaClass,
630 g_MediaPlayerMethods, NELEM(g_MediaPlayerMethods));
633 int registerMediaPlayerAudio(JNIEnv* env)
635 return jniRegisterNativeMethods(env, g_ProxyJavaClassAudio,
636 g_MediaAudioPlayerMethods, NELEM(g_MediaAudioPlayerMethods));