OSDN Git Service

Only download the same poster URL once.
[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
317 class MediaPlayerAudioPrivate : public MediaPlayerPrivate {
318 public:
319     void load(const String& url) {
320         m_url = url;
321         JNIEnv* env = JSC::Bindings::getJNIEnv();
322         if (!env || !m_url.length())
323             return;
324
325         createJavaPlayerIfNeeded();
326
327         if (!m_glue->m_javaProxy)
328             return;
329
330         jstring jUrl = WtfStringToJstring(env, m_url);
331         // start loading the data asynchronously
332         env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_setDataSource, jUrl);
333         env->DeleteLocalRef(jUrl);
334         checkException(env);
335     }
336
337     void play() {
338         JNIEnv* env = JSC::Bindings::getJNIEnv();
339         if (!env || !m_url.length())
340             return;
341
342         createJavaPlayerIfNeeded();
343
344         if (!m_glue->m_javaProxy)
345             return;
346
347         m_paused = false;
348         env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_play);
349         checkException(env);
350     }
351
352     bool hasAudio() { return true; }
353
354     float maxTimeSeekable() const {
355         if (m_glue->m_javaProxy) {
356             JNIEnv* env = JSC::Bindings::getJNIEnv();
357             if (env) {
358                 float maxTime = env->CallFloatMethod(m_glue->m_javaProxy,
359                                                      m_glue->m_getMaxTimeSeekable);
360                 checkException(env);
361                 return maxTime;
362             }
363         }
364         return 0;
365     }
366
367     MediaPlayerAudioPrivate(MediaPlayer* player) : MediaPlayerPrivate(player) {
368         JNIEnv* env = JSC::Bindings::getJNIEnv();
369         if (!env)
370             return;
371
372         jclass clazz = env->FindClass(g_ProxyJavaClassAudio);
373
374         if (!clazz)
375             return;
376
377         m_glue = new JavaGlue;
378         m_glue->m_newInstance = env->GetMethodID(clazz, "<init>", "(I)V");
379         m_glue->m_setDataSource = env->GetMethodID(clazz, "setDataSource", "(Ljava/lang/String;)V");
380         m_glue->m_play = env->GetMethodID(clazz, "play", "()V");
381         m_glue->m_getMaxTimeSeekable = env->GetMethodID(clazz, "getMaxTimeSeekable", "()F");
382         m_glue->m_teardown = env->GetMethodID(clazz, "teardown", "()V");
383         m_glue->m_seek = env->GetMethodID(clazz, "seek", "(I)V");
384         m_glue->m_pause = env->GetMethodID(clazz, "pause", "()V");
385         m_glue->m_javaProxy = NULL;
386         env->DeleteLocalRef(clazz);
387         // An exception is raised if any of the above fails.
388         checkException(env);
389     }
390
391     void createJavaPlayerIfNeeded() {
392         // Check if we have been already created.
393         if (m_glue->m_javaProxy)
394             return;
395
396         JNIEnv* env = JSC::Bindings::getJNIEnv();
397         if (!env)
398             return;
399
400         jclass clazz = env->FindClass(g_ProxyJavaClassAudio);
401
402         if (!clazz)
403             return;
404
405         jobject obj = NULL;
406
407         // Get the HTML5Audio instance
408         obj = env->NewObject(clazz, m_glue->m_newInstance, this);
409         m_glue->m_javaProxy = env->NewGlobalRef(obj);
410
411         // Clean up.
412         if (obj)
413             env->DeleteLocalRef(obj);
414         env->DeleteLocalRef(clazz);
415         checkException(env);
416     }
417
418     void onPrepared(int duration, int width, int height) {
419         // Android media player gives us a duration of 0 for a live
420         // stream, so in that case set the real duration to infinity.
421         // We'll still be able to handle the case that we genuinely
422         // get an audio clip with a duration of 0s as we'll get the
423         // ended event when it stops playing.
424         if (duration > 0) {
425             m_duration = duration / 1000.0f;
426         } else {
427             m_duration = std::numeric_limits<float>::infinity();
428         }
429         m_player->durationChanged();
430         m_player->sizeChanged();
431         m_player->prepareToPlay();
432     }
433 };
434
435 MediaPlayerPrivateInterface* MediaPlayerPrivate::create(MediaPlayer* player)
436 {
437     if (player->mediaElementType() == MediaPlayer::Video)
438        return new MediaPlayerVideoPrivate(player);
439     return new MediaPlayerAudioPrivate(player);
440 }
441
442 }
443
444 namespace android {
445
446 static void OnPrepared(JNIEnv* env, jobject obj, int duration, int width, int height, int pointer) {
447     if (pointer) {
448         WebCore::MediaPlayerPrivate* player = reinterpret_cast<WebCore::MediaPlayerPrivate*>(pointer);
449         player->onPrepared(duration, width, height);
450     }
451 }
452
453 static void OnEnded(JNIEnv* env, jobject obj, int pointer) {
454     if (pointer) {
455         WebCore::MediaPlayerPrivate* player = reinterpret_cast<WebCore::MediaPlayerPrivate*>(pointer);
456         player->onEnded();
457     }
458 }
459
460 static void OnPaused(JNIEnv* env, jobject obj, int pointer) {
461     if (pointer) {
462         WebCore::MediaPlayerPrivate* player = reinterpret_cast<WebCore::MediaPlayerPrivate*>(pointer);
463         player->onPaused();
464     }
465 }
466
467 static void OnPosterFetched(JNIEnv* env, jobject obj, jobject poster, int pointer) {
468     if (!pointer || !poster)
469         return;
470
471     WebCore::MediaPlayerPrivate* player = reinterpret_cast<WebCore::MediaPlayerPrivate*>(pointer);
472     SkBitmap* posterNative = GraphicsJNI::getNativeBitmap(env, poster);
473     if (!posterNative)
474         return;
475     player->onPosterFetched(posterNative);
476 }
477
478 static void OnBuffering(JNIEnv* env, jobject obj, int percent, int pointer) {
479     if (pointer) {
480         WebCore::MediaPlayerPrivate* player = reinterpret_cast<WebCore::MediaPlayerPrivate*>(pointer);
481         //TODO: player->onBuffering(percent);
482     }
483 }
484
485 static void OnTimeupdate(JNIEnv* env, jobject obj, int position, int pointer) {
486     if (pointer) {
487         WebCore::MediaPlayerPrivate* player = reinterpret_cast<WebCore::MediaPlayerPrivate*>(pointer);
488         player->onTimeupdate(position);
489     }
490 }
491
492 /*
493  * JNI registration
494  */
495 static JNINativeMethod g_MediaPlayerMethods[] = {
496     { "nativeOnPrepared", "(IIII)V",
497         (void*) OnPrepared },
498     { "nativeOnEnded", "(I)V",
499         (void*) OnEnded },
500     { "nativeOnPaused", "(I)V",
501         (void*) OnPaused },
502     { "nativeOnPosterFetched", "(Landroid/graphics/Bitmap;I)V",
503         (void*) OnPosterFetched },
504     { "nativeOnTimeupdate", "(II)V",
505         (void*) OnTimeupdate },
506 };
507
508 static JNINativeMethod g_MediaAudioPlayerMethods[] = {
509     { "nativeOnBuffering", "(II)V",
510         (void*) OnBuffering },
511     { "nativeOnEnded", "(I)V",
512         (void*) OnEnded },
513     { "nativeOnPrepared", "(IIII)V",
514         (void*) OnPrepared },
515     { "nativeOnTimeupdate", "(II)V",
516         (void*) OnTimeupdate },
517 };
518
519 int registerMediaPlayerVideo(JNIEnv* env)
520 {
521     return jniRegisterNativeMethods(env, g_ProxyJavaClass,
522             g_MediaPlayerMethods, NELEM(g_MediaPlayerMethods));
523 }
524
525 int registerMediaPlayerAudio(JNIEnv* env)
526 {
527     return jniRegisterNativeMethods(env, g_ProxyJavaClassAudio,
528             g_MediaAudioPlayerMethods, NELEM(g_MediaAudioPlayerMethods));
529 }
530
531 }
532 #endif // VIDEO