Как создать андроид-приложение с виртуальной реальностью – работаем с VR SDK Android

В этом уроке вы узнаете, как  создать приложение для просмотра так называемых фотосфер, или круговых панорамных фото и видео в формате 360 градусов, с помощью очков Cardboard. Используем официальный туториал от Google, ссылка в конце статьи.
[embedyt] http://www.youtube.com/watch?v=kHWwHP94Ybo[/embedyt]
Для разработки нам понадобится:
  • Смартфон  Android 5.0 (Lollipop) или выше, подключенный к компьютеру с помощью USB кабеля. Например, Nexus 5.
  • Настольный компьютер Android Studio версии 2.0 или выше.
  • Android SDK включая следующие компоненты:
  • Android 6.0 (API 23)
  • Google VR SDK для Android – это набор инструментов для разработки приложений с виртуальной реальностью.
  • Приложение Cardboard Camera на устройстве, с помощью которого можно отснять и создать круговую панораму – фотосферу.
  • Также понадобится приложение Cardboard для настройки очков виртуальной реальности.
  •  И наконец, сами очки виртуальной реальности Cardboard, или как их называют еще, Oculus Rift.
Подойдут недорогие очки, изготовленные из картона, которые можно заказать, например, здесь: http://www.3dmania.xyz/
New-Cardboard---Top-copy
Такие очки можно использовать для просмотра 3D фильмов, игр с виртуальной реальностью, для просмотра видео в формате 360 градусов на Youtube, путешествий по красивым местам планеты не выходя из дома, и даже для просмотра трехмерного видео для взрослых)

Клонируем проект

Для быстрого старта будем использовать заготовку приложения с двумя вкладками – фрагментами.
Клонируем  гугловский проект с github (ссылка под видео). Копируем ссылку на проект:
Чтобы иметь возможность работать с Git, нужно установить его на компьютер. Как это сделать, смотрите видеоуроки по работе с Git: https://youtu.be/mcOq0FbkfWI
Запускаем Android Studio и в окне приветствия выбираем “Check out project from Version Control”, в выпадающем списке жмем GitHub. Вставляем ссылку, выбираем папку, куда будет сохраняться проект и жмем Clone.
Далее, если при открытии проекта возникает ошибка “Не найден SDK”, попробуйте открыть проект из папки, куда он был клонирован.
После клонирования нужно выполнить сборку проекта через пункт меню Build > Make Project.  После этой первой сборки все изменения проекта будут автоматически обнаружены Android Studio.
Теперь нам понадобится  панорамный снимок со стереоскопическим эффектом. Чтобы его создать, используем приложение Cardboard Camera на реальном устройстве.
Нажимаем на кнопку камеры и следуем инструкциям на экране, чтобы сделать снимок. Приложению понадобится некоторое время, чтобы обработать изображение и сохранить его.
Cardboard Camera довольно привередливо к устройствам. Если вы не сможете сделать панорамный снимок, используйте готовое изображение в папке проекта по пути  app/assets/sample_converted.jpg
Если же у вас получилось сделать снимок с помощью смартфона, скопируйте его на компьютер. Самый простой способ сделать-  подключить телефон к компьютеру в качестве мультимедийного устройства. Затем скопируйте изображения с Cardboard Camera , который находится в папке DCIM / CardboardCamera.
Он будет иметь название что – то вродеIMG_20160512_105228.vr.jpg . и выглядеть примерно так.
IMG_20160719_144033.vr
Теперь у нас есть изображение, но, к сожалению, оно не совсем в том формате, что нам нужно. VR View имеет специфические требования к конфигурации изображения:
  • изображения должны быть в формате  PNG, JPEG или GIF. Рекомендуется использовать формат JPEG для улучшения сжатия.
  • Для обеспечения максимальной совместимости и производительности, размеры изображения должны быть степенью двойки (например, 2048 или 4096).
  • Моно-изображения должны иметь соотношение сторон 2:1 (например, 4096 х 2048).
  • Стереоизображения должны иметь соотношение сторон 1:1 (например, 4096 х 4096).
  • Изображения должны быть еквидистантной, или  равнопромежуточной проекции. В такой проекции, например, отображается географическая карта мира.
  • Если у вас есть изображения в других форматах , таких как cubemap , они должны быть преобразованы в еквидистантную проекцию для того , чтобы  отображаться в режиме VR.
