├── .gitignore ├── .floo ├── .flooignore ├── public ├── js │ ├── index.js │ ├── controllers │ │ ├── video.js │ │ ├── videoAdmin.js │ │ └── chat.js │ └── services.js ├── admin.html ├── index.html ├── admin │ └── index.html └── css │ └── index.css ├── package.json ├── readme.md ├── app.js └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.floo: -------------------------------------------------------------------------------- 1 | { 2 | "url": "https://floobits.com/Celkam/MST4K" 3 | } -------------------------------------------------------------------------------- /.flooignore: -------------------------------------------------------------------------------- 1 | #* 2 | *.o 3 | *.pyc 4 | *~ 5 | extern/ 6 | node_modules/ 7 | tmp 8 | vendor/ -------------------------------------------------------------------------------- /public/js/index.js: -------------------------------------------------------------------------------- 1 | angular.module('theApp', ['chat', 'videoAdmin','video','services'] ); 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "MST4K", 3 | "version": "1.0.0", 4 | "description": "A server package for serving up synced video in a browser with chat", 5 | "main": "app.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "Cassandra Cruz", 10 | "license": "ISC", 11 | "dependencies": { 12 | "express": "^4.12.3", 13 | "socket.io": "^1.3.5", 14 | "vid-streamer": "^1.0.0" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /public/js/controllers/video.js: -------------------------------------------------------------------------------- 1 | angular.module('video', []) 2 | .controller('videoController', function ($scope, socket) { 3 | $scope.video = document.getElementById('movie'); 4 | 5 | socket.on('movie:play', function() { 6 | $scope.video.play(); 7 | }); 8 | 9 | socket.on('movie:pause', function() { 10 | $scope.video.pause(); 11 | }); 12 | 13 | socket.on('movie:sync', function(data) { 14 | var current = $scope.video.currentTime; 15 | if (Math.abs(data - current) > 5) { 16 | $scope.video.currentTime = data; 17 | } 18 | 19 | // TODO: Make client option to ignore server sync 20 | }); 21 | 22 | 23 | 24 | }); 25 | -------------------------------------------------------------------------------- /public/js/services.js: -------------------------------------------------------------------------------- 1 | angular.module('services', []) 2 | .factory('socket', function ($rootScope) { 3 | var socket = io.connect(); 4 | return { 5 | on: function (eventName, callback) { 6 | socket.on(eventName, function () { 7 | var args = arguments; 8 | $rootScope.$apply(function () { 9 | callback.apply(socket, args); 10 | }); 11 | }); 12 | }, 13 | emit: function (eventName, data, callback) { 14 | socket.emit(eventName, data, function () { 15 | var args = arguments; 16 | $rootScope.$apply(function () { 17 | if (callback) { 18 | callback.apply(socket, args); 19 | } 20 | }); 21 | }) 22 | } 23 | }; 24 | }); 25 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ## Synopsis 2 | 3 | MST4K is a simple server package that lets you host a virtual theater website with viewer chat. 4 | 5 | ## Motivation 6 | 7 | As a Hack Reactor Remote student, we didn't really have any way to do the standard social nights that onsite students have. This is an attempt to facilitate that, with movie nights. 8 | 9 | ## Installation 10 | 11 | Inside the config folder, there will eventually be a sample file to config the Vid-Streamer middleware. For now, the configuration info can be found [here][1]. 12 | 13 | ## Tests 14 | 15 | Coming soon. 16 | 17 | ## Contributors 18 | 19 | This project will use the git flow model, but it is currently unenforced. Until then, only approved contributors will have their pull requests considered. 20 | 21 | ## License 22 | 23 | [ISC][2] 24 | 25 | [1]: https://github.com/meloncholy/vid-streamer 26 | [2]: http://opensource.org/licenses/ISC -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | // Require dependencies 2 | var express = require('express'); 3 | var app = express(); 4 | var http = require('http').Server(app); 5 | var io = require('socket.io')(http); 6 | // var vidStreamer = require('vid-streamer'); 7 | 8 | // Define main route 9 | app.use(express.static(__dirname + '/public')); 10 | // app.get('/videos/', vidStreamer); 11 | 12 | io.on('connection', function(socket) { 13 | socket.on('message', function(data) { 14 | console.log('incoming message: ', data); 15 | socket.broadcast.emit('message', data); 16 | }); 17 | 18 | socket.on('movie:play', function(data) { 19 | console.log('playing video'); 20 | socket.broadcast.emit('movie:play', data); 21 | }); 22 | 23 | socket.on('movie:pause', function(data) { 24 | console.log('pausing video'); 25 | socket.broadcast.emit('movie:pause', data); 26 | }); 27 | 28 | socket.on('movie:sync', function(data) { 29 | console.log('syncing video'); 30 | socket.broadcast.emit('movie:sync', data); 31 | }); 32 | }); 33 | 34 | 35 | http.listen(8080); 36 | 37 | -------------------------------------------------------------------------------- /public/js/controllers/videoAdmin.js: -------------------------------------------------------------------------------- 1 | angular.module('videoAdmin', []) 2 | .controller('videoAdminController', function ($scope, socket) { 3 | $scope.video = document.getElementById('movie'); 4 | 5 | $scope.video.addEventListener('playing', function() { 6 | socket.emit('movie:play', $scope.video.currentTime); 7 | }); 8 | 9 | $scope.video.addEventListener('pause', function() { 10 | socket.emit('movie:pause', $scope.video.currentTime); 11 | }); 12 | 13 | $scope.video.addEventListener('timeupdate', function() { 14 | socket.emit('movie:sync', $scope.video.currentTime); 15 | }); 16 | 17 | }); 18 | 19 | /* 20 | socket.on('movie:play', function() { 21 | $scope.video.play(); 22 | }); 23 | 24 | socket.on('movie:pause', function() { 25 | $scope.video.pause(); 26 | }); 27 | 28 | socket.on('movie:sync', function(data) { 29 | var current = $scope.video.currentTime; 30 | if (Math.abs(data-current) > 5) { 31 | $scope.video.fastseek(data); 32 | } 33 | 34 | // TODO: Make client option to ignore server sync 35 | }); 36 | */ 37 | 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 MST4K 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, 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, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /public/js/controllers/chat.js: -------------------------------------------------------------------------------- 1 | angular.module('chat', []) 2 | .controller('chatController', function ($scope, socket) { 3 | 4 | // Socket listeners 5 | // ================ 6 | $scope.isThisTheEnterKey = function(e){ 7 | if(e.keyCode === 13){ 8 | $scope.sendMessage(); 9 | } 10 | } 11 | 12 | $scope.name; 13 | $scope.messages = $scope.messages || []; 14 | 15 | $scope.$watchCollection('messages', function() { 16 | console.log("change observed"); 17 | $scope.scrollMessages(); 18 | }); 19 | 20 | $scope.scrollMessages = function(){ 21 | setTimeout(function(){ 22 | document.getElementById('messagesHolder').scrollTop = 9999999; 23 | }, 110); 24 | 25 | } 26 | 27 | for(var i = 0; i < 50; i++){ 28 | $scope.messages.push({name:"someName", text: "this is a message from PHMessages. It's very very long to ensure that Cooper can see what line wrapping is doing inside of the index.html"}); 29 | } 30 | 31 | socket.on('message', function (message) { 32 | $scope.messages.push(message); 33 | // $scope.scrollMessages(); 34 | console.log(messages.length); 35 | }); 36 | 37 | // Methods published to the scope 38 | // ============================== 39 | 40 | $scope.changeName = function (newName) { 41 | $scope.name = newName; 42 | }; 43 | 44 | $scope.sendMessage = function () { 45 | socket.emit('message', { 46 | name: $scope.name, 47 | text: $scope.input 48 | }); 49 | 50 | // add the message to our model locally 51 | $scope.messages.push({ 52 | name: $scope.name, 53 | text: $scope.input 54 | }); 55 | 56 | // clear message box 57 | $scope.input = ''; 58 | 59 | }; 60 | }); 61 | -------------------------------------------------------------------------------- /public/admin.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | MST4K 6 | 7 | 8 | 9 | 10 | 11 |
12 |
13 | 14 |
15 |
16 |
17 |
18 | Chat Room 19 | 20 |
21 |
22 |
{{ message.name }}
23 |
{{ message.text }}
24 |
25 |
26 |
27 | 28 | 29 |
30 | 31 | 32 | 33 | 34 |
35 |
36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | MST4K 6 | 7 | 8 | 9 | 10 | 11 |
12 |
13 | 14 |
15 |
16 |
17 |
18 |
Chat Room
19 |
20 |
21 |
{{ message.name }}
22 |
23 |
{{ message.text }}
24 |
25 |
26 |
27 |
28 |
29 | 30 |
SEND
31 | 32 |
33 |
34 | 35 |
CREATE
36 |
37 |
38 |
39 |
40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /public/admin/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | MST4K 6 | 7 | 8 | 9 | 10 | 11 |
12 |
13 | 14 |
15 |
16 |
17 |
18 |
Admin Page
19 |
20 |
21 |
{{ message.name }}
22 |
23 |
{{ message.text }}
24 |
25 |
26 |
27 |
28 |
29 | 30 |
SEND
31 | 32 |
33 |
34 | 35 |
CREATE
36 |
37 |
38 |
39 |
40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /public/css/index.css: -------------------------------------------------------------------------------- 1 | 2 | /* http://meyerweb.com/eric/tools/css/reset/ 3 | v2.0 | 20110126 4 | License: none (public domain) 5 | */ 6 | 7 | *, html, body, div, span, applet, object, iframe, 8 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 9 | a, abbr, acronym, address, big, cite, code, 10 | del, dfn, em, img, ins, kbd, q, s, samp, 11 | small, strike, strong, sub, sup, tt, var, 12 | b, u, i, center,button, 13 | dl, dt, dd, ol, ul, li, 14 | fieldset, form, label, legend, 15 | table, caption, tbody, tfoot, thead, tr, th, td, 16 | article, aside, canvas, details, embed, 17 | figure, figcaption, footer, header, hgroup, 18 | menu, nav, output, ruby, section, summary, 19 | time, mark, audio, video, input { 20 | margin: 0; 21 | padding: 0; 22 | border: 0; 23 | font-size: 100%; 24 | font: inherit; 25 | vertical-align: baseline; 26 | -moz-box-sizing: border-box; 27 | box-sizing: border-box; 28 | } 29 | /* HTML5 display-role reset for older browsers */ 30 | article, aside, details, figcaption, figure, 31 | footer, header, hgroup, menu, nav, section { 32 | display: block; 33 | } 34 | body { 35 | line-height: 1; 36 | } 37 | ol, ul { 38 | list-style: none; 39 | } 40 | blockquote, q { 41 | quotes: none; 42 | } 43 | blockquote:before, blockquote:after, 44 | q:before, q:after { 45 | content: ''; 46 | content: none; 47 | } 48 | table { 49 | border-collapse: collapse; 50 | border-spacing: 0; 51 | } 52 | 53 | .group:before, 54 | .group:after { 55 | content: ""; 56 | display: table; 57 | } 58 | .group:after { 59 | clear: both; 60 | } 61 | .group { 62 | clear: both; 63 | *zoom: 1; 64 | } 65 | .smallShadow{ 66 | -webkit-box-shadow: 2px 2px 1px 1px rgba(0,0,0,0.75); 67 | -moz-box-shadow: 2px 2px 1px 1px rgba(0,0,0,0.75); 68 | box-shadow: 2px 2px 1px 1px rgba(0,0,0,0.75); 69 | } 70 | 71 | html{ 72 | 73 | width: 100%; 74 | height: 100%; 75 | } 76 | 77 | body{ 78 | font-size: 14px; 79 | line-height: 1.38em; 80 | font-family: "helvetica", helvetica; 81 | font-weight: normal; 82 | background-color: #444444; 83 | color: #cbcbcb; 84 | height: 100%; 85 | width:100%; 86 | } 87 | textarea{ 88 | width: 100%; 89 | resize: none; 90 | } 91 | #movieHolder{ 92 | display: block; 93 | float: right; 94 | width: 75%; 95 | height: 100%; 96 | background-color: #343434; 97 | } 98 | #movieInnerHolder{ 99 | height: 100%; 100 | display: block; 101 | } 102 | video{ 103 | height: 100%; 104 | width: 100%; 105 | display: block; 106 | margin-left: auto; 107 | margin-left: auto; 108 | } 109 | 110 | #chatTitle{ 111 | text-align: center; 112 | color: white; 113 | line-height: 2.0em; 114 | } 115 | 116 | #chatHolder{ 117 | display: block; 118 | float: right; 119 | width: 25%; 120 | height: 100%; 121 | background-color: #343434; 122 | border-top: 2px solid white; 123 | } 124 | 125 | #chatInnerHolder{ 126 | margin: 1px; 127 | height: 100%; 128 | display: block; 129 | -webkit-box-shadow: inset 2px 2px 1px 0px rgba(0,0,0,0.75); 130 | -moz-box-shadow: inset 2px 2px 1px 0px rgba(0,0,0,0.75); 131 | box-shadow: inset 2px 2px 1px 0px rgba(0,0,0,0.75); 132 | } 133 | #chatTitle{ 134 | top: 0px; 135 | border-bottom: 5px solid rgba(0,0,0,.4); 136 | } 137 | #messagesHolder{ 138 | /*height: 100%;*/ 139 | width: 100%; 140 | display: block; 141 | margin-left: auto; 142 | margin-left: auto; 143 | min-height: 5%; 144 | display: block; 145 | overflow-y: scroll; 146 | max-height: 100%; 147 | overflow:scroll; 148 | background-color: #303030; 149 | padding: 2px; 150 | 151 | padding-bottom: 6em; 152 | } 153 | 154 | .messageBox{ 155 | text-align: center; 156 | display: block; 157 | width: 100%; 158 | margin: 4px; 159 | padding-top: 2px; 160 | padding-left: 8px; 161 | } 162 | .messageName{ 163 | text-align: left; 164 | overflow: hidden; 165 | text-overflow: ellipsis; 166 | display: inline-block; 167 | width: 19%; 168 | float:left; 169 | } 170 | .messageDivider{ 171 | display: inline-block; 172 | float: left; 173 | width: 5px; 174 | height: 5px; 175 | background-color: rgba(60,255,60,.5); 176 | -webkit-box-shadow: inset 2px 2px 1px 0px rgba(0,0,0,0.75); 177 | -moz-box-shadow: inset 2px 2px 1px 0px rgba(0,0,0,0.75); 178 | box-shadow: inset 2px 2px 1px 0px rgba(0,0,0,0.75); 179 | } 180 | .messageText{ 181 | text-align: left; 182 | display: inline-block; 183 | float: right; 184 | width:79%; 185 | } 186 | 187 | #messagesHolder > div:nth-of-type(even){ 188 | -webkit-box-shadow: inset 2px 2px 1px 0px rgba(0,0,0,0.75); 189 | -moz-box-shadow: inset 2px 2px 1px 0px rgba(0,0,0,0.75); 190 | box-shadow: inset 2px 2px 1px 0px rgba(0,0,0,0.75); 191 | padding-top: 3px; 192 | background-color: #202020; 193 | color: #bababa; 194 | } 195 | 196 | #messagesHolder > div:nth-of-type(even){ 197 | 198 | } 199 | 200 | html #textEntryArea{ 201 | display: block; 202 | position: fixed; 203 | bottom: 0px; 204 | left: 0px; 205 | background-color: #303030; 206 | width: 25%; 207 | border-top: 2px solid white; 208 | -moz-box-sizing: border-box; 209 | box-sizing: border-box; 210 | } 211 | 212 | input, textarea{ 213 | -moz-box-sizing: border-box; 214 | box-sizing: border-box; 215 | font-family: "helvetica", helvetica; 216 | font-weight: normal; 217 | font-size: 15px; 218 | background-color: #222222; 219 | color: #cbcbcb; 220 | margin: 0; 221 | display:block; 222 | } 223 | 224 | #messageSubmit{ 225 | display: block; 226 | width: 100%; 227 | } 228 | 229 | html body #messageTextarea{ 230 | margin: 1px; 231 | border: 2px solid #7777aa; 232 | } 233 | 234 | #messageButton{ 235 | display: inline-block; 236 | font-family: "helvetica", helvetica; 237 | font-weight: normal; 238 | font-size: 15px; 239 | color: #cbcbcb; 240 | } 241 | 242 | #usernameSubmit{ 243 | display:block; 244 | width: 100%; 245 | } 246 | 247 | #usernameInput{ 248 | display: inline-block; 249 | border: 2px solid gray; 250 | margin: 2px; 251 | } 252 | 253 | #usernameButton{ 254 | display: inline-block; 255 | font-family: "helvetica", helvetica; 256 | font-weight: normal; 257 | font-size: 15px; 258 | color: #cbcbcb; 259 | } 260 | 261 | .button{ 262 | margin: 2px; 263 | min-width: 80px; 264 | max-width: 100px; 265 | border: 1px solid white; 266 | background-color: black; 267 | padding-left: .76em; 268 | padding-right: .76em; 269 | padding-bottom: .44em; 270 | padding-top: .44em; 271 | } 272 | 273 | @media screen and (max-width: 1280px){ 274 | 275 | body{ 276 | 277 | } 278 | #movieHolder{ 279 | float: none; 280 | width: 100%; 281 | height: 50% 282 | 283 | } 284 | #chatHolder{ 285 | float: none; 286 | width: 100%; 287 | height:50%; 288 | text-align: center; 289 | } 290 | html #textEntryArea{ 291 | width: 100%; 292 | text-align: center; 293 | } 294 | #messagesHolder{ 295 | max-height: 100%; 296 | } 297 | 298 | } 299 | 300 | @media screen and (max-height: 400px){ 301 | 302 | body{ 303 | 304 | } 305 | #movieHolder{ 306 | position: fixed; 307 | bottom: 0px; 308 | right: 0px; 309 | float: right; 310 | width: 75%; 311 | height: 100% 312 | 313 | } 314 | #chatHolder{ 315 | float: left; 316 | width: 25%; 317 | height:100%; 318 | text-align: center; 319 | } 320 | html #textEntryArea{ 321 | width: 25%%; 322 | text-align: center; 323 | } 324 | #messagesHolder{ 325 | max-height: 100%; 326 | } 327 | 328 | } 329 | 330 | 331 | @media screen and (max-width: 768px){ 332 | 333 | body{ 334 | font-size: 13px; 335 | } 336 | } 337 | --------------------------------------------------------------------------------