| OLD | NEW |
| (Empty) |
| 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.chromecast.shell; | |
| 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 fo
llows. | |
| 27 * 1) If it requests the surface, it will call requestExternalVideoSurface(). | |
| 28 * When the resolution of the video is changed, it'll call requestExternalVid
eoSurface(). | |
| 29 * 2) Whenever the size or the position of the video element is changed, it'll n
otify 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 * Note(gunsch): This class was copied wholesale from android_webview/native | |
| 41 * for the use of chromecast/. Owners of content/ have expressed not wanting | |
| 42 * this code in content/ to discourage usage, since they don't plan to support | |
| 43 * hole-punching long term. In the meantime, this copy is a workaround to avoid | |
| 44 * a dependency on android_webview. | |
| 45 */ | |
| 46 @JNINamespace("chromecast::shell") | |
| 47 public class ExternalVideoSurfaceContainer implements SurfaceHolder.Callback { | |
| 48 protected static final int INVALID_PLAYER_ID = -1; | |
| 49 | |
| 50 // Because WebView does hole-punching by itself, instead, the hole-punching
logic | |
| 51 // in SurfaceView can clear out some web elements like media control or subt
itle. | |
| 52 // So we need to disable its hole-punching logic. | |
| 53 private static class NoPunchingSurfaceView extends SurfaceView { | |
| 54 public NoPunchingSurfaceView(Context context) { | |
| 55 super(context); | |
| 56 } | |
| 57 // SurfaceView.dispatchDraw implementation punches a hole in the view hi
erarchy. | |
| 58 // Disable this by making this a no-op. | |
| 59 @Override | |
| 60 protected void dispatchDraw(Canvas canvas) {} | |
| 61 } | |
| 62 | |
| 63 // There can be at most 1 external video surface for now. | |
| 64 // If there are the multiple requests for the surface, then the second video
will | |
| 65 // kick the first one off. | |
| 66 // To support the mulitple video surfaces seems impractical, because z-order
between | |
| 67 // the multiple SurfaceViews is non-deterministic. | |
| 68 private static WeakReference<ExternalVideoSurfaceContainer> sActiveContainer
= | |
| 69 new WeakReference<ExternalVideoSurfaceContainer>(null); | |
| 70 | |
| 71 private final long mNativeExternalVideoSurfaceContainer; | |
| 72 private final ContentViewCore mContentViewCore; | |
| 73 private int mPlayerId = INVALID_PLAYER_ID; | |
| 74 private SurfaceView mSurfaceView; | |
| 75 | |
| 76 // The absolute CSS coordinates of the video element. | |
| 77 private float mLeft; | |
| 78 private float mTop; | |
| 79 private float mRight; | |
| 80 private float mBottom; | |
| 81 | |
| 82 // The physical location/size of the external video surface in pixels. | |
| 83 private int mX; | |
| 84 private int mY; | |
| 85 private int mWidth; | |
| 86 private int mHeight; | |
| 87 | |
| 88 /** | |
| 89 * Factory class to facilitate dependency injection. | |
| 90 */ | |
| 91 public static class Factory { | |
| 92 public ExternalVideoSurfaceContainer create( | |
| 93 long nativeExternalVideoSurfaceContainer, ContentViewCore conten
tViewCore) { | |
| 94 return new ExternalVideoSurfaceContainer( | |
| 95 nativeExternalVideoSurfaceContainer, contentViewCore); | |
| 96 } | |
| 97 } | |
| 98 private static Factory sFactory = new Factory(); | |
| 99 | |
| 100 @VisibleForTesting | |
| 101 public static void setFactory(Factory factory) { | |
| 102 sFactory = factory; | |
| 103 } | |
| 104 | |
| 105 @CalledByNative | |
| 106 private static ExternalVideoSurfaceContainer create( | |
| 107 long nativeExternalVideoSurfaceContainer, ContentViewCore contentVie
wCore) { | |
| 108 return sFactory.create(nativeExternalVideoSurfaceContainer, contentViewC
ore); | |
| 109 } | |
| 110 | |
| 111 protected ExternalVideoSurfaceContainer( | |
| 112 long nativeExternalVideoSurfaceContainer, ContentViewCore contentVie
wCore) { | |
| 113 assert contentViewCore != null; | |
| 114 mNativeExternalVideoSurfaceContainer = nativeExternalVideoSurfaceContain
er; | |
| 115 mContentViewCore = contentViewCore; | |
| 116 initializeCurrentPositionOfSurfaceView(); | |
| 117 } | |
| 118 | |
| 119 /** | |
| 120 * Called when a media player wants to request an external video surface. | |
| 121 * @param playerId The ID of the media player. | |
| 122 */ | |
| 123 @CalledByNative | |
| 124 protected void requestExternalVideoSurface(int playerId) { | |
| 125 if (mPlayerId == playerId) return; | |
| 126 | |
| 127 if (mPlayerId == INVALID_PLAYER_ID) { | |
| 128 setActiveContainer(this); | |
| 129 } | |
| 130 | |
| 131 mPlayerId = playerId; | |
| 132 initializeCurrentPositionOfSurfaceView(); | |
| 133 | |
| 134 createSurfaceView(); | |
| 135 } | |
| 136 | |
| 137 /** | |
| 138 * Called when a media player wants to release an external video surface. | |
| 139 * @param playerId The ID of the media player. | |
| 140 */ | |
| 141 @CalledByNative | |
| 142 protected void releaseExternalVideoSurface(int playerId) { | |
| 143 if (mPlayerId != playerId) return; | |
| 144 | |
| 145 releaseIfActiveContainer(this); | |
| 146 | |
| 147 mPlayerId = INVALID_PLAYER_ID; | |
| 148 } | |
| 149 | |
| 150 @CalledByNative | |
| 151 protected void destroy() { | |
| 152 releaseExternalVideoSurface(mPlayerId); | |
| 153 } | |
| 154 | |
| 155 private void initializeCurrentPositionOfSurfaceView() { | |
| 156 mX = Integer.MIN_VALUE; | |
| 157 mY = Integer.MIN_VALUE; | |
| 158 mWidth = 0; | |
| 159 mHeight = 0; | |
| 160 } | |
| 161 | |
| 162 private static void setActiveContainer(ExternalVideoSurfaceContainer contain
er) { | |
| 163 ExternalVideoSurfaceContainer activeContainer = sActiveContainer.get(); | |
| 164 if (activeContainer != null) { | |
| 165 activeContainer.removeSurfaceView(); | |
| 166 } | |
| 167 sActiveContainer = new WeakReference<ExternalVideoSurfaceContainer>(cont
ainer); | |
| 168 } | |
| 169 | |
| 170 private static void releaseIfActiveContainer(ExternalVideoSurfaceContainer c
ontainer) { | |
| 171 ExternalVideoSurfaceContainer activeContainer = sActiveContainer.get(); | |
| 172 if (activeContainer == container) { | |
| 173 setActiveContainer(null); | |
| 174 } | |
| 175 } | |
| 176 | |
| 177 private void createSurfaceView() { | |
| 178 mSurfaceView = new NoPunchingSurfaceView(mContentViewCore.getContext()); | |
| 179 mSurfaceView.getHolder().addCallback(this); | |
| 180 // SurfaceHoder.surfaceCreated() will be called after the SurfaceView is
attached to | |
| 181 // the Window and becomes visible. | |
| 182 mContentViewCore.getContainerView().addView(mSurfaceView); | |
| 183 } | |
| 184 | |
| 185 private void removeSurfaceView() { | |
| 186 // SurfaceHoder.surfaceDestroyed() will be called in ViewGroup.removeVie
w() | |
| 187 // as soon as the SurfaceView is detached from the Window. | |
| 188 mContentViewCore.getContainerView().removeView(mSurfaceView); | |
| 189 mSurfaceView = null; | |
| 190 } | |
| 191 | |
| 192 /** | |
| 193 * Called when the position of the video element which uses the external | |
| 194 * video surface is changed. | |
| 195 * @param playerId The ID of the media player. | |
| 196 * @param left The absolute CSS X coordinate of the left side of the video e
lement. | |
| 197 * @param top The absolute CSS Y coordinate of the top side of the video ele
ment. | |
| 198 * @param right The absolute CSS X coordinate of the right side of the video
element. | |
| 199 * @param bottom The absolute CSS Y coordinate of the bottom side of the vid
eo element. | |
| 200 */ | |
| 201 @CalledByNative | |
| 202 protected void onExternalVideoSurfacePositionChanged( | |
| 203 int playerId, float left, float top, float right, float bottom) { | |
| 204 if (mPlayerId != playerId) return; | |
| 205 | |
| 206 mLeft = left; | |
| 207 mTop = top; | |
| 208 mRight = right; | |
| 209 mBottom = bottom; | |
| 210 | |
| 211 layOutSurfaceView(); | |
| 212 } | |
| 213 | |
| 214 /** | |
| 215 * Called when the page that contains the video element is scrolled or zoome
d. | |
| 216 */ | |
| 217 @CalledByNative | |
| 218 protected void onFrameInfoUpdated() { | |
| 219 if (mPlayerId == INVALID_PLAYER_ID) return; | |
| 220 | |
| 221 layOutSurfaceView(); | |
| 222 } | |
| 223 | |
| 224 private void layOutSurfaceView() { | |
| 225 RenderCoordinates renderCoordinates = mContentViewCore.getRenderCoordina
tes(); | |
| 226 RenderCoordinates.NormalizedPoint topLeft = renderCoordinates.createNorm
alizedPoint(); | |
| 227 RenderCoordinates.NormalizedPoint bottomRight = renderCoordinates.create
NormalizedPoint(); | |
| 228 topLeft.setAbsoluteCss(mLeft, mTop); | |
| 229 bottomRight.setAbsoluteCss(mRight, mBottom); | |
| 230 float top = topLeft.getYPix(); | |
| 231 float left = topLeft.getXPix(); | |
| 232 float bottom = bottomRight.getYPix(); | |
| 233 float right = bottomRight.getXPix(); | |
| 234 | |
| 235 int x = Math.round(left + renderCoordinates.getScrollXPix()); | |
| 236 int y = Math.round(top + renderCoordinates.getScrollYPix()); | |
| 237 int width = Math.round(right - left); | |
| 238 int height = Math.round(bottom - top); | |
| 239 if (mX == x && mY == y && mWidth == width && mHeight == height) return; | |
| 240 mX = x; | |
| 241 mY = y; | |
| 242 mWidth = width; | |
| 243 mHeight = height; | |
| 244 | |
| 245 mSurfaceView.setX(x); | |
| 246 mSurfaceView.setY(y); | |
| 247 ViewGroup.LayoutParams layoutParams = mSurfaceView.getLayoutParams(); | |
| 248 layoutParams.width = width; | |
| 249 layoutParams.height = height; | |
| 250 mSurfaceView.requestLayout(); | |
| 251 } | |
| 252 | |
| 253 // SurfaceHolder.Callback methods. | |
| 254 @Override | |
| 255 public void surfaceChanged(SurfaceHolder holder, int format, int width, int
height) {} | |
| 256 | |
| 257 @Override | |
| 258 // surfaceCreated() callback can be called regardless of requestExternalVide
oSurface, | |
| 259 // if the activity comes back from the background and becomes visible. | |
| 260 public void surfaceCreated(SurfaceHolder holder) { | |
| 261 if (mPlayerId != INVALID_PLAYER_ID) { | |
| 262 nativeSurfaceCreated( | |
| 263 mNativeExternalVideoSurfaceContainer, mPlayerId, holder.getS
urface()); | |
| 264 } | |
| 265 } | |
| 266 | |
| 267 // surfaceDestroyed() callback can be called regardless of releaseExternalVi
deoSurface, | |
| 268 // if the activity moves to the backgound and becomes invisible. | |
| 269 @Override | |
| 270 public void surfaceDestroyed(SurfaceHolder holder) { | |
| 271 if (mPlayerId != INVALID_PLAYER_ID) { | |
| 272 nativeSurfaceDestroyed(mNativeExternalVideoSurfaceContainer, mPlayer
Id); | |
| 273 } | |
| 274 } | |
| 275 | |
| 276 private native void nativeSurfaceCreated( | |
| 277 long nativeExternalVideoSurfaceContainerImpl, int playerId, Surface
surface); | |
| 278 | |
| 279 private native void nativeSurfaceDestroyed( | |
| 280 long nativeExternalVideoSurfaceContainerImpl, int playerId); | |
| 281 } | |
| OLD | NEW |