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 "GraphicsContext.h"
32 #include "SkiaUtils.h"
33 #include "WebCoreJni.h"
34 #include "WebViewCore.h"
36 #include <GraphicsJNI.h>
38 #include <JNIUtility.h>
41 using namespace android;
45 static const char* g_ProxyJavaClass = "android/webkit/HTML5VideoViewProxy";
46 static const char* g_ProxyJavaClassAudio = "android/webkit/HTML5Audio";
48 struct MediaPlayerPrivate::JavaGlue
56 jmethodID m_newInstance;
57 jmethodID m_setDataSource;
58 jmethodID m_getMaxTimeSeekable;
60 jmethodID m_getInstance;
61 jmethodID m_loadPoster;
64 MediaPlayerPrivate::~MediaPlayerPrivate()
66 if (m_glue->m_javaProxy) {
67 JNIEnv* env = JSC::Bindings::getJNIEnv();
69 env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_teardown);
70 env->DeleteGlobalRef(m_glue->m_javaProxy);
76 void MediaPlayerPrivate::registerMediaEngine(MediaEngineRegistrar registrar)
78 registrar(create, getSupportedTypes, supportsType);
81 MediaPlayer::SupportsType MediaPlayerPrivate::supportsType(const String& type, const String& codecs)
83 if (WebViewCore::isSupportedMediaMimeType(type))
84 return MediaPlayer::MayBeSupported;
85 return MediaPlayer::IsNotSupported;
88 void MediaPlayerPrivate::pause()
90 JNIEnv* env = JSC::Bindings::getJNIEnv();
91 if (!env || !m_glue->m_javaProxy || !m_url.length())
95 env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_pause);
99 void MediaPlayerPrivate::setVisible(bool visible)
101 m_isVisible = visible;
103 createJavaPlayerIfNeeded();
106 void MediaPlayerPrivate::seek(float time)
108 JNIEnv* env = JSC::Bindings::getJNIEnv();
109 if (!env || !m_url.length())
112 if (m_glue->m_javaProxy) {
113 env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_seek, static_cast<jint>(time * 1000.0f));
114 m_currentTime = time;
120 void MediaPlayerPrivate::prepareToPlay() {
121 // We are about to start playing. Since our Java VideoView cannot
122 // buffer any data, we just simply transition to the HaveEnoughData
123 // state in here. This will allow the MediaPlayer to transition to
124 // the "play" state, at which point our VideoView will start downloading
125 // the content and start the playback.
126 m_networkState = MediaPlayer::Loaded;
127 m_player->networkStateChanged();
128 m_readyState = MediaPlayer::HaveEnoughData;
129 m_player->readyStateChanged();
132 MediaPlayerPrivate::MediaPlayerPrivate(MediaPlayer* player)
139 m_readyState(MediaPlayer::HaveNothing),
140 m_networkState(MediaPlayer::Empty),
142 m_naturalSize(100, 100),
143 m_naturalSizeUnknown(true),
148 void MediaPlayerPrivate::onEnded() {
149 m_currentTime = duration();
150 m_player->timeChanged();
154 m_networkState = MediaPlayer::Idle;
155 m_readyState = MediaPlayer::HaveNothing;
158 void MediaPlayerPrivate::onPaused() {
162 m_networkState = MediaPlayer::Idle;
163 m_readyState = MediaPlayer::HaveNothing;
164 m_player->playbackStateChanged();
167 void MediaPlayerPrivate::onTimeupdate(int position) {
168 m_currentTime = position / 1000.0f;
169 m_player->timeChanged();
172 class MediaPlayerVideoPrivate : public MediaPlayerPrivate {
174 void load(const String& url) { m_url = url; }
176 JNIEnv* env = JSC::Bindings::getJNIEnv();
177 if (!env || !m_url.length() || !m_glue->m_javaProxy)
181 jstring jUrl = wtfStringToJstring(env, m_url);
182 env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_play, jUrl);
183 env->DeleteLocalRef(jUrl);
187 bool canLoadPoster() const { return true; }
188 void setPoster(const String& url) {
189 if (m_posterUrl == url)
193 JNIEnv* env = JSC::Bindings::getJNIEnv();
194 if (!env || !m_glue->m_javaProxy || !m_posterUrl.length())
197 jstring jUrl = wtfStringToJstring(env, m_posterUrl);
198 env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_loadPoster, jUrl);
199 env->DeleteLocalRef(jUrl);
201 void paint(GraphicsContext* ctxt, const IntRect& r) {
202 if (ctxt->paintingDisabled())
208 if (!m_poster || (!m_poster->getPixels() && !m_poster->pixelRef()))
211 SkCanvas* canvas = ctxt->platformContext()->mCanvas;
212 // We paint with the following rules in mind:
213 // - only downscale the poster, never upscale
214 // - maintain the natural aspect ratio of the poster
215 // - the poster should be centered in the target rect
216 float originalRatio = static_cast<float>(m_poster->width()) / static_cast<float>(m_poster->height());
217 int posterWidth = r.width() > m_poster->width() ? m_poster->width() : r.width();
218 int posterHeight = posterWidth / originalRatio;
219 int posterX = ((r.width() - posterWidth) / 2) + r.x();
220 int posterY = ((r.height() - posterHeight) / 2) + r.y();
221 IntRect targetRect(posterX, posterY, posterWidth, posterHeight);
222 canvas->drawBitmapRect(*m_poster, 0, targetRect, 0);
225 void onPosterFetched(SkBitmap* poster) {
227 if (m_naturalSizeUnknown) {
228 // We had to fake the size at startup, or else our paint
229 // method would not be called. If we haven't yet received
230 // the onPrepared event, update the intrinsic size to the size
231 // of the poster. That will be overriden when onPrepare comes.
232 // In case of an error, we should report the poster size, rather
233 // than our initial fake value.
234 m_naturalSize = IntSize(poster->width(), poster->height());
235 m_player->sizeChanged();
239 void onPrepared(int duration, int width, int height) {
240 m_duration = duration / 1000.0f;
241 m_naturalSize = IntSize(width, height);
242 m_naturalSizeUnknown = false;
244 m_player->durationChanged();
245 m_player->sizeChanged();
248 bool hasAudio() { return false; } // do not display the audio UI
249 bool hasVideo() { return m_hasVideo; }
251 MediaPlayerVideoPrivate(MediaPlayer* player) : MediaPlayerPrivate(player) {
252 JNIEnv* env = JSC::Bindings::getJNIEnv();
256 jclass clazz = env->FindClass(g_ProxyJavaClass);
261 m_glue = new JavaGlue;
262 m_glue->m_getInstance = env->GetStaticMethodID(clazz, "getInstance", "(Landroid/webkit/WebViewCore;I)Landroid/webkit/HTML5VideoViewProxy;");
263 m_glue->m_loadPoster = env->GetMethodID(clazz, "loadPoster", "(Ljava/lang/String;)V");
264 m_glue->m_play = env->GetMethodID(clazz, "play", "(Ljava/lang/String;)V");
266 m_glue->m_teardown = env->GetMethodID(clazz, "teardown", "()V");
267 m_glue->m_seek = env->GetMethodID(clazz, "seek", "(I)V");
268 m_glue->m_pause = env->GetMethodID(clazz, "pause", "()V");
269 m_glue->m_javaProxy = NULL;
270 env->DeleteLocalRef(clazz);
271 // An exception is raised if any of the above fails.
275 void createJavaPlayerIfNeeded() {
276 // Check if we have been already created.
277 if (m_glue->m_javaProxy)
280 JNIEnv* env = JSC::Bindings::getJNIEnv();
284 jclass clazz = env->FindClass(g_ProxyJavaClass);
291 FrameView* frameView = m_player->frameView();
294 WebViewCore* webViewCore = WebViewCore::getWebViewCore(frameView);
297 // Get the HTML5VideoViewProxy instance
298 obj = env->CallStaticObjectMethod(clazz, m_glue->m_getInstance, webViewCore->getJavaObject().get(), this);
299 m_glue->m_javaProxy = env->NewGlobalRef(obj);
302 if (m_posterUrl.length())
303 jUrl = wtfStringToJstring(env, m_posterUrl);
304 // Sending a NULL jUrl allows the Java side to try to load the default poster.
305 env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_loadPoster, jUrl);
307 env->DeleteLocalRef(jUrl);
311 env->DeleteLocalRef(obj);
312 env->DeleteLocalRef(clazz);
316 float maxTimeSeekable() const {
321 class MediaPlayerAudioPrivate : public MediaPlayerPrivate {
323 void load(const String& url) {
325 JNIEnv* env = JSC::Bindings::getJNIEnv();
326 if (!env || !m_url.length())
329 createJavaPlayerIfNeeded();
331 if (!m_glue->m_javaProxy)
334 jstring jUrl = wtfStringToJstring(env, m_url);
335 // start loading the data asynchronously
336 env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_setDataSource, jUrl);
337 env->DeleteLocalRef(jUrl);
342 JNIEnv* env = JSC::Bindings::getJNIEnv();
343 if (!env || !m_url.length())
346 createJavaPlayerIfNeeded();
348 if (!m_glue->m_javaProxy)
352 env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_play);
356 bool hasAudio() { return true; }
358 float maxTimeSeekable() const {
359 if (m_glue->m_javaProxy) {
360 JNIEnv* env = JSC::Bindings::getJNIEnv();
362 float maxTime = env->CallFloatMethod(m_glue->m_javaProxy,
363 m_glue->m_getMaxTimeSeekable);
371 MediaPlayerAudioPrivate(MediaPlayer* player) : MediaPlayerPrivate(player) {
372 JNIEnv* env = JSC::Bindings::getJNIEnv();
376 jclass clazz = env->FindClass(g_ProxyJavaClassAudio);
381 m_glue = new JavaGlue;
382 m_glue->m_newInstance = env->GetMethodID(clazz, "<init>", "(I)V");
383 m_glue->m_setDataSource = env->GetMethodID(clazz, "setDataSource", "(Ljava/lang/String;)V");
384 m_glue->m_play = env->GetMethodID(clazz, "play", "()V");
385 m_glue->m_getMaxTimeSeekable = env->GetMethodID(clazz, "getMaxTimeSeekable", "()F");
386 m_glue->m_teardown = env->GetMethodID(clazz, "teardown", "()V");
387 m_glue->m_seek = env->GetMethodID(clazz, "seek", "(I)V");
388 m_glue->m_pause = env->GetMethodID(clazz, "pause", "()V");
389 m_glue->m_javaProxy = NULL;
390 env->DeleteLocalRef(clazz);
391 // An exception is raised if any of the above fails.
395 void createJavaPlayerIfNeeded() {
396 // Check if we have been already created.
397 if (m_glue->m_javaProxy)
400 JNIEnv* env = JSC::Bindings::getJNIEnv();
404 jclass clazz = env->FindClass(g_ProxyJavaClassAudio);
411 // Get the HTML5Audio instance
412 obj = env->NewObject(clazz, m_glue->m_newInstance, this);
413 m_glue->m_javaProxy = env->NewGlobalRef(obj);
417 env->DeleteLocalRef(obj);
418 env->DeleteLocalRef(clazz);
422 void onPrepared(int duration, int width, int height) {
423 // Android media player gives us a duration of 0 for a live
424 // stream, so in that case set the real duration to infinity.
425 // We'll still be able to handle the case that we genuinely
426 // get an audio clip with a duration of 0s as we'll get the
427 // ended event when it stops playing.
429 m_duration = duration / 1000.0f;
431 m_duration = std::numeric_limits<float>::infinity();
433 m_player->durationChanged();
434 m_player->sizeChanged();
435 m_player->prepareToPlay();
439 MediaPlayerPrivateInterface* MediaPlayerPrivate::create(MediaPlayer* player)
441 if (player->mediaElementType() == MediaPlayer::Video)
442 return new MediaPlayerVideoPrivate(player);
443 return new MediaPlayerAudioPrivate(player);
450 static void OnPrepared(JNIEnv* env, jobject obj, int duration, int width, int height, int pointer) {
452 WebCore::MediaPlayerPrivate* player = reinterpret_cast<WebCore::MediaPlayerPrivate*>(pointer);
453 player->onPrepared(duration, width, height);
457 static void OnEnded(JNIEnv* env, jobject obj, int pointer) {
459 WebCore::MediaPlayerPrivate* player = reinterpret_cast<WebCore::MediaPlayerPrivate*>(pointer);
464 static void OnPaused(JNIEnv* env, jobject obj, int pointer) {
466 WebCore::MediaPlayerPrivate* player = reinterpret_cast<WebCore::MediaPlayerPrivate*>(pointer);
471 static void OnPosterFetched(JNIEnv* env, jobject obj, jobject poster, int pointer) {
472 if (!pointer || !poster)
475 WebCore::MediaPlayerPrivate* player = reinterpret_cast<WebCore::MediaPlayerPrivate*>(pointer);
476 SkBitmap* posterNative = GraphicsJNI::getNativeBitmap(env, poster);
479 player->onPosterFetched(posterNative);
482 static void OnBuffering(JNIEnv* env, jobject obj, int percent, int pointer) {
484 WebCore::MediaPlayerPrivate* player = reinterpret_cast<WebCore::MediaPlayerPrivate*>(pointer);
485 //TODO: player->onBuffering(percent);
489 static void OnTimeupdate(JNIEnv* env, jobject obj, int position, int pointer) {
491 WebCore::MediaPlayerPrivate* player = reinterpret_cast<WebCore::MediaPlayerPrivate*>(pointer);
492 player->onTimeupdate(position);
499 static JNINativeMethod g_MediaPlayerMethods[] = {
500 { "nativeOnPrepared", "(IIII)V",
501 (void*) OnPrepared },
502 { "nativeOnEnded", "(I)V",
504 { "nativeOnPaused", "(I)V",
506 { "nativeOnPosterFetched", "(Landroid/graphics/Bitmap;I)V",
507 (void*) OnPosterFetched },
508 { "nativeOnTimeupdate", "(II)V",
509 (void*) OnTimeupdate },
512 static JNINativeMethod g_MediaAudioPlayerMethods[] = {
513 { "nativeOnBuffering", "(II)V",
514 (void*) OnBuffering },
515 { "nativeOnEnded", "(I)V",
517 { "nativeOnPrepared", "(IIII)V",
518 (void*) OnPrepared },
519 { "nativeOnTimeupdate", "(II)V",
520 (void*) OnTimeupdate },
523 int registerMediaPlayerVideo(JNIEnv* env)
525 return jniRegisterNativeMethods(env, g_ProxyJavaClass,
526 g_MediaPlayerMethods, NELEM(g_MediaPlayerMethods));
529 int registerMediaPlayerAudio(JNIEnv* env)
531 return jniRegisterNativeMethods(env, g_ProxyJavaClassAudio,
532 g_MediaAudioPlayerMethods, NELEM(g_MediaAudioPlayerMethods));