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