Мое изображение имеет размер  10430×1678. Используем гугловский онлайн-конвертер, чтобы преобразовать изображение в нужный нам формат: https://storage.googleapis.com/cardboard-camera-converter/index.html
Выберем изображение  или перетащим его на страницу конвертера. Затем загрузим преобразованный файл и скопируем его в   app/src/main/assets/converted.jpg

Добавим Google VR SDK в проект

Загрузим  Google VR SDK с GitHub. Для этого откроем окно терминала в Android Studio, и клонируем SDK:
$ git clone https://github.com/googlevr/gvr-android-sdk.git
Откроем settings.gradle в Android Studio допишем набор библиотек VR SDK для Android с директивой include:
include ':app'
include ':gvr-android-sdk/libraries:audio'
include ':gvr-android-sdk/libraries:base'
include ':gvr-android-sdk/libraries:common'
include ':gvr-android-sdk/libraries:commonwidget'
include ':gvr-android-sdk/libraries:panowidget'
include ':gvr-android-sdk/libraries:videowidget'
Сохраним файл и нажмем “Синхронизировать сейчас” . Теперь мы можем ссылаться на компоненты библиотеки в нашем проекте.
Примечание : Если вы получаете уведомление “обнаружен незарегистрированный VCS корень”, можете нажать кнопку “Пропустить”.

Добавим изображение в окне приветствия фрагмента

Теперь, когда у нас есть панорамное (и стерео) изображение, давайте добавим его в наше приложение.
Во-первых,  добавим зависимости в файл сборки. В менеджере проекта развернем “Gradle Scripts” и Откроем build.gradle  (Module: app).
Добавим зависимости библиотеки VR SDK
В разделе зависимостей, добавим VR SDK для Android компонентов библиотеки:
    • compile project(‘:gvr-android-sdk/libraries:common’)
    • compile project(‘:gvr-android-sdk/libraries:commonwidget’)
    • compile project(‘:gvr-android-sdk/libraries:panowidget’)

Это будет выглядеть следующим образом:

dependencies {
    compile 'com.android.support:appcompat-v7:23.4.0'
    compile 'com.android.support:design:23.4.0'
    compile project(':gvr-android-sdk/libraries:common')
    compile project(':gvr-android-sdk/libraries:commonwidget')
    compile project(':gvr-android-sdk/libraries:panowidget')
}

Затем нажмем кнопку “Sync Now”, чтобы обновить проект.

Добавим VrPanoramaView в макет
Теперь, когда мы добавили зависимости,   отредактируем макет, заменив статическое изображение панорамным.
Открыть app/res/layout/welcome_fragment.xml
 Убедитесь , что вы выбрали вид “текст” в нижней части панели редактора макетов.
Найдем ImageView элемент и заменим его на VrPanoramaView
<com.google.vr.sdk.widgets.pano.VrPanoramaView
        android:id="@+id/pano_view"
        android:layout_weight="5"
        android:layout_height="0dp"
        android:layout_margin="5dip"
        android:layout_width="match_parent"
        android:scrollbars="none"
        android:contentDescription="@string/codelab_img_description"/>

Добавим код для управления VrPanoramaView

Откроем файл  app/java/com.google.devrel.vrviewapp/WelcomeFragment.java  в Android Studio.
В верхней части класса, добавим переменную VrPanoramaView :
private VrPanoramaView panoWidgetView;
Убедитесь, что класс импортируется, добавив оператор импорта класса:
import com.google.vr.sdk.widgets.pano.VrPanoramaView;
Для этого нажмем Alt-Enter.
Тогда, в методе  onCreateView (), инициализируем panoWidgetView. Замените содержимое onCreateView () :
View v =  inflater.inflate(R.layout.welcome_fragment, container,false);
panoWidgetView = (VrPanoramaView) v.findViewById(R.id.pano_view);
return v;
Кроме того, добавим методы жизненного цикла OnPause (), onResume (), и OnDestroy () , чтобы передать им VrPanoramaView,  добавим их чуть ниже метода onCreateView ().
@Override
public void onPause() {
   panoWidgetView.pauseRendering();
   super.onPause();
}

