|
|
|
@ -1,3 +1,5 @@
|
|
|
|
|
var printStats = false; |
|
|
|
|
|
|
|
|
|
var socket = null; |
|
|
|
|
var ReconnectDelay = 0; |
|
|
|
|
var reconnectTimeout; |
|
|
|
@ -6,14 +8,15 @@ var connected;
|
|
|
|
|
var chatprefix = ""; |
|
|
|
|
var voice = false; |
|
|
|
|
var ptt = false; |
|
|
|
|
var printStats = false; |
|
|
|
|
var nickname = "Anonymous"; |
|
|
|
|
|
|
|
|
|
var pc; |
|
|
|
|
var numConnections = 3; |
|
|
|
|
var peerConnections = []; |
|
|
|
|
var RTCConstraints = { |
|
|
|
|
audio: { |
|
|
|
|
autoGainControl: true, |
|
|
|
|
echoCancellation: true, |
|
|
|
|
noiseSuppression: true, |
|
|
|
|
autoGainControl: false, |
|
|
|
|
echoCancellation: false, |
|
|
|
|
noiseSuppression: false, |
|
|
|
|
}, |
|
|
|
|
video: false |
|
|
|
|
}; |
|
|
|
@ -28,6 +31,8 @@ var audioTrack;
|
|
|
|
|
var shownPTTHelp = false; |
|
|
|
|
var muteOnMouseUp = true; |
|
|
|
|
|
|
|
|
|
var userListStatus = ''; |
|
|
|
|
|
|
|
|
|
var MessageBinary = 2; |
|
|
|
|
var MessagePing = 100; |
|
|
|
|
var MessageCall = 101; |
|
|
|
@ -40,6 +45,7 @@ var MessageTopic = 114;
|
|
|
|
|
var MessageAction = 115; |
|
|
|
|
var MessageDisconnect = 119; |
|
|
|
|
var MessageChat = 120; |
|
|
|
|
var MessageUsers = 121; |
|
|
|
|
|
|
|
|
|
var tagsToReplace = { |
|
|
|
|
'&': '&', |
|
|
|
@ -75,55 +81,14 @@ function HandleInput(e) {
|
|
|
|
|
|
|
|
|
|
$(document).ready(function () { |
|
|
|
|
$("#voiceButtonJoin").on("click touchstart", function () { |
|
|
|
|
pc = new RTCPeerConnection({ |
|
|
|
|
iceServers: RTCICEServers |
|
|
|
|
}); |
|
|
|
|
pc.onicecandidate = event => { |
|
|
|
|
if (event.candidate === null) { |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
navigator.mediaDevices.getUserMedia(RTCConstraints).then(localStream => { |
|
|
|
|
audioTracks = localStream.getAudioTracks(); |
|
|
|
|
if (audioTracks.length > 0) { |
|
|
|
|
console.log(`Using Audio device: ${audioTracks[0].label} - tracks available: ${audioTracks}`); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
audioTrack = audioTracks[0]; |
|
|
|
|
|
|
|
|
|
pc.addTrack(audioTrack); |
|
|
|
|
pc.getSenders()[0].replaceTrack(null); |
|
|
|
|
|
|
|
|
|
pc.ontrack = function (event) { |
|
|
|
|
console.log(`onTrack ${event.streams.length} ${event.streams[0].getAudioTracks().length}`); |
|
|
|
|
|
|
|
|
|
pc.addTransceiver(event.streams[0].getAudioTracks()[0], {'direction': 'sendrecv'}); |
|
|
|
|
|
|
|
|
|
var el = document.getElementById('audioplayer'); |
|
|
|
|
el.autoplay = true; |
|
|
|
|
el.srcObject = event.streams[0]; |
|
|
|
|
|
|
|
|
|
voice = true; |
|
|
|
|
|
|
|
|
|
if (!shownPTTHelp) { |
|
|
|
|
shownPTTHelp = true; |
|
|
|
|
|
|
|
|
|
Log("* Note: Push-to-talk is bound to <F8>"); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
$('#voicepttcontainer').css('display', 'table-row'); |
|
|
|
|
$('#voiceinactiveleft').css('display', 'none'); |
|
|
|
|
$('#voiceactiveleft').css('display', 'inline-block'); |
|
|
|
|
$('#voiceinactiveright').css('display', 'none'); |
|
|
|
|
$('#voiceactiveright').css('display', 'inline-block'); |
|
|
|
|
$('#voiceButton').html('Quit voice chat'); |
|
|
|
|
|
|
|
|
|
updateVoiceStatus(); |
|
|
|
|
}; |
|
|
|
|
if (peerConnections.length > 0 || voice) { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pc.createOffer(RTCOfferOptions) |
|
|
|
|
.then(onRTCDescription, onRTCDescriptionError); |
|
|
|
|
}).catch(Log); |
|
|
|
|
var i; |
|
|
|
|
for (i = 0; i < numConnections; i++) { |
|
|
|
|
peerConnections.push(createPeerConnection(i)); |
|
|
|
|
} |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
$("#voiceptt").on("touchstart", function (e) { |
|
|
|
@ -165,10 +130,18 @@ $(document).ready(function () {
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
$("#voiceButtonQuit").on("click touchstart", function () { |
|
|
|
|
pc.close(); |
|
|
|
|
w(MessageQuit, ""); |
|
|
|
|
if (!voice) { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
voice = false; |
|
|
|
|
|
|
|
|
|
w(MessageQuit, ""); |
|
|
|
|
|
|
|
|
|
peerConnections.forEach(function (pc) { |
|
|
|
|
pc.close(); |
|
|
|
|
}); |
|
|
|
|
peerConnections = []; |
|
|
|
|
|
|
|
|
|
$('#voicepttcontainer').css('display', 'none'); |
|
|
|
|
$('#voiceinactiveleft').css('display', 'inline-block'); |
|
|
|
|
$('#voiceactiveleft').css('display', 'none'); |
|
|
|
@ -179,8 +152,6 @@ $(document).ready(function () {
|
|
|
|
|
updateVoiceStatus(); |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
Connect(); |
|
|
|
|
|
|
|
|
|
window.setInterval(() => { |
|
|
|
|
if (!webSocketReady()) { |
|
|
|
|
return; |
|
|
|
@ -191,6 +162,8 @@ $(document).ready(function () {
|
|
|
|
|
|
|
|
|
|
if (printStats) { |
|
|
|
|
window.setInterval(() => { |
|
|
|
|
// TODO Fix
|
|
|
|
|
|
|
|
|
|
if (!pc) { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
@ -223,24 +196,103 @@ $(document).ready(function () {
|
|
|
|
|
}); |
|
|
|
|
}, 1000); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
nickname = prompt("What is your name?", nickname); |
|
|
|
|
Connect(); |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
function onRTCDescriptionError(error) { |
|
|
|
|
console.log(`Failed to create/set session description: ${error.toString()}`); |
|
|
|
|
function createPeerConnection(id) { |
|
|
|
|
var pc = new RTCPeerConnection({ |
|
|
|
|
iceServers: RTCICEServers |
|
|
|
|
}); |
|
|
|
|
pc.onicecandidate = event => { |
|
|
|
|
if (event.candidate === null) { |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
if (id > 0) { |
|
|
|
|
pc.addTransceiver('audio', {'direction': 'recvonly'}); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pc.ontrack = function (event) { |
|
|
|
|
console.log(`PC ${id} onTrack ${event.streams.length} ${event.streams[0].id} ${event.streams[0].getAudioTracks().length}`); |
|
|
|
|
|
|
|
|
|
if (id > 0) { |
|
|
|
|
pc.addTransceiver(event.streams[0].getAudioTracks()[0], {'direction': 'sendrecv'}); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
$("#hidden").append('<audio id="audiostream' + id + '"></audio>'); |
|
|
|
|
|
|
|
|
|
var el = document.getElementById('audiostream' + id); |
|
|
|
|
el.autoplay = true; |
|
|
|
|
el.srcObject = event.streams[0]; |
|
|
|
|
|
|
|
|
|
voice = true; |
|
|
|
|
|
|
|
|
|
if (id == 0 && !shownPTTHelp) { |
|
|
|
|
shownPTTHelp = true; |
|
|
|
|
|
|
|
|
|
Log("* Note: Push-to-talk is bound to <F8>"); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
$('#voicepttcontainer').css('display', 'table-row'); |
|
|
|
|
$('#voiceinactiveleft').css('display', 'none'); |
|
|
|
|
$('#voiceactiveleft').css('display', 'inline-block'); |
|
|
|
|
$('#voiceinactiveright').css('display', 'none'); |
|
|
|
|
$('#voiceactiveright').css('display', 'inline-block'); |
|
|
|
|
$('#voiceButton').html('Quit voice chat'); |
|
|
|
|
|
|
|
|
|
updateVoiceStatus(); |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
if (id == 0) { |
|
|
|
|
navigator.mediaDevices.getUserMedia(RTCConstraints).then(localStream => { |
|
|
|
|
audioTracks = localStream.getAudioTracks(); |
|
|
|
|
if (audioTracks.length > 0) { |
|
|
|
|
console.log(`PC ${id} Using Audio device: ${audioTracks[0].label} - tracks available: ${audioTracks}`); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
audioTrack = audioTracks[0]; |
|
|
|
|
|
|
|
|
|
pc.addTrack(audioTrack); |
|
|
|
|
pc.getSenders()[0].replaceTrack(null); |
|
|
|
|
|
|
|
|
|
pc.createOffer(RTCOfferOptions) |
|
|
|
|
.then(onRTCDescription(id), onRTCDescriptionError(id)); |
|
|
|
|
}).catch(Log); |
|
|
|
|
} else { |
|
|
|
|
pc.createOffer(RTCOfferOptions) |
|
|
|
|
.then(onRTCDescription(id), onRTCDescriptionError(id)); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return pc; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function onRTCDescription(desc) { |
|
|
|
|
console.log(`Offer from pc\n${desc.sdp}`); |
|
|
|
|
pc.setLocalDescription(desc) |
|
|
|
|
.then(() => { |
|
|
|
|
desc.sdp = maybePreferCodec(desc.sdp, 'audio', 'send', 'opus'); |
|
|
|
|
desc.sdp = maybePreferCodec(desc.sdp, 'audio', 'receive', 'opus'); |
|
|
|
|
function onRTCDescriptionError(id) { |
|
|
|
|
return function (error) { |
|
|
|
|
console.log(`Failed to create/set session description: ${error.toString()}`); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function onRTCDescription(id) { |
|
|
|
|
return function (desc) { |
|
|
|
|
console.log(`PC ${id} Offer received \n${desc.sdp}`); |
|
|
|
|
|
|
|
|
|
if (peerConnections.length < id) { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
console.log("onRTCDescription"); |
|
|
|
|
console.log(desc); |
|
|
|
|
peerConnections[id].setLocalDescription(desc) |
|
|
|
|
.then(() => { |
|
|
|
|
desc.sdp = maybePreferCodec(desc.sdp, 'audio', 'send', 'opus'); |
|
|
|
|
desc.sdp = maybePreferCodec(desc.sdp, 'audio', 'receive', 'opus'); |
|
|
|
|
|
|
|
|
|
w(MessageCall, desc.sdp); |
|
|
|
|
}, onRTCDescriptionError); |
|
|
|
|
console.log(`PC ${id} onRTCDescription`); |
|
|
|
|
|
|
|
|
|
wpc(id, MessageCall, desc.sdp); |
|
|
|
|
}, onRTCDescriptionError); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function Connect() { |
|
|
|
@ -275,6 +327,8 @@ function Connect() {
|
|
|
|
|
clearTimeout(reconnectTimeout); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
w(MessageNick, nickname); |
|
|
|
|
|
|
|
|
|
updateVoiceStatus(); |
|
|
|
|
}; |
|
|
|
|
socket.onmessage = function (e) { |
|
|
|
@ -290,17 +344,59 @@ function Connect() {
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (p.T == MessageAnswer) { |
|
|
|
|
if (p.PC === undefined || p.PC > peerConnections.length) { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
var pc = peerConnections[p.PC]; |
|
|
|
|
pc.setRemoteDescription(new RTCSessionDescription({type: 'answer', sdp: p.M})); |
|
|
|
|
} else if (p.T == MessageConnect) { |
|
|
|
|
Log("* " + p.M + " connected"); |
|
|
|
|
if (p.N === undefined) { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
Log("* " + escapeEntities(p.N) + " connected"); |
|
|
|
|
} else if (p.T == MessageJoin) { |
|
|
|
|
Log("* " + p.M + " joined #lobby voice chat"); |
|
|
|
|
if (p.N === undefined) { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
Log("* " + escapeEntities(p.N) + " joined #lobby voice chat"); |
|
|
|
|
} else if (p.T == MessageQuit) { |
|
|
|
|
Log("* " + p.M + " quit #lobby voice chat"); |
|
|
|
|
if (p.N === undefined) { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
Log("* " + escapeEntities(p.N) + " quit #lobby voice chat"); |
|
|
|
|
} else if (p.T == MessageDisconnect) { |
|
|
|
|
Log("* " + p.M + " disconnected"); |
|
|
|
|
if (p.N === undefined) { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
Log("* " + escapeEntities(p.N) + " disconnected"); |
|
|
|
|
} else if (p.T == MessageChat) { |
|
|
|
|
Log("<Anonymous> " + escapeEntities(p.M)); |
|
|
|
|
if (p.N === undefined) { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
Log("<" + escapeEntities(p.N) + "> " + escapeEntities(p.M)); |
|
|
|
|
} else if (p.T == MessageUsers) { |
|
|
|
|
var usersconnected = 0; |
|
|
|
|
var usersvoice = 0; |
|
|
|
|
|
|
|
|
|
var u = JSON.parse(p.M); |
|
|
|
|
for (let i = 0; i < u.length; i++) { |
|
|
|
|
usersconnected++; |
|
|
|
|
|
|
|
|
|
if (u[i].V) { |
|
|
|
|
usersvoice++; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
userListStatus = "Users: " + usersconnected + " - Voice chatting: " + usersvoice; |
|
|
|
|
|
|
|
|
|
$("#voiceinactiveright").html(userListStatus); |
|
|
|
|
updateVoiceStatus(); |
|
|
|
|
} |
|
|
|
|
} else { |
|
|
|
|
// TODO Binary data
|
|
|
|
@ -354,26 +450,26 @@ function Log(msg) {
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function StartPTT() { |
|
|
|
|
if (ptt) { |
|
|
|
|
if (ptt || !voice || peerConnections.length == 0) { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
ptt = true; |
|
|
|
|
|
|
|
|
|
var sender = pc.getSenders()[0]; |
|
|
|
|
var sender = peerConnections[0].getSenders()[0]; |
|
|
|
|
sender.replaceTrack(audioTrack); |
|
|
|
|
|
|
|
|
|
updateVoiceStatus(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function StopPTT() { |
|
|
|
|
if (!ptt) { |
|
|
|
|
if (!ptt || !voice || peerConnections.length == 0) { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
ptt = false; |
|
|
|
|
|
|
|
|
|
var sender = pc.getSenders()[0]; |
|
|
|
|
var sender = peerConnections[0].getSenders()[0]; |
|
|
|
|
sender.replaceTrack(null); |
|
|
|
|
|
|
|
|
|
updateVoiceStatus(); |
|
|
|
@ -383,7 +479,7 @@ function updateVoiceStatus() {
|
|
|
|
|
if (ptt) { |
|
|
|
|
$('#voiceactiveleft').html('<b>Transmitting</b>'); |
|
|
|
|
} else { |
|
|
|
|
$('#voiceactiveleft').html('# users voice chatting'); |
|
|
|
|
$('#voiceactiveleft').html(userListStatus); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -395,6 +491,14 @@ function w(t, m) {
|
|
|
|
|
socket.send(JSON.stringify({T: t, M: btoa(m)})); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function wpc(pc, t, m) { |
|
|
|
|
if (!webSocketReady()) { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
socket.send(JSON.stringify({PC: pc, T: t, M: btoa(m)})); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function escapeEntitiesCallback(tag) { |
|
|
|
|
return tagsToReplace[tag] || tag; |
|
|
|
|
} |
|
|
|
|