<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no"> <title>Home - KEVIN</title> <link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css?h=5d0606363cf6affbcda5495e4fefc96c"> <link rel="stylesheet" href="assets/fonts/Lato.css"> <link rel="stylesheet" href="assets/fonts/font-awesome.min.css?h=9db842b3dc3336737559eb4abc0f1b3d"> <link rel="stylesheet" href="assets/fonts/material-icons.min.css?h=9db842b3dc3336737559eb4abc0f1b3d"> <link rel="stylesheet" href="assets/fonts/simple-line-icons.min.css?h=9db842b3dc3336737559eb4abc0f1b3d"> <link rel="stylesheet" href="assets/css/animate.css"> </head> <body> <nav id="nav" class="navbar navbar-light navbar-expand bg-light navigation-clean"> <div class="container"><a class="navbar-brand" href="/">KEVIN</a><button data-toggle="collapse" class="navbar-toggler" data-target="#navcol-1"></button> <div class="collapse navbar-collapse" id="navcol-1"></div> </div> </nav> <section class="features-icons bg-light text-center"> <div class="container"> <div class="row" id="header"> <div class="col-lg-4"> <div class="mx-auto features-icons-item mb-5 mb-lg-0 mb-lg-3"> <div class="d-flex features-icons-icon" id="open-room"><i class="material-icons m-auto text-primary" data-bs-hover-animate="pulse">group_add</i></div> <h3>Group Chat</h3> <p class="lead mb-0">Create a Group Chat with several people</p> </div> </div> <div class="col-lg-4"> <div class="mx-auto features-icons-item mb-5 mb-lg-0 mb-lg-3"> <div class="d-flex features-icons-icon"><i class="icon-camrecorder m-auto text-primary" data-toggle="modal" data-target="#preview-webcam" data-bs-hover-animate="pulse"></i></div> <h3>Share Webcam</h3> <p class="lead mb-0">Share your Webcam + Audio</p> </div> </div> <div class="col-lg-4"> <div class="mx-auto features-icons-item mb-5 mb-lg-0 mb-lg-3"> <div class="d-flex features-icons-icon" id="screen-share"><i class="icon-screen-desktop m-auto text-primary" data-bs-hover-animate="pulse"></i></div> <h3>Share Screen</h3> <p class="lead mb-0">Share your Screen</p> </div> </div> </div> <div class="row"> <div class="col-auto align-self-center mx-auto"> <div id="videos-container" style="margin: 20px 0;"></div> </div> </div> <div class="row"> <div class="col-auto align-self-center mx-auto"> <div id="videos-container"></div> <div id="room-urls" style="display: none;background: #F1EDED;border: 1px solid rgb(189, 189, 189);border-left: 0;border-right: 0;margin: 10px;"></div> <input type="text" id="room-id" value="abcdef" autocorrect=off autocapitalize=off size=20 style="display: none;"> </div> </div> <div class="row"> <div class="col-auto align-self-center mx-auto"> <label id="record"><input type="checkbox" id="record-entire-conference">Record?</label> </div> </div> <div class="row"> <div class="col-lg-4 align-self-center mx-auto"> <span id="recording-status" style="display: none;"></span> </div> <div class="col-lg-4 align-self-center mx-auto"> <span id="recording-time" style="display: none;"></span> </div> <div class="col-lg-4 align-self-center mx-auto"> <button class="btn btn-primary" id="btn-stop-recording" style="display: none;" type="button">Stop Recording</button> </div> </div> </div> </section> <div class="modal fade" id="preview-webcam" tabindex="-1" aria-labelledby="preview-webcam-label" aria-hidden="true"> <div class="modal-dialog modal-xl"> <div class="modal-content"> <div class="modal-header"> <h5 class="modal-title" id="preview-webcam-label">Preview</h5> <button type="button" class="close" data-dismiss="modal" aria-label="Close"> <span aria-hidden="true">×</span> </button> </div> <div class="modal-body"> <div id="container-preview" style="margin: 0px auto;width: 100%;"> <div class="d-flex justify-content-center"> <video playsinline autoplay id="videoPreview" width="640"></video> </div> <div class="d-flex justify-content-center"> <label for="videoSource" style="margin-top: 5px;">Video source: </label><select id="videoSource"></select> <label for="audioSource" style="margin-top: 5px;margin-left: 10px;">Audio source: </label><select id="audioSource"></select> </div> <div class="d-flex justify-content-center"> <canvas width="320px" height="30px" id="vumeter" style="background-color: black;"></canvas> </div> </div> </div> <div class="modal-footer"> <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button> <button type="button" class="btn btn-primary" id="webcam-share">Start</button> </div> </div> </div> </div> <footer class="footer bg-light"> <div class="container"> <div class="row" id="footer"> <div class="col-lg-6 my-auto h-100 text-center text-lg-left"> <ul class="list-inline mb-2"></ul> <p class="text-muted small mb-4 mb-lg-0">rC3 Edition</p> </div> <div class="col-lg-6 my-auto h-100 text-center text-lg-right"> <ul class="list-inline mb-0"> <li class="list-inline-item"><a href="#"></a></li> <li class="list-inline-item"><a href="#"></a></li> <li class="list-inline-item"><a href="https://github.com/voc/kevin"><i class="fa fa-github fa-2x fa-fw"></i></a></li> </ul> </div> </div> </div> </footer> <script src="assets/js/jquery.min.js?h=89312d34339dcd686309fe284b3f226f"></script> <script src="assets/bootstrap/js/bootstrap.min.js?h=03ab36d1dde930b7d44a712f19075641"></script> <script src="assets/js/script.min.js?h=84a111365b31658389bfd638cc1392eb"></script> <script src="assets/js/RTCMultiConnection.js"></script> <script src="assets/js/adapter.js"></script> <script src="/socket.io/socket.io.js"></script> <link rel="stylesheet" href="assets/css/getHTMLMediaElement.css"> <script src="assets/js/getHTMLMediaElement.js"></script> <script src="assets/js/RecordRTC.js"></script> <script> // ...................................................... // .......................UI Code........................ // ...................................................... document.getElementById('open-room').onclick = function() { window.addEventListener('beforeunload', function (e) { e.preventDefault(); e.returnValue = ''; }); disableInputButtons(); connection.open(document.getElementById('room-id').value, function(isRoomExist, roomid, error) { if(error) { disableInputButtons(true); alert(error); } else if (connection.isInitiator === true) { showRoomURL(roomid); } }); }; document.getElementById('webcam-share').onclick = function() { window.addEventListener('beforeunload', function (e) { e.preventDefault(); e.returnValue = ''; }); disableInputButtons(); connection.sdpConstraints.mandatory = { OfferToReceiveAudio: true, OfferToReceiveVideo: true }; connection.session = { audio: true, video: true, oneway: true }; const audioInputSelect = document.querySelector('select#audioSource'); const videoSelect = document.querySelector('select#videoSource'); const audioSource = audioInputSelect.value; const videoSource = videoSelect.value; connection.mediaConstraints.audio = { deviceId: audioSource }; connection.mediaConstraints.video = { deviceId: videoSource }; connection.open(document.getElementById('room-id').value, function(isRoomExist, roomid, error) { if(error) { disableInputButtons(true); alert(error); } else if (connection.isInitiator === true) { showRoomURL(roomid); } }); params.source = 'webcam'; }; document.getElementById('screen-share').onclick = function() { window.addEventListener('beforeunload', function (e) { e.preventDefault(); e.returnValue = ''; }); disableInputButtons(); connection.sdpConstraints.mandatory = { OfferToReceiveAudio: false, OfferToReceiveVideo: false }; connection.session = { screen: true, oneway: true }; connection.open(document.getElementById('room-id').value, function(isRoomExist, roomid, error) { if(error) { disableInputButtons(true); alert(error); } else if (connection.isInitiator === true) { showRoomURL(roomid); } }); params.source = 'screen'; }; // ...................................................... // ..................RTCMultiConnection Code............. // ...................................................... var connection = new RTCMultiConnection(); connection.socketURL = 'https://kevin.c3voc.de/'; connection.socketMessageEvent = 'rC3-VOCcast'; connection.session = { audio: true, video: true, //oneway: true }; connection.sdpConstraints.mandatory = { OfferToReceiveAudio: true, OfferToReceiveVideo: true }; // STAR_FIX_VIDEO_AUTO_PAUSE_ISSUES // via: https://github.com/muaz-khan/RTCMultiConnection/issues/778#issuecomment-524853468 var bitrates = 512; var resolutions = 'Ultra-HD'; var videoConstraints = {}; if (resolutions == 'HD') { videoConstraints = { width: { ideal: 1280 }, height: { ideal: 720 }, frameRate: 30 }; } if (resolutions == 'Ultra-HD') { videoConstraints = { width: { ideal: 1920 }, height: { ideal: 1080 }, frameRate: 30 }; } connection.mediaConstraints = { video: videoConstraints, audio: true }; var CodecsHandler = connection.CodecsHandler; connection.processSdp = function(sdp) { var codecs = 'vp8'; if (codecs.length) { sdp = CodecsHandler.preferCodec(sdp, codecs.toLowerCase()); } if (resolutions == 'HD') { sdp = CodecsHandler.setApplicationSpecificBandwidth(sdp, { audio: 128, video: bitrates, screen: bitrates }); sdp = CodecsHandler.setVideoBitrates(sdp, { min: bitrates * 8 * 1024, max: bitrates * 8 * 1024, }); } if (resolutions == 'Ultra-HD') { sdp = CodecsHandler.setApplicationSpecificBandwidth(sdp, { audio: 128, video: bitrates, screen: bitrates }); sdp = CodecsHandler.setVideoBitrates(sdp, { min: bitrates * 8 * 1024, max: bitrates * 8 * 1024, }); } return sdp; }; // END_FIX_VIDEO_AUTO_PAUSE_ISSUES connection.iceServers = []; connection.iceServers.push({ 'urls': 'stun:stun.franconian.net:3478', }); connection.iceServers.push({ 'urls': 'turn:stun.franconian.net:3478', 'credential': 'ec0WlmT6lj0p4ou8XFAV66nTDopm9JPra3TnIIkxUM1VaqwAzTtoFqbXVeOoFsdU', 'username': 'turnuser', }); connection.videosContainer = document.getElementById('videos-container'); connection.onstream = function(event) { var existing = document.getElementById(event.streamid); if(existing && existing.parentNode) { existing.parentNode.removeChild(existing); } event.mediaElement.removeAttribute('src'); event.mediaElement.removeAttribute('srcObject'); event.mediaElement.muted = true; event.mediaElement.volume = 0; var video = document.createElement('video'); try { video.setAttributeNode(document.createAttribute('autoplay')); video.setAttributeNode(document.createAttribute('playsinline')); } catch (e) { video.setAttribute('autoplay', true); video.setAttribute('playsinline', true); } if(event.type === 'local') { video.volume = 0; try { video.setAttributeNode(document.createAttribute('muted')); } catch (e) { video.setAttribute('muted', true); } } video.srcObject = event.stream; if (params.action && params.action == 'view') { var width = params.width; var mediaElement = getHTMLMediaElement(video, { buttons: [''], width: width, showOnMouseEnter: false }); } else if (params.source && params.source == 'webcam') { try { video.setAttributeNode(document.createAttribute('controls')); } catch (e) { video.setAttribute('controls', true); } var mediaElement = getHTMLMediaElement(video, { buttons: ['full-screen'], showOnMouseEnter: true }); } else if (params.source && params.source == 'screen') { var mediaElement = getHTMLMediaElement(video, { buttons: ['full-screen'], showOnMouseEnter: true }); } else { var width = parseInt(connection.videosContainer.clientWidth / 3) - 20; var mediaElement = getHTMLMediaElement(video, { title: event.userid, buttons: ['full-screen'], width: width, showOnMouseEnter: true }); } connection.videosContainer.appendChild(mediaElement); setTimeout(function() { mediaElement.media.play(); }, 5000); mediaElement.id = event.streamid; console.log('sessionid: ' + connection.sessionid); // to keep room-id in cache //localStorage.setItem(connection.socketMessageEvent, connection.sessionid); function calculateTimeDuration(secs) { var hr = Math.floor(secs / 3600); var min = Math.floor((secs - (hr * 3600)) / 60); var sec = Math.floor(secs - (hr * 3600) - (min * 60)); if (min < 10) { min = "0" + min; } if (sec < 10) { sec = "0" + sec; } if(hr <= 0) { return min + ':' + sec; } return hr + ':' + min + ':' + sec; } chkRecordConference.parentNode.style.display = 'none'; if(chkRecordConference.checked === true) { btnStopRecording.style.display = 'inline-block'; recordingStatus.style.display = 'inline-block'; recordingtime.style.display = 'inline-block'; var recorder = connection.recorder; if(!recorder) { recorder = RecordRTC([event.stream], { type: 'video', disableLogs: false, video: { width: 1920, height: 1080 } }); recorder.startRecording(); dateStarted = new Date().getTime(); connection.recorder = recorder; } else { recorder.getInternalRecorder().addStreams([event.stream]); } if(!connection.recorder.streams) { connection.recorder.streams = []; } connection.recorder.streams.push(event.stream); recordingStatus.innerHTML = 'Recording ' + connection.recorder.streams.length + ' streams'; (function looper() { if(!recorder) { return; } recordingtime.innerHTML = 'Recording Duration: ' + calculateTimeDuration((new Date().getTime() - dateStarted) / 1000); setTimeout(looper, 1000); })(); } if(event.type === 'local') { connection.socket.on('disconnect', function() { if(!connection.getAllParticipants().length) { location.reload(); } }); } }; var recordingtime = document.getElementById('recording-time'); var recordingStatus = document.getElementById('recording-status'); var chkRecordConference = document.getElementById('record-entire-conference'); var btnStopRecording = document.getElementById('btn-stop-recording'); btnStopRecording.onclick = function() { var recorder = connection.recorder; if(!recorder) return alert('No recorder found.'); recorder.stopRecording(function() { var blob = recorder.getBlob(); invokeSaveAsDialog(blob); connection.recorder = null; btnStopRecording.style.display = 'none'; recordingStatus.style.display = 'none'; chkRecordConference.parentNode.style.display = 'none'; recordingtime.style.display = 'none'; }); }; connection.onstreamended = function(event) { var mediaElement = document.getElementById(event.streamid); if (mediaElement) { mediaElement.parentNode.removeChild(mediaElement); } }; connection.onMediaError = function(e) { if (e.message === 'Concurrent mic process limit.') { if (DetectRTC.audioInputDevices.length <= 1) { alert('Please select external microphone. Check github issue number 483.'); return; } var secondaryMic = DetectRTC.audioInputDevices[1].deviceId; connection.mediaConstraints.audio = { deviceId: secondaryMic }; connection.join(connection.sessionid); } }; // .................................. // ALL below scripts are redundant!!! // .................................. function disableInputButtonsView() { //document.getElementById('room-id').onkeyup(); document.getElementById('record-entire-conference').style.display = "none"; document.getElementById('record').style.display = "none"; document.getElementById('header').style.display = "none"; document.getElementById('nav').style.display = "none"; document.getElementById('footer').style.display = "none"; document.getElementById('open-room').disabled = true; document.getElementById('webcam-share').disabled = true; document.getElementById('screen-share').disabled = true; document.getElementById('room-id').disabled = true; } function disableInputButtons(enable) { //document.getElementById('room-id').onkeyup(); document.getElementById('record-entire-conference').style.display = "none"; document.getElementById('record').style.display = "none"; document.getElementById('header').style.display = "none"; document.getElementById('open-room').disabled = !enable; document.getElementById('webcam-share').disabled = !enable; document.getElementById('screen-share').disabled = !enable; document.getElementById('room-id').disabled = !enable; } // ...................................................... // ......................Handling Room-ID................ // ...................................................... function showRoomURL(roomid, source, width) { var roomHashURL = '#' + roomid; var roomQueryStringURL = '?roomid=' + roomid; var roomViewQueryStringURL = '?action=view&roomid=' + roomid; var html = '<h3>Share URLs:</h3><br>'; //html += 'Hash URL: <a href="' + roomHashURL + '" target="_blank">' + roomHashURL + '</a>'; //html += '<br>'; if (source == 'group') { html += 'Join URL: <a href="' + roomQueryStringURL + '" target="_blank">' + roomQueryStringURL + '</a>'; html += '<br />'; } html += 'View URL: <a href="' + roomViewQueryStringURL + '" target="_blank">' + roomViewQueryStringURL + '</a>'; html += '<br />'; html += 'View URL OBS (360p): <a href="view.html' + roomViewQueryStringURL + '&width=640" target="_blank">view.html' + roomViewQueryStringURL + '&width=640</a>'; html += '<br />'; html += 'View URL OBS (720p): <a href="view.html' + roomViewQueryStringURL + '&width=1280" target="_blank">view.html' + roomViewQueryStringURL + '&width=1280</a>'; html += '<br />'; html += 'View URL OBS (1080p): <a href="view.html' + roomViewQueryStringURL + '&width=1920" target="_blank">view.html' + roomViewQueryStringURL + '&width=1920</a>'; var roomURLsDiv = document.getElementById('room-urls'); roomURLsDiv.innerHTML = html; roomURLsDiv.style.display = 'block'; } (function() { var params = {}, r = /([^&=]+)=?([^&]*)/g; function d(s) { return decodeURIComponent(s.replace(/\+/g, ' ')); } var match, search = window.location.search; while (match = r.exec(search.substring(1))) params[d(match[1])] = d(match[2]); window.params = params; console.log(params); })(); function genID() { var chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; var idsize = 8; var streamid = ''; for (var i = 0; i < idsize; i++) { streamid += chars.charAt(Math.floor(Math.random() * chars.length)); } return streamid; } var roomid = ''; //if (localStorage.getItem(connection.socketMessageEvent)) { // roomid = localStorage.getItem(connection.socketMessageEvent); //} else { // roomid = genID(); // console.log('roomid gen: ' + roomid); //} roomid = genID(); console.log('roomid gen: ' + roomid); var txtRoomId = document.getElementById('room-id'); txtRoomId.value = roomid; //txtRoomId.onkeyup = txtRoomId.oninput = txtRoomId.onpaste = function() { // localStorage.setItem(connection.socketMessageEvent, document.getElementById('room-id').value); //}; var hashString = location.hash.replace('#', ''); if (hashString.length && hashString.indexOf('comment-') == 0) { hashString = ''; } var roomid = params.roomid; if (!roomid && hashString.length) { roomid = hashString; } var action = params.action if (roomid && roomid.length) { document.getElementById('room-id').value = roomid; //localStorage.setItem(connection.socketMessageEvent, roomid); if (action && action == 'join') { connection.join(roomid); } // auto-join-room (function reCheckRoomPresence() { connection.checkPresence(roomid, function(isRoomExist) { if (isRoomExist) { if (action && action == 'view') { connection.sdpConstraints.mandatory = { OfferToReceiveAudio: true, OfferToReceiveVideo: true }; connection.session = { audio: true, video: true, oneway: true }; connection.join(roomid); disableInputButtonsView(); return; } else { connection.openOrJoin(roomid); return; } } setTimeout(reCheckRoomPresence, 5000); }); })(); disableInputButtons(); } // detect 2G if(navigator.connection && navigator.connection.type === 'cellular' && navigator.connection.downlinkMax <= 0.115) { alert('2G is not supported. Please use a better internet service.'); } $(document).ready(function(){ $('#preview-webcam').on('shown.bs.modal', function (e) { console.log('starting preview'); const videoElement = document.querySelector('#videoPreview'); const audioInputSelect = document.querySelector('select#audioSource'); const videoSelect = document.querySelector('select#videoSource'); const selectors = [audioInputSelect, videoSelect]; function gotDevices(deviceInfos) { const values = selectors.map(select => select.value); selectors.forEach(select => { while (select.firstChild) { select.removeChild(select.firstChild); } }); for (let i = 0; i !== deviceInfos.length; ++i) { const deviceInfo = deviceInfos[i]; const option = document.createElement('option'); option.value = deviceInfo.deviceId; if (deviceInfo.kind === 'audioinput') { option.text = deviceInfo.label || `microphone ${audioInputSelect.length + 1}`; audioInputSelect.appendChild(option); } else if (deviceInfo.kind === 'videoinput') { option.text = deviceInfo.label || `camera ${videoSelect.length + 1}`; videoSelect.appendChild(option); } else { console.log('Some other kind of source/device: ', deviceInfo); } } selectors.forEach((select, selectorIndex) => { if (Array.prototype.slice.call(select.childNodes).some(n => n.value === values[selectorIndex])) { select.value = values[selectorIndex]; } }); } var max_level_L = 0; var old_level_L = 0; var cnvs = document.getElementById("vumeter"); var cnvs_cntxt = cnvs.getContext("2d"); function gotStream(stream) { window.stream = stream; videoElement.srcObject = stream; var audioContext = new AudioContext(); var microphone = audioContext.createMediaStreamSource(stream); var javascriptNode = audioContext.createScriptProcessor(1024, 1, 1); microphone.connect(javascriptNode); javascriptNode.connect(audioContext.destination); javascriptNode.onaudioprocess = function(event) { var inpt_L = event.inputBuffer.getChannelData(0); var instant_L = 0.0; var sum_L = 0.0; for(var i = 0; i < inpt_L.length; ++i) { sum_L += inpt_L[i] * inpt_L[i]; } instant_L = Math.sqrt(sum_L / inpt_L.length); max_level_L = Math.max(max_level_L, instant_L); instant_L = Math.max( instant_L, old_level_L -0.008 ); old_level_L = instant_L; cnvs_cntxt.clearRect(0, 0, cnvs.width, cnvs.height); cnvs_cntxt.fillStyle = '#00ff00'; cnvs_cntxt.fillRect(10,10,(cnvs.width-20)*(instant_L/max_level_L),(cnvs.height-20)); // x,y,w,h } return navigator.mediaDevices.enumerateDevices(); } function handleError(error) { console.log('navigator.MediaDevices.getUserMedia error: ', error.message, error.name); if (error.name == 'NotAllowedError') { //document.getElementById('container-preview').style.display = "none"; //document.getElementById('webcam-share').style.display = "none"; alert("No devices found. Please check browser permissions for audio/video devices."); window.location.href = "index.html"; } } function start() { if (window.stream) { window.stream.getTracks().forEach(track => { track.stop(); }); } const audioSource = audioInputSelect.value; const videoSource = videoSelect.value; const constraints = { audio: {deviceId: audioSource ? {exact: audioSource} : undefined}, video: {deviceId: videoSource ? {exact: videoSource} : undefined} }; navigator.mediaDevices.getUserMedia(constraints).then(gotStream).then(gotDevices).catch(handleError); } navigator.mediaDevices.enumerateDevices().then(gotDevices); audioInputSelect.onchange = start; videoSelect.onchange = start; start(); }) $('#preview-webcam').on('hide.bs.modal', function (e) { console.log('stoping preview'); var preview = document.querySelector("#videoPreview"); if (preview.srcObject) { var stream = preview.srcObject; var tracks = stream.getTracks(); for (var i = 0; i < tracks.length; i++) { var track = tracks[i]; track.stop(); } preview.srcObject = null; } }) $('#webcam-share').click(function(){ console.log('starting share'); $('#preview-webcam').modal('hide'); }) }); </script> </body> </html>