blob: 3caf3452822b5ebbcebf0bfcd5b77e4d2d2c627b [file] [log] [blame]
Andrei Popescu6fa29582009-06-19 14:54:09 +01001/*
2 * Copyright (C) 2009 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.webkit;
18
19import android.content.Context;
Andrei Popescu64b86a12009-09-15 20:34:18 +010020import android.graphics.Bitmap;
21import android.graphics.BitmapFactory;
Andrei Popescu3c946a1a2009-07-03 08:20:53 +010022import android.media.MediaPlayer;
23import android.media.MediaPlayer.OnPreparedListener;
Andrei Popescu64b86a12009-09-15 20:34:18 +010024import android.media.MediaPlayer.OnCompletionListener;
25import android.media.MediaPlayer.OnErrorListener;
26import android.net.http.EventHandler;
27import android.net.http.Headers;
28import android.net.http.RequestHandle;
29import android.net.http.RequestQueue;
30import android.net.http.SslCertificate;
31import android.net.http.SslError;
Andrei Popescu6fa29582009-06-19 14:54:09 +010032import android.net.Uri;
33import android.os.Bundle;
34import android.os.Handler;
35import android.os.Looper;
36import android.os.Message;
37import android.util.Log;
Andrei Popescu3c946a1a2009-07-03 08:20:53 +010038import android.view.MotionEvent;
Andrei Popescubf385d72009-09-18 18:59:52 +010039import android.view.Gravity;
Andrei Popescu6fa29582009-06-19 14:54:09 +010040import android.view.View;
Andrei Popescu3c946a1a2009-07-03 08:20:53 +010041import android.view.ViewGroup;
Andrei Popescu3c946a1a2009-07-03 08:20:53 +010042import android.widget.AbsoluteLayout;
Andrei Popescubf385d72009-09-18 18:59:52 +010043import android.widget.FrameLayout;
Andrei Popescu6fa29582009-06-19 14:54:09 +010044import android.widget.MediaController;
45import android.widget.VideoView;
46
Andrei Popescu64b86a12009-09-15 20:34:18 +010047import java.io.ByteArrayOutputStream;
48import java.io.IOException;
Ben Murdoch42509792011-02-18 12:34:35 +000049import java.net.MalformedURLException;
50import java.net.URL;
Andrei Popescu6fa29582009-06-19 14:54:09 +010051import java.util.HashMap;
Andrei Popescu290c34a2009-09-17 15:55:24 +010052import java.util.Map;
Andrei Popescu048eb3b2010-01-11 21:12:54 +000053import java.util.Timer;
54import java.util.TimerTask;
Andrei Popescu6fa29582009-06-19 14:54:09 +010055
56/**
Andrei Popescu3c946a1a2009-07-03 08:20:53 +010057 * <p>Proxy for HTML5 video views.
Andrei Popescu6fa29582009-06-19 14:54:09 +010058 */
Andrei Popescu290c34a2009-09-17 15:55:24 +010059class HTML5VideoViewProxy extends Handler
60 implements MediaPlayer.OnPreparedListener,
Andrei Popescu50c86232009-09-30 16:51:25 +010061 MediaPlayer.OnCompletionListener,
62 MediaPlayer.OnErrorListener {
Andrei Popescu6fa29582009-06-19 14:54:09 +010063 // Logging tag.
64 private static final String LOGTAG = "HTML5VideoViewProxy";
65
Andrei Popescu3c946a1a2009-07-03 08:20:53 +010066 // Message Ids for WebCore thread -> UI thread communication.
Andrei Popescu50c86232009-09-30 16:51:25 +010067 private static final int PLAY = 100;
68 private static final int SEEK = 101;
69 private static final int PAUSE = 102;
70 private static final int ERROR = 103;
71 private static final int LOAD_DEFAULT_POSTER = 104;
Andrei Popescu6fa29582009-06-19 14:54:09 +010072
Andrei Popescu64b86a12009-09-15 20:34:18 +010073 // Message Ids to be handled on the WebCore thread
Andrei Popescu290c34a2009-09-17 15:55:24 +010074 private static final int PREPARED = 200;
75 private static final int ENDED = 201;
Andrei Popescu50c86232009-09-30 16:51:25 +010076 private static final int POSTER_FETCHED = 202;
Shimeng (Simon) Wang43aaa2d2010-10-18 16:25:59 -070077 private static final int PAUSED = 203;
Andrei Popescu64b86a12009-09-15 20:34:18 +010078
Andreas Huber25643002010-01-28 11:19:57 -080079 private static final String COOKIE = "Cookie";
80
Andrei Popescu048eb3b2010-01-11 21:12:54 +000081 // Timer thread -> UI thread
82 private static final int TIMEUPDATE = 300;
83
Andrei Popescu290c34a2009-09-17 15:55:24 +010084 // The C++ MediaPlayerPrivateAndroid object.
85 int mNativePointer;
Andrei Popescu64b86a12009-09-15 20:34:18 +010086 // The handler for WebCore thread messages;
87 private Handler mWebCoreHandler;
Andrei Popescu3c946a1a2009-07-03 08:20:53 +010088 // The WebView instance that created this view.
89 private WebView mWebView;
Andrei Popescu64b86a12009-09-15 20:34:18 +010090 // The poster image to be shown when the video is not playing.
Andrei Popescu50c86232009-09-30 16:51:25 +010091 // This ref prevents the bitmap from being GC'ed.
92 private Bitmap mPoster;
Andrei Popescu64b86a12009-09-15 20:34:18 +010093 // The poster downloader.
94 private PosterDownloader mPosterDownloader;
Andrei Popescu290c34a2009-09-17 15:55:24 +010095 // The seek position.
96 private int mSeekPosition;
Andrei Popescu64b86a12009-09-15 20:34:18 +010097 // A helper class to control the playback. This executes on the UI thread!
98 private static final class VideoPlayer {
99 // The proxy that is currently playing (if any).
100 private static HTML5VideoViewProxy mCurrentProxy;
101 // The VideoView instance. This is a singleton for now, at least until
102 // http://b/issue?id=1973663 is fixed.
103 private static VideoView mVideoView;
Andrei Popescubf385d72009-09-18 18:59:52 +0100104 // The progress view.
105 private static View mProgressView;
106 // The container for the progress view and video view
107 private static FrameLayout mLayout;
Andrei Popescu048eb3b2010-01-11 21:12:54 +0000108 // The timer for timeupate events.
109 // See http://www.whatwg.org/specs/web-apps/current-work/#event-media-timeupdate
110 private static Timer mTimer;
111 private static final class TimeupdateTask extends TimerTask {
112 private HTML5VideoViewProxy mProxy;
113
114 public TimeupdateTask(HTML5VideoViewProxy proxy) {
115 mProxy = proxy;
116 }
117
118 public void run() {
119 mProxy.onTimeupdate();
120 }
121 }
122 // The spec says the timer should fire every 250 ms or less.
123 private static final int TIMEUPDATE_PERIOD = 250; // ms
Shimeng (Simon) Wang43aaa2d2010-10-18 16:25:59 -0700124 static boolean isVideoSelfEnded = false;
Andrei Popescu3c946a1a2009-07-03 08:20:53 +0100125
Andrei Popescu64b86a12009-09-15 20:34:18 +0100126 private static final WebChromeClient.CustomViewCallback mCallback =
127 new WebChromeClient.CustomViewCallback() {
128 public void onCustomViewHidden() {
129 // At this point the videoview is pretty much destroyed.
130 // It listens to SurfaceHolder.Callback.SurfaceDestroyed event
131 // which happens when the video view is detached from its parent
132 // view. This happens in the WebChromeClient before this method
133 // is invoked.
Andrei Popescu048eb3b2010-01-11 21:12:54 +0000134 mTimer.cancel();
135 mTimer = null;
Andrei Popescu31d2aa12010-04-08 15:19:22 +0100136 if (mVideoView.isPlaying()) {
137 mVideoView.stopPlayback();
138 }
Shimeng (Simon) Wang43aaa2d2010-10-18 16:25:59 -0700139 if (isVideoSelfEnded)
140 mCurrentProxy.dispatchOnEnded();
141 else
142 mCurrentProxy.dispatchOnPaused();
Ben Murdoch1708ad52010-11-11 15:56:16 +0000143
144 // Re enable plugin views.
145 mCurrentProxy.getWebView().getViewManager().showAll();
146
Shimeng (Simon) Wang43aaa2d2010-10-18 16:25:59 -0700147 isVideoSelfEnded = false;
Andrei Popescu64b86a12009-09-15 20:34:18 +0100148 mCurrentProxy = null;
Andrei Popescubf385d72009-09-18 18:59:52 +0100149 mLayout.removeView(mVideoView);
Andrei Popescu64b86a12009-09-15 20:34:18 +0100150 mVideoView = null;
Andrei Popescubf385d72009-09-18 18:59:52 +0100151 if (mProgressView != null) {
152 mLayout.removeView(mProgressView);
153 mProgressView = null;
154 }
155 mLayout = null;
Andrei Popescu64b86a12009-09-15 20:34:18 +0100156 }
157 };
Andrei Popescu3c946a1a2009-07-03 08:20:53 +0100158
Andrei Popescu290c34a2009-09-17 15:55:24 +0100159 public static void play(String url, int time, HTML5VideoViewProxy proxy,
160 WebChromeClient client) {
Andrei Popescua41f97b2010-01-11 18:36:25 +0000161 if (mCurrentProxy == proxy) {
162 if (!mVideoView.isPlaying()) {
163 mVideoView.start();
164 }
165 return;
166 }
167
Andrei Popescu64b86a12009-09-15 20:34:18 +0100168 if (mCurrentProxy != null) {
169 // Some other video is already playing. Notify the caller that its playback ended.
Andrei Popescuc4fbceb2010-04-14 13:30:23 +0100170 proxy.dispatchOnEnded();
Andrei Popescu3c946a1a2009-07-03 08:20:53 +0100171 return;
172 }
Andrei Popescua41f97b2010-01-11 18:36:25 +0000173
Andrei Popescu64b86a12009-09-15 20:34:18 +0100174 mCurrentProxy = proxy;
Andrei Popescubf385d72009-09-18 18:59:52 +0100175 // Create a FrameLayout that will contain the VideoView and the
176 // progress view (if any).
177 mLayout = new FrameLayout(proxy.getContext());
178 FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(
179 ViewGroup.LayoutParams.WRAP_CONTENT,
180 ViewGroup.LayoutParams.WRAP_CONTENT,
181 Gravity.CENTER);
Andrei Popescu64b86a12009-09-15 20:34:18 +0100182 mVideoView = new VideoView(proxy.getContext());
183 mVideoView.setWillNotDraw(false);
184 mVideoView.setMediaController(new MediaController(proxy.getContext()));
Andreas Huber25643002010-01-28 11:19:57 -0800185
186 String cookieValue = CookieManager.getInstance().getCookie(url);
187 Map<String, String> headers = null;
188 if (cookieValue != null) {
189 headers = new HashMap<String, String>();
190 headers.put(COOKIE, cookieValue);
191 }
192
193 mVideoView.setVideoURI(Uri.parse(url), headers);
Andrei Popescu290c34a2009-09-17 15:55:24 +0100194 mVideoView.setOnCompletionListener(proxy);
195 mVideoView.setOnPreparedListener(proxy);
Andrei Popescu50c86232009-09-30 16:51:25 +0100196 mVideoView.setOnErrorListener(proxy);
Andrei Popescu290c34a2009-09-17 15:55:24 +0100197 mVideoView.seekTo(time);
Andrei Popescubf385d72009-09-18 18:59:52 +0100198 mLayout.addView(mVideoView, layoutParams);
199 mProgressView = client.getVideoLoadingProgressView();
200 if (mProgressView != null) {
201 mLayout.addView(mProgressView, layoutParams);
202 mProgressView.setVisibility(View.VISIBLE);
203 }
204 mLayout.setVisibility(View.VISIBLE);
Andrei Popescu048eb3b2010-01-11 21:12:54 +0000205 mTimer = new Timer();
Andrei Popescu64b86a12009-09-15 20:34:18 +0100206 mVideoView.start();
Andrei Popescubf385d72009-09-18 18:59:52 +0100207 client.onShowCustomView(mLayout, mCallback);
Ben Murdoch1708ad52010-11-11 15:56:16 +0000208 // Plugins like Flash will draw over the video so hide
209 // them while we're playing.
210 mCurrentProxy.getWebView().getViewManager().hideAll();
Andrei Popescu3c946a1a2009-07-03 08:20:53 +0100211 }
Andrei Popescu290c34a2009-09-17 15:55:24 +0100212
Andrei Popescu048eb3b2010-01-11 21:12:54 +0000213 public static boolean isPlaying(HTML5VideoViewProxy proxy) {
214 return (mCurrentProxy == proxy && mVideoView != null && mVideoView.isPlaying());
215 }
216
217 public static int getCurrentPosition() {
218 int currentPosMs = 0;
219 if (mVideoView != null) {
220 currentPosMs = mVideoView.getCurrentPosition();
221 }
222 return currentPosMs;
223 }
224
Andrei Popescu290c34a2009-09-17 15:55:24 +0100225 public static void seek(int time, HTML5VideoViewProxy proxy) {
226 if (mCurrentProxy == proxy && time >= 0 && mVideoView != null) {
227 mVideoView.seekTo(time);
228 }
229 }
230
231 public static void pause(HTML5VideoViewProxy proxy) {
232 if (mCurrentProxy == proxy && mVideoView != null) {
233 mVideoView.pause();
Andrei Popescu048eb3b2010-01-11 21:12:54 +0000234 mTimer.purge();
Andrei Popescu290c34a2009-09-17 15:55:24 +0100235 }
236 }
Andrei Popescubf385d72009-09-18 18:59:52 +0100237
238 public static void onPrepared() {
Andrei Popescu50c86232009-09-30 16:51:25 +0100239 if (mProgressView == null || mLayout == null) {
240 return;
Andrei Popescubf385d72009-09-18 18:59:52 +0100241 }
Andrei Popescu8aab46a2010-02-27 15:47:46 +0000242 mTimer.schedule(new TimeupdateTask(mCurrentProxy), TIMEUPDATE_PERIOD, TIMEUPDATE_PERIOD);
Andrei Popescu50c86232009-09-30 16:51:25 +0100243 mProgressView.setVisibility(View.GONE);
244 mLayout.removeView(mProgressView);
245 mProgressView = null;
Andrei Popescubf385d72009-09-18 18:59:52 +0100246 }
Andrei Popescu290c34a2009-09-17 15:55:24 +0100247 }
248
249 // A bunch event listeners for our VideoView
250 // MediaPlayer.OnPreparedListener
251 public void onPrepared(MediaPlayer mp) {
Andrei Popescubf385d72009-09-18 18:59:52 +0100252 VideoPlayer.onPrepared();
Andrei Popescu290c34a2009-09-17 15:55:24 +0100253 Message msg = Message.obtain(mWebCoreHandler, PREPARED);
254 Map<String, Object> map = new HashMap<String, Object>();
255 map.put("dur", new Integer(mp.getDuration()));
256 map.put("width", new Integer(mp.getVideoWidth()));
257 map.put("height", new Integer(mp.getVideoHeight()));
258 msg.obj = map;
259 mWebCoreHandler.sendMessage(msg);
260 }
261
262 // MediaPlayer.OnCompletionListener;
263 public void onCompletion(MediaPlayer mp) {
Andrei Popescuc4fbceb2010-04-14 13:30:23 +0100264 // The video ended by itself, so we need to
265 // send a message to the UI thread to dismiss
266 // the video view and to return to the WebView.
Shimeng (Simon) Wang43aaa2d2010-10-18 16:25:59 -0700267 // arg1 == 1 means the video ends by itself.
268 sendMessage(obtainMessage(ENDED, 1, 0));
Andrei Popescu290c34a2009-09-17 15:55:24 +0100269 }
270
Andrei Popescu50c86232009-09-30 16:51:25 +0100271 // MediaPlayer.OnErrorListener
272 public boolean onError(MediaPlayer mp, int what, int extra) {
273 sendMessage(obtainMessage(ERROR));
274 return false;
275 }
276
Andrei Popescuc4fbceb2010-04-14 13:30:23 +0100277 public void dispatchOnEnded() {
Andrei Popescu290c34a2009-09-17 15:55:24 +0100278 Message msg = Message.obtain(mWebCoreHandler, ENDED);
279 mWebCoreHandler.sendMessage(msg);
Andrei Popescu64b86a12009-09-15 20:34:18 +0100280 }
Andrei Popescu3c946a1a2009-07-03 08:20:53 +0100281
Shimeng (Simon) Wang43aaa2d2010-10-18 16:25:59 -0700282 public void dispatchOnPaused() {
283 Message msg = Message.obtain(mWebCoreHandler, PAUSED);
284 mWebCoreHandler.sendMessage(msg);
285 }
286
Andrei Popescu048eb3b2010-01-11 21:12:54 +0000287 public void onTimeupdate() {
288 sendMessage(obtainMessage(TIMEUPDATE));
289 }
290
291 // Handler for the messages from WebCore or Timer thread to the UI thread.
Andrei Popescu64b86a12009-09-15 20:34:18 +0100292 @Override
293 public void handleMessage(Message msg) {
294 // This executes on the UI thread.
295 switch (msg.what) {
Andrei Popescu64b86a12009-09-15 20:34:18 +0100296 case PLAY: {
297 String url = (String) msg.obj;
298 WebChromeClient client = mWebView.getWebChromeClient();
299 if (client != null) {
Andrei Popescu290c34a2009-09-17 15:55:24 +0100300 VideoPlayer.play(url, mSeekPosition, this, client);
Andrei Popescu64b86a12009-09-15 20:34:18 +0100301 }
302 break;
303 }
Andrei Popescu290c34a2009-09-17 15:55:24 +0100304 case SEEK: {
305 Integer time = (Integer) msg.obj;
306 mSeekPosition = time;
307 VideoPlayer.seek(mSeekPosition, this);
308 break;
309 }
310 case PAUSE: {
311 VideoPlayer.pause(this);
312 break;
313 }
Andrei Popescu46a83b42009-10-23 13:49:46 +0100314 case ENDED:
Shimeng (Simon) Wang43aaa2d2010-10-18 16:25:59 -0700315 if (msg.arg1 == 1)
316 VideoPlayer.isVideoSelfEnded = true;
Andrei Popescu50c86232009-09-30 16:51:25 +0100317 case ERROR: {
318 WebChromeClient client = mWebView.getWebChromeClient();
319 if (client != null) {
320 client.onHideCustomView();
321 }
322 break;
323 }
324 case LOAD_DEFAULT_POSTER: {
325 WebChromeClient client = mWebView.getWebChromeClient();
326 if (client != null) {
327 doSetPoster(client.getDefaultVideoPoster());
328 }
329 break;
330 }
Andrei Popescu048eb3b2010-01-11 21:12:54 +0000331 case TIMEUPDATE: {
332 if (VideoPlayer.isPlaying(this)) {
333 sendTimeupdate();
334 }
335 break;
336 }
Andrei Popescu64b86a12009-09-15 20:34:18 +0100337 }
338 }
339
Andrei Popescu64b86a12009-09-15 20:34:18 +0100340 // Everything below this comment executes on the WebCore thread, except for
341 // the EventHandler methods, which are called on the network thread.
342
343 // A helper class that knows how to download posters
344 private static final class PosterDownloader implements EventHandler {
345 // The request queue. This is static as we have one queue for all posters.
346 private static RequestQueue mRequestQueue;
347 private static int mQueueRefCount = 0;
348 // The poster URL
Ben Murdoch42509792011-02-18 12:34:35 +0000349 private URL mUrl;
Andrei Popescu64b86a12009-09-15 20:34:18 +0100350 // The proxy we're doing this for.
351 private final HTML5VideoViewProxy mProxy;
352 // The poster bytes. We only touch this on the network thread.
353 private ByteArrayOutputStream mPosterBytes;
354 // The request handle. We only touch this on the WebCore thread.
355 private RequestHandle mRequestHandle;
356 // The response status code.
357 private int mStatusCode;
358 // The response headers.
359 private Headers mHeaders;
360 // The handler to handle messages on the WebCore thread.
361 private Handler mHandler;
362
363 public PosterDownloader(String url, HTML5VideoViewProxy proxy) {
Ben Murdoch42509792011-02-18 12:34:35 +0000364 try {
365 mUrl = new URL(url);
366 } catch (MalformedURLException e) {
367 mUrl = null;
368 }
Andrei Popescu64b86a12009-09-15 20:34:18 +0100369 mProxy = proxy;
370 mHandler = new Handler();
371 }
372 // Start the download. Called on WebCore thread.
373 public void start() {
374 retainQueue();
Ben Murdoch42509792011-02-18 12:34:35 +0000375
376 if (mUrl == null) {
377 return;
378 }
379
380 // Only support downloading posters over http/https.
381 // FIXME: Add support for other schemes. WebKit seems able to load
382 // posters over other schemes e.g. file://, but gets the dimensions wrong.
383 String protocol = mUrl.getProtocol();
384 if ("http".equals(protocol) || "https".equals(protocol)) {
385 mRequestHandle = mRequestQueue.queueRequest(mUrl.toString(), "GET", null,
386 this, null, 0);
387 }
Andrei Popescu64b86a12009-09-15 20:34:18 +0100388 }
389 // Cancel the download if active and release the queue. Called on WebCore thread.
390 public void cancelAndReleaseQueue() {
391 if (mRequestHandle != null) {
392 mRequestHandle.cancel();
393 mRequestHandle = null;
394 }
395 releaseQueue();
396 }
397 // EventHandler methods. Executed on the network thread.
398 public void status(int major_version,
399 int minor_version,
400 int code,
401 String reason_phrase) {
402 mStatusCode = code;
Andrei Popescu3c946a1a2009-07-03 08:20:53 +0100403 }
404
Andrei Popescu64b86a12009-09-15 20:34:18 +0100405 public void headers(Headers headers) {
406 mHeaders = headers;
407 }
408
409 public void data(byte[] data, int len) {
410 if (mPosterBytes == null) {
411 mPosterBytes = new ByteArrayOutputStream();
412 }
413 mPosterBytes.write(data, 0, len);
414 }
415
416 public void endData() {
417 if (mStatusCode == 200) {
418 if (mPosterBytes.size() > 0) {
419 Bitmap poster = BitmapFactory.decodeByteArray(
420 mPosterBytes.toByteArray(), 0, mPosterBytes.size());
Andrei Popescu50c86232009-09-30 16:51:25 +0100421 mProxy.doSetPoster(poster);
Andrei Popescu64b86a12009-09-15 20:34:18 +0100422 }
423 cleanup();
424 } else if (mStatusCode >= 300 && mStatusCode < 400) {
425 // We have a redirect.
Ben Murdoch42509792011-02-18 12:34:35 +0000426 try {
427 mUrl = new URL(mHeaders.getLocation());
428 } catch (MalformedURLException e) {
429 mUrl = null;
430 }
Andrei Popescu64b86a12009-09-15 20:34:18 +0100431 if (mUrl != null) {
432 mHandler.post(new Runnable() {
433 public void run() {
434 if (mRequestHandle != null) {
Ben Murdoch42509792011-02-18 12:34:35 +0000435 mRequestHandle.setupRedirect(mUrl.toString(), mStatusCode,
Andrei Popescu64b86a12009-09-15 20:34:18 +0100436 new HashMap<String, String>());
437 }
438 }
439 });
440 }
441 }
442 }
443
444 public void certificate(SslCertificate certificate) {
445 // Don't care.
446 }
447
448 public void error(int id, String description) {
449 cleanup();
450 }
451
452 public boolean handleSslErrorRequest(SslError error) {
453 // Don't care. If this happens, data() will never be called so
454 // mPosterBytes will never be created, so no need to call cleanup.
455 return false;
456 }
457 // Tears down the poster bytes stream. Called on network thread.
458 private void cleanup() {
459 if (mPosterBytes != null) {
460 try {
461 mPosterBytes.close();
462 } catch (IOException ignored) {
463 // Ignored.
464 } finally {
465 mPosterBytes = null;
466 }
467 }
468 }
469
470 // Queue management methods. Called on WebCore thread.
471 private void retainQueue() {
472 if (mRequestQueue == null) {
473 mRequestQueue = new RequestQueue(mProxy.getContext());
474 }
475 mQueueRefCount++;
476 }
477
478 private void releaseQueue() {
479 if (mQueueRefCount == 0) {
480 return;
481 }
482 if (--mQueueRefCount == 0) {
483 mRequestQueue.shutdown();
484 mRequestQueue = null;
485 }
Andrei Popescu3c946a1a2009-07-03 08:20:53 +0100486 }
487 }
Andrei Popescu6fa29582009-06-19 14:54:09 +0100488
489 /**
490 * Private constructor.
Andrei Popescu290c34a2009-09-17 15:55:24 +0100491 * @param webView is the WebView that hosts the video.
492 * @param nativePtr is the C++ pointer to the MediaPlayerPrivate object.
Andrei Popescu6fa29582009-06-19 14:54:09 +0100493 */
Andrei Popescu290c34a2009-09-17 15:55:24 +0100494 private HTML5VideoViewProxy(WebView webView, int nativePtr) {
Andrei Popescu6fa29582009-06-19 14:54:09 +0100495 // This handler is for the main (UI) thread.
496 super(Looper.getMainLooper());
Andrei Popescu3c946a1a2009-07-03 08:20:53 +0100497 // Save the WebView object.
498 mWebView = webView;
Andrei Popescu290c34a2009-09-17 15:55:24 +0100499 // Save the native ptr
500 mNativePointer = nativePtr;
Andrei Popescu64b86a12009-09-15 20:34:18 +0100501 // create the message handler for this thread
502 createWebCoreHandler();
Andrei Popescu6fa29582009-06-19 14:54:09 +0100503 }
504
Andrei Popescu64b86a12009-09-15 20:34:18 +0100505 private void createWebCoreHandler() {
506 mWebCoreHandler = new Handler() {
507 @Override
508 public void handleMessage(Message msg) {
509 switch (msg.what) {
Andrei Popescu290c34a2009-09-17 15:55:24 +0100510 case PREPARED: {
511 Map<String, Object> map = (Map<String, Object>) msg.obj;
512 Integer duration = (Integer) map.get("dur");
513 Integer width = (Integer) map.get("width");
514 Integer height = (Integer) map.get("height");
515 nativeOnPrepared(duration.intValue(), width.intValue(),
516 height.intValue(), mNativePointer);
517 break;
518 }
519 case ENDED:
Ben Murdochff19d192011-01-17 18:08:56 +0000520 mSeekPosition = 0;
Andrei Popescu290c34a2009-09-17 15:55:24 +0100521 nativeOnEnded(mNativePointer);
522 break;
Shimeng (Simon) Wang43aaa2d2010-10-18 16:25:59 -0700523 case PAUSED:
524 nativeOnPaused(mNativePointer);
525 break;
Andrei Popescu50c86232009-09-30 16:51:25 +0100526 case POSTER_FETCHED:
527 Bitmap poster = (Bitmap) msg.obj;
528 nativeOnPosterFetched(poster, mNativePointer);
529 break;
Andrei Popescu048eb3b2010-01-11 21:12:54 +0000530 case TIMEUPDATE:
531 nativeOnTimeupdate(msg.arg1, mNativePointer);
532 break;
Andrei Popescu3c946a1a2009-07-03 08:20:53 +0100533 }
Andrei Popescu64b86a12009-09-15 20:34:18 +0100534 }
535 };
Andrei Popescu6fa29582009-06-19 14:54:09 +0100536 }
537
Andrei Popescu64b86a12009-09-15 20:34:18 +0100538 private void doSetPoster(Bitmap poster) {
539 if (poster == null) {
540 return;
541 }
Andrei Popescu50c86232009-09-30 16:51:25 +0100542 // Save a ref to the bitmap and send it over to the WebCore thread.
543 mPoster = poster;
544 Message msg = Message.obtain(mWebCoreHandler, POSTER_FETCHED);
545 msg.obj = poster;
546 mWebCoreHandler.sendMessage(msg);
Andrei Popescu64b86a12009-09-15 20:34:18 +0100547 }
548
Andrei Popescu048eb3b2010-01-11 21:12:54 +0000549 private void sendTimeupdate() {
550 Message msg = Message.obtain(mWebCoreHandler, TIMEUPDATE);
551 msg.arg1 = VideoPlayer.getCurrentPosition();
552 mWebCoreHandler.sendMessage(msg);
553 }
554
Andrei Popescu64b86a12009-09-15 20:34:18 +0100555 public Context getContext() {
556 return mWebView.getContext();
557 }
558
559 // The public methods below are all called from WebKit only.
Andrei Popescu6fa29582009-06-19 14:54:09 +0100560 /**
561 * Play a video stream.
562 * @param url is the URL of the video stream.
Andrei Popescu6fa29582009-06-19 14:54:09 +0100563 */
Ben Murdochff19d192011-01-17 18:08:56 +0000564 public void play(String url, int position) {
Andrei Popescu64b86a12009-09-15 20:34:18 +0100565 if (url == null) {
566 return;
567 }
Ben Murdochff19d192011-01-17 18:08:56 +0000568
569 if (position > 0) {
570 seek(position);
571 }
572
Andrei Popescu6fa29582009-06-19 14:54:09 +0100573 Message message = obtainMessage(PLAY);
Andrei Popescu64b86a12009-09-15 20:34:18 +0100574 message.obj = url;
Andrei Popescu6fa29582009-06-19 14:54:09 +0100575 sendMessage(message);
576 }
577
Andrei Popescu64b86a12009-09-15 20:34:18 +0100578 /**
Andrei Popescu290c34a2009-09-17 15:55:24 +0100579 * Seek into the video stream.
580 * @param time is the position in the video stream.
581 */
582 public void seek(int time) {
583 Message message = obtainMessage(SEEK);
584 message.obj = new Integer(time);
585 sendMessage(message);
586 }
587
588 /**
589 * Pause the playback.
590 */
591 public void pause() {
592 Message message = obtainMessage(PAUSE);
593 sendMessage(message);
594 }
595
596 /**
Andrei Popescu50c86232009-09-30 16:51:25 +0100597 * Tear down this proxy object.
Andrei Popescu64b86a12009-09-15 20:34:18 +0100598 */
Andrei Popescu50c86232009-09-30 16:51:25 +0100599 public void teardown() {
Andrei Popescu64b86a12009-09-15 20:34:18 +0100600 // This is called by the C++ MediaPlayerPrivate dtor.
601 // Cancel any active poster download.
602 if (mPosterDownloader != null) {
603 mPosterDownloader.cancelAndReleaseQueue();
604 }
Andrei Popescu50c86232009-09-30 16:51:25 +0100605 mNativePointer = 0;
Andrei Popescu64b86a12009-09-15 20:34:18 +0100606 }
607
608 /**
609 * Load the poster image.
610 * @param url is the URL of the poster image.
611 */
612 public void loadPoster(String url) {
613 if (url == null) {
Andrei Popescu50c86232009-09-30 16:51:25 +0100614 Message message = obtainMessage(LOAD_DEFAULT_POSTER);
615 sendMessage(message);
Andrei Popescu64b86a12009-09-15 20:34:18 +0100616 return;
617 }
618 // Cancel any active poster download.
619 if (mPosterDownloader != null) {
620 mPosterDownloader.cancelAndReleaseQueue();
621 }
622 // Load the poster asynchronously
623 mPosterDownloader = new PosterDownloader(url, this);
624 mPosterDownloader.start();
Patrick Scott0a5ce012009-07-02 08:56:10 -0400625 }
626
Andrei Popescu6fa29582009-06-19 14:54:09 +0100627 /**
Andrei Popescu3c946a1a2009-07-03 08:20:53 +0100628 * The factory for HTML5VideoViewProxy instances.
Andrei Popescu6fa29582009-06-19 14:54:09 +0100629 * @param webViewCore is the WebViewCore that is requesting the proxy.
630 *
Andrei Popescu3c946a1a2009-07-03 08:20:53 +0100631 * @return a new HTML5VideoViewProxy object.
Andrei Popescu6fa29582009-06-19 14:54:09 +0100632 */
Andrei Popescu290c34a2009-09-17 15:55:24 +0100633 public static HTML5VideoViewProxy getInstance(WebViewCore webViewCore, int nativePtr) {
634 return new HTML5VideoViewProxy(webViewCore.getWebView(), nativePtr);
Andrei Popescu6fa29582009-06-19 14:54:09 +0100635 }
Andrei Popescu290c34a2009-09-17 15:55:24 +0100636
Ben Murdoch1708ad52010-11-11 15:56:16 +0000637 /* package */ WebView getWebView() {
638 return mWebView;
639 }
640
Andrei Popescu290c34a2009-09-17 15:55:24 +0100641 private native void nativeOnPrepared(int duration, int width, int height, int nativePointer);
642 private native void nativeOnEnded(int nativePointer);
Shimeng (Simon) Wang43aaa2d2010-10-18 16:25:59 -0700643 private native void nativeOnPaused(int nativePointer);
Andrei Popescu50c86232009-09-30 16:51:25 +0100644 private native void nativeOnPosterFetched(Bitmap poster, int nativePointer);
Andrei Popescu048eb3b2010-01-11 21:12:54 +0000645 private native void nativeOnTimeupdate(int position, int nativePointer);
Andrei Popescu6fa29582009-06-19 14:54:09 +0100646}