Created
February 21, 2019 06:24
-
-
Save zacck-zz/898c9c0e702c2aa7de640b45cac694ae to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import "phoenix_html" | |
// import socket from "./socket" | |
import "phoenix_html" | |
import {Socket, Presence} from "phoenix" | |
import qrcode from "qrcode-generator" | |
//set up QRCODE Generator | |
let typeNumber = 4; | |
let errorCorrectionLevel = 'L'; | |
let qr = qrcode(typeNumber, errorCorrectionLevel); | |
// presense state | |
let presences = {} | |
// build a socket with auth params | |
let socket = new Socket("/socket", { | |
params: { token: window.userToken } | |
}) | |
// connect to the socket | |
socket.connect() | |
// subscribe to a topic on a channel on the socket | |
let lobby = socket.channel("room:lobby", { | |
params: { token: window.userToken } | |
}) | |
//TODO Handle errors with joining here | |
// listen for presense state changes | |
lobby.on("presence_state", state => { | |
presences = Presence.syncState(presences, state) | |
renderOnlineUsers(presences) | |
}) | |
lobby.on("presence_diff", diff => { | |
presences = Presence.syncDiff(presences, diff) | |
renderOnlineUsers(presences) | |
}) | |
// handle chat | |
let chatInput = document.querySelector("#chat-input") | |
let callAlert = document.querySelector("#call-popup") | |
let callNotif = document.querySelector("#call-notif") | |
let answerCall = document.querySelector("#answer-call") | |
let messagesContainer = document.querySelector("#group-messages") | |
let qCode = document.querySelector("#my-code") | |
// listen for enter key to send message | |
chatInput.addEventListener("keypress", event => { | |
if(event.keyCode === 13){ | |
lobby.push("new_msg", {body: chatInput.value}, 10000) | |
chatInput.value = "" | |
} | |
}) | |
//handle new message event | |
lobby.on("new_msg", payload => { | |
let messageItem = document.createElement("li") | |
messageItem.innerText = `[${Date()}] ${payload.body}` | |
messagesContainer.appendChild(messageItem) | |
}) | |
lobby.on("incoming_call", payload => { | |
callAlert.style.visibility = "visible"; | |
callNotif.innerHTML = payload["body"]; | |
}) | |
lobby.on("call_answered", payload => { | |
console.log('qrcode data', payload) | |
qr.addData(payload["conv"]); | |
qr.make(); | |
callAlert.style.visibility = "hidden"; | |
const myImage = qr.createImgTag(); | |
qCode.innerHTML = myImage; | |
getUserMedia(); | |
}) | |
answerCall.addEventListener("click", function() { | |
lobby.push("call_answered", { | |
body: `Incoming call from ${window.usr}`, | |
caller: window.usr, | |
callee: event.target.id | |
}) | |
.receive("ok", function(msg) { | |
console.log('sync start broadcasted', msg); | |
}) | |
}) | |
//join channel on a topic | |
lobby.join() | |
// Initiate call | |
const callAction = (event) => { | |
isInitiator = true; | |
lobby.push("incoming_call", | |
{ | |
body: `Incoming call from ${window.usr}`, | |
caller: window.usr, | |
callee: event.target.id | |
}) | |
} | |
// -------------------- RTC --------------------------------------------------- // | |
let isInitiator = false; | |
let isStarted = false; | |
let localStream; | |
let peerConnection; | |
let remoteStream; | |
let turnReady; | |
let photoContextW, photoContextH; | |
let pcConfig = { | |
'iceServers': [{ | |
'urls': 'stun:stun.l.google.com:19302' | |
}] | |
} | |
// set up audion and video | |
let sdpConstraints = { | |
offerToReceiveAudio: true, | |
offerToReceiveVideo: true | |
} | |
const sendSignallingMessage = (message) => { | |
lobby.push('signalling', { body: message}); | |
} | |
// -------------- Handle Signalling Messages ------------------------ // | |
lobby.on('signalling', (message) => { | |
if(message.body === 'got user media') { | |
maybeStart(); | |
} else if (message.body.type === 'offer') { | |
if(!isInitiator) { | |
if(!isStarted) { | |
maybeStart(); | |
} | |
peerConnection.setRemoteDescription(new RTCSessionDescription(message.body)); | |
doAnswer(); | |
} | |
} else if (message.body.type === 'answer' && isStarted) { | |
peerConnection.setRemoteDescription(new RTCSessionDescription(message.body)); | |
} else if (message.body.type === 'candidate' && isStarted) { | |
var candidate = new RTCIceCandidate({ | |
sdpMLineIndex: message.body.label, | |
candidate: message.body.candidate | |
}); | |
peerConnection.addIceCandidate(candidate); | |
} else if (message.body === 'bye' && isStarted) { | |
handleRemoteHangup(); | |
} | |
}); | |
// ------------------- End handle Signalling ------------------------------ // | |
let localVideo = document.querySelector('#localVideo'); | |
let remoteVideo = document.querySelector('#remoteVideo'); | |
let photo = document.querySelector('#photo'); | |
let videos = document.querySelector('#videos'); | |
let photoContext = photo.getContext('2d'); | |
// get users media | |
function getUserMedia() { | |
navigator.mediaDevices.getUserMedia({ | |
audio: true, | |
video: true | |
}) | |
.then(gotStream) | |
.catch(function(error) { | |
alert('getUserMedia() Error: ', error.message) | |
}); | |
} | |
function gotStream(stream) { | |
localStream = stream; | |
localVideo.srcObject = stream; | |
sendSignallingMessage('got user media'); | |
// start recording | |
buildChunks(); | |
let chunkStream = photo.captureStream(); | |
let mediaRecorder = new MediaRecorder(chunkStream, {mimeType : 'video/webm'}); | |
mediaRecorder.start(60000); | |
mediaRecorder.ondataavailable = handleRecordedBlob | |
if(isInitiator) { | |
maybeStart(); | |
} | |
} | |
function buildChunks() { | |
let h = remoteVideo.videoHeight; | |
let w = remoteVideo.videoWidth; | |
photo.width = w; | |
photo.height = h * 2; | |
photoContext.drawImage(remoteVideo, 0, 0, w, h); | |
photoContext.drawImage(localVideo, 0, h, w, h); | |
qr.renderTo2dContext(photoContext); | |
requestAnimationFrame(buildChunks) | |
} | |
function handleRecordedBlob(chunkEvent) { | |
// push blob | |
let reader = new FileReader(); | |
reader.onloadend = function() { | |
const plain_chunk = reader.result.split(',').pop() | |
console.log('chunk', plain_chunk) | |
lobby.push("keep_frame", plain_chunk) | |
.receive("ok", (msg) => console.log('video chunk saved at', msg)); | |
} | |
reader.readAsDataURL(chunkEvent.data); | |
const keyframe = photo.toDataURL() | |
const plain_data = keyframe.split(',').pop() | |
lobby.push("keep_frame", plain_data) | |
.receive("ok", (msg) => console.log('keyframe saved at', msg)); | |
//console.log('recorded blob: ',blob, ' at:', window.performance.now()) | |
} | |
const constraints = { | |
video: true | |
}; | |
function maybeStart() { | |
if(!isStarted && typeof localStream !== 'undefined') { | |
createPeerConnection(); | |
peerConnection.addStream(localStream); | |
isStarted = true; | |
if(isInitiator) { | |
doCall(); | |
} | |
} | |
} | |
// handle Hanging up | |
window.onbeforeunload = () => { | |
sendSignallingMessage('bye'); | |
} | |
function createPeerConnection() { | |
let servers = { | |
"iceServers": [{ | |
url: "turn:numb.viagenie.ca", | |
username: "[email protected]", | |
credential: "alphabeta1" | |
}] | |
}; | |
try { | |
peerConnection = new RTCPeerConnection(servers); | |
peerConnection.onicecandidate = handleIceCandidate; | |
peerConnection.onaddstream = handleRemoteStreamAdded; | |
peerConnection.onremovestream = handleRemoteStreamRemoved; | |
} catch (e) { | |
alert('Cannot create PeerConnection object: ' + e.message); | |
return; | |
} | |
} | |
function handleIceCandidate(event) { | |
if(event.candidate) { | |
sendSignallingMessage({ | |
type: 'candidate', | |
label: event.candidate.sdpMLineIndex, | |
id: event.candidate.sdpMid, | |
candidate: event.candidate.candidate | |
}); | |
} else { | |
console.log('End of Candidates'); | |
} | |
} | |
function handleCreateOfferError(event) { | |
//console.log('createOffer() error: ', event); | |
} | |
function doCall() { | |
peerConnection.createOffer(setLocalAndSendMessage, handleCreateOfferError); | |
} | |
function doAnswer() { | |
//console.log('Sending Answer to peer'); | |
peerConnection.createAnswer().then( | |
setLocalAndSendMessage, | |
onCreateSessionDescriptionError | |
); | |
} | |
function setLocalAndSendMessage(sessionDescription) { | |
peerConnection.setLocalDescription(sessionDescription); | |
sendSignallingMessage(sessionDescription); | |
} | |
function onCreateSessionDescriptionError(error) { | |
//console.log('Failed to create session description: ' + error.toString()); | |
} | |
function handleRemoteStreamAdded(event) { | |
remoteStream = event.stream; | |
remoteVideo.srcObject = remoteStream; | |
} | |
function handleRemoteStreamRemoved(event) { | |
//console.log('Remote Stream removed, Event: ', event); | |
} | |
function hangup() { | |
stop(); | |
sendSignallingMessage('bye'); | |
} | |
function handleRemoteHangup() { | |
stop(); | |
isInitator = false; | |
} | |
function stop() { | |
isStarted = false; | |
peerConnection.close(); | |
peerConnection = null; | |
} | |
// -------------------- END RTC --------------------------------------------------- // | |
// ------------- Util Functions ---------------------------------------------// | |
// render a list of online users | |
function renderOnlineUsers(presences) { | |
let response = "" | |
Presence.list(presences, (id, {metas: [first, ...rest]}) => { | |
response += `<br>${id} <button id=${id}>Call</button> </br>` | |
}) | |
document.querySelector("#people").innerHTML = response | |
// event listener | |
for (var person in presences) { | |
document.querySelector(`#${person}`).addEventListener("click", callAction) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment