├── Components ├── User │ ├── User.js │ └── UserController.js ├── index.js ├── Room │ ├── Room.js │ ├── RoomManager.js │ └── RoomController.js └── Socket │ └── index.js ├── public ├── fonts │ ├── wordcast.eot │ ├── wordcast.ttf │ ├── wordcast.woff │ └── wordcast.svg ├── scss │ ├── _utilities.scss │ ├── _colors.scss │ ├── main.scss │ ├── _forms.scss │ ├── _footer.scss │ ├── _layout.scss │ ├── _fonts.scss │ ├── _ui.scss │ └── _header.scss ├── stylesheets │ └── style.css └── javascripts │ ├── viewer.js │ └── listener.js ├── .gitignore ├── views ├── create-room.jade ├── viewer.jade ├── index.jade ├── listener.jade └── layout.jade ├── config └── index.js ├── package.json ├── README.md ├── LICENSE ├── Gruntfile.js └── server.js /Components/User/User.js: -------------------------------------------------------------------------------- 1 | var User = function() { 2 | 3 | }; 4 | 5 | module.exports = User; 6 | -------------------------------------------------------------------------------- /public/fonts/wordcast.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/a11yproject/Wordcast/HEAD/public/fonts/wordcast.eot -------------------------------------------------------------------------------- /public/fonts/wordcast.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/a11yproject/Wordcast/HEAD/public/fonts/wordcast.ttf -------------------------------------------------------------------------------- /public/fonts/wordcast.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/a11yproject/Wordcast/HEAD/public/fonts/wordcast.woff -------------------------------------------------------------------------------- /public/scss/_utilities.scss: -------------------------------------------------------------------------------- 1 | .pull-left 2 | { 3 | float: left; 4 | } 5 | 6 | .pull-right 7 | { 8 | float: right; 9 | } -------------------------------------------------------------------------------- /Components/User/UserController.js: -------------------------------------------------------------------------------- 1 | var userController = require('express').Router(); 2 | 3 | 4 | 5 | module.exports = userController; 6 | -------------------------------------------------------------------------------- /public/scss/_colors.scss: -------------------------------------------------------------------------------- 1 | $RED: #D51F09; 2 | $NAVY: #007790; 3 | $YELLOW: #F5DA4D; 4 | $BLUE: #07C2EB; 5 | $LIME: #E8F3B7; 6 | $DARK_GRAY: #556270; -------------------------------------------------------------------------------- /public/stylesheets/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 50px; 3 | font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; 4 | } 5 | 6 | a { 7 | color: #00B7FF; 8 | } -------------------------------------------------------------------------------- /Components/index.js: -------------------------------------------------------------------------------- 1 | var router = require('express').Router(); 2 | 3 | router.get('/', function(req, res){ 4 | res.render('index'); 5 | }); 6 | 7 | module.exports = router; 8 | -------------------------------------------------------------------------------- /public/scss/main.scss: -------------------------------------------------------------------------------- 1 | @import "fonts"; 2 | @import "colors"; 3 | @import "layout"; 4 | @import "header"; 5 | @import "footer"; 6 | @import "utilities"; 7 | @import "ui"; 8 | @import "forms"; 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | 10 | pids 11 | logs 12 | results 13 | .DS_Store 14 | 15 | npm-debug.log 16 | node_modules 17 | data 18 | .sass-cache 19 | build/ 20 | lib 21 | public/bootstrap -------------------------------------------------------------------------------- /views/create-room.jade: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block content 4 | h1= title 5 | 6 | form(id="createNewRoom" action="createNewRoom", method="POST") 7 | label 8 | input(type="text" name="RoomName" placeholder="Room Name..") 9 | input(type="submit", value="Start Casting!") -------------------------------------------------------------------------------- /config/index.js: -------------------------------------------------------------------------------- 1 | var config = { 2 | dev : { 3 | baseURI: 'localhost:3000' 4 | }, 5 | 6 | production : { 7 | baseURI: 'http://wordcast.io' 8 | } 9 | 10 | }; 11 | 12 | module.exports = function(mode) { 13 | return config[mode || process.argv[2] || 'dev'] || config.dev; 14 | } 15 | -------------------------------------------------------------------------------- /views/viewer.jade: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block content 4 | article 5 | h1(id="roomName")= title 6 | 7 | div 8 | div(class='log-captions') 9 | 10 | script(type="text/javascript", src="/socket.io/socket.io.js") 11 | script(type="text/javascript", src="/javascripts/viewer.js") 12 | -------------------------------------------------------------------------------- /public/scss/_forms.scss: -------------------------------------------------------------------------------- 1 | input[type="text"], input[type="password"], input[type="email"] 2 | { 3 | padding: 10px 5px; 4 | 5 | border: none; 6 | border-bottom: 2px solid $DARK_GRAY; 7 | font-size: 1.5em; 8 | 9 | color: $DARK_GRAY; 10 | 11 | &:focus 12 | { 13 | outline: none; 14 | } 15 | } 16 | 17 | #createNewRoom 18 | { 19 | width: 100%; 20 | 21 | input[type="text"] { width: 70%; margin-right: 15px; } 22 | input[type="submit"] { width: 20%; } 23 | } 24 | -------------------------------------------------------------------------------- /public/scss/_footer.scss: -------------------------------------------------------------------------------- 1 | footer 2 | { 3 | position: fixed; 4 | bottom: 0; 5 | left: 0; right: 0; 6 | 7 | background: WHITE; 8 | 9 | width: 95%; 10 | margin: 0 auto; 11 | 12 | border-top: 1px dotted $DARK_GRAY; 13 | 14 | .nav 15 | { 16 | list-style-type: none; 17 | 18 | text-align: center; 19 | 20 | li 21 | { 22 | display: inline-block; 23 | padding: 0 5px; 24 | 25 | &:not(:last-child):after 26 | { 27 | content: ' | '; 28 | } 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /Components/Room/Room.js: -------------------------------------------------------------------------------- 1 | var Room = function(ID, name, ownerId) { 2 | 3 | this.ID = ID; 4 | this.name = name; 5 | this.ownerID = ownerId; 6 | 7 | }; 8 | 9 | Room.prototype.getID = function() { 10 | return this.ID; 11 | }; 12 | 13 | Room.prototype.getName = function() { 14 | return this.name; 15 | }; 16 | 17 | Room.prototype.getOwnerID = function() { 18 | return this.ownerID; 19 | }; 20 | 21 | Room.prototype.isOwner = function(user) { 22 | return (this.ownerID === user.ID); 23 | }; 24 | 25 | Room.prototype.getUrl = function() { 26 | return "/room/ID/" + this.ID; 27 | }; 28 | 29 | module.exports = Room; -------------------------------------------------------------------------------- /views/index.jade: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block content 4 | div(class='jumbotron') 5 | h1= title 6 | p Welcome to #{title}! The best way to serve live captions! From classrooms to boardrooms, Wordcast delivers! Provide almost instant captions to listeners with just an internet connection. 7 | 8 | p 9 | | ...and its completely 10 | strong(style='color: red;') 11 | | Free! 12 | 13 | | But, 14 | 15 | //- Open Source Definition 16 | a(href="http://bit.ly/1ixwz0C") 17 | small 18 | | why? 19 | 20 | p 21 | a(class="btn btn-primary btn-lg" href="/room/new" role="button") 22 | | Create Room (Free) -------------------------------------------------------------------------------- /public/scss/_layout.scss: -------------------------------------------------------------------------------- 1 | /* Layout styling */ 2 | 3 | body 4 | { 5 | color: $DARK_GRAY; 6 | 7 | margin: 0 auto; 8 | max-width: 960px; 9 | 10 | font-family: 'Ubuntu', sans-serif; 11 | font-weight: 200; 12 | line-height: 1.5; 13 | padding: 4em 1em 1em; 14 | 15 | padding-top: 70px; 16 | padding-bottom: 70px; 17 | 18 | box-sizing: border-box; 19 | } 20 | 21 | ol 22 | { 23 | padding-left: 0; 24 | } 25 | 26 | li 27 | { 28 | list-style-position: inside; 29 | } 30 | 31 | p 32 | { 33 | margin-bottom: 30px; 34 | } 35 | 36 | .container 37 | { 38 | width: 100%; 39 | } 40 | 41 | [class^='grid-'] 42 | { 43 | float: left; 44 | margin: 0; 45 | } 46 | 47 | .grid-1-2 { width: 50%; } 48 | .grid-1-3 { width: 33%; } 49 | .grid-2-3 { width: 66%; } 50 | .grid-1-4 { width: 25%; } 51 | .grid-3-4 { width: 75%; } 52 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wordcast", 3 | "version": "0.0.1", 4 | "description": "A real-time captioning service over Google Chrome and Socket.IO", 5 | "author": "A11yProject", 6 | "contributors": [ 7 | { 8 | "name": "Jordan Foreman", 9 | "email": "me@jordanforeman.com" 10 | } 11 | ], 12 | "private": true, 13 | "scripts": { 14 | "start": "node app.js" 15 | }, 16 | "dependencies": { 17 | "express": "4.4.3", 18 | "morgan": "~1.0.0", 19 | "method-override": "~1.0.0", 20 | "body-parser": "~1.0.0", 21 | "jade": "latest", 22 | "socket.io": "~1.0.0" 23 | }, 24 | "devDependencies": { 25 | "grunt": "latest", 26 | "grunt-contrib-sass": "~0.7.2", 27 | "grunt-contrib-watch": "~0.5.3", 28 | "grunt-nodemon": "~0.2.0", 29 | "grunt-concurrent": "~0.4.3" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Components/Room/RoomManager.js: -------------------------------------------------------------------------------- 1 | var RoomManager = function() { 2 | if (arguments.callee._singletonInstance) 3 | return arguments.callee._singletonInstance; 4 | arguments.callee._singletonInstance = this; 5 | 6 | this.rooms = new Array(); 7 | }; 8 | 9 | RoomManager.prototype.addRoom = function(room) { 10 | if (!this.hasRoom(room)) 11 | this.rooms.push(room); 12 | else 13 | return false; //TODO: better error maybe? 14 | }; 15 | 16 | RoomManager.prototype.hasRoom = function(room) { 17 | var room = this.getRoomById(room.getID()); 18 | return (room != null); 19 | }; 20 | 21 | RoomManager.prototype.getRoomById = function(ID) { 22 | for (var i = 0, len = this.rooms.length; i < len; i++) { 23 | var room = this.rooms[i], 24 | roomID = room.getID(); 25 | 26 | if (roomID == ID) { 27 | return room; 28 | } 29 | } 30 | }; 31 | 32 | module.exports = new RoomManager() || RoomManager(); 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Wordcast 2 | ======== 3 | 4 | A real-time captioning service over Google Chrome and Socket.IO. 5 | 6 | ## Installation 7 | 8 | Project is in beta. If you find any issues or have any feedback, open an issue. 9 | 10 | * `git clone git@github.com:a11yproject/Wordcast.git` or download the project 11 | * `cd path/to/Wordcast` 12 | * `npm install` the required modules 13 | * `npm install -g grunt-cli` if you don't already have it 14 | * `grunt` to build the app 15 | * `node server.js` to run the app 16 | * Open `http://localhost:3000` in two different browser windows. Chrome 25+ required to broadcast. 17 | * To broadcast in a room, use the `?viewMode=broadcast` param on the end of your room URL. 18 | 19 | ## Contributing 20 | 21 | If you'd like to help, fork the repo and make a pull request! To start working on things, simply: 22 | 23 | $ grunt dev 24 | 25 | This will automate the build process, so you don't have to `grunt` everytime you make a change :) 26 | -------------------------------------------------------------------------------- /public/scss/_fonts.scss: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'wordcast'; 3 | src:url('../../fonts/wordcast.eot'); 4 | src:url('../../fonts/wordcast.eot?#iefix') format('embedded-opentype'), 5 | url('../../fonts/wordcast.woff') format('woff'), 6 | url('../../fonts/wordcast.ttf') format('truetype'), 7 | url('../../fonts/wordcast.svg#wordcast') format('svg'); 8 | font-weight: normal; 9 | font-style: normal; 10 | } 11 | 12 | [class^="icon-"], [class*=" icon-"] { 13 | font-family: 'wordcast'; 14 | speak: none; 15 | font-style: normal; 16 | font-weight: normal; 17 | font-variant: normal; 18 | text-transform: none; 19 | line-height: 1; 20 | 21 | /* Better Font Rendering =========== */ 22 | -webkit-font-smoothing: antialiased; 23 | -moz-osx-font-smoothing: grayscale; 24 | } 25 | 26 | .icon-microphone:before { 27 | content: "\e600"; 28 | } 29 | .icon-cog:before { 30 | content: "\e601"; 31 | } 32 | .icon-user-add:before { 33 | content: "\e602"; 34 | } 35 | .icon-voicemail:before { 36 | content: "\e603"; 37 | } 38 | -------------------------------------------------------------------------------- /views/listener.jade: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block content 4 | div(class="controls") 5 | button(id='cc-toggle' class='btn-lg' onclick="toggleSpeechRecognition(event)") 6 | i(class="icon-microphone") 7 | 8 | //-button(class='btn btn-default', onclick="dummyMsg('here is a message')")| Send Msg 9 | 10 | a(href='#' class='btn') 11 | i(class='icon-cog') 12 | | Settings 13 | 14 | a(href='#' class='btn') 15 | i(class='icon-user-add') 16 | | Invite 17 | 18 | 19 | article(class='grid-1-2') 20 | h1(id="roomName", contenteditable)= title 21 | ol 22 | li 23 | | Open this page in Chrome desktop (25+) 24 | li 25 | | Click button to begin captioning 26 | li 27 | | Begin talking. May need to pause occasionaly 28 | li 29 | | Click button again to stop captioning 30 | 31 | div(class='grid-1-2') 32 | h3 33 | | Caption Log 34 | div(class='log-captions') 35 | 36 | block scripts 37 | script(type='text/javascript', src='/socket.io/socket.io.js') 38 | script(type='text/javascript', src='/javascripts/listener.js') 39 | -------------------------------------------------------------------------------- /public/javascripts/viewer.js: -------------------------------------------------------------------------------- 1 | var socketPath = window.location.protocol + "//" + window.location.host; 2 | var pathArray = window.location.pathname.split('/'); 3 | var roomID = pathArray[3]; 4 | 5 | var socket = io.connect(socketPath); 6 | 7 | var roomName = document.querySelector("#roomName"); 8 | var captionLog = document.querySelector(".log-captions"); 9 | 10 | //=========================== 11 | // 12 | // Socket 13 | // 14 | //=========================== 15 | 16 | // Successfully connected to server 17 | socket.on("connect", function(){ 18 | socket.emit("join room", roomID); 19 | }); 20 | 21 | // Received a new caption 22 | socket.on("new caption", function(data){ 23 | console.log("new caption: " + data); 24 | log(data); 25 | }); 26 | 27 | // Get room name from server 28 | socket.on("room name", function(data){ 29 | console.log("room name: " + data); 30 | roomName.innerHTML = data; 31 | }); 32 | 33 | //=========================== 34 | // 35 | // Util 36 | // 37 | //=========================== 38 | 39 | function log(caption) { 40 | captionLog.innerHTML = caption; 41 | } 42 | -------------------------------------------------------------------------------- /public/scss/_ui.scss: -------------------------------------------------------------------------------- 1 | .btn, button, input[type="submit"] 2 | { 3 | display: inline; 4 | width: auto; 5 | margin-right: 5px; 6 | 7 | background: WHITE; 8 | 9 | padding: 10px 15px; 10 | 11 | border: 2px solid $NAVY; 12 | border-radius: 3px; 13 | 14 | color: $NAVY; 15 | 16 | text-align: center; 17 | font-size: .9em; 18 | text-decoration: none; 19 | 20 | &:hover 21 | { 22 | background: $NAVY; 23 | 24 | color: WHITE; 25 | text-shadow: 0 1px 1px $DARK_GRAY; 26 | 27 | cursor: pointer; 28 | } 29 | } 30 | 31 | .btn-lg 32 | { 33 | padding: 20px 30px; 34 | } 35 | 36 | .jumbotron 37 | { 38 | padding: 10px 20px; 39 | } 40 | 41 | .log-captions { 42 | border: 1px solid $DARK_GRAY; 43 | padding: 10px; 44 | text-align: center; 45 | 46 | min-height: 300px; 47 | 48 | font-size: 2em; 49 | color: $DARK_GRAY; 50 | } 51 | 52 | #cc-toggle 53 | { 54 | display: block; 55 | margin: 0 auto; 56 | 57 | text-align: center; 58 | 59 | &:after 60 | { 61 | content: " Click to caption"; 62 | } 63 | 64 | &.listening 65 | { 66 | &:after 67 | { 68 | content: " Click to stop"; 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 a11yproject 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /Components/Socket/index.js: -------------------------------------------------------------------------------- 1 | var RoomManager = require('../Room/RoomManager'), 2 | Room = require('../Room/Room'); 3 | 4 | var Socket = function(io) { 5 | 6 | var sock = null; 7 | 8 | var joinRoomHandler = function(data) { 9 | var room = RoomManager.getRoomById(data); 10 | 11 | if (!room) 12 | return; 13 | 14 | var roomName = room.getName(); 15 | 16 | if (roomName) 17 | sock.emit("room name", roomName); 18 | 19 | sock.join(data) 20 | }; 21 | 22 | var newCaptionHandler = function(data) { 23 | io.sockets.in(data.room).emit("new caption", data.text); 24 | }; 25 | 26 | var getRoomNameHandler = function(data) { 27 | 28 | console.log("RoomID: " + data); 29 | var room = RoomManager.getRoomById(data); 30 | if (!room) 31 | { 32 | sock.emit("no room exists"); 33 | return; 34 | } 35 | 36 | sock.emit("room name", room.getName()); 37 | }; 38 | 39 | io.sockets.on('connection', function(socket) { 40 | 41 | sock = socket; 42 | 43 | socket.on("join room", joinRoomHandler); 44 | socket.on("new caption", newCaptionHandler); 45 | socket.on("get room name", getRoomNameHandler); 46 | }); 47 | 48 | }; 49 | 50 | module.exports = Socket; -------------------------------------------------------------------------------- /public/scss/_header.scss: -------------------------------------------------------------------------------- 1 | $NAV_HEIGHT: 60px; 2 | 3 | .navbar 4 | { 5 | background: PINK; 6 | 7 | padding: 0px 15px; 8 | 9 | box-shadow: 0 2px 2px $DARK_GRAY; 10 | } 11 | 12 | .navbar-wordcast 13 | { 14 | background: $NAVY; 15 | 16 | a 17 | { 18 | color: WHITE; 19 | text-decoration: none; 20 | } 21 | } 22 | 23 | .navbar-fixed-top 24 | { 25 | position: fixed; 26 | top: 0; 27 | left: 0; right: 0; 28 | 29 | display: block; 30 | height: $NAV_HEIGHT; 31 | } 32 | 33 | .navbar-toggle 34 | { 35 | display: none; 36 | } 37 | 38 | .navbar-right 39 | { 40 | float: right; 41 | } 42 | 43 | .navbar-brand 44 | { 45 | line-height: $NAV_HEIGHT; 46 | 47 | font-size: 1.3em; 48 | } 49 | 50 | .navbar-header 51 | { 52 | float: left; 53 | } 54 | 55 | .nav 56 | { 57 | margin: 0; 58 | padding: 0; 59 | 60 | line-height: $NAV_HEIGHT; 61 | 62 | &.navbar-nav 63 | { 64 | list-style-type: none; 65 | 66 | li 67 | { 68 | height: $NAV_HEIGHT; 69 | display: inline-block; 70 | line-height: $NAV_HEIGHT; 71 | 72 | a 73 | { 74 | display: block; 75 | padding: 0 10px; 76 | 77 | &:hover 78 | { 79 | background: darken($NAVY, 5); 80 | } 81 | } 82 | } 83 | } 84 | } -------------------------------------------------------------------------------- /views/layout.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | head 4 | title= title 5 | link(rel='stylesheet', href='/css/build/global.css') 6 | script(type='text/javascript', src='http://code.jquery.com/jquery-1.10.2.min.js') 7 | body 8 | 9 | div(class='container') 10 | 11 | header 12 | nav(class='navbar navbar-fixed-top navbar-wordcast', role='navigation') 13 | div(class='navbar-header',) 14 | button(type='button', class="navbar-toggle", data-toggle="collapse", data-target="#bs-example-navbar-collapse-1") 15 | span(class='sr-only')|Toggle navigation 16 | span(class='icon-bar') 17 | span(class='icon-bar') 18 | span(class='icon-bar') 19 | a(class='navbar-brand', href='/')|WordCast 20 | div(class='collapse navbar-collapse', id='bs-example-navbar-collapse-1') 21 | ul(class='nav navbar-nav navbar-right') 22 | li 23 | a(href='#')|Login 24 | li 25 | a(href='#')|Register 26 | 27 | block content 28 | footer 29 | ul(class="nav") 30 | li 31 | a(href='/') Home 32 | li 33 | a(href='http://opensource.org/ToS') Terms 34 | li 35 | a(href='http://github.com/a11yproject/WordCast') Contribute 36 | 37 | block scripts -------------------------------------------------------------------------------- /Components/Room/RoomController.js: -------------------------------------------------------------------------------- 1 | var router = require('express').Router(), 2 | RoomManager = require('./RoomManager'), 3 | Room = require('./Room'); 4 | 5 | router.get('/new', function(req, res){ 6 | res.render('create-room', {title: "Create a new Wordcast Room"}); 7 | }); 8 | 9 | router.get('/ID/:id', function(req, res){ 10 | 11 | var mode = req.query.viewMode, 12 | room = RoomManager.getRoomById(req.params.id); 13 | 14 | if (!room) 15 | res.render('viewer', {title: "Room No Exist"}); 16 | 17 | if (mode == 'broadcast') { 18 | res.render('listener', {title: room.getName()}); 19 | } else { 20 | res.render('viewer', {title: room.getName()}); 21 | } 22 | }); 23 | 24 | router.post('/createNewRoom', function(req, res){ 25 | 26 | var GUID = Math.round(Math.random() * (99999 - 1) + 1), 27 | roomName = req.body.RoomName; 28 | 29 | var room = new Room(GUID, roomName, "99999"); 30 | 31 | if (!RoomManager.hasRoom(room)) { 32 | console.log("Created room: #" + room.getID() + " " + room.getName()); 33 | RoomManager.addRoom(room); 34 | } 35 | else { 36 | console.log("ERROR: Cannot Create Room, Room Already Exists"); 37 | } 38 | 39 | res.redirect(room.getUrl()); 40 | 41 | }); 42 | 43 | module.exports = router; 44 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | 3 | grunt.initConfig({ 4 | 5 | pkg: grunt.file.readJSON('package.json'), 6 | 7 | sass: { 8 | dist: { 9 | options: { 10 | style: 'compressed' 11 | }, 12 | files: { 13 | 'public/css/build/global.css': 'public/scss/main.scss' 14 | } 15 | } 16 | }, 17 | 18 | watch: { 19 | css: { 20 | files: ['public/scss/*.scss'], 21 | tasks: ['sass'], 22 | options: { 23 | spawn: false, 24 | livereload: true, 25 | } 26 | } 27 | }, 28 | 29 | nodemon: { 30 | dev: { 31 | script: 'server.js', 32 | options: { 33 | file: 'server.js', 34 | watchedFolders: ['Components', 'Config'], 35 | nodeArgs: ['--debug'], 36 | env: { 37 | PORT: '3000' 38 | } 39 | } 40 | } 41 | }, 42 | 43 | concurrent: { 44 | dev: { 45 | tasks: ['watch', 'nodemon'], 46 | options: { 47 | logConcurrentOutput: true 48 | } 49 | } 50 | } 51 | 52 | }); 53 | 54 | grunt.loadNpmTasks('grunt-contrib-sass'); 55 | grunt.loadNpmTasks('grunt-contrib-watch'); 56 | grunt.loadNpmTasks('grunt-nodemon'); 57 | grunt.loadNpmTasks('grunt-concurrent'); 58 | 59 | grunt.registerTask('default', ['sass']); 60 | grunt.registerTask('dev', ['concurrent']); 61 | 62 | }; -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | var config = require('./config')(); 2 | 3 | // BASE SETUP 4 | // =================================================================== 5 | 6 | // Packages 7 | var express = require('express'), 8 | morgan = require('morgan'), 9 | bodyParser = require('body-parser'), 10 | methodOverride = require('method-override'), 11 | app = express(), 12 | server = require('http').Server(app), 13 | io = require('socket.io')(server), 14 | socketManager = require('./Components/Socket')(io); 15 | 16 | app.set('view engine', 'jade'); 17 | app.use(express.static(__dirname + '/public')); 18 | app.use(morgan('dev')); 19 | app.use(bodyParser()); 20 | app.use(methodOverride()); 21 | 22 | var PORT = process.env.PORT || 3000; 23 | 24 | // DBA SETUP 25 | // =================================================================== 26 | // TODO: 27 | 28 | // ROUTES / CONTROLLERS 29 | // =================================================================== 30 | var base = require('./Components'), 31 | room = require('./Components/Room/RoomController'); 32 | 33 | // register routes 34 | app.use('/', base); 35 | app.use('/room', room); 36 | 37 | // START THE SERVER 38 | // =================================================================== 39 | 40 | server.listen(PORT); 41 | console.log('Listening on port ' + PORT); 42 | -------------------------------------------------------------------------------- /public/javascripts/listener.js: -------------------------------------------------------------------------------- 1 | var socketPath = window.location.protocol + "//" + window.location.host; 2 | var pathArray = window.location.pathname.split('/'); 3 | var roomID = pathArray[3]; 4 | var roomName = document.querySelector("#roomName"); 5 | 6 | var button = document.getElementById('cc-toggle'); 7 | var captionLog = document.querySelector(".log-captions"); 8 | 9 | //=========================== 10 | // 11 | // Socket 12 | // 13 | //=========================== 14 | 15 | var socket = io.connect(socketPath); 16 | 17 | // Successfully connected to server 18 | socket.on("connect", function(){ 19 | socket.emit("get room name", roomID); // get room by id 20 | }); 21 | 22 | // Get room name from server 23 | socket.on("room name", function(data){ 24 | console.log("Room Name: " + data); 25 | roomName.innerHTML = data; 26 | }); 27 | 28 | //TODO: more graceful error here 29 | // If page is loaded, but a room doesn't exist 30 | socket.on("no room exists", function(data){ 31 | console.log("no room exists"); 32 | //window.location = '../'; 33 | }); 34 | 35 | //=========================== 36 | // 37 | // Voice Recognition 38 | // 39 | //=========================== 40 | 41 | var recognizing = false; 42 | var recognition = new webkitSpeechRecognition(); 43 | recognition.continuous = true; 44 | recognition.interimResults = true; 45 | 46 | recognition.onstart = function() { 47 | recognizing = true; 48 | }; 49 | 50 | recognition.onerror = function(event) {}; 51 | recognition.onend = function() { 52 | recognizing = false; 53 | }; 54 | 55 | recognition.onresult = function(event) { 56 | for (var i = event.resultIndex; i < event.results.length; ++i) { 57 | if(event.results[i][0].confidence > 0.4) { 58 | var captionText = capitalize(event.results[i][0].transcript); 59 | socket.emit("new caption", {text: captionText, room: roomID}); 60 | log(captionText); 61 | } 62 | } 63 | }; 64 | 65 | //=========================== 66 | // 67 | // Util 68 | // 69 | //=========================== 70 | 71 | function capitalize(s) { 72 | var first_char = /\S/; 73 | return s.replace(first_char, function(m) { 74 | return m.toUpperCase(); 75 | }); 76 | } 77 | 78 | function log(caption) { 79 | captionLog.innerHTML = caption; 80 | } 81 | 82 | function toggleSpeechRecognition(event) { 83 | if(recognizing) { 84 | recognition.stop(); 85 | button.classList.remove("listening"); 86 | return; 87 | } else { 88 | recognition.start(); 89 | button.classList.add("listening"); 90 | } 91 | } 92 | 93 | function dummyMsg(msg){ 94 | var messages = ["here is a caption", "The Lazy Brown Fox", "Wizards", "santa claws"]; 95 | var message = messages[Math.floor(Math.random()*messages.length)]; 96 | socket.emit("new caption", {text: message, room: roomID}); 97 | log(message); 98 | } 99 | -------------------------------------------------------------------------------- /public/fonts/wordcast.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Generated by IcoMoon 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | --------------------------------------------------------------------------------