No início deste ano, criei um projeto muito divertido que chamei de "Brain
to the Cloud" onde salvei meus dados cerebrais na nuvem enquanto jogava
Call of Duty para que eu pudesse analisar a relação entre meu
função cognitiva e desempenho de videogame. Eu escrevi um em três partes
série de postagens no blog e criei alguns vídeos divertidos para resumir minhas descobertas sobre
aquele projeto. Se você gostaria de verificá-los, você pode consultar o
links no final deste post. Alguns meses depois de publicar aquele
projeto, comecei a trabalhar no Twitch como o principal Developer Advocate para
Amazon Interactive Video Service (Amazon IVS) - uma solução totalmente gerenciada
para criar soluções de transmissão de vídeo ao vivo e interativas (confira esta série para saber mais). A próxima etapa do meu projeto "Brain to the Cloud" era óbvia - eu precisava transmitir meu cérebro ao vivo .
Antes de olharmos para o código, vamos ver o produto final. Há 2
visualizações para o aplicativo: uma visualização de transmissão e uma visualização de reprodução. Dentro
a exibição de transmissão, podemos visualizar o vídeo ao vivo, iniciar o
transmissão e conecte a faixa de cabeça Muse para transmitir os dados do cérebro
obtido da faixa de cabeça. Na exibição de reprodução, exibimos o ao vivo
transmitir com um
<video>
elemento e mapear os dados do cérebro em tempo real Existem 5 passos para este projeto:
Se você preferir representações gráficas de tais coisas, veja como fica:
Eu usei o React para este projeto. Por quê? Bem, eu tenho muita experiência
com Vue e Angular, mas provavelmente sou um dos últimos desenvolvedores
earth para tentar React. Achei que era hora de descobrir o que tudo
o hype era sobre, e eu sabia que isso não seria uma tarefa difícil
projeto para construir com ele. Devido à minha falta de experiência anterior, não estou
o que você chamaria de usuário "avançado" da estrutura, mas devo dizer
que estou muito feliz com o que vejo até agora. achei o processo
agradável e não me vi "brigando" com o framework. Mas
esta postagem do blog não é sobre minha opinião sobre frameworks JavaScript, então vou
guarde isso para um post futuro. Em vez disso, vamos falar sobre como eu transmito
meu cérebro!
No meu projeto original "Brain to the Cloud", usei um EEG "vintage"
um fone de ouvido chamado MindFlex para capturar minhas leituras cerebrais. Funcionou bastante
bem, mas exigiu que eu "hackeasse" o dispositivo adicionando um ESP-12
microcontrolador para extrair as leituras do dispositivo e enviar
eles para a nuvem. Desta vez, procurei algo um pouco mais novo -
e algo que eu poderia usar sem modificações. Depois de um pouco de
pesquisa, optei pela bandana Muse S. Felizmente, existe uma biblioteca de código aberto realmente incrível chamada muse-js que me permite acessar as leituras do cérebro diretamente em um navegador da Web com Web Bluetooth (em navegadores compatíveis, é claro).
Até recentemente, a transmissão ao vivo com o Amazon IVS exigia que usássemos um
cliente de terceiros para transmitir nossos fluxos como RTMPS. Mas nós recentemente
lançou um divisor de águas: o Amazon IVS Web Broadcast SDK .
Como o nome indica, este SDK nos dá a capacidade de transmitir nosso
transmissão ao vivo via WebRTC diretamente de um navegador da web. Claramente, este foi um
ajuste perfeito para transmitir ao vivo meu cérebro, pois significa que posso criar
uma solução "tudo-em-um" para transmitir meus dados cerebrais junto com meu
transmissão ao vivo sem depender de software de terceiros ou scripts externos.
Adicionando transmissão da Web ao aplicativo React
Não vamos examinar cada etapa necessária para utilizar o
Web Broadcast SDK nesta postagem. Em vez disso, veremos os destaques para
ter uma ideia geral de como funciona. Não se preocupe - tenho outro post
em breve, onde vamos nos aprofundar no processo "passo a passo" para usar
o Web Broadcast SDK, portanto, fique atento a isso. Dito isso, vamos dar uma
jornada rápida para ver como usei o SDK neste projeto. meu primeiro passo
era usar uma transmissão na web para instalar o
amazon-ivs-web-broadcast
módulo. Usando sua ferramenta de gerenciamento de pacotes favorita, execute: $ npm install amazon-ivs-web-broadcast
Em seguida, precisamos importá-lo para o nosso componente. No meu componente Broadcast.jsx, adicionei:
import IVSBroadcastClient, { STANDARD_LANDSCAPE } from 'amazon-ivs-web-broadcast' ;
Podemos criar uma instância do IVSBroadcastClient com a configuração de fluxo desejada e ingerir o endpoint de nosso canal Amazon IVS e configurá-lo no estado de nosso componente.
this .setState({ broadcastClient : IVSBroadcastClient.create({ streamConfig : STANDARD_LANDSCAPE, ingestEndpoint : this .state.ingestEndpoint, }) });
Agora que temos uma instância do cliente, podemos adicionar nossa câmera ao cliente. Para isso 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 });
Adicionar o microfone do usuário ao cliente segue um padrão semelhante.
const audioStream = await navigator.mediaDevices.getUserMedia({ audio : { deviceId : this .state.selectedAudioDeviceId }, }); this .state.broadcastClient.addAudioInputDevice(audioStream, 'mic1' );
Observação: devido ao modelo de segurança do navegador, precisamos obter permissões para acessar a câmera e o microfone do usuário. Consulte a fonte do projeto no GitHub para obter mais informações sobre isso e para ver como capturei uma lista de
dispositivos e os apresentou em uma caixa de diálogo para permitir que o usuário escolha o
dispositivo de transmissão se várias opções estiverem disponíveis.
Agora podemos adicionar uma visualização ao vivo à página para que possamos ver o que nossos visualizadores verão no final das contas do jogador.
<canvas ref={ this .previewRef} id= 'broadcast-preview' ></canvas>
E anexe a visualização ao
broadcastClient
: this .state.broadcastClient.attachPreview( this .previewRef.current);
Para iniciar a transmissão, adicione um botão à página e, no
onClick
manipulador para a chamada do botão startBroadcast()
no broadcastClient
(passando o necessário streamKey
). this .state.broadcastClient.startBroadcast( this .state.streamKey);
Como mencionei acima, usei o
muse-js
biblioteca, que fornece a capacidade de conectar-se à faixa de cabeça e extrair os dados brutos. No entanto, muse-js
não calcula as potências absolutas da banda para os dados EEG. Para isso, precisei buscar outra biblioteca : eeg-pipes
.A primeira etapa é adicionar e importar as bibliotecas.
$ npm install muse-js $ npm install @neurosity/pipes
import { zipSamples, MuseClient } from 'muse-js' ; import { powerByBand, epoch, fft } from '@neurosity/pipes' ;
Em seguida, adicionei um botão com um manipulador de cliques. No manipulador, eu
conecte-se ao fone de ouvido, comece a ouvir os dados e assine o
fluxo.
const client = new MuseClient(); await client.connect(); await client.start(); zipSamples(client.eegReadings) .pipe( epoch({ duration : 1024 , interval : 250 , samplingRate : 256 }), fft({ bins : 256 }), powerByBand(), ) .subscribe( ( 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
} );
Agora que tenho um manipulador que coleta meus dados cerebrais do Muse
headband, é hora de publicar esses dados como metadados cronometrados no live
fluxo.
A coisa incrível sobreé que ele está diretamente incorporado no fluxo de vídeo e permanece uma parte permanente desse fluxo. Isso significa que existe mesmo em versões gravadas, o que significa que, mesmo na reprodução sob demanda, podemos ouvir e responder aos eventos.timed metadata
O Web Broadcast SDK não oferece suporte à publicação de metadados cronometrados do lado do cliente, portanto, teremos que usar
putMetadata
( docs ) por meio do AWS SDK para JavaScript . Para isso, criei uma função AWS Lambda. 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 ; response.body = err.stack; callback( null , response); return ; } };
Para publicar meus dados cerebrais como metadados cronometrados, criei um Amazon API Gateway para invocar a função e modificar o
subscribe()
método acima para chamar a função AWS Lambda. zipSamples(client.eegReadings) .pipe( epoch({ duration : 1024 , interval : 250 , samplingRate : 256 }), fft({ bins : 256 }), powerByBand(), ) .subscribe( ( 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) { fetch(LAMBDA_URL, { 'method' : 'POST' , 'mode' : 'no-cors' , 'headers' : { 'Content-Type' : 'application/json' , }, 'body' : JSON .stringify({ channelArn : this .state.channelArn, metadata : JSON .stringify(meta) }) }); } } );
Depois que a transmissão ao vivo com exibição de transmissão de dados cerebrais foi concluída, foi
tempo para criar uma experiência de reprodução que exibiria a transmissão ao vivo
e mapeie os dados do cérebro em tempo real conforme eles chegam por meio de metadados cronometrados.
Criando o reprodutor de transmissão ao vivo
Podemos usar o IVS Web Player SDK via NPM, mas como ele usa WebAssembly, as coisas podem ficar complicadas . Para evitar essa complicação, acho mais fácil usar o web player por meio de um
<script>
tag e eu adicionei isso ao meu index.html
no meu aplicativo React. <script src= "https://player.live-video.net/1.12.0/amazon-ivs-player.min.js" ></script>
No meu
Playback.jsx
componente, pego uma referência ao jogador e alguns elementos necessários. const { IVSPlayer } = window ; const { create : createMediaPlayer, isPlayerSupported, PlayerEventType, PlayerState } = IVSPlayer; const { ENDED, PLAYING, READY, BUFFERING } = PlayerState; const { TEXT_METADATA_CUE, ERROR } = PlayerEventType;
Para reprodução, usamos o nativo
<video>
marcação. <video ref={ this .videoRef} controls playsInline></video>
E para inicializar o player e iniciar a reprodução:
this .playerRef.current = createMediaPlayer(); this .playerRef.current.attachHTMLVideoElement( this .videoRef.current); this .playerRef.current.load(STREAM_URL); this .playerRef.current.play();
Agora que estamos reproduzindo a transmissão ao vivo, podemos ouvir e responder aos dados cerebrais recebidos.
this .playerRef.current.addEventListener(TEXT_METADATA_CUE, this .onPlayerMetadata);
Defina os dados do cérebro em nosso 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 }); }); };
E renderize-o com um gráfico de barras (com Chart.js):
<Bar data={ this .state.ch0} ref={ this .chartReferenceCh0} options={ { aspectRatio : 1 , title : { display : true , text : 'Channel: ' + channelNames[ 0 ] }, responsive : true , tooltips : { enabled : false
}, legend : { display : false
} } } />
A visualização é legal e certamente fornece uma maneira divertida de ver meu
dados cerebrais enquanto estou transmitindo um jogo ao vivo, mas não fornece uma tonelada de
contexto. Achei que faria sentido incluir alguns cálculos
para dar uma visão sobre o que os dados realmente significam. Para isso, encontrei
alguns cálculos no
muse-lsl
projeto no GitHub <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> < 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 >
</Row>
Neste post, vimos como criei um aplicativo React para viver
transmitir meus dados cerebrais com o Amazon IVS. Se você gostaria de saber mais sobre
Amazon IVS, confira a série Getting Started with Amazon Interactive Video Service aqui no dev.to. Se você estiver interessado em experimentar o aplicativo ou apenas verificar o código-fonte completo do aplicativo, verifique-o no GitHub . Seus comentários, perguntas e feedback são sempre bem-vindos, então deixe um comentário aqui ou conecte-se comigo no Twitter
Publicado pela primeira vezaqui .