Dance Tonite en WebVR

Me emocionó cuando el equipo de Data Arts de Google nos acercó a Moniker y a mí para trabajar juntos en la exploración de las posibilidades que presenta WebVR. He observado el trabajo de su equipo a lo largo de los años y sus proyectos siempre me han dado en la tecla. Nuestra colaboración dio como resultado Dance Tonite, una experiencia de baile en RV que cambia constantemente con LCD Soundsystem y sus fans. Así es como lo hicimos.

El concepto

Comenzamos con el desarrollo de una serie de prototipos utilizando WebVR, un estándar abierto que hace posible ingresar a la RV mediante la visita a un sitio web con el navegador. El objetivo es facilitar el acceso a las experiencias de RV, sin importar el dispositivo que tengas.

Nos tomamos esto muy en serio. Lo que se nos haya ocurrido debería funcionar en todos los tipos de RV, desde los visores de RV que funcionan con teléfonos celulares como Daydream View de Google, Cardboard y Gear VR de Samsung hasta sistemas a escala habitación como HTC VIVE y Oculus Rift que reflejan tus movimientos físicos en tu entorno virtual. Tal vez lo más importante es que creímos que conducía el espíritu de la Web hacer algo que también funcionara para todos los que no tienen un dispositivo de RV.

1. Captura de movimiento que puedes hacer tú mismo

Como queríamos involucrar a los usuarios de forma creativa, empezamos a buscar las posibilidades de participación y expresión personal mediante la RV. Nos impresionó la precisión con la que podías moverse y mirar alrededor en RV, y la fidelidad que había. Esto nos dio una idea. En lugar de que los usuarios miren o creen algo, ¿qué tal si grabas sus movimientos?

Alguien se graba a sí mismo en Dance Tonite. En la pantalla detrás de ellos, se muestra lo que ven en sus auriculares

Cocinamos un prototipo en el que registramos las posiciones de las gafas y los controles de RV mientras bailamos. Reemplazamos las posiciones registradas por formas abstractas y nos maravillamos con los resultados. Los resultados fueron tan humanos y con mucha personalidad. Rápidamente nos dimos cuenta de que podíamos usar WebVR para realizar capturas de movimiento económicas en casa.

Con WebVR, el desarrollador tiene acceso a la orientación y posición de la cabeza del usuario mediante el objeto VRPose. El hardware de RV actualiza cada fotograma para que tu código pueda renderizar fotogramas nuevos desde el punto de vista correcto. A través de la API de GamePad con WebVR, también podemos acceder a la posición y la orientación de los controladores de los usuarios a través del objeto GamepadPose. Simplemente almacenamos todos estos valores de posición y orientación en cada fotograma, lo que crea una "grabación" de los movimientos del usuario.

2. Minimalismo y vestuario

Con el equipo de RV a escala de habitación actual, podemos hacer un seguimiento de tres puntos del cuerpo del usuario: su cabeza y dos manos. En Dance Tonite, quisimos mantener el foco en la humanidad en el movimiento de estos 3 puntos en el espacio. Para lograr esto, aplicamos la estética al mínimo posible para enfocarnos en el movimiento. Nos gustó la idea de poner el cerebro de las personas en funcionamiento.

Este video demuestra el trabajo del psicólogo sueco Gunnar Johansson fue uno de los ejemplos a los que nos referimos cuando consideramos simplificar las cosas tanto como sea posible. Muestra cómo los puntos blancos flotantes se reconocen al instante como cuerpos cuando se ven en movimiento.

En términos visuales, las habitaciones coloridas y los trajes geométricos nos inspiraron en esta grabación de la reposición de Margarete Hastings en 1970 del Ballet triádico de Oskar Schlemmer.

Mientras que la razón por la que Schlemmer eligió atuendos geométricos abstractos era limitar los movimientos de sus bailarinas a los de los títeres y las marionetas, teníamos el objetivo opuesto para Dance Tonite.

