Skip to content

Commit

Permalink
Initial commit of code
Browse files Browse the repository at this point in the history
  • Loading branch information
QVDev committed Sep 5, 2019
1 parent a54e954 commit c526392
Show file tree
Hide file tree
Showing 6 changed files with 371 additions and 1 deletion.
80 changes: 79 additions & 1 deletion README.md
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);
```
20 changes: 20 additions & 0 deletions index.html
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>
87 changes: 87 additions & 0 deletions js/GunRecorder.js
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);
}
}
}
74 changes: 74 additions & 0 deletions js/GunStreamer.js
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);
}
}

}
45 changes: 45 additions & 0 deletions js/integration.js
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);
66 changes: 66 additions & 0 deletions js/parser_worker.js
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;
}
}

0 comments on commit c526392

Please sign in to comment.