Compare commits
24 Commits
parcel-bun
...
master
Author | SHA1 | Date |
---|---|---|
xdrm-brackets | 5166847f77 | |
xdrm-brackets | b25d0b6249 | |
xdrm-brackets | 3f06b3dc39 | |
xdrm-brackets | d205985305 | |
xdrm-brackets | d21674e5ad | |
xdrm-brackets | 9695d30e5f | |
xdrm-brackets | bdbfa902fb | |
xdrm-brackets | fc0150cc74 | |
xdrm-brackets | 95d3487820 | |
xdrm-brackets | 066cae054e | |
xdrm-brackets | baf7804857 | |
xdrm-brackets | 04c6e37527 | |
xdrm-brackets | 9f6bc383bf | |
xdrm-brackets | 151e76b30a | |
xdrm-brackets | 3aab1a93ba | |
xdrm-brackets | 80e9bd2b05 | |
xdrm-brackets | 86864ab8b2 | |
xdrm-brackets | aa3d896ed9 | |
xdrm-brackets | 8cc640c47b | |
xdrm-brackets | 7d767343d6 | |
xdrm-brackets | a06e9c1789 | |
xdrm-brackets | f7d6e530b4 | |
xdrm-brackets | cdd2c42129 | |
xdrm-brackets | e783de3675 |
|
@ -5,4 +5,7 @@
|
||||||
/public_html/*
|
/public_html/*
|
||||||
!/public_htm/.htaccess
|
!/public_htm/.htaccess
|
||||||
/package-lock.json
|
/package-lock.json
|
||||||
.cache
|
.cache
|
||||||
|
/releases
|
||||||
|
/packages
|
||||||
|
/electron-build
|
|
@ -0,0 +1,98 @@
|
||||||
|
# Douscord
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Created for a study project in <u>Advanced web development</u>, we had to replicate [Discord](https://discordapp.com/) the famous text/vocal/video streaming platform. We had to use *WebSockets*, the *Java Persistence API* and the option we chose is to support *audio streaming* support. We worked on this project for 3-5 weeks.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
> This work is under the MIT Licence.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
**LEGAL ISSUES**
|
||||||
|
|
||||||
|
Discord is an open-source project, but we decided to "*copy*" its behavior without looking at its source. We dumped assets from the original website, any legal issue is protected by non-profit university work.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Manifest
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
##### Client
|
||||||
|
|
||||||
|
- author: `xdrm-brackets`
|
||||||
|
- repository: https://www.git.xdrm.io/MTI/discord-client
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
##### Server
|
||||||
|
|
||||||
|
- author: `SeekDaSky`
|
||||||
|
- repository: https://www.git.xdrm.io/MTI/discord-server
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Technologies
|
||||||
|
|
||||||
|
- Java Persistence API ([JPA](http://www.oracle.com/technetwork/java/javaee/tech/persistence-jsp-140049.html))
|
||||||
|
|
||||||
|
|
||||||
|
- WebSocket (*SeekDaSky'*s library [kWebSocket](https://git.seekdasky.ovh/SeekDaSKy/kWebSocket) written in [kotlin](https://kotlinlang.org/))
|
||||||
|
- [VueJS](https://vuejs.org/) a javascript template renderer framework to manage highly related components in real-time.
|
||||||
|
- [Electron](https://electronjs.org/) to build native cross-platform desktop applications from the client source.
|
||||||
|
- [Electron Packager](https://github.com/electron-userland/electron-packager) to build bundled out of the box working applications.
|
||||||
|
- [Parcel](https://github.com/parcel-bundler/parcel) bundler to build javascript dependencies for either the browser or electron.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Getting the app
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Linux
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### Debian / Ubuntu
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
###### With CURL
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl https://cloud.xdrm.io/index.php/s/bzR594w2MKyc83m/download -o /tmp/douscord.deb;
|
||||||
|
dpkg -i /tmp/douscord.deb && rm /tmp/douscord.deb;
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
###### With wget
|
||||||
|
|
||||||
|
```bash
|
||||||
|
wget https://cloud.xdrm.io/index.php/s/bzR594w2MKyc83m/download -o /tmp/douscord.deb;
|
||||||
|
dpkg -i /tmp/douscord.deb && rm /tmp/douscord.deb;
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
###### Or manually from [this link](https://cloud.xdrm.io/index.php/s/bzR594w2MKyc83m/download) then run the following command in the download folder:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo dpkg -i douscord.deb
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### From source
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl https://cloud.xdrm.io/index.php/s/bzR594w2MKyc83m/download -o /douscord-src;
|
||||||
|
sh ./douscord-src/install.sh;
|
||||||
|
```
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
const electron = require('electron');
|
||||||
|
const app = electron.app; // Module to control application life.
|
||||||
|
const BrowserWindow = electron.BrowserWindow; // Module to create native browser window.
|
||||||
|
const path = require('path');
|
||||||
|
const url = require('url');
|
||||||
|
|
||||||
|
// Keep a global reference of the window object, if you don't, the window will
|
||||||
|
// be closed automatically when the JavaScript object is garbage collected.
|
||||||
|
var mainWindow = null;
|
||||||
|
|
||||||
|
// Quit when all windows are closed.
|
||||||
|
app.on('window-all-closed', function() {
|
||||||
|
// On OS X it is common for applications and their menu bar
|
||||||
|
// to stay active until the user quits explicitly with Cmd + Q
|
||||||
|
if (process.platform != 'darwin') {
|
||||||
|
app.quit();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// This method will be called when Electron has finished
|
||||||
|
// initialization and is ready to create browser windows.
|
||||||
|
app.on('ready', function() {
|
||||||
|
// Create the browser window.
|
||||||
|
mainWindow = new BrowserWindow({width: 800, height: 600});
|
||||||
|
|
||||||
|
// and load the index.html of the app.
|
||||||
|
// mainWindow.loadURL('file://' + __dirname + '/index.html');
|
||||||
|
mainWindow.loadURL( url.format({
|
||||||
|
pathname: path.join(__dirname, 'index.html'),
|
||||||
|
protocol: 'file:',
|
||||||
|
slashes: true
|
||||||
|
}) );
|
||||||
|
// mainWindow.loadURL('http://douscord/');
|
||||||
|
|
||||||
|
// Emitted when the window is closed.
|
||||||
|
mainWindow.on('closed', function(){
|
||||||
|
// Dereference the window object, usually you would store windows
|
||||||
|
// in an array if your app supports multi windows, this is the time
|
||||||
|
// when you should delete the corresponding element.
|
||||||
|
mainWindow = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"name": "douscord",
|
||||||
|
"description": "Douscord non-copyright respectful copy",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"author": "xdrm-brackets <xdrm.brackets.dev@gmail.com> SeekDaSky <mascaro.lucas@yahoo.fr G. Fauvet <gfauvet@gmail.com>",
|
||||||
|
"license": "MIT",
|
||||||
|
"private": true,
|
||||||
|
"dependencies": {
|
||||||
|
"vue": "^2.5.9",
|
||||||
|
"vue-router": "^2.5.3"
|
||||||
|
},
|
||||||
|
"browserslist": [
|
||||||
|
"> 1%",
|
||||||
|
"last 2 versions",
|
||||||
|
"not ie <= 8"
|
||||||
|
],
|
||||||
|
"devDependencies": {
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# 1. Get + Go to current directory
|
||||||
|
ROOT="`realpath $(dirname $0)`";
|
||||||
|
cd $ROOT;
|
||||||
|
|
||||||
|
# 2. Install npm dependencies
|
||||||
|
echo -e "[1/3] installing dependencies";
|
||||||
|
npm --prefix=$ROOT install;
|
||||||
|
|
||||||
|
# 3. Build electron-app
|
||||||
|
echo -e "[2/3] building electron app";
|
||||||
|
npm --prefix=$ROOT run build:electron;
|
||||||
|
|
||||||
|
# 4. Create launching application
|
||||||
|
echo -e "[3/3] creating application shorcut";
|
||||||
|
DESKTOP_APP="[Desktop Entry]\n";
|
||||||
|
DESKTOP_APP+="Name=Douscord\n";
|
||||||
|
DESKTOP_APP+="GenericName=Douscord\n"
|
||||||
|
DESKTOP_APP+="Exec=/bin/bash -c 'cd /home/xdrm-brackets/ubuntu/git.xdrm.io/discord/client/; npm run electron;'\n";
|
||||||
|
DESKTOP_APP+="Terminal=false\n";
|
||||||
|
DESKTOP_APP+="Type=Application\n";
|
||||||
|
DESKTOP_APP+="Categories=Chat;Audio;Messages;Communication\n";
|
||||||
|
|
||||||
|
echo -e "$DESKTOP_APP" | sudo tee /usr/share/applications/douscord.desktop > /dev/null && echo ">>> INSTALLATION SUCCESSFUL <<<" || echo ">>> CANNOT CREATE SHORTUT <<<";
|
22
package.json
22
package.json
|
@ -1,19 +1,30 @@
|
||||||
{
|
{
|
||||||
"name": "ptut-vhost",
|
"name": "douscord",
|
||||||
"description": "PTUT",
|
"description": "PTUT",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"author": "xdrm-brackets <xdrm.brackets.dev@gmail.com> SeekDaSky <mascaro.lucas@yahoo.fr G. Fauvet <gfauvet@gmail.com>",
|
"author": "xdrm-brackets <xdrm.brackets.dev@gmail.com> SeekDaSky <mascaro.lucas@yahoo.fr G. Fauvet <gfauvet@gmail.com>",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"clean": "rm ./public_html/*.{js,css,html,svg,map}",
|
"clean": "rm ./public_html/*.html; rm ./public_html/*.js; rm ./public_html/*.css; rm ./public_html/*svg; rm ./public_html/*.map; exit 0",
|
||||||
|
"clean:all": "npm run clean; npm run clean:electron; npm run clean:release; npm run clean:package; exit 0",
|
||||||
|
"clean:electron": "rm -r ./electron-build; exit 0",
|
||||||
|
"clean:release": "rm -r ./releases; exit 0",
|
||||||
|
"clean:package": "rm -r ./package; exit 0",
|
||||||
"dev": "parcel watch ./parcel/index.html --out-dir ./public_html --no-hmr",
|
"dev": "parcel watch ./parcel/index.html --out-dir ./public_html --no-hmr",
|
||||||
"build:alternative": "cross-env NODE_ENV=production parcel watch ./parcel/index.html --out-dir ./public_html --no-hmr",
|
"build:alternative": "cross-env NODE_ENV=production parcel watch ./parcel/index.html --out-dir ./public_html --no-hmr",
|
||||||
"build": "parcel build ./parcel/index.html --out-dir ./public_html --no-source-maps --no-minify",
|
"build": "parcel build ./parcel/index.html --public-url ./ --out-dir ./public_html --no-source-maps --no-minify",
|
||||||
"build:electron": "parcel build ./parcel/index.html --out-dir ./public_html --no-source-maps --no-minify --target electron"
|
"build:electron": "parcel build ./parcel/index.html --public-url ./ --out-dir ./electron-build --no-source-maps --no-minify --target=electron; npm run build:electron:setup",
|
||||||
|
"build:electron:setup": "npm run build:electron:setup-config; npm run build:electron:setup-index;",
|
||||||
|
"build:electron:setup-config": "cp ./electron.json ./electron-build/package.json; npm --prefix ./electron-build install",
|
||||||
|
"build:electron:setup-index": "cp ./electron.js ./electron-build/index.js",
|
||||||
|
"electron": "electron ./electron-build",
|
||||||
|
"package": "npm run build:electron; electron-packager ./electron-build douscord --asar --platform linux --arch x64 --out ./releases --overwrite;",
|
||||||
|
"package:deb": "electron-installer-debian --src ./releases/douscord-linux-x64 --dest ./packages --arch x64"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"vue": "^2.5.9",
|
"vue": "^2.5.9",
|
||||||
|
"vue-hot-reload-api": "^2.3.0",
|
||||||
"vue-router": "^2.5.3"
|
"vue-router": "^2.5.3"
|
||||||
},
|
},
|
||||||
"browserslist": [
|
"browserslist": [
|
||||||
|
@ -28,6 +39,9 @@
|
||||||
"babel-preset-env": "^1.6.0",
|
"babel-preset-env": "^1.6.0",
|
||||||
"babel-preset-stage-3": "^6.24.1",
|
"babel-preset-stage-3": "^6.24.1",
|
||||||
"cross-env": "^5.0.5",
|
"cross-env": "^5.0.5",
|
||||||
|
"electron": "^1.8.4",
|
||||||
|
"electron-installer-debian": "^0.8.1",
|
||||||
|
"electron-packager": "^12.0.0",
|
||||||
"node-sass": "^4.8.3",
|
"node-sass": "^4.8.3",
|
||||||
"parcel-bundler": "^1.7.0",
|
"parcel-bundler": "^1.7.0",
|
||||||
"vue-template-compiler": "^2.5.16"
|
"vue-template-compiler": "^2.5.16"
|
||||||
|
|
|
@ -12,6 +12,9 @@ export default class AudioManager{
|
||||||
|
|
||||||
/* (2) Create the MASTER gain */
|
/* (2) Create the MASTER gain */
|
||||||
this.master = this.ctx.createGain();
|
this.master = this.ctx.createGain();
|
||||||
|
this.volume = this.ctx.createGain();
|
||||||
|
this.peaks = { low: 0, high: 0 };
|
||||||
|
this.volume_value = 1;
|
||||||
|
|
||||||
/* (3) Initialise input (typically bound from recorder) */
|
/* (3) Initialise input (typically bound from recorder) */
|
||||||
this.input = null;
|
this.input = null;
|
||||||
|
@ -56,6 +59,12 @@ export default class AudioManager{
|
||||||
/* (6) Set up our filters' parameters */
|
/* (6) Set up our filters' parameters */
|
||||||
this.setUpFilters();
|
this.setUpFilters();
|
||||||
|
|
||||||
|
/* (7) Initialise coordinator to manage received */
|
||||||
|
this.stack = [];
|
||||||
|
this.stack_size = 2;
|
||||||
|
this.fade_in = 0.1;
|
||||||
|
this.fade_out = 0.1;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -91,7 +100,6 @@ export default class AudioManager{
|
||||||
|
|
||||||
}.bind(this.dbg), this.dbg.interval*1000);
|
}.bind(this.dbg), this.dbg.interval*1000);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -104,27 +112,28 @@ export default class AudioManager{
|
||||||
---------------------------------------------------------*/
|
---------------------------------------------------------*/
|
||||||
/* (1) Setup EQ#1 -> voice clarity */
|
/* (1) Setup EQ#1 -> voice clarity */
|
||||||
this.filters.voice_clarity.type = 'peaking';
|
this.filters.voice_clarity.type = 'peaking';
|
||||||
this.filters.voice_clarity.frequency.value = 3000;
|
this.filters.voice_clarity.frequency.setValueAtTime(3000, this.ctx.currentTime);
|
||||||
this.filters.voice_clarity.Q.value = .8;
|
this.filters.voice_clarity.Q.setValueAtTime(.8, this.ctx.currentTime);
|
||||||
this.filters.voice_clarity.gain.value = 2;
|
this.filters.voice_clarity.gain.setValueAtTime(2, this.ctx.currentTime);
|
||||||
|
|
||||||
/* (2) Setup EQ#2 -> voice fullness */
|
/* (2) Setup EQ#2 -> voice fullness */
|
||||||
this.filters.voice_fullness.type = 'peaking';
|
this.filters.voice_fullness.type = 'peaking';
|
||||||
this.filters.voice_fullness.frequency.value = 200;
|
this.filters.voice_fullness.frequency.setValueAtTime(200, this.ctx.currentTime);
|
||||||
this.filters.voice_fullness.Q.value = .8;
|
this.filters.voice_fullness.Q.setValueAtTime(.8, this.ctx.currentTime);
|
||||||
this.filters.voice_fullness.gain.value = 2;
|
this.filters.voice_fullness.gain.setValueAtTime(2, this.ctx.currentTime);
|
||||||
|
|
||||||
/* (3) Setup EQ#3 -> reduce voice presence */
|
/* (3) Setup EQ#3 -> reduce voice presence */
|
||||||
this.filters.voice_presence.type = 'peaking';
|
this.filters.voice_presence.type = 'peaking';
|
||||||
this.filters.voice_presence.frequency.value = 5000;
|
this.filters.voice_presence.frequency.setValueAtTime(5000, this.ctx.currentTime);
|
||||||
this.filters.voice_presence.Q.value = .8;
|
this.filters.voice_presence.Q.setValueAtTime(.8, this.ctx.currentTime);
|
||||||
this.filters.voice_presence.gain.value = -2;
|
this.filters.voice_presence.gain.setValueAtTime(-2, this.ctx.currentTime);
|
||||||
|
|
||||||
/* (4) Setup EQ#3 -> reduce 'sss' metallic sound */
|
/* (4) Setup EQ#3 -> reduce 'sss' metallic sound */
|
||||||
this.filters.voice_sss.type = 'peaking';
|
this.filters.voice_sss.type = 'peaking';
|
||||||
this.filters.voice_sss.frequency.value = 7000;
|
this.filters.voice_sss.frequency.setValueAtTime(7000, this.ctx.currentTime);
|
||||||
this.filters.voice_sss.Q.value = .8;
|
this.filters.voice_sss.Q.setValueAtTime(.8, this.ctx.currentTime);
|
||||||
this.filters.voice_sss.gain.value = -8;
|
this.filters.voice_sss.gain.setValueAtTime(-8, this.ctx.currentTime);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* (2) Connect filters
|
/* (2) Connect filters
|
||||||
|
@ -151,18 +160,25 @@ export default class AudioManager{
|
||||||
/* (1) Disconnect all by default */
|
/* (1) Disconnect all by default */
|
||||||
this.input.disconnect();
|
this.input.disconnect();
|
||||||
|
|
||||||
/* (2) Get first filter */
|
/* (2) Also link to analyser */
|
||||||
|
this.input.connect(this.analyser);
|
||||||
|
/*Chrome fix*/this.network.out.connect(this.output);
|
||||||
|
|
||||||
|
/* (3) Get first filter */
|
||||||
let first_filter = this.filters.voice_clarity;
|
let first_filter = this.filters.voice_clarity;
|
||||||
let last_filter = this.filters.voice_sss;
|
let last_filter = this.filters.voice_sss;
|
||||||
|
|
||||||
/* (3) If unlink -> connect directly to NETWORK output */
|
/* (4) If unlink -> connect directly to NETWORK output */
|
||||||
if( unlink === true )
|
if( unlink === true )
|
||||||
return this.input.connect(this.network.out);
|
return this.input.connect(this.network.out);
|
||||||
|
|
||||||
/* (4) If linking -> connect input to filter stack */
|
/* (5) If linking -> connect input to volume */
|
||||||
this.input.connect(first_filter);
|
this.input.connect(this.volume);
|
||||||
|
|
||||||
/* (5) If linking -> connect stack end to network.out */
|
/* (6) If linking -> connect volume to filter stack */
|
||||||
|
this.volume.connect(first_filter);
|
||||||
|
|
||||||
|
/* (7) If linking -> connect stack end to network.out */
|
||||||
last_filter.connect(this.network.out);
|
last_filter.connect(this.network.out);
|
||||||
|
|
||||||
|
|
||||||
|
@ -185,9 +201,6 @@ export default class AudioManager{
|
||||||
/* (1) Link through filters */
|
/* (1) Link through filters */
|
||||||
this.linkFilters();
|
this.linkFilters();
|
||||||
|
|
||||||
/* (2) Also link to analyser */
|
|
||||||
this.input.connect(this.analyser);
|
|
||||||
|
|
||||||
|
|
||||||
gs.get.audio_conn = 2; // voice connected
|
gs.get.audio_conn = 2; // voice connected
|
||||||
|
|
||||||
|
@ -199,7 +212,62 @@ export default class AudioManager{
|
||||||
---------------------------------------------------------*/
|
---------------------------------------------------------*/
|
||||||
send(_audioprocess){
|
send(_audioprocess){
|
||||||
|
|
||||||
/* (1) Manage analyser
|
/* Exit here if not connected */
|
||||||
|
if( this.ws === null || this.ws.readyState !== 1 )
|
||||||
|
return;
|
||||||
|
|
||||||
|
|
||||||
|
/* (1) WebSocket send packet
|
||||||
|
---------------------------------------------------------*/
|
||||||
|
/* (1) Initialize buffer (Float32Array) */
|
||||||
|
let buf32 = new Float32Array(AudioManager.BUFFER_SIZE);
|
||||||
|
|
||||||
|
/* (2) Extract stream into buffer */
|
||||||
|
_audioprocess.inputBuffer.copyFromChannel(buf32, 0);
|
||||||
|
|
||||||
|
/* (4) Convert for WS connection (Int16Array) */
|
||||||
|
this.peaks.low = 0;
|
||||||
|
this.peaks.high = 0;
|
||||||
|
let buf16 = this.f32toi16(buf32);
|
||||||
|
|
||||||
|
/* (5) Send buffer through websocket */
|
||||||
|
this.ws.send(buf16);
|
||||||
|
|
||||||
|
/* (5) Adapt microphone volume if had peaks */
|
||||||
|
if( this.peaks.high > .01 ) // 30% saturation -> decrease
|
||||||
|
this.volume_value *= .8;
|
||||||
|
|
||||||
|
else if( this.peaks.low > .99 && this.volume_value*1.01 < 1 ) // 90% too low volume + less than 30% saturation -> increase
|
||||||
|
this.volume_value *= 1.01;
|
||||||
|
|
||||||
|
// apply new volume
|
||||||
|
this.volume.gain.setValueAtTime(this.volume_value, this.ctx.currentTime);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* (2) WebSocket buffer stack read
|
||||||
|
---------------------------------------------------------*/
|
||||||
|
setTimeout(function(){
|
||||||
|
|
||||||
|
/* (1) Pop too large stack */
|
||||||
|
this.stack.length > this.stack_size && this.stack.pop();
|
||||||
|
|
||||||
|
/* (2) Read input buffer stack */
|
||||||
|
if( this.stack.length > 0 ){
|
||||||
|
|
||||||
|
// 1. extract our source
|
||||||
|
let source_node = this.stack.shift();
|
||||||
|
|
||||||
|
// 2. Play source node
|
||||||
|
source_node.start();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}.bind(this), 0);
|
||||||
|
|
||||||
|
|
||||||
|
/* (3) Manage analyser
|
||||||
---------------------------------------------------------*/
|
---------------------------------------------------------*/
|
||||||
/* (1) Process only if 'freq_drawer' is set */
|
/* (1) Process only if 'freq_drawer' is set */
|
||||||
if( this.freq_drawer instanceof Function ){
|
if( this.freq_drawer instanceof Function ){
|
||||||
|
@ -211,7 +279,7 @@ export default class AudioManager{
|
||||||
this.analyser.getByteFrequencyData(freqArray);
|
this.analyser.getByteFrequencyData(freqArray);
|
||||||
|
|
||||||
// 3. Send to callback
|
// 3. Send to callback
|
||||||
this.freq_drawer(freqArray);
|
setTimeout(this.freq_drawer.bind(this,freqArray), 0);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -225,34 +293,17 @@ export default class AudioManager{
|
||||||
this.analyser.getByteTimeDomainData(waveArray);
|
this.analyser.getByteTimeDomainData(waveArray);
|
||||||
|
|
||||||
// 3. Send to callback
|
// 3. Send to callback
|
||||||
this.wave_drawer(waveArray);
|
setTimeout(this.wave_drawer.bind(this,waveArray), 0);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* (2) WebSocket send packet
|
|
||||||
---------------------------------------------------------*/
|
|
||||||
/* (1) Exit here if not connected */
|
|
||||||
if( this.ws === null || this.ws.readyState !== 1 )
|
|
||||||
return;
|
|
||||||
|
|
||||||
/* (2) Initialize buffer (Float32Array) */
|
|
||||||
let buf32 = new Float32Array(AudioManager.BUFFER_SIZE);
|
|
||||||
|
|
||||||
/* (3) Extract stream into buffer */
|
|
||||||
_audioprocess.inputBuffer.copyFromChannel(buf32, 0);
|
|
||||||
|
|
||||||
/* (4) Convert for WS connection (Int16Array) */
|
|
||||||
let buf16 = this.f32toi16(buf32);
|
|
||||||
|
|
||||||
/* (5) Send buffer through websocket */
|
|
||||||
this.ws.send(buf16);
|
|
||||||
|
|
||||||
|
|
||||||
// DEBUG
|
// DEBUG
|
||||||
this.dbg.data.packets_sent++;
|
this.dbg.data.packets_sent++;
|
||||||
this.dbg.data.kB_sent += buf16.length * 16. / 8 / 1024;
|
this.dbg.data.kB_sent += buf16.length * 16. / 8 / 1024;
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (5) Play received chunks (Int16Array)
|
/* (5) Play received chunks (Int16Array)
|
||||||
|
@ -280,11 +331,8 @@ export default class AudioManager{
|
||||||
source.connect(gain);
|
source.connect(gain);
|
||||||
gain.connect(this.master);
|
gain.connect(this.master);
|
||||||
|
|
||||||
/* (7) Start playing */
|
/* (7) Push in buffer stack */
|
||||||
source.start(this.ctx.currentTime);
|
this.stack.push(source);
|
||||||
|
|
||||||
this.dbg.data.packets_received++;
|
|
||||||
this.dbg.data.kB_received += _buffer.length * 16. / 8 / 1024;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -305,8 +353,15 @@ export default class AudioManager{
|
||||||
let i = 0, l = buf32.length;
|
let i = 0, l = buf32.length;
|
||||||
|
|
||||||
/* (3) Convert each value */
|
/* (3) Convert each value */
|
||||||
for( ; i < l ; i++ )
|
for( ; i < l ; i++ ){
|
||||||
buf16[i] = (buf32[i] < 0) ? 0x8000 * buf32[i] : 0x7FFF * buf32[i];
|
buf16[i] = (buf32[i] < 0) ? 0x8000 * buf32[i] : 0x7FFF * buf32[i];
|
||||||
|
( buf32[i] > 0.9 ) && ( this.peaks.high++ );
|
||||||
|
( buf32[i] < 0.1 ) && ( this.peaks.low++ );
|
||||||
|
}
|
||||||
|
|
||||||
|
/* (4) Report peaks in percentage */
|
||||||
|
this.peaks.high /= l;
|
||||||
|
this.peaks.low /= l;
|
||||||
|
|
||||||
return buf16;
|
return buf16;
|
||||||
}
|
}
|
||||||
|
@ -367,7 +422,7 @@ export default class AudioManager{
|
||||||
}.bind(this);
|
}.bind(this);
|
||||||
|
|
||||||
/* (3) Debug */
|
/* (3) Debug */
|
||||||
this.ws.onopen = () => ( gs.get.audio_conn = 1 ); // listening
|
this.ws.onopen = () => (gs.get.audio_conn !== 2 && (gs.get.audio_conn = 1)); // listening
|
||||||
this.ws.onclose = () => ( gs.get.audio_conn = null ); // disconnected
|
this.ws.onclose = () => ( gs.get.audio_conn = null ); // disconnected
|
||||||
|
|
||||||
|
|
||||||
|
@ -378,34 +433,60 @@ export default class AudioManager{
|
||||||
/* (9) Access microphone + launch all
|
/* (9) Access microphone + launch all
|
||||||
*
|
*
|
||||||
---------------------------------------------------------*/
|
---------------------------------------------------------*/
|
||||||
launch(wsAddress='wss://ws.douscord.xdrm.io/audio/2'){
|
launch(room_id=0){
|
||||||
|
|
||||||
/* (1) Start websocket */
|
/* (1) Start websocket */
|
||||||
this.wsconnect(wsAddress);
|
this.wsconnect(`wss://ws.douscord.xdrm.io/audio/${room_id}`);
|
||||||
|
|
||||||
|
/* (2) Set our streaming binding function */
|
||||||
|
let streaming_binding = function(stream){
|
||||||
|
|
||||||
|
this.recorder = new MediaRecorder(stream);
|
||||||
|
|
||||||
|
this.recorder.onstart = function(){
|
||||||
|
this.bindRecorderStream(stream);
|
||||||
|
console.warn('[audio] recording');
|
||||||
|
}.bind(this);
|
||||||
|
|
||||||
|
this.recorder.onstop = () => {
|
||||||
|
this.recorder.stream.getTracks().map( t => t.stop() );
|
||||||
|
this.recorder = null;
|
||||||
|
console.warn('[audio] stopped recording');
|
||||||
|
};
|
||||||
|
|
||||||
|
// start recording
|
||||||
|
this.recorder.start();
|
||||||
|
|
||||||
|
}.bind(this);
|
||||||
|
|
||||||
|
|
||||||
|
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
|
||||||
|
|
||||||
|
|
||||||
|
/* (3) If navigator.mediaDevices.getUserMedia */
|
||||||
if( navigator.mediaDevices && navigator.mediaDevices.getUserMedia ){
|
if( navigator.mediaDevices && navigator.mediaDevices.getUserMedia ){
|
||||||
|
|
||||||
navigator.mediaDevices.getUserMedia({ audio: true })
|
console.log('[audio] using "navigator.mediaDevices.getUserMedia"')
|
||||||
.then( stream => {
|
|
||||||
|
|
||||||
this.recorder = new MediaRecorder(stream);
|
return navigator.mediaDevices.getUserMedia({ audio: true })
|
||||||
this.bindRecorderStream(stream);
|
.then(streaming_binding)
|
||||||
|
.catch((e) => console.warn('[audio] microphone recorder issue', e));
|
||||||
|
|
||||||
this.recorder.onstart = () => console.warn('[audio] recording');
|
}
|
||||||
this.recorder.onstop = () => {
|
|
||||||
this.recorder.stream.getTracks().map( t => t.stop() );
|
|
||||||
this.recorder = null;
|
|
||||||
console.warn('[audio] stopped recording');
|
|
||||||
};
|
|
||||||
|
|
||||||
// start recording
|
/* (4) If old version */
|
||||||
this.recorder.start();
|
if( navigator.getUserMedia ){
|
||||||
|
|
||||||
})
|
console.log('[audio] using "navigator.getUserMedia"')
|
||||||
.catch( e => console.warn('[audio] microphone permission issue', e) );
|
|
||||||
|
|
||||||
}else
|
return navigator.getUserMedia({ audio: true },
|
||||||
console.warn('[audio] microphone not supported');
|
streaming_binding,
|
||||||
|
(e) => console.warn('[audio] microphone recorder issue', e));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
console.warn('[audio] recorder not supported');
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -416,10 +497,10 @@ export default class AudioManager{
|
||||||
kill(){
|
kill(){
|
||||||
|
|
||||||
/* (1) Close websocket */
|
/* (1) Close websocket */
|
||||||
this.ws.close();
|
this.ws && this.ws.close();
|
||||||
|
|
||||||
/* (2) Stop recording */
|
/* (2) Stop recording */
|
||||||
this.recorder.stop();
|
this.recorder && this.recorder.stop();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -46,20 +46,20 @@ export default class RoomController{
|
||||||
if( type === 'text' )
|
if( type === 'text' )
|
||||||
this[type].current = room.id;
|
this[type].current = room.id;
|
||||||
|
|
||||||
/* (5) Tell websocket: new text room */
|
/* (5) If 'voice' room -> toggle audio */
|
||||||
if( type === 'text' && window.csock instanceof wscd )
|
|
||||||
csock.send({ buffer: { rid: room.id } });
|
|
||||||
|
|
||||||
/* (6) If 'voice' room -> launch audio */
|
|
||||||
if( type === 'voice' ){
|
if( type === 'voice' ){
|
||||||
|
|
||||||
|
AudioManager.kill();
|
||||||
|
csock.send({ buffer: { audio: false } });
|
||||||
|
|
||||||
if( typeof this[type].current === 'number' )
|
if( typeof this[type].current === 'number' )
|
||||||
AudioManager.launch();
|
AudioManager.launch(this[type].current);
|
||||||
else
|
|
||||||
AudioManager.kill();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* (6) Tell websocket we connect to room */
|
||||||
|
if( typeof this[type].current === 'number' && window.csock instanceof wscd )
|
||||||
|
csock.send({ buffer: { rid: this[type].current } });
|
||||||
|
|
||||||
/* (6) Update buffer */
|
/* (6) Update buffer */
|
||||||
this._buffer[type] = {};
|
this._buffer[type] = {};
|
||||||
|
|
|
@ -182,7 +182,7 @@
|
||||||
|
|
||||||
display: none;
|
display: none;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: calc( 50% - 1em/2 );
|
top: calc( .5em );
|
||||||
left: calc( 100% - .5em - 1em );
|
left: calc( 100% - .5em - 1em );
|
||||||
width: 1em;
|
width: 1em;
|
||||||
height: 1em;
|
height: 1em;
|
||||||
|
@ -196,6 +196,45 @@
|
||||||
|
|
||||||
// only show 'remove' icon on hover
|
// only show 'remove' icon on hover
|
||||||
&:hover > span.rem{ display: block; }
|
&:hover > span.rem{ display: block; }
|
||||||
|
|
||||||
|
// Member List
|
||||||
|
& > div.member-list{
|
||||||
|
|
||||||
|
display: block;
|
||||||
|
position: relative;
|
||||||
|
width: calc( 100% - 1em );
|
||||||
|
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
& > span{
|
||||||
|
|
||||||
|
display: inline-block;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
& > span{
|
||||||
|
display: inline-block;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
margin-top: -1.9em;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
div.icon{
|
||||||
|
display: inline-block;
|
||||||
|
position: relative;
|
||||||
|
width: 1.2em;
|
||||||
|
height: 1.2em;
|
||||||
|
|
||||||
|
margin: .2em .5em;
|
||||||
|
margin-left: 0;
|
||||||
|
|
||||||
|
border-radius: 50% / 50%;
|
||||||
|
|
||||||
|
background-color: url() center center no-repeat;
|
||||||
|
background-size: contain;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -35,6 +35,12 @@
|
||||||
:data-type='r.type'
|
:data-type='r.type'
|
||||||
@click='gs.room.nav(r.type, r.id)'>{{ r.name }}
|
@click='gs.room.nav(r.type, r.id)'>{{ r.name }}
|
||||||
<span class='rem' @click="gs.popup.show('room.remove'); gs.popup.get('room.remove').data=r"></span>
|
<span class='rem' @click="gs.popup.show('room.remove'); gs.popup.get('room.remove').data=r"></span>
|
||||||
|
<div v-if='r.type===`voice`' v-show='r.members.length>0' class='member-list'>
|
||||||
|
<span v-for='uid in r.members'>
|
||||||
|
<div class='icon' :style='`background-image: url("https://picsum.photos/150/?random&nonce=${uid}");`'></div>
|
||||||
|
<span>{{ ( uid == gs.auth.user.uid ) ? 'You' : gs.content.user(uid).username }} </span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
@ -138,23 +144,25 @@ export default {
|
||||||
/* (2) Adjust dimensions */
|
/* (2) Adjust dimensions */
|
||||||
can.width = buffer.length;
|
can.width = buffer.length;
|
||||||
can.height = 256;
|
can.height = 256;
|
||||||
let precision = 5; // bigger -> less precise
|
let precision = 1; // bigger -> less precise
|
||||||
|
|
||||||
/* (3) Erase previous drawing */
|
/* (3) Erase previous drawing */
|
||||||
ctx.clearRect(0, 0, can.width, can.height);
|
ctx.clearRect(0, 0, can.width, can.height);
|
||||||
|
ctx.lineWidth = 1;
|
||||||
|
|
||||||
/* (4) Begin tracing */
|
/* (4) Trace through each value */
|
||||||
ctx.beginPath();
|
for( let i = precision ; i < buffer.length ; i+=precision ){
|
||||||
ctx.moveTo(0, can.height-128-buffer[0]/2);
|
|
||||||
|
|
||||||
/* (5) Trace through each value */
|
ctx.beginPath();
|
||||||
for( let i = precision ; i < buffer.length ; i+=precision )
|
ctx.strokeStyle = '#44484f';
|
||||||
ctx.lineTo(i, can.height-128-buffer[i]/2);
|
|
||||||
|
ctx.moveTo(i, can.height);
|
||||||
|
ctx.lineTo(i, can.height-buffer[i]);
|
||||||
|
|
||||||
|
ctx.stroke();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/* (6) End tracing */
|
|
||||||
ctx.lineWidth = 7;
|
|
||||||
ctx.strokeStyle = '#44484f';
|
|
||||||
ctx.stroke();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,35 +0,0 @@
|
||||||
<html>
|
|
||||||
|
|
||||||
<!-- HEADER -->
|
|
||||||
<head>
|
|
||||||
<title>Douscord</title>
|
|
||||||
|
|
||||||
<!-- META -->
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="author" content="xdrm-brackets (Adrien Marquès)">
|
|
||||||
<meta name="description" content="[Home] Home page">
|
|
||||||
|
|
||||||
<!-- STYLESHEET -->
|
|
||||||
<link type="text/css" rel="stylesheet" href="/layout.0156ff23.css">
|
|
||||||
<link type="text/css" rel="stylesheet" href="/global.60d0b554.css">
|
|
||||||
<link type="text/css" rel="stylesheet" href="/menu.2dbbff61.css">
|
|
||||||
<link type="text/css" rel="stylesheet" href="/dialog.b6510e56.css">
|
|
||||||
<link type="text/css" rel="stylesheet" href="/side-menu.d44973a4.css">
|
|
||||||
<link type="text/css" rel="stylesheet" href="/container.5709ee19.css">
|
|
||||||
<link type="text/css" rel="stylesheet" href="/pop-up.65b1ed2f.css">
|
|
||||||
|
|
||||||
<!-- FONT -->
|
|
||||||
<link href="https://fonts.googleapis.com/css?family=Source+Sans+Pro" rel="stylesheet">
|
|
||||||
|
|
||||||
<!-- BODY -->
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<div id="vue"></div>
|
|
||||||
|
|
||||||
|
|
||||||
<script type="text/javascript" src="/main.2909f75e.js"></script>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
|
|
||||||
</html>
|
|
Loading…
Reference in New Issue