blob: ab884dfbdd8cc667f90ebf883526d85e757603ad [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;
Jonathan Dixon3c909522012-02-28 18:45:06 +000078 // The WebViewClassic instance that created this view.
79 private WebViewClassic 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 Zhuc2b06d52012-05-09 14:13:52 -0700115 SurfaceTexture surfTexture =
116 HTML5VideoInline.getSurfaceTexture(currentVideoLayerId);
Teng-Hui Zhu3fafd392011-05-31 15:15:31 -0700117 int textureName = mHTML5VideoView.getTextureName();
118
Teng-Hui Zhu661e8b12011-03-02 15:09:34 -0800119 if (layer != 0 && surfTexture != null && currentVideoLayerId != -1) {
Teng-Hui Zhu265db322011-03-18 14:56:10 -0700120 int playerState = mHTML5VideoView.getCurrentState();
Teng-Hui Zhuf4d4e9e2011-03-30 14:39:56 -0700121 if (mHTML5VideoView.getPlayerBuffering())
Teng-Hui Zhuc2b06d52012-05-09 14:13:52 -0700122 playerState = HTML5VideoView.STATE_PREPARING;
Teng-Hui Zhu661e8b12011-03-02 15:09:34 -0800123 boolean foundInTree = nativeSendSurfaceTexture(surfTexture,
124 layer, currentVideoLayerId, textureName,
Teng-Hui Zhu265db322011-03-18 14:56:10 -0700125 playerState);
Teng-Hui Zhu1e26d822011-03-23 16:54:20 -0700126 if (playerState >= HTML5VideoView.STATE_PREPARED
Teng-Hui Zhu265db322011-03-18 14:56:10 -0700127 && !foundInTree) {
Teng-Hui Zhu661e8b12011-03-02 15:09:34 -0800128 mHTML5VideoView.pauseAndDispatch(mCurrentProxy);
129 mHTML5VideoView.deleteSurfaceTexture();
Andrei Popescu31d2aa12010-04-08 15:19:22 +0100130 }
Andrei Popescu64b86a12009-09-15 20:34:18 +0100131 }
Andrei Popescua41f97b2010-01-11 18:36:25 +0000132 }
Teng-Hui Zhu661e8b12011-03-02 15:09:34 -0800133 }
Andrei Popescua41f97b2010-01-11 18:36:25 +0000134
Teng-Hui Zhu661e8b12011-03-02 15:09:34 -0800135 // When a WebView is paused, we also want to pause the video in it.
136 public static void pauseAndDispatch() {
137 if (mHTML5VideoView != null) {
138 mHTML5VideoView.pauseAndDispatch(mCurrentProxy);
139 // When switching out, clean the video content on the old page
140 // by telling the layer not readyToUseSurfTex.
141 setBaseLayer(mBaseLayer);
142 }
143 }
144
Teng-Hui Zhu10ab6542011-03-16 16:42:32 -0700145 public static void enterFullScreenVideo(int layerId, String url,
Jonathan Dixon3c909522012-02-28 18:45:06 +0000146 HTML5VideoViewProxy proxy, WebViewClassic webView) {
Teng-Hui Zhu10ab6542011-03-16 16:42:32 -0700147 // Save the inline video info and inherit it in the full screen
148 int savePosition = 0;
Teng-Hui Zhuc2b06d52012-05-09 14:13:52 -0700149 boolean canSkipPrepare = false;
Teng-Hui Zhu6a9586b2012-06-15 11:22:23 -0700150 boolean forceStart = false;
Teng-Hui Zhu10ab6542011-03-16 16:42:32 -0700151 if (mHTML5VideoView != null) {
Teng-Hui Zhu2b64c5a2012-03-19 13:56:49 -0700152 // We don't allow enter full screen mode while the previous
153 // full screen video hasn't finished yet.
154 if (!mHTML5VideoView.fullScreenExited() && mHTML5VideoView.isFullScreenMode()) {
155 Log.w(LOGTAG, "Try to reenter the full screen mode");
156 return;
157 }
Teng-Hui Zhu6a9586b2012-06-15 11:22:23 -0700158 int playerState = mHTML5VideoView.getCurrentState();
Teng-Hui Zhu10ab6542011-03-16 16:42:32 -0700159 // If we are playing the same video, then it is better to
160 // save the current position.
161 if (layerId == mHTML5VideoView.getVideoLayerId()) {
162 savePosition = mHTML5VideoView.getCurrentPosition();
Teng-Hui Zhuc2b06d52012-05-09 14:13:52 -0700163 canSkipPrepare = (playerState == HTML5VideoView.STATE_PREPARING
164 || playerState == HTML5VideoView.STATE_PREPARED
165 || playerState == HTML5VideoView.STATE_PLAYING)
166 && !mHTML5VideoView.isFullScreenMode();
Teng-Hui Zhu10ab6542011-03-16 16:42:32 -0700167 }
Teng-Hui Zhuc2b06d52012-05-09 14:13:52 -0700168 if (!canSkipPrepare) {
169 mHTML5VideoView.reset();
Teng-Hui Zhu6a9586b2012-06-15 11:22:23 -0700170 } else {
171 forceStart = playerState == HTML5VideoView.STATE_PREPARING
172 || playerState == HTML5VideoView.STATE_PLAYING;
Teng-Hui Zhuc2b06d52012-05-09 14:13:52 -0700173 }
Teng-Hui Zhu10ab6542011-03-16 16:42:32 -0700174 }
175 mHTML5VideoView = new HTML5VideoFullScreen(proxy.getContext(),
Teng-Hui Zhuc2b06d52012-05-09 14:13:52 -0700176 layerId, savePosition, canSkipPrepare);
Teng-Hui Zhu6a9586b2012-06-15 11:22:23 -0700177 mHTML5VideoView.setStartWhenPrepared(forceStart);
Teng-Hui Zhu10ab6542011-03-16 16:42:32 -0700178 mCurrentProxy = proxy;
Teng-Hui Zhu10ab6542011-03-16 16:42:32 -0700179 mHTML5VideoView.setVideoURI(url, mCurrentProxy);
Teng-Hui Zhu10ab6542011-03-16 16:42:32 -0700180 mHTML5VideoView.enterFullScreenVideoState(layerId, proxy, webView);
181 }
182
Teng-Hui Zhud7678a12012-01-16 15:33:09 -0800183 public static void exitFullScreenVideo(HTML5VideoViewProxy proxy,
Jonathan Dixon3c909522012-02-28 18:45:06 +0000184 WebViewClassic webView) {
Teng-Hui Zhud7678a12012-01-16 15:33:09 -0800185 if (!mHTML5VideoView.fullScreenExited() && mHTML5VideoView.isFullScreenMode()) {
186 WebChromeClient client = webView.getWebChromeClient();
187 if (client != null) {
188 client.onHideCustomView();
189 }
190 }
191 }
192
Teng-Hui Zhu661e8b12011-03-02 15:09:34 -0800193 // This is on the UI thread.
194 // When native tell Java to play, we need to check whether or not it is
195 // still the same video by using videoLayerId and treat it differently.
196 public static void play(String url, int time, HTML5VideoViewProxy proxy,
197 WebChromeClient client, int videoLayerId) {
198 int currentVideoLayerId = -1;
Teng-Hui Zhu10ab6542011-03-16 16:42:32 -0700199 boolean backFromFullScreenMode = false;
Teng-Hui Zhu10ab6542011-03-16 16:42:32 -0700200 if (mHTML5VideoView != null) {
201 currentVideoLayerId = mHTML5VideoView.getVideoLayerId();
Teng-Hui Zhu1a88acb2011-06-01 13:28:41 -0700202 backFromFullScreenMode = mHTML5VideoView.fullScreenExited();
Teng-Hui Zhu96fae5e2012-01-18 15:25:51 -0800203
204 // When playing video back to back in full screen mode,
205 // javascript will switch the src and call play.
206 // In this case, we can just reuse the same full screen view,
207 // and play the video after prepared.
208 if (mHTML5VideoView.isFullScreenMode()
209 && !backFromFullScreenMode
210 && currentVideoLayerId != videoLayerId
211 && mCurrentProxy != proxy) {
212 mCurrentProxy = proxy;
213 mHTML5VideoView.setStartWhenPrepared(true);
214 mHTML5VideoView.setVideoURI(url, proxy);
215 mHTML5VideoView.reprepareData(proxy);
216 return;
217 }
Teng-Hui Zhu10ab6542011-03-16 16:42:32 -0700218 }
219
220 if (backFromFullScreenMode
Teng-Hui Zhu3fafd392011-05-31 15:15:31 -0700221 || currentVideoLayerId != videoLayerId
222 || mHTML5VideoView.surfaceTextureDeleted()) {
Teng-Hui Zhu661e8b12011-03-02 15:09:34 -0800223 // Here, we handle the case when switching to a new video,
224 // either inside a WebView or across WebViews
225 // For switching videos within a WebView or across the WebView,
226 // we need to pause the old one and re-create a new media player
227 // inside the HTML5VideoView.
228 if (mHTML5VideoView != null) {
Teng-Hui Zhu22954d42011-04-07 17:13:18 -0700229 if (!backFromFullScreenMode) {
230 mHTML5VideoView.pauseAndDispatch(mCurrentProxy);
231 }
Teng-Hui Zhuc2b06d52012-05-09 14:13:52 -0700232 mHTML5VideoView.reset();
Teng-Hui Zhu661e8b12011-03-02 15:09:34 -0800233 }
Teng-Hui Zhu661e8b12011-03-02 15:09:34 -0800234 mCurrentProxy = proxy;
Teng-Hui Zhue4c89e32012-01-10 16:00:21 -0800235 mHTML5VideoView = new HTML5VideoInline(videoLayerId, time);
Teng-Hui Zhu661e8b12011-03-02 15:09:34 -0800236
Teng-Hui Zhu10ab6542011-03-16 16:42:32 -0700237 mHTML5VideoView.setVideoURI(url, mCurrentProxy);
238 mHTML5VideoView.prepareDataAndDisplayMode(proxy);
Teng-Hui Zhu661e8b12011-03-02 15:09:34 -0800239 } else if (mCurrentProxy == proxy) {
240 // Here, we handle the case when we keep playing with one video
241 if (!mHTML5VideoView.isPlaying()) {
242 mHTML5VideoView.seekTo(time);
243 mHTML5VideoView.start();
244 }
245 } else if (mCurrentProxy != null) {
Teng-Hui Zhu10ab6542011-03-16 16:42:32 -0700246 // Some other video is already playing. Notify the caller that
247 // its playback ended.
Andrei Popescuc4fbceb2010-04-14 13:30:23 +0100248 proxy.dispatchOnEnded();
Andrei Popescu3c946a1a2009-07-03 08:20:53 +0100249 }
Andrei Popescu3c946a1a2009-07-03 08:20:53 +0100250 }
Andrei Popescu290c34a2009-09-17 15:55:24 +0100251
Andrei Popescu048eb3b2010-01-11 21:12:54 +0000252 public static boolean isPlaying(HTML5VideoViewProxy proxy) {
Teng-Hui Zhu661e8b12011-03-02 15:09:34 -0800253 return (mCurrentProxy == proxy && mHTML5VideoView != null
254 && mHTML5VideoView.isPlaying());
Andrei Popescu048eb3b2010-01-11 21:12:54 +0000255 }
256
257 public static int getCurrentPosition() {
258 int currentPosMs = 0;
Teng-Hui Zhu661e8b12011-03-02 15:09:34 -0800259 if (mHTML5VideoView != null) {
260 currentPosMs = mHTML5VideoView.getCurrentPosition();
Andrei Popescu048eb3b2010-01-11 21:12:54 +0000261 }
262 return currentPosMs;
263 }
264
Andrei Popescu290c34a2009-09-17 15:55:24 +0100265 public static void seek(int time, HTML5VideoViewProxy proxy) {
Teng-Hui Zhu661e8b12011-03-02 15:09:34 -0800266 if (mCurrentProxy == proxy && time >= 0 && mHTML5VideoView != null) {
267 mHTML5VideoView.seekTo(time);
Andrei Popescu290c34a2009-09-17 15:55:24 +0100268 }
269 }
270
271 public static void pause(HTML5VideoViewProxy proxy) {
Teng-Hui Zhu661e8b12011-03-02 15:09:34 -0800272 if (mCurrentProxy == proxy && mHTML5VideoView != null) {
273 mHTML5VideoView.pause();
Andrei Popescu290c34a2009-09-17 15:55:24 +0100274 }
275 }
Andrei Popescubf385d72009-09-18 18:59:52 +0100276
277 public static void onPrepared() {
Teng-Hui Zhue4c89e32012-01-10 16:00:21 -0800278 if (!mHTML5VideoView.isFullScreenMode()) {
Teng-Hui Zhu05049672011-04-06 18:09:27 -0700279 mHTML5VideoView.start();
280 }
Teng-Hui Zhu265db322011-03-18 14:56:10 -0700281 if (mBaseLayer != 0) {
282 setBaseLayer(mBaseLayer);
283 }
Teng-Hui Zhu661e8b12011-03-02 15:09:34 -0800284 }
285
286 public static void end() {
Teng-Hui Zhu4dd9dc82012-05-10 17:20:19 -0700287 mHTML5VideoView.showControllerInFullScreen();
Teng-Hui Zhu661e8b12011-03-02 15:09:34 -0800288 if (mCurrentProxy != null) {
289 if (isVideoSelfEnded)
290 mCurrentProxy.dispatchOnEnded();
291 else
292 mCurrentProxy.dispatchOnPaused();
293 }
294 isVideoSelfEnded = false;
Andrei Popescubf385d72009-09-18 18:59:52 +0100295 }
Andrei Popescu290c34a2009-09-17 15:55:24 +0100296 }
297
298 // A bunch event listeners for our VideoView
299 // MediaPlayer.OnPreparedListener
300 public void onPrepared(MediaPlayer mp) {
Andrei Popescubf385d72009-09-18 18:59:52 +0100301 VideoPlayer.onPrepared();
Andrei Popescu290c34a2009-09-17 15:55:24 +0100302 Message msg = Message.obtain(mWebCoreHandler, PREPARED);
303 Map<String, Object> map = new HashMap<String, Object>();
304 map.put("dur", new Integer(mp.getDuration()));
305 map.put("width", new Integer(mp.getVideoWidth()));
306 map.put("height", new Integer(mp.getVideoHeight()));
307 msg.obj = map;
308 mWebCoreHandler.sendMessage(msg);
309 }
310
311 // MediaPlayer.OnCompletionListener;
312 public void onCompletion(MediaPlayer mp) {
Andrei Popescuc4fbceb2010-04-14 13:30:23 +0100313 // The video ended by itself, so we need to
314 // send a message to the UI thread to dismiss
315 // the video view and to return to the WebView.
Shimeng (Simon) Wang43aaa2d2010-10-18 16:25:59 -0700316 // arg1 == 1 means the video ends by itself.
317 sendMessage(obtainMessage(ENDED, 1, 0));
Andrei Popescu290c34a2009-09-17 15:55:24 +0100318 }
319
Andrei Popescu50c86232009-09-30 16:51:25 +0100320 // MediaPlayer.OnErrorListener
321 public boolean onError(MediaPlayer mp, int what, int extra) {
322 sendMessage(obtainMessage(ERROR));
323 return false;
324 }
325
Andrei Popescuc4fbceb2010-04-14 13:30:23 +0100326 public void dispatchOnEnded() {
Andrei Popescu290c34a2009-09-17 15:55:24 +0100327 Message msg = Message.obtain(mWebCoreHandler, ENDED);
328 mWebCoreHandler.sendMessage(msg);
Andrei Popescu64b86a12009-09-15 20:34:18 +0100329 }
Andrei Popescu3c946a1a2009-07-03 08:20:53 +0100330
Shimeng (Simon) Wang43aaa2d2010-10-18 16:25:59 -0700331 public void dispatchOnPaused() {
Teng-Hui Zhub109c882011-05-04 16:19:49 -0700332 Message msg = Message.obtain(mWebCoreHandler, PAUSED);
333 mWebCoreHandler.sendMessage(msg);
334 }
335
336 public void dispatchOnStopFullScreen() {
337 Message msg = Message.obtain(mWebCoreHandler, STOPFULLSCREEN);
338 mWebCoreHandler.sendMessage(msg);
Shimeng (Simon) Wang43aaa2d2010-10-18 16:25:59 -0700339 }
340
Teng-Hui Zhue4c89e32012-01-10 16:00:21 -0800341 public void dispatchOnRestoreState() {
342 Message msg = Message.obtain(mWebCoreHandler, RESTORESTATE);
343 mWebCoreHandler.sendMessage(msg);
344 }
345
Andrei Popescu048eb3b2010-01-11 21:12:54 +0000346 public void onTimeupdate() {
347 sendMessage(obtainMessage(TIMEUPDATE));
348 }
349
Teng-Hui Zhu661e8b12011-03-02 15:09:34 -0800350 // When there is a frame ready from surface texture, we should tell WebView
351 // to refresh.
352 @Override
353 public void onFrameAvailable(SurfaceTexture surfaceTexture) {
354 // TODO: This should support partial invalidation too.
355 mWebView.invalidate();
356 }
357
Andrei Popescu048eb3b2010-01-11 21:12:54 +0000358 // Handler for the messages from WebCore or Timer thread to the UI thread.
Andrei Popescu64b86a12009-09-15 20:34:18 +0100359 @Override
360 public void handleMessage(Message msg) {
361 // This executes on the UI thread.
362 switch (msg.what) {
Andrei Popescu64b86a12009-09-15 20:34:18 +0100363 case PLAY: {
364 String url = (String) msg.obj;
365 WebChromeClient client = mWebView.getWebChromeClient();
Teng-Hui Zhu661e8b12011-03-02 15:09:34 -0800366 int videoLayerID = msg.arg1;
Andrei Popescu64b86a12009-09-15 20:34:18 +0100367 if (client != null) {
Teng-Hui Zhu661e8b12011-03-02 15:09:34 -0800368 VideoPlayer.play(url, mSeekPosition, this, client, videoLayerID);
Andrei Popescu64b86a12009-09-15 20:34:18 +0100369 }
370 break;
371 }
Andrei Popescu290c34a2009-09-17 15:55:24 +0100372 case SEEK: {
373 Integer time = (Integer) msg.obj;
374 mSeekPosition = time;
375 VideoPlayer.seek(mSeekPosition, this);
376 break;
377 }
378 case PAUSE: {
379 VideoPlayer.pause(this);
380 break;
381 }
Andrei Popescu46a83b42009-10-23 13:49:46 +0100382 case ENDED:
Shimeng (Simon) Wang43aaa2d2010-10-18 16:25:59 -0700383 if (msg.arg1 == 1)
384 VideoPlayer.isVideoSelfEnded = true;
Teng-Hui Zhu661e8b12011-03-02 15:09:34 -0800385 VideoPlayer.end();
386 break;
Andrei Popescu50c86232009-09-30 16:51:25 +0100387 case ERROR: {
388 WebChromeClient client = mWebView.getWebChromeClient();
389 if (client != null) {
390 client.onHideCustomView();
391 }
392 break;
393 }
394 case LOAD_DEFAULT_POSTER: {
395 WebChromeClient client = mWebView.getWebChromeClient();
396 if (client != null) {
397 doSetPoster(client.getDefaultVideoPoster());
398 }
399 break;
400 }
Andrei Popescu048eb3b2010-01-11 21:12:54 +0000401 case TIMEUPDATE: {
402 if (VideoPlayer.isPlaying(this)) {
403 sendTimeupdate();
404 }
405 break;
406 }
Teng-Hui Zhuc0fccd12011-03-29 10:35:11 -0700407 case BUFFERING_START: {
Teng-Hui Zhuf4d4e9e2011-03-30 14:39:56 -0700408 VideoPlayer.setPlayerBuffering(true);
Teng-Hui Zhuc0fccd12011-03-29 10:35:11 -0700409 break;
410 }
411 case BUFFERING_END: {
Teng-Hui Zhuf4d4e9e2011-03-30 14:39:56 -0700412 VideoPlayer.setPlayerBuffering(false);
Teng-Hui Zhuc0fccd12011-03-29 10:35:11 -0700413 break;
414 }
Andrei Popescu64b86a12009-09-15 20:34:18 +0100415 }
416 }
417
Andrei Popescu64b86a12009-09-15 20:34:18 +0100418 // Everything below this comment executes on the WebCore thread, except for
419 // the EventHandler methods, which are called on the network thread.
420
421 // A helper class that knows how to download posters
422 private static final class PosterDownloader implements EventHandler {
423 // The request queue. This is static as we have one queue for all posters.
424 private static RequestQueue mRequestQueue;
425 private static int mQueueRefCount = 0;
426 // The poster URL
Ben Murdoch42509792011-02-18 12:34:35 +0000427 private URL mUrl;
Andrei Popescu64b86a12009-09-15 20:34:18 +0100428 // The proxy we're doing this for.
429 private final HTML5VideoViewProxy mProxy;
430 // The poster bytes. We only touch this on the network thread.
431 private ByteArrayOutputStream mPosterBytes;
432 // The request handle. We only touch this on the WebCore thread.
433 private RequestHandle mRequestHandle;
434 // The response status code.
435 private int mStatusCode;
436 // The response headers.
437 private Headers mHeaders;
438 // The handler to handle messages on the WebCore thread.
439 private Handler mHandler;
440
441 public PosterDownloader(String url, HTML5VideoViewProxy proxy) {
Ben Murdoch42509792011-02-18 12:34:35 +0000442 try {
443 mUrl = new URL(url);
444 } catch (MalformedURLException e) {
445 mUrl = null;
446 }
Andrei Popescu64b86a12009-09-15 20:34:18 +0100447 mProxy = proxy;
448 mHandler = new Handler();
449 }
450 // Start the download. Called on WebCore thread.
451 public void start() {
452 retainQueue();
Ben Murdoch42509792011-02-18 12:34:35 +0000453
454 if (mUrl == null) {
455 return;
456 }
457
458 // Only support downloading posters over http/https.
459 // FIXME: Add support for other schemes. WebKit seems able to load
460 // posters over other schemes e.g. file://, but gets the dimensions wrong.
461 String protocol = mUrl.getProtocol();
462 if ("http".equals(protocol) || "https".equals(protocol)) {
463 mRequestHandle = mRequestQueue.queueRequest(mUrl.toString(), "GET", null,
464 this, null, 0);
465 }
Andrei Popescu64b86a12009-09-15 20:34:18 +0100466 }
467 // Cancel the download if active and release the queue. Called on WebCore thread.
468 public void cancelAndReleaseQueue() {
469 if (mRequestHandle != null) {
470 mRequestHandle.cancel();
471 mRequestHandle = null;
472 }
473 releaseQueue();
474 }
475 // EventHandler methods. Executed on the network thread.
476 public void status(int major_version,
477 int minor_version,
478 int code,
479 String reason_phrase) {
480 mStatusCode = code;
Andrei Popescu3c946a1a2009-07-03 08:20:53 +0100481 }
482
Andrei Popescu64b86a12009-09-15 20:34:18 +0100483 public void headers(Headers headers) {
484 mHeaders = headers;
485 }
486
487 public void data(byte[] data, int len) {
488 if (mPosterBytes == null) {
489 mPosterBytes = new ByteArrayOutputStream();
490 }
491 mPosterBytes.write(data, 0, len);
492 }
493
494 public void endData() {
495 if (mStatusCode == 200) {
496 if (mPosterBytes.size() > 0) {
497 Bitmap poster = BitmapFactory.decodeByteArray(
498 mPosterBytes.toByteArray(), 0, mPosterBytes.size());
Andrei Popescu50c86232009-09-30 16:51:25 +0100499 mProxy.doSetPoster(poster);
Andrei Popescu64b86a12009-09-15 20:34:18 +0100500 }
501 cleanup();
502 } else if (mStatusCode >= 300 && mStatusCode < 400) {
503 // We have a redirect.
Ben Murdoch42509792011-02-18 12:34:35 +0000504 try {
505 mUrl = new URL(mHeaders.getLocation());
506 } catch (MalformedURLException e) {
507 mUrl = null;
508 }
Andrei Popescu64b86a12009-09-15 20:34:18 +0100509 if (mUrl != null) {
510 mHandler.post(new Runnable() {
511 public void run() {
512 if (mRequestHandle != null) {
Ben Murdoch42509792011-02-18 12:34:35 +0000513 mRequestHandle.setupRedirect(mUrl.toString(), mStatusCode,
Andrei Popescu64b86a12009-09-15 20:34:18 +0100514 new HashMap<String, String>());
515 }
516 }
517 });
518 }
519 }
520 }
521
522 public void certificate(SslCertificate certificate) {
523 // Don't care.
524 }
525
526 public void error(int id, String description) {
527 cleanup();
528 }
529
530 public boolean handleSslErrorRequest(SslError error) {
531 // Don't care. If this happens, data() will never be called so
532 // mPosterBytes will never be created, so no need to call cleanup.
533 return false;
534 }
535 // Tears down the poster bytes stream. Called on network thread.
536 private void cleanup() {
537 if (mPosterBytes != null) {
538 try {
539 mPosterBytes.close();
540 } catch (IOException ignored) {
541 // Ignored.
542 } finally {
543 mPosterBytes = null;
544 }
545 }
546 }
547
548 // Queue management methods. Called on WebCore thread.
549 private void retainQueue() {
550 if (mRequestQueue == null) {
551 mRequestQueue = new RequestQueue(mProxy.getContext());
552 }
553 mQueueRefCount++;
554 }
555
556 private void releaseQueue() {
557 if (mQueueRefCount == 0) {
558 return;
559 }
560 if (--mQueueRefCount == 0) {
561 mRequestQueue.shutdown();
562 mRequestQueue = null;
563 }
Andrei Popescu3c946a1a2009-07-03 08:20:53 +0100564 }
565 }
Andrei Popescu6fa29582009-06-19 14:54:09 +0100566
567 /**
568 * Private constructor.
Andrei Popescu290c34a2009-09-17 15:55:24 +0100569 * @param webView is the WebView that hosts the video.
570 * @param nativePtr is the C++ pointer to the MediaPlayerPrivate object.
Andrei Popescu6fa29582009-06-19 14:54:09 +0100571 */
Jonathan Dixon3c909522012-02-28 18:45:06 +0000572 private HTML5VideoViewProxy(WebViewClassic webView, int nativePtr) {
Andrei Popescu6fa29582009-06-19 14:54:09 +0100573 // This handler is for the main (UI) thread.
574 super(Looper.getMainLooper());
Andrei Popescu3c946a1a2009-07-03 08:20:53 +0100575 // Save the WebView object.
576 mWebView = webView;
Teng-Hui Zhu661e8b12011-03-02 15:09:34 -0800577 // Pass Proxy into webview, such that every time we have a setBaseLayer
578 // call, we tell this Proxy to call the native to update the layer tree
579 // for the Video Layer's surface texture info
580 mWebView.setHTML5VideoViewProxy(this);
Andrei Popescu290c34a2009-09-17 15:55:24 +0100581 // Save the native ptr
582 mNativePointer = nativePtr;
Andrei Popescu64b86a12009-09-15 20:34:18 +0100583 // create the message handler for this thread
584 createWebCoreHandler();
Andrei Popescu6fa29582009-06-19 14:54:09 +0100585 }
586
Andrei Popescu64b86a12009-09-15 20:34:18 +0100587 private void createWebCoreHandler() {
588 mWebCoreHandler = new Handler() {
589 @Override
590 public void handleMessage(Message msg) {
591 switch (msg.what) {
Andrei Popescu290c34a2009-09-17 15:55:24 +0100592 case PREPARED: {
593 Map<String, Object> map = (Map<String, Object>) msg.obj;
594 Integer duration = (Integer) map.get("dur");
595 Integer width = (Integer) map.get("width");
596 Integer height = (Integer) map.get("height");
597 nativeOnPrepared(duration.intValue(), width.intValue(),
598 height.intValue(), mNativePointer);
599 break;
600 }
601 case ENDED:
Ben Murdochff19d192011-01-17 18:08:56 +0000602 mSeekPosition = 0;
Andrei Popescu290c34a2009-09-17 15:55:24 +0100603 nativeOnEnded(mNativePointer);
604 break;
Shimeng (Simon) Wang43aaa2d2010-10-18 16:25:59 -0700605 case PAUSED:
606 nativeOnPaused(mNativePointer);
607 break;
Andrei Popescu50c86232009-09-30 16:51:25 +0100608 case POSTER_FETCHED:
609 Bitmap poster = (Bitmap) msg.obj;
610 nativeOnPosterFetched(poster, mNativePointer);
611 break;
Andrei Popescu048eb3b2010-01-11 21:12:54 +0000612 case TIMEUPDATE:
613 nativeOnTimeupdate(msg.arg1, mNativePointer);
614 break;
Teng-Hui Zhub109c882011-05-04 16:19:49 -0700615 case STOPFULLSCREEN:
616 nativeOnStopFullscreen(mNativePointer);
617 break;
Teng-Hui Zhue4c89e32012-01-10 16:00:21 -0800618 case RESTORESTATE:
619 nativeOnRestoreState(mNativePointer);
620 break;
Andrei Popescu3c946a1a2009-07-03 08:20:53 +0100621 }
Andrei Popescu64b86a12009-09-15 20:34:18 +0100622 }
623 };
Andrei Popescu6fa29582009-06-19 14:54:09 +0100624 }
625
Andrei Popescu64b86a12009-09-15 20:34:18 +0100626 private void doSetPoster(Bitmap poster) {
627 if (poster == null) {
628 return;
629 }
Andrei Popescu50c86232009-09-30 16:51:25 +0100630 // Save a ref to the bitmap and send it over to the WebCore thread.
631 mPoster = poster;
632 Message msg = Message.obtain(mWebCoreHandler, POSTER_FETCHED);
633 msg.obj = poster;
634 mWebCoreHandler.sendMessage(msg);
Andrei Popescu64b86a12009-09-15 20:34:18 +0100635 }
636
Andrei Popescu048eb3b2010-01-11 21:12:54 +0000637 private void sendTimeupdate() {
638 Message msg = Message.obtain(mWebCoreHandler, TIMEUPDATE);
639 msg.arg1 = VideoPlayer.getCurrentPosition();
640 mWebCoreHandler.sendMessage(msg);
641 }
642
Andrei Popescu64b86a12009-09-15 20:34:18 +0100643 public Context getContext() {
644 return mWebView.getContext();
645 }
646
647 // The public methods below are all called from WebKit only.
Andrei Popescu6fa29582009-06-19 14:54:09 +0100648 /**
649 * Play a video stream.
650 * @param url is the URL of the video stream.
Andrei Popescu6fa29582009-06-19 14:54:09 +0100651 */
Teng-Hui Zhu661e8b12011-03-02 15:09:34 -0800652 public void play(String url, int position, int videoLayerID) {
Andrei Popescu64b86a12009-09-15 20:34:18 +0100653 if (url == null) {
654 return;
655 }
Ben Murdochff19d192011-01-17 18:08:56 +0000656
657 if (position > 0) {
658 seek(position);
659 }
Andrei Popescu6fa29582009-06-19 14:54:09 +0100660 Message message = obtainMessage(PLAY);
Teng-Hui Zhu661e8b12011-03-02 15:09:34 -0800661 message.arg1 = videoLayerID;
Andrei Popescu64b86a12009-09-15 20:34:18 +0100662 message.obj = url;
Andrei Popescu6fa29582009-06-19 14:54:09 +0100663 sendMessage(message);
664 }
665
Andrei Popescu64b86a12009-09-15 20:34:18 +0100666 /**
Andrei Popescu290c34a2009-09-17 15:55:24 +0100667 * Seek into the video stream.
668 * @param time is the position in the video stream.
669 */
670 public void seek(int time) {
671 Message message = obtainMessage(SEEK);
672 message.obj = new Integer(time);
673 sendMessage(message);
674 }
675
676 /**
677 * Pause the playback.
678 */
679 public void pause() {
680 Message message = obtainMessage(PAUSE);
681 sendMessage(message);
682 }
683
684 /**
Andrei Popescu50c86232009-09-30 16:51:25 +0100685 * Tear down this proxy object.
Andrei Popescu64b86a12009-09-15 20:34:18 +0100686 */
Andrei Popescu50c86232009-09-30 16:51:25 +0100687 public void teardown() {
Andrei Popescu64b86a12009-09-15 20:34:18 +0100688 // This is called by the C++ MediaPlayerPrivate dtor.
689 // Cancel any active poster download.
690 if (mPosterDownloader != null) {
691 mPosterDownloader.cancelAndReleaseQueue();
692 }
Andrei Popescu50c86232009-09-30 16:51:25 +0100693 mNativePointer = 0;
Andrei Popescu64b86a12009-09-15 20:34:18 +0100694 }
695
696 /**
697 * Load the poster image.
698 * @param url is the URL of the poster image.
699 */
700 public void loadPoster(String url) {
701 if (url == null) {
Andrei Popescu50c86232009-09-30 16:51:25 +0100702 Message message = obtainMessage(LOAD_DEFAULT_POSTER);
703 sendMessage(message);
Andrei Popescu64b86a12009-09-15 20:34:18 +0100704 return;
705 }
706 // Cancel any active poster download.
707 if (mPosterDownloader != null) {
708 mPosterDownloader.cancelAndReleaseQueue();
709 }
710 // Load the poster asynchronously
711 mPosterDownloader = new PosterDownloader(url, this);
712 mPosterDownloader.start();
Patrick Scott0a5ce012009-07-02 08:56:10 -0400713 }
714
Teng-Hui Zhu10ab6542011-03-16 16:42:32 -0700715 // These three function are called from UI thread only by WebView.
Teng-Hui Zhu661e8b12011-03-02 15:09:34 -0800716 public void setBaseLayer(int layer) {
717 VideoPlayer.setBaseLayer(layer);
718 }
719
720 public void pauseAndDispatch() {
721 VideoPlayer.pauseAndDispatch();
722 }
Teng-Hui Zhu10ab6542011-03-16 16:42:32 -0700723
724 public void enterFullScreenVideo(int layerId, String url) {
725 VideoPlayer.enterFullScreenVideo(layerId, url, this, mWebView);
726 }
727
Teng-Hui Zhud7678a12012-01-16 15:33:09 -0800728 public void exitFullScreenVideo() {
729 VideoPlayer.exitFullScreenVideo(this, mWebView);
730 }
731
Andrei Popescu6fa29582009-06-19 14:54:09 +0100732 /**
Andrei Popescu3c946a1a2009-07-03 08:20:53 +0100733 * The factory for HTML5VideoViewProxy instances.
Andrei Popescu6fa29582009-06-19 14:54:09 +0100734 * @param webViewCore is the WebViewCore that is requesting the proxy.
735 *
Andrei Popescu3c946a1a2009-07-03 08:20:53 +0100736 * @return a new HTML5VideoViewProxy object.
Andrei Popescu6fa29582009-06-19 14:54:09 +0100737 */
Andrei Popescu290c34a2009-09-17 15:55:24 +0100738 public static HTML5VideoViewProxy getInstance(WebViewCore webViewCore, int nativePtr) {
Jonathan Dixona6c4d8e2012-03-08 17:55:31 +0000739 return new HTML5VideoViewProxy(webViewCore.getWebViewClassic(), nativePtr);
Andrei Popescu6fa29582009-06-19 14:54:09 +0100740 }
Andrei Popescu290c34a2009-09-17 15:55:24 +0100741
Jonathan Dixon3c909522012-02-28 18:45:06 +0000742 /* package */ WebViewClassic getWebView() {
Ben Murdoch1708ad52010-11-11 15:56:16 +0000743 return mWebView;
744 }
745
Andrei Popescu290c34a2009-09-17 15:55:24 +0100746 private native void nativeOnPrepared(int duration, int width, int height, int nativePointer);
747 private native void nativeOnEnded(int nativePointer);
Shimeng (Simon) Wang43aaa2d2010-10-18 16:25:59 -0700748 private native void nativeOnPaused(int nativePointer);
Andrei Popescu50c86232009-09-30 16:51:25 +0100749 private native void nativeOnPosterFetched(Bitmap poster, int nativePointer);
Andrei Popescu048eb3b2010-01-11 21:12:54 +0000750 private native void nativeOnTimeupdate(int position, int nativePointer);
Teng-Hui Zhub109c882011-05-04 16:19:49 -0700751 private native void nativeOnStopFullscreen(int nativePointer);
Teng-Hui Zhue4c89e32012-01-10 16:00:21 -0800752 private native void nativeOnRestoreState(int nativePointer);
Teng-Hui Zhu661e8b12011-03-02 15:09:34 -0800753 private native static boolean nativeSendSurfaceTexture(SurfaceTexture texture,
754 int baseLayer, int videoLayerId, int textureName,
Teng-Hui Zhu265db322011-03-18 14:56:10 -0700755 int playerState);
Teng-Hui Zhuc0fccd12011-03-29 10:35:11 -0700756
757 @Override
758 public boolean onInfo(MediaPlayer mp, int what, int extra) {
759 if (what == MediaPlayer.MEDIA_INFO_BUFFERING_START) {
760 sendMessage(obtainMessage(BUFFERING_START, what, extra));
761 } else if (what == MediaPlayer.MEDIA_INFO_BUFFERING_END) {
762 sendMessage(obtainMessage(BUFFERING_END, what, extra));
763 }
764 return false;
765 }
Andrei Popescu6fa29582009-06-19 14:54:09 +0100766}