Dance Tonite en WebVR

Me emocionó cuando el equipo de Google Data Arts se acercó a Moniker y a mí para trabajar juntos y explorar las posibilidades que ofrece WebVR. He contemplado el trabajo de su equipo a lo largo de los años y sus proyectos siempre me han llamado la atención. Nuestra colaboración dio como resultado Dance Tonite, una experiencia de baile en RV en constante cambio con LCD Soundsystem y sus fans. Así es como lo hicimos.

El concepto

Comenzamos con el desarrollo de una serie de prototipos con WebVR, un estándar abierto que permite ingresar a la realidad virtual visitando un sitio web con tu navegador. El objetivo es que todos puedan acceder a las experiencias de RV más fácilmente, sin importar el dispositivo que tengas.

Nos tomamos esto muy en serio. Lo que creamos 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 los sistemas de escala de habitación, como HTC VIVE y Oculus Rift, que reflejan tus movimientos físicos en tu entorno virtual. Quizás lo más importante es que sentimos que sería acorde con el espíritu de la Web crear algo que también funcione para todos los que no tienen un dispositivo de realidad virtual.

1. Captura de movimiento de bricolaje

Como queríamos involucrar a los usuarios de forma creativa, comenzamos a analizar las posibilidades de participación y expresión a través de la RV. Nos impresionó la precisión con la que podías moverte y mirar a tu alrededor en la RV, y la fidelidad que había. Eso nos dio una idea. En lugar de que los usuarios miren o creen algo, ¿qué tal si graban sus movimientos?

Alguien que se graba en Dance Tonite. La pantalla detrás de él muestra lo que ve en sus auriculares.

Preparamos un prototipo en el que grabamos las posiciones de nuestros controles y gafas de RV mientras bailamos. Reemplazamos las posiciones registradas con formas abstractas y nos maravillamos con los resultados. Los resultados fueron muy humanos y tenían mucha personalidad. Rápidamente, nos dimos cuenta de que podíamos usar WebVR para realizar capturas de movimiento baratas en casa.

Con WebVR, el desarrollador tiene acceso a la posición de la cabeza y la orientación del usuario a través del objeto VRPose. El hardware de VR actualiza este valor en 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 o orientación de los controles 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 disfraces

Con los equipos de VR de sala actual, podemos hacer un seguimiento de tres puntos del cuerpo del usuario: la cabeza y las dos manos. En Dance Tonite, queríamos mantener el enfoque en la humanidad en el movimiento de estos 3 puntos en el espacio. Para lograrlo, llevamos la estética al límite de lo minimalista para enfocarnos en el movimiento. Nos gustó la idea de poner a trabajar el cerebro de las personas.

Este video que muestra el trabajo del psicólogo sueco Gunnar Johansson fue uno de los ejemplos a los que nos referimos cuando consideramos reducir todo lo posible. Muestra cómo los puntos blancos flotantes son reconocibles al instante como cuerpos cuando se ven en movimiento.

Visualmente, nos inspiró las habitaciones de colores y los trajes geométricos en esta grabación de la reedición del ballet triádico de Oskar Schlemmer, realizada en 1970 por Margarete Hastings.

Mientras que el motivo de Schlemmer para elegir trajes geométricos abstractos era limitar los movimientos de sus bailarines a los de marionetas, nuestro objetivo para Dance Tonite era el opuesto.

Al final, basamos nuestra elección de formas en la cantidad de información que transmitían con la rotación. Una esfera se ve igual sin importar cómo se rote, pero un cono realmente apunta en la dirección en la que se mira y se ve diferente de la parte frontal que de la posterior.

3. Pedal de bucle para movimiento

Queríamos mostrar grandes grupos de personas grabadas bailando y moviéndose entre sí. No sería factible hacerlo en vivo, ya que los dispositivos de realidad virtual no están disponibles en cantidades suficientes. Sin embargo, aún queríamos tener grupos de personas que reaccionaran entre sí a través del movimiento. Nuestra mente acudió a la presentación recurrente de Norman McClaren en su obra de video “Canon”.

La presentación de McClaren incluye una serie de movimientos altamente coreografiados que comienzan a interactuar entre sí después de cada bucle. Al igual que un pedal de bucle en la música, en el que los músicos improvisan con diferentes capas de música en vivo, nos interesaba 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 mucha música, las pistas de LCD Soundsystem se crean con mediciones sincronizadas con precisión. Su pista, Tonite, que se incluye en nuestro proyecto, tiene medidas de exactamente 8 segundos. Queríamos que los usuarios realizaran una representación para cada bucle de 8 segundos en la pista. Aunque el ritmo de estas medidas no cambia, sí lo hace su contenido musical. A medida que avanza la canción, hay momentos con diferentes instrumentos y voces a los que los intérpretes pueden reaccionar de diferentes maneras. Cada una de estas medidas se expresa como una sala, en la que las personas pueden realizar una presentación que se adapte a ella.

Optimizaciones para el rendimiento: no pierdas 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.

Cuando usas la realidad virtual, una de las sensaciones más desagradables que puedes experimentar se produce porque la frecuencia de fotogramas no sigue el ritmo de tu movimiento. Si giras la cabeza, pero las imágenes que ven tus ojos no coinciden con el movimiento que siente el oído interno, se produce un malestar estomacal instantáneo. Por este motivo, necesitábamos evitar un gran retraso en la velocidad de fotogramas. Estas son algunas de las optimizaciones que implementamos.

1. Geometría del búfer instanciado

Dado que todo nuestro proyecto usa solo unos pocos objetos en 3D, pudimos obtener un gran aumento de rendimiento con la geometría de búfer instanciado. Básicamente, te permite subir tu 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 potencialmente cientos de copias de esos objetos. 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 del búfer de instancias.

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

Al igual que con muchos otros lenguajes de secuencias de comandos, JavaScript libera memoria automáticamente, ya que averigua qué objetos asignados ya no se usan. Este proceso se denomina recolección de basura.

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 toman su tiempo dulce.

La solución para esto es producir la menor cantidad posible de basura reciclando nuestros objetos. En lugar de crear un objeto vectorial nuevo para cada cálculo, marcamos los objetos de scratch para su reutilización. Como los conservamos cuando trasladamos nuestra referencia a ellos fuera de nuestro alcance, no fueron marcados para su eliminación.

Por ejemplo, este es el código para convertir la matriz de ubicación de la cabeza y las manos del usuario en el array de valores de posición/rotación que almacenamos cada fotograma. Cuando reutilizamos SERIALIZE_POSITION, SERIALIZE_ROTATION y SERIALIZE_SCALE, evitamos la asignación de memoria y la recolección de basura que se produciría si creáramos 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 del movimiento y reproducción progresiva

Para capturar los movimientos de los usuarios en la realidad virtual, necesitábamos serializar la posición y la rotación de sus auriculares y controladores, y subir estos datos a nuestros servidores. Comenzamos capturando las matrices de transformación completas para cada fotograma. Esto funcionó bien, pero con 16 números multiplicados por 3 posiciones cada uno a 90 fotogramas por segundo, dio lugar a archivos muy grandes y, por lo tanto, esperaron mucho tiempo mientras se subían y descargaban los datos. Extrayendo solo los datos de posición y rotación de las matrices de transformación, pudimos reducir estos valores de 16 a 7.

Dado que los visitantes de la Web suelen hacer clic en un vínculo sin saber exactamente qué esperar, debemos mostrar contenido visual rápidamente, de lo contrario, se irán en segundos.

Por este motivo, queríamos asegurarnos de que nuestro proyecto pudiera comenzar a reproducirse lo antes posible. Al principio, 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 es muy progresista.

Para que un proyecto como Dance Tonite se muestre a la velocidad de fotogramas más alta posible, el navegador solo tiene una pequeña cantidad de tiempo por fotograma para los cálculos de JavaScript. Si tardas demasiado, las animaciones comenzarán a tartamudear. Al principio, experimentamos interrupciones cuando el navegador decodificaba estos archivos JSON enormes.

Encontramos un formato de datos de transmisión conveniente llamado 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 las ejecuciones antes de que se carguen por completo.

Esta es la apariencia de 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 los rendimientos como strings. Podríamos esperar hasta alcanzar el tiempo necesario, antes de decodificarlos en datos posicionales, repartiendo así el procesamiento necesario con el tiempo.

4. Interpolación del movimiento

Como queríamos mostrar entre 30 y 60 ejecuciones en simultáneo, necesitábamos reducir aún más nuestra tasa de datos. El equipo de Data Arts abordó el mismo problema en su proyecto Virtual Art Sessions, en el que reproducen grabaciones de artistas que pintan en RV con Tilt Brush. Para resolverlo, crearon versiones intermedias de los datos del usuario con velocidades de fotogramas más bajas y, además, interpolaron entre los fotogramas mientras los reproducían. Nos sorprendió descubrir que apenas podíamos detectar la diferencia entre una grabación interpolada que se ejecuta a 15 FPS y la grabación original de 90 FPS.

Para comprobarlo, puedes forzar a Dance Tonite a reproducir los datos a varias tasas con la cadena de consulta ?dataRate=. Puedes usar esta función para comparar el movimiento registrado 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 la proximidad 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 la música

Para saber qué fotograma de las animaciones grabadas se debe reproducir, necesitamos conocer el tiempo actual de la música hasta el milisegundo. 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 desfasado. A veces, una fracción de ms demasiado pronto, a veces, una fracción demasiado tarde.

Esto genera interrupciones en nuestras hermosas grabaciones de baile, lo que queremos evitar a toda costa. Para solucionar este 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. Cada vez que el temporizador se desincroniza más de 10 ms con la música, lo sincronizamos de nuevo.

6. Eliminación y niebla

Cada historia necesita un buen final y queríamos hacer algo sorprendente para los usuarios que llegaron al final de nuestra experiencia. Cuando sales de la última habitación, entras en lo que parece un paisaje tranquilo de conos y cilindros. se preguntarán: “¿Es este el final?”. A medida que te adentras en el campo, de repente, los tonos de la música hacen que diferentes grupos de conos y cilindros se conviertan en bailarines. Te encuentras en medio de una gran fiesta. Luego, cuando la música se detiene abruptamente, todo cae al suelo.

Si bien esto fue muy agradable para los usuarios, presentó algunos obstáculos de rendimiento que se debían resolver. Los dispositivos de RV de escala de habitación y sus plataformas de juegos de alta gama funcionaron perfectamente con las 40 ejecuciones adicionales necesarias para nuestro nuevo final. Sin embargo, las tasas de fotogramas en ciertos dispositivos móviles se redujeron a la mitad.

Para contrarrestar esto, presentamos la niebla. Después de una cierta distancia, todo se vuelve negro lentamente. Como no necesitamos calcular ni dibujar lo que no es visible, eliminamos las cargas de trabajo en las habitaciones que no son visibles, lo que nos permite ahorrar trabajo para la CPU y la GPU. Pero ¿cómo decidir cuál es la distancia correcta?

Algunos dispositivos pueden soportar todo lo que se les arroja y otros son más limitados. Elegimos implementar una escala variable. Si midemos continuamente la cantidad de fotogramas por segundo, podemos ajustar la distancia de la niebla según corresponda. Siempre y cuando la velocidad de fotogramas funcione sin problemas, intentamos realizar más trabajo de renderización expulsando la niebla. Si la velocidad de fotogramas no funciona lo suficientemente fluida, acercamos la niebla para omitir los rendimientos de 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 todos: crea experiencias de RV para la Web

Diseñar y desarrollar experiencias asimétricas multiplataforma significa tener en cuenta las necesidades de cada usuario según su dispositivo. Y con cada decisión de diseño, debíamos ver cómo eso podría afectar a otros usuarios. ¿Cómo te aseguras de que lo que ves en la RV sea igual de emocionante que sin ella y viceversa?

1. El orbe amarillo

Por lo tanto, nuestros usuarios de RV de sala completa serían los que realizarían las presentaciones, pero ¿cómo experimentarían el proyecto los usuarios de dispositivos de RV para dispositivos móviles (como Cardboard, Daydream View o Samsung Gear)? Para ello, agregamos un elemento nuevo a nuestro ambiente: el orbe amarillo.

El orbe amarillo
El orbe amarillo

Cuando miras el proyecto en RV, lo haces desde el punto de vista del ovillo amarillo. A medida que flotas de una habitación a otra, los bailarines reaccionan a tu presencia. Te hacen gestos, bailan a tu alrededor, hacen movimientos graciosos a tus espaldas y se quitan rápidamente del camino para no chocar contigo. La esfera amarilla es siempre el centro de atención.

Esto se debe a que, mientras se graba una presentación, la esfera amarilla se mueve por el centro de la habitación en sincronización con la música y se repite. La posición del orbe le da al intérprete una idea de dónde se encuentra en el tiempo y cuánto tiempo le queda en el bucle. Les proporciona un enfoque natural para desarrollar un rendimiento en torno a él.

2. Otro punto de vista

No queríamos dejar de lado a los usuarios que no tienen VR, en especial porque es probable que sean nuestro público más grande. En lugar de crear una experiencia de RV falsa, queríamos darles a los dispositivos con pantalla su propia experiencia. Se nos ocurrió mostrar las ejecuciones desde arriba desde una perspectiva isométrica. Esta perspectiva tiene una rica historia en los juegos de computadora. Se usó por primera vez en Zaxxon, un juego de disparos espaciales de 1982. Mientras que los usuarios de la realidad virtual están en el centro de la acción, la perspectiva isométrica proporciona una vista panorámica de la acción. Decidimos aumentar ligeramente el tamaño de los modelos, lo que les da un toque de estética de casa de muñecas.

3. Sombras: fingir hasta lograrlo

