mirror of
https://gitlab.com/wgp/dougal/software.git
synced 2025-12-06 12:57:08 +00:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1f8157cc5a | ||
|
|
b72b55c6dd |
194
lib/www/client/source/src/lib/rtc.js
Normal file
194
lib/www/client/source/src/lib/rtc.js
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
|
||||||
|
let ws = null;
|
||||||
|
let peerId = null;
|
||||||
|
let peers = {};
|
||||||
|
let stream = null;
|
||||||
|
|
||||||
|
function init (socket) {
|
||||||
|
ws = socket;
|
||||||
|
ws.addEventListener("message", (ev) => {
|
||||||
|
try {
|
||||||
|
const payload = JSON.parse(ev.data);
|
||||||
|
if (payload.rtc === true) {
|
||||||
|
// Handle this message
|
||||||
|
handle (payload);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Invalid message", ev, err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function talk () {
|
||||||
|
try {
|
||||||
|
if (!stream) {
|
||||||
|
const constraints = { audio: true, video: false };
|
||||||
|
stream = await navigator.mediaDevices.getUserMedia(constraints);
|
||||||
|
console.log("Grabbed stream", stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (peerId && Object.keys(peers).length) {
|
||||||
|
for (const track of stream.getTracks()) {
|
||||||
|
for (const peer in peers) {
|
||||||
|
peers[peer].addTrack(track, stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("talk() error", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PeerConnection {
|
||||||
|
|
||||||
|
constructor (otherPeerId) {
|
||||||
|
this.otherPeerId = otherPeerId;
|
||||||
|
this.makingOffer = false;
|
||||||
|
this.polite = this.otherPeerId > peerId;
|
||||||
|
this.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
send (message) {
|
||||||
|
message.from = peerId;
|
||||||
|
message.to = this.otherPeerId;
|
||||||
|
send(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
async handle (message) {
|
||||||
|
try {
|
||||||
|
let ignoreOffer = false;
|
||||||
|
|
||||||
|
if ("description" in message) {
|
||||||
|
const offerCollision = (message.description.type == "offer") &&
|
||||||
|
(this.makingOffer || this.conn.signalingState != "stable");
|
||||||
|
|
||||||
|
ignoreOffer = !this.polite && offerCollision;
|
||||||
|
|
||||||
|
if (ignoreOffer) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.conn.setRemoteDescription(message.description);
|
||||||
|
if (message.description.type == "offer") {
|
||||||
|
await this.conn.setLocalDescription();
|
||||||
|
this.send({ description: this.conn.localDescription });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("candidate" in message) {
|
||||||
|
try {
|
||||||
|
await this.conn.addIceCandidate(message.candidate);
|
||||||
|
} catch (err) {
|
||||||
|
if (!ignoreOffer) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
start () {
|
||||||
|
const config = { iceServers: [] };
|
||||||
|
this.conn = new RTCPeerConnection(config);
|
||||||
|
console.log("Have peer connection", this.conn);
|
||||||
|
|
||||||
|
this.conn.ontrack = ({track, streams}) => {
|
||||||
|
// FIXME Need to remove these elements when done
|
||||||
|
if (!this.remoteAudio) {
|
||||||
|
this.remoteAudio = document.createElement("audio");
|
||||||
|
this.remoteAudio.setAttribute("autoplay", "true");
|
||||||
|
this.remoteAudio.controls = true;
|
||||||
|
document.getElementsByTagName("footer")[0].appendChild(this.remoteAudio);
|
||||||
|
console.log("Added <audio> element", this.remoteAudio);
|
||||||
|
}
|
||||||
|
|
||||||
|
track.onunmute = () => {
|
||||||
|
if (this.remoteAudio.srcObject) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.remoteAudio.srcObject = streams[0];
|
||||||
|
console.log("unmuted");
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
this.conn.onnegotiationneeded = async () => {
|
||||||
|
console.log("negotiation needed");
|
||||||
|
try {
|
||||||
|
this.makingOffer = true;
|
||||||
|
await this.conn.setLocalDescription();
|
||||||
|
this.send({ description: this.conn.localDescription });
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
} finally {
|
||||||
|
this.makingOffer = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.conn.oniceconnectionstatechange = () => {
|
||||||
|
console.log("state change");
|
||||||
|
if (this.conn.iceConnectionState == "failed") {
|
||||||
|
this.conn.restartIce();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.conn.onicecandidate = async ({candidate}) => {
|
||||||
|
console.log("send candidate", candidate);
|
||||||
|
this.send({candidate});
|
||||||
|
};
|
||||||
|
|
||||||
|
if (stream) {
|
||||||
|
for (const track of stream.getAudioTracks()) {
|
||||||
|
this.addTrack(track, stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
addTrack (track, stream) {
|
||||||
|
console.log("add track to connection", this.conn, track, stream);
|
||||||
|
if (this.conn) {
|
||||||
|
this.conn.addTrack(track, stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
function send (message) {
|
||||||
|
console.log("Send message", message, "via", ws);
|
||||||
|
if (ws) {
|
||||||
|
message.rtc = true;
|
||||||
|
ws.send(JSON.stringify(message));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handle (message) {
|
||||||
|
console.log("Handle message", message);
|
||||||
|
|
||||||
|
if ("peerId" in message) {
|
||||||
|
peerId = message.peerId;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("otherPeers" in message) {
|
||||||
|
for (const peer of message.otherPeers) {
|
||||||
|
if (peer in peers) continue;
|
||||||
|
|
||||||
|
peers[peer] = new PeerConnection(peer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("newPeer" in message) {
|
||||||
|
peers[message.newPeer] = new PeerConnection(message.newPeer);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("to" in message && "from" in message && message.to == peerId) {
|
||||||
|
peers[message.from].handle(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
init, talk
|
||||||
|
};
|
||||||
@@ -6,6 +6,8 @@ import vuetify from './plugins/vuetify'
|
|||||||
import vueDebounce from 'vue-debounce'
|
import vueDebounce from 'vue-debounce'
|
||||||
import { mapMutations } from 'vuex';
|
import { mapMutations } from 'vuex';
|
||||||
|
|
||||||
|
import rtc from './lib/rtc';
|
||||||
|
|
||||||
|
|
||||||
Vue.config.productionTip = false
|
Vue.config.productionTip = false
|
||||||
|
|
||||||
@@ -51,12 +53,18 @@ new Vue({
|
|||||||
|
|
||||||
this.ws.addEventListener("message", (ev) => {
|
this.ws.addEventListener("message", (ev) => {
|
||||||
const msg = JSON.parse(ev.data);
|
const msg = JSON.parse(ev.data);
|
||||||
this.setServerEvent(msg);
|
if (msg.rtc === true) {
|
||||||
|
// Handle WebRTC message
|
||||||
|
} else {
|
||||||
|
this.setServerEvent(msg);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.ws.addEventListener("open", (ev) => {
|
this.ws.addEventListener("open", (ev) => {
|
||||||
console.log("WebSocket connection open", ev);
|
console.log("WebSocket connection open", ev);
|
||||||
this.setServerConnectionState(true);
|
this.setServerConnectionState(true);
|
||||||
|
rtc.init(this.ws);
|
||||||
|
rtc.talk();
|
||||||
});
|
});
|
||||||
|
|
||||||
this.ws.addEventListener("close", (ev) => {
|
this.ws.addEventListener("close", (ev) => {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
const ws = require('ws');
|
const ws = require('ws');
|
||||||
const URL = require('url');
|
const URL = require('url');
|
||||||
const db = require('./db');
|
const db = require('./db');
|
||||||
|
const ptt = require('./ptt');
|
||||||
|
|
||||||
function start (server, pingInterval=30000) {
|
function start (server, pingInterval=30000) {
|
||||||
|
|
||||||
@@ -16,7 +17,9 @@ function start (server, pingInterval=30000) {
|
|||||||
wsServer.on('connection', socket => {
|
wsServer.on('connection', socket => {
|
||||||
socket.alive = true;
|
socket.alive = true;
|
||||||
socket.on('pong', function () { this.alive = true; })
|
socket.on('pong', function () { this.alive = true; })
|
||||||
socket.on('message', message => console.log(message));
|
socket.on('message', (message) => ptt.handler(socket, message));
|
||||||
|
socket.on('close', () => ptt.removeConnection(socket));
|
||||||
|
ptt.addConnection(socket);
|
||||||
});
|
});
|
||||||
|
|
||||||
server.on('upgrade', (request, socket, head) => {
|
server.on('upgrade', (request, socket, head) => {
|
||||||
|
|||||||
62
lib/www/server/ws/ptt.js
Normal file
62
lib/www/server/ws/ptt.js
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
|
||||||
|
|
||||||
|
const connections = [];
|
||||||
|
|
||||||
|
function broadcast (message, sender) {
|
||||||
|
message.rtc = true;
|
||||||
|
const body = JSON.stringify(message);
|
||||||
|
for (const socket of connections) {
|
||||||
|
if (socket != sender) {
|
||||||
|
socket.send(body);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function unicast (message, socket) {
|
||||||
|
message.rtc = true;
|
||||||
|
socket.send(JSON.stringify(message));
|
||||||
|
}
|
||||||
|
|
||||||
|
function addConnection (socket) {
|
||||||
|
if (!connections.includes(socket)) {
|
||||||
|
const peerId = Math.round(Math.random()*1000000000);
|
||||||
|
const otherPeers = connections.map(c => c.peerId);
|
||||||
|
broadcast({newPeer: peerId}, socket);
|
||||||
|
unicast({peerId, otherPeers}, socket);
|
||||||
|
socket.peerId = peerId;
|
||||||
|
connections.push(socket);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeConnection (socket) {
|
||||||
|
const pos = connections.indexOf(socket);
|
||||||
|
|
||||||
|
if (pos != -1) {
|
||||||
|
connections.splice(pos, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handler (socket, message) {
|
||||||
|
try {
|
||||||
|
const payload = JSON.parse(message);
|
||||||
|
if (payload.rtc === true) {
|
||||||
|
console.log("RTC Message", payload);
|
||||||
|
if ("to" in payload) {
|
||||||
|
const dest = connections.find(c => c.peerId == payload.to);
|
||||||
|
if (dest) {
|
||||||
|
unicast(payload, dest);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
broadcast(payload, socket);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Invalid message", message, err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
addConnection,
|
||||||
|
removeConnection,
|
||||||
|
handler
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user