Clean up GvrPlayerActivity

PiperOrigin-RevId: 274845045
This commit is contained in:
olly 2019-10-15 19:09:09 +01:00 committed by Oliver Woodman
parent 34c531764e
commit 5e538a2a28
8 changed files with 89 additions and 117 deletions

View file

@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.ext.gvr;
import android.content.Context;
@ -21,8 +20,6 @@ import android.content.Intent;
import android.graphics.SurfaceTexture;
import android.opengl.Matrix;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.view.ContextThemeWrapper;
import android.view.MotionEvent;
import android.view.Surface;
@ -47,6 +44,7 @@ import com.google.vr.sdk.base.HeadTransform;
import com.google.vr.sdk.base.Viewport;
import com.google.vr.sdk.controller.Controller;
import com.google.vr.sdk.controller.ControllerManager;
import com.google.vr.sdk.controller.Orientation;
import javax.microedition.khronos.egl.EGLConfig;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@ -58,46 +56,36 @@ public abstract class GvrPlayerActivity extends GvrActivity {
private static final int EXIT_FROM_VR_REQUEST_CODE = 42;
private final Handler mainHandler;
@Nullable private Player player;
private @MonotonicNonNull GlViewGroup glView;
private @MonotonicNonNull ControllerManager controllerManager;
private @MonotonicNonNull SurfaceTexture surfaceTexture;
private @MonotonicNonNull Surface surface;
private @MonotonicNonNull SceneRenderer scene;
private @MonotonicNonNull PlayerControlView playerControl;
public GvrPlayerActivity() {
mainHandler = new Handler(Looper.getMainLooper());
}
private @MonotonicNonNull SceneRenderer sceneRenderer;
private @MonotonicNonNull PlayerControlView playerControlView;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setScreenAlwaysOn(true);
GvrView gvrView = new GvrView(this);
// Since videos typically have fewer pixels per degree than the phones, reducing the render
// target scaling factor reduces the work required to render the scene.
gvrView.setRenderTargetScale(.5f);
GvrView gvrView = new GvrView(/* context= */ this);
gvrView.setRenderTargetScale(getRenderTargetScale());
// If a custom theme isn't specified, the Context's theme is used. For VR Activities, this is
// the old Android default theme rather than a modern theme. Override this with a custom theme.
Context theme = new ContextThemeWrapper(this, R.style.VrTheme);
glView = new GlViewGroup(theme, R.layout.vr_ui);
Context theme = new ContextThemeWrapper(this, R.style.ExoVrTheme);
GlViewGroup glViewGroup = new GlViewGroup(theme, R.layout.exo_vr_ui);
playerControl = Assertions.checkNotNull(glView.findViewById(R.id.controller));
playerControl.setShowVrButton(true);
playerControl.setVrButtonListener(v -> exit());
playerControlView = Assertions.checkNotNull(glViewGroup.findViewById(R.id.controller));
playerControlView.setShowVrButton(true);
playerControlView.setVrButtonListener(v -> exit());
sceneRenderer = new SceneRenderer();
PointerRenderer pointerRenderer = new PointerRenderer();
scene = new SceneRenderer();
Renderer renderer = new Renderer(scene, glView, pointerRenderer);
// Attach glView to gvrView in order to properly handle UI events.
gvrView.addView(glView, 0);
Renderer renderer = new Renderer(sceneRenderer, pointerRenderer, glViewGroup);
// Attach glViewGroup to gvrView in order to properly handle UI events.
gvrView.addView(glViewGroup);
// Standard GvrView configuration
gvrView.setEGLConfigChooser(
8, 8, 8, 8, // RGBA bits.
@ -106,15 +94,13 @@ public abstract class GvrPlayerActivity extends GvrActivity {
gvrView.setRenderer(renderer);
setContentView(gvrView);
// Most Daydream phones can render a 4k video at 60fps in sustained performance mode. These
// options can be tweaked along with the render target scale.
if (gvrView.setAsyncReprojectionEnabled(true)) {
AndroidCompat.setSustainedPerformanceMode(this, true);
AndroidCompat.setSustainedPerformanceMode(/* activity= */ this, true);
}
// Handle the user clicking on the 'X' in the top left corner. Since this is done when the user
// has taken the headset out of VR, it should launch the app's exit flow directly rather than
// using the transition flow.
// using Daydream's exit transition.
gvrView.setOnCloseButtonListener(this::finish);
ControllerManager.EventListener listener =
@ -126,15 +112,14 @@ public abstract class GvrPlayerActivity extends GvrActivity {
@Override
public void onRecentered() {
// TODO if in cardboard mode call gvrView.recenterHeadTracker();
glView.post(() -> Util.castNonNull(playerControl).show());
// TODO: If in cardboard mode call gvrView.recenterHeadTracker().
runOnUiThread(() -> Util.castNonNull(playerControlView).show());
}
};
controllerManager = new ControllerManager(this, listener);
Controller controller = controllerManager.getController();
ControllerEventListener controllerEventListener =
new ControllerEventListener(controller, pointerRenderer, glView);
new ControllerEventListener(controller, pointerRenderer, glViewGroup);
controller.setEventListener(controllerEventListener);
}
@ -144,7 +129,7 @@ public abstract class GvrPlayerActivity extends GvrActivity {
* @param newPlayer The {@link Player} to use, or {@code null} to detach the current player.
*/
protected void setPlayer(@Nullable Player newPlayer) {
Assertions.checkNotNull(scene);
Assertions.checkNotNull(sceneRenderer);
if (player == newPlayer) {
return;
}
@ -154,20 +139,20 @@ public abstract class GvrPlayerActivity extends GvrActivity {
if (surface != null) {
videoComponent.clearVideoSurface(surface);
}
videoComponent.clearVideoFrameMetadataListener(scene);
videoComponent.clearCameraMotionListener(scene);
videoComponent.clearVideoFrameMetadataListener(sceneRenderer);
videoComponent.clearCameraMotionListener(sceneRenderer);
}
}
player = newPlayer;
if (player != null) {
Player.VideoComponent videoComponent = player.getVideoComponent();
if (videoComponent != null) {
videoComponent.setVideoFrameMetadataListener(scene);
videoComponent.setCameraMotionListener(scene);
videoComponent.setVideoFrameMetadataListener(sceneRenderer);
videoComponent.setCameraMotionListener(sceneRenderer);
videoComponent.setVideoSurface(surface);
}
}
Assertions.checkNotNull(playerControl).setPlayer(player);
Assertions.checkNotNull(playerControlView).setPlayer(player);
}
/**
@ -177,7 +162,19 @@ public abstract class GvrPlayerActivity extends GvrActivity {
* @param stereoMode A {@link C.StereoMode} value.
*/
protected void setDefaultStereoMode(@C.StereoMode int stereoMode) {
Assertions.checkNotNull(scene).setDefaultStereoMode(stereoMode);
Assertions.checkNotNull(sceneRenderer).setDefaultStereoMode(stereoMode);
}
/**
* Returns the render target scale passed to {@link GvrView#setRenderTargetScale(float)}. Since
* videos typically have fewer pixels per degree than the phone displays, the target can normally
* be lower than 1 to reduce the amount of work required to render the scene. The default value is
* 0.5.
*
* @return The render target scale passed to {@link GvrView#setRenderTargetScale(float)}.
*/
protected float getRenderTargetScale() {
return 0.5f;
}
@CallSuper
@ -210,12 +207,12 @@ public abstract class GvrPlayerActivity extends GvrActivity {
/** Tries to exit gracefully from VR using a VR transition dialog. */
@SuppressWarnings("nullness:argument.type.incompatible")
protected void exit() {
// This needs to use GVR's exit transition to avoid disorienting the user.
DaydreamApi api = DaydreamApi.create(this);
if (api != null) {
api.exitFromVr(this, EXIT_FROM_VR_REQUEST_CODE, null);
// Eventually, the Activity's onActivityResult will be called.
api.close();
DaydreamApi daydreamApi = DaydreamApi.create(this);
if (daydreamApi != null) {
// Use Daydream's exit transition to avoid disorienting the user. This will cause
// onActivityResult to be called.
daydreamApi.exitFromVr(/* activity= */ this, EXIT_FROM_VR_REQUEST_CODE, /* intent= */ null);
daydreamApi.close();
} else {
finish();
}
@ -224,16 +221,16 @@ public abstract class GvrPlayerActivity extends GvrActivity {
/** Toggles PlayerControl visibility. */
@UiThread
protected void togglePlayerControlVisibility() {
if (Assertions.checkNotNull(playerControl).isVisible()) {
playerControl.hide();
if (Assertions.checkNotNull(playerControlView).isVisible()) {
playerControlView.hide();
} else {
playerControl.show();
playerControlView.show();
}
}
// Called on GL thread.
private void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture) {
mainHandler.post(
// Called on the GL thread. Post to the main thread.
runOnUiThread(
() -> {
SurfaceTexture oldSurfaceTexture = this.surfaceTexture;
Surface oldSurface = this.surface;
@ -260,18 +257,20 @@ public abstract class GvrPlayerActivity extends GvrActivity {
}
private class Renderer implements GvrView.StereoRenderer {
private static final float Z_NEAR = .1f;
private static final float Z_NEAR = 0.1f;
private static final float Z_FAR = 100;
private final float[] viewProjectionMatrix = new float[16];
private final SceneRenderer scene;
private final GlViewGroup glView;
private final SceneRenderer sceneRenderer;
private final PointerRenderer pointerRenderer;
private final GlViewGroup glViewGroup;
private final float[] viewProjectionMatrix;
public Renderer(SceneRenderer scene, GlViewGroup glView, PointerRenderer pointerRenderer) {
this.scene = scene;
this.glView = glView;
public Renderer(
SceneRenderer sceneRenderer, PointerRenderer pointerRenderer, GlViewGroup glViewGroup) {
this.sceneRenderer = sceneRenderer;
this.pointerRenderer = pointerRenderer;
this.glViewGroup = glViewGroup;
viewProjectionMatrix = new float[16];
}
@Override
@ -281,9 +280,9 @@ public abstract class GvrPlayerActivity extends GvrActivity {
public void onDrawEye(Eye eye) {
Matrix.multiplyMM(
viewProjectionMatrix, 0, eye.getPerspective(Z_NEAR, Z_FAR), 0, eye.getEyeView(), 0);
scene.drawFrame(viewProjectionMatrix, eye.getType() == Eye.Type.RIGHT);
if (glView.isVisible()) {
glView.getRenderer().draw(viewProjectionMatrix);
sceneRenderer.drawFrame(viewProjectionMatrix, eye.getType() == Eye.Type.RIGHT);
if (glViewGroup.isVisible()) {
glViewGroup.getRenderer().draw(viewProjectionMatrix);
pointerRenderer.draw(viewProjectionMatrix);
}
}
@ -293,8 +292,8 @@ public abstract class GvrPlayerActivity extends GvrActivity {
@Override
public void onSurfaceCreated(EGLConfig config) {
onSurfaceTextureAvailable(scene.init());
glView.getRenderer().init();
onSurfaceTextureAvailable(sceneRenderer.init());
glViewGroup.getRenderer().init();
pointerRenderer.init();
}
@ -303,9 +302,9 @@ public abstract class GvrPlayerActivity extends GvrActivity {
@Override
public void onRendererShutdown() {
glView.getRenderer().shutdown();
glViewGroup.getRenderer().shutdown();
pointerRenderer.shutdown();
scene.shutdown();
sceneRenderer.shutdown();
}
}
@ -313,16 +312,16 @@ public abstract class GvrPlayerActivity extends GvrActivity {
private final Controller controller;
private final PointerRenderer pointerRenderer;
private final GlViewGroup glView;
private final GlViewGroup glViewGroup;
private final float[] controllerOrientationMatrix;
private boolean clickButtonDown;
private boolean appButtonDown;
public ControllerEventListener(
Controller controller, PointerRenderer pointerRenderer, GlViewGroup glView) {
Controller controller, PointerRenderer pointerRenderer, GlViewGroup glViewGroup) {
this.controller = controller;
this.pointerRenderer = pointerRenderer;
this.glView = glView;
this.glViewGroup = glViewGroup;
controllerOrientationMatrix = new float[16];
}
@ -330,7 +329,8 @@ public abstract class GvrPlayerActivity extends GvrActivity {
@BinderThread
public void onUpdate() {
controller.update();
controller.orientation.toRotationMatrix(controllerOrientationMatrix);
Orientation orientation = controller.orientation;
orientation.toRotationMatrix(controllerOrientationMatrix);
pointerRenderer.setControllerOrientation(controllerOrientationMatrix);
if (clickButtonDown || controller.clickButtonState) {
@ -341,18 +341,19 @@ public abstract class GvrPlayerActivity extends GvrActivity {
} else {
action = MotionEvent.ACTION_MOVE;
}
glView.post(
() -> {
float[] angles = controller.orientation.toYawPitchRollRadians(new float[3]);
boolean clickedOnView = glView.simulateClick(action, angles[0], angles[1]);
if (action == MotionEvent.ACTION_DOWN && !clickedOnView) {
togglePlayerControlVisibility();
}
});
float[] yawPitchRoll = orientation.toYawPitchRollRadians(new float[3]);
runOnUiThread(() -> dispatchClick(action, yawPitchRoll[0], yawPitchRoll[1]));
} else if (!appButtonDown && controller.appButtonState) {
glView.post(GvrPlayerActivity.this::togglePlayerControlVisibility);
runOnUiThread(GvrPlayerActivity.this::togglePlayerControlVisibility);
}
appButtonDown = controller.appButtonState;
}
private void dispatchClick(int action, float yaw, float pitch) {
boolean clickedOnView = glViewGroup.simulateClick(action, yaw, pitch);
if (action == MotionEvent.ACTION_DOWN && !clickedOnView) {
togglePlayerControlVisibility();
}
}
}
}

View file

@ -13,6 +13,8 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources>
<style name="VrTheme" parent="android:Theme.Material"/>
</resources>
<com.google.android.exoplayer2.ui.PlayerControlView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/controller"
android:layout_width="match_parent"
android:layout_height="match_parent"/>

View file

@ -1,28 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2018 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/video_ui_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/black"
android:orientation="horizontal"
tools:ignore="Overdraw">
<com.google.android.exoplayer2.ui.PlayerControlView
android:id="@+id/controller"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</merge>

View file

@ -14,5 +14,5 @@
limitations under the License.
-->
<resources>
<style name="VrTheme" parent="android:Theme.Holo"/>
<style name="ExoVrTheme" parent="android:Theme.DeviceDefault"/>
</resources>

View file

@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.ui.spherical;
import static com.google.android.exoplayer2.util.GlUtil.checkGlError;

View file

@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.ui.spherical;
import android.content.Context;

View file

@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.ui.spherical;
import static com.google.android.exoplayer2.util.GlUtil.checkGlError;
@ -26,7 +25,7 @@ import java.nio.FloatBuffer;
/** Renders a pointer. */
public final class PointerRenderer {
// The pointer quad is 2 * SIZE units.
private static final float SIZE = .01f;
private static final float SIZE = 0.01f;
private static final float DISTANCE = 1;
// Standard vertex shader.

View file

@ -55,7 +55,7 @@ public final class SphericalSurfaceView extends GLSurfaceView {
// Arbitrary vertical field of view.
private static final int FIELD_OF_VIEW_DEGREES = 90;
private static final float Z_NEAR = .1f;
private static final float Z_NEAR = 0.1f;
private static final float Z_FAR = 100;
// TODO Calculate this depending on surface size and field of view.
@ -84,7 +84,7 @@ public final class SphericalSurfaceView extends GLSurfaceView {
// Configure sensors and touch.
sensorManager =
(SensorManager) Assertions.checkNotNull(context.getSystemService(Context.SENSOR_SERVICE));
Sensor orientationSensor = null;
@Nullable Sensor orientationSensor = null;
if (Util.SDK_INT >= 18) {
// TYPE_GAME_ROTATION_VECTOR is the easiest sensor since it handles all the complex math for
// fusion. It's used instead of TYPE_ROTATION_VECTOR since the latter uses the magnetometer on