今年早些时候,我创建了一个非常有趣的项目,我称之为“大脑
到云端”,我边玩边将我的大脑数据保存到云端
使命召唤,以便我可以分析我之间的关系
认知功能和视频游戏性能。我写了一个三部分
博客文章系列并制作了一些有趣的视频来总结我的发现
那个项目。如果你想检查这些,你可以参考
这篇文章底部的链接。在我发表这篇文章几个月后
项目,我开始在 Twitch 工作,担任
Amazon Interactive Video Service (Amazon IVS) - 一个完全托管的解决方案
用于创建实时交互式视频流解决方案(查看本系列以了解更多信息)。我的“大脑到云端”项目的下一步很明显——我需要直播我的大脑。
在查看代码之前,让我们先看看最终产品。有2个
应用程序的视图:广播视图和回放视图。在
广播视图,我们可以预览直播视频,启动
广播,并连接 Muse 头带以传输大脑数据
从头带获得。在播放视图中,我们显示实时
流与
<video>
元素,并实时绘制大脑数据图表这个项目有5个步骤:
如果您更喜欢此类事物的图形描述,则如下所示:
我在这个项目中使用了 React。为什么?还好我经验丰富
使用 Vue 和 Angular,但我可能是最后一批开发者之一
earth 尝试 React。我想是时候弄清楚一切了
炒作是关于,我知道这不会是一个困难
用它构建的项目。由于我之前缺乏经验,我不是
你称之为框架的“高级”用户,但我不得不说
我对目前所看到的很满意。我找到了过程
愉快并且没有发现自己与框架“战斗”。但
这篇博文不是关于我对 JavaScript 框架的看法,所以我会
将其保存在以后的帖子中。相反,让我们谈谈我如何广播
我的脑子!
在我最初的“大脑到云端”项目中,我使用了“老式”脑电图
称为 MindFlex 的耳机可以捕捉我的大脑读数。它工作得很好
很好,但需要我通过添加 ESP-12 来“破解”设备
微控制器,以便从设备中提取读数并发送
他们到云端。这次我接触到了一些更新的东西——
以及我无需修改即可使用的东西。过了一会儿
研究后,我选择了Muse S 头带。值得庆幸的是,有一个非常棒的开源库,叫做muse-js ,它让我可以直接在带有网络蓝牙的网络浏览器中访问大脑读数(当然,在支持的浏览器中)。
直到最近,使用Amazon IVS 进行直播还需要我们使用
第三方客户端将我们的流广播为 RTMPS。但是我们最近
推出了改变游戏规则的产品: Amazon IVS Web Broadcast SDK 。
顾名思义,这个 SDK 使我们能够广播我们的
直接从网络浏览器通过 WebRTC 直播。显然,这是一个
非常适合直播我的大脑,因为这意味着我可以创造
一种“一体式”解决方案,用于将我的大脑数据与我的大脑一起广播
无需依赖第三方软件或外部脚本即可进行直播。
将网络广播添加到 React 应用程序
我们不会查看使用
这篇文章中的 Web Broadcast SDK。相反,我们将着眼于亮点
大致了解它是如何工作的。别担心 - 我有另一篇文章
即将推出,我们将深入研究使用的“逐步”过程
Web Broadcast SDK,敬请期待。也就是说,让我们采取一个
快速了解我如何在此项目中使用 SDK。我的第一步
是使用网络广播来安装
amazon-ivs-web-broadcast
模块。使用您最喜欢的包管理工具,运行: $ npm install amazon-ivs-web-broadcast
接下来,我们需要将它导入到我们的组件中。在我的 Broadcast.jsx 组件中,我添加了:
import IVSBroadcastClient, { STANDARD_LANDSCAPE } from 'amazon-ivs-web-broadcast' ;
我们可以使用所需的流配置创建 IVSBroadcastClient 的实例,并从我们的 Amazon IVS 通道获取端点并将其设置为我们组件的状态。
this .setState({ broadcastClient : IVSBroadcastClient.create({ streamConfig : STANDARD_LANDSCAPE, ingestEndpoint : this .state.ingestEndpoint, }) });
现在我们已经有了客户端实例,我们可以将相机添加到客户端。为此我们使用
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 });
将用户的麦克风添加到客户端遵循类似的模式。
const audioStream = await navigator.mediaDevices.getUserMedia({ audio : { deviceId : this .state.selectedAudioDeviceId }, }); this .state.broadcastClient.addAudioInputDevice(audioStream, 'mic1' );
注意:由于浏览器安全模型,我们需要获得访问用户摄像头和麦克风的权限。有关这方面的更多信息,请参阅GitHub 上的项目源代码,并查看我如何捕获列表
设备并在对话框中显示它们以允许用户选择
如果有多个选项可用,则广播设备。
现在我们可以在页面上添加实时预览,这样我们就可以看到我们的观众最终会在玩家端看到什么。
<canvas ref={ this .previewRef} id= 'broadcast-preview' ></canvas>
并将预览附加到
broadcastClient
: this .state.broadcastClient.attachPreview( this .previewRef.current);
要开始广播,请在页面中添加一个按钮,并在
onClick
按钮调用的处理程序startBroadcast()
在broadcastClient
(通过必要的streamKey
). this .state.broadcastClient.startBroadcast( this .state.streamKey);
正如我上面提到的,我使用了
muse-js
库,它提供了连接头带和提取原始数据的能力。然而, muse-js
不计算 EEG 数据的绝对波段功率。为此,我需要找到另一个图书馆: eeg-pipes
.第一步是添加和导入库。
$ npm install muse-js $ npm install @neurosity/pipes
import { zipSamples, MuseClient } from 'muse-js' ; import { powerByBand, epoch, fft } from '@neurosity/pipes' ;
接下来,我添加了一个带有点击处理程序的按钮。在处理程序中,我
连接耳机,开始监听数据,订阅
溪流。
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
} );
现在我有了一个处理程序,可以从 Muse 收集我的大脑数据
头带,是时候将该数据作为实时元数据发布
溪流。
令人敬畏的事情是它直接嵌入到视频流中,并且始终是该流的永久部分。这意味着它甚至存在于录制版本中,这意味着即使在点播播放中我们也可以侦听和响应事件。timed metadata
Web Broadcast SDK 不支持从客户端发布定时元数据,所以我们必须使用
putMetadata
( docs ) 通过适用于 JavaScript 的 AWS 开发工具包。为此,我创建了一个 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 ; } };
为了将我的大脑数据发布为定时元数据,我创建了一个 Amazon API Gateway 来调用函数并修改
subscribe()
上面的方法来调用 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) }) }); } } );
大脑数据广播视图的直播完成后,
是时候创建显示直播流的播放体验了
并通过定时元数据实时绘制大脑数据图表。
创建直播流媒体播放器
我们可以通过 NPM 使用 IVS Web Player SDK,但由于它使用 WebAssembly, 所以事情会变得棘手。为了避免这种麻烦,我发现通过
<script>
标签,我将其添加到我的index.html
在我的 React 应用程序中。 <script src= "https://player.live-video.net/1.12.0/amazon-ivs-player.min.js" ></script>
在我的
Playback.jsx
组件,我获取了对播放器的引用和一些必要的元素。 const { IVSPlayer } = window ; const { create : createMediaPlayer, isPlayerSupported, PlayerEventType, PlayerState } = IVSPlayer; const { ENDED, PLAYING, READY, BUFFERING } = PlayerState; const { TEXT_METADATA_CUE, ERROR } = PlayerEventType;
对于回放,我们使用原生
<video>
标签。 <video ref={ this .videoRef} controls playsInline></video>
并初始化播放器并开始播放:
this .playerRef.current = createMediaPlayer(); this .playerRef.current.attachHTMLVideoElement( this .videoRef.current); this .playerRef.current.load(STREAM_URL); this .playerRef.current.play();
现在我们正在播放直播,我们可以收听传入的大脑数据并做出响应。
this .playerRef.current.addEventListener(TEXT_METADATA_CUE, this .onPlayerMetadata);
将大脑数据设置为我们的组件状态:
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 }); }); };
并使用条形图(使用 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
} } } />
可视化很酷,当然提供了一种有趣的方式来查看我的
我在直播游戏时的大脑数据,但没有提供大量
语境。所以我认为包括一些计算是有意义的
深入了解数据的实际含义。为此,我发现
中的一些计算
muse-lsl
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>
在这篇文章中,我们了解了我是如何创建一个 React 应用程序来运行的
使用 Amazon IVS 流式传输我的大脑数据。如果您想了解更多
Amazon IVS,请在 dev.to 上查看Amazon Interactive Video Service 入门系列。如果您有兴趣试用该应用程序或只是查看该应用程序的完整源代码,请在GitHub上查看。随时欢迎您提出意见、问题和反馈,请在此处发表评论或在Twitter上与我联系