@Override
public void onResume() {
   panoWidgetView.resumeRendering();
   super.onResume();
}

@Override
public void onDestroy() {
   // Destroy the widget and free memory.
   panoWidgetView.shutdown();
   super.onDestroy();
}
Теперь добавим код для загрузки изображения.
Загрузим изображение асинхронно
Так как изображение велико , нельзя  загружать его в главном потоке пользовательского интерфейса при запуске приложения, поскольку интерфейс будет заметно тормозить. Нужно загрузить картинку в асинхронном режиме. Для получения более подробной информации по теме смотрите наш урок по ссылке. https://goo.gl/0XcWWg

Создадим новый класс

Выберем папку app / Java в менеджере проекта, а затем правой кнопкой мыши и Выберем New> Java Class . Назовите класс ImageLoaderTask .

Примечание : Если вам будет предложено добавить файл в Git, вы можете выбрать “нет”.
Необходимо унаследоваться от класса AsyncTask, чтобы выполнить загрузку изображений в фоновом потоке. Параметрами для AsyncTask являются:
  • AssetManager используемый для загрузки изображения.
  • Пустой параметр для метода прогресса (который мы не используем).
  • Изображение,  возвращаемое обратно в основной поток.
public class ImageLoaderTask extends AsyncTask<AssetManager, Void, Bitmap>  {
}

Убедитесь в том, что такие классы импортируются:

import android.content.res.AssetManager;
import android.graphics.Bitmap;
import android.os.AsyncTask;
AsyncTask является абстрактным классом, так что нам нужно реализовать абстрактные методы.
Совет : Для того, чтобы быстро устранить эту ошибку в Android Studio, на строке с ошибкой нажмем Ctrl+Enter и Выберем Выполнить методы.
добавим реализацию метода doInBackground в нижней части класса, чтобы устранить ошибку.
@Override
protected Bitmap doInBackground(AssetManager... params) {
    return null;
}
Добавим переменные и конструктор
Когда мы загружаем изображение, нам нужно передать, какое изображение загружать и где его отображать. Мы будем передавать эту информацию через конструктор. Кроме того, добавим элемент TAG по которому мы сможем регистрировать ошибки. Чуть ниже открывающей скобки этого класса объявляем
private static final String TAG = "ImageLoaderTask";
private final String assetName;
private final WeakReference<VrPanoramaView> viewReference;
private final VrPanoramaView.Options viewOptions;
Мы используем WeakReference для VrPanoramaView , так как  view может быть уничтожено при загрузке изображения. Общей причиной этого является поворот телефона в другую ориентацию. С использованием слабой ссылки, объект может быть уничтожен сборщиком мусора, не дожидаясь этой асинхронной задачи.
Убедитесь в том, что импортировали класс для VrPanoramaView. Если вы еще не добавили их, добавим операторы импорта в настоящее время:
import com.google.vr.sdk.widgets.pano.VrPanoramaView;
import java.lang.ref.WeakReference;
Чтобы избежать повторной загрузки изображения, когда устройство вращается, мы будем кэшировать последнее загруженное изображение. Добавим эти переменные класса:
private static WeakReference<Bitmap> lastBitmap = new WeakReference<>(null);
private static String lastName;
Так как эти переменные класса являются окончательными, они должны быть инициализированы в конструкторе. Добавим требуемый конструктор ниже переменных класса:
public ImageLoaderTask(VrPanoramaView view, VrPanoramaView.Options viewOptions, String assetName) {
    viewReference = new WeakReference<>(view);
    this.viewOptions = viewOptions;
    this.assetName = assetName;
}

Загрузим изображение в фоновом режиме