Terminamos basando nuestra elección de formas en la cantidad de información que transmitían por rotación. Un orbe se ve igual sin importar cómo rota, pero un cono en realidad apunta en la dirección a la que mira y se ve diferente desde el frente que desde el posterior.

3. Pedal de bucle para moverte

Queríamos mostrar grandes grupos de personas grabadas bailando y moviéndose entre sí. No sería posible hacerlo en vivo, ya que los dispositivos de RV no están disponibles en cantidades lo suficientemente grandes. Pero queríamos que algunos grupos reaccionen entre sí a través del movimiento. Pensamos en una representación recursiva de Norman McClaren en su obra de video “Canon” de 1964.

La actuación de McClaren presenta una serie de movimientos altamente coreográficos que comienzan a interactuar entre sí después de cada repetición. Al igual que un pedal en forma de bucle en la música, en el que los músicos improvisan consigo mismos mediante diferentes piezas de música en vivo, queríamos ver si podíamos crear un entorno en el que los usuarios pudieran improvisar versiones más flexibles de las presentaciones con libertad.

4. Habitaciones interconectadas

Habitaciones interconectadas

Al igual que ocurre con mucha música, las pistas de LCD Soundsystem se crean utilizando medidas con tiempos precisos. Su pista, Tonite, que se incluye en nuestro proyecto, incluye medidas que duran exactamente 8 segundos. Queríamos que los usuarios realizaran un rendimiento por cada bucle de 8 segundos en la pista. Aunque el ritmo de estas medidas no cambia, su contenido musical sí. A medida que avanza la canción, hay momentos con diferentes instrumentos y voces a los que los artistas pueden reaccionar de diferentes maneras. Cada una de estas mediciones se expresa como una sala, en la que las personas pueden realizar un rendimiento que se adapte a ellas.

Optimizaciones para el rendimiento: no descartar fotogramas

Crear una experiencia de RV multiplataforma que se ejecute en una sola base de código con un rendimiento óptimo para cada dispositivo o plataforma no es una tarea sencilla.

En el modo RV, una de las situaciones más náuseas que puedes experimentar se debe a que la velocidad de fotogramas no está a la altura de tus movimientos. Si giras la cabeza, pero las imágenes que ven no coinciden con el movimiento que siente tu oído interno, se produce una sacudida estomacal instantánea. Por este motivo, tuvimos que evitar cualquier retraso en la velocidad de fotogramas alta. Estas son algunas optimizaciones que implementamos.

1. Geometría del búfer con instancias

Dado que todo nuestro proyecto usa solo un puñado de objetos 3D, pudimos obtener un gran aumento en el rendimiento gracias a la geometría de búfer con instancias. En pocas palabras, te permite subir el objeto a la GPU una vez y dibujar tantas "instancias" de ese objeto como desees en una sola llamada de dibujo. En Dance Tonite, solo tenemos 3 objetos diferentes (un cono, un cilindro y una habitación con un agujero), pero posiblemente cientos de copias de ellos. La geometría del búfer de instancias es parte de ThreeJS, pero usamos la bifurcación experimental y en curso de Dusan Bosnjak que implementa THREE.InstanceMesh, lo que facilita mucho el trabajo con la geometría de búfer con instancias.

2. Cómo evitar el recolector de elementos no utilizados

Al igual que con muchos otros lenguajes de programación, JavaScript libera memoria automáticamente cuando descubre qué objetos asignados ya no se usan. Este proceso se denomina recolección de elementos no utilizados.

Los desarrolladores no tienen control sobre cuándo sucede esto. El recolector de elementos no utilizados podría aparecer en nuestras puertas en cualquier momento y comenzar a vaciarla, lo que provocaría una caída de marcos a medida que se toma su tiempo.

La solución es reciclar nuestros objetos para producir la menor cantidad de residuos posibles. En lugar de crear un nuevo objeto vectorial para cada cálculo, marcamos objetos reutilizables para reutilizarlos. Debido a que los conservamos moviéndola fuera de nuestro alcance, no se marcaron para su eliminación.

