Siguiendo con la temática del último post, volvemos a crear Apps enfocadas a la realidad virtual. Si hace un mes vimos cómo visualizar fotografías en 360º, hoy toca jugar con videos 360º y Google VR.

La última vez, hicimos una pequeña introducción a la plataforma Google VR, comentando las facilidades que proporciona para incluir contenido de realidad virtual en nuestra aplicación. Si el ejemplo utilizado en esa ocasión era una imagen estática, en este caso utilizaremos un video de realidad virtual.

El contenido a mostrar

Si consultamos los ejemplos que proporciona la plataforma, podemos observar cómo tienen un video de gorilas. Para no utilizar siempre el mismo vídeo y utilizar distintas opciones, echaremos mano de YouTube. Realizando una búsqueda de contenido para realidad virtual nos toparemos con un montón de videos de distinta temática: tiburones, montañas rusas, star wars, …

Vídeo Demo Gorilas

Una vez hayamos seleccionado el video que nos interese debemos descargarlo. Podemos utilizar herramientas como http://keepvid.com/ . Keepvid nos ofrece distintas posibilidades de descargar contenido de youtube pegando el enlace del video en su buscador. En este paso hay que ser contenidos: aunque queramos mostrar la mayor calidad, lo ideal no es descargar la mejor versión, ya que el tamaño de nuestra aplicación sería demasiado grande (dependiendo del video y la calidad podemos hablar de archivos de 500 megas). La recomendación para este ejemplo es buscar algún vídeo de un minuto y descargarlo en la peor calidad disponible. En un futuro ya veremos cómo lidiar con el tema de la calidad y los tamaños.

El video utilizado en el ejemplo ocupa 44 MB y si lo queréis utilizar lo podéis descargar desde aquí.