Для этого примера, мы добавили изображение в папку  assets проекта, поэтому мы будем использовать AssetManager , чтобы получить картинку. Затем передаем его в BitmapFactory , чтобы загрузить изображение и вернуть его обратно в основной поток. Если есть проблема, мы будем регистрировать ее и возвращать null вместо картинки. Мы проверяем последнее загруженное изображение перед открытием потока в целях экономии памяти.
Пишем в методе doInBackground (AssetManager ... PARAMS)  следующий код:

 

AssetManager assetManager = params[0];

if (assetName.equals(lastName) && lastBitmap.get() != null) {
    return lastBitmap.get();
}

try(InputStream istr = assetManager.open(assetName)) {
    Bitmap b = BitmapFactory.decodeStream(istr);
    lastBitmap = new WeakReference<>(b);
    lastName = assetName;
    return b;
} catch (IOException e) {
    Log.e(TAG, "Could not decode default bitmap: " + e);
    return null;
}
Затем добавим операторы импорта в верхней части
import android.graphics.BitmapFactory;
import android.util.Log;

import java.io.IOException;
import java.io.InputStream;

Отображение изображения

Чуть ниже doInBackground () , добавим такой код:

 

@Override
protected void onPostExecute(Bitmap bitmap) {
    final VrPanoramaView vw = viewReference.get();
    if (vw != null && bitmap != null) {
        vw.loadImageFromBitmap(bitmap, viewOptions);
    }
}
После того, как фоновый поток отработает, AsyncTask вызывает метод onPostExecute(Bitmap bitmap) в главном потоке. Здесь мы будем передавать изображение в VrPanoramaView.
Запуск фоновой загрузки
OK , мы создали AsyncTask для загрузки и отображения изображения, осталось только вызвать его.
Вернитесь к WelcomeFragment.java. Нам нужна новая переменная класса, объявим ее:
private ImageLoaderTask backgroundImageLoaderTask;

Затем в нижней части класса добавим новый метод loadPanoImage (). Это позволит создать новую задачу загрузчика и запустить его.

private synchronized void loadPanoImage() {
    ImageLoaderTask task = backgroundImageLoaderTask;
    if (task != null && !task.isCancelled()) {
        // Cancel any task from a previous loading.
        task.cancel(true);
    }

    // pass in the name of the image to load from assets.
    VrPanoramaView.Options viewOptions = new VrPanoramaView.Options();
    viewOptions.inputType = VrPanoramaView.Options.TYPE_STEREO_OVER_UNDER;

    // use the name of the image in the assets/ directory.
    String panoImageName = "converted.jpg";

    // create the task passing the widget view and call execute to start.
    task = new ImageLoaderTask(panoWidgetView, viewOptions, panoImageName);
    task.execute(getActivity().getAssets());
    backgroundImageLoaderTask = task;
}

И, наконец, нам нужно привязать загрузчик к событиям жизненного цикла , чтобы начать фактическую загрузку. Во фрагменте мы добавим его в метод onActivityCreated () переопределим его в конце класса WelcomeFragment:

@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    loadPanoImage();
}
Теперь можно испытать VR изображение!
Убедитесь, что ваше устройство подключено к компьютеру, и выберите пункт Run.
Когда приложение запустится, вставьте смартфон в очки виртуальной реальности.  Теперь оденьте очки и покрутите головой. Изображение будет  менять вид таким образом, как будто вы вы находитесь внутри панорамы и оглядываетесь вокруг.
При нажатии на иконку “весь экран” в правом нижнем углу изображения, изображение будет отображаться с использованием всего экрана.
Если щелкнуть значок Cardboard , он начнет вид в режиме Cardboard для стерео режима  VR.
Давайте попробуем проделать то же самое с видео!

Добавим видео фрагмент с гориллами

Панорамное видео на смартфоне создать не получится. Для этого нужна специальная 360 градусная камера, самая дешевая сейчас стоит около 300 долларов. Для этого примера, мы будем использовать готовое панорамное видео, оно лежит в папке asset с именем congo_2048.mp4. Этим видео мы заменим изображение во втором фрагменте.

Используем общий подход, просто заменим ImageView на VideoWidget . Также добавим элемент управления SeekBar, чтобы мы могли управлять видео.
Добавим зависимости библиотеки VR SDK

Откроем снова файл сценария сборки build.gradle для модуля приложения. Затем в разделе зависимостей, добавим компонент VideoWidget:

  • anroid-sdk/libraries:videowidget

Это будет выглядеть следующим образом:

 

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:23.4.0'
    compile 'com.android.support:design:23.4.0'
    compile project(':gvr-android-sdk/libraries:common')
    compile project(':gvr-android-sdk/libraries:commonwidget')
    compile project(':gvr-android-sdk/libraries:panowidget')
    compile project(':gvr-android-sdk/libraries:videowidget')
}

Нажмем кнопку “Sync Now”, чтобы обновить проект.

добавим видео в макете

Откроем  res/layout/gorilla_fragment.xml  и заменим ImageView   следующими элементами:
<com.google.vr.sdk.widgets.video.VrVideoView
    android:id="@+id/video_view"
    android:layout_width="match_parent"
    android:scrollbars="none"
    android:layout_height="250dip"/>

<!-- Seeking UI & progress indicator.-->
<SeekBar
    android:id="@+id/seek_bar"
    style="?android:attr/progressBarStyleHorizontal"
    android:layout_height="32dp"
    android:layout_width="fill_parent"/>
<TextView
    android:id="@+id/status_text"
    android:text="Loading Video..."
    android:layout_height="wrap_content"
    android:layout_width="fill_parent"
    android:textSize="12sp"
    android:paddingStart="32dp"
    android:paddingEnd="32dp"/>
Добавим переменные класса
Откроем теперь  app/java/com.google.devrel.vrviewapp/GorillaFragment.java , и в верхней части добавим эти переменные класса:

 

/**
     * Preserve the video's state and duration when rotating the phone. This improves 
     * performance when rotating or reloading the video.
     */
    private static final String STATE_IS_PAUSED = "isPaused";
    private static final String STATE_VIDEO_DURATION = "videoDuration";
    private static final String STATE_PROGRESS_TIME = "progressTime";


    /**
     * The video view and its custom UI elements.
     */
    private VrVideoView videoWidgetView;

    /**
     * Seeking UI & progress indicator. The seekBar's progress value represents milliseconds in the
     * video.
     */
    private SeekBar seekBar;
    private TextView statusText;

    /**
     * By default, the video will start playing as soon as it is loaded. 
     */
    private boolean isPaused = false;
 

Не забудьте добавим операторы импорта, если их еще нет:

import com.google.vr.sdk.widgets.video.VrVideoView;
import android.widget.SeekBar;
import android.widget.TextView;

Инициализируем переменные класса

Теперь, когда мы объявили нужные переменные, мы должны инициализировать и найти view компоненты по ID. Допишем в метод  onCreateView () такой код:

@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
    View view = inflater.inflate(R.layout.gorilla_fragment, container,false);
    seekBar = (SeekBar) view.findViewById(R.id.seek_bar);
    statusText = (TextView) view.findViewById(R.id.status_text);
    videoWidgetView = (VrVideoView) view.findViewById(R.id.video_view);

    // Add the restore state code here.
        
    // Add the seekbar listener here.

    // Add the VrVideoView listener here

    return view;
}

Инициализируем SeekBar Слушатель

SeekBar должен иметь слушателя который бедет реагировать на изменение положения ползунка в видео.

Добавим слушателя onCreateView () перед оператором return.

// initialize the seekbar listener 
 seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
    @Override
    public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {

        // if the user changed the position, seek to the new position.
        if (fromUser) {
            videoWidgetView.seekTo(progress);
            updateStatusText();
        }
    }

    @Override
    public void onStartTrackingTouch(SeekBar seekBar) {
        // ignore for now.
    }

    @Override
    public void onStopTrackingTouch(SeekBar seekBar) {
        // ignore for now.
    }
});
Метод updateStatusText () еще не определен. Он будет добавлен позже.

Инициализируем VrVideoView Слушатель

VrVideoView должен иметь слушателя, чтобы реагировать на изменения состояния.
Добавим слушателя в onCreateView ()  перед оператором return . Также нужно переопределить методы слушателя.
  • onLoadSuccess – вызывается при успешной загрузке видео. Здесь мы устанавливаем длительность видео для компонента seekBar и активируем его. Также здесь обновляем текст состояния.
  • onLoadError – вызывается при ошибке загрузки видео. Здесь создаем тост с сообщением об ошибке.
  • OnClick – вызывается при касании или клике по video view. Будем приостанавливать видео и возобновлять воспроизведение поочередно. Также здесь обновляем текст состояния.
  • onNewFrame – называется при воспроизведении каждого видеокадра. Здесь обновляем пользовательский интерфейс – текст состояния и текущее положение ползунка.
  • onCompletion – вызывается по окончании видео. Воспроизводим видео в цикле. Этот метод также может использоваться для перемещения к следующему видео в списке воспроизведения.
// initialize the video listener
videoWidgetView.setEventListener(new VrVideoEventListener() {
    /**
     * Called by video widget on the UI thread when it's done loading the video.
     */
    @Override
    public void onLoadSuccess() {
        Log.i(TAG, "Successfully loaded video " + videoWidgetView.getDuration());
        seekBar.setMax((int) videoWidgetView.getDuration());
        seekBar.setEnabled(true);
        updateStatusText();
   }

   /**
    * Called by video widget on the UI thread on any asynchronous error.
    */
   @Override
   public void onLoadError(String errorMessage) {
       Toast.makeText(
          getActivity(), "Error loading video: " + errorMessage, Toast.LENGTH_LONG)
                      .show();
       Log.e(TAG, "Error loading video: " + errorMessage);
    }

    @Override
    public void onClick() {
        if (isPaused) {
              videoWidgetView.playVideo();
        } else {
            videoWidgetView.pauseVideo();
        }

        isPaused = !isPaused;
        updateStatusText();
    }

    /**
    * Update the UI every frame.
    */
    @Override
    public void onNewFrame() {
        updateStatusText();
        seekBar.setProgress((int) videoWidgetView.getCurrentPosition());
    }

    /**
     * Make the video play in a loop. This method could also be used to move to the next video in
     * a playlist.
     */
    @Override
    public void onCompletion() {
        videoWidgetView.seekTo(0);
    }
});
Проследите за добавлением таких операторов импорта в верхней части:
import com.google.vr.sdk.widgets.video.VrVideoEventListener;
import android.util.Log;
import android.widget.Toast;
Этот код почти готов, осталось добавить метод обновления строки состояния видеоплеера updateStatusText () . Давайте сделаем это в конце класса.
private void updateStatusText() {
    String status = (isPaused ? "Paused: " : "Playing: ") +
            String.format(Locale.getDefault(), "%.2f", videoWidgetView.getCurrentPosition() / 1000f) +
            " / " +
            videoWidgetView.getDuration() / 1000f +
            " seconds.";
    statusText.setText(status);
}

И добавим еще один оператор импорта:

import java.util.Locale;

Обработка сохранения состояния видео

