Home | History | Annotate | Download | only in webkit
      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