OSDN Git Service

Fix (or partial fix) for 3355185, crash on broken-ideograph-small-caps.html
[android-x86/external-webkit.git] / WebKit / android / WebCoreSupport / MediaPlayerPrivateAndroid.cpp
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 MediaPlayer::SupportsType MediaPlayerPrivate::supportsType(const String& type, const String& codecs)
82 {
83     if (WebViewCore::isSupportedMediaMimeType(type))
84         return MediaPlayer::MayBeSupported;
85     return MediaPlayer::IsNotSupported;
86 }
87
88 void MediaPlayerPrivate::pause()
89 {
90     JNIEnv* env = JSC::Bindings::getJNIEnv();
91     if (!env || !m_glue->m_javaProxy || !m_url.length())
92         return;
93
94     m_paused = true;
95     env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_pause);
96     checkException(env);
97 }
98
99 void MediaPlayerPrivate::setVisible(bool visible)
100 {
101     m_isVisible = visible;
102     if (m_isVisible)
103         createJavaPlayerIfNeeded();
104 }
105
106 void MediaPlayerPrivate::seek(float time)
107 {
108     JNIEnv* env = JSC::Bindings::getJNIEnv();
109     if (!env || !m_url.length())
110         return;
111
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;
115     }
116     checkException(env);
117 }
118
119
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();
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     m_player->playbackStateChanged();
165 }
166
167 void MediaPlayerPrivate::onTimeupdate(int position) {
168     m_currentTime = position / 1000.0f;
169     m_player->timeChanged();
170 }
171
172 class MediaPlayerVideoPrivate : public MediaPlayerPrivate {
173 public:
174     void load(const String& url) { m_url = url; }
175     void play() {
176         JNIEnv* env = JSC::Bindings::getJNIEnv();
177         if (!env || !m_url.length() || !m_glue->m_javaProxy)
178             return;
179
180         m_paused = false;
181         jstring jUrl = wtfStringToJstring(env, m_url);
182         env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_play, jUrl);
183         env->DeleteLocalRef(jUrl);
184
185         checkException(env);
186     }
187     bool canLoadPoster() const { return true; }
188     void setPoster(const String& url) {
189         if (m_posterUrl == url)
190             return;
191
192         m_posterUrl = url;
193         JNIEnv* env = JSC::Bindings::getJNIEnv();
194         if (!env || !m_glue->m_javaProxy || !m_posterUrl.length())
195             return;
196         // Send the poster
197         jstring jUrl = wtfStringToJstring(env, m_posterUrl);
198         env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_loadPoster, jUrl);
199         env->DeleteLocalRef(jUrl);
200     }
201     void paint(GraphicsContext* ctxt, const IntRect& r) {
202         if (ctxt->paintingDisabled())
203             return;
204
205         if (!m_isVisible)
206             return;
207
208         if (!m_poster || (!m_poster->getPixels() && !m_poster->pixelRef()))
209             return;
210
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);
223     }
224
225     void onPosterFetched(SkBitmap* poster) {
226         m_poster = 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();
236         }
237     }
238
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;
243         m_hasVideo = true;
244         m_player->durationChanged();
245         m_player->sizeChanged();
246     }
247
248     bool hasAudio() { return false; } // do not display the audio UI
249     bool hasVideo() { return m_hasVideo; }
250
251     MediaPlayerVideoPrivate(MediaPlayer* player) : MediaPlayerPrivate(player) {
252         JNIEnv* env = JSC::Bindings::getJNIEnv();
253         if (!env)
254             return;
255
256         jclass clazz = env->FindClass(g_ProxyJavaClass);
257
258         if (!clazz)
259             return;
260
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");
265
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.
272         checkException(env);
273     }
274
275     void createJavaPlayerIfNeeded() {
276         // Check if we have been already created.
277         if (m_glue->m_javaProxy)
278             return;
279
280         JNIEnv* env = JSC::Bindings::getJNIEnv();
281         if (!env)
282             return;
283
284         jclass clazz = env->FindClass(g_ProxyJavaClass);
285
286         if (!clazz)
287             return;
288
289         jobject obj = NULL;
290
291         FrameView* frameView = m_player->frameView();
292         if (!frameView)
293             return;
294         WebViewCore* webViewCore =  WebViewCore::getWebViewCore(frameView);
295         ASSERT(webViewCore);
296
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);
300         // Send the poster
301         jstring jUrl = 0;
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);
306         if (jUrl)
307             env->DeleteLocalRef(jUrl);
308
309         // Clean up.
310         if (obj)
311             env->DeleteLocalRef(obj);
312         env->DeleteLocalRef(clazz);
313         checkException(env);
314     }
315
316     float maxTimeSeekable() const {
317         return m_duration;
318     }
319 };
320
321 class MediaPlayerAudioPrivate : public MediaPlayerPrivate {
322 public:
323     void load(const String& url) {
324         m_url = url;
325         JNIEnv* env = JSC::Bindings::getJNIEnv();
326         if (!env || !m_url.length())
327             return;
328
329         createJavaPlayerIfNeeded();
330
331         if (!m_glue->m_javaProxy)
332             return;
333
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);
338         checkException(env);
339     }
340
341     void play() {
342         JNIEnv* env = JSC::Bindings::getJNIEnv();
343         if (!env || !m_url.length())
344             return;
345
346         createJavaPlayerIfNeeded();
347
348         if (!m_glue->m_javaProxy)
349             return;
350
351         m_paused = false;
352         env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_play);
353         checkException(env);
354     }
355
356     bool hasAudio() { return true; }
357
358     float maxTimeSeekable() const {
359         if (m_glue->m_javaProxy) {
360             JNIEnv* env = JSC::Bindings::getJNIEnv();
361             if (env) {
362                 float maxTime = env->CallFloatMethod(m_glue->m_javaProxy,
363                                                      m_glue->m_getMaxTimeSeekable);
364                 checkException(env);
365                 return maxTime;
366             }
367         }
368         return 0;
369     }
370
371     MediaPlayerAudioPrivate(MediaPlayer* player) : MediaPlayerPrivate(player) {
372         JNIEnv* env = JSC::Bindings::getJNIEnv();
373         if (!env)
374             return;
375
376         jclass clazz = env->FindClass(g_ProxyJavaClassAudio);
377
378         if (!clazz)
379             return;
380
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.
392         checkException(env);
393     }
394
395     void createJavaPlayerIfNeeded() {
396         // Check if we have been already created.
397         if (m_glue->m_javaProxy)
398             return;
399
400         JNIEnv* env = JSC::Bindings::getJNIEnv();
401         if (!env)
402             return;
403
404         jclass clazz = env->FindClass(g_ProxyJavaClassAudio);
405
406         if (!clazz)
407             return;
408
409         jobject obj = NULL;
410
411         // Get the HTML5Audio instance
412         obj = env->NewObject(clazz, m_glue->m_newInstance, this);
413         m_glue->m_javaProxy = env->NewGlobalRef(obj);
414
415         // Clean up.
416         if (obj)
417             env->DeleteLocalRef(obj);
418         env->DeleteLocalRef(clazz);
419         checkException(env);
420     }
421
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.
428         if (duration > 0) {
429             m_duration = duration / 1000.0f;
430         } else {
431             m_duration = std::numeric_limits<float>::infinity();
432         }
433         m_player->durationChanged();
434         m_player->sizeChanged();
435         m_player->prepareToPlay();
436     }
437 };
438
439 MediaPlayerPrivateInterface* MediaPlayerPrivate::create(MediaPlayer* player)
440 {
441     if (player->mediaElementType() == MediaPlayer::Video)
442        return new MediaPlayerVideoPrivate(player);
443     return new MediaPlayerAudioPrivate(player);
444 }
445
446 }
447
448 namespace android {
449
450 static void OnPrepared(JNIEnv* env, jobject obj, int duration, int width, int height, int pointer) {
451     if (pointer) {
452         WebCore::MediaPlayerPrivate* player = reinterpret_cast<WebCore::MediaPlayerPrivate*>(pointer);
453         player->onPrepared(duration, width, height);
454     }
455 }
456
457 static void OnEnded(JNIEnv* env, jobject obj, int pointer) {
458     if (pointer) {
459         WebCore::MediaPlayerPrivate* player = reinterpret_cast<WebCore::MediaPlayerPrivate*>(pointer);
460         player->onEnded();
461     }
462 }
463
464 static void OnPaused(JNIEnv* env, jobject obj, int pointer) {
465     if (pointer) {
466         WebCore::MediaPlayerPrivate* player = reinterpret_cast<WebCore::MediaPlayerPrivate*>(pointer);
467         player->onPaused();
468     }
469 }
470
471 static void OnPosterFetched(JNIEnv* env, jobject obj, jobject poster, int pointer) {
472     if (!pointer || !poster)
473         return;
474
475     WebCore::MediaPlayerPrivate* player = reinterpret_cast<WebCore::MediaPlayerPrivate*>(pointer);
476     SkBitmap* posterNative = GraphicsJNI::getNativeBitmap(env, poster);
477     if (!posterNative)
478         return;
479     player->onPosterFetched(posterNative);
480 }
481
482 static void OnBuffering(JNIEnv* env, jobject obj, int percent, int pointer) {
483     if (pointer) {
484         WebCore::MediaPlayerPrivate* player = reinterpret_cast<WebCore::MediaPlayerPrivate*>(pointer);
485         //TODO: player->onBuffering(percent);
486     }
487 }
488
489 static void OnTimeupdate(JNIEnv* env, jobject obj, int position, int pointer) {
490     if (pointer) {
491         WebCore::MediaPlayerPrivate* player = reinterpret_cast<WebCore::MediaPlayerPrivate*>(pointer);
492         player->onTimeupdate(position);
493     }
494 }
495
496 /*
497  * JNI registration
498  */
499 static JNINativeMethod g_MediaPlayerMethods[] = {
500     { "nativeOnPrepared", "(IIII)V",
501         (void*) OnPrepared },
502     { "nativeOnEnded", "(I)V",
503         (void*) OnEnded },
504     { "nativeOnPaused", "(I)V",
505         (void*) OnPaused },
506     { "nativeOnPosterFetched", "(Landroid/graphics/Bitmap;I)V",
507         (void*) OnPosterFetched },
508     { "nativeOnTimeupdate", "(II)V",
509         (void*) OnTimeupdate },
510 };
511
512 static JNINativeMethod g_MediaAudioPlayerMethods[] = {
513     { "nativeOnBuffering", "(II)V",
514         (void*) OnBuffering },
515     { "nativeOnEnded", "(I)V",
516         (void*) OnEnded },
517     { "nativeOnPrepared", "(IIII)V",
518         (void*) OnPrepared },
519     { "nativeOnTimeupdate", "(II)V",
520         (void*) OnTimeupdate },
521 };
522
523 int registerMediaPlayerVideo(JNIEnv* env)
524 {
525     return jniRegisterNativeMethods(env, g_ProxyJavaClass,
526             g_MediaPlayerMethods, NELEM(g_MediaPlayerMethods));
527 }
528
529 int registerMediaPlayerAudio(JNIEnv* env)
530 {
531     return jniRegisterNativeMethods(env, g_ProxyJavaClassAudio,
532             g_MediaAudioPlayerMethods, NELEM(g_MediaAudioPlayerMethods));
533 }
534
535 }
536 #endif // VIDEO