Por ejemplo, este es nuestro código para convertir la matriz de ubicación de la cabeza y las manos del usuario en un array de valores de posición/rotación que almacenamos en cada fotograma. Cuando reutilizas SERIALIZE_POSITION, SERIALIZE_ROTATION y SERIALIZE_SCALE, evitamos la asignación de memoria y la recolección de elementos no utilizados que se producirían si se crearan objetos nuevos cada vez que se llama a la función.

const SERIALIZE_POSITION = new THREE.Vector3();
const SERIALIZE_ROTATION = new THREE.Quaternion();
const SERIALIZE_SCALE = new THREE.Vector3();
export const serializeMatrix = (matrix) => {
    matrix.decompose(SERIALIZE_POSITION, SERIALIZE_ROTATION, SERIALIZE_SCALE);
    return SERIALIZE_POSITION.toArray()
    .concat(SERIALIZE_ROTATION.toArray())
    .map(compressNumber);
};

3. Serialización de movimiento y reproducción progresiva

Para capturar los movimientos de los usuarios en RV, tuvimos que serializar la posición y rotación de sus visores y controladores, y subir estos datos a nuestros servidores. Comenzamos capturando las matrices de transformación completas para cada marco. Esto funcionó bien, pero con 16 números multiplicados por 3 posiciones cada uno a 90 fotogramas por segundo, los archivos fueron muy grandes y, por lo tanto, se deben esperar mucho tiempo para subir y descargar los datos. Mediante la extracción solo de los datos posicionales y rotativos de las matrices de transformación pudimos reducir estos valores de 16 a 7.

Debido a que los visitantes de la Web a menudo hacen clic en un vínculo sin saber exactamente qué esperar, debemos mostrar el contenido visual rápidamente; de lo contrario, lo abandonarán en segundos.

Por este motivo, queríamos asegurarnos de que nuestro proyecto comenzara a publicarse lo antes posible. Inicialmente, usábamos JSON como formato para cargar nuestros datos de movimiento. El problema es que tenemos que cargar el archivo JSON completo antes de poder analizarlo. No muy progresivo.

Para que un proyecto como Dance Tonite se muestre con la velocidad de fotogramas más alta posible, el navegador solo tiene una pequeña cantidad de tiempo por cada fotograma para los cálculos de JavaScript. Si tardas demasiado, las animaciones empiezan a entrecortarse. Al principio, se produjeron inestabilidades porque el navegador decodificaba estos enormes archivos JSON.

Encontramos un formato de datos de transmisión conveniente basado en NDJSON o JSON delimitado por saltos de línea. El truco aquí es crear un archivo con una serie de cadenas JSON válidas, cada una en su propia línea. Esto te permite analizar el archivo mientras se carga, lo que nos permite mostrar los rendimientos antes de que se carguen por completo.

Así se ve una sección de una de nuestras grabaciones:

{"fps":15,"count":1,"loopIndex":"1","hideHead":false}
[-464,17111,-6568,-235,-315,-44,9992,-3509,7823,-7074, ... ]
[-583,17146,-6574,-215,-361,-38,9991,-3743,7821,-7092, ... ]
[-693,17158,-6580,-117,-341,64,9993,-3977,7874,-7171, ... ]
[-772,17134,-6591,-93,-273,205,9994,-4125,7889,-7319, ... ]
[-814,17135,-6620,-123,-248,408,9988,-4196,7882,-7376, ... ]
[-840,17125,-6644,-173,-227,530,9982,-4174,7815,-7356, ... ]
[-868,17120,-6670,-148,-183,564,9981,-4069,7732,-7366, ... ]
...

El uso de NDJSON nos permite conservar la representación de datos de los marcos individuales de las presentaciones como strings. Se podía esperar hasta alcanzar el tiempo necesario, antes de decodificarlos en datos posicionales, repartiendo así el procesamiento necesario con el tiempo.

4. Movimiento de interpolación

