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

This commit is contained in:
Teng-Hui Zhu
2011-03-16 23:01:02 -07:00
committed by Android (Google) Code Review
6 changed files with 593 additions and 168 deletions

View File

@@ -0,0 +1,286 @@
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;
}
}

View File

@@ -0,0 +1,99 @@
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);
}
}

View File

@@ -4,72 +4,93 @@ package android.webkit;
import android.graphics.SurfaceTexture; import android.graphics.SurfaceTexture;
import android.media.MediaPlayer; import android.media.MediaPlayer;
import android.util.Log; import android.util.Log;
import android.view.SurfaceView;
import android.webkit.HTML5VideoViewProxy; import android.webkit.HTML5VideoViewProxy;
import android.widget.MediaController;
import android.opengl.GLES20;
import java.io.IOException; import java.io.IOException;
import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
/** /**
* @hide This is only used by the browser * @hide This is only used by the browser
*/ */
public class HTML5VideoView implements MediaPlayer.OnPreparedListener{ 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. protected static final String LOGTAG = "HTML5VideoView";
// This in fact is used to avoid showing the obsolete content when
// switching videos. protected static final String COOKIE = "Cookie";
private static boolean mReadyToUseSurfTex = false; 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 // For handling the seekTo before prepared, we need to know whether or not
// the video is prepared. Therefore, we differentiate the state between // the video is prepared. Therefore, we differentiate the state between
// prepared and not prepared. // prepared and not prepared.
// When the video is not prepared, we will have to save the seekTo time, // When the video is not prepared, we will have to save the seekTo time,
// and use it when prepared to play. // and use it when prepared to play.
private static final int STATE_NOTPREPARED = 0; protected static final int STATE_NOTPREPARED = 0;
private static final int STATE_PREPARED = 1; protected static final int STATE_PREPARED = 1;
// We only need state for handling seekTo protected int mCurrentState;
private int mCurrentState;
// Basically for calling back the OnPrepared in the proxy protected HTML5VideoViewProxy mProxy;
private HTML5VideoViewProxy mProxy;
// Save the seek time when not prepared. This can happen when switching // Save the seek time when not prepared. This can happen when switching
// video besides initial load. // video besides initial load.
private int mSaveSeekTime; protected int mSaveSeekTime;
// This is used to find the VideoLayer on the native side. // 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 // Every video will have one MediaPlayer. Given the fact we only have one
// SurfaceTexture, there is only one MediaPlayer in action. Every time we // SurfaceTexture, there is only one MediaPlayer in action. Every time we
// switch videos, a new instance of MediaPlayer will be created in reset(). // 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;
private static HTML5VideoView mInstance = new HTML5VideoView(); // 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;
// Video control FUNCTIONS: // 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;
// The spec says the timer should fire every 250 ms or less.
private static final int TIMEUPDATE_PERIOD = 250; // ms
// common Video control FUNCTIONS:
public void start() { public void start() {
if (mCurrentState == STATE_PREPARED) { if (mCurrentState == STATE_PREPARED) {
mPlayer.start(); mPlayer.start();
mReadyToUseSurfTex = true;
} }
} }
public void pause() { public void pause() {
mPlayer.pause(); if (mCurrentState == STATE_PREPARED && mPlayer.isPlaying()) {
mPlayer.pause();
}
if (mTimer != null) {
mTimer.purge();
}
} }
public int getDuration() { public int getDuration() {
return mPlayer.getDuration(); if (mCurrentState == STATE_PREPARED) {
return mPlayer.getDuration();
} else {
return -1;
}
} }
public int getCurrentPosition() { public int getCurrentPosition() {
return mPlayer.getCurrentPosition(); if (mCurrentState == STATE_PREPARED) {
return mPlayer.getCurrentPosition();
}
return 0;
} }
public void seekTo(int pos) { public void seekTo(int pos) {
@@ -88,53 +109,50 @@ public class HTML5VideoView implements MediaPlayer.OnPreparedListener{
} }
public void stopPlayback() { public void stopPlayback() {
mPlayer.stop(); if (mCurrentState == STATE_PREPARED) {
mPlayer.stop();
}
} }
private void reset(int videoLayerId) { public boolean getAutostart() {
return mAutostart;
}
// 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(); mPlayer = new MediaPlayer();
mCurrentState = STATE_NOTPREPARED; mCurrentState = STATE_NOTPREPARED;
mProxy = null; mProxy = null;
mVideoLayerId = videoLayerId; mVideoLayerId = videoLayerId;
mReadyToUseSurfTex = false; mSaveSeekTime = position;
mAutostart = autoStart;
} }
public static HTML5VideoView getInstance(int videoLayerId) { protected HTML5VideoView() {
// 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;
} }
private HTML5VideoView() { protected static Map<String, String> generateHeaders(String url,
// This is a singleton across WebViews (i.e. Tabs). HTML5VideoViewProxy proxy) {
// HTML5VideoViewProxy will reset the internal state every time a new boolean isPrivate = proxy.getWebView().isPrivateBrowsingEnabled();
// video start. String cookieValue = CookieManager.getInstance().getCookie(url, isPrivate);
} Map<String, String> headers = new HashMap<String, String>();
if (cookieValue != null) {
public void setMediaController(MediaController m) { headers.put(COOKIE, cookieValue);
this.setMediaController(m);
}
public void setVideoURI(String uri, Map<String, String> headers) {
// When switching players, surface texture will be reused.
mPlayer.setTexture(getSurfaceTextureInstance());
// 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();
} }
if (isPrivate) {
headers.put(HIDE_URL_LOGS, "true");
}
return headers;
} }
// TODO [FULL SCREEN SUPPORT] public void setVideoURI(String uri, HTML5VideoViewProxy proxy) {
// When switching players, surface texture will be reused.
mUri = uri;
mHeaders = generateHeaders(uri, proxy);
mTimer = new Timer();
}
// Listeners setup FUNCTIONS: // Listeners setup FUNCTIONS:
public void setOnCompletionListener(HTML5VideoViewProxy proxy) { public void setOnCompletionListener(HTML5VideoViewProxy proxy) {
@@ -150,43 +168,47 @@ public class HTML5VideoView implements MediaPlayer.OnPreparedListener{
mPlayer.setOnPreparedListener(this); 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() { setOnCompletionListener(proxy);
return mSurfaceTexture; setOnPreparedListener(proxy);
} setOnErrorListener(proxy);
public void deleteSurfaceTexture() { // When there is exception, we could just bail out silently.
mSurfaceTexture = null; // No Video will be played though. Write the stack for debug
return; try {
} mPlayer.setDataSource(mUri, mHeaders);
mPlayer.prepareAsync();
// SurfaceTexture is a singleton here , too } catch (IllegalArgumentException e) {
private SurfaceTexture getSurfaceTextureInstance() { e.printStackTrace();
// Create the surface texture. } catch (IllegalStateException e) {
if (mSurfaceTexture == null) e.printStackTrace();
{ } catch (IOException e) {
mTextureNames = new int[1]; e.printStackTrace();
GLES20.glGenTextures(1, mTextureNames, 0);
mSurfaceTexture = new SurfaceTexture(mTextureNames[0]);
} }
return mSurfaceTexture;
} }
public int getTextureName() {
return mTextureNames[0];
}
// Common code
public int getVideoLayerId() { public int getVideoLayerId() {
return mVideoLayerId; return mVideoLayerId;
} }
public boolean getReadyToUseSurfTex() { private static final class TimeupdateTask extends TimerTask {
return mReadyToUseSurfTex; private HTML5VideoViewProxy mProxy;
}
public void setFrameAvailableListener(SurfaceTexture.OnFrameAvailableListener l) { public TimeupdateTask(HTML5VideoViewProxy proxy) {
mSurfaceTexture.setOnFrameAvailableListener(l); mProxy = proxy;
}
@Override
public void run() {
mProxy.onTimeupdate();
}
} }
@Override @Override
@@ -195,6 +217,9 @@ public class HTML5VideoView implements MediaPlayer.OnPreparedListener{
seekTo(mSaveSeekTime); seekTo(mSaveSeekTime);
if (mProxy != null) if (mProxy != null)
mProxy.onPrepared(mp); mProxy.onPrepared(mp);
mTimer.schedule(new TimeupdateTask(mProxy), TIMEUPDATE_PERIOD, TIMEUPDATE_PERIOD);
} }
// Pause the play and update the play/pause button // Pause the play and update the play/pause button
@@ -205,7 +230,42 @@ public class HTML5VideoView implements MediaPlayer.OnPreparedListener{
proxy.dispatchOnPaused(); 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;
} }
} }

