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