OSDN Git Service

8f84c2ff834328c8e2d17e20a8300bf2ec9d0d13
[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 "BaseLayerAndroid.h"
32 #include "DocumentLoader.h"
33 #include "Frame.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>
42 #include <JNIHelp.h>
43 #include <JNIUtility.h>
44 #include <SkBitmap.h>
45 #include <gui/SurfaceTexture.h>
46
47 using namespace android;
48 // Forward decl
49 namespace android {
50 sp<SurfaceTexture> SurfaceTexture_getSurfaceTexture(JNIEnv* env, jobject thiz);
51 };
52
53 namespace WebCore {
54
55 static const char* g_ProxyJavaClass = "android/webkit/HTML5VideoViewProxy";
56 static const char* g_ProxyJavaClassAudio = "android/webkit/HTML5Audio";
57
58 struct MediaPlayerPrivate::JavaGlue {
59     jobject   m_javaProxy;
60     jmethodID m_play;
61     jmethodID m_teardown;
62     jmethodID m_seek;
63     jmethodID m_pause;
64     // Audio
65     jmethodID m_newInstance;
66     jmethodID m_setDataSource;
67     jmethodID m_getMaxTimeSeekable;
68     // Video
69     jmethodID m_getInstance;
70     jmethodID m_loadPoster;
71 };
72
73 MediaPlayerPrivate::~MediaPlayerPrivate()
74 {
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();
79         if (env) {
80             env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_teardown);
81             env->DeleteGlobalRef(m_glue->m_javaProxy);
82         }
83     }
84     delete m_glue;
85 }
86
87 void MediaPlayerPrivate::registerMediaEngine(MediaEngineRegistrar registrar)
88 {
89     registrar(create, getSupportedTypes, supportsType);
90 }
91
92 MediaPlayer::SupportsType MediaPlayerPrivate::supportsType(const String& type, const String& codecs)
93 {
94     if (WebViewCore::isSupportedMediaMimeType(type))
95         return MediaPlayer::MayBeSupported;
96     return MediaPlayer::IsNotSupported;
97 }
98
99 void MediaPlayerPrivate::pause()
100 {
101     JNIEnv* env = JSC::Bindings::getJNIEnv();
102     if (!env || !m_glue->m_javaProxy || !m_url.length())
103         return;
104
105     m_paused = true;
106     env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_pause);
107     checkException(env);
108 }
109
110 void MediaPlayerPrivate::setVisible(bool visible)
111 {
112     m_isVisible = visible;
113     if (m_isVisible)
114         createJavaPlayerIfNeeded();
115 }
116
117 void MediaPlayerPrivate::seek(float time)
118 {
119     JNIEnv* env = JSC::Bindings::getJNIEnv();
120     if (!env || !m_url.length())
121         return;
122
123     if (m_glue->m_javaProxy) {
124         env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_seek, static_cast<jint>(time * 1000.0f));
125         m_currentTime = time;
126     }
127     checkException(env);
128 }
129
130 void MediaPlayerPrivate::prepareToPlay()
131 {
132     // We are about to start playing. Since our Java VideoView cannot
133     // buffer any data, we just simply transition to the HaveEnoughData
134     // state in here. This will allow the MediaPlayer to transition to
135     // the "play" state, at which point our VideoView will start downloading
136     // the content and start the playback.
137     m_networkState = MediaPlayer::Loaded;
138     m_player->networkStateChanged();
139     m_readyState = MediaPlayer::HaveEnoughData;
140     m_player->readyStateChanged();
141 }
142
143 MediaPlayerPrivate::MediaPlayerPrivate(MediaPlayer* player)
144     : m_player(player),
145     m_glue(0),
146     m_duration(1), // keep this minimal to avoid initial seek problem
147     m_currentTime(0),
148     m_paused(true),
149     m_hasVideo(false),
150     m_readyState(MediaPlayer::HaveNothing),
151     m_networkState(MediaPlayer::Empty),
152     m_poster(0),
153     m_naturalSize(100, 100),
154     m_naturalSizeUnknown(true),
155     m_isVisible(false),
156     m_videoLayer(new VideoLayerAndroid())
157 {
158 }
159
160 void MediaPlayerPrivate::onEnded()
161 {
162     m_currentTime = duration();
163     m_player->timeChanged();
164     m_paused = true;
165     m_hasVideo = false;
166     m_networkState = MediaPlayer::Idle;
167 }
168
169 void MediaPlayerPrivate::onPaused()
170 {
171     m_paused = true;
172     m_hasVideo = false;
173     m_networkState = MediaPlayer::Idle;
174     m_player->playbackStateChanged();
175 }
176
177 void MediaPlayerPrivate::onTimeupdate(int position)
178 {
179     m_currentTime = position / 1000.0f;
180     m_player->timeChanged();
181 }
182
183 class MediaPlayerVideoPrivate : public MediaPlayerPrivate {
184 public:
185     void load(const String& url)
186     {
187         m_url = url;
188         // Cheat a bit here to make sure Window.onLoad event can be triggered
189         // at the right time instead of real video play time, since only full
190         // screen video play is supported in Java's VideoView.
191         // See also comments in prepareToPlay function.
192         m_networkState = MediaPlayer::Loading;
193         m_player->networkStateChanged();
194         m_readyState = MediaPlayer::HaveCurrentData;
195         m_player->readyStateChanged();
196     }
197
198     void play()
199     {
200         JNIEnv* env = JSC::Bindings::getJNIEnv();
201         if (!env || !m_url.length() || !m_glue->m_javaProxy)
202             return;
203
204         // We only play video fullscreen on Android, so stop sites playing fullscreen video in the onload handler.
205         Frame* frame = m_player->frameView()->frame();
206         if (frame && !frame->loader()->documentLoader()->wasOnloadHandled())
207             return;
208
209         m_paused = false;
210
211         if (m_currentTime == duration())
212             m_currentTime = 0;
213
214         jstring jUrl = wtfStringToJstring(env, m_url);
215         env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_play, jUrl,
216                             static_cast<jint>(m_currentTime * 1000.0f),
217                             m_videoLayer->uniqueId());
218         env->DeleteLocalRef(jUrl);
219
220         checkException(env);
221     }
222     bool canLoadPoster() const { return true; }
223     void setPoster(const String& url)
224     {
225         if (m_posterUrl == url)
226             return;
227
228         m_posterUrl = url;
229         JNIEnv* env = JSC::Bindings::getJNIEnv();
230         if (!env || !m_glue->m_javaProxy || !m_posterUrl.length())
231             return;
232         // Send the poster
233         jstring jUrl = wtfStringToJstring(env, m_posterUrl);
234         env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_loadPoster, jUrl);
235         env->DeleteLocalRef(jUrl);
236     }
237     void paint(GraphicsContext* ctxt, const IntRect& r)
238     {
239         if (ctxt->paintingDisabled())
240             return;
241
242         if (!m_isVisible)
243             return;
244
245         if (!m_poster || (!m_poster->getPixels() && !m_poster->pixelRef()))
246             return;
247
248         SkCanvas*   canvas = ctxt->platformContext()->mCanvas;
249         // We paint with the following rules in mind:
250         // - only downscale the poster, never upscale
251         // - maintain the natural aspect ratio of the poster
252         // - the poster should be centered in the target rect
253         float originalRatio = static_cast<float>(m_poster->width()) / static_cast<float>(m_poster->height());
254         int posterWidth = r.width() > m_poster->width() ? m_poster->width() : r.width();
255         int posterHeight = posterWidth / originalRatio;
256         int posterX = ((r.width() - posterWidth) / 2) + r.x();
257         int posterY = ((r.height() - posterHeight) / 2) + r.y();
258         IntRect targetRect(posterX, posterY, posterWidth, posterHeight);
259         canvas->drawBitmapRect(*m_poster, 0, targetRect, 0);
260     }
261
262     void onPosterFetched(SkBitmap* poster)
263     {
264         m_poster = poster;
265         if (m_naturalSizeUnknown) {
266             // We had to fake the size at startup, or else our paint
267             // method would not be called. If we haven't yet received
268             // the onPrepared event, update the intrinsic size to the size
269             // of the poster. That will be overriden when onPrepare comes.
270             // In case of an error, we should report the poster size, rather
271             // than our initial fake value.
272             m_naturalSize = IntSize(poster->width(), poster->height());
273             m_player->sizeChanged();
274         }
275     }
276
277     void onPrepared(int duration, int width, int height)
278     {
279         m_duration = duration / 1000.0f;
280         m_naturalSize = IntSize(width, height);
281         m_naturalSizeUnknown = false;
282         m_hasVideo = true;
283         m_player->durationChanged();
284         m_player->sizeChanged();
285     }
286
287     bool hasAudio() { return false; } // do not display the audio UI
288     bool hasVideo() { return m_hasVideo; }
289     bool suppportsFullscreen() { return true; }
290
291     MediaPlayerVideoPrivate(MediaPlayer* player) : MediaPlayerPrivate(player)
292     {
293         JNIEnv* env = JSC::Bindings::getJNIEnv();
294         if (!env)
295             return;
296
297         jclass clazz = env->FindClass(g_ProxyJavaClass);
298
299         if (!clazz)
300             return;
301
302         m_glue = new JavaGlue;
303         m_glue->m_getInstance = env->GetStaticMethodID(clazz, "getInstance", "(Landroid/webkit/WebViewCore;I)Landroid/webkit/HTML5VideoViewProxy;");
304         m_glue->m_loadPoster = env->GetMethodID(clazz, "loadPoster", "(Ljava/lang/String;)V");
305         m_glue->m_play = env->GetMethodID(clazz, "play", "(Ljava/lang/String;II)V");
306
307         m_glue->m_teardown = env->GetMethodID(clazz, "teardown", "()V");
308         m_glue->m_seek = env->GetMethodID(clazz, "seek", "(I)V");
309         m_glue->m_pause = env->GetMethodID(clazz, "pause", "()V");
310         m_glue->m_javaProxy = 0;
311         env->DeleteLocalRef(clazz);
312         // An exception is raised if any of the above fails.
313         checkException(env);
314     }
315
316     void createJavaPlayerIfNeeded()
317     {
318         // Check if we have been already created.
319         if (m_glue->m_javaProxy)
320             return;
321
322         JNIEnv* env = JSC::Bindings::getJNIEnv();
323         if (!env)
324             return;
325
326         jclass clazz = env->FindClass(g_ProxyJavaClass);
327
328         if (!clazz)
329             return;
330
331         jobject obj = 0;
332
333         FrameView* frameView = m_player->frameView();
334         if (!frameView)
335             return;
336         WebViewCore* webViewCore =  WebViewCore::getWebViewCore(frameView);
337         ASSERT(webViewCore);
338
339         // Get the HTML5VideoViewProxy instance
340         obj = env->CallStaticObjectMethod(clazz, m_glue->m_getInstance, webViewCore->getJavaObject().get(), this);
341         m_glue->m_javaProxy = env->NewGlobalRef(obj);
342         // Send the poster
343         jstring jUrl = 0;
344         if (m_posterUrl.length())
345             jUrl = wtfStringToJstring(env, m_posterUrl);
346         // Sending a NULL jUrl allows the Java side to try to load the default poster.
347         env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_loadPoster, jUrl);
348         if (jUrl)
349             env->DeleteLocalRef(jUrl);
350
351         // Clean up.
352         if (obj)
353             env->DeleteLocalRef(obj);
354         env->DeleteLocalRef(clazz);
355         checkException(env);
356     }
357
358     float maxTimeSeekable() const
359     {
360         return m_duration;
361     }
362 };
363
364 class MediaPlayerAudioPrivate : public MediaPlayerPrivate {
365 public:
366     void load(const String& url)
367     {
368         m_url = url;
369         JNIEnv* env = JSC::Bindings::getJNIEnv();
370         if (!env || !m_url.length())
371             return;
372
373         createJavaPlayerIfNeeded();
374
375         if (!m_glue->m_javaProxy)
376             return;
377
378         jstring jUrl = wtfStringToJstring(env, m_url);
379         // start loading the data asynchronously
380         env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_setDataSource, jUrl);
381         env->DeleteLocalRef(jUrl);
382         checkException(env);
383     }
384
385     void play()
386     {
387         JNIEnv* env = JSC::Bindings::getJNIEnv();
388         if (!env || !m_url.length())
389             return;
390
391         createJavaPlayerIfNeeded();
392
393         if (!m_glue->m_javaProxy)
394             return;
395
396         m_paused = false;
397         env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_play);
398         checkException(env);
399     }
400
401     bool hasAudio() { return true; }
402     bool hasVideo() { return false; }
403     bool suppportsFullscreen() { return false; }
404
405     float maxTimeSeekable() const
406     {
407         if (m_glue->m_javaProxy) {
408             JNIEnv* env = JSC::Bindings::getJNIEnv();
409             if (env) {
410                 float maxTime = env->CallFloatMethod(m_glue->m_javaProxy,
411                                                      m_glue->m_getMaxTimeSeekable);
412                 checkException(env);
413                 return maxTime;
414             }
415         }
416         return 0;
417     }
418
419     MediaPlayerAudioPrivate(MediaPlayer* player) : MediaPlayerPrivate(player)
420     {
421         JNIEnv* env = JSC::Bindings::getJNIEnv();
422         if (!env)
423             return;
424
425         jclass clazz = env->FindClass(g_ProxyJavaClassAudio);
426
427         if (!clazz)
428             return;
429
430         m_glue = new JavaGlue;
431         m_glue->m_newInstance = env->GetMethodID(clazz, "<init>", "(I)V");
432         m_glue->m_setDataSource = env->GetMethodID(clazz, "setDataSource", "(Ljava/lang/String;)V");
433         m_glue->m_play = env->GetMethodID(clazz, "play", "()V");
434         m_glue->m_getMaxTimeSeekable = env->GetMethodID(clazz, "getMaxTimeSeekable", "()F");
435         m_glue->m_teardown = env->GetMethodID(clazz, "teardown", "()V");
436         m_glue->m_seek = env->GetMethodID(clazz, "seek", "(I)V");
437         m_glue->m_pause = env->GetMethodID(clazz, "pause", "()V");
438         m_glue->m_javaProxy = 0;
439         env->DeleteLocalRef(clazz);
440         // An exception is raised if any of the above fails.
441         checkException(env);
442     }
443
444     void createJavaPlayerIfNeeded()
445     {
446         // Check if we have been already created.
447         if (m_glue->m_javaProxy)
448             return;
449
450         JNIEnv* env = JSC::Bindings::getJNIEnv();
451         if (!env)
452             return;
453
454         jclass clazz = env->FindClass(g_ProxyJavaClassAudio);
455
456         if (!clazz)
457             return;
458
459         jobject obj = 0;
460
461         // Get the HTML5Audio instance
462         obj = env->NewObject(clazz, m_glue->m_newInstance, this);
463         m_glue->m_javaProxy = env->NewGlobalRef(obj);
464
465         // Clean up.
466         if (obj)
467             env->DeleteLocalRef(obj);
468         env->DeleteLocalRef(clazz);
469         checkException(env);
470     }
471
472     void onPrepared(int duration, int width, int height)
473     {
474         // Android media player gives us a duration of 0 for a live
475         // stream, so in that case set the real duration to infinity.
476         // We'll still be able to handle the case that we genuinely
477         // get an audio clip with a duration of 0s as we'll get the
478         // ended event when it stops playing.
479         if (duration > 0) {
480             m_duration = duration / 1000.0f;
481         } else {
482             m_duration = std::numeric_limits<float>::infinity();
483         }
484         m_player->durationChanged();
485         m_player->sizeChanged();
486         m_player->prepareToPlay();
487     }
488 };
489
490 MediaPlayerPrivateInterface* MediaPlayerPrivate::create(MediaPlayer* player)
491 {
492     if (player->mediaElementType() == MediaPlayer::Video)
493        return new MediaPlayerVideoPrivate(player);
494     return new MediaPlayerAudioPrivate(player);
495 }
496
497 }
498
499 namespace android {
500
501 static void OnPrepared(JNIEnv* env, jobject obj, int duration, int width, int height, int pointer)
502 {
503     if (pointer) {
504         WebCore::MediaPlayerPrivate* player = reinterpret_cast<WebCore::MediaPlayerPrivate*>(pointer);
505         player->onPrepared(duration, width, height);
506     }
507 }
508
509 static void OnEnded(JNIEnv* env, jobject obj, int pointer)
510 {
511     if (pointer) {
512         WebCore::MediaPlayerPrivate* player = reinterpret_cast<WebCore::MediaPlayerPrivate*>(pointer);
513         player->onEnded();
514     }
515 }
516
517 static void OnPaused(JNIEnv* env, jobject obj, int pointer)
518 {
519     if (pointer) {
520         WebCore::MediaPlayerPrivate* player = reinterpret_cast<WebCore::MediaPlayerPrivate*>(pointer);
521         player->onPaused();
522     }
523 }
524
525 static void OnPosterFetched(JNIEnv* env, jobject obj, jobject poster, int pointer)
526 {
527     if (!pointer || !poster)
528         return;
529
530     WebCore::MediaPlayerPrivate* player = reinterpret_cast<WebCore::MediaPlayerPrivate*>(pointer);
531     SkBitmap* posterNative = GraphicsJNI::getNativeBitmap(env, poster);
532     if (!posterNative)
533         return;
534     player->onPosterFetched(posterNative);
535 }
536
537 static void OnBuffering(JNIEnv* env, jobject obj, int percent, int pointer)
538 {
539     if (pointer) {
540         WebCore::MediaPlayerPrivate* player = reinterpret_cast<WebCore::MediaPlayerPrivate*>(pointer);
541         // TODO: player->onBuffering(percent);
542     }
543 }
544
545 static void OnTimeupdate(JNIEnv* env, jobject obj, int position, int pointer)
546 {
547     if (pointer) {
548         WebCore::MediaPlayerPrivate* player = reinterpret_cast<WebCore::MediaPlayerPrivate*>(pointer);
549         player->onTimeupdate(position);
550     }
551 }
552
553 // This is called on the UI thread only.
554 // The video layers are composited on the webkit thread and then copied over
555 // to the UI thread with the same ID. For rendering, we are only using the
556 // video layers on the UI thread. Therefore, on the UI thread, we have to use
557 // the videoLayerId from Java side to find the exact video layer in the tree
558 // to set the surface texture.
559 // Every time a play call into Java side, the videoLayerId will be sent and
560 // saved in Java side. Then every time setBaseLayer call, the saved
561 // videoLayerId will be passed to this function to find the Video Layer.
562 // Return value: true when the video layer is found.
563 static bool SendSurfaceTexture(JNIEnv* env, jobject obj, jobject surfTex,
564                                int baseLayer, int videoLayerId,
565                                int textureName, bool updateTexture) {
566     if (!surfTex)
567         return false;
568
569     sp<SurfaceTexture> texture = android::SurfaceTexture_getSurfaceTexture(env, surfTex);
570     if (!texture.get())
571         return false;
572
573     BaseLayerAndroid* layerImpl = reinterpret_cast<BaseLayerAndroid*>(baseLayer);
574     if (!layerImpl)
575         return false;
576     if (!layerImpl->countChildren())
577         return false;
578     LayerAndroid* compositedRoot = static_cast<LayerAndroid*>(layerImpl->getChild(0));
579     if (!compositedRoot)
580         return false;
581
582     VideoLayerAndroid* videoLayer =
583         static_cast<VideoLayerAndroid*>(compositedRoot->findById(videoLayerId));
584     if (!videoLayer)
585         return false;
586
587     // Set the SurfaceTexture to the layer we found
588     videoLayer->setSurfaceTexture(texture, textureName, updateTexture);
589     return true;
590 }
591
592
593 /*
594  * JNI registration
595  */
596 static JNINativeMethod g_MediaPlayerMethods[] = {
597     { "nativeOnPrepared", "(IIII)V",
598         (void*) OnPrepared },
599     { "nativeOnEnded", "(I)V",
600         (void*) OnEnded },
601     { "nativeOnPaused", "(I)V",
602         (void*) OnPaused },
603     { "nativeOnPosterFetched", "(Landroid/graphics/Bitmap;I)V",
604         (void*) OnPosterFetched },
605     { "nativeSendSurfaceTexture", "(Landroid/graphics/SurfaceTexture;IIIZ)Z",
606         (void*) SendSurfaceTexture },
607     { "nativeOnTimeupdate", "(II)V",
608         (void*) OnTimeupdate },
609 };
610
611 static JNINativeMethod g_MediaAudioPlayerMethods[] = {
612     { "nativeOnBuffering", "(II)V",
613         (void*) OnBuffering },
614     { "nativeOnEnded", "(I)V",
615         (void*) OnEnded },
616     { "nativeOnPrepared", "(IIII)V",
617         (void*) OnPrepared },
618     { "nativeOnTimeupdate", "(II)V",
619         (void*) OnTimeupdate },
620 };
621
622 int registerMediaPlayerVideo(JNIEnv* env)
623 {
624     return jniRegisterNativeMethods(env, g_ProxyJavaClass,
625             g_MediaPlayerMethods, NELEM(g_MediaPlayerMethods));
626 }
627
628 int registerMediaPlayerAudio(JNIEnv* env)
629 {
630     return jniRegisterNativeMethods(env, g_ProxyJavaClassAudio,
631             g_MediaAudioPlayerMethods, NELEM(g_MediaAudioPlayerMethods));
632 }
633
634 }
635 #endif // VIDEO