blob: 701ef359467131fb2fd53ba57c7a87eff3b8e39b [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;
Teng-Hui Zhuc3a28582012-05-31 17:36:17 -070062 private static final int ENTER_FULLSCREEN = 107;
Andrei Popescu6fa29582009-06-19 14:54:09 +010063
Andrei Popescu64b86a12009-09-15 20:34:18 +010064 // Message Ids to be handled on the WebCore thread
Andrei Popescu290c34a2009-09-17 15:55:24 +010065 private static final int PREPARED = 200;
66 private static final int ENDED = 201;
Andrei Popescu50c86232009-09-30 16:51:25 +010067 private static final int POSTER_FETCHED = 202;
Shimeng (Simon) Wang43aaa2d2010-10-18 16:25:59 -070068 private static final int PAUSED = 203;
Teng-Hui Zhub109c882011-05-04 16:19:49 -070069 private static final int STOPFULLSCREEN = 204;
Teng-Hui Zhue4c89e32012-01-10 16:00:21 -080070 private static final int RESTORESTATE = 205;
Andrei Popescu64b86a12009-09-15 20:34:18 +010071
Andrei Popescu048eb3b2010-01-11 21:12:54 +000072 // Timer thread -> UI thread
73 private static final int TIMEUPDATE = 300;
74
Andrei Popescu290c34a2009-09-17 15:55:24 +010075 // The C++ MediaPlayerPrivateAndroid object.
76 int mNativePointer;
Andrei Popescu64b86a12009-09-15 20:34:18 +010077 // The handler for WebCore thread messages;
78 private Handler mWebCoreHandler;
Jonathan Dixon3c909522012-02-28 18:45:06 +000079 // The WebViewClassic instance that created this view.
80 private WebViewClassic mWebView;
Andrei Popescu64b86a12009-09-15 20:34:18 +010081 // The poster image to be shown when the video is not playing.
Andrei Popescu50c86232009-09-30 16:51:25 +010082 // This ref prevents the bitmap from being GC'ed.
83 private Bitmap mPoster;
Andrei Popescu64b86a12009-09-15 20:34:18 +010084 // The poster downloader.
85 private PosterDownloader mPosterDownloader;
Andrei Popescu290c34a2009-09-17 15:55:24 +010086 // The seek position.
87 private int mSeekPosition;
Andrei Popescu64b86a12009-09-15 20:34:18 +010088 // A helper class to control the playback. This executes on the UI thread!
89 private static final class VideoPlayer {
90 // The proxy that is currently playing (if any).
91 private static HTML5VideoViewProxy mCurrentProxy;
92 // The VideoView instance. This is a singleton for now, at least until
93 // http://b/issue?id=1973663 is fixed.
Teng-Hui Zhu661e8b12011-03-02 15:09:34 -080094 private static HTML5VideoView mHTML5VideoView;
Andrei Popescu048eb3b2010-01-11 21:12:54 +000095
Teng-Hui Zhu661e8b12011-03-02 15:09:34 -080096 private static boolean isVideoSelfEnded = false;
97 // By using the baseLayer and the current video Layer ID, we can
98 // identify the exact layer on the UI thread to use the SurfaceTexture.
99 private static int mBaseLayer = 0;
Andrei Popescu3c946a1a2009-07-03 08:20:53 +0100100
Teng-Hui Zhuf4d4e9e2011-03-30 14:39:56 -0700101 private static void setPlayerBuffering(boolean playerBuffering) {
102 mHTML5VideoView.setPlayerBuffering(playerBuffering);
103 }
104
Teng-Hui Zhu661e8b12011-03-02 15:09:34 -0800105 // Every time webView setBaseLayer, this will be called.
106 // When we found the Video layer, then we set the Surface Texture to it.
107 // Otherwise, we may want to delete the Surface Texture to save memory.
108 public static void setBaseLayer(int layer) {
Teng-Hui Zhuc3a28582012-05-31 17:36:17 -0700109 mBaseLayer = layer;
Teng-Hui Zhu10ab6542011-03-16 16:42:32 -0700110 // Don't do this for full screen mode.
111 if (mHTML5VideoView != null
Teng-Hui Zhu156f97b2012-07-09 15:54:32 -0700112 && !mHTML5VideoView.isFullScreenMode()
113 && !mHTML5VideoView.isReleased()) {
Teng-Hui Zhu661e8b12011-03-02 15:09:34 -0800114 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);
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);
Teng-Hui Zhu661e8b12011-03-02 15:09:34 -0800138 }
139 }
140
Teng-Hui Zhu10ab6542011-03-16 16:42:32 -0700141 public static void enterFullScreenVideo(int layerId, String url,
Jonathan Dixon3c909522012-02-28 18:45:06 +0000142 HTML5VideoViewProxy proxy, WebViewClassic webView) {
Teng-Hui Zhu10ab6542011-03-16 16:42:32 -0700143 // Save the inline video info and inherit it in the full screen
144 int savePosition = 0;
Teng-Hui Zhuc2b06d52012-05-09 14:13:52 -0700145 boolean canSkipPrepare = false;
Teng-Hui Zhu6a9586b2012-06-15 11:22:23 -0700146 boolean forceStart = false;
Teng-Hui Zhu10ab6542011-03-16 16:42:32 -0700147 if (mHTML5VideoView != null) {
Teng-Hui Zhu2b64c5a2012-03-19 13:56:49 -0700148 // We don't allow enter full screen mode while the previous
149 // full screen video hasn't finished yet.
150 if (!mHTML5VideoView.fullScreenExited() && mHTML5VideoView.isFullScreenMode()) {
151 Log.w(LOGTAG, "Try to reenter the full screen mode");
152 return;
153 }
Teng-Hui Zhu6a9586b2012-06-15 11:22:23 -0700154 int playerState = mHTML5VideoView.getCurrentState();
Teng-Hui Zhu10ab6542011-03-16 16:42:32 -0700155 // If we are playing the same video, then it is better to
156 // save the current position.
157 if (layerId == mHTML5VideoView.getVideoLayerId()) {
158 savePosition = mHTML5VideoView.getCurrentPosition();
Teng-Hui Zhuc2b06d52012-05-09 14:13:52 -0700159 canSkipPrepare = (playerState == HTML5VideoView.STATE_PREPARING
160 || playerState == HTML5VideoView.STATE_PREPARED
161 || playerState == HTML5VideoView.STATE_PLAYING)
162 && !mHTML5VideoView.isFullScreenMode();
Teng-Hui Zhu10ab6542011-03-16 16:42:32 -0700163 }
Teng-Hui Zhuc2b06d52012-05-09 14:13:52 -0700164 if (!canSkipPrepare) {
165 mHTML5VideoView.reset();
Teng-Hui Zhu6a9586b2012-06-15 11:22:23 -0700166 } else {
167 forceStart = playerState == HTML5VideoView.STATE_PREPARING
168 || playerState == HTML5VideoView.STATE_PLAYING;
Teng-Hui Zhuc2b06d52012-05-09 14:13:52 -0700169 }
Teng-Hui Zhu10ab6542011-03-16 16:42:32 -0700170 }
171 mHTML5VideoView = new HTML5VideoFullScreen(proxy.getContext(),
Teng-Hui Zhuc2b06d52012-05-09 14:13:52 -0700172 layerId, savePosition, canSkipPrepare);
Teng-Hui Zhu6a9586b2012-06-15 11:22:23 -0700173 mHTML5VideoView.setStartWhenPrepared(forceStart);
Teng-Hui Zhu10ab6542011-03-16 16:42:32 -0700174 mCurrentProxy = proxy;
Teng-Hui Zhu10ab6542011-03-16 16:42:32 -0700175 mHTML5VideoView.setVideoURI(url, mCurrentProxy);
Teng-Hui Zhu10ab6542011-03-16 16:42:32 -0700176 mHTML5VideoView.enterFullScreenVideoState(layerId, proxy, webView);
177 }
178
Teng-Hui Zhud7678a12012-01-16 15:33:09 -0800179 public static void exitFullScreenVideo(HTML5VideoViewProxy proxy,
Jonathan Dixon3c909522012-02-28 18:45:06 +0000180 WebViewClassic webView) {
Teng-Hui Zhud7678a12012-01-16 15:33:09 -0800181 if (!mHTML5VideoView.fullScreenExited() && mHTML5VideoView.isFullScreenMode()) {
182 WebChromeClient client = webView.getWebChromeClient();
183 if (client != null) {
184 client.onHideCustomView();
185 }
186 }
187 }
188
Teng-Hui Zhu661e8b12011-03-02 15:09:34 -0800189 // This is on the UI thread.
190 // When native tell Java to play, we need to check whether or not it is
191 // still the same video by using videoLayerId and treat it differently.
192 public static void play(String url, int time, HTML5VideoViewProxy proxy,
193 WebChromeClient client, int videoLayerId) {
194 int currentVideoLayerId = -1;
Teng-Hui Zhu10ab6542011-03-16 16:42:32 -0700195 boolean backFromFullScreenMode = false;
Teng-Hui Zhu10ab6542011-03-16 16:42:32 -0700196 if (mHTML5VideoView != null) {
197 currentVideoLayerId = mHTML5VideoView.getVideoLayerId();
Teng-Hui Zhu1a88acb2011-06-01 13:28:41 -0700198 backFromFullScreenMode = mHTML5VideoView.fullScreenExited();
Teng-Hui Zhu96fae5e2012-01-18 15:25:51 -0800199
200 // When playing video back to back in full screen mode,
201 // javascript will switch the src and call play.
202 // In this case, we can just reuse the same full screen view,
203 // and play the video after prepared.
204 if (mHTML5VideoView.isFullScreenMode()
205 && !backFromFullScreenMode
206 && currentVideoLayerId != videoLayerId
207 && mCurrentProxy != proxy) {
208 mCurrentProxy = proxy;
209 mHTML5VideoView.setStartWhenPrepared(true);
210 mHTML5VideoView.setVideoURI(url, proxy);
211 mHTML5VideoView.reprepareData(proxy);
212 return;
213 }
Teng-Hui Zhu10ab6542011-03-16 16:42:32 -0700214 }
215
Teng-Hui Zhuc3a28582012-05-31 17:36:17 -0700216 boolean skipPrepare = false;
217 boolean createInlineView = false;
Teng-Hui Zhu156f97b2012-07-09 15:54:32 -0700218 if (backFromFullScreenMode
219 && currentVideoLayerId == videoLayerId
220 && !mHTML5VideoView.isReleased()) {
Teng-Hui Zhuc3a28582012-05-31 17:36:17 -0700221 skipPrepare = true;
222 createInlineView = true;
223 } else if(backFromFullScreenMode
Teng-Hui Zhu3fafd392011-05-31 15:15:31 -0700224 || currentVideoLayerId != videoLayerId
Teng-Hui Zhuc3a28582012-05-31 17:36:17 -0700225 || HTML5VideoInline.surfaceTextureDeleted()) {
Teng-Hui Zhu661e8b12011-03-02 15:09:34 -0800226 // Here, we handle the case when switching to a new video,
227 // either inside a WebView or across WebViews
228 // For switching videos within a WebView or across the WebView,
229 // we need to pause the old one and re-create a new media player
230 // inside the HTML5VideoView.
231 if (mHTML5VideoView != null) {
Teng-Hui Zhu22954d42011-04-07 17:13:18 -0700232 if (!backFromFullScreenMode) {
233 mHTML5VideoView.pauseAndDispatch(mCurrentProxy);
234 }
Teng-Hui Zhuc2b06d52012-05-09 14:13:52 -0700235 mHTML5VideoView.reset();
Teng-Hui Zhu661e8b12011-03-02 15:09:34 -0800236 }
Teng-Hui Zhuc3a28582012-05-31 17:36:17 -0700237 createInlineView = true;
238 }
239 if (createInlineView) {
Teng-Hui Zhu661e8b12011-03-02 15:09:34 -0800240 mCurrentProxy = proxy;
Teng-Hui Zhuc3a28582012-05-31 17:36:17 -0700241 mHTML5VideoView = new HTML5VideoInline(videoLayerId, time, skipPrepare);
Teng-Hui Zhu661e8b12011-03-02 15:09:34 -0800242
Teng-Hui Zhu10ab6542011-03-16 16:42:32 -0700243 mHTML5VideoView.setVideoURI(url, mCurrentProxy);
244 mHTML5VideoView.prepareDataAndDisplayMode(proxy);
Teng-Hui Zhuc3a28582012-05-31 17:36:17 -0700245 return;
246 }
247
248 if (mCurrentProxy == proxy) {
Teng-Hui Zhu661e8b12011-03-02 15:09:34 -0800249 // Here, we handle the case when we keep playing with one video
250 if (!mHTML5VideoView.isPlaying()) {
251 mHTML5VideoView.seekTo(time);
252 mHTML5VideoView.start();
253 }
254 } else if (mCurrentProxy != null) {
Teng-Hui Zhu10ab6542011-03-16 16:42:32 -0700255 // Some other video is already playing. Notify the caller that
256 // its playback ended.
Andrei Popescuc4fbceb2010-04-14 13:30:23 +0100257 proxy.dispatchOnEnded();
Andrei Popescu3c946a1a2009-07-03 08:20:53 +0100258 }
Andrei Popescu3c946a1a2009-07-03 08:20:53 +0100259 }
Andrei Popescu290c34a2009-09-17 15:55:24 +0100260
Andrei Popescu048eb3b2010-01-11 21:12:54 +0000261 public static boolean isPlaying(HTML5VideoViewProxy proxy) {
Teng-Hui Zhu661e8b12011-03-02 15:09:34 -0800262 return (mCurrentProxy == proxy && mHTML5VideoView != null
263 && mHTML5VideoView.isPlaying());
Andrei Popescu048eb3b2010-01-11 21:12:54 +0000264 }
265
266 public static int getCurrentPosition() {
267 int currentPosMs = 0;
Teng-Hui Zhu661e8b12011-03-02 15:09:34 -0800268 if (mHTML5VideoView != null) {
269 currentPosMs = mHTML5VideoView.getCurrentPosition();
Andrei Popescu048eb3b2010-01-11 21:12:54 +0000270 }
271 return currentPosMs;
272 }
273
Andrei Popescu290c34a2009-09-17 15:55:24 +0100274 public static void seek(int time, HTML5VideoViewProxy proxy) {
Teng-Hui Zhu661e8b12011-03-02 15:09:34 -0800275 if (mCurrentProxy == proxy && time >= 0 && mHTML5VideoView != null) {
276 mHTML5VideoView.seekTo(time);
Andrei Popescu290c34a2009-09-17 15:55:24 +0100277 }
278 }
279
280 public static void pause(HTML5VideoViewProxy proxy) {
Teng-Hui Zhu661e8b12011-03-02 15:09:34 -0800281 if (mCurrentProxy == proxy && mHTML5VideoView != null) {
282 mHTML5VideoView.pause();
Andrei Popescu290c34a2009-09-17 15:55:24 +0100283 }
284 }
Andrei Popescubf385d72009-09-18 18:59:52 +0100285
286 public static void onPrepared() {
Teng-Hui Zhue4c89e32012-01-10 16:00:21 -0800287 if (!mHTML5VideoView.isFullScreenMode()) {
Teng-Hui Zhu05049672011-04-06 18:09:27 -0700288 mHTML5VideoView.start();
289 }
Teng-Hui Zhu661e8b12011-03-02 15:09:34 -0800290 }
291
292 public static void end() {
Teng-Hui Zhu4dd9dc82012-05-10 17:20:19 -0700293 mHTML5VideoView.showControllerInFullScreen();
Teng-Hui Zhu661e8b12011-03-02 15:09:34 -0800294 if (mCurrentProxy != null) {
295 if (isVideoSelfEnded)
296 mCurrentProxy.dispatchOnEnded();
297 else
298 mCurrentProxy.dispatchOnPaused();
299 }
300 isVideoSelfEnded = false;
Andrei Popescubf385d72009-09-18 18:59:52 +0100301 }
Andrei Popescu290c34a2009-09-17 15:55:24 +0100302 }
303
304 // A bunch event listeners for our VideoView
305 // MediaPlayer.OnPreparedListener
306 public void onPrepared(MediaPlayer mp) {
Andrei Popescubf385d72009-09-18 18:59:52 +0100307 VideoPlayer.onPrepared();
Andrei Popescu290c34a2009-09-17 15:55:24 +0100308 Message msg = Message.obtain(mWebCoreHandler, PREPARED);
309 Map<String, Object> map = new HashMap<String, Object>();
310 map.put("dur", new Integer(mp.getDuration()));
311 map.put("width", new Integer(mp.getVideoWidth()));
312 map.put("height", new Integer(mp.getVideoHeight()));
313 msg.obj = map;
314 mWebCoreHandler.sendMessage(msg);
315 }
316
317 // MediaPlayer.OnCompletionListener;
318 public void onCompletion(MediaPlayer mp) {
Andrei Popescuc4fbceb2010-04-14 13:30:23 +0100319 // The video ended by itself, so we need to
320 // send a message to the UI thread to dismiss
321 // the video view and to return to the WebView.
Shimeng (Simon) Wang43aaa2d2010-10-18 16:25:59 -0700322 // arg1 == 1 means the video ends by itself.
323 sendMessage(obtainMessage(ENDED, 1, 0));
Andrei Popescu290c34a2009-09-17 15:55:24 +0100324 }
325
Andrei Popescu50c86232009-09-30 16:51:25 +0100326 // MediaPlayer.OnErrorListener
327 public boolean onError(MediaPlayer mp, int what, int extra) {
328 sendMessage(obtainMessage(ERROR));
329 return false;
330 }
331
Andrei Popescuc4fbceb2010-04-14 13:30:23 +0100332 public void dispatchOnEnded() {
Andrei Popescu290c34a2009-09-17 15:55:24 +0100333 Message msg = Message.obtain(mWebCoreHandler, ENDED);
334 mWebCoreHandler.sendMessage(msg);
Andrei Popescu64b86a12009-09-15 20:34:18 +0100335 }
Andrei Popescu3c946a1a2009-07-03 08:20:53 +0100336
Shimeng (Simon) Wang43aaa2d2010-10-18 16:25:59 -0700337 public void dispatchOnPaused() {
Teng-Hui Zhub109c882011-05-04 16:19:49 -0700338 Message msg = Message.obtain(mWebCoreHandler, PAUSED);
339 mWebCoreHandler.sendMessage(msg);
340 }
341
Teng-Hui Zhuc3a28582012-05-31 17:36:17 -0700342 public void dispatchOnStopFullScreen(boolean stillPlaying) {
Teng-Hui Zhub109c882011-05-04 16:19:49 -0700343 Message msg = Message.obtain(mWebCoreHandler, STOPFULLSCREEN);
Teng-Hui Zhuc3a28582012-05-31 17:36:17 -0700344 msg.arg1 = stillPlaying ? 1 : 0;
Teng-Hui Zhub109c882011-05-04 16:19:49 -0700345 mWebCoreHandler.sendMessage(msg);
Shimeng (Simon) Wang43aaa2d2010-10-18 16:25:59 -0700346 }
347
Teng-Hui Zhue4c89e32012-01-10 16:00:21 -0800348 public void dispatchOnRestoreState() {
349 Message msg = Message.obtain(mWebCoreHandler, RESTORESTATE);
350 mWebCoreHandler.sendMessage(msg);
351 }
352
Andrei Popescu048eb3b2010-01-11 21:12:54 +0000353 public void onTimeupdate() {
354 sendMessage(obtainMessage(TIMEUPDATE));
355 }
356
Teng-Hui Zhu661e8b12011-03-02 15:09:34 -0800357 // When there is a frame ready from surface texture, we should tell WebView
358 // to refresh.
359 @Override
360 public void onFrameAvailable(SurfaceTexture surfaceTexture) {
361 // TODO: This should support partial invalidation too.
362 mWebView.invalidate();
363 }
364
Andrei Popescu048eb3b2010-01-11 21:12:54 +0000365 // Handler for the messages from WebCore or Timer thread to the UI thread.
Andrei Popescu64b86a12009-09-15 20:34:18 +0100366 @Override
367 public void handleMessage(Message msg) {
368 // This executes on the UI thread.
369 switch (msg.what) {
Andrei Popescu64b86a12009-09-15 20:34:18 +0100370 case PLAY: {
371 String url = (String) msg.obj;
372 WebChromeClient client = mWebView.getWebChromeClient();
Teng-Hui Zhu661e8b12011-03-02 15:09:34 -0800373 int videoLayerID = msg.arg1;
Andrei Popescu64b86a12009-09-15 20:34:18 +0100374 if (client != null) {
Teng-Hui Zhu661e8b12011-03-02 15:09:34 -0800375 VideoPlayer.play(url, mSeekPosition, this, client, videoLayerID);
Andrei Popescu64b86a12009-09-15 20:34:18 +0100376 }
377 break;
378 }
Teng-Hui Zhuc3a28582012-05-31 17:36:17 -0700379 case ENTER_FULLSCREEN:{
380 String url = (String) msg.obj;
381 WebChromeClient client = mWebView.getWebChromeClient();
382 int videoLayerID = msg.arg1;
383 if (client != null) {
384 VideoPlayer.enterFullScreenVideo(videoLayerID, url, this, mWebView);
385 }
386 break;
387 }
Andrei Popescu290c34a2009-09-17 15:55:24 +0100388 case SEEK: {
389 Integer time = (Integer) msg.obj;
390 mSeekPosition = time;
391 VideoPlayer.seek(mSeekPosition, this);
392 break;
393 }
394 case PAUSE: {
395 VideoPlayer.pause(this);
396 break;
397 }
Andrei Popescu46a83b42009-10-23 13:49:46 +0100398 case ENDED:
Shimeng (Simon) Wang43aaa2d2010-10-18 16:25:59 -0700399 if (msg.arg1 == 1)
400 VideoPlayer.isVideoSelfEnded = true;
Teng-Hui Zhu661e8b12011-03-02 15:09:34 -0800401 VideoPlayer.end();
402 break;
Andrei Popescu50c86232009-09-30 16:51:25 +0100403 case ERROR: {
404 WebChromeClient client = mWebView.getWebChromeClient();
405 if (client != null) {
406 client.onHideCustomView();
407 }
408 break;
409 }
410 case LOAD_DEFAULT_POSTER: {
411 WebChromeClient client = mWebView.getWebChromeClient();
412 if (client != null) {
413 doSetPoster(client.getDefaultVideoPoster());
414 }
415 break;
416 }
Andrei Popescu048eb3b2010-01-11 21:12:54 +0000417 case TIMEUPDATE: {
418 if (VideoPlayer.isPlaying(this)) {
419 sendTimeupdate();
420 }
421 break;
422 }
Teng-Hui Zhuc0fccd12011-03-29 10:35:11 -0700423 case BUFFERING_START: {
Teng-Hui Zhuf4d4e9e2011-03-30 14:39:56 -0700424 VideoPlayer.setPlayerBuffering(true);
Teng-Hui Zhuc0fccd12011-03-29 10:35:11 -0700425 break;
426 }
427 case BUFFERING_END: {
Teng-Hui Zhuf4d4e9e2011-03-30 14:39:56 -0700428 VideoPlayer.setPlayerBuffering(false);
Teng-Hui Zhuc0fccd12011-03-29 10:35:11 -0700429 break;
430 }
Andrei Popescu64b86a12009-09-15 20:34:18 +0100431 }
432 }
433
Andrei Popescu64b86a12009-09-15 20:34:18 +0100434 // Everything below this comment executes on the WebCore thread, except for
435 // the EventHandler methods, which are called on the network thread.
436
437 // A helper class that knows how to download posters
438 private static final class PosterDownloader implements EventHandler {
439 // The request queue. This is static as we have one queue for all posters.
440 private static RequestQueue mRequestQueue;
441 private static int mQueueRefCount = 0;
442 // The poster URL
Ben Murdoch42509792011-02-18 12:34:35 +0000443 private URL mUrl;
Andrei Popescu64b86a12009-09-15 20:34:18 +0100444 // The proxy we're doing this for.
445 private final HTML5VideoViewProxy mProxy;
446 // The poster bytes. We only touch this on the network thread.
447 private ByteArrayOutputStream mPosterBytes;
448 // The request handle. We only touch this on the WebCore thread.
449 private RequestHandle mRequestHandle;
450 // The response status code.
451 private int mStatusCode;
452 // The response headers.
453 private Headers mHeaders;
454 // The handler to handle messages on the WebCore thread.
455 private Handler mHandler;
456
457 public PosterDownloader(String url, HTML5VideoViewProxy proxy) {
Ben Murdoch42509792011-02-18 12:34:35 +0000458 try {
459 mUrl = new URL(url);
460 } catch (MalformedURLException e) {
461 mUrl = null;
462 }
Andrei Popescu64b86a12009-09-15 20:34:18 +0100463 mProxy = proxy;
464 mHandler = new Handler();
465 }
466 // Start the download. Called on WebCore thread.
467 public void start() {
468 retainQueue();
Ben Murdoch42509792011-02-18 12:34:35 +0000469
470 if (mUrl == null) {
471 return;
472 }
473
474 // Only support downloading posters over http/https.
475 // FIXME: Add support for other schemes. WebKit seems able to load
476 // posters over other schemes e.g. file://, but gets the dimensions wrong.
477 String protocol = mUrl.getProtocol();
478 if ("http".equals(protocol) || "https".equals(protocol)) {
479 mRequestHandle = mRequestQueue.queueRequest(mUrl.toString(), "GET", null,
480 this, null, 0);
481 }
Andrei Popescu64b86a12009-09-15 20:34:18 +0100482 }
483 // Cancel the download if active and release the queue. Called on WebCore thread.
484 public void cancelAndReleaseQueue() {
485 if (mRequestHandle != null) {
486 mRequestHandle.cancel();
487 mRequestHandle = null;
488 }
489 releaseQueue();
490 }
491 // EventHandler methods. Executed on the network thread.
492 public void status(int major_version,
493 int minor_version,
494 int code,
495 String reason_phrase) {
496 mStatusCode = code;
Andrei Popescu3c946a1a2009-07-03 08:20:53 +0100497 }
498
Andrei Popescu64b86a12009-09-15 20:34:18 +0100499 public void headers(Headers headers) {
500 mHeaders = headers;
501 }
502
503 public void data(byte[] data, int len) {
504 if (mPosterBytes == null) {
505 mPosterBytes = new ByteArrayOutputStream();
506 }
507 mPosterBytes.write(data, 0, len);
508 }
509
510 public void endData() {
511 if (mStatusCode == 200) {
512 if (mPosterBytes.size() > 0) {
513 Bitmap poster = BitmapFactory.decodeByteArray(
514 mPosterBytes.toByteArray(), 0, mPosterBytes.size());
Andrei Popescu50c86232009-09-30 16:51:25 +0100515 mProxy.doSetPoster(poster);
Andrei Popescu64b86a12009-09-15 20:34:18 +0100516 }
517 cleanup();
518 } else if (mStatusCode >= 300 && mStatusCode < 400) {
519 // We have a redirect.
Ben Murdoch42509792011-02-18 12:34:35 +0000520 try {
521 mUrl = new URL(mHeaders.getLocation());
522 } catch (MalformedURLException e) {
523 mUrl = null;
524 }
Andrei Popescu64b86a12009-09-15 20:34:18 +0100525 if (mUrl != null) {
526 mHandler.post(new Runnable() {
527 public void run() {
528 if (mRequestHandle != null) {
Ben Murdoch42509792011-02-18 12:34:35 +0000529 mRequestHandle.setupRedirect(mUrl.toString(), mStatusCode,
Andrei Popescu64b86a12009-09-15 20:34:18 +0100530 new HashMap<String, String>());
531 }
532 }
533 });
534 }
535 }
536 }
537
538 public void certificate(SslCertificate certificate) {
539 // Don't care.
540 }
541
542 public void error(int id, String description) {
543 cleanup();
544 }
545
546 public boolean handleSslErrorRequest(SslError error) {
547 // Don't care. If this happens, data() will never be called so
548 // mPosterBytes will never be created, so no need to call cleanup.
549 return false;
550 }
551 // Tears down the poster bytes stream. Called on network thread.
552 private void cleanup() {
553 if (mPosterBytes != null) {
554 try {
555 mPosterBytes.close();
556 } catch (IOException ignored) {
557 // Ignored.
558 } finally {
559 mPosterBytes = null;
560 }
561 }
562 }
563
564 // Queue management methods. Called on WebCore thread.
565 private void retainQueue() {
566 if (mRequestQueue == null) {
567 mRequestQueue = new RequestQueue(mProxy.getContext());
568 }
569 mQueueRefCount++;
570 }
571
572 private void releaseQueue() {
573 if (mQueueRefCount == 0) {
574 return;
575 }
576 if (--mQueueRefCount == 0) {
577 mRequestQueue.shutdown();
578 mRequestQueue = null;
579 }
Andrei Popescu3c946a1a2009-07-03 08:20:53 +0100580 }
581 }
Andrei Popescu6fa29582009-06-19 14:54:09 +0100582
583 /**
584 * Private constructor.
Andrei Popescu290c34a2009-09-17 15:55:24 +0100585 * @param webView is the WebView that hosts the video.
586 * @param nativePtr is the C++ pointer to the MediaPlayerPrivate object.
Andrei Popescu6fa29582009-06-19 14:54:09 +0100587 */
Jonathan Dixon3c909522012-02-28 18:45:06 +0000588 private HTML5VideoViewProxy(WebViewClassic webView, int nativePtr) {
Andrei Popescu6fa29582009-06-19 14:54:09 +0100589 // This handler is for the main (UI) thread.
590 super(Looper.getMainLooper());
Andrei Popescu3c946a1a2009-07-03 08:20:53 +0100591 // Save the WebView object.
592 mWebView = webView;
Teng-Hui Zhu661e8b12011-03-02 15:09:34 -0800593 // Pass Proxy into webview, such that every time we have a setBaseLayer
594 // call, we tell this Proxy to call the native to update the layer tree
595 // for the Video Layer's surface texture info
596 mWebView.setHTML5VideoViewProxy(this);
Andrei Popescu290c34a2009-09-17 15:55:24 +0100597 // Save the native ptr
598 mNativePointer = nativePtr;
Andrei Popescu64b86a12009-09-15 20:34:18 +0100599 // create the message handler for this thread
600 createWebCoreHandler();
Andrei Popescu6fa29582009-06-19 14:54:09 +0100601 }
602
Andrei Popescu64b86a12009-09-15 20:34:18 +0100603 private void createWebCoreHandler() {
604 mWebCoreHandler = new Handler() {
605 @Override
606 public void handleMessage(Message msg) {
607 switch (msg.what) {
Andrei Popescu290c34a2009-09-17 15:55:24 +0100608 case PREPARED: {
609 Map<String, Object> map = (Map<String, Object>) msg.obj;
610 Integer duration = (Integer) map.get("dur");
611 Integer width = (Integer) map.get("width");
612 Integer height = (Integer) map.get("height");
613 nativeOnPrepared(duration.intValue(), width.intValue(),
614 height.intValue(), mNativePointer);
615 break;
616 }
617 case ENDED:
Ben Murdochff19d192011-01-17 18:08:56 +0000618 mSeekPosition = 0;
Andrei Popescu290c34a2009-09-17 15:55:24 +0100619 nativeOnEnded(mNativePointer);
620 break;
Shimeng (Simon) Wang43aaa2d2010-10-18 16:25:59 -0700621 case PAUSED:
622 nativeOnPaused(mNativePointer);
623 break;
Andrei Popescu50c86232009-09-30 16:51:25 +0100624 case POSTER_FETCHED:
625 Bitmap poster = (Bitmap) msg.obj;
626 nativeOnPosterFetched(poster, mNativePointer);
627 break;
Andrei Popescu048eb3b2010-01-11 21:12:54 +0000628 case TIMEUPDATE:
629 nativeOnTimeupdate(msg.arg1, mNativePointer);
630 break;
Teng-Hui Zhub109c882011-05-04 16:19:49 -0700631 case STOPFULLSCREEN:
Teng-Hui Zhuc3a28582012-05-31 17:36:17 -0700632 nativeOnStopFullscreen(msg.arg1, mNativePointer);
Teng-Hui Zhub109c882011-05-04 16:19:49 -0700633 break;
Teng-Hui Zhue4c89e32012-01-10 16:00:21 -0800634 case RESTORESTATE:
635 nativeOnRestoreState(mNativePointer);
636 break;
Andrei Popescu3c946a1a2009-07-03 08:20:53 +0100637 }
Andrei Popescu64b86a12009-09-15 20:34:18 +0100638 }
639 };
Andrei Popescu6fa29582009-06-19 14:54:09 +0100640 }
641
Andrei Popescu64b86a12009-09-15 20:34:18 +0100642 private void doSetPoster(Bitmap poster) {
643 if (poster == null) {
644 return;
645 }
Andrei Popescu50c86232009-09-30 16:51:25 +0100646 // Save a ref to the bitmap and send it over to the WebCore thread.
647 mPoster = poster;
648 Message msg = Message.obtain(mWebCoreHandler, POSTER_FETCHED);
649 msg.obj = poster;
650 mWebCoreHandler.sendMessage(msg);
Andrei Popescu64b86a12009-09-15 20:34:18 +0100651 }
652
Andrei Popescu048eb3b2010-01-11 21:12:54 +0000653 private void sendTimeupdate() {
654 Message msg = Message.obtain(mWebCoreHandler, TIMEUPDATE);
655 msg.arg1 = VideoPlayer.getCurrentPosition();
656 mWebCoreHandler.sendMessage(msg);
657 }
658
Andrei Popescu64b86a12009-09-15 20:34:18 +0100659 public Context getContext() {
660 return mWebView.getContext();
661 }
662
663 // The public methods below are all called from WebKit only.
Andrei Popescu6fa29582009-06-19 14:54:09 +0100664 /**
665 * Play a video stream.
666 * @param url is the URL of the video stream.
Andrei Popescu6fa29582009-06-19 14:54:09 +0100667 */
Teng-Hui Zhu661e8b12011-03-02 15:09:34 -0800668 public void play(String url, int position, int videoLayerID) {
Andrei Popescu64b86a12009-09-15 20:34:18 +0100669 if (url == null) {
670 return;
671 }
Ben Murdochff19d192011-01-17 18:08:56 +0000672
673 if (position > 0) {
674 seek(position);
675 }
Andrei Popescu6fa29582009-06-19 14:54:09 +0100676 Message message = obtainMessage(PLAY);
Teng-Hui Zhu661e8b12011-03-02 15:09:34 -0800677 message.arg1 = videoLayerID;
Andrei Popescu64b86a12009-09-15 20:34:18 +0100678 message.obj = url;
Andrei Popescu6fa29582009-06-19 14:54:09 +0100679 sendMessage(message);
680 }
681
Andrei Popescu64b86a12009-09-15 20:34:18 +0100682 /**
Teng-Hui Zhuc3a28582012-05-31 17:36:17 -0700683 * Play a video stream in full screen mode.
684 * @param url is the URL of the video stream.
685 */
686 public void enterFullscreenForVideoLayer(String url, int videoLayerID) {
687 if (url == null) {
688 return;
689 }
690
691 Message message = obtainMessage(ENTER_FULLSCREEN);
692 message.arg1 = videoLayerID;
693 message.obj = url;
694 sendMessage(message);
695 }
696
697 /**
Andrei Popescu290c34a2009-09-17 15:55:24 +0100698 * Seek into the video stream.
699 * @param time is the position in the video stream.
700 */
701 public void seek(int time) {
702 Message message = obtainMessage(SEEK);
703 message.obj = new Integer(time);
704 sendMessage(message);
705 }
706
707 /**
708 * Pause the playback.
709 */
710 public void pause() {
711 Message message = obtainMessage(PAUSE);
712 sendMessage(message);
713 }
714
715 /**
Andrei Popescu50c86232009-09-30 16:51:25 +0100716 * Tear down this proxy object.
Andrei Popescu64b86a12009-09-15 20:34:18 +0100717 */
Andrei Popescu50c86232009-09-30 16:51:25 +0100718 public void teardown() {
Andrei Popescu64b86a12009-09-15 20:34:18 +0100719 // This is called by the C++ MediaPlayerPrivate dtor.
720 // Cancel any active poster download.
721 if (mPosterDownloader != null) {
722 mPosterDownloader.cancelAndReleaseQueue();
723 }
Andrei Popescu50c86232009-09-30 16:51:25 +0100724 mNativePointer = 0;
Andrei Popescu64b86a12009-09-15 20:34:18 +0100725 }
726
727 /**
728 * Load the poster image.
729 * @param url is the URL of the poster image.
730 */
731 public void loadPoster(String url) {
732 if (url == null) {
Andrei Popescu50c86232009-09-30 16:51:25 +0100733 Message message = obtainMessage(LOAD_DEFAULT_POSTER);
734 sendMessage(message);
Andrei Popescu64b86a12009-09-15 20:34:18 +0100735 return;
736 }
737 // Cancel any active poster download.
738 if (mPosterDownloader != null) {
739 mPosterDownloader.cancelAndReleaseQueue();
740 }
741 // Load the poster asynchronously
742 mPosterDownloader = new PosterDownloader(url, this);
743 mPosterDownloader.start();
Patrick Scott0a5ce012009-07-02 08:56:10 -0400744 }
745
Teng-Hui Zhu10ab6542011-03-16 16:42:32 -0700746 // These three function are called from UI thread only by WebView.
Teng-Hui Zhu661e8b12011-03-02 15:09:34 -0800747 public void setBaseLayer(int layer) {
748 VideoPlayer.setBaseLayer(layer);
749 }
750
751 public void pauseAndDispatch() {
752 VideoPlayer.pauseAndDispatch();
753 }
Teng-Hui Zhu10ab6542011-03-16 16:42:32 -0700754
755 public void enterFullScreenVideo(int layerId, String url) {
756 VideoPlayer.enterFullScreenVideo(layerId, url, this, mWebView);
757 }
758
Teng-Hui Zhud7678a12012-01-16 15:33:09 -0800759 public void exitFullScreenVideo() {
760 VideoPlayer.exitFullScreenVideo(this, mWebView);
761 }
762
Andrei Popescu6fa29582009-06-19 14:54:09 +0100763 /**
Andrei Popescu3c946a1a2009-07-03 08:20:53 +0100764 * The factory for HTML5VideoViewProxy instances.
Andrei Popescu6fa29582009-06-19 14:54:09 +0100765 * @param webViewCore is the WebViewCore that is requesting the proxy.
766 *
Andrei Popescu3c946a1a2009-07-03 08:20:53 +0100767 * @return a new HTML5VideoViewProxy object.
Andrei Popescu6fa29582009-06-19 14:54:09 +0100768 */
Andrei Popescu290c34a2009-09-17 15:55:24 +0100769 public static HTML5VideoViewProxy getInstance(WebViewCore webViewCore, int nativePtr) {
Jonathan Dixona6c4d8e2012-03-08 17:55:31 +0000770 return new HTML5VideoViewProxy(webViewCore.getWebViewClassic(), nativePtr);
Andrei Popescu6fa29582009-06-19 14:54:09 +0100771 }
Andrei Popescu290c34a2009-09-17 15:55:24 +0100772
Jonathan Dixon3c909522012-02-28 18:45:06 +0000773 /* package */ WebViewClassic getWebView() {
Ben Murdoch1708ad52010-11-11 15:56:16 +0000774 return mWebView;
775 }
776
Andrei Popescu290c34a2009-09-17 15:55:24 +0100777 private native void nativeOnPrepared(int duration, int width, int height, int nativePointer);
778 private native void nativeOnEnded(int nativePointer);
Shimeng (Simon) Wang43aaa2d2010-10-18 16:25:59 -0700779 private native void nativeOnPaused(int nativePointer);
Andrei Popescu50c86232009-09-30 16:51:25 +0100780 private native void nativeOnPosterFetched(Bitmap poster, int nativePointer);
Andrei Popescu048eb3b2010-01-11 21:12:54 +0000781 private native void nativeOnTimeupdate(int position, int nativePointer);
Teng-Hui Zhuc3a28582012-05-31 17:36:17 -0700782 private native void nativeOnStopFullscreen(int stillPlaying, int nativePointer);
Teng-Hui Zhue4c89e32012-01-10 16:00:21 -0800783 private native void nativeOnRestoreState(int nativePointer);
Teng-Hui Zhu661e8b12011-03-02 15:09:34 -0800784 private native static boolean nativeSendSurfaceTexture(SurfaceTexture texture,
785 int baseLayer, int videoLayerId, int textureName,
Teng-Hui Zhu265db322011-03-18 14:56:10 -0700786 int playerState);
Teng-Hui Zhuc0fccd12011-03-29 10:35:11 -0700787
788 @Override
789 public boolean onInfo(MediaPlayer mp, int what, int extra) {
790 if (what == MediaPlayer.MEDIA_INFO_BUFFERING_START) {
791 sendMessage(obtainMessage(BUFFERING_START, what, extra));
792 } else if (what == MediaPlayer.MEDIA_INFO_BUFFERING_END) {
793 sendMessage(obtainMessage(BUFFERING_END, what, extra));
794 }
795 return false;
796 }
Andrei Popescu6fa29582009-06-19 14:54:09 +0100797}