Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 7af7e00b authored by Teng-Hui Zhu's avatar Teng-Hui Zhu Committed by Android (Google) Code Review
Browse files

Merge "Support html5 video switching b/t full screen and inline mode" into honeycomb-mr1

parents 8e42d39f 10ab6549
Loading
Loading
Loading
Loading
+286 −0
Original line number Diff line number Diff line

package android.webkit;

import android.content.Context;
import android.media.MediaPlayer;
import android.media.Metadata;
import android.util.Log;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.HTML5VideoView;
import android.webkit.HTML5VideoViewProxy;
import android.widget.FrameLayout;
import android.widget.MediaController;
import android.widget.MediaController.MediaPlayerControl;


/**
 * @hide This is only used by the browser
 */
public class HTML5VideoFullScreen extends HTML5VideoView
    implements MediaPlayerControl, MediaPlayer.OnPreparedListener,
    View.OnTouchListener {

    private SurfaceView mSurfaceView;

    // We need the full screen state to decide which surface to render to and
    // when to create the MediaPlayer accordingly.
    static final int FULLSCREEN_OFF               = 0;
    static final int FULLSCREEN_SURFACECREATING   = 1;
    static final int FULLSCREEN_SURFACECREATED    = 2;

    private int mFullScreenMode;
    // The Media Controller only used for full screen mode
    private MediaController mMediaController;

    // SurfaceHolder for full screen
    private SurfaceHolder mSurfaceHolder = null;

    // Data only for MediaController
    private boolean mCanSeekBack;
    private boolean mCanSeekForward;
    private boolean mCanPause;
    private int mCurrentBufferPercentage;

    // The progress view.
    private static View mProgressView;
    // The container for the progress view and video view
    private static FrameLayout mLayout;

    SurfaceHolder.Callback mSHCallback = new SurfaceHolder.Callback()
    {
        public void surfaceChanged(SurfaceHolder holder, int format,
                                    int w, int h)
        {
            if (mPlayer != null && mMediaController != null
                    && mCurrentState == STATE_PREPARED) {
                if (mMediaController.isShowing()) {
                    // ensure the controller will get repositioned later
                    mMediaController.hide();
                }
                mMediaController.show();
            }
        }

        public void surfaceCreated(SurfaceHolder holder)
        {
            mSurfaceHolder = holder;
            mFullScreenMode = FULLSCREEN_SURFACECREATED;

            prepareForFullScreen();
        }

        public void surfaceDestroyed(SurfaceHolder holder)
        {
            // after we return from this we can't use the surface any more
            mSurfaceHolder = null;
            // The current Video View will be destroy when we play a new video.
        }
    };

    public SurfaceView getSurfaceView() {
        return mSurfaceView;
    }

    HTML5VideoFullScreen(Context context, int videoLayerId, int position,
            boolean autoStart) {
        mSurfaceView = new SurfaceView(context);
        mFullScreenMode = FULLSCREEN_OFF;
        init(videoLayerId, position, autoStart);
    }

    private void setMediaController(MediaController m) {
        mMediaController  = m;
        attachMediaController();
    }

    private void attachMediaController() {
        if (mPlayer != null && mMediaController != null) {
            mMediaController.setMediaPlayer(this);
            mMediaController.setAnchorView(mSurfaceView);
            //Will be enabled when prepared
            mMediaController.setEnabled(false);
        }
    }

    @Override
    public void decideDisplayMode() {
        mPlayer.setDisplay(mSurfaceHolder);
    }

    @Override
    public void prepareForFullScreen() {
        // So in full screen, we reset the MediaPlayer
        mPlayer.reset();
        setMediaController(new MediaController(mProxy.getContext()));

        prepareDataAndDisplayMode(mProxy);
    }


    private void toggleMediaControlsVisiblity() {
        if (mMediaController.isShowing()) {
            mMediaController.hide();
        } else {
            mMediaController.show();
        }
    }

    @Override
    public void onPrepared(MediaPlayer mp) {
        super.onPrepared(mp);

        mSurfaceView.setOnTouchListener(this);
        // Get the capabilities of the player for this stream
        Metadata data = mp.getMetadata(MediaPlayer.METADATA_ALL,
                MediaPlayer.BYPASS_METADATA_FILTER);
        if (data != null) {
            mCanPause = !data.has(Metadata.PAUSE_AVAILABLE)
                    || data.getBoolean(Metadata.PAUSE_AVAILABLE);
            mCanSeekBack = !data.has(Metadata.SEEK_BACKWARD_AVAILABLE)
                    || data.getBoolean(Metadata.SEEK_BACKWARD_AVAILABLE);
            mCanSeekForward = !data.has(Metadata.SEEK_FORWARD_AVAILABLE)
                    || data.getBoolean(Metadata.SEEK_FORWARD_AVAILABLE);
        } else {
            mCanPause = mCanSeekBack = mCanSeekForward = true;
        }

        // mMediaController status depends on the Metadata result, so put it
        // after reading the MetaData
        if (mMediaController != null) {
            mMediaController.setEnabled(true);
            // If paused , should show the controller for ever!
            if (getAutostart())
                mMediaController.show();
            else
                mMediaController.show(0);
        }

        if (mProgressView != null) {
            mProgressView.setVisibility(View.GONE);
            mLayout.removeView(mProgressView);
            mProgressView = null;
        }
    }


    private final WebChromeClient.CustomViewCallback mCallback =
        new WebChromeClient.CustomViewCallback() {
            public void onCustomViewHidden() {
                // It listens to SurfaceHolder.Callback.SurfaceDestroyed event
                // which happens when the video view is detached from its parent
                // view. This happens in the WebChromeClient before this method
                // is invoked.
                mTimer.cancel();
                mTimer = null;

                pauseAndDispatch(mProxy);

                mLayout.removeView(getSurfaceView());

                if (mProgressView != null) {
                    mLayout.removeView(mProgressView);
                    mProgressView = null;
                }
                mLayout = null;
                // Re enable plugin views.
                mProxy.getWebView().getViewManager().showAll();

                mProxy = null;
            }
        };

    @Override
    public void enterFullScreenVideoState(int layerId,
            HTML5VideoViewProxy proxy, WebView webView) {
        mFullScreenMode = FULLSCREEN_SURFACECREATING;
        mCurrentBufferPercentage = 0;
        mPlayer.setOnBufferingUpdateListener(mBufferingUpdateListener);
        mProxy = proxy;

        mSurfaceView.getHolder().addCallback(mSHCallback);
        mSurfaceView.getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
        mSurfaceView.setFocusable(true);
        mSurfaceView.setFocusableInTouchMode(true);
        mSurfaceView.requestFocus();

        // Create a FrameLayout that will contain the VideoView and the
        // progress view (if any).
        mLayout = new FrameLayout(mProxy.getContext());
        FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(
                            ViewGroup.LayoutParams.WRAP_CONTENT,
                            ViewGroup.LayoutParams.WRAP_CONTENT,
                            Gravity.CENTER);

        mLayout.addView(getSurfaceView(), layoutParams);

        mLayout.setVisibility(View.VISIBLE);

        WebChromeClient client = webView.getWebChromeClient();
        client.onShowCustomView(mLayout, mCallback);
        // Plugins like Flash will draw over the video so hide
        // them while we're playing.
        mProxy.getWebView().getViewManager().hideAll();

        mProgressView = client.getVideoLoadingProgressView();
        if (mProgressView != null) {
            mLayout.addView(mProgressView, layoutParams);
            mProgressView.setVisibility(View.VISIBLE);
        }

    }

    /**
     * @return true when we are in full screen mode, even the surface not fully
     * created.
     */
    public boolean isFullScreenMode() {
        return true;
    }

    // MediaController FUNCTIONS:
    @Override
    public boolean canPause() {
        return mCanPause;
    }

    @Override
    public boolean canSeekBackward() {
        return mCanSeekBack;
    }

    @Override
    public boolean canSeekForward() {
        return mCanSeekForward;
    }

    @Override
    public int getBufferPercentage() {
        if (mPlayer != null) {
            return mCurrentBufferPercentage;
        }
    return 0;
    }

    // Other listeners functions:
    private MediaPlayer.OnBufferingUpdateListener mBufferingUpdateListener =
        new MediaPlayer.OnBufferingUpdateListener() {
        public void onBufferingUpdate(MediaPlayer mp, int percent) {
            mCurrentBufferPercentage = percent;
        }
    };

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        if (mFullScreenMode >= FULLSCREEN_SURFACECREATED
                && mMediaController != null) {
            toggleMediaControlsVisiblity();
        }
        return false;
    }

}
+99 −0
Original line number Diff line number Diff line

