Chromium Code Reviews| Index: android_webview/java/src/org/chromium/android_webview/ExternalVideoSurfaceContainer.java |
| diff --git a/android_webview/java/src/org/chromium/android_webview/ExternalVideoSurfaceContainer.java b/android_webview/java/src/org/chromium/android_webview/ExternalVideoSurfaceContainer.java |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..f92966e0e1bf4e51ab5297597035125b4050f491 |
| --- /dev/null |
| +++ b/android_webview/java/src/org/chromium/android_webview/ExternalVideoSurfaceContainer.java |
| @@ -0,0 +1,259 @@ |
| +// Copyright 2014 The Chromium Authors. All rights reserved. |
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +package org.chromium.android_webview; |
| + |
| +import android.content.Context; |
| +import android.graphics.Canvas; |
| +import android.view.Surface; |
| +import android.view.SurfaceHolder; |
| +import android.view.SurfaceView; |
| +import android.view.ViewGroup; |
| + |
| +import org.chromium.base.CalledByNative; |
| +import org.chromium.base.JNINamespace; |
| +import org.chromium.content.browser.ContentViewCore; |
| +import org.chromium.content.browser.RenderCoordinates; |
| + |
| +import java.lang.ref.WeakReference; |
|
Ted C
2014/02/18 20:03:14
one too many spaces after import
ycheo (away)
2014/02/18 22:28:40
Done.
|
| + |
| +/** |
| + * This is a container for external video surfaces. |
| + * The object is owned by the native peer and it is owned by WebContents. |
| + * |
| + * The expected behavior of the media player on the video hole punching is as follows. |
| + * 1) If it requests the surface, it will call requestExternalVideoSurface(). |
| + * When the resolution of the video is changed, it'll call requestExternalVideoSurface(). |
| + * 2) Whenever the size or the position of the video element is changed, it'll notify through |
| + * onExternalVideoSurfacePositionChanged(). |
| + * 3) Whenever the page that contains the video element is scrolled or zoomed, |
| + * onFrameInfoUpdated() will be called. |
| + * 4) Usually steps 1) ~ 3) are repeated during the playback. |
| + * 5) If the player no longer needs the surface any more, it'll call |
| + * releaseExternalVideoSurface(). |
| + * |
| + * Please contact ycheo@chromium.org or wonsik@chromium.org if you have any |
| + * questions or issues for this class. |
| + */ |
| +@JNINamespace("android_webview") |
| +public class ExternalVideoSurfaceContainer implements SurfaceHolder.Callback { |
| + private static final int INVALID_PLAYER_ID = -1; |
| + |
| + // Because WebView does hole-punching by itself, instead, the hole-punching logic |
| + // in SurfaceView can clear out some web elements like media control or subtitle. |
| + // So we need to disable its hole-punching logic. |
| + private static class NoPunchingSurfaceView extends SurfaceView { |
| + public NoPunchingSurfaceView(Context context) { |
| + super(context); |
| + } |
| + // SurfaceView.dispatchDraw implementation punches a hole in the view hierarchy. |
| + // Disable this by making this a no-op. |
| + @Override |
| + protected void dispatchDraw(Canvas canvas) {} |
| + } |
| + |
| + // There can be at most 1 external video surface for now. |
| + // If there are the multiple requests for the surface, then the second video will |
| + // kick the first one off. |
| + // To support the mulitple video surfaces seems impractical, because z-order between |
| + // the multiple SurfaceViews is non-deterministic. |
| + private static WeakReference<ExternalVideoSurfaceContainer> sActiveContainer = |
| + new WeakReference<ExternalVideoSurfaceContainer>(null); |
| + |
| + private final int mNativeExternalVideoSurfaceContainer; |
| + private final ContentViewCore mContentViewCore; |
| + private int mPlayerId = INVALID_PLAYER_ID; |
| + private SurfaceView mSurfaceView; |
| + |
| + // The absolute CSS coordinates of the video element. |
| + private float mLeft; |
| + private float mTop; |
| + private float mRight; |
| + private float mBottom; |
| + |
| + // The physical location/size of the external video surface. |
| + private float mX; |
| + private float mY; |
| + private int mWidth; |
| + private int mHeight; |
| + |
| + @CalledByNative |
| + private static ExternalVideoSurfaceContainer create( |
| + int nativeExternalVideoSurfaceContainer, ContentViewCore contentViewCore) { |
| + return new ExternalVideoSurfaceContainer( |
| + nativeExternalVideoSurfaceContainer, contentViewCore); |
| + } |
| + |
| + private ExternalVideoSurfaceContainer( |
| + int nativeExternalVideoSurfaceContainer, ContentViewCore contentViewCore) { |
| + assert contentViewCore != null; |
| + mNativeExternalVideoSurfaceContainer = nativeExternalVideoSurfaceContainer; |
| + mContentViewCore = contentViewCore; |
| + initializeCurrentPositionOfSurfaceView(); |
| + } |
| + |
| + /** |
| + * Called when a media player wants to request an external video surface. |
| + * @param playerId The ID of the media player. |
| + */ |
| + @CalledByNative |
| + private void requestExternalVideoSurface(int playerId) { |
| + if (mPlayerId == playerId) return; |
| + |
| + if (mPlayerId == INVALID_PLAYER_ID) { |
| + setActiveContainer(this); |
| + } |
| + |
| + mPlayerId = playerId; |
| + initializeCurrentPositionOfSurfaceView(); |
| + |
| + createSurfaceView(); |
| + } |
| + |
| + /** |
| + * Called when a media player wants to release an external video surface. |
| + * @param playerId The ID of the media player. |
| + */ |
| + @CalledByNative |
| + private void releaseExternalVideoSurface(int playerId) { |
| + if (mPlayerId != playerId) return; |
| + |
| + releaseIfActiveContainer(this); |
| + |
| + mPlayerId = INVALID_PLAYER_ID; |
| + } |
| + |
| + @CalledByNative |
| + private void destroy() { |
| + releaseExternalVideoSurface(mPlayerId); |
| + } |
| + |
| + private void initializeCurrentPositionOfSurfaceView() { |
| + mX = Float.NaN; |
| + mY = Float.NaN; |
| + mWidth = 0; |
| + mHeight = 0; |
| + } |
| + |
| + private static void setActiveContainer(ExternalVideoSurfaceContainer container) { |
| + ExternalVideoSurfaceContainer activeContainer = sActiveContainer.get(); |
| + if (activeContainer != null) { |
| + activeContainer.removeSurfaceView(); |
| + } |
| + sActiveContainer = new WeakReference<ExternalVideoSurfaceContainer>(container); |
| + } |
| + |
| + private static void releaseIfActiveContainer(ExternalVideoSurfaceContainer container) { |
| + ExternalVideoSurfaceContainer activeContainer = sActiveContainer.get(); |
| + if (activeContainer == container) { |
| + setActiveContainer(null); |
| + } |
| + } |
| + |
| + private void createSurfaceView() { |
| + mSurfaceView = new NoPunchingSurfaceView(mContentViewCore.getContext()); |
| + mSurfaceView.getHolder().addCallback(this); |
| + // SurfaceHoder.surfaceCreated() will be called after the SurfaceView is attached to |
| + // the Window and becomes visible. |
| + mContentViewCore.getContainerView().addView(mSurfaceView); |
| + } |
| + |
| + private void removeSurfaceView() { |
| + // SurfaceHoder.surfaceDestroyed() will be called in ViewGroup.removeView() |
| + // as soon as the SurfaceView is detached from the Window. |
| + mContentViewCore.getContainerView().removeView(mSurfaceView); |
| + mSurfaceView = null; |
| + } |
| + |
| + /** |
| + * Called when the position of the video element which uses the external |
| + * video surface is changed. |
| + * @param playerId The ID of the media player. |
| + * @param left The absolute CSS X coordinate of the left side of the video element. |
| + * @param top The absolute CSS Y coordinate of the top side of the video element. |
| + * @param right The absolute CSS X coordinate of the right side of the video element. |
| + * @param bottom The absolute CSS Y coordinate of the bottom side of the video element. |
| + */ |
| + @CalledByNative |
| + private void onExternalVideoSurfacePositionChanged( |
| + int playerId, float left, float top, float right, float bottom) { |
| + if (mPlayerId != playerId) return; |
| + |
| + mLeft = left; |
| + mTop = top; |
| + mRight = right; |
| + mBottom = bottom; |
| + |
| + layOutSurfaceView(); |
| + } |
| + |
| + /** |
| + * Called when the page that contains the video element is scrolled or zoomed. |
| + */ |
| + @CalledByNative |
| + private void onFrameInfoUpdated() { |
| + if (mPlayerId == INVALID_PLAYER_ID) return; |
| + |
| + layOutSurfaceView(); |
| + } |
| + |
| + private void layOutSurfaceView() { |
| + RenderCoordinates renderCoordinates = mContentViewCore.getRenderCoordinates(); |
| + RenderCoordinates.NormalizedPoint topLeft = renderCoordinates.createNormalizedPoint(); |
| + RenderCoordinates.NormalizedPoint bottomRight = renderCoordinates.createNormalizedPoint(); |
| + topLeft.setAbsoluteCss(mLeft, mTop); |
| + bottomRight.setAbsoluteCss(mRight, mBottom); |
| + float top = topLeft.getYPix(); |
| + float left = topLeft.getXPix(); |
| + float bottom = bottomRight.getYPix(); |
| + float right = bottomRight.getXPix(); |
| + |
| + float x = left + renderCoordinates.getScrollXPix(); |
| + float y = top + renderCoordinates.getScrollYPix(); |
| + int width = Math.round(right - left); |
| + int height = Math.round(bottom - top); |
| + if (mX == x && mY == y && mWidth == width && mHeight == height) return; |
| + mX = x; |
| + mY = y; |
| + mWidth = width; |
| + mHeight = height; |
| + |
| + mSurfaceView.setX(x); |
| + mSurfaceView.setY(y); |
| + ViewGroup.LayoutParams layoutParams = mSurfaceView.getLayoutParams(); |
| + layoutParams.width = width; |
| + layoutParams.height = height; |
| + mSurfaceView.requestLayout(); |
| + } |
| + |
| + // SurfaceHolder.Callback methods. |
| + @Override |
| + public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {} |
| + |
| + @Override |
| + // surfaceCreated() callback can be called regardless of requestExternalVideoSurface, |
| + // if the activity comes back from the background and becomes visible. |
| + public void surfaceCreated(SurfaceHolder holder) { |
| + if (mPlayerId != INVALID_PLAYER_ID) { |
| + nativeSurfaceCreated( |
| + mNativeExternalVideoSurfaceContainer, mPlayerId, holder.getSurface()); |
| + } |
| + } |
| + |
| + // surfaceDestroyed() callback can be called regardless of releaseExternalVideoSurface, |
| + // if the activity moves to the backgound and becomes invisible. |
| + @Override |
| + public void surfaceDestroyed(SurfaceHolder holder) { |
| + if (mPlayerId != INVALID_PLAYER_ID) { |
| + nativeSurfaceDestroyed(mNativeExternalVideoSurfaceContainer, mPlayerId); |
| + } |
| + } |
| + |
| + private native void nativeSurfaceCreated( |
| + long nativeExternalVideoSurfaceContainerImpl, int playerId, Surface surface); |
| + |
| + private native void nativeSurfaceDestroyed( |
| + long nativeExternalVideoSurfaceContainerImpl, int playerId); |
| +} |
| + |