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 APPLE COMPUTER, INC. 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
28 #if ENABLE(VIDEO)
29
30 #include "GraphicsContext.h"
31 #include "MediaPlayerPrivateAndroid.h"
32 #include "SkiaUtils.h"
33 #include "WebCoreJni.h"
34 #include "WebViewCore.h"
35 #include "jni_utility.h"
36
37 #include <GraphicsJNI.h>
38 #include <JNIHelp.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
47 struct MediaPlayerPrivate::JavaGlue
48 {
49 jobject m_javaProxy;
50 jmethodID m_getInstance;
51 jmethodID m_play;
52 jmethodID m_teardown;
53 jmethodID m_loadPoster;
54 jmethodID m_seek;
55 jmethodID m_pause;
56 };
57
~MediaPlayerPrivate()58 MediaPlayerPrivate::~MediaPlayerPrivate()
59 {
60 if (m_glue->m_javaProxy) {
61 JNIEnv* env = JSC::Bindings::getJNIEnv();
62 if (env) {
63 env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_teardown);
64 env->DeleteGlobalRef(m_glue->m_javaProxy);
65 }
66 }
67
68 delete m_glue;
69 }
70
registerMediaEngine(MediaEngineRegistrar registrar)71 void MediaPlayerPrivate::registerMediaEngine(MediaEngineRegistrar registrar)
72 {
73 registrar(create, getSupportedTypes, supportsType);
74 }
75
load(const String & url)76 void MediaPlayerPrivate::load(const String& url)
77 {
78 // Just save the URl.
79 m_url = url;
80 }
81
cancelLoad()82 void MediaPlayerPrivate::cancelLoad()
83 {
84 }
85
play()86 void MediaPlayerPrivate::play()
87 {
88 JNIEnv* env = JSC::Bindings::getJNIEnv();
89 if (!env || !m_glue->m_javaProxy || !m_url.length())
90 return;
91
92 m_paused = false;
93 jstring jUrl = env->NewString((unsigned short *)m_url.characters(), m_url.length());
94 env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_play, jUrl);
95 env->DeleteLocalRef(jUrl);
96 checkException(env);
97 }
98
pause()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
naturalSize() const110 IntSize MediaPlayerPrivate::naturalSize() const
111 {
112 return m_naturalSize;
113 }
114
hasVideo() const115 bool MediaPlayerPrivate::hasVideo() const
116 {
117 return false;
118 }
119
setVisible(bool visible)120 void MediaPlayerPrivate::setVisible(bool visible)
121 {
122 m_isVisible = visible;
123 if (m_isVisible)
124 createJavaPlayerIfNeeded();
125 }
126
duration() const127 float MediaPlayerPrivate::duration() const
128 {
129 return m_duration;
130 }
131
currentTime() const132 float MediaPlayerPrivate::currentTime() const
133 {
134 return m_currentTime;
135 }
136
seek(float time)137 void MediaPlayerPrivate::seek(float time)
138 {
139 JNIEnv* env = JSC::Bindings::getJNIEnv();
140 if (!env || !m_glue->m_javaProxy || !m_url.length())
141 return;
142
143 m_currentTime = time;
144 env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_seek, static_cast<jint>(time * 1000.0f));
145 checkException(env);
146 }
147
seeking() const148 bool MediaPlayerPrivate::seeking() const
149 {
150 return false;
151 }
152
setEndTime(float)153 void MediaPlayerPrivate::setEndTime(float)
154 {
155 }
156
setRate(float)157 void MediaPlayerPrivate::setRate(float)
158 {
159 }
160
paused() const161 bool MediaPlayerPrivate::paused() const
162 {
163 return m_paused;
164 }
165
setVolume(float)166 void MediaPlayerPrivate::setVolume(float)
167 {
168 }
169
networkState() const170 MediaPlayer::NetworkState MediaPlayerPrivate::networkState() const
171 {
172 return m_networkState;
173 }
174
readyState() const175 MediaPlayer::ReadyState MediaPlayerPrivate::readyState() const
176 {
177 return m_readyState;
178 }
179
maxTimeSeekable() const180 float MediaPlayerPrivate::maxTimeSeekable() const
181 {
182 return 0;
183 }
184
maxTimeBuffered() const185 float MediaPlayerPrivate::maxTimeBuffered() const
186 {
187 return 0;
188 }
189
dataRate() const190 int MediaPlayerPrivate::dataRate() const
191 {
192 return 0;
193 }
194
totalBytes() const195 unsigned MediaPlayerPrivate::totalBytes() const
196 {
197 return 0;
198 }
199
bytesLoaded() const200 unsigned MediaPlayerPrivate::bytesLoaded() const
201 {
202 return 0;
203 }
204
setSize(const IntSize &)205 void MediaPlayerPrivate::setSize(const IntSize&)
206 {
207 }
208
setPoster(const String & url)209 void MediaPlayerPrivate::setPoster(const String& url)
210 {
211 m_posterUrl = url;
212 JNIEnv* env = JSC::Bindings::getJNIEnv();
213 if (!env || !m_glue->m_javaProxy || !m_posterUrl.length())
214 return;
215 // Send the poster
216 jstring jUrl = env->NewString((unsigned short *)m_posterUrl.characters(), m_posterUrl.length());
217 env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_loadPoster, jUrl);
218 env->DeleteLocalRef(jUrl);
219 }
220
prepareToPlay()221 void MediaPlayerPrivate::prepareToPlay() {
222 // We are about to start playing. Since our Java VideoView cannot
223 // buffer any data, we just simply transition to the HaveEnoughData
224 // state in here. This will allow the MediaPlayer to transition to
225 // the "play" state, at which point our VideoView will start downloading
226 // the content and start the playback.
227 m_networkState = MediaPlayer::Loaded;
228 m_player->networkStateChanged();
229 m_readyState = MediaPlayer::HaveEnoughData;
230 m_player->readyStateChanged();
231 }
232
paint(GraphicsContext * ctxt,const IntRect & r)233 void MediaPlayerPrivate::paint(GraphicsContext* ctxt, const IntRect& r)
234 {
235 if (ctxt->paintingDisabled())
236 return;
237
238 if (!m_isVisible)
239 return;
240
241 if (!m_poster || (!m_poster->getPixels() && !m_poster->pixelRef()))
242 return;
243
244 SkCanvas* canvas = ctxt->platformContext()->mCanvas;
245 // We paint with the following rules in mind:
246 // - only downscale the poster, never upscale
247 // - maintain the natural aspect ratio of the poster
248 // - the poster should be centered in the target rect
249 float originalRatio = static_cast<float>(m_poster->width()) / static_cast<float>(m_poster->height());
250 int posterWidth = r.width() > m_poster->width() ? m_poster->width() : r.width();
251 int posterHeight = posterWidth / originalRatio;
252 int posterX = ((r.width() - posterWidth) / 2) + r.x();
253 int posterY = ((r.height() - posterHeight) / 2) + r.y();
254 IntRect targetRect(posterX, posterY, posterWidth, posterHeight);
255 canvas->drawBitmapRect(*m_poster, 0, targetRect, 0);
256 }
257
create(MediaPlayer * player)258 MediaPlayerPrivateInterface* MediaPlayerPrivate::create(MediaPlayer* player)
259 {
260 return new MediaPlayerPrivate(player);
261 }
262
getSupportedTypes(HashSet<String> &)263 void MediaPlayerPrivate::getSupportedTypes(HashSet<String>&)
264 {
265 }
266
supportsType(const String & type,const String & codecs)267 MediaPlayer::SupportsType MediaPlayerPrivate::supportsType(const String& type, const String& codecs)
268 {
269 return MediaPlayer::IsNotSupported;
270 }
271
MediaPlayerPrivate(MediaPlayer * player)272 MediaPlayerPrivate::MediaPlayerPrivate(MediaPlayer* player)
273 : m_player(player),
274 m_glue(0),
275 m_duration(6000),
276 m_currentTime(0),
277 m_paused(true),
278 m_readyState(MediaPlayer::HaveNothing),
279 m_networkState(MediaPlayer::Empty),
280 m_poster(0),
281 m_naturalSize(100, 100),
282 m_naturalSizeUnknown(true),
283 m_isVisible(false)
284 {
285 JNIEnv* env = JSC::Bindings::getJNIEnv();
286 if (!env)
287 return;
288
289 jclass clazz = env->FindClass(g_ProxyJavaClass);
290 if (!clazz)
291 return;
292
293 m_glue = new JavaGlue;
294 m_glue->m_getInstance = env->GetStaticMethodID(clazz, "getInstance", "(Landroid/webkit/WebViewCore;I)Landroid/webkit/HTML5VideoViewProxy;");
295 m_glue->m_play = env->GetMethodID(clazz, "play", "(Ljava/lang/String;)V");
296 m_glue->m_teardown = env->GetMethodID(clazz, "teardown", "()V");
297 m_glue->m_loadPoster = env->GetMethodID(clazz, "loadPoster", "(Ljava/lang/String;)V");
298 m_glue->m_seek = env->GetMethodID(clazz, "seek", "(I)V");
299 m_glue->m_pause = env->GetMethodID(clazz, "pause", "()V");
300 m_glue->m_javaProxy = NULL;
301 env->DeleteLocalRef(clazz);
302 // An exception is raised if any of the above fails.
303 checkException(env);
304 }
305
createJavaPlayerIfNeeded()306 void MediaPlayerPrivate::createJavaPlayerIfNeeded()
307 {
308 // Check if we have been already created.
309 if (m_glue->m_javaProxy)
310 return;
311
312 FrameView* frameView = m_player->frameView();
313 if (!frameView)
314 return;
315
316 JNIEnv* env = JSC::Bindings::getJNIEnv();
317 if (!env)
318 return;
319
320 jclass clazz = env->FindClass(g_ProxyJavaClass);
321 if (!clazz)
322 return;
323
324 WebViewCore* webViewCore = WebViewCore::getWebViewCore(frameView);
325 ASSERT(webViewCore);
326
327 // Get the HTML5VideoViewProxy instance
328 jobject obj = env->CallStaticObjectMethod(clazz, m_glue->m_getInstance, webViewCore->getJavaObject().get(), this);
329 m_glue->m_javaProxy = env->NewGlobalRef(obj);
330 // Send the poster
331 jstring jUrl = 0;
332 if (m_posterUrl.length())
333 jUrl = env->NewString((unsigned short *)m_posterUrl.characters(), m_posterUrl.length());
334 // Sending a NULL jUrl allows the Java side to try to load the default poster.
335 env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_loadPoster, jUrl);
336 if (jUrl)
337 env->DeleteLocalRef(jUrl);
338 // Clean up.
339 env->DeleteLocalRef(obj);
340 env->DeleteLocalRef(clazz);
341 checkException(env);
342 }
343
onPrepared(int duration,int width,int height)344 void MediaPlayerPrivate::onPrepared(int duration, int width, int height) {
345 m_duration = duration / 1000.0f;
346 m_naturalSize = IntSize(width, height);
347 m_naturalSizeUnknown = false;
348 m_player->durationChanged();
349 m_player->sizeChanged();
350 }
351
onEnded()352 void MediaPlayerPrivate::onEnded() {
353 m_paused = true;
354 m_currentTime = 0;
355 m_networkState = MediaPlayer::Idle;
356 m_readyState = MediaPlayer::HaveNothing;
357 }
358
onPosterFetched(SkBitmap * poster)359 void MediaPlayerPrivate::onPosterFetched(SkBitmap* poster) {
360 m_poster = poster;
361 if (m_naturalSizeUnknown) {
362 // We had to fake the size at startup, or else our paint
363 // method would not be called. If we haven't yet received
364 // the onPrepared event, update the intrinsic size to the size
365 // of the poster. That will be overriden when onPrepare comes.
366 // In case of an error, we should report the poster size, rather
367 // than our initial fake value.
368 m_naturalSize = IntSize(poster->width(), poster->height());
369 m_player->sizeChanged();
370 }
371 }
372
373 }
374
375 namespace android {
376
OnPrepared(JNIEnv * env,jobject obj,int duration,int width,int height,int pointer)377 static void OnPrepared(JNIEnv* env, jobject obj, int duration, int width, int height, int pointer) {
378 if (pointer) {
379 WebCore::MediaPlayerPrivate* player = reinterpret_cast<WebCore::MediaPlayerPrivate*>(pointer);
380 player->onPrepared(duration, width, height);
381 }
382 }
383
OnEnded(JNIEnv * env,jobject obj,int pointer)384 static void OnEnded(JNIEnv* env, jobject obj, int pointer) {
385 if (pointer) {
386 WebCore::MediaPlayerPrivate* player = reinterpret_cast<WebCore::MediaPlayerPrivate*>(pointer);
387 player->onEnded();
388 }
389 }
390
OnPosterFetched(JNIEnv * env,jobject obj,jobject poster,int pointer)391 static void OnPosterFetched(JNIEnv* env, jobject obj, jobject poster, int pointer) {
392 if (!pointer || !poster)
393 return;
394
395 WebCore::MediaPlayerPrivate* player = reinterpret_cast<WebCore::MediaPlayerPrivate*>(pointer);
396 SkBitmap* posterNative = GraphicsJNI::getNativeBitmap(env, poster);
397 if (!posterNative)
398 return;
399 player->onPosterFetched(posterNative);
400 }
401
402 /*
403 * JNI registration
404 */
405 static JNINativeMethod g_MediaPlayerMethods[] = {
406 { "nativeOnPrepared", "(IIII)V",
407 (void*) OnPrepared },
408 { "nativeOnEnded", "(I)V",
409 (void*) OnEnded },
410 { "nativeOnPosterFetched", "(Landroid/graphics/Bitmap;I)V",
411 (void*) OnPosterFetched },
412 };
413
register_mediaplayer(JNIEnv * env)414 int register_mediaplayer(JNIEnv* env)
415 {
416 return jniRegisterNativeMethods(env, g_ProxyJavaClass,
417 g_MediaPlayerMethods, NELEM(g_MediaPlayerMethods));
418 }
419
420 }
421 #endif // VIDEO
422