Descubrimos que a algunos usuarios les costaba ver profundidad en nuestro punto de vista isométrico. Estoy bastante seguro de que es por este motivo que Zaxxon también fue uno de los primeros juegos de computadora en la historia en proyectar una sombra dinámica debajo de sus objetos voladores.

Sombras

Resulta que hacer sombras en 3D es difícil. Especialmente para dispositivos con limitaciones, como teléfonos celulares. Al principio, tuvimos que tomar la difícil decisión de eliminarlos de la ecuación, pero después de pedirle consejo al autor de Three.js y al hacker de demostraciones experimentado Mr doob, se le ocurrió la idea novedosa de… falsificarlos.

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

Para crearlos, usamos esta textura con un gradiente suave de blanco a negro (sin transparencia alfa). Configuramos el material como transparente y usamos una combinación sustractiva. Esto ayuda a que se combinen bien 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 allí

Si hacen clic en las cabezas de los artistas, los visitantes que no tienen visores de RV pueden mirar desde el punto de vista del bailarín. Desde este ángulo, se hacen evidentes muchos detalles. Para mantener el ritmo de sus actuaciones, los bailarines se miran rápidamente. Cuando la esfera entra en la habitación, lo ves mirar con nerviosismo hacia su dirección. Si bien como espectador no puedes influir en estos movimientos, transmiten la sensación de inmersión de manera sorprendente. Nuevamente, preferimos hacer esto en lugar de presentar a nuestros usuarios una versión de RV falsa controlada con el mouse.

5. Cómo compartir grabaciones

Sabemos lo orgulloso que puedes sentirte cuando logras una grabación con una coreografía intrincada de 20 capas de artistas que reaccionan entre sí. Sabíamos que a nuestros usuarios probablemente les gustaría mostrárselo a sus amigos. Sin embargo, una imagen fija de esta hazaña no comunica lo suficiente. En cambio, queríamos permitir que nuestros usuarios compartieran videos de sus actuaciones. En realidad, ¿por qué no un GIF? Nuestras animaciones tienen sombras planas, perfectas para las paletas de colores limitadas del formato.

Cómo compartir grabaciones

Usamos GIF.js, una biblioteca de JavaScript que te permite codificar GIFs animados desde el navegador. Descarga la codificación de fotogramas a los trabajadores web que pueden ejecutarse en segundo plano como procesos independientes, lo que permite aprovechar varios procesadores que funcionan en paralelo.

Lamentablemente, con la cantidad de fotogramas que necesitábamos para las animaciones, el proceso de codificación seguía siendo 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 en 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 debe mostrar cada fotograma sobre el último definiendo su 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 esto volvió a ralentizar el proceso de codificación, redujo bastante el tamaño de los archivos.

6. Base sólida: Google Cloud y Firebase

El backend de un sitio de “contenido generado por usuarios” suele ser complicado y frágil, pero creamos un sistema que es simple y sólido gracias a Google Cloud y Firebase. Cuando un intérprete sube un baile nuevo al sistema, Firebase Authentication lo autentica de forma anónima. Se le otorga permiso para subir su grabación a un espacio temporal con 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 base de datos y mueve la grabación a un directorio público en Google Cloud Storage.

Suelo firme

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 que no tenemos que preocuparnos por que las cargas de tráfico alto afecten la disponibilidad de los datos de ninguna manera.

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

7. Service workers

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 a la velocidad del rayo para los visitantes recurrentes y hasta permiten que el sitio funcione sin conexión. Estas son funciones importantes, ya que muchos de nuestros visitantes tendrán conexiones móviles de diferente calidad.

Agregar un servicio de trabajo al proyecto fue fácil gracias a un práctico complemento de Webpack que se encarga de la mayor parte del trabajo pesado. En la siguiente configuración, generamos un service worker que almacenará en caché automáticamente todos nuestros archivos estáticos. Extraerá el archivo de playlist más reciente de la red, si está disponible, ya que la playlist se actualizará todo el tiempo. Todos los archivos JSON de grabación deberían 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 los recursos multimedia cargados de forma progresiva, como nuestros archivos de música, por lo que solucionamos el problema configurando el encabezado Cache-Control de Cloud Storage en estos archivos como public, max-age=31536000 para que el navegador almacene en caché el archivo durante un máximo de un año.

Conclusión

Nos entusiasma ver cómo los artistas se unirán a esta experiencia y la usarán como una herramienta de expresión creativa con movimiento. Lanzamos todo el código fuente abierto, que puedes encontrar en https://github.com/puckey/dance-tonite. En estos primeros días de la realidad virtual y, en especial, de la WebVR, esperamos ver qué nuevas direcciones creativas y inesperadas tomará este nuevo medio. Baila.