Add VR player demo

PiperOrigin-RevId: 251460113
This commit is contained in:
eguven 2019-06-04 18:03:32 +01:00 committed by Oliver Woodman
parent be88499615
commit e4feaa68f2
15 changed files with 573 additions and 1 deletions

View File

@ -16,6 +16,7 @@
for the underlying track was changing (e.g., at some period transitions).
* Add a workaround for broken raw audio decoding on Oppo R9
([#5782](https://github.com/google/ExoPlayer/issues/5782)).
* Add VR player demo.
### 2.10.2 ###

4
demos/gvr/README.md Normal file
View File

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

59
demos/gvr/build.gradle Normal file
View File

@ -0,0 +1,59 @@
// 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:1.0.2'
}
apply plugin: 'com.google.android.gms.strict-version-matcher-plugin'

View File

@ -0,0 +1,74 @@
<?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>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="http"/>
<data android:scheme="https"/>
<data android:scheme="content"/>
<data android:scheme="asset"/>
<data android:scheme="file"/>
<data android:host="*"/>
<data android:pathPattern=".*\\.exolist\\.json"/>
</intent-filter>
</activity>
<activity
android:name="com.google.android.exoplayer2.gvrdemo.PlayerActivity"
android:configChanges="density|keyboardHidden|navigation|orientation|screenSize|uiMode"
android:enableVrMode="@string/gvr_vr_mode_component"
android:exported="false"
android:label="@string/application_name"
android:launchMode="singleTask"
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"/> <!-- copybara:strip(development-only) -->
<category android:name="com.google.intent.category.DAYDREAM"/>
</intent-filter>
</activity>
</application>
</manifest>

View File

@ -0,0 +1,243 @@
/*
* 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 androidx.annotation.Nullable;
import android.widget.Toast;
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.ExoPlayerFactory;
import com.google.android.exoplayer2.PlaybackPreparer;
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.AdaptiveTrackSelection;
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.DataSource;
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 implements PlaybackPreparer {
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 DataSource.Factory dataSourceFactory;
private SimpleExoPlayer player;
private MediaSource mediaSource;
private DefaultTrackSelector trackSelector;
private TrackGroupArray lastSeenTrackGroupArray;
private boolean startAutoPlay;
private int startWindow;
private long startPosition;
// Activity lifecycle
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String userAgent = Util.getUserAgent(this, "ExoPlayerDemo");
dataSourceFactory =
new DefaultDataSourceFactory(this, new DefaultHttpDataSourceFactory(userAgent));
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
public void onResume() {
super.onResume();
if (Util.SDK_INT <= 23 || player == null) {
initializePlayer();
}
}
@Override
public void onPause() {
super.onPause();
if (Util.SDK_INT <= 23) {
releasePlayer();
}
}
@Override
public void onDestroy() {
super.onDestroy();
}
// PlaybackControlView.PlaybackPreparer implementation
@Override
public void preparePlayback() {
initializePlayer();
}
// Internal methods
private void initializePlayer() {
if (player == null) {
Intent intent = getIntent();
Uri uri = intent.getData();
if (!Util.checkCleartextTrafficPermitted(uri)) {
showToast(R.string.error_cleartext_not_permitted);
return;
}
DefaultRenderersFactory renderersFactory = new DefaultRenderersFactory(this);
trackSelector = new DefaultTrackSelector(new AdaptiveTrackSelection.Factory());
lastSeenTrackGroupArray = null;
player =
ExoPlayerFactory.newSimpleInstance(/* context= */ this, renderersFactory, trackSelector);
player.addListener(new PlayerEventListener());
player.setPlayWhenReady(startAutoPlay);
player.addAnalyticsListener(new EventLogger(trackSelector));
setPlayer(player);
mediaSource = buildMediaSource(uri, intent.getStringExtra(EXTENSION_EXTRA));
}
boolean haveStartPosition = startWindow != C.INDEX_UNSET;
if (haveStartPosition) {
player.seekTo(startWindow, startPosition);
}
player.prepare(mediaSource, !haveStartPosition, false);
}
private MediaSource buildMediaSource(Uri uri, @Nullable String overrideExtension) {
@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 releasePlayer() {
if (player != null) {
updateStartPosition();
player.release();
player = null;
mediaSource = null;
trackSelector = null;
}
}
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

@ -0,0 +1,133 @@
/*
* 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),
new Sample(
"Camera motion metadata test",
"https://storage.googleapis.com/exoplayer-test-media-internal-"
+ "63834241aced7884c2544af1a3452e01/vr180/synthetic_with_camm.mp4",
SPHERICAL_STEREO_MODE_TOP_BOTTOM),
new Sample(
"actual_camera_cat",
"https://storage.googleapis.com/exoplayer-test-media-internal-"
+ "63834241aced7884c2544af1a3452e01/vr180/actual_camera_cat.mp4",
SPHERICAL_STEREO_MODE_TOP_BOTTOM),
new Sample(
"johnny_stitched",
"https://storage.googleapis.com/exoplayer-test-media-internal-"
+ "63834241aced7884c2544af1a3452e01/vr180/johnny_stitched.mp4",
SPHERICAL_STEREO_MODE_TOP_BOTTOM),
new Sample(
"lenovo_birds.vr",
"https://storage.googleapis.com/exoplayer-test-media-internal-"
+ "63834241aced7884c2544af1a3452e01/vr180/lenovo_birds.vr.mp4",
SPHERICAL_STEREO_MODE_TOP_BOTTOM),
new Sample(
"mono_v1_sample",
"https://storage.googleapis.com/exoplayer-test-media-internal-"
+ "63834241aced7884c2544af1a3452e01/vr180/mono_v1_sample.mp4",
SPHERICAL_STEREO_MODE_MONO),
new Sample(
"not_vr180_actually_shot_with_moto_mod",
"https://storage.googleapis.com/exoplayer-test-media-internal-"
+ "63834241aced7884c2544af1a3452e01/vr180/"
+ "not_vr180_actually_shot_with_moto_mod.mp4",
SPHERICAL_STEREO_MODE_TOP_BOTTOM),
new Sample(
"stereo_v1_sample",
"https://storage.googleapis.com/exoplayer-test-media-internal-"
+ "63834241aced7884c2544af1a3452e01/vr180/stereo_v1_sample.mp4",
SPHERICAL_STEREO_MODE_TOP_BOTTOM),
new Sample(
"yi_giraffes.vr",
"https://storage.googleapis.com/exoplayer-test-media-internal-"
+ "63834241aced7884c2544af1a3452e01/vr180/yi_giraffes.vr.mp4",
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

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

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

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

@ -50,7 +50,10 @@ import com.google.vr.sdk.controller.ControllerManager;
import javax.microedition.khronos.egl.EGLConfig;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/** Base activity for VR 360 video playback. */
/**
* Base activity for VR 360 video playback. Before starting the video playback a player needs to be
* set using {@link #setPlayer(Player)}.
*/
public abstract class GvrPlayerActivity extends GvrActivity {
private static final int EXIT_FROM_VR_REQUEST_CODE = 42;

View File

@ -21,10 +21,12 @@ if (gradle.ext.has('exoplayerModulePrefix')) {
include modulePrefix + 'demo'
include modulePrefix + 'demo-cast'
include modulePrefix + 'demo-ima'
include modulePrefix + 'demo-gvr'
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-ima').projectDir = new File(rootDir, 'demos/ima')
project(modulePrefix + 'demo-gvr').projectDir = new File(rootDir, 'demos/gvr')
project(modulePrefix + 'playbacktests').projectDir = new File(rootDir, 'playbacktests')
apply from: 'core_settings.gradle'