Commit 7ccbc4c4 by olly Committed by Oliver Woodman

Remove VR code

- Leaving GvrAudioProcessor for now.
- Removing GvrPlayerActivity because it was never released. Also removing
  related UI classes. These were released, but it's unlikely anyone would
  have been using them directly.

PiperOrigin-RevId: 275822516
parent 3101a5df
......@@ -40,7 +40,6 @@
even if they are listed lower in the `MediaCodecList`.
* Add a workaround for broken raw audio decoding on Oppo R9
([#5782](https://github.com/google/ExoPlayer/issues/5782)).
* Add VR player demo.
* Wrap decoder exceptions in a new `DecoderException` class and report as
renderer error.
* Do not pass the manifest to callbacks of `Player.EventListener` and
......@@ -99,6 +98,7 @@
[#6315](https://github.com/google/ExoPlayer/issues/6315) and
[#5658](https://github.com/google/ExoPlayer/issues/5658)).
* Pass the codec output `MediaFormat` to `VideoFrameMetadataListener`.
* Deprecate the GVR extension.
### 2.10.6 (2019-10-17) ###
......
# ExoPlayer VR player demo #
This folder contains a demo application that showcases 360 video playback using
ExoPlayer GVR extension.
// Copyright (C) 2019 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.
apply from: '../../constants.gradle'
apply plugin: 'com.android.application'
android {
compileSdkVersion project.ext.compileSdkVersion
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
defaultConfig {
versionName project.ext.releaseVersion
versionCode project.ext.releaseVersionCode
minSdkVersion 19
targetSdkVersion project.ext.targetSdkVersion
}
buildTypes {
release {
shrinkResources true
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt')
}
debug {
jniDebuggable = true
}
}
lintOptions {
// The demo app isn't indexed and doesn't have translations.
disable 'GoogleAppIndexingWarning','MissingTranslation'
}
}
dependencies {
implementation project(modulePrefix + 'library-core')
implementation project(modulePrefix + 'library-ui')
implementation project(modulePrefix + 'library-dash')
implementation project(modulePrefix + 'library-hls')
implementation project(modulePrefix + 'library-smoothstreaming')
implementation project(modulePrefix + 'extension-gvr')
implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
}
apply plugin: 'com.google.android.gms.strict-version-matcher-plugin'
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 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.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.google.android.exoplayer2.gvrdemo">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-sdk/>
<application
android:allowBackup="false"
android:icon="@mipmap/ic_launcher"
android:label="@string/application_name"
android:largeHeap="true">
<activity
android:name="com.google.android.exoplayer2.gvrdemo.SampleChooserActivity"
android:configChanges="keyboardHidden"
android:exported="true"
android:label="@string/application_name">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity
android:name="com.google.android.exoplayer2.gvrdemo.PlayerActivity"
android:configChanges="density|keyboardHidden|navigation|orientation|screenSize|uiMode"
android:exported="false"
android:label="@string/application_name"
android:launchMode="singleTask"
android:enableVrMode="@string/gvr_vr_mode_component"
android:resizeableActivity="false"
android:screenOrientation="landscape"
android:theme="@style/VrActivityTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="com.google.intent.category.CARDBOARD"/>
<category android:name="com.google.intent.category.DAYDREAM"/>
</intent-filter>
</activity>
</application>
</manifest>
/*
* 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.
*/
package com.google.android.exoplayer2.gvrdemo;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.widget.Toast;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.C.ContentType;
import com.google.android.exoplayer2.DefaultRenderersFactory;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.ext.gvr.GvrPlayerActivity;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.ProgressiveMediaSource;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.source.dash.DashMediaSource;
import com.google.android.exoplayer2.source.hls.HlsMediaSource;
import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.trackselection.MappingTrackSelector.MappedTrackInfo;
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;
import com.google.android.exoplayer2.util.EventLogger;
import com.google.android.exoplayer2.util.Util;
/** An activity that plays media using {@link SimpleExoPlayer}. */
public class PlayerActivity extends GvrPlayerActivity {
public static final String EXTENSION_EXTRA = "extension";
public static final String SPHERICAL_STEREO_MODE_EXTRA = "spherical_stereo_mode";
public static final String SPHERICAL_STEREO_MODE_MONO = "mono";
public static final String SPHERICAL_STEREO_MODE_TOP_BOTTOM = "top_bottom";
public static final String SPHERICAL_STEREO_MODE_LEFT_RIGHT = "left_right";
private SimpleExoPlayer player;
private DefaultTrackSelector trackSelector;
private TrackGroupArray lastSeenTrackGroupArray;
private boolean startAutoPlay;
private int startWindow;
private long startPosition;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String sphericalStereoMode = getIntent().getStringExtra(SPHERICAL_STEREO_MODE_EXTRA);
if (sphericalStereoMode != null) {
int stereoMode;
if (SPHERICAL_STEREO_MODE_MONO.equals(sphericalStereoMode)) {
stereoMode = C.STEREO_MODE_MONO;
} else if (SPHERICAL_STEREO_MODE_TOP_BOTTOM.equals(sphericalStereoMode)) {
stereoMode = C.STEREO_MODE_TOP_BOTTOM;
} else if (SPHERICAL_STEREO_MODE_LEFT_RIGHT.equals(sphericalStereoMode)) {
stereoMode = C.STEREO_MODE_LEFT_RIGHT;
} else {
showToast(R.string.error_unrecognized_stereo_mode);
finish();
return;
}
setDefaultStereoMode(stereoMode);
}
clearStartPosition();
}
@Override
protected Player createPlayer() {
Intent intent = getIntent();
Uri uri = intent.getData();
DefaultRenderersFactory renderersFactory = new DefaultRenderersFactory(this);
trackSelector = new DefaultTrackSelector(/* context= */ this);
lastSeenTrackGroupArray = null;
player =
new SimpleExoPlayer.Builder(/* context= */ this, renderersFactory)
.setTrackSelector(trackSelector)
.build();
player.addListener(new PlayerEventListener());
player.setPlayWhenReady(startAutoPlay);
player.addAnalyticsListener(new EventLogger(trackSelector));
MediaSource mediaSource = buildMediaSource(uri, intent.getStringExtra(EXTENSION_EXTRA));
boolean haveStartPosition = startWindow != C.INDEX_UNSET;
if (haveStartPosition) {
player.seekTo(startWindow, startPosition);
}
player.prepare(mediaSource, !haveStartPosition, false);
return player;
}
private MediaSource buildMediaSource(Uri uri, @Nullable String overrideExtension) {
String userAgent = Util.getUserAgent(this, "ExoPlayerVrDemo");
DefaultDataSourceFactory dataSourceFactory =
new DefaultDataSourceFactory(this, new DefaultHttpDataSourceFactory(userAgent));
@ContentType int type = Util.inferContentType(uri, overrideExtension);
switch (type) {
case C.TYPE_DASH:
return new DashMediaSource.Factory(dataSourceFactory).createMediaSource(uri);
case C.TYPE_SS:
return new SsMediaSource.Factory(dataSourceFactory).createMediaSource(uri);
case C.TYPE_HLS:
return new HlsMediaSource.Factory(dataSourceFactory).createMediaSource(uri);
case C.TYPE_OTHER:
return new ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(uri);
default:
throw new IllegalStateException("Unsupported type: " + type);
}
}
private void updateStartPosition() {
if (player != null) {
startAutoPlay = player.getPlayWhenReady();
startWindow = player.getCurrentWindowIndex();
startPosition = Math.max(0, player.getContentPosition());
}
}
private void clearStartPosition() {
startAutoPlay = true;
startWindow = C.INDEX_UNSET;
startPosition = C.TIME_UNSET;
}
private void showToast(int messageId) {
showToast(getString(messageId));
}
private void showToast(String message) {
Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG).show();
}
private class PlayerEventListener implements Player.EventListener {
@Override
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {}
@Override
public void onPositionDiscontinuity(@Player.DiscontinuityReason int reason) {
if (player.getPlaybackError() != null) {
// The user has performed a seek whilst in the error state. Update the resume position so
// that if the user then retries, playback resumes from the position to which they seeked.
updateStartPosition();
}
}
@Override
public void onPlayerError(ExoPlaybackException e) {
updateStartPosition();
}
@Override
@SuppressWarnings("ReferenceEquality")
public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {
if (trackGroups != lastSeenTrackGroupArray) {
MappedTrackInfo mappedTrackInfo = trackSelector.getCurrentMappedTrackInfo();
if (mappedTrackInfo != null) {
if (mappedTrackInfo.getTypeSupport(C.TRACK_TYPE_VIDEO)
== MappedTrackInfo.RENDERER_SUPPORT_UNSUPPORTED_TRACKS) {
showToast(R.string.error_unsupported_video);
}
if (mappedTrackInfo.getTypeSupport(C.TRACK_TYPE_AUDIO)
== MappedTrackInfo.RENDERER_SUPPORT_UNSUPPORTED_TRACKS) {
showToast(R.string.error_unsupported_audio);
}
}
lastSeenTrackGroupArray = trackGroups;
}
}
}
}
/*
* Copyright (C) 2016 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.
*/
package com.google.android.exoplayer2.gvrdemo;
import static com.google.android.exoplayer2.gvrdemo.PlayerActivity.SPHERICAL_STEREO_MODE_LEFT_RIGHT;
import static com.google.android.exoplayer2.gvrdemo.PlayerActivity.SPHERICAL_STEREO_MODE_MONO;
import static com.google.android.exoplayer2.gvrdemo.PlayerActivity.SPHERICAL_STEREO_MODE_TOP_BOTTOM;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.widget.ArrayAdapter;
import android.widget.ListView;
/** An activity for selecting from a list of media samples. */
public class SampleChooserActivity extends Activity {
private final Sample[] samples =
new Sample[] {
new Sample(
"Congo (360 top-bottom stereo)",
"https://storage.googleapis.com/exoplayer-test-media-1/360/congo.mp4",
SPHERICAL_STEREO_MODE_TOP_BOTTOM),
new Sample(
"Sphericalv2 (180 top-bottom stereo)",
"https://storage.googleapis.com/exoplayer-test-media-1/360/sphericalv2.mp4",
SPHERICAL_STEREO_MODE_TOP_BOTTOM),
new Sample(
"Iceland (360 top-bottom stereo ts)",
"https://storage.googleapis.com/exoplayer-test-media-1/360/iceland0.ts",
SPHERICAL_STEREO_MODE_TOP_BOTTOM),
};
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.sample_chooser_activity);
ListView sampleListView = findViewById(R.id.sample_list);
sampleListView.setAdapter(
new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, samples));
sampleListView.setOnItemClickListener(
(parent, view, position, id) ->
startActivity(
samples[position].buildIntent(/* context= */ SampleChooserActivity.this)));
}
private static final class Sample {
public final String name;
public final String uri;
public final String extension;
public final String sphericalStereoMode;
public Sample(String name, String uri, String sphericalStereoMode) {
this(name, uri, sphericalStereoMode, null);
}
public Sample(String name, String uri, String sphericalStereoMode, String extension) {
this.name = name;
this.uri = uri;
this.extension = extension;
this.sphericalStereoMode = sphericalStereoMode;
}
public Intent buildIntent(Context context) {
Intent intent = new Intent(context, PlayerActivity.class);
return intent
.setData(Uri.parse(uri))
.putExtra(PlayerActivity.EXTENSION_EXTRA, extension)
.putExtra(PlayerActivity.SPHERICAL_STEREO_MODE_EXTRA, sphericalStereoMode);
}
@Override
public String toString() {
return name;
}
}
}
<?xml version="1.0" encoding="UTF-8"?>
<!-- Copyright (C) 2016 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.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ListView android:id="@+id/sample_list"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2019 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.
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="application_name">ExoPlayer VR Demo</string>
<string name="error_cleartext_not_permitted">Cleartext traffic not permitted</string>
<string name="error_unrecognized_stereo_mode">Unrecognized stereo mode</string>
<string name="error_unsupported_video">Media includes video tracks, but none are playable by this device</string>
<string name="error_unsupported_audio">Media includes audio tracks, but none are playable by this device</string>
</resources>
# ExoPlayer GVR extension #
**DEPRECATED - If you still need this extension, please contact us by filing an
issue on our [issue tracker][].**
The GVR extension wraps the [Google VR SDK for Android][]. It provides a
GvrAudioProcessor, which uses [GvrAudioSurround][] to provide binaural rendering
of surround sound and ambisonic soundfields.
[Google VR SDK for Android]: https://developers.google.com/vr/android/
[GvrAudioSurround]: https://developers.google.com/vr/android/reference/com/google/vr/sdk/audio/GvrAudioSurround
[issue tracker]: https://github.com/google/ExoPlayer/issues
## Getting the extension ##
......
......@@ -28,7 +28,11 @@ import java.nio.ByteOrder;
/**
* An {@link AudioProcessor} that uses {@code GvrAudioSurround} to provide binaural rendering of
* surround sound and ambisonic soundfields.
*
* @deprecated If you still need this component, please contact us by filing an issue on our <a
* href="https://github.com/google/ExoPlayer/issues">issue tracker</a>.
*/
@Deprecated
public final class GvrAudioProcessor implements AudioProcessor {
static {
......
# ExoPlayer Firebase JobDispatcher extension #
**DEPRECATED - Please use [WorkManager extension][] or [PlatformScheduler][] instead.**
**DEPRECATED - Please use [WorkManager extension][] or [PlatformScheduler][]
instead.**
This extension provides a Scheduler implementation which uses [Firebase JobDispatcher][].
......
/*
* 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.
*/
package com.google.android.exoplayer2.ui.spherical;
import static com.google.android.exoplayer2.util.GlUtil.checkGlError;
import android.opengl.GLES20;
import android.opengl.Matrix;
import com.google.android.exoplayer2.util.GlUtil;
import java.nio.FloatBuffer;
/** Renders a pointer. */
public final class PointerRenderer {
// The pointer quad is 2 * SIZE units.
private static final float SIZE = 0.01f;
private static final float DISTANCE = 1;
// Standard vertex shader.
private static final String[] VERTEX_SHADER_CODE =
new String[] {
"uniform mat4 uMvpMatrix;",
"attribute vec3 aPosition;",
"varying vec2 vCoords;",
// Pass through normalized vertex coordinates.
"void main() {",
" gl_Position = uMvpMatrix * vec4(aPosition, 1);",
" vCoords = aPosition.xy / vec2(" + SIZE + ", " + SIZE + ");",
"}"
};
// Procedurally render a ring on the quad between the specified radii.
private static final String[] FRAGMENT_SHADER_CODE =
new String[] {
"precision mediump float;",
"varying vec2 vCoords;",
// Simple ring shader that is white between the radii and transparent elsewhere.
"void main() {",
" float r = length(vCoords);",
// Blend the edges of the ring at .55 +/- .05 and .85 +/- .05.
" float alpha = smoothstep(0.5, 0.6, r) * (1.0 - smoothstep(0.8, 0.9, r));",
" if (alpha == 0.0) {",
" discard;",
" } else {",
" gl_FragColor = vec4(alpha);",
" }",
"}"
};
// Simple quad mesh.
private static final int COORDS_PER_VERTEX = 3;
private static final float[] VERTEX_DATA = {
-SIZE, -SIZE, -DISTANCE, SIZE, -SIZE, -DISTANCE, -SIZE, SIZE, -DISTANCE, SIZE, SIZE, -DISTANCE,
};
private final FloatBuffer vertexBuffer;
// The pointer doesn't have a real modelMatrix. Its distance is baked into the mesh and it
// uses a rotation matrix when rendered.
private final float[] modelViewProjectionMatrix;
// This is accessed on the binder & GL Threads.
private final float[] controllerOrientationMatrix;
// Program-related GL items. These are only valid if program != 0.
private int program = 0;
private int mvpMatrixHandle;
private int positionHandle;
public PointerRenderer() {
vertexBuffer = GlUtil.createBuffer(VERTEX_DATA);
modelViewProjectionMatrix = new float[16];
controllerOrientationMatrix = new float[16];
Matrix.setIdentityM(controllerOrientationMatrix, 0);
}
/** Finishes initialization of this object on the GL thread. */
public void init() {
if (program != 0) {
return;
}
program = GlUtil.compileProgram(VERTEX_SHADER_CODE, FRAGMENT_SHADER_CODE);
mvpMatrixHandle = GLES20.glGetUniformLocation(program, "uMvpMatrix");
positionHandle = GLES20.glGetAttribLocation(program, "aPosition");
checkGlError();
}
/**
* Renders the pointer.
*
* @param viewProjectionMatrix Scene's view projection matrix.
*/
public void draw(float[] viewProjectionMatrix) {
// Configure shader.
GLES20.glUseProgram(program);
checkGlError();
synchronized (controllerOrientationMatrix) {
Matrix.multiplyMM(
modelViewProjectionMatrix, 0, viewProjectionMatrix, 0, controllerOrientationMatrix, 0);
}
GLES20.glUniformMatrix4fv(mvpMatrixHandle, 1, false, modelViewProjectionMatrix, 0);
checkGlError();
// Render quad.
GLES20.glEnableVertexAttribArray(positionHandle);
checkGlError();
GLES20.glVertexAttribPointer(
positionHandle, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false, /* stride= */ 0, vertexBuffer);
checkGlError();
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, VERTEX_DATA.length / COORDS_PER_VERTEX);
checkGlError();
GLES20.glDisableVertexAttribArray(positionHandle);
}
/** Frees GL resources. */
public void shutdown() {
if (program != 0) {
GLES20.glDeleteProgram(program);
}
}
/** Updates the pointer's position with the latest Controller pose. */
public void setControllerOrientation(float[] rotationMatrix) {
synchronized (controllerOrientationMatrix) {
System.arraycopy(rotationMatrix, 0, controllerOrientationMatrix, 0, rotationMatrix.length);
}
}
}
......@@ -37,7 +37,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/** Renders a GL Scene. */
public final class SceneRenderer implements VideoFrameMetadataListener, CameraMotionListener {
/* package */ final class SceneRenderer
implements VideoFrameMetadataListener, CameraMotionListener {
private final AtomicBoolean frameAvailable;
private final AtomicBoolean resetRotationAtNextFrame;
......
/*
* 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.
*/
package com.google.android.exoplayer2.ui.spherical;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.PointF;
import android.graphics.PorterDuff;
import android.os.SystemClock;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import com.google.android.exoplayer2.util.Assertions;
/** Renders a {@link View} on a quad and supports simulated clicks on the view. */
public final class ViewRenderer {
private final CanvasRenderer canvasRenderer;
private final View view;
private final InternalFrameLayout frameLayout;
/**
* @param context A context.
* @param parentView The parent view.
* @param view The view to render.
*/
public ViewRenderer(Context context, ViewGroup parentView, View view) {
this.canvasRenderer = new CanvasRenderer();
this.view = view;
// Wrap the view in an internal view that redirects rendering.
frameLayout = new InternalFrameLayout(context, view, canvasRenderer);
canvasRenderer.setSize(frameLayout.getMeasuredWidth(), frameLayout.getMeasuredHeight());
// The internal view must be added to the parent to ensure proper delivery of UI events.
parentView.addView(frameLayout);
}
/** Finishes constructing this object on the GL Thread. */
public void init() {
canvasRenderer.init();
}
/**
* Renders the view as a quad.
*
* @param viewProjectionMatrix Array of floats containing the quad's 4x4 perspective matrix in the
* {@link android.opengl.Matrix} format.
*/
public void draw(float[] viewProjectionMatrix) {
canvasRenderer.draw(viewProjectionMatrix);
}
/** Frees GL resources. */
public void shutdown() {
canvasRenderer.shutdown();
}
/** Returns whether the view is currently visible. */
@UiThread
public boolean isVisible() {
return view.getVisibility() == View.VISIBLE;
}
/**
* Simulates a click on the view.
*
* @param action Click action.
* @param yaw Yaw of the click's orientation in radians.
* @param pitch Pitch of the click's orientation in radians.
* @return Whether the click was simulated. If false then the view is not visible or the click was
* outside of its bounds.
*/
@UiThread
public boolean simulateClick(int action, float yaw, float pitch) {
if (!isVisible()) {
return false;
}
@Nullable PointF point = canvasRenderer.translateClick(yaw, pitch);
if (point == null) {
return false;
}
long now = SystemClock.uptimeMillis();
MotionEvent event = MotionEvent.obtain(now, now, action, point.x, point.y, /* metaState= */ 1);
frameLayout.dispatchTouchEvent(event);
return true;
}
private static final class InternalFrameLayout extends FrameLayout {
private final CanvasRenderer canvasRenderer;
public InternalFrameLayout(Context context, View wrappedView, CanvasRenderer canvasRenderer) {
super(context);
this.canvasRenderer = canvasRenderer;
addView(wrappedView);
measure(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
int width = getMeasuredWidth();
int height = getMeasuredHeight();
Assertions.checkState(width > 0 && height > 0);
setLayoutParams(new FrameLayout.LayoutParams(width, height));
}
@Override
public void dispatchDraw(Canvas notUsed) {
@Nullable Canvas glCanvas = canvasRenderer.lockCanvas();
if (glCanvas == null) {
// This happens if Android tries to draw this View before GL initialization completes. We
// need to retry until the draw call happens after GL invalidation.
postInvalidate();
return;
}
// Clear the canvas first.
glCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
// Have Android render the child views.
super.dispatchDraw(glCanvas);
// Commit the changes.
canvasRenderer.unlockCanvasAndPost(glCanvas);
}
}
}
/*
* 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.
*/
package com.google.android.exoplayer2.ui.spherical;
import static com.google.common.truth.Truth.assertThat;
import android.graphics.PointF;
import androidx.annotation.Nullable;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
/** Tests for {@link CanvasRenderer}. */
@RunWith(AndroidJUnit4.class)
public class CanvasRendererTest {
private static final float JUST_BELOW_45_DEGREES = (float) (Math.PI / 4 - 1.0E-08);
private static final float JUST_ABOVE_45_DEGREES = (float) (Math.PI / 4 + 1.0E-08);
private static final float TOLERANCE = .00001f;
@Test
public void testClicksOnCanvas() {
assertClick(translateClick(JUST_BELOW_45_DEGREES, JUST_BELOW_45_DEGREES), 0, 0);
assertClick(translateClick(JUST_BELOW_45_DEGREES, -JUST_BELOW_45_DEGREES), 0, 100);
assertClick(translateClick(0, 0), 50, 50);
assertClick(translateClick(-JUST_BELOW_45_DEGREES, JUST_BELOW_45_DEGREES), 100, 0);
assertClick(translateClick(-JUST_BELOW_45_DEGREES, -JUST_BELOW_45_DEGREES), 100, 100);
}
@Test
public void testClicksNotOnCanvas() {
assertThat(translateClick(JUST_ABOVE_45_DEGREES, JUST_ABOVE_45_DEGREES)).isNull();
assertThat(translateClick(JUST_ABOVE_45_DEGREES, -JUST_ABOVE_45_DEGREES)).isNull();
assertThat(translateClick(-JUST_ABOVE_45_DEGREES, JUST_ABOVE_45_DEGREES)).isNull();
assertThat(translateClick(-JUST_ABOVE_45_DEGREES, -JUST_ABOVE_45_DEGREES)).isNull();
assertThat(translateClick((float) (Math.PI / 2), 0)).isNull();
assertThat(translateClick(0, (float) Math.PI)).isNull();
}
private static PointF translateClick(float yaw, float pitch) {
return CanvasRenderer.internalTranslateClick(
yaw,
pitch,
/* xUnit= */ -1,
/* yUnit= */ -1,
/* widthUnit= */ 2,
/* heightUnit= */ 2,
/* widthPixel= */ 100,
/* heightPixel= */ 100);
}
private static void assertClick(@Nullable PointF actual, float expectedX, float expectedY) {
assertThat(actual).isNotNull();
assertThat(actual.x).isWithin(TOLERANCE).of(expectedX);
assertThat(actual.y).isWithin(TOLERANCE).of(expectedY);
}
}
......@@ -20,12 +20,10 @@ if (gradle.ext.has('exoplayerModulePrefix')) {
include modulePrefix + 'demo'
include modulePrefix + 'demo-cast'
include modulePrefix + 'demo-gvr'
include modulePrefix + 'demo-surface'
include modulePrefix + 'playbacktests'
project(modulePrefix + 'demo').projectDir = new File(rootDir, 'demos/main')
project(modulePrefix + 'demo-cast').projectDir = new File(rootDir, 'demos/cast')
project(modulePrefix + 'demo-gvr').projectDir = new File(rootDir, 'demos/gvr')
project(modulePrefix + 'demo-surface').projectDir = new File(rootDir, 'demos/surface')
project(modulePrefix + 'playbacktests').projectDir = new File(rootDir, 'playbacktests')
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment