diff --git a/RELEASENOTES.md b/RELEASENOTES.md
index a345976c34..e03a0d2dc9 100644
--- a/RELEASENOTES.md
+++ b/RELEASENOTES.md
@@ -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 ###
diff --git a/demos/gvr/README.md b/demos/gvr/README.md
new file mode 100644
index 0000000000..8cc52c5f10
--- /dev/null
+++ b/demos/gvr/README.md
@@ -0,0 +1,4 @@
+# ExoPlayer VR player demo #
+
+This folder contains a demo application that showcases 360 video playback using
+ExoPlayer GVR extension.
diff --git a/demos/gvr/build.gradle b/demos/gvr/build.gradle
new file mode 100644
index 0000000000..457af80a8d
--- /dev/null
+++ b/demos/gvr/build.gradle
@@ -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'
diff --git a/demos/gvr/src/main/AndroidManifest.xml b/demos/gvr/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000..8545787064
--- /dev/null
+++ b/demos/gvr/src/main/AndroidManifest.xml
@@ -0,0 +1,74 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/demos/gvr/src/main/java/com/google/android/exoplayer2/gvrdemo/PlayerActivity.java b/demos/gvr/src/main/java/com/google/android/exoplayer2/gvrdemo/PlayerActivity.java
new file mode 100644
index 0000000000..bd9c85da51
--- /dev/null
+++ b/demos/gvr/src/main/java/com/google/android/exoplayer2/gvrdemo/PlayerActivity.java
@@ -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;
+ }
+ }
+ }
+}
diff --git a/demos/gvr/src/main/java/com/google/android/exoplayer2/gvrdemo/SampleChooserActivity.java b/demos/gvr/src/main/java/com/google/android/exoplayer2/gvrdemo/SampleChooserActivity.java
new file mode 100644
index 0000000000..1ddf5c1517
--- /dev/null
+++ b/demos/gvr/src/main/java/com/google/android/exoplayer2/gvrdemo/SampleChooserActivity.java
@@ -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;
+ }
+ }
+}
diff --git a/demos/gvr/src/main/res/layout/sample_chooser_activity.xml b/demos/gvr/src/main/res/layout/sample_chooser_activity.xml
new file mode 100644
index 0000000000..ce520e70e4
--- /dev/null
+++ b/demos/gvr/src/main/res/layout/sample_chooser_activity.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
diff --git a/demos/gvr/src/main/res/mipmap-hdpi/ic_launcher.png b/demos/gvr/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000000..adaa93220e
Binary files /dev/null and b/demos/gvr/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/demos/gvr/src/main/res/mipmap-mdpi/ic_launcher.png b/demos/gvr/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000000..9b6f7d5e80
Binary files /dev/null and b/demos/gvr/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/demos/gvr/src/main/res/mipmap-xhdpi/ic_launcher.png b/demos/gvr/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000000..2101026c9f
Binary files /dev/null and b/demos/gvr/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/demos/gvr/src/main/res/mipmap-xxhdpi/ic_launcher.png b/demos/gvr/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000000..223ec8bd11
Binary files /dev/null and b/demos/gvr/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/demos/gvr/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/demos/gvr/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000000..698ed68c42
Binary files /dev/null and b/demos/gvr/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/demos/gvr/src/main/res/values/strings.xml b/demos/gvr/src/main/res/values/strings.xml
new file mode 100644
index 0000000000..08feccb398
--- /dev/null
+++ b/demos/gvr/src/main/res/values/strings.xml
@@ -0,0 +1,28 @@
+
+
+
+
+ ExoPlayer VR Demo
+
+ Cleartext traffic not permitted
+
+ Unrecognized stereo mode
+
+ Media includes video tracks, but none are playable by this device
+
+ Media includes audio tracks, but none are playable by this device
+
+
diff --git a/extensions/gvr/src/main/java/com/google/android/exoplayer2/ext/gvr/GvrPlayerActivity.java b/extensions/gvr/src/main/java/com/google/android/exoplayer2/ext/gvr/GvrPlayerActivity.java
index 2c912c17f2..38fa3a36e5 100644
--- a/extensions/gvr/src/main/java/com/google/android/exoplayer2/ext/gvr/GvrPlayerActivity.java
+++ b/extensions/gvr/src/main/java/com/google/android/exoplayer2/ext/gvr/GvrPlayerActivity.java
@@ -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;
diff --git a/settings.gradle b/settings.gradle
index d4530d67b7..50fdb68f30 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -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'