Como esperábamos mostrar entre 30 y 60 rendimientos al mismo tiempo, necesitábamos reducir la tasa de datos aún más de lo que ya teníamos. El equipo de Data Arts abordó el mismo problema en su proyecto Virtual Art Sessions, en el que reproduciron grabaciones de artistas que pintaban en RV con Tilt Brush. Lo solucionaron con versiones intermedias de los datos del usuario con velocidades de fotogramas más bajas e interpolando entre los fotogramas mientras los reproducían. Nos sorprendió descubrir que apenas podíamos detectar la diferencia entre una grabación interpolada que se ejecutaba a 15 FPS y la grabación original a 90 FPS.

Para comprobarlo, puedes forzar a Dance Tonite para que reproduzca los datos a varias velocidades mediante la cadena de consulta ?dataRate=. Puedes usar esto para comparar el movimiento grabado a 90 fotogramas por segundo, 45 fotogramas por segundo o 15 fotogramas por segundo.

Para la posición, hacemos una interpolación lineal entre el fotograma clave anterior y el siguiente, según lo cerca que estamos en el tiempo entre los fotogramas clave (proporción):

const { x: x1, y: y1, z: z1 } = getPosition(previous, performanceIndex, limbIndex);
const { x: x2, y: y2, z: z2 } = getPosition(next, performanceIndex, limbIndex);
interpolatedPosition = new THREE.Vector3();
interpolatedPosition.set(
    x1 + (x2 - x1) * ratio,
    y1 + (y2 - y1) * ratio,
    z1 + (z2 - z1) * ratio
    );

Para la orientación, hacemos una interpolación lineal esférica (slerp) entre fotogramas clave. La orientación se almacena como cuaterniones.

const quaternion = getQuaternion(previous, performanceIndex, limbIndex);
quaternion.slerp(
    getQuaternion(next, performanceIndex, limbIndex),
    ratio
    );

5. Sincronizando movimientos con música

Para saber en qué fotograma de las animaciones grabadas se debe reproducir, necesitamos conocer la hora actual de la música en milisegundos. Resulta que, aunque el elemento de audio HTML es perfecto para cargar y reproducir sonido de forma progresiva, la propiedad de tiempo que proporciona no cambia en sincronización con el bucle de fotogramas del navegador. Siempre está un poco fuera de lugar. A veces, una fracción de un ms es demasiado pronto y, otras, una fracción demasiado tarde.

Esto provoca saltos en nuestras hermosas grabaciones de baile, que queremos evitar a toda costa. Para solucionar el problema, implementamos nuestro propio temporizador en JavaScript. De esta manera, podemos estar seguros de que la cantidad de tiempo que cambia entre fotogramas es exactamente la cantidad de tiempo que pasó desde el último fotograma. Cuando el temporizador se desincroniza más de 10 ms con la música, lo volvemos a sincronizar.

6. Sacrificio y niebla

Toda historia necesitaba un buen final y quisimos hacer algo sorpresivo para los usuarios que llegara al final de nuestra experiencia. Cuando sales de la última habitación, ingresas a lo que parece un paisaje tranquilo de conos y cilindros. “¿Este es el fin?”, te preguntas. A medida que te adentras en el campo, de repente, los tonos de la música hacen que se formen diferentes grupos de conos y cilindros en bailarines. ¡Te encuentras en medio de una gran fiesta! Luego, cuando la música se detiene de repente, todo se cae al suelo.

Si bien esto resultó genial para los usuarios, introdujo algunos obstáculos de rendimiento que resolver. Los dispositivos de RV a escala Room y sus equipos de videojuegos de alta gama tuvieron un rendimiento perfecto con los 40 específicos actuaciones adicionales que se necesitan para nuestro nuevo final. Pero la velocidad de fotogramas en ciertos dispositivos móviles se redujo a la mitad.

Para contrarrestarlo, presentamos la niebla. Después de cierta distancia, todo se vuelve negro lentamente. Como no necesitamos calcular ni dibujar lo que no es visible, eliminamos los rendimientos en salas que no son visibles, lo que nos permite ahorrar trabajo tanto para la CPU como para la GPU. Pero ¿cómo decidir la distancia correcta?

