A principios de este año, creé un proyecto muy divertido que llamé "Brain a la nube", donde guardé los datos de mi cerebro en la nube mientras jugaba Call of Duty para poder analizar la relación entre mi Función cognitiva y rendimiento en videojuegos. Escribí una de tres partes serie de publicaciones de blog y creé algunos videos divertidos para resumir mis hallazgos sobre ese proyecto Si desea comprobarlos, puede consultar el enlaces en la parte inferior de esta publicación. Unos meses después de que publiqué eso proyecto, comencé a trabajar en Twitch como desarrollador defensor principal para Amazon Interactive Video Service (Amazon IVS): una solución completamente administrada para crear soluciones de transmisión de video interactivas en vivo (consulte para obtener más información). El siguiente paso de mi proyecto "Brain to the Cloud" era obvio: necesitaba mi cerebro. esta serie transmitir en vivo Transmitiendo mi cerebro Antes de mirar el código, veamos el producto final. Hay 2 vistas para la aplicación: una vista de transmisión y una vista de reproducción. En la vista de transmisión, podemos obtener una vista previa del video en vivo, iniciar el transmitir y conectar la diadema Muse para transmitir los datos del cerebro obtenido de la diadema. En la vista de reproducción, mostramos el vivo corriente con un elemento, y trazar los datos del cerebro en tiempo real <video> Descripción del proyecto Hay 5 pasos para este proyecto: Transmitir transmisión en vivo Captura datos cerebrales Publique datos cerebrales como metadatos cronometrados dentro de la transmisión en vivo Reproducción de transmisión en vivo Escuche metadatos cronometrados y represente datos cerebrales en un gráfico en tiempo real Si prefiere representaciones gráficas de tales cosas, así es como se ve: Construyendo el Proyecto Usé React para este proyecto. ¿Por qué? Bueno, tengo mucha experiencia. con Vue y Angular, pero probablemente sea uno de los últimos desarrolladores en tierra para probar React. Supuse que ya era hora de descubrir qué era todo el bombo estaba a punto, y yo sabía que esto no sería difícil proyecto a construir con él. Debido a mi falta de experiencia previa, no estoy lo que llamarías un usuario "avanzado" del marco, pero tengo que decir que estoy bastante contento con lo que veo hasta ahora. encontré el proceso agradable y no me encontré "luchando" con el marco. Pero esta publicación de blog no se trata de mi opinión sobre los marcos de JavaScript, así que guárdalo para una publicación futura. En su lugar, hablemos de cómo transmito ¡mi cerebro! El hardware En mi proyecto original "Brain to the Cloud", usé un EEG "antiguo" auricular llamado MindFlex para capturar las lecturas de mi cerebro. funcionó bastante bueno, pero me requirió "hackear" el dispositivo agregando un ESP-12 microcontrolador para extraer las lecturas del dispositivo y enviarlas ellos a la nube. Esta vez busqué algo un poco más nuevo: y algo que podría usar sin modificaciones. Después de un poco de investigación, me decidí por la Afortunadamente, hay una biblioteca de código abierto realmente increíble llamada que me permite acceder a las lecturas cerebrales directamente en un navegador web con Web Bluetooth (en navegadores compatibles, por supuesto). diadema Muse S. muse-js La transmisión en vivo Hasta hace poco, la transmisión en vivo con IVS requería que usáramos un cliente de terceros para transmitir nuestras transmisiones como RTMPS. Pero recientemente lanzó un cambio de juego: el . Como su nombre lo indica, este SDK nos brinda la capacidad de transmitir nuestro transmisión en vivo a través de WebRTC directamente desde un navegador web. Claramente, esto fue un ajuste perfecto para la transmisión en vivo de mi cerebro, ya que significa que puedo crear una solución "todo en uno" para transmitir los datos de mi cerebro junto con mi transmisión en vivo sin depender de software de terceros o scripts externos. Amazon SDK de transmisión web de Amazon IVS Agregar transmisión web a la aplicación React No vamos a ver cada uno de los pasos necesarios para utilizar el Web Broadcast SDK en esta publicación. En su lugar, veremos los aspectos más destacados para obtener una idea general de cómo funciona. No te preocupes, tengo otra publicación. próximamente donde profundizaremos en el proceso "paso a paso" para usar el SDK de Web Broadcast, así que estad atentos. Dicho esto, tomemos un recorrido rápido para ver cómo utilicé el SDK en este proyecto. mi primer paso fue usar una transmisión web para instalar el módulo. Con su herramienta de administración de paquetes favorita, ejecute: amazon-ivs-web-broadcast $ npm install amazon-ivs-web-broadcast A continuación, debemos importarlo a nuestro componente. En mi componente Broadcast.jsx, agregué: import IVSBroadcastClient, { STANDARD_LANDSCAPE } from 'amazon-ivs-web-broadcast' ; Podemos crear una instancia de IVSBroadcastClient con la configuración de flujo deseada e ingerir el punto final de nuestro canal Amazon IVS y establecerlo en el estado de nuestro componente. }) }); this .setState({ broadcastClient : IVSBroadcastClient.create({ streamConfig : STANDARD_LANDSCAPE, ingestEndpoint : this .state.ingestEndpoint, Ahora que tenemos una instancia del cliente, podemos agregar nuestra cámara al cliente. Para esto usamos . navigator.mediaDevices.getUserMedia() }, }, }, }); const streamConfig = STANDARD_LANDSCAPE; const videoStream = await navigator.mediaDevices.getUserMedia({ video : { deviceId : { exact : this .state.selectedVideoDeviceId }, width : { ideal : streamConfig.maxResolution.width, max : streamConfig.maxResolution.width, height : { ideal : streamConfig.maxResolution.height, max : streamConfig.maxResolution.height, this .state.broadcastClient.addVideoInputDevice(videoStream, 'camera1' , { index : 0 }); Agregar el micrófono del usuario al cliente sigue un patrón similar. }, }); const audioStream = await navigator.mediaDevices.getUserMedia({ audio : { deviceId : this .state.selectedAudioDeviceId this .state.broadcastClient.addAudioInputDevice(audioStream, 'mic1' ); debido al modelo de seguridad del navegador, necesitamos obtener permisos para acceder a la cámara y al micrófono del usuario. Consulte la para obtener más información sobre esto y para ver cómo capturé una lista de dispositivos y los presentó en un cuadro de diálogo para permitir al usuario elegir el dispositivo de transmisión si hay varias opciones disponibles. Nota: fuente del proyecto en GitHub Ahora podemos agregar una vista previa en vivo a la página para que podamos ver lo que nuestros espectadores finalmente verán en el lado del jugador. <canvas ref={ this .previewRef} id= 'broadcast-preview' ></canvas> Y adjunte la vista previa a la : broadcastClient this .state.broadcastClient.attachPreview( this .previewRef.current); Para iniciar la transmisión, agregue un botón a la página, y en el controlador para la llamada de botón sobre el (pasando lo necesario ). onClick startBroadcast() broadcastClient streamKey this .state.broadcastClient.startBroadcast( this .state.streamKey); Obtención de datos de mi cerebro Como mencioné anteriormente, usé el biblioteca, que brinda la capacidad de conectarse a la banda para la cabeza y extraer los datos sin procesar. Sin embargo, no calcula las potencias de banda absolutas para los datos de EEG. Para esto, necesitaba buscar : . muse-js muse-js otra biblioteca eeg-pipes El primer paso es agregar e importar las bibliotecas. $ npm install @neurosity/pipes $ npm install muse-js import { zipSamples, MuseClient } from 'muse-js' ; import { powerByBand, epoch, fft } from '@neurosity/pipes' ; A continuación, agregué un botón con un controlador de clic. En el controlador, yo conéctese a los auriculares, comience a escuchar datos y suscríbase al corriente. zipSamples(client.eegReadings) .pipe( powerByBand(), ) .subscribe( } ); const client = new MuseClient(); await client.connect(); await client.start(); epoch({ duration : 1024 , interval : 250 , samplingRate : 256 }), fft({ bins : 256 }), ( data ) => { const ch0 = [data.delta[ 0 ], data.theta[ 0 ], data.alpha[ 0 ], data.beta[ 0 ], data.gamma[ 0 ]]; const ch1 = [data.delta[ 1 ], data.theta[ 1 ], data.alpha[ 1 ], data.beta[ 1 ], data.gamma[ 1 ]]; const ch2 = [data.delta[ 2 ], data.theta[ 2 ], data.alpha[ 2 ], data.beta[ 2 ], data.gamma[ 2 ]]; const ch3 = [data.delta[ 3 ], data.theta[ 3 ], data.alpha[ 3 ], data.beta[ 3 ], data.gamma[ 3 ]]; const meta = [ch0, ch1, ch2, ch3]; //publish metadata Publicación de mis datos cerebrales como metadatos cronometrados Ahora que tengo un controlador que recopila datos de mi cerebro de Muse banda para la cabeza, es hora de publicar esos datos como metadatos cronometrados en vivo corriente. Lo maravilloso de es que está directamente incrustado en la transmisión de video y sigue siendo una parte permanente de esa transmisión. Eso significa que existe incluso en versiones grabadas, lo que significa que incluso en la reproducción bajo demanda podemos escuchar y responder a los eventos. timed metadata El SDK de Web Broadcast no admite la publicación de metadatos cronometrados desde el lado del cliente, por lo que tendremos que usar ( ) a través de . Para esto, creé una función AWS Lambda. putMetadata docs AWS SDK para JavaScript }); }, }; } } } } }; } response.body = err.stack; } }; const AWS = require ( 'aws-sdk' ); const ivs = new AWS.IVS({ apiVersion : '2020-07-14' , region : 'us-east-1' exports .send = async (event, context, callback) => { // response object const response = { 'statusCode' : 200 , 'headers' : { 'Access-Control-Allow-Origin' : '*' , 'Access-Control-Allow-Methods' : 'OPTIONS,GET,PUT,POST,DELETE' , 'Content-Type' : 'application/json' 'body' : '' , 'isBase64Encoded' : false // parse payload let payload; try { payload = JSON .parse(event.body); catch (err) { response.statusCode = 500 ; response.body = JSON .stringify(err); callback( null , response); return ; // validate payload if (!payload || !payload.channelArn || !payload.metadata) { response.statusCode = 400 ; response.body = 'Must provide, channelArn and metadata' ; callback( null , response); return ; // check payload size let byteLength = Buffer.byteLength(payload.metadata, 'utf8' ); if (byteLength > 1024 ) { response.statusCode = 400 ; response.body = 'Too big. Must be less than or equal to 1K' ; callback( null , response); return ; // putmetadata input let params = { channelArn : payload.channelArn, metadata : payload.metadata try { await ivs.putMetadata(params).promise(); response.statusCode = 200 ; response.body = JSON .stringify({ 'published' : true }, '' , 2 ); callback( null , response); catch (err) { response.statusCode = 500 ; callback( null , response); return ; Para publicar mis datos cerebrales como metadatos cronometrados, creé un Amazon API Gateway para invocar la función y modificar el método anterior para llamar a la función AWS Lambda. subscribe() .pipe( powerByBand(), ) .subscribe( fetch(LAMBDA_URL, { }, }) }); } } ); zipSamples(client.eegReadings) epoch({ duration : 1024 , interval : 250 , samplingRate : 256 }), fft({ bins : 256 }), ( data ) => { const ch0 = [data.delta[ 0 ], data.theta[ 0 ], data.alpha[ 0 ], data.beta[ 0 ], data.gamma[ 0 ]]; const ch1 = [data.delta[ 1 ], data.theta[ 1 ], data.alpha[ 1 ], data.beta[ 1 ], data.gamma[ 1 ]]; const ch2 = [data.delta[ 2 ], data.theta[ 2 ], data.alpha[ 2 ], data.beta[ 2 ], data.gamma[ 2 ]]; const ch3 = [data.delta[ 3 ], data.theta[ 3 ], data.alpha[ 3 ], data.beta[ 3 ], data.gamma[ 3 ]]; const meta = [ch0, ch1, ch2, ch3]; // put metadata if broadcasting if ( this .state.isBroadcasting) { 'method' : 'POST' , 'mode' : 'no-cors' , 'headers' : { 'Content-Type' : 'application/json' , 'body' : JSON .stringify({ channelArn : this .state.channelArn, metadata : JSON .stringify(meta) Construyendo la reproducción de transmisión en vivo y registrando los datos de mi cerebro Una vez que se completó la transmisión en vivo con la vista de transmisión de datos cerebrales, se tiempo para crear una experiencia de reproducción que muestre la transmisión en vivo y registre los datos cerebrales en tiempo real tal como llegaron a través de metadatos cronometrados. Crear el reproductor de transmisión en vivo Podemos usar el IVS Web Player SDK a través de NPM, pero dado que usa WebAssembly, . Para evitar ese engaño, me resulta más fácil usar el reproductor web a través de un etiqueta y lo agregué a mi en mi aplicación React. las cosas pueden complicarse <script> index.html <script src= "https://player.live-video.net/1.12.0/amazon-ivs-player.min.js" ></script> En mi componente, agarro una referencia al jugador y algunos elementos necesarios. Playback.jsx const { IVSPlayer } = window ; const { create : createMediaPlayer, isPlayerSupported, PlayerEventType, PlayerState } = IVSPlayer; const { ENDED, PLAYING, READY, BUFFERING } = PlayerState; const { TEXT_METADATA_CUE, ERROR } = PlayerEventType; Para la reproducción, usamos el nativo etiqueta. <video> <video ref={ this .videoRef} controls playsInline></video> Y para inicializar el reproductor y comenzar la reproducción: this .playerRef.current = createMediaPlayer(); this .playerRef.current.attachHTMLVideoElement( this .videoRef.current); this .playerRef.current.load(STREAM_URL); this .playerRef.current.play(); Escuchar y responder a metadatos cronometrados Ahora que estamos reproduciendo la transmisión en vivo, podemos escuchar y responder a los datos cerebrales entrantes. this .playerRef.current.addEventListener(TEXT_METADATA_CUE, this .onPlayerMetadata); Establezca los datos del cerebro en nuestro estado de componente: }); }); }; onPlayerMetadata = ( e ) => { //console.log(e); const data = JSON .parse(e.text); this .setState( state => { state.ch0.datasets[ 0 ].data = data[ 0 ]; state.ch1.datasets[ 0 ].data = data[ 1 ]; state.ch2.datasets[ 0 ].data = data[ 2 ]; state.ch3.datasets[ 0 ].data = data[ 3 ]; this .chartReferenceCh0.current.data.datasets[ 0 ].data = state.ch0.datasets[ 0 ].data; this .chartReferenceCh1.current.data.datasets[ 0 ].data = state.ch1.datasets[ 0 ].data; this .chartReferenceCh2.current.data.datasets[ 0 ].data = state.ch2.datasets[ 0 ].data; this .chartReferenceCh3.current.data.datasets[ 0 ].data = state.ch3.datasets[ 0 ].data; return ({ ch0 : state.ch0, ch1 : state.ch1, ch2 : state.ch2, ch3 : state.ch3 Y renderícelo con un gráfico de barras (con Chart.js): options={ { }, } } } /> <Bar data={ this .state.ch0} ref={ this .chartReferenceCh0} aspectRatio : 1 , title : { display : true , text : 'Channel: ' + channelNames[ 0 ] }, responsive : true , tooltips : { enabled : false legend : { display : false La visualización es genial y ciertamente proporciona una forma divertida de ver mi datos cerebrales mientras estoy transmitiendo en vivo un juego, pero no proporciona una tonelada de contexto. Así que pensé que tendría sentido incluir algunos cálculos para dar una idea de lo que los datos realmente significan. Por eso encontré algunos cálculos en el que incluía algunas fórmulas que se pueden usar para calcular factores como relajación (alfa dividida por delta) y concentración (beta dividida por theta). Otra una forma de derivar la fatiga ((theta + alpha) / beta). Envolví estos cálculos en un componente útil y reutilizable. muse-lsl proyecto en GitHub excelente publicación de blog que encontré destacaba </Col> </Row> <Row className= 'mb-2' > { /* Delta: 0 Theta: 1 Alpha: 2 Beta: 3 Gamma: 4 */ } <Col xs={ 12 } xxl={ 4 } className= 'align-items-center mb-2 mb-xxl-0' > < Badge className = 'fs-6 w-100' bg = 'info' > Relaxation: < span className = 'fw-bold' > < NumberFormat value = {this.props.dataset.data[0] ? ( this.props.dataset.data [ 2 ] / this.props.dataset.data [ 0 ]) : 0 } decimalScale = {2} displayType = { ' text '} /> </ span > </ Badge > < Col xs = {12} xxl = {4} className = 'align-items-center mb-2 mb-xxl-0' > < Badge className = 'fs-6 w-100' bg = 'info' > Fatigue: < span className = 'fw-bold' > < NumberFormat value = { this.props.dataset.data [ 3 ] ? ( ( this.props.dataset.data [ 1 ] + this.props.dataset.data [ 2 ]) / this.props.dataset.data [ 3 ] ) : 0 } decimalScale = {2} displayType = { ' text '} /> </ span > </ Badge > </ Col > < Col xs = {12} xxl = {4} className = 'align-items-center mb-2 mb-xxl-0' > < Badge className = 'fs-6 w-100' bg = 'info' > Focus: < span className = 'fw-bold' > < NumberFormat value = {this.props.dataset.data[1] ? ( this.props.dataset.data [ 3 ] / this.props.dataset.data [ 1 ]) : 0 } decimalScale = {2} displayType = { ' text '} /> </ span > </ Badge > </ Col > Resumen En esta publicación, vimos cómo creé una aplicación React para vivir transmitir mis datos cerebrales con Amazon IVS. Si desea obtener más información sobre Amazon IVS, consulte la serie aquí en dev.to. Si está interesado en probar la aplicación o simplemente consultar el código fuente completo de la aplicación, compruébelo en . Sus comentarios, preguntas y comentarios siempre son bienvenidos, así que deje un comentario aquí o conéctese conmigo en Getting Started with Amazon Interactive Video Service GitHub Twitter Enlaces Brain to the Cloud - Parte I - Introducción al proyecto y descripción general de la arquitectura Brain to the Cloud - Parte II - Cómo subí mi cerebro a la nube Brain to the Cloud - Parte III - Examinando la relación entre la actividad cerebral y el rendimiento de los videojuegos Código fuente del proyecto Publicado por primera vez . aquí