UNPKG

rtcmulticonnection

Version:

RTCMultiConnection is a WebRTC JavaScript wrapper library runs top over RTCPeerConnection API to support all possible peer-to-peer features.

390 lines (319 loc) 14.2 kB
<!-- Demo version: 2018.09.30 --> <!DOCTYPE html> <html lang="en" dir="ltr"> <head> <meta charset="utf-8"> <title>WebRTC Scalable Broadcast using RTCMultiConnection</title> <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0"> <link rel="shortcut icon" href="/demos/logo.png"> <link rel="stylesheet" href="/demos/stylesheet.css"> <script src="/demos/menu.js"></script> </head> <body> <header> <a class="logo" href="/demos/"><img src="/demos/logo.png" alt="RTCMultiConnection"></a> <a href="/demos/" class="menu-explorer">Menu<img src="/demos/menu-icon.png" alt="Menu"></a> <nav> <li> <a href="/demos/">Home</a> </li> <li> <a href="https://www.rtcmulticonnection.org/docs/getting-started/">Getting Started</a> </li> <li> <a href="https://www.rtcmulticonnection.org/FAQ/">FAQ</a> </li> <li> <a href="https://www.youtube.com/playlist?list=PLPRQUXAnRydKdyun-vjKPMrySoow2N4tl">YouTube</a> </li> <li> <a href="https://rtcmulticonnection.herokuapp.com/demos/">Demos</a> </li> <li> <a href="https://github.com/muaz-khan/RTCMultiConnection/wiki">Wiki</a> </li> <li> <a href="https://github.com/muaz-khan/RTCMultiConnection">Github</a> </li> </nav> </header> <h1> WebRTC Scalable Broadcast using RTCMultiConnection <p class="no-mobile"> Use peer-to-peer protocol to broadcast your video over 20+ users. </p> </h1> <section class="make-center"> <p style="margin: 0; padding: 0; padding-bottom: 20px;"> <div class="make-center"> <input type="text" id="broadcast-id" value="room-xyz" autocorrect=off autocapitalize=off size=20> <button id="open-or-join">Open or Join Broadcast</button> <div class="make-center" id="broadcast-viewers-counter"></div> </p> <video id="video-preview" controls loop></video> </section> <script src="/dist/RTCMultiConnection.min.js"></script> <script src="/dev/adapter.js"></script> <script src="/socket.io/socket.io.js"></script> <!-- <script src="https://cdn.webrtc-experiment.com/RecordRTC.js"></script> --> <script> // recording is disabled because it is resulting for browser-crash // if you enable below line, please also uncomment above "RecordRTC.js" var enableRecordings = false; var connection = new RTCMultiConnection(); // its mandatory in v3 connection.enableScalableBroadcast = true; // each relaying-user should serve only 1 users connection.maxRelayLimitPerUser = 1; // we don't need to keep room-opened // scalable-broadcast.js will handle stuff itself. connection.autoCloseEntireSession = true; // by default, socket.io server is assumed to be deployed on your own URL connection.socketURL = '/'; // comment-out below line if you do not have your own socket.io server // connection.socketURL = 'https://rtcmulticonnection.herokuapp.com:443/'; connection.socketMessageEvent = 'scalable-media-broadcast-demo'; // document.getElementById('broadcast-id').value = connection.userid; // user need to connect server, so that others can reach him. connection.connectSocket(function(socket) { socket.on('logs', function(log) { document.querySelector('h1').innerHTML = log.replace(/</g, '----').replace(/>/g, '___').replace(/----/g, '(<span style="color:red;">').replace(/___/g, '</span>)'); }); // this event is emitted when a broadcast is already created. socket.on('join-broadcaster', function(hintsToJoinBroadcast) { console.log('join-broadcaster', hintsToJoinBroadcast); connection.session = hintsToJoinBroadcast.typeOfStreams; connection.sdpConstraints.mandatory = { OfferToReceiveVideo: !!connection.session.video, OfferToReceiveAudio: !!connection.session.audio }; connection.broadcastId = hintsToJoinBroadcast.broadcastId; connection.join(hintsToJoinBroadcast.userid); }); socket.on('rejoin-broadcast', function(broadcastId) { console.log('rejoin-broadcast', broadcastId); connection.attachStreams = []; socket.emit('check-broadcast-presence', broadcastId, function(isBroadcastExists) { if (!isBroadcastExists) { // the first person (i.e. real-broadcaster) MUST set his user-id connection.userid = broadcastId; } socket.emit('join-broadcast', { broadcastId: broadcastId, userid: connection.userid, typeOfStreams: connection.session }); }); }); socket.on('broadcast-stopped', function(broadcastId) { // alert('Broadcast has been stopped.'); // location.reload(); console.error('broadcast-stopped', broadcastId); alert('This broadcast has been stopped.'); }); // this event is emitted when a broadcast is absent. socket.on('start-broadcasting', function(typeOfStreams) { console.log('start-broadcasting', typeOfStreams); // host i.e. sender should always use this! connection.sdpConstraints.mandatory = { OfferToReceiveVideo: false, OfferToReceiveAudio: false }; connection.session = typeOfStreams; // "open" method here will capture media-stream // we can skip this function always; it is totally optional here. // we can use "connection.getUserMediaHandler" instead connection.open(connection.userid); }); }); window.onbeforeunload = function() { // Firefox is ugly. document.getElementById('open-or-join').disabled = false; }; var videoPreview = document.getElementById('video-preview'); connection.onstream = function(event) { if (connection.isInitiator && event.type !== 'local') { return; } connection.isUpperUserLeft = false; videoPreview.srcObject = event.stream; videoPreview.play(); videoPreview.userid = event.userid; if (event.type === 'local') { videoPreview.muted = true; } if (connection.isInitiator == false && event.type === 'remote') { // he is merely relaying the media connection.dontCaptureUserMedia = true; connection.attachStreams = [event.stream]; connection.sdpConstraints.mandatory = { OfferToReceiveAudio: false, OfferToReceiveVideo: false }; var socket = connection.getSocket(); socket.emit('can-relay-broadcast'); if (connection.DetectRTC.browser.name === 'Chrome') { connection.getAllParticipants().forEach(function(p) { if (p + '' != event.userid + '') { var peer = connection.peers[p].peer; peer.getLocalStreams().forEach(function(localStream) { peer.removeStream(localStream); }); event.stream.getTracks().forEach(function(track) { peer.addTrack(track, event.stream); }); connection.dontAttachStream = true; connection.renegotiate(p); connection.dontAttachStream = false; } }); } if (connection.DetectRTC.browser.name === 'Firefox') { // Firefox is NOT supporting removeStream method // that's why using alternative hack. // NOTE: Firefox seems unable to replace-tracks of the remote-media-stream // need to ask all deeper nodes to rejoin connection.getAllParticipants().forEach(function(p) { if (p + '' != event.userid + '') { connection.replaceTrack(event.stream, p); } }); } // Firefox seems UN_ABLE to record remote MediaStream // WebAudio solution merely records audio // so recording is skipped for Firefox. if (connection.DetectRTC.browser.name === 'Chrome') { repeatedlyRecordStream(event.stream); } } // to keep room-id in cache localStorage.setItem(connection.socketMessageEvent, connection.sessionid); }; // ask node.js server to look for a broadcast // if broadcast is available, simply join it. i.e. "join-broadcaster" event should be emitted. // if broadcast is absent, simply create it. i.e. "start-broadcasting" event should be fired. document.getElementById('open-or-join').onclick = function() { var broadcastId = document.getElementById('broadcast-id').value; if (broadcastId.replace(/^\s+|\s+$/g, '').length <= 0) { alert('Please enter broadcast-id'); document.getElementById('broadcast-id').focus(); return; } document.getElementById('open-or-join').disabled = true; connection.extra.broadcastId = broadcastId; connection.session = { audio: true, video: true, oneway: true }; var socket = connection.getSocket(); socket.emit('check-broadcast-presence', broadcastId, function(isBroadcastExists) { if (!isBroadcastExists) { // the first person (i.e. real-broadcaster) MUST set his user-id connection.userid = broadcastId; } console.log('check-broadcast-presence', broadcastId, isBroadcastExists); socket.emit('join-broadcast', { broadcastId: broadcastId, userid: connection.userid, typeOfStreams: connection.session }); }); }; connection.onstreamended = function() {}; connection.onleave = function(event) { if (event.userid !== videoPreview.userid) return; var socket = connection.getSocket(); socket.emit('can-not-relay-broadcast'); connection.isUpperUserLeft = true; if (allRecordedBlobs.length) { // playing lats recorded blob var lastBlob = allRecordedBlobs[allRecordedBlobs.length - 1]; videoPreview.src = URL.createObjectURL(lastBlob); videoPreview.play(); allRecordedBlobs = []; } else if (connection.currentRecorder) { var recorder = connection.currentRecorder; connection.currentRecorder = null; recorder.stopRecording(function() { if (!connection.isUpperUserLeft) return; videoPreview.src = URL.createObjectURL(recorder.getBlob()); videoPreview.play(); }); } if (connection.currentRecorder) { connection.currentRecorder.stopRecording(); connection.currentRecorder = null; } }; var allRecordedBlobs = []; function repeatedlyRecordStream(stream) { if (!enableRecordings) { return; } connection.currentRecorder = RecordRTC(stream, { type: 'video' }); connection.currentRecorder.startRecording(); setTimeout(function() { if (connection.isUpperUserLeft || !connection.currentRecorder) { return; } connection.currentRecorder.stopRecording(function() { allRecordedBlobs.push(connection.currentRecorder.getBlob()); if (connection.isUpperUserLeft) { return; } connection.currentRecorder = null; repeatedlyRecordStream(stream); }); }, 30 * 1000); // 30-seconds }; function disableInputButtons() { document.getElementById('open-or-join').disabled = true; document.getElementById('broadcast-id').disabled = true; } // ...................................................... // ......................Handling broadcast-id................ // ...................................................... var broadcastId = ''; if (localStorage.getItem(connection.socketMessageEvent)) { broadcastId = localStorage.getItem(connection.socketMessageEvent); } else { broadcastId = connection.token(); } var txtBroadcastId = document.getElementById('broadcast-id'); txtBroadcastId.value = broadcastId; txtBroadcastId.onkeyup = txtBroadcastId.oninput = txtBroadcastId.onpaste = function() { localStorage.setItem(connection.socketMessageEvent, this.value); }; // below section detects how many users are viewing your broadcast connection.onNumberOfBroadcastViewersUpdated = function(event) { if (!connection.isInitiator) return; document.getElementById('broadcast-viewers-counter').innerHTML = 'Number of broadcast viewers: <b>' + event.numberOfBroadcastViewers + '</b>'; }; </script> <section class="no-mobile"> <h2>How this works?</h2> <p> This module simply initializes socket.io and configures it in a way that single audio/video/screen stream can be shared/relayed over unlimited users without any <a href="https://www.webrtc-experiment.com/docs/RTP-usage.html">bandwidth/CPU usage issues</a>. Everything happens peer-to-peer! </p> <p> Check <a href="https://github.com/muaz-khan/WebRTC-Experiment/issues/2">this thread</a> or <a href="https://github.com/muaz-khan/WebRTC-Scalable-Broadcast">this github repository</a>. </p> </section> <section class="no-mobile"> <h2>iOS or Android</h2> <p> You can write <a href="http://www.rtcmulticonnection.org/docs/Write-iOS-Apps/" target="_blank">iOS</a> or <a href="http://www.rtcmulticonnection.org/docs/Write-Android-Apps/" target="_blank">Android</a> apps for this demo as well. </p> <p> Install following Android app and join broadcasts from this demo: <a href="https://play.google.com/store/apps/details?id=com.webrtc.scalablebroadcast" target="_blank">https://play.google.com/store/apps/details?id=com.webrtc.scalablebroadcast</a> </p> </section> <footer> <small id="send-message"></small> </footer> <script src="https://cdn.webrtc-experiment.com/common.js"></script> </body> </html>