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