// Broadcast Types class Signaling {} const JOIN_ROOM = "JOIN_ROOM"; const EXCHANGE = "EXCHANGE"; const REMOVE_USER = "REMOVE_USER"; // DOM Elements let currentUser; let localVideo; let remoteVideoContainer; // Objects let pcPeers = {}; // peer connection let localstream; window.onload = () => { currentUser = document.getElementById("current-user").innerHTML; localVideo = document.getElementById("local-video"); remoteVideoContainer = document.getElementById("remote-video-container"); }; // Ice Credentials const ice = { iceServers: [{urls: ['stun:stun.l.google.com:19302', 'stun:stun.1.google.com:19302']}]}; // Initialize user's own video document.onreadystatechange = async () => { if (document.readyState === "interactive") { try { const stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: true }) localstream = stream; localVideo.srcObject = stream localVideo.muted = true } catch (e) { console.error(e); } } }; // find chatroom const chatroomId = document.getElementById('chatroom-hook').dataset["chatroomId"] const handleJoinSession = async () => { App.session = await App.cable.subscriptions.create({ channel: "ChatRoomsChannel", room: chatroomId }, { connected: () => { broadcastData({ type: JOIN_ROOM, from: currentUser }); }, received: data => { console.log("received", data); if (data.from === currentUser) return; switch (data.type) { case JOIN_ROOM: return joinRoom(data); case EXCHANGE: if (data.to !== currentUser) return; return exchange(data); case REMOVE_USER: return removeUser(data); default: return; } } }); }; const handleLeaveSession = () => { for (user in pcPeers) { pcPeers[user].close(); } pcPeers = {}; App.session.unsubscribe(); remoteVideoContainer.innerHTML = ""; broadcastData({ type: REMOVE_USER, from: currentUser }); }; const joinRoom = data => { createPC(data.from, true); }; const removeUser = data => { console.log("removing user", data.from); let video = document.getElementById(`remoteVideoContainer+${data.from}`); video && video.remove(); delete pcPeers[data.from]; }; const broadcastData = data => { fetch("sessions", { method: "POST", body: JSON.stringify(data), headers: { "content-type": "application/json" } }); }; const createPC = (userId, isOffer) => { let pc = new RTCPeerConnection(ice); pcPeers[userId] = pc; pc.addStream(localstream); if (isOffer) { pc .createOffer() .then(offer => { pc.setLocalDescription(offer); broadcastData({ type: EXCHANGE, from: currentUser, to: userId, sdp: JSON.stringify(pc.localDescription) }); }) .catch(logError); } pc.onicecandidate = event => { if (event.candidate) { broadcastData({ type: EXCHANGE, from: currentUser, to: userId, candidate: JSON.stringify(event.candidate) }); } }; pc.onaddstream = event => { const element = document.createElement("video"); element.id = `remoteVideoContainer+${userId}`; // why is the userId being interpolated? element.autoplay = "autoplay"; element.srcObject = event.stream; element.height = Math.max(document.documentElement.clientHeight, window.innerHeight || 0); remoteVideoContainer.appendChild(element); localVideo.classList.add("video-sm"); }; pc.oniceconnectionstatechange = event => { if (pc.iceConnectionState == "disconnected") { console.log("Disconnected:", userId); broadcastData({ type: REMOVE_USER, from: userId }); } }; return pc; }; const exchange = data => { let pc; if (!pcPeers[data.from]) { pc = createPC(data.from, false); } else { pc = pcPeers[data.from]; } if (data.candidate) { pc .addIceCandidate(new RTCIceCandidate(JSON.parse(data.candidate))) .then(() => console.log("Ice candidate added")) .catch(logError); } if (data.sdp) { sdp = JSON.parse(data.sdp); pc .setRemoteDescription(new RTCSessionDescription(sdp)) .then(() => { if (sdp.type === "offer") { pc.createAnswer().then(answer => { pc.setLocalDescription(answer); broadcastData({ type: EXCHANGE, from: currentUser, to: data.from, sdp: JSON.stringify(pc.localDescription) }); }); } }) .catch(logError); } }; const logError = error => console.warn("Whoops! Error:", error);