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
This commit is contained in:
olly 2019-10-21 13:14:38 +01:00 committed by Oliver Woodman
parent 3101a5df58
commit 7ccbc4c436
23 changed files with 13 additions and 1452 deletions

View File

@ -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) ###

View File

@ -1,4 +0,0 @@
# ExoPlayer VR player demo #
This folder contains a demo application that showcases 360 video playback using
ExoPlayer GVR extension.

View File

@ -1,59 +0,0 @@
// 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'

View File

@ -1,60 +0,0 @@
<?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>

View File

@ -1,189 +0,0 @@
/*
* 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;
}
}
}
}

View File

@ -1,92 +0,0 @@
/*
* 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;
}
}
}

View File

@ -1,25 +0,0 @@
<?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>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

View File

@ -1,28 +0,0 @@
<?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>

View File

@ -1,11 +1,15 @@
# 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 ##

View File

@ -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 {

View File

@ -1,344 +0,0 @@
/*
* 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.ext.gvr;
import android.content.Context;
import android.content.Intent;
import android.graphics.SurfaceTexture;
import android.opengl.Matrix;
import android.os.Bundle;
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.View;
import androidx.annotation.BinderThread;
import androidx.annotation.CallSuper;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.ui.PlayerControlView;
import com.google.android.exoplayer2.ui.spherical.PointerRenderer;
import com.google.android.exoplayer2.ui.spherical.SceneRenderer;
import com.google.android.exoplayer2.ui.spherical.ViewRenderer;
import com.google.android.exoplayer2.util.Assertions;
import com.google.vr.ndk.base.DaydreamApi;
import com.google.vr.sdk.base.AndroidCompat;
import com.google.vr.sdk.base.Eye;
import com.google.vr.sdk.base.GvrActivity;
import com.google.vr.sdk.base.GvrView;
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;
/** Base activity for VR 360 video playback. */
public abstract class GvrPlayerActivity extends GvrActivity {
private static final int EXIT_FROM_VR_REQUEST_CODE = 42;
@Nullable private Player player;
private @MonotonicNonNull ControllerManager controllerManager;
private @MonotonicNonNull SurfaceTexture surfaceTexture;
private @MonotonicNonNull Surface surface;
private @MonotonicNonNull SceneRenderer sceneRenderer;
private @MonotonicNonNull PlayerControlView playerControlView;
@CallSuper
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setScreenAlwaysOn(true);
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.ExoVrTheme);
View viewGroup = LayoutInflater.from(theme).inflate(R.layout.exo_vr_ui, /* root= */ null);
ViewRenderer viewRenderer = new ViewRenderer(/* context= */ this, gvrView, viewGroup);
playerControlView = Assertions.checkNotNull(viewGroup.findViewById(R.id.controller));
playerControlView.setShowVrButton(true);
playerControlView.setVrButtonListener(v -> exit());
sceneRenderer = new SceneRenderer();
PointerRenderer pointerRenderer = new PointerRenderer();
Renderer renderer = new Renderer(sceneRenderer, pointerRenderer, viewRenderer);
// Standard GvrView configuration
gvrView.setEGLConfigChooser(
8, 8, 8, 8, // RGBA bits.
16, // Depth bits.
0); // Stencil bits.
gvrView.setRenderer(renderer);
setContentView(gvrView);
if (gvrView.setAsyncReprojectionEnabled(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 Daydream's exit transition.
gvrView.setOnCloseButtonListener(this::finish);
controllerManager =
new ControllerManager(/* context= */ this, new ControllerManagerEventListener());
Controller controller = controllerManager.getController();
ControllerEventListener controllerEventListener =
new ControllerEventListener(controller, pointerRenderer, viewRenderer);
controller.setEventListener(controllerEventListener);
}
@CallSuper
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent unused) {
if (requestCode == EXIT_FROM_VR_REQUEST_CODE && resultCode == RESULT_OK) {
finish();
}
}
@CallSuper
@Override
protected void onResume() {
super.onResume();
player = createPlayer();
Player.VideoComponent videoComponent = player.getVideoComponent();
if (videoComponent != null) {
videoComponent.setVideoFrameMetadataListener(Assertions.checkNotNull(sceneRenderer));
videoComponent.setCameraMotionListener(sceneRenderer);
videoComponent.setVideoSurface(surface);
}
Assertions.checkNotNull(playerControlView).setPlayer(player);
Assertions.checkNotNull(controllerManager).start();
}
@CallSuper
@Override
protected void onPause() {
Assertions.checkNotNull(controllerManager).stop();
Assertions.checkNotNull(playerControlView).setPlayer(null);
Assertions.checkNotNull(player).release();
player = null;
super.onPause();
}
@CallSuper
@Override
protected void onDestroy() {
releaseSurface(surfaceTexture, surface);
super.onDestroy();
}
/**
* Called by {@link #onCreate(Bundle)} to get the render target scale value that will be 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 value that will be passed to {@link
* GvrView#setRenderTargetScale(float)}.
*/
protected float getRenderTargetScale() {
return 0.5f;
}
/** Called by {@link #onResume()} to create a player instance for this activity to use. */
protected abstract Player createPlayer();
/**
* Sets the stereo mode that will be used for video content that does not specify its own mode.
*
* @param stereoMode The default {@link C.StereoMode}.
*/
protected void setDefaultStereoMode(@C.StereoMode int stereoMode) {
Assertions.checkNotNull(sceneRenderer).setDefaultStereoMode(stereoMode);
}
/** Tries to exit gracefully from VR using a VR transition dialog. */
@SuppressWarnings("nullness:argument.type.incompatible")
protected void exit() {
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, /* data= */ null);
daydreamApi.close();
} else {
finish();
}
}
/** Toggles PlayerControl visibility. */
@UiThread
protected void togglePlayerControlVisibility() {
if (Assertions.checkNotNull(playerControlView).isVisible()) {
playerControlView.hide();
} else {
playerControlView.show();
}
}
private void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture) {
// Called on the GL thread. Post to the main thread.
runOnUiThread(
() -> {
SurfaceTexture oldSurfaceTexture = this.surfaceTexture;
Surface oldSurface = this.surface;
this.surfaceTexture = surfaceTexture;
this.surface = new Surface(surfaceTexture);
if (player != null) {
Player.VideoComponent videoComponent = player.getVideoComponent();
if (videoComponent != null) {
videoComponent.setVideoSurface(surface);
}
}
releaseSurface(oldSurfaceTexture, oldSurface);
});
}
private static void releaseSurface(
@Nullable SurfaceTexture oldSurfaceTexture, @Nullable Surface oldSurface) {
if (oldSurfaceTexture != null) {
oldSurfaceTexture.release();
}
if (oldSurface != null) {
oldSurface.release();
}
}
private class Renderer implements GvrView.StereoRenderer {
private static final float Z_NEAR = 0.1f;
private static final float Z_FAR = 100;
private final SceneRenderer sceneRenderer;
private final PointerRenderer pointerRenderer;
private final ViewRenderer viewRenderer;
private final float[] viewProjectionMatrix;
public Renderer(
SceneRenderer sceneRenderer, PointerRenderer pointerRenderer, ViewRenderer viewRenderer) {
this.sceneRenderer = sceneRenderer;
this.pointerRenderer = pointerRenderer;
this.viewRenderer = viewRenderer;
viewProjectionMatrix = new float[16];
}
@Override
public void onNewFrame(HeadTransform headTransform) {}
@Override
public void onDrawEye(Eye eye) {
Matrix.multiplyMM(
viewProjectionMatrix, 0, eye.getPerspective(Z_NEAR, Z_FAR), 0, eye.getEyeView(), 0);
sceneRenderer.drawFrame(viewProjectionMatrix, eye.getType() == Eye.Type.RIGHT);
if (viewRenderer.isVisible()) {
viewRenderer.draw(viewProjectionMatrix);
pointerRenderer.draw(viewProjectionMatrix);
}
}
@Override
public void onFinishFrame(Viewport viewport) {}
@Override
public void onSurfaceCreated(EGLConfig config) {
onSurfaceTextureAvailable(sceneRenderer.init());
viewRenderer.init();
pointerRenderer.init();
}
@Override
public void onSurfaceChanged(int width, int height) {}
@Override
public void onRendererShutdown() {
viewRenderer.shutdown();
pointerRenderer.shutdown();
sceneRenderer.shutdown();
}
}
private class ControllerEventListener extends Controller.EventListener {
private final Controller controller;
private final PointerRenderer pointerRenderer;
private final ViewRenderer viewRenderer;
private final float[] controllerOrientationMatrix;
private boolean clickButtonDown;
private boolean appButtonDown;
public ControllerEventListener(
Controller controller, PointerRenderer pointerRenderer, ViewRenderer viewRenderer) {
this.controller = controller;
this.pointerRenderer = pointerRenderer;
this.viewRenderer = viewRenderer;
controllerOrientationMatrix = new float[16];
}
@Override
@BinderThread
public void onUpdate() {
controller.update();
Orientation orientation = controller.orientation;
orientation.toRotationMatrix(controllerOrientationMatrix);
pointerRenderer.setControllerOrientation(controllerOrientationMatrix);
if (clickButtonDown || controller.clickButtonState) {
int action;
if (clickButtonDown != controller.clickButtonState) {
clickButtonDown = controller.clickButtonState;
action = clickButtonDown ? MotionEvent.ACTION_DOWN : MotionEvent.ACTION_UP;
} else {
action = MotionEvent.ACTION_MOVE;
}
float[] yawPitchRoll = orientation.toYawPitchRollRadians(new float[3]);
runOnUiThread(() -> dispatchClick(action, yawPitchRoll[0], yawPitchRoll[1]));
} else if (!appButtonDown && controller.appButtonState) {
runOnUiThread(GvrPlayerActivity.this::togglePlayerControlVisibility);
}
appButtonDown = controller.appButtonState;
}
private void dispatchClick(int action, float yaw, float pitch) {
boolean clickedOnView = viewRenderer.simulateClick(action, yaw, pitch);
if (action == MotionEvent.ACTION_DOWN && !clickedOnView) {
togglePlayerControlVisibility();
}
}
}
private final class ControllerManagerEventListener implements ControllerManager.EventListener {
@Override
public void onApiStatusChanged(int status) {
// Do nothing.
}
@Override
public void onRecentered() {
// TODO: If in cardboard mode call gvrView.recenterHeadTracker().
runOnUiThread(() -> Assertions.checkNotNull(playerControlView).show());
}
}
}

