Home | History | Annotate | Download | only in webkit
      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_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 surfaceTextureDeleted() {
    341         // Only meaningful for HTML5VideoInline
    342         return false;
    343     }
    344 
    345     public boolean fullScreenExited() {
    346         // Only meaningful for HTML5VideoFullScreen
    347         return false;
    348     }
    349 
    350     private boolean mStartWhenPrepared = false;
    351 
    352     public void setStartWhenPrepared(boolean willPlay) {
    353         mStartWhenPrepared  = willPlay;
    354     }
    355 
    356     public boolean getStartWhenPrepared() {
    357         return mStartWhenPrepared;
    358     }
    359 
    360     public void showControllerInFullScreen() {
    361     }
    362 
    363 }
    364