├── README.md ├── public ├── favicon.ico ├── tile_bg.jpg └── js │ ├── memorygame.js │ └── datachannel.js ├── package.json ├── .gitignore ├── server.js └── views └── index.ejs /README.md: -------------------------------------------------------------------------------- 1 | # memory-webrtc-data-channel 2 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agilityfeat/memory-webrtc-data-channel/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /public/tile_bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agilityfeat/memory-webrtc-data-channel/HEAD/public/tile_bg.jpg -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "memory-webrtc-data-channel", 3 | "version": "0.0.1", 4 | "private": true, 5 | "dependencies": { 6 | "express": "4.x", 7 | "ejs": "2.x", 8 | "express.io": "1.x" 9 | } 10 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 27 | node_modules 28 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | /* 2 | This code was developed by @ArinSime and WebRTC.ventures for a WebRTC blog post. 3 | You are welcome to use it at your own risk as starter code for your applications, 4 | but please be aware that this is not a complete code example with all the necessary 5 | security and privacy considerations implemented that a production app would require. 6 | It is for educational purposes only, and any other use is done at your own risk. 7 | */ 8 | 9 | //Server.js: This is the core Node.js configuration code, and also used for 10 | //setting up signaling channels to be used by socket.io 11 | 12 | var express = require('express.io'); 13 | var app = express(); 14 | app.http().io(); 15 | var PORT = 3000; 16 | console.log('server started on port ' + PORT); 17 | 18 | app.use(express.static(__dirname + '/public')); 19 | 20 | app.get('/', function(req, res){ 21 | res.render('index.ejs'); 22 | }); 23 | 24 | app.listen(process.env.PORT || PORT); 25 | 26 | app.io.route('ready', function(req) { 27 | req.io.join(req.data.signal_room); 28 | }) 29 | 30 | app.io.route('signal', function(req) { 31 | //Note the use of req here for broadcasting so only the sender doesn't receive their own messages 32 | req.io.room(req.data.room).broadcast('signaling_message', { 33 | type: req.data.type, 34 | message: req.data.message 35 | }); 36 | }) 37 | -------------------------------------------------------------------------------- /views/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | DataChannel Example 5 | 6 | 11 | 12 | 13 | 14 | 36 | 37 | 38 | 39 | 40 |
41 | 42 |
43 | 44 |
45 | 46 |
47 |
Data Channel Messages:
48 |
Signaling Messages:
49 |
50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /public/js/memorygame.js: -------------------------------------------------------------------------------- 1 | // Scripted By Adam Khoury in connection with the following video tutorial: 2 | // http://www.youtube.com/watch?v=c_ohDPWmsM0 3 | // https://www.developphp.com/video/JavaScript/Memory-Game-Programming-Tutorial 4 | 5 | // Some changes made by @ArinSime to this Memory Game in order to accomodate use of WebRTC Data Channel to show a simple multi-player use case 6 | 7 | var memory_array = ['A','A','B','B','C','C','D','D','E','E','F','F','G','G','H','H','I','I','J','J','K','K','L','L']; 8 | var memory_values = []; 9 | var memory_tile_ids = []; 10 | var tiles_flipped = 0; 11 | Array.prototype.memory_tile_shuffle = function(){ 12 | var i = this.length, j, temp; 13 | while(--i > 0){ 14 | j = Math.floor(Math.random() * (i+1)); 15 | temp = this[j]; 16 | this[j] = this[i]; 17 | this[i] = temp; 18 | } 19 | } 20 | function newBoard(){ 21 | tiles_flipped = 0; 22 | var output = ''; 23 | for(var i = 0; i < memory_array.length; i++){ 24 | output += '
'; 25 | } 26 | document.getElementById('memory_board').innerHTML = output; 27 | } 28 | function memoryFlipTile(tile,val){ 29 | dataChannel.send("memoryFlipTile " + tile.id); 30 | flipTheTile(tile,val); 31 | } 32 | 33 | function flipTheTile(tile,val){ 34 | 35 | if(tile.innerHTML == "" && memory_values.length < 2){ 36 | tile.style.background = '#FFF'; 37 | tile.innerHTML = val; 38 | if(memory_values.length == 0){ 39 | memory_values.push(val); 40 | memory_tile_ids.push(tile.id); 41 | } else if(memory_values.length == 1){ 42 | memory_values.push(val); 43 | memory_tile_ids.push(tile.id); 44 | if(memory_values[0] == memory_values[1]){ 45 | tiles_flipped += 2; 46 | // Clear both arrays 47 | memory_values = []; 48 | memory_tile_ids = []; 49 | // Check to see if the whole board is cleared 50 | if(tiles_flipped == memory_array.length){ 51 | alert("Board cleared... generating new board"); 52 | document.getElementById('memory_board').innerHTML = ""; 53 | newBoard(); 54 | } 55 | } else { 56 | function flip2Back(){ 57 | // Flip the 2 tiles back over 58 | var tile_1 = document.getElementById(memory_tile_ids[0]); 59 | var tile_2 = document.getElementById(memory_tile_ids[1]); 60 | tile_1.style.background = 'url(tile_bg.jpg) no-repeat'; 61 | tile_1.innerHTML = ""; 62 | tile_2.style.background = 'url(tile_bg.jpg) no-repeat'; 63 | tile_2.innerHTML = ""; 64 | // Clear both arrays 65 | memory_values = []; 66 | memory_tile_ids = []; 67 | } 68 | setTimeout(flip2Back, 700); 69 | } 70 | } 71 | } 72 | } 73 | 74 | var setupBoard = document.querySelector("#setupBoard"); 75 | setupBoard.addEventListener('click', function(ev){ 76 | memory_array.memory_tile_shuffle(); 77 | newBoard(); 78 | dataChannel.send("newBoard " + memory_array.toString()); 79 | ev.preventDefault(); 80 | }, false); -------------------------------------------------------------------------------- /public/js/datachannel.js: -------------------------------------------------------------------------------- 1 | /* 2 | This code was developed by @ArinSime and WebRTC.ventures for a WebRTC blog post. 3 | You are welcome to use it at your own risk as starter code for your applications, 4 | but please be aware that this is not a complete code example with all the necessary 5 | security and privacy considerations implemented that a production app would require. 6 | It is for educational purposes only, and any other use is done at your own risk. 7 | */ 8 | 9 | //datachannel.js: This file contains the WebRTC and DataChannel specific code 10 | 11 | //Page controls 12 | var chatArea = document.querySelector("#chatArea"); 13 | var signalingArea = document.querySelector("#signalingArea"); 14 | 15 | //Signaling Code Setup 16 | var SIGNAL_ROOM = "signaling"; 17 | var configuration = { 18 | 'iceServers': [{ 19 | 'url': 'stun:stun.l.google.com:19302' 20 | }] 21 | }; 22 | var rtcPeerConn; 23 | var dataChannelOptions = { 24 | ordered: false, //no guaranteed delivery, unreliable but faster 25 | maxRetransmitTime: 1000, //milliseconds 26 | }; 27 | var dataChannel; 28 | 29 | io = io.connect(); 30 | io.emit('ready', {"signal_room": SIGNAL_ROOM}); 31 | 32 | //Send a first signaling message to anyone listening 33 | //In other apps this would be on a button click, we are just doing it on page load 34 | io.emit('signal',{"type":"user_here", "message":"Would you like to play a game?", "room":SIGNAL_ROOM}); 35 | 36 | io.on('signaling_message', function(data) { 37 | displaySignalMessage("Signal received: " + data.type); 38 | //Setup the RTC Peer Connection object 39 | if (!rtcPeerConn) 40 | startSignaling(); 41 | 42 | if (data.type != "user_here") { 43 | var message = JSON.parse(data.message); 44 | if (message.sdp) { 45 | rtcPeerConn.setRemoteDescription(new RTCSessionDescription(message.sdp), function () { 46 | // if we received an offer, we need to answer 47 | if (rtcPeerConn.remoteDescription.type == 'offer') { 48 | rtcPeerConn.createAnswer(sendLocalDesc, logError); 49 | } 50 | }, logError); 51 | } 52 | else { 53 | rtcPeerConn.addIceCandidate(new RTCIceCandidate(message.candidate)); 54 | } 55 | } 56 | 57 | }); 58 | 59 | function startSignaling() { 60 | displaySignalMessage("starting signaling..."); 61 | rtcPeerConn = new webkitRTCPeerConnection(configuration, null); 62 | dataChannel = rtcPeerConn.createDataChannel('textMessages', dataChannelOptions); 63 | 64 | dataChannel.onopen = dataChannelStateChanged; 65 | rtcPeerConn.ondatachannel = receiveDataChannel; 66 | 67 | // send any ice candidates to the other peer 68 | rtcPeerConn.onicecandidate = function (evt) { 69 | if (evt.candidate) 70 | io.emit('signal',{"type":"ice candidate", "message": JSON.stringify({ 'candidate': evt.candidate }), "room":SIGNAL_ROOM}); 71 | displaySignalMessage("completed that ice candidate..."); 72 | }; 73 | 74 | // let the 'negotiationneeded' event trigger offer generation 75 | rtcPeerConn.onnegotiationneeded = function () { 76 | displaySignalMessage("on negotiation called"); 77 | rtcPeerConn.createOffer(sendLocalDesc, logError); 78 | } 79 | } 80 | 81 | function sendLocalDesc(desc) { 82 | rtcPeerConn.setLocalDescription(desc, function () { 83 | displaySignalMessage("sending local description"); 84 | io.emit('signal',{"type":"SDP", "message": JSON.stringify({ 'sdp': rtcPeerConn.localDescription }), "room":SIGNAL_ROOM}); 85 | }, logError); 86 | } 87 | 88 | //Data Channel Specific methods 89 | function dataChannelStateChanged() { 90 | if (dataChannel.readyState === 'open') { 91 | displaySignalMessage("Data Channel open"); 92 | dataChannel.onmessage = receiveDataChannelMessage; 93 | } 94 | } 95 | 96 | function receiveDataChannel(event) { 97 | displaySignalMessage("Receiving a data channel"); 98 | dataChannel = event.channel; 99 | dataChannel.onmessage = receiveDataChannelMessage; 100 | } 101 | 102 | function receiveDataChannelMessage(event) { 103 | displayMessage("From DataChannel: " + event.data); 104 | 105 | if (event.data.split(" ")[0] == "memoryFlipTile") { 106 | var tileToFlip = event.data.split(" ")[1]; 107 | displayMessage("Flipping tile " + tileToFlip); 108 | var tile = document.querySelector("#" + tileToFlip); 109 | var index = tileToFlip.split("_")[1]; 110 | var tile_value = memory_array[index]; 111 | flipTheTile(tile,tile_value); 112 | } else if (event.data.split(" ")[0] == "newBoard") { 113 | displayMessage("Setting up new board"); 114 | memory_array = event.data.split(" ")[1].split(","); 115 | newBoard(); 116 | } 117 | 118 | } 119 | 120 | //Logging/Display Methods 121 | function logError(error) { 122 | displaySignalMessage(error.name + ': ' + error.message); 123 | } 124 | 125 | function displayMessage(message) { 126 | chatArea.innerHTML = chatArea.innerHTML + "
" + message; 127 | } 128 | 129 | function displaySignalMessage(message) { 130 | signalingArea.innerHTML = signalingArea.innerHTML + "
" + message; 131 | } --------------------------------------------------------------------------------