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