Algunos dispositivos pueden controlar cualquier cosa que les arrojes y otros son más restringidos. Elegimos implementar una escala variable. Medir de manera continua la cantidad de fotogramas por segundo permite ajustar la distancia de la niebla según corresponda. Siempre que nuestra velocidad de fotogramas funcione sin problemas, intentamos realizar más tareas de renderización disponiendo de más energía. Si la velocidad de fotogramas no es lo suficientemente fluida, acercamos la niebla para omitir la renderización en la oscuridad.

// this is called every frame
// the FPS calculation is based on stats.js by @mrdoob
tick: (interval = 3000) => {
    frames++;
    const time = (performance || Date).now();
    if (prevTime == null) prevTime = time;
    if (time > prevTime + interval) {
    fps = Math.round((frames * 1000) / (time - prevTime));
    frames = 0;
    prevTime = time;
    const lastCullDistance = settings.cullDistance;

    // if the fps is lower than 52 reduce the cull distance
    if (fps <= 52) {
        settings.cullDistance = Math.max(
        settings.minCullDistance,
        settings.cullDistance - settings.roomDepth
        );
    }
    // if the FPS is higher than 56, increase the cull distance
    else if (fps > 56) {
        settings.cullDistance = Math.min(
        settings.maxCullDistance,
        settings.cullDistance + settings.roomDepth
        );
    }
    }

    // gradually increase the cull distance to the new setting
    cullDistance = cullDistance * 0.95 + settings.cullDistance * 0.05;

    // mask the edge of the cull distance with fog
    viewer.fog.near = cullDistance - settings.roomDepth;
    viewer.fog.far = cullDistance;
}

Algo para todo el mundo: creación de RV para la Web

Diseñar y desarrollar experiencias asimétricas multiplataforma implica tener en cuenta las necesidades de cada usuario en función del dispositivo. Con cada decisión de diseño, necesitábamos ver cómo podía afectar a otros usuarios. ¿Cómo puedes asegurarte de que lo que ves en RV sea tan emocionante como sin RV, y viceversa?

1. El orbe amarillo

Por lo tanto, nuestros usuarios de RV a escala de habitación realizarían las presentaciones, pero ¿cómo experimentarían el proyecto los usuarios de dispositivos móviles de RV (como Cardboard, Daydream View o Samsung Gear)? Para ello, introdujimos un nuevo elemento en nuestro entorno: el orbe amarillo.

El orbe amarillo
El orbe amarillo

Cuando miras el proyecto en RV, lo haces desde el punto de vista del orbe amarillo. Mientras flotas de una habitación a otra, los bailarines reaccionan ante tu presencia. Hacen gestos hacia ti, bailan a tu alrededor, hacen movimientos graciosos detrás de la espalda y se apartan rápidamente para que no te golpeen. El orbe amarillo es siempre el centro de atención.

Esto se debe a que, mientras se graba una presentación, el orbe amarillo se mueve por el centro de la habitación en sincronía con la música y se repite de manera indefinida. La posición del orbe le da al artista una idea de dónde se encuentra en el tiempo y cuánto tiempo le queda en el bucle. Proporciona un enfoque natural para que desarrollen un rendimiento en torno a ellos.

2. Otro punto de vista

No queríamos dejar afuera a los usuarios sin RV, especialmente porque probablemente serían nuestro público más grande. En lugar de crear una experiencia de RV falsa, quisimos darles su propia experiencia a los dispositivos basados en pantalla. Se nos ocurrió mostrar las presentaciones desde una perspectiva isométrica. Esta perspectiva tiene una rica historia en los juegos de computadora. Se utilizó por primera vez en Zaxxon, un juego de disparos espaciales de 1982. Mientras que los usuarios de RV están en el más pleno de la acción, la perspectiva isométrica ofrece una visión divina de la acción. Elegimos escalar un poco los modelos para darle un toque de estética a una casa de muñecas.

3. Sombras: fingir hasta conseguirlo