View File

@ -1,6 +1,7 @@
# 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][].

View File

@ -1,294 +0,0 @@
/*
* 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.graphics.Canvas;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.SurfaceTexture;
import android.opengl.GLES11Ext;
import android.opengl.GLES20;
import android.view.Surface;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.util.GlUtil;
import java.nio.FloatBuffer;
import java.util.concurrent.atomic.AtomicBoolean;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/**
* Renders a canvas on a quad.
*
* <p>A CanvasRenderer can be created on any thread, but {@link #init()} needs to be called on the
* GL thread before it can be rendered.
*/
public final class CanvasRenderer {
private static final float WIDTH_UNIT = 0.8f;
private static final float DISTANCE_UNIT = 1f;
private static final float X_UNIT = -WIDTH_UNIT / 2;
private static final float Y_UNIT = -0.3f;
// Standard vertex shader that passes through the texture data.
private static final String[] VERTEX_SHADER_CODE = {
"uniform mat4 uMvpMatrix;",
// 3D position data.
"attribute vec3 aPosition;",
// 2D UV vertices.
"attribute vec2 aTexCoords;",
"varying vec2 vTexCoords;",
// Standard transformation.
"void main() {",
" gl_Position = uMvpMatrix * vec4(aPosition, 1);",
" vTexCoords = aTexCoords;",
"}"
};
private static final String[] FRAGMENT_SHADER_CODE = {
// This is required since the texture data is GL_TEXTURE_EXTERNAL_OES.
"#extension GL_OES_EGL_image_external : require",
"precision mediump float;",
"uniform samplerExternalOES uTexture;",
"varying vec2 vTexCoords;",
"void main() {",
" gl_FragColor = texture2D(uTexture, vTexCoords);",
"}"
};
// The quad has 2 triangles built from 4 total vertices. Each vertex has 3 position and 2 texture
// coordinates.
private static final int POSITION_COORDS_PER_VERTEX = 3;
private static final int TEXTURE_COORDS_PER_VERTEX = 2;
private static final int COORDS_PER_VERTEX =
POSITION_COORDS_PER_VERTEX + TEXTURE_COORDS_PER_VERTEX;
private static final int VERTEX_STRIDE_BYTES = COORDS_PER_VERTEX * C.BYTES_PER_FLOAT;
private static final int VERTEX_COUNT = 4;
private static final float HALF_PI = (float) (Math.PI / 2);
private final FloatBuffer vertexBuffer;
private final AtomicBoolean surfaceDirty;
private int width;
private int height;
private float heightUnit;
// Program-related GL items. These are only valid if program != 0.
private int program = 0;
private int mvpMatrixHandle;
private int positionHandle;
private int textureCoordsHandle;
private int textureHandle;
private int textureId;
// Components used to manage the Canvas that the View is rendered to. These are only valid after
// GL initialization. The client of this class acquires a Canvas from the Surface, writes to it
// and posts it. This marks the Surface as dirty. The GL code then updates the SurfaceTexture
// when rendering only if it is dirty.
private @MonotonicNonNull SurfaceTexture displaySurfaceTexture;
private @MonotonicNonNull Surface displaySurface;
public CanvasRenderer() {
vertexBuffer = GlUtil.createBuffer(COORDS_PER_VERTEX * VERTEX_COUNT);
surfaceDirty = new AtomicBoolean();
}
public void setSize(int width, int height) {
this.width = width;
this.height = height;
heightUnit = WIDTH_UNIT * height / width;
float[] vertexData = new float[COORDS_PER_VERTEX * VERTEX_COUNT];
int vertexDataIndex = 0;
for (int y = 0; y < 2; y++) {
for (int x = 0; x < 2; x++) {
vertexData[vertexDataIndex++] = X_UNIT + (WIDTH_UNIT * x);
vertexData[vertexDataIndex++] = Y_UNIT + (heightUnit * y);
vertexData[vertexDataIndex++] = -DISTANCE_UNIT;
vertexData[vertexDataIndex++] = x;
vertexData[vertexDataIndex++] = 1 - y;
}
}
vertexBuffer.position(0);
vertexBuffer.put(vertexData);
}
/**
* Calls {@link Surface#lockCanvas(Rect)}.
*
* @return {@link Canvas} for the View to render to or {@code null} if {@link #init()} has not yet
* been called.
*/
@Nullable
public Canvas lockCanvas() {
return displaySurface == null ? null : displaySurface.lockCanvas(/* inOutDirty= */ null);
}
/**
* Calls {@link Surface#unlockCanvasAndPost(Canvas)} and marks the SurfaceTexture as dirty.
*
* @param canvas the canvas returned from {@link #lockCanvas()}
*/
public void unlockCanvasAndPost(@Nullable Canvas canvas) {
if (canvas == null || displaySurface == null) {
// glInit() hasn't run yet.
return;
}
displaySurface.unlockCanvasAndPost(canvas);
}
/** Finishes constructing this object on the GL Thread. */
public void init() {
if (program != 0) {
return;
}
// Create the program.
program = GlUtil.compileProgram(VERTEX_SHADER_CODE, FRAGMENT_SHADER_CODE);
mvpMatrixHandle = GLES20.glGetUniformLocation(program, "uMvpMatrix");
positionHandle = GLES20.glGetAttribLocation(program, "aPosition");
textureCoordsHandle = GLES20.glGetAttribLocation(program, "aTexCoords");
textureHandle = GLES20.glGetUniformLocation(program, "uTexture");
textureId = GlUtil.createExternalTexture();
checkGlError();
// Create the underlying SurfaceTexture with the appropriate size.
displaySurfaceTexture = new SurfaceTexture(textureId);
displaySurfaceTexture.setOnFrameAvailableListener(surfaceTexture -> surfaceDirty.set(true));
displaySurfaceTexture.setDefaultBufferSize(width, height);
displaySurface = new Surface(displaySurfaceTexture);
}
/**
* Renders the 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) {
if (displaySurfaceTexture == null) {
return;
}
GLES20.glUseProgram(program);
checkGlError();
GLES20.glEnableVertexAttribArray(positionHandle);
GLES20.glEnableVertexAttribArray(textureCoordsHandle);
checkGlError();
GLES20.glUniformMatrix4fv(mvpMatrixHandle, 1, false, viewProjectionMatrix, 0);
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textureId);
GLES20.glUniform1i(textureHandle, 0);
checkGlError();
// Load position data.
vertexBuffer.position(0);
GLES20.glVertexAttribPointer(
positionHandle,
POSITION_COORDS_PER_VERTEX,
GLES20.GL_FLOAT,
false,
VERTEX_STRIDE_BYTES,
vertexBuffer);
checkGlError();
// Load texture data.
vertexBuffer.position(POSITION_COORDS_PER_VERTEX);
GLES20.glVertexAttribPointer(
textureCoordsHandle,
TEXTURE_COORDS_PER_VERTEX,
GLES20.GL_FLOAT,
false,
VERTEX_STRIDE_BYTES,
vertexBuffer);
checkGlError();
if (surfaceDirty.compareAndSet(true, false)) {
// If the Surface has been written to, get the new data onto the SurfaceTexture.
displaySurfaceTexture.updateTexImage();
}
// Render.
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, VERTEX_COUNT);
checkGlError();
GLES20.glDisableVertexAttribArray(positionHandle);
GLES20.glDisableVertexAttribArray(textureCoordsHandle);
}
/** Frees GL resources. */
public void shutdown() {
if (program != 0) {
GLES20.glDeleteProgram(program);
GLES20.glDeleteTextures(1, new int[] {textureId}, 0);
}
if (displaySurfaceTexture != null) {
displaySurfaceTexture.release();
}
if (displaySurface != null) {
displaySurface.release();
}
}
/**
* Translates an orientation into pixel coordinates on the canvas.
*
* <p>This is a minimal hit detection system that works for this quad because it has no model
* matrix. All the math is based on the fact that its size and distance are hard-coded into this
* class. For a more complex 3D mesh, a general bounding box and ray collision system would be
* required.
*
* @param yaw Yaw of the orientation in radians.
* @param pitch Pitch of the orientation in radians.
* @return A {@link PointF} which contains the translated coordinate, or null if the point is
* outside of the quad's bounds.
*/
@Nullable
public PointF translateClick(float yaw, float pitch) {
return internalTranslateClick(
yaw, pitch, X_UNIT, Y_UNIT, WIDTH_UNIT, heightUnit, width, height);
}
@Nullable
/* package */ static PointF internalTranslateClick(
float yaw,
float pitch,
float xUnit,
float yUnit,
float widthUnit,
float heightUnit,
int widthPixel,
int heightPixel) {
if (yaw >= HALF_PI || yaw <= -HALF_PI || pitch >= HALF_PI || pitch <= -HALF_PI) {
return null;
}
double clickXUnit = Math.tan(yaw) * DISTANCE_UNIT - xUnit;
double clickYUnit = Math.tan(pitch) * DISTANCE_UNIT - yUnit;
if (clickXUnit < 0 || clickXUnit > widthUnit || clickYUnit < 0 || clickYUnit > heightUnit) {
return null;
}
// Convert from the polar coordinates of the controller to the rectangular coordinates of the
// View. Note the negative yaw and pitch used to generate Android-compliant x and y coordinates.
float clickXPixel = (float) (widthPixel - clickXUnit * widthPixel / widthUnit);
float clickYPixel = (float) (heightPixel - clickYUnit * heightPixel / heightUnit);
return new PointF(clickXPixel, clickYPixel);
}
}

View File

@ -1,145 +0,0 @@
/*
* 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);
}
}
}

View File

@ -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;

View File

@ -1,137 +0,0 @@
/*
* 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);
}
}
}

View File

@ -1,70 +0,0 @@
/*
* 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);
}
}

View File

@ -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')