Đầu năm nay, tôi đã tạo một dự án thực sự thú vị mà tôi gọi là "Brain
to the Cloud" nơi tôi đã lưu dữ liệu bộ não của mình lên đám mây khi chơi
Call of Duty để tôi có thể phân tích mối quan hệ giữa
chức năng nhận thức và hiệu suất trò chơi video. Tôi đã viết lên một phần ba
loạt bài đăng trên blog và tạo một số video thú vị để tóm tắt những phát hiện của tôi về
dự án đó. Nếu bạn muốn kiểm tra chúng, bạn có thể tham khảo
các liên kết ở dưới cùng của bài viết này. Một vài tháng sau khi tôi xuất bản nó
dự án, tôi bắt đầu làm việc tại Twitch với tư cách là Người hỗ trợ phát triển chính cho
Amazon Interactive Video Service (Amazon IVS) - một giải pháp được quản lý toàn phần
để tạo các giải pháp truyền phát video trực tiếp, tương tác (hãy xem loạt bài này để tìm hiểu thêm). Bước tiếp theo trong dự án "Brain to the Cloud" của tôi rất rõ ràng - tôi cần phát trực tiếp bộ não của mình.
Trước khi xem mã, hãy xem sản phẩm cuối cùng. Có 2
chế độ xem cho ứng dụng: chế độ xem phát sóng và chế độ xem phát lại. Trong
chế độ xem phát sóng, chúng tôi có thể xem trước video trực tiếp, bắt đầu
phát sóng và kết nối băng đô Muse để truyền dữ liệu não
thu được từ băng đô. Trong chế độ xem phát lại, chúng tôi hiển thị trực tiếp
phát trực tuyến với một
<video>
yếu tố và biểu đồ dữ liệu não trong thời gian thực Có 5 bước cho dự án này:
Nếu bạn thích mô tả đồ họa của những thứ như vậy, đây là hình thức của nó:
Tôi đã sử dụng React cho dự án này. Tại sao? Vâng, tôi đã có rất nhiều kinh nghiệm
với Vue và Angular, nhưng có lẽ tôi là một trong những nhà phát triển cuối cùng trên
trái đất để thử React. Tôi hình dung đã đến lúc tìm ra tất cả những gì
sự cường điệu đã xảy ra, và tôi biết rằng điều này sẽ không khó
dự án để xây dựng với nó. Do thiếu kinh nghiệm trước đây, tôi không
những gì bạn gọi là người dùng "nâng cao" của khung, nhưng tôi phải nói
rằng tôi khá hài lòng với những gì tôi thấy cho đến nay. Tôi tìm thấy quá trình
thú vị và không thấy mình "chiến đấu" với khuôn khổ. Nhưng mà
bài đăng trên blog này không phải là quan điểm của tôi về các khung JavaScript, vì vậy tôi sẽ
lưu nó cho một bài viết trong tương lai. Thay vào đó, hãy nói về cách tôi phát sóng
não của tôi!
Trong dự án "Brain to the Cloud" ban đầu của tôi, tôi đã sử dụng điện não đồ "cổ điển"
tai nghe có tên là MindFlex để ghi lại các chỉ số não bộ của tôi. Nó hoạt động khá
tốt nhưng yêu cầu tôi "hack" thiết bị bằng cách thêm một chiếc ESP-12
vi điều khiển để lấy số đọc ra khỏi thiết bị và gửi
chúng lên đám mây. Lần này tôi với tới một cái gì đó mới hơn một chút -
và một cái gì đó mà tôi có thể sử dụng mà không cần sửa đổi. sau một chút
nghiên cứu, tôi quyết định chọn Muse S Headband . Rất may, có một thư viện mã nguồn mở thực sự tuyệt vời được gọi là muse-js cho phép tôi truy cập trực tiếp vào các bài đọc não trong trình duyệt web bằng Web Bluetooth (tất nhiên là trong các trình duyệt được hỗ trợ).
Cho đến gần đây, phát trực tiếp với Amazon IVS yêu cầu chúng tôi sử dụng
ứng dụng khách bên thứ ba để phát các luồng của chúng tôi dưới dạng RTMPS. Nhưng chúng tôi gần đây
đã ra mắt công cụ thay đổi cuộc chơi: Amazon IVS Web Broadcast SDK .
Như tên ngụ ý, SDK này cung cấp cho chúng tôi khả năng phát sóng
phát trực tiếp qua WebRTC trực tiếp từ trình duyệt web. Rõ ràng, đây là một
hoàn toàn phù hợp để phát trực tiếp bộ não của tôi vì điều đó có nghĩa là tôi có thể tạo
một giải pháp "tất cả trong một" để phát dữ liệu não của tôi cùng với
phát trực tiếp mà không cần dựa vào phần mềm của bên thứ ba hoặc tập lệnh bên ngoài.
Thêm Web Broadcast vào ứng dụng React
Chúng tôi sẽ không xem xét từng bước cần thiết để sử dụng
Web Broadcast SDK trong bài đăng này. Thay vào đó, chúng ta sẽ xem xét những điểm nổi bật để
có được một ý tưởng chung về cách nó hoạt động. Đừng lo lắng - tôi đã có một bài viết khác
sắp ra mắt, nơi chúng tôi sẽ đào sâu vào quy trình "từng bước" để sử dụng
Web Broadcast SDK, vì vậy hãy chú ý theo dõi điều đó. Điều đó nói rằng, chúng ta hãy lấy một
hành trình nhanh để xem cách tôi sử dụng SDK trong dự án này. bước đầu tiên của tôi
là sử dụng một chương trình phát sóng trên web để cài đặt
amazon-ivs-web-broadcast
mô-đun. Sử dụng công cụ quản lý gói yêu thích của bạn, hãy chạy: $ npm install amazon-ivs-web-broadcast
Tiếp theo, chúng ta cần nhập nó vào thành phần của mình. Trong thành phần Broadcast.jsx của tôi, tôi đã thêm:
import IVSBroadcastClient, { STANDARD_LANDSCAPE } from 'amazon-ivs-web-broadcast' ;
Chúng tôi có thể tạo một phiên bản IVSBroadcastClient với cấu hình luồng mong muốn và nhập điểm cuối từ kênh Amazon IVS của chúng tôi và đặt nó vào trạng thái của thành phần.
this .setState({ broadcastClient : IVSBroadcastClient.create({ streamConfig : STANDARD_LANDSCAPE, ingestEndpoint : this .state.ingestEndpoint, }) });
Bây giờ chúng ta đã có một phiên bản của ứng dụng khách, chúng ta có thể thêm camera của mình vào ứng dụng khách. Đối với điều này, chúng tôi sử dụng
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 });
Việc thêm micrô của người dùng vào ứng dụng khách theo mô hình tương tự.
const audioStream = await navigator.mediaDevices.getUserMedia({ audio : { deviceId : this .state.selectedAudioDeviceId }, }); this .state.broadcastClient.addAudioInputDevice(audioStream, 'mic1' );
Lưu ý: Do mô hình bảo mật của trình duyệt, chúng tôi cần có quyền truy cập vào máy ảnh và micrô của người dùng. Tham khảo nguồn dự án trên GitHub để biết thêm thông tin về điều này và để xem cách tôi nắm bắt danh sách
thiết bị và trình bày chúng trong hộp thoại để cho phép người dùng chọn
thiết bị phát sóng nếu có nhiều tùy chọn.
Giờ đây, chúng tôi có thể thêm bản xem trước trực tiếp vào trang để chúng tôi có thể xem những gì người xem cuối cùng sẽ thấy ở phía người chơi.
<canvas ref={ this .previewRef} id= 'broadcast-preview' ></canvas>
Và đính kèm bản xem trước vào
broadcastClient
: this .state.broadcastClient.attachPreview( this .previewRef.current);
Để bắt đầu truyền phát, hãy thêm một nút vào trang và trong
onClick
trình xử lý cho cuộc gọi nút startBroadcast()
trên broadcastClient
(vượt qua cần thiết streamKey
). this .state.broadcastClient.startBroadcast( this .state.streamKey);
Như tôi đã đề cập ở trên, tôi đã sử dụng
muse-js
thư viện, cung cấp khả năng kết nối với băng đô và lấy dữ liệu thô. Tuy nhiên, muse-js
không tính toán công suất dải tuyệt đối cho dữ liệu điện não đồ. Đối với điều này, tôi cần tiếp cận với một thư viện khác : eeg-pipes
.Bước đầu tiên là thêm và nhập các thư viện.
$ npm install muse-js $ npm install @neurosity/pipes
import { zipSamples, MuseClient } from 'muse-js' ; import { powerByBand, epoch, fft } from '@neurosity/pipes' ;
Tiếp theo, tôi đã thêm một nút có trình xử lý nhấp chuột. Trong trình xử lý, tôi
kết nối với tai nghe, bắt đầu nghe dữ liệu và đăng ký
dòng.
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
} );
Bây giờ tôi đã có một trình xử lý thu thập dữ liệu não bộ của tôi từ Muse
headband, đã đến lúc xuất bản dữ liệu đó dưới dạng siêu dữ liệu theo thời gian trong chương trình trực tiếp
dòng.
Điều tuyệt vời vềlà nó được nhúng trực tiếp vào luồng video và vẫn là một phần vĩnh viễn của luồng đó. Điều đó có nghĩa là nó tồn tại ngay cả trong các phiên bản được ghi lại, nghĩa là ngay cả khi phát lại theo yêu cầu, chúng tôi có thể nghe và phản hồi các sự kiện.timed metadata
Web Broadcast SDK không hỗ trợ xuất bản siêu dữ liệu theo thời gian từ phía máy khách, vì vậy chúng tôi sẽ phải sử dụng
putMetadata
( tài liệu ) qua AWS SDK dành cho JavaScript . Đối với điều này, tôi đã tạo một hàm 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 ; } };
Để xuất bản dữ liệu bộ não của tôi dưới dạng siêu dữ liệu theo thời gian, tôi đã tạo một Cổng API Amazon để gọi hàm và sửa đổi
subscribe()
phương pháp trên để gọi hàm 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) }) }); } } );
Sau khi luồng trực tiếp với chế độ xem phát dữ liệu não hoàn tất, nó đã được
thời gian để tạo trải nghiệm phát lại sẽ hiển thị luồng trực tiếp
và lập biểu đồ dữ liệu não trong thời gian thực khi nó đến thông qua siêu dữ liệu được định thời gian.
Tạo trình phát trực tiếp
Chúng ta có thể sử dụng IVS Web Player SDK thông qua NPM, nhưng vì nó sử dụng WebAssembly nên mọi thứ có thể trở nên rắc rối . Để tránh sự phức tạp đó, tôi thấy việc sử dụng trình phát trên web dễ dàng hơn thông qua một
<script>
thẻ và tôi đã thêm nó vào index.html
trong ứng dụng React của tôi. <script src= "https://player.live-video.net/1.12.0/amazon-ivs-player.min.js" ></script>
trong tôi
Playback.jsx
thành phần, tôi lấy một tham chiếu đến trình phát và một số thành phần cần thiết. const { IVSPlayer } = window ; const { create : createMediaPlayer, isPlayerSupported, PlayerEventType, PlayerState } = IVSPlayer; const { ENDED, PLAYING, READY, BUFFERING } = PlayerState; const { TEXT_METADATA_CUE, ERROR } = PlayerEventType;
Để phát lại, chúng tôi sử dụng bản gốc
<video>
nhãn. <video ref={ this .videoRef} controls playsInline></video>
Và để khởi tạo trình phát và bắt đầu phát lại:
this .playerRef.current = createMediaPlayer(); this .playerRef.current.attachHTMLVideoElement( this .videoRef.current); this .playerRef.current.load(STREAM_URL); this .playerRef.current.play();
Bây giờ chúng tôi đang phát luồng trực tiếp, chúng tôi có thể lắng nghe và phản hồi dữ liệu não đến.
this .playerRef.current.addEventListener(TEXT_METADATA_CUE, this .onPlayerMetadata);
Đặt dữ liệu não vào trạng thái thành phần của chúng tôi:
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 }); }); };
Và hiển thị nó bằng biểu đồ thanh (với 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
} } } />
Hình ảnh trực quan rất thú vị và chắc chắn cung cấp một cách thú vị để xem
dữ liệu não bộ khi tôi đang phát trực tiếp trò chơi, nhưng không cung cấp nhiều
định nghĩa bài văn. Vì vậy, tôi nghĩ rằng sẽ hợp lý hơn nếu bao gồm một số tính toán
để cung cấp cái nhìn sâu sắc về ý nghĩa thực sự của dữ liệu. Đối với điều đó, tôi tìm thấy
một số tính toán trong
muse-lsl
dự án trên 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>
Trong bài đăng này, chúng tôi đã xem cách tôi tạo một ứng dụng React để hoạt động
truyền dữ liệu não bộ của tôi với Amazon IVS. Nếu bạn muốn tìm hiểu thêm về
Amazon IVS, vui lòng xem loạt bài Bắt đầu với Amazon Interactive Video Service tại đây trên dev.to. Nếu bạn quan tâm đến việc dùng thử ứng dụng hoặc chỉ cần xem nguồn đầy đủ của ứng dụng, hãy xem nó trên GitHub . Nhận xét, câu hỏi và phản hồi của bạn luôn được chào đón, vì vậy hãy để lại nhận xét tại đây hoặc kết nối với tôi trên Twitter
Lần đầu tiên được xuất bảnở đây .