package android.webkit;

import android.graphics.SurfaceTexture;
import android.media.MediaPlayer;
import android.webkit.HTML5VideoView;
import android.webkit.HTML5VideoViewProxy;
import android.opengl.GLES20;

/**
 * @hide This is only used by the browser
 */
public class HTML5VideoInline extends HTML5VideoView{

    // Due to the fact that SurfaceTexture consume a lot of memory, we make it
    // as static. m_textureNames is the texture bound with this SurfaceTexture.
    private static SurfaceTexture mSurfaceTexture = null;
    private static int[] mTextureNames;

    // Only when the video is prepared, we render using SurfaceTexture.
    // This in fact is used to avoid showing the obsolete content when
    // switching videos.
    private static boolean mReadyToUseSurfTex = false;

    // Video control FUNCTIONS:
    @Override
    public void start() {
        super.start();
        if (mCurrentState == STATE_PREPARED) {
            mReadyToUseSurfTex = true;
        }
    }

    HTML5VideoInline(int videoLayerId, int position,
            boolean autoStart) {
        init(videoLayerId, position, autoStart);
        mReadyToUseSurfTex = false;
    }

    @Override
    public void decideDisplayMode() {
        mPlayer.setTexture(getSurfaceTextureInstance());
    }

    // Normally called immediately after setVideoURI. But for full screen,
    // this should be after surface holder created
    @Override
    public void prepareDataAndDisplayMode(HTML5VideoViewProxy proxy) {
        super.prepareDataAndDisplayMode(proxy);
        setFrameAvailableListener(proxy);
    }

