OSDN Git Service

Improving HTML5 video controls
[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     m_player->playbackStateChanged();
107     env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_pause);
108     checkException(env);
109 }
110
111 void MediaPlayerPrivate::setVisible(bool visible)
112 {
113     m_isVisible = visible;
114     if (m_isVisible)
115         createJavaPlayerIfNeeded();
116 }
117
118 void MediaPlayerPrivate::seek(float time)
119 {
120     JNIEnv* env = JSC::Bindings::getJNIEnv();
121     if (!env || !m_url.length())
122         return;
123
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;
127     }
128     checkException(env);
129 }
130
131 void MediaPlayerPrivate::prepareToPlay()
132 {
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();
142 }
143
144 MediaPlayerPrivate::MediaPlayerPrivate(MediaPlayer* player)
145     : m_player(player),
146     m_glue(0),
147     m_duration(1), // keep this minimal to avoid initial seek problem
148     m_currentTime(0),
149     m_paused(true),
150     m_hasVideo(false),
151     m_readyState(MediaPlayer::HaveNothing),
152     m_networkState(MediaPlayer::Empty),
153     m_poster(0),
154     m_naturalSize(100, 100),
155     m_naturalSizeUnknown(true),
156     m_isVisible(false),
157     m_videoLayer(new VideoLayerAndroid())
158 {
159 }
160
161 void MediaPlayerPrivate::onEnded()
162 {
163     m_currentTime = duration();
164     m_player->timeChanged();
165     m_paused = true;
166     m_player->playbackStateChanged();
167     m_hasVideo = false;
168     m_networkState = MediaPlayer::Idle;
169 }
170
171 void MediaPlayerPrivate::onPaused()
172 {
173     m_paused = true;
174     m_player->playbackStateChanged();
175     m_hasVideo = false;
176     m_networkState = MediaPlayer::Idle;
177     m_player->playbackStateChanged();
178 }
179
180 void MediaPlayerPrivate::onTimeupdate(int position)
181 {
182     m_currentTime = position / 1000.0f;
183     m_player->timeChanged();
184 }
185
186 class MediaPlayerVideoPrivate : public MediaPlayerPrivate {
187 public:
188     void load(const String& url)
189     {
190         m_url = 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();
199     }
200
201     void play()
202     {
203         JNIEnv* env = JSC::Bindings::getJNIEnv();
204         if (!env || !m_url.length() || !m_glue->m_javaProxy)
205             return;
206
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())
210             return;
211
212         m_paused = false;
213         m_player->playbackStateChanged();
214
215         if (m_currentTime == duration())
216             m_currentTime = 0;
217
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);
223
224         checkException(env);
225     }
226     bool canLoadPoster() const { return true; }
227     void setPoster(const String& url)
228     {
229         if (m_posterUrl == url)
230             return;
231
232         m_posterUrl = url;
233         JNIEnv* env = JSC::Bindings::getJNIEnv();
234         if (!env || !m_glue->m_javaProxy || !m_posterUrl.length())
235             return;
236         // Send the poster
237         jstring jUrl = wtfStringToJstring(env, m_posterUrl);
238         env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_loadPoster, jUrl);
239         env->DeleteLocalRef(jUrl);
240     }
241     void paint(GraphicsContext* ctxt, const IntRect& r)
242     {
243         if (ctxt->paintingDisabled())
244             return;
245
246         if (!m_isVisible)
247             return;
248
249         if (!m_poster || (!m_poster->getPixels() && !m_poster->pixelRef()))
250             return;
251
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);
264     }
265
266     void onPosterFetched(SkBitmap* poster)
267     {
268         m_poster = 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();
278         }
279     }
280
281     void onPrepared(int duration, int width, int height)
282     {
283         m_duration = duration / 1000.0f;
284         m_naturalSize = IntSize(width, height);
285         m_naturalSizeUnknown = false;
286         m_hasVideo = true;
287         m_player->durationChanged();
288         m_player->sizeChanged();
289     }
290
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; }
294
295     MediaPlayerVideoPrivate(MediaPlayer* player) : MediaPlayerPrivate(player)
296     {
297         JNIEnv* env = JSC::Bindings::getJNIEnv();
298         if (!env)
299             return;
300
301         jclass clazz = env->FindClass(g_ProxyJavaClass);
302
303         if (!clazz)
304             return;
305
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");
310
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.
317         checkException(env);
318     }
319
320     void createJavaPlayerIfNeeded()
321     {
322         // Check if we have been already created.
323         if (m_glue->m_javaProxy)
324             return;
325
326         JNIEnv* env = JSC::Bindings::getJNIEnv();
327         if (!env)
328             return;
329
330         jclass clazz = env->FindClass(g_ProxyJavaClass);
331
332         if (!clazz)
333             return;
334
335         jobject obj = 0;
336
337         FrameView* frameView = m_player->frameView();
338         if (!frameView)
339             return;
340         WebViewCore* webViewCore =  WebViewCore::getWebViewCore(frameView);
341         ASSERT(webViewCore);
342
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);
346         // Send the poster
347         jstring jUrl = 0;
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);
352         if (jUrl)
353             env->DeleteLocalRef(jUrl);
354
355         // Clean up.
356         if (obj)
357             env->DeleteLocalRef(obj);
358         env->DeleteLocalRef(clazz);
359         checkException(env);
360     }
361
362     float maxTimeSeekable() const
363     {
364         return m_duration;
365     }
366 };
367
368 class MediaPlayerAudioPrivate : public MediaPlayerPrivate {
369 public:
370     void load(const String& url)
371     {
372         m_url = url;
373         JNIEnv* env = JSC::Bindings::getJNIEnv();
374         if (!env || !m_url.length())
375             return;
376
377         createJavaPlayerIfNeeded();
378
379         if (!m_glue->m_javaProxy)
380             return;
381
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);
386         checkException(env);
387     }
388
389     void play()
390     {
391         JNIEnv* env = JSC::Bindings::getJNIEnv();
392         if (!env || !m_url.length())
393             return;
394
395         createJavaPlayerIfNeeded();
396
397         if (!m_glue->m_javaProxy)
398             return;
399
400         m_paused = false;
401         m_player->playbackStateChanged();
402         env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_play);
403         checkException(env);
404     }
405
406     virtual bool hasAudio() const { return true; }
407     virtual bool hasVideo() const { return false; }
408     virtual bool supportsFullscreen() const { return false; }
409
410     float maxTimeSeekable() const
411     {
412         if (m_glue->m_javaProxy) {
413             JNIEnv* env = JSC::Bindings::getJNIEnv();
414             if (env) {
415                 float maxTime = env->CallFloatMethod(m_glue->m_javaProxy,
416                                                      m_glue->m_getMaxTimeSeekable);
417                 checkException(env);
418                 return maxTime;
419             }
420         }
421         return 0;
422     }
423
424     MediaPlayerAudioPrivate(MediaPlayer* player) : MediaPlayerPrivate(player)
425     {
426         JNIEnv* env = JSC::Bindings::getJNIEnv();
427         if (!env)
428             return;
429
430         jclass clazz = env->FindClass(g_ProxyJavaClassAudio);
431
432         if (!clazz)
433             return;
434
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.
446         checkException(env);
447     }
448
449     void createJavaPlayerIfNeeded()
450     {
451         // Check if we have been already created.
452         if (m_glue->m_javaProxy)
453             return;
454
455         JNIEnv* env = JSC::Bindings::getJNIEnv();
456         if (!env)
457             return;
458
459         jclass clazz = env->FindClass(g_ProxyJavaClassAudio);
460
461         if (!clazz)
462             return;
463
464         jobject obj = 0;
465
466         // Get the HTML5Audio instance
467         obj = env->NewObject(clazz, m_glue->m_newInstance, this);
468         m_glue->m_javaProxy = env->NewGlobalRef(obj);
469
470         // Clean up.
471         if (obj)
472             env->DeleteLocalRef(obj);
473         env->DeleteLocalRef(clazz);
474         checkException(env);
475     }
476
477     void onPrepared(int duration, int width, int height)
478     {
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.
484         if (duration > 0) {
485             m_duration = duration / 1000.0f;
486         } else {
487             m_duration = std::numeric_limits<float>::infinity();
488         }
489         m_player->durationChanged();
490         m_player->sizeChanged();
491         m_player->prepareToPlay();
492     }
493 };
494
495 MediaPlayerPrivateInterface* MediaPlayerPrivate::create(MediaPlayer* player)
496 {
497     if (player->mediaElementType() == MediaPlayer::Video)
498        return new MediaPlayerVideoPrivate(player);
499     return new MediaPlayerAudioPrivate(player);
500 }
501
502 }
503
504 namespace android {
505
506 static void OnPrepared(JNIEnv* env, jobject obj, int duration, int width, int height, int pointer)
507 {
508     if (pointer) {
509         WebCore::MediaPlayerPrivate* player = reinterpret_cast<WebCore::MediaPlayerPrivate*>(pointer);
510         player->onPrepared(duration, width, height);
511     }
512 }
513
514 static void OnEnded(JNIEnv* env, jobject obj, int pointer)
515 {
516     if (pointer) {
517         WebCore::MediaPlayerPrivate* player = reinterpret_cast<WebCore::MediaPlayerPrivate*>(pointer);
518         player->onEnded();
519     }
520 }
521
522 static void OnPaused(JNIEnv* env, jobject obj, int pointer)
523 {
524     if (pointer) {
525         WebCore::MediaPlayerPrivate* player = reinterpret_cast<WebCore::MediaPlayerPrivate*>(pointer);
526         player->onPaused();
527     }
528 }
529
530 static void OnPosterFetched(JNIEnv* env, jobject obj, jobject poster, int pointer)
531 {
532     if (!pointer || !poster)
533         return;
534
535     WebCore::MediaPlayerPrivate* player = reinterpret_cast<WebCore::MediaPlayerPrivate*>(pointer);
536     SkBitmap* posterNative = GraphicsJNI::getNativeBitmap(env, poster);
537     if (!posterNative)
538         return;
539     player->onPosterFetched(posterNative);
540 }
541
542 static void OnBuffering(JNIEnv* env, jobject obj, int percent, int pointer)
543 {
544     if (pointer) {
545         WebCore::MediaPlayerPrivate* player = reinterpret_cast<WebCore::MediaPlayerPrivate*>(pointer);
546         // TODO: player->onBuffering(percent);
547     }
548 }
549
550 static void OnTimeupdate(JNIEnv* env, jobject obj, int position, int pointer)
551 {
552     if (pointer) {
553         WebCore::MediaPlayerPrivate* player = reinterpret_cast<WebCore::MediaPlayerPrivate*>(pointer);
554         player->onTimeupdate(position);
555     }
556 }
557
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) {
571     if (!surfTex)
572         return false;
573
574     sp<SurfaceTexture> texture = android::SurfaceTexture_getSurfaceTexture(env, surfTex);
575     if (!texture.get())
576         return false;
577
578     BaseLayerAndroid* layerImpl = reinterpret_cast<BaseLayerAndroid*>(baseLayer);
579     if (!layerImpl)
580         return false;
581     if (!layerImpl->countChildren())
582         return false;
583     LayerAndroid* compositedRoot = static_cast<LayerAndroid*>(layerImpl->getChild(0));
584     if (!compositedRoot)
585         return false;
586
587     VideoLayerAndroid* videoLayer =
588         static_cast<VideoLayerAndroid*>(compositedRoot->findById(videoLayerId));
589     if (!videoLayer)
590         return false;
591
592     // Set the SurfaceTexture to the layer we found
593     videoLayer->setSurfaceTexture(texture, textureName, updateTexture);
594     return true;
595 }
596
597
598 /*
599  * JNI registration
600  */
601 static JNINativeMethod g_MediaPlayerMethods[] = {
602     { "nativeOnPrepared", "(IIII)V",
603         (void*) OnPrepared },
604     { "nativeOnEnded", "(I)V",
605         (void*) OnEnded },
606     { "nativeOnPaused", "(I)V",
607         (void*) OnPaused },
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 },
614 };
615
616 static JNINativeMethod g_MediaAudioPlayerMethods[] = {
617     { "nativeOnBuffering", "(II)V",
618         (void*) OnBuffering },
619     { "nativeOnEnded", "(I)V",
620         (void*) OnEnded },
621     { "nativeOnPrepared", "(IIII)V",
622         (void*) OnPrepared },
623     { "nativeOnTimeupdate", "(II)V",
624         (void*) OnTimeupdate },
625 };
626
627 int registerMediaPlayerVideo(JNIEnv* env)
628 {
629     return jniRegisterNativeMethods(env, g_ProxyJavaClass,
630             g_MediaPlayerMethods, NELEM(g_MediaPlayerMethods));
631 }
632
633 int registerMediaPlayerAudio(JNIEnv* env)
634 {
635     return jniRegisterNativeMethods(env, g_ProxyJavaClassAudio,
636             g_MediaAudioPlayerMethods, NELEM(g_MediaAudioPlayerMethods));
637 }
638
639 }
640 #endif // VIDEO