blob: 14bc33b925a43df435a72f18c6b5a8b43686526e [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 Popescu6fa29582009-06-19 14:54:09 +010039import android.view.View;
Andrei Popescu3c946a1a2009-07-03 08:20:53 +010040import android.view.ViewGroup;
Patrick Scott0a5ce012009-07-02 08:56:10 -040041import android.webkit.ViewManager.ChildView;
Andrei Popescu3c946a1a2009-07-03 08:20:53 +010042import android.widget.AbsoluteLayout;
Andrei Popescu64b86a12009-09-15 20:34:18 +010043import android.widget.ImageView;
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;
Andrei Popescu6fa29582009-06-19 14:54:09 +010049import java.util.HashMap;
50
51/**
Andrei Popescu3c946a1a2009-07-03 08:20:53 +010052 * <p>Proxy for HTML5 video views.
Andrei Popescu6fa29582009-06-19 14:54:09 +010053 */
54class HTML5VideoViewProxy extends Handler {
55 // Logging tag.
56 private static final String LOGTAG = "HTML5VideoViewProxy";
57
Andrei Popescu3c946a1a2009-07-03 08:20:53 +010058 // Message Ids for WebCore thread -> UI thread communication.
Andrei Popescu6fa29582009-06-19 14:54:09 +010059 private static final int INIT = 100;
60 private static final int PLAY = 101;
Andrei Popescu64b86a12009-09-15 20:34:18 +010061 private static final int SET_POSTER = 102;
Andrei Popescu6fa29582009-06-19 14:54:09 +010062
Andrei Popescu64b86a12009-09-15 20:34:18 +010063 // Message Ids to be handled on the WebCore thread
64
65 // The handler for WebCore thread messages;
66 private Handler mWebCoreHandler;
Andrei Popescu3c946a1a2009-07-03 08:20:53 +010067 // The WebView instance that created this view.
68 private WebView mWebView;
69 // The ChildView instance used by the ViewManager.
70 private ChildView mChildView;
Andrei Popescu64b86a12009-09-15 20:34:18 +010071 // The poster image to be shown when the video is not playing.
72 private ImageView mPosterView;
73 // The poster downloader.
74 private PosterDownloader mPosterDownloader;
75 // A helper class to control the playback. This executes on the UI thread!
76 private static final class VideoPlayer {
77 // The proxy that is currently playing (if any).
78 private static HTML5VideoViewProxy mCurrentProxy;
79 // The VideoView instance. This is a singleton for now, at least until
80 // http://b/issue?id=1973663 is fixed.
81 private static VideoView mVideoView;
Andrei Popescu3c946a1a2009-07-03 08:20:53 +010082
Andrei Popescu64b86a12009-09-15 20:34:18 +010083 private static final WebChromeClient.CustomViewCallback mCallback =
84 new WebChromeClient.CustomViewCallback() {
85 public void onCustomViewHidden() {
86 // At this point the videoview is pretty much destroyed.
87 // It listens to SurfaceHolder.Callback.SurfaceDestroyed event
88 // which happens when the video view is detached from its parent
89 // view. This happens in the WebChromeClient before this method
90 // is invoked.
91 mCurrentProxy.playbackEnded();
92 mCurrentProxy = null;
93 mVideoView = null;
94 }
95 };
Andrei Popescu3c946a1a2009-07-03 08:20:53 +010096
Andrei Popescu64b86a12009-09-15 20:34:18 +010097 public static void play(String url, HTML5VideoViewProxy proxy, WebChromeClient client) {
98 if (mCurrentProxy != null) {
99 // Some other video is already playing. Notify the caller that its playback ended.
100 proxy.playbackEnded();
Andrei Popescu3c946a1a2009-07-03 08:20:53 +0100101 return;
102 }
Andrei Popescu64b86a12009-09-15 20:34:18 +0100103 mCurrentProxy = proxy;
104 mVideoView = new VideoView(proxy.getContext());
105 mVideoView.setWillNotDraw(false);
106 mVideoView.setMediaController(new MediaController(proxy.getContext()));
107 mVideoView.setVideoURI(Uri.parse(url));
108 mVideoView.start();
109 client.onShowCustomView(mVideoView, mCallback);
Andrei Popescu3c946a1a2009-07-03 08:20:53 +0100110 }
Andrei Popescu64b86a12009-09-15 20:34:18 +0100111 }
Andrei Popescu3c946a1a2009-07-03 08:20:53 +0100112
Andrei Popescu64b86a12009-09-15 20:34:18 +0100113 // Handler for the messages from WebCore thread to the UI thread.
114 @Override
115 public void handleMessage(Message msg) {
116 // This executes on the UI thread.
117 switch (msg.what) {
118 case INIT: {
119 mPosterView = new ImageView(mWebView.getContext());
120 mChildView.mView = mPosterView;
121 break;
Andrei Popescu3c946a1a2009-07-03 08:20:53 +0100122 }
Andrei Popescu64b86a12009-09-15 20:34:18 +0100123 case PLAY: {
124 String url = (String) msg.obj;
125 WebChromeClient client = mWebView.getWebChromeClient();
126 if (client != null) {
127 VideoPlayer.play(url, this, client);
128 }
129 break;
130 }
131 case SET_POSTER: {
132 Bitmap poster = (Bitmap) msg.obj;
133 mPosterView.setImageBitmap(poster);
134 break;
135 }
136 }
137 }
138
139 public void playbackEnded() {
140 // TODO: notify WebKit
141 }
142
143 // Everything below this comment executes on the WebCore thread, except for
144 // the EventHandler methods, which are called on the network thread.
145
146 // A helper class that knows how to download posters
147 private static final class PosterDownloader implements EventHandler {
148 // The request queue. This is static as we have one queue for all posters.
149 private static RequestQueue mRequestQueue;
150 private static int mQueueRefCount = 0;
151 // The poster URL
152 private String mUrl;
153 // The proxy we're doing this for.
154 private final HTML5VideoViewProxy mProxy;
155 // The poster bytes. We only touch this on the network thread.
156 private ByteArrayOutputStream mPosterBytes;
157 // The request handle. We only touch this on the WebCore thread.
158 private RequestHandle mRequestHandle;
159 // The response status code.
160 private int mStatusCode;
161 // The response headers.
162 private Headers mHeaders;
163 // The handler to handle messages on the WebCore thread.
164 private Handler mHandler;
165
166 public PosterDownloader(String url, HTML5VideoViewProxy proxy) {
167 mUrl = url;
168 mProxy = proxy;
169 mHandler = new Handler();
170 }
171 // Start the download. Called on WebCore thread.
172 public void start() {
173 retainQueue();
174 mRequestHandle = mRequestQueue.queueRequest(mUrl, "GET", null, this, null, 0);
175 }
176 // Cancel the download if active and release the queue. Called on WebCore thread.
177 public void cancelAndReleaseQueue() {
178 if (mRequestHandle != null) {
179 mRequestHandle.cancel();
180 mRequestHandle = null;
181 }
182 releaseQueue();
183 }
184 // EventHandler methods. Executed on the network thread.
185 public void status(int major_version,
186 int minor_version,
187 int code,
188 String reason_phrase) {
189 mStatusCode = code;
Andrei Popescu3c946a1a2009-07-03 08:20:53 +0100190 }
191
Andrei Popescu64b86a12009-09-15 20:34:18 +0100192 public void headers(Headers headers) {
193 mHeaders = headers;
194 }
195
196 public void data(byte[] data, int len) {
197 if (mPosterBytes == null) {
198 mPosterBytes = new ByteArrayOutputStream();
199 }
200 mPosterBytes.write(data, 0, len);
201 }
202
203 public void endData() {
204 if (mStatusCode == 200) {
205 if (mPosterBytes.size() > 0) {
206 Bitmap poster = BitmapFactory.decodeByteArray(
207 mPosterBytes.toByteArray(), 0, mPosterBytes.size());
208 if (poster != null) {
209 mProxy.doSetPoster(poster);
210 }
211 }
212 cleanup();
213 } else if (mStatusCode >= 300 && mStatusCode < 400) {
214 // We have a redirect.
215 mUrl = mHeaders.getLocation();
216 if (mUrl != null) {
217 mHandler.post(new Runnable() {
218 public void run() {
219 if (mRequestHandle != null) {
220 mRequestHandle.setupRedirect(mUrl, mStatusCode,
221 new HashMap<String, String>());
222 }
223 }
224 });
225 }
226 }
227 }
228
229 public void certificate(SslCertificate certificate) {
230 // Don't care.
231 }
232
233 public void error(int id, String description) {
234 cleanup();
235 }
236
237 public boolean handleSslErrorRequest(SslError error) {
238 // Don't care. If this happens, data() will never be called so
239 // mPosterBytes will never be created, so no need to call cleanup.
240 return false;
241 }
242 // Tears down the poster bytes stream. Called on network thread.
243 private void cleanup() {
244 if (mPosterBytes != null) {
245 try {
246 mPosterBytes.close();
247 } catch (IOException ignored) {
248 // Ignored.
249 } finally {
250 mPosterBytes = null;
251 }
252 }
253 }
254
255 // Queue management methods. Called on WebCore thread.
256 private void retainQueue() {
257 if (mRequestQueue == null) {
258 mRequestQueue = new RequestQueue(mProxy.getContext());
259 }
260 mQueueRefCount++;
261 }
262
263 private void releaseQueue() {
264 if (mQueueRefCount == 0) {
265 return;
266 }
267 if (--mQueueRefCount == 0) {
268 mRequestQueue.shutdown();
269 mRequestQueue = null;
270 }
Andrei Popescu3c946a1a2009-07-03 08:20:53 +0100271 }
272 }
Andrei Popescu6fa29582009-06-19 14:54:09 +0100273
274 /**
275 * Private constructor.
276 * @param context is the application context.
277 */
Andrei Popescu3c946a1a2009-07-03 08:20:53 +0100278 private HTML5VideoViewProxy(WebView webView) {
Andrei Popescu6fa29582009-06-19 14:54:09 +0100279 // This handler is for the main (UI) thread.
280 super(Looper.getMainLooper());
Andrei Popescu3c946a1a2009-07-03 08:20:53 +0100281 // Save the WebView object.
282 mWebView = webView;
Andrei Popescu64b86a12009-09-15 20:34:18 +0100283 // create the message handler for this thread
284 createWebCoreHandler();
Andrei Popescu6fa29582009-06-19 14:54:09 +0100285 }
286
Andrei Popescu64b86a12009-09-15 20:34:18 +0100287 private void createWebCoreHandler() {
288 mWebCoreHandler = new Handler() {
289 @Override
290 public void handleMessage(Message msg) {
291 switch (msg.what) {
292 // TODO here we will process the messages from the VideoPlayer
293 // and will call native WebKit methods.
Andrei Popescu3c946a1a2009-07-03 08:20:53 +0100294 }
Andrei Popescu64b86a12009-09-15 20:34:18 +0100295 }
296 };
Andrei Popescu6fa29582009-06-19 14:54:09 +0100297 }
298
Andrei Popescu64b86a12009-09-15 20:34:18 +0100299 private void doSetPoster(Bitmap poster) {
300 if (poster == null) {
301 return;
302 }
303 // Send the bitmap over to the UI thread.
304 Message message = obtainMessage(SET_POSTER);
305 message.obj = poster;
306 sendMessage(message);
307 }
308
309 public Context getContext() {
310 return mWebView.getContext();
311 }
312
313 // The public methods below are all called from WebKit only.
Andrei Popescu6fa29582009-06-19 14:54:09 +0100314 /**
315 * Play a video stream.
316 * @param url is the URL of the video stream.
317 * @param webview is the WebViewCore that is requesting the playback.
318 */
Andrei Popescu3c946a1a2009-07-03 08:20:53 +0100319 public void play(String url) {
Andrei Popescu64b86a12009-09-15 20:34:18 +0100320 if (url == null) {
321 return;
322 }
Andrei Popescu3c946a1a2009-07-03 08:20:53 +0100323 // We need to know the webview that is requesting the playback.
Andrei Popescu6fa29582009-06-19 14:54:09 +0100324 Message message = obtainMessage(PLAY);
Andrei Popescu64b86a12009-09-15 20:34:18 +0100325 message.obj = url;
Andrei Popescu6fa29582009-06-19 14:54:09 +0100326 sendMessage(message);
327 }
328
Andrei Popescu64b86a12009-09-15 20:34:18 +0100329 /**
330 * Create the child view that will cary the poster.
331 */
Andrei Popescu3c946a1a2009-07-03 08:20:53 +0100332 public void createView() {
333 mChildView = mWebView.mViewManager.createView();
334 sendMessage(obtainMessage(INIT));
335 }
336
Andrei Popescu64b86a12009-09-15 20:34:18 +0100337 /**
338 * Attach the poster view.
339 * @param x, y are the screen coordinates where the poster should be hung.
340 * @param width, height denote the size of the poster.
341 */
Andrei Popescu3c946a1a2009-07-03 08:20:53 +0100342 public void attachView(int x, int y, int width, int height) {
343 if (mChildView == null) {
344 return;
Patrick Scott0a5ce012009-07-02 08:56:10 -0400345 }
Andrei Popescu3c946a1a2009-07-03 08:20:53 +0100346 mChildView.attachView(x, y, width, height);
Patrick Scott0a5ce012009-07-02 08:56:10 -0400347 }
348
Andrei Popescu64b86a12009-09-15 20:34:18 +0100349 /**
350 * Remove the child view and, thus, the poster.
351 */
Andrei Popescu3c946a1a2009-07-03 08:20:53 +0100352 public void removeView() {
353 if (mChildView == null) {
354 return;
355 }
356 mChildView.removeView();
Andrei Popescu64b86a12009-09-15 20:34:18 +0100357 // This is called by the C++ MediaPlayerPrivate dtor.
358 // Cancel any active poster download.
359 if (mPosterDownloader != null) {
360 mPosterDownloader.cancelAndReleaseQueue();
361 }
362 }
363
364 /**
365 * Load the poster image.
366 * @param url is the URL of the poster image.
367 */
368 public void loadPoster(String url) {
369 if (url == null) {
370 return;
371 }
372 // Cancel any active poster download.
373 if (mPosterDownloader != null) {
374 mPosterDownloader.cancelAndReleaseQueue();
375 }
376 // Load the poster asynchronously
377 mPosterDownloader = new PosterDownloader(url, this);
378 mPosterDownloader.start();
Patrick Scott0a5ce012009-07-02 08:56:10 -0400379 }
380
Andrei Popescu6fa29582009-06-19 14:54:09 +0100381 /**
Andrei Popescu3c946a1a2009-07-03 08:20:53 +0100382 * The factory for HTML5VideoViewProxy instances.
Andrei Popescu6fa29582009-06-19 14:54:09 +0100383 * @param webViewCore is the WebViewCore that is requesting the proxy.
384 *
Andrei Popescu3c946a1a2009-07-03 08:20:53 +0100385 * @return a new HTML5VideoViewProxy object.
Andrei Popescu6fa29582009-06-19 14:54:09 +0100386 */
387 public static HTML5VideoViewProxy getInstance(WebViewCore webViewCore) {
Andrei Popescu3c946a1a2009-07-03 08:20:53 +0100388 return new HTML5VideoViewProxy(webViewCore.getWebView());
Andrei Popescu6fa29582009-06-19 14:54:09 +0100389 }
390}