Skip to content

Instantly share code, notes, and snippets.

@zacck-zz
Created February 21, 2019 06:24
Show Gist options
  • Save zacck-zz/898c9c0e702c2aa7de640b45cac694ae to your computer and use it in GitHub Desktop.
Save zacck-zz/898c9c0e702c2aa7de640b45cac694ae to your computer and use it in GitHub Desktop.
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