Descubrimos que algunos usuarios tenían dificultades para ver la profundidad en nuestro punto de vista isométrico. Estoy segura de que por esta razón Zaxxon también fue uno de los primeros juegos de computadora de la historia en proyectar una sombra dinámica bajo sus objetos voladores.

Sombras

Resulta que hacer sombras en 3D es difícil. Especialmente para dispositivos limitados, como teléfonos móviles. Al principio, tuvimos que tomar la difícil decisión de quitarlos de la ecuación. Sin embargo, después de pedirle consejos al autor de Three.js y de experiencia en el hacker de demostraciones Mr doob, se le ocurrió una idea nueva de... fingirlos.

En lugar de tener que calcular cómo cada uno de los objetos flotantes oscurece nuestras luces y, por lo tanto, arroja sombras de diferentes formas, dibujamos la misma imagen circular de textura desenfocada debajo de cada uno de ellos. Dado que nuestras imágenes no intentan imitar la realidad en primer lugar, descubrimos que podíamos salir con ella fácilmente con solo algunos retoques. Cuando los objetos se acercan al suelo, las texturas se vuelven más oscuras y pequeñas. Cuando se mueven hacia arriba, hacemos que las texturas sean más transparentes y grandes.

Para crearlas, usamos esta textura con un gradiente suave de blanco a negro (sin transparencia alfa). Establecemos el material como transparente y usamos una combinación de sustracción. Esto los ayuda a combinar de forma adecuada cuando se superponen:

function createShadow() {
    const texture = new THREE.TextureLoader().load(shadowTextureUrl);
    const material = new THREE.MeshLambertMaterial({
        map: texture,
        transparent: true,
        side: THREE.BackSide,
        depthWrite: false,
        blending: THREE.SubtractiveBlending,
    });
    const geometry = new THREE.PlaneBufferGeometry(0.5, 0.5, 1, 1);
    const plane = new THREE.Mesh(geometry, material);
    return plane;
    }

4. Estar presente

Cuando hacen clic en la cabeza de un artista, los visitantes sin RV pueden ver cosas desde el punto de vista del bailarín. Desde este ángulo, muchos detalles pequeños se hacen evidentes. Los bailarines, que intentan seguir el ritmo, se miran rápidamente. A medida que el orbe entra en la habitación, lo ves mirando nervioso en su dirección. Si bien como espectador no puedes influir en estos movimientos, transmiten la sensación de inmersión sorprendentemente bien. Una vez más, preferimos hacerlo en lugar de presentar a nuestros usuarios una versión de RV falsa controlada por mouse.

5. Uso compartido de grabaciones

Sabemos lo orgulloso que puedes sentir cuando haces una grabación con una coreografía intrincada de 20 capas de artistas reaccionando unos a otros. Sabíamos que nuestros usuarios probablemente querrían mostrárselo a sus amigos. Pero una imagen estática de este logro no comunica lo suficiente. En cambio, quisimos permitirles a los usuarios compartir videos de sus presentaciones. En realidad, ¿por qué no un GIF? Nuestras animaciones tienen sombras planas, ideales para las paletas de colores limitadas del formato.

Uso compartido de grabaciones

Recurrimos a GIF.js, una biblioteca de JavaScript que permite codificar GIF animados desde el navegador. Transfiere la codificación de los marcos a los trabajadores web, que pueden ejecutarse en segundo plano como procesos independientes, lo que puede aprovechar el funcionamiento en paralelo de varios procesadores.

Lamentablemente, con la cantidad de fotogramas que necesitábamos para las animaciones, el proceso de codificación seguía demasiado lento. El GIF puede crear archivos pequeños con una paleta de colores limitada. Descubrimos que la mayor parte del tiempo se dedicaba a encontrar el color más cercano para cada píxel. Pudimos optimizar este proceso diez veces mediante el hackeo de un pequeño atajo: si el color del píxel es el mismo que el del último, usa el mismo color de la paleta que antes.

Ahora teníamos codificaciones rápidas, pero los archivos GIF resultantes eran demasiado grandes. El formato GIF te permite indicar cómo se mostrará cada fotograma encima del último mediante la definición del método de eliminación. Para obtener archivos más pequeños, en lugar de actualizar cada píxel en cada fotograma, solo actualizamos los píxeles que cambiaron. Si bien se volvió a ralentizar el proceso de codificación, se redujeron los tamaños de los archivos de manera correcta.

6. Bases sólidas: Google Cloud y Firebase

El backend de un sitio de “contenido generado por usuarios” a menudo puede ser complicado y frágil, pero se creó un sistema simple y sólido gracias a Google Cloud y Firebase. Cuando un artista sube un baile nuevo al sistema, Firebase Authentication lo autentica de forma anónima. Se les otorga permiso para subir su grabación a un espacio temporal mediante Cloud Storage para Firebase. Cuando se completa la carga, la máquina cliente llama a un activador HTTP de Cloud Functions para Firebase con su token de Firebase. Esto activa un proceso del servidor que valida el envío, crea un registro de la base de datos y mueve el registro a un directorio público en Google Cloud Storage.

Terreno sólido

Todo nuestro contenido público se almacena en una serie de archivos planos en un bucket de Cloud Storage. Esto significa que se puede acceder rápidamente a nuestros datos en todo el mundo y no tenemos que preocuparnos por que las cargas de tráfico altas afecten la disponibilidad de los datos de ninguna manera.

Usamos los extremos de Firebase Realtime Database y Cloud Functions para compilar una herramienta simple de moderación y selección que nos permite ver cada envío nuevo en RV y publicar nuevas playlists desde cualquier dispositivo.

7. Trabajadores de servicios

Los service workers son una innovación bastante reciente que ayuda a administrar el almacenamiento en caché de los recursos de sitios web. En nuestro caso, los service workers cargan nuestro contenido con gran rapidez para los visitantes recurrentes y permiten que el sitio funcione sin conexión. Estas son funciones importantes, ya que muchos de nuestros visitantes usarán conexiones móviles de diferente calidad.

Agregar un service worker al proyecto fue fácil gracias a un complemento práctico para webpack que se encarga de la mayor parte del trabajo pesado. En la siguiente configuración, generamos un service worker que almacenará en caché todos los archivos estáticos de forma automática. Extraerá el archivo de la lista de reproducción más reciente de la red, si está disponible, ya que la lista de reproducción se actualizará todo el tiempo. Todos los archivos JSON de grabación deben extraerse de la caché si están disponibles, ya que nunca cambiarán.

const SWPrecacheWebpackPlugin = require('sw-precache-webpack-plugin');
config.plugins.push(
    new SWPrecacheWebpackPlugin({
    dontCacheBustUrlsMatching: /\.\w{8}\./,
    filename: 'service-worker.js',
    minify: true,
    navigateFallback: 'index.html',
    staticFileGlobsIgnorePatterns: [/\.map$/, /asset-manifest\.json$/],
    runtimeCaching: [{
        urlPattern: /playlist\.json$/,
        handler: 'networkFirst',
    }, {
        urlPattern: /\/recordings\//,
        handler: 'cacheFirst',
        options: {
        cache: {
            maxEntries: 120,
            name: 'recordings',
        },
        },
    }],
    })
);

Actualmente, el complemento no controla elementos multimedia cargados de forma progresiva, como nuestros archivos de música, por lo que lo solucionamos configurando el encabezado Cache-Control de Cloud Storage en estos archivos como public, max-age=31536000 para que el navegador almacene el archivo en caché durante un máximo de un año.

Conclusión

Estamos ansiosos por ver cómo los artistas aportarán a esta experiencia y la usarán como herramienta de expresión creativa a través del movimiento. Lanzamos todo el código abierto, que puedes encontrar en https://github.com/puckey/dance-tonite. En estos comienzos de la RV y, en especial, de WebVR, esperamos con ansias ver las nuevas direcciones creativas e inesperadas que tomará este nuevo medio. Bailar activado.