При вращении телефона, активити будет пересоздаваться. После начальной загрузки видео необходимо установить значение ползунка SeekBar, поэтому нужно сохранять это значение для использования в случае пересоздания активити. Также нужно сохранить текущее состояние видео, чтобы при повороте телефона не начиналось воспроизведение видео, если оно было установлено на паузу.
Для этого перегрузим метод: onSaveInstanceState () , добавим это в конце класса:
@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
    savedInstanceState.putLong(STATE_PROGRESS_TIME, videoWidgetView.getCurrentPosition());
    savedInstanceState.putLong(STATE_VIDEO_DURATION, videoWidgetView.getDuration());
    savedInstanceState.putBoolean(STATE_IS_PAUSED, isPaused);
    super.onSaveInstanceState(savedInstanceState);
}
Будем считывать сохраненное состояние в методе onCreateView (). Добавим этот код сразу после инициализации переменных класса,
(где Eсть комментарий ” // Добавление восстановления состояния кода здесь. “)
// initialize based on the saved state
if (savedInstanceState != null) {
    long progressTime = savedInstanceState.getLong(STATE_PROGRESS_TIME);
    videoWidgetView.seekTo(progressTime);
    seekBar.setMax((int)savedInstanceState.getLong(STATE_VIDEO_DURATION));
    seekBar.setProgress((int) progressTime);

    isPaused = savedInstanceState.getBoolean(STATE_IS_PAUSED);
    if (isPaused) {
        videoWidgetView.pauseVideo();
    }
} else {
    seekBar.setEnabled(false);
}

Обработка события жизненного цикла

Нам нужно обработать события жизненного цикла OnPause , onResult и OnDestroy в VrVideoView. Переопределим эти методы в конце класса, перед последней фигурной скобкой:
@Override
public void onPause() {
    super.onPause();
    // Prevent the view from rendering continuously when in the background.
    videoWidgetView.pauseRendering();
    // If the video was playing when onPause() is called, the default behavior will be to pause
    // the video and keep it paused when onResume() is called.
    isPaused = true;
}

@Override
public void onResume() {
    super.onResume();
    // Resume the 3D rendering.
    videoWidgetView.resumeRendering();
    // Update the text to account for the paused video in onPause().
    updateStatusText();
}

@Override
public void onDestroy() {
    // Destroy the widget and free memory.
    videoWidgetView.shutdown();
    super.onDestroy();
}

Запускаем видео при открытии фрагмента

Так как мы используем фрагменты, есть только один активити для обоих фрагментов WelcomeFragment и GorillaFragment. Мы не хотим, чтобы началась загрузка видео , пока мы не переключимся на GorillaFragment. Чтобы реализовать это, перегрузим метод setUserVisibleHint () в GorillaFragment. Этот метод будет вызываться при изменении видимости фрагмента.
Когда фрагмент виден, проверим, загружено ли видео, и если нет, то стартуем загрузку. В противном случае, просто приостановим воспроизведение видео, так как его больше не видно.
Добавим этот метод к концу класса:
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
    super.setUserVisibleHint(isVisibleToUser);

    if (isVisibleToUser) {
        try {
            if (videoWidgetView.getDuration() <= 0) {
                videoWidgetView.loadVideoFromAsset("congo_2048.mp4");
            }
        } catch (Exception e) {
            Toast.makeText(getActivity(), "Error opening video: " + e.getMessage(), Toast.LENGTH_LONG)
                        .show();
        }
    } else {
        isPaused = true;
        if (videoWidgetView != null) {
            videoWidgetView.pauseVideo();
        }
    }
}
Сохраните и запустите приложение. Для просмотра видео выберите вторую вкладку.
При повороте устройства вы можете смотреть панорамное видео по кругу на 360 градусов.
Теперь вы знаете, как создавать приложения с виртуальной реальностью в android.
Сайт разработчиков Google VR: https://developer.google.com/vr
Источник https://goo.gl/2fDCwR
Исходный код полностью: https://yadi.sk/d/H2BfOwSCtrmEA
Коментарі: 4
  1. joi97

    Нужна помощь((( Нужно добавить в WelcomeFragment ещё несколько изображений, чтобы можно было их выбирать. Помогите что куда вставлять (printsdoma@gmail.com)

  2. Александр

    Добрый день. Пробую запустить приложения с изображением на 360 гр. Но при запуске вылазит ошибка
    FATAL EXCEPTION: main
    ругается на строку
    panoWidgetView.resumeRendering();
    в WelcomeFragment.java. Кто-нибудь сталкивался ?
    p.s. примеры кода брал с этой страницы, а так же пробовал с оф. страницы от гугла.

  3. Ger

    Спасибо за статью, но исходный код полностью: https://yadi.sk/d/H2BfOwSCtrmEA не содержит всех файлов. Хотя-бы даже “settings.gradle” и при попытке сбилдить проект выдает ошибку Error:(1, 0) Plugin with id ‘com.android.application’ not found.

    Помогите в исправлении пожалуйста

    1. admin (автор)

      загуглите ошибку – есть масса решений

Додати коментар