-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
371 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,80 @@ | ||
# GunStreamer | ||
Streaming component for Gun db. | ||
Streaming component for Gun db. This is only the streaming part. The viewer part is a different component. For now it will be published to the root of Gun. To verify that it is publishing you can view currently view it at: https://gunmeeting.herokuapp.com/ | ||
|
||
# Integration | ||
For an example use the index.html and the .js folder. | ||
|
||
### HTML | ||
```html | ||
<head> | ||
... | ||
<script src="https://cdn.jsdelivr.net/npm/gun/gun.js"></script> | ||
<script type="text/javascript" src="js/GunRecorder.js"></script> | ||
<script type="text/javascript" src="js/GunStreamer.js"></script> | ||
... | ||
</head> | ||
``` | ||
|
||
```html | ||
<body> | ||
... | ||
<button type="button" onclick="gunRecorder.startCamera()">Start Camera</button> | ||
<button id="record_button" type="button" onclick="gunRecorder.record()">Start Recording</button> | ||
<br><br> | ||
<video id="record_video" width="20%" poster="https://www.srsd.net/images/video-poster.png" autoplay controls muted /> | ||
<script type="text/javascript" src="js/initialiation.js"></script> | ||
... | ||
</body> | ||
``` | ||
|
||
### initialiation.js | ||
The gun part, writing to gun and publish it. | ||
```javascript | ||
//Configure GUN to pass to streamer | ||
var peers = ['https://gunmeetingserver.herokuapp.com/gun']; | ||
var opt = { peers: peers, localStorage: false, radisk: false }; | ||
var gunDB = Gun(opt); | ||
|
||
//Config for the GUN GunStreamer | ||
var streamer_config = { | ||
dbRecord: "gunmeeting",//The root of the streams | ||
streamId: "qvdev",//The user id you wanna stream | ||
gun: gunDB,//Gun instance | ||
debug: false//For debug logs | ||
} | ||
``` | ||
The recorder part record parse and notify ondataavailable | ||
```javascript | ||
//GUN Streamer is the data side. It will convert data and write to GUN db | ||
const gunStreamer = new GunStreamer(streamer_config) | ||
|
||
//This is a callback function about the recording state, following states possible | ||
// STOPPED: 1¸ | ||
// RECORDING:2 | ||
// NOT_AVAILABLE:3 | ||
// UNKNOWN:4 | ||
var onRecordStateChange = function (state) { | ||
var recordButton = document.getElementById("record_button"); | ||
switch (state) { | ||
case recordSate.RECORDING: | ||
recordButton.innerText = "Stop recording"; | ||
break; | ||
default: | ||
recordButton.innerText = "Start recording"; | ||
break; | ||
} | ||
} | ||
|
||
//Config for the gun recorder | ||
var recorder_config = { | ||
video_id: "record_video",//Video html element id | ||
onDataAvailable: gunStreamer.onDataAvailable,//MediaRecorder data available callback | ||
onRecordStateChange: onRecordStateChange,//Callback for recording state | ||
audioBitsPerSecond: 6000,//Audio bits per second this is the lowest quality | ||
videoBitsPerSecond: 100000,//Video bits per second this is the lowest quality | ||
debug: false//For debug logs | ||
} | ||
|
||
//Init the recorder | ||
const gunRecorder = new GunRecorder(recorder_config); | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
<!DOCTYPE html> | ||
<html lang="en" dir="ltr"> | ||
|
||
<head> | ||
<meta charset="utf-8"> | ||
<title></title> | ||
<script src="https://cdn.jsdelivr.net/npm/gun/gun.js"></script> | ||
<script type="text/javascript" src="js/GunRecorder.js"></script> | ||
<script type="text/javascript" src="js/GunStreamer.js"></script> | ||
</head> | ||
|
||
<body> | ||
<button type="button" onclick="gunRecorder.startCamera()">Start Camera</button> | ||
<button id="record_button" type="button" onclick="gunRecorder.record()">Start Recording</button> | ||
<br><br> | ||
<video id="record_video" width="20%" poster="https://www.srsd.net/images/video-poster.png" autoplay controls muted /> | ||
<script type="text/javascript" src="js/integration.js"></script> | ||
</body> | ||
|
||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
var recordSate = { | ||
STOPPED: 1, | ||
RECORDING: 2, | ||
NOT_AVAILABLE: 3, | ||
UNKNOWN: 4, | ||
}; | ||
|
||
const MIMETYPE = 'video/webm; codecs="opus,vp8"'; | ||
const RECORDER_TIME_SLICE = 300; | ||
const CAMERA_OPTIONS = { video: true, audio: true } | ||
|
||
class GunRecorder { | ||
constructor(config) { | ||
this.video = document.getElementById(config.video_id); | ||
this.mediaRecorder = null; | ||
this.onDataAvailable = config.onDataAvailable; | ||
this.onRecordStateChange = config.onRecordStateChange | ||
this.recorderOptions = { | ||
mimeType: MIMETYPE, | ||
audioBitsPerSecond: config.audioBitsPerSecond, | ||
videoBitsPerSecond: config.videoBitsPerSecond | ||
} | ||
this.debug = config.debug; | ||
this.setRecordingState(recordSate.UNKNOWN); | ||
} | ||
|
||
record() { | ||
if (this.recordSate == recordSate.RECORDING) { | ||
this.mediaRecorder.stop(); | ||
this.changeRecordState(); | ||
} else if (this.recordSate == recordSate.STOPPED) { | ||
this.mediaRecorder = new MediaRecorder(gunRecorder.video.captureStream(), this.recorderOptions); | ||
this.mediaRecorder.ondataavailable = gunRecorder.onDataAvailable; | ||
this.mediaRecorder.start(RECORDER_TIME_SLICE); | ||
this.changeRecordState(); | ||
} else { | ||
this.debugLog("The camera has not been initialized yet. First call startCamera()") | ||
} | ||
} | ||
|
||
startCamera() { | ||
if (this.recordSate == recordSate.RECORDING || this.recordSate == recordSate.STOPPED) { | ||
this.debugLog("Camera already started no need to do again"); | ||
return; | ||
} | ||
var gunRecorder = this; | ||
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) { | ||
navigator.mediaDevices.getUserMedia(CAMERA_OPTIONS).then(function (stream) { | ||
gunRecorder.video.srcObject = stream; | ||
gunRecorder.video.play(); | ||
}); | ||
this.setRecordingState(recordSate.STOPPED); | ||
} else { | ||
this.setRecordingState(recordSate.NOT_AVAILABLE); | ||
} | ||
} | ||
|
||
changeRecordState() { | ||
switch (this.recordSate) { | ||
case recordSate.STOPPED: | ||
this.setRecordingState(recordSate.RECORDING); | ||
break; | ||
case recordSate.NOT_AVAILABLE: | ||
this.debugLog("Sorry camera not available") | ||
break; | ||
case recordSate.UNKNOWN: | ||
this.debugLog("State is unknown check if camera is intialized") | ||
break; | ||
default: | ||
this.setRecordingState(recordSate.STOPPED); | ||
break; | ||
} | ||
} | ||
|
||
setRecordingState(recordSate) { | ||
this.debugLog("STATE BEFORE::" + this.recordSate); | ||
this.recordSate = recordSate; | ||
this.onRecordStateChange(this.recordSate); | ||
this.debugLog("STATE AFTER::" + this.recordSate); | ||
} | ||
|
||
debugLog(logData) { | ||
if (this.debug) { | ||
console.log(logData); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
|
||
const RECORD_PREFIX = "GkXf" | ||
var parseWorker | ||
var initialData | ||
|
||
class GunStreamer { | ||
constructor(config) { | ||
this.dbRecord = config.dbRecord; | ||
this.streamId = config.streamId; | ||
this.gunDB = config.gun; | ||
this.debug = config.debug; | ||
this.startWorker(); | ||
} | ||
|
||
onDataAvailable(event) { | ||
if (event.data.size > 0) { | ||
var blob = event.data; | ||
var response = new Response(blob).arrayBuffer().then(function (arrayBuffer) { | ||
blob = null; | ||
if (parseWorker != undefined) { | ||
parseWorker.postMessage(arrayBuffer); | ||
} | ||
}); | ||
response = null; | ||
} else { | ||
this.debugLog("data not available") | ||
} | ||
} | ||
|
||
startWorker() { | ||
if (typeof (Worker) !== "undefined") { | ||
if (typeof (parseWorker) == "undefined") { | ||
parseWorker = new Worker("js/parser_worker.js"); | ||
} | ||
parseWorker.onmessage = e => { | ||
const message = e.data; | ||
this.writeToGun(message); | ||
}; | ||
} else { | ||
LOG("Sorry! No Web Worker support."); | ||
} | ||
} | ||
|
||
stopWorker() { | ||
parseWorker.terminate(); | ||
parseWorker = undefined; | ||
} | ||
|
||
writeToGun(base64data) { | ||
this.debugLog("Write to GUN::" + base64data.substring(0, 100)); | ||
let lastUpdate = new Date().getTime(); | ||
let user; | ||
if (initialData == undefined && base64data.startsWith(RECORD_PREFIX)) { | ||
this.debugLog("INITIAL"); | ||
var n = base64data.indexOf("wIEB"); | ||
this.debugLog("RAW::" + n + "::" + base64data.substring(0, 252)); | ||
initialData = base64data.substring(0, 252); | ||
} else { | ||
var n = base64data.indexOf("H0O2dQH"); | ||
this.debugLog("RAW::" + n + "::" + base64data); | ||
} | ||
|
||
//Probably has to be changed to different data structure | ||
user = gunDB.get(this.streamId).put({ initial: initialData, name: base64data, id: this.streamId, timestamp: lastUpdate, isSpeaking: false }); | ||
gunDB.get(this.dbRecord).set(user); | ||
} | ||
|
||
debugLog(logData) { | ||
if (this.debug) { | ||
console.log(logData); | ||
} | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
//Configure GUN to pass to streamer | ||
var peers = ['https://gunmeetingserver.herokuapp.com/gun']; | ||
var opt = { peers: peers, localStorage: false, radisk: false }; | ||
var gunDB = Gun(opt); | ||
|
||
//Config for the GUN GunStreamer | ||
var streamer_config = { | ||
dbRecord: "gunmeeting",//The root of the streams | ||
streamId: "qvdev",//The user id you wanna stream | ||
gun: gunDB,//Gun instance | ||
debug: false//For debug logs | ||
} | ||
|
||
//GUN Streamer is the data side. It will convert data and write to GUN db | ||
const gunStreamer = new GunStreamer(streamer_config) | ||
|
||
//This is a callback function about the recording state, following states possible | ||
// STOPPED: 1¸ | ||
// RECORDING:2 | ||
// NOT_AVAILABLE:3 | ||
// UNKNOWN:4 | ||
var onRecordStateChange = function (state) { | ||
var recordButton = document.getElementById("record_button"); | ||
switch (state) { | ||
case recordSate.RECORDING: | ||
recordButton.innerText = "Stop recording"; | ||
break; | ||
default: | ||
recordButton.innerText = "Start recording"; | ||
break; | ||
} | ||
} | ||
|
||
//Config for the gun recorder | ||
var recorder_config = { | ||
video_id: "record_video",//Video html element id | ||
onDataAvailable: gunStreamer.onDataAvailable,//MediaRecorder data available callback | ||
onRecordStateChange: onRecordStateChange,//Callback for recording state | ||
audioBitsPerSecond: 6000,//Audio bits per second this is the lowest quality | ||
videoBitsPerSecond: 100000,//Video bits per second this is the lowest quality | ||
debug: false//For debug logs | ||
} | ||
|
||
//Init the recorder | ||
const gunRecorder = new GunRecorder(recorder_config); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
onmessage = e => { | ||
const message = e.data; | ||
parseSelf(message); | ||
}; | ||
|
||
var allClusterHex = ""; | ||
var initSegment = ""; | ||
|
||
function hex2buf(hex) { | ||
return new Uint8Array(hex.match(/[\da-f]{2}/gi).map(function (h) { | ||
return parseInt(h, 16) | ||
})).buffer | ||
} | ||
|
||
function buf2hex(buffer) { // buffer is an ArrayBuffer | ||
return Array.prototype.map.call(new Uint8Array(buffer), x => ('00' + x.toString(16)).slice(-2)).join(''); | ||
} | ||
|
||
function parseSelf(arrayBuffer) { | ||
|
||
var hex = buf2hex(arrayBuffer); | ||
var initSeg; | ||
|
||
var ebmlIndex = hex.indexOf("1a45dfa3"); | ||
var clusterIndex = hex.indexOf("1f43b675"); | ||
var trackIndex = hex.indexOf("1654ae6b"); | ||
var cuesIndex = hex.indexOf("1c53bb6b"); | ||
var segmentIndex = hex.indexOf("18538067"); | ||
var infoIndex = hex.indexOf("1549a966"); | ||
var seekIndex = hex.indexOf("114d9b74"); | ||
|
||
|
||
if (ebmlIndex == -1 && clusterIndex == -1 && trackIndex == -1 && cuesIndex == -1 && segmentIndex == -1 && infoIndex == -1 && seekIndex == -1) { | ||
allClusterHex += hex; | ||
|
||
} | ||
|
||
|
||
if (ebmlIndex != -1) { | ||
initSeg = hex.substring(ebmlIndex, clusterIndex); | ||
var initArray = new Uint8Array(hex2buf(initSeg)); | ||
var base64String = btoa( | ||
new Uint8Array(initArray) | ||
.reduce((onData, byte) => onData + String.fromCharCode(byte), '') | ||
); | ||
postMessage(base64String); | ||
} | ||
|
||
if (clusterIndex != -1) { | ||
if (allClusterHex.length != 0) { | ||
|
||
allClusterHex += hex.substring(0, clusterIndex); | ||
|
||
var clusters = new Uint8Array(hex2buf(allClusterHex)); | ||
|
||
var base64String = btoa(clusters.reduce((onData, byte) => onData + String.fromCharCode(byte), '')) | ||
|
||
allClusterHex = ""; | ||
postMessage(base64String); | ||
} | ||
var cluster = hex.substring(clusterIndex, hex.length); | ||
var dataIndex = cluster.indexOf("a3"); | ||
var clusterStartString = cluster.substring(0, dataIndex + 2); | ||
allClusterHex += cluster; | ||
} | ||
} |