mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
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:
parent
3101a5df58
commit
7ccbc4c436
@ -40,7 +40,6 @@
|
|||||||
even if they are listed lower in the `MediaCodecList`.
|
even if they are listed lower in the `MediaCodecList`.
|
||||||
* Add a workaround for broken raw audio decoding on Oppo R9
|
* Add a workaround for broken raw audio decoding on Oppo R9
|
||||||
([#5782](https://github.com/google/ExoPlayer/issues/5782)).
|
([#5782](https://github.com/google/ExoPlayer/issues/5782)).
|
||||||
* Add VR player demo.
|
|
||||||
* Wrap decoder exceptions in a new `DecoderException` class and report as
|
* Wrap decoder exceptions in a new `DecoderException` class and report as
|
||||||
renderer error.
|
renderer error.
|
||||||
* Do not pass the manifest to callbacks of `Player.EventListener` and
|
* Do not pass the manifest to callbacks of `Player.EventListener` and
|
||||||
@ -99,6 +98,7 @@
|
|||||||
[#6315](https://github.com/google/ExoPlayer/issues/6315) and
|
[#6315](https://github.com/google/ExoPlayer/issues/6315) and
|
||||||
[#5658](https://github.com/google/ExoPlayer/issues/5658)).
|
[#5658](https://github.com/google/ExoPlayer/issues/5658)).
|
||||||
* Pass the codec output `MediaFormat` to `VideoFrameMetadataListener`.
|
* Pass the codec output `MediaFormat` to `VideoFrameMetadataListener`.
|
||||||
|
* Deprecate the GVR extension.
|
||||||
|
|
||||||
### 2.10.6 (2019-10-17) ###
|
### 2.10.6 (2019-10-17) ###
|
||||||
|
|
||||||
|
@ -1,4 +0,0 @@
|
|||||||
# ExoPlayer VR player demo #
|
|
||||||
|
|
||||||
This folder contains a demo application that showcases 360 video playback using
|
|
||||||
ExoPlayer GVR extension.
|
|
@ -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'
|
|
@ -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>
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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 |
@ -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>
|
|
@ -1,11 +1,15 @@
|
|||||||
# ExoPlayer GVR extension #
|
# 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
|
The GVR extension wraps the [Google VR SDK for Android][]. It provides a
|
||||||
GvrAudioProcessor, which uses [GvrAudioSurround][] to provide binaural rendering
|
GvrAudioProcessor, which uses [GvrAudioSurround][] to provide binaural rendering
|
||||||
of surround sound and ambisonic soundfields.
|
of surround sound and ambisonic soundfields.
|
||||||
|
|
||||||
[Google VR SDK for Android]: https://developers.google.com/vr/android/
|
[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
|
[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 ##
|
## Getting the extension ##
|
||||||
|
|
||||||
|
@ -28,7 +28,11 @@ import java.nio.ByteOrder;
|
|||||||
/**
|
/**
|
||||||
* An {@link AudioProcessor} that uses {@code GvrAudioSurround} to provide binaural rendering of
|
* An {@link AudioProcessor} that uses {@code GvrAudioSurround} to provide binaural rendering of
|
||||||
* surround sound and ambisonic soundfields.
|
* 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 {
|
public final class GvrAudioProcessor implements AudioProcessor {
|
||||||
|
|
||||||
static {
|
static {
|
||||||
|
@ -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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,6 +1,7 @@
|
|||||||
# ExoPlayer Firebase JobDispatcher extension #
|
# 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][].
|
This extension provides a Scheduler implementation which uses [Firebase JobDispatcher][].
|
||||||
|
|
||||||
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -37,7 +37,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||||
|
|
||||||
/** Renders a GL Scene. */
|
/** 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 frameAvailable;
|
||||||
private final AtomicBoolean resetRotationAtNextFrame;
|
private final AtomicBoolean resetRotationAtNextFrame;
|
||||||
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -20,12 +20,10 @@ if (gradle.ext.has('exoplayerModulePrefix')) {
|
|||||||
|
|
||||||
include modulePrefix + 'demo'
|
include modulePrefix + 'demo'
|
||||||
include modulePrefix + 'demo-cast'
|
include modulePrefix + 'demo-cast'
|
||||||
include modulePrefix + 'demo-gvr'
|
|
||||||
include modulePrefix + 'demo-surface'
|
include modulePrefix + 'demo-surface'
|
||||||
include modulePrefix + 'playbacktests'
|
include modulePrefix + 'playbacktests'
|
||||||
project(modulePrefix + 'demo').projectDir = new File(rootDir, 'demos/main')
|
project(modulePrefix + 'demo').projectDir = new File(rootDir, 'demos/main')
|
||||||
project(modulePrefix + 'demo-cast').projectDir = new File(rootDir, 'demos/cast')
|
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 + 'demo-surface').projectDir = new File(rootDir, 'demos/surface')
|
||||||
project(modulePrefix + 'playbacktests').projectDir = new File(rootDir, 'playbacktests')
|
project(modulePrefix + 'playbacktests').projectDir = new File(rootDir, 'playbacktests')
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user