    // Pause the play and update the play/pause button
    @Override
    public void pauseAndDispatch(HTML5VideoViewProxy proxy) {
        super.pauseAndDispatch(proxy);
        mReadyToUseSurfTex = false;
    }

    // Inline Video specific FUNCTIONS:

    @Override
    public SurfaceTexture getSurfaceTexture() {
        return mSurfaceTexture;
    }

    @Override
    public void deleteSurfaceTexture() {
        mSurfaceTexture = null;
        return;
    }

    // SurfaceTexture is a singleton here , too
    private SurfaceTexture getSurfaceTextureInstance() {
        // Create the surface texture.
        if (mSurfaceTexture == null)
        {
            mTextureNames = new int[1];
            GLES20.glGenTextures(1, mTextureNames, 0);
            mSurfaceTexture = new SurfaceTexture(mTextureNames[0]);
        }
        return mSurfaceTexture;
    }

    @Override
    public int getTextureName() {
        return mTextureNames[0];
    }

    @Override
    public boolean getReadyToUseSurfTex() {
        return mReadyToUseSurfTex;
    }

    private void setFrameAvailableListener(SurfaceTexture.OnFrameAvailableListener l) {
        mSurfaceTexture.setOnFrameAvailableListener(l);
    }

}
+143 −83
Original line number Diff line number Diff line
@@ -4,73 +4,94 @@ package android.webkit;
import android.graphics.SurfaceTexture;
import android.media.MediaPlayer;
import android.util.Log;
import android.view.SurfaceView;
import android.webkit.HTML5VideoViewProxy;
import android.widget.MediaController;
import android.opengl.GLES20;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;

/**
 * @hide This is only used by the browser
 */
public class HTML5VideoView implements MediaPlayer.OnPreparedListener{
    // Due to the fact that SurfaceTexture consume a lot of memory, we make it
    // as static. m_textureNames is the texture bound with this SurfaceTexture.
    private static SurfaceTexture mSurfaceTexture = null;
    private static int[] mTextureNames;

    // Only when the video is prepared, we render using SurfaceTexture.
    // This in fact is used to avoid showing the obsolete content when
    // switching videos.
    private static boolean mReadyToUseSurfTex = false;
    protected static final String LOGTAG = "HTML5VideoView";

    protected static final String COOKIE = "Cookie";
    protected static final String HIDE_URL_LOGS = "x-hide-urls-from-log";

    // For handling the seekTo before prepared, we need to know whether or not
    // the video is prepared. Therefore, we differentiate the state between
    // prepared and not prepared.
    // When the video is not prepared, we will have to save the seekTo time,
    // and use it when prepared to play.
    private static final int STATE_NOTPREPARED        = 0;
    private static final int STATE_PREPARED           = 1;
    protected static final int STATE_NOTPREPARED        = 0;
    protected static final int STATE_PREPARED           = 1;

    // We only need state for handling seekTo
    private int mCurrentState;
    protected int mCurrentState;

    // Basically for calling back the OnPrepared in the proxy
    private HTML5VideoViewProxy mProxy;
    protected HTML5VideoViewProxy mProxy;

