Fixed light client & added Docker config
This commit is contained in:
21
.gitignore
vendored
Normal file
21
.gitignore
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
.DS_Store
|
||||||
|
node_modules
|
||||||
|
/dist
|
||||||
|
|
||||||
|
# local env files
|
||||||
|
.env.local
|
||||||
|
.env.*.local
|
||||||
|
|
||||||
|
# Log files
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.idea
|
||||||
|
.vscode
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
3
buildAndRunLight.sh
Executable file
3
buildAndRunLight.sh
Executable file
@@ -0,0 +1,3 @@
|
|||||||
|
cd server
|
||||||
|
docker build -t thomsb/lilstreamy .
|
||||||
|
docker run -p 8000:8002 -d thomsb/lilstreamy
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
<h3>Enter a username</h3>
|
<h3>Enter a username</h3>
|
||||||
<b-field position="is-centered">
|
<b-field position="is-centered">
|
||||||
<b-input :value="name" v-model="name" placeholder="..."></b-input>
|
<b-input :value="name" v-model="name" placeholder="..."></b-input>
|
||||||
<button class="button is-success" @click="connect">Login</button>
|
<button class="button is-success" @click="sendName">Login</button>
|
||||||
</b-field>
|
</b-field>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -18,7 +18,12 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async sendName () {
|
async sendName () {
|
||||||
|
if (this.name.length > 0) {
|
||||||
|
this.$rtc.send({
|
||||||
|
type: "login",
|
||||||
|
name: this.name
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
var name
|
var name
|
||||||
var connections = {}
|
var connections = {}
|
||||||
|
var stream
|
||||||
|
|
||||||
const configuration = {
|
const configuration = {
|
||||||
iceServers: [{ urls: 'stun:stun.l.google.com:19302' }]
|
iceServers: [{ urls: 'stun:stun.l.google.com:19302' }]
|
||||||
@@ -11,181 +12,177 @@ const offerOptions = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleLogin (success) {
|
function handleLogin (success) {
|
||||||
if (success === false) {
|
if (success === false) {
|
||||||
alert('try a different username')
|
alert('try a different username')
|
||||||
} else {
|
} else {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createPeerConnection (target) {
|
async function createPeerConnection (target) {
|
||||||
console.log('CREATED PEER CONNECTION')
|
console.log('CREATED PEER CONNECTION')
|
||||||
var connection = new RTCPeerConnection(configuration)
|
var connection = new RTCPeerConnection(configuration)
|
||||||
connections[target] = connection
|
connections[target] = connection
|
||||||
|
|
||||||
connection.onicecandidate = function(event) {
|
connection.onicecandidate = function (event) {
|
||||||
if (event.candidate) {
|
if (event.candidate) {
|
||||||
send({
|
send({
|
||||||
type: 'candidate',
|
type: 'candidate',
|
||||||
name: name,
|
name: name,
|
||||||
target: target,
|
target: target,
|
||||||
candidate: event.candidate
|
candidate: event.candidate
|
||||||
})
|
})
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
connection.onnegotiationneeded = function() { handleNegotiationNeededEvent(target); }
|
connection.onnegotiationneeded = function () { handleNegotiationNeededEvent(target) }
|
||||||
connection.ontrack = function(event) { handleTrackEvent(event); }
|
connection.ontrack = function (event) { handleTrackEvent(event) }
|
||||||
connection.onsignalingstatechange = function() { handleSignalingStateChangeEvent(connection); }
|
connection.onsignalingstatechange = function () { handleSignalingStateChangeEvent(connection) }
|
||||||
connection.oniceconnectionstatechange = function() { handleICEConnectionStateChangeEvent(connection); }
|
connection.oniceconnectionstatechange = function () { handleICEConnectionStateChangeEvent(connection) }
|
||||||
connection.onicegatheringstatechange = function() { handleICEGatheringStateChangeEvent(connection); }
|
connection.onicegatheringstatechange = function () { handleICEGatheringStateChangeEvent(connection) }
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleICEConnectionStateChangeEvent (connection) {
|
function handleICEConnectionStateChangeEvent (connection) {
|
||||||
console.log('ICE CONNECTION CHANGE '+connection.iceConnectionState)
|
console.log('ICE CONNECTION CHANGE ' + connection.iceConnectionState)
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleICEGatheringStateChangeEvent (connection) {
|
function handleICEGatheringStateChangeEvent (connection) {
|
||||||
console.log('ICE GATHERING CHANGE '+connection.iceGatheringState)
|
console.log('ICE GATHERING CHANGE ' + connection.iceGatheringState)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function makeOffer (target) {
|
async function makeOffer (target) {
|
||||||
createPeerConnection(target)
|
createPeerConnection(target)
|
||||||
|
|
||||||
var connection = connections[target]
|
|
||||||
|
|
||||||
var offer = await connection.createOffer()
|
var connection = connections[target]
|
||||||
send({
|
|
||||||
type: 'offer',
|
var offer = await connection.createOffer()
|
||||||
name: name,
|
send({
|
||||||
target: target,
|
type: 'offer',
|
||||||
offer: offer
|
name: name,
|
||||||
});
|
target: target,
|
||||||
await connection.setLocalDescription(offer)
|
offer: offer
|
||||||
|
})
|
||||||
|
await connection.setLocalDescription(offer)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleOffer (offer, target) {
|
async function handleOffer (offer, target) {
|
||||||
console.log('GOT OFFER FROM '+target)
|
console.log('GOT OFFER FROM ' + target)
|
||||||
await createPeerConnection(target)
|
await createPeerConnection(target)
|
||||||
|
|
||||||
var connection = connections[target]
|
var connection = connections[target]
|
||||||
|
|
||||||
await connection.setRemoteDescription(new RTCSessionDescription(offer))
|
await connection.setRemoteDescription(new RTCSessionDescription(offer))
|
||||||
|
|
||||||
if (stream) {
|
if (stream) {
|
||||||
console.log('STREAM DETECTED')
|
console.log('STREAM DETECTED')
|
||||||
stream.getTracks().forEach((track) => {
|
stream.getTracks().forEach((track) => {
|
||||||
console.log('ADDED TRACK')
|
console.log('ADDED TRACK')
|
||||||
connection.addTrack(track, stream)
|
connection.addTrack(track, stream)
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
//create an answer to an offer
|
|
||||||
var answer = await connection.createAnswer()
|
|
||||||
|
|
||||||
await connection.setLocalDescription(answer)
|
|
||||||
|
|
||||||
send({
|
|
||||||
type: 'answer',
|
|
||||||
name: name,
|
|
||||||
target: target,
|
|
||||||
answer: answer
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
async function handleAnswer (answer, target) {
|
var answer = await connection.createAnswer()
|
||||||
console.log('GOT ANSWER FROM '+target)
|
|
||||||
var connection = connections[target]
|
|
||||||
await connection.setRemoteDescription(new RTCSessionDescription(answer))
|
|
||||||
};
|
|
||||||
|
|
||||||
async function handleCandidate (candidate, target) {
|
await connection.setLocalDescription(answer)
|
||||||
console.log('GOT CANDIDATE FROM '+target)
|
|
||||||
var connection = connections[target]
|
send({
|
||||||
await connection.addIceCandidate(new RTCIceCandidate(candidate))
|
type: 'answer',
|
||||||
};
|
name: name,
|
||||||
|
target: target,
|
||||||
|
answer: answer
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleAnswer (answer, target) {
|
||||||
|
console.log('GOT ANSWER FROM ' + target)
|
||||||
|
var connection = connections[target]
|
||||||
|
await connection.setRemoteDescription(new RTCSessionDescription(answer))
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleCandidate (candidate, target) {
|
||||||
|
console.log('GOT CANDIDATE FROM ' + target)
|
||||||
|
var connection = connections[target]
|
||||||
|
await connection.addIceCandidate(new RTCIceCandidate(candidate))
|
||||||
|
}
|
||||||
|
|
||||||
async function handleNegotiationNeededEvent (target) {
|
async function handleNegotiationNeededEvent (target) {
|
||||||
console.log('NEGOTIATION NEEDED FROM '+target)
|
console.log('NEGOTIATION NEEDED FROM ' + target)
|
||||||
|
|
||||||
var connection = connections[target]
|
var connection = connections[target]
|
||||||
var offer = await connection.createOffer(offerOptions)
|
var offer = await connection.createOffer(offerOptions)
|
||||||
|
|
||||||
await connection.setLocalDescription(offer)
|
await connection.setLocalDescription(offer)
|
||||||
|
|
||||||
send({
|
send({
|
||||||
type: 'video-offer',
|
type: 'video-offer',
|
||||||
name: name,
|
name: name,
|
||||||
target: target,
|
target: target,
|
||||||
sdp: connection.localDescription
|
sdp: connection.localDescription
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleLeave () {
|
function handleLeave () {
|
||||||
connections.foreach( (connection) => {
|
connections.foreach((connection) => {
|
||||||
connection.close()
|
connection.close()
|
||||||
connection.onicecandidate = null
|
connection.onicecandidate = null
|
||||||
//connection.onaddstream = null
|
// connection.onaddstream = null
|
||||||
connection = null
|
connection = null
|
||||||
});
|
})
|
||||||
connections = {};
|
connections = {}
|
||||||
|
remoteVideo.src = null
|
||||||
remoteVideo.src = null
|
}
|
||||||
};
|
|
||||||
|
|
||||||
function handleUserlist (list) {
|
function handleUserlist (list) {
|
||||||
console.log('GOT USER LIST')
|
console.log('GOT USER LIST')
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleVideoOffer (sdp, target) {
|
async function handleVideoOffer (sdp, target) {
|
||||||
console.log('GOT VIDEO OFFER FROM '+target)
|
console.log('GOT VIDEO OFFER FROM '+target)
|
||||||
await createPeerConnection(target)
|
await createPeerConnection(target)
|
||||||
var connection = connections[target]
|
var connection = connections[target]
|
||||||
await connection.setRemoteDescription(new RTCSessionDescription(sdp))
|
await connection.setRemoteDescription(new RTCSessionDescription(sdp))
|
||||||
|
|
||||||
if (stream) {
|
if (stream) {
|
||||||
console.log('STREAM DETECTED')
|
console.log('STREAM DETECTED')
|
||||||
stream.getTracks().forEach((track) => {
|
stream.getTracks().forEach((track) => {
|
||||||
console.log('ADDED TRACK')
|
console.log('ADDED TRACK')
|
||||||
connection.addTrack(track, stream)
|
connection.addTrack(track, stream)
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
var answer = await connection.createAnswer()
|
var answer = await connection.createAnswer()
|
||||||
await connection.setLocalDescription(answer)
|
await connection.setLocalDescription(answer)
|
||||||
|
|
||||||
send({
|
send({
|
||||||
type: 'video-answer',
|
type: 'video-answer',
|
||||||
name: name,
|
name: name,
|
||||||
target: target,
|
target: target,
|
||||||
sdp: answer
|
sdp: answer
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleVideoAnswer (sdp, target) {
|
async function handleVideoAnswer (sdp, target) {
|
||||||
console.log('GOT VIDEO ANSWER FROM '+target)
|
console.log('GOT VIDEO ANSWER FROM '+target)
|
||||||
|
|
||||||
var connection = connections[target]
|
var connection = connections[target]
|
||||||
await connection.setRemoteDescription(new RTCSessionDescription(sdp))
|
await connection.setRemoteDescription(new RTCSessionDescription(sdp))
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleSignalingStateChangeEvent (connection) {
|
async function handleSignalingStateChangeEvent (connection) {
|
||||||
console.log('STATE CHANGED TO : ' + connection.signalingState)
|
console.log('STATE CHANGED TO : ' + connection.signalingState)
|
||||||
switch(connection.signalingState) {
|
switch(connection.signalingState) {
|
||||||
case 'closed':
|
case 'closed':
|
||||||
await connection.close()
|
await connection.close()
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleTrackEvent (event) {
|
function handleTrackEvent (event) {
|
||||||
console.log('GOT TRACK')
|
console.log('GOT TRACK')
|
||||||
remoteVideo.srcObject = event.streams[0]
|
remoteVideo.srcObject = event.streams[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
//connecting to our signaling server
|
//connecting to our signaling server
|
||||||
var conn = new WebSocket('ws://localhost:9090')
|
const conn = new WebSocket('ws://localhost:9090')
|
||||||
|
|
||||||
conn.onopen = function () {
|
conn.onopen = function () {
|
||||||
console.log('Connected to the signaling server')
|
console.log('Connected to the signaling server')
|
||||||
@@ -193,46 +190,46 @@ conn.onopen = function () {
|
|||||||
|
|
||||||
//when we got a message from a signaling server
|
//when we got a message from a signaling server
|
||||||
conn.onmessage = function (msg) {
|
conn.onmessage = function (msg) {
|
||||||
console.log('Got message', msg.data)
|
console.log('Got message', msg.data)
|
||||||
|
|
||||||
var data = JSON.parse(msg.data)
|
var data = JSON.parse(msg.data)
|
||||||
|
|
||||||
switch(data.type) {
|
switch(data.type) {
|
||||||
case 'login':
|
case 'login':
|
||||||
handleLogin(data.success)
|
handleLogin(data.success)
|
||||||
break
|
break
|
||||||
|
|
||||||
case 'offer':
|
|
||||||
handleOffer(data.offer, data.name)
|
|
||||||
break
|
|
||||||
|
|
||||||
case 'answer':
|
|
||||||
handleAnswer(data.answer, data.name)
|
|
||||||
break
|
|
||||||
|
|
||||||
case 'candidate':
|
|
||||||
handleCandidate(data.candidate, data.name)
|
|
||||||
break
|
|
||||||
|
|
||||||
case 'userlist':
|
|
||||||
handleUserlist(data)
|
|
||||||
break
|
|
||||||
|
|
||||||
case 'leave':
|
|
||||||
handleLeave()
|
|
||||||
break
|
|
||||||
|
|
||||||
case 'video-offer':
|
|
||||||
handleVideoOffer(data.sdp, data.name)
|
|
||||||
break
|
|
||||||
|
|
||||||
case 'video-answer':
|
case 'offer':
|
||||||
handleVideoAnswer(data.sdp, data.name)
|
handleOffer(data.offer, data.name)
|
||||||
break
|
break
|
||||||
|
|
||||||
default:
|
case 'answer':
|
||||||
break
|
handleAnswer(data.answer, data.name)
|
||||||
}
|
break
|
||||||
|
|
||||||
|
case 'candidate':
|
||||||
|
handleCandidate(data.candidate, data.name)
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'userlist':
|
||||||
|
handleUserlist(data)
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'leave':
|
||||||
|
handleLeave()
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'video-offer':
|
||||||
|
handleVideoOffer(data.sdp, data.name)
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'video-answer':
|
||||||
|
handleVideoAnswer(data.sdp, data.name)
|
||||||
|
break
|
||||||
|
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
conn.onerror = function (err) {
|
conn.onerror = function (err) {
|
||||||
@@ -244,7 +241,8 @@ function send (message) {
|
|||||||
conn.send(JSON.stringify(message))
|
conn.send(JSON.stringify(message))
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export default{
|
||||||
|
conn,
|
||||||
send,
|
send,
|
||||||
makeOffer
|
makeOffer
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
const connection = new WebSocket('ws://localhost:9090')
|
||||||
|
|
||||||
|
connection.onopen = function () {
|
||||||
|
console.log('connectionected to the signaling server')
|
||||||
|
};
|
||||||
|
|
||||||
|
//when we got a message from a signaling server
|
||||||
|
connection.onmessage = function (msg) {
|
||||||
|
console.log('Got message', msg.data)
|
||||||
|
|
||||||
|
var data = JSON.parse(msg.data)
|
||||||
|
|
||||||
|
switch(data.type) {
|
||||||
|
case 'login':
|
||||||
|
handleLogin(data.success)
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'offer':
|
||||||
|
handleOffer(data.offer, data.name)
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'answer':
|
||||||
|
handleAnswer(data.answer, data.name)
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'candidate':
|
||||||
|
handleCandidate(data.candidate, data.name)
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'userlist':
|
||||||
|
handleUserlist(data)
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'leave':
|
||||||
|
handleLeave()
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'video-offer':
|
||||||
|
handleVideoOffer(data.sdp, data.name)
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'video-answer':
|
||||||
|
handleVideoAnswer(data.sdp, data.name)
|
||||||
|
break
|
||||||
|
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
connection.onerror = function (err) {
|
||||||
|
console.log('Got error', err)
|
||||||
|
}
|
||||||
|
|
||||||
|
function send (message) {
|
||||||
|
console.log('Sended message', message)
|
||||||
|
connection.send(JSON.stringify(message))
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
send
|
||||||
|
}
|
||||||
1
clientV/bulma.min.css
vendored
1
clientV/bulma.min.css
vendored
File diff suppressed because one or more lines are too long
@@ -1,34 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="fr">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta content="width=device-width, initial-scale=1.0" name="viewport">
|
|
||||||
<link rel="stylesheet" href="style.css" />
|
|
||||||
<link rel="stylesheet" href="bulma.min.css" />
|
|
||||||
<title>Lil'Streamy</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<button id="loginBt" >Login</button>
|
|
||||||
<input id="loginInput" placeholder="..." type="text"/>
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
|
|
||||||
<video autoplay controls id="video"></video>
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
|
|
||||||
<button id="callBt" >Call</button>
|
|
||||||
<input id="callInput" placeholder="..." type="text"/>
|
|
||||||
<button id="disconnectBt" >Disconnect</button>
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
|
|
||||||
<input id="videoInput" type="file" accept="video/*"/>
|
|
||||||
|
|
||||||
<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
|
|
||||||
<script src = "scripts/script.js"></script>
|
|
||||||
<script src = "scripts/rtc3.js"></script>
|
|
||||||
<script src = "scripts/signal.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
<html>
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<title>WebRTC Video Demo</title>
|
|
||||||
<link rel = "stylesheet" href = "node_modules/bootstrap/dist/css/bootstrap.min.css"/>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
|
|
||||||
body {
|
|
||||||
background: #eee;
|
|
||||||
padding: 5% 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
video {
|
|
||||||
background: black;
|
|
||||||
border: 1px solid gray;
|
|
||||||
}
|
|
||||||
|
|
||||||
.call-page {
|
|
||||||
position: relative;
|
|
||||||
display: block;
|
|
||||||
margin: 0 auto;
|
|
||||||
width: 500px;
|
|
||||||
height: 500px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#localVideo {
|
|
||||||
width: 150px;
|
|
||||||
height: 150px;
|
|
||||||
position: absolute;
|
|
||||||
top: 15px;
|
|
||||||
right: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#remoteVideo {
|
|
||||||
width: 500px;
|
|
||||||
height: 500px;
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<div id = "loginPage" class = "container text-center">
|
|
||||||
|
|
||||||
<div class = "row">
|
|
||||||
<div class = "col-md-4 col-md-offset-4">
|
|
||||||
|
|
||||||
<h2>WebRTC Video Demo. Please sign in</h2>
|
|
||||||
<label for = "usernameInput" class = "sr-only">Login</label>
|
|
||||||
<input type = "email" id = "usernameInput" c
|
|
||||||
lass = "form-control formgroup" placeholder = "Login"
|
|
||||||
required = "" autofocus = "">
|
|
||||||
<button id = "loginBtn" class = "btn btn-lg btn-primary btnblock">
|
|
||||||
Sign in</button>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id = "callPage" class = "call-page">
|
|
||||||
<video id = "localVideo" autoplay></video>
|
|
||||||
<video id = "remoteVideo" autoplay></video>
|
|
||||||
|
|
||||||
<div class = "row text-center">
|
|
||||||
<div class = "col-md-12">
|
|
||||||
<input id = "callToUsernameInput" type = "text"
|
|
||||||
placeholder = "username to call" />
|
|
||||||
<button id = "callBtn" class = "btn-success btn">Call</button>
|
|
||||||
<button id = "hangUpBtn" class = "btn-danger btn">Hang Up</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script src = "scripts/rtc_old.js"></script>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
||||||
@@ -1,220 +0,0 @@
|
|||||||
var name;
|
|
||||||
var connectedUser;
|
|
||||||
|
|
||||||
var loginInput = document.querySelector('#loginInput');
|
|
||||||
var loginBt = document.querySelector('#loginBt');
|
|
||||||
|
|
||||||
var callToUsernameInput = document.querySelector('#callInput');
|
|
||||||
var callBtn = document.querySelector('#callBt');
|
|
||||||
|
|
||||||
var hangUpBtn = document.querySelector('#disconnectBt');
|
|
||||||
|
|
||||||
const remoteVideo = document.querySelector('#video');
|
|
||||||
const remoteVideo2 = document.querySelector('#video2');
|
|
||||||
|
|
||||||
var videoInput = document.querySelector('#videoInput');
|
|
||||||
|
|
||||||
var yourConn;
|
|
||||||
var stream;
|
|
||||||
|
|
||||||
var reader;
|
|
||||||
|
|
||||||
const configuration = {
|
|
||||||
iceServers: [{ urls: "stun:stun.l.google.com:19302" }]
|
|
||||||
};
|
|
||||||
|
|
||||||
const offerOptions = {
|
|
||||||
offerToReceiveAudio: 1,
|
|
||||||
offerToReceiveVideo: 1
|
|
||||||
};
|
|
||||||
|
|
||||||
videoInput.addEventListener("change", function (event) {
|
|
||||||
var file = this.files[0]
|
|
||||||
var type = file.type
|
|
||||||
var videoNode = remoteVideo2
|
|
||||||
var canPlay = videoNode.canPlayType(type)
|
|
||||||
if (canPlay === '') canPlay = 'no'
|
|
||||||
var message = 'Can play type "' + type + '": ' + canPlay
|
|
||||||
var isError = canPlay === 'no'
|
|
||||||
//displayMessage(message, isError)
|
|
||||||
|
|
||||||
if (isError) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var fileURL = URL.createObjectURL(file)
|
|
||||||
videoNode.src = fileURL
|
|
||||||
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
remoteVideo.onplay = function() {
|
|
||||||
if(remoteVideo.mozCaptureStream()) stream = remoteVideo.mozCaptureStream();
|
|
||||||
else stream = remoteVideo.captureStream();
|
|
||||||
|
|
||||||
//remoteVideo2.srcObject = stream;
|
|
||||||
|
|
||||||
stream.getTracks().forEach((track) => {
|
|
||||||
console.log("ADDED TRACK");
|
|
||||||
yourConn.addTrack(track, stream);
|
|
||||||
});
|
|
||||||
|
|
||||||
//yourConn.addStream(stream);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Login when the user clicks the button
|
|
||||||
loginBt.addEventListener("click", function (event) {
|
|
||||||
name = loginInput.value;
|
|
||||||
|
|
||||||
if (name.length > 0) {
|
|
||||||
send({
|
|
||||||
type: "login",
|
|
||||||
name: name
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
function handleLogin(success) {
|
|
||||||
if (success === false) {
|
|
||||||
alert("Ooops...try a different username");
|
|
||||||
} else {
|
|
||||||
//**********************
|
|
||||||
//Starting a peer connection
|
|
||||||
//**********************
|
|
||||||
|
|
||||||
yourConn = new RTCPeerConnection(configuration);
|
|
||||||
|
|
||||||
yourConn.ontrack = function (event) {
|
|
||||||
console.log("GOT TRACK");
|
|
||||||
remoteVideo.srcObject = event.streams[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (stream) {
|
|
||||||
remoteVideo2.srcObject = stream;
|
|
||||||
|
|
||||||
stream.getTracks().forEach((track) => {
|
|
||||||
yourConn.addTrack(track, stream);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// setup stream listening
|
|
||||||
//yourConn.addStream(stream);
|
|
||||||
|
|
||||||
|
|
||||||
// Setup ice handling
|
|
||||||
yourConn.onicecandidate = function (event) {
|
|
||||||
if (event.candidate) {
|
|
||||||
send({
|
|
||||||
type: "candidate",
|
|
||||||
candidate: event.candidate
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
yourConn.onnegotiationneeded = handleNegotiationNeededEvent;
|
|
||||||
|
|
||||||
window.setInterval(getConnectionStats, 1000);
|
|
||||||
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function getConnectionStats() {
|
|
||||||
/*yourConn.getStats().then(stats => {
|
|
||||||
var statsOutput = "";
|
|
||||||
stats.forEach(report => {
|
|
||||||
statsOutput += report;
|
|
||||||
});
|
|
||||||
console.log(statsOutput);
|
|
||||||
});*/
|
|
||||||
console.log(yourConn.connectionState);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
//initiating a call
|
|
||||||
callBtn.addEventListener("click", function () {
|
|
||||||
var callToUsername = callToUsernameInput.value;
|
|
||||||
|
|
||||||
if (callToUsername.length > 0) {
|
|
||||||
|
|
||||||
connectedUser = callToUsername;
|
|
||||||
|
|
||||||
// create an offer
|
|
||||||
yourConn.createOffer(function (offer) {
|
|
||||||
send({
|
|
||||||
type: "offer",
|
|
||||||
offer: offer
|
|
||||||
});
|
|
||||||
|
|
||||||
yourConn.setLocalDescription(offer);
|
|
||||||
}, function (error) {
|
|
||||||
alert("Error when creating an offer");
|
|
||||||
},
|
|
||||||
offerOptions);
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
//when somebody sends us an offer
|
|
||||||
function handleOffer(offer, name) {
|
|
||||||
console.log("GOT OFFER");
|
|
||||||
connectedUser = name;
|
|
||||||
yourConn.setRemoteDescription(new RTCSessionDescription(offer));
|
|
||||||
|
|
||||||
//create an answer to an offer
|
|
||||||
yourConn.createAnswer(function (answer) {
|
|
||||||
yourConn.setLocalDescription(answer);
|
|
||||||
|
|
||||||
send({
|
|
||||||
type: "answer",
|
|
||||||
answer: answer
|
|
||||||
});
|
|
||||||
|
|
||||||
}, function (error) {
|
|
||||||
alert("Error when creating an answer");
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
//when we got an answer from a remote user
|
|
||||||
function handleAnswer(answer) {
|
|
||||||
console.log("GOT ANSWER");
|
|
||||||
yourConn.setRemoteDescription(new RTCSessionDescription(answer));
|
|
||||||
};
|
|
||||||
|
|
||||||
//when we got an ice candidate from a remote user
|
|
||||||
function handleCandidate(candidate) {
|
|
||||||
console.log("GOT CANDIDATE");
|
|
||||||
yourConn.addIceCandidate(new RTCIceCandidate(candidate));
|
|
||||||
};
|
|
||||||
|
|
||||||
function handleNegotiationNeededEvent() {
|
|
||||||
console.log("NEGOTIATION NEEDED");
|
|
||||||
yourConn.createOffer().then(function(offer) {
|
|
||||||
return yourConn.setLocalDescription(offer);
|
|
||||||
})
|
|
||||||
.then(function() {
|
|
||||||
send({
|
|
||||||
type: "video-offer",
|
|
||||||
sdp: yourConn.localDescription
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
//hang up
|
|
||||||
hangUpBtn.addEventListener("click", function () {
|
|
||||||
|
|
||||||
send({
|
|
||||||
type: "leave"
|
|
||||||
});
|
|
||||||
|
|
||||||
handleLeave();
|
|
||||||
});
|
|
||||||
|
|
||||||
function handleLeave() {
|
|
||||||
connectedUser = null;
|
|
||||||
remoteVideo.src = null;
|
|
||||||
|
|
||||||
yourConn.close();
|
|
||||||
yourConn.onicecandidate = null;
|
|
||||||
//yourConn.onaddstream = null;
|
|
||||||
};
|
|
||||||
@@ -1,658 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
// Get our hostname
|
|
||||||
|
|
||||||
var myHostname = window.location.hostname;
|
|
||||||
if (!myHostname) {
|
|
||||||
myHostname = "localhost";
|
|
||||||
}
|
|
||||||
log("Hostname: " + myHostname);
|
|
||||||
|
|
||||||
// WebSocket chat/signaling channel variables.
|
|
||||||
|
|
||||||
var connection = null;
|
|
||||||
var clientID = 0;
|
|
||||||
|
|
||||||
// The media constraints object describes what sort of stream we want
|
|
||||||
// to request from the local A/V hardware (typically a webcam and
|
|
||||||
// microphone). Here, we specify only that we want both audio and
|
|
||||||
// video; however, you can be more specific. It's possible to state
|
|
||||||
// that you would prefer (or require) specific resolutions of video,
|
|
||||||
// whether to prefer the user-facing or rear-facing camera (if available),
|
|
||||||
// and so on.
|
|
||||||
//
|
|
||||||
// See also:
|
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamConstraints
|
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia
|
|
||||||
//
|
|
||||||
|
|
||||||
var mediaConstraints = {
|
|
||||||
audio: true, // We want an audio track
|
|
||||||
video: {
|
|
||||||
aspectRatio: {
|
|
||||||
ideal: 1.333333 // 3:2 aspect is preferred
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var myUsername = null;
|
|
||||||
var targetUsername = null; // To store username of other peer
|
|
||||||
var myPeerConnection = null; // RTCPeerConnection
|
|
||||||
var transceiver = null; // RTCRtpTransceiver
|
|
||||||
var webcamStream = null; // MediaStream from webcam
|
|
||||||
|
|
||||||
// Output logging information to console.
|
|
||||||
|
|
||||||
function log(text) {
|
|
||||||
var time = new Date();
|
|
||||||
|
|
||||||
console.log("[" + time.toLocaleTimeString() + "] " + text);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Output an error message to console.
|
|
||||||
|
|
||||||
function log_error(text) {
|
|
||||||
var time = new Date();
|
|
||||||
|
|
||||||
console.trace("[" + time.toLocaleTimeString() + "] " + text);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send a JavaScript object by converting it to JSON and sending
|
|
||||||
// it as a message on the WebSocket connection.
|
|
||||||
|
|
||||||
function sendToServer(msg) {
|
|
||||||
var msgJSON = JSON.stringify(msg);
|
|
||||||
|
|
||||||
log("Sending '" + msg.type + "' message: " + msgJSON);
|
|
||||||
connection.send(msgJSON);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Called when the "id" message is received; this message is sent by the
|
|
||||||
// server to assign this login session a unique ID number; in response,
|
|
||||||
// this function sends a "username" message to set our username for this
|
|
||||||
// session.
|
|
||||||
function setUsername() {
|
|
||||||
myUsername = document.getElementById("name").value;
|
|
||||||
|
|
||||||
sendToServer({
|
|
||||||
name: myUsername,
|
|
||||||
date: Date.now(),
|
|
||||||
id: clientID,
|
|
||||||
type: "username"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Open and configure the connection to the WebSocket server.
|
|
||||||
|
|
||||||
function connect() {
|
|
||||||
var serverUrl;
|
|
||||||
var scheme = "ws";
|
|
||||||
|
|
||||||
// If this is an HTTPS connection, we have to use a secure WebSocket
|
|
||||||
// connection too, so add another "s" to the scheme.
|
|
||||||
|
|
||||||
if (document.location.protocol === "https:") {
|
|
||||||
scheme += "s";
|
|
||||||
}
|
|
||||||
serverUrl = scheme + "://" + myHostname + ":6503";
|
|
||||||
|
|
||||||
log(`Connecting to server: ${serverUrl}`);
|
|
||||||
connection = new WebSocket(serverUrl, "json");
|
|
||||||
|
|
||||||
connection.onopen = function(evt) {
|
|
||||||
document.getElementById("text").disabled = false;
|
|
||||||
document.getElementById("send").disabled = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
connection.onerror = function(evt) {
|
|
||||||
console.dir(evt);
|
|
||||||
}
|
|
||||||
|
|
||||||
connection.onmessage = function(evt) {
|
|
||||||
var chatBox = document.querySelector(".chatbox");
|
|
||||||
var text = "";
|
|
||||||
var msg = JSON.parse(evt.data);
|
|
||||||
log("Message received: ");
|
|
||||||
console.dir(msg);
|
|
||||||
var time = new Date(msg.date);
|
|
||||||
var timeStr = time.toLocaleTimeString();
|
|
||||||
|
|
||||||
switch(msg.type) {
|
|
||||||
case "id":
|
|
||||||
clientID = msg.id;
|
|
||||||
setUsername();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "username":
|
|
||||||
text = "<b>User <em>" + msg.name + "</em> signed in at " + timeStr + "</b><br>";
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "message":
|
|
||||||
text = "(" + timeStr + ") <b>" + msg.name + "</b>: " + msg.text + "<br>";
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "rejectusername":
|
|
||||||
myUsername = msg.name;
|
|
||||||
text = "<b>Your username has been set to <em>" + myUsername +
|
|
||||||
"</em> because the name you chose is in use.</b><br>";
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "userlist": // Received an updated user list
|
|
||||||
handleUserlistMsg(msg);
|
|
||||||
break;
|
|
||||||
|
|
||||||
// Signaling messages: these messages are used to trade WebRTC
|
|
||||||
// signaling information during negotiations leading up to a video
|
|
||||||
// call.
|
|
||||||
|
|
||||||
case "video-offer": // Invitation and offer to chat
|
|
||||||
handleVideoOfferMsg(msg);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "video-answer": // Callee has answered our offer
|
|
||||||
handleVideoAnswerMsg(msg);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "new-ice-candidate": // A new ICE candidate has been received
|
|
||||||
handleNewICECandidateMsg(msg);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "hang-up": // The other peer has hung up the call
|
|
||||||
handleHangUpMsg(msg);
|
|
||||||
break;
|
|
||||||
|
|
||||||
// Unknown message; output to console for debugging.
|
|
||||||
|
|
||||||
default:
|
|
||||||
log_error("Unknown message received:");
|
|
||||||
log_error(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If there's text to insert into the chat buffer, do so now, then
|
|
||||||
// scroll the chat panel so that the new text is visible.
|
|
||||||
|
|
||||||
if (text.length) {
|
|
||||||
chatBox.innerHTML += text;
|
|
||||||
chatBox.scrollTop = chatBox.scrollHeight - chatBox.clientHeight;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handles a click on the Send button (or pressing return/enter) by
|
|
||||||
// building a "message" object and sending it to the server.
|
|
||||||
function handleSendButton() {
|
|
||||||
var msg = {
|
|
||||||
text: document.getElementById("text").value,
|
|
||||||
type: "message",
|
|
||||||
id: clientID,
|
|
||||||
date: Date.now()
|
|
||||||
};
|
|
||||||
sendToServer(msg);
|
|
||||||
document.getElementById("text").value = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handler for keyboard events. This is used to intercept the return and
|
|
||||||
// enter keys so that we can call send() to transmit the entered text
|
|
||||||
// to the server.
|
|
||||||
function handleKey(evt) {
|
|
||||||
if (evt.keyCode === 13 || evt.keyCode === 14) {
|
|
||||||
if (!document.getElementById("send").disabled) {
|
|
||||||
handleSendButton();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the RTCPeerConnection which knows how to talk to our
|
|
||||||
// selected STUN/TURN server and then uses getUserMedia() to find
|
|
||||||
// our camera and microphone and add that stream to the connection for
|
|
||||||
// use in our video call. Then we configure event handlers to get
|
|
||||||
// needed notifications on the call.
|
|
||||||
|
|
||||||
async function createPeerConnection() {
|
|
||||||
log("Setting up a connection...");
|
|
||||||
|
|
||||||
// Create an RTCPeerConnection which knows to use our chosen
|
|
||||||
// STUN server.
|
|
||||||
|
|
||||||
myPeerConnection = new RTCPeerConnection({
|
|
||||||
iceServers: [ // Information about ICE servers - Use your own!
|
|
||||||
{
|
|
||||||
urls: "turn:" + myHostname, // A TURN server
|
|
||||||
username: "webrtc",
|
|
||||||
credential: "turnserver"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
});
|
|
||||||
|
|
||||||
// Set up event handlers for the ICE negotiation process.
|
|
||||||
|
|
||||||
myPeerConnection.onicecandidate = handleICECandidateEvent;
|
|
||||||
myPeerConnection.oniceconnectionstatechange = handleICEConnectionStateChangeEvent;
|
|
||||||
myPeerConnection.onicegatheringstatechange = handleICEGatheringStateChangeEvent;
|
|
||||||
myPeerConnection.onsignalingstatechange = handleSignalingStateChangeEvent;
|
|
||||||
myPeerConnection.onnegotiationneeded = handleNegotiationNeededEvent;
|
|
||||||
myPeerConnection.ontrack = handleTrackEvent;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Called by the WebRTC layer to let us know when it's time to
|
|
||||||
// begin, resume, or restart ICE negotiation.
|
|
||||||
|
|
||||||
async function handleNegotiationNeededEvent() {
|
|
||||||
log("*** Negotiation needed");
|
|
||||||
|
|
||||||
try {
|
|
||||||
log("---> Creating offer");
|
|
||||||
const offer = await myPeerConnection.createOffer();
|
|
||||||
|
|
||||||
// If the connection hasn't yet achieved the "stable" state,
|
|
||||||
// return to the caller. Another negotiationneeded event
|
|
||||||
// will be fired when the state stabilizes.
|
|
||||||
|
|
||||||
if (myPeerConnection.signalingState != "stable") {
|
|
||||||
log(" -- The connection isn't stable yet; postponing...")
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Establish the offer as the local peer's current
|
|
||||||
// description.
|
|
||||||
|
|
||||||
log("---> Setting local description to the offer");
|
|
||||||
await myPeerConnection.setLocalDescription(offer);
|
|
||||||
|
|
||||||
// Send the offer to the remote peer.
|
|
||||||
|
|
||||||
log("---> Sending the offer to the remote peer");
|
|
||||||
sendToServer({
|
|
||||||
name: myUsername,
|
|
||||||
target: targetUsername,
|
|
||||||
type: "video-offer",
|
|
||||||
sdp: myPeerConnection.localDescription
|
|
||||||
});
|
|
||||||
} catch(err) {
|
|
||||||
log("*** The following error occurred while handling the negotiationneeded event:");
|
|
||||||
reportError(err);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Called by the WebRTC layer when events occur on the media tracks
|
|
||||||
// on our WebRTC call. This includes when streams are added to and
|
|
||||||
// removed from the call.
|
|
||||||
//
|
|
||||||
// track events include the following fields:
|
|
||||||
//
|
|
||||||
// RTCRtpReceiver receiver
|
|
||||||
// MediaStreamTrack track
|
|
||||||
// MediaStream[] streams
|
|
||||||
// RTCRtpTransceiver transceiver
|
|
||||||
//
|
|
||||||
// In our case, we're just taking the first stream found and attaching
|
|
||||||
// it to the <video> element for incoming media.
|
|
||||||
|
|
||||||
function handleTrackEvent(event) {
|
|
||||||
log("*** Track event");
|
|
||||||
document.getElementById("received_video").srcObject = event.streams[0];
|
|
||||||
document.getElementById("hangup-button").disabled = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handles |icecandidate| events by forwarding the specified
|
|
||||||
// ICE candidate (created by our local ICE agent) to the other
|
|
||||||
// peer through the signaling server.
|
|
||||||
|
|
||||||
function handleICECandidateEvent(event) {
|
|
||||||
if (event.candidate) {
|
|
||||||
log("*** Outgoing ICE candidate: " + event.candidate.candidate);
|
|
||||||
|
|
||||||
sendToServer({
|
|
||||||
type: "new-ice-candidate",
|
|
||||||
target: targetUsername,
|
|
||||||
candidate: event.candidate
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle |iceconnectionstatechange| events. This will detect
|
|
||||||
// when the ICE connection is closed, failed, or disconnected.
|
|
||||||
//
|
|
||||||
// This is called when the state of the ICE agent changes.
|
|
||||||
|
|
||||||
function handleICEConnectionStateChangeEvent(event) {
|
|
||||||
log("*** ICE connection state changed to " + myPeerConnection.iceConnectionState);
|
|
||||||
|
|
||||||
switch(myPeerConnection.iceConnectionState) {
|
|
||||||
case "closed":
|
|
||||||
case "failed":
|
|
||||||
case "disconnected":
|
|
||||||
closeVideoCall();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set up a |signalingstatechange| event handler. This will detect when
|
|
||||||
// the signaling connection is closed.
|
|
||||||
//
|
|
||||||
// NOTE: This will actually move to the new RTCPeerConnectionState enum
|
|
||||||
// returned in the property RTCPeerConnection.connectionState when
|
|
||||||
// browsers catch up with the latest version of the specification!
|
|
||||||
|
|
||||||
function handleSignalingStateChangeEvent(event) {
|
|
||||||
log("*** WebRTC signaling state changed to: " + myPeerConnection.signalingState);
|
|
||||||
switch(myPeerConnection.signalingState) {
|
|
||||||
case "closed":
|
|
||||||
closeVideoCall();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle the |icegatheringstatechange| event. This lets us know what the
|
|
||||||
// ICE engine is currently working on: "new" means no networking has happened
|
|
||||||
// yet, "gathering" means the ICE engine is currently gathering candidates,
|
|
||||||
// and "complete" means gathering is complete. Note that the engine can
|
|
||||||
// alternate between "gathering" and "complete" repeatedly as needs and
|
|
||||||
// circumstances change.
|
|
||||||
//
|
|
||||||
// We don't need to do anything when this happens, but we log it to the
|
|
||||||
// console so you can see what's going on when playing with the sample.
|
|
||||||
|
|
||||||
function handleICEGatheringStateChangeEvent(event) {
|
|
||||||
log("*** ICE gathering state changed to: " + myPeerConnection.iceGatheringState);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Given a message containing a list of usernames, this function
|
|
||||||
// populates the user list box with those names, making each item
|
|
||||||
// clickable to allow starting a video call.
|
|
||||||
|
|
||||||
function handleUserlistMsg(msg) {
|
|
||||||
var i;
|
|
||||||
var listElem = document.querySelector(".userlistbox");
|
|
||||||
|
|
||||||
// Remove all current list members. We could do this smarter,
|
|
||||||
// by adding and updating users instead of rebuilding from
|
|
||||||
// scratch but this will do for this sample.
|
|
||||||
|
|
||||||
while (listElem.firstChild) {
|
|
||||||
listElem.removeChild(listElem.firstChild);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add member names from the received list.
|
|
||||||
|
|
||||||
msg.users.forEach(function(username) {
|
|
||||||
var item = document.createElement("li");
|
|
||||||
item.appendChild(document.createTextNode(username));
|
|
||||||
item.addEventListener("click", invite, false);
|
|
||||||
|
|
||||||
listElem.appendChild(item);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close the RTCPeerConnection and reset variables so that the user can
|
|
||||||
// make or receive another call if they wish. This is called both
|
|
||||||
// when the user hangs up, the other user hangs up, or if a connection
|
|
||||||
// failure is detected.
|
|
||||||
|
|
||||||
function closeVideoCall() {
|
|
||||||
var localVideo = document.getElementById("local_video");
|
|
||||||
|
|
||||||
log("Closing the call");
|
|
||||||
|
|
||||||
// Close the RTCPeerConnection
|
|
||||||
|
|
||||||
if (myPeerConnection) {
|
|
||||||
log("--> Closing the peer connection");
|
|
||||||
|
|
||||||
// Disconnect all our event listeners; we don't want stray events
|
|
||||||
// to interfere with the hangup while it's ongoing.
|
|
||||||
|
|
||||||
myPeerConnection.ontrack = null;
|
|
||||||
myPeerConnection.onnicecandidate = null;
|
|
||||||
myPeerConnection.oniceconnectionstatechange = null;
|
|
||||||
myPeerConnection.onsignalingstatechange = null;
|
|
||||||
myPeerConnection.onicegatheringstatechange = null;
|
|
||||||
myPeerConnection.onnotificationneeded = null;
|
|
||||||
|
|
||||||
// Stop all transceivers on the connection
|
|
||||||
|
|
||||||
myPeerConnection.getTransceivers().forEach(transceiver => {
|
|
||||||
transceiver.stop();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Stop the webcam preview as well by pausing the <video>
|
|
||||||
// element, then stopping each of the getUserMedia() tracks
|
|
||||||
// on it.
|
|
||||||
|
|
||||||
if (localVideo.srcObject) {
|
|
||||||
localVideo.pause();
|
|
||||||
localVideo.srcObject.getTracks().forEach(track => {
|
|
||||||
track.stop();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close the peer connection
|
|
||||||
|
|
||||||
myPeerConnection.close();
|
|
||||||
myPeerConnection = null;
|
|
||||||
webcamStream = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Disable the hangup button
|
|
||||||
|
|
||||||
document.getElementById("hangup-button").disabled = true;
|
|
||||||
targetUsername = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle the "hang-up" message, which is sent if the other peer
|
|
||||||
// has hung up the call or otherwise disconnected.
|
|
||||||
|
|
||||||
function handleHangUpMsg(msg) {
|
|
||||||
log("*** Received hang up notification from other peer");
|
|
||||||
|
|
||||||
closeVideoCall();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hang up the call by closing our end of the connection, then
|
|
||||||
// sending a "hang-up" message to the other peer (keep in mind that
|
|
||||||
// the signaling is done on a different connection). This notifies
|
|
||||||
// the other peer that the connection should be terminated and the UI
|
|
||||||
// returned to the "no call in progress" state.
|
|
||||||
|
|
||||||
function hangUpCall() {
|
|
||||||
closeVideoCall();
|
|
||||||
|
|
||||||
sendToServer({
|
|
||||||
name: myUsername,
|
|
||||||
target: targetUsername,
|
|
||||||
type: "hang-up"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle a click on an item in the user list by inviting the clicked
|
|
||||||
// user to video chat. Note that we don't actually send a message to
|
|
||||||
// the callee here -- calling RTCPeerConnection.addTrack() issues
|
|
||||||
// a |notificationneeded| event, so we'll let our handler for that
|
|
||||||
// make the offer.
|
|
||||||
|
|
||||||
async function invite(evt) {
|
|
||||||
log("Starting to prepare an invitation");
|
|
||||||
if (myPeerConnection) {
|
|
||||||
alert("You can't start a call because you already have one open!");
|
|
||||||
} else {
|
|
||||||
var clickedUsername = evt.target.textContent;
|
|
||||||
|
|
||||||
// Don't allow users to call themselves, because weird.
|
|
||||||
|
|
||||||
if (clickedUsername === myUsername) {
|
|
||||||
alert("I'm afraid I can't let you talk to yourself. That would be weird.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Record the username being called for future reference
|
|
||||||
|
|
||||||
targetUsername = clickedUsername;
|
|
||||||
log("Inviting user " + targetUsername);
|
|
||||||
|
|
||||||
// Call createPeerConnection() to create the RTCPeerConnection.
|
|
||||||
// When this returns, myPeerConnection is our RTCPeerConnection
|
|
||||||
// and webcamStream is a stream coming from the camera. They are
|
|
||||||
// not linked together in any way yet.
|
|
||||||
|
|
||||||
log("Setting up connection to invite user: " + targetUsername);
|
|
||||||
createPeerConnection();
|
|
||||||
|
|
||||||
// Get access to the webcam stream and attach it to the
|
|
||||||
// "preview" box (id "local_video").
|
|
||||||
|
|
||||||
try {
|
|
||||||
webcamStream = await navigator.mediaDevices.getUserMedia(mediaConstraints);
|
|
||||||
document.getElementById("local_video").srcObject = webcamStream;
|
|
||||||
} catch(err) {
|
|
||||||
handleGetUserMediaError(err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add the tracks from the stream to the RTCPeerConnection
|
|
||||||
|
|
||||||
try {
|
|
||||||
webcamStream.getTracks().forEach(
|
|
||||||
transceiver = track => myPeerConnection.addTransceiver(track, {streams: [webcamStream]})
|
|
||||||
);
|
|
||||||
} catch(err) {
|
|
||||||
handleGetUserMediaError(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Accept an offer to video chat. We configure our local settings,
|
|
||||||
// create our RTCPeerConnection, get and attach our local camera
|
|
||||||
// stream, then create and send an answer to the caller.
|
|
||||||
|
|
||||||
async function handleVideoOfferMsg(msg) {
|
|
||||||
targetUsername = msg.name;
|
|
||||||
|
|
||||||
// If we're not already connected, create an RTCPeerConnection
|
|
||||||
// to be linked to the caller.
|
|
||||||
|
|
||||||
log("Received video chat offer from " + targetUsername);
|
|
||||||
if (!myPeerConnection) {
|
|
||||||
createPeerConnection();
|
|
||||||
}
|
|
||||||
|
|
||||||
// We need to set the remote description to the received SDP offer
|
|
||||||
// so that our local WebRTC layer knows how to talk to the caller.
|
|
||||||
|
|
||||||
var desc = new RTCSessionDescription(msg.sdp);
|
|
||||||
|
|
||||||
// If the connection isn't stable yet, wait for it...
|
|
||||||
|
|
||||||
if (myPeerConnection.signalingState != "stable") {
|
|
||||||
log(" - But the signaling state isn't stable, so triggering rollback");
|
|
||||||
|
|
||||||
// Set the local and remove descriptions for rollback; don't proceed
|
|
||||||
// until both return.
|
|
||||||
await Promise.all([
|
|
||||||
myPeerConnection.setLocalDescription({type: "rollback"}),
|
|
||||||
myPeerConnection.setRemoteDescription(desc)
|
|
||||||
]);
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
log (" - Setting remote description");
|
|
||||||
await myPeerConnection.setRemoteDescription(desc);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the webcam stream if we don't already have it
|
|
||||||
|
|
||||||
if (!webcamStream) {
|
|
||||||
try {
|
|
||||||
webcamStream = await navigator.mediaDevices.getUserMedia(mediaConstraints);
|
|
||||||
} catch(err) {
|
|
||||||
handleGetUserMediaError(err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
document.getElementById("local_video").srcObject = webcamStream;
|
|
||||||
|
|
||||||
// Add the camera stream to the RTCPeerConnection
|
|
||||||
|
|
||||||
try {
|
|
||||||
webcamStream.getTracks().forEach(
|
|
||||||
transceiver = track => myPeerConnection.addTransceiver(track, {streams: [webcamStream]})
|
|
||||||
);
|
|
||||||
} catch(err) {
|
|
||||||
handleGetUserMediaError(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log("---> Creating and sending answer to caller");
|
|
||||||
|
|
||||||
await myPeerConnection.setLocalDescription(await myPeerConnection.createAnswer());
|
|
||||||
|
|
||||||
sendToServer({
|
|
||||||
name: myUsername,
|
|
||||||
target: targetUsername,
|
|
||||||
type: "video-answer",
|
|
||||||
sdp: myPeerConnection.localDescription
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Responds to the "video-answer" message sent to the caller
|
|
||||||
// once the callee has decided to accept our request to talk.
|
|
||||||
|
|
||||||
async function handleVideoAnswerMsg(msg) {
|
|
||||||
log("*** Call recipient has accepted our call");
|
|
||||||
|
|
||||||
// Configure the remote description, which is the SDP payload
|
|
||||||
// in our "video-answer" message.
|
|
||||||
|
|
||||||
var desc = new RTCSessionDescription(msg.sdp);
|
|
||||||
await myPeerConnection.setRemoteDescription(desc).catch(reportError);
|
|
||||||
}
|
|
||||||
|
|
||||||
// A new ICE candidate has been received from the other peer. Call
|
|
||||||
// RTCPeerConnection.addIceCandidate() to send it along to the
|
|
||||||
// local ICE framework.
|
|
||||||
|
|
||||||
async function handleNewICECandidateMsg(msg) {
|
|
||||||
var candidate = new RTCIceCandidate(msg.candidate);
|
|
||||||
|
|
||||||
log("*** Adding received ICE candidate: " + JSON.stringify(candidate));
|
|
||||||
try {
|
|
||||||
await myPeerConnection.addIceCandidate(candidate)
|
|
||||||
} catch(err) {
|
|
||||||
reportError(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle errors which occur when trying to access the local media
|
|
||||||
// hardware; that is, exceptions thrown by getUserMedia(). The two most
|
|
||||||
// likely scenarios are that the user has no camera and/or microphone
|
|
||||||
// or that they declined to share their equipment when prompted. If
|
|
||||||
// they simply opted not to share their media, that's not really an
|
|
||||||
// error, so we won't present a message in that situation.
|
|
||||||
|
|
||||||
function handleGetUserMediaError(e) {
|
|
||||||
log_error(e);
|
|
||||||
switch(e.name) {
|
|
||||||
case "NotFoundError":
|
|
||||||
alert("Unable to open your call because no camera and/or microphone" +
|
|
||||||
"were found.");
|
|
||||||
break;
|
|
||||||
case "SecurityError":
|
|
||||||
case "PermissionDeniedError":
|
|
||||||
// Do nothing; this is the same as the user canceling the call.
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
alert("Error opening your camera and/or microphone: " + e.message);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure we shut down our end of the RTCPeerConnection so we're
|
|
||||||
// ready to try again.
|
|
||||||
|
|
||||||
closeVideoCall();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handles reporting errors. Currently, we just dump stuff to console but
|
|
||||||
// in a real-world application, an appropriate (and user-friendly)
|
|
||||||
// error message should be displayed.
|
|
||||||
|
|
||||||
function reportError(errMessage) {
|
|
||||||
log_error(`Error ${errMessage.name}: ${errMessage.message}`);
|
|
||||||
}
|
|
||||||
@@ -1,195 +0,0 @@
|
|||||||
var name;
|
|
||||||
var connections = {};
|
|
||||||
|
|
||||||
const configuration = {
|
|
||||||
iceServers: [{ urls: "stun:stun.l.google.com:19302" }]
|
|
||||||
};
|
|
||||||
|
|
||||||
const offerOptions = {
|
|
||||||
offerToReceiveAudio: 1,
|
|
||||||
offerToReceiveVideo: 1
|
|
||||||
};
|
|
||||||
|
|
||||||
function handleLogin(success) {
|
|
||||||
if (success === false) {
|
|
||||||
alert("try a different username");
|
|
||||||
} else {
|
|
||||||
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
async function createPeerConnection(target) {
|
|
||||||
console.log("CREATED PEER CONNECTION");
|
|
||||||
var connection = new RTCPeerConnection(configuration);
|
|
||||||
connections[target] = connection;
|
|
||||||
|
|
||||||
connection.onicecandidate = function(event) {
|
|
||||||
if (event.candidate) {
|
|
||||||
send({
|
|
||||||
type: "candidate",
|
|
||||||
name: name,
|
|
||||||
target: target,
|
|
||||||
candidate: event.candidate
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
connection.onnegotiationneeded = function() { handleNegotiationNeededEvent(target); };
|
|
||||||
connection.ontrack = function(event) { handleTrackEvent(event); }
|
|
||||||
connection.onsignalingstatechange = function() { handleSignalingStateChangeEvent(connection); }
|
|
||||||
connection.oniceconnectionstatechange = function() { handleICEConnectionStateChangeEvent(connection); }
|
|
||||||
connection.onicegatheringstatechange = function() { handleICEGatheringStateChangeEvent(connection); }
|
|
||||||
|
|
||||||
//window.setInterval(getConnectionStats, 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleICEConnectionStateChangeEvent(connection) {
|
|
||||||
console.log("ICE CONNECTION CHANGE "+connection.iceConnectionState);
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleICEGatheringStateChangeEvent(connection) {
|
|
||||||
console.log("ICE GATHERING CHANGE "+connection.iceGatheringState);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function makeOffer(target) {
|
|
||||||
createPeerConnection(target);
|
|
||||||
|
|
||||||
var connection = connections[target];
|
|
||||||
|
|
||||||
var offer = await connection.createOffer();
|
|
||||||
send({
|
|
||||||
type: "offer",
|
|
||||||
name: name,
|
|
||||||
target: target,
|
|
||||||
offer: offer
|
|
||||||
});
|
|
||||||
await connection.setLocalDescription(offer);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleOffer(offer, target) {
|
|
||||||
console.log("GOT OFFER FROM "+target);
|
|
||||||
await createPeerConnection(target);
|
|
||||||
|
|
||||||
var connection = connections[target];
|
|
||||||
|
|
||||||
await connection.setRemoteDescription(new RTCSessionDescription(offer));
|
|
||||||
|
|
||||||
if (stream) {
|
|
||||||
console.log("STREAM DETECTED");
|
|
||||||
stream.getTracks().forEach((track) => {
|
|
||||||
console.log("ADDED TRACK");
|
|
||||||
connection.addTrack(track, stream);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
//create an answer to an offer
|
|
||||||
var answer = await connection.createAnswer();
|
|
||||||
|
|
||||||
await connection.setLocalDescription(answer);
|
|
||||||
|
|
||||||
send({
|
|
||||||
type: "answer",
|
|
||||||
name: name,
|
|
||||||
target: target,
|
|
||||||
answer: answer
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
async function handleAnswer(answer, target) {
|
|
||||||
console.log("GOT ANSWER FROM "+target);
|
|
||||||
var connection = connections[target];
|
|
||||||
await connection.setRemoteDescription(new RTCSessionDescription(answer));
|
|
||||||
};
|
|
||||||
|
|
||||||
async function handleCandidate(candidate, target) {
|
|
||||||
console.log("GOT CANDIDATE FROM "+target);
|
|
||||||
var connection = connections[target];
|
|
||||||
await connection.addIceCandidate(new RTCIceCandidate(candidate));
|
|
||||||
};
|
|
||||||
|
|
||||||
async function handleNegotiationNeededEvent(target) {
|
|
||||||
console.log("NEGOTIATION NEEDED FROM "+target);
|
|
||||||
|
|
||||||
var connection = connections[target];
|
|
||||||
var offer = await connection.createOffer(offerOptions);
|
|
||||||
|
|
||||||
await connection.setLocalDescription(offer);
|
|
||||||
|
|
||||||
send({
|
|
||||||
type: "video-offer",
|
|
||||||
name: name,
|
|
||||||
target: target,
|
|
||||||
sdp: connection.localDescription
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleLeave() {
|
|
||||||
connections.foreach( (connection) => {
|
|
||||||
connection.close();
|
|
||||||
connection.onicecandidate = null;
|
|
||||||
//connection.onaddstream = null;
|
|
||||||
connection = null;
|
|
||||||
});
|
|
||||||
connections = {};
|
|
||||||
|
|
||||||
remoteVideo.src = null;
|
|
||||||
};
|
|
||||||
|
|
||||||
function handleUserlist(list) {
|
|
||||||
console.log("GOT USER LIST");
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleVideoOffer(sdp, target) {
|
|
||||||
console.log("GOT VIDEO OFFER FROM "+target);
|
|
||||||
await createPeerConnection(target);
|
|
||||||
var connection = connections[target];
|
|
||||||
await connection.setRemoteDescription(new RTCSessionDescription(sdp))
|
|
||||||
|
|
||||||
if (stream) {
|
|
||||||
console.log("STREAM DETECTED");
|
|
||||||
stream.getTracks().forEach((track) => {
|
|
||||||
console.log("ADDED TRACK");
|
|
||||||
connection.addTrack(track, stream);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
var answer = await connection.createAnswer();
|
|
||||||
await connection.setLocalDescription(answer);
|
|
||||||
|
|
||||||
send({
|
|
||||||
type: "video-answer",
|
|
||||||
name: name,
|
|
||||||
target: target,
|
|
||||||
sdp: answer
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleVideoAnswer(sdp, target) {
|
|
||||||
console.log("GOT VIDEO ANSWER FROM "+target);
|
|
||||||
|
|
||||||
var connection = connections[target];
|
|
||||||
await connection.setRemoteDescription(new RTCSessionDescription(sdp));
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleSignalingStateChangeEvent(connection) {
|
|
||||||
console.log("STATE CHANGED TO : " + connection.signalingState);
|
|
||||||
switch(connection.signalingState) {
|
|
||||||
case "closed":
|
|
||||||
await connection.close();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleTrackEvent(event) {
|
|
||||||
console.log("GOT TRACK");
|
|
||||||
remoteVideo.srcObject = event.streams[0];
|
|
||||||
//document.getElementById("hangup-button").disabled = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getConnectionStats() {
|
|
||||||
for ([connection, target] in connections) {
|
|
||||||
console.log("[" + target + "] " + connection.connectionState);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,203 +0,0 @@
|
|||||||
//our username
|
|
||||||
var name;
|
|
||||||
var connectedUser;
|
|
||||||
|
|
||||||
//connecting to our signaling server
|
|
||||||
var conn = new WebSocket('ws://localhost:9090');
|
|
||||||
|
|
||||||
conn.onopen = function () {
|
|
||||||
console.log("Connected to the signaling server");
|
|
||||||
};
|
|
||||||
|
|
||||||
//when we got a message from a signaling server
|
|
||||||
conn.onmessage = function (msg) {
|
|
||||||
console.log("Got message", msg.data);
|
|
||||||
|
|
||||||
var data = JSON.parse(msg.data);
|
|
||||||
|
|
||||||
switch(data.type) {
|
|
||||||
case "login":
|
|
||||||
handleLogin(data.success);
|
|
||||||
break;
|
|
||||||
//when somebody wants to call us
|
|
||||||
case "offer":
|
|
||||||
handleOffer(data.offer, data.name);
|
|
||||||
break;
|
|
||||||
case "answer":
|
|
||||||
handleAnswer(data.answer);
|
|
||||||
break;
|
|
||||||
//when a remote peer sends an ice candidate to us
|
|
||||||
case "candidate":
|
|
||||||
handleCandidate(data.candidate);
|
|
||||||
break;
|
|
||||||
case "leave":
|
|
||||||
handleLeave();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
conn.onerror = function (err) {
|
|
||||||
console.log("Got error", err);
|
|
||||||
};
|
|
||||||
|
|
||||||
//alias for sending JSON encoded messages
|
|
||||||
function send(message) {
|
|
||||||
//attach the other peer username to our messages
|
|
||||||
if (connectedUser) {
|
|
||||||
message.name = connectedUser;
|
|
||||||
}
|
|
||||||
|
|
||||||
conn.send(JSON.stringify(message));
|
|
||||||
};
|
|
||||||
|
|
||||||
//******
|
|
||||||
//UI selectors block
|
|
||||||
//******
|
|
||||||
|
|
||||||
var loginPage = document.querySelector('#loginPage');
|
|
||||||
var usernameInput = document.querySelector('#usernameInput');
|
|
||||||
var loginBtn = document.querySelector('#loginBtn');
|
|
||||||
|
|
||||||
var callPage = document.querySelector('#callPage');
|
|
||||||
var callToUsernameInput = document.querySelector('#callToUsernameInput');
|
|
||||||
var callBtn = document.querySelector('#callBtn');
|
|
||||||
|
|
||||||
var hangUpBtn = document.querySelector('#hangUpBtn');
|
|
||||||
|
|
||||||
var localVideo = document.querySelector('#localVideo');
|
|
||||||
var remoteVideo = document.querySelector('#remoteVideo');
|
|
||||||
|
|
||||||
var yourConn;
|
|
||||||
var stream;
|
|
||||||
|
|
||||||
callPage.style.display = "none";
|
|
||||||
|
|
||||||
// Login when the user clicks the button
|
|
||||||
loginBtn.addEventListener("click", function (event) {
|
|
||||||
name = usernameInput.value;
|
|
||||||
|
|
||||||
if (name.length > 0) {
|
|
||||||
send({
|
|
||||||
type: "login",
|
|
||||||
name: name
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
function handleLogin(success) {
|
|
||||||
if (success === false) {
|
|
||||||
alert("Ooops...try a different username");
|
|
||||||
} else {
|
|
||||||
loginPage.style.display = "none";
|
|
||||||
callPage.style.display = "block";
|
|
||||||
|
|
||||||
//**********************
|
|
||||||
//Starting a peer connection
|
|
||||||
//**********************
|
|
||||||
|
|
||||||
|
|
||||||
//stream = myStream;
|
|
||||||
|
|
||||||
//displaying local video stream on the page
|
|
||||||
//localVideo.src = window.URL.createObjectURL(stream);
|
|
||||||
|
|
||||||
//using Google public stun server
|
|
||||||
var configuration = {
|
|
||||||
"iceServers": [{ "url": "stun:stun2.1.google.com:19302" }]
|
|
||||||
};
|
|
||||||
|
|
||||||
yourConn = new RTCPeerConnection(configuration);
|
|
||||||
|
|
||||||
// setup stream listening
|
|
||||||
//yourConn.addStream(stream);
|
|
||||||
|
|
||||||
//when a remote user adds stream to the peer connection, we display it
|
|
||||||
yourConn.onaddstream = function (e) {
|
|
||||||
//remoteVideo.src = window.URL.createObjectURL(e.stream);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Setup ice handling
|
|
||||||
yourConn.onicecandidate = function (event) {
|
|
||||||
if (event.candidate) {
|
|
||||||
send({
|
|
||||||
type: "candidate",
|
|
||||||
candidate: event.candidate
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
//initiating a call
|
|
||||||
callBtn.addEventListener("click", function () {
|
|
||||||
var callToUsername = callToUsernameInput.value;
|
|
||||||
|
|
||||||
if (callToUsername.length > 0) {
|
|
||||||
|
|
||||||
connectedUser = callToUsername;
|
|
||||||
|
|
||||||
// create an offer
|
|
||||||
yourConn.createOffer(function (offer) {
|
|
||||||
send({
|
|
||||||
type: "offer",
|
|
||||||
offer: offer
|
|
||||||
});
|
|
||||||
|
|
||||||
yourConn.setLocalDescription(offer);
|
|
||||||
}, function (error) {
|
|
||||||
alert("Error when creating an offer");
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
//when somebody sends us an offer
|
|
||||||
function handleOffer(offer, name) {
|
|
||||||
connectedUser = name;
|
|
||||||
yourConn.setRemoteDescription(new RTCSessionDescription(offer));
|
|
||||||
|
|
||||||
//create an answer to an offer
|
|
||||||
yourConn.createAnswer(function (answer) {
|
|
||||||
yourConn.setLocalDescription(answer);
|
|
||||||
|
|
||||||
send({
|
|
||||||
type: "answer",
|
|
||||||
answer: answer
|
|
||||||
});
|
|
||||||
|
|
||||||
}, function (error) {
|
|
||||||
alert("Error when creating an answer");
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
//when we got an answer from a remote user
|
|
||||||
function handleAnswer(answer) {
|
|
||||||
yourConn.setRemoteDescription(new RTCSessionDescription(answer));
|
|
||||||
};
|
|
||||||
|
|
||||||
//when we got an ice candidate from a remote user
|
|
||||||
function handleCandidate(candidate) {
|
|
||||||
yourConn.addIceCandidate(new RTCIceCandidate(candidate));
|
|
||||||
};
|
|
||||||
|
|
||||||
//hang up
|
|
||||||
hangUpBtn.addEventListener("click", function () {
|
|
||||||
|
|
||||||
send({
|
|
||||||
type: "leave"
|
|
||||||
});
|
|
||||||
|
|
||||||
handleLeave();
|
|
||||||
});
|
|
||||||
|
|
||||||
function handleLeave() {
|
|
||||||
connectedUser = null;
|
|
||||||
remoteVideo.src = null;
|
|
||||||
|
|
||||||
yourConn.close();
|
|
||||||
yourConn.onicecandidate = null;
|
|
||||||
yourConn.onaddstream = null;
|
|
||||||
};
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
var loginInput = document.querySelector('#loginInput');
|
|
||||||
var loginBt = document.querySelector('#loginBt');
|
|
||||||
|
|
||||||
var callInput = document.querySelector('#callInput');
|
|
||||||
var callBt = document.querySelector('#callBt');
|
|
||||||
|
|
||||||
var disconnectBt = document.querySelector('#disconnectBt');
|
|
||||||
|
|
||||||
const remoteVideo = document.querySelector('#video');
|
|
||||||
|
|
||||||
var videoInput = document.querySelector('#videoInput');
|
|
||||||
|
|
||||||
var stream;
|
|
||||||
|
|
||||||
loginBt.addEventListener("click", function (event) {
|
|
||||||
name = loginInput.value;
|
|
||||||
|
|
||||||
if (name.length > 0) {
|
|
||||||
send({
|
|
||||||
type: "login",
|
|
||||||
name: name
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
callBt.addEventListener("click", function () {
|
|
||||||
var callToUsername = callInput.value;
|
|
||||||
|
|
||||||
if (callToUsername.length > 0) {
|
|
||||||
makeOffer(callToUsername);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
disconnectBt.addEventListener("click", function () {
|
|
||||||
send({
|
|
||||||
type: "leave",
|
|
||||||
name: name
|
|
||||||
});
|
|
||||||
handleLeave();
|
|
||||||
});
|
|
||||||
|
|
||||||
videoInput.addEventListener("change", function (event) {
|
|
||||||
var file = this.files[0]
|
|
||||||
var type = file.type
|
|
||||||
var videoNode = remoteVideo2
|
|
||||||
var canPlay = videoNode.canPlayType(type)
|
|
||||||
if (canPlay === '') canPlay = 'no'
|
|
||||||
var message = 'Can play type "' + type + '": ' + canPlay
|
|
||||||
var isError = canPlay === 'no'
|
|
||||||
//displayMessage(message, isError)
|
|
||||||
|
|
||||||
if (isError) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var fileURL = URL.createObjectURL(file)
|
|
||||||
videoNode.src = fileURL
|
|
||||||
});
|
|
||||||
|
|
||||||
remoteVideo.onplay = function() {
|
|
||||||
console.log("ADD STREAM");
|
|
||||||
if(remoteVideo.mozCaptureStream()) stream = remoteVideo.mozCaptureStream();
|
|
||||||
else stream = remoteVideo.captureStream();
|
|
||||||
}
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
//connecting to our signaling server
|
|
||||||
var conn = new WebSocket('ws://localhost:9090');
|
|
||||||
|
|
||||||
conn.onopen = function () {
|
|
||||||
console.log("Connected to the signaling server");
|
|
||||||
};
|
|
||||||
|
|
||||||
//when we got a message from a signaling server
|
|
||||||
conn.onmessage = function (msg) {
|
|
||||||
console.log("Got message", msg.data);
|
|
||||||
|
|
||||||
var data = JSON.parse(msg.data);
|
|
||||||
|
|
||||||
switch(data.type) {
|
|
||||||
case "login":
|
|
||||||
handleLogin(data.success);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "offer":
|
|
||||||
handleOffer(data.offer, data.name);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "answer":
|
|
||||||
handleAnswer(data.answer, data.name);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "candidate":
|
|
||||||
handleCandidate(data.candidate, data.name);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "userlist":
|
|
||||||
handleUserlist(data);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "leave":
|
|
||||||
handleLeave();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "video-offer":
|
|
||||||
handleVideoOffer(data.sdp, data.name);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "video-answer":
|
|
||||||
handleVideoAnswer(data.sdp, data.name);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
conn.onerror = function (err) {
|
|
||||||
console.log("Got error", err);
|
|
||||||
};
|
|
||||||
|
|
||||||
//alias for sending JSON encoded messages
|
|
||||||
function send(message) {
|
|
||||||
console.log("Sended message", message);
|
|
||||||
conn.send(JSON.stringify(message));
|
|
||||||
};
|
|
||||||
2
server/.dockerignore
Normal file
2
server/.dockerignore
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
node_modules
|
||||||
|
npm-debug.log
|
||||||
13
server/Dockerfile
Normal file
13
server/Dockerfile
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
FROM node:10
|
||||||
|
|
||||||
|
WORKDIR /usr/src/app
|
||||||
|
|
||||||
|
COPY package*.json ./
|
||||||
|
|
||||||
|
RUN npm install
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
EXPOSE 8080
|
||||||
|
EXPOSE 9090
|
||||||
|
CMD [ "npm", "start" ]
|
||||||
38
server/client/index.html
Normal file
38
server/client/index.html
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="fr">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta content="width=device-width, initial-scale=1.0" name="viewport">
|
||||||
|
<link rel="stylesheet" href="style.css" />
|
||||||
|
<title>Lil'Streamy</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="loginDiv">
|
||||||
|
<input class="input" id="loginInput" placeholder="username" type="text"/>
|
||||||
|
<button class="button" id="loginBt" >Login</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="connectDiv">
|
||||||
|
<input id="callInput" placeholder="user to call" type="text"/>
|
||||||
|
<button id="callBt" >Connect</button>
|
||||||
|
<hr>
|
||||||
|
<input id="videoInput" type="file" accept="video/*"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="videoDiv">
|
||||||
|
<h2 id="videoTitle"></h2>
|
||||||
|
<video autoplay controls id="video"></video>
|
||||||
|
<hr>
|
||||||
|
<button id="disconnectBt">Disconnect</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="loadDiv">
|
||||||
|
<div class="loader" style="display:block"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
|
||||||
|
<script src = "scripts/script.js"></script>
|
||||||
|
<script src = "scripts/rtc.js"></script>
|
||||||
|
<script src = "scripts/signal.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
200
server/client/scripts/rtc.js
Normal file
200
server/client/scripts/rtc.js
Normal file
@@ -0,0 +1,200 @@
|
|||||||
|
var name
|
||||||
|
var connections = new Map()
|
||||||
|
|
||||||
|
const configuration = {
|
||||||
|
iceServers: [{ urls: 'stun:stun.l.google.com:19302' }]
|
||||||
|
}
|
||||||
|
|
||||||
|
const offerOptions = {
|
||||||
|
offerToReceiveAudio: 1,
|
||||||
|
offerToReceiveVideo: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleLogin(success) {
|
||||||
|
if (success === false) {
|
||||||
|
alert('try a different username')
|
||||||
|
} else {
|
||||||
|
loginDiv.style.display = 'none'
|
||||||
|
connectDiv.style.display = 'block'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createPeerConnection(target) {
|
||||||
|
console.log('CREATED PEER CONNECTION')
|
||||||
|
var connection = new RTCPeerConnection(configuration)
|
||||||
|
connections[target] = connection
|
||||||
|
|
||||||
|
connection.onicecandidate = function(event) {
|
||||||
|
if (event.candidate) {
|
||||||
|
send({
|
||||||
|
type: 'candidate',
|
||||||
|
name: name,
|
||||||
|
target: target,
|
||||||
|
candidate: event.candidate
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
connection.onnegotiationneeded = function() { handleNegotiationNeededEvent(target) }
|
||||||
|
connection.ontrack = function(event) { handleTrackEvent(event) }
|
||||||
|
connection.onsignalingstatechange = function() { handleSignalingStateChangeEvent(connection) }
|
||||||
|
connection.oniceconnectionstatechange = function() { handleICEConnectionStateChangeEvent(connection) }
|
||||||
|
connection.onicegatheringstatechange = function() { handleICEGatheringStateChangeEvent(connection) }
|
||||||
|
|
||||||
|
// window.setInterval(getConnectionStats, 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleICEConnectionStateChangeEvent(connection) {
|
||||||
|
console.log('ICE CONNECTION CHANGE ' + connection.iceConnectionState)
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleICEGatheringStateChangeEvent(connection) {
|
||||||
|
console.log('ICE GATHERING CHANGE ' + connection.iceGatheringState)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function makeOffer(target) {
|
||||||
|
createPeerConnection(target)
|
||||||
|
|
||||||
|
var connection = connections[target]
|
||||||
|
|
||||||
|
var offer = await connection.createOffer()
|
||||||
|
send({
|
||||||
|
type: 'offer',
|
||||||
|
name: name,
|
||||||
|
target: target,
|
||||||
|
offer: offer
|
||||||
|
})
|
||||||
|
await connection.setLocalDescription(offer)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleOffer(offer, target) {
|
||||||
|
console.log('GOT OFFER FROM ' + target)
|
||||||
|
await createPeerConnection(target)
|
||||||
|
|
||||||
|
var connection = connections[target]
|
||||||
|
|
||||||
|
await connection.setRemoteDescription(new RTCSessionDescription(offer))
|
||||||
|
|
||||||
|
if (stream) {
|
||||||
|
console.log('STREAM DETECTED')
|
||||||
|
stream.getTracks().forEach((track) => {
|
||||||
|
console.log('ADDED TRACK')
|
||||||
|
connection.addTrack(track, stream)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var answer = await connection.createAnswer()
|
||||||
|
|
||||||
|
await connection.setLocalDescription(answer)
|
||||||
|
|
||||||
|
send({
|
||||||
|
type: 'answer',
|
||||||
|
name: name,
|
||||||
|
target: target,
|
||||||
|
answer: answer
|
||||||
|
})
|
||||||
|
|
||||||
|
videoTitle.innerHTML = name + ' | ' + connections.size + ' users connected'
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleAnswer(answer, target) {
|
||||||
|
console.log('GOT ANSWER FROM ' + target)
|
||||||
|
var connection = connections[target]
|
||||||
|
await connection.setRemoteDescription(new RTCSessionDescription(answer))
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleCandidate(candidate, target) {
|
||||||
|
console.log('GOT CANDIDATE FROM ' + target)
|
||||||
|
var connection = connections[target]
|
||||||
|
await connection.addIceCandidate(new RTCIceCandidate(candidate))
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleNegotiationNeededEvent(target) {
|
||||||
|
console.log('NEGOTIATION NEEDED FROM ' + target)
|
||||||
|
|
||||||
|
var connection = connections[target]
|
||||||
|
var offer = await connection.createOffer(offerOptions)
|
||||||
|
|
||||||
|
await connection.setLocalDescription(offer)
|
||||||
|
|
||||||
|
send({
|
||||||
|
type: 'video-offer',
|
||||||
|
name: name,
|
||||||
|
target: target,
|
||||||
|
sdp: connection.localDescription
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleLeave() {
|
||||||
|
connections.forEach((connection) => {
|
||||||
|
connection.close()
|
||||||
|
connection = null
|
||||||
|
})
|
||||||
|
connections = new Map()
|
||||||
|
|
||||||
|
remoteVideo.pause()
|
||||||
|
remoteVideo.src = null
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleUserlist(list) {
|
||||||
|
console.log('GOT USER LIST')
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleVideoOffer(sdp, target) {
|
||||||
|
console.log('GOT VIDEO OFFER FROM ' + target)
|
||||||
|
await createPeerConnection(target)
|
||||||
|
var connection = connections[target]
|
||||||
|
await connection.setRemoteDescription(new RTCSessionDescription(sdp))
|
||||||
|
|
||||||
|
if (stream) {
|
||||||
|
console.log('STREAM DETECTED')
|
||||||
|
stream.getTracks().forEach((track) => {
|
||||||
|
console.log('ADDED TRACK')
|
||||||
|
connection.addTrack(track, stream)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var answer = await connection.createAnswer()
|
||||||
|
await connection.setLocalDescription(answer)
|
||||||
|
|
||||||
|
send({
|
||||||
|
type: 'video-answer',
|
||||||
|
name: name,
|
||||||
|
target: target,
|
||||||
|
sdp: answer
|
||||||
|
})
|
||||||
|
|
||||||
|
//var keys = connections.keys().next().value
|
||||||
|
videoTitle.innerHTML = name + ' | connected to '
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleVideoAnswer(sdp, target) {
|
||||||
|
console.log('GOT VIDEO ANSWER FROM ' + target)
|
||||||
|
|
||||||
|
var connection = connections[target]
|
||||||
|
await connection.setRemoteDescription(new RTCSessionDescription(sdp))
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleSignalingStateChangeEvent(connection) {
|
||||||
|
console.log('STATE CHANGED TO : ' + connection.signalingState)
|
||||||
|
switch(connection.signalingState) {
|
||||||
|
case 'closed':
|
||||||
|
await connection.close()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleTrackEvent(event) {
|
||||||
|
console.log('GOT TRACK')
|
||||||
|
remoteVideo.srcObject = event.streams[0]
|
||||||
|
videoDiv.style.display = 'block'
|
||||||
|
loadDiv.style.display = 'none'
|
||||||
|
}
|
||||||
|
|
||||||
|
function getConnectionStats() {
|
||||||
|
connections.forEach((connection, target) => {
|
||||||
|
console.log('[' + target + '] ' + connection.connectionState)
|
||||||
|
})
|
||||||
|
}
|
||||||
64
server/client/scripts/script.js
Normal file
64
server/client/scripts/script.js
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
const loginDiv = document.querySelector('#loginDiv')
|
||||||
|
const loginInput = document.querySelector('#loginInput')
|
||||||
|
const loginBt = document.querySelector('#loginBt')
|
||||||
|
|
||||||
|
const connectDiv = document.querySelector('#connectDiv')
|
||||||
|
const callInput = document.querySelector('#callInput')
|
||||||
|
const callBt = document.querySelector('#callBt')
|
||||||
|
const videoInput = document.querySelector('#videoInput')
|
||||||
|
|
||||||
|
const videoDiv = document.querySelector('#videoDiv')
|
||||||
|
const videoTitle = document.querySelector('#videoTitle')
|
||||||
|
const remoteVideo = document.querySelector('#video')
|
||||||
|
const disconnectBt = document.querySelector('#disconnectBt')
|
||||||
|
|
||||||
|
const loadDiv = document.querySelector('#loadDiv')
|
||||||
|
|
||||||
|
var stream
|
||||||
|
|
||||||
|
loginDiv.style.display = 'block'
|
||||||
|
|
||||||
|
loginBt.addEventListener('click', function (event) {
|
||||||
|
name = loginInput.value
|
||||||
|
|
||||||
|
if (name.length > 0) {
|
||||||
|
send({
|
||||||
|
type: 'login',
|
||||||
|
name: name
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
callBt.addEventListener('click', function () {
|
||||||
|
var callToUsername = callInput.value;
|
||||||
|
|
||||||
|
if (callToUsername.length > 0) {
|
||||||
|
makeOffer(callToUsername)
|
||||||
|
loadDiv.style.display = 'block'
|
||||||
|
connectDiv.style.display = 'none'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
disconnectBt.addEventListener('click', function () {
|
||||||
|
send({
|
||||||
|
type: 'leave',
|
||||||
|
name: name
|
||||||
|
})
|
||||||
|
handleLeave()
|
||||||
|
videoDiv.style.display = 'none'
|
||||||
|
loginDiv.style.display = 'block'
|
||||||
|
})
|
||||||
|
|
||||||
|
videoInput.addEventListener('change', function (event) {
|
||||||
|
remoteVideo.src = URL.createObjectURL(this.files[0])
|
||||||
|
|
||||||
|
videoDiv.style.display = 'block'
|
||||||
|
connectDiv.style.display = 'none'
|
||||||
|
videoTitle.innerHTML = name + ' | ' + connections.size + ' user connected'
|
||||||
|
})
|
||||||
|
|
||||||
|
remoteVideo.onplay = function () {
|
||||||
|
console.log('ADD STREAM')
|
||||||
|
if(remoteVideo.mozCaptureStream()) stream = remoteVideo.mozCaptureStream()
|
||||||
|
else stream = remoteVideo.captureStream()
|
||||||
|
}
|
||||||
58
server/client/scripts/signal.js
Normal file
58
server/client/scripts/signal.js
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
//connecting to our signaling server
|
||||||
|
var conn = new WebSocket('ws://gltronic.ovh/lilstreamy/ws');
|
||||||
|
|
||||||
|
conn.onopen = function () {
|
||||||
|
console.log('Connected to the signaling server')
|
||||||
|
}
|
||||||
|
|
||||||
|
conn.onmessage = function (msg) {
|
||||||
|
console.log('Got message', msg.data);
|
||||||
|
|
||||||
|
var data = JSON.parse(msg.data);
|
||||||
|
|
||||||
|
switch (data.type) {
|
||||||
|
case 'login':
|
||||||
|
handleLogin(data.success)
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'offer':
|
||||||
|
handleOffer(data.offer, data.name)
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'answer':
|
||||||
|
handleAnswer(data.answer, data.name)
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'candidate':
|
||||||
|
handleCandidate(data.candidate, data.name)
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'userlist':
|
||||||
|
handleUserlist(data)
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'leave':
|
||||||
|
handleLeave()
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'video-offer':
|
||||||
|
handleVideoOffer(data.sdp, data.name)
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'video-answer':
|
||||||
|
handleVideoAnswer(data.sdp, data.name)
|
||||||
|
break
|
||||||
|
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
conn.onerror = function (err) {
|
||||||
|
console.log('Got error', err)
|
||||||
|
}
|
||||||
|
|
||||||
|
function send(message) {
|
||||||
|
console.log('Sended message', message)
|
||||||
|
conn.send(JSON.stringify(message))
|
||||||
|
}
|
||||||
49
server/client/style.css
Normal file
49
server/client/style.css
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
body {
|
||||||
|
color: #ffffff;
|
||||||
|
background-color: #23232e;
|
||||||
|
font: 14px normal Arial, Helvetica, sans-serif;
|
||||||
|
z-index: -4;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
div {
|
||||||
|
display: none;
|
||||||
|
margin: auto;
|
||||||
|
padding: 20px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
video {
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#loadDiv {
|
||||||
|
display: none;
|
||||||
|
position: fixed;
|
||||||
|
z-index: 1;
|
||||||
|
padding-top: 100px;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: auto;
|
||||||
|
background-color: rgb(0,0,0);
|
||||||
|
background-color: rgba(0,0,0,0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.loader {
|
||||||
|
margin: auto;
|
||||||
|
padding: 20px;
|
||||||
|
border: 16px solid #f3f3f3;
|
||||||
|
border-top: 16px solid #3498db;
|
||||||
|
border-bottom: 16px solid violet;
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 120px;
|
||||||
|
height: 120px;
|
||||||
|
animation: spin 0.5s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
0% { transform: rotate(0deg); }
|
||||||
|
100% { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
{
|
{
|
||||||
"port" : 8080
|
"port" : 8080,
|
||||||
|
"ws_port" : 9090
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
const express = require('express');
|
const express = require('express')
|
||||||
const bodyParser = require("body-parser");
|
const bodyParser = require('body-parser')
|
||||||
const signal = require("./signal3");
|
const cors = require('cors')
|
||||||
const cors = require("cors");
|
const config = require('../config.json')
|
||||||
const config = require("../config.json");
|
|
||||||
|
require('./signal')
|
||||||
|
|
||||||
const app = express()
|
const app = express()
|
||||||
const {port} = config
|
const {port} = config
|
||||||
@@ -11,7 +12,7 @@ app.use(cors())
|
|||||||
app.use(bodyParser.urlencoded({ extended: false }))
|
app.use(bodyParser.urlencoded({ extended: false }))
|
||||||
app.use(bodyParser.json())
|
app.use(bodyParser.json())
|
||||||
|
|
||||||
app.use(express.static("../clientV"))
|
app.use(express.static('./client'))
|
||||||
|
|
||||||
app.listen(port, () => {
|
app.listen(port, () => {
|
||||||
console.log(`Launching Lil'Streamy on ${port}`)
|
console.log(`Launching Lil'Streamy on ${port}`)
|
||||||
|
|||||||
@@ -1,154 +1,102 @@
|
|||||||
const WebSocketServer = require('ws').Server;
|
const WebSocketServer = require('ws').Server
|
||||||
|
|
||||||
var wss = new WebSocketServer({port: 9090});
|
var wss = new WebSocketServer({port: 9090})
|
||||||
|
|
||||||
var users = {};
|
var users = {}
|
||||||
|
|
||||||
wss.on('connection', function(connection) {
|
wss.on('connection', function(connection) {
|
||||||
|
console.log('User connected')
|
||||||
|
|
||||||
console.log("User connected");
|
connection.on('message', (message) => onMessage(connection, message))
|
||||||
|
|
||||||
//when server gets a message from a connected user
|
connection.on('close', () => onClose(connection))
|
||||||
connection.on('message', (message) => onMessage(connection, message));
|
})
|
||||||
|
|
||||||
//when user exits, for example closes a browser window
|
|
||||||
//this may help if we are still in "offer","answer" or "candidate" state
|
|
||||||
connection.on("close", () => onClose(connection));
|
|
||||||
});
|
|
||||||
|
|
||||||
function sendTo(connection, message) {
|
function sendTo(connection, message) {
|
||||||
connection.send(JSON.stringify(message));
|
connection.send(JSON.stringify(message))
|
||||||
}
|
}
|
||||||
|
|
||||||
function onClose(connection) {
|
function onClose(connection) {
|
||||||
if(connection.name) {
|
if(connection.name) {
|
||||||
delete users[connection.name];
|
delete users[connection.name]
|
||||||
|
|
||||||
if(connection.otherName) {
|
if(connection.otherName) {
|
||||||
console.log("Disconnecting from ", connection.otherName);
|
console.log('Disconnecting from ', connection.otherName)
|
||||||
var conn = users[connection.otherName];
|
var conn = users[connection.otherName]
|
||||||
conn.otherName = null;
|
conn.otherName = null
|
||||||
|
|
||||||
if(conn != null) {
|
if(conn != null) {
|
||||||
sendTo(conn, {
|
sendTo(conn, {
|
||||||
type: "leave"
|
type: 'leave'
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onMessage(connection, message) {
|
function onMessage(connection, message) {
|
||||||
var data;
|
var data
|
||||||
|
|
||||||
//accepting only JSON messages
|
try {
|
||||||
try {
|
data = JSON.parse(message);
|
||||||
data = JSON.parse(message);
|
} catch (e) {
|
||||||
} catch (e) {
|
console.log('Invalid JSON')
|
||||||
console.log("Invalid JSON");
|
data = {}
|
||||||
data = {};
|
}
|
||||||
}
|
|
||||||
|
|
||||||
//switching type of the user message
|
switch (data.type) {
|
||||||
switch (data.type) {
|
case 'login' :
|
||||||
//when a user tries to login
|
console.log('User logged', data.name);
|
||||||
case "login":
|
if(users[data.name]) {
|
||||||
console.log("User logged", data.name);
|
sendTo(connection, {
|
||||||
|
type: 'login',
|
||||||
//if anyone is logged in with this username then refuse
|
success: false
|
||||||
if(users[data.name]) {
|
})
|
||||||
sendTo(connection, {
|
} else {
|
||||||
type: "login",
|
users[data.name] = connection
|
||||||
success: false
|
connection.name = data.name
|
||||||
});
|
|
||||||
} else {
|
sendTo(connection, {
|
||||||
//save user connection on the server
|
type: 'login',
|
||||||
users[data.target] = connection;
|
success: true
|
||||||
connection.name = data.target;
|
})
|
||||||
|
}
|
||||||
sendTo(connection, {
|
break
|
||||||
type: "login",
|
|
||||||
success: true
|
case 'leave' :
|
||||||
});
|
console.log('Disconnecting from', data.name)
|
||||||
}
|
var conn = users[data.name]
|
||||||
break;
|
conn.otherName = null
|
||||||
|
|
||||||
case "offer":
|
|
||||||
//for ex. UserA wants to call UserB
|
|
||||||
console.log("Sending offer to: ", data.target);
|
|
||||||
|
|
||||||
//if UserB exists then send him offer details
|
|
||||||
var conn = users[data.target];
|
|
||||||
|
|
||||||
if(conn != null) {
|
|
||||||
//setting that UserA connected with UserB
|
|
||||||
connection.otherName = data.target;
|
|
||||||
|
|
||||||
sendTo(conn, {
|
|
||||||
type: "offer",
|
|
||||||
offer: data.offer,
|
|
||||||
name: connection.target
|
|
||||||
});
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "answer":
|
|
||||||
console.log("Sending answer to: ", data.target);
|
|
||||||
//for ex. UserB answers UserA
|
|
||||||
var conn = users[data.target];
|
|
||||||
|
|
||||||
if(conn != null) {
|
|
||||||
connection.otherName = data.target;
|
|
||||||
sendTo(conn, {
|
|
||||||
type: "answer",
|
|
||||||
answer: data.answer
|
|
||||||
});
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "candidate":
|
|
||||||
console.log("Sending candidate to:",data.target);
|
|
||||||
var conn = users[data.name];
|
|
||||||
|
|
||||||
if(conn != null) {
|
|
||||||
sendTo(conn, {
|
|
||||||
type: "candidate",
|
|
||||||
candidate: data.candidate
|
|
||||||
});
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "leave":
|
|
||||||
console.log("Disconnecting from", data.target);
|
|
||||||
var conn = users[data.target];
|
|
||||||
conn.otherName = null;
|
|
||||||
|
|
||||||
//notify the other user so he can disconnect his peer connection
|
|
||||||
if(conn != null) {
|
|
||||||
sendTo(conn, {
|
|
||||||
type: "leave"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "video-offer":
|
//notify the other user so he can disconnect his peer connection
|
||||||
console.log("Send video offer to", data.target);
|
/*
|
||||||
var conn = users[data.name];
|
if(conn != null) {
|
||||||
|
sendTo(conn, {
|
||||||
if(conn != null) {
|
type: 'leave'
|
||||||
connection.otherName = data.target;
|
});
|
||||||
sendTo(conn, {
|
}*/
|
||||||
type: "video-offer",
|
break;
|
||||||
answer: data.answer
|
|
||||||
});
|
case 'userlist' :
|
||||||
}
|
console.log('Send list to', data.name)
|
||||||
break;
|
sendTo(connection, {
|
||||||
|
type: 'userlist',
|
||||||
default:
|
list: users
|
||||||
sendTo(connection, {
|
});
|
||||||
type: "error",
|
break
|
||||||
message: "Command not found: " + data.type
|
|
||||||
});
|
default:
|
||||||
break;
|
if (data.target){
|
||||||
}
|
var targetConnection = users[data.target];
|
||||||
|
if(targetConnection){
|
||||||
|
console.log('Forward message from ' + data.name + ' to ' + data.target + ' (' + data.type + ')')
|
||||||
|
sendTo(targetConnection, data)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sendTo(connection, {
|
||||||
|
type: 'error',
|
||||||
|
message: 'Command not found: ' + data.type
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,100 +0,0 @@
|
|||||||
const WebSocketServer = require('ws').Server;
|
|
||||||
|
|
||||||
var wss = new WebSocketServer({port: 9090});
|
|
||||||
|
|
||||||
var users = {};
|
|
||||||
|
|
||||||
wss.on('connection', function(connection) {
|
|
||||||
console.log("User connected");
|
|
||||||
|
|
||||||
connection.on('message', (message) => onMessage(connection, message));
|
|
||||||
|
|
||||||
connection.on("close", () => onClose(connection));
|
|
||||||
});
|
|
||||||
|
|
||||||
function sendTo(connection, message) {
|
|
||||||
connection.send(JSON.stringify(message));
|
|
||||||
}
|
|
||||||
|
|
||||||
function onClose(connection) {
|
|
||||||
if(connection.name) {
|
|
||||||
delete users[connection.name];
|
|
||||||
|
|
||||||
if(connection.otherName) {
|
|
||||||
console.log("Disconnecting from ", connection.otherName);
|
|
||||||
var conn = users[connection.otherName];
|
|
||||||
conn.otherName = null;
|
|
||||||
|
|
||||||
if(conn != null) {
|
|
||||||
sendTo(conn, {
|
|
||||||
type: "leave"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function onMessage(connection, message) {
|
|
||||||
var data;
|
|
||||||
|
|
||||||
try {
|
|
||||||
data = JSON.parse(message);
|
|
||||||
} catch (e) {
|
|
||||||
console.log("Invalid JSON");
|
|
||||||
data = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (data.type) {
|
|
||||||
case "login" :
|
|
||||||
console.log("User logged", data.name);
|
|
||||||
if(users[data.name]) {
|
|
||||||
sendTo(connection, {
|
|
||||||
type: "login",
|
|
||||||
success: false
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
users[data.name] = connection;
|
|
||||||
connection.name = data.name;
|
|
||||||
|
|
||||||
sendTo(connection, {
|
|
||||||
type: "login",
|
|
||||||
success: true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "leave" :
|
|
||||||
console.log("Disconnecting from", data.name);
|
|
||||||
var conn = users[data.name];
|
|
||||||
conn.otherName = null;
|
|
||||||
|
|
||||||
//notify the other user so he can disconnect his peer connection
|
|
||||||
/*
|
|
||||||
if(conn != null) {
|
|
||||||
sendTo(conn, {
|
|
||||||
type: "leave"
|
|
||||||
});
|
|
||||||
}*/
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "userlist" :
|
|
||||||
console.log("Send list to", data.name);
|
|
||||||
sendTo(connection, {
|
|
||||||
type: "userlist",
|
|
||||||
list: users
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
if (data.target){
|
|
||||||
var targetConnection = users[data.target];
|
|
||||||
console.log("Forward message from " + data.name + " to " + data.target + " (" + data.type + ")");
|
|
||||||
sendTo(targetConnection, data);
|
|
||||||
} else {
|
|
||||||
sendTo(connection, {
|
|
||||||
type: "error",
|
|
||||||
message: "Command not found: " + data.type
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user