• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 package org.chromium.android_webview;
6 
7 import android.content.Context;
8 import android.graphics.Canvas;
9 import android.view.Surface;
10 import android.view.SurfaceHolder;
11 import android.view.SurfaceView;
12 import android.view.ViewGroup;
13 
14 import com.google.common.annotations.VisibleForTesting;
15 
16 import org.chromium.base.CalledByNative;
17 import org.chromium.base.JNINamespace;
18 import org.chromium.content.browser.ContentViewCore;
19 import org.chromium.content.browser.RenderCoordinates;
20 
21 import java.lang.ref.WeakReference;
22 
23 /**
24  * This is a container for external video surfaces.
25  * The object is owned by the native peer and it is owned by WebContents.
26  *
27  * The expected behavior of the media player on the video hole punching is as follows.
28  * 1) If it requests the surface, it will call requestExternalVideoSurface().
29  *    When the resolution of the video is changed, it'll call requestExternalVideoSurface().
30  * 2) Whenever the size or the position of the video element is changed, it'll notify through
31  *    onExternalVideoSurfacePositionChanged().
32  * 3) Whenever the page that contains the video element is scrolled or zoomed,
33  *    onFrameInfoUpdated() will be called.
34  * 4) Usually steps 1) ~ 3) are repeated during the playback.
35  * 5) If the player no longer needs the surface any more, it'll call
36  *    releaseExternalVideoSurface().
37  *
38  * Please contact ycheo@chromium.org or wonsik@chromium.org if you have any
39  * questions or issues for this class.
40  */
41 @JNINamespace("android_webview")
42 public class ExternalVideoSurfaceContainer implements SurfaceHolder.Callback {
43     protected static final int INVALID_PLAYER_ID = -1;
44 
45     // Because WebView does hole-punching by itself, instead, the hole-punching logic
46     // in SurfaceView can clear out some web elements like media control or subtitle.
47     // So we need to disable its hole-punching logic.
48     private static class NoPunchingSurfaceView extends SurfaceView {
NoPunchingSurfaceView(Context context)49         public NoPunchingSurfaceView(Context context) {
50             super(context);
51         }
52         // SurfaceView.dispatchDraw implementation punches a hole in the view hierarchy.
53         // Disable this by making this a no-op.
54         @Override
dispatchDraw(Canvas canvas)55         protected void dispatchDraw(Canvas canvas) {}
56     }
57 
58     // There can be at most 1 external video surface for now.
59     // If there are the multiple requests for the surface, then the second video will
60     // kick the first one off.
61     // To support the mulitple video surfaces seems impractical, because z-order between
62     // the multiple SurfaceViews is non-deterministic.
63     private static WeakReference<ExternalVideoSurfaceContainer> sActiveContainer =
64             new WeakReference<ExternalVideoSurfaceContainer>(null);
65 
66     private final long mNativeExternalVideoSurfaceContainer;
67     private final ContentViewCore mContentViewCore;
68     private int mPlayerId = INVALID_PLAYER_ID;
69     private SurfaceView mSurfaceView;
70 
71     // The absolute CSS coordinates of the video element.
72     private float mLeft;
73     private float mTop;
74     private float mRight;
75     private float mBottom;
76 
77     // The physical location/size of the external video surface in pixels.
78     private int mX;
79     private int mY;
80     private int mWidth;
81     private int mHeight;
82 
83     /**
84      * Factory class to facilitate dependency injection.
85      */
86     public static class Factory {
create( long nativeExternalVideoSurfaceContainer, ContentViewCore contentViewCore)87         public ExternalVideoSurfaceContainer create(
88                 long nativeExternalVideoSurfaceContainer, ContentViewCore contentViewCore) {
89             return new ExternalVideoSurfaceContainer(
90                     nativeExternalVideoSurfaceContainer, contentViewCore);
91         }
92     }
93     private static Factory sFactory = new Factory();
94 
95     @VisibleForTesting
setFactory(Factory factory)96     public static void setFactory(Factory factory) {
97         sFactory = factory;
98     }
99 
100     @CalledByNative
create( long nativeExternalVideoSurfaceContainer, ContentViewCore contentViewCore)101     private static ExternalVideoSurfaceContainer create(
102             long nativeExternalVideoSurfaceContainer, ContentViewCore contentViewCore) {
103         return sFactory.create(nativeExternalVideoSurfaceContainer, contentViewCore);
104     }
105 
ExternalVideoSurfaceContainer( long nativeExternalVideoSurfaceContainer, ContentViewCore contentViewCore)106     protected ExternalVideoSurfaceContainer(
107             long nativeExternalVideoSurfaceContainer, ContentViewCore contentViewCore) {
108         assert contentViewCore != null;
109         mNativeExternalVideoSurfaceContainer = nativeExternalVideoSurfaceContainer;
110         mContentViewCore = contentViewCore;
111         initializeCurrentPositionOfSurfaceView();
112     }
113 
114     /**
115      * Called when a media player wants to request an external video surface.
116      * @param playerId The ID of the media player.
117      */
118     @CalledByNative
requestExternalVideoSurface(int playerId)119     protected void requestExternalVideoSurface(int playerId) {
120         if (mPlayerId == playerId) return;
121 
122         if (mPlayerId == INVALID_PLAYER_ID) {
123             setActiveContainer(this);
124         }
125 
126         mPlayerId = playerId;
127         initializeCurrentPositionOfSurfaceView();
128 
129         createSurfaceView();
130     }
131 
132     /**
133      * Called when a media player wants to release an external video surface.
134      * @param playerId The ID of the media player.
135      */
136     @CalledByNative
releaseExternalVideoSurface(int playerId)137     protected void releaseExternalVideoSurface(int playerId) {
138         if (mPlayerId != playerId) return;
139 
140         releaseIfActiveContainer(this);
141 
142         mPlayerId = INVALID_PLAYER_ID;
143     }
144 
145     @CalledByNative
destroy()146     protected void destroy() {
147         releaseExternalVideoSurface(mPlayerId);
148     }
149 
initializeCurrentPositionOfSurfaceView()150     private void initializeCurrentPositionOfSurfaceView() {
151         mX = Integer.MIN_VALUE;
152         mY = Integer.MIN_VALUE;
153         mWidth = 0;
154         mHeight = 0;
155     }
156 
setActiveContainer(ExternalVideoSurfaceContainer container)157     private static void setActiveContainer(ExternalVideoSurfaceContainer container) {
158         ExternalVideoSurfaceContainer activeContainer = sActiveContainer.get();
159         if (activeContainer != null) {
160             activeContainer.removeSurfaceView();
161         }
162         sActiveContainer = new WeakReference<ExternalVideoSurfaceContainer>(container);
163     }
164 
releaseIfActiveContainer(ExternalVideoSurfaceContainer container)165     private static void releaseIfActiveContainer(ExternalVideoSurfaceContainer container) {
166         ExternalVideoSurfaceContainer activeContainer = sActiveContainer.get();
167         if (activeContainer == container) {
168             setActiveContainer(null);
169         }
170     }
171 
createSurfaceView()172     private void createSurfaceView() {
173         mSurfaceView = new NoPunchingSurfaceView(mContentViewCore.getContext());
174         mSurfaceView.getHolder().addCallback(this);
175         // SurfaceHoder.surfaceCreated() will be called after the SurfaceView is attached to
176         // the Window and becomes visible.
177         mContentViewCore.getContainerView().addView(mSurfaceView);
178     }
179 
removeSurfaceView()180     private void removeSurfaceView() {
181         // SurfaceHoder.surfaceDestroyed() will be called in ViewGroup.removeView()
182         // as soon as the SurfaceView is detached from the Window.
183         mContentViewCore.getContainerView().removeView(mSurfaceView);
184         mSurfaceView = null;
185     }
186 
187     /**
188      * Called when the position of the video element which uses the external
189      * video surface is changed.
190      * @param playerId The ID of the media player.
191      * @param left The absolute CSS X coordinate of the left side of the video element.
192      * @param top The absolute CSS Y coordinate of the top side of the video element.
193      * @param right The absolute CSS X coordinate of the right side of the video element.
194      * @param bottom The absolute CSS Y coordinate of the bottom side of the video element.
195      */
196     @CalledByNative
onExternalVideoSurfacePositionChanged( int playerId, float left, float top, float right, float bottom)197     protected void onExternalVideoSurfacePositionChanged(
198             int playerId, float left, float top, float right, float bottom) {
199         if (mPlayerId != playerId) return;
200 
201         mLeft = left;
202         mTop = top;
203         mRight = right;
204         mBottom = bottom;
205 
206         layOutSurfaceView();
207     }
208 
209     /**
210      * Called when the page that contains the video element is scrolled or zoomed.
211      */
212     @CalledByNative
onFrameInfoUpdated()213     protected void onFrameInfoUpdated() {
214         if (mPlayerId == INVALID_PLAYER_ID) return;
215 
216         layOutSurfaceView();
217     }
218 
layOutSurfaceView()219     private void layOutSurfaceView() {
220         RenderCoordinates renderCoordinates = mContentViewCore.getRenderCoordinates();
221         RenderCoordinates.NormalizedPoint topLeft = renderCoordinates.createNormalizedPoint();
222         RenderCoordinates.NormalizedPoint bottomRight = renderCoordinates.createNormalizedPoint();
223         topLeft.setAbsoluteCss(mLeft, mTop);
224         bottomRight.setAbsoluteCss(mRight, mBottom);
225         float top = topLeft.getYPix();
226         float left = topLeft.getXPix();
227         float bottom = bottomRight.getYPix();
228         float right = bottomRight.getXPix();
229 
230         int x = Math.round(left + renderCoordinates.getScrollXPix());
231         int y = Math.round(top + renderCoordinates.getScrollYPix());
232         int width = Math.round(right - left);
233         int height = Math.round(bottom - top);
234         if (mX == x && mY == y && mWidth == width && mHeight == height) return;
235         mX = x;
236         mY = y;
237         mWidth = width;
238         mHeight = height;
239 
240         mSurfaceView.setX(x);
241         mSurfaceView.setY(y);
242         ViewGroup.LayoutParams layoutParams = mSurfaceView.getLayoutParams();
243         layoutParams.width = width;
244         layoutParams.height = height;
245         mSurfaceView.requestLayout();
246     }
247 
248     // SurfaceHolder.Callback methods.
249     @Override
surfaceChanged(SurfaceHolder holder, int format, int width, int height)250     public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {}
251 
252     @Override
253     // surfaceCreated() callback can be called regardless of requestExternalVideoSurface,
254     // if the activity comes back from the background and becomes visible.
surfaceCreated(SurfaceHolder holder)255     public void surfaceCreated(SurfaceHolder holder) {
256         if (mPlayerId != INVALID_PLAYER_ID) {
257             nativeSurfaceCreated(
258                     mNativeExternalVideoSurfaceContainer, mPlayerId, holder.getSurface());
259         }
260     }
261 
262     // surfaceDestroyed() callback can be called regardless of releaseExternalVideoSurface,
263     // if the activity moves to the backgound and becomes invisible.
264     @Override
surfaceDestroyed(SurfaceHolder holder)265     public void surfaceDestroyed(SurfaceHolder holder) {
266         if (mPlayerId != INVALID_PLAYER_ID) {
267             nativeSurfaceDestroyed(mNativeExternalVideoSurfaceContainer, mPlayerId);
268         }
269     }
270 
nativeSurfaceCreated( long nativeExternalVideoSurfaceContainerImpl, int playerId, Surface surface)271     private native void nativeSurfaceCreated(
272             long nativeExternalVideoSurfaceContainerImpl, int playerId, Surface surface);
273 
nativeSurfaceDestroyed( long nativeExternalVideoSurfaceContainerImpl, int playerId)274     private native void nativeSurfaceDestroyed(
275             long nativeExternalVideoSurfaceContainerImpl, int playerId);
276 }
277 
278