    // Save the seek time when not prepared. This can happen when switching
    // video besides initial load.
    private int mSaveSeekTime;
    protected int mSaveSeekTime;

    // This is used to find the VideoLayer on the native side.
    private int mVideoLayerId;
    protected int mVideoLayerId;

    // Every video will have one MediaPlayer. Given the fact we only have one
    // SurfaceTexture, there is only one MediaPlayer in action. Every time we
    // switch videos, a new instance of MediaPlayer will be created in reset().
    private MediaPlayer mPlayer;
    // Switching between inline and full screen will also create a new instance.
    protected MediaPlayer mPlayer;

    // This will be set up every time we create the Video View object.
    // Set to true only when switching into full screen while playing
    protected boolean mAutostart;

    // We need to save such info.
    protected String mUri;
    protected Map<String, String> mHeaders;

    // The timer for timeupate events.
    // See http://www.whatwg.org/specs/web-apps/current-work/#event-media-timeupdate
    protected static Timer mTimer;

    private static HTML5VideoView mInstance = new HTML5VideoView();
    // The spec says the timer should fire every 250 ms or less.
    private static final int TIMEUPDATE_PERIOD = 250;  // ms

    // Video control FUNCTIONS:
    // common Video control FUNCTIONS:
    public void start() {
        if (mCurrentState == STATE_PREPARED) {
            mPlayer.start();
            mReadyToUseSurfTex = true;
        }
    }

    public void pause() {
        if (mCurrentState == STATE_PREPARED && mPlayer.isPlaying()) {
            mPlayer.pause();
        }
        if (mTimer != null) {
            mTimer.purge();
        }
    }

    public int getDuration() {
        if (mCurrentState == STATE_PREPARED) {
            return mPlayer.getDuration();
        } else {
            return -1;
        }
    }

    public int getCurrentPosition() {
        if (mCurrentState == STATE_PREPARED) {
            return mPlayer.getCurrentPosition();
        }
        return 0;
    }

    public void seekTo(int pos) {
        if (mCurrentState == STATE_PREPARED)
@@ -88,54 +109,51 @@ public class HTML5VideoView implements MediaPlayer.OnPreparedListener{
    }

    public void stopPlayback() {
        if (mCurrentState == STATE_PREPARED) {
            mPlayer.stop();
        }
    }

    public boolean getAutostart() {
        return mAutostart;
    }

    private void reset(int videoLayerId) {
    // Every time we start a new Video, we create a VideoView and a MediaPlayer
    public void init(int videoLayerId, int position, boolean autoStart) {
        mPlayer = new MediaPlayer();
        mCurrentState = STATE_NOTPREPARED;
        mProxy = null;
        mVideoLayerId = videoLayerId;
        mReadyToUseSurfTex = false;
        mSaveSeekTime = position;
        mAutostart = autoStart;
    }

    public static HTML5VideoView getInstance(int videoLayerId) {
        // Every time we switch between the videos, a new MediaPlayer will be
        // created. Make sure we call the m_player.release() when it is done.
        mInstance.reset(videoLayerId);
        return mInstance;
    protected HTML5VideoView() {
    }

    private HTML5VideoView() {
        // This is a singleton across WebViews (i.e. Tabs).
        // HTML5VideoViewProxy will reset the internal state every time a new
        // video start.
    protected static Map<String, String> generateHeaders(String url,
            HTML5VideoViewProxy proxy) {
        boolean isPrivate = proxy.getWebView().isPrivateBrowsingEnabled();
        String cookieValue = CookieManager.getInstance().getCookie(url, isPrivate);
        Map<String, String> headers = new HashMap<String, String>();
        if (cookieValue != null) {
            headers.put(COOKIE, cookieValue);
        }
        if (isPrivate) {
            headers.put(HIDE_URL_LOGS, "true");
        }

    public void setMediaController(MediaController m) {
        this.setMediaController(m);
        return headers;
    }

    public void setVideoURI(String uri, Map<String, String> headers) {
    public void setVideoURI(String uri, HTML5VideoViewProxy proxy) {
        // When switching players, surface texture will be reused.
        mPlayer.setTexture(getSurfaceTextureInstance());
        mUri = uri;
        mHeaders = generateHeaders(uri, proxy);

        // When there is exception, we could just bail out silently.
        // No Video will be played though. Write the stack for debug
        try {
            mPlayer.setDataSource(uri, headers);
            mPlayer.prepareAsync();
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (IllegalStateException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        mTimer = new Timer();
    }

    // TODO [FULL SCREEN SUPPORT]

    // Listeners setup FUNCTIONS:
    public void setOnCompletionListener(HTML5VideoViewProxy proxy) {
        mPlayer.setOnCompletionListener(proxy);
@@ -150,43 +168,47 @@ public class HTML5VideoView implements MediaPlayer.OnPreparedListener{
        mPlayer.setOnPreparedListener(this);
    }

    // Inline Video specific FUNCTIONS:
    // Normally called immediately after setVideoURI. But for full screen,
    // this should be after surface holder created
    public void prepareDataAndDisplayMode(HTML5VideoViewProxy proxy) {
        // SurfaceTexture will be created lazily here for inline mode
        decideDisplayMode();

    public SurfaceTexture getSurfaceTexture() {
        return mSurfaceTexture;
    }
        setOnCompletionListener(proxy);
        setOnPreparedListener(proxy);
        setOnErrorListener(proxy);

    public void deleteSurfaceTexture() {
        mSurfaceTexture = null;
        return;
    }

    // SurfaceTexture is a singleton here , too
    private SurfaceTexture getSurfaceTextureInstance() {
        // Create the surface texture.
        if (mSurfaceTexture == null)
        {
            mTextureNames = new int[1];
            GLES20.glGenTextures(1, mTextureNames, 0);
            mSurfaceTexture = new SurfaceTexture(mTextureNames[0]);
        // When there is exception, we could just bail out silently.
        // No Video will be played though. Write the stack for debug
        try {
            mPlayer.setDataSource(mUri, mHeaders);
            mPlayer.prepareAsync();
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (IllegalStateException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return mSurfaceTexture;
    }

    public int getTextureName() {
        return mTextureNames[0];
    }

    // Common code
    public int getVideoLayerId() {
        return mVideoLayerId;
    }

    public boolean getReadyToUseSurfTex() {
        return mReadyToUseSurfTex;
    private static final class TimeupdateTask extends TimerTask {
        private HTML5VideoViewProxy mProxy;

        public TimeupdateTask(HTML5VideoViewProxy proxy) {
            mProxy = proxy;
        }

    public void setFrameAvailableListener(SurfaceTexture.OnFrameAvailableListener l) {
        mSurfaceTexture.setOnFrameAvailableListener(l);
        @Override
        public void run() {
            mProxy.onTimeupdate();
        }
    }

    @Override
@@ -195,6 +217,9 @@ public class HTML5VideoView implements MediaPlayer.OnPreparedListener{
        seekTo(mSaveSeekTime);
        if (mProxy != null)
            mProxy.onPrepared(mp);

        mTimer.schedule(new TimeupdateTask(mProxy), TIMEUPDATE_PERIOD, TIMEUPDATE_PERIOD);

    }

    // Pause the play and update the play/pause button
@@ -205,7 +230,42 @@ public class HTML5VideoView implements MediaPlayer.OnPreparedListener{
                proxy.dispatchOnPaused();
            }
        }
        mReadyToUseSurfTex = false;
    }

    // Below are functions that are different implementation on inline and full-
    // screen mode. Some are specific to one type, but currently are called
    // directly from the proxy.
    public void enterFullScreenVideoState(int layerId,
            HTML5VideoViewProxy proxy, WebView webView) {
    }

    public boolean isFullScreenMode() {
        return false;
    }

    public SurfaceView getSurfaceView() {
        return null;
    }

    public void decideDisplayMode() {
    }

    public void prepareForFullScreen() {
    }

    public boolean getReadyToUseSurfTex() {
        return false;
    }

    public SurfaceTexture getSurfaceTexture() {
        return null;
    }

    public void deleteSurfaceTexture() {
    }

    public int getTextureName() {
        return 0;
    }

}
+51 −77

File changed.

Preview size limit exceeded, changes collapsed.

+5 −1
Original line number Diff line number Diff line
@@ -7860,7 +7860,11 @@ public class WebView extends AbsoluteLayout

                case ENTER_FULLSCREEN_VIDEO:
                    int layerId = msg.arg1;
                    Log.v(LOGTAG, "Display the video layer " + layerId + " fullscreen");

                    String url = (String) msg.obj;
                    if (mHTML5VideoViewProxy != null) {
                        mHTML5VideoViewProxy.enterFullScreenVideo(layerId, url);
                    }
                    break;

                case SHOW_FULLSCREEN: {
Loading