Merge "Support html5 video switching b/t full screen and inline mode" into honeycomb-mr1
This commit is contained in:
286
core/java/android/webkit/HTML5VideoFullScreen.java
Normal file
286
core/java/android/webkit/HTML5VideoFullScreen.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
99
core/java/android/webkit/HTML5VideoInline.java
Normal file
99
core/java/android/webkit/HTML5VideoInline.java
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -4,72 +4,93 @@ 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;
|
||||
|
||||
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() {
|
||||
if (mCurrentState == STATE_PREPARED) {
|
||||
mPlayer.start();
|
||||
mReadyToUseSurfTex = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void pause() {
|
||||
mPlayer.pause();
|
||||
if (mCurrentState == STATE_PREPARED && mPlayer.isPlaying()) {
|
||||
mPlayer.pause();
|
||||
}
|
||||
if (mTimer != null) {
|
||||
mTimer.purge();
|
||||
}
|
||||
}
|
||||
|
||||
public int getDuration() {
|
||||
return mPlayer.getDuration();
|
||||
if (mCurrentState == STATE_PREPARED) {
|
||||
return mPlayer.getDuration();
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
public int getCurrentPosition() {
|
||||
return mPlayer.getCurrentPosition();
|
||||
if (mCurrentState == STATE_PREPARED) {
|
||||
return mPlayer.getCurrentPosition();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public void seekTo(int pos) {
|
||||
@@ -88,53 +109,50 @@ public class HTML5VideoView implements MediaPlayer.OnPreparedListener{
|
||||
}
|
||||
|
||||
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();
|
||||
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.
|
||||
}
|
||||
|
||||
public void setMediaController(MediaController m) {
|
||||
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();
|
||||
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");
|
||||
}
|
||||
|
||||
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:
|
||||
public void setOnCompletionListener(HTML5VideoViewProxy 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 void setFrameAvailableListener(SurfaceTexture.OnFrameAvailableListener l) {
|
||||
mSurfaceTexture.setOnFrameAvailableListener(l);
|
||||
public TimeupdateTask(HTML5VideoViewProxy proxy) {
|
||||
mProxy = proxy;
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -21,29 +21,16 @@ import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.SurfaceTexture;
|
||||
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.Headers;
|
||||
import android.net.http.RequestHandle;
|
||||
import android.net.http.RequestQueue;
|
||||
import android.net.http.SslCertificate;
|
||||
import android.net.http.SslError;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
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.IOException;
|
||||
@@ -51,8 +38,6 @@ import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
/**
|
||||
* <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 PAUSED = 203;
|
||||
|
||||
private static final String COOKIE = "Cookie";
|
||||
private static final String HIDE_URL_LOGS = "x-hide-urls-from-log";
|
||||
|
||||
// Timer thread -> UI thread
|
||||
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
|
||||
// http://b/issue?id=1973663 is fixed.
|
||||
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;
|
||||
// By using the baseLayer and the current video Layer ID, we can
|
||||
// identify the exact layer on the UI thread to use the SurfaceTexture.
|
||||
private static int mBaseLayer = 0;
|
||||
|
||||
// TODO: [FULL SCREEN SUPPORT]
|
||||
|
||||
// Every time webView setBaseLayer, this will be called.
|
||||
// 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.
|
||||
public static void setBaseLayer(int layer) {
|
||||
if (mHTML5VideoView != null) {
|
||||
// Don't do this for full screen mode.
|
||||
if (mHTML5VideoView != null
|
||||
&& !mHTML5VideoView.isFullScreenMode()) {
|
||||
mBaseLayer = layer;
|
||||
SurfaceTexture surfTexture = mHTML5VideoView.getSurfaceTexture();
|
||||
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.
|
||||
// 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.
|
||||
public static void play(String url, int time, HTML5VideoViewProxy proxy,
|
||||
WebChromeClient client, int videoLayerId) {
|
||||
int currentVideoLayerId = -1;
|
||||
if (mHTML5VideoView != null)
|
||||
currentVideoLayerId = mHTML5VideoView.getVideoLayerId();
|
||||
boolean backFromFullScreenMode = false;
|
||||
|
||||
if (currentVideoLayerId != videoLayerId
|
||||
if (mHTML5VideoView != null) {
|
||||
currentVideoLayerId = mHTML5VideoView.getVideoLayerId();
|
||||
if (mHTML5VideoView instanceof HTML5VideoFullScreen) {
|
||||
backFromFullScreenMode = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (backFromFullScreenMode
|
||||
|| currentVideoLayerId != videoLayerId
|
||||
|| mHTML5VideoView.getSurfaceTexture() == null) {
|
||||
// Here, we handle the case when switching to a new video,
|
||||
// either inside a WebView or across WebViews
|
||||
@@ -186,35 +180,11 @@ class HTML5VideoViewProxy extends Handler
|
||||
// release the media player to avoid finalize error
|
||||
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;
|
||||
mHTML5VideoView = new HTML5VideoInline(videoLayerId, time, false);
|
||||
|
||||
// TODO: [FULL SCREEN SUPPORT]
|
||||
|
||||
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();
|
||||
|
||||
mHTML5VideoView.setVideoURI(url, mCurrentProxy);
|
||||
mHTML5VideoView.prepareDataAndDisplayMode(proxy);
|
||||
} else if (mCurrentProxy == proxy) {
|
||||
// Here, we handle the case when we keep playing with one video
|
||||
if (!mHTML5VideoView.isPlaying()) {
|
||||
@@ -222,7 +192,8 @@ class HTML5VideoViewProxy extends Handler
|
||||
mHTML5VideoView.start();
|
||||
}
|
||||
} 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();
|
||||
}
|
||||
}
|
||||
@@ -249,14 +220,14 @@ class HTML5VideoViewProxy extends Handler
|
||||
public static void pause(HTML5VideoViewProxy proxy) {
|
||||
if (mCurrentProxy == proxy && mHTML5VideoView != null) {
|
||||
mHTML5VideoView.pause();
|
||||
mTimer.purge();
|
||||
}
|
||||
}
|
||||
|
||||
public static void onPrepared() {
|
||||
mHTML5VideoView.start();
|
||||
mTimer.schedule(new TimeupdateTask(mCurrentProxy), TIMEUPDATE_PERIOD, TIMEUPDATE_PERIOD);
|
||||
// TODO: [FULL SCREEN SUPPORT]
|
||||
if (!mHTML5VideoView.isFullScreenMode() ||
|
||||
mHTML5VideoView.isFullScreenMode() &&
|
||||
mHTML5VideoView.getAutostart() )
|
||||
mHTML5VideoView.start();
|
||||
}
|
||||
|
||||
public static void end() {
|
||||
@@ -349,8 +320,6 @@ class HTML5VideoViewProxy extends Handler
|
||||
VideoPlayer.isVideoSelfEnded = true;
|
||||
VideoPlayer.end();
|
||||
break;
|
||||
// TODO: [FULL SCREEN SUPPORT]
|
||||
// For full screen case, end may need hide the view.
|
||||
case ERROR: {
|
||||
WebChromeClient client = mWebView.getWebChromeClient();
|
||||
if (client != null) {
|
||||
@@ -665,7 +634,7 @@ class HTML5VideoViewProxy extends Handler
|
||||
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) {
|
||||
VideoPlayer.setBaseLayer(layer);
|
||||
}
|
||||
@@ -673,6 +642,11 @@ class HTML5VideoViewProxy extends Handler
|
||||
public void pauseAndDispatch() {
|
||||
VideoPlayer.pauseAndDispatch();
|
||||
}
|
||||
|
||||
public void enterFullScreenVideo(int layerId, String url) {
|
||||
VideoPlayer.enterFullScreenVideo(layerId, url, this, mWebView);
|
||||
}
|
||||
|
||||
/**
|
||||
* The factory for HTML5VideoViewProxy instances.
|
||||
* @param webViewCore is the WebViewCore that is requesting the proxy.
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -483,10 +483,12 @@ final class WebViewCore {
|
||||
/**
|
||||
* 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;
|
||||
Message.obtain(mWebView.mPrivateHandler,
|
||||
WebView.ENTER_FULLSCREEN_VIDEO, layerId, 0).sendToTarget();
|
||||
Message message = Message.obtain(mWebView.mPrivateHandler,
|
||||
WebView.ENTER_FULLSCREEN_VIDEO, layerId, 0);
|
||||
message.obj = url;
|
||||
message.sendToTarget();
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user