HTML5VideoView.java revision 778029e5c62c414c958d2bc15e35c301a92c95cd
1/* 2 * Copyright (C) 2012 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.graphics.SurfaceTexture; 20import android.media.MediaPlayer; 21import android.net.Uri; 22import android.util.Log; 23import android.view.SurfaceView; 24import android.webkit.HTML5VideoViewProxy; 25import java.io.IOException; 26import java.util.HashMap; 27import java.util.Map; 28import java.util.Timer; 29import java.util.TimerTask; 30 31/** 32 * @hide This is only used by the browser 33 */ 34public class HTML5VideoView implements MediaPlayer.OnPreparedListener { 35 36 protected static final String LOGTAG = "HTML5VideoView"; 37 38 protected static final String COOKIE = "Cookie"; 39 protected static final String HIDE_URL_LOGS = "x-hide-urls-from-log"; 40 41 // For handling the seekTo before prepared, we need to know whether or not 42 // the video is prepared. Therefore, we differentiate the state between 43 // prepared and not prepared. 44 // When the video is not prepared, we will have to save the seekTo time, 45 // and use it when prepared to play. 46 // NOTE: these values are in sync with VideoLayerAndroid.h in webkit side. 47 // Please keep them in sync when changed. 48 static final int STATE_INITIALIZED = 0; 49 static final int STATE_PREPARING = 1; 50 static final int STATE_PREPARED = 2; 51 static final int STATE_PLAYING = 3; 52 static final int STATE_RESETTED = 4; 53 static final int STATE_RELEASED = 5; 54 55 protected HTML5VideoViewProxy mProxy; 56 57 // Save the seek time when not prepared. This can happen when switching 58 // video besides initial load. 59 protected int mSaveSeekTime; 60 61 // This is used to find the VideoLayer on the native side. 62 protected int mVideoLayerId; 63 64 // Given the fact we only have one SurfaceTexture, we cannot support multiple 65 // player at the same time. We may recreate a new one and abandon the old 66 // one at transition time. 67 protected static MediaPlayer mPlayer = null; 68 protected static int mCurrentState = -1; 69 70 // We need to save such info. 71 protected Uri mUri; 72 protected Map<String, String> mHeaders; 73 74 // The timer for timeupate events. 75 // See http://www.whatwg.org/specs/web-apps/current-work/#event-media-timeupdate 76 protected static Timer mTimer; 77 78 protected boolean mPauseDuringPreparing; 79 80 // The spec says the timer should fire every 250 ms or less. 81 private static final int TIMEUPDATE_PERIOD = 250; // ms 82 private boolean mSkipPrepare = false; 83 84 // common Video control FUNCTIONS: 85 public void start() { 86 if (mCurrentState == STATE_PREPARED) { 87 // When replaying the same video, there is no onPrepared call. 88 // Therefore, the timer should be set up here. 89 if (mTimer == null) 90 { 91 mTimer = new Timer(); 92 mTimer.schedule(new TimeupdateTask(mProxy), TIMEUPDATE_PERIOD, 93 TIMEUPDATE_PERIOD); 94 } 95 mPlayer.start(); 96 setPlayerBuffering(false); 97 } 98 } 99 100 public void pause() { 101 if (isPlaying()) { 102 mPlayer.pause(); 103 } else if (mCurrentState == STATE_PREPARING) { 104 mPauseDuringPreparing = true; 105 } 106 // Delete the Timer to stop it since there is no stop call. 107 if (mTimer != null) { 108 mTimer.purge(); 109 mTimer.cancel(); 110 mTimer = null; 111 } 112 } 113 114 public int getDuration() { 115 if (mCurrentState == STATE_PREPARED) { 116 return mPlayer.getDuration(); 117 } else { 118 return -1; 119 } 120 } 121 122 public int getCurrentPosition() { 123 if (mCurrentState == STATE_PREPARED) { 124 return mPlayer.getCurrentPosition(); 125 } 126 return 0; 127 } 128 129 public void seekTo(int pos) { 130 if (mCurrentState == STATE_PREPARED) 131 mPlayer.seekTo(pos); 132 else 133 mSaveSeekTime = pos; 134 } 135 136 public boolean isPlaying() { 137 if (mCurrentState == STATE_PREPARED) { 138 return mPlayer.isPlaying(); 139 } else { 140 return false; 141 } 142 } 143 144 public void reset() { 145 if (mCurrentState < STATE_RESETTED) { 146 mPlayer.reset(); 147 } 148 mCurrentState = STATE_RESETTED; 149 } 150 151 public void stopPlayback() { 152 if (mCurrentState == STATE_PREPARED) { 153 mPlayer.stop(); 154 } 155 } 156 157 public static void release() { 158 if (mPlayer != null && mCurrentState != STATE_RELEASED) { 159 mPlayer.release(); 160 mPlayer = null; 161 } 162 mCurrentState = STATE_RELEASED; 163 } 164 165 public boolean isReleased() { 166 return mCurrentState == STATE_RELEASED; 167 } 168 169 public boolean getPauseDuringPreparing() { 170 return mPauseDuringPreparing; 171 } 172 173 // Every time we start a new Video, we create a VideoView and a MediaPlayer 174 public void init(int videoLayerId, int position, boolean skipPrepare) { 175 if (mPlayer == null) { 176 mPlayer = new MediaPlayer(); 177 mCurrentState = STATE_INITIALIZED; 178 } 179 mSkipPrepare = skipPrepare; 180 // If we want to skip the prepare, then we keep the state. 181 if (!mSkipPrepare) { 182 mCurrentState = STATE_INITIALIZED; 183 } 184 mProxy = null; 185 mVideoLayerId = videoLayerId; 186 mSaveSeekTime = position; 187 mTimer = null; 188 mPauseDuringPreparing = false; 189 } 190 191 protected HTML5VideoView() { 192 } 193 194 protected static Map<String, String> generateHeaders(String url, 195 HTML5VideoViewProxy proxy) { 196 boolean isPrivate = proxy.getWebView().isPrivateBrowsingEnabled(); 197 String cookieValue = CookieManager.getInstance().getCookie(url, isPrivate); 198 Map<String, String> headers = new HashMap<String, String>(); 199 if (cookieValue != null) { 200 headers.put(COOKIE, cookieValue); 201 } 202 if (isPrivate) { 203 headers.put(HIDE_URL_LOGS, "true"); 204 } 205 206 return headers; 207 } 208 209 public void setVideoURI(String uri, HTML5VideoViewProxy proxy) { 210 // When switching players, surface texture will be reused. 211 mUri = Uri.parse(uri); 212 mHeaders = generateHeaders(uri, proxy); 213 } 214 215 // Listeners setup FUNCTIONS: 216 public void setOnCompletionListener(HTML5VideoViewProxy proxy) { 217 mPlayer.setOnCompletionListener(proxy); 218 } 219 220 public void setOnErrorListener(HTML5VideoViewProxy proxy) { 221 mPlayer.setOnErrorListener(proxy); 222 } 223 224 public void setOnPreparedListener(HTML5VideoViewProxy proxy) { 225 mProxy = proxy; 226 mPlayer.setOnPreparedListener(this); 227 } 228 229 public void setOnInfoListener(HTML5VideoViewProxy proxy) { 230 mPlayer.setOnInfoListener(proxy); 231 } 232 233 public void prepareDataCommon(HTML5VideoViewProxy proxy) { 234 if (!mSkipPrepare) { 235 try { 236 mPlayer.reset(); 237 mPlayer.setDataSource(proxy.getContext(), mUri, mHeaders); 238 mPlayer.prepareAsync(); 239 } catch (IllegalArgumentException e) { 240 e.printStackTrace(); 241 } catch (IllegalStateException e) { 242 e.printStackTrace(); 243 } catch (IOException e) { 244 e.printStackTrace(); 245 } 246 mCurrentState = STATE_PREPARING; 247 } else { 248 // If we skip prepare and the onPrepared happened in inline mode, we 249 // don't need to call prepare again, we just need to call onPrepared 250 // to refresh the state here. 251 if (mCurrentState >= STATE_PREPARED) { 252 onPrepared(mPlayer); 253 } 254 mSkipPrepare = false; 255 } 256 } 257 258 public void reprepareData(HTML5VideoViewProxy proxy) { 259 mPlayer.reset(); 260 prepareDataCommon(proxy); 261 } 262 263 // Normally called immediately after setVideoURI. But for full screen, 264 // this should be after surface holder created 265 public void prepareDataAndDisplayMode(HTML5VideoViewProxy proxy) { 266 // SurfaceTexture will be created lazily here for inline mode 267 decideDisplayMode(); 268 269 setOnCompletionListener(proxy); 270 setOnPreparedListener(proxy); 271 setOnErrorListener(proxy); 272 setOnInfoListener(proxy); 273 274 prepareDataCommon(proxy); 275 } 276 277 278 // Common code 279 public int getVideoLayerId() { 280 return mVideoLayerId; 281 } 282 283 284 public int getCurrentState() { 285 if (isPlaying()) { 286 return STATE_PLAYING; 287 } else { 288 return mCurrentState; 289 } 290 } 291 292 private static final class TimeupdateTask extends TimerTask { 293 private HTML5VideoViewProxy mProxy; 294 295 public TimeupdateTask(HTML5VideoViewProxy proxy) { 296 mProxy = proxy; 297 } 298 299 @Override 300 public void run() { 301 mProxy.onTimeupdate(); 302 } 303 } 304 305 @Override 306 public void onPrepared(MediaPlayer mp) { 307 mCurrentState = STATE_PREPARED; 308 seekTo(mSaveSeekTime); 309 if (mProxy != null) { 310 mProxy.onPrepared(mp); 311 } 312 if (mPauseDuringPreparing) { 313 pauseAndDispatch(mProxy); 314 mPauseDuringPreparing = false; 315 } 316 } 317 318 // Pause the play and update the play/pause button 319 public void pauseAndDispatch(HTML5VideoViewProxy proxy) { 320 pause(); 321 if (proxy != null) { 322 proxy.dispatchOnPaused(); 323 } 324 } 325 326 // Below are functions that are different implementation on inline and full- 327 // screen mode. Some are specific to one type, but currently are called 328 // directly from the proxy. 329 public void enterFullScreenVideoState(int layerId, 330 HTML5VideoViewProxy proxy, WebViewClassic webView) { 331 } 332 333 public boolean isFullScreenMode() { 334 return false; 335 } 336 337 public void decideDisplayMode() { 338 } 339 340 public boolean getReadyToUseSurfTex() { 341 return false; 342 } 343 344 public void deleteSurfaceTexture() { 345 } 346 347 public int getTextureName() { 348 return 0; 349 } 350 351 // This is true only when the player is buffering and paused 352 public boolean mPlayerBuffering = false; 353 354 public boolean getPlayerBuffering() { 355 return mPlayerBuffering; 356 } 357 358 public void setPlayerBuffering(boolean playerBuffering) { 359 mPlayerBuffering = playerBuffering; 360 switchProgressView(playerBuffering); 361 } 362 363 364 protected void switchProgressView(boolean playerBuffering) { 365 // Only used in HTML5VideoFullScreen 366 } 367 368 public boolean fullScreenExited() { 369 // Only meaningful for HTML5VideoFullScreen 370 return false; 371 } 372 373 private boolean mStartWhenPrepared = false; 374 375 public void setStartWhenPrepared(boolean willPlay) { 376 mStartWhenPrepared = willPlay; 377 } 378 379 public boolean getStartWhenPrepared() { 380 return mStartWhenPrepared; 381 } 382 383 public void showControllerInFullScreen() { 384 } 385 386} 387