blob: d306c861e28f01b499365e62a608c47f26e51085 [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;
Teng-Hui Zhu661e8b12011-03-02 15:09:34 -080022import android.graphics.SurfaceTexture;
Andrei Popescu3c946a1a2009-07-03 08:20:53 +010023import android.media.MediaPlayer;
Andrei Popescu64b86a12009-09-15 20:34:18 +010024import android.net.http.EventHandler;
25import android.net.http.Headers;
26import android.net.http.RequestHandle;
27import android.net.http.RequestQueue;
28import android.net.http.SslCertificate;
29import android.net.http.SslError;
Andrei Popescu6fa29582009-06-19 14:54:09 +010030import android.os.Handler;
31import android.os.Looper;
32import android.os.Message;
33import android.util.Log;
Andrei Popescu6fa29582009-06-19 14:54:09 +010034
Andrei Popescu64b86a12009-09-15 20:34:18 +010035import java.io.ByteArrayOutputStream;
36import java.io.IOException;
Ben Murdoch42509792011-02-18 12:34:35 +000037import java.net.MalformedURLException;
38import java.net.URL;
Andrei Popescu6fa29582009-06-19 14:54:09 +010039import java.util.HashMap;
Andrei Popescu290c34a2009-09-17 15:55:24 +010040import java.util.Map;
Andrei Popescu6fa29582009-06-19 14:54:09 +010041
42/**
Andrei Popescu3c946a1a2009-07-03 08:20:53 +010043 * <p>Proxy for HTML5 video views.
Andrei Popescu6fa29582009-06-19 14:54:09 +010044 */
Andrei Popescu290c34a2009-09-17 15:55:24 +010045class HTML5VideoViewProxy extends Handler
46 implements MediaPlayer.OnPreparedListener,
Andrei Popescu50c86232009-09-30 16:51:25 +010047 MediaPlayer.OnCompletionListener,
Teng-Hui Zhu661e8b12011-03-02 15:09:34 -080048 MediaPlayer.OnErrorListener,
Teng-Hui Zhuc0fccd12011-03-29 10:35:11 -070049 MediaPlayer.OnInfoListener,
Teng-Hui Zhu661e8b12011-03-02 15:09:34 -080050 SurfaceTexture.OnFrameAvailableListener {
Andrei Popescu6fa29582009-06-19 14:54:09 +010051 // Logging tag.
52 private static final String LOGTAG = "HTML5VideoViewProxy";
53
Andrei Popescu3c946a1a2009-07-03 08:20:53 +010054 // Message Ids for WebCore thread -> UI thread communication.
Andrei Popescu50c86232009-09-30 16:51:25 +010055 private static final int PLAY = 100;
56 private static final int SEEK = 101;
57 private static final int PAUSE = 102;
58 private static final int ERROR = 103;
59 private static final int LOAD_DEFAULT_POSTER = 104;
Teng-Hui Zhuc0fccd12011-03-29 10:35:11 -070060 private static final int BUFFERING_START = 105;
61 private static final int BUFFERING_END = 106;
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
Andrei Popescu290c34a2009-09-17 15:55:24 +010064 private static final int PREPARED = 200;
65 private static final int ENDED = 201;
Andrei Popescu50c86232009-09-30 16:51:25 +010066 private static final int POSTER_FETCHED = 202;
Shimeng (Simon) Wang43aaa2d2010-10-18 16:25:59 -070067 private static final int PAUSED = 203;
Teng-Hui Zhub109c882011-05-04 16:19:49 -070068 private static final int STOPFULLSCREEN = 204;
Teng-Hui Zhue4c89e32012-01-10 16:00:21 -080069 private static final int RESTORESTATE = 205;
Andrei Popescu64b86a12009-09-15 20:34:18 +010070
Andrei Popescu048eb3b2010-01-11 21:12:54 +000071 // Timer thread -> UI thread
72 private static final int TIMEUPDATE = 300;
73
Andrei Popescu290c34a2009-09-17 15:55:24 +010074 // The C++ MediaPlayerPrivateAndroid object.
75 int mNativePointer;
Andrei Popescu64b86a12009-09-15 20:34:18 +010076 // The handler for WebCore thread messages;
77 private Handler mWebCoreHandler;
Andrei Popescu3c946a1a2009-07-03 08:20:53 +010078 // The WebView instance that created this view.
79 private WebView mWebView;
Andrei Popescu64b86a12009-09-15 20:34:18 +010080 // The poster image to be shown when the video is not playing.
Andrei Popescu50c86232009-09-30 16:51:25 +010081 // This ref prevents the bitmap from being GC'ed.
82 private Bitmap mPoster;
Andrei Popescu64b86a12009-09-15 20:34:18 +010083 // The poster downloader.
84 private PosterDownloader mPosterDownloader;
Andrei Popescu290c34a2009-09-17 15:55:24 +010085 // The seek position.
86 private int mSeekPosition;
Andrei Popescu64b86a12009-09-15 20:34:18 +010087 // A helper class to control the playback. This executes on the UI thread!
88 private static final class VideoPlayer {
89 // The proxy that is currently playing (if any).
90 private static HTML5VideoViewProxy mCurrentProxy;
91 // The VideoView instance. This is a singleton for now, at least until
92 // http://b/issue?id=1973663 is fixed.
Teng-Hui Zhu661e8b12011-03-02 15:09:34 -080093 private static HTML5VideoView mHTML5VideoView;
Andrei Popescu048eb3b2010-01-11 21:12:54 +000094
Teng-Hui Zhu661e8b12011-03-02 15:09:34 -080095 private static boolean isVideoSelfEnded = false;
96 // By using the baseLayer and the current video Layer ID, we can
97 // identify the exact layer on the UI thread to use the SurfaceTexture.
98 private static int mBaseLayer = 0;
Andrei Popescu3c946a1a2009-07-03 08:20:53 +010099
Teng-Hui Zhuf4d4e9e2011-03-30 14:39:56 -0700100 private static void setPlayerBuffering(boolean playerBuffering) {
101 mHTML5VideoView.setPlayerBuffering(playerBuffering);
102 }
103
Teng-Hui Zhu661e8b12011-03-02 15:09:34 -0800104 // Every time webView setBaseLayer, this will be called.
105 // When we found the Video layer, then we set the Surface Texture to it.
106 // Otherwise, we may want to delete the Surface Texture to save memory.
107 public static void setBaseLayer(int layer) {
Teng-Hui Zhu10ab6542011-03-16 16:42:32 -0700108 // Don't do this for full screen mode.
109 if (mHTML5VideoView != null
Teng-Hui Zhu3fafd392011-05-31 15:15:31 -0700110 && !mHTML5VideoView.isFullScreenMode()
111 && !mHTML5VideoView.surfaceTextureDeleted()) {
Teng-Hui Zhu661e8b12011-03-02 15:09:34 -0800112 mBaseLayer = layer;
Teng-Hui Zhu661e8b12011-03-02 15:09:34 -0800113
114 int currentVideoLayerId = mHTML5VideoView.getVideoLayerId();
Teng-Hui Zhu3fafd392011-05-31 15:15:31 -0700115 SurfaceTexture surfTexture = mHTML5VideoView.getSurfaceTexture(currentVideoLayerId);
116 int textureName = mHTML5VideoView.getTextureName();
117
Teng-Hui Zhu661e8b12011-03-02 15:09:34 -0800118 if (layer != 0 && surfTexture != null && currentVideoLayerId != -1) {
Teng-Hui Zhu265db322011-03-18 14:56:10 -0700119 int playerState = mHTML5VideoView.getCurrentState();
Teng-Hui Zhuf4d4e9e2011-03-30 14:39:56 -0700120 if (mHTML5VideoView.getPlayerBuffering())
Teng-Hui Zhuc0fccd12011-03-29 10:35:11 -0700121 playerState = HTML5VideoView.STATE_NOTPREPARED;
Teng-Hui Zhu661e8b12011-03-02 15:09:34 -0800122 boolean foundInTree = nativeSendSurfaceTexture(surfTexture,
123 layer, currentVideoLayerId, textureName,
Teng-Hui Zhu265db322011-03-18 14:56:10 -0700124 playerState);
Teng-Hui Zhu1e26d822011-03-23 16:54:20 -0700125 if (playerState >= HTML5VideoView.STATE_PREPARED
Teng-Hui Zhu265db322011-03-18 14:56:10 -0700126 && !foundInTree) {
Teng-Hui Zhu661e8b12011-03-02 15:09:34 -0800127 mHTML5VideoView.pauseAndDispatch(mCurrentProxy);
128 mHTML5VideoView.deleteSurfaceTexture();
Andrei Popescu31d2aa12010-04-08 15:19:22 +0100129 }
Andrei Popescu64b86a12009-09-15 20:34:18 +0100130 }
Andrei Popescua41f97b2010-01-11 18:36:25 +0000131 }
Teng-Hui Zhu661e8b12011-03-02 15:09:34 -0800132 }
Andrei Popescua41f97b2010-01-11 18:36:25 +0000133
Teng-Hui Zhu661e8b12011-03-02 15:09:34 -0800134 // When a WebView is paused, we also want to pause the video in it.
135 public static void pauseAndDispatch() {
136 if (mHTML5VideoView != null) {
137 mHTML5VideoView.pauseAndDispatch(mCurrentProxy);
138 // When switching out, clean the video content on the old page
139 // by telling the layer not readyToUseSurfTex.
140 setBaseLayer(mBaseLayer);
141 }
142 }
143
Teng-Hui Zhu10ab6542011-03-16 16:42:32 -0700144 public static void enterFullScreenVideo(int layerId, String url,
145 HTML5VideoViewProxy proxy, WebView webView) {
146 // Save the inline video info and inherit it in the full screen
147 int savePosition = 0;
Teng-Hui Zhu10ab6542011-03-16 16:42:32 -0700148 if (mHTML5VideoView != null) {
149 // If we are playing the same video, then it is better to
150 // save the current position.
151 if (layerId == mHTML5VideoView.getVideoLayerId()) {
152 savePosition = mHTML5VideoView.getCurrentPosition();
Teng-Hui Zhu10ab6542011-03-16 16:42:32 -0700153 }
Teng-Hui Zhu10ab6542011-03-16 16:42:32 -0700154 mHTML5VideoView.release();
155 }
156 mHTML5VideoView = new HTML5VideoFullScreen(proxy.getContext(),
Teng-Hui Zhue4c89e32012-01-10 16:00:21 -0800157 layerId, savePosition);
Teng-Hui Zhu10ab6542011-03-16 16:42:32 -0700158 mCurrentProxy = proxy;
159
160 mHTML5VideoView.setVideoURI(url, mCurrentProxy);
161
162 mHTML5VideoView.enterFullScreenVideoState(layerId, proxy, webView);
163 }
164
Teng-Hui Zhud7678a12012-01-16 15:33:09 -0800165 public static void exitFullScreenVideo(HTML5VideoViewProxy proxy,
166 WebView webView) {
167 if (!mHTML5VideoView.fullScreenExited() && mHTML5VideoView.isFullScreenMode()) {
168 WebChromeClient client = webView.getWebChromeClient();
169 if (client != null) {
170 client.onHideCustomView();
171 }
172 }
173 }
174
Teng-Hui Zhu661e8b12011-03-02 15:09:34 -0800175 // This is on the UI thread.
176 // When native tell Java to play, we need to check whether or not it is
177 // still the same video by using videoLayerId and treat it differently.
178 public static void play(String url, int time, HTML5VideoViewProxy proxy,
179 WebChromeClient client, int videoLayerId) {
180 int currentVideoLayerId = -1;
Teng-Hui Zhu10ab6542011-03-16 16:42:32 -0700181 boolean backFromFullScreenMode = false;
Teng-Hui Zhu10ab6542011-03-16 16:42:32 -0700182 if (mHTML5VideoView != null) {
183 currentVideoLayerId = mHTML5VideoView.getVideoLayerId();
Teng-Hui Zhu1a88acb2011-06-01 13:28:41 -0700184 backFromFullScreenMode = mHTML5VideoView.fullScreenExited();
Teng-Hui Zhu96fae5e2012-01-18 15:25:51 -0800185
186 // When playing video back to back in full screen mode,
187 // javascript will switch the src and call play.
188 // In this case, we can just reuse the same full screen view,
189 // and play the video after prepared.
190 if (mHTML5VideoView.isFullScreenMode()
191 && !backFromFullScreenMode
192 && currentVideoLayerId != videoLayerId
193 && mCurrentProxy != proxy) {
194 mCurrentProxy = proxy;
195 mHTML5VideoView.setStartWhenPrepared(true);
196 mHTML5VideoView.setVideoURI(url, proxy);
197 mHTML5VideoView.reprepareData(proxy);
198 return;
199 }
Teng-Hui Zhu10ab6542011-03-16 16:42:32 -0700200 }
201
202 if (backFromFullScreenMode
Teng-Hui Zhu3fafd392011-05-31 15:15:31 -0700203 || currentVideoLayerId != videoLayerId
204 || mHTML5VideoView.surfaceTextureDeleted()) {
Teng-Hui Zhu661e8b12011-03-02 15:09:34 -0800205 // Here, we handle the case when switching to a new video,
206 // either inside a WebView or across WebViews
207 // For switching videos within a WebView or across the WebView,
208 // we need to pause the old one and re-create a new media player
209 // inside the HTML5VideoView.
210 if (mHTML5VideoView != null) {
Teng-Hui Zhu22954d42011-04-07 17:13:18 -0700211 if (!backFromFullScreenMode) {
212 mHTML5VideoView.pauseAndDispatch(mCurrentProxy);
213 }
Teng-Hui Zhu661e8b12011-03-02 15:09:34 -0800214 // release the media player to avoid finalize error
215 mHTML5VideoView.release();
216 }
Teng-Hui Zhu661e8b12011-03-02 15:09:34 -0800217 mCurrentProxy = proxy;
Teng-Hui Zhue4c89e32012-01-10 16:00:21 -0800218 mHTML5VideoView = new HTML5VideoInline(videoLayerId, time);
Teng-Hui Zhu661e8b12011-03-02 15:09:34 -0800219
Teng-Hui Zhu10ab6542011-03-16 16:42:32 -0700220 mHTML5VideoView.setVideoURI(url, mCurrentProxy);
221 mHTML5VideoView.prepareDataAndDisplayMode(proxy);
Teng-Hui Zhu661e8b12011-03-02 15:09:34 -0800222 } else if (mCurrentProxy == proxy) {
223 // Here, we handle the case when we keep playing with one video
224 if (!mHTML5VideoView.isPlaying()) {
225 mHTML5VideoView.seekTo(time);
226 mHTML5VideoView.start();
227 }
228 } else if (mCurrentProxy != null) {
Teng-Hui Zhu10ab6542011-03-16 16:42:32 -0700229 // Some other video is already playing. Notify the caller that
230 // its playback ended.
Andrei Popescuc4fbceb2010-04-14 13:30:23 +0100231 proxy.dispatchOnEnded();
Andrei Popescu3c946a1a2009-07-03 08:20:53 +0100232 }
Andrei Popescu3c946a1a2009-07-03 08:20:53 +0100233 }
Andrei Popescu290c34a2009-09-17 15:55:24 +0100234
Andrei Popescu048eb3b2010-01-11 21:12:54 +0000235 public static boolean isPlaying(HTML5VideoViewProxy proxy) {
Teng-Hui Zhu661e8b12011-03-02 15:09:34 -0800236 return (mCurrentProxy == proxy && mHTML5VideoView != null
237 && mHTML5VideoView.isPlaying());
Andrei Popescu048eb3b2010-01-11 21:12:54 +0000238 }
239
240 public static int getCurrentPosition() {
241 int currentPosMs = 0;
Teng-Hui Zhu661e8b12011-03-02 15:09:34 -0800242 if (mHTML5VideoView != null) {
243 currentPosMs = mHTML5VideoView.getCurrentPosition();
Andrei Popescu048eb3b2010-01-11 21:12:54 +0000244 }
245 return currentPosMs;
246 }
247
Andrei Popescu290c34a2009-09-17 15:55:24 +0100248 public static void seek(int time, HTML5VideoViewProxy proxy) {
Teng-Hui Zhu661e8b12011-03-02 15:09:34 -0800249 if (mCurrentProxy == proxy && time >= 0 && mHTML5VideoView != null) {
250 mHTML5VideoView.seekTo(time);
Andrei Popescu290c34a2009-09-17 15:55:24 +0100251 }
252 }
253
254 public static void pause(HTML5VideoViewProxy proxy) {
Teng-Hui Zhu661e8b12011-03-02 15:09:34 -0800255 if (mCurrentProxy == proxy && mHTML5VideoView != null) {
256 mHTML5VideoView.pause();
Andrei Popescu290c34a2009-09-17 15:55:24 +0100257 }
258 }
Andrei Popescubf385d72009-09-18 18:59:52 +0100259
260 public static void onPrepared() {
Teng-Hui Zhue4c89e32012-01-10 16:00:21 -0800261 if (!mHTML5VideoView.isFullScreenMode()) {
Teng-Hui Zhu05049672011-04-06 18:09:27 -0700262 mHTML5VideoView.start();
263 }
Teng-Hui Zhu265db322011-03-18 14:56:10 -0700264 if (mBaseLayer != 0) {
265 setBaseLayer(mBaseLayer);
266 }
Teng-Hui Zhu661e8b12011-03-02 15:09:34 -0800267 }
268
269 public static void end() {
270 if (mCurrentProxy != null) {
271 if (isVideoSelfEnded)
272 mCurrentProxy.dispatchOnEnded();
273 else
274 mCurrentProxy.dispatchOnPaused();
275 }
276 isVideoSelfEnded = false;
Andrei Popescubf385d72009-09-18 18:59:52 +0100277 }
Andrei Popescu290c34a2009-09-17 15:55:24 +0100278 }
279
280 // A bunch event listeners for our VideoView
281 // MediaPlayer.OnPreparedListener
282 public void onPrepared(MediaPlayer mp) {
Andrei Popescubf385d72009-09-18 18:59:52 +0100283 VideoPlayer.onPrepared();
Andrei Popescu290c34a2009-09-17 15:55:24 +0100284 Message msg = Message.obtain(mWebCoreHandler, PREPARED);
285 Map<String, Object> map = new HashMap<String, Object>();
286 map.put("dur", new Integer(mp.getDuration()));
287 map.put("width", new Integer(mp.getVideoWidth()));
288 map.put("height", new Integer(mp.getVideoHeight()));
289 msg.obj = map;
290 mWebCoreHandler.sendMessage(msg);
291 }
292
293 // MediaPlayer.OnCompletionListener;
294 public void onCompletion(MediaPlayer mp) {
Andrei Popescuc4fbceb2010-04-14 13:30:23 +0100295 // The video ended by itself, so we need to
296 // send a message to the UI thread to dismiss
297 // the video view and to return to the WebView.
Shimeng (Simon) Wang43aaa2d2010-10-18 16:25:59 -0700298 // arg1 == 1 means the video ends by itself.
299 sendMessage(obtainMessage(ENDED, 1, 0));
Andrei Popescu290c34a2009-09-17 15:55:24 +0100300 }
301
Andrei Popescu50c86232009-09-30 16:51:25 +0100302 // MediaPlayer.OnErrorListener
303 public boolean onError(MediaPlayer mp, int what, int extra) {
304 sendMessage(obtainMessage(ERROR));
305 return false;
306 }
307
Andrei Popescuc4fbceb2010-04-14 13:30:23 +0100308 public void dispatchOnEnded() {
Andrei Popescu290c34a2009-09-17 15:55:24 +0100309 Message msg = Message.obtain(mWebCoreHandler, ENDED);
310 mWebCoreHandler.sendMessage(msg);
Andrei Popescu64b86a12009-09-15 20:34:18 +0100311 }
Andrei Popescu3c946a1a2009-07-03 08:20:53 +0100312
Shimeng (Simon) Wang43aaa2d2010-10-18 16:25:59 -0700313 public void dispatchOnPaused() {
Teng-Hui Zhub109c882011-05-04 16:19:49 -0700314 Message msg = Message.obtain(mWebCoreHandler, PAUSED);
315 mWebCoreHandler.sendMessage(msg);
316 }
317
318 public void dispatchOnStopFullScreen() {
319 Message msg = Message.obtain(mWebCoreHandler, STOPFULLSCREEN);
320 mWebCoreHandler.sendMessage(msg);
Shimeng (Simon) Wang43aaa2d2010-10-18 16:25:59 -0700321 }
322
Teng-Hui Zhue4c89e32012-01-10 16:00:21 -0800323 public void dispatchOnRestoreState() {
324 Message msg = Message.obtain(mWebCoreHandler, RESTORESTATE);
325 mWebCoreHandler.sendMessage(msg);
326 }
327
Andrei Popescu048eb3b2010-01-11 21:12:54 +0000328 public void onTimeupdate() {
329 sendMessage(obtainMessage(TIMEUPDATE));
330 }
331
Teng-Hui Zhu661e8b12011-03-02 15:09:34 -0800332 // When there is a frame ready from surface texture, we should tell WebView
333 // to refresh.
334 @Override
335 public void onFrameAvailable(SurfaceTexture surfaceTexture) {
336 // TODO: This should support partial invalidation too.
337 mWebView.invalidate();
338 }
339
Andrei Popescu048eb3b2010-01-11 21:12:54 +0000340 // Handler for the messages from WebCore or Timer thread to the UI thread.
Andrei Popescu64b86a12009-09-15 20:34:18 +0100341 @Override
342 public void handleMessage(Message msg) {
343 // This executes on the UI thread.
344 switch (msg.what) {
Andrei Popescu64b86a12009-09-15 20:34:18 +0100345 case PLAY: {
346 String url = (String) msg.obj;
347 WebChromeClient client = mWebView.getWebChromeClient();
Teng-Hui Zhu661e8b12011-03-02 15:09:34 -0800348 int videoLayerID = msg.arg1;
Andrei Popescu64b86a12009-09-15 20:34:18 +0100349 if (client != null) {
Teng-Hui Zhu661e8b12011-03-02 15:09:34 -0800350 VideoPlayer.play(url, mSeekPosition, this, client, videoLayerID);
Andrei Popescu64b86a12009-09-15 20:34:18 +0100351 }
352 break;
353 }
Andrei Popescu290c34a2009-09-17 15:55:24 +0100354 case SEEK: {
355 Integer time = (Integer) msg.obj;
356 mSeekPosition = time;
357 VideoPlayer.seek(mSeekPosition, this);
358 break;
359 }
360 case PAUSE: {
361 VideoPlayer.pause(this);
362 break;
363 }
Andrei Popescu46a83b42009-10-23 13:49:46 +0100364 case ENDED:
Shimeng (Simon) Wang43aaa2d2010-10-18 16:25:59 -0700365 if (msg.arg1 == 1)
366 VideoPlayer.isVideoSelfEnded = true;
Teng-Hui Zhu661e8b12011-03-02 15:09:34 -0800367 VideoPlayer.end();
368 break;
Andrei Popescu50c86232009-09-30 16:51:25 +0100369 case ERROR: {
370 WebChromeClient client = mWebView.getWebChromeClient();
371 if (client != null) {
372 client.onHideCustomView();
373 }
374 break;
375 }
376 case LOAD_DEFAULT_POSTER: {
377 WebChromeClient client = mWebView.getWebChromeClient();
378 if (client != null) {
379 doSetPoster(client.getDefaultVideoPoster());
380 }
381 break;
382 }
Andrei Popescu048eb3b2010-01-11 21:12:54 +0000383 case TIMEUPDATE: {
384 if (VideoPlayer.isPlaying(this)) {
385 sendTimeupdate();
386 }
387 break;
388 }
Teng-Hui Zhuc0fccd12011-03-29 10:35:11 -0700389 case BUFFERING_START: {
Teng-Hui Zhuf4d4e9e2011-03-30 14:39:56 -0700390 VideoPlayer.setPlayerBuffering(true);
Teng-Hui Zhuc0fccd12011-03-29 10:35:11 -0700391 break;
392 }
393 case BUFFERING_END: {
Teng-Hui Zhuf4d4e9e2011-03-30 14:39:56 -0700394 VideoPlayer.setPlayerBuffering(false);
Teng-Hui Zhuc0fccd12011-03-29 10:35:11 -0700395 break;
396 }
Andrei Popescu64b86a12009-09-15 20:34:18 +0100397 }
398 }
399
Andrei Popescu64b86a12009-09-15 20:34:18 +0100400 // Everything below this comment executes on the WebCore thread, except for
401 // the EventHandler methods, which are called on the network thread.
402
403 // A helper class that knows how to download posters
404 private static final class PosterDownloader implements EventHandler {
405 // The request queue. This is static as we have one queue for all posters.
406 private static RequestQueue mRequestQueue;
407 private static int mQueueRefCount = 0;
408 // The poster URL
Ben Murdoch42509792011-02-18 12:34:35 +0000409 private URL mUrl;
Andrei Popescu64b86a12009-09-15 20:34:18 +0100410 // The proxy we're doing this for.
411 private final HTML5VideoViewProxy mProxy;
412 // The poster bytes. We only touch this on the network thread.
413 private ByteArrayOutputStream mPosterBytes;
414 // The request handle. We only touch this on the WebCore thread.
415 private RequestHandle mRequestHandle;
416 // The response status code.
417 private int mStatusCode;
418 // The response headers.
419 private Headers mHeaders;
420 // The handler to handle messages on the WebCore thread.
421 private Handler mHandler;
422
423 public PosterDownloader(String url, HTML5VideoViewProxy proxy) {
Ben Murdoch42509792011-02-18 12:34:35 +0000424 try {
425 mUrl = new URL(url);
426 } catch (MalformedURLException e) {
427 mUrl = null;
428 }
Andrei Popescu64b86a12009-09-15 20:34:18 +0100429 mProxy = proxy;
430 mHandler = new Handler();
431 }
432 // Start the download. Called on WebCore thread.
433 public void start() {
434 retainQueue();
Ben Murdoch42509792011-02-18 12:34:35 +0000435
436 if (mUrl == null) {
437 return;
438 }
439
440 // Only support downloading posters over http/https.
441 // FIXME: Add support for other schemes. WebKit seems able to load
442 // posters over other schemes e.g. file://, but gets the dimensions wrong.
443 String protocol = mUrl.getProtocol();
444 if ("http".equals(protocol) || "https".equals(protocol)) {
445 mRequestHandle = mRequestQueue.queueRequest(mUrl.toString(), "GET", null,
446 this, null, 0);
447 }
Andrei Popescu64b86a12009-09-15 20:34:18 +0100448 }
449 // Cancel the download if active and release the queue. Called on WebCore thread.
450 public void cancelAndReleaseQueue() {
451 if (mRequestHandle != null) {
452 mRequestHandle.cancel();
453 mRequestHandle = null;
454 }
455 releaseQueue();
456 }
457 // EventHandler methods. Executed on the network thread.
458 public void status(int major_version,
459 int minor_version,
460 int code,
461 String reason_phrase) {
462 mStatusCode = code;
Andrei Popescu3c946a1a2009-07-03 08:20:53 +0100463 }
464
Andrei Popescu64b86a12009-09-15 20:34:18 +0100465 public void headers(Headers headers) {
466 mHeaders = headers;
467 }
468
469 public void data(byte[] data, int len) {
470 if (mPosterBytes == null) {
471 mPosterBytes = new ByteArrayOutputStream();
472 }
473 mPosterBytes.write(data, 0, len);
474 }
475
476 public void endData() {
477 if (mStatusCode == 200) {
478 if (mPosterBytes.size() > 0) {
479 Bitmap poster = BitmapFactory.decodeByteArray(
480 mPosterBytes.toByteArray(), 0, mPosterBytes.size());
Andrei Popescu50c86232009-09-30 16:51:25 +0100481 mProxy.doSetPoster(poster);
Andrei Popescu64b86a12009-09-15 20:34:18 +0100482 }
483 cleanup();
484 } else if (mStatusCode >= 300 && mStatusCode < 400) {
485 // We have a redirect.
Ben Murdoch42509792011-02-18 12:34:35 +0000486 try {
487 mUrl = new URL(mHeaders.getLocation());
488 } catch (MalformedURLException e) {
489 mUrl = null;
490 }
Andrei Popescu64b86a12009-09-15 20:34:18 +0100491 if (mUrl != null) {
492 mHandler.post(new Runnable() {
493 public void run() {
494 if (mRequestHandle != null) {
Ben Murdoch42509792011-02-18 12:34:35 +0000495 mRequestHandle.setupRedirect(mUrl.toString(), mStatusCode,
Andrei Popescu64b86a12009-09-15 20:34:18 +0100496 new HashMap<String, String>());
497 }
498 }
499 });
500 }
501 }
502 }
503
504 public void certificate(SslCertificate certificate) {
505 // Don't care.
506 }
507
508 public void error(int id, String description) {
509 cleanup();
510 }
511
512 public boolean handleSslErrorRequest(SslError error) {
513 // Don't care. If this happens, data() will never be called so
514 // mPosterBytes will never be created, so no need to call cleanup.
515 return false;
516 }
517 // Tears down the poster bytes stream. Called on network thread.
518 private void cleanup() {
519 if (mPosterBytes != null) {
520 try {
521 mPosterBytes.close();
522 } catch (IOException ignored) {
523 // Ignored.
524 } finally {
525 mPosterBytes = null;
526 }
527 }
528 }
529
530 // Queue management methods. Called on WebCore thread.
531 private void retainQueue() {
532 if (mRequestQueue == null) {
533 mRequestQueue = new RequestQueue(mProxy.getContext());
534 }
535 mQueueRefCount++;
536 }
537
538 private void releaseQueue() {
539 if (mQueueRefCount == 0) {
540 return;
541 }
542 if (--mQueueRefCount == 0) {
543 mRequestQueue.shutdown();
544 mRequestQueue = null;
545 }
Andrei Popescu3c946a1a2009-07-03 08:20:53 +0100546 }
547 }
Andrei Popescu6fa29582009-06-19 14:54:09 +0100548
549 /**
550 * Private constructor.
Andrei Popescu290c34a2009-09-17 15:55:24 +0100551 * @param webView is the WebView that hosts the video.
552 * @param nativePtr is the C++ pointer to the MediaPlayerPrivate object.
Andrei Popescu6fa29582009-06-19 14:54:09 +0100553 */
Andrei Popescu290c34a2009-09-17 15:55:24 +0100554 private HTML5VideoViewProxy(WebView webView, int nativePtr) {
Andrei Popescu6fa29582009-06-19 14:54:09 +0100555 // This handler is for the main (UI) thread.
556 super(Looper.getMainLooper());
Andrei Popescu3c946a1a2009-07-03 08:20:53 +0100557 // Save the WebView object.
558 mWebView = webView;
Teng-Hui Zhu661e8b12011-03-02 15:09:34 -0800559 // Pass Proxy into webview, such that every time we have a setBaseLayer
560 // call, we tell this Proxy to call the native to update the layer tree
561 // for the Video Layer's surface texture info
562 mWebView.setHTML5VideoViewProxy(this);
Andrei Popescu290c34a2009-09-17 15:55:24 +0100563 // Save the native ptr
564 mNativePointer = nativePtr;
Andrei Popescu64b86a12009-09-15 20:34:18 +0100565 // create the message handler for this thread
566 createWebCoreHandler();
Andrei Popescu6fa29582009-06-19 14:54:09 +0100567 }
568
Andrei Popescu64b86a12009-09-15 20:34:18 +0100569 private void createWebCoreHandler() {
570 mWebCoreHandler = new Handler() {
571 @Override
572 public void handleMessage(Message msg) {
573 switch (msg.what) {
Andrei Popescu290c34a2009-09-17 15:55:24 +0100574 case PREPARED: {
575 Map<String, Object> map = (Map<String, Object>) msg.obj;
576 Integer duration = (Integer) map.get("dur");
577 Integer width = (Integer) map.get("width");
578 Integer height = (Integer) map.get("height");
579 nativeOnPrepared(duration.intValue(), width.intValue(),
580 height.intValue(), mNativePointer);
581 break;
582 }
583 case ENDED:
Ben Murdochff19d192011-01-17 18:08:56 +0000584 mSeekPosition = 0;
Andrei Popescu290c34a2009-09-17 15:55:24 +0100585 nativeOnEnded(mNativePointer);
586 break;
Shimeng (Simon) Wang43aaa2d2010-10-18 16:25:59 -0700587 case PAUSED:
588 nativeOnPaused(mNativePointer);
589 break;
Andrei Popescu50c86232009-09-30 16:51:25 +0100590 case POSTER_FETCHED:
591 Bitmap poster = (Bitmap) msg.obj;
592 nativeOnPosterFetched(poster, mNativePointer);
593 break;
Andrei Popescu048eb3b2010-01-11 21:12:54 +0000594 case TIMEUPDATE:
595 nativeOnTimeupdate(msg.arg1, mNativePointer);
596 break;
Teng-Hui Zhub109c882011-05-04 16:19:49 -0700597 case STOPFULLSCREEN:
598 nativeOnStopFullscreen(mNativePointer);
599 break;
Teng-Hui Zhue4c89e32012-01-10 16:00:21 -0800600 case RESTORESTATE:
601 nativeOnRestoreState(mNativePointer);
602 break;
Andrei Popescu3c946a1a2009-07-03 08:20:53 +0100603 }
Andrei Popescu64b86a12009-09-15 20:34:18 +0100604 }
605 };
Andrei Popescu6fa29582009-06-19 14:54:09 +0100606 }
607
Andrei Popescu64b86a12009-09-15 20:34:18 +0100608 private void doSetPoster(Bitmap poster) {
609 if (poster == null) {
610 return;
611 }
Andrei Popescu50c86232009-09-30 16:51:25 +0100612 // Save a ref to the bitmap and send it over to the WebCore thread.
613 mPoster = poster;
614 Message msg = Message.obtain(mWebCoreHandler, POSTER_FETCHED);
615 msg.obj = poster;
616 mWebCoreHandler.sendMessage(msg);
Andrei Popescu64b86a12009-09-15 20:34:18 +0100617 }
618
Andrei Popescu048eb3b2010-01-11 21:12:54 +0000619 private void sendTimeupdate() {
620 Message msg = Message.obtain(mWebCoreHandler, TIMEUPDATE);
621 msg.arg1 = VideoPlayer.getCurrentPosition();
622 mWebCoreHandler.sendMessage(msg);
623 }
624
Andrei Popescu64b86a12009-09-15 20:34:18 +0100625 public Context getContext() {
626 return mWebView.getContext();
627 }
628
629 // The public methods below are all called from WebKit only.
Andrei Popescu6fa29582009-06-19 14:54:09 +0100630 /**
631 * Play a video stream.
632 * @param url is the URL of the video stream.
Andrei Popescu6fa29582009-06-19 14:54:09 +0100633 */
Teng-Hui Zhu661e8b12011-03-02 15:09:34 -0800634 public void play(String url, int position, int videoLayerID) {
Andrei Popescu64b86a12009-09-15 20:34:18 +0100635 if (url == null) {
636 return;
637 }
Ben Murdochff19d192011-01-17 18:08:56 +0000638
639 if (position > 0) {
640 seek(position);
641 }
Andrei Popescu6fa29582009-06-19 14:54:09 +0100642 Message message = obtainMessage(PLAY);
Teng-Hui Zhu661e8b12011-03-02 15:09:34 -0800643 message.arg1 = videoLayerID;
Andrei Popescu64b86a12009-09-15 20:34:18 +0100644 message.obj = url;
Andrei Popescu6fa29582009-06-19 14:54:09 +0100645 sendMessage(message);
646 }
647
Andrei Popescu64b86a12009-09-15 20:34:18 +0100648 /**
Andrei Popescu290c34a2009-09-17 15:55:24 +0100649 * Seek into the video stream.
650 * @param time is the position in the video stream.
651 */
652 public void seek(int time) {
653 Message message = obtainMessage(SEEK);
654 message.obj = new Integer(time);
655 sendMessage(message);
656 }
657
658 /**
659 * Pause the playback.
660 */
661 public void pause() {
662 Message message = obtainMessage(PAUSE);
663 sendMessage(message);
664 }
665
666 /**
Andrei Popescu50c86232009-09-30 16:51:25 +0100667 * Tear down this proxy object.
Andrei Popescu64b86a12009-09-15 20:34:18 +0100668 */
Andrei Popescu50c86232009-09-30 16:51:25 +0100669 public void teardown() {
Andrei Popescu64b86a12009-09-15 20:34:18 +0100670 // This is called by the C++ MediaPlayerPrivate dtor.
671 // Cancel any active poster download.
672 if (mPosterDownloader != null) {
673 mPosterDownloader.cancelAndReleaseQueue();
674 }
Andrei Popescu50c86232009-09-30 16:51:25 +0100675 mNativePointer = 0;
Andrei Popescu64b86a12009-09-15 20:34:18 +0100676 }
677
678 /**
679 * Load the poster image.
680 * @param url is the URL of the poster image.
681 */
682 public void loadPoster(String url) {
683 if (url == null) {
Andrei Popescu50c86232009-09-30 16:51:25 +0100684 Message message = obtainMessage(LOAD_DEFAULT_POSTER);
685 sendMessage(message);
Andrei Popescu64b86a12009-09-15 20:34:18 +0100686 return;
687 }
688 // Cancel any active poster download.
689 if (mPosterDownloader != null) {
690 mPosterDownloader.cancelAndReleaseQueue();
691 }
692 // Load the poster asynchronously
693 mPosterDownloader = new PosterDownloader(url, this);
694 mPosterDownloader.start();
Patrick Scott0a5ce012009-07-02 08:56:10 -0400695 }
696
Teng-Hui Zhu10ab6542011-03-16 16:42:32 -0700697 // These three function are called from UI thread only by WebView.
Teng-Hui Zhu661e8b12011-03-02 15:09:34 -0800698 public void setBaseLayer(int layer) {
699 VideoPlayer.setBaseLayer(layer);
700 }
701
702 public void pauseAndDispatch() {
703 VideoPlayer.pauseAndDispatch();
704 }
Teng-Hui Zhu10ab6542011-03-16 16:42:32 -0700705
706 public void enterFullScreenVideo(int layerId, String url) {
707 VideoPlayer.enterFullScreenVideo(layerId, url, this, mWebView);
708 }
709
Teng-Hui Zhud7678a12012-01-16 15:33:09 -0800710 public void exitFullScreenVideo() {
711 VideoPlayer.exitFullScreenVideo(this, mWebView);
712 }
713
Andrei Popescu6fa29582009-06-19 14:54:09 +0100714 /**
Andrei Popescu3c946a1a2009-07-03 08:20:53 +0100715 * The factory for HTML5VideoViewProxy instances.
Andrei Popescu6fa29582009-06-19 14:54:09 +0100716 * @param webViewCore is the WebViewCore that is requesting the proxy.
717 *
Andrei Popescu3c946a1a2009-07-03 08:20:53 +0100718 * @return a new HTML5VideoViewProxy object.
Andrei Popescu6fa29582009-06-19 14:54:09 +0100719 */
Andrei Popescu290c34a2009-09-17 15:55:24 +0100720 public static HTML5VideoViewProxy getInstance(WebViewCore webViewCore, int nativePtr) {
721 return new HTML5VideoViewProxy(webViewCore.getWebView(), nativePtr);
Andrei Popescu6fa29582009-06-19 14:54:09 +0100722 }
Andrei Popescu290c34a2009-09-17 15:55:24 +0100723
Ben Murdoch1708ad52010-11-11 15:56:16 +0000724 /* package */ WebView getWebView() {
725 return mWebView;
726 }
727
Andrei Popescu290c34a2009-09-17 15:55:24 +0100728 private native void nativeOnPrepared(int duration, int width, int height, int nativePointer);
729 private native void nativeOnEnded(int nativePointer);
Shimeng (Simon) Wang43aaa2d2010-10-18 16:25:59 -0700730 private native void nativeOnPaused(int nativePointer);
Andrei Popescu50c86232009-09-30 16:51:25 +0100731 private native void nativeOnPosterFetched(Bitmap poster, int nativePointer);
Andrei Popescu048eb3b2010-01-11 21:12:54 +0000732 private native void nativeOnTimeupdate(int position, int nativePointer);
Teng-Hui Zhub109c882011-05-04 16:19:49 -0700733 private native void nativeOnStopFullscreen(int nativePointer);
Teng-Hui Zhue4c89e32012-01-10 16:00:21 -0800734 private native void nativeOnRestoreState(int nativePointer);
Teng-Hui Zhu661e8b12011-03-02 15:09:34 -0800735 private native static boolean nativeSendSurfaceTexture(SurfaceTexture texture,
736 int baseLayer, int videoLayerId, int textureName,
Teng-Hui Zhu265db322011-03-18 14:56:10 -0700737 int playerState);
Teng-Hui Zhuc0fccd12011-03-29 10:35:11 -0700738
739 @Override
740 public boolean onInfo(MediaPlayer mp, int what, int extra) {
741 if (what == MediaPlayer.MEDIA_INFO_BUFFERING_START) {
742 sendMessage(obtainMessage(BUFFERING_START, what, extra));
743 } else if (what == MediaPlayer.MEDIA_INFO_BUFFERING_END) {
744 sendMessage(obtainMessage(BUFFERING_END, what, extra));
745 }
746 return false;
747 }
Andrei Popescu6fa29582009-06-19 14:54:09 +0100748}