Creando la aplicación
    1. Creamos una nueva aplicación con la plantilla “Empty Activity” y fijamos el SDK en 25.
    2. Abrimos el gradle de la aplicación y fijamos el SDK mínimo en 21. Este paso no es obligatorio pero puede servir para ejecutar la app en dispositivos con versiones anteriores a Android 7.
      android {
          compileSdkVersion 25
          buildToolsVersion "25.0.0"
          defaultConfig {
              applicationId "com.example.ertzil.videovr"
              minSdkVersion 21
              targetSdkVersion 25
              versionCode 1
              versionName "1.0"
              testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
          }
      
    3. Nos aseguramos que en al gradle del proyecto está el jcenter.
      allprojects {
          repositories {
              jcenter()
          }
      }
    4. En el gradle de la aplicación, en el apartado de las dependencias, añadimos la siguiente dependencia y sincronizamos:
      compile 'com.google.vr:sdk-videowidget:1.70.0'
    5. Antes de meternos con el código debemos añadir el video que se mostrará. Para ello debemos añadir la carpeta Assets en el proyecto. En el árbol del proyecto pulsamos con el botón derecho sobre app y seleccionamos New/Folder/Assets Folder y dentro copiamos el fichero. En el caso del ejemplo será coaster.mp4.
    6. Vaciamos el contenido del layout activity_main.xml y añadimos la siguiente vista:
      <com.google.vr.sdk.widgets.video.VrVideoView
          android:id="@+id/video_view"
          android:layout_width="match_parent"
          android:scrollbars="@null"
          android:layout_height="250dip"/>

      Esta vista se encargará de visualizar el video. Es parte de la librería añadida en el paso 4.

    7. Abrimos el MainActivity y añadimos las variables que vamos a utilizar:
      private static final String TAG = MainActivity.class.getSimpleName();
      public static final int LOAD_VIDEO_STATUS_UNKNOWN = 0;
      public static final int LOAD_VIDEO_STATUS_SUCCESS = 1;
      public static final int LOAD_VIDEO_STATUS_ERROR = 2;
      
      private int loadVideoStatus = LOAD_VIDEO_STATUS_UNKNOWN;
      private boolean isPaused = false;
      
      private Uri fileUri;
      
      private VrVideoView.Options videoOptions = new VrVideoView.Options();
      
      private VideoLoaderTask backgroundVideoLoaderTask;
      protected VrVideoView videoWidgetView;

      El tag lo utilizaremos en los logs.
      Tendremos una serie de variables que utilizaremos para guardar del estado del video en el momento de carga, un boolean para saber si está parado o no, un URI que utilizaremos para el fichero a cargar, las opciones de reproducción del video, un hilo para cargar el mismo en paralelo y la variable de la vista que hemos añadido en el paso 6.

    8. En el método onCreate añadimos este código:
      videoWidgetView = (VrVideoView) findViewById(R.id.video_view);
      videoWidgetView.setEventListener(new ActivityEventListener());
      
      handleIntent(getIntent());

      Inicializamos la variable de la vista y le añadimos un escuchador de eventos. También añadimos un método que se llamará en el inicio de la actividad y en los cambios de rotación.

    9. Añadimos el método handleIntent:
      private void handleIntent(Intent intent) {
          // Determine if the Intent contains a file to load.
          if (Intent.ACTION_VIEW.equals(intent.getAction())) {
              Log.i(TAG, "ACTION_VIEW Intent received");
      
              fileUri = intent.getData();
              if (fileUri == null) {
                  Log.w(TAG, "No data uri specified. Use \"-d /path/filename\".");
              } else {
                  Log.i(TAG, "Using file " + fileUri.toString());
              }
      
              videoOptions.inputFormat = intent.getIntExtra("inputFormat", VrVideoView.Options.FORMAT_DEFAULT);
              videoOptions.inputType = intent.getIntExtra("inputType", VrVideoView.Options.TYPE_MONO);
          } else {
              Log.i(TAG, "Intent is not ACTION_VIEW. Using the default video.");
              fileUri = null;
          }
      
          // Load the bitmap in a background thread to avoid blocking the UI thread. This operation can
          // take 100s of milliseconds.
          if (backgroundVideoLoaderTask != null) {
              // Cancel any task from a previous intent sent to this activity.
              backgroundVideoLoaderTask.cancel(true);
          }
          backgroundVideoLoaderTask = new VideoLoaderTask();
          backgroundVideoLoaderTask.execute(Pair.create(fileUri, videoOptions));
      }

      Este método se encargará de cargar el video utilizando un hilo de background. Se fijan las opciones del video antes de su reproducción. Estas opciones dependerán del video que se vaya a reproducir por lo que la configuración de este ejemplo puede no valer para el video que se haya seleccionado.

    10. Añadimos los métodos que nos interesa sobrescribir:
      @Override
      protected void onNewIntent(Intent intent) {
          Log.i(TAG, this.hashCode() + ".onNewIntent()");
          setIntent(intent);
          handleIntent(intent);
      }
      
      @Override
      protected void onPause() {
          super.onPause();
          videoWidgetView.pauseRendering();
          isPaused = true;
      }
      
      @Override
      protected void onResume() {
          super.onResume();
          videoWidgetView.resumeRendering();
       }
      
      @Override
      protected void onDestroy() {
          videoWidgetView.shutdown();
          super.onDestroy();
      }

      En los métodos onPause, onResume y onDestroy hacemos acciones sobre el video. Para cada cual el suyo: pausar, reanudar y apagar.

    11. Añadimos el método togglePause:
      private void togglePause() {
          if (isPaused) {
              videoWidgetView.playVideo();
          } else {
              videoWidgetView.pauseVideo();
          }
          isPaused = !isPaused;
      }

      Este método se encargará de reanudar o pausar el video dependiendo del valor del boolean isPaused. Este método se llamará desde el escuchador de la vista.

    12. Añadimos la clase que utilizaremos como escuchador del video.
      private class ActivityEventListener extends VrVideoEventListener {
          @Override
          public void onLoadSuccess() {
              Log.i(TAG, "Successfully loaded video " + videoWidgetView.getDuration());
              loadVideoStatus = LOAD_VIDEO_STATUS_SUCCESS;
      
          }
      
          @Override
          public void onLoadError(String errorMessage) {
              loadVideoStatus = LOAD_VIDEO_STATUS_ERROR;
              Toast.makeText(
                      MainActivity.this, "Error loading video: " + errorMessage, Toast.LENGTH_LONG)
                      .show();
              Log.e(TAG, "Error loading video: " + errorMessage);
          }
      
          @Override
          public void onClick() {
              togglePause();
          }
      
          @Override
          public void onNewFrame() {
          }
      
          @Override
          public void onCompletion() {
              videoWidgetView.seekTo(0);
          }
      }

      Controlará si el video se ha cargado correctamente, llamará al método togglePause cuando se pulse sobre el video y reiniciará el mismo cuando llegue al final.

    13. Añadimos el hilo que se encargará de cargar el video.
      class VideoLoaderTask extends AsyncTask<Pair<Uri, VrVideoView.Options>, Void, Boolean> {
          @Override
          protected Boolean doInBackground(Pair<Uri, VrVideoView.Options>... fileInformation) {
              try {
                  if (fileInformation == null || fileInformation.length < 1
                          || fileInformation[0] == null || fileInformation[0].first == null) {
                      // No intent was specified, so we default to playing the local stereo-over-under video.
                      VrVideoView.Options options = new VrVideoView.Options();
                      options.inputType = VrVideoView.Options.TYPE_STEREO_OVER_UNDER;
                      videoWidgetView.loadVideoFromAsset("coaster.mp4", options);
                  } else {
                      videoWidgetView.loadVideo(fileInformation[0].first, fileInformation[0].second);
                  }
              } catch (IOException e) {
                  // An error here is normally due to being unable to locate the file.
                  loadVideoStatus = LOAD_VIDEO_STATUS_ERROR;
                  // Since this is a background thread, we need to switch to the main thread to show a toast.
                  videoWidgetView.post(new Runnable() {
                      @Override
                      public void run() {
                          Toast
                                  .makeText(MainActivity.this, "Error opening file. ", Toast.LENGTH_LONG)
                                  .show();
                      }
                  });
                  Log.e(TAG, "Could not open video: " + e);
              }
      
              return true;
          }
      }

      Carga el video desde el fichero (coaster.mp4 en nuestro caso).

    14. Ejecutamos y disfrutamos del video en 360º.


Si os fijáis, cada vez que se gira el dispositivo, el video se reinicia. Esto se debe al método handleIntent que se llama en cada nuevo intent. Para evitar esto de una forma rápida podemos fijar la orientación de la pantalla. Abrimos el AndroidManifest y a la actividad MainActivity le añadimos el siguiente atributo:

android:screenOrientation="portrait"

De esta forma la orientación queda fijada y el video no se reinicia si giramos el dispositivo.
Con este post cerraremos el paréntesis de la realidad virtual después de ver qué fácil es visualizar fotos y videos utilizando Google VR.

El código de este ejemplo lo podéis descargar desde aquí.

¡Nos vemos en el siguiente post!