View File

@@ -21,29 +21,16 @@ import android.graphics.Bitmap;
import android.graphics.BitmapFactory; import android.graphics.BitmapFactory;
import android.graphics.SurfaceTexture; import android.graphics.SurfaceTexture;
import android.media.MediaPlayer; import android.media.MediaPlayer;
import android.media.MediaPlayer.OnPreparedListener;
import android.media.MediaPlayer.OnCompletionListener;
import android.media.MediaPlayer.OnErrorListener;
import android.net.http.EventHandler; import android.net.http.EventHandler;
import android.net.http.Headers; import android.net.http.Headers;
import android.net.http.RequestHandle; import android.net.http.RequestHandle;
import android.net.http.RequestQueue; import android.net.http.RequestQueue;
import android.net.http.SslCertificate; import android.net.http.SslCertificate;
import android.net.http.SslError; import android.net.http.SslError;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.os.Message; import android.os.Message;
import android.util.Log; import android.util.Log;
import android.view.MotionEvent;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsoluteLayout;
import android.widget.FrameLayout;
import android.widget.MediaController;
import android.widget.VideoView;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
@@ -51,8 +38,6 @@ import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
/** /**
* <p>Proxy for HTML5 video views. * <p>Proxy for HTML5 video views.
@@ -78,9 +63,6 @@ class HTML5VideoViewProxy extends Handler
private static final int POSTER_FETCHED = 202; private static final int POSTER_FETCHED = 202;
private static final int PAUSED = 203; private static final int PAUSED = 203;
private static final String COOKIE = "Cookie";
private static final String HIDE_URL_LOGS = "x-hide-urls-from-log";
// Timer thread -> UI thread // Timer thread -> UI thread
private static final int TIMEUPDATE = 300; private static final int TIMEUPDATE = 300;
@@ -104,38 +86,19 @@ class HTML5VideoViewProxy extends Handler
// The VideoView instance. This is a singleton for now, at least until // The VideoView instance. This is a singleton for now, at least until
// http://b/issue?id=1973663 is fixed. // http://b/issue?id=1973663 is fixed.
private static HTML5VideoView mHTML5VideoView; private static HTML5VideoView mHTML5VideoView;
// The progress view.
private static View mProgressView;
// The container for the progress view and video view
private static FrameLayout mLayout;
// The timer for timeupate events.
// See http://www.whatwg.org/specs/web-apps/current-work/#event-media-timeupdate
private static Timer mTimer;
private static final class TimeupdateTask extends TimerTask {
private HTML5VideoViewProxy mProxy;
public TimeupdateTask(HTML5VideoViewProxy proxy) {
mProxy = proxy;
}
public void run() {
mProxy.onTimeupdate();
}
}
// The spec says the timer should fire every 250 ms or less.
private static final int TIMEUPDATE_PERIOD = 250; // ms
private static boolean isVideoSelfEnded = false; private static boolean isVideoSelfEnded = false;
// By using the baseLayer and the current video Layer ID, we can // By using the baseLayer and the current video Layer ID, we can
// identify the exact layer on the UI thread to use the SurfaceTexture. // identify the exact layer on the UI thread to use the SurfaceTexture.
private static int mBaseLayer = 0; private static int mBaseLayer = 0;
// TODO: [FULL SCREEN SUPPORT]
// Every time webView setBaseLayer, this will be called. // Every time webView setBaseLayer, this will be called.
// When we found the Video layer, then we set the Surface Texture to it. // When we found the Video layer, then we set the Surface Texture to it.
// Otherwise, we may want to delete the Surface Texture to save memory. // Otherwise, we may want to delete the Surface Texture to save memory.
public static void setBaseLayer(int layer) { public static void setBaseLayer(int layer) {
if (mHTML5VideoView != null) { // Don't do this for full screen mode.
if (mHTML5VideoView != null
&& !mHTML5VideoView.isFullScreenMode()) {
mBaseLayer = layer; mBaseLayer = layer;
SurfaceTexture surfTexture = mHTML5VideoView.getSurfaceTexture(); SurfaceTexture surfTexture = mHTML5VideoView.getSurfaceTexture();
int textureName = mHTML5VideoView.getTextureName(); int textureName = mHTML5VideoView.getTextureName();
@@ -165,16 +128,47 @@ class HTML5VideoViewProxy extends Handler
} }
} }
public static void enterFullScreenVideo(int layerId, String url,
HTML5VideoViewProxy proxy, WebView webView) {
// Save the inline video info and inherit it in the full screen
int savePosition = 0;
boolean savedIsPlaying = false;
if (mHTML5VideoView != null) {
// If we are playing the same video, then it is better to
// save the current position.
if (layerId == mHTML5VideoView.getVideoLayerId()) {
savePosition = mHTML5VideoView.getCurrentPosition();
savedIsPlaying = mHTML5VideoView.isPlaying();
}
mHTML5VideoView.pauseAndDispatch(mCurrentProxy);
mHTML5VideoView.release();
}
mHTML5VideoView = new HTML5VideoFullScreen(proxy.getContext(),
layerId, savePosition, savedIsPlaying);
mCurrentProxy = proxy;
mHTML5VideoView.setVideoURI(url, mCurrentProxy);
mHTML5VideoView.enterFullScreenVideoState(layerId, proxy, webView);
}
// This is on the UI thread. // This is on the UI thread.
// When native tell Java to play, we need to check whether or not it is // When native tell Java to play, we need to check whether or not it is
// still the same video by using videoLayerId and treat it differently. // still the same video by using videoLayerId and treat it differently.
public static void play(String url, int time, HTML5VideoViewProxy proxy, public static void play(String url, int time, HTML5VideoViewProxy proxy,
WebChromeClient client, int videoLayerId) { WebChromeClient client, int videoLayerId) {
int currentVideoLayerId = -1; int currentVideoLayerId = -1;
if (mHTML5VideoView != null) boolean backFromFullScreenMode = false;
currentVideoLayerId = mHTML5VideoView.getVideoLayerId();
if (currentVideoLayerId != videoLayerId if (mHTML5VideoView != null) {
currentVideoLayerId = mHTML5VideoView.getVideoLayerId();
if (mHTML5VideoView instanceof HTML5VideoFullScreen) {
backFromFullScreenMode = true;
}
}
if (backFromFullScreenMode
|| currentVideoLayerId != videoLayerId
|| mHTML5VideoView.getSurfaceTexture() == null) { || mHTML5VideoView.getSurfaceTexture() == null) {
// Here, we handle the case when switching to a new video, // Here, we handle the case when switching to a new video,
// either inside a WebView or across WebViews // either inside a WebView or across WebViews
@@ -186,35 +180,11 @@ class HTML5VideoViewProxy extends Handler
// release the media player to avoid finalize error // release the media player to avoid finalize error
mHTML5VideoView.release(); mHTML5VideoView.release();
} }
// HTML5VideoView is singleton, however, the internal state will
// be reset since we are switching from one video to another.
// Then we need to set up all the source/listener etc...
mHTML5VideoView = HTML5VideoView.getInstance(videoLayerId);
mCurrentProxy = proxy; mCurrentProxy = proxy;
mHTML5VideoView = new HTML5VideoInline(videoLayerId, time, false);
// TODO: [FULL SCREEN SUPPORT] mHTML5VideoView.setVideoURI(url, mCurrentProxy);
mHTML5VideoView.prepareDataAndDisplayMode(proxy);
boolean isPrivate = mCurrentProxy.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");
}
mHTML5VideoView.setVideoURI(url, headers);
mHTML5VideoView.setOnCompletionListener(proxy);
mHTML5VideoView.setOnPreparedListener(proxy);
mHTML5VideoView.setOnErrorListener(proxy);
mHTML5VideoView.setFrameAvailableListener(proxy);
mHTML5VideoView.seekTo(time);
mTimer = new Timer();
} else if (mCurrentProxy == proxy) { } else if (mCurrentProxy == proxy) {
// Here, we handle the case when we keep playing with one video // Here, we handle the case when we keep playing with one video
if (!mHTML5VideoView.isPlaying()) { if (!mHTML5VideoView.isPlaying()) {
@@ -222,7 +192,8 @@ class HTML5VideoViewProxy extends Handler
mHTML5VideoView.start(); mHTML5VideoView.start();
} }
} else if (mCurrentProxy != null) { } else if (mCurrentProxy != null) {
// Some other video is already playing. Notify the caller that its playback ended. // Some other video is already playing. Notify the caller that
// its playback ended.
proxy.dispatchOnEnded(); proxy.dispatchOnEnded();
} }
} }
@@ -249,14 +220,14 @@ class HTML5VideoViewProxy extends Handler
public static void pause(HTML5VideoViewProxy proxy) { public static void pause(HTML5VideoViewProxy proxy) {
if (mCurrentProxy == proxy && mHTML5VideoView != null) { if (mCurrentProxy == proxy && mHTML5VideoView != null) {
mHTML5VideoView.pause(); mHTML5VideoView.pause();
mTimer.purge();
} }
} }
public static void onPrepared() { public static void onPrepared() {
mHTML5VideoView.start(); if (!mHTML5VideoView.isFullScreenMode() ||
mTimer.schedule(new TimeupdateTask(mCurrentProxy), TIMEUPDATE_PERIOD, TIMEUPDATE_PERIOD); mHTML5VideoView.isFullScreenMode() &&
// TODO: [FULL SCREEN SUPPORT] mHTML5VideoView.getAutostart() )
mHTML5VideoView.start();
} }
public static void end() { public static void end() {
@@ -349,8 +320,6 @@ class HTML5VideoViewProxy extends Handler
VideoPlayer.isVideoSelfEnded = true; VideoPlayer.isVideoSelfEnded = true;
VideoPlayer.end(); VideoPlayer.end();
break; break;
// TODO: [FULL SCREEN SUPPORT]
// For full screen case, end may need hide the view.
case ERROR: { case ERROR: {
WebChromeClient client = mWebView.getWebChromeClient(); WebChromeClient client = mWebView.getWebChromeClient();
if (client != null) { if (client != null) {
@@ -665,7 +634,7 @@ class HTML5VideoViewProxy extends Handler
mPosterDownloader.start(); mPosterDownloader.start();
} }
// These two function are called from UI thread only by WebView. // These three function are called from UI thread only by WebView.
public void setBaseLayer(int layer) { public void setBaseLayer(int layer) {
VideoPlayer.setBaseLayer(layer); VideoPlayer.setBaseLayer(layer);
} }
@@ -673,6 +642,11 @@ class HTML5VideoViewProxy extends Handler
public void pauseAndDispatch() { public void pauseAndDispatch() {
VideoPlayer.pauseAndDispatch(); VideoPlayer.pauseAndDispatch();
} }
public void enterFullScreenVideo(int layerId, String url) {
VideoPlayer.enterFullScreenVideo(layerId, url, this, mWebView);
}
/** /**
* The factory for HTML5VideoViewProxy instances. * The factory for HTML5VideoViewProxy instances.
* @param webViewCore is the WebViewCore that is requesting the proxy. * @param webViewCore is the WebViewCore that is requesting the proxy.

View File

@@ -7860,7 +7860,11 @@ public class WebView extends AbsoluteLayout
case ENTER_FULLSCREEN_VIDEO: case ENTER_FULLSCREEN_VIDEO:
int layerId = msg.arg1; 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; break;
case SHOW_FULLSCREEN: { case SHOW_FULLSCREEN: {

View File

@@ -483,10 +483,12 @@ final class WebViewCore {
/** /**
* Notify the webview that we want to display the video layer fullscreen. * Notify the webview that we want to display the video layer fullscreen.
*/ */
protected void enterFullscreenForVideoLayer(int layerId) { protected void enterFullscreenForVideoLayer(int layerId, String url) {
if (mWebView == null) return; if (mWebView == null) return;
Message.obtain(mWebView.mPrivateHandler, Message message = Message.obtain(mWebView.mPrivateHandler,
WebView.ENTER_FULLSCREEN_VIDEO, layerId, 0).sendToTarget(); WebView.ENTER_FULLSCREEN_VIDEO, layerId, 0);
message.obj = url;
message.sendToTarget();
} }
//------------------------------------------------------------------------- //